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
|
#include <stdio.h>
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include "debug.h"
#include "i2c.h"
/** I2C notes
* TWDR: data address shift register
* contians address or data bytes to be transmitted/recieved
* ACK bit not accesible
* TWAR: matches recieved addresses
* TWCR: control register, sets settings
* TWINT: tw interrupt flag
* also is dependent on GIE in SREG
* TWSR: TWI status register (only matters if TWI interurupt flag asserted)
* Steps simplified:
* SEND START
* Send START with TWCR
* Set TWINT in TWCR to 1; as this clears the flag
* TWSR will then say start has begun
* Load SLA+W into TWDR (what is this? TODO)
* Write something to TWCR to initiate send? (make sure twint is set after)
* poll TWSR interrupt bit to see if we recieve a message (or figure out interrupts)
*
* Note: You may be wondering why I'm not handling i2c errors.
* I have limited time, and I don't think it's nessesary unless I start running into them.
*
* Some I may work on handling later.
*
*/
void error() { debug_crash(); }
void i2c_init() {
TWBR = 12;
TWSR &= 0b11111100;
}
void i2c_start(uint8_t addr, bool rw) {
TWCR = (_BV(TWSTA) | _BV(TWEN) | _BV(TWINT)); //send start signal
loop_until_bit_is_set(TWCR, TWINT);
if(((TWSR & 0xf8) != (TW_START)) && ((TWSR & 0xf8) != (TW_REP_START))) {
#ifdef DEBUG_BUILD
printf("Couldn't set start condition? TWSR: 0x%x\r\n", TWSR & 0xf8);
#endif
error();
}
TWDR = (addr << 1) | rw;
TWCR = _BV(TWINT) | _BV(TWEN);
loop_until_bit_is_set(TWCR, TWINT);
if(((TWSR & 0xf8) != (TW_MT_SLA_ACK)) && (TWSR & 0xf8) != (TW_MR_SLA_ACK)) {
#if DEBUG_BUILD
printf("unhandled NACK in address transmission\
TWCR: 0x%x\
TWSR: 0x%x\r\n", TWCR, (TWSR & ~(0b11)));
#endif
error();
}
}
void i2c_stop() { TWCR = (_BV(TWSTO) | _BV(TWEN) | _BV(TWINT)); }
void i2c_send(uint8_t byte) {
TWDR = byte; //fist packet is address
TWCR = _BV(TWINT) | _BV(TWEN); //send over the byte
loop_until_bit_is_set(TWCR, TWINT);
if((TWSR & 0xf8) != TW_MT_DATA_ACK) {
#ifdef DEBUG_BUILD
printf("unhandled NACK in data transmission\
TWCR: 0x%x\
TWSR: 0x%x\r\n", TWCR, (TWSR & ~(0b11)));
#endif
error();
}
}
uint8_t i2c_recv() {
uint8_t value;
TWCR = _BV(TWINT) | _BV(TWEN);
loop_until_bit_is_set(TWCR, TWINT);
value = TWDR;
//the eeprom supposedly doesn't return an ack here,
//as it should (according to its datasheet), but it still works?
if(((TWSR & 0xf8) != TW_MR_DATA_ACK) && ((TWSR & 0xf8) != TW_MR_DATA_NACK)) {
#ifdef DEBUG_BUILD
printf("Error recieving byte from i2c device: 0x%x\r\n", TWSR & 0xf8);
#endif
error();
}
return value;
}
//does NOT control start/stop, simply reads TWDR
uint8_t i2c_read_reg_addr16(uint8_t device, uint16_t addr) {
uint8_t value;
i2c_start(device, I2C_WRITE);
i2c_send((uint8_t)addr & 0xff);
i2c_send((uint8_t)addr >> 8);
i2c_start(device, I2C_READ);
value = i2c_recv();
i2c_stop();
return(value);
}
void i2c_write_reg(uint8_t device_addr, uint8_t device_reg, uint8_t value) {
i2c_start(device_addr, I2C_WRITE);
i2c_send(device_reg);
i2c_send(value);
i2c_stop();
}
void i2c_write_reg_multi(uint8_t device_addr, uint8_t device_reg, size_t len, uint8_t *values) {
i2c_start(device_addr, I2C_WRITE);
i2c_send(device_reg);
for(size_t on_byte = 0; on_byte < len; on_byte++) i2c_send(values[on_byte]);
i2c_stop();
}
void i2c_write_reg_addr16(uint8_t device_addr, uint16_t device_reg, uint8_t data) {
i2c_start(device_addr, I2C_WRITE);
i2c_send((uint8_t)device_reg & 0xff);
i2c_send((uint8_t)device_reg >> 8);
i2c_send(data);
i2c_stop();
}
|