#include #include ///////////////////////////////////////////////////////////////////////////// /* 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 and the maximum byte length) and then to sit back and wait 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 protocol is as follows: Byte 1: Generate address & instruct the slave to transmit data Byte 2: Slave sends the number of bytes that will follow Byte 3: Slave byte #1 : Byte N+2: Slave byte #N The number of bytes that is actually transferred is the min of the number of bytes that the slave wants to transfer and the size specified by the master Also: if N==0, the slave will transmit a dummy byte for byte #3. This was necessary due to the way in which the atmel hardware requires the timing of the acknowledgements */ ///////////////////////////////////////////////////////////////////////////// // Different FSM states #define I2C_MASTER_STATE_ERROR -2 #define I2C_MASTER_STATE_DONE -1 #define I2C_MASTER_STATE_WSTART 0 #define I2C_MASTER_STATE_WADDRESS 1 #define I2C_MASTER_STATE_WNBYTES 2 #define I2C_MASTER_STATE_WDATA 3 volatile int8_t master_state; // FSM state volatile uint8_t master_size; // Maximum size that the buffer can accept volatile uint8_t master_buffer[BUFFER_SIZE]; // Buffer that the data is being dropped into volatile uint8_t master_address; // Address of the slave volatile uint8_t master_slave_size; // Actual number of bytes that the master plans to retrieve uint8_t master_counter; // Current byte count volatile uint8_t master_status; // Copy of the status register (useful for error processing) ISR(TWI_vect) { uint8_t status; 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) { // Send slave address; move on to the next state master_state = I2C_MASTER_STATE_WADDRESS; twi_send_byte((master_address & 0xfe) | 0x1); }else{ // An error has occurred master_state = I2C_MASTER_STATE_ERROR; master_status = status; //twi_set_twint(); // Force twint flag to be cleared // We assume that we lost arbitration, so we do nothing // to the hardware }; break; case I2C_MASTER_STATE_WADDRESS: status = twi_get_status(); if(status == TW_MR_SLA_ACK) { // The slave device acknowledged // We will positively acknowledge the next byte twi_set_ack(TWI_ACKNOWLEDGE_ENABLE); master_state = I2C_MASTER_STATE_WNBYTES; twi_set_twint(); // Force twint flag to be cleared }else{ // An error has occurred master_state = I2C_MASTER_STATE_ERROR; twi_send_stop(); master_status = status; }; break; case I2C_MASTER_STATE_WNBYTES: // We were waiting for the number of bytes to be transferred // by the slave status = twi_get_status(); if(status == TW_MR_DATA_ACK) { // Get size master_slave_size = twi_get_byte(); // Will the size fit into our buffer? if(master_slave_size > master_size) { master_slave_size = master_size; }; // Do we acknowledge the next bit? if(master_slave_size > 1){ twi_set_ack(TWI_ACKNOWLEDGE_ENABLE); }else{ twi_set_ack(TWI_ACKNOWLEDGE_DISABLE); } master_state = I2C_MASTER_STATE_WDATA; master_counter = 0; twi_set_twint(); }else{ // An error has occurred master_state = I2C_MASTER_STATE_ERROR; twi_send_stop(); master_status = status; }; break; case I2C_MASTER_STATE_WDATA: // We are waiting for bytes of data status = twi_get_status(); if(status == TW_MR_DATA_NACK) { // We did not ACK the last byte (so it was the last one) master_buffer[master_counter] = twi_get_byte(); // We have received all of the bytes 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 master_buffer[master_counter] = twi_get_byte(); // Increment byte count ++master_counter; if(master_counter == master_slave_size) { // 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_ERROR; master_status = status; twi_set_twint(); }; break; }; }; void i2c_master_init(void) { // Turn on twi device twi_set_state(_BV(TWEN)); // Initialize FSM master_state = I2C_MASTER_STATE_DONE; // Set up transmission rate twi_set_rate(0x80); twi_set_prescalar(TWI_PRESCALAR_64); // Enable interrupt twi_interrupt_config(TWI_INTERRUPT_ENABLE); sei(); }; uint8_t i2c_initiate_receive(uint8_t address, uint8_t size) { if(master_state < I2C_MASTER_STATE_WSTART) { // 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 = BUFFER_SIZE; // Initiate the transmission twi_send_start(); return(1); }else{ // DEBUG_PORT = 0xa; return(0); }; }; ///////////////////////////////////////////////////////////////////////////// void receive_test(void) { uint8_t status; while(1) { DEBUG_PORT = 0x1f; delay_ms(100); DEBUG_PORT = 0; delay_ms(100); // Initiate transmission i2c_initiate_receive(0x10, BUFFER_SIZE); delay_ms(100); // Wait for completion while(master_state != I2C_MASTER_STATE_DONE && master_state != I2C_MASTER_STATE_ERROR) {}; // Decode the state if(master_state == I2C_MASTER_STATE_DONE) { // Successful completion // Do something with the data that we have received if(master_slave_size == 0 || (master_slave_size == 1 && master_buffer[0] == 0x5a) || (master_slave_size == 2 && master_buffer[0] == 0x5a && master_buffer[1] == 0xf0) || (master_slave_size == 3 && master_buffer[0] == 0x5a && master_buffer[1] == 0xf0 && master_buffer[2] == 0x0f)) DEBUG_PORT = DEBUG_LED3 | master_slave_size; else DEBUG_PORT = DEBUG_LED2 | master_slave_size; }else{ // An error has occurred status = master_status; if(status == TW_MR_SLA_NACK) { // No slave acknowledged the address DEBUG_PORT = DEBUG_LED1; }else if(status == TW_MR_DATA_NACK) { // Slave did not acknowledge the data transmission DEBUG_PORT = DEBUG_LED1 | DEBUG_LED0; }else if(status == TW_MR_ARB_LOST) { // Lost arbitration (should not occur in a single master environment) DEBUG_PORT = DEBUG_LED2; }else{ // ??? }; }; delay_ms(500); }; }; void start_test(void) { DEBUG_PORT = 0x1f; delay_ms(100); DEBUG_PORT = 0; delay_ms(500); //twi_send_start(); TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); while(1) { DEBUG_PORT = twi_get_state() >> 5; }; }; int main(void) { // Configure the LED port PORTB = 0; DEBUG_PORT_DDR = DEBUG_LED0 | DEBUG_LED1 | DEBUG_LED2 | DEBUG_LED3 | DEBUG_LED4; // Initialize as a master i2c_master_init(); // Do the test receive_test(); //start_test(); }