PIC32 I2C Projects

I2C

Note that some of the examples shown here will only work with version 1.xx or less. This page will be updated in due course.

I2C is a popular communication link and protocol for interconnecting electronic devices. It uses a 2 wire serial link consisting of a clock, and a data line, either can be input or output. The link features are:

  • 2 Wire interface
  • 1 wire is clock either input or output
  • 1 wire is data in or data out
  • Semi-fixed addresses used for device selection.
  • Master-Slave operation, the master device operates the clock.

This particular PIC32 processor has built in hardware for handling the I2C protocol and there are two channels 0 and 1. An I2C device can either be a master or slave. This text deals with using the PIC32 as a master. PIC32-Basic provides a very flexible but simple interface for I2C that is contained within just 3 statements, read, write and test. This section will show how to use these commands with some popular devices. The i2c statements work with single dimension integer or character arrays and it is also possible to send a sting to the i2c device. Full information is given in the PIC32-Basic section.

PCF8574

This is an 8 bit I/O expander that has been around a long time (for a better IC see the BV4206).

schematic of pcf8574

Typically the above will give a useful demonstration of input and output. The practical layout looks like this:

prectical layout using pcf8574

The SDA and SCK lines have been taken from the auxiliary connector designed for I2C devices. There are no pull up resistors on board and so these need to be provided. Once the circuit has been set up it may be worth typing in a few commands to see if it is working. The address on this particular device is 0x70 because A0-A2 have been tied to 0. Some devices are 0x40. To get some immediate results see the screen shot of a session below:

i2c session screenshot

It is essential that the i2c channel is open first, for this device it is set to 100kHz using the i2copen keyword. The array variable t[] is used for reading.

The first write will switch on all both LED’s and the second, writing 0xff will switch them off. An i2c read is always into an integer array, the first read with the push button up and the second with it down. In the last line the mistake was done deliberately, the error is the missing comma between the address and the array variable. If the comma is not there then an attempt will be made to write the array to the device. This is because the read command has the option of sending commands before reading. The comma is the differentiator between send and receive.

The Code

It is quite straightforward to design two functionss that put and get data from this simple device:

constant PCF8 0x70

function i2c_init
    i2copen 100000
endf

function pcf_in
dim t[1]
    i2cread PCF8, t[1];1
    result t[1]
endf

function pcf_out(value)
    i2cwrite PCF8 value
endf

Function 'pcf_in' reads a single byte into the array and presents it as a result. The constant can be changed to suit the device. The initialisation needs to be run first in order to open the I2C channel.

23LC512

24lc512 practical layout

This is a very useful serial EEPROM device that can come in many different sizes and part numbers, mostly they all owrk the same way. The two pull up resistors are still needed for the I2C bus of course but other than that no other external components are required.

chip top sketch of 24lc512

The wiring for these is very straightforward, WP can be left disconnected and A0-A2 tied to ground. DON’T FORGET THE PULL UP RESISTORS. With this arrangement the i2c address is 0xA0 and as it is a modern device it should be able to handle 400k.

To keep things simple we will look at byte read and write. Page writes are possible and quite easy to implement with PIC32-Basic but the code gets complicated if the write does not occur on a page boundary.

constant I2CEE 0xa0

function ee_write(address, value)
dim hb%,lb%
    lb%=and(address,0xff)
    hb%=rshift(and(address,0xff00),8)
    i2cwrite I2CEE hb%,lb%,value
endf

The address required by the 24L512 is a 16 bit address made up of two bytes, a high byte and a low byte. The low byte is easily obtained by masking of the other 24 bits using 0xff and AND, this is assigned to lb%. The ‘%’ is unsigned integer which seems appropriate for this use but ‘lb’ would do just fine.

The high byte is obtained by masking bits 15:8 with 0xff00 and then shifting theses down to bits 7:0. If the i2cwrite is temporarily replaced by ‘print hb%,lb%’ it can be verified that this arrangement works okay. To write to the 24L512, the address is sent first followed by the data. e.g. ee_write 0 130

Reading

To read a byte form the EEPROM the address of where the data is stored is sent first. If the address is not sent then the last address is used.

constant I2CEE 0xa0

function ee_read(address)
dim t[1],hb%,lb%
    lb%=and(address,0xff)
    hb%=rshift(and(address,0xff00),8)
    i2cread I2CEE hb% lb%, t[1];1
    result t[1]
endf

This particular function will read a byte form the requested address. It is important to note here where the comma’s are place in the i2cread command. The high and low bytes of the address are extracted and sent following the I2C address (no commas). The fist comma denotes the start of reading and so the array follows that.

A Page can be read quite easily by increasing the dimension of t[127] and replacing t[1];1 with t[1];127. The array will now fill with bytes from 1 to 127. There are some other considerations though and these are interesting to discuss.

function ee_read(address)
dim t&[127],hb%,lb%
    ee_init
    lb%=and(address,0xff)
    hb%=rshift(and(address,0xff00),8)
    i2cread I2CEE hb% lb%, t&[1];127
    result adr(t&[1])
endf

First using an integer array is not suitable for this application as each element in the array occupies 4 bytes (32 bits), this is a bit wasteful  so a character array is used instead. A character array is defined by using ‘&’ as a postfix to the array name.

The next problem is how to get back the results of the array. Two approaches could be used here and this depends on what is to be done with the data when it gets back. The first is to define a global array and fill that but the second method used in the above is to return the address of the first element of the character array, this will be effectively a pointer to the whole array.

To see the returned results use:

dump ee_read 0

The dump statement will show the contents of the address returned. As an example of actually using the data, to get say the 6th element of the array:

x%=ee_read
0 e6=peek(x%+6)

As we are dealing with the address then peek will obtain the contents of the address. To complement this, the ee_write can be modified to:

function ee_write$(address, v$)
dim hb%,lb%
    lb%=and(address,0xff)
    hb%=rshift(and(address,0xff00),8)
    i2cwrite I2CEE hb%,lb%,v$
endf

Instead of 'ee_write' accepting a single value it will now accept a string of data. This will only work up to a point as the 24LC512 and devices like it will only write to specific pages and this must be taken into account. It is not shown here as this varies from device to device.