aboutsummaryrefslogtreecommitdiff
path: root/platforms/chibios/drivers/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'platforms/chibios/drivers/serial.c')
-rw-r--r--platforms/chibios/drivers/serial.c278
1 files changed, 278 insertions, 0 deletions
diff --git a/platforms/chibios/drivers/serial.c b/platforms/chibios/drivers/serial.c
new file mode 100644
index 000000000..f54fbcee4
--- /dev/null
+++ b/platforms/chibios/drivers/serial.c
@@ -0,0 +1,278 @@
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
77void soft_serial_initiator_init(void) {
78 serial_output();
79 serial_high();
80}
81
82void soft_serial_target_init(void) {
83 serial_input();
84
85 palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE);
86 chThdCreateStatic(waThread1, sizeof(waThread1), HIGHPRIO, Thread1, NULL);
87}
88
89// Used by the master to synchronize timing with the slave.
90static void __attribute__((noinline)) sync_recv(void) {
91 serial_input();
92 // This shouldn't hang if the slave disconnects because the
93 // serial line will float to high if the slave does disconnect.
94 while (!serial_read_pin()) {
95 }
96
97 serial_delay();
98}
99
100// Used by the slave to send a synchronization signal to the master.
101static void __attribute__((noinline)) sync_send(void) {
102 serial_output();
103
104 serial_low();
105 serial_delay();
106
107 serial_high();
108}
109
110// Reads a byte from the serial line
111static uint8_t __attribute__((noinline)) serial_read_byte(void) {
112 uint8_t byte = 0;
113 serial_input();
114 for (uint8_t i = 0; i < 8; ++i) {
115 byte = (byte << 1) | serial_read_pin();
116 serial_delay();
117 }
118
119 return byte;
120}
121
122// Sends a byte with MSB ordering
123static void __attribute__((noinline)) serial_write_byte(uint8_t data) {
124 uint8_t b = 8;
125 serial_output();
126 while (b--) {
127 if (data & (1 << b)) {
128 serial_high();
129 } else {
130 serial_low();
131 }
132 serial_delay();
133 }
134}
135
136// interrupt handle to be used by the slave device
137void interrupt_handler(void *arg) {
138 chSysLockFromISR();
139
140 sync_send();
141
142 // read mid pulses
143 serial_delay_blip();
144
145 uint8_t checksum_computed = 0;
146 int sstd_index = 0;
147
148 sstd_index = serial_read_byte();
149 sync_send();
150
151 split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
152 for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
153 split_trans_initiator2target_buffer(trans)[i] = serial_read_byte();
154 sync_send();
155 checksum_computed += split_trans_initiator2target_buffer(trans)[i];
156 }
157 checksum_computed ^= 7;
158 uint8_t checksum_received = serial_read_byte();
159 sync_send();
160
161 // wait for the sync to finish sending
162 serial_delay();
163
164 // Allow any slave processing to occur
165 if (trans->slave_callback) {
166 trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
167 }
168
169 uint8_t checksum = 0;
170 for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
171 serial_write_byte(split_trans_target2initiator_buffer(trans)[i]);
172 sync_send();
173 serial_delay_half();
174 checksum += split_trans_target2initiator_buffer(trans)[i];
175 }
176 serial_write_byte(checksum ^ 7);
177 sync_send();
178
179 // wait for the sync to finish sending
180 serial_delay();
181
182 *trans->status = (checksum_computed == checksum_received) ? TRANSACTION_ACCEPTED : TRANSACTION_DATA_ERROR;
183
184 // end transaction
185 serial_input();
186
187 // TODO: remove extra delay between transactions
188 serial_delay();
189
190 chSysUnlockFromISR();
191}
192
193/////////
194// start transaction by initiator
195//
196// int soft_serial_transaction(int sstd_index)
197//
198// Returns:
199// TRANSACTION_END
200// TRANSACTION_NO_RESPONSE
201// TRANSACTION_DATA_ERROR
202// this code is very time dependent, so we need to disable interrupts
203int soft_serial_transaction(int sstd_index) {
204 if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR;
205 split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
206 if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered
207
208 // TODO: remove extra delay between transactions
209 serial_delay();
210
211 // this code is very time dependent, so we need to disable interrupts
212 chSysLock();
213
214 // signal to the slave that we want to start a transaction
215 serial_output();
216 serial_low();
217 serial_delay_blip();
218
219 // wait for the slaves response
220 serial_input();
221 serial_high();
222 serial_delay();
223
224 // check if the slave is present
225 if (serial_read_pin()) {
226 // slave failed to pull the line low, assume not present
227 dprintf("serial::NO_RESPONSE\n");
228 chSysUnlock();
229 return TRANSACTION_NO_RESPONSE;
230 }
231
232 // if the slave is present syncronize with it
233
234 uint8_t checksum = 0;
235 // send data to the slave
236 serial_write_byte(sstd_index); // first chunk is transaction id
237 sync_recv();
238
239 for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
240 serial_write_byte(split_trans_initiator2target_buffer(trans)[i]);
241 sync_recv();
242 checksum += split_trans_initiator2target_buffer(trans)[i];
243 }
244 serial_write_byte(checksum ^ 7);
245 sync_recv();
246
247 serial_delay();
248 serial_delay(); // read mid pulses
249
250 // receive data from the slave
251 uint8_t checksum_computed = 0;
252 for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
253 split_trans_target2initiator_buffer(trans)[i] = serial_read_byte();
254 sync_recv();
255 checksum_computed += split_trans_target2initiator_buffer(trans)[i];
256 }
257 checksum_computed ^= 7;
258 uint8_t checksum_received = serial_read_byte();
259
260 sync_recv();
261 serial_delay();
262
263 if ((checksum_computed) != (checksum_received)) {
264 dprintf("serial::FAIL[%u,%u,%u]\n", checksum_computed, checksum_received, sstd_index);
265 serial_output();
266 serial_high();
267
268 chSysUnlock();
269 return TRANSACTION_DATA_ERROR;
270 }
271
272 // always, release the line when not in use
273 serial_high();
274 serial_output();
275
276 chSysUnlock();
277 return TRANSACTION_END;
278}