summaryrefslogtreecommitdiff
path: root/src/arch/arduino-nano/driver/i2c.cc
blob: 0a2f595290903ba0434b61b0a64dd69dd420af81 (plain)
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
#include "driver/i2c.h"
#include "arch.h"
#include <avr/io.h>
#include <avr/interrupt.h>

#ifndef F_I2C
#define F_I2C 100000UL
#endif

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 EEPROM 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 momodule 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 EEPROM 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);
}

/*
 * Sends len bytes to the EEPROM. 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 EEPROM 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 EEPROM 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)
{
	unsigned char i;

	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)
{
}