##// END OF EJS Templates

File last commit:

r216:217
r220:221
Show More
usi_i2c_slave.c
349 lines | 11.7 KiB | text/x-c | CLexer
/*-----------------------------------------------------*\
| 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;
}
}