]> git.donarmstrong.com Git - qmk_firmware.git/blob - keyboards/helix/serial.c
11ceff0b37cd2cac53db383a711fa2f18e8c43a9
[qmk_firmware.git] / keyboards / helix / 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 <stddef.h>
13 #include <stdbool.h>
14 #include "serial.h"
15 //#include <pro_micro.h>
16
17 #ifdef USE_SERIAL
18
19 #ifndef SERIAL_USE_MULTI_TRANSACTION
20 /* --- USE Simple API (OLD API, compatible with let's split serial.c) */
21   #if SERIAL_SLAVE_BUFFER_LENGTH > 0
22   uint8_t volatile serial_slave_buffer[SERIAL_SLAVE_BUFFER_LENGTH] = {0};
23   #endif
24   #if SERIAL_MASTER_BUFFER_LENGTH > 0
25   uint8_t volatile serial_master_buffer[SERIAL_MASTER_BUFFER_LENGTH] = {0};
26   #endif
27   uint8_t volatile status0 = 0;
28
29 SSTD_t transactions[] = {
30     { (uint8_t *)&status0,
31   #if SERIAL_MASTER_BUFFER_LENGTH > 0
32       sizeof(serial_master_buffer), (uint8_t *)serial_master_buffer,
33   #else
34       0, (uint8_t *)NULL,
35   #endif
36   #if SERIAL_SLAVE_BUFFER_LENGTH > 0
37       sizeof(serial_slave_buffer), (uint8_t *)serial_slave_buffer
38   #else
39       0, (uint8_t *)NULL,
40   #endif
41   }
42 };
43
44 void serial_master_init(void)
45 { soft_serial_initiator_init(transactions); }
46
47 void serial_slave_init(void)
48 { soft_serial_target_init(transactions); }
49
50 // 0 => no error
51 // 1 => slave did not respond
52 // 2 => checksum error
53 int serial_update_buffers()
54 { return soft_serial_transaction(); }
55
56 #endif // Simple API (OLD API, compatible with let's split serial.c)
57
58 #define ALWAYS_INLINE __attribute__((always_inline))
59 #define NO_INLINE __attribute__((noinline))
60 #define _delay_sub_us(x)    __builtin_avr_delay_cycles(x)
61
62 // Serial pulse period in microseconds.
63 #define TID_SEND_ADJUST 14
64
65 #define SELECT_SERIAL_SPEED 1
66 #if SELECT_SERIAL_SPEED == 0
67   // Very High speed
68   #define SERIAL_DELAY 4             // micro sec
69   #define READ_WRITE_START_ADJUST 33 // cycles
70   #define READ_WRITE_WIDTH_ADJUST 3 // cycles
71 #elif SELECT_SERIAL_SPEED == 1
72   // High speed
73   #define SERIAL_DELAY 6             // micro sec
74   #define READ_WRITE_START_ADJUST 30 // cycles
75   #define READ_WRITE_WIDTH_ADJUST 3 // cycles
76 #elif SELECT_SERIAL_SPEED == 2
77   // Middle speed
78   #define SERIAL_DELAY 12            // micro sec
79   #define READ_WRITE_START_ADJUST 30 // cycles
80   #define READ_WRITE_WIDTH_ADJUST 3 // cycles
81 #elif SELECT_SERIAL_SPEED == 3
82   // Low speed
83   #define SERIAL_DELAY 24            // micro sec
84   #define READ_WRITE_START_ADJUST 30 // cycles
85   #define READ_WRITE_WIDTH_ADJUST 3 // cycles
86 #elif SELECT_SERIAL_SPEED == 4
87   // Very Low speed
88   #define SERIAL_DELAY 50            // micro sec
89   #define READ_WRITE_START_ADJUST 30 // cycles
90   #define READ_WRITE_WIDTH_ADJUST 3 // cycles
91 #else
92 #error Illegal Serial Speed
93 #endif
94
95
96 #define SERIAL_DELAY_HALF1 (SERIAL_DELAY/2)
97 #define SERIAL_DELAY_HALF2 (SERIAL_DELAY - SERIAL_DELAY/2)
98
99 #define SLAVE_INT_WIDTH_US 1
100 #ifndef SERIAL_USE_MULTI_TRANSACTION
101   #define SLAVE_INT_RESPONSE_TIME SERIAL_DELAY
102 #else
103   #define SLAVE_INT_ACK_WIDTH_UNIT 2
104   #define SLAVE_INT_ACK_WIDTH 4
105 #endif
106
107 static SSTD_t *Transaction_table = NULL;
108
109 inline static
110 void serial_delay(void) {
111   _delay_us(SERIAL_DELAY);
112 }
113
114 inline static
115 void serial_delay_half1(void) {
116   _delay_us(SERIAL_DELAY_HALF1);
117 }
118
119 inline static
120 void serial_delay_half2(void) {
121   _delay_us(SERIAL_DELAY_HALF2);
122 }
123
124 inline static void serial_output(void) ALWAYS_INLINE;
125 inline static
126 void serial_output(void) {
127   SERIAL_PIN_DDR |= SERIAL_PIN_MASK;
128 }
129
130 // make the serial pin an input with pull-up resistor
131 inline static void serial_input_with_pullup(void) ALWAYS_INLINE;
132 inline static
133 void serial_input_with_pullup(void) {
134   SERIAL_PIN_DDR  &= ~SERIAL_PIN_MASK;
135   SERIAL_PIN_PORT |= SERIAL_PIN_MASK;
136 }
137
138 inline static
139 uint8_t serial_read_pin(void) {
140   return !!(SERIAL_PIN_INPUT & SERIAL_PIN_MASK);
141 }
142
143 inline static void serial_low(void) ALWAYS_INLINE;
144 inline static
145 void serial_low(void) {
146   SERIAL_PIN_PORT &= ~SERIAL_PIN_MASK;
147 }
148
149 inline static void serial_high(void) ALWAYS_INLINE;
150 inline static
151 void serial_high(void) {
152   SERIAL_PIN_PORT |= SERIAL_PIN_MASK;
153 }
154
155 void soft_serial_initiator_init(SSTD_t *sstd_table)
156 {
157     Transaction_table = sstd_table;
158     serial_output();
159     serial_high();
160 }
161
162 void soft_serial_target_init(SSTD_t *sstd_table)
163 {
164     Transaction_table = sstd_table;
165     serial_input_with_pullup();
166
167 #if SERIAL_PIN_MASK == _BV(PD0)
168     // Enable INT0
169     EIMSK |= _BV(INT0);
170     // Trigger on falling edge of INT0
171     EICRA &= ~(_BV(ISC00) | _BV(ISC01));
172 #elif SERIAL_PIN_MASK == _BV(PD2)
173     // Enable INT2
174     EIMSK |= _BV(INT2);
175     // Trigger on falling edge of INT2
176     EICRA &= ~(_BV(ISC20) | _BV(ISC21));
177 #else
178  #error unknown SERIAL_PIN_MASK value
179 #endif
180 }
181
182 // Used by the sender to synchronize timing with the reciver.
183 static void sync_recv(void) NO_INLINE;
184 static
185 void sync_recv(void) {
186   for (uint8_t i = 0; i < SERIAL_DELAY*5 && serial_read_pin(); i++ ) {
187   }
188   // This shouldn't hang if the target disconnects because the
189   // serial line will float to high if the target does disconnect.
190   while (!serial_read_pin());
191 }
192
193 // Used by the reciver to send a synchronization signal to the sender.
194 static void sync_send(void)NO_INLINE;
195 static
196 void sync_send(void) {
197   serial_low();
198   serial_delay();
199   serial_high();
200 }
201
202 // Reads a byte from the serial line
203 static uint8_t serial_read_chunk(uint8_t *pterrcount, uint8_t bit) NO_INLINE;
204 static uint8_t serial_read_chunk(uint8_t *pterrcount, uint8_t bit) {
205     uint8_t byte, i, p, pb;
206
207   _delay_sub_us(READ_WRITE_START_ADJUST);
208   for( i = 0, byte = 0, p = 0; i < bit; i++ ) {
209       serial_delay_half1();   // read the middle of pulses
210       if( serial_read_pin() ) {
211           byte = (byte << 1) | 1; p ^= 1;
212       } else {
213           byte = (byte << 1) | 0; p ^= 0;
214       }
215       _delay_sub_us(READ_WRITE_WIDTH_ADJUST);
216       serial_delay_half2();
217   }
218   /* recive parity bit */
219   serial_delay_half1();   // read the middle of pulses
220   pb = serial_read_pin();
221   _delay_sub_us(READ_WRITE_WIDTH_ADJUST);
222   serial_delay_half2();
223
224   *pterrcount += (p != pb)? 1 : 0;
225
226   return byte;
227 }
228
229 // Sends a byte with MSB ordering
230 void serial_write_chunk(uint8_t data, uint8_t bit) NO_INLINE;
231 void serial_write_chunk(uint8_t data, uint8_t bit) {
232     uint8_t b, p;
233     for( p = 0, b = 1<<(bit-1); b ; b >>= 1) {
234         if(data & b) {
235             serial_high(); p ^= 1;
236         } else {
237             serial_low();  p ^= 0;
238         }
239         serial_delay();
240     }
241     /* send parity bit */
242     if(p & 1) { serial_high(); }
243     else      { serial_low(); }
244     serial_delay();
245
246     serial_low(); // sync_send() / senc_recv() need raise edge
247 }
248
249 static void serial_send_packet(uint8_t *buffer, uint8_t size) NO_INLINE;
250 static
251 void serial_send_packet(uint8_t *buffer, uint8_t size) {
252   for (uint8_t i = 0; i < size; ++i) {
253     uint8_t data;
254     data = buffer[i];
255     sync_send();
256     serial_write_chunk(data,8);
257   }
258 }
259
260 static uint8_t serial_recive_packet(uint8_t *buffer, uint8_t size) NO_INLINE;
261 static
262 uint8_t serial_recive_packet(uint8_t *buffer, uint8_t size) {
263   uint8_t pecount = 0;
264   for (uint8_t i = 0; i < size; ++i) {
265     uint8_t data;
266     sync_recv();
267     data = serial_read_chunk(&pecount, 8);
268     buffer[i] = data;
269   }
270   return pecount == 0;
271 }
272
273 inline static
274 void change_sender2reciver(void) {
275     sync_send();          //0
276     serial_delay_half1(); //1
277     serial_low();         //2
278     serial_input_with_pullup(); //2
279     serial_delay_half1(); //3
280 }
281
282 inline static
283 void change_reciver2sender(void) {
284     sync_recv();     //0
285     serial_delay();  //1
286     serial_low();    //3
287     serial_output(); //3
288     serial_delay_half1(); //4
289 }
290
291 // interrupt handle to be used by the target device
292 ISR(SERIAL_PIN_INTERRUPT) {
293
294 #ifndef SERIAL_USE_MULTI_TRANSACTION
295   serial_low();
296   serial_output();
297   SSTD_t *trans = Transaction_table;
298 #else
299   // recive transaction table index
300   uint8_t tid;
301   uint8_t pecount = 0;
302   sync_recv();
303   tid = serial_read_chunk(&pecount,4);
304   if(pecount> 0)
305       return;
306   serial_delay_half1();
307
308   serial_high(); // response step1 low->high
309   serial_output();
310   _delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT*SLAVE_INT_ACK_WIDTH);
311   SSTD_t *trans = &Transaction_table[tid];
312   serial_low(); // response step2 ack high->low
313 #endif
314
315   // target send phase
316   if( trans->target2initiator_buffer_size > 0 )
317       serial_send_packet((uint8_t *)trans->target2initiator_buffer,
318                          trans->target2initiator_buffer_size);
319   // target switch to input
320   change_sender2reciver();
321
322   // target recive phase
323   if( trans->initiator2target_buffer_size > 0 ) {
324       if (serial_recive_packet((uint8_t *)trans->initiator2target_buffer,
325                                trans->initiator2target_buffer_size) ) {
326           *trans->status = TRANSACTION_ACCEPTED;
327       } else {
328           *trans->status = TRANSACTION_DATA_ERROR;
329       }
330   } else {
331       *trans->status = TRANSACTION_ACCEPTED;
332   }
333
334   sync_recv(); //weit initiator output to high
335 }
336
337 /////////
338 //  start transaction by initiator
339 //
340 // int  soft_serial_transaction(int sstd_index)
341 //
342 // Returns:
343 //    TRANSACTION_END
344 //    TRANSACTION_NO_RESPONSE
345 //    TRANSACTION_DATA_ERROR
346 // this code is very time dependent, so we need to disable interrupts
347 #ifndef SERIAL_USE_MULTI_TRANSACTION
348 int  soft_serial_transaction(void) {
349   SSTD_t *trans = Transaction_table;
350 #else
351 int  soft_serial_transaction(int sstd_index) {
352   SSTD_t *trans = &Transaction_table[sstd_index];
353 #endif
354   cli();
355
356   // signal to the target that we want to start a transaction
357   serial_output();
358   serial_low();
359   _delay_us(SLAVE_INT_WIDTH_US);
360
361 #ifndef SERIAL_USE_MULTI_TRANSACTION
362   // wait for the target response
363   serial_input_with_pullup();
364   _delay_us(SLAVE_INT_RESPONSE_TIME);
365
366   // check if the target is present
367   if (serial_read_pin()) {
368     // target failed to pull the line low, assume not present
369     serial_output();
370     serial_high();
371     *trans->status = TRANSACTION_NO_RESPONSE;
372     sei();
373     return TRANSACTION_NO_RESPONSE;
374   }
375
376 #else
377   // send transaction table index
378   sync_send();
379   _delay_sub_us(TID_SEND_ADJUST);
380   serial_write_chunk(sstd_index, 4);
381   serial_delay_half1();
382
383   // wait for the target response (step1 low->high)
384   serial_input_with_pullup();
385   while( !serial_read_pin() ) {
386       _delay_sub_us(2);
387   }
388
389   // check if the target is present (step2 high->low)
390   for( int i = 0; serial_read_pin(); i++ ) {
391       if (i > SLAVE_INT_ACK_WIDTH + 1) {
392           // slave failed to pull the line low, assume not present
393           serial_output();
394           serial_high();
395           *trans->status = TRANSACTION_NO_RESPONSE;
396           sei();
397           return TRANSACTION_NO_RESPONSE;
398       }
399       _delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT);
400   }
401 #endif
402
403   // initiator recive phase
404   // if the target is present syncronize with it
405   if( trans->target2initiator_buffer_size > 0 ) {
406       if (!serial_recive_packet((uint8_t *)trans->target2initiator_buffer,
407                                 trans->target2initiator_buffer_size) ) {
408           serial_output();
409           serial_high();
410           *trans->status = TRANSACTION_DATA_ERROR;
411           sei();
412           return TRANSACTION_DATA_ERROR;
413       }
414    }
415
416   // initiator switch to output
417   change_reciver2sender();
418
419   // initiator send phase
420   if( trans->initiator2target_buffer_size > 0 ) {
421       serial_send_packet((uint8_t *)trans->initiator2target_buffer,
422                          trans->initiator2target_buffer_size);
423   }
424
425   // always, release the line when not in use
426   sync_send();
427
428   *trans->status = TRANSACTION_END;
429   sei();
430   return TRANSACTION_END;
431 }
432
433 #ifdef SERIAL_USE_MULTI_TRANSACTION
434 int soft_serial_get_and_clean_status(int sstd_index) {
435     SSTD_t *trans = &Transaction_table[sstd_index];
436     cli();
437     int retval = *trans->status;
438     *trans->status = 0;;
439     sei();
440     return retval;
441 }
442 #endif
443
444 #endif