-
Notifications
You must be signed in to change notification settings - Fork 2
/
twi.c
318 lines (253 loc) · 8.49 KB
/
twi.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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#include <avr/io.h>
#include <avr/interrupt.h>
#include "twi.h"
#include "bits.h"
// The device address
static twi_address_t twi_address;
static twi_loader_t twi_loader;
static twi_reader_t twi_reader;
static twi_writer_t twi_writer;
// The current state handler
static enum {
STATE_ADDR,
STATE_READ_DATA,
STATE_READ_ACK,
STATE_WRITE_DATA,
STATE_WRITE_ACK
} twi_state;
uint8_t twi_buffer;
void twi_init(twi_address_t address,
twi_loader_t loader,
twi_reader_t reader,
twi_writer_t writer) {
// Remember the address and the handlers
twi_address = address;
twi_loader = loader;
twi_reader = reader;
twi_writer = writer;
// Init USI in 2-wire mode
USICR = (1 << USISIE) // Disable start condition interrupt
| (0 << USIOIE) // Disable overflow interrupt
| (1 << USIWM1) // Two-wire mode
| (0 << USIWM0) // ... hold SCL low on start condition
| (1 << USICS1) // External clock
| (0 << USICS0) // ... on positive edge
| (0 << USICLK) // ...
| (0 << USITC) // Don't toggle CLK pin
;
// Clear the USI status
USISR = (1 << USISIF) // Clear start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x00 // Wait for 8 clock cycles
;
// Configure port
bit_set(PORTB, PB0); // SDA as tristate
bit_clr(DDRB, DDB0); // ... with pull-up
bit_set(PORTB, PB2); // SCL as tristate
bit_set(DDRB, DDB2); // ... with pull-up
}
ISR(USI_START_vect) {
// Set SDA as input
bit_clr(DDRB, DDB0);
// Ensure start condition has completed
while (bit_get(PINB, PB2) &&
!bit_get(PINB, PB0)) {
}
// Check if this start condition was directly followed by a stop condition
if (!bit_get(PINB, PB0)) {
// Got a real start condition - reconfigure USI for receiving data
USICR = (1 << USISIE) // Enable start condition interrupt
| (1 << USIOIE) // Enable overflow interrupt
| (1 << USIWM1) // Two-wire mode
| (1 << USIWM0) // ... hold SCL low on start condition and overflow
| (1 << USICS1) // External clock
| (0 << USICS0) // ... on positive edge
| (0 << USICLK) // ...
| (0 << USITC) // Don't toggle CLK pin
;
} else {
// Got a stop condition - reset
USICR = (1 << USISIE) // Enable start condition interrupt
| (0 << USIOIE) // Disable overflow interrupt
| (1 << USIWM1) // Two-wire mode
| (0 << USIWM0) // ... hold SCL low on start condition
| (1 << USICS1) // External clock
| (0 << USICS0) // ... on positive edge
| (0 << USICLK) // ...
| (0 << USITC) // Don't toggle CLK pin
;
}
// Handle incoming address
twi_state = STATE_ADDR;
// Clear the USI status
USISR = (1 << USISIF) // Clear start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Keep stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x00 // Wait for 8 clock cycles
;
}
ISR(USI_OVF_vect) {
// Dispatch to state handler
switch (twi_state) {
case STATE_ADDR: {
// We received the address (7 bit address and 1 bit R/W flag and
// must send an ACK bit if the transmitted address belongs to this
// controller. If another address was received, nothing must be
// done.
// Check if received address is our own
if ((USIDR >> 1) != twi_address) {
// Got another address - do not acquire bus
goto reset;
}
// Check if read or write mode
if (USIDR & 0b00000001) {
// Read mode - we are sending data and the master sends ACK bits
// Call the load handler
if (twi_loader(TWI_DIRECTION_READ)) {
// Prepare sending ACK
USIDR = 0x00;
} else {
// Prepare sending NACK
USIDR = 0xFF;
}
// Send a data byte after sending the address ACK
twi_state = STATE_READ_DATA;
} else {
// Write mode - we are receiving data and the master receives
// ACK bits
// Call the load handler
if (twi_loader(TWI_DIRECTION_WRITE)) {
// Prepare sending ACK
USIDR = 0x00;
} else {
// Prepare sending NACK
USIDR = 0xFF;
}
// Receive a data byte after sending the address ACK
twi_state = STATE_WRITE_DATA;
}
// Set SDA as output
bit_set(DDRB, DDB0);
// Clear all interrupt flags except start condition - shift out a single bit
USISR = (0 << USISIF) // Keep start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x0E // Wait for a single clock cycle
;
break;
}
case STATE_READ_DATA: {
// The previous byte (previous data byte or address) transmission was
// acknowledget (or not) and we must check if transmission was
// sucessfull and load the next byte for transmission.
// Check if last byte was not ACKed or we have nothing left to
// transmit
if (USIDR != 0x00) {
// Transmission failed
goto reset;
}
// Set data to send
if (!twi_reader(&USIDR)) {
// End of data
goto reset;
}
// Set SDA as output
bit_set(DDRB, DDB0);
// Receiving ACK after sending the byte
twi_state = STATE_READ_ACK;
// Clear all interrupt flags except start condition - shift out 8 bit
USISR = (0 << USISIF) // Keep start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x00 // Wait for 8 clock cycles
;
break;
}
case STATE_READ_ACK: {
// The last byte was transmitted and we have to receive an
// acknowledge bit from master.
// Set SDA as input
bit_clr(DDRB, DDB0);
// Prepare read
USIDR = 0x00;
// Send next byte after receiving ACK
twi_state = STATE_READ_DATA;
// Clear all interrupt flags except start condition - shift out a single bit
USISR = (0 << USISIF) // Keep start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x0E // Wait for a single clock cycle
;
break;
}
case STATE_WRITE_DATA: {
// Master is about to send data
// Check if last byte was ACKed
if (USIDR != 0x00) {
// Transmission failed
goto reset;
}
// Set SDA as input
bit_clr(DDRB, DDB0);
// Sending ACK after receiving the byte
twi_state = STATE_WRITE_ACK;
// Clear all interrupt flags except start condition - shift in 8 bit
USISR = (0 << USISIF) // Keep start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x00 // Wait for 8 clock cycles
;
break;
}
case STATE_WRITE_ACK: {
// Master has send data - reply with ACK / NACK accordinly
// Handle incoming data
if (! twi_writer(&USIDR)) {
// End of data - prepare sending NACK
USIDR = 0xFF;
} else {
// Prepare sending ACK
USIDR = 0x00;
}
// Set SDA as output
bit_set(DDRB, DDB0);
twi_state = STATE_WRITE_DATA;
// Clear all interrupt flags except start condition - shift out a single bit
USISR = (0 << USISIF) // Keep start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x0E // Wait for a single clock cycle
;
break;
}
}
return;
reset:
// Set SDA as input
bit_clr(DDRB, DDB0);
// Reset communication mode
USICR = (1 << USISIE) // Enable start condition interrupt
| (0 << USIOIE) // Disable overflow interrupt
| (1 << USIWM1) // Two-wire mode
| (0 << USIWM0) // ... hold SCL low on start condition
| (1 << USICS1) // External clock
| (0 << USICS0) // ... on positive edge
| (0 << USICLK) // ...
| (0 << USITC) // Don't toggle CLK pin
;
// Clear all interrupt flags except start condition
USISR = (0 << USISIF) // Keep start condition flag
| (1 << USIOIF) // Clear overflow condition flag
| (1 << USIPF) // Clear stop condition flag
| (1 << USIDC) // Clear arbitration error flag
| 0x00 // Wait for 8 clock cycles
;
}