t r o n i x s t u f f

fun and learning with electronics

Tutorial: Arduino and multiple push-wheel switches

This is an addendum to chapter forty of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – a series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.

Updated 24/11/2012

This article continues with the push-wheel switches introduced in chapter 40. In the previous article, we learned how to read the value of a single digit using the digital pins of our Arduino. With this instalment we will examine how to read four digits – and not waste all those digital pins in the process. Instead, we will use the Microchip MCP23017 16-bit port expander IC that communicates via the I2C bus. It has sixteen digital input/output pins that we can use to read the status of each switch.

Before moving forward, please note that some assumed knowledge is required for this article – the I2C bus (parts one and two) and the MCP23017.

We first will describe the hardware connections, and then the Arduino sketch. Recall the schematic used for the single switch example:

When the switch was directly connected to the Arduino, we read the status of each pin to determine the value of the switch. We will do this again, on a larger scale using the MCP23017. Consider the pinout diagram:

We have 16 pins, which allows four switches to be connected. The commons for each switch still connect to 5V, and each switch contact still has a 10k pull-down resistor to GND. Then we connect the 1,2,4,8 pins of digit one to GPBA0~3; digit two’s 1,2,4,8 to GPA4~7; digit three’s 1,2,4,8 to GPB0~3 and digit four’s 1,2,4,8 to GPB4~7. For demonstration purposes we are using the Gravitech 7-segment shield as reviewed in the past.

Now how do we read the switches? All those wires may cause you to think it is difficult, but the sketch is quite simple. When we read the value of GPBA and B, one byte is returned for each bank, with the most-significant bit first. Each four bits will match the setting of the switch connected to the matching I/O pins.

For example, if we request the data for both IO banks and the switches are set to 1 2 3 4 – bank A will return 0010 0001 and bank B will return 0100 0011. We use some bitshift operations to separate each four bits into a separate variable – which leaves us with the value of each digit. For example, to separate the value of switch four, we shift the bits from bank B >> 4. This pushes the value of switch three out, and the blank bits on the left become zero. To separate the value for switch three, we use a compound bitwise & – which leaves the value of switch three.

Below is a breakdown of the binary switch values – it shows the raw GPIOA and B byte values, then each digit’s binary value, and decimal value:

So let’s see the demonstration sketch (download):

Example 40a

/*
 Example 40a - Read four pushwheel BCD switches via MCP23017, display on SAA1064/4-digit 7-segment LED display
 http://tronixstuff.wordpress.com | John Boxall CC by-sa-nc
 Using Arduino v1.0.1
 */
// MCP23017 pins 15~17 to GND, I2C bus address is 0x20
// SAA1064 I2C bus address 0x38
#include "Wire.h"
// for LED digit definitions
int digits[16]={
 63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113};
byte GPIOA, GPIOB, dig1, dig2, dig3, dig4;
void initSAA1064()
{
 //setup 0x38
 Wire.beginTransmission(0x38);
 Wire.write(0);
 Wire.write(B01000111); // 12mA output, no digit blanking
 Wire.endTransmission();
}
void setup()
{
 Serial.begin(9600);
 Wire.begin(); // start up I2C bus
 initSAA1064();
}
void loop()
{
 // read the inputs of bank A
 Wire.beginTransmission(0x20);
 Wire.write(0x12);
 Wire.endTransmission();
 Wire.requestFrom(0x20, 1);
 GPIOA=Wire.read(); // this byte contains the switch data for digits 1 and 2
// read the inputs of bank B
 Wire.beginTransmission(0x20);
 Wire.write(0x13);
 Wire.endTransmission();
 Wire.requestFrom(0x20, 1);
 GPIOB=Wire.read(); // this byte contains the switch data for digits 3 and 4
 // extract value for each switch
 // dig1 LHS, dig4 RHS
 dig4=GPIOB >> 4;
 dig3=GPIOB & B00001111;
 dig2=GPIOA >> 4;
 dig1=GPIOA & B00001111;
 // send all GPIO and individual switch data to serial monitor
 // for debug and interest's sake
 Serial.print("GPIOA = ");
 Serial.println(GPIOA, BIN);
 Serial.print("GPIOB = ");
 Serial.println(GPIOB, BIN);
 Serial.println();
Serial.print("digit 1 = ");
 Serial.println(dig1, BIN);
 Serial.print("digit 2 = ");
 Serial.println(dig2, BIN);
 Serial.print("digit 3 = ");
 Serial.println(dig3, BIN);
 Serial.print("digit 4 = ");
 Serial.println(dig4, BIN);
 Serial.println();
Serial.print("digit 1 = ");
 Serial.println(dig1, DEC);
 Serial.print("digit 2 = ");
 Serial.println(dig2, DEC);
 Serial.print("digit 3 = ");
 Serial.println(dig3, DEC);
 Serial.print("digit 4 = ");
 Serial.println(dig4, DEC);
 Serial.println();
// send switch value to LED display via SAA1064
 Wire.beginTransmission(0x38);
 Wire.write(1);
 Wire.write(digits[dig4]);
 Wire.write(digits[dig3]);
 Wire.write(digits[dig2]);
 Wire.write(digits[dig1]);
 Wire.endTransmission();
 delay(10);
 delay(1000);
}

And for the non-believers … a video demonstration:

So there you have it. Four digits instead of one, and over the I2C bus conserving Arduino digital I/O pins. Using eight MCP23017s you could read 32 digits at once. Have fun with doing that!

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

August 27, 2011 Posted by | arduino, education, I2C, microcontrollers | , , , , , , , , , , , , , , , , | 1 Comment

Tutorial: Maximising your Arduino’s I/O ports

This is chapter forty-one of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – a series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.

[Updated 25/11/2012]

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 I think the I2C bus is underappreciated! So let’s get started…

Here is our subject of the article in DIP form:

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:

The sixteen I/O ports are separated into two ‘banks’ – 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 0×20. If you have other devices with that address or need to use multiple MCP23017s, see figure 1-2 on page eight of the data sheet. 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 0×27 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:

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 bank is an input or an output. First, we’ll examine setting them as outputs. So to set bank A to outputs, we use:

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

Then to set bank B to outputs, we use:

 Wire.beginTransmission(0x20);
 Wire.write(0x01); // IODIRB register
 Wire.write(0x00); // set all of bank 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 bank A, we use:

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

To control bank B, we use:

  Wire.beginTransmission(0x20);
  Wire.write(0x13); // address bank 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 0×82, 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 0×82:

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

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

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

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 (download):

Example 41.1

/*
 Example 41.1 - Microchip MCP23017 with Arduino
 http://tronixstuff.wordpress.com/tutorials > chapter 41
 John Boxall | CC by-sa-nc
*/
// 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 bank A to outputs
 Wire.endTransmission();
Wire.beginTransmission(0x20);
 Wire.write(0x01); // IODIRB register
 Wire.write(0x00); // set all of bank B to outputs
 Wire.endTransmission();
}
void binaryCount()
{
 for (byte a=0; a<256; a++)
 {
 Wire.beginTransmission(0x20);
 Wire.write(0x12); // GPIOA
 Wire.write(a); // bank A
 Wire.endTransmission();
Wire.beginTransmission(0x20);
 Wire.write(0x13); // GPIOA
 Wire.write(a); // bank 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 (once again using the ‘button board‘) which are connected to bank B inputs 0~3. Consider the first five lines of void loop() in the following example (download);

Example 41.2

/*
 Example 41.2 - Microchip MCP23017 with Arduino
 http://tronixstuff.wordpress.com/tutorials > chapter 41
 John Boxall | CC by-sa-nc
 */
// 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 (0×13) to the IC. Then using Wire.requestFrom() it asks for one byte of data from the IC – the contents of the register at 0×13. 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:

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

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 bank A as outputs and bank B as inputs. Four LEDs with matching resistors are connected to bank A outputs 0~3, with the buttons connected as per example 41.2. Here is the sketch (download):

Example 41.3

/*
 Example 41.2 - Microchip MCP23017 with Arduino
 http://tronixstuff.wordpress.com/tutorials > chapter 41
 John Boxall | CC by-sa-nc
 */
// 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
 }
}

By now there shouldn’t be any surprises in the last example – it receives a byte that represents bank B, and sends that byte out to bank 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.

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

August 26, 2011 Posted by | arduino, education, I2C, microcontrollers | , , , , , , , , , , , , , , , , | Leave a Comment

Tutorial: Arduino and Push-wheel switches

This is chapter forty of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – a series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.

[Updated 20/01/13]

In this article we go back to the past via the use of push-wheel/thumb-wheel switches with out Arduino systems. Here are some examples sourced from somewhere on eBay:

For the uninitiated, each switch is one vertical segment and they can be connected together to form various sizes. You can use the buttons to select from digits zero through to nine. There are alternatives available that have a wheel you can move with your thumb instead of the increase/decrease buttons. Before the days of fancy user interfaces these switches were quite popular methods for setting numerical data entry. However they are still available today, so let’s see how they work and how we can use them.

The switch’s value is made available via binary-coded decimal. Consider the rear of the switch:

We have common on the left, then contacts for 1, 2, 4 and 8. If you apply a small voltage (say 5V) to common, the value of the switch can be measured by adding the values of the contacts that are in the HIGH state. For example, if you select 3 – contacts 1 and 2 will be at the voltage at common. The values between zero and nine can be represented as such:

By now you should realise that it would be easy to read the value of a switch – and you’re right, it is. We can connect 5V to the common,  the outputs to digital input pins of our Arduino boards, then use digitalRead() to determine the value of each output. In the sketch we use some basic mathematics to convert the BCD value to a decimal number. So let’s do that now.

From a hardware perspective, we need to take into account one more thing – the push-wheel switch behaves electrically like four normally-open push buttons. This means we need to use pull-down resistors in order to have a clear difference between high and low states. So the schematic for one switch would be (click image to enlarge):

Now it is a simple matter to connect the outputs labelled 1, 2, 4, and 8 to (for example) digital pins 8, 9, 10 and 11. Connect 5V to the switch ‘C’ point, and GND to … GND. Next, we need to have a sketch that can read the inputs and convert the BCD output to decimal. Consider the following sketch (download):

Example 40.1

/*
 Example 40.1 - display single thumbwheel switch data
 http://tronixstuff.wordpress.com/tutorials > chapter 40 | cc v3.0 by-sa-nc
 Uses Gravitech SAA1064 numerical display shield http://www.gravitech.us/7segmentshield.html
 Uses serial monitor if you don't have the SAA1064 shield
*/
#include "Wire.h"
#define q1 8
#define q2 9
#define q4 10
#define q8 11
void setup()
{
 Serial.begin(9600);
 Wire.begin(); // join i2c bus (address optional for master)
 delay(500);
 pinMode(q1, INPUT); // thumbwheel '1'
 pinMode(q2, INPUT); // thumbwheel '2'
 pinMode(q4, INPUT); // thumbwheel '4'
 pinMode(q8, INPUT); // thumbwheel '8'
}
void dispSAA1064(int Count)
// sends integer 'Count' to Gravitech SAA1064 shield
{
 const int lookup[10] = {
 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F };
 int Thousands, Hundreds, Tens, Base;
 Wire.beginTransmission(0x38);
 Wire.write(0);
 Wire.write(B01000111);
 Wire.endTransmission();
 Wire.beginTransmission(0x38);
 Wire.write(1);
 Thousands = Count/1000;
 Hundreds = (Count-(Thousands*1000))/100;
 Tens = (Count-((Thousands*1000)+(Hundreds*100)))/10;
 Base = Count-((Thousands*1000)+(Hundreds*100)+(Tens*10));
 Wire.write(lookup[Base]);
 Wire.write(lookup[Tens]);
 Wire.write(lookup[Hundreds]);
 Wire.write(lookup[Thousands]);
 Wire.endTransmission();
 delay(10);
}
int readSwitch()
{
 int total=0;
 if (digitalRead(q1)==HIGH) { total+=1; }
 if (digitalRead(q2)==HIGH) { total+=2; }
 if (digitalRead(q4)==HIGH) { total+=4; }
 if (digitalRead(q8)==HIGH) { total+=8; }
 return total;
}
void loop()
{
 dispSAA1064(readSwitch()); // sends switch value to display shield
 Serial.println(readSwitch()); // sends switch value to serial monitor box
}

The function readSwitch()  is the key. It calculates the value of the switch by adding the numerical representation of each switch output and returns the total as its result. For this example we used a numerical display shield that is controlled by the NXP SAA1064. If you don’t have one, that’s ok – the results are also sent to the serial monitor. Now, let’s see it in action:

Ok it doesn’t look like much, but if you need numerical entry it saves a lot of physical space and offers a precise method of entry.

So there you have it. Would you actually use these in a project? For one digit – yes. For four? Probably not – perhaps it would be easier to use a 12-digit keypad. There’s an idea…  But for now I hope you enjoyed reading this as much as I did writing it for you.

Update! See the addendum for using four switches at once to read four-digit numbers here

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

August 15, 2011 Posted by | arduino, education, I2C, microcontrollers | , , , , , , , , , , , , , , , , | 7 Comments

Tutorial: Arduino and the SPI bus part II

This is chapter thirty-six of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A seemingly endless series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here

[Updated 10/01/2013]

This is the second of several chapters in which we are 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:

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:

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 (download):

Example 36.1

/*
 Example 36.1 - Multiple SPI bus device demo using four Microchip MCP4162s [http://bit.ly/iwDmnd]
 http://tronixstuff.com/tutorials > chapter 36 | CC by-sa-nc | John Boxall
*/

#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:


(If you are wondering what I have done to the Freetronics board in that video, it was to add a DS1307 real-time clock IC in the prototyping section).

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):

(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:

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:

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 (download):

Example 36.2

/*
 Example 36.2 - SPI bus device demo using a Microchip MCP4921 DAC [http://bit.ly/j3TSak]
 http://tronixstuff.com/tutorials > chapter 36 | CC by-sa-nc | John Boxall
 */

#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 very convenient breakout board:

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 interesting 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. For other ways to convert between BCD and decimal, see the examples using the Maxim DS1307 in chapter seven.

Finally here is another example of reading the time data from the DS3234 (download):

/*
 Example 36.3 - SPI bus device demo using a Maxim IC DS3234 Accurate RTC
 http://tronixstuff.com/tutorials > chapter 36 | CC by-sa-nc | John Boxall
 */
#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.

In the meanwhile have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column? And join our friendly Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

June 15, 2011 Posted by | arduino, BOB-10160, dac, education, learning electronics, microcontrollers, SPI, tutorial, Uncategorized | , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , | 6 Comments

Tutorial: Arduino and the SPI bus

Learn how to use the SPI data bus with Arduino in chapter thirty-four of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A seemingly endless tutorial on the Arduino universe. The first chapter is here, the complete series is detailed here

[Updated 10/01/2013]

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 Duemilanove/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:

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:


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:

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:

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.

Example 34.1

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:


Here is the data sheet.pdf 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:

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 (download):

/*
 Example 34.1 - SPI bus demo using a Microchip MCP4162 digital potentiometer [http://bit.ly/iwDmnd]
 http://tronixstuff.com/tutorials > chapter 34 | CC by-sa-nc | John Boxall
*/

#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.

Example 34.2

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.

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

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


And the sketch (download):

// Example 34.2 - SPI bus demo using Analog Devices AD5204 digital potentiometer
#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:

Example 34.3

In this example, we will use use a four-digit, seven-segment LED display that has an SPI interface. Using such a display considerably reduces the amount of pins required on the micro controller and also negates the use of shift register ICs which helps reduce power consumption and component count. The front of our example:

and the rear:

Thankfully the pins are labelled quite clearly. Please note that the board does not include header pins – they were soldered in after receiving the board. Although this board is documented by Sparkfun there seems to be issues in the operation, so instead we will use a sketch designed by members of the Arduino forum. Not wanting to ignore this nice piece of hardware we will see how it works and use it with the new sketch from the forum.

Again, wiring is quite simple:

  • Board GND to Arduino GND
  • Board VCC to Arduino 5V
  • Board SCK to Arduino D12
  • Board SI to Arduino D11
  • Board CSN to Arduino D10

The sketch is easy to use, you need to replicate all the functions as well as the library calls and variable definitions. To display numbers (or the letters A~F) on the display, call the function

write_led(a,b,c);

where a is the number to display, b is the base system used (2 for binary, 8 for octal, 10 for usual, and 16 for hexadecimal), and c is for padded zeros (0 =off, 1=on). If you look at the void loop() part of the example sketch, we use all four number systems in the demonstration. If your number is too large for the display, it will show OF for overflow. To control the decimal points, colon and the LED at the top-right the third digit, we can use the following:

  write_led_decimals(1); // left-most decimal point
  write_led_decimals(2);
  write_led_decimals(4);
  write_led_decimals(8); // right-most decimal point
  write_led_decimals(16); // colon LEDs
  write_led_decimals(32); // apostrophe LED
  write_led_decimals(0); // off

After all that, here is the demonstration sketch for your perusal (download):

/*
 Example 34.3 - SPI bus demo using SFE 4-digit LED display [http://bit.ly/ixQdbT]
 http://tronixstuff.com/tutorials > chapter 34
 Based on code by Quazar & Busaboi on Arduio forum - http://bit.ly/iecYBQ
*/
#define DATAOUT 11 //MOSI
#define DATAIN 12 //MISO - not used, but part of builtin SPI
#define SPICLOCK 13 //sck
#define SLAVESELECT 10 //ss
char spi_transfer(volatile char data)
{
 SPDR = data; // Start the transmission
 while (!(SPSR & (1<
 {
 };
 return SPDR; // return the received byte
}
void setup()
{
 byte clr;
 pinMode(DATAOUT, OUTPUT);
 pinMode(DATAIN, INPUT);
 pinMode(SPICLOCK, OUTPUT);
 pinMode(SLAVESELECT, OUTPUT);
 digitalWrite(SLAVESELECT, HIGH); //disable device
 SPCR = (1<
 clr=SPSR;
 clr=SPDR;
 delay(10);
 write_led_numbers(0x78,0x78,0x78,0x78); //Blank display
 write_led_decimals(0x00); // All decimal points off
}
void write_led_decimals(int value)
{
 digitalWrite(SLAVESELECT, LOW);
 delay(10);
 spi_transfer(0x77); // Decimal Point OpCode
 spi_transfer(value); // Decimal Point Values
 digitalWrite(SLAVESELECT, HIGH); //release chip, signal end transfer
}
void write_led_numbers(int digit1, int digit2, int digit3, int digit4)
{
 digitalWrite(SLAVESELECT, LOW);
 delay(10);
 spi_transfer(digit1); // Thousands Digit
 spi_transfer(digit2); // Hundreds Digit
 spi_transfer(digit3); // Tens Digit
 spi_transfer(digit4); // Ones Digit
 digitalWrite(SLAVESELECT, HIGH); //release chip, signal end transfer
}
void write_led(unsigned short num, unsigned short base, unsigned short pad)
{
 unsigned short digit[4] = { 
 0, ' ', ' ', ' ' };
 unsigned short place = 0;
if ( (base<2) || (base>16) || (num>(base*base*base*base-1)) ) {
 write_led_numbers(' ', 0x00, 0x0f, ' '); // indicate overflow
 } 
 else {
 while ( (num || pad) && (place<4) ) {
 if ( (num>0) || pad )
 digit[place++] = num % base;
 num /= base;
 }
 write_led_numbers(digit[3], digit[2], digit[1], digit[0]);
 }
}
void pointDemo()
{
 write_led_decimals(1); 
 delay(1000);
 write_led_decimals(2);
 delay(1000);
 write_led_decimals(4);
 delay(1000);
 write_led_decimals(8);
 delay(1000);
 write_led_decimals(16);
 delay(1000);
 write_led_decimals(32);
 delay(1000);
 write_led_decimals(0); // non-digits all off 
}
void loop()
{
 pointDemo();
 delay(500);
 for (int i = 0; i < 100; i++) {
 write_led (i,10,1);
 delay(25);
 }
 delay(500);
 for (int i = 100; i >=0; --i) {
 write_led (i,10,0);
 delay(25);
 }
 delay(500); // now binary
 for (int i = 0; i < 16; i++) {
 write_led (i,2,0);
 delay(100);
 }
 delay(500);
 for (int i = 15; i >=0; --i) {
 write_led (i,2,0);
 delay(100);
 }
 delay(500); // now octal
 for (int i = 0; i < 500; i++) {
 write_led (i,8,0);
 delay(50);
 }
 delay(500);
 // now hexadecimal
 for (int i = 20000; i < 22000; i++) {
 write_led (i,16,0);
 delay(50);
 }
 delay(500);
}

And a short video of the demonstration:

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.

In the meanwhile have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column? And join our friendly Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

Otherwise, have fun, stay safe, be good to each other – and make something!


May 13, 2011 Posted by | arduino, COM-09767, education, learning electronics, microcontrollers, SPI | , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , | 23 Comments

Tutorial: Arduino and the I2C bus – Part Two

The first chapter is here, the complete series is detailed here. Please note from November 1, 2010 files from tutorials will be found here.

[Updated 10/01/2013]

Today we are going to continue learning about the I2C bus and how it can work for us. If you have not already, please read and understand the first I2C article before continuing.

First of all, there are some limitations of I2C to take into account when designing your projects. One of these is the physical length of the SDA and SCL lines. If all your devices are on the same PCB, then there is nothing to worry about, however if your I2C bus is longer than around one metre, it is recommended that you use an I2C bus extender IC. These ICs reduce electrical noise over the extended-length bus runs and buffer the I2C signals to reduce signal degradation and chance of errors in the data. An example of such an IC is the NXP P82B715 (data sheet). Using a pair of these ICs, you can have cable runs of 20 to 30 metres, using shielded twisted-pair cable. Below is a good example of this, from the aforementioned NXP data sheet:

Several applications come to mind with an extended I2C bus, for example remote temperature monitoring using the the ST Microelectronics CN75 temperature sensor from part one; or controlling several I/O ports using an I2C expander without the expense or worry of using a wireless system. Speaking of which, let’s do that now…

Example 21.1

A very useful and inexpensive part is the PCF8574 I/O expander (data sheet.pdf). This gives us another eight outputs, in a very similar method to the 74HC595; or can be used as eight extra inputs. In fact, if you were to use more than one 74HC595 this IC might be preferable, as you can individually address each chip instead of having to readdress every IC in line as you would with shift registers. So how do we do this? First, let’s consult the pinout:

There should not be any surprises for you there. A2~A0 are used to select the last three bits of the device address, P0~P7 are the I/O pins, and INT is an interrupt output which we will not use. To address the PCF8574 we need two things, the device address, and a byte of data which represents the required output pin state. Huh? Consider:

So if we set pins A0 to A2 to GND, our device address in binary will be 0100000, or 0×20 in hexadecimal. And the same again to set the output pins, for example to turn them all on we send binary 0 in hexadecimal which is 0; or to have the first four on and the second four off, use 00001111 which is Ox0F. Hopefully you noticed that those last two values seemed backwards – why would we send a zero to turn all the pins on?

The reason is that the PCF8574 is a current sink. This means that current runs from +5v, through into the I/O pins. For example, an LED would have the anode on the +5V, and the cathode connected to an I/O pin. Normally (for example with a 74HC595) current would run from the IC, through the resistor, LED and then to earth. That is a current source. Consider the following quick diagram:

In the example above, please note that the PCF8574N can take care of current limitation with LEDs, whereas the 74HC595 needs a current-limiting resistor to protect the LED.

Luckily this IC can handle higher volumes of current, so a resistor will not be required. It sounds a bit odd, but like anything is easy once you spend a few moments looking into it. So now let’s use three PCF8574s to control 24 LEDs. To recreate this masterpiece of blinkiness you will need:

  • Arduino Uno/Duemilanove or Freetronics Eleven board
  • A large solderless breadboard
  • Three PCF8574 I/O extenders
  • Eight each of red, green and yellow (or your choice) LEDs, each with a current draw of no more than 20mA
  • Two 4.7 kilo ohm resistors
  • Hook-up wires
  • Three 0.1 uF ceramic capacitors

Here is the schematic:

… and the example board layout:


and the example sketch. Note that the device addresses in the sketch match the schematic above. If for some reason you are wiring your PCF8574s differently, you will need to recalculate your device addresses: (download sketch)

/*
 Example 21.1
 Texas Instruments PCF8574N demonstration sketch
 element-14 part number 7527718; RS part number 517-0687
 http://tronixstuff.com/tutorials > chapter 21
 CC by-sa v3.0
 */
#include "Wire.h"
#define redchip 0x20 // device addresses for PCF8547Ns on each LED colour bank 
#define yellowchip 0x22 // addresses in this example match the published schematic in the tutorial
#define greenchip 0x21 // you will need to change addresses if you vary from the schematic
int dd=20; // used for delay timing
void setup()
{
 Wire.begin();
 allOff(); // the PCF8574N defaults to high, so this functions turns all outputs off
}
// remember that the IC "sinks" current, that is current runs fro +5v through the LED and then to I/O pin
// this means that 'high' = off, 'low' = on.
void testfunc()
{
 Wire.beginTransmission(redchip);
 Wire.write(0); 
 Wire.endTransmission();
 delay(dd+50);
 Wire.beginTransmission(redchip);
 Wire.write(255); 
 Wire.endTransmission();
 delay(dd+50);
 Wire.beginTransmission(yellowchip);
 Wire.write(0); 
 Wire.endTransmission();
 delay(dd+50);
 Wire.beginTransmission(yellowchip);
 Wire.write(255); 
 Wire.endTransmission();
 delay(dd+50);
 Wire.beginTransmission(greenchip);
 Wire.write(0); 
 Wire.endTransmission();
 delay(dd+50);
 Wire.beginTransmission(greenchip);
 Wire.write(255); 
 Wire.endTransmission();
 delay(dd+50);
}
void testfunc2()
{
 for (int y=1; y<256; y*=2)
 {
 Wire.beginTransmission(redchip);
 Wire.write(255-y); // we need the inverse, that is high = off
 Wire.endTransmission();
 delay(dd);
 Wire.beginTransmission(redchip);
 Wire.write(255); 
 Wire.endTransmission();
 delay(dd);
 }

 for (int y=1; y<256; y*=2)
 {
 Wire.beginTransmission(yellowchip);
 Wire.write(255-y); 
 Wire.endTransmission();
 delay(dd);
 Wire.beginTransmission(yellowchip);
 Wire.write(255); 
 Wire.endTransmission();
 delay(dd);
 }
 for (int y=1; y<256; y*=2)
 {
 Wire.beginTransmission(greenchip);
 Wire.write(255-y); 
 Wire.endTransmission();
 delay(dd);
 Wire.beginTransmission(greenchip);
 Wire.write(255); 
 Wire.endTransmission();
 delay(dd);
 }
}
void testfunc3()
{
 Wire.beginTransmission(redchip);
 Wire.write(0); 
 Wire.endTransmission();
 Wire.beginTransmission(yellowchip);
 Wire.write(0); 
 Wire.endTransmission();
 Wire.beginTransmission(greenchip);
 Wire.write(0); 
 Wire.endTransmission();
 delay(dd+50);
 allOff();
 delay(dd+50);
}
void allOff()
{
 Wire.beginTransmission(redchip);
 Wire.write(255); 
 Wire.endTransmission();
 Wire.beginTransmission(yellowchip);
 Wire.write(255); 
 Wire.endTransmission();
 Wire.beginTransmission(greenchip);
 Wire.write(255); 
 Wire.endTransmission();
}
void loop()
{
 for (int z=0; z<10; z++)
 {
 testfunc();
 }
 for (int z=0; z<10; z++)
 {
 testfunc2();
 }
 for (int z=0; z<10; z++)
 {
 testfunc3();
 }
}

And finally our demonstration video:


That was a good example of controlling many outputs with our humble I2C bus. You could literally control hundreds of outputs if necessary – a quite inexpensive way of doing so. Don’t forget to take into account the total current draw of any extended circuits if you are powering from your Arduino boards.

The next devices to examine on our I2C bus ride are EEPROMs - Electrically Erasable Programmable Read-Only Memory. These are memory chips that can store data without requiring power to retain memory. Why would we want to use these? Sometimes you might need to store a lot of reference data for use in calculations during a sketch, such as a mathematical table; or perhaps numerical representations of maps or location data; or create your own interpreter within a sketch that takes instruction from data stored in an array.

In other words, an EEPROM can be used to store data of a more permanent use, ideal for when your main microcontroller doesn’t haven enough memory for you to store the data in the program code. However, EEPROMs are not really designed for random-access or constant read/write operations – they have a finite lifespan. But their use is quite simple, so we can take advantage of them.

EEPROMS, like anything else come in many shapes and sizes. The model we will examine today is the Microchip 24LC256 (data sheet.pdf). It can hold 256 kilobits of data (that’s 32 kilobytes) and is quite inexpensive. This model also has selectable device addresses using three pins, so we can use up to eight at once on the same bus. An example:

The pinouts are very simple:

Pin 7 is “write protect” – set this low for read/write or high for read only. You could also control this in software if necessary. Once again we need to create a slave I2C device address using pins 1, 2 and 3 – these correlate to A2, A1 and A0 in the following table:

So if you were just using one 24LC256, the easiest solution would be to set A0~A2 to GND – which makes your slave address 1010000 or 0×50 in hexadecimal. There are several things to understand when it comes to reading and writing our bytes of data. As this IC has 32 kilobytes of storage, we need to be able to reference each byte in order to read or write to it. There is a slight catch in that you need more than one byte to reference 32767 (as in binary 32767 is 11111111 0100100 [16 bits]).

So when it comes time to send read and write requests, we need to send two bytes down the bus – one representing the higher end of the address (the first 8 bits from left to right), and the next one representing the lower end of the address (the final 8 bits from left to right) – see figure 6.1 on page 9 of the data sheet.

An example – we need to reference byte number 25000. In binary, 25000 is 0110000110101000. So we split that up into 01100001 and 10101000, then covert the binary values to numerical bytes with which to send using the Wire.send(). Thankfully there are two operators to help us with this. This first is >>, known as bitshift right. This will take the higher end of the byte and drop off the lower end, leaving us with the first 8 bits. To isolate the lower end of the address, we use another operator &, known as bitwise and. This unassuming character, when used with 0XFF can separate the lower bits for us. This may seem odd, but will work in the examples below.

Writing data to the 24LC256

Writing data is quite easy. But first remember that a byte of data is 11111111 in binary, or 255 in decimal. First we wake up the I2C bus with

Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND

then send down some data. The first data are the two bytes representing the address (25000) of the byte (12) we want to write to the memory.

Wire.write(25000 >> 8);  // send the left-hand side of the address down
Wire.write(25000 & 0xFF); // send the right-hand side of the address down

And finally, we send the byte of data to store at address 25000, then finish the connection:

Wire.write(12);
Wire.endTransmission();

There we have it. Now for getting it back…

Reading data from the 24LC256

Reading is quite similar. First we need to start things up and move the pointer to the data we want to read:

Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND
Wire.write(25000 >> 8);  // send the left-hand side of the address down
Wire.write(25000 & 0xFF); // send the right-hand side of the address down
Wire.endTransmission();

Then, ask for the byte(s) of data starting at the current address:

Wire.beginTransmission(0x50); // if pins A0~A2 are set to GND
Wire.requestFrom(0x50,1);
Wire.read(incomingbyte);

In this example, incomingbyte is a byte variable used to store the data we retrieved from the IC. 

Example 21.2

Now we have the theory, let’s put it into practice with the test circuit below, which contains two 24LC256 EEPROMs. To recreate this you will need:

  • Arduino Uno or Freetronics Eleven board
  • A large solderless breadboard
  • Two Microchip 24LC256 EEPROMs
  • Two 4.7 kilo ohm resistors
  • Hook-up wires
  • Two 0.1 uF ceramic capacitors

Here is the schematic:

… the board layout:

and the example sketch. Note that the device addresses in the sketch match the schematic above. If for some reason you are wiring your 24LC256s differently, you will need to recalculate your device addresses. To save time with future coding, we have our own functions for reading and writing bytes to the EEPROM – readData() and writeData(). Consider the sketch for our example: (download sketch)

/*
 Example 21.2
 Reading and writing data to Microchip 24LC256 EEPROMS over I2C
 tronixstuff.com/tutorials > Chapter 21
 CC by-sa v3.0 
*/
#include  // for I2C
#define chip1 0x50 // device address for left-hand chip on our breadboard
#define chip2 0x51 // and the right
// always have your values in variables
unsigned int pointer = 69; // we need this to be unsigned, as you may have an address > 32767
byte d=0; // example variable to handle data going in and out of EERPROMS
void setup()
{
 Serial.begin(9600); // for screen output
 Wire.begin(); // wake up, I2C!
}
void writeData(int device, unsigned int add, byte data) 
// writes a byte of data 'data' to the chip at I2C address 'device', in memory location 'add'
{
 Wire.beginTransmission(device);
 Wire.write((int)(add >> 8)); // left-part of pointer address
 Wire.write((int)(add & 0xFF)); // and the right
 Wire.write(data);
 Wire.endTransmission();
 delay(10);
}
byte readData(int device, unsigned int add) 
// reads a byte of data from memory location 'add' in chip at I2C address 'device' 
{
 byte result; // returned value
 Wire.beginTransmission(device); // these three lines set the pointer position in the EEPROM
 Wire.write((int)(add >> 8)); // left-part of pointer address
 Wire.write((int)(add & 0xFF)); // and the right
 Wire.endTransmission();
 Wire.requestFrom(device,1); // now get the byte of data...
 result = Wire.read();
 return result; // and return it as a result of the function readData
}
void loop()
{
 Serial.println("Writing data...");
 for (int a=0; a<20; a++)
 {
 writeData(chip1,a,a);
 writeData(chip2,a,a); // looks like a tiny EEPROM RAID solution!
 }
 Serial.println("Reading data...");
 for (int a=0; a<20; a++)
 {
 Serial.print("chip1 pointer ");
 Serial.print(a);
 Serial.print(" holds ");
 d=readData(chip1,a);
 Serial.println(d, DEC);
 }
 for (int a=0; a<20; a++)
 {
 Serial.print("chip2 pointer ");
 Serial.print(a);
 Serial.print(" holds ");
 d=readData(chip2,a);
 Serial.println(d, DEC);
 } 
}

And the output from the example sketch:

Although the sketch in itself was simple, you now have the functions to read and write byte data to EEPROMS. Now it is up to your imagination to take use of the extra memory.

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

October 29, 2010 Posted by | arduino, I2C, learning electronics, microcontrollers, tutorial | , , , , , , , , , , , , , , , , , , , , , , | 11 Comments

Tutorial: Arduino and the I2C bus – Part One

This is part one of several tutorials on how to use the I2C bus with Arduino, and chapter twenty of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A tutorial on the Arduino universe. The first chapter is here, the complete series is detailed here.

[Updated 10/01/2013]

In this first of several tutorials we are going to investigate the I2C data bus, and how we can control devices using it with our Arduino systems. The I2C bus can be a complex interface to master, so I will do my best to simplify it for you. In this article we will learn the necessary theory, and then apply it by controlling a variety of devices. Furthermore it would be in your interest to have an understanding of the binary, binary-coded decimal and hexadecimal number systems.

But first of all, what is it?

I2C is an acronym for “Inter-Integrated Circuit”. In the late 1970s, Philips’ semiconductor division (now NXP) saw the need for simplifying and standardising the data lines that travel between various integrated circuits in their products. Their solution was the I2C bus. This reduced the number of wires to two (SDA – data, and SCL – clock). Here is a nice introductory video from NXP:

Why would we want to use I2C devices?

As there are literally thousands of components that use the I2C interface! And our Arduino boards can control them all. There are many applications, such a real-time clocks, digital potentiometers, temperature sensors, digital compasses, memory chips, FM radio circuits, I/O expanders, LCD controllers, amplifiers, and so on. And you can have more than one on the bus at any time, in fact the maximum number of I2C devices used at any one time is 112.

From a hardware perspective, the wiring is very easy. Those of you with an Arduino Duemilanove, Uno or 100% compatible board, you will be using pins A4 for SDA (data) and A5 for SCL (clock).

If you are using an Arduino Mega, SDA is pin 20 and SCL is 21, so note that shields with I2C need to be specifically for the Mega. If you have another type of board, check your data sheet or try the Arduino team’s hardware website.  And finally, if you are using a bare DIP ATmega328-PU microcontroller, you will use pins 27 for SDA and 28 for SCL.

The bus wiring is simple:

If you are only using one I2C device, the pull-up resistors are (normally) not required, as the ATmega328 microcontroller in our Arduino has them built-in.  However if you are running a string of devices, use two 10 kilo ohm resistors. Like anything, some testing on a breadboard or prototype circuit will determine their necessity. Sometimes you may see in a particular device’s data sheet the use of different value pull-up resistors – for example 4.7k ohm. If so, heed that advice. The maximum length of an I2C bus is around one metre, and is a function of the capacitance of the bus. This distance can be extended with the use of a special IC, which we will examine during the next I2C chapter.

Each device can be connected to the bus in any order, and devices can be masters or slaves. In our Arduino situation, the board is the master and the devices on the I2C bus are the slaves. We can write data to a device, or read data from a device. By now you should be thinking “how do we differentiate each device on the bus?”… Each device has a unique address. We use that address in the functions described later on to direct our read or write requests to the correct device. It is possible to use two devices with identical addresses on an I2C bus, but that will be discussed in a later article.

As like most devices, we make use of an Arduino library, in this case <wire.h>. Then use the function Wire.begin(); inside of void setup() and we’re ready to go.

Sending data from our Arduino to the I2C devices requires two things: the unique device address (we need this in hexadecimal) and at least one byte of data to send. For example, the address of the part in example 20.1 (below) is 00101111 (binary) which is 0X2F in hexadecimal. Then we want to set the wiper value, which is a value between 0 and 127, or 0×00 and 0x7F in hexadecimal. So to set the wiper to zero, we would use the following three functions:

Wire.beginTransmission(0x2F);      // part address is 0x2F or 0101111b

This sends the device address down the SDA (data) line of the bus. It travels along the bus, and “notifies” the matching device that it has some data coming…

Wire.write(69); // sends 69 down the bus

This sends the byte of data to the device – into the device register (or memory of sorts), which is waiting for it with open arms. Any other devices on the bus will ignore this. Note that you can only perform one I2C operation at a time! Then when we have finished sending data to the device, we “end transmission”. This tells the device that we’re finished, and frees up the I2C bus for the next operation:

Wire.endTransmission();

Some devices may have more than one register, and require more bytes of data in each transmission. For example, the DS1307 real-time clock IC has eight registers to store timing data, each requiring eight bits of data (one byte):

However with the DS1307  - the entire lot need to be rewritten every time. So in this case we would use eight wire.send(); functions every time. Each device will interpret the byte of data sent to it, so you need the data sheet for your device to understand how to use it.

Receiving data from an I2C device into our Arduino requires two things: the unique device address (we need this in hexadecimal) and the number of bytes of data to accept from the device. Receiving data at this point is a two stage process. If you review the table above from the DS1307 data sheet, note that there is eight registers, or bytes of data in there. The first thing we need to do is have the I2C device start reading from the first register, which is done by sending a zero to the device:

Wire.beginTransmission(device_address);
Wire.write(0);
Wire.endTransmission();

Now the I2C device will send data from the first register when requested. We now need to ask the device for the data, and how many bytes we want. For example, if a device held three bytes of data, we would ask for three, and store each byte in its own variable (for example, we have three variables of type byte: a, b, and c. The first function to execute is:

Wire.requestFrom(device_address, 3);

Which tells the device to send three bytes of data back to the Arduino. We then immediately follow this with:

*a = Wire.read();
*b = Wire.read();
*c = Wire.read();

We do not need to use Wire.endTransmission() when reading data. Now that the requested data is in their respective variables, you can treat them like any ordinary byte variable.

For a more detailed explanation of the I2C bus, read this explanatory document by NXP. Now let’s use our I2C knowledge by controlling a range of devices…

Example 20.1

A new part for today, the Microchip MCP4018T digital linear potentiometer. The value of this model is 10 kilo ohms. Inside this tiny, tiny SMD part is a resistor array consisting of 127 elements and a wiper that we control by sending a value of between 0 and 127 (in hexadecimal) down the I2C bus. This is a volatile digital potentiometer, it forgets the wiper position when the power is removed. However naturally there is a compromise with using such a small part, it is only rated for 2.5 milliamps – but used in conjunction with op amps and so on. For more information, please consult the data sheet.

As this is an SMD part, for breadboard prototyping purposes it needed to be mounted on a breakout board. Here it is in raw form:

Above the IC is a breakout board. Consider that the graph paper is 5mm square! It is the incorrect size, but all I have. However soldering was bearable. Put a drop of solder on one pad of the breakout board, then hold the IC with tweezers in one hand, and reheat the solder with the other hand – then push the IC into place. A few more tiny blobs of solder over the remaining pins, and remove the excess with solder wick. Well … it worked for me:

Our example schematic is as follows:


As you can see, the part is simple to use, your signal enters pin 6 and the result of the voltage division is found on pin 5. Please note that this is not a replacement for a typical mechanical potentiometer, we can’t just hook this up as a volume or motor-speed control! Again, please read the data sheet.

Control is very simple, we only need to send one byte of data down, the hexadecimal reference point for the wiper, e.g.:

Wire.beginTransmission(0x2F);      // part address is 0x2F or 0101111b
Wire.write(0x3F); //
Wire.endTransmission();

Here is a quick demonstration that moves the wiper across all points: (download)

/*
 Example 20.1
 Microchip MCP4018 digital potentiometer demonstration sketch
 http://tronixstuff.com/tutorials > chapter 20
 CC by-sa v3.0
*/
int dt = 2000; // used for delay duration
byte rval = 0x00; // used for value sent to potentiometer
#include "Wire.h"
#define pot_address 0x2F // each I2C object has a unique bus address, the MCP4018 is 0x2F or 0101111 in binary
void setup()
{
 Wire.begin();
 Serial.begin(9600); 
}
void potLoop()
// sends values of 0x00 to 0x7F to pot in order to change the resistance
// equates to 0~127
{
 for (rval=0; rval<128; rval++)
 {
 Wire.beginTransmission(pot_address);
 Wire.write(rval); // 
 Wire.endTransmission();
 Serial.print(" sent - ");
 Serial.println(rval, HEX);
 delay(dt);
 }
}

void loop()
{
 potLoop();
}

and a video demonstration:


Example 20.2

Now we will read some data from an I2C device. Our test subject is the ST Microelectronics CN75 temperature sensor. Again, we have another SMD component, but the CN75 is the next stage larger than the part from example 20.1. Thankfully this makes the soldering process much easier, however still requiring some delicate handiwork:

First, a small blob of solder, then slide the IC into it. Once that has cooled, you can complete the rest and solder the header pins into the breakout board:

Our example schematic is as follows:


Pins 5, 6 and 7 determine the final three bits of the device address – in this case they are all set to GND, which sets the address to 1001000. This allows you to use multiple sensors on the same bus. Pin 3 is not used for basic temperature use, however it is an output for the thermostat functions, which we will examine in the next chapter.

As a thermometer it can return temperatures down to the nearest half of a degree Celsius. Although that may not be accurate enough, it was designed for automotive and thermostat use. For more details please read the data sheet. The CN75 stores the temperature data in two bytes, let’s call them A and B. So we use

Wire.requestFrom(cn75address, 2)

with the second paramater as 2, as we want two bytes of data. Which we then store using the following functions:

*a = Wire.read(); // first received byte stored here
*b = Wire.read(); // second received byte stored here

where *a and *b are variables of the type byte.

And as always, there is a twist to decoding the temperature from these bytes. Here are two example pieces of sample data:

Example bytes one: 00011001 10000000
Example bytes two: 11100111 00000000

The bits in each byte note particular values… the most significant bit (leftmost) of byte A determines whether it is below or above zero degrees – 1 for below zero. The remaining seven bits are the binary representation of the integer part of the temperature; if it is below zero, we subtract 128 from the value of the whole byte and multiply by -1. The most significant bit of byte B determines the fraction, either zero or half a degree. So as you will see in the following example sketch (download), there is some decision making done in showCN75data():

/*
 Example 20.2
 ST Microelectronics CN75 Digital Temperature sensor demonstration sketch
 CC by-sa v3.0
 */
#include "Wire.h"
#define cn75address 0x48 // with pins 5~7 set to GND, the device address is 0x48
void setup()
{
 Wire.begin(); // wake up I2C bus
 Serial.begin(9600);
}
void getCN75data(byte *a, byte *b)
{
 // move the register pointer back to the first register
 Wire.beginTransmission(cn75address); // "Hey, CN75 @ 0x48! Message for you"
 Wire.write(0); // "move your register pointer back to 00h"
 Wire.endTransmission(); // "Thanks, goodbye..."
// now get the data from the CN75
 Wire.requestFrom(cn75address, 2); // "Hey, CN75 @ 0x48 - please send me the contents of your first two registers"
 *a = Wire.read(); // first received byte stored here
 *b = Wire.read(); // second received byte stored here
}
void showCN75data()
{
 byte aa,bb;
 float temperature=0;
 getCN75data(&aa,&bb);
 if (aa>127) // check for below zero degrees
 {
 temperature=((aa-128)*-1);
 if (bb==128) // check for 0.5 fraction
 {
 temperature-=0.5;
 }
 } 
 else // it must be above zero degrees
 {
 temperature=aa;
 if (bb==128) // check for 0.5 fraction
 {
 temperature+=0.5;
 }
 }
 Serial.print("Temperature = ");
 Serial.print(temperature,1);
 Serial.println(" degrees C");
 delay(1000);
}
void loop()
{
 showCN75data();
}

And here is the result from the serial monitor:

Example 20.3

Now that we know how to read and write data to devices on the I2C bus – here is an example of doing both, with a very popular device – the Maxim DS1307 real-time clock IC. Before moving on, consider reading their good data sheet. For those of you new to the world of tronixstuff, we use this part quite often, for example with our Arduino RTC shield and modifications, or blinky – the one-eyed clock. It is an 8-pin DIP IC that allows timing with accuracy down to a few seconds a day:

Furthermore, it also has a programmable square-wave generator. Connection and use is quite simple:

However some external components are required: a 32.768 kHz crystal, a 3V battery for time retention when the power is off, and a 10k ohm pullup resistor is required if using as a square-wave generator, and 10k ohm pull-up resistors on the SCL and SDA lines. You can use the SQW and timing simultaneously. If we have a more detailed look at the register map for the DS1307:

We see that the first seven registers are for timing data, the eighth is the square-wave control, and then another eight RAM registers. In this chapter we will look at the first eight only. Hopefully you have noticed that various time parameters are represented by less than eight bits of data – the DS1307 uses binary-coded decimal. But don’t panic, we have some functions to do the conversions for us.

However, in general  - remember that each bit in each register can only be zero or one – so how do we represent a register’s contents in hexadecimal? First, we need to find the binary representation, then convert that to hexadecimal. So, using the third register of the DS1307 as an example, and a time of 12:34 pm – we will read from left to right. Bit 7 is unused, so it is 0. Bit 6 determines whether the time kept is 12- or 24-hour time. So we’ll choose 1 for 12-hour time. Bit 5 (when bit 6 is 0) is the AM/PM indicator – choose 1 for PM. Bit 4 represents the left-most digit of the time, that is the 1 in 12:34 pm. So we’ll choose 1. Bits 3 to 0 represent the BCD version of 2 which is 0010.

So to store 12pm as hours we need to write 00110010 as hexadecimal into the hours register – which is 0×32.

Reading data from the DS1307 should be easy for you now, reset the register pointed, then request seven bytes of data and receive them into seven variables. The device address is 0×68.  For example:

Wire.beginTransmission(0x68);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
*second     = bcdToDec(Wire.read();
*minute     = bcdToDec(Wire.read();
*hour       = bcdToDec(Wire.read();
*dayOfWeek  = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month      = bcdToDec(Wire.read());
*year       = bcdToDec(Wire.read());

At which point the time data will need to be converted to decimal numbers, which we will take care of in the example sketch later. Setting the time, or controlling the square-wave output is another long operation – you need to write seven variables to set the time or eight to change the square-wave output. For example, the time:

Wire.beginTransmission(0x68);
Wire.write(0);
Wire.write(decToBcd(second));
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.endTransmission();

The decToBcd is a function defined in our example to convert the decimal numbers to BCD suitable for the DS1307.

You can also address each register individually. We will demonstrate doing this with an explanation of how to control the DS1037′s in built square-wave generator (download sketch):

/*
DS1307 Square-wave machine
 Used to demonstrate the four different square-wave outputs from Maxim DS1307
 See page nine of data sheet for more information
 John Boxall - tronixstuff.wordpress.com
 */
#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // each I2C object has a unique bus address, the DS1307 is 0x68
void setup()
{
 Wire.begin();
}
void sqw1() // set to 1Hz
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address
 Wire.write(0x10); // sends 0x10 (hex) 00010000 (binary)
 Wire.endTransmission();
}
void sqw2() // set to 4.096 kHz
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address 
 Wire.write(0x11); // sends 0x11 (hex) 00010001 (binary)
 Wire.endTransmission();
}
void sqw3() // set to 8.192 kHz
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address 
 Wire.write(0x12); // sends 0x12 (hex) 00010010 (binary)
 Wire.endTransmission();
}
void sqw4() // set to 32.768 kHz (the crystal frequency)
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address 
 Wire.write(0x13); // sends 0x13 (hex) 00010011 (binary)
 Wire.endTransmission();
}
void sqwOff()
// turns the SQW off
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address
 Wire.write(0x00); // turns the SQW pin off
 Wire.endTransmission();
}
void loop()
{
 sqw1();
 delay(5000);
 sqw2();
 delay(5000);
 sqw3();
 delay(5000);
 sqw4();
 delay(5000);
 sqwOff();
 delay(5000);
}

Here is the SQW output in action – we measure the frequency using my very old Tek CFC-250:

For further DS1307 examples, I will not repeat myself and instead direct you to the list of many tronixstuff articles that make use of the DS1307.

So there you have it – hopefully an easy to understand introduction to the world of the I2C bus and how to control the devices within. Part two of the I2C tutorial has now been published, as well as an article about the NXP SAA1064 LED display driver IC and the Microchip MC23017 16-bit port expander IC.

Have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

October 20, 2010 Posted by | arduino, education, I2C, learning electronics, microcontrollers, tutorial | , , , , , , , , , , , , , , , , , , , , , , , , , | 7 Comments

   

Follow

Get every new post delivered to your Inbox.

Join 3,836 other followers

%d bloggers like this: