Tuto. 11 - I2C extension
PIC tutorial 11 - I2C extension
Fig. 1a Board 4 with I2C expander, 8 leds output and 4leds/4switches boards
Description:
In the 11th tutorial, we'll cover more I2C communication, with 8 bits extensions.
The extension can be provided as a single 8 bits bus (I2C 8 bits with PCF8574) or as 2 8 bits ports (I2C expander with MCP23016).
You will need a processor board, a power supply, the I2C 8bits or I2C expander, 8 leds output and 4leds/4switches boards if using the I2C expander.
The tutorial will display a counter on the 8 leds on the first extension port. The optional 2nd port, if using the I2C expander, is used to connect a board with 4 leds and 4 switches. When a switch is pressed, the corresponding led will light up.
The I2C 8 bits board does not require a specific library, it is basic input/output over the I2C bus. The I2C expander needs a library to manage its various functions.
The boards can be powered by one of the PSU boards (powered from USB port), or between 2.5 to 5V.
Requirements:
Processor boardAny board (board 4 in example)
Extension(s)I2C expander or I2C 8 bits
Leds output
4leds/4switches if using I2C expander
Power supplyPSU 1, 2 or 3. Depends on board used.
LanguageCCS C
ProgrammerPicKIT 2
Program:
#define board_id 4
#define I2C_IO_Chip 2 // 1: I2C 8 bits (PCF8574), 2: I2C expander (MCP23016)
#include "lib\mainboard.c"
#include "lib\i2c_lib.c"
#if (I2C_IO_Chip == 2 )
#include "lib\MCP23016_lib.c"
#endif
#priority RTCC
unsigned char I2Cout=0; // Values for counter
#if (I2C_IO_Chip == 2 )
long I2CExp_in; // 16 bits int to read MCP23016
unsigned char tmp;
#endif
#int_rtcc
void Timer0Interrupt ( void ) {
I2Cout++;
}
void main() {
boot_up();
#if (I2C_IO_Chip == 2 ) // if I2C expander, initialise it and set IO direction
init_23016(); // initialise MCP23016
config_io_23016(0x00, 0xf0); // Port 0 as output, upper nibble of port 1 as input, lower as output
#endif
setup_timer_0 ( RTCC_INTERNAL | RTCC_DIV_256 | RTCC_8_BIT ); // Setup timer 0
enable_interrupts ( INT_RTCC ); // enable Timer0 interrupt
enable_interrupts ( GLOBAL ); // enable all interrupts
while (true) {
#if (I2C_IO_Chip == 2 ) // ifI2C expander, read switches and update outputs
I2CExp_in = read_23016(_23016_GP); // Reads MCP23016
tmp = (I2CExp_in >> 8); // keep the upper nibble of port 1
tmp = tmp >> 4; // and put it into lower nibble of tmp to write back to MCP23016 for led output
tmp ^= 0x0f;
write_23016(_23016_GP, I2Cout, tmp); // update MCP23016 with counter and key status
#else // else just write counter to I2C chip
i2c_start();
i2c_write(I2C_8bits);
i2c_write(I2Cout);
i2c_stop();
#endif
}
}
The program defines the processor board used and the type of I2Cextension used (1 or 2 for PCF8574 or MCP23016). It is defined intially as 3, so we'll use the I2C expander.
We then loads the libraries for the processor and the I2C library. If the expander is selected, it also loads its library. The I2C 8 bits does not need a dedicated library.The I2C library loads the list of slave addresses and determines how the I2C interface is implemented on the processor board. Some processors have a hardware I2C interface, some don't. If the processor has a hardware interface, it is used, else, the I2C protocol is emulated by software.
Interrupts from timer 0 will be used to update the counter to display on the leds. The timer 0 interrupt routine just increment that counter.
Once all libraries are loaded, variables declared and interrupt routine ready, the processor board is initialised.
If the I2C expander is used, it is initialised too, and its ports pins are defined as input (1) or output (0). Port 0 is used to display the counter on the leds, so it is set as outputs. Port 1 has the 4 upper bits as inputs for switches, and the 4 lower as outputs for leds.
Timer 0 is setup with internal clock, divided by 256, and as 8 bits counter (it can be 16 bits on 18F family).
The interrupts are initialised and the lopp is started.
If the I2C expander is used, we first read its port to get the status of the switches. The 4 upper bits of P1 are kept and inverted so we can output the status of the switches on the leds. (When a switch is not pressed, it returns 1). We can then update the GP register of the MCP23016 with the counter for port 0 and the status of the switches for port 1.
If the I2C 8 bits board is used, no need to read any switches. The counter is just wrote with generic I2C function to the PCF8574 chip.
The loop is restarted undefinitely.
The library for the MCP23016 is described below:
#ifndef _I2C_LIB
#error "I2C library not loaded"
#else
#ifndef _23016_LIB
#define _23016_LIB
#define _23016_GP 0x00
#define _23016_OLAT 0x02
#define _23016_IPOL 0x04 // INPUT POLARITY PORT REGISTER
#define _23016_IODIR 0x06 // I/O DIRECTION REGISTER
#define _23016_INTCAP 0x08 // INTERRUPT CAPTURE REGISTER
#define _23016_IOCON 0x0A // I/O EXPANDER CONTROL REGISTER
void init_23016(void);
void config_io_23016(unsigned char dirGP0, unsigned char dirGP1);
void write_23016(unsigned char reg, unsigned char dataGP0, unsigned char dataGP1);
long read_23016(unsigned char reg);
void init_23016() { // Wait for MCP23016 Expander Device to power-up.
delay_ms(750);
write_23016(_23016_IPOL, 0x00, 0x00); // NonInvert all input polarities
write_23016(_23016_IOCON, 0x00, 0x00); // IARES to normal sample rate.
write_23016(_23016_OLAT,0xFF,0xFF); // initiallize the ouput latch
}
void config_io_23016(unsigned char dirGP0, unsigned char dirGP1) {
write_23016(_23016_IODIR, dirGP0, dirGP1); // Update IO direction register (1=In, 0=Out).
}
void write_23016(unsigned char reg, unsigned char dataGP0, unsigned char dataGP1) {
i2c_start();
delay_us(12);
i2c_write(I2C_IOexp);
delay_us(12);
i2c_write(reg); // point to register
delay_us(12);
i2c_write(dataGP0);
delay_us(12);
i2c_write(dataGP1);
delay_us(12);
i2c_stop();
}
long read_23016(unsigned char reg) {
unsigned char lsb, msb;
i2c_start();
delay_us(12);
i2c_write(I2C_IOexp);
delay_us(12);
i2c_write(reg); // point to register
delay_us(12);
i2c_start(); // restart before read command
delay_us(12);
i2c_write(I2C_IOexp+1);
delay_us(12);
lsb = i2c_read(1); // Data from LSB or MSB of register
delay_us(12);
msb = i2c_read(0); // Data from LSB or MSB of register
delay_us(12);
i2c_stop();
return(make16(msb,lsb));
}
#endif
#endif
Fig. 1b Board 3 with I2C 8 bits and 8 leds output boards
First we check that the I2C library is loaded and define variables used to point to various registers of the MCP23016 chip.
The library waits 12uS after each I2C operation to wait for the chip to be ready again. Another solution would be to wait for the chip to be online again, like in tutorial 9 with the I2C eeprom.
The functions available are:
- init_23016: waits for the chip to power up and initialise various registers.
- config_io_23016: configure the direction of each pins of the 2 ports (1=input, 0=output).
- write_23016: sends 2 bytes to the specified register.
- read_23016: returns the values of port 0 and port 1 a 16 bits integer.
Compile the project and program the target with a PicKIT 2 programmer for example. Connect the processor board to the I2C extension on the I2C port, the 8 leds output on port 0 of the I2C expander or the only port on the I2C 8 bits board, and optionnaly the 4 leds / 4 switches board on port 1 of the I2C expander. Make sure the jumpers on the board match the slave address defined in I2C_Addr.h.
Power up the boards.
The 8 leds will start blinking, and if you press a switch, the led beside it will light up.
To change extension type, just define I2C_IO_Chip as 1 and recompile. The program is then set for the I2C 8 bits board.
Files and links:
Tutorial 11 source files.Processor board 4.
8 leds output board.
4 leds / 4 switches board.
I2C 8 bits board.
I2C expander.

