t r o n i x s t u f f

fun and learning with electronics

Tutorial – Arduino and PCF8591 ADC DAC IC

Learn how to use the NXP PCF 8591 8-bit A/D and D/A IC with Arduino in chapter fifty-two of my Arduino Tutorials. The first chapter is here, the complete series is detailed here.

Updated 17/06/2013

Introduction

Have you ever wanted more analogue input pins on your Arduino project, but not wanted to fork out for a Mega? Or would you like to generate analogue signals? Then check out the subject of our tutorial – the NXP PCF8591 IC. It solves both these problems as it has a single DAC (digital to analogue) converter as well as four ADCs (analogue to digital converters) – all accessible via the I2C bus. If the I2C bus is new to you, please familiarise yourself with the readings here before moving forward.

The PCF8591 is available in DIP form, which makes it easy to experiment with:

pcf8591

You can get them from the usual retailers. Before moving on, download the data sheet. The PCF8591 can operate on both 5V and 3.3V so if you’re using an Arduino Due, Raspberry Pi or other 3.3 V development board you’re fine. Now we’ll first explain the DAC, then the ADCs.

Using the DAC (digital-to-analogue converter)

The DAC on the PCF8591 has a resolution of 8-bits – so it can generate a theoretical signal of between zero volts and the reference voltage (Vref) in 255 steps. For demonstration purposes we’ll use a Vref of 5V, and you can use a lower Vref such as 3.3V or whatever you wish the maximum value to be … however it must be less than the supply voltage. Note that when there is a load on the analogue output (a real-world situation), the maximum output voltage will drop – the data sheet (which you downloaded) shows a 10% drop for a 10kΩ load. Now for our demonstration circuit:

pcf8591basic_schem

Note the use of 10kΩ pull-up resistors on the I2C bus, and the 10μF capacitor between 5V and GND. The I2C bus address is set by a combination of pins A0~A2, and with them all to GND the address is 0×90. The analogue output can be taken from pin 15 (and there’s a seperate analogue GND on pin 13. Also, connect pin 13 to GND, and circuit GND to Arduino GND.

To control the DAC we need to send two bytes of data. The first is the control byte, which simply activates the DAC and is 1000000 (or 0×40) and the next byte is the value between 0 and 255 (the output level). This is demonstrated in the following sketch (download):

// Example 52.1 PCF8591 DAC demo
// http://tronixstuff.com/tutorials Chapter 52
// John Boxall June 2013
#include 
#define PCF8591 (0x90 >> 1) // I2C bus address
void setup()
{
 Wire.begin();
}
void loop()
{ 
 for (int i=0; i<256; i++)
 {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x40); // control byte - turn on DAC (binary 1000000)
 Wire.write(i); // value to send to DAC
 Wire.endTransmission(); // end tranmission
 }

 for (int i=255; i>=0; --i)
 {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x40); // control byte - turn on DAC (binary 1000000)
 Wire.write(i); // value to send to DAC
 Wire.endTransmission(); // end tranmission
 }
}

Did you notice the bit shift of the bus address in the #define statement? Arduino sends 7-bit addresses but the PCF8591 wants an 8-bit, so we shift the byte over by one bit. 

The results of the sketch are shown below, we’ve connected the Vref to 5V and the oscilloscope probe and GND to the analogue output and GND respectively (click image to enlarge):

triangle

If you like curves you can generate sine waves with the sketch below. It uses a lookup table in an array which contains the necessary pre-calculated data points (download):

// Example 52.2 PCF8591 DAC demo - sine wave
// http://tronixstuff.com/tutorials Chapter 52
// John Boxall June 2013
#include 
#define PCF8591 (0x90 >> 1) // I2C bus address
uint8_t sine_wave[256] = {
 0x80, 0x83, 0x86, 0x89, 0x8C, 0x90, 0x93, 0x96,
 0x99, 0x9C, 0x9F, 0xA2, 0xA5, 0xA8, 0xAB, 0xAE,
 0xB1, 0xB3, 0xB6, 0xB9, 0xBC, 0xBF, 0xC1, 0xC4,
 0xC7, 0xC9, 0xCC, 0xCE, 0xD1, 0xD3, 0xD5, 0xD8,
 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6, 0xE8,
 0xEA, 0xEB, 0xED, 0xEF, 0xF0, 0xF1, 0xF3, 0xF4,
 0xF5, 0xF6, 0xF8, 0xF9, 0xFA, 0xFA, 0xFB, 0xFC,
 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF,
 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFD,
 0xFD, 0xFC, 0xFB, 0xFA, 0xFA, 0xF9, 0xF8, 0xF6,
 0xF5, 0xF4, 0xF3, 0xF1, 0xF0, 0xEF, 0xED, 0xEB,
 0xEA, 0xE8, 0xE6, 0xE4, 0xE2, 0xE0, 0xDE, 0xDC,
 0xDA, 0xD8, 0xD5, 0xD3, 0xD1, 0xCE, 0xCC, 0xC9,
 0xC7, 0xC4, 0xC1, 0xBF, 0xBC, 0xB9, 0xB6, 0xB3,
 0xB1, 0xAE, 0xAB, 0xA8, 0xA5, 0xA2, 0x9F, 0x9C,
 0x99, 0x96, 0x93, 0x90, 0x8C, 0x89, 0x86, 0x83,
 0x80, 0x7D, 0x7A, 0x77, 0x74, 0x70, 0x6D, 0x6A,
 0x67, 0x64, 0x61, 0x5E, 0x5B, 0x58, 0x55, 0x52,
 0x4F, 0x4D, 0x4A, 0x47, 0x44, 0x41, 0x3F, 0x3C,
 0x39, 0x37, 0x34, 0x32, 0x2F, 0x2D, 0x2B, 0x28,
 0x26, 0x24, 0x22, 0x20, 0x1E, 0x1C, 0x1A, 0x18,
 0x16, 0x15, 0x13, 0x11, 0x10, 0x0F, 0x0D, 0x0C,
 0x0B, 0x0A, 0x08, 0x07, 0x06, 0x06, 0x05, 0x04,
 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03,
 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x0A,
 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x13, 0x15,
 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24,
 0x26, 0x28, 0x2B, 0x2D, 0x2F, 0x32, 0x34, 0x37,
 0x39, 0x3C, 0x3F, 0x41, 0x44, 0x47, 0x4A, 0x4D,
 0x4F, 0x52, 0x55, 0x58, 0x5B, 0x5E, 0x61, 0x64,
 0x67, 0x6A, 0x6D, 0x70, 0x74, 0x77, 0x7A, 0x7D
};
void setup()
{
 Wire.begin();
}
void loop()
{ 
 for (int i=0; i<256; i++)
 {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x40); // control byte - turn on DAC (binary 1000000)
 Wire.write(sine_wave[i]); // value to send to DAC
 Wire.endTransmission(); // end tranmission
 }
}

And the results (click image to enlarge):

sine

For the following DSO image dump, we changed the Vref to 3.3V – note the change in the maxima on the sine wave:

sine3v3

Now you can experiment with the DAC to make sound effects, signals or control other analogue circuits.

Using the ADCs (analogue-to-digital converters)

If you’ve used the analogRead() function on your Arduino (way back in Chapter One) then you’re already familiar with an ADC. With out PCF8591 we can read a voltage between zero and the Vref and it will return a value of between zero and 255 which is directly proportional to zero and the Vref. For example, measuring 3.3V should return 168. The resolution (8-bit) of the ADC is lower than the onboard Arduino (10-bit) however the PCF8591 can do something the Arduino’s ADC cannot. But we’ll get to that in a moment.

First, to simply read the values of each ADC pin we send a control byte to tell the PCF8591 which ADC we want to read. For ADCs zero to three the control byte is 0×00, 0×01, ox02 and 0×03 respectively. Then we ask for two bytes of data back from the ADC, and store the second byte for use. Why two bytes? The PCF8591 returns the previously measured value first – then the current byte. (See Figure 8 in the data sheet). Finally, if you’re not using all the ADC pins, connect the unused ones to GND.

The following example sketch simply retrieves values from each ADC pin one at a time, then displays them in the serial monitor (download):

// Example 52.3 PCF8591 ADC demo 
// http://tronixstuff.com/tutorials Chapter 52
// John Boxall June 2013
#include 
#define PCF8591 (0x90 >> 1) // I2C bus address
#define ADC0 0x00 // control bytes for reading individual ADCs
#define ADC1 0x01
#define ADC2 0x02
#define ADC3 0x03
byte value0, value1, value2, value3;
void setup()
{
 Wire.begin();
 Serial.begin(9600);
}
void loop()
{ 
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(ADC0); // control byte - read ADC0
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 2);
 value0=Wire.read();
 value0=Wire.read();
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(ADC1); // control byte - read ADC1
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 2);
 value1=Wire.read();
 value1=Wire.read();
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(ADC2); // control byte - read ADC2
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 2);
 value2=Wire.read();
 value2=Wire.read();
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(ADC3); // control byte - read ADC3
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 2);
 value3=Wire.read();
 value3=Wire.read();
 Serial.print(value0); Serial.print(" ");
 Serial.print(value1); Serial.print(" ");
 Serial.print(value2); Serial.print(" ");
 Serial.print(value3); Serial.print(" "); 
 Serial.println();
}

Upon running the sketch you’ll be presented with the values of each ADC in the serial monitor. Although it was a simple demonstration to show you how to individually read each ADC, it is a cumbersome method of getting more than one byte at a time from a particular ADC.

To do this, change the control byte to request auto-increment, which is done by setting bit 2 of the control byte to 1. So to start from ADC0 we use a new control byte of binary 00000100 or hexadecimal 0×04. Then request five bytes of data (once again we ignore the first byte) which will cause the PCF8591 to return all values in one chain of bytes. This process is demonstrated in the following sketch (download):

// Example 52.4 PCF8591 ADC demo 
// http://tronixstuff.com/tutorials Chapter 52
// John Boxall June 2013
#include 
#define PCF8591 (0x90 >> 1) // I2C bus address
byte value0, value1, value2, value3;
void setup()
{
 Wire.begin();
 Serial.begin(9600);
}
void loop()
{ 
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x04); // control byte - read ADC0 then auto-increment
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 5); 
 value0=Wire.read();
 value0=Wire.read(); 
 value1=Wire.read(); 
 value2=Wire.read(); 
 value3=Wire.read();
 Serial.print(value0); Serial.print(" ");
 Serial.print(value1); Serial.print(" ");
 Serial.print(value2); Serial.print(" ");
 Serial.print(value3); Serial.print(" "); 
 Serial.println();
}

Previously we mentioned that the PCF8591 can do something that the Arduino’s ADC cannot, and this is offer a differential ADC. As opposed to the Arduino’s single-ended (i.e. it returns the difference between the positive signal voltage and GND, the differential ADC accepts two signals (that don’t necessarily have to be referenced to ground), and returns the difference between the two signals. This can be convenient for measuring small changes in voltages for load cells and so on.

Setting up the PCF8591 for differential ADC is a simple matter of changing the control byte. If you turn to page seven of the data sheet, then consider the different types of analogue input programming. Previously we used mode ’00′ for four inputs, however you can select the others which are clearly illustrated, for example:

adcmodes

So to set the control byte for two differential inputs, use binary 00110000 or 0×30. Then it’s a simple matter of requesting the bytes of data and working with them. As you can see there’s also combination single/differential and a complex three-differential input. However we’ll leave them for the time being.

Conclusion

Hopefully you found this of interest, whether adding a DAC to your experiments or learning a bit more about ADCs. We’ll have some more analogue to digital articles coming up soon, so stay tuned. And if you enjoy my tutorials, or want to introduce someone else to the interesting world of Arduino – check out my new book “Arduino Workshop” from No Starch Press.

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

June 17, 2013 Posted by | ADC, arduino, beginnner, dac, differential, education, electronics, I2C, lesson, NXP, PCF8591, tronixstuff, tutorial | , , , , , , , , , , , , , | Leave a Comment

Tutorial: Analog input for multiple buttons – Part Two

This is chapter forty-six 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 19/01/2013]

A while back I described how to read multiple buttons using only one analogue input pin. However we could only read one button at a time. In this instalment we revisit this topic and examine an improved method of doing so which allows for detecting more than one button being pressed at the same time. This method is being demonstrated as it is inexpensive and very easy to configure.

(For a more exact and expensive method please consider the use of the Microchip MCP23017 which allows for sixteen inputs via the I2C bus).

As you know the analogue input pins of the Arduino can read a voltage of between zero and five volts DC and return this measurement as an integer between zero and 1023. Using a small external circuit called a “R-2R ladder”, we can alter the voltage being measured by the analogue pin by diverting the current through one or more resistors by our multiple buttons. Each combination of buttons theoretically will cause a unique voltage to be measured, which we can then interpret in our Arduino sketch and make decisions based on the button(s) pressed.

First the circuit containing four buttons:

Can you see why this is called an R-2R circuit? When building your circuit – use 1% tolerance resistors – and check them with a multimeter to be sure. As always, test and experiment before committing to anything permanent.

Now to determine a method for detecting each button pressed, and also combinations. When each button is closed, the voltage applied to analogue pin zero will be different. And if two buttons are pressed at once, the voltage again will be different. Therefore the value returned by the function analogRead() will vary for each button-press combination. To determine these, I connected a numeric display to my Arduino-compatible board, then simply sent the analogRead() value to the display. You can see some of the results of this in the following video:

The analogRead() results of pressing every combination of button can be found in the following table:

After this experiment we now have the values returned by analogRead() and can use them in a switch… case function or other decision-making functions in our sketches to read button(s) and make decisions based on the user input. Unfortunately there was some overlap with the returned values and therefore in some cases not every possible combination of press will be available.

However, we’re still doing well and you can get at least eleven or twelve combinations still with only one analog input pin. You can add delay() functions in your sketch if necessary to take care of switch debouncing or do it with hardware if you feel it is necessary.

So now you have a more useful method for receiving input via buttons without wasting many digital input pins. I hope you found this article useful or at least interesting. This series of tutorials has been going for almost two years now, and may soon start to wind down – it’s time to move forward to the next series of tutorials :)

So if you have any suggestions for further articles (and not thinly-veiled methods of asking me to do your work for you…) – email them to john at tronixstuff dot com.

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

February 29, 2012 Posted by | arduino, education, electronics, learning electronics | , , , , , , , , , , , , , , , , , | 7 Comments

Using an ATtiny as an Arduino

Learn how to use ATtiny45 and ATtiny85 microcontrollers with Arduino in chapter forty-four 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.

Updated 07/02/2013

Did you know you can use an Atmel ATtiny45 or ATtiny85 microcontroller with Arduino software? Well you do now. The team at the High-Low Tech Group at MIT have published the information and examples on how to do this, and it looked like fun – so the purpose of this article is to document my experience with the ATtiny and Arduino and share the instructions with you in my own words. All credit goes to the interesting people at the MIT HLT Group for their article and of course to Alessandro Saporetti for his work on making all this possible.

Introduction

Before anyone gets too excited – there are a few limitations to doing this…

Limitation one – the ATtiny has “tiny” in the name for a reason:

it’s the one on the left

Therefore we have less I/O pins to play with. Consider the pinout for the ATtiny from the data sheet:

So as you can see we have thee analogue inputs (pins 7, 3 and 2) and two digital outputs with PWM (pins 5 and 6). Pin 4 is GND, and pin 8 is 5V.

Limitation two – memory. The ATtiny45 has 4096 bytes of flash memory available, the -85 has 8192. So you may not be controlling your home-built R2D2 with it.

Limitation three – available Arduino functions. As stated by the HLT article, the following commands are supported:

Other functions may work or become available over time.

Limitation four - You need Arduino IDE v1.0.1 or higher, except for v1.0.2. So v1.0.3 and higher is fine.

So please keep these limitations in mind when planning your ATtiny project.

Getting Started

There are two ways you can program your ATtiny. Method one describes using an Arduino board, method two describes using a separate USB programmer.

Programmer method one

You can use an existing Arduino-compatible board as a programmer with some external wiring. Before wiring it all up – plug in your Arduino board, load the IDE and upload the ArduinoISP sketch which is in the File>Examples menu. Whenever you want to upload a sketch to your ATtiny, you need to upload the ArduinoISP sketch to your Arduino first. Consider this sketch the “bridge” between the IDE and the ATtiny.

Next, build the circuit as shown below (click image to enlarge):

Depending on the Arduino board you’re using, you may or may not need the 10uF capacitor between Arduino RST and GND. Follow the schematic above each time you want to program the ATtiny.

Software

From a software perspective, to use the ATtinys you need to add some files to your Arduino IDE. First, download this zip file. Then extract the”attiny” folder and copy it to the “hardware” folder which sits under your main Arduino IDE folder, for example (click image to enlarge):

 Now restart the Arduino IDE. As you’re using the Arduino as a programmer, you need select “Arduino as ISP” – which is found in the Tools>Programmer menu. Next – select the board type using the Tools>Board  menu. Select the appropriate ATtiny that you’re using – with the 1 MHz internal clock option. Now you can enter and upload your ATtiny sketch. When uploading sketches you may see error messages as shown below:

The message is “normal” in this situation, so nothing to worry about. Now – scroll down until you reach the “Creating Arduino sketches for ATtinys” section to continue with the tutorial.

Programmer method two

Get yourself one of these USB programming boards:

They’re great – you just drop in the ATtiny – plus you can use it as a breakout board. And there’s an LED on pin 5 for testing with blink and debugging in general.

Insert your ATtiny into the board, plug it into a free USB port and the hardware is done. If you’re using Windows – install the necessary drivers (32-bit or 64-bit). Next, restart the Arduino IDE and select “USBtinyISP” – which is found in the Tools>Programmer menu. Next – select the board type using the Tools>Board  menu. Select the appropriate ATtiny that you’re using – with the 1 MHz internal clock option. Now you can enter and upload your ATtiny sketch. Note that you don’t need to select a USB port in the IDE, and the option may well be blanked out – instead, check the bottom-right of the IDE. It will show the USB port being used by the programmer board, for example:

Creating Arduino sketches for ATtinys

When creating your sketches, note that the pin number allocations are different for ATtinys in the IDE. Note the following pin number allocations:

  • digital pin zero is physical pin five (also PWM)
  • digital pin one is physical pin six (also PWM)
  • analogue input two is physical pin seven
  • analogue input three is physical pin two
  • analogue input four is physical pin three

For a quick demonstration, load the Blink example sketch – File>Examples>1. Basics>Blink. Change the pin number for the digital output from 13 to 0. For example:

void setup()
{
 pinMode(0, OUTPUT);
}
void loop() {
 digitalWrite(0, HIGH); // set the LED on
 delay(1000); // wait for a second
 digitalWrite(0, LOW); // set the LED off
 delay(1000); // wait for a second
}

Upload the sketch using the methods described earlier. If you’re using programmer method one, your matching circuit is:

If you’re using programmer method two, this will blink the on-board LED.

Final example

We test the digital outputs with digital and PWM outputs using two LEDs instead of one:

And the sketch:

void setup()
{
   pinMode(0, OUTPUT);
   pinMode(1, OUTPUT);
}
void loop()
{
   for (int a=0; a<6; a++)
   {
     digitalWrite(0, HIGH); // set the LED on
     digitalWrite(1, LOW); // set the LED off
     delay(1000); // wait for a second
     digitalWrite(0, LOW); // set the LED off
     digitalWrite(1, HIGH); // set the LED on
     delay(1000); // wait for a second
   }
for (int z=0; z<3; z++)
 {
   for (int a=0; a<256; a++)
   {
     analogWrite(0, a);
     analogWrite(1, a);
     delay(1);
   }
   for (int a=255; a>=0; --a)
   {
     analogWrite(0, a);
     analogWrite(1, a);
     delay(1);
   }
 }
}

And a quick demonstration video:

So there you have it – another interesting derivative of the Arduino system. Once again, thanks and credit to Alesssandro Saporetti and the MIT HLT Group for their published information.

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

November 23, 2011 Posted by | arduino, attiny, attiny45, attiny85, COM-09378, microcontrollers, PGM-11460, tutorial | , , , , , , , , , , , , , , , , , , | 6 Comments

Tutorial: Arduino and the SPI bus part II

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

[Updated 10/01/2013]

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

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

Notice how the slave devices share the clock, MOSI and MISO lines – however they both have their own chip select line back to the master device. At this point a limitation of the SPI bus becomes prevalent – for each slave device we need another digital pin to control chip select for that device. If you were looking to control many devices, it would be better to consider finding I2C solutions to the problem. To implement multiple devices is very easy. Consider the example 34.1 from part one – we controlled a digital rheostat. Now we will repeat the example, but instead control four instead of one. For reference, here is the pinout diagram:

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

Example 36.1

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

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

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

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

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

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

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

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

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

Although the example sketch may be longer than necessary, it is quite simple. We have four SPI devices each controlling one LED, so to keep things easy to track we have defined led1~led4 to match the chip select digital out pins used for each SPI device. Then see the first four lines in void setup(); these pins are set to output in order to function as required. Next – this is very important – we set the pins’ state to HIGH. You must do this to every chip select line! Otherwise more than one CS pins may be initially low in some instances and cause the first data sent from MOSI to travel along to two or more SPI devices. With LEDs this may not be an issue, but for motor controllers … well it could be.

The other point of interest is the function

void setValue(int l, int value)

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


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

Next on the agenda is a digital-to-analogue converter, to be referred to using the acronym DAC. What is a DAC? In simple terms, it accepts a numerical value between zero and a maximum value (digital) and outputs a voltage between the range of zero and a maximum relative to the input value (analogue). One could consider this to be the opposite of the what we use the function analogRead(); for. For our example we will use a Microchip MCP4921 (data sheet.pdf):

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

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

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

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

Bits 0 to 11 are the 12 bits of the output value; bit 15 is an output selector (unused on the MPC4921); bit 14 controls the input buffer; bit 13 controls an inbuilt output amplifier; and bit 12 can shutdown the DAC. Unlike previous devices, the input data is spread across two bytes (or a word of data). Therefore a small amount of work needs to be done to format the data ready for the DAC. Let’s explain this through looking at the sketch for example 36.2 that follows. The purpose of the sketch is to go through all possible DAC values, from 0 to 4095, then back to 0 and so on.

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

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

Now for our demonstration sketch (download):

Example 36.2

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

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

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

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

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

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

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

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

A device will have one or more registers where information is read from and written to. Look at page twelve of the DS3234 data sheet, there are twenty-three registers, each containing eight bits (one byte) of data. Please take note that each register has a read and write address. An example – to retrieve the contents of the register at location 08h (alarm minutes) and place it into the byte data we need to do the following:

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

Don’t forget to take note of  the function SPI.setBitOrder(MSBFIRST); in your sketch, as this also determines the bit order of the data coming from the device.

To write data to a specific address is also quite simple, for example:

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

Up to this point, we have not concerned ourselves with what is called the SPI data mode. The mode determines how the SPI device interprets the ‘pulses’ of data going in and out of the device. For a well-defined explanation, please read this article. With some devices (and in our forthcoming example) the data mode needs to be defined. So we use:

SPI.setDataMode(SPI_MODE1);

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

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

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

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

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

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

Don’t let the use of custom functions and loops put you off, they are there to save time. Looking in the function SetTimeDate();, you can see that the data is written to the registers 80h through to 86h (skipping 83h – day of week) in the way as described earlier (set CS low, send out address to write to, send out data, set CS high). You will also notice some bitwise arithmetic going on as well. This is done to convert data between binary-coded decimal and decimal numbers.

Why? Go back to page twelve of the DS3234 data sheet and look at (e.g.) register 00h/80h – seconds. The bits 7~4 are used to represent the ‘tens’ column of the value, and bits 3~0 represent the ‘ones’ column of the value. So some bit shifting is necessary to isolate the digit for each column in order to convert the data to decimal. For other ways to convert between BCD and decimal, see the examples using the Maxim DS1307 in chapter seven.

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

/*
 Example 36.3 - SPI bus device demo using a Maxim IC DS3234 Accurate RTC
 http://tronixstuff.com/tutorials > chapter 36 | CC by-sa-nc | John Boxall
 */
#include "SPI.h" // necessary library
// store hours, minutes, seconds, day of month, month, year
byte h,m,s,d,mo,y;
void setup() 
{
 pinMode(10, OUTPUT);
 SPI.begin(); // wake up the SPI bus.
 SPI.setBitOrder(MSBFIRST);
 SPI.setDataMode(SPI_MODE1); 
 digitalWrite(10, LOW); 

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

 Serial.begin(9600);
}

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

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

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

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

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

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

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

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

Tutorial: Arduino and the SPI bus

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

[Updated 10/01/2013]

This is the first of two chapters in which we are going to start investigating the SPI data bus, and how we can control devices using it with our Arduino systems. The SPI bus may seem to be a complex interface to master, however with some brief study of this explanation and practical examples you will soon become a bus master! To do this we will learn the necessary theory, and then apply it by controlling a variety of devices. In this tutorial things will be kept as simple as possible.

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

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

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

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

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

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

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


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

Data is sent to the SPI device in byte form. You should know by now that eight bits make one byte, therefore representing a binary number with a value of between zero and 255. When communicating with our SPI devices, we need to know which way the device deals with the data – MSB or LSB first. MSB (most significant bit) is the left-hand side of the binary number, and LSB (least significant bit) is the right-hand side of the number. That is:

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

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

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

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

#include "SPI.h"

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

    pinMode(ss, OUTPUT);

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

SPI.begin();

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

SPI.setBitOrder(MSBFIRST);

or

    SPI.setBitOrder(LSBFIRST);

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

digitalWrite(SS, LOW);

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

SPI.transfer(value);

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

digitalWrite(ss, HIGH);

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

Now for some practical examples!

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

Example 34.1

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


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

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

digitalWrite(10, LOW);

Then send the two byes of data:

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

Finally set the SS line back to high:

digitalWrite(10, HIGH);

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

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

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

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

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

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

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

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


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

Example 34.2

In this example, we will use the Analog Devices AD5204 four-channel digital potentiometer (data sheet.pdf). It contains four 10k ohm linear potentiometers, and each potentiometer is adjustable to one of 256 positions. The settings are volatile, which means they are not remembered when the power is turned off. Therefore when power is applied the potentiometers are all pre set to the middle of the scale. Our example is the SOIC-24 surface mount example, however it is also manufactured in DIP format as well.

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

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


And the sketch (download):

// Example 34.2
#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<count; z++)
 {
 allOn();
 delay(1000);
 allOff();
 delay(1000);
 }
}
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);
 }
 for (int l=255; l>=0; --l)
 {
 setPot(0,l);
 setPot(1,l);
 setPot(2,l); 
 setPot(3,l); 
 delay(del);
 }
 }
}
void loop()
{
 blinkAll(3);
 delay(1000);
 indFade();
 allFade(3);
}

The function allOff() and allOn() are used to set the potentiometers to minimum and maximum respectively. We use allOff() at the start of the sketch to turn the LEDs off. This is necessary as on power-up the wipers are generally set half-way. Furthermore we use them in the blinkAll() function to … blink the LEDs. The function setPot() accepts a wiper number (0~3) and value to set that wiper (0~255). Finally the function indFade() does a nice job of fading each LED on and off in order – causing an effect very similar to pulse-width modulation.

Finally, here it is in action:

Example 34.3

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

and the rear:

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

Again, wiring is quite simple:

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

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

write_led(a,b,c);

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

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

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

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

And a short video of the demonstration:

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

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

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


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

Kit review: Freetronics 16×2 LCD Arduino Shield

Hello everyone

This kit has now been discontinued, however Freetronics now have a great LCD+Keypad Shield.

Today we examine their latest kit, the “16×2 LCD Arduino Shield“. This is a very easy to construct, yet useful tool for those experimenting, prototyping and generally making things with their Arduino-based systems.  The purpose of the shield is to offer easy access to a 16 x 2 character LCD module, and also the use of five buttons – connected to an analog input using the resistor ladder method. The kit comes packaged very well, and includes not only detailed printed instructions in colour, but also the full circuit schematic:

It is nice to see such a high level of documentation, even though most people may not need it – there is generally someone who does. Sparkfun – get the hint. All the parts are included, and for the first time in my life the resistors were labelled as well:

So being Mr Pedantic I followed the instructions, and happily had the components in without any troubles. The next step was the Arduino shield pins – the best way to solder these is to insert into your Arduino board, drop the shield on top then solder away as such:

And finally, bolting on the LCD whilst keeping the header pins for the LCD in line. Some people may find the bolt closest to D0 interferes with the shield pin, so you can insert the bolt upside down as I have. Remember to not solder the LCD pins until you are happy it is seated in correctly:

Once you are satisfied the pins are lined up and sitting in their required position – solder them in, tighten your nuts and that’s it:

The contrast of the LCD in real life is better than shown in the photo above – photographing them is a little difficult for me. However once assembled, using the shield is quite easy. If your LCD doesn’t seem to be working after your first sketch, adjust the contrast using the potentiometer. The LCD is a standard HD44780-interface model, and wired in to use a 4-bit parallel data interface. If using these types of LCD is new to you, perhaps visit this article then return. Our shield uses the pins: A0 and D4~D9.

One uses the standard Arduino liquidCrystal library with this LCD, and the function parameters to use are as follows:

LiquidCrystal lcd(8,9,4,5,6,7)

The buttons are read using analog pin A0. Use the following sketch to find the values returned by the analogRead function:

/*  analogRead + button demonstration using Freetronics 16x2 LCD shield
John Boxall - http://tronixstuff.com/kitreviews - Jan 2011  */
#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7);
int a=0;
void setup()
{
lcd.begin(16, 2);
}

void loop()
{
lcd.clear();
a=analogRead(0);
lcd.setCursor(0,0);
lcd.print("analogRead()");
lcd.setCursor(0,1);
lcd.print("value = ");
lcd.print(a);
delay(200);
}

and a quick video of this in action:

Now that we know the values returned for each button, we can take advantage of them to create, for example, a type of menu system – or some sort of controller. In the second example, we have used a modified TwentyTen with a DS1307 real-time clock IC to make a digital clock. The buttons on the LCD shield are utilised to create a user-friendly menu to set the clock time.

You can download the demonstration sketch from here.

In general this is an excellent kit, and considering the price of doing it yourself – good value as well. To get your hands on this product in kit or assembled form – visit Freetronics’ website, or your local reseller.

Remember, if you have any questions about these modules please contact Freetronics via their website.

Higher resolution images available on flickr.

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

[Note - the kit assembled in this article was received from Freetronics for review purposes]

January 15, 2011 Posted by | arduino, kit review, LCD | , , , , , , , , , , , , , , , , , , , , , , , , , , , , | 6 Comments

Tutorial: Using analog input for multiple buttons

This is chapter twenty-five 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. Please note from November 1, 2010 files from tutorials will be found here.

[Updated 14/03/2013]

The purpose of this article is demonstrate how you can read many push buttons (used for user-input) using only one analog input pin. This will allow you to save digital I/O pins for other uses such as LCD modules and so on. Hopefully you recall how we used analogRead() in chapter one, and how we used a potentiometer to control menu options in exercise 10.1. For this article, we will be looking at reading individual presses, not simultaneous (i.e. detecting multiple button presses).

To recap, an analog input pin is connected to an analog to digital (ADC) converter in our Arduino’s microcontroller. It has a ten bit resolution, and can return a numerical value between 0 and 1023 which relates to an analog voltage being read of between 0 and 5 volts DC.

Example 25.1

With this sketch:

/*  Example 25.1 - Demonstrating analogRead()
http://tronixstuff.com/tutorials > chapter 25
CC by-sa-nc*/
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 2, 3);
int a=0;
void setup()
{
lcd.begin(20, 4);
pinMode(A5, INPUT_PULLUP);
}
void loop()
{
a = analogRead(5);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("  analogRead() ");
lcd.setCursor(0,1);
lcd.print("  value is :");
lcd.print(a);
delay(250);
}

and in the following short video, we have demonstrated the possible values returned by measuring the voltage from the centre pin of a 10k ohm potentiometer, which is connected between 5V and GND:

As the potentiometer’s resistance decreases, the value returned by analogRead() increases. Therefore at certain resistance values, analogRead() will return certain numerical values. So, if we created a circuit with (for example) five buttons that allowed various voltages to be read by an analog pin, each voltage read would cause analogRead() to return a particular value. And thus we can read the status of a number of buttons using one analog pin.

Example 25.2

The following circuit is an example of using five buttons on one analog input, using the sketch from example 25.1:

And here it is in action:

Where is the current coming from? Using pinMode(A5, INPUT_PULLUP); turns on the internal pull-up resistor in the microcontroller, which gives us ~4.8V to use. Some of you may have notice that when the right-most button is pressed, there is a direct short between A5 and GND. When that button is depressed, the current flow is less than one milliamp due to the pull-up resistor protecting us from a short circuit. Also note that you don’t have to use A5, any analog pin is fine.

As shown in the previous video clip, the values returned by analogRead() were:

  • 1023 for nothing pressed (default state)
  • 454 for button one
  • 382 for button two
  • 291 for button three
  • 168 for button four
  • 0 for button five

So for our sketches to react to the various button presses, they need to make decisions based on the value returned by analogRead(). Keeping all the resistors at the same value gives us a pretty fair spread between values, however the values can change slightly due to the tolerance of resistors and parasitic resistance in the circuit.

So after making a prototype circuit, you should determine the values for each button, and then have your sketch look at a range of values when reading the analog pin. Doing so becomes more important if you are producing more than one of your project, as resistors of the same value from the same batch can still vary slightly.

Example 25.3

Using the circuit from example 25.2, we will use a function to read the buttons and return the button number for the sketch to act upon.

/*  Example 25.3 - Digital buttons with analog input 
http://tronixstuff.com/tutorials > chapter 25 CC by-sa-nc */
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 2, 3);
int a=0;
void setup()
{
lcd.begin(20, 4);
     pinMode(A5, INPUT_PULLUP); // sets analog pin for input 

} 

int readButtons(int pin)
 // returns the button number pressed, or zero for none pressed 
 // int pin is the analog pin number to read 
{
int b,c = 0;
c=analogRead(pin); // get the analog value  if (c>1000)
{
b=0; // buttons have not been pressed
}   else
if (c>440 && c<470)
{
b=1; // button 1 pressed
}     else
if (c<400 && c>370)
{
b=2; // button 2 pressed
}       else
if (c>280 && c<310)
{
b=3; // button 3 pressed
}         else
if (c>150 && c<180)
{
b=4; // button 4 pressed
}           else
if (c<20)
{
b=5; // button 5 pressed
}
return b;
}
void loop()
{
a=readButtons(5);
lcd.clear();
if (a==0) // no buttons pressed
{
lcd.setCursor(0,1);
lcd.print("Press a button");
}   else
if (a>0) // someone pressed a button!
{
lcd.setCursor(0,2);
lcd.print("Pressed button ");
lcd.print(a);
}
delay(1000); // give the human time to read LCD
}

And now our video demonstration:

So now you have a useful method for receiving input via buttons without wasting many digital input pins. I hope you found this article useful or at least interesting.

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

January 11, 2011 Posted by | arduino, learning electronics, microcontrollers, tutorial | , , , , , , , , , , , , , , , , , , , , , | Leave a Comment

Kit review – Sparkfun Function Generator

Hello readers

[10/09/2011 Update - It would seem that this kit has been discontinued - most likely due to the unavailability of the XR2206 function generator IC - which is a damn shame as it was a great kit. If you are 'feeling lucky' eBay seems to have a flood of them. Purchase at your own risk!]

Time for another kit review (anything to take the heat off from the kid-e-log!). Today we will examine the Sparkfun Function Generator kit. This is based from an original design by Nuxie and has now been given a nice thick red PCB and layout redesign. Although quite a bare-bones kit, it can provide us with the following functions:

  • sine waves
  • triangle waves
  • a 5V square wave with adjustable frequency

There are two frequency ranges to choose from, either 15~4544Hz or 4.1~659.87kHz. Your experience may vary, as these values will vary depending on the individual tolerance of your components.  The coarse and fine adjustment potentiometers do a reasonable job of adjustment, however if you were really specific perhaps a multi-turn pot could be used for the fine adjustment. With the use of a frequency counter one could calibrate this quite well.

The maximum amplitude of the sine and triangle waves is 12V peak to peak, and doing so requires a DC power supply of between 14~22 volts (it could be higher, up to 30 volts – however the included capacitors are only rated for 25V). However if you just need the 5V square-wave, or a lower amplitude, a lesser supply voltage such as 9 volts can be substituted. After running the generator from a 20V supply, the 7812 regulator started to become quite warm – a heatsink would be required for extended use. The main brains of the generator are held by the Exar XR2206 monolithic function generator IC – please see the detailed data sheet for more information.

Now what do you get? Not much, just the bare minimum once more. Everything you need and nothing you don’t …

Upon turfing out the parts we are presented with:

Not a bad bill of materials – nice to see a DC socket for use with a plug-pack. Considering the XR2206 is somewhat expensive and rare here in the relative antipodes, an IC socket would be nice – however I have learned to just shut up and keep my own range in stock now instead of complaining. Having 5% tolerance resistors took me as a surprise at first, but considering that the kit is not really laboratory-precision equipment the tolerance should be fine. One could always measure the output and make a panel up later on.

Once again, I am impressed with the PCB from Sparkfun. Thick, heavy, a good solder mask and descriptive silk-screen:

Which is necessary as there aren’t any instructions with the kit nor much on the Sparkfun website. The original Nuxie site does have a bit of a walk through if you like to read about things before making them. Finally, some resistors and capacitors included are so small, a decent multimeter will be necessary to read them (or at least a good magnifying glass!).

Construction was very simple, starting with the low-profile components such as resistors and capacitors:

followed by the switches, terminal blocks, IC sockets and the ICs:


and finally the potentiometers:

The easiest way to solder in the pots while keeping them in line was to turn the board upside down, resting on the pots. They balance nicely and allow a quick and easy soldering job. At this point the function generator is now ready to go – after the addition of some spacers to elevate it from the bench when in use:

Now for the obligatory demonstration video. Once again, the CRO is not in the best condition, but I hope you get the idea…


Although a very simple, barebones-style of kit (in a similar method to the JYETech Capacitance meter) this function generator will quickly knock out some functions in a hurry and at a decent price. A good kit for those who are learning to solder, perhaps a great next step from a TV-B-Gone or Simon kit. And for the more advanced among us, this kit is licensed under Creative Commons attribution+share-alike, and the full Eagle design files are available for download – so perhaps make your own?

High resolution images are available on flickr.

[Note - The kit was purchased by myself personally and reviewed without notifying the manufacturer or retailer]

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

December 22, 2010 Posted by | education, kit review, learning electronics, oscilloscope, test equipment, XR2206 | , , , , , , , , , , , , , , , , , , , , , , , | Leave a Comment

Tutorial: Arduino and the AREF pin

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

[Updated 09/01/2013]

Today we are going to spend some time with the AREF pin – what it is, how it works and why you may want to use it.

First of all, here it is on our boards:

Uno/TwentyTen AREF

Mega AREF

[Please read the entire article before working with your hardware]

In chapter one of this series we used the analogRead() function to measure a voltage that fell between zero and five volts DC. In doing so, we used one of the six analog input pins. Each of these are connected to ADC (analog to digital conversion) pins in the Arduino’s microcontroller. And the analogRead() function returned a value that fell between 0 and 1023, relative to the input voltage.

But why is the result a value between 0~1023? This is due to the resolution of the ADC. The resolution (for this article) is the degree to which something can be represented numerically. The higher the resolution, the greater accuracy with which something can be represented. We call the 5V our reference voltage.

We measure resolution in the terms of the number of bits of resolution. For example, a 1-bit resolution would only allow two (two to the power of one) values – zero and one. A 2-bit resolution would allow four (two to the power of two) values – zero, one, two and three. If we tried to measure  a five volt range with a two-bit resolution, and the measured voltage was four volts, our ADC would return a value of 3 – as four volts falls between 3.75 and 5V.

It is easier to imagine this with the following image:

So with our example ADC with 2-bit resolution, it can only represent the voltage with four possible resulting values. If the input voltage falls between 0 and 1.25, the ADC returns 0; if the voltage falls between 1.25 and 2.5, the ADC returns a value of 1. And so on.

With our Arduino’s ADC range of 0~1023 – we have 1024 possible values – or 2 to the power of 10. So our Arduinos have an ADC with a 10-bit resolution. Not too shabby at all. If you divide 5 (volts) by 1024, the quotient is 0.00488 – so each step of the ADC represents 4.88 millivolts.

However – not all Arduino boards are created equally. Your default reference voltage of 5V is for Arduino Duemilanoves, Unos, Megas, Freetronics Elevens and others that have an MCU that is designed to run from 5V. If your Arduino board is designed for 3.3V, such as an Arduino Pro Mini-3.3 – your default reference voltage is 3.3V. So as always, check your board’s data sheet.

Note – if you’re powering your 5V board from USB, the default reference voltage will be a little less – check with a multimeter by measuring the potential across the 5V pin and GND. Then use the reading as your reference voltage.

What if we want to measure voltages between 0 and 2, or 0 and 4.6? How would the ADC know what is 100% of our voltage range?

And therein lies the reason for the AREF pin! AREF means Analogue REFerence. It allows us to feed the Arduino a reference voltage from an external power supply. For example, if we want to measure voltages with a maximum range of 3.3V, we would feed a nice smooth 3.3V into the AREF pin – perhaps from a voltage regulator IC. Then the each step of the ADC would represent 3.22 millivolts.

Interestingly enough, our Arduino boards already have some internal reference voltages to make use of. Boards with an ATmega328 microcontroller also have a 1.1V internal reference voltage. If you have a Mega (!), you also have available reference voltages of 1.1 and 2.56V. At the time of writing the lowest workable reference voltage would be 1.1V.

So how do we tell our Arduinos to use AREF? Simple. Use the function analogReference(type); in the following ways:

For Duemilanove and compatibles with ATmega328 microcontrollers:

  • analogReference(INTERNAL); – selects the internal 1.1V reference voltage
  • analogReference(EXTERNAL); – selects the voltage on the AREF pin (that must be between zero and five volts DC)
  • And to return to the internal 5V reference voltage – use analogReference(DEFAULT);

If you have a Mega:

  • analogReference(INTERNAL1V1); – selects the internal 1.1V reference voltage
  • analogReference(INTERNAL2V56); – selects the internal 2.56V reference voltage
  • analogReference(EXTERNAL); – selects the voltage on the AREF pin (that must be between zero and five volts DC)
  • And to return to the internal 5V reference voltage – use analogReference(DEFAULT)

Note you must call analogReference() before using analogRead(); otherwise you will short the internal reference voltage to the AREF pin – possibly damaging your board. If unsure about your particular board, ask the supplier or perhaps in our Google Group.

Now that we understand the Arduino functions, let’s look at some ways to make a reference voltage. The most inexpensive method would be using resistors as a voltage divider. For example, to halve a voltage, use two identical resistors as such:

For a thorough explanation on dividing voltage with resistors, please read this article. Try and use resistors with a low tolerance, such as 1%, otherwise your reference voltage may not be accurate enough. However this method is very cheap.

A more accurate method of generating a reference voltage is with a zener diode. Zener diodes are available in various breakdown voltages, and can be used very easily. Here is an example of using a 3.6V zener diode to generate a 3.6V reference voltage:

For more information about zener (and other diodes) please read this article. Finally, you could also use a linear voltage regulator as mentioned earlier. Physically this would be the easiest and most accurate solution, however regulators are not available in such a wide range nor work with such low voltages (i.e. below 5V).

Finally, when developing your sketch with your new AREF voltage for analogRead();, don’t forget to take into account the mathematics of the operation. For example, if you have a reference voltage of 5V, divide it by 1024 to arrive at a value of 4.88 millivolts per analogRead() unit. Or as in the following example, if you have a reference voltage of 1.8V, dividing it by 1024 gives you 1.75 millivolts per analogRead() unit: (download sketch)

/*
Example 22.1  Measuring range of analogRead() using a 1.8V AREF voltage  
tronixstuff.com/tutorials  CC by-sa-nc
*/
int analoginput = 0; // our analog pin
int analogamount = 0; // stores incoming value
float percentage = 0; // used to store our percentage value
float voltage =0; // used to store voltage value
void setup()
{
Serial.begin(9600);
analogReference(EXTERNAL); // use AREF for reference voltage
}
void loop()
{
delay(200);
analogamount=analogRead(analoginput);
percentage=(analogamount/1024)*100;
voltage=analogamount*1.75; // in millivolts
Serial.print("Percentage of AREF: ");
Serial.println(percentage,2);
Serial.print("voltage on analog input (mV): ");
Serial.println(voltage,2);
}

So if necessary, you can now reduce your voltage range for analog inputs and measure them effectively.

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

December 7, 2010 Posted by | arduino, aref, education, microcontrollers, tutorial | , , , , , , , , , , , , , , , , , , , , , , , , , , , , | 38 Comments

Review – Starman Electric Databridge Wireless I/O Modules

[30/09/2010 - Updated with radio licensing information at end of article]

Hello everyone!

Today we are going to have a look at some new wireless data modules that have just arrived on the market. They are the Databridge Wireless I/O modules from Starman Electric. Although there are many types of wireless modules out there, such as the discount 315 MHz units that are somewhat unreliable (well for me); and the great XBee series (as we used in Moving Forward with Arduino – Chapter Fourteen) – these Starman modules take it to the next level. How?

The concept of a databridge is a delightfully simple one. The two modules take the place of a wire. Digital, analogue, UART, even PC serial. No firmware settings to adjust, just plug them in and they work!

First of all, there are two physical types of unit, either DIP mount or SMD. The units below are the DIP version, 1mW output power:

The graph paper is 5mm square, and the module measures 53.85mm by 25.91mm. The DIP packaging (above) is meant for experimenters and prototypes, you can order SMD versions for production runs. There are also two power-output versions, 1mW with a theoretical range of 1km, and a 100 mW with a range of 4km. The higher power modules require the use of an external antenna. They require 3.3 volts DC, with a peak current draw of 37mA for the 1mW, and 120mA for the 100mW. For demonstration purposes I am using a Texas Instruments LP2950 to provide 3.3 volts DC at up to 100 mA.

Although the specification sheet is quite long (and you can download it from here) there are a few features that really stand out, including:

  • Automatic connection – a pair of modules will ‘lock’ onto each other without any extra work by the user;
  • A very high sampling rate of 200 samples per second with a latency of five millisconds;
  • Spread-spectrum radio operation – the modules will skip frequencies themselves for reliable connections;
  • You can have sixteen unique pairs working in the same area without cross-interference;
  • You can have two analogue channels and multiple digital channels simultaneously.

But enough talking, time to put them to the test. I will recreate some examples found in the Getting Started manual available for download here.

As I only have one pair of modules, and somehow I think my neighbours won’t be using any at this point in time, there is no need to set the pair’s unique network ID. However you do need to specify the master and slave in the module relationship (no switches…), which is done with pin 4 – to Vcc for master, and pin 4 to GND for slave. Now on with the show!

The first example of interest is number two in the guide – the wireless digital and analogue I/O bridge. To me this seems like an interesting wireless “repeater” to some Arduino analogue and digital outputs. Here is my test schematic used for the demonstrations in this review:


and my usual messy breadboards:

Well this is a temporary test! The slave module board is running from a 9V PP3 battery so I can take it for a walk.

Anyhow, the setup is – four digital out lines from the TwentyTen Arduino-compatible board, which are either high or low (+5v or GND). These are connected to pins ‘digital signal’ 1~4 on the master Databridge. Furthermore, TwentyTen analogue pin 1 went to the Databridge ‘analog signal’ pin 1. At the slave side of things, there are four LEDs with current limiting resistors connected to pins ‘digital signal’ 1~4; and two wires each from ‘analog signal’ 1 would be connected to a voltmeter. The digital output pins on slave modules default to ‘high’ unless driven otherwise.

Finally, there is also an LED and current limiting resistor coming from pin 32 of each unit – the ‘link’ pin. The link pin is a lifesaver. Here is a great feature – when the pair of units are within range of each other and matched as a pair, link goes high (3.3V). Out of range? It goes low (0V). Therefore you can test the range on these modules just by powering them up on a breadboard each, with the LEDs on pin 32, and go for a walk with a unit. When the LED is off – you’re out of range. And when you come back into range, the modules reconnect automatically.

Back to the test. First I just created a loop which turned the digital pins on and off, and the matching LEDs on the slave unit blinked on and off as expected. No extra code, no trying to create wacky functions to multiplex/demultiplex signals – this just works. The modules are like an invisible bunch of wires between two points. Never has anything wireless worked so easily for me.

Here is a quick video clip, first notice the lonely LEDs on each breadboard – the are the link LEDs. When I power cycle the master or slave, notice how quickly they reconnect. Please note that the slave unit retains the state of the digital outputs if connection is lost. So if a pin is high while connected – if the module loses radio contact, the pin will stay high.

The theoretical maximum working range is quoted as 1km for these 1mW modules. My indoor test allowed a distance of 11 metres, with three concrete walls of a thickness of ~110mm in between. Unfortunately living in my area I could not find a flat, open area large enough to test the maximum open-air range – however considering the indoor ‘concrete wall’ test and my experience with other wireless equipment of this power output, it would be accurate in an outdoor, line-of-sight application. As always, conduct your own real-life tests before making any project commitments  and so on.

And as always, I was curious about the current draw of the units while in use. The master module with the link LED on measured 53 milliamps, with the slave at the boundary of the radio range:

The current use only dropped around 2 or 3 milliamps when the slave was next to the master. The slave module used 59 milliamps with the link LED on:

Therefore taking the LED current draw into consideration, the power usage of these modules is quite low considering the level of communication between them and the high sampling rate.

The next test was to see how the analogue data lines performed. According to example four in the Getting Started guide, the modules will reproduce an input of between 0 and 2.4 volts DC. So I have placed an 11k ohm resistor in series with a 10k ohm potentiometer with analog input 1, and measured the resulting output from the slave. Notice how I still have the digital data lines in use while using the analogue line.  Here is a short clip of this in action:

Amazing – a multitasking wireless module. Note that you could always use an op-amp to boost the output voltage back to the 0~5V DC range, an example of this is on page nine of the Getting Started guide.

Those above were but two from the many possibilities available when using these units:

  • wireless serial data links
  • remote on/off control of six items
  • robotics remote control
  • microcontroller I/O wireless extension…

Frankly – if you need to wirelessly connect more than one data line simultaneously, you have an excellent solution with the Databridge modules.

Update! – Radio licensing information:

These modules operate in the 2.4 GHz ISM (industrial, scientific and medical) band. For those in the USA, the Databridge is an FCC-approved “class B” device, and is only for use by OEM integrators (see page 16 of the datasheet.pdf). Starman Electric also state that the Databridge is certified for Canada and the EU (ETSI).

For those here in Australia, these units are operated under the conditions of the Radiocommunications (Low Interference Potential Devices) Class Licence 2000, and I feel are classed as “spread spectrum unit” under the preceding license.

Please conduct your own research with regards to radio transmitter licensing in your area. Furthermore, please read the tronixstuff “boring stuff” here.

But enough about that, where you can get them?

Australian customers can purchase these modules from our local distributor - Interworld Electronics; North Americans and the rest of the world directly via Starman Electric.

Remember, if you have any questions about these modules please contact Starman Electric via their website.

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

[Note - these wireless modules were loan units received from Starman Electric for review purposes]

September 30, 2010 Posted by | part review, wireless | , , , , , , , , , , , , , , , , , , , , | 7 Comments

Follow

Get every new post delivered to your Inbox.

Join 4,018 other followers

%d bloggers like this: