ASCII Serial Com
Serial communication library between computers, microcontrollers, FPGAs, etc. Uses only ASCII. Not the most efficient protocol, but meant to be easy to read
Loading...
Searching...
No Matches
asc_helpers.h
Go to the documentation of this file.
1#ifndef ASC_HELPERS_H
2#define ASC_HELPERS_H
3
4/** \file
5 *
6 * Helper macros to help with UARTs, etc.
7 *
8 * */
9
10#include <inttypes.h>
11#include <stdbool.h>
12#include <stddef.h>
13#include <stdint.h>
14
15#include "ascii_serial_com.h"
16#include "circular_buffer.h"
17
18uint8_t ASC_HELPERS_BYTE;
19
20/**
21 *
22 * Helper to check if UART has a received a byte and is waiting on the user to
23 * read it
24 *
25 * Use uart_no as 1,2,3, etc.
26 *
27 */
28#define is_uart_rx_data_waiting(uart_no) _is_uart_rx_data_waiting(uart_no)
29
30/**
31 * Second level of macro makes sure parameters are expanded before subbing into
32 * string concatenation
33 */
34#if defined(__ARM_ARCH)
35#define _is_uart_rx_data_waiting(uart_no) \
36 ((USART_ISR(USART##uart_no) & USART_ISR_RXNE))
37#elif defined(__AVR)
38#define _is_uart_rx_data_waiting(uart_no) (UCSR##uart_no##A & (1 << RXC0))
39#elif defined(NEORV32)
40#define _is_uart_rx_data_waiting(uart_no) \
41 (neorv32_uart##uart_no##_char_received())
42#else
43#define _is_uart_rx_data_waiting(uart_no) __builtin_unreachable()
44#endif
45
46/**
47 *
48 * Helper to check if UART tx buffer is ready for the user to write to it
49 *
50 * Use uart_no as 1,2,3, etc.
51 *
52 */
53#define is_uart_ready_to_tx(uart_no) _is_uart_ready_to_tx(uart_no)
54
55/**
56 * Second level of macro makes sure parameters are expanded before subbing into
57 * string concatenation
58 */
59#if defined(__ARM_ARCH)
60#define _is_uart_ready_to_tx(uart_no) \
61 ((USART_ISR(USART##uart_no) & USART_ISR_TXE))
62#elif defined(__AVR)
63#define _is_uart_ready_to_tx(uart_no) (UCSR##uart_no##A & (1 << UDRE0))
64#elif defined(NEORV32)
65#define _is_uart_ready_to_tx(uart_no) \
66 ((NEORV32_UART##uart_no##.CTRL & (1 << UART_CTRL_TX_FULL)) == 0)
67#else
68#define _is_uart_ready_to_tx(uart_no) __builtin_unreachable()
69#endif
70
71/**
72 *
73 * Reads from the UART rx buffer, non-blocking, without checking if data is
74 * there
75 *
76 */
77#define uart_rx(uart_no, _tmp_byte) _uart_rx(uart_no, _tmp_byte)
78
79/**
80 * Second level of macro makes sure parameters are expanded before subbing into
81 * string concatenation
82 */
83#if defined(__ARM_ARCH)
84#define _uart_rx(uart_no, _tmp_byte) \
85 do { \
86 _tmp_byte = usart_recv(USART##uart_no); \
87 } while (0)
88#elif defined(__AVR)
89#define _uart_rx(uart_no, _tmp_byte) \
90 do { \
91 _tmp_byte = UDR##uart_no; \
92 } while (0)
93#elif defined(NEORV32)
94#define _uart_rx(uart_no, _tmp_byte) \
95 do { \
96 _tmp_byte = neorv32_uart##uart_no##_getc(); \
97 } while (0)
98#else
99#define _uart_rx(uart, _tmp_byte) __builtin_unreachable()
100#endif
101
102/**
103 *
104 * Reads from the UART rx buffer, blocking until data is there
105 *
106 */
107#define uart_rx_blocking(uart_no, _tmp_byte) \
108 do { \
109 while (1) { \
110 if (is_uart_rx_data_waiting(uart_no)) { \
111 uart_rx(uart_no, _tmp_byte); \
112 break; \
113 } \
114 } \
115 } while (0)
116
117/**
118 *
119 * Writes to the UART tx buffer, non-blocking, without checking if it's ready to
120 * receive data
121 *
122 */
123#define uart_tx(uart_no, data) _uart_tx(uart_no, data)
124
125/**
126 * Second level of macro makes sure parameters are expanded before subbing into
127 * string concatenation
128 */
129#if defined(__ARM_ARCH)
130#define _uart_tx(uart_no, data) usart_send(USART##uart_no, data)
131#elif defined(__AVR)
132#define _uart_tx(uart_no, _tmp_byte) \
133 do { \
134 UDR##uart_no = _tmp_byte; \
135 } while (0)
136#elif defined(NEORV32)
137#define _uart_tx(uart_no, _tmp_byte) (neorv32_uart##uart_no##_putc(_tmp_byte))
138#else
139#define _uart_tx(uart, _tmp_byte) __builtin_unreachable()
140#endif
141
142/**
143 *
144 * Writes to the UART tx buffer, blocking until the
145 * buffer is ready for writing
146 *
147 */
148#define uart_tx_blocking(uart_no, _tmp_byte) \
149 do { \
150 while (1) { \
151 if (is_uart_ready_to_tx(uart_no)) { \
152 uart_tx(uart_no, _tmp_byte); \
153 break; \
154 } \
155 } \
156 } while (0)
157
158/**
159 *
160 * Cross-platform atomic block
161 *
162 */
163#if defined(__ARM_ARCH)
164#define _ATOMIC CM_ATOMIC_BLOCK()
165#elif defined(__AVR)
166#define _ATOMIC ATOMIC_BLOCK(ATOMIC_FORCEON)
167#elif defined(NEORV32)
168#define _ATOMIC
169#else
170#define _ATOMIC __builtin_unreachable()
171#endif
172
173/** \brief Receive a byte from UART into circular buffer
174 *
175 * Checks if uart has a received byte waiting, and if so,
176 * pushes it onto the back of the circular buffer.
177 *
178 */
179#define uart_rx_to_circ_buf(uart_no, circ_buf_ptr) \
180 do { \
181 if (is_uart_rx_data_waiting(uart_no)) { \
182 uart_rx(uart_no, ASC_HELPERS_BYTE); \
183 circular_buffer_push_back_uint8(circ_buf_ptr, ASC_HELPERS_BYTE); \
184 } \
185 } while (0)
186
187/** \brief Transmit a byte from the circular buffer
188 *
189 * Checks if uart can accept a byte and the circular
190 * buffer isn't empty, and if so, pops a byte off of
191 * the circular buffer and transmits it.
192 *
193 */
194#define uart_tx_from_circ_buf(uart_no, circ_buf_ptr) \
195 do { \
196 if (is_uart_ready_to_tx(uart_no) && \
197 !circular_buffer_is_empty_uint8(circ_buf_ptr)) { \
198 uart_tx(uart_no, circular_buffer_pop_front_uint8(circ_buf_ptr)); \
199 } \
200 } while (0)
201
202///////////////////////////////////////////////////
203////// For ASC Device with Register Pointers //////
204///////////////////////////////////////////////////
205
206#define _extraInputBuffer_size_ 64
207
208/** \brief Declarations for ascii_serial_com_device and
209 * ascii_serial_com_register_pointers
210 *
211 * Declarations that should be outside of (and before) main() to ease use of
212 * ascii_serial_com_register_pointers with ascii_serial_com_device.
213 *
214 * The only "public" variables are:
215 *
216 * bool streaming_is_on;
217 * circular_buffer_uint8 extraInputBuffer;
218 *
219 * ## Notes
220 *
221 * **Don't follow this with a semicolon**
222 *
223 * ## Parameters
224 *
225 * None
226 *
227 */
228#define DECLARE_ASC_DEVICE_W_REGISTER_POINTERS() \
229 void _handle_nf_messages(ascii_serial_com *asc, char ascVersion, \
230 char appVersion, char command, char *data, \
231 size_t dataLen, void *state_vp); \
232 bool streaming_is_on = false; \
233 circular_buffer_uint8 extraInputBuffer; \
234 \
235 uint8_t _tmp_byte; \
236 uint8_t _extraInputBuffer_raw[_extraInputBuffer_size_]; \
237 ascii_serial_com_device _ascd; \
238 ascii_serial_com_register_pointers _reg_pointers_state; \
239 ascii_serial_com_device_config _ascd_config = { \
240 .func_rw = ascii_serial_com_register_pointers_handle_message, \
241 .state_rw = &_reg_pointers_state, \
242 .func_nf = _handle_nf_messages, \
243 .state_nf = &streaming_is_on}; \
244 void _handle_nf_messages(__attribute__((unused)) ascii_serial_com *asc, \
245 __attribute__((unused)) char ascVersion, \
246 __attribute__((unused)) char appVersion, \
247 char command, __attribute__((unused)) char *data, \
248 __attribute__((unused)) size_t dataLen, \
249 void *state_vp) { \
250 bool *state = (bool *)state_vp; \
251 if (command == 'n') { \
252 *state = true; \
253 } else if (command == 'f') { \
254 *state = false; \
255 } \
256 }
257
258/** \brief Setup for ascii_serial_com_device and
259 * ascii_serial_com_register_pointers
260 *
261 * Setup that should be inside main() before the polling loop. This is part of
262 * a group of macros to ease use of ascii_serial_com_register_pointers with
263 * ascii_serial_com_device.
264 *
265 * ## Notes
266 *
267 * **Make sure this is inside a Try/Catch block**
268 *
269 * **Follow this with a semicolon**
270 *
271 * ## Parameters
272 *
273 * register_map: the register map, type: REGTYPE * array
274 *
275 * register_write_masks: the register write masks, type: REGTYPE array
276 *
277 * nRegs: the length of the two arrays above
278 *
279 */
280#define SETUP_ASC_DEVICE_W_REGISTER_POINTERS(register_map, \
281 register_write_masks, nRegs) \
282 do { \
283 ascii_serial_com_register_pointers_init( \
284 &_reg_pointers_state, register_map, register_write_masks, nRegs); \
285 ascii_serial_com_device_init(&_ascd, &_ascd_config); \
286 \
287 circular_buffer_init_uint8(&extraInputBuffer, _extraInputBuffer_size_, \
288 _extraInputBuffer_raw); \
289 } while (0)
290
291/** \brief Polling for ascii_serial_com_device and
292 * ascii_serial_com_register_pointers
293 *
294 * Within the polling loop, handles transmitting bytes, processing received
295 * bytes, and handling received messages. This is part of a group of macros to
296 * ease use of ascii_serial_com_register_pointers with ascii_serial_com_device.
297 *
298 * ## Notes
299 *
300 * **Assumes that something else receives bytes and puts them in**
301 * `extraInputBuffer`
302 *
303 * **Make sure this is inside a Try/Catch block**
304 *
305 * **Follow this with a semicolon**
306 *
307 * ## Parameters
308 *
309 * uart: the address of the USART used for transmitting bytes. For STM32:
310 * USART1, USART2, .... For AVR: UDR0, ....
311 *
312 */
313#define HANDLE_ASC_COMM_IN_POLLING_LOOP(uart_no) \
314 do { \
315 uart_tx_from_circ_buf(uart_no, \
316 ascii_serial_com_device_get_output_buffer(&_ascd)); \
317 if (!circular_buffer_is_empty_uint8(&extraInputBuffer)) { \
318 _ATOMIC { \
319 _tmp_byte = circular_buffer_pop_front_uint8(&extraInputBuffer); \
320 } \
321 circular_buffer_push_back_uint8( \
322 ascii_serial_com_device_get_input_buffer(&_ascd), _tmp_byte); \
323 } \
324 ascii_serial_com_device_receive(&_ascd); \
325 } while (0)
326
327/** \brief Check if you should stream a message to the host
328 *
329 * This is a boolean value macro that checks if streaming is enabled and the
330 * transmit buffer is empty. If true, the user may stream a message to the host
331 * now with STREAM_TO_HOST_ASC_DEVICE_W_REGISTER_POINTERS
332 *
333 * This is part of a group of macros to ease use of
334 * ascii_serial_com_register_pointers with ascii_serial_com_device.
335 *
336 * ## Notes
337 *
338 * **Make sure this is inside a Try/Catch block**
339 *
340 */
341#define READY_TO_STREAM_ASC_DEVICE_W_REGISTER_POINTERS \
342 (streaming_is_on && \
343 circular_buffer_get_size_uint8( \
344 ascii_serial_com_device_get_output_buffer(&_ascd)) == 0)
345
346/** \brief Stream data to the host
347 *
348 * This macro puts a message in the output buffer
349 *
350 * This is part of a group of macros to ease use of
351 * ascii_serial_com_register_pointers with ascii_serial_com_device.
352 *
353 * ## Notes
354 *
355 * **Make sure this is inside a Try/Catch block**
356 *
357 * ## Parameters
358 *
359 * data: a pointer to data or array of data to be sent
360 *
361 * data_size: a uint describing the data size in bytes
362 *
363 */
364#define STREAM_TO_HOST_ASC_DEVICE_W_REGISTER_POINTERS(data, data_size) \
365 ascii_serial_com_device_put_s_message_in_output_buffer(&_ascd, '0', '0', \
366 data, data_size)
367
368#endif