Author Archives: Cindy Wu

About Cindy Wu

Staff writer and technical support for PMD Way Limited (pmdway.com) and tronixstuff.com

Project – Scrolling text clock

The purpose of this project is to build a scrolling text clock that displays the time as it is spoken (for example, “it’s midnight”).

scrolling-clock-pmdway

This is a quick project – we give you enough to get going with the hardware and sketch, and then you can take it further to suit your needs.

Hardware

You’ll need three major items –

You might want an external power supply, but we’ll get to that later on. The first stage is to fit your real-time clock. Click here for the tutorial if you need help with that. By now I hope you’re thinking “how do you set the time?”.

There’s two answers to that question. If you’re using the DS3231 just set it in the sketch (see below) as the accuracy is very good, you only need to upload the sketch with the new time twice a year to cover daylight savings.

Otherwise add a simple user-interface – a couple of buttons could do it. Finally you just need to put the hardware on the back of the DMD. There’s plenty of scope to meet your own needs, a simple solution might be to align the control board so you can access the USB socket with ease – and then stick it down with some Sugru.

With regards to powering the clock – you can run ONE LED display from the Arduino, and it runs at a good brightness for indoor use. If you want the DMD to run at full, retina-burning brightness you need to use a separate 5V 4A DC power supply. If you’re using two DMDs – that goes to 8A, and so on. Simply connect the external power to one DMD’s terminals (connect the second or more DMDs to these terminals):

highpowerss

If you don’t fancy chopping the end of your power supply cable, use a DC socket breakout.

The Arduino Sketch

You will need to install the following two Arduino libraries – TimerOne and DMD. Then upload the sketch:

// for RTC
#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // the DS1307 RTC is 0x68

// for LED display
#include "SPI.h"
#include "DMD.h"
#include "TimerOne.h"
#include "SystemFont5x7.h"
#include "Arial_black_16.h"
#define DISPLAYS_ACROSS 1 // you could have more than one DMD in a row
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

String finalString; // used to hold final sentence to display on DMD

void ScanDMD() // required for DMD
{
dmd.scanDisplayBySPI();
}

void setup()
{
// for DMD
Timer1.initialize( 5000 );
Timer1.attachInterrupt( ScanDMD );
dmd.clearScreen(true);

// for RTC
Wire.begin(); // fire up I2C bus
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
// change the variables and uncomment the setDateDs1307 to set the time
// then re-comment out the function and upload the sketch again
second = 0;
minute = 13;
hour = 23;
dayOfWeek = 4;
dayOfMonth = 19;
month = 5;
year = 13;
// setDateDs1307(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}

// usual RTC functions
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val/16*10) + (val%16) );
}

void setDateDs1307(byte second, // 0-59
byte minute, // 0-59
byte hour, // 1-23
byte dayOfWeek, // 1-7
byte dayOfMonth, // 1-28/29/30/31
byte month, // 1-12
byte year) // 0-99
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.write(decToBcd(second)); // 0 to bit 7 starts the clock
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.write(00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
Wire.endTransmission();
}

// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
// Reset the register pointer
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();

Wire.requestFrom(DS1307_I2C_ADDRESS, 7);

// A few of these need masks because certain bits are control bits
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f); // Need to change this if 12 hour am/pm
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}

void drawText(String oldString)
{
dmd.clearScreen(true);
dmd.selectFont(Arial_Black_16);
char newString[256];
int sLength = oldString.length();
oldString.toCharArray(newString, sLength+1);
dmd.drawMarquee(newString,sLength,(32*DISPLAYS_ACROSS)-1,0);
long start=millis();
long timer=start;
long timer2=start;
boolean ret=false;
while(!ret){
if ((timer+20) < millis()) {
ret=dmd.stepMarquee(-1,0);
timer=millis();
}
}
}

void createTextTime(int hh, int mm)
// this mashes up all the time data into text as one sentence
{
finalString=" "; // wipe the sentence out for special cases (below)
finalString=finalString+"It's ";

// now add the hour
if (hh==1 || hh==13) {
finalString=finalString+"one ";
}
if (hh==2 || hh==14) {
finalString=finalString+"two ";
}
if (hh==3 || hh==15) {
finalString=finalString+"three ";
}
if (hh==4 || hh==16) {
finalString=finalString+"four ";
}
if (hh==5 || hh==17) {
finalString=finalString+"five ";
}
if (hh==6 || hh==18) {
finalString=finalString+"six ";
}
if (hh==7 || hh==19) {
finalString=finalString+"seven ";
}
if (hh==8 || hh==20) {
finalString=finalString+"eight ";
}
if (hh==9 || hh==21) {
finalString=finalString+"nine ";
}
if (hh==10 || hh==22) {
finalString=finalString+"ten ";
}
if (hh==11 || hh==23) {
finalString=finalString+"eleven ";
}

// now add the minutes
switch(mm){
case 1:
finalString=finalString+"oh one ";
break;
case 2:
finalString=finalString+"oh two ";
break;
case 3:
finalString=finalString+"oh three ";
break;
case 4:
finalString=finalString+"oh four ";
break;
case 5:
finalString=finalString+"oh five ";
break;
case 6:
finalString=finalString+"oh six ";
break;
case 7:
finalString=finalString+"oh seven ";
break;
case 8:
finalString=finalString+"oh eight ";
break;
case 9:
finalString=finalString+"oh nine ";
break;
case 10:
finalString=finalString+"ten ";
break;
case 11:
finalString=finalString+"eleven ";
break;
case 12:
finalString=finalString+"twelve ";
break;
case 13:
finalString=finalString+"thirteen ";
break;
case 14:
finalString=finalString+"fourteen ";
break;
case 15:
finalString=finalString+"fifteen ";
break;
case 16:
finalString=finalString+"sixteen ";
break;
case 17:
finalString=finalString+"seventeen ";
break;
case 18:
finalString=finalString+"eighteen ";
break;
case 19:
finalString=finalString+"nineteen ";
break;
case 20:
finalString=finalString+"twenty ";
break;
case 21:
finalString=finalString+"twenty one ";
break;
case 22:
finalString=finalString+"twenty two ";
break;
case 23:
finalString=finalString+"twenty three ";
break;
case 24:
finalString=finalString+"twenty four ";
break;
case 25:
finalString=finalString+"twenty five";
break;
case 26:
finalString=finalString+"twenty six";
break;
case 27:
finalString=finalString+"twenty seven";
break;
case 28:
finalString=finalString+"twenty eight ";
break;
case 29:
finalString=finalString+"twenty nine ";
break;
case 30:
finalString=finalString+"thirty ";
break;
case 31:
finalString=finalString+"thirty one ";
break;
case 32:
finalString=finalString+"thirty two";
break;
case 33:
finalString=finalString+"thirty three ";
break;
case 34:
finalString=finalString+"thirty four";
break;
case 35:
finalString=finalString+"thirty five ";
break;
case 36:
finalString=finalString+"thirty six";
break;
case 37:
finalString=finalString+"thirty seven";
break;
case 38:
finalString=finalString+"thirty eight ";
break;
case 39:
finalString=finalString+"thirty nine ";
break;
case 40:
finalString=finalString+"forty ";
break;
case 41:
finalString=finalString+"forty one ";
break;
case 42:
finalString=finalString+"forty two ";
break;
case 43:
finalString=finalString+"forty three ";
break;
case 44:
finalString=finalString+"forty four ";
break;
case 45:
finalString=finalString+"forty five ";
break;
case 46:
finalString=finalString+"forty six ";
break;
case 47:
finalString=finalString+"forty seven ";
break;
case 48:
finalString=finalString+"forty eight ";
break;
case 49:
finalString=finalString+"forty nine ";
break;
case 50:
finalString=finalString+"fifty ";
break;
case 51:
finalString=finalString+"fifty one ";
break;
case 52:
finalString=finalString+"fifty two ";
break;
case 53:
finalString=finalString+"fifty three ";
break;
case 54:
finalString=finalString+"fifty four ";
break;
case 55:
finalString=finalString+"fifty five ";
break;
case 56:
finalString=finalString+"fifty six ";
break;
case 57:
finalString=finalString+"fifty seven ";
break;
case 58:
finalString=finalString+"fifty eight ";
break;
case 59: finalString=finalString+"fifty nine "; break;
}

// midday?
if (hh==12 && mm==0) {
finalString=finalString+"midday ";
}
// midnight?
if (hh==00 && mm==0) {
finalString=finalString+"midnight ";
}

}

void loop()
{
// get the time from the RTC
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

// convert the time into a sentence string
createTextTime(hour,minute);

// now send the text to the DMD
drawText(finalString);
}

The sketch has the usual functions to set and retrieve the time from DS1307/3232 real-time clock ICs, and as usual with all our clocks you can enter the time information into the variables in void setup(), then uncomment setDateDs1307(), upload the sketch, re-comment setDateDs1307, then upload the sketch once more. Repeat that process to re-set the time if you didn’t add any hardware-based user interface.

Once the time is retrieved in void loop(), it is passed to the function createTextTime(). This function creates the text string to display by starting with “It’s “, and then determines which words to follow depending on the current time. Finally the function drawText() converts the string holding the text to display into a character variable which can be passed to the DMD.

And here it is in action:

Conclusion

This was a quick project, however we hope you found it either entertaining or useful – and another random type of clock that’s easy to reproduce or modify yourself.

This post brought to you by pmdway.com – offering everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

Tutorial: Maximising your Arduino’s I/O ports with MCP23017

In this article we discuss how to use the Microchip MCP23017 16-bit serial expander with I2C serial interface. This 28-pin IC offers sixteen inputs or outputs – and up to eight of the ICs can be used on one I2C bus… offering a maximum of 128 extra I/O ports.

A few people may be thinking “Why not just get an Arduino Mega2560?” – a good question. However you may have a distance between the Arduino and the end-point of the I/O pins – so with these ICs you can run just four wires instead of a lot more; save board space with custom designs, and preserve precious digital I/O pins for other uses. Plus we think the I2C bus is underappreciated! So let’s get started…

Here is our subject of the article in DIP form:

mcp23017ss

You can order these in through-hole, surface-mount and also mounted on a breakout board. At this point you should also download yourself a copy of data sheet – it will be referred to several times, and very useful for reference and further reading.

Furthermore if you are not familiar with Arduino and the I2C bus, please familiarise yourself with the I2C tutorials parts one and two. The MCP23017 can be quite simple or complex to understand, so the goal of this article is to try and make it as simple as possible. After reading this you should have the knowledge and confidence to move forward with using a MCP23017.

First, let’s look at the hardware basics of this IC. Consider the pinouts:

pinouts

The sixteen I/O ports are separated into two ‘ports’ – A (on the right) and B (on the left. Pin 9 connects to 5V, 10 to GND, 11 isn’t used, 12 is the I2C bus clock line (Arduino Uno/Duemilanove analogue pin 5, Mega pin  21), and 13 is the I2C bus data line (Arduino Uno/Duemailnove analogue pin 4, Mega pin 20).

External pull-up resistors should be used on the I2C bus – in our examples we use 4.7k ohm values. Pin 14 is unused, and we won’t be looking at interrupts, so ignore pins 19 and 20. Pin 18 is the reset pin, which is normally high – therefore you ground it to reset the IC. So connect it to 5V!

Finally we have the three hardware address pins 15~17. These are used to determine the I2C bus address for the chip. If you connect them all to GND, the address is 0x20. If you have other devices with that address or need to use multiple MCP23017s, see figure 1-2 in the datasheet.

You can alter the address by connecting a combination of pins 15~17 to 5V (1) or GND (0). For example, if you connect 15~17 all to 5V, the control byte becomes 0100111 in binary, or 0x27 in hexadecimal.

Next, here is a basic schematic illustrating how to connect an MCP23017 to a typical Arduino board. It contains the minimum to use the IC, without any sensors or components on the I/O pins:

mcp20317_schemss

Now to examine how to use the IC in our sketches.

As you should know by now most I2C devices have several registers that can be addressed. Each address holds one byte of data that determines various options. So before using we need to set whether each port is an input or an output. First, we’ll examine setting them as outputs. So to set port A to outputs, we use:

Wire.beginTransmission(0x20);
Wire.write(0x00); // IODIRA register
Wire.write(0x00); // set all of port A to outputs
Wire.endTransmission();

Then to set port B to outputs, we use:

Wire.beginTransmission(0x20);
Wire.write(0x01); // IODIRB register
Wire.write(0x00); // set all of port B to outputs
Wire.endTransmission();

So now we are in void loop()  or a function of your own creation and want to control some output pins. To control port A, we use:

Wire.beginTransmission(0x20);
Wire.write(0x12); // address port A
Wire.write(??);  // value to send
Wire.endTransmission();

To control port B, we use:

Wire.beginTransmission(0x20);
Wire.write(0x13); // address port B
Wire.write(??);  // value to send
Wire.endTransmission();

… replacing ?? with the binary or equivalent hexadecimal or decimal value to send to the register.

To calculate the required number, consider each I/O pin from 7 to 0 matches one bit of a binary number – 1 for on, 0 for off. So you can insert a binary number representing the status of each output pin. Or if binary does your head in, convert it to hexadecimal. Or a decimal number.

So for example, you want pins 7 and 1 on. In binary that would be 10000010, in hexadecimal that is 0x82, or 130 decimal. (Using decimals is convenient if you want to display values from an incrementing value or function result).

If you had some LEDs via resistors connected to the outputs, you would have this as a result of sending 0x82:

0x82

For example, we want port A to be 11001100 and port B to be 10001000 – so we send the following (note we converted the binary values to decimal):

Wire.beginTransmission(0x20);
Wire.write(0x12); // address port A
Wire.write(204); // value to send
Wire.endTransmission();
Wire.beginTransmission(0x20);
Wire.write(0x13); // address port B 
Wire.write(136);     // value to send
Wire.endTransmission();

… with the results as such (port B on the left, port A on the right):

0xcc0x88

Now let’s put all of this output knowledge into a more detailed example. From a hardware perspective we are using a circuit as described above, with the addition of a 560 ohm resistor followed by an LED thence to ground from on each of the sixteen outputs. Here is the sketch:

/*
 Example 41.1 - Microchip MCP23017 with Arduino
*/
// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
void setup()
{
 Wire.begin(); // wake up I2C bus
// set I/O pins to outputs
 Wire.beginTransmission(0x20);
 Wire.write(0x00); // IODIRA register
 Wire.write(0x00); // set all of port A to outputs
 Wire.endTransmission();
Wire.beginTransmission(0x20);
 Wire.write(0x01); // IODIRB register
 Wire.write(0x00); // set all of port B to outputs
 Wire.endTransmission();
}
void binaryCount()
{
 for (byte a=0; a<256; a++)
 {
 Wire.beginTransmission(0x20);
 Wire.write(0x12); // GPIOA
 Wire.write(a); // port A
 Wire.endTransmission();
Wire.beginTransmission(0x20);
 Wire.write(0x13); // GPIOB
 Wire.write(a); // port B
 Wire.endTransmission();
 }
}
void loop()
{
 binaryCount();
 delay(500);
}

And here is the example blinking away:

Although that may have seemed like a simple demonstration, it was created show how the outputs can be used. So now you know how to control the I/O pins set as outputs. Note that you can’t source more than 25 mA of current from each pin, so if switching higher current loads use a transistor and an external power supply and so on.

Now let’s turn the tables and work on using the I/O pins as digital inputs. The MCP23017 I/O pins default to input mode, so we just need to initiate the I2C bus. Then in the void loop() or other function all we do is set the address of the register to read and receive one byte of data.

For our next example, we have our basic sketch as described at the start of this article using four normally-open buttons which are connected to port B inputs 0~3. Consider the first five lines of void loop() in the following example:

/*
 Example 41.2 - Microchip MCP23017 with Arduino
 */
// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
byte inputs=0;
void setup()
{
 Serial.begin(9600);
 Wire.begin(); // wake up I2C bus
}
void loop()
{
 Wire.beginTransmission(0x20);
 Wire.write(0x13); // set MCP23017 memory pointer to GPIOB address
 Wire.endTransmission();
 Wire.requestFrom(0x20, 1); // request one byte of data from MCP20317
 inputs=Wire.read(); // store the incoming byte into "inputs"
 if (inputs>0) // if a button was pressed
 {
 Serial.println(inputs, BIN); // display the contents of the GPIOB register in binary
 delay(200); // for debounce
 }
}

In this example void loop() sends the GPIOB address (0x13) to the IC. Then using Wire.requestFrom() it asks for one byte of data from the IC – the contents of the register at 0x13. This byte is stored in the variable inputs. Finally if inputs is greater than zero (i.e. a button has been pressed) the result is sent to the serial monitor window and displayed in binary. We display it in binary as this represents the state of the inputs 0~7. Here is an example of pressing the buttons 1, 2, 3 then 4 – three times:

ex41p2smon

And as we are reading eight inputs at once – you can detect multiple keypresses. The following is an example of doing just that:

ex41p2smon2

As you can see pressing all four buttons returned 1111, or the first and third returned 101. Each combination of highs and lows on the inputs is a unique 8-bit number that can also be interpreted in decimal or hexadecimal. And if you wanted to read all sixteen inputs at once, just request and store two bytes of data instead of one.

For our last example – a demonstration of using port A as outputs and port B as inputs. Four LEDs with matching resistors are connected to port A outputs 0~3, with the buttons connected as per example 41.2. Here is the sketch:

/*
 Example 41.3 - Microchip MCP23017 with Arduino
*/

// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
byte inputs=0;

void setup()
{
  Serial.begin(9600);
  Wire.begin(); // wake up I2C bus

  Wire.beginTransmission(0x20);
  Wire.write(0x00); // IODIRA register
  Wire.write(0x00); // set all of bank A to outputs
  Wire.endTransmission();
}

void loop()
{
  // read the inputs of bank B
  Wire.beginTransmission(0x20);
  Wire.write(0x13);
  Wire.endTransmission();
  Wire.requestFrom(0x20, 1);
  inputs=Wire.read();

  // now send the input data to bank A
  Wire.beginTransmission(0x20);
  Wire.write(0x12); // GPIOA
  Wire.write(inputs);    // bank A
  Wire.endTransmission();
  delay(200); // for debounce
}

By now there shouldn’t be any surprises in the last example – it receives a byte that represents port B, and sends that byte out to port A to turn on the matching outputs and LEDs. For the curious, here it is in action:

So there you have it… another way to massively increase the quantity of digital I/O pins on any Arduino system by using the I2C bus. You can get the MCP23017 from PMD Way with free delivery worldwide.

This post brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

Tutorial: Arduino and the SPI bus part II

This is part two of investigating the SPI data bus, and how we can control devices using it with our Arduino systems. If you have not done so already, please read part one of the SPI articles. Again we will learn the necessary theory, and then apply it by controlling a variety of devices. As always things will be kept as simple as possible.

First on our list today is the use of multiple SPI devices on the single bus. We briefly touched on this in part one, by showing how multiple devices are wired, for example:

sspiss2

Notice how the slave devices share the clock, MOSI and MISO lines – however they both have their own chip select line back to the master device. At this point a limitation of the SPI bus becomes prevalent – for each slave device we need another digital pin to control chip select for that device. If you were looking to control many devices, it would be better to consider finding I2C solutions to the problem.

To implement multiple devices is very easy. Consider the example 34.1 from part one – we controlled a digital rheostat. Now we will repeat the example, but instead control four instead of one. For reference, here is the pinout diagram:

mcp4162pinout

Doing so may sound complex, but it is not. We connect the SCK, MOSI and  MISO pins together, then to Arduino pins D13, D11, D12 respectively. Each CS pin is wired to a separate Arduino digital pin. In our example rheostats 1 to 4 connect to D10 through to D7 respectively. To show the resistance is changing on each rheostat, there is an LED between pin 5 and GND and a 470 ohm resistor between 5V and pin 6. Next, here is the sketch:

/*
Multiple SPI bus device demo using four Microchip MCP4162s 
*/

#include "SPI.h" // necessary library
int del=3; // used for various delays

int led1=10; // CS lines for each SPI device
int led2=9;
int led3=8;
int led4=7;

void setup()
{
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
  pinMode(led4, OUTPUT);
  digitalWrite(led1, HIGH);
  digitalWrite(led2, HIGH);
  digitalWrite(led3, HIGH);
  digitalWrite(led4, HIGH);
  SPI.begin(); // wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);
  // our MCP4162s requires data to be sent MSB (most significant byte) first
}

void setValue(int l, int value)
// sends value 'value' to SPI device on CS digital out pin 'l'
{
  digitalWrite(l, LOW);
  SPI.transfer(0); // send command byte
  SPI.transfer(value); // send value (0~255)
  digitalWrite(l, HIGH);
}

void allOff()
// sets all pots to max resistance
{
     setValue(led1,255);
     setValue(led2,255);
     setValue(led3,255);
     setValue(led4,255);
}

void pulse(int l)
{
  allOff();
  for (int a=255; a>=0; --a)
  {
    setValue(l,a);
    delay(del);
  }
  for (int a=0; a<256; a++)
  {
    setValue(l,a);
    delay(del);
  }
}

void pulseAll()
{
  allOff();
  for (int a=255; a>=0; --a)
  {
    setValue(led1,a);
    setValue(led2,a);
    setValue(led3,a);
    setValue(led4,a);
    delay(del);
  }
  for (int a=0; a<256; a++)
  {
    setValue(led1,a);
    setValue(led2,a);
    setValue(led3,a);
    setValue(led4,a);
    delay(del);
  }
}

void loop()
{
  pulse(led1);
  pulse(led2);
  pulse(led3);
  pulse(led4);
  pulseAll();
}

Although the example sketch may be longer than necessary, it is quite simple. We have four SPI devices each controlling one LED, so to keep things easy to track we have defined led1~led4 to match the chip select digital out pins used for each SPI device.

Then see the first four lines in void setup(); these pins are set to output in order to function as required. Next – this is very important – we set the pins’ state to HIGH.

You must do this to every chip select line! Otherwise more than one CS pins may be initially low in some instances and cause the first data sent from MOSI to travel along to two or more SPI devices. With LEDs this may not be an issue, but for motor controllers … well it could be.

The other point of interest is the function

void setValue(int l, int value)

We pass the value for the SPI device we want to control, and the value to send to the device. The value for l is the chip select value for the SPI device to control, and ranges from 10~7 – or as defined earlier, led1~4. The rest of the sketch is involved in controlling the LED’s brightness by varying the resistance of the rheostats. Now to see example 36.1 in action via the following video clip:


Next on the agenda is a digital-to-analogue converter, to be referred to using the acronym DAC. What is a DAC? In simple terms, it accepts a numerical value between zero and a maximum value (digital) and outputs a voltage between the range of zero and a maximum relative to the input value (analogue).

One could consider this to be the opposite of the what we use the function analogRead(); for. For our example we will use a Microchip MCP4921 (data sheet.pdf):

mcp4921ss

(Please note that this is a beginners’ tutorial and is somewhat simplified). This DAC has a 12-bit resolution. This means that it can accept a decimal number between 0 and 4095 – in binary this is 0 to 1111 1111 1111 (see why it is called 12-bit) – and the outpout voltage is divided into 4096 steps. The output voltage for this particular DAC can fall between 0 and just under the supply voltage (5V). So for each increase of 1 in the decimal input value, the DAC will output around 1.221 millivolts.

It is also possible to reduce the size of the voltage output steps by using a lower reference voltage. Then the DAC will consider the reference voltage to be the maximum output with a value of 4095. So (for example) if the reference voltage was 2.5V, each increase of 1 in the decimal input value, the DAC will output around 0.6105 millivolts. The minimum reference voltage possible is 0.8V, which offers a step of 200 microvolts (uV).

The output of a DAC can be used for many things, such as a function generator or the playback of audio recorded in a digital form. For now we will examine how to use the hardware, and monitoring output on an oscilloscope. First we need the pinouts:

mcp4921pinout

By now these sorts of diagrams shouldn’t present any problems. In this example, we keep pin 5 permanently set to GND; pin 6 is where you feed in the reference voltage – we will set this to +5V; AVss is GND; and Vouta is the output signal pin – where the magic comes from 🙂 The next thing to investigate is the MCP4921’s write command register:

mcp4921wcr

Bits 0 to 11 are the 12 bits of the output value; bit 15 is an output selector (unused on the MPC4921); bit 14 controls the input buffer; bit 13 controls an inbuilt output amplifier; and bit 12 can shutdown the DAC. Unlike previous devices, the input data is spread across two bytes (or a word of data).

Therefore a small amount of work needs to be done to format the data ready for the DAC. Let’s explain this through looking at the sketch for example 36.2 that follows. The purpose of the sketch is to go through all possible DAC values, from 0 to 4095, then back to 0 and so on.

First. note the variable outputvalue – it is a word, a 16-bit unsigned variable. This is perfect as we will be sending a word of data to the DAC. We put the increasing/decreasing value for a into outputValue. However as we can only send bytes of data at a time down the SPI bus, we will use the function highbyte() to separate the high side of the word (bits 15~8) into a byte variable called data.

We then use the bitwise AND and OR operators to set the parameter bits 15~12. Then this byte is sent to the SPI bus. Finally, the function lowbyte() is used to send the low side of the word (bits 7~0) into data and thence down the SPI bus as well.

Now for our demonstration sketch:

/*
SPI bus device demo using a Microchip MCP4921 DAC 
 */

#include "SPI.h" // necessary library
int del=0; // used for various delays
word outputValue = 0; // a word is a 16-bit number
byte data = 0; // and a byte is an 8-bit number
void setup()
{
  //set pin(s) to input and output
  pinMode(10, OUTPUT);
  SPI.begin(); // wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);
}

void loop()
{
  for (int a=0; a<=4095; a++)
  {
    outputValue = a;
    digitalWrite(10, LOW);
    data = highByte(outputValue);
    data = 0b00001111 & data;
    data = 0b00110000 | data;
    SPI.transfer(data);
    data = lowByte(outputValue);
    SPI.transfer(data);
    digitalWrite(10, HIGH);
    delay(del);
  }
  delay(del+25);
  for (int a=4095; a>=0; --a)
  {
    outputValue = a;
    digitalWrite(10, LOW);
    data = highByte(outputValue);
    data = 0b00001111 & data;
    data = 0b00110000 | data;
    SPI.transfer(data);
    data = lowByte(outputValue);
    SPI.transfer(data);
    digitalWrite(10, HIGH);
    delay(del);
  }
  delay(del+25);
}

And a quick look at the DAC in action via an oscilloscope:

By now we have covered in detail how to send data to a device on the SPI bus. But how do we receive data from a device?

Doing so is quite simple, but some information is required about the particular device. For the rest of this chapter, we will use the Maxim DS3234 “extremely accurate” real-time clock. Please download the data sheet (.pdf) now, as it will be referred to many times.

The DS3234 is not available in through-hole packaging, so we will be using one that comes pre-soldered onto a breakout board:

ds3234ss

 

It only takes a few moments to solder in some header pins for breadboard use. The battery type is CR1220 (12 x 2.0mm, 3V); if you don’t have a battery you will need to short out the battery holder with some wire otherwise the IC will not work. Readers have reported that the IC doesn’t keep time if the USB and external power are both applied to the Arduino at the same time.

A device will have one or more registers where information is read from and written to. Look at page twelve of the DS3234 data sheet, there are twenty-three registers, each containing eight bits (one byte) of data.

Please take note that each register has a read and write address. An example – to retrieve the contents of the register at location 08h (alarm minutes) and place it into the byte data we need to do the following:

digitalWrite(10, LOW); // select the DS3234 that has its CS line on digital 10
SPI.transfer(0x08); // tell the DS3234 device we're requesting data from the register at 08h
data=SPI.transfer(0); // the DS3234 sends the data back and stores it in the byte data
digitalWrite(10, HIGH);  // deselect the DS3234 if finished with it

Don’t forget to take note of  the function SPI.setBitOrder(MSBFIRST); in your sketch, as this also determines the bit order of the data coming from the device. To write data to a specific address is also quite simple, for example:

digitalWrite(10, LOW);
SPI.transfer(0x80); // tells the device which address to write to
SPI.transfer(b00001010);   // you can send any representation of a byte
digitalWrite(10, HIGH);

Up to this point, we have not concerned ourselves with what is called the SPI data mode. The mode determines how the SPI device interprets the ‘pulses’ of data going in and out of the device.

For a well-defined explanation, please read this article. With some devices (and in our forthcoming example) the data mode needs to be defined. So we use:

SPI.setDataMode(SPI_MODE1);

to set the data mode, within void(setup);. To determine a device’s data mode, as always – consult the data sheet. With our DS3234 example, the mode is mentioned on page 1 under Features List.

Finally, let’s delve a little deeper into SPI via the DS3234. The people at Sparkfun have already written a good demonstration sketch for the DS3234, so let’s have a look at that and deconstruct it a little to see what is going on. You can download the sketch below from here, then change the file extension from .c to .pde.

#include "SPI.h"
const int  cs=8; //chip select 

void setup() {
  Serial.begin(9600);
  RTC_init();
  //day(1-31), month(1-12), year(0-99), hour(0-23), minute(0-59), second(0-59)
  SetTimeDate(11,12,13,14,15,16);
}

void loop() {
  Serial.println(ReadTimeDate());
  delay(1000);
}
//=====================================
int RTC_init(){
	  pinMode(cs,OUTPUT); // chip select
	  // start the SPI library:
	  SPI.begin();
	  SPI.setBitOrder(MSBFIRST);
	  SPI.setDataMode(SPI_MODE1); // both mode 1 & 3 should work
	  //set control register
	  digitalWrite(cs, LOW);
	  SPI.transfer(0x8E);
	  SPI.transfer(0x60); //60= disable Osciallator and Battery SQ wave @1hz, temp compensation, Alarms disabled
	  digitalWrite(cs, HIGH);
	  delay(10);
}
//=====================================
int SetTimeDate(int d, int mo, int y, int h, int mi, int s){
	int TimeDate [7]={s,mi,h,0,d,mo,y};
	for(int i=0; i<=6;i++){
		if(i==3)
			i++;
		int b= TimeDate[i]/10;
		int a= TimeDate[i]-b*10;
		if(i==2){
			if (b==2)
				b=B00000010;
			else if (b==1)
				b=B00000001;
		}
		TimeDate[i]= a+(b<<4);

		digitalWrite(cs, LOW);
		SPI.transfer(i+0x80);
		SPI.transfer(TimeDate[i]);
		digitalWrite(cs, HIGH);
  }
}
//=====================================
String ReadTimeDate(){
	String temp;
	int TimeDate [7]; //second,minute,hour,null,day,month,year
	for(int i=0; i<=6;i++){
		if(i==3)
			i++;
		digitalWrite(cs, LOW);
		SPI.transfer(i+0x00);
		unsigned int n = SPI.transfer(0x00);
		digitalWrite(cs, HIGH);
		int a=n & B00001111;
		if(i==2){
			int b=(n & B00110000)>>4; //24 hour mode
			if(b==B00000010)
				b=20;
			else if(b==B00000001)
				b=10;
			TimeDate[i]=a+b;
		}
		else if(i==4){
			int b=(n & B00110000)>>4;
			TimeDate[i]=a+b*10;
		}
		else if(i==5){
			int b=(n & B00010000)>>4;
			TimeDate[i]=a+b*10;
		}
		else if(i==6){
			int b=(n & B11110000)>>4;
			TimeDate[i]=a+b*10;
		}
		else{
			int b=(n & B01110000)>>4;
			TimeDate[i]=a+b*10;
			}
	}
	temp.concat(TimeDate[4]);
	temp.concat("/") ;
	temp.concat(TimeDate[5]);
	temp.concat("/") ;
	temp.concat(TimeDate[6]);
	temp.concat("     ") ;
	temp.concat(TimeDate[2]);
	temp.concat(":") ;
	temp.concat(TimeDate[1]);
	temp.concat(":") ;
	temp.concat(TimeDate[0]);
  return(temp);
}

Don’t let the use of custom functions and loops put you off, they are there to save time. Looking in the function SetTimeDate();, you can see that the data is written to the registers 80h through to 86h (skipping 83h – day of week) in the way as described earlier (set CS low, send out address to write to, send out data, set CS high).

You will also notice some bitwise arithmetic going on as well. This is done to convert data between binary-coded decimal and decimal numbers.

Why? Go back to page twelve of the DS3234 data sheet and look at (e.g.) register 00h/80h – seconds. The bits 7~4 are used to represent the ‘tens’ column of the value, and bits 3~0 represent the ‘ones’ column of the value.

So some bit shifting is necessary to isolate the digit for each column in order to convert the data to decimal. Finally here is another example of reading the time data from the DS3234:

/*
SPI bus device demo using a Maxim IC DS3234 Accurate RTC
 */
#include "SPI.h" // necessary library
// store hours, minutes, seconds, day of month, month, year
byte h,m,s,d,mo,y;
void setup() 
{
 pinMode(10, OUTPUT);
 SPI.begin(); // wake up the SPI bus.
 SPI.setBitOrder(MSBFIRST);
 SPI.setDataMode(SPI_MODE1); 
 digitalWrite(10, LOW); 

 SPI.transfer(0x8E); // write to control register 8Eh (page 14 data sheet)
 SPI.transfer(0x60); // oscillator on, 1Hz, alarms off (b01100000)
 digitalWrite(10, HIGH);
 Serial.begin(9600);
}

void readDS3234() 
{
 byte data=0;
 int a,b=0;
 digitalWrite(10, LOW);

 // get seconds
 SPI.transfer(0x00); // get seconds data from register 00h
 data=SPI.transfer(0x00);
 a = data & B00001111; // isolates 1's column
 b = (data & B01110000)>>4; // isolates 10's digit
 s=(b*10)+a; // calculate seconds!

 // get minutes
 SPI.transfer(0x01); // get minutes data from register 00h
 data=SPI.transfer(0x00);
 a = data & B00001111; // isolates 1's column
 b = (data & B01110000)>>4; // isolates 10's digit
 m=(b*10)+a; // calculate minutes!

 // get hours
 SPI.transfer(0x02); // get minutes data from register 00h
 data=SPI.transfer(0x00);
 a = data & B00001111; // isolates 1's column
 b = (data & B00110000)>>4; // isolate upper nibble
 if (b==B00000010) // 24 hr time
 { 
 b=20;
 } else
 if (b==B00000001) // 12 hr time
 {
 b=10;
 }
 h = a + b; // calculate hours

 digitalWrite(10, HIGH);
}
void loop()
{
 readDS3234();
 Serial.print(h, DEC);
 Serial.print(":");
 if (m<10)
 {
 Serial.print("0");
 }
 Serial.print(m, DEC);
 Serial.print(":");
 if (s<10)
 {
 Serial.print("0");
 }
 Serial.print(s, DEC);
 Serial.println();
 delay(500);
}

So there you have it – more about the world of the SPI bus and how to control the devices within.

This post brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

Tutorial: Arduino and the SPI bus

This is the first of two chapters in which we are going to start investigating the SPI data bus, and how we can control devices using it with our Arduino systems.

The SPI bus may seem to be a complex interface to master, however with some brief study of this explanation and practical examples you will soon become a bus master! To do this we will learn the necessary theory, and then apply it by controlling a variety of devices. In this tutorial things will be kept as simple as possible.

But first of all, what is it? And some theory…

SPI is an acronym for “Serial Peripheral Interface”. It is a synchronous serial data bus – data can travel in both directions at the same time, as opposed to (for example) the I2C bus that cannot do so. To allow synchronous data transmission, the SPI bus uses four wires. They are called:

  • MOSI – Master-out, Slave-in. This line carries data from our Arduino to the SPI-controlled device(s);
  • MISO – Master-in, Slave out. This line carries data from the SPI-controlled device(s) back to the Arduino;
  • SS – Slave-select. This line tells the device on the bus we wish to communicate with it. Each SPI device needs a unique SS line back to the Arduino;
  • SCK – Serial clock.

Within these tutorials we consider the Arduino board to be the master and the SPI devices to be slaves. On our Arduino Uno and compatible boards the pins used are:

  • SS – digital 10. You can use other digital pins, but 10 is generally the default as it is next to the other SPI pins;
  • MOSI – digital 11;
  • MISO – digital 12;
  • SCK – digital 13;

Arduino Mega users – MISO is 50, MOSI is 51, SCK is 52 and SS is usually 53. If you are using an Arduino Leonardo, the SPI pins are on the ICSP header pins. See here for more information. You can control one or more devices with the SPI bus. For example, for one device the wiring would be:

sspiss1

Data travels back and forth along the MOSI and MISO lines between our Arduino and the SPI device. This can only happen when the SS line is set to LOW. In other words, to communicate with a particular SPI device on the bus, we set the SS line to that device to LOW, then communicate with it, then set the line back to HIGH. If we have two or more SPI devices on the bus, the wiring would resemble the following:

sspiss2


Notice how there are two SS lines – we need one for each SPI device on the bus. You can use any free digital output pin on your Arduino as an SS line. Just remember to have all SS lines high except for the line connected to the SPI device you wish to use at the time.

Data is sent to the SPI device in byte form. You should know by now that eight bits make one byte, therefore representing a binary number with a value of between zero and 255.

When communicating with our SPI devices, we need to know which way the device deals with the data – MSB or LSB first. MSB (most significant bit) is the left-hand side of the binary number, and LSB (least significant bit) is the right-hand side of the number. That is:

binnum

Apart from sending numerical values along the SPI bus, binary numbers can also represent commands. You can represent eight on/off settings using one byte of data, so a device’s parameters can be set by sending a byte of data. These parameters will vary with each device and should be illustrated in the particular device’s data sheet. For example, a digital potentiometer IC with six pots:

sdata1

This device requires two bytes of data. The ADDR byte tells the device which of six potentiometers to control (numbered 0 to 5), and the DATA byte is the value for the potentiometer (0~255). We can use integers to represent these two values. For example, to set potentiometer number two to 125, we would send 2 then 125 to the device.

How do we send data to SPI devices in our sketches?

First of all, we need to use the SPI library. It is included with the default Arduino IDE installation, so put the following at the start of your sketch:

#include "SPI.h"

Next, in void.setup() declare which pin(s) will be used for SS and set them as OUTPUT. For example,

pinMode(ss, OUTPUT);

where ss has previously been declared as an integer of value ten. Now, to activate the SPI bus:

SPI.begin();

and finally we need to tell the sketch which way to send data, MSB or LSB first by using

SPI.setBitOrder(MSBFIRST);

or

SPI.setBitOrder(LSBFIRST);

When it is time to send data down the SPI bus to our device, three things need to happen. First, set the digital pin with SS to low:

digitalWrite(SS, LOW);

Then send the data in bytes, one byte at a time using:

SPI.transfer(value);

Value can be an integer/byte between zero and 255. Finally, when finished sending data to your device, end the transmission by setting SS high:

digitalWrite(ss, HIGH);

Sending data is quite simple. Generally the most difficult part for people is interpreting the device data sheet to understand how commands and data need to be structured for transmission. But with some practice, these small hurdles can be overcome.

Now for some practical examples!

Time to get on the SPI bus and control some devices. By following the examples below, you should gain a practical understanding of how the SPI bus and devices can be used with our Arduino boards.

MCP4162 Example

Our first example will use a simple yet interesting part – a digital potentiometer (we also used one in the I2C tutorial). This time we have a Microchip MCP4162-series 10k rheostat:

digipotss

Here is the data sheet for your perusal. To control it we need to send two bytes of data – the first byte is the control byte, and thankfully for this example it is always zero (as the address for the wiper value is 00h [see table 4-1 of the data sheet]).  The second byte is the the value to set the wiper, which controls the resistance. So to set the wiper we need to do three things in our sketch…

First, set the SS (slave select) line to low:

digitalWrite(10, LOW);

Then send the two byes of data:

SPI.transfer(0); // command byte
SPI.transfer(value); // wiper value

Finally set the SS line back to high:

digitalWrite(10, HIGH);

Easily done. Connection to our Arduino board is very simple – consider the MCP4162 pinout:

mcp4162pinout

Vdd connects to 5V, Vss to GND, CS to digital 10, SCK to digital 13, SDI to digital 11 and SDO to digital 12. Now let’s run through the available values of the MCP4162 in the following sketch:

/*
 SPI bus demo using a Microchip MCP4162 digital potentiometer [http://bit.ly/iwDmnd]
 */

#include "SPI.h" // necessary library
int ss=10; // using digital pin 10 for SPI slave select
int del=200; // used for various delays

void setup()
{
  pinMode(ss, OUTPUT); // we use this for SS pin
  SPI.begin(); // wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);
  // our MCP4162 requires data to be sent MSB (most significant byte) first
}

void setValue(int value)
{
  digitalWrite(ss, LOW);
  SPI.transfer(0); // send command byte
  SPI.transfer(value); // send value (0~255)
  digitalWrite(ss, HIGH);
}

void loop()
{
  for (int a=0; a<256; a++)
  {
    setValue(a);
    delay(del);
  }
  for (int a=255; a>=0; --a)
  {
    setValue(a);
    delay(del);
  }
}

Now to see the results of the sketch. In the following video clip, a we run up through the resistance range and measure the rheostat value with a multimeter:

Before moving forward, if digital potentiometers are new for you, consider reading this short guide written by Microchip about the differences between mechanical and digital potentiometers.

Another example:

In this example, we will use the Analog Devices AD5204 four-channel digital potentiometer (data sheet.pdf). It contains four 10k ohm linear potentiometers, and each potentiometer is adjustable to one of 256 positions.

The settings are volatile, which means they are not remembered when the power is turned off. Therefore when power is applied the potentiometers are all pre set to the middle of the scale. Our example is the SOIC-24 surface mount example, however it is also manufactured in DIP format as well.

ad5204ss

To make life easier it can be soldered onto a SOIC breakout board which converts it to a through-hole package:

ad5204boardss1

In this example, we will control the brightness of four LEDs. Wiring is very simple. Pinouts are in the data sheet.

ex34p2schematic1

And the sketch:

#include <SPI.h> // necessary library
int ss=10; // using digital pin 10 for SPI slave select
int del=5; // used for fading delay
void setup()
{
 pinMode(ss, OUTPUT); // we use this for SS pin
 SPI.begin(); // wake up the SPI bus. 
 SPI.setBitOrder(MSBFIRST); 
 // our AD5204 requires data to be sent MSB (most significant byte) first. See data sheet page 5
 allOff(); // we do this as pot memories are volatile
}
void allOff()
// sets all potentiometers to minimum value
{
 for (int z=0; z<4; z++)
 {
 setPot(z,0);
 }
}
void allOn()
// sets all potentiometers to maximum value
{
 for (int z=0; z<4; z++)
 {
 setPot(z,255);
 }
}
void setPot(int pot, int level)
// sets potentiometer 'pot' to level 'level'
{
 digitalWrite(ss, LOW);
 SPI.transfer(pot);
 SPI.transfer(level);
 digitalWrite(ss, HIGH);
}
void blinkAll(int count)
{
 for (int z=0; z
void indFade()
{
 for (int a=0; a<4; a++)
 {
 for (int l=0; l<255; l++)
 {
 setPot(a,l);
 delay(del);
 }
 for (int l=255; l>=0; --l)
 {
 setPot(a,l);
 delay(del);
 }
 }
}
void allFade(int count)
{
 for (int a=0; a<count; a++)="" {="" for="" (int="" l="0;" l<255;="" l++)="" setpot(0,l);="" setpot(1,l);="" setpot(2,l);="" setpot(3,l);="" delay(del);="" }="">=0; --l)
 {
 setPot(0,l);
 setPot(1,l);
 setPot(2,l); 
 setPot(3,l); 
 delay(del);
 }
 }
}
void loop()
{
 blinkAll(3);
 delay(1000);
 indFade();
 allFade(3);
}

The function allOff() and allOn() are used to set the potentiometers to minimum and maximum respectively. We use allOff() at the start of the sketch to turn the LEDs off. This is necessary as on power-up the wipers are generally set half-way.

Furthermore we use them in the blinkAll() function to … blink the LEDs. The function setPot() accepts a wiper number (0~3) and value to set that wiper (0~255). Finally the function indFade() does a nice job of fading each LED on and off in order – causing an effect very similar to pulse-width modulation.

Finally, here it is in action:

So there you have it – hopefully an easy to understand introduction to the world of the SPI bus and how to control the devices within. As always, now it is up to you and your imagination to find something to control or get up to other shenanigans. In the next SPI article we will look at reading and writing data via the SPI bus.

This post brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.