-
Notifications
You must be signed in to change notification settings - Fork 2
/
i2c.c
296 lines (238 loc) · 7.42 KB
/
i2c.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
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stddef.h>
#include <util/twi.h>
#include "i2c.h"
#include "task.h"
struct i2c_op_s {
uint8_t error;
uint8_t address;
struct i2c_iovec_s *iov;
uint8_t iovcnt;
};
// Task waiting for I2C operation completion.
static task_t *i2c_task;
// Current I2C operation.
static struct i2c_op_s *i2c_op;
// By default, the control register is set to:
// - TWEA: Automatically send acknowledge bit in receive mode.
// - TWEN: Enable the I2C system.
// - TWIE: Enable interrupt requests when TWINT is set.
#define TWCR_DEFAULT (_BV(TWEA) | _BV(TWEN) | _BV(TWIE))
#define TWCR_ACK (TWCR_DEFAULT | _BV(TWINT))
#define TWCR_NOT_ACK (TWCR_ACK & ~_BV(TWEA))
#define TWCR_START (TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTA))
#define TWCR_STOP (TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTO))
void i2c_init(void) {
uint8_t sreg;
sreg = SREG;
cli();
// From ATmega328p datasheet:
// SCL freq = (CPU Clock freq) / (16 + 2(TWBR) * (PrescalerValue))
//
// Which means:
// TWBR = ((CPU Clock freq) / (SCL freq) - 16) / (2 * (PrescalerValue))
//
// Disable the prescaler and set TWBR according to CPU freq and SCL freq.
//
TWSR &= ~(_BV(TWPS1) | _BV(TWPS0));
TWBR = ((F_CPU / I2C_FREQ) - 16) / (2 * 1);
// Active internal pull-up resistors for SCL and SDA.
// Their ports are PC5 for SCL and PC4 for SDA on the ATmega328p.
PORTC |= _BV(PC5) | _BV(PC4);
// Enable the I2C system.
TWCR = TWCR_DEFAULT;
// Disable slave mode.
TWAR = 0;
SREG = sreg;
}
void i2c_open(void) {
// No-op for now.
}
void i2c_close(void) {
TWCR = TWCR_STOP;
// Stop is unset when done.. wait for that
while (TWCR & _BV(TWSTO)) {
continue;
}
}
// Prepares I2C operation and suspends task to wait for completion.
static int8_t i2c__io(uint8_t address, struct i2c_iovec_s *iov, uint8_t iovcnt) {
struct i2c_op_s op;
uint8_t sreg;
sreg = SREG;
op.error = 0;
op.address = address;
op.iov = iov;
op.iovcnt = iovcnt;
cli();
i2c_task = task_current();
i2c_op = &op;
// Transmit START to kickstart operation.
TWCR = TWCR_START;
task_suspend(NULL);
SREG = sreg;
if (op.error) {
return -1;
}
return 0;
}
int8_t i2c_readv(uint8_t address, struct i2c_iovec_s *iov, uint8_t iovcnt) {
return i2c__io((address << 1) | TW_READ, iov, iovcnt);
}
int8_t i2c_writev(uint8_t address, struct i2c_iovec_s *iov, uint8_t iovcnt) {
return i2c__io((address << 1) | TW_WRITE, iov, iovcnt);
}
int8_t i2c_read(uint8_t address, uint8_t *buf, uint8_t len) {
struct i2c_iovec_s iov = { buf, len };
return i2c_readv(address, &iov, 1);
}
int8_t i2c_write(uint8_t address, uint8_t *buf, uint8_t len) {
struct i2c_iovec_s iov = { buf, len };
return i2c_writev(address, &iov, 1);
}
int8_t i2c_read_from(uint8_t address, uint8_t reg, uint8_t *buf, uint8_t len) {
int8_t rv;
rv = i2c_write(address, ®, sizeof(reg));
if (rv < 0) {
return rv;
}
return i2c_read(address, buf, len);
}
int8_t i2c_write_to(uint8_t address, uint8_t reg, uint8_t *buf, uint8_t len) {
struct i2c_iovec_s iov[2] = { { ®, 1 }, { buf, len } };
return i2c_writev(address, (struct i2c_iovec_s *) &iov, 2);
}
ISR(TWI_vect, ISR_BLOCK) {
uint8_t status;
status = TW_STATUS;
switch (i2c_op->address & 0x1) {
case TW_READ:
// Master Receiver mode.
switch (status) {
// A START condition has been transmitted.
case TW_START:
// A repeated START condition has been transmitted.
case TW_REP_START:
TWDR = i2c_op->address;
TWCR = TWCR_ACK;
return;
// Arbitration lost in SLA+R or NOT ACK bit.
case TW_MR_ARB_LOST:
// A START condition will be transmitted when the bus becomes free.
TWCR = TWCR_START;
return;
// SLA+R has been transmitted; ACK has been received.
case TW_MR_SLA_ACK:
// Return NACK after next byte if it is the last one.
if (i2c_op->iov[0].len == 1 && i2c_op->iovcnt == 1) {
TWCR = TWCR_NOT_ACK;
} else {
TWCR = TWCR_ACK;
}
return;
// SLA+R has been transmitted; NOT ACK has been received.
case TW_MR_SLA_NACK:
i2c_op->error = 1;
goto done;
// Data byte has been received; ACK has been returned.
case TW_MR_DATA_ACK:
i2c_op->iov[0].base[0] = TWDR;
i2c_op->iov[0].base++;
if (--i2c_op->iov[0].len == 0) {
i2c_op->iov++;
i2c_op->iovcnt--;
// iovcnt is > 0, or we would have run TW_MR_DATA_NACK.
}
// Return NACK after next byte if it is the last one.
if (i2c_op->iov[0].len == 1 && i2c_op->iovcnt == 1) {
TWCR = TWCR_NOT_ACK;
} else {
TWCR = TWCR_ACK;
}
return;
// Data byte has been received; NOT ACK has been returned.
case TW_MR_DATA_NACK:
i2c_op->iov[0].base[0] = TWDR;
goto done;
}
// Never reached, but be sure...
return;
case TW_WRITE:
// Master Transmitter mode.
switch (status) {
// A START condition has been transmitted.
case TW_START:
// A repeated START condition has been transmitted.
case TW_REP_START:
TWDR = i2c_op->address;
TWCR = TWCR_DEFAULT | _BV(TWINT);
return;
// Arbitration lost in SLA+W or data bytes.
case TW_MT_ARB_LOST:
// A START condition will be transmitted when the bus becomes free.
TWCR = TWCR_START;
return;
// SLA+W has been transmitted; ACK has been received.
case TW_MT_SLA_ACK:
TWDR = i2c_op->iov[0].base[0];
TWCR = TWCR_DEFAULT | _BV(TWINT);
i2c_op->iov[0].base++;
i2c_op->iov[0].len--;
return;
// SLA+W has been transmitted; NOT ACK has been received.
case TW_MT_SLA_NACK:
i2c_op->error = 1;
goto done;
// Data byte has been transmitted; ACK has been received.
case TW_MT_DATA_ACK:
if (i2c_op->iov[0].len == 0) {
i2c_op->iov++;
if (--i2c_op->iovcnt == 0) {
// No more bytes left to transmit...
goto done;
}
}
TWDR = i2c_op->iov[0].base[0];
TWCR = TWCR_DEFAULT | _BV(TWINT);
i2c_op->iov[0].base++;
i2c_op->iov[0].len--;
return;
// Data byte has been transmitted; NOT ACK has been received.
case TW_MT_DATA_NACK:
if (i2c_op->iov[0].len == 0) {
i2c_op->iov++;
if (--i2c_op->iovcnt == 0) {
// No more bytes left to transmit...
goto done;
}
}
// There were more bytes left to transmit!
i2c_op->error = 1;
goto done;
}
// Never reached, but be sure...
return;
}
// Never reached, but be sure...
return;
done:
// From the ATmega328p datasheet (section 21.9.2):
//
// The TWINT Flag must be cleared by software by writing a logic one to it.
// Note that this flag is not automatically cleared by hardware when
// executing the interrupt routine. Also note that clearing this flag
// starts the operation of the TWI, so all accesses to the TWI Address
// Register (TWAR), TWI Status Register (TWSR), and TWI Data Register
// (TWDR) must be complete before clearing this flag.
//
// It is up to the code that uses I2C functions whether it wants to bundle
// multiple operations in one atomic set or not (this is done using repeated
// start). This means that TWINT cannot be cleared here. However, the
// interrupt handler cannot be allowed to fire again, so we clear TWIE to
// disable I2C interrupts altogether.
//
TWCR = TWCR_DEFAULT & ~_BV(TWIE);
task_wakeup(i2c_task);
return;
}