t r o n i x s t u f f

fun and learning with electronics

Review: Gooligum Electronics PIC Training Course and Development Board

Introduction

[Updated 18/06/2013]

There are many types of microcontrollers on the market, and it would be fair to say one of the two most popular types is the Microchip PIC series. The PICs are great as there is a huge range of microcontrollers available across a broad range of prices. However learning how to get started with the PIC platform isn’t exactly simple. Not that we expect it to be, however a soft start is always better. There are some older books, however they can cost more than $100 – and are generally outdated. So where do you start?

It is with this problem in mind that led fellow Australian David Meiklejohn to develop and offer his PIC Training Course and Development Board to the marketplace via his company Gooligum Electronics.

In his words:

There is plenty of material available on PICs, which can make it daunting to get started.  And some of the available material is dated, originally developed before modern “flash” PICs were available, or based on older devices that are no longer the best choice for new designs.  Our approach is to introduce PIC programming and design in easy stages, based on a solid grounding in theory, creating a set of building blocks and techniques and giving you the confidence to draw on as we move up to more complex designs.

So in this article we’ll examine David’s course package. First of all, let’s look at the development board and inclusions. Almost everything you will need to complete all the lessons is included in the package, including the following PIC microcontrollers:

You can choose to purchase the board in kit form or pre-assembled. If you enjoy soldering, save the money and get the kit – it’s simple to assemble and a nice way to spend a few hours with a soldering iron.

Although the board includes all the electronic components and PICs – you will need are a computer capable of running Microchip MPLAB software, a Microchip PICkit3 (or -2) programming device and an IC extractor. If you’re building the kit, a typical soldering iron and so on will be required. Being the  ultra-paranoid type, I bought a couple extra of each PIC to have as spares, however none were damaged in my experimenting. Just use common-sense when handling the PICs and you will be fine.

Assembly

Putting the kit board together wasn’t difficult at all. There isn’t any surface-mount parts to worry about, and the PCB is silk-screened very well:

The rest of the parts are shipped in antistatic bags, appropriately labelled and protected:

Assembly was straight forward, just start with the low-profile parts and work your way up. The assembly guide is useful to help with component placement. After working at a normal pace, it was ready in just over an hour:

The Hardware

Once assembled (or you’ve opened the packaging) the various sections of the board are obvious and clearly labelled – as they should be for an educational board. You will notice a large amount of jumper headers – they are required to bridge in and out various LEDs, select various input methods and so on. A large amount of jumper shunts is included with the board.

It might appear a little disconcerting at first, but all is revealed and explained as you progress through the lessons. The board has decent rubber feet, and is powered either by the PICkit3 programmer, or a regulated DC power source between 5 and 6V DC, such as from a plug-pack if you want to operate your board away from a PC.

However there is a wide range of functions, input and output devices on the board – and an adjustable oscillator, as shown in the following diagram:

The Lessons

There is some assumed knowledge, which is a reasonable understanding of basic electronics, some computer and mathematical savvy and the C programming language.

You can view the first group of lessons for free on the kit website, and these are included along with the additional lessons in the included CDROM. They’re in .pdf format and easy to read. The CDROM also includes all the code so you don’t have to transcribe it from the lessons. Students start with an absolute introduction to the system, and first learn how to program in assembly language in the first group of tutorials, followed by C in the second set.

This is great as you learn about the microcontroller itself, and basically start from the bottom. Although it’s no secret I enjoy using the Arduino system – it really does hide a lot of the actual hardware knowledge away from the end user which won’t be learned. With David’s system – you will learn.

If you scroll down to the bottom of this page, you can review the tutorial summaries. Finally here’s a quick demonstration of the 7-segment displays in action:

Update – 18/06/2013

David has continued publishing more tutorials for his customers every few months – including such topics as the EEPROM and pulse-width modulation. As part of the expanded lessons you can also get a pack which allows experimenting with electric motors that includes a small DC motor, the TI SN75441 h-bridge IC, N-channel and P-channel MOSFETS and more:

motorkit

So after the initial purchase, you won’t be left on your own. Kudos to David for continuing to support and develop more material for his customers.

Where to from here? 

Once you run through all the tutorials, and feel confident with your knowledge, the world of Microchip PIC will be open to you. Plus you now have a great development board for prototyping with 6 to 14-pin PIC microcontrollers. Don’t forget all the pins are brought out to the row of sockets next to the solderless breadboard, so general prototyping is a breeze.

Conclusion

For those who have mastered basic electronics, and have some C or C-like programming experience from using other development environments or PCs – this package is perfect for getting started with the Microchip PIC environment. Plus you’ll learn about assembly language – which is a good thing. I genuinely recommend this to anyone who wants to learn about PIC and/or move into more advanced microcontroller work. And as the entire package is cheaper than some books –  you can’t go wrong. The training course is available directly from the Gooligum website.

Disclaimer - The Baseline and Mid-Range PIC Training Course and Development Board was a promotional consideration from Gooligum Electronics.

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.

October 25, 2012 Posted by | beginnner, kit review, microchip, microcontrollers, pic, tutorial | , , , , , , , , , , , , , , , , | 6 Comments

Review – Digilent chipKIT Uno32

In this review we consider a Digilent chipKIT Uno32 development board made available by element14.

Introduction

This is a development board that is based on the Arduino Uno, however uses a Microchip PIC32MX320F128 microcontroller instead of the Atmel ATmega328 we are used to:

Digilent’s decision to use the PIC32 introduces some interesting changes to the Uno format, and the largest change to take note of is the clock speed – 80 MHz instead of the Uno’s 16 MHz. That certainly took my attention, and we can see this demonstrated shortly.

When shipped the board arrives alone in a cardboard box, without a USB cable:

All documentation is found on the Digilent website. There is also a support forum to discuss libraries, IDE updates and so on. The board itself is quite familiar upon initial inspection:

You can see that Arduino shield will physically fit onto the board, and the extra I/O pins are accessed through the second rows of jumpers inside the board. With some crafty PCB creation skills you could make your own Uno32 shields, or consider one of the boards available from element14 or Digilent.

As for the other specifications of the Uno32:

  • Clock speed – 80 MHz
  • 128K flash program memory
  • 16K SRAM data memory
  • I/O pins – 42 (12 used as analogue inputs or digital I/O)
  • Five PWM pins
  • FTDI chip for USB interface
  • Two user LEDs
  • Same form factor as Arduino Uno boards, which allows physical shield compatibility
  • Five interrupt pins
  • On board real-time clock (external crystal required)

You will need a new IDE, and you can download Uno32-modified versions of the Arduino v22 and v23 IDE from here for Windows, MacOS and 32-bit Linux (no 64-bit…). The bootloader is preinstalled on the Uno32 and after installing the special IDE it works just as our normal Arduinos do in terms of editing and uploading sketches. The board also is compatible with the Microchip MPLAB IDE and PICkit3 in-circuit debugger if you want to use the Uno32 as a normal PIC32 development board. There is a row of holes between the USB socket and the DC socket that will need header pins soldered in for PICkit3 use.

Speed comparison

Naturally you want to see the speed test. The following sketch was run on an Arduino Uno and the Uno32 boards using IDE v1.0 for the Uno and the MPIDE v23 for the Uno32:

void setup()
{
 Serial.begin(9600);
}
unsigned long a,b,c,d,e;
void loop()
{
 a=millis();
 for (c=1; c<1000000; c++)
 {
   d=sq(c);
 }
 b=millis();
 e=b-a;
 Serial.print("Duration: ");
 Serial.print(e);
 Serial.println("ms");
 do {} while (1>0);
}

And here are the results of running the sketch four times on each board (click image to enlarge):

Well that’s pretty impressive – over sixty times faster than the Arduino Uno. Therein lies the major reason to use this board over the Uno. The eagle-eyed among you may have also noticed the difference in the compiled binary sketch size – 6432 bytes for the Uno32 vs. 2540 bytes for the Arduino Uno. That’s interesting.

Nevertheless there are many things to take note of when moving from Arduino to Uno32, or in other words – you can’t just swap out an Arduino Uno for an Uno32, recompile and run your sketch at the faster speed. The Microchip PIC32 is very much a different beast to the Atmel AVRs we’re used to, so it is important that you understand the differences in hardware and software to take advantage of the Uno32. So let’s run through those  now.

Power Differences

The Uno32 is a 3.3V board due to the PIC32. You can still power it via USB, or connect between 7~15 VDC to the power socket on the board. You can change a jumper and feed 5V directly into the board bypassing the 5V regulator. External power is regulated to 5V then to 3.3V. From a total of 1A current, the PIC32 uses 75mA, so you can draw up to 925mA from the 5V bus or 425mA from the 3.3V bus (or a mixture from both). It would pay to determine your current load before testing to avoid damaging the board, however  the  manual notes that the regulators will become hot at high current loads but do have thermal protection. Finally there is also a jumper that chooses between a 5V or 3.3V voltage feed to the shields. As always, consult the manual first.

I/O Differences

Although the PIC32 being a 3.3V part, the manual states that the digital I/O pins are 5V tolerant, so applying 5V to a digital input won’t damage the PIC32. Logic on the other hand is a different kettle of fish. According to the manual a digital ‘high’ when sourcing 12mA of current will only reach close to 3.3V. This may be too low in some situations so check your threshold voltages when choosing external parts. Furthermore, the analogue reference voltage (AREF) is restricted to 3.3V.

One stand-out difference is that you can only source 18mA from a digital pin, which is OK if you’re blinking some LEDs. However for logic output to keep the voltage range below 0.4V for ‘low’ and above 2.4V for ‘high’ the current must be restricted to -12~+7mA – another different limitaion. Finally, the maximum current you can source over all the I/O pins at once is 200mA.

There are two UARTs, number one where we expect it (D0/D1) and another on pins 39 and 40. I2C is on A4/A5 but needs to be activated with a jumper. Note that unlike an Arduino there aren’t any inbuilt pull-up resistors for the I2C bus, so add your own. There is also an SPI bus at the usual position (D10~13) and interestingly you can change the board between SPI master and slave via another set of jumpers. There are five pulse-width modulation outputs, however one is on D10 which is also part of the SPI bus. Finally there are five hardware interrupt pins.

Shield Compatibility  

Arduino shields will physically fit onto the Uno32 – but you need to be aware of the I/O differences listed above, the voltage and current specification and also the software side of things. Again – do your research before making the commitment to the hardware.

Software Compatibility

The Uno32 is compatible with a variety of Arduino sketches, but not all. This in a large part is due to the libraries which will need to be sourced from the community or rewritten yourself if not provided with the MPIDE software. There is a community on the support forum which is contributing their own, such as the real-time clock library – but again, research needs to be done before use. When trying to use an existing Arduino sketch and hardware, you will need to spend some time checking for compatibility. Again – it’s much easier to design a new project around the Uno32 than rejig an existing one.

Open Source? 

One of the things many people love about the Arduino ecosystem is that the entire system is open source hardware and software. Without causing a pro/con argument about software licensing you should note that not all of the software toolchain for the Uno32 is open, nor the USB or TCP/IP stack. There is some interesting discourse about this here.

Conclusion

A lot of work needs to be done to ensure compatibility with existing Arduino applications. The Uno32 is tempting due to the raw clock-speed increase, however the sketch/library and hardware differences may introduce a few road blocks. However, when designing a project from scratch and understand the licensing limitations, the Uno32 would be great as you know what you have to work with – a much faster board with much more I/O. And it is very inexpensive, less than ~$35. You can order your new Uno32 from element14.

Finally, if you’re looking for a very inexpensive PIC32 development board to use with Microchip MPLAB, the Uno32 is a great deal that can possibly interface with a wide variety of shields from the Arduino world.

Disclaimer – The Chipkit Uno32 board reviewed in this article was a promotional consideration made available by element14.

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 29, 2012 Posted by | arduino, review | , , , , , , , , , , , , , | 6 Comments

Tutorial: Maximising your Arduino’s I/O ports

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

[Updated 25/11/2012]

In this article we discuss how to use the Microchip MCP23017 16-bit serial expander with I2C serial interface. This 28-pin IC offers sixteen inputs or outputs – and up to eight of the ICs can be used on one I2C bus… offering a maximum of 128 extra I/O ports. A few people may be thinking “Why not just get an Arduino Mega2560?” – a good question. However you may have a distance between the Arduino and the end-point of the I/O pins – so with these ICs you can run just four wires instead of a lot more; save board space with custom designs, and preserve precious digital I/O pins for other uses. Plus I think the I2C bus is underappreciated! So let’s get started…

Here is our subject of the article in DIP form:

At this point you should also download yourself a copy of data sheet – it will be referred to several times, and very useful for reference and further reading. Furthermore if you are not familiar with Arduino and the I2C bus, please familiarise yourself with the I2C tutorials parts one and two. The MCP23017 can be quite simple or complex to understand, so the goal of this article is to try and make it as simple as possible. After reading this you should have the knowledge and confidence to move forward with using a MCP23017.

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

The sixteen I/O ports are separated into two ‘banks’ – A (on the right) and B (on the left. Pin 9 connects to 5V, 10 to GND, 11 isn’t used, 12 is the I2C bus clock line (Arduino Uno/Duemilanove analogue pin 5, Mega pin  21), and 13 is the I2C bus data line (Arduino Uno/Duemailnove analogue pin 4, Mega pin 20). External pull-up resistors should be used on the I2C bus – in our examples we use 4.7k ohm values. Pin 14 is unused, and we won’t be looking at interrupts, so ignore pins 19 and 20. Pin 18 is the reset pin, which is normally high – therefore you ground it to reset the IC. So connect it to 5V!

Finally we have the three hardware address pins 15~17. These are used to determine the I2C bus address for the chip. If you connect them all to GND, the address is 0×20. If you have other devices with that address or need to use multiple MCP23017s, see figure 1-2 on page eight of the data sheet. You can alter the address by connecting a combination of pins 15~17 to 5V (1) or GND (0). For example, if you connect 15~17 all to 5V, the control byte becomes 0100111 in binary, or 0×27 in hexadecimal.

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

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

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

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

Then to set bank B to outputs, we use:

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

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

To control bank A, we use:

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

To control bank B, we use:

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

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

To calculate the required number, consider each I/O pin from 7 to 0 matches one bit of a binary number – 1 for on, 0 for off. So you can insert a binary number representing the status of each output pin. Or if binary does your head in, convert it to hexadecimal. Or a decimal number. So for example, you want pins 7 and 1 on. In binary that would be 10000010, in hexadecimal that is 0×82, or 130 decimal. (Using decimals is convenient if you want to display values from an incrementing value or function result).

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

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

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

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

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

Example 41.1

/*
 Example 41.1 - Microchip MCP23017 with Arduino
 http://tronixstuff.wordpress.com/tutorials > chapter 41
 John Boxall | CC by-sa-nc
*/
// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
void setup()
{
 Wire.begin(); // wake up I2C bus
// set I/O pins to outputs
 Wire.beginTransmission(0x20);
 Wire.write(0x00); // IODIRA register
 Wire.write(0x00); // set all of bank A to outputs
 Wire.endTransmission();
Wire.beginTransmission(0x20);
 Wire.write(0x01); // IODIRB register
 Wire.write(0x00); // set all of bank B to outputs
 Wire.endTransmission();
}
void binaryCount()
{
 for (byte a=0; a<256; a++)
 {
 Wire.beginTransmission(0x20);
 Wire.write(0x12); // GPIOA
 Wire.write(a); // bank A
 Wire.endTransmission();
Wire.beginTransmission(0x20);
 Wire.write(0x13); // GPIOA
 Wire.write(a); // bank B
 Wire.endTransmission();
 }
}
void loop()
{
 binaryCount();
 delay(500);
}

And here is the example blinking away:

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

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

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

Example 41.2

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

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

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

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

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

Example 41.3

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

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

So there you have it… another way to massively increase the quantity of digital I/O pins on any Arduino system by using the I2C bus.

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

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

Tutorial: Arduino and 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 I2C bus – Part Two

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

[Updated 10/01/2013]

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

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

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

Example 21.1

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

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

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

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

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

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

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

Here is the schematic:

… and the example board layout:


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

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

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

And finally our demonstration video:


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

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

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

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

The pinouts are very simple:

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

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

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

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

Writing data to the 24LC256

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

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

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

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

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

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

There we have it. Now for getting it back…

Reading data from the 24LC256

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

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

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

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

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

Example 21.2

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

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

Here is the schematic:

… the board layout:

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

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

And the output from the example sketch:

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

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

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

Tutorial: Arduino and the I2C bus – Part One

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

[Updated 10/01/2013]

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

But first of all, what is it?

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

Why would we want to use I2C devices?

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

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

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

The bus wiring is simple:

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

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

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

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

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

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

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

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

Wire.endTransmission();

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

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

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

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

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

Wire.requestFrom(device_address, 3);

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

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

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

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

Example 20.1

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

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

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

Our example schematic is as follows:


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

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

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

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

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

void loop()
{
 potLoop();
}

and a video demonstration:


Example 20.2

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

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

Our example schematic is as follows:


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

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

Wire.requestFrom(cn75address, 2)

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

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

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

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

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

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

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

And here is the result from the serial monitor:

Example 20.3

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

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

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

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

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

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

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

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

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

Wire.beginTransmission(0x68);
Wire.write(0);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();

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

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

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

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

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

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

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

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

   

Follow

Get every new post delivered to your Inbox.

Join 4,007 other followers

%d bloggers like this: