A Circular Reference

Adventures of a vagabond electron.

A Simple I2C Driver for PIC32, PIC24 and PIC18

| Comments

Introduction

The Microchip I2C drivers and their APIs that ship with the MPLAB compiler are almost impossible to debug or understand. For some unfathomable reason, they have distributed the I2C access code in 10 (or more) different files with 5 lines of code each. So I’ve managed to put together a simple (and what I consider to be intuitive) driver. I have tested it with different slaves on PIC18, PIC24 and PIC32, and it seems to work seamlessly with only a change in .h file #defines for PIC24 and PIC32, while for PIC18 some register names are different.

Before you start, you should be aware that the PIC I2C engine cannot queue commands. So you have to wait in software for each command to be completed before issuing the next one. For example, you issue the start condition command then wait till the engine is not busy before issuing the next command.

Details

The driver implements the following interfaces:

  • Reading from the I2C peripheral:
1
2
3
BOOL ReadRegisterLoop(UINT8 reg, UINT8* rxPtr, UINT8 len) //(PIC18)

BOOL drvI2CReadRegisters(UINT8 reg, UINT8* rxPtr, UINT8 len, UINT8 slave_adr) //(PIC24/32)

This Writes the Regiser Number (reg) on to the slave, and then issues continuous Reads for a total of len times. Typically the slave will just have one byte of data per resister, in which case you just have to pass len = 1. If the slave stores more than one byte in a perticular “register” location, you can read out that too. All the data is copied to the rxPtr. slave_adr is the 7-bit I2C address of the slave device. Internally, the function also waits for some time (100 iterations of a loop) for the slave to generate an ACK, in case it is slow to respond. You can modify this to suit your peripheral.

  • Writing to the I2C peripheral
1
2
3
BOOL WriteRegLoop(UINT8 reg, UINT8* rxPtr, UINT8 len) //(PIC18)

BOOL drvI2CWriteRegisters(UINT8 adr, UINT8* data, UINT8 len, UINT8 slave_adr)  //(PIC24/32)

Write is a bit simpler than the Read. You write the resigter number you want to write to, wait and repeat if there is no ACK. Once you get an ACK, just dump the data byte by byte on the poor old slave.

For most I2C slaves, this is all the access you need.

To port the driver, change the register mapping section in the .h file, along with the Slave address, and then configure the correct baud rate. These sections are highlighted below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define TPI2CCON    I2C1CON
#define TPCONbits   I2C1CONbits
#define TPI2CSTAT   I2C1STAT
#define TPSTATbits  I2C1STATbits
#define TPI2CMSK     I2C1MSK
#define TPI2CRCV     I2C1RCV
#define TPI2CTRN     I2C1TRN
#define TPI2CADD     I2C1ADD
#define TPI2CBRG    I2C1BRG

#define C_TP_ADDR   0x3E
#define C_TP_READ   0x7D
#define C_TP_WRITE  0x7C

// calculate baud rate of I2C
#define Fosc       (GetSystemClock())
#define Fcy        (Fosc/2)
#define Fsck       100000
#define I2C_BRG    ((GetPeripheralClock()/(2*Fsck))-1)

The code can be downloaded here. The PIC18 driver includes code to communicate with MLX90614 Infra Red Temperature Sensor and implements the SMBus protocol including the calculation of CRC. So you can use it to modify all of the MLX registers, not just read temperature.

An example of the usage is:

1
2
3
4
5
6
7
8
9
10
11
12
#define LIS_I2C_ADDRESS             0x19
#define LIS_ID_REG                  0x0F
#define LIS_CNTRL_REG1              0x20 

drvI2CInit();   // Initialize the i2c module

UART2PutString("Reading ID reg..");
drvI2CReadRegisters(LIS_ID_REG, &temp, 1, LIS_I2C_ADDRESS); // Read the ID register
UART2PutHex(temp);
UART2PutString(".. is the ID");

drvI2CWriteByte(LIS_CNTRL_REG1, 0x00, LIS_I2C_ADDRESS); // Write some register

Comments