|
|
/*-----------------------------------------------------*\
|
|
|
| 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|