Subversion Repositories Code-Repo

Rev

Rev 235 | Blame | Last modification | View Log | RSS feed

#include "defines.h"
#include "I2C1.h"

static I2C1_DATA *i2c_data_p;

// Initialize the data structures, should be called once before any I2C routines are called
void I2C1_Init(I2C1_DATA *data, uint8_t speed, uint8_t address) {
    i2c_data_p = data;

    i2c_data_p->buffer_in_len = 0;
    i2c_data_p->buffer_in_read_ind = 0;
    i2c_data_p->buffer_in_write_ind = 0;

    i2c_data_p->buffer_out_ind = 0;
    i2c_data_p->buffer_out_len = 0;

    i2c_data_p->operating_state = I2C1_IDLE;
    i2c_data_p->return_status = 0;

    i2c_data_p->slave_in_last_byte = 0;
    i2c_data_p->slave_sending_data = 0;

    i2c_data_p->master_dest_addr = 0;
    i2c_data_p->master_status = I2C1_MASTER_IDLE;

    INTDisableInterrupts();

    // Enable the I2C module and set the clock stretch enable bit
    // Note: Automatically overrides any other pin settings
    I2C1CONSET = 0x00008040;
    I2C1ADD = address;
    if (!speed) I2C1BRG = 0x05A;    // Operate at 400kHZ (80MHz)
    else        I2C1BRG = 0x186;    // Operate at 100kHZ (80MHz)
    IFS0CLR = 0xE0000000;   // Clear any existing events
    IPC6CLR = 0x00001F00;   // Reset priority levels
    IPC6SET = 0x00001500;   // Set IPL=5, Subpriority 1
    IEC0SET = 0xE0000000;   // Enable I2C1 interrupts

    INTEnableInterrupts();
}

// Sends length number of bytes in msg to specified address (no R/W bit)
// Will return status I2C1_SEND_OK or I2C1_SEND_FAIL
void I2C1_Master_Send(uint8_t address, uint8_t *msg, uint32_t length) {
    uint32_t i;
    if (length == 0)
        return;

    // Copy message to send into buffer and save length/address
    for (i = 0; i < length; i++) {
        i2c_data_p->buffer_in[i] = msg[i];
    }
    i2c_data_p->buffer_in_len = length;
    i2c_data_p->master_dest_addr = address;
    i2c_data_p->buffer_in_read_ind = 0;
    i2c_data_p->buffer_in_write_ind = 0;

    // Change status to 'next' operation
    i2c_data_p->operating_state = I2C1_SEND_ADDR;
    i2c_data_p->master_status = I2C1_MASTER_SEND;

    // Generate start condition
    I2C1CONbits.SEN = 1;
}

// Reads length number of bytes from address (no R/W bit)
// Will return status I2C1_RECV_OK or I2C1_RECV_FAIL
void I2C1_Master_Recv(uint8_t address, uint32_t length) {
    if (length == 0)
        return;

    // Save length and address to get data from
    i2c_data_p->buffer_in_len = length;
    i2c_data_p->master_dest_addr = address;
    i2c_data_p->buffer_in_read_ind = 0;
    i2c_data_p->buffer_in_write_ind = 0;

    // Change status to 'next' operation
    i2c_data_p->operating_state = I2C1_SEND_ADDR;
    i2c_data_p->master_status = I2C1_MASTER_RECV;

    // Generate start condition
    I2C1CONbits.SEN = 1;
}

// Writes msg to address then reads length number of bytes from address
// Will return status I2C1_SEND_FAIL or I2C1_RECV_FAIL or I2C1_RECV_OK
void I2C1_Master_Restart(uint8_t address, uint8_t msg, uint32_t length) {
    uint8_t c;
    if (length == 0) {
        c = msg;
        I2C1_Master_Send(address, &c, 1);
        return;
    }

    // Save length and address to get data from
    i2c_data_p->buffer_in[0] = msg;
    i2c_data_p->buffer_in_len = length;
    i2c_data_p->master_dest_addr = address;
    i2c_data_p->buffer_in_read_ind = 0;
    i2c_data_p->buffer_in_write_ind = 0;

    // Change status to 'next' operation
    i2c_data_p->operating_state = I2C1_SEND_ADDR;
    i2c_data_p->master_status = I2C1_MASTER_RESTART;

    // Generate start condition
    I2C1CONbits.SEN = 1;
}

void __ISR(_I2C_1_VECTOR, ipl5) __I2C_1_Interrupt_Handler(void) {
    // Bus collision event
    if (IFS0bits.I2C1BIF) {
        // TODO: Handle bus collision events here
        IFS0CLR = 0x20000000;
    }
    // Slave event
    if (IFS0bits.I2C1SIF) {
        I2C1_Interrupt_Slave();
        IFS0CLR = 0x40000000;
    }
    // Master event
    if (IFS0bits.I2C1MIF) {
        I2C1_Interrupt_Master();
        IFS0CLR = 0x80000000;
    }
}

// An internal subroutine used in the master version of the i2c_interrupt_handler
void I2C1_Interrupt_Master() {

    /* The PIC32 family has different master interrupts than the PIC8 family
     * Master mode operations that generate a slave interrupt are:
     * 1. Start condition
     * 2. Repeated start sequence
     * 3. Stop condition
     * 4. Data transfer byte received
     * 5. During a send ACK or NACK sequence to slave
     * 6. Data transfer byte transmitted
     * 7. During a slave-detected stop
     */

    if (I2C1STATbits.IWCOL == 1) {
        // TODO: Handle write collisions
        I2C1STATbits.IWCOL = 0;
    }

    // If we are in the middle of sending data
    if (i2c_data_p->master_status == I2C1_MASTER_SEND) {
        switch (i2c_data_p->operating_state) {
            case I2C1_IDLE:
                break;
            case I2C1_SEND_ADDR:
                // Send the address with read bit set
                i2c_data_p->operating_state = I2C1_CHECK_ACK_SEND;
                I2C1TRN = (i2c_data_p->master_dest_addr << 1) | 0x0;
                break;
            case I2C1_CHECK_ACK_SEND:
                // Check if ACK is received or not
                if (!I2C1STATbits.ACKSTAT) {
                    // If an ACK is received, send next byte of data
                    if (i2c_data_p->buffer_in_read_ind < i2c_data_p->buffer_in_len) {
                        I2C1TRN = i2c_data_p->buffer_in[i2c_data_p->buffer_in_read_ind];
                        i2c_data_p->buffer_in_read_ind++;
                    } else {
                        // If no more data is to be sent, send stop bit
                        i2c_data_p->operating_state = I2C1_STOPPED;
                        I2C1CONbits.PEN = 1;
                        i2c_data_p->return_status = I2C1_SEND_OK;
                    }
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C1_STOPPED;
                    I2C1CONbits.PEN = 1;
                    i2c_data_p->return_status = I2C1_SEND_FAIL;
                }
                break;
            case I2C1_STOPPED:
                i2c_data_p->operating_state = I2C1_IDLE;
                i2c_data_p->master_status = I2C1_MASTER_IDLE;
                break;
        }
    // If we are in the middle of receiving data
    } else if (i2c_data_p->master_status == I2C1_MASTER_RECV) {
        switch (i2c_data_p->operating_state) {
            case I2C1_IDLE:
                break;
            case I2C1_SEND_ADDR:
                // Send address with write bit set
                i2c_data_p->operating_state = I2C1_CHECK_ACK_RECV;
                I2C1TRN = (i2c_data_p->master_dest_addr << 1) | 0x1;
                break;
            case I2C1_CHECK_ACK_RECV:
                // Check if ACK is received
                if (!I2C1STATbits.ACKSTAT) {
                    // If an ACK is received, set module to receive 1 byte of data
                    i2c_data_p->operating_state = I2C1_RCV_DATA;
                    I2C1CONbits.RCEN = 1;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C1_STOPPED;
                    I2C1CONbits.PEN = 1;
                    i2c_data_p->return_status = I2C1_RECV_FAIL;
                }
                break;
            case I2C1_RCV_DATA:
                // On receive, save byte into buffer
                // TODO: Handle possible I2C buffer overflow
                i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = I2C1RCV;
                i2c_data_p->buffer_in_write_ind++;
                if (i2c_data_p->buffer_in_write_ind < i2c_data_p->buffer_in_len) {
                    // If we still need to read, send an ACK to the slave
                    i2c_data_p->operating_state = I2C1_REQ_DATA;
                    I2C1CONbits.ACKDT = 0;  // ACK
                    I2C1CONbits.ACKEN = 1;
                } else {
                    // If we are done reading, send an NACK to the slave
                    i2c_data_p->operating_state = I2C1_SEND_STOP;
                    I2C1CONbits.ACKDT = 1;  // NACK
                    I2C1CONbits.ACKEN = 1;
                }
                break;
            case I2C1_REQ_DATA:
                // Set module to receive one byte of data
                i2c_data_p->operating_state = I2C1_RCV_DATA;
                I2C1CONbits.RCEN = 1;
                break;
            case I2C1_SEND_STOP:
                // Send the stop bit
                i2c_data_p->operating_state = I2C1_STOPPED;
                I2C1CONbits.PEN = 1;
                i2c_data_p->return_status = I2C1_RECV_OK;
                break;
            case I2C1_STOPPED:
                i2c_data_p->operating_state = I2C1_IDLE;
                i2c_data_p->master_status = I2C1_MASTER_IDLE;
                break;
        }
    } else if (i2c_data_p->master_status == I2C1_MASTER_RESTART) {
        switch (i2c_data_p->operating_state) {
            case I2C1_IDLE:
                break;
            case I2C1_SEND_ADDR:
                // Send the address with read bit set
                i2c_data_p->operating_state = I2C1_CHECK_ACK_SEND;
                I2C1TRN = (i2c_data_p->master_dest_addr << 1) | 0x0;
                break;
            case I2C1_CHECK_ACK_SEND:
                // Check if ACK is received or not
                if (!I2C1STATbits.ACKSTAT) {
                    // If an ACK is received, send first byte of data
                    I2C1TRN = i2c_data_p->buffer_in[0];
                    i2c_data_p->operating_state = I2C1_CHECK_ACK_RESTART;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C1_STOPPED;
                    I2C1CONbits.PEN = 1;
                    i2c_data_p->return_status = I2C1_SEND_FAIL;
                }
                break;
            case I2C1_CHECK_ACK_RESTART:
                if (!I2C1STATbits.ACKSTAT) {
                    I2C1CONbits.RSEN = 1;
                    i2c_data_p->operating_state = I2C1_SEND_ADDR_2;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C1_STOPPED;
                    I2C1CONbits.PEN = 1;
                    i2c_data_p->return_status = I2C1_SEND_FAIL;
                }
                break;
            case I2C1_SEND_ADDR_2:
                // Send the address with read bit set
                i2c_data_p->operating_state = I2C1_CHECK_ACK_RECV;
                I2C1TRN = (i2c_data_p->master_dest_addr << 1) | 0x1;
                break;
            case I2C1_CHECK_ACK_RECV:
                // Check if ACK is received
                if (!I2C1STATbits.ACKSTAT) {
                    // If an ACK is received, set module to receive 1 byte of data
                    i2c_data_p->operating_state = I2C1_RCV_DATA;
                    I2C1CONbits.RCEN = 1;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C1_STOPPED;
                    I2C1CONbits.PEN = 1;
                    i2c_data_p->return_status = I2C1_RECV_FAIL;
                }
                break;
            case I2C1_RCV_DATA:
                // On receive, save byte into buffer
                // TODO: Handle possible I2C buffer overflow
                i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = I2C1RCV;
                i2c_data_p->buffer_in_write_ind++;
                if (i2c_data_p->buffer_in_write_ind < i2c_data_p->buffer_in_len) {
                    // If we still need to read, send an ACK to the slave
                    i2c_data_p->operating_state = I2C1_REQ_DATA;
                    I2C1CONbits.ACKDT = 0;  // ACK
                    I2C1CONbits.ACKEN = 1;
                } else {
                    // If we are done reading, send an NACK to the slave
                    i2c_data_p->operating_state = I2C1_SEND_STOP;
                    I2C1CONbits.ACKDT = 1;  // NACK
                    I2C1CONbits.ACKEN = 1;
                }
                break;
            case I2C1_REQ_DATA:
                // Set module to receive one byte of data
                i2c_data_p->operating_state = I2C1_RCV_DATA;
                I2C1CONbits.RCEN = 1;
                break;
            case I2C1_SEND_STOP:
                // Send the stop bit
                i2c_data_p->operating_state = I2C1_STOPPED;
                I2C1CONbits.PEN = 1;
                i2c_data_p->return_status = I2C1_RECV_OK;
                break;
            case I2C1_STOPPED:
                i2c_data_p->operating_state = I2C1_IDLE;
                i2c_data_p->master_status = I2C1_MASTER_IDLE;
                break;
        }
    }
}

void I2C1_Interrupt_Slave() {
    // !!WARNING!! THIS CODE DOES -NOT- HAVE ANY ERROR HANDLING !!
    // TODO: Add error handling to this interrupt function

    /* The PIC32 family has different slave interrupts than the PIC8 family
     * Slave mode operations that generate a slave interrupt are:
     * 1. Detection of a valid device address (including general call)
     * 2. Reception of data
     * 3. Request to transmit data
     */

    uint8_t received_data;
    uint8_t data_read_from_buffer = 0;
    uint8_t data_written_to_buffer = 0;
    uint8_t overrun_error = 0;

    // Clear SSPOV (overflow bit)
    if (I2C1STATbits.I2COV == 1) {
        I2C1STATbits.I2COV = 0;
        overrun_error = 1;
        i2c_data_p->return_status = I2C1_ERR_OVERRUN;
    }

    // Read SPPxBUF if it is full
    if (I2C1STATbits.RBF == 1) {
        received_data = I2C1RCV;
        data_read_from_buffer = 1;
    }

    if (!overrun_error) {
        if (I2C1STATbits.R_W == 0) {
            // Slave is receiving data
            i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = received_data;
            if (i2c_data_p->buffer_in_write_ind == MAXI2C1BUF - 1) {
                i2c_data_p->buffer_in_write_ind = 0;
            } else {
                i2c_data_p->buffer_in_write_ind++;
            }
            if (i2c_data_p->buffer_in_len < MAXI2C1BUF - 1) {
                i2c_data_p->buffer_in_len++;
            }
            i2c_data_p->slave_in_last_byte = received_data;
            i2c_data_p->return_status = I2C1_RECV_OK;
        } else {
            // Slave is returning data
            if (!i2c_data_p->slave_sending_data) {
                // If we are not currently sending data, figure out what to reply with
                if (I2C1_Process_Request(i2c_data_p->slave_in_last_byte)) {
                    // Data exists to be returned, send first byte
                    I2C1TRN = i2c_data_p->buffer_out[0];
                    data_written_to_buffer = 1;
                    i2c_data_p->buffer_out_ind = 1;
                    i2c_data_p->slave_sending_data = 1;
                } else {
                    // Unknown request, fill rest of request with 0s
                    I2C1TRN = 0x0;
                    data_written_to_buffer = 1;
                    i2c_data_p->slave_sending_data = 0;
                    i2c_data_p->return_status = I2C1_SEND_FAIL;
                }
            } else {
                // Sending remaining data back to master
                if (i2c_data_p->buffer_out_ind < i2c_data_p->buffer_out_len) {
                    I2C1TRN = i2c_data_p->buffer_out[i2c_data_p->buffer_out_ind];
                    data_written_to_buffer = 1;
                    i2c_data_p->buffer_out_ind++;
                } else {
                    // Nothing left to send, fill rest of request with 0s
                    I2C1TRN = 0x0;
                    data_written_to_buffer = 1;
                    i2c_data_p->slave_sending_data = 0;
                    i2c_data_p->return_status = I2C1_SEND_OK;
                }
            }
        }
    }

    // Release the clock stretching bit (if we should)
    if (data_read_from_buffer || data_written_to_buffer) {
        // Release the clock
        if (I2C1CONbits.SCLREL == 0) {
            I2C1CONbits.SCLREL = 1;
        }
    }
}

/* Returns 0 if I2C module is currently busy, otherwise returns status code */
uint8_t I2C1_Get_Status() {
        if (i2c_data_p->master_status == I2C1_MASTER_IDLE &&
                i2c_data_p->operating_state == I2C1_IDLE &&
                I2C1STATbits.TBF == 0) {
            return i2c_data_p->return_status;
        } else {
            return 0;
        }
}

uint8_t I2C1_Buffer_Len() {
    return i2c_data_p->buffer_in_len;
}

/* Returns 0 if I2C module is currently busy, otherwise returns buffer length */
uint8_t I2C1_Read_Buffer(uint8_t *buffer) {
    uint32_t i = 0;
    while (i2c_data_p->buffer_in_len != 0) {
        buffer[i] = i2c_data_p->buffer_in[i2c_data_p->buffer_in_read_ind];
        i++;
        if (i2c_data_p->buffer_in_read_ind == MAXI2C1BUF-1) {
            i2c_data_p->buffer_in_read_ind = 0;
        } else {
            i2c_data_p->buffer_in_read_ind++;
        }
        i2c_data_p->buffer_in_len--;
    }
    return i;
}

/* Put data to be returned here */
uint8_t I2C1_Process_Request(uint8_t c) {
    uint8_t ret = 0;
    switch (c) {
        case 0x01:
            i2c_data_p->buffer_out[0] = 0x12;
            i2c_data_p->buffer_out_len = 1;
            ret = 1;
            break;
        case 0x02:
            i2c_data_p->buffer_out[0] = 0x34;
            i2c_data_p->buffer_out[1] = 0x56;
            i2c_data_p->buffer_out_len = 2;
            ret = 1;
            break;
    }
    return ret;
}