usi_i2c_slave.c
349 lines
| 11.7 KiB
| text/x-c
|
CLexer
|
r216 | /*-----------------------------------------------------*\ | ||
| USI I2C Slave Driver | | ||||
| | | ||||
| This library provides a robust, interrupt-driven I2C | | ||||
| slave implementation built on the ATTiny Universal | | ||||
| Serial Interface (USI) hardware. Slave operation is | | ||||
| implemented as a register bank, where each 'register' | | ||||
| is a pointer to an 8-bit variable in the main code. | | ||||
| This was chosen to make I2C integration transparent | | ||||
| to the mainline code and making I2C reads simple. | | ||||
| This library also works well with the Linux I2C-Tools | | ||||
| utilities i2cdetect, i2cget, i2cset, and i2cdump. | | ||||
| | | ||||
| Adam Honse (GitHub: CalcProgrammer1) - 7/29/2012 | | ||||
| -calcprogrammer1@gmail.com | | ||||
\*-----------------------------------------------------*/ | ||||
#include "usi_i2c_slave.h" | ||||
char usi_i2c_slave_internal_address; | ||||
char usi_i2c_slave_address; | ||||
char usi_i2c_mode; | ||||
/////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
////USI Slave States/////////////////////////////////////////////////////////////////////////////// | ||||
/////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
// The I2C register file is stored as an array of pointers, point these to whatever your I2C registers | ||||
// need to read/write in your code. This abstracts the buffer and makes it easier to write directly | ||||
// to values in your code. | ||||
char* USI_Slave_register_buffer[USI_SLAVE_REGISTER_COUNT]; | ||||
char USI_Slave_internal_address = 0; | ||||
char USI_Slave_internal_address_set = 0; | ||||
enum | ||||
{ | ||||
USI_SLAVE_CHECK_ADDRESS, | ||||
USI_SLAVE_SEND_DATA, | ||||
USI_SLAVE_SEND_DATA_ACK_WAIT, | ||||
USI_SLAVE_SEND_DATA_ACK_CHECK, | ||||
USI_SLAVE_RECV_DATA_WAIT, | ||||
USI_SLAVE_RECV_DATA_ACK_SEND | ||||
} USI_I2C_Slave_State; | ||||
///////////////////////////////////////////////// | ||||
////USI Register Setup Values//////////////////// | ||||
///////////////////////////////////////////////// | ||||
#define USI_SLAVE_COUNT_ACK_USISR 0b01110000 | (0x0E << USICNT0) //Counts one clock (ACK) | ||||
#define USI_SLAVE_COUNT_BYTE_USISR 0b01110000 | (0x00 << USICNT0) //Counts 8 clocks (BYTE) | ||||
#define USI_SLAVE_CLEAR_START_USISR 0b11110000 | (0x00 << USICNT0) //Clears START flag | ||||
#define USI_SLAVE_SET_START_COND_USISR 0b01110000 | (0x00 << USICNT0) | ||||
#define USI_SLAVE_SET_START_COND_USICR 0b10101000 | ||||
#define USI_SLAVE_STOP_DID_OCCUR_USICR 0b10111000 | ||||
#define USI_SLAVE_STOP_NOT_OCCUR_USICR 0b11101000 | ||||
///////////////////////////////////////////////// | ||||
////USI Direction Macros///////////////////////// | ||||
///////////////////////////////////////////////// | ||||
#define USI_SET_SDA_OUTPUT() { DDR_USI |= (1 << PORT_USI_SDA); } | ||||
#define USI_SET_SDA_INPUT() { DDR_USI &= ~(1 << PORT_USI_SDA); } | ||||
#define USI_SET_SCL_OUTPUT() { DDR_USI |= (1 << PORT_USI_SCL); } | ||||
#define USI_SET_SCL_INPUT() { DDR_USI &= ~(1 << PORT_USI_SCL); } | ||||
#define USI_SET_BOTH_OUTPUT() { DDR_USI |= (1 << PORT_USI_SDA) | (1 << PORT_USI_SCL); } | ||||
#define USI_SET_BOTH_INPUT() { DDR_USI &= ~((1 << PORT_USI_SDA) | (1 << PORT_USI_SCL)); } | ||||
//////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
void USI_I2C_Init(char address) | ||||
{ | ||||
PORT_USI &= ~(1 << PORT_USI_SCL); | ||||
PORT_USI &= ~(1 << PORT_USI_SDA); | ||||
usi_i2c_slave_address = address; | ||||
USI_SET_BOTH_INPUT(); | ||||
USICR = (1 << USISIE) | (0 << USIOIE) | (1 << USIWM1) | (0 << USIWM0) | (1 << USICS1) | (0 << USICS0) | (0 << USICLK) | (0 << USITC); | ||||
USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | (1 << USIDC); | ||||
} | ||||
///////////////////////////////////////////////////////////////////////////////// | ||||
// ISR USI_START_vect - USI Start Condition Detector Interrupt // | ||||
// // | ||||
// This interrupt occurs when the USI Start Condition Detector detects a // | ||||
// start condition. A start condition marks the beginning of an I2C // | ||||
// transmission and occurs when SDA has a high->low transition followed by an // | ||||
// SCL high->low transition. When a start condition occurs, the I2C slave // | ||||
// state is set to check address mode and the counter is set to wait 8 clocks // | ||||
// (enough for the address/rw byte to be transmitted) before overflowing and // | ||||
// triggering the first state table interrupt. If a stop condition occurs, // | ||||
// reset the start condition detector to detect the next start condition. // | ||||
///////////////////////////////////////////////////////////////////////////////// | ||||
ISR(USI_START_vect) | ||||
{ | ||||
USI_I2C_Slave_State = USI_SLAVE_CHECK_ADDRESS; | ||||
USI_SET_SDA_INPUT(); | ||||
// wait for SCL to go low to ensure the Start Condition has completed (the | ||||
// start detector will hold SCL low ) - if a Stop Condition arises then leave | ||||
// the interrupt to prevent waiting forever - don't use USISR to test for Stop | ||||
// Condition as in Application Note AVR312 because the Stop Condition Flag is | ||||
// going to be set from the last TWI sequence | ||||
while((PIN_USI & (1 << PIN_USI_SCL)) && !((PIN_USI & (1 << PIN_USI_SDA)))); | ||||
if(!(PIN_USI & (1 << PIN_USI_SDA))) | ||||
{ | ||||
// a Stop Condition did not occur | ||||
USICR = USI_SLAVE_STOP_NOT_OCCUR_USICR; | ||||
} | ||||
else | ||||
{ | ||||
// a Stop Condition did occur | ||||
USICR = USI_SLAVE_STOP_DID_OCCUR_USICR; | ||||
} | ||||
USISR = USI_SLAVE_CLEAR_START_USISR; | ||||
} | ||||
///////////////////////////////////////////////////////////////////////////////// | ||||
// ISR USI_OVERFLOW_vect - USI Overflow Interrupt // | ||||
// // | ||||
// This interrupt occurs when the USI counter overflows. By setting this // | ||||
// counter to 8, the USI can be commanded to wait one byte length before // | ||||
// causing another interrupt (and thus state change). To wait for an ACK, // | ||||
// set the counter to 1 (actually -1, or 0x0E) it will wait one clock. // | ||||
// This is used to set up a state table of I2C transmission states that fits // | ||||
// the I2C protocol for proper transmission. // | ||||
///////////////////////////////////////////////////////////////////////////////// | ||||
ISR(USI_OVERFLOW_vect) | ||||
{ | ||||
switch (USI_I2C_Slave_State) | ||||
{ | ||||
///////////////////////////////////////////////////////////////////////// | ||||
// Case USI_SLAVE_CHECK_ADDRESS // | ||||
// // | ||||
// The first state after the start condition, this state checks the // | ||||
// received byte against the stored slave address as well as the // | ||||
// global transmission address of 0x00. If there is a match, the R/W // | ||||
// bit is checked to branch either to sending or receiving modes. // | ||||
// If the address was not for this device, the USI system is // | ||||
// re-initialized for start condition. // | ||||
///////////////////////////////////////////////////////////////////////// | ||||
case USI_SLAVE_CHECK_ADDRESS: | ||||
if((USIDR == 0) || ((USIDR >> 1) == usi_i2c_slave_address)) | ||||
{ | ||||
if (USIDR & 0x01) | ||||
{ | ||||
USI_I2C_Slave_State = USI_SLAVE_SEND_DATA; | ||||
} | ||||
else | ||||
{ | ||||
USI_Slave_internal_address_set = 0; | ||||
USI_I2C_Slave_State = USI_SLAVE_RECV_DATA_WAIT; | ||||
} | ||||
//Set USI to send ACK | ||||
USIDR = 0; | ||||
USI_SET_SDA_OUTPUT(); | ||||
USISR = USI_SLAVE_COUNT_ACK_USISR; | ||||
} | ||||
else | ||||
{ | ||||
//Set USI to Start Condition Mode | ||||
USICR = USI_SLAVE_SET_START_COND_USICR; | ||||
USISR = USI_SLAVE_SET_START_COND_USISR; | ||||
} | ||||
break; | ||||
///////////////////////////////////////////////////////////////////////// | ||||
// Case USI_SLAVE_SEND_DATA_ACK_WAIT // | ||||
// // | ||||
// Wait 1 clock period for the master to ACK or NACK the sent data // | ||||
// If master NACK's, it means that master doesn't want any more data. // | ||||
///////////////////////////////////////////////////////////////////////// | ||||
case USI_SLAVE_SEND_DATA_ACK_WAIT: | ||||
//After sending, immediately shut off PORT = 1 to prevent driving | ||||
//the line high (I2C should *NEVER* drive high, and could damage | ||||
//connected devices if operating at different voltage levels) | ||||
PORT_USI &= ~(1 << PORT_USI_SDA); | ||||
USI_I2C_Slave_State = USI_SLAVE_SEND_DATA_ACK_CHECK; | ||||
USI_SET_SDA_INPUT(); | ||||
USISR = USI_SLAVE_COUNT_ACK_USISR; | ||||
break; | ||||
///////////////////////////////////////////////////////////////////////// | ||||
// Case USI_SLAVE_SEND_DATA_ACK_CHECK // | ||||
// // | ||||
// Check USIDR to see if master sent ACK or NACK. If NACK, set up // | ||||
// a reset to START conditions, if ACK, fall through into SEND_DATA // | ||||
// to continue sending data. // | ||||
///////////////////////////////////////////////////////////////////////// | ||||
case USI_SLAVE_SEND_DATA_ACK_CHECK: | ||||
if(USIDR) | ||||
{ | ||||
//The master sent a NACK, indicating that it will not accept | ||||
//more data. Reset into START condition state | ||||
USICR = USI_SLAVE_SET_START_COND_USICR; | ||||
USISR = USI_SLAVE_SET_START_COND_USISR; | ||||
return; | ||||
} | ||||
//else: fall through into SEND_DATA | ||||
///////////////////////////////////////////////////////////////////////// | ||||
// Case USI_SLAVE_SEND_DATA // | ||||
// // | ||||
// Set USIDR to the data to be sent, then set up SDA registers to // | ||||
// enable data transmission in the next 8 clocks. Set to wait 8 // | ||||
// clocks and proceed to wait for ACK. // | ||||
///////////////////////////////////////////////////////////////////////// | ||||
case USI_SLAVE_SEND_DATA: | ||||
if(USI_Slave_internal_address <= USI_SLAVE_REGISTER_COUNT) | ||||
{ | ||||
USIDR = *(USI_Slave_register_buffer[USI_Slave_internal_address]); | ||||
} | ||||
else | ||||
{ | ||||
USIDR = 0x00; | ||||
} | ||||
USI_Slave_internal_address++; | ||||
USI_I2C_Slave_State = USI_SLAVE_SEND_DATA_ACK_WAIT; | ||||
//To send data, DDR for SDA must be 1 (Output) and PORT for SDA | ||||
//must also be 1 (line drives low on USIDR MSB = 0 or PORT = 0) | ||||
USI_SET_SDA_OUTPUT(); | ||||
PORT_USI |= (1 << PORT_USI_SDA); | ||||
USISR = USI_SLAVE_COUNT_BYTE_USISR; | ||||
break; | ||||
///////////////////////////////////////////////////////////////////////// | ||||
// Case USI_SLAVE_RECV_DATA_WAIT // | ||||
// // | ||||
// Prepares to wait 8 clocks to receive a data byte from the master. // | ||||
///////////////////////////////////////////////////////////////////////// | ||||
case USI_SLAVE_RECV_DATA_WAIT: | ||||
USI_I2C_Slave_State = USI_SLAVE_RECV_DATA_ACK_SEND; | ||||
USI_SET_SDA_INPUT(); | ||||
USISR = USI_SLAVE_COUNT_BYTE_USISR; | ||||
break; | ||||
///////////////////////////////////////////////////////////////////////// | ||||
// Case USI_SLAVE_RECV_DATA_ACK_SEND // | ||||
// // | ||||
// After waiting for the master to finish transmission, this reads // | ||||
// USIDR into either the i2c buffer or internal address, then sends // | ||||
// an acknowledgement to the master. // | ||||
///////////////////////////////////////////////////////////////////////// | ||||
case USI_SLAVE_RECV_DATA_ACK_SEND: | ||||
USI_I2C_Slave_State = USI_SLAVE_RECV_DATA_WAIT; | ||||
if(USI_Slave_internal_address_set == 0) | ||||
{ | ||||
USI_Slave_internal_address = USIDR; | ||||
USI_Slave_internal_address_set = 1; | ||||
} | ||||
else if(USI_Slave_internal_address <= USI_SLAVE_REGISTER_COUNT) | ||||
{ | ||||
*(USI_Slave_register_buffer[USI_Slave_internal_address]) = USIDR; | ||||
} | ||||
USIDR = 0; | ||||
USI_SET_SDA_OUTPUT(); | ||||
USISR = USI_SLAVE_COUNT_ACK_USISR; | ||||
break; | ||||
} | ||||
} | ||||