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 - 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 twitter, Google+, 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.
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 twitter, Google+, 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.
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 - 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 - 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 twitter, Google+, 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.
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 twitter, Google+, 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!
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 twitter, Google+, 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.























































