aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorzvecr <git@zvecr.com>2019-10-18 20:06:28 +0100
committerzvecr <git@zvecr.com>2020-05-21 18:04:30 +0100
commitb95979560c9b789d143a51d58446ef5c284c107d (patch)
tree4759c3d49ad30bb3c839b0b0ab5f637d753a52dd /drivers
parent65150984bd1f9c301b080652fe60b181765bb9be (diff)
downloadqmk_firmware-b95979560c9b789d143a51d58446ef5c284c107d.tar.gz
qmk_firmware-b95979560c9b789d143a51d58446ef5c284c107d.zip
Initial arm serial partially based on old lets split code
Diffstat (limited to 'drivers')
-rw-r--r--drivers/chibios/serial.c290
1 files changed, 290 insertions, 0 deletions
diff --git a/drivers/chibios/serial.c b/drivers/chibios/serial.c
new file mode 100644
index 000000000..26c680653
--- /dev/null
+++ b/drivers/chibios/serial.c
@@ -0,0 +1,290 @@
1/*
2 * WARNING: be careful changing this code, it is very timing dependent
3 */
4
5#include "quantum.h"
6#include "serial.h"
7#include "wait.h"
8
9#include "hal.h"
10
11// TODO: resolve/remove build warnings
12#if defined(RGBLIGHT_ENABLE) && defined(RGBLED_SPLIT) && defined(PROTOCOL_CHIBIOS) && defined(WS2812_DRIVER_BITBANG)
13# warning "RGBLED_SPLIT not supported with bitbang WS2812 driver"
14#endif
15
16// default wait implementation cannot be called within interrupt
17// this method seems to be more accurate than GPT timers
18#if PORT_SUPPORTS_RT == FALSE
19# error "chSysPolledDelayX method not supported on this platform"
20#else
21# undef wait_us
22# define wait_us(x) chSysPolledDelayX(US2RTC(STM32_SYSCLK, x))
23#endif
24
25#ifndef SELECT_SOFT_SERIAL_SPEED
26# define SELECT_SOFT_SERIAL_SPEED 1
27// TODO: correct speeds...
28// 0: about 189kbps (Experimental only)
29// 1: about 137kbps (default)
30// 2: about 75kbps
31// 3: about 39kbps
32// 4: about 26kbps
33// 5: about 20kbps
34#endif
35
36// Serial pulse period in microseconds. At the moment, going lower than 12 causes communication failure
37#if SELECT_SOFT_SERIAL_SPEED == 0
38# define SERIAL_DELAY 12
39#elif SELECT_SOFT_SERIAL_SPEED == 1
40# define SERIAL_DELAY 16
41#elif SELECT_SOFT_SERIAL_SPEED == 2
42# define SERIAL_DELAY 24
43#elif SELECT_SOFT_SERIAL_SPEED == 3
44# define SERIAL_DELAY 32
45#elif SELECT_SOFT_SERIAL_SPEED == 4
46# define SERIAL_DELAY 48
47#elif SELECT_SOFT_SERIAL_SPEED == 5
48# define SERIAL_DELAY 64
49#else
50# error invalid SELECT_SOFT_SERIAL_SPEED value
51#endif
52
53inline static void serial_delay(void) { wait_us(SERIAL_DELAY); }
54inline static void serial_delay_half(void) { wait_us(SERIAL_DELAY / 2); }
55inline static void serial_delay_blip(void) { wait_us(1); }
56inline static void serial_output(void) { setPinOutput(SOFT_SERIAL_PIN); }
57inline static void serial_input(void) { setPinInputHigh(SOFT_SERIAL_PIN); }
58inline static bool serial_read_pin(void) { return !!readPin(SOFT_SERIAL_PIN); }
59inline static void serial_low(void) { writePinLow(SOFT_SERIAL_PIN); }
60inline static void serial_high(void) { writePinHigh(SOFT_SERIAL_PIN); }
61
62void interrupt_handler(void *arg);
63
64// Use thread + palWaitLineTimeout instead of palSetLineCallback
65// - Methods like setPinOutput and palEnableLineEvent/palDisableLineEvent
66// cause the interrupt to lock up, which would limit to only receiving data...
67static THD_WORKING_AREA(waThread1, 128);
68static THD_FUNCTION(Thread1, arg) {
69 (void)arg;
70 chRegSetThreadName("blinker");
71 while (true) {
72 palWaitLineTimeout(SOFT_SERIAL_PIN, TIME_INFINITE);
73 interrupt_handler(NULL);
74 }
75}
76
77static SSTD_t *Transaction_table = NULL;
78static uint8_t Transaction_table_size = 0;
79
80void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) {
81 Transaction_table = sstd_table;
82 Transaction_table_size = (uint8_t)sstd_table_size;
83
84 serial_output();
85 serial_high();
86}
87
88void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) {
89 Transaction_table = sstd_table;
90 Transaction_table_size = (uint8_t)sstd_table_size;
91
92 serial_input();
93
94 palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE);
95 chThdCreateStatic(waThread1, sizeof(waThread1), HIGHPRIO, Thread1, NULL);
96}
97
98// Used by the master to synchronize timing with the slave.
99static void __attribute__((noinline)) sync_recv(void) {
100 serial_input();
101 // This shouldn't hang if the slave disconnects because the
102 // serial line will float to high if the slave does disconnect.
103 while (!serial_read_pin()) {
104 }
105
106 serial_delay();
107}
108
109// Used by the slave to send a synchronization signal to the master.
110static void __attribute__((noinline)) sync_send(void) {
111 serial_output();
112
113 serial_low();
114 serial_delay();
115
116 serial_high();
117}
118
119// Reads a byte from the serial line
120static uint8_t __attribute__((noinline)) serial_read_byte(void) {
121 uint8_t byte = 0;
122 serial_input();
123 for (uint8_t i = 0; i < 8; ++i) {
124 byte = (byte << 1) | serial_read_pin();
125 serial_delay();
126 }
127
128 return byte;
129}
130
131// Sends a byte with MSB ordering
132static void __attribute__((noinline)) serial_write_byte(uint8_t data) {
133 uint8_t b = 8;
134 serial_output();
135 while (b--) {
136 if (data & (1 << b)) {
137 serial_high();
138 } else {
139 serial_low();
140 }
141 serial_delay();
142 }
143}
144
145// interrupt handle to be used by the slave device
146void interrupt_handler(void *arg) {
147 chSysLockFromISR();
148
149 sync_send();
150
151 // read mid pulses
152 serial_delay_blip();
153
154 uint8_t checksum_computed = 0;
155 int sstd_index = 0;
156
157#ifdef SERIAL_USE_MULTI_TRANSACTION
158 sstd_index = serial_read_byte();
159 sync_send();
160#endif
161
162 SSTD_t *trans = &Transaction_table[sstd_index];
163 for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
164 trans->initiator2target_buffer[i] = serial_read_byte();
165 sync_send();
166 checksum_computed += trans->initiator2target_buffer[i];
167 }
168 checksum_computed ^= 7;
169 uint8_t checksum_received = serial_read_byte();
170 sync_send();
171
172 // wait for the sync to finish sending
173 serial_delay();
174
175 uint8_t checksum = 0;
176 for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
177 serial_write_byte(trans->target2initiator_buffer[i]);
178 sync_send();
179 serial_delay_half();
180 checksum += trans->target2initiator_buffer[i];
181 }
182 serial_write_byte(checksum ^ 7);
183 sync_send();
184
185 // wait for the sync to finish sending
186 serial_delay();
187
188 *trans->status = (checksum_computed == checksum_received) ? TRANSACTION_ACCEPTED : TRANSACTION_DATA_ERROR;
189
190 // end transaction
191 serial_input();
192
193 // TODO: remove extra delay between transactions
194 serial_delay();
195
196 chSysUnlockFromISR();
197}
198
199/////////
200// start transaction by initiator
201//
202// int soft_serial_transaction(int sstd_index)
203//
204// Returns:
205// TRANSACTION_END
206// TRANSACTION_NO_RESPONSE
207// TRANSACTION_DATA_ERROR
208// this code is very time dependent, so we need to disable interrupts
209#ifndef SERIAL_USE_MULTI_TRANSACTION
210int soft_serial_transaction(void) {
211 int sstd_index = 0;
212#else
213int soft_serial_transaction(int sstd_index) {
214#endif
215
216 if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
217 SSTD_t *trans = &Transaction_table[sstd_index];
218
219 // TODO: remove extra delay between transactions
220 serial_delay();
221
222 // this code is very time dependent, so we need to disable interrupts
223 chSysLock();
224
225 // signal to the slave that we want to start a transaction
226 serial_output();
227 serial_low();
228 serial_delay_blip();
229
230 // wait for the slaves response
231 serial_input();
232 serial_high();
233 serial_delay();
234
235 // check if the slave is present
236 if (serial_read_pin()) {
237 // slave failed to pull the line low, assume not present
238 dprintf("serial::NO_RESPONSE\n");
239 chSysUnlock();
240 return TRANSACTION_NO_RESPONSE;
241 }
242
243 // if the slave is present syncronize with it
244
245 uint8_t checksum = 0;
246 // send data to the slave
247#ifdef SERIAL_USE_MULTI_TRANSACTION
248 serial_write_byte(sstd_index); // first chunk is transaction id
249 sync_recv();
250#endif
251 for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
252 serial_write_byte(trans->initiator2target_buffer[i]);
253 sync_recv();
254 checksum += trans->initiator2target_buffer[i];
255 }
256 serial_write_byte(checksum ^ 7);
257 sync_recv();
258
259 serial_delay();
260 serial_delay(); // read mid pulses
261
262 // receive data from the slave
263 uint8_t checksum_computed = 0;
264 for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
265 trans->target2initiator_buffer[i] = serial_read_byte();
266 sync_recv();
267 checksum_computed += trans->target2initiator_buffer[i];
268 }
269 checksum_computed ^= 7;
270 uint8_t checksum_received = serial_read_byte();
271
272 sync_recv();
273 serial_delay();
274
275 if ((checksum_computed) != (checksum_received)) {
276 dprintf("serial::FAIL[%u,%u,%u]\n", checksum_computed, checksum_received, sstd_index);
277 serial_output();
278 serial_high();
279
280 chSysUnlock();
281 return TRANSACTION_DATA_ERROR;
282 }
283
284 // always, release the line when not in use
285 serial_high();
286 serial_output();
287
288 chSysUnlock();
289 return TRANSACTION_END;
290}