Electronic – Implementing an I2C buffer in C


I'm implementing a read-only I2C slave on a PIC18F4620. I have made a -working- ISR handler for the MSSP module:

unsigned char dataFromMaster;

unsigned char SSPISR(void) {
    unsigned char temp = SSPSTAT & 0x2d;
    if ((temp ^ 0x09) == 0x00) {
        // State 1: write operation, last byte was address
        return 1;
    } else if ((temp ^ 0x29) == 0x00) { 
        // State 2: write operation, last byte was data
        dataFromMaster = ReadI2C();
        return 2;
    } else if (((temp & 0x2c) ^ 0x0c) == 0x00) {
        // State 3: read operation, last byte was address
        return 3;
    } else if (!SSPCON1bits.CKP) {
        // State 4: read operation, last byte was data
        return 4;
    } else {                                        
        // State 5: slave logic reset by NACK from master
        return 5;

This is just a port to C from a part of the ASM code in appendix B of AN734.

In my main loop, I'm checking if there is new data, like this:

void main(void) {
    if (dataFromMaster != 0x00) {
        dataFromMaster = 0x00;

This gives a problem when the master sends bytes very fast, and new data comes in before the main loop gets to doSomething. I therefore want to implement a buffer where data from the master is stored. I need a 16-char null-terminated array (the null won't be used as a command for the slave). The ISR has to write new data to that array, and the main loop should read it from the array in the order it was received, and clear the array.

I have no idea how to implement this. Do you?

Best Answer

I have no experience with PIC, but the problem seems generic enough. I would create a simple array with two independent pointers into the array: one read pointer and one write pointer. Whenever you receive a byte, you increment the write pointer and write at the new position; in your main loop you could then check if the read pointer and the write pointer are the same. If not, you simply read and process from the buffer and increase the read pointer for every byte until they are.

You could then either reset the pointers to the beginning of the array, or let them "flow over" to the beginning making essentially a circular buffer. This is easiest if the size of the array is a factor of 2 as you can then simply bitmask both pointers after their increments.

Some example (pseudo)code:

volatile unsigned int readPointer= 0;
volatile unsigned int writePointer=0;
volatile char theBuffer[32];
//in your ISR
writePointer = (writePointer+1) & 0x1F;
theBuffer[writePointer] = ReadI2C(); // assuming this is the easiest way to do it
                                     // I would probably just read the register directly
//in main
while (readPointer != writePointer) {
  readPointer = (readPointer+1) & 0x1F;
  nextByte = theBuffer[readPointer];
  // do whatever necessary with nextByte