Commit | Line | Data |
---|---|---|
55fcac46 TC |
1 | /* |
2 | * WARNING: be careful changing this code, it is very timing dependent | |
3 | */ | |
4 | ||
5 | #ifndef F_CPU | |
6 | #define F_CPU 16000000 | |
7 | #endif | |
8 | ||
9 | #include <avr/io.h> | |
10 | #include <avr/interrupt.h> | |
11 | #include <util/delay.h> | |
12 | #include <stdbool.h> | |
13 | #include "serial.h" | |
14 | ||
15 | #ifdef USE_SERIAL | |
16 | ||
17 | // Serial pulse period in microseconds. Its probably a bad idea to lower this | |
18 | // value. | |
19 | #define SERIAL_DELAY 24 | |
20 | ||
21 | uint8_t volatile serial_slave_buffer[SERIAL_SLAVE_BUFFER_LENGTH] = {0}; | |
22 | uint8_t volatile serial_master_buffer[SERIAL_MASTER_BUFFER_LENGTH] = {0}; | |
23 | ||
24 | #define SLAVE_DATA_CORRUPT (1<<0) | |
25 | volatile uint8_t status = 0; | |
26 | ||
27 | inline static | |
28 | void serial_delay(void) { | |
29 | _delay_us(SERIAL_DELAY); | |
30 | } | |
31 | ||
32 | inline static | |
33 | void serial_output(void) { | |
34 | SERIAL_PIN_DDR |= SERIAL_PIN_MASK; | |
35 | } | |
36 | ||
37 | // make the serial pin an input with pull-up resistor | |
38 | inline static | |
39 | void serial_input(void) { | |
40 | SERIAL_PIN_DDR &= ~SERIAL_PIN_MASK; | |
41 | SERIAL_PIN_PORT |= SERIAL_PIN_MASK; | |
42 | } | |
43 | ||
44 | inline static | |
45 | uint8_t serial_read_pin(void) { | |
46 | return !!(SERIAL_PIN_INPUT & SERIAL_PIN_MASK); | |
47 | } | |
48 | ||
49 | inline static | |
50 | void serial_low(void) { | |
51 | SERIAL_PIN_PORT &= ~SERIAL_PIN_MASK; | |
52 | } | |
53 | ||
54 | inline static | |
55 | void serial_high(void) { | |
56 | SERIAL_PIN_PORT |= SERIAL_PIN_MASK; | |
57 | } | |
58 | ||
59 | void serial_master_init(void) { | |
60 | serial_output(); | |
61 | serial_high(); | |
62 | } | |
63 | ||
64 | void serial_slave_init(void) { | |
65 | serial_input(); | |
66 | ||
67 | // Enable INT0 | |
68 | EIMSK |= _BV(INT0); | |
69 | // Trigger on falling edge of INT0 | |
70 | EICRA &= ~(_BV(ISC00) | _BV(ISC01)); | |
71 | } | |
72 | ||
73 | // Used by the master to synchronize timing with the slave. | |
74 | static | |
75 | void sync_recv(void) { | |
76 | serial_input(); | |
77 | // This shouldn't hang if the slave disconnects because the | |
78 | // serial line will float to high if the slave does disconnect. | |
79 | while (!serial_read_pin()); | |
80 | serial_delay(); | |
81 | } | |
82 | ||
83 | // Used by the slave to send a synchronization signal to the master. | |
84 | static | |
85 | void sync_send(void) { | |
86 | serial_output(); | |
87 | ||
88 | serial_low(); | |
89 | serial_delay(); | |
90 | ||
91 | serial_high(); | |
92 | } | |
93 | ||
94 | // Reads a byte from the serial line | |
95 | static | |
96 | uint8_t serial_read_byte(void) { | |
97 | uint8_t byte = 0; | |
98 | serial_input(); | |
99 | for ( uint8_t i = 0; i < 8; ++i) { | |
100 | byte = (byte << 1) | serial_read_pin(); | |
101 | serial_delay(); | |
102 | _delay_us(1); | |
103 | } | |
104 | ||
105 | return byte; | |
106 | } | |
107 | ||
108 | // Sends a byte with MSB ordering | |
109 | static | |
110 | void serial_write_byte(uint8_t data) { | |
111 | uint8_t b = 8; | |
112 | serial_output(); | |
113 | while( b-- ) { | |
114 | if(data & (1 << b)) { | |
115 | serial_high(); | |
116 | } else { | |
117 | serial_low(); | |
118 | } | |
119 | serial_delay(); | |
120 | } | |
121 | } | |
122 | ||
123 | // interrupt handle to be used by the slave device | |
124 | ISR(SERIAL_PIN_INTERRUPT) { | |
125 | sync_send(); | |
126 | ||
127 | uint8_t checksum = 0; | |
128 | for (int i = 0; i < SERIAL_SLAVE_BUFFER_LENGTH; ++i) { | |
129 | serial_write_byte(serial_slave_buffer[i]); | |
130 | sync_send(); | |
131 | checksum += serial_slave_buffer[i]; | |
132 | } | |
133 | serial_write_byte(checksum); | |
134 | sync_send(); | |
135 | ||
136 | // wait for the sync to finish sending | |
137 | serial_delay(); | |
138 | ||
139 | // read the middle of pulses | |
140 | _delay_us(SERIAL_DELAY/2); | |
141 | ||
142 | uint8_t checksum_computed = 0; | |
143 | for (int i = 0; i < SERIAL_MASTER_BUFFER_LENGTH; ++i) { | |
144 | serial_master_buffer[i] = serial_read_byte(); | |
145 | sync_send(); | |
146 | checksum_computed += serial_master_buffer[i]; | |
147 | } | |
148 | uint8_t checksum_received = serial_read_byte(); | |
149 | sync_send(); | |
150 | ||
151 | serial_input(); // end transaction | |
152 | ||
153 | if ( checksum_computed != checksum_received ) { | |
154 | status |= SLAVE_DATA_CORRUPT; | |
155 | } else { | |
156 | status &= ~SLAVE_DATA_CORRUPT; | |
157 | } | |
158 | } | |
159 | ||
160 | inline | |
161 | bool serial_slave_DATA_CORRUPT(void) { | |
162 | return status & SLAVE_DATA_CORRUPT; | |
163 | } | |
164 | ||
165 | // Copies the serial_slave_buffer to the master and sends the | |
166 | // serial_master_buffer to the slave. | |
167 | // | |
168 | // Returns: | |
169 | // 0 => no error | |
170 | // 1 => slave did not respond | |
171 | int serial_update_buffers(void) { | |
172 | // this code is very time dependent, so we need to disable interrupts | |
173 | cli(); | |
174 | ||
175 | // signal to the slave that we want to start a transaction | |
176 | serial_output(); | |
177 | serial_low(); | |
178 | _delay_us(1); | |
179 | ||
180 | // wait for the slaves response | |
181 | serial_input(); | |
182 | serial_high(); | |
183 | _delay_us(SERIAL_DELAY); | |
184 | ||
185 | // check if the slave is present | |
186 | if (serial_read_pin()) { | |
187 | // slave failed to pull the line low, assume not present | |
188 | sei(); | |
189 | return 1; | |
190 | } | |
191 | ||
192 | // if the slave is present syncronize with it | |
193 | sync_recv(); | |
194 | ||
195 | uint8_t checksum_computed = 0; | |
196 | // receive data from the slave | |
197 | for (int i = 0; i < SERIAL_SLAVE_BUFFER_LENGTH; ++i) { | |
198 | serial_slave_buffer[i] = serial_read_byte(); | |
199 | sync_recv(); | |
200 | checksum_computed += serial_slave_buffer[i]; | |
201 | } | |
202 | uint8_t checksum_received = serial_read_byte(); | |
203 | sync_recv(); | |
204 | ||
205 | if (checksum_computed != checksum_received) { | |
206 | sei(); | |
207 | return 1; | |
208 | } | |
209 | ||
210 | uint8_t checksum = 0; | |
211 | // send data to the slave | |
212 | for (int i = 0; i < SERIAL_MASTER_BUFFER_LENGTH; ++i) { | |
213 | serial_write_byte(serial_master_buffer[i]); | |
214 | sync_recv(); | |
215 | checksum += serial_master_buffer[i]; | |
216 | } | |
217 | serial_write_byte(checksum); | |
218 | sync_recv(); | |
219 | ||
220 | // always, release the line when not in use | |
221 | serial_output(); | |
222 | serial_high(); | |
223 | ||
224 | sei(); | |
225 | return 0; | |
226 | } | |
227 | ||
228 | #endif |