Tutorial: Arduino and the MSGEQ7 Spectrum Analyzer
This is a tutorial on using the MSGEQ7 Spectrum Analyser with Arduino, and chapter forty-eight 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 30/01/2013
In this article we’re going to explain how to make simple spectrum analysers with an Arduino-style board. (Analyser? Analyzer? Take your pick).
First of all, what is a spectrum analyser? Good question. Do you remember what this is?
It’s a mixed graphic equaliser/spectrum analyser deck for a hi-fi system. The display in the middle is the spectrum analyser, and roughly-speaking it shows the strength of different frequencies in the music being listened to – and looked pretty awesome doing it. We can recreate displays similar to this for entertainment and also as a base for creative lighting effects. By working through this tutorial you’ll have the base knowledge to recreate these yourself.
We’ll be using the MSGEQ7 “seven band graphic equaliser IC” from Mixed Signal Integration. Here’s the MSGEQ7 data sheet (.pdf). This little IC can accept a single audio source, analyse seven frequency bands of the audio, and output a DC representation of each frequency band. This isn’t super-accurate or calibrated in any way, but it works. You can get the IC separately, for example:
and then build your own circuit around it… or like most things in the Arduino world – get a shield. In this case, a derivative of the original Bliptronics shield by Sparkfun. It’s designed to pass through stereo audio via 3.5mm audio sockets and contains two MSGEQ7s, so we can do a stereo analyser:
As usual Sparkfun have saved a few cents by not including the stackable header sockets, so you’ll need to buy and solder those in yourself. There is also space for three header pins for direct audio input (left, right and common), which are useful – so if you can add those as well.
So now you have a shield that’s ready for use. Before moving forward let’s examine how the MSGEQ7 works for us. As mentioned earlier, it analyses seven frequency bands. These are illustrated in the following graph from the data sheet:
It will return the strengths of the audio at seven points – 63 Hz, 160 Hz, 400 Hz, 1 kHz, 2.5 kHz, 6.25 kHz and 16 kHz – and as you can see there is some overlap between the bands. The strength is returned as a DC voltage – which we can then simply measure with the Arduino’s analogue input and create a display of some sort. At this point audio purists, Sheldonites and RF people might get a little cranky, so once again – this is more for visual indication than any sort of calibration device.
However as an 8-pin IC a different approach is required to get the different levels. The IC will sequentially give out the levels for each band on pin 3- e.g. 63 Hz then 160 Hz then 400 Hz then 1 kHz then 2.5 kHz then 6.25 kHz then 16 kHz then back to 63 Hz and so on. To start this sequence we first reset the IC by pulsing the RESET pin HIGH then low. This tells the IC to start at the first band. Next, we set the STROBE pin to LOW, take the DC reading from pin 3 with analogue input, store the value in a variable (an array), then set the STROBE pin HIGH. We repeat the strobe-measure sequence six more times to get the rest of the data, then RESET the IC and start all over again. For the visual learners consider the diagram below from the data sheet:
To demonstrate this process, consider the function
readMSGEQ7()
in the following example sketch (download):
// Example 48.1 - tronixstuff.com/tutorials > chapter 48 - 30 Jan 2013 // MSGEQ7 spectrum analyser shield - basic demonstration
int strobe = 4; // strobe pins on digital 4 int res = 5; // reset pins on digital 5
int left[7]; // store band values in these arrays int right[7];
int band;
void setup()
{
Serial.begin(115200);
pinMode(res, OUTPUT); // reset
pinMode(strobe, OUTPUT); // strobe
digitalWrite(res,LOW); // reset low
digitalWrite(strobe,HIGH); //pin 5 is RESET on the shield
}
void readMSGEQ7()
// Function to read 7 band equalizers
{
digitalWrite(res, HIGH);
digitalWrite(res, LOW);
for(band=0; band <7; band++)
{
digitalWrite(strobe,LOW); // strobe pin on the shield - kicks the IC up to the next band
delayMicroseconds(30); //
left[band] = analogRead(0); // store left band reading
right[band] = analogRead(1); // ... and the right
digitalWrite(strobe,HIGH);
}
}
void loop()
{
readMSGEQ7();
// display values of left channel on serial monitor
for (band = 0; band < 7; band++)
{
Serial.print(left[band]);
Serial.print(" ");
}
Serial.println();
// display values of right channel on serial monitor
for (band = 0; band < 7; band++)
{
Serial.print(right[band]);
Serial.print(" ");
}
Serial.println();
}
If you follow through the sketch, you can see that it reads both left- and right-channel values from the two MSGEQ7s on the shield, then stores each value in the arrays left[] and right[]. These values are then sent to the serial monitor for display – for example:
If you have a function generator, connect the output to one of the channels and GND – then adjust the frequency and amplitude to see how the values change. The following video clip is a short demonstration of this – we set the generator to 1 kHz and adjust the amplitude of the signal. To make things easier to read we only measure and display the left channel:
Keep an eye on the fourth column of data – this is the analogRead() value returned by the Arduino when reading the 1khz frequency band. You can also see the affect on the other bands around 1 kHz as we increase and decrease the frequency. However that wasn’t really visually appealing – so now we’ll create a small and large graphical version.
First we’ll use an inexpensive LCD, the I2C model from akafugu reviewed previously. To save repeating myself, also review how to create custom LCD characters from here.
With the LCD with have two rows of sixteen characters. The plan is to use the top row for the levels, the left-channel’s on … the left, and the right on the right. Each character will be a little bar graph for the level. The bottom row can be for a label. We don’t have too many pixels to work with, but it’s a compact example:
We have eight rows for each character, and the results from an analogueRead() fall between 0 and 1023. So that’s 1024 possible values spread over eight sections. Thus each row of pixels in each character will represent 128 “units of analogue read” or around 0.63 V if the Arduino is running from true 5 V (remember your AREF notes?). The sketch will again read the values from the MSGEQ7, feed them into two arrays – then display the required character in each band space on the LCD.
Here’s the resulting sketch (download):
// Example 48.2 - tronixstuff.com/tutorials > chapter 48 - 30 Jan 2013 // MSGEQ7 spectrum analyser shield and I2C LCD from akafugu
// for akafugu I2C LCD #include #include "TWILiquidCrystal.h" LiquidCrystal lcd(50);
// create custom characters for LCD
byte level0[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111};
byte level1[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111};
byte level2[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111};
byte level3[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111};
byte level4[8] = { 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte level5[8] = { 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte level6[8] = { 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte level7[8] = { 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
int strobe = 4; // strobe pins on digital 4 int res = 5; // reset pins on digital 5
int left[7]; // store band values in these arrays int right[7];
int band;
void setup()
{
Serial.begin(9600);
// setup LCD and custom characters
lcd.begin(16, 2);
lcd.setContrast(24);
lcd.clear();
lcd.createChar(0,level0); lcd.createChar(1,level1); lcd.createChar(2,level2); lcd.createChar(3,level3); lcd.createChar(4,level4); lcd.createChar(5,level5); lcd.createChar(6,level6); lcd.createChar(7,level7);
lcd.setCursor(0,1);
lcd.print("Left");
lcd.setCursor(11,1);
lcd.print("Right");
pinMode(res, OUTPUT); // reset pinMode(strobe, OUTPUT); // strobe digitalWrite(res,LOW); // reset low digitalWrite(strobe,HIGH); //pin 5 is RESET on the shield }
void readMSGEQ7()
// Function to read 7 band equalizers
{
digitalWrite(res, HIGH);
digitalWrite(res, LOW);
for( band = 0; band < 7; band++ )
{
digitalWrite(strobe,LOW); // strobe pin on the shield - kicks the IC up to the next band
delayMicroseconds(30); //
left[band] = analogRead(0); // store left band reading
right[band] = analogRead(1); // ... and the right
digitalWrite(strobe,HIGH);
}
}
void loop()
{
readMSGEQ7();
// display values of left channel on LCD
for( band = 0; band < 7; band++ )
{
lcd.setCursor(band,0);
if (left[band]>=895) { lcd.write(7); } else
if (left[band]>=767) { lcd.write(6); } else
if (left[band]>=639) { lcd.write(5); } else
if (left[band]>=511) { lcd.write(4); } else
if (left[band]>=383) { lcd.write(3); } else
if (left[band]>=255) { lcd.write(2); } else
if (left[band]>=127) { lcd.write(1); } else
if (left[band]>=0) { lcd.write(0); }
}
// display values of right channel on LCD
for( band = 0; band < 7; band++ )
{
lcd.setCursor(band+9,0);
if (right[band]>=895) { lcd.write(7); } else
if (right[band]>=767) { lcd.write(6); } else
if (right[band]>=639) { lcd.write(5); } else
if (right[band]>=511) { lcd.write(4); } else
if (right[band]>=383) { lcd.write(3); } else
if (right[band]>=255) { lcd.write(2); } else
if (right[band]>=127) { lcd.write(1); } else
if (right[band]>=0) { lcd.write(0); }
}
}
If you’ve been reading through my tutorials there isn’t anything new to worry about. And now for the demo, with sound -
That would look great on the side of a Walkman, however it’s a bit small. Let’s scale it up by using a Freetronics Dot Matrix Display - you may recall these from Clock One. For some background knowledge check the review here. Don’t forget to use a suitable power supply for the DMD – 5 V at 4 A will do nicely. The DMD contains 16 rows of 32 LEDs. This gives us twice the “resolution” to display each band level if desired. The display style is subjective, so for this example we’ll use a single column of LEDs for each frequency band, with a blank column between each one.
We use a lot of line-drawing statements to display the levels, and clear the DMD after each display. With this and the previous sketches, there could be room for efficiency – however I write these with the beginner in mind. Here’s the sketch (download):
// Example 48.3 - tronixstuff.com/tutorials > chapter 48 - 30 Jan 2013 // MSGEQ7 spectrum analyser shield with a Freetronics DMD
// for DMD #include // for DMD #include // SPI.h must be included as DMD is written by SPI (the IDE complains otherwise) #include #include "SystemFont5x7.h" // keep next two lines if you want to add some text #include "Arial_black_16.h" DMD dmd(1, 1); // creates instance of DMD to refer to in sketch
void ScanDMD() // necessary interrupt handler for refresh scanning of DMD
{
dmd.scanDisplayBySPI();
}
int strobe = 4; // strobe pins on digital 4 int res = 5; // reset pins on digital 5
int left[7]; // store band values in these arrays int right[7];
int band;
void setup()
{
// for DMD
//initialize TimerOne's interrupt/CPU usage used to scan and refresh the display
Timer1.initialize( 5000 ); //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
Timer1.attachInterrupt( ScanDMD ); //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()
dmd.clearScreen( true ); //true is normal (all pixels off), false is negative (all pixels on)
// for MSGEQ7
pinMode(res, OUTPUT); // reset
pinMode(strobe, OUTPUT); // strobe
digitalWrite(res,LOW); // reset low
digitalWrite(strobe,HIGH); //pin 5 is RESET on the shield
}
void readMSGEQ7()
// Function to read 7 band equalizers
{
digitalWrite(res, HIGH);
digitalWrite(res, LOW);
for( band = 0; band < 7; band++ )
{
digitalWrite(strobe,LOW); // strobe pin on the shield - kicks the IC up to the next band
delayMicroseconds(30); //
left[band] = analogRead(0); // store left band reading
right[band] = analogRead(1); // ... and the right
digitalWrite(strobe,HIGH);
}
}
void loop()
{
int xpos;
readMSGEQ7();
dmd.clearScreen( true );
// display values of left channel on DMD
for( band = 0; band < 7; band++ )
{
xpos = (band*2)+1;
if (left[band]>=895) { dmd.drawLine( xpos, 15, xpos, 1, GRAPHICS_NORMAL ); } else
if (left[band]>=767) { dmd.drawLine( xpos, 15, xpos, 3, GRAPHICS_NORMAL ); } else
if (left[band]>=639) { dmd.drawLine( xpos, 15, xpos, 5, GRAPHICS_NORMAL ); } else
if (left[band]>=511) { dmd.drawLine( xpos, 15, xpos, 7, GRAPHICS_NORMAL ); } else
if (left[band]>=383) { dmd.drawLine( xpos, 15, xpos, 9, GRAPHICS_NORMAL ); } else
if (left[band]>=255) { dmd.drawLine( xpos, 15, xpos, 11, GRAPHICS_NORMAL ); } else
if (left[band]>=127) { dmd.drawLine( xpos, 15, xpos, 13, GRAPHICS_NORMAL ); } else
if (left[band]>=0) { dmd.drawLine( xpos, 15, xpos, 15, GRAPHICS_NORMAL ); }
}
// display values of right channel on DMD
for( band = 0; band < 7; band++ )
{
xpos = (band*2)+18;
if (right[band]>=895) { dmd.drawLine( xpos, 15, xpos, 1, GRAPHICS_NORMAL ); } else
if (right[band]>=767) { dmd.drawLine( xpos, 15, xpos, 3, GRAPHICS_NORMAL ); } else
if (right[band]>=639) { dmd.drawLine( xpos, 15, xpos, 5, GRAPHICS_NORMAL ); } else
if (right[band]>=511) { dmd.drawLine( xpos, 15, xpos, 7, GRAPHICS_NORMAL ); } else
if (right[band]>=383) { dmd.drawLine( xpos, 15, xpos, 9, GRAPHICS_NORMAL ); } else
if (right[band]>=255) { dmd.drawLine( xpos, 15, xpos, 11, GRAPHICS_NORMAL ); } else
if (right[band]>=127) { dmd.drawLine( xpos, 15, xpos, 13, GRAPHICS_NORMAL ); } else
if (right[band]>=0) { dmd.drawLine( xpos, 15, xpos, 15, GRAPHICS_NORMAL ); }
}
}
… and here it is in action:
Conclusion
At this point you have the knowledge to use the MSGEQ7 ICs to create some interesting spectrum analysers for entertainment and visual appeal – now you just choose the type of display enjoy the results.
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 NXP SAA1064 4-digit LED display driver
This is chapter thirty-nine 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 19/01/2013
In this article we investigate controlling the NXP (formerly Philips) SAA1064 4-digit LED display driver IC with Arduino and the I2C bus interface. If you are not familiar with using the I2C bus, please read my tutorials (parts one and two) before moving on. Although the SAA1064 is not the newest on the market, it is still popular, quite inexpensive and easy to source. Furthermore as it is controlled over the I2C bus – you don’t waste any digital I/O pins on your Arduino, and you can also operate up to four SAA1064s at once (allowing 16 digits!). Finally, it has a constant-current output – keeping all the segments of your LED display at a constant brightness (which is also adjustable). So let’s get started…
Here is an example of the SAA1064 in SOIC surface mount packaging:
It measures around 15mm in length. For use in a solderless breadboard, I have soldered the IC onto a through-hole adaptor:
The SAA1064 is also available in a regular through-hole DIP package. At this point, please download the data sheet (.pdf) as you will need to refer to it during the article. Next, our LED display examples. We need common-anode displays, and for this article we use two Agilent HDSP521G two-digit modules (data sheet [.pdf]) as shown below:
For the uninitiated – a common anode display has all the segments’ anodes connected together, with the cathodes terminated separately. For example, our LED displays are wired as such:
Notice the anodes for the left digit are pin 14, and the right digit pin 13. A device that is connected to all the cathodes (e.g. our SAA1064) will control the current flow through each element – thereby turning each segment on (and controlling the brightness) or off. Our SAA1064 is known as a current-sink as the current flows through the LED, and then sinks into the IC.
Now, let’s get it connected. There is an excellent demonstration circuit on page twelve of the data sheet that we will follow for our demonstrations:
It looks pretty straight-forward, and it is. The two transistors are standard NPN-type, such as PN2222. The two transistors are used to each turn on or off a pair of digits – as the IC can only drive digits 1+3 or 2+4 together. (When presented in real life the digits are numbered 4-3-2-1). So the pairs are alternatively turned on and off at a rapid rate, which is controlled by the capacitor between pin 2 and GND. The recommended value is 2.7 nF. At the time of writing, I didn’t have that value in stock, so chose a 3.3 nF instead. However due to the tolerance of the ceramic capacitor it was actually measured to be 2.93 nF:
So close enough to 2.7 nF will be OK. The other capacitor shown between pins 12 and 13 is a standard 0.1 uF smoothing capacitor. Pin 1 on the SAA1064 is used to determine the I2C bus address – for our example we have connected it straight to GND (no resistors at all) resulting in an address of 0×70. See the bottom page five of the data sheet for other address options. Power for the circuit can be taken from your Arduino’s 5V pin – and don’t forget to connect the circuit GND to Arduino GND. You will also use 4.7k ohm pull-up resistors on the SDA and SCL lines of the I2C bus.
The last piece of the schematic puzzle is how to connect the cathodes of the LED displays to the SAA1064. Display pins 14 and 13 are the common anodes of the digits.
The cathodes for the left-hand display module:
- LED display pins 4, 16, 15, 3, 2, 1, 18 and 17 connect to SAA1064 pins 22, 21, 20, 19, 18, 17, 16 and 15 respectively (that is, LED pin 4 to IC pin 22, etc.);
- LED display pins 9, 11, 10, 8, 6, 5, 12 and 7 also connect to SAA1064 pins 22, 21, 20, 19, 18, 17, 16 and 15 respectively.
- LED display pins 4, 16, 15, 3, 2, 1, 18 and 17 connect to SAA1064 pins 3, 4, 5, 6, 7, 8, 9 and 10 respectively;
- LED display pins 9, 11, 10, 8, 6, 5, 12 and 7 also connect to SAA1064 pins 3, 4, 5, 6, 7, 8, 9 and 10 respectively.
Wire.beginTransmission(saa1064);
Wire.write(B00000000);
Wire.write(B01000111);
Wire.beginTransmission(saa1064);
Wire.write(B00000001);
The digit bytes describe which digit elements to turn on or off. The bytes are described as such: Bpgfedcba. (p is the decimal point). So if you wanted to display the number 7, you would send B00000111 – as this would turn on elements a, b and c. To add the decimal point with 7 you would send B10000111. You can also send the byte as a decimal number. So to send the digit 7 as a decimal, you would send 7 – as 00000111 in base-10 is 7. To include the decimal point, send 135 – as 100000111 in base-10 is 135. Easy! You can also create other characters such as A~F for hexadecimal. In fact let’s do that now in the following example sketch (download):/* Example 39.1 - NXP SAA1064 I2C LED Driver IC Demo I Demonstrating display of digits http://tronixstuff.wordpress.com/tutorials > chapter 39 John Boxall July 2011 | CC by-sa-nc */
#include "Wire.h" // enable I2C bus
byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
int digits[16]={63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113};
// these are the byte representations of pins required to display each digit 0~9 then A~F
void setup()
{
Wire.begin(); // start up I2C bus
delay(500);
initDisplay();
}
void initDisplay()
// turns on dynamic mode and adjusts segment current to 12mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
Wire.endTransmission();
}
void displayDigits()
// show all digits 0~9, A~F on all digits of display
{
for (int z=0; z<16; z++)
{
Wire.beginTransmission(saa1064);
Wire.write(1); // instruction byte - first digit to control is 1 (right hand side)
Wire.write(digits[z]); // digit 1 (RHS)
Wire.write(digits[z]); // digit 2
Wire.write(digits[z]); // digit 3
Wire.write(digits[z]); // digit 4 (LHS)
Wire.endTransmission();
delay(500);
}
// now repeat but with decimal point
for (int z=0; z<16; z++)
{
Wire.beginTransmission(saa1064);
Wire.write(1); // instruction byte - first digit to control is 1 (right hand side)
Wire.write(digits[z]+128); // digit 1 (RHS)
Wire.write(digits[z]+128); // digit 2
Wire.write(digits[z]+128); // digit 3
Wire.write(digits[z]+128); // digit 4 (LHS)
Wire.endTransmission();
delay(500);
}
}
void clearDisplay()
// clears all digits
{
Wire.beginTransmission(saa1064);
Wire.write(1); // instruction byte - first digit to control is 1 (right hand side)
Wire.write(0); // digit 1 (RHS)
Wire.write(0); // digit 2
Wire.write(0); // digit 3
Wire.write(0); // digit 4 (LHS)
Wire.endTransmission();
}
void loop()
{
displayDigits();
clearDisplay();
delay(1000);
}
In the function initDisplay() you can see an example of using the instruction then the control byte. In the function clearDisplay() you can see the simplest form of sending digits to the display – we send 0 for each digit to turn off all elements in each digit. The bytes that define the digits 0~9 and A~F are stored in the array digits[]. For example, the digit zero is 63 in decimal, which is B00111111 in binary – which turns on elements a,b,c,d,e and f. Finally, notice the second loop in displayDigits() – 128 is added to each digit value to turn on the decimal point. Before moving on, let’s see it in action:
Our next example revisits the instruction and control byte – we change the brightness of the digits by setting bits 4~6 in the control byte. Each level of brightness is separated into a separate function, and should be self-explanatory. Here is the sketch (download):
/* Example 39.2 - NXP SAA1064 I2C LED Driver IC Demo II Demonstrating brightness levels via adjusting segment current http://tronixstuff.wordpress.com/tutorials > chapter 39 John Boxall July 2011 | CC by-sa-nc */
#include "Wire.h" // enable I2C bus
byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
int digits[16]={63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113};
// these are the byte representations of pins required to display each digit 0~9 then A~F
void setup()
{
Wire.begin(); // start up I2C bus
delay(500);
init12ma();
}
void init3ma()
// turns on dynamic mode and adjusts segment current to 3mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B00010111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 3mA segment current
Wire.endTransmission();
}
void init6ma()
// turns on dynamic mode and adjusts segment current to 6mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B00100111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 6mA segment current
Wire.endTransmission();
}
void init9ma()
// turns on dynamic mode and adjusts segment current to 9mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B00110111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 9mA segment current
Wire.endTransmission();
}
void init12ma()
// turns on dynamic mode and adjusts segment current to 12mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
Wire.endTransmission();
}
void init18ma()
// turns on dynamic mode and adjusts segment current to 12mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B01100111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 18mA segment current
Wire.endTransmission();
}
void init21ma()
// turns on dynamic mode and adjusts segment current to 12mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B01110111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 21mA segment current
Wire.endTransmission();
}
void loop()
{
int d=250; // for delay
// first, light up all segments
Wire.beginTransmission(saa1064);
Wire.write(0); // instruction byte - Zero means the next byte is the control byte
Wire.write(B01001111); // set current and bit 3 to 1 for all segments on
Wire.endTransmission();
// now loop through the brightness levels
do
{
init3ma();
delay(d);
init6ma();
delay(d);
init9ma();
delay(d);
init12ma();
delay(d);
init18ma();
delay(d);
init21ma();
delay(d);
}
while (1>0);
}
And again, see it in action:
For our final example, there is a function displayInteger(a,b) which can be used to easily display numbers from 0~9999 on the 4-digit display. The parameter a is the number to display, and b is the leading-zero control – zero – off, one – on. The function does some maths on the integet to display and separates the digits for each column, then sends them to the SAA1064 in reverse order. By now you should be able to understand the following sketch (download):
/* Example 39.3 - NXP SAA1064 I2C LED Driver IC Demo III Displaying numbers on command http://tronixstuff.wordpress.com/tutorials > chapter 39 John Boxall July 2011 | CC by-sa-nc */
#include "Wire.h" // enable I2C bus
byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
int digits[17]={
63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113, 0};
// these are the byte representations of pins required to display each digit 0~9 then A~F, and blank digit
void setup()
{
Wire.begin(); // start up I2C bus
delay(500);
initDisplay();
}
void initDisplay()
// turns on dynamic mode and adjusts segment current to 12mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
Wire.endTransmission();
}
void clearDisplay()
{
Wire.beginTransmission(saa1064);
Wire.write(1); // start with digit 1 (right-hand side)
Wire.write(0); // blank digit 1
Wire.write(0); // blank digit 2
Wire.write(0); // blank digit 3
Wire.write(0); // blank digit 4
Wire.endTransmission();
}
void displayInteger(int num, int zero)
// displays the number 'num'
// zero = 0 - no leading zero
// zero = 1 - leading zero
{
int thousand, hundred, ten, one;
// breakdown number into columns
thousand = num/1000;
hundred = (num-(thousand*1000))/100;
ten = (num-((thousand*1000)+(hundred*100)))/10;
one = num-((thousand*1000)+(hundred*100)+(ten*10));
if (zero==1) // yes to leading zero
{
Wire.beginTransmission(saa1064);
Wire.write(1);
Wire.write(digits[one]);
Wire.write(digits[ten]);
Wire.write(digits[hundred]);
Wire.write(digits[thousand]);
Wire.endTransmission();
delay(10);
}
else
if (zero==0) // no to leading zero
{
if (thousand==0) { thousand=16; }
if (hundred==0 && num<100) { hundred=16; }
if (ten==0 && num<10) { ten=16; }
Wire.beginTransmission(saa1064);
Wire.write(1);
Wire.write(digits[one]);
Wire.write(digits[ten]);
Wire.write(digits[hundred]);
Wire.write(digits[thousand]);
Wire.endTransmission();
delay(10);
}
}
void loop()
{
for (int a=0; a<251; a++)
// display 0~250 without leading zero
{
displayInteger(a,0);
delay(20);
}
delay(1000);
clearDisplay();
delay(1000);
for (int a=0; a<1000; a++)
// display 0~999 with leading zero
{
displayInteger(a,1);
delay(5);
}
}
And the final example in action:
So there you have it – another useful IC that can be used in conjunction with our Arduino systems to make life easier and reduce the required digital output pins.
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.
Kit Review – Snootlab Mémoire SD card/RTC/prototyping shield
Hello Readers
In this article we will examine another product from a bundle sent for review by Snootlab, a Toulouse, France-based company that in their own words:
… designs and develops electronic products with an Open Hardware and Open Source approach. We are particularly specialized in the design of new shields for Arduino. The products we create are licensed under CC BY-SA v3.0 (as shown in documents associated with each of our creations). In accordance with the principles of the definition of Open Source Hardware (OSHW), we have signed it the 10th February 2011. We wish to contribute to the development of the ecosystem of “do it yourself” through original designs of products, uses and events.
Furthermore, all of their products are RoHS compliant and as part of the Open Hardware commitment, all the design files are available from the Snootlab website.
The subject of the review is the Snootlab Mémoire – an SD card data logging shield with on-board DS1307 real time clock [and matching backup battery] and prototyping area. It uses the standard SdFat library to write to normal SD memory cards formatted in FAT16 or FAT32. You can download the library from here. The real time clock IC is an easy to use I2C-interface model, and I have documented its use in great detail in this tutorial.
Once again, shield assembly is simple and quite straightforward. You can download an illustrated assembly guide from here, however it is in French. But everything you need to know is laid out on the PCB silk-screen, or the last page of the instructions. The it arrives in a reusable ESD bag:
… and all the required parts are included – including an IC socket and the RTC backup battery:
… the PCB is thick, with a very detailed silk-screen. Furthermore, it arrives with the SD card and 3.3V LDO (underneath) already pre-soldered – a nice touch:
The order of soldering the components is generally a subjective decision, and in this case I started with the resistors:
… and then worked my way out, but not fitting the battery nor IC until last. Intrestingly, the instructions require the crystal to be tacked down with some solder onto the PCB. Frankly I didn’t think it would withstand the temperature, however it did and all is well:
Which leaves us with a fully-assembled Mémoire shield ready for action:
Please note that a memory card is not included with the kit. If you are following along with your own Mémoire, the first thing to do after inserting the battery, IC and shield into your Arduino board and run some tests to ensure all is well. First thing is to test the DS1307 real-time clock IC. You can use the following sketch from chapter seven of my Arduino tutorial series: (download sketch file)
/*
Example 7.3
reading and writing to the Maxim DS1307 real time clock IC
tronixstuff.com/tutorials
based on code by Maurice Ribble
17-4-2008 - http://www.glacialwanderer.com/hobbyrobotics
*/
#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val/16*10) + (val%16) );
}
// 1) Sets the date and time on the ds1307
// 2) Starts the clock
// 3) Sets hour mode to 24 hour clock
// Assumes you're passing in valid numbers
void setDateDs1307(byte second, // 0-59
byte minute, // 0-59
byte hour, // 1-23
byte dayOfWeek, // 1-7
byte dayOfMonth, // 1-28/29/30/31
byte month, // 1-12
byte year) // 0-99
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0);
Wire.send(decToBcd(second)); // 0 to bit 7 starts the clock
Wire.send(decToBcd(minute));
Wire.send(decToBcd(hour));
Wire.send(decToBcd(dayOfWeek));
Wire.send(decToBcd(dayOfMonth));
Wire.send(decToBcd(month));
Wire.send(decToBcd(year));
Wire.send(00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
Wire.endTransmission();
}
// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
// Reset the register pointer
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
// A few of these need masks because certain bits are control bits
*second = bcdToDec(Wire.receive() & 0x7f);
*minute = bcdToDec(Wire.receive());
*hour = bcdToDec(Wire.receive() & 0x3f); // Need to change this if 12 hour am/pm
*dayOfWeek = bcdToDec(Wire.receive());
*dayOfMonth = bcdToDec(Wire.receive());
*month = bcdToDec(Wire.receive());
*year = bcdToDec(Wire.receive());
}
void setup()
{
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
Wire.begin();
Serial.begin(9600);
// Change these values to what you want to set your clock to.
// You probably only want to set your clock once and then remove
// the setDateDs1307 call.
second = 0;
minute = 33;
hour = 22;
dayOfWeek = 2;
dayOfMonth = 4;
month = 07;
year = 11;
setDateDs1307(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}
void loop()
{
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
Serial.print(hour, DEC);// convert the byte variable to a decimal number when being displayed
Serial.print(":");
if (minute<10)
{
Serial.print("0");
}
Serial.print(minute, DEC);
Serial.print(":");
if (second<10)
{
Serial.print("0");
}
Serial.print(second, DEC);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/");
Serial.print(year, DEC);
Serial.print(" Day of week:");
switch(dayOfWeek){
case 1:
Serial.println("Sunday");
break;
case 2:
Serial.println("Monday");
break;
case 3:
Serial.println("Tuesday");
break;
case 4:
Serial.println("Wednesday");
break;
case 5:
Serial.println("Thursday");
break;
case 6:
Serial.println("Friday");
break;
case 7:
Serial.println("Saturday");
break;
}
// Serial.println(dayOfWeek, DEC);
delay(1000);
}
If you are unsure about using I2C, please review my tutorial which can be found here.
Don’t forget to update the time and date data in void setup(), and also comment out the setDateDS1307() function and upload the sketch a second time. The sketch output will be found on the serial monitor box – such as:
Those of you familiar with the DS1307 RTC IC know that it can generate a nice 1 Hz pulse. To take advantage of this the SQW pin has an access hole on the PCB, beetween R10 and pin 8 of the IC:
For instruction on how to activate the SQW output, please visit the last section of this tutorial.
The next test is the SD card section of the shield. If you have not already done so, download and install the SdFat libary. Then, in the Arduino IDE, select File > Examples > SdFat > SdFatInfo. Insert the formatted (FAT16/32) SD card into the shield, upload the sketch, then open the serial monitor. You should be presented with something like this:
As you can see the sketch has returned various data about the SD card. Finally, let’s log some data. You can deconstruct the excellent example that comes with the SdFat library titled SdFatAnalogLogger (select File > Examples > SdFat > SdFatAnalogLogger). Using the functions:
file.print();
file.println();
you can “write” to the SD card in the same way as you would the serial output (that is, the serial monitor).
If you have reached this far without any errors – Congratulations! You’re ready to log. If not, remove the battery, SD card and IC from your shield (you used the IC socket, didn’t you?). Check the polarised components are in correctly, double-check your soldering and then reinsert the IC, shield and battery and try again. If that fails, support is available on the Snootlab website, and there is also a customer forum in French (use Google Translate). However as noted previously the team at Snootlab converse in excellent English and have been easy to contact via email if you have any questions. Stay tuned for the final Snootlab product review.
Snootlab products including the Snootlab Mémoire are available directly from their website.
High-resolution images available on flickr.
As always, thank you for reading and I look forward to your comments and so on. Furthermore, don’t be shy in pointing out errors or places that could use improvement. Please subscribe using one of the methods at the top-right of this web page to receive updates on new posts, follow on twitter, facebook, or join our Google Group.
[Disclaimer - the products reviewed in this article are promotional considerations made available by Snootlab]
Otherwise, have fun, be good to each other – and make something!
Learn to solder with David L. Jones!
Hello Readers
How is your soldering? Have you always wanted to improve your soldering skills, or never heated an iron in your life and didn’t know where to start? No matter your level of skill you could do a lot worse than review the following video blogs in this article by David L. Jones.
Who?
[David] shares some of his 20 years experience in the electronics design industry in his unique non-scripted naturally overly enthusiastic and passionate style.
Bullsh!t and political correctness don’t get a look-in.Dave started out in hobby electronics over 30 years ago and since then has worked in such diverse areas as design engineering, production engineering, test engineering, electro-mechanical engineering, that wacky ISO quality stuff, field service, concept design, underwater acoustics, ceramic sensors, military sonar systems, red tape, endless paperwork trails, environmental testing, embedded firmware and software application design, PCB design (he’s CID certified), power distribution systems, ultra low noise and low power design, high speed digital design, telemetry systems, and too much other stuff he usually doesn’t talk about.
He has been published in various magazines including: Electronic Today International, Electronics Australia, Silicon Chip, Elektor, Everyday Practical Electronics (EPE), Make, and ReNew.
Few people know Dave is also a world renowned expert and author on Internet Dating, a qualified fitness instructor, geocacher, canyoner, and environmentalist.
Regular readers of this website would know that I rarely publish outside material – however the depth and quality of the tutorials make them a must-see for beginners and experienced people alike. Furthermore, if you have the bandwidth they can be viewed in 1080p. And as a fellow Australian I’m proud to support Dave and his efforts. So I hope you can view, enjoy and possibly learn from the following videos:
The first covers the variety of tools you would use:
And the second covers through-hole PCB soldering:
The third covers surface-mount soldering:
Finally, watch the procedure for soldering a tiny SMD IC using the ‘dead bug’ method:
And for something completely different:
If you enjoyed those videos then don’t forget to check out what’s new on Dave’s eevblog website and forum. Videos shown are (C) David L. Jones 2011 and embedded with permission.
As always, thank you for reading and I look forward to your comments and so on. Furthermore, don’t be shy in pointing out errors or places that could use improvement. Please subscribe using one of the methods at the top-right of this web page to receive updates on new posts, follow on twitter, facebook, or join our Google Group.
[Disclaimer - I really enjoy the content at eevblog.com]
Otherwise, have fun, be good to each other – and make something!
Review: The Gravitech Arduino Nano family
Hello Readers
In this article we will examine a variety of products received for review from Gravitech in the United States – the company that designed and build the Arduino Nano. We have a Nano and some very interesting additional modules to have a look at.
So let’s start out review with the Arduino Nano. What is a Nano? A very, very small version of our Arduino Duemilanove boards. It contains the same microcontroller (ATmega328) but in SMD form; has all the I/O pins (plus two extra analogue inputs); and still has a USB interface via the FT232 chip. But more on that later. Nanos arrive in reusable ESD packaging which is useful for storage when not in use:
Patriotic Americans should note that the Nano line is made in the USA. Furthermore, here is a video clip of Nanos being made:
For those who were unsure about the size of the Nano, consider the following images:
You can easily see all the pin labels and compare them to your Duemilanove or Uno board. There is also a tiny reset button, the usual LEDs, and the in circuit software programmer pins. So you don’t miss out on anything by going to a Nano. When you flip the board over, the rest of the circuitry is revealed, including the FTDI USB>serial converter IC:
Those of you familiar with Arduino systems should immediately recognise the benefit of the Nano – especially for short-run prototype production. The reduction in size really is quite large. In the following image, I have traced the outline of an Arduino Uno and placed the Nano inside for comparison:
So tiny… the board measures 43.1mm (1.7″) by 17.8mm (0.7″). The pins on this example were pre-soldered – and are spaced at standard 2.54mm (0.1″) intervals – perfect for breadboarding or designing into your own PCB - however you can purchase a Nano without the pins to suit your own mounting purposes. The Nano meets all the specifications of the standard Arduino Duemilanove-style boards, except naturally the physical dimensions.
Power can be supplied to the Nano via the USB cable; feeding 5V directly into the 5V pin, or 7~12 (20 max, not recommended) into the Vin pin. You can only draw 3.3V at up to 50 mA when the Nano is running on USB power, as the 3.3V is sourced from the FTDI USB>serial IC. And the digital I/O pins still allow a current draw up to 40 mA each. From a software perspective you will not have any problems, as the Nano falls under the same board classification as the (for example) Arduino Duemilanove:
Therefore one could take advantage of all the Arduino fun and games – except for the full-size shields. But as you will read soon, Gravitech have got us covered on that front. If the Arduino system is new to you, why not consider following my series of tutorials? They can be found here. In the meanwhile, to put the size into perspective – here is a short video of a Nano blinking some LEDs!
Now back to business. As the Nano does not use standard Arduino shields, the team at Gravitech have got us covered with a range of equivalent shields to enable all sorts of activities. The first of this is their Ethernet and microSD card add-on module:
and the underside:
Again this is designed for breadboarding, or you could most likely remove the pins if necessary. The microSD socket is connected as expected via the SPI bus, and is fully compatible with the default Arduino SD library. As shown in the following image the Nano can slot directly into the ethernet add-in module:
The Ethernet board requires an external power supply, from 7 to 12 volts DC. The controller chip is the usual Wiznet 5100 model, and therefore the Ethernet board is fully compatible with the default Ethernet Arduino library. We tested it with the example web server sketch provided with the Arduino IDE and it all just worked.
The next add-on module to examine is the 2MOTOR board:
Using this module allows control of two DC motors with up to two amps of current each via pulse-width modulation. Furthermore, there is a current feedback circuit for each motor so you measure the motor load and adjust power output – interesting. So a motorised device could sense when it was working too hard and ease back a little (like me on a Saturday). All this is made possible by the use of the common L298 dual full-bridge motor driver IC. This is quite a common motor driver IC and is easy to implement in your sketches. The use of this module and the Nano will help reduce the size of any robotics or motorised project. Stay tuned for use of this board in future articles.
Next in this veritable cornucopia of add-on modules is the USBHOST board:
turning it over …
Using the Maxim MAX3421E host controller IC you can interface with all sorts of devices via USB, as well as work with the new Android ADK. The module will require an external power supply of between 7 and 12 volts DC, with enough current to deal with the board, a Nano and the USB device under control – one amp should be more than sufficient. I will be honest and note that USB and Arduino is completely new to me, however it is somewhat fascinating and I intend to write more about using this module in the near future. In the meanwhile, many examples can be found here.
For a change of scene there is also a group of Xbee wireless communication modules, starting with the Xbee add-on module:
The Xbee itself is not included, only shown for a size comparison. Turning the module over:
It is nice to see a clearly-labelled silk screen on the PCB. If you are unfamiliar with using the Xbee wireless modules for data communication, you may find my introductory tutorial of interest. Furthermore, all of the Gravitech Nano modules are fully software compatible with my tutorial examples, so getting started will be a breeze. Naturally Gravitech also produce an Xbee USB interface board, to enable PC communication over your wireless modules:
Again, note that the Xbee itself is not included, however they can be supplied by Gravitech. Turning the board over reveals another highly-detailed silk screen:
All of the Gravitech Xbee modules support both series 1.0 and 2.5 Xbees, in both standard and professional variants. The USB module also supports the X-CTU configuration software from Digi.
Finally – leaving possibly the most interesting part until last, we have the MP3 Player add-on board:
and on the B-side:
The MP3 board is designed around the VS1053B MP3 decoder IC. It can also decode Ogg Vorbis, AAC, WMA and MID files. There is a 3.5mm stereo output socket to connect headphones and so on. As expected, the microSD card runs from the SPI pins, however SS is pin 4. Although it may be tempting to use this to make a home-brew MP3 player, other uses could include: recorded voice messages for PA systems such as fire alarm notices, adding sound effects to various projects or amusement machines, or whatever else you can come up with.
Update – We have examined the MP3 board in more detail with a beginner’s tutorial.
The Arduino Nano and related boards really are tiny, fully compatible with their larger brethren, and will prove very useful. Although this article was an introductory review, stay tuned for further projects and articles that will make use of the Nano and other boards. If you have any questions or enquiries please direct them to Gravitech via their contact page. Gravitech products including the Arduino Nano family are available directly from their website or these distributors.
As always, thank you for reading and I look forward to your comments and so on. Furthermore, don’t be shy in pointing out errors or places that could use improvement. Please subscribe using one of the methods at the top-right of this web page to receive updates on new posts, follow on twitter, facebook, or join our Google Group.
[Disclaimer - the products reviewed in this article are promotional considerations made available by Gravitech]
High resolution photos are available on flickr.
Otherwise, have fun, be good to each other – and make something!
Kit Reviews: Snootlab Power ScrewShield and I2C Power Protoshield
Hello Readers
In this article we will examine the first two products from a bundle sent for review by Snootlab, a Toulouse, France-based company that in their own words:
… designs and develops electronic products with an Open Hardware and Open Source approach. We are particularly specialized in the design of new shields for Arduino. The products we create are licensed under CC BY-SA v3.0 (as shown in documents associated with each of our creations). In accordance with the principles of the definition of Open Source Hardware (OSHW), we have signed it the 10th February 2011. We wish to contribute to the development of the ecosystem of “do it yourself” through original designs of products, uses and events.
Furthermore, all of their products are RoHS compliant and as part of the Open Hardware commitment, all the design files are available from the Snootlab website.
First, let’s examine the Power Screwshield kit. This is a feature-laden prototyping shield suitable for Arduino Uno and compatible series boards. It can be used with the Mega, however not all of the I/O pins will be available.
Apart from obvious use as a prototyping shield, there are also three other useful features:
- space for a 16-pin SOIC SMD part in the prototyping area;
- a full line of screw terminals that connect to all the shield pin connections (in a similar way to the Wingshield Screwshield);
- and a socket to allow power to be sourced from a standard computer ATX power supply, which brings 5V and 12V DC to the shield. I have never seen this implemented on a shield in the past – a very novel and useful idea.

… which contains all the necessary parts:
… and a very high quality PCB:
The PCB thickness is over 1mm, and as you can see from the image above the silk-screening describes all the areas of the PCB in a detailed manner. Note that this shield is much larger than a standard Arduino shield – this becomes obvious when compared with a standard prototyping shield:
Assembly was very smooth and quick. There are a couple of things to watch out for, for example you need to slide the terminal blocks together so that they are flush on the sides, such as:
… if you want to enable the 12V DC rail from the ATX power lead, short out the jumper SJ1 with a blob of solder:
… when soldering the PC power connector, be sure to make the clamp bracket flush with the socket, for example:
… and finally, to enable use of the shield’s LED, you need to cut the track in this area on the underside of the PCB:
Although at first the introduction of another Arduino prototyping shield may not have seemed that interesting – this version from Snootlab really goes all out to cover almost every possible need in a shield all at the same time. Sure, it is a lot larger – but none of the board space is wasted – and those terminal blocks would be very hand for making some more permanent-style prototypes with lots of external wiring. And the ability to accept power from a PC ATX-style power supply unit is certainly original and possibly very useful depending on your application. So if you need to create something that needs a lot of power, a lot of prototyping space, and a lot of wiring – this is the protoshield for you.
For the second half of the review we have the Snootlab I2C Power Protoshield. This is another example of an Arduino prototyping shield with some interesting twists. Apart from employing the same PC power connector as used with the Power ScrewShield, this shield is designed for hard-core I2C-bus enthusiasts. (What’s I2C? Check my tutorials). This is due to the 10-pin HE connector on the edge of the board – it contains pins for SCL, SDA, 3.3V, 5V and GND. With this you could use you own cable connections to daisy-chain other devices communicating via the I2C bus. Again, the shield is a kit and assembly was simple.
Like other Snootlab products, the kit arrives in a reusable ESD bag:
… with a high-quality thick PCB that has a very detailed silk-screen layer:
… and all the required parts are included:
When soldering in the shield connectors, using another shield as a jig can save time:
And we’re finished:
One could also mount a small solderless breadboad on the I2C Power Protoshield:
One great feature is the inclusion of an NCP1117DT33 3.3V 1A voltage regulator. Using this you can source 3.3 volts at up to one amp of current (only) when using the PC power supply connection. This is a great idea, as in the past it can be too easy to accidentally burn out the FTDI chip on an Arduino Duemilanove by drawing too much current from the 3.3V pin. The use of the external 3.3V supply is controlled by a jumper on the header pins here:
Finally, in the image above you can see the area for external I2C pull-up resistors. Generally with our Arduino the internal pull-up resistors in the microcontroller are adequate, however with many I2C devices in use (e.g. eight 24LC512 EEPROMS!) external pull-ups are required.
After examining the two shields I am impressed with the quality of the components and PCBs, as well as the interesting features described in the review. Theyare certainly unique and very much useful if required, especially the PC power supply connections. Support is available on the Snootlab website, and there is also a customer forum in French (use Google Translate). However the people at Snootlab converse in excellent English and have been easy to contact via email if you have any questions. Stay tuned for more interesting Snootlab product reviews.
Snootlab products including the I2C Power Protoshield and the Power ScrewShield are available directly from their website.
As always, thank you for reading and I look forward to your comments and so on. Furthermore, don’t be shy in pointing out errors or places that could use improvement. Please subscribe using one of the methods at the top-right of this web page to receive updates on new posts, follow on twitter, facebook, or join our Google Group.
[Disclaimer - the products reviewed in this article are promotional considerations made available by Snootlab]
Otherwise, have fun, be good to each other – and make something!
Project – Single button combination lock
Time for something different - a single button combination lock. Allow me to explain…
Updated 18/03/2013
Normally a combination lock would require the entry of a series of unique numbers in order to unlock something or start an action. For example:
A more contemporary type of lock could be controlled electronically, for example by a keypad where the user enters a series of digits to cause something to happen. Such as the keypad on this dodgy $30 safe from Officeworks:
As you can see there is a button for each digit. You would think that this would be a good idea – however people can watch you enter the digits, or users can be silly enough to write down the combination somewhere. In some cases the more cunning monkeys have even placed cameras that can observe keypads to record people entering the combination. There must be a better way. Possibly! However in the meanwhile you can consider my idea instead – just have one button. Only one button – and the combination is made up of the time that elapses between presses of the button. There are many uses for such an odd lock:
- A type of combination lock that controls an electric door strike, or activates a device of some sort;
- A way of testing mind-hand coordination for skill, or the base of a painfully frustrating game;
- Perhaps an interlock on motor vehicle to prevent drink driving. After a few drinks there’s no way you could get the timing right. Then again, after a double espresso or two you might have problems as well.
We measure the duration of time between each press of the button (in this case – delay 1~4). These delay times are then compared against values stored in the program that controls the lock. It is also prudent to allow for some tolerance in the user’s press delay – say plus or minus ten to fifteen percent. We are not concerned with the duration of each button press, however it is certainly feasible.
To create this piece of hardware is quite easy, and once again we will use the Arduino way of doing things. For prototyping and experimenting it is simple enough to create with a typical board such as a Uno or Eleven and a solderless breadboard – however to create a final product you could minimise it by using a bare-bones solution (as described here). Now let’s get started…
For demonstration purposes we have a normally-open button connected to digital pin 2 on our Arduino-compatible board using the 10k ohm pull down resistor as such:
The next thing to do is determine our delay time values. Our example will use five presses, so we measure four delays. With the following sketch, you can generate the delay data by pushing the button yourself – the sketch will return the delay times on the serial monitor (download).
/*
Single-button combination lock
Delay Time data generation sketch - John Boxall June 2011
http://tronixstuff.wordpress.com/projects | CC by-sa-nc
*/
int count=0;
unsigned long a[]={0,0,0,0,0,0};
unsigned long del[]={0,0,0,0,0};
void setup()
{
pinMode(2, INPUT);
Serial.begin(115200);
Serial.println("Ready...");
}
void dataCapture()
{
a[count]=millis();
delay(500); // for button debounce, your value may differ
count++;
}
void displayData()
{
// calcualate delays between keypresses
del[0]=a[1]-a[0];
del[1]=a[2]-a[1];
del[2]=a[3]-a[2];
del[3]=a[4]-a[3];
// display delay data for use in combination lock sketch
for (int a=0; a<4; a++)
{
Serial.println();
Serial.print("Delay ");
Serial.print(a);
Serial.print(" use ");
Serial.print(del[a]);
Serial.println(" ms");
}
Serial.println("- - - - - - - - -");
count=0;
}
void loop()
{
if (digitalRead(2)==HIGH)
{
dataCapture();
}
if (count>4)
{
displayData();
}
}
So what’s going on the this sketch? Each time the button is pressed a reading of millis() is taken and stored in an array. [More on millis() in the tutorial]. Once the button has been pressed five times, the difference in time between each press is calculated and stored in the array del[]. Note the use of a 500 ms delay in the function dataCapture(), this is to prevent the button bouncing and will need to be altered to suit your particular button. Finally the delay data is then displayed on the serial monitor. For example:
The example was an attempt to count one second between each press. This example also illustrates the need to incorporate some tolerance in the actual lock sketch. With a tolerance of +/- 10% and delay values of one second, the lock would activate. With 5% – no. Etcetera.
Now for the lock sketch. Again it measures the millis() value on each button press and after five presses calculates the duration between each press. Finally in the function checkCombination() the durations are compared against the stored delay values (generated using the first sketch) which are stored in the array del[]. In our example lock sketch we have values of one second between each button press. The tolerance is stored as a decimal fraction in the variable tolerance; for example to have a tolerance of ten percent, use 0.1.
/*
Single-button combination lock - John Boxall June 2011
http://tronixstuff.wordpress.com/projects | CC by-sa-nc
*/
int count=0;
unsigned long a[]={0,0,0,0,0};
unsigned long b[]={0,0,0,0,0};
unsigned long del[]={1000,1000,1000,1000}; // these are the values obtained from running combilock1.pde
// delay between each button press is 1000 ms
float tolerance=.2;
void setup()
{
pinMode(2, INPUT);
Serial.begin(115200); // for testing purposes
Serial.println("Start"); // for testing purposes
}
void dataCapture()
{
a[count]=millis();
delay(500); // for button debounce, your value may differ
count++;
}
void checkCombination()
{
int compare=0;
count=0;
// calcualate delays between keypresses
b[0]=a[1]-a[0];
b[1]=a[2]-a[1];
b[2]=a[3]-a[2];
b[3]=a[4]-a[3];
// compare the button delay values
if (b[0]<=(del[0]*(1+tolerance)) && b[0]>=(del[0]*(1-tolerance))) { compare++; }
if (b[1]<=(del[1]*(1+tolerance)) && b[1]>=(del[1]*(1-tolerance))) { compare++; }
if (b[2]<=(del[2]*(1+tolerance)) && b[2]>=(del[2]*(1-tolerance))) { compare++; }
if (b[3]<=(del[3]*(1+tolerance)) && b[3]>=(del[3]*(1-tolerance))) { compare++; }
if (compare==4) { success(); }
if (compare!=4) { failure(); }
}
void success()
{
// contains code to run when combination successfully entered
Serial.println("Success!");
delay(5000);
Serial.println("- - - - -");
}
void failure()
{
// contains code to run when combination unsuccessfully entered
Serial.println("Failure!");
delay(5000);
Serial.println("- - - - -");
}
void loop()
{
if (digitalRead(2)==HIGH)
{
dataCapture();
}
if (count>4)
{
checkCombination();
}
}
When choosing your time delays, ensure they are larger than the value used for button debounce (the delay() function call) in the dataCapture() function. Notice the two functions success() and failure() – these will contain the results of what happens when the user successfully enters the combination or does not.
For a demonstration of the final product, I have connected an LCD to display the outcomes of the entry attempts. You can download the sketch from here. The key used in this example is 1,2,3,4 seconds:
Although there are four buttons on the board used in the video, only one is used. Well I hope someone out there found this interesting or slightly useful…
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 timing methods with millis()
This is chapter thirty-seven of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – in what feels like an endless 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/2013]
In this article we introduce the millis(); function and put it to use to create various timing examples.
Millis? Nothing to do with lip-syncers… hopefully you recognised milli as being the numerical prefix for one-thousandths; that is multiplying a unit of measure by 0.001 (or ten to the power of negative 3). Interestingly our Arduino systems will count the number of milliseconds (thousands of a second) from the start of a sketch running until the count reaches the maximum number capable of being stored in the variable type unsigned long (a 32-bit [four byte] integer – that ranges from zero to (2^32)-1.
(2^32)-1, or 4294967295 milliseconds converts to 49.71027-odd days. The counter resets when the Arduino is reset, it reaches the maximum value or a new sketch is uploaded. To get the value of the counter at a particular juncture, just call the function – for example:
start=millis();
Where start is an unsigned long variable. Here is a very simple example (download sketch) to show you millis() in action:
/*
Example 37.1 - millis() demonstration
http://tronixstuff.wordpress.com/tutorials > chapter 37
John Boxall | CC by-sa-nc
*/
unsigned long start, finished, elapsed;
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.println("Start...");
start=millis();
delay(1000);
finished=millis();
Serial.println("Finished");
elapsed=finished-start;
Serial.print(elapsed);
Serial.println(" milliseconds elapsed");
Serial.println();
delay(500);
}
The sketch stores the current millis count in start, then waits one second, then stores the value of millis again in finished. Finally it calculates the elapsed time of the delay. In the following screen dump of the serial monitor, you can see that the duration was not always exactly 1000 milliseconds:
To put it simply, the millis function makes use of an internal counter within the ATmega microcontroller at the heart of your Arduino. This counter increments every clock cycle – which happens (in standard Arduino and compatibles) at a clock speed of 16 Mhz. This speed is controlled by the crystal on the Arduino board (the silver thing with T16.000 stamped on it):
Crystal accuracy can vary depending on external temperature, and the tolerance of the crystal itself. This in turn will affect the accuracy of your millis result. Anecdotal experience has reported the drift in timing accuracy can be around three or four seconds per twenty-four hour period. If you are using a board or your own version that is using a ceramic resonator instead of a crystal, note that they are not as accurate and will introduce the possibility of higher drift levels. If you need a much higher level of timing accuracy, consider specific timer ICs such as the Maxim DS3232.
Now we can make use of the millis for various timing functions. As demonstrated in the previous example sketch, we can calculate elapsed time. To take this idea forward, let’s make a simple stopwatch. Doing so can be as simple or as complex as necessary, but for this case we will veer towards simple. On the hardware perspective, we will have two buttons – Start and Stop - with the 10k ohm pull-down resistors connected to digital pins 2 and 3 respectively.
When the user presses start the sketch will note the value for millis – then after stop is pressed, the sketch will again note the value for millis, calculate and display the elapsed time. The user can then press start to repeat the process, or stop for updated data. Here is the sketch (download):
/*
Example 37.2 – Super-basic stopwatch using millis();
http://tronixstuff.wordpress.com/tutorials > chapter 37
John Boxall | CC by-sa-nc
*/
unsigned long start, finished, elapsed;
void setup()
{
Serial.begin(9600);
pinMode(2, INPUT); // start button
pinMode(3, INPUT); // stop button
Serial.println("Press 1 for Start/reset, 2 for elapsed time");
}
void displayResult()
{
float h,m,s,ms;
unsigned long over;
elapsed=finished-start;
h=int(elapsed/3600000);
over=elapsed%3600000;
m=int(over/60000);
over=over%60000;
s=int(over/1000);
ms=over%1000;
Serial.print("Raw elapsed time: ");
Serial.println(elapsed);
Serial.print("Elapsed time: ");
Serial.print(h,0);
Serial.print("h ");
Serial.print(m,0);
Serial.print("m ");
Serial.print(s,0);
Serial.print("s ");
Serial.print(ms,0);
Serial.println("ms");
Serial.println();
}
void loop()
{
if (digitalRead(2)==HIGH)
{
start=millis();
delay(200); // for debounce
Serial.println("Started...");
}
if (digitalRead(3)==HIGH)
{
finished=millis();
delay(200); // for debounce
displayResult();
}
}
The calls to delay() are used to debounce the switches – these are optional and their use will depend on your hardware. Below is an example of the sketch’s serial monitor output – the stopwatch has started, and then button two pressed six times across periods of time:
If you had a sensor at the start and end of a fixed distance, speed could be calculated: speed = distance ÷ time.
You can also make a speedometer for a wheeled form of motion, for example a bicycle. At the present time I do not have a bicycle to mess about with, however we can describe the process to do so – it is quite simple. (Disclaimer – do so at your own risk etc.) First of all, let’s review the necessary maths. You will need to know the circumference of the wheel. Hardware – you will need a sensor. For example – a reed switch and magnet. Consider the reed switch to be a normally-open button, and connect as usual with a 10k ohm pull-down resistor. Others may use a hall-effect sensor – each to their own). Remember from maths class:
To calculate the circumference – use the formula:
circumference = 2πr
where r is the radius of the circle. Now that you have the wheel circumference, this value can be considered as our ‘fixed distance’, and therefore the speed can be calculated by measuring the elapsed time between of a full rotation.
Your sensor – once fitted – should act in the same method as a normally-open button that is pushed every rotation. Our sketch will measure the time elapsed between every pulse from the sensor. To do this, our example will have the sensor output connected to digital pin 2 – as it will trigger an interrupt to calculate the speed. (Interrupts? See chapter three). The sketch will otherwise be displaying the speed on a normal I2C-interface LCD module. The I2C interface is suggested as this requires only 4 wires from the Arduino board to the LCD – the less wires the better.
Here is the sketch for your perusal (download):
/*
Example 37.3 – Basic speedometer using millis();
http://tronixstuff.wordpress.com/tutorials > chapter 37
John Boxall | CC by-sa-nc
*/
#include "Wire.h" // for I2C bus LCD
#include "LiquidCrystal_I2C.h" // for I2C bus LCD module - http://bit.ly/m7K5wt
LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display
float start, finished;
float elapsed, time;
float circMetric=1.2; // wheel circumference relative to sensor position (in meters)
float circImperial; // using 1 kilometer = 0.621371192 miles
float speedk, speedm; // holds calculated speed vales in metric and imperial
void setup()
{
attachInterrupt(0, speedCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)
start=millis();
// setup LCD
lcd.init(); // initialize the lcd
lcd.backlight(); // turn on LCD backlight
lcd.clear();
lcd.println(" Wear a helmet! ");
delay(3000);
lcd.clear();
Serial.begin(115200);
circImperial=circMetric*.62137; // convert metric to imperial for MPH calculations
}
void speedCalc()
{
elapsed=millis()-start;
start=millis();
speedk=(3600*circMetric)/elapsed; // km/h
speedm=(3600*circImperial)/elapsed; // Miles per hour
}
void loop()
{
lcd.setCursor(0,0);
lcd.print(int(speedk));
lcd.print(" km/h ");
lcd.print(int(speedm));
lcd.print(" MPH ");
lcd.setCursor(0,1);
lcd.print(int(elapsed));
lcd.print(" ms/rev ");
delay(1000); // adjust for personal preference to minimise flicker
}
There isn’t that much going on – every time the wheel completes one revolution the signal from the sensor will go from low to high – triggering an interrupt which calls the function speedCalc(). This takes a reading of millis() and then calculates the difference between the current reading and the previous reading – this value becomes the time to cover the distance (which is the circumference of the wheel relative to the sensor – stored in
float circMetric=1.2;
and is measured in metres). It finally calculates the speed in km/h and MPH. Between interrupts the sketch displays the updated speed data on the LCD as well as the raw time value for each revolution for curiosity’s sake. In real life I don’t think anyone would mount an LCD on a bicycle, perhaps an LED display would be more relevant.
In the meanwhile, you can see how this example works in the following short video clip. Instead of a bike wheel and reed switch/magnet combination, I have connected the square-wave output from a function generator to the interrupt pin to simulate the pulses from the sensor, so you can get an idea of how it works:
That just about sums up the use of millis() for the time being. There is also the micros(); function which counts microseconds.
So there you have it – another practical function that can allow more problems to be solved via the world of Arduino. As always, now it is up to you and your imagination to find something to control or get up to other shenanigans.
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.
















































































