-
Notifications
You must be signed in to change notification settings - Fork 1
/
tiny_i2c_adc.cpp
202 lines (172 loc) · 5.52 KB
/
tiny_i2c_adc.cpp
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
/* HARDWARE:
ATTiny85
+----------+
RESET -- | 1 8 | -- VCC
PB3 -- | 2 7 | -- PB2/SCL \
INPUT --> ADC2/PB4 -- | 3 6 | -- PB1 | I2C
GND -- | 4 5 | -- PB0/SDA /
+----------+
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
// --- hardware connections ---
const uint8_t INPUT_ADC = 2;
const uint8_t ADC_PRESCALER = _BV(ADPS1) | _BV(ADPS0); // Set prescaler to 8 for 1MHz CPU = 125KHz
const uint8_t SCL_PIN = 2;
const uint8_t SDA_PIN = 0;
#define SCL (PORTB & _BV(SCL_PIN))
#define SDA (PORTB & _BV(SDA_PIN))
// --- i2c address ---
const uint8_t I2C_ADC_READ_ADDR = ('A' << 1) & 1;
// --- state ---
const uint8_t OVF_STATE_RECEIVING_ADDR = 1;
const uint8_t OVF_STATE_SENDING_ACK_ADDR = 2;
const uint8_t OVF_STATE_SENDING_BYTES = 3;
const uint8_t OVF_STATE_RECEIVING_ACK_BYTES = 4;
const uint8_t N_BYTES = 2;
volatile uint8_t ovfState;
volatile uint8_t byteIndex;
volatile uint8_t bytes[N_BYTES];
// --- code ---
inline void usiSleepMode() {
MCUCR = _BV(SE) | _BV(SM1); // power-down
}
inline void adcSleepMode() {
MCUCR = _BV(SE) | _BV(SM0); // ADC noise reduction
}
inline void usiWaitStart() {
// input on SDA
DDRB &= ~_BV(SDA_PIN);
// Set USI in Two-wire waiting for START interrupt, hold SCL low on START, external clock source, 4-Bit Counter on both edges
USICR = _BV(USISIE) | _BV(USIWM1) | _BV(USICS1);
// Clear all interrupt flags and reset overflow counter
USISR = _BV(USISIF) | _BV(USIOIF) | _BV(USIPF) | _BV(USIDC);
}
inline void usiReceiveAddr() {
// input on SDA
DDRB &= ~_BV(SDA_PIN);
// Set USI in Two-wire waiting for START & OVF, hold SCL low on START & OVF, external clock source, 4-Bit Counter on both edges
USICR = _BV(USISIE) | _BV(USIOIE) | _BV(USIWM1) | _BV(USIWM0) |_BV(USICS1);
// Clear all interrupt flags and reset overflow counter
USISR = _BV(USISIF) | _BV(USIOIF) | _BV(USIPF) | _BV(USIDC);
}
inline void usiSendAck() {
// ack with zero bit
USIDR = 0;
// output on SDA
DDRB |= _BV(SDA_PIN);
// Clear overflow and set USI counter to shift 1 bit
USISR = _BV(USIOIF) | _BV(USIPF) | _BV(USIDC) | (0x0E << USICNT0);
}
inline void usiSendBytes() {
// byte to send
if (byteIndex < N_BYTES)
USIDR = bytes[byteIndex++];
else
USIDR = 0; // all other bytes are zero if master wants more
// output on SDA
DDRB |= _BV(SDA_PIN);
// Set USI counter to shift all bits
USISR = _BV(USIOIF) | _BV(USIPF) | _BV(USIDC);
}
inline void usiReceiveAck() {
// clear data register
USIDR = 0;
// input on SDA
DDRB &= ~_BV(SDA_PIN);
// Clear overflow and set USI counter to shift 1 bit
USISR = _BV(USIOIF) | _BV(USIPF) | _BV(USIDC) | (0x0E << USICNT0);
}
// USI start condition interrupt vector
ISR(USI_START_vect) {
// wait while start condition is in progress (SCL is high and SDA is low)
while (SCL & !SDA);
if (SDA) {
// stop did occur, get back to waiting start condition
usiWaitStart();
return;
}
// stop did not occur, wait for address bytes
usiReceiveAddr();
ovfState = OVF_STATE_RECEIVING_ADDR;
}
// USI counter overflow interrupt vector
ISR(USI_OVF_vect) {
if (USISR & _BV(USIPF)) {
// stop did occur, get back to waiting start condition
usiWaitStart();
return;
}
// stop did not occur, check state
switch (ovfState) {
case OVF_STATE_RECEIVING_ADDR:
// address received
if (USIBR == I2C_ADC_READ_ADDR) {
// our address received -- acknowledge
usiSendAck();
ovfState = OVF_STATE_SENDING_ACK_ADDR;
} else {
// something else -- wait start again
usiWaitStart();
}
break;
case OVF_STATE_SENDING_ACK_ADDR:
// addr ack sent -- start ADC reading,
PRR &= ~_BV(PRADC); // power on ADC
ADMUX = INPUT_ADC; // Use VCC as reference, single-ended on input adc
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADIF) | _BV(ADIE) | ADC_PRESCALER; // enable ADC & start conversion
// Change sleep mode to to ADC Noise Reduction
adcSleepMode();
// don't reset flags -- keep SCL low while doing ADC (stretch clock)
break;
case OVF_STATE_SENDING_BYTES:
usiReceiveAck();
ovfState = OVF_STATE_RECEIVING_ACK_BYTES;
break;
case OVF_STATE_RECEIVING_ACK_BYTES:
if (USIDR) {
// NACK from master
usiWaitStart();
} else {
// ACK from master
usiSendBytes();
ovfState = OVF_STATE_SENDING_BYTES;
}
break;
default:
// just in case of anything else -- go to wating for start again
usiWaitStart();
}
}
// ADC interrupt vector
ISR(ADC_vect) {
bytes[0] = ADCH;
bytes[1] = ADCL;
ADCSRA = 0; // turn off ADC
PRR |= _BV(PRADC); // power off ADC
byteIndex = 0;
usiSendBytes();
ovfState = OVF_STATE_SENDING_BYTES;
// deep sleep again
usiSleepMode();
}
// --- setup and main loop ---
int main() {
// Power configuration
PRR = _BV(PRTIM0) | _BV(PRTIM1) | _BV(PRADC); // Turn off all timers (don't need them) and ADC until needed
ACSR = _BV(ACD); // Disable analog comparator
// Configure USI -- initialy wait for start condition
usiWaitStart();
// Set output to 1 on SCL ia intermediate pull-up enabled state (will not pull-up or write one because of TwoWire USI mode)
PORTB |= SCL_PIN;
DDRB |= SCL_PIN;
// Enable sleep and configure "power down sleep" (USI can wake up)
usiSleepMode();
// Enable interrupts at last
sei();
// Sleep until interrupted
while (1)
sleep_cpu();
return 0;
}