]> git.donarmstrong.com Git - qmk_firmware.git/blob - quantum/split_common/serial.c
f34f824d7d5ec5d7e3dc929204b238bb9f397555
[qmk_firmware.git] / quantum / split_common / serial.c
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 #ifndef USE_I2C
16
17 #ifndef SOFT_SERIAL_PIN
18   #error quantum/split_common/serial.c need SOFT_SERIAL_PIN define
19 #endif
20
21 #ifdef __AVR_ATmega32U4__
22   // if using ATmega32U4 I2C, can not use PD0 and PD1 in soft serial.
23   #ifdef USE_I2C
24     #if SOFT_SERIAL_PIN == D0 || SOFT_SERIAL_PIN == D1
25       #error Using ATmega32U4 I2C, so can not use PD0, PD1
26     #endif
27   #endif
28
29   #if SOFT_SERIAL_PIN >= D0 && SOFT_SERIAL_PIN <= D3
30     #define SERIAL_PIN_DDR   DDRD
31     #define SERIAL_PIN_PORT  PORTD
32     #define SERIAL_PIN_INPUT PIND
33     #if SOFT_SERIAL_PIN == D0
34       #define SERIAL_PIN_MASK _BV(PD0)
35       #define EIMSK_BIT       _BV(INT0)
36       #define EICRx_BIT       (~(_BV(ISC00) | _BV(ISC01)))
37       #define SERIAL_PIN_INTERRUPT INT0_vect
38     #elif  SOFT_SERIAL_PIN == D1
39       #define SERIAL_PIN_MASK _BV(PD1)
40       #define EIMSK_BIT       _BV(INT1)
41       #define EICRx_BIT       (~(_BV(ISC10) | _BV(ISC11)))
42       #define SERIAL_PIN_INTERRUPT INT1_vect
43     #elif  SOFT_SERIAL_PIN == D2
44       #define SERIAL_PIN_MASK _BV(PD2)
45       #define EIMSK_BIT       _BV(INT2)
46       #define EICRx_BIT       (~(_BV(ISC20) | _BV(ISC21)))
47       #define SERIAL_PIN_INTERRUPT INT2_vect
48     #elif  SOFT_SERIAL_PIN == D3
49       #define SERIAL_PIN_MASK _BV(PD3)
50       #define EIMSK_BIT       _BV(INT3)
51       #define EICRx_BIT       (~(_BV(ISC30) | _BV(ISC31)))
52       #define SERIAL_PIN_INTERRUPT INT3_vect
53     #endif
54   #elif  SOFT_SERIAL_PIN == E6
55     #define SERIAL_PIN_DDR   DDRE
56     #define SERIAL_PIN_PORT  PORTE
57     #define SERIAL_PIN_INPUT PINE
58     #define SERIAL_PIN_MASK  _BV(PE6)
59     #define EIMSK_BIT        _BV(INT6)
60     #define EICRx_BIT        (~(_BV(ISC60) | _BV(ISC61)))
61     #define SERIAL_PIN_INTERRUPT INT6_vect
62   #else
63   #error invalid SOFT_SERIAL_PIN value
64   #endif
65
66 #else
67  #error serial.c now support ATmega32U4 only
68 #endif
69
70 // Serial pulse period in microseconds. Its probably a bad idea to lower this
71 // value.
72 #define SERIAL_DELAY 24
73
74 uint8_t volatile serial_slave_buffer[SERIAL_SLAVE_BUFFER_LENGTH] = {0};
75 uint8_t volatile serial_master_buffer[SERIAL_MASTER_BUFFER_LENGTH] = {0};
76
77 #define SLAVE_DATA_CORRUPT (1<<0)
78 volatile uint8_t status = 0;
79
80 inline static
81 void serial_delay(void) {
82   _delay_us(SERIAL_DELAY);
83 }
84
85 inline static
86 void serial_output(void) {
87   SERIAL_PIN_DDR |= SERIAL_PIN_MASK;
88 }
89
90 // make the serial pin an input with pull-up resistor
91 inline static
92 void serial_input(void) {
93   SERIAL_PIN_DDR  &= ~SERIAL_PIN_MASK;
94   SERIAL_PIN_PORT |= SERIAL_PIN_MASK;
95 }
96
97 inline static
98 uint8_t serial_read_pin(void) {
99   return !!(SERIAL_PIN_INPUT & SERIAL_PIN_MASK);
100 }
101
102 inline static
103 void serial_low(void) {
104   SERIAL_PIN_PORT &= ~SERIAL_PIN_MASK;
105 }
106
107 inline static
108 void serial_high(void) {
109   SERIAL_PIN_PORT |= SERIAL_PIN_MASK;
110 }
111
112 void serial_master_init(void) {
113   serial_output();
114   serial_high();
115 }
116
117 void serial_slave_init(void) {
118   serial_input();
119
120   // Enable INT0
121   EIMSK |= _BV(INT0);
122   // Trigger on falling edge of INT0
123   EICRA &= ~(_BV(ISC00) | _BV(ISC01));
124 }
125
126 // Used by the master to synchronize timing with the slave.
127 static
128 void sync_recv(void) {
129   serial_input();
130   // This shouldn't hang if the slave disconnects because the
131   // serial line will float to high if the slave does disconnect.
132   while (!serial_read_pin());
133   serial_delay();
134 }
135
136 // Used by the slave to send a synchronization signal to the master.
137 static
138 void sync_send(void) {
139   serial_output();
140
141   serial_low();
142   serial_delay();
143
144   serial_high();
145 }
146
147 // Reads a byte from the serial line
148 static
149 uint8_t serial_read_byte(void) {
150   uint8_t byte = 0;
151   serial_input();
152   for ( uint8_t i = 0; i < 8; ++i) {
153     byte = (byte << 1) | serial_read_pin();
154     serial_delay();
155     _delay_us(1);
156   }
157
158   return byte;
159 }
160
161 // Sends a byte with MSB ordering
162 static
163 void serial_write_byte(uint8_t data) {
164   uint8_t b = 8;
165   serial_output();
166   while( b-- ) {
167     if(data & (1 << b)) {
168       serial_high();
169     } else {
170       serial_low();
171     }
172     serial_delay();
173   }
174 }
175
176 // interrupt handle to be used by the slave device
177 ISR(SERIAL_PIN_INTERRUPT) {
178   sync_send();
179
180   uint8_t checksum = 0;
181   for (int i = 0; i < SERIAL_SLAVE_BUFFER_LENGTH; ++i) {
182     serial_write_byte(serial_slave_buffer[i]);
183     sync_send();
184     checksum += serial_slave_buffer[i];
185   }
186   serial_write_byte(checksum);
187   sync_send();
188
189   // wait for the sync to finish sending
190   serial_delay();
191
192   // read the middle of pulses
193   _delay_us(SERIAL_DELAY/2);
194
195   uint8_t checksum_computed = 0;
196   for (int i = 0; i < SERIAL_MASTER_BUFFER_LENGTH; ++i) {
197     serial_master_buffer[i] = serial_read_byte();
198     sync_send();
199     checksum_computed += serial_master_buffer[i];
200   }
201   uint8_t checksum_received = serial_read_byte();
202   sync_send();
203
204   serial_input(); // end transaction
205
206   if ( checksum_computed != checksum_received ) {
207     status |= SLAVE_DATA_CORRUPT;
208   } else {
209     status &= ~SLAVE_DATA_CORRUPT;
210   }
211 }
212
213 inline
214 bool serial_slave_DATA_CORRUPT(void) {
215   return status & SLAVE_DATA_CORRUPT;
216 }
217
218 // Copies the serial_slave_buffer to the master and sends the
219 // serial_master_buffer to the slave.
220 //
221 // Returns:
222 // 0 => no error
223 // 1 => slave did not respond
224 int serial_update_buffers(void) {
225   // this code is very time dependent, so we need to disable interrupts
226   cli();
227
228   // signal to the slave that we want to start a transaction
229   serial_output();
230   serial_low();
231   _delay_us(1);
232
233   // wait for the slaves response
234   serial_input();
235   serial_high();
236   _delay_us(SERIAL_DELAY);
237
238   // check if the slave is present
239   if (serial_read_pin()) {
240     // slave failed to pull the line low, assume not present
241     sei();
242     return 1;
243   }
244
245   // if the slave is present syncronize with it
246   sync_recv();
247
248   uint8_t checksum_computed = 0;
249   // receive data from the slave
250   for (int i = 0; i < SERIAL_SLAVE_BUFFER_LENGTH; ++i) {
251     serial_slave_buffer[i] = serial_read_byte();
252     sync_recv();
253     checksum_computed += serial_slave_buffer[i];
254   }
255   uint8_t checksum_received = serial_read_byte();
256   sync_recv();
257
258   if (checksum_computed != checksum_received) {
259     sei();
260     return 1;
261   }
262
263   uint8_t checksum = 0;
264   // send data to the slave
265   for (int i = 0; i < SERIAL_MASTER_BUFFER_LENGTH; ++i) {
266     serial_write_byte(serial_master_buffer[i]);
267     sync_recv();
268     checksum += serial_master_buffer[i];
269   }
270   serial_write_byte(checksum);
271   sync_recv();
272
273   // always, release the line when not in use
274   serial_output();
275   serial_high();
276
277   sei();
278   return 0;
279 }
280
281 #endif