#include "i2c_isr.h" /** i2c_isr: an interrupt-based implementation of a generic I2C master */ /** Buffer for bytes received by the processor. \ingroup i2c*/ BUFFER* i2c_buffer_in; /** Buffer for bytes to be sent by the processor. \ingroup i2c*/ BUFFER* i2c_buffer_out; /** Data structure for communication between the main program and the I2C ISR \ingroup i2c */ struct i2c_master_struct master; /** I2C ISR Theory of operation The interrupt service routine handles the details of moving bytes around: the client program's job is to configure the transfer (fill in the address, the number of bytes to send to the slave, and the number of bytes to receive from the slave). The client then sits back and waits for the transfer to be completed. The ISR is implemented using a FSM. This way, it can keep track of what is happening from one interrupt to the other. The high-level I2C protocol is as follows: Start Byte 0: Generate address & instruct the slave to receive data (if there bytes to send) Bytes 1 ... Nt: data bytes sent from master to slave Restart Byte Nt+1: Generate address & instruct the slave to transmit data (if there are bytes to be received) Bytes Nt+2 ... Nt+Nr+1: data bytes sent from slave to master Stop -------------------------------------------------------- The client program sends and receives bytes through the i2c_buffer_in and i2c_buffer_out structures. In order to initiate a transaction, the client program: 1. Stuffs the bytes to send into i2c_buffer_out (which must be large enough to hold all of the bytes) 2. Calls i2c_initiate_transmit_receive(), providing the slave address, and the number of bytes to send and receive 3. Waits for completion of the transaction: i2c_wait_for_completion() 4. If the FSM state is DONE, then pulls the specified number of bytes out of the it2c_buffer_in --------------------------------------------------------------- References - See the buffer module documentation in OUlib \ingroup i2c */ ISR(TWI_vect) { uint8_t status; //DEBUG_PORT = 8; //PORTB ^= 2; switch(master.state) { case I2C_MASTER_STATE_WSTART: // We were waiting for the start to be asserted status = twi_get_status(); if(status == TW_START || status == TW_REP_START) { // Send slave address; move on to the next state if(master.size_out > 0) { // We are going to write master.state = I2C_MASTER_STATE_WADDRESS_WRITE; twi_send_byte((master.address & 0xfe)); //DEBUG_PORT = 0x1; }else{ // We are going to read master.state = I2C_MASTER_STATE_WADDRESS_READ; twi_send_byte(master.address | 0x1); //DEBUG_PORT = 0x6; } }else{ // An error has occurred master.state = I2C_MASTER_STATE_ERROR; master.status = status; //twi_set_twint(); // Force twint flag to be cleared }; break; case I2C_MASTER_STATE_WADDRESS_READ: status = twi_get_status(); //DEBUG_PORT = 2; if(status == TW_MR_SLA_ACK) { // The slave device acknowledged if(master.size_in == 1) { // We will negatively acknowledge the next byte twi_set_ack(TWI_ACKNOWLEDGE_DISABLE); }else{ // We will positively acknowledge the next byte twi_set_ack(TWI_ACKNOWLEDGE_ENABLE); }; master.state = I2C_MASTER_STATE_WDATA; master.counter = 0; twi_set_twint(); // Force twint flag to be cleared }else{ // An error has occurred //master_state = I2C_MASTER_STATE_WERROR; master.state = I2C_MASTER_STATE_ERROR; twi_send_stop(); master.status = status; }; break; case I2C_MASTER_STATE_WADDRESS_WRITE: status = twi_get_status(); if(status == TW_MT_SLA_ACK) { // The slave device acknowledge: get ready to send the first byte master.counter = 0; master.state = I2C_MASTER_STATE_W_SDATA; //twi_send_byte(master_buffer_out[0]); if(BUFFER_EMPTY(i2c_buffer_out)) { // This should never happen, but the ISR needs // to protect itself twi_send_byte(0x5A); }else{ // Send the first byte twi_send_byte(buffer_remove(i2c_buffer_out)); }; //DEBUG_PORT = 0x2; }else{ // An error has occurred //master_state = I2C_MASTER_STATE_WERROR; master.state = I2C_MASTER_STATE_ERROR; twi_send_stop(); master.status = status; }; break; case I2C_MASTER_STATE_WDATA: // We are waiting for master.size_in bytes of data status = twi_get_status(); if(status == TW_MR_DATA_NACK) { // Get the byte if(!BUFFER_FULL(i2c_buffer_in)){ // Store the byte buffer_add(i2c_buffer_in, twi_get_byte()); }else{ // Drop the byte on the floor since it does not fit // This should never happen under normal operations twi_get_byte(); }; //master_buffer_in[master_counter] = twi_get_byte(); // We have received all of the bytes //***master_state = I2C_MASTER_STATE_WDONE; master.state = I2C_MASTER_STATE_DONE; twi_send_stop(); master.status = status; }else if(status == TW_MR_DATA_ACK) { // This is not the last byte // Get the byte if(!BUFFER_FULL(i2c_buffer_in)){ // Store the byte buffer_add(i2c_buffer_in, twi_get_byte()); }else{ // Drop the byte on the floor since it does not fit // This should never happen under normal operation twi_get_byte(); }; //master_buffer_in[master_counter] = twi_get_byte(); // Increment byte count ++master.counter; if(master.counter == (master.size_in - 1)) { // This is going to be the last byte to be received twi_set_ack(TWI_ACKNOWLEDGE_DISABLE); }else{ // This is not the last byte twi_set_ack(TWI_ACKNOWLEDGE_ENABLE); }; twi_set_twint(); }else{ // An error has occurred //master_state = I2C_MASTER_STATE_WERROR; master.state = I2C_MASTER_STATE_ERROR; master.status = status; twi_set_twint(); }; break; case I2C_MASTER_STATE_W_SDATA: status = twi_get_status(); if(status == TW_MT_DATA_ACK) { // The slave acknowledged the last byte if(++master.counter == master.size_out) { // We have now sent all of the bytes if(master.size_in > 0) { // We have to receive still master.state = I2C_MASTER_STATE_WSTART; // Force reading on next cycle master.size_out = 0; twi_send_start(); //DEBUG_PORT = 0x4; }else{ // We are done master.state = I2C_MASTER_STATE_DONE; //DEBUG_PORT = 0x5; twi_send_stop(); }; }else{ // We still have bytes to send //twi_send_byte(master_buffer_out[master.counter]); if(BUFFER_EMPTY(i2c_buffer_out)) { // This should never happen, but the ISR needs // to protect itself twi_send_byte(0x5A); }else{ // Send the next byte twi_send_byte(buffer_remove(i2c_buffer_out)); }; //DEBUG_PORT = 0x3; }; }else{ // An error has occurred master.state = I2C_MASTER_STATE_ERROR; master.status = status; twi_send_stop(); //twi_set_twint(); }; break; }; }; /** Initialize the processor as a master @param size_in The size of the input buffer. @param size_out The size of the output buffer. \ingroup i2c */ void i2c_master_init(uint8_t size_in, uint8_t size_out) { // Create buffer space i2c_buffer_in = buffer_create(size_in); i2c_buffer_out = buffer_create(size_out); // Turn on twi device twi_set_state(_BV(TWEN)); // Initialize FSM master.state = I2C_MASTER_STATE_DONE; // This gives us 400Kbs twi_set_rate(24); //twi_set_prescalar(TWI_PRESCALAR_1); twi_set_prescalar(TWI_PRESCALAR_4); // Enable interrupt twi_interrupt_config(TWI_INTERRUPT_ENABLE); // Note: global interrupts need to be turned on, too }; /** void i2c_wait_for_completion(void) Wait for the ISR to complete the full transaction \ingroup i2c */ void i2c_wait_for_completion(void) { //printf("state: %d\n\r", master.state); while((master.state >= I2C_MASTER_STATE_WSTART) || twi_get_stop()){ }; //printf("end state: %d\n\r", master.state); }; /** Initiate a full transaction with the slave device that is specified by address. We will first send size_out bytes to the slave, and then ask for size_in bytes back. Precondition: the size_out bytes must already be loaded into the i2c_buffer_out. Also - there must be enough space in i2c_buffer_in in order to hold the incoming bytes Postcondition: if there are no errors, then there will be size_in new bytes in the i2c_buffer_in. size_out bytes will be removed from i2c_buffer_out @param address The I2C address of the slave device @param size_out The number of bytes to send to the slave device @param size_in The number of bytes to then receive from the slave device @return = 1 if successful
= 0 if not successful \ingroup i2c */ uint8_t i2c_initiate_transmit_receive(uint8_t address, uint8_t size_out, uint8_t size_in) { // Wait for I2C device to complete operation i2c_wait_for_completion(); // OK to initiate transmission // Update FSM state master.state = I2C_MASTER_STATE_WSTART; // Address of slave master.address = address; // Maximum size of object we will accept master.size_in = size_in; master.size_out = size_out; // Initiate the transmission twi_send_start(); return(1); };