-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathusi_i2c_slave.c
349 lines (240 loc) · 12.1 KB
/
usi_i2c_slave.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/*-----------------------------------------------------*\
| 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 |
\*-----------------------------------------------------*/
#include "usi_i2c_slave.h"
char usi_i2c_slave_internal_address;
char usi_i2c_slave_address;
char usi_i2c_mode;
///////////////////////////////////////////////////////////////////////////////////////////////////
////USI Slave States///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#define USI_SLAVE_REGISTER_COUNT 8
// 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;
}
}