Subversion Repositories Code-Repo

Rev

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

#include <xc.h>
#include <stdio.h>
#include <string.h>
#include "defines.h"
#include "i2c.h"
#include "uart.h"

static I2C_DATA *i2c_data_p;

// 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_data_p = data;
    
    i2c_data_p->buffer_in_len = 0;
    i2c_data_p->buffer_in_len_tmp = 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_mode = 0;
    i2c_data_p->operating_state = I2C_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 = I2C_MASTER_IDLE;
    
    // Enable I2C interrupt
    PIE1bits.SSPIE = 1;
}

// Setup the PIC to operate as a master.
void I2C_Configure_Master(char speed) {
    i2c_data_p->operating_mode = I2C_MODE_MASTER;

    I2C_CLK_TRIS = 1;
    I2C_DAT_TRIS = 1;

    SSPSTAT = 0x0;
    SSPCON1 = 0x0;
    SSPCON2 = 0x0;
    SSPCON1bits.SSPM = 0x8; // I2C Master Mode
    if (speed) {
        SSPADD = 0x74;          // Operate at 100KHz (48MHz)
    } else {
        SSPADD = 0x1A;          // Operate at 400KHz (48MHz)
    }
    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(char address, char length, char *msg) {
    char 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 = I2C_SEND_ADDR;
    i2c_data_p->master_status = I2C_MASTER_SEND;
    
    // Generate start condition
    SSPCON2bits.SEN = 1;
}

// Reads length number of bytes from address (no R/W bit)
void I2C_Master_Recv(char address, char 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 = I2C_SEND_ADDR;
    i2c_data_p->master_status = I2C_MASTER_RECV;
    
    // Generate start condition
    SSPCON2bits.SEN = 1;
}

// Writes msg to address then reads length number of bytes from address
void I2C_Master_Restart(char address, char msg, char length) {
    char c;
    if (length == 0) {
        c = msg;
        I2C_Master_Send(address, 1, &c);
        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 = I2C_SEND_ADDR;
    i2c_data_p->master_status = I2C_MASTER_RESTART;

    // 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(char addr) {
    i2c_data_p->operating_mode = I2C_MODE_SLAVE;

    // Ensure the two lines are set for input (we are a slave)
    I2C_CLK_TRIS = 1;
    I2C_DAT_TRIS = 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_data_p->operating_mode == I2C_MODE_MASTER) {
        I2C_Interrupt_Master();
    } else if (i2c_data_p->operating_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_data_p->master_status == I2C_MASTER_SEND) {
        switch (i2c_data_p->operating_state) {
            case I2C_IDLE:
                break;
            case I2C_SEND_ADDR:
                // Send the address with read bit set
                i2c_data_p->operating_state = I2C_CHECK_ACK_SEND;
                SSPBUF = (i2c_data_p->master_dest_addr << 1) | 0x0;
                break;
            case I2C_CHECK_ACK_SEND:
                // Check if ACK is received or not
                if (!SSPCON2bits.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) {
                        SSPBUF = 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 = I2C_IDLE;
                        SSPCON2bits.PEN = 1;
                        i2c_data_p->master_status = I2C_MASTER_IDLE;
                        i2c_data_p->return_status = I2C_SEND_OK;
                    }
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_data_p->master_status = I2C_MASTER_IDLE;
                    i2c_data_p->return_status = I2C_SEND_FAIL;
                }
                break;
        }
    // If we are in the middle of receiving data
    } else if (i2c_data_p->master_status == I2C_MASTER_RECV) {
        switch (i2c_data_p->operating_state) {
            case I2C_IDLE:
                break;
            case I2C_SEND_ADDR:
                // Send address with write bit set
                i2c_data_p->operating_state = I2C_CHECK_ACK_RECV;
                SSPBUF = (i2c_data_p->master_dest_addr << 1) | 0x1;
                break;
            case I2C_CHECK_ACK_RECV:
                // Check if ACK is received
                if (!SSPCON2bits.ACKSTAT) {
                    // If an ACK is received, set module to receive 1 byte of data
                    i2c_data_p->operating_state = I2C_RCV_DATA;
                    SSPCON2bits.RCEN = 1;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_data_p->master_status = I2C_MASTER_IDLE;
                    i2c_data_p->return_status = I2C_RECV_FAIL;
                }
                break;
            case I2C_RCV_DATA:
                // On receive, save byte into buffer
                // TODO: Handle I2C buffer overflow
                i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = SSPBUF;
                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 = I2C_REQ_DATA;
                    SSPCON2bits.ACKDT = 0;  // ACK
                    SSPCON2bits.ACKEN = 1;
                } else {
                    // If we are done reading, send an NACK to the slave
                    i2c_data_p->operating_state = I2C_SEND_STOP;
                    SSPCON2bits.ACKDT = 1;  // NACK
                    SSPCON2bits.ACKEN = 1;
                }
                break;
            case I2C_REQ_DATA:
                // Set module to receive one byte of data
                i2c_data_p->operating_state = I2C_RCV_DATA;
                SSPCON2bits.RCEN = 1;
                break;
            case I2C_SEND_STOP:
                // Send the stop bit and copy message to send to Main()
                i2c_data_p->operating_state = I2C_IDLE;
                SSPCON2bits.PEN = 1;
                i2c_data_p->master_status = I2C_MASTER_IDLE;
                i2c_data_p->return_status = I2C_RECV_OK;
                break;
        }
    } else if (i2c_data_p->master_status == I2C_MASTER_RESTART) {
        switch (i2c_data_p->operating_state) {
            case I2C_IDLE:
                break;
            case I2C_SEND_ADDR:
                // Send the address with read bit set
                i2c_data_p->operating_state = I2C_CHECK_ACK_SEND;
                SSPBUF = (i2c_data_p->master_dest_addr << 1) | 0x0;
                break;
            case I2C_CHECK_ACK_SEND:
                // Check if ACK is received or not
                if (!SSPCON2bits.ACKSTAT) {
                    // If an ACK is received, send first byte of data
                    SSPBUF = i2c_data_p->buffer_in[0];
                    i2c_data_p->operating_state = I2C_CHECK_ACK_RESTART;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_data_p->master_status = I2C_MASTER_IDLE;
                    i2c_data_p->return_status = I2C_SEND_FAIL;
                }
                break;
            case I2C_CHECK_ACK_RESTART:
                if (!SSPCON2bits.ACKSTAT) {
                    SSPCON2bits.RSEN = 1;
                    i2c_data_p->operating_state = I2C_SEND_ADDR_2;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_data_p->master_status = I2C_MASTER_IDLE;
                    i2c_data_p->return_status = I2C_SEND_FAIL;
                }
                break;
            case I2C_SEND_ADDR_2:
                // Send the address with read bit set
                i2c_data_p->operating_state = I2C_CHECK_ACK_RECV;
                SSPBUF = (i2c_data_p->master_dest_addr << 1) | 0x1;
                break;
            case I2C_CHECK_ACK_RECV:
                // Check if ACK is received
                if (!SSPCON2bits.ACKSTAT) {
                    // If an ACK is received, set module to receive 1 byte of data
                    i2c_data_p->operating_state = I2C_RCV_DATA;
                    SSPCON2bits.RCEN = 1;
                } else {
                    // If a NACK is received, stop transmission and send error
                    i2c_data_p->operating_state = I2C_IDLE;
                    SSPCON2bits.PEN = 1;
                    i2c_data_p->master_status = I2C_MASTER_IDLE;
                    i2c_data_p->return_status = I2C_RECV_FAIL;
                }
                break;
            case I2C_RCV_DATA:
                // On receive, save byte into buffer
                // TODO: Handle I2C buffer overflow
                i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = SSPBUF;
                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 = I2C_REQ_DATA;
                    SSPCON2bits.ACKDT = 0;  // ACK
                    SSPCON2bits.ACKEN = 1;
                } else {
                    // If we are done reading, send an NACK to the slave
                    i2c_data_p->operating_state = I2C_SEND_STOP;
                    SSPCON2bits.ACKDT = 1;  // NACK
                    SSPCON2bits.ACKEN = 1;
                }
                break;
            case I2C_REQ_DATA:
                // Set module to receive one byte of data
                i2c_data_p->operating_state = I2C_RCV_DATA;
                SSPCON2bits.RCEN = 1;
                break;
            case I2C_SEND_STOP:
                // Send the stop bit and copy message to send to Main()
                i2c_data_p->operating_state = I2C_IDLE;
                SSPCON2bits.PEN = 1;
                i2c_data_p->master_status = I2C_MASTER_IDLE;
                i2c_data_p->return_status = I2C_RECV_OK;
                break;
        }
    }
}

void I2C_Interrupt_Slave() {
    char received_data;
    char data_read_from_buffer = 0;
    char data_written_to_buffer = 0;
    char overrun_error = 0;
    char output[64];

    // Clear SSPOV (overflow bit)
    if (SSPCON1bits.SSPOV == 1) {
        sprintf(output, "I2C: (ERROR) overflow detectedr\r\n");
        DBG_PRINT_I2C(output, strlen(output));
        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_data_p->operating_state = I2C_IDLE;
        overrun_error = 1;
        i2c_data_p->return_status = I2C_ERR_OVERRUN;
    }

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

    if (!overrun_error) {
        switch (i2c_data_p->operating_state) {
            case I2C_IDLE:
            {
                // Ignore anything except a start
                if (SSPSTATbits.S == 1) {
                    i2c_data_p->buffer_in_len_tmp = 0;
                    i2c_data_p->operating_state = I2C_STARTED;
//                    if (data_read_from_buffer) {
//                        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_data_p->operating_state = I2C_IDLE;
//                            i2c_data_p->return_status = I2C_ERR_NOADDR;
//                        } else {
//                            // Determine if we are sending or receiving data
//                            if (SSPSTATbits.R_W == 1) {
//                                i2c_data_p->operating_state = I2C_SEND_DATA;
//                            } else {
//                                i2c_data_p->operating_state = I2C_RCV_DATA;
//                            }
//                        }
//                    } else {
//                        i2c_data_p->operating_state = I2C_STARTED;
//                    }
                }
                break;
            }
            case I2C_STARTED:
            {
                // In this case, we expect either an address or a stop bit
                if (SSPSTATbits.P == 1) {
                    // Return to idle mode
                    i2c_data_p->operating_state = I2C_IDLE;
                } else if (data_read_from_buffer) {
                    if (SSPSTATbits.D_A == 0) {
                        // Address received
                        if (SSPSTATbits.R_W == 0) {
                            // Slave write mode
                            i2c_data_p->operating_state = I2C_RCV_DATA;
                        } else {
                            // Slave read mode
                            i2c_data_p->operating_state = I2C_SEND_DATA;
                            // Process the first byte immediatly if sending data
                            goto send;
                        }
                    } else {
                        sprintf(output, "I2C: (ERROR) no data recieved\r\n");
                        DBG_PRINT_I2C(output, strlen(output));
                        i2c_data_p->operating_state = I2C_IDLE;
                        i2c_data_p->return_status = I2C_ERR_NODATA;
                    }
                }
                break;
            }
            send:
            case I2C_SEND_DATA:
            {
                if (!i2c_data_p->slave_sending_data) {
                    // If we are not currently sending data, figure out what to reply with
                    if (I2C_Process_Send(i2c_data_p->slave_in_last_byte)) {
                        // Data exists to be returned, send first byte
                        SSPBUF = i2c_data_p->buffer_out[0];
                        i2c_data_p->buffer_out_ind = 1;
                        i2c_data_p->slave_sending_data = 1;
                        data_written_to_buffer = 1;
                    } else {
                        // Unknown request
                        i2c_data_p->slave_sending_data = 0;
                        i2c_data_p->operating_state = I2C_IDLE;
                    }
                } else {
                    // Sending remaining data back to master
                    if (i2c_data_p->buffer_out_ind < i2c_data_p->buffer_out_len) {
                        SSPBUF = i2c_data_p->buffer_out[i2c_data_p->buffer_out_ind];
                        i2c_data_p->buffer_out_ind++;
                        data_written_to_buffer = 1;
                    } else {
                        // Nothing left to send
                        i2c_data_p->slave_sending_data = 0;
                        i2c_data_p->operating_state = 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) {
                    // Stop bit detected, we need to check to see if we also read data
                    if (data_read_from_buffer) {
                        if (SSPSTATbits.D_A == 1) {
                            // Data received with stop bit
                            // TODO: Handle I2C buffer overflow
                            i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = received_data;
                            if (i2c_data_p->buffer_in_write_ind == MAXI2CBUF-1) {
                                i2c_data_p->buffer_in_write_ind = 0;
                            } else {
                                i2c_data_p->buffer_in_write_ind++;
                            }
                            i2c_data_p->buffer_in_len_tmp++;
                            // Save the last byte received
                            i2c_data_p->slave_in_last_byte = received_data;
                            i2c_data_p->return_status = I2C_DATA_AVAL;
                        } else {
                            sprintf(output, "I2C: (ERROR) no data recieved\r\n");
                            DBG_PRINT_I2C(output, strlen(output));
                            i2c_data_p->operating_state = I2C_IDLE;
                            i2c_data_p->return_status = I2C_ERR_NODATA;
                        }
                    }
                    i2c_data_p->buffer_in_len += i2c_data_p->buffer_in_len_tmp;
                    i2c_data_p->operating_state = I2C_IDLE;
                } else if (data_read_from_buffer) {
                    if (SSPSTATbits.D_A == 1) {
                        // Data received
                        i2c_data_p->buffer_in[i2c_data_p->buffer_in_write_ind] = received_data;
                        if (i2c_data_p->buffer_in_write_ind == MAXI2CBUF-1) {
                            i2c_data_p->buffer_in_write_ind = 0;
                        } else {
                            i2c_data_p->buffer_in_write_ind++;
                        }
                        i2c_data_p->buffer_in_len_tmp++;
                        // Save the last byte received
                        i2c_data_p->slave_in_last_byte = received_data;
                        i2c_data_p->return_status = I2C_DATA_AVAL;
                    } else {
                        // Restart bit detected
                        if (SSPSTATbits.R_W == 1) {
                            i2c_data_p->buffer_in_len += i2c_data_p->buffer_in_len_tmp;
                            i2c_data_p->operating_state = I2C_SEND_DATA;
                            // Process the first byte immediatly if sending data
                            goto send;
                        } else {
                            // Bad to recv an address again, we aren't ready
                            sprintf(output, "I2C: (ERROR) no data recieved\r\n");
                            DBG_PRINT_I2C(output, strlen(output));
                            i2c_data_p->operating_state = I2C_IDLE;
                            i2c_data_p->return_status = I2C_ERR_NODATA;
                        }
                    }
                }
                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;
        }
    }
}

/* Returns 0 if I2C module is currently busy, otherwise returns status code */
char I2C_Get_Status() {
    if (i2c_data_p->operating_mode == I2C_MODE_MASTER) {
        if (i2c_data_p->master_status != I2C_MASTER_IDLE || i2c_data_p->buffer_in_len == 0) {
            return 0;
        } else {
            return i2c_data_p->return_status;
        }
    } else {
        if (i2c_data_p->operating_state != I2C_IDLE || i2c_data_p->buffer_in_len == 0) {
            return 0;
        } else {
            return i2c_data_p->return_status;
        }
    }
}

char I2C_Buffer_Len() {
    return i2c_data_p->buffer_in_len;
}

/* Returns 0 if I2C module is currently busy, otherwise returns buffer length */
char I2C_Read_Buffer(char *buffer) {
    char 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 == MAXI2CBUF-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 */
char I2C_Process_Send(char c) {
    char ret = 0;
    switch (c) {
        case 0xAA:
            i2c_data_p->buffer_out[0] = 'A';
            i2c_data_p->buffer_out_len = 1;
            ret = 1;
            break;
        case 0xBB:
            i2c_data_p->buffer_out[0] = '1';
            i2c_data_p->buffer_out[1] = '2';
            i2c_data_p->buffer_out_len = 2;
            ret = 1;
            break;
    }
    return ret;
}