##// END OF EJS Templates
Versión inicial de los bloques de comunicación para el módulo de procesamiento.
Versión inicial de los bloques de comunicación para el módulo de procesamiento.

File last commit:

r216:217
r219:220
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;
}
}