Subversion Repositories Code-Repo

Rev

Rev 114 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "msg_queues.h"
#include "maindefs.h"
//#include <i2c.h>
#include "i2c.h"

#pragma udata i2c_data
unsigned char i2c_buffer[MAXI2CBUF];
#pragma udata
I2C_DATA *i2c_pdata;

// Set up the data structures for the i2c code
// Should be called once before any i2c routines are called
void i2c_init(I2C_DATA *data) {
    i2c_pdata = data;
    i2c_pdata->buffer = i2c_buffer;
    i2c_pdata->buflen = 0;
    i2c_pdata->slave_event_count = 0;
    i2c_pdata->status = I2C_IDLE;
    i2c_pdata->slave_error_count = 0;
    i2c_pdata->bufind = 0;
    i2c_pdata->buflen = 0;
    i2c_pdata->slave_in_last_byte = 0;
    i2c_pdata->slave_sending_data = 0;
    i2c_pdata->slave_sending_blank_data = 0;
    i2c_pdata->mode = 0;
    i2c_pdata->master_dest_addr = 0;
    i2c_pdata->master_state = I2C_MASTER_IDLE;
}

// Setup the PIC to operate as a master.
void i2c_configure_master() {
    i2c_pdata->mode = I2C_MODE_MASTER;

    TRISCbits.TRISC3 = 1;
    TRISCbits.TRISC4 = 1;

    SSPSTAT = 0x0;
    SSPCON1 = 0x0;
    SSPCON2 = 0x0;
    SSPCON1bits.SSPM = 0x8; // I2C Master Mode
    SSPADD = 0x63;          // Operate at 100KHz (40MHz)
    SSPSTATbits.SMP = 1;    // Disable Slew Rate Control
    SSPCON1bits.SSPEN = 1;  // Enable MSSP Module
}

// Sends length number of bytes in msg to specified address (no R/W bit)
void i2c_master_send(unsigned char address, unsigned char length, unsigned char *msg) {
    int i;
    if (length == 0)
        return;
    
    // Copy message to send into buffer and save length/address
    for (i = 0; i < length; i++) {
        i2c_pdata->buffer[i] = msg[i];
    }
    i2c_pdata->buflen = length;
    i2c_pdata->master_dest_addr = address;
    i2c_pdata->bufind = 0;

    // Change status to 'next' operation
    i2c_pdata->status = I2C_SEND_ADDR;
    i2c_pdata->master_state = I2C_MASTER_SEND;
    
    // Generate start condition
    SSPCON2bits.SEN = 1;
}

// Reads length number of bytes from address (no R/W bit)
void i2c_master_recv(unsigned char address, unsigned char length) {
    if (length == 0)
        return;

    // Save length and address to get data from
    i2c_pdata->buflen = length;
    i2c_pdata->master_dest_addr = address;
    i2c_pdata->bufind = 0;

    // Change status to 'next' operation
    i2c_pdata->status = I2C_SEND_ADDR;
    i2c_pdata->master_state = I2C_MASTER_RECV;
    
    // Generate start condition
    SSPCON2bits.SEN = 1;
}

// Setup the PIC to operate as a slave. The address must not include the R/W bit
void i2c_configure_slave(unsigned char addr) {
    i2c_pdata->mode = I2C_MODE_SLAVE;
    
    // Ensure the two lines are set for input (we are a slave)
    TRISCbits.TRISC3 = 1;
    TRISCbits.TRISC4 = 1;

    SSPADD = addr << 1;     // Set the slave address

    SSPSTAT = 0x0;
    SSPCON1 = 0x0;
    SSPCON2 = 0x0;
    SSPCON1bits.SSPM = 0xE; // Enable Slave 7-bit w/ start/stop interrupts
    SSPSTATbits.SMP = 1;    // Slew Off
    SSPCON2bits.SEN = 1;    // Enable clock-stretching
    SSPCON1bits.SSPEN = 1;  // Enable MSSP Module
}

void i2c_interrupt_handler() {
    // Call interrupt depending on which mode we are operating in
    if (i2c_pdata->mode == I2C_MODE_MASTER) {
        i2c_interrupt_master();
    } else if (i2c_pdata->mode == I2C_MODE_SLAVE) {
        i2c_interrupt_slave();
    }
}

// An internal subroutine used in the master version of the i2c_interrupt_handler
void i2c_interrupt_master() {
    // If we are in the middle of sending data
    if (i2c_pdata->master_state == I2C_MASTER_SEND) {
        switch (i2c_pdata->status) {
            case I2C_IDLE:
                break;
            case I2C_SEND_ADDR:
                // Send the address with read bit set
                i2c_pdata->status = I2C_CHECK_ACK;
                SSPBUF = (i2c_pdata->master_dest_addr << 1) | 0x0;
                break;
            case I2C_CHECK_ACK:
                // Check if ACK is received or not
                if (!SSPCON2bits.ACKSTAT) {
                    // If an ACK is received, send next byte of data
                    if (i2c_pdata->bufind < i2c_pdata->buflen) {
                        SSPBUF = i2c_pdata->buffer[i2c_pdata->bufind];
                        i2c_pdata->bufind++;
                    } else {
                        // If no more data is to be sent, send stop bit
                        i2c_pdata->status = I2C_IDLE;
                        SSPCON2bits.PEN = 1;
                        i2c_pdata->master_state = I2C_MASTER_IDLE;
                        MQ_sendmsg_ToMainFromHigh(0, MSGTYPE_I2C_MASTER_SEND_COMPLETE, (void *) 0);
                    }
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_pdata->status = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_pdata->master_state = I2C_MASTER_IDLE;
                    MQ_sendmsg_ToMainFromHigh(0, MSGTYPE_I2C_MASTER_SEND_FAILED, (void *) 0);
                }
                break;
        }
    // If we are in the middle of receiving data
    } else if (i2c_pdata->master_state == I2C_MASTER_RECV) {
        switch (i2c_pdata->status) {
            case I2C_IDLE:
                break;
            case I2C_SEND_ADDR:
                // Send address with write bit set
                i2c_pdata->status = I2C_CHECK_ACK;
                SSPBUF = (i2c_pdata->master_dest_addr << 1) | 0x1;
                break;
            case I2C_CHECK_ACK:
                // Check if ACK is received
                if (!SSPCON2bits.ACKSTAT) {
                    // If an ACK is received, set module to receive 1 byte of data
                    i2c_pdata->status = I2C_RCV_DATA;
                    SSPCON2bits.RCEN = 1;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_pdata->status = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_pdata->master_state = I2C_MASTER_IDLE;
                    MQ_sendmsg_ToMainFromHigh(0, MSGTYPE_I2C_MASTER_RECV_FAILED, (void *) 0);
                }
                break;
            case I2C_RCV_DATA:
                // On receive, save byte into buffer
                i2c_pdata->buffer[i2c_pdata->bufind] = SSPBUF;
                i2c_pdata->bufind++;
                if (i2c_pdata->bufind < i2c_pdata->buflen) {
                    // If we still need to read, send an ACK to the slave
                    i2c_pdata->status = I2C_REQ_DATA;
                    SSPCON2bits.ACKDT = 0;  // ACK
                    SSPCON2bits.ACKEN = 1;
                } else {
                    // If we are done reading, send an NACK to the slave
                    i2c_pdata->status = I2C_SEND_STOP;
                    SSPCON2bits.ACKDT = 1;  // NACK
                    SSPCON2bits.ACKEN = 1;
                }
                break;
            case I2C_REQ_DATA:
                // Set module to receive one byte of data
                i2c_pdata->status = I2C_RCV_DATA;
                SSPCON2bits.RCEN = 1;
                break;
            case I2C_SEND_STOP:
                // Send the stop bit and copy message to send to Main()
                i2c_pdata->status = I2C_IDLE;
                SSPCON2bits.PEN = 1;
                i2c_pdata->master_state = I2C_MASTER_IDLE;
                MQ_sendmsg_ToMainFromHigh(i2c_pdata->buflen, MSGTYPE_I2C_MASTER_RECV_COMPLETE, (void *) i2c_pdata->buffer);
                break;
        }
    }
}

// An internal subroutine used in the slave version of the i2c_interrupt_handler
void i2c_handle_start(unsigned char data_read) {
    i2c_pdata->slave_event_count = 1;
    i2c_pdata->buflen = 0;

    // Check to see if we also got the address
    if (data_read) {
        if (SSPSTATbits.D_A == 1) {
            DBG_PRINT_I2C("I2C Start: (ERROR) no address recieved\r\n");
            // This is bad because we got data and we wanted an address
            i2c_pdata->status = I2C_IDLE;
            i2c_pdata->slave_error_count++;
            i2c_pdata->slave_error_code = I2C_ERR_NOADDR;
        } else {
            if (SSPSTATbits.R_W == 1) {
                i2c_pdata->status = I2C_SEND_DATA;
            } else {
                i2c_pdata->status = I2C_RCV_DATA;
            }
        }
    } else {
        i2c_pdata->status = I2C_STARTED;
    }
}

void i2c_interrupt_slave() {
    unsigned char i2c_data;
    unsigned char data_read_from_buffer = 0;
    unsigned char data_written_to_buffer = 0;
    unsigned char msg_send_data_to_main = 0;
    unsigned char overrun_error = 0;
    unsigned char error_buf[3];
    unsigned char msgtype = 0;

    // Clear SSPOV (overflow bit)
    if (SSPCON1bits.SSPOV == 1) {
        DBG_PRINT_I2C("I2C: overflow detected\r\n");
        SSPCON1bits.SSPOV = 0;
        // We failed to read the buffer in time, so we know we
        //  can't properly receive this message, just put us in the
        //  a state where we are looking for a new message
        i2c_pdata->status = I2C_IDLE;
        overrun_error = 1;
        i2c_pdata->slave_error_count++;
        i2c_pdata->slave_error_code = I2C_ERR_OVERRUN;
    }

    // Read SPPxBUF if it is full
    if (SSPSTATbits.BF == 1) {
        i2c_data = SSPBUF;
        DBG_PRINT_I2C("I2C: data read from buffer: %x\r\n", SSPBUF);
        data_read_from_buffer = 1;
    }

    if (!overrun_error) {
        switch (i2c_pdata->status) {
            case I2C_IDLE:
            {
                // Ignore anything except a start
                if (SSPSTATbits.S == 1) {
                    i2c_handle_start(data_read_from_buffer);
                    // If we see a slave read, then we need to handle it here
                    if (i2c_pdata->status == I2C_SEND_DATA) {
                        // Return the first byte (message id)
                        SSPBUF = 0x3;
                    }
                }
                break;
            }
            case I2C_STARTED:
            {
                // In this case, we expect either an address or a stop bit
                if (SSPSTATbits.P == 1) {
                    // We need to check to see if we also read an address (a message of length 0)
                    i2c_pdata->slave_event_count++;
                    if (data_read_from_buffer) {
                        if (SSPSTATbits.D_A == 0) {
                            msg_send_data_to_main = 1;
                        } else {
                            DBG_PRINT_I2C("I2C: (ERROR) no data recieved\r\n");
                            i2c_pdata->slave_error_count++;
                            i2c_pdata->slave_error_code = I2C_ERR_NODATA;
                        }
                    }
                    // Return to idle mode
                    i2c_pdata->status = I2C_IDLE;
                } else if (data_read_from_buffer) {
                    i2c_pdata->slave_event_count++;
                    if (SSPSTATbits.D_A == 0) {
                        if (SSPSTATbits.R_W == 0) {  // Slave write
                            i2c_pdata->status = I2C_RCV_DATA;
                        } else {    // Slave read
                            i2c_pdata->status = I2C_SEND_DATA;
                            // Return the first byte (message id)
                            SSPBUF = 0x3;
                        }
                    } else {
                        DBG_PRINT_I2C("I2C: (ERROR) no data recieved\r\n");
                        i2c_pdata->slave_error_count++;
                        i2c_pdata->status = I2C_IDLE;
                        i2c_pdata->slave_error_code = I2C_ERR_NODATA;
                    }
                }
                break;
            }
            case I2C_SEND_DATA:
            {
                // If we arnt current in the middle of sending data, check to see
                //  if there is a message in the queue to send
                if (!i2c_pdata->slave_sending_data) {
                    // Check the message type of the next message in queue
                    msgtype = MQ_peek_FromMainToHigh();
                    if (msgtype != MSGTYPE_I2C_REPLY || msgtype == 0) {
                        // If the message queue is empty or to another interrupt processor, return 0xFF
                        DBG_PRINT_I2C("I2C: Returning 0xFF [%d:%d]\r\n", 0, i2c_pdata->slave_in_last_byte-1);
                        SSPBUF = 0xFF;
                        i2c_pdata->bufind = 1;
                        i2c_pdata->slave_sending_data = 1;
                        i2c_pdata->slave_sending_blank_data = 1;
                        data_written_to_buffer = 1;
                    } else {
                        i2c_pdata->buflen = MQ_recvmsg_FromMainToHigh(MSGLEN, (unsigned char *)i2c_pdata->slave_outbufmsgtype, (void *) i2c_pdata->buffer);
//                        DBG_PRINT_I2C("%x\r\n",i2c_ptr->buffer[0]);
//                        DBG_PRINT_I2C("I2C: buffer Message Length: %d\r\n",i2c_ptr->outbuflen);
                        if (i2c_pdata->buflen > 0) {
                            // Otherwise return the first byte of data
                            DBG_PRINT_I2C("I2C: Returning %x [%d,%d]\r\n", i2c_pdata->buffer[0], 0, i2c_pdata->buflen-1);
                            SSPBUF = i2c_pdata->buffer[0];
                            i2c_pdata->bufind = 1;
                            i2c_pdata->slave_sending_data = 1;
                            data_written_to_buffer = 1;
                        } else {
                            DBG_PRINT_I2C("I2C: (ERROR) Unexpected msg in queue, type = %x\r\n", i2c_pdata->slave_outbufmsgtype);
                        }
                    }
                } else if (i2c_pdata->slave_sending_blank_data) {
                    // If we are currently sending 0xFFs back, keep sending for the requested number of bytes
                    if (i2c_pdata->bufind < i2c_pdata->slave_in_last_byte) {
                        DBG_PRINT_I2C("I2C: Returning 0xFF [%d:%d]\r\n", i2c_pdata->bufind, i2c_pdata->slave_in_last_byte-1);
                        SSPBUF = 0xFF;
                        i2c_pdata->bufind++;
                        data_written_to_buffer = 1;
                    } else {
                        // We have nothing left to send
                        i2c_pdata->slave_sending_data = 0;
                        i2c_pdata->slave_sending_blank_data = 0;
                        i2c_pdata->status = I2C_IDLE;
                    }
                } else {
                    // Otherwise keep sending back the requested data
                    if (i2c_pdata->bufind < i2c_pdata->buflen) {
                        DBG_PRINT_I2C("I2C: Returning %x [%d,%d]\r\n", i2c_pdata->buffer[i2c_pdata->bufind], i2c_pdata->bufind, i2c_pdata->buflen-1);
                        SSPBUF = i2c_pdata->buffer[i2c_pdata->bufind];
                        i2c_pdata->bufind++;
                        data_written_to_buffer = 1;
                    } else {
                        // We have nothing left to send
                        i2c_pdata->slave_sending_data = 0;
                        i2c_pdata->status = I2C_IDLE;
                    }
                }
                break;
            }
            case I2C_RCV_DATA:
            {
                // We expect either data or a stop bit or a (if a restart, an addr)
                if (SSPSTATbits.P == 1) {
                    // We need to check to see if we also read data
                    i2c_pdata->slave_event_count++;
                    if (data_read_from_buffer) {
                        if (SSPSTATbits.D_A == 1) {
                            i2c_pdata->buffer[i2c_pdata->buflen] = i2c_data;
                            i2c_pdata->buflen++;
                            msg_send_data_to_main = 1;
                        } else {
                            DBG_PRINT_I2C("I2C: (ERROR) no data recieved\r\n");
                            i2c_pdata->slave_error_count++;
                            i2c_pdata->slave_error_code = I2C_ERR_NODATA;
                            i2c_pdata->status = I2C_IDLE;
                        }
                    } else {
                        msg_send_data_to_main = 1;
                    }
                    i2c_pdata->status = I2C_IDLE;
                } else if (data_read_from_buffer) {
                    i2c_pdata->slave_event_count++;
                    if (SSPSTATbits.D_A == 1) {
                        i2c_pdata->buffer[i2c_pdata->buflen] = i2c_data;
                        i2c_pdata->buflen++;
                    } else /* a restart */ {
                        if (SSPSTATbits.R_W == 1) {
                            i2c_pdata->status = I2C_SEND_DATA;
                            msg_send_data_to_main = 1;
                            // Return the first byte (message id)
                            SSPBUF = 0x3;

                        } else { // Bad to recv an address again, we aren't ready
                            DBG_PRINT_I2C("I2C: (ERROR) no data recieved\r\n");
                            i2c_pdata->slave_error_count++;
                            i2c_pdata->slave_error_code = I2C_ERR_NODATA;
                            i2c_pdata->status = I2C_IDLE;
                        }
                    }
                }
                break;
            }
        }
    }

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

    // Must check if the message is too long
    if ((i2c_pdata->buflen > MAXI2CBUF - 2) && (!msg_send_data_to_main)) {
        DBG_PRINT_I2C("I2C: (ERROR) message too long\r\n");
        i2c_pdata->status = I2C_IDLE;
        i2c_pdata->slave_error_count++;
        i2c_pdata->slave_error_code = I2C_ERR_MSGTOOLONG;
    }

    if (msg_send_data_to_main) {
        DBG_PRINT_I2C("I2C: sending message to main()\r\n");
        i2c_pdata->slave_in_last_byte = i2c_pdata->buffer[i2c_pdata->buflen-1];
        i2c_pdata->buffer[i2c_pdata->buflen] = i2c_pdata->slave_event_count;
        MQ_sendmsg_ToMainFromHigh(i2c_pdata->buflen + 1, MSGTYPE_I2C_DATA, (void *) i2c_pdata->buffer);
        i2c_pdata->buflen = 0;
    } else if (i2c_pdata->slave_error_count >= I2C_ERR_THRESHOLD) {
        DBG_PRINT_I2C("I2C: (ERROR) error threshold passed\r\n");
        error_buf[0] = i2c_pdata->slave_error_count;
        error_buf[1] = i2c_pdata->slave_error_code;
        error_buf[2] = i2c_pdata->slave_event_count;
        MQ_sendmsg_ToMainFromHigh(sizeof (unsigned char) *3, MSGTYPE_I2C_DBG, (void *) error_buf);
        i2c_pdata->slave_error_count = 0;
    }
}

unsigned char i2c_master_busy() {
    if (i2c_pdata->master_state == I2C_MASTER_IDLE) {
        return 0;
    } else {
        return 1;
    }
}