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
|
/*
* Copyright 2021 Daniel Friesel
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "driver/i2c.h"
#include "arch.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#ifndef F_I2C
#define F_I2C 100000UL
#endif
// TODO timeouts (e.g. stuck bus or missing pull-ups)
inline void await_twint(unsigned char twcr_values)
{
TWCR = twcr_values | _BV(TWINT) | _BV(TWIE);
while (!(TWCR & _BV(TWINT))) {
arch.idle();
}
}
/*
* Send an I2C (re)start condition and the device address in read mode. Returns
* after it has been transmitted successfully.
*/
static signed char i2c_start_read(unsigned char addr)
{
await_twint(_BV(TWSTA) | _BV(TWEN));
if (!(TWSR & 0x18)) // 0x08 == START ok, 0x10 == RESTART ok
return -1;
// Note: The R byte ("... | 1") causes the TWI module to switch to
// Master Receive mode
TWDR = (addr << 1) | 1;
await_twint(_BV(TWEN));
if (TWSR != 0x40) // 0x40 == SLA+R transmitted, ACK receveid
return -2;
return 0;
}
/*
* Send an I2C (re)start condition and the device address in write mode.
* Returns after it has been transmitted successfully.
*/
static signed char i2c_start_write(unsigned char addr)
{
await_twint(_BV(TWSTA) | _BV(TWEN));
if (!(TWSR & 0x18)) // 0x08 == START ok, 0x10 == RESTART ok
return -1;
TWDR = (addr<< 1) | 0;
await_twint(_BV(TWEN));
if (TWSR != 0x18) // 0x18 == SLA+W transmitted, ACK received
return -2;
return 0;
}
/*
* Send an I2C stop condition.
*/
static signed char i2c_stop()
{
TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
return 0;
}
/*
* Sends len bytes to the device. Note that this method does NOT
* send I2C start or stop conditions.
*/
static signed char i2c_send(uint8_t len, uint8_t *data)
{
uint8_t pos = 0;
for (pos = 0; pos < len; pos++) {
TWDR = data[pos];
await_twint(_BV(TWEN));
if (TWSR != 0x28) // 0x28 == byte transmitted, ACK received
return pos;
}
return pos;
}
/*
* Receives len bytes from the device into data. Note that this method does
* NOT send I2C start or stop conditions.
*/
static signed char i2c_receive(uint8_t len, uint8_t *data)
{
uint8_t pos = 0;
for (pos = 0; pos < len; pos++) {
await_twint(_BV(TWEN) | ( _BV(TWEA) * (pos < len-1) ) );
data[pos] = TWDR;
/*
* No error handling here -- We send the acks, the device only
* supplies raw data, so there's no way of knowing whether it's still
* talking to us or we're just reading garbage.
*/
}
return pos;
}
signed char I2C::setup()
{
TWSR = 0;
TWBR = ((F_CPU / F_I2C) - 16) / 2;
return 0;
}
void I2C::scan(unsigned int *results)
{
for (unsigned char address = 0; address < 128; address++) {
if (i2c_start_read(address) == 0) {
results[address / (8 * sizeof(unsigned int))] |= 1 << (address % (8 * sizeof(unsigned int)));
i2c_stop();
}
}
i2c_stop();
}
signed char I2C::xmit(unsigned char address,
unsigned char tx_len, unsigned char *tx_buf,
unsigned char rx_len, unsigned char *rx_buf)
{
if (tx_len) {
if (i2c_start_write(address) < 0) {
return -1;
}
if (i2c_send(tx_len, tx_buf) < 0) {
return -1;
}
}
if (rx_len) {
if (i2c_start_read(address) < 0) {
return -1;
}
if (i2c_receive(rx_len, rx_buf) < 0) {
return -1;
}
}
i2c_stop();
return 0;
}
I2C i2c;
ISR(TWI_vect)
{
}
|