ether2ser 0.1.0
Ethernet <-> synchronous V.24 bridge firmware for RP2040 + W5500
Loading...
Searching...
No Matches
pio_tx_rx_driver.c
Go to the documentation of this file.
1/*
2 * ether2ser - Ethernet <-> synchronous V.24 (RS-232/V.28) bridge
3 *
4 * File: src/drivers/pio_tx_rx_driver.c
5 * Purpose: PIO TX/RX clock driver implementation.
6 *
7 * SPDX-License-Identifier: Apache-2.0
8 *
9 * Copyright (c) 2026 Florian <f.leuze@outlook.de>
10 */
11
12// Related headers
13#include "pio_tx_rx_driver.h"
14
15// Standard library headers
16#include <assert.h>
17#include <limits.h>
18#include <stdbool.h>
19#include <stddef.h>
20#include <stdint.h>
21
22// Library Headers
23#include "hardware/gpio.h"
24#include "hardware/pio.h"
25#include "hardware/platform_defs.h"
26#include "hardware/regs/pio.h"
27#include "pico/time.h"
28#include "pico/types.h"
29
30// Project Headers
31#include "drivers/gpio_driver.h"
32#include "drivers/v24_config.h"
33#include "platform/pinmap.h"
34#include "system/common.h"
35
36// Generated headers
37#include "led_activity_mirror.pio.h"
38#include "rck_rxd.pio.h"
39#include "tck_txd.pio.h"
40#include "xck_txd.pio.h"
41
45#define V24_RTS_MIN_HOLDOFF 200U
51#define V24_RTS_HOLDOFF_MARGIN 41U
52
53// This struct holds the runtime state of the v24 and shall not be exposed
55
57{
58 return (const v24_runtime_t*)&v24_runtime;
59}
60
62{
64}
65
66static void cts_raw_irq_handler(void)
67{
68 uint32_t events = gpio_get_irq_event_mask(V24_CTS);
69 uint32_t mask = GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE;
70 if (events & mask)
71 {
72 gpio_acknowledge_irq(V24_CTS, events & mask);
74 }
75}
76
77static void cts_irq_init(void)
78{
79 static bool initialized = false;
80 if (!initialized)
81 {
82 gpio_add_raw_irq_handler_masked(1U << V24_CTS, cts_raw_irq_handler);
83 initialized = true;
84 }
85
86 gpio_set_irq_enabled(V24_CTS, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);
87 irq_set_enabled(IO_IRQ_BANK0, true);
88}
89
90// NOLINTBEGIN(misc-include-cleaner)
91// These are actualy visible and clang-tidy has an issue
92// with the pico-sdk here
93static gpio_function_t pio_gpio_func(PIO pio)
94{
95 if (pio == pio0)
96 {
97 return GPIO_FUNC_PIO0;
98 }
99 if (pio == pio1)
100 {
101 return GPIO_FUNC_PIO1;
102 }
103 return GPIO_FUNC_NULL; // or assert(false)
104}
105// NOLINTEND(misc-include-cleaner)
106
107static float baud_to_clockdiv(V24_BAUDRATE_T baudrate)
108{
109 return SYS_CLK_HZ / (TX_PIO_CYCLES_PER_BIT * (float)baudrate);
110}
111
112#define LED_MIRROR_PIO pio0
114{
115 // For now this cant be used as pin 25 is also the
116 // reset signal for w5500
117 PIO pio = LED_MIRROR_PIO; // keep pio1 free for W5500 PIO-SPI
118 int pio_sm = pio_claim_unused_sm(pio, false);
119 if (pio_sm < 0)
120 {
121 LOG_ERROR("LED mirror: no free SM on pio0\r\n");
122 return;
123 }
124 LOG_INFO("LED Mirror: init pio%u sm%u \r\n", (unsigned)pio_get_index(pio), (unsigned)pio_sm);
125
126 if (!pio_can_add_program(pio, &led_mirror_program))
127 {
128 LOG_ERROR("LED mirror: no room for program on pio0\r\n");
129 pio_sm_unclaim(pio, (uint)pio_sm);
130 return;
131 }
132
133 uint offset = pio_add_program(pio, &led_mirror_program);
134
135 pio_sm_config cfg = led_mirror_program_get_default_config(offset);
136 sm_config_set_set_pins(&cfg, V24_STATUS_LED, 1);
137 sm_config_set_jmp_pin(&cfg, V24_TXD);
138 sm_config_set_in_pins(&cfg, V24_RXD);
139 sm_config_set_in_shift(&cfg, false, false, sizeof(uint32_t) * CHAR_BIT);
140
141 pio_gpio_init(pio, V24_STATUS_LED);
142 pio_sm_set_consecutive_pindirs(pio, (uint)pio_sm, V24_STATUS_LED, 1, true);
143
144 // TXD/RXD stay inputs; no pio_gpio_init needed for read-only pin sampling.
145 pio_sm_init(pio, (uint)pio_sm, offset, &cfg);
146 pio_sm_set_enabled(pio, (uint)pio_sm, true);
147
148 LOG_INFO("LED mirror: enabled on pio%u sm%d offset=%u\r\n", (unsigned)pio_get_index(pio),
149 pio_sm, (unsigned)offset);
150}
151
153{
154 config->baudrate = (baudrate >= V24_BAUD_1200) ? baudrate : V24_BAUD_1200;
155 uint32_t t_bit_us = (US_PER_SECOND + (uint32_t)baudrate - 1U) / (uint32_t)baudrate;
156 v24_runtime.tx_rts_holdoff_us = V24_RTS_HOLDOFF_MARGIN * t_bit_us; // start conservative
158 {
160 }
161 v24_runtime.rts_set = false;
162}
163
165{
166 config->polarities = init_polarities();
167 reinit_v24_config(config, baudrate);
168}
169
171{
172 if (v24_runtime.rx_pio != NULL)
173 {
174 uint32_t rx_stall_mask = (1U << (PIO_FDEBUG_RXSTALL_LSB + v24_runtime.rx_sm));
175 if ((v24_runtime.rx_pio->fdebug & rx_stall_mask) != 0U)
176 {
177 v24_runtime.rx_pio->fdebug = rx_stall_mask;
178 return true;
179 }
180 }
181 return false;
182}
183
184bool rx_get(uint8_t* data)
185{
186 assert(v24_runtime.rx_pio != NULL);
187 if (v24_runtime.rx_pio == NULL ||
188 pio_sm_is_rx_fifo_empty(v24_runtime.rx_pio, v24_runtime.rx_sm))
189 {
190 return false;
191 }
192 *data = (uint8_t)(pio_sm_get(v24_runtime.rx_pio, v24_runtime.rx_sm) >> RX_SHIFT_TO_LSB);
193 return true;
194}
196{
197 assert(v24_runtime.rx_pio != NULL);
198 pio_sm_set_enabled(v24_runtime.rx_pio, v24_runtime.rx_sm, false);
199
200 if (polarities)
201 {
202 gpio_set_inover(V24_RXC,
203 polarities->rxc_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
204 gpio_set_inover(V24_RXD,
205 polarities->rxd_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
206 gpio_set_inover(V24_DCD,
207 polarities->dcd_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
208 }
209
210 pio_sm_set_enabled(v24_runtime.rx_pio, v24_runtime.rx_sm, true);
211}
212
214{
215 if (v24_runtime.rx_pio != NULL)
216 {
217 pio_sm_set_enabled(v24_runtime.rx_pio, v24_runtime.rx_sm, false);
218 pio_sm_clear_fifos(v24_runtime.rx_pio, v24_runtime.rx_sm);
219 pio_sm_restart(v24_runtime.rx_pio, v24_runtime.rx_sm);
220 pio_sm_clkdiv_restart(v24_runtime.rx_pio, v24_runtime.rx_sm);
221 pio_sm_set_enabled(v24_runtime.rx_pio, v24_runtime.rx_sm, true);
222 }
223}
224
225void rx_clock_init(PIO pio, uint pio_sm, V24_RX_POLARITIES_T* polarities)
226{
227 LOG_INFO("RXC: init pio%u sm%u pin%u\r\n", (unsigned)pio_get_index(pio), (unsigned)pio_sm,
228 (unsigned)V24_RXC);
229
230 pio_sm_claim(pio, pio_sm);
231 v24_runtime.rx_pio = pio;
232 v24_runtime.rx_sm = pio_sm;
233 // Load PIO program
234 uint offset = pio_add_program(pio, &rck_rxd_program);
235 LOG_INFO("RXC: program offset=%u\r\n", (unsigned)offset);
236
237 // Route GPIO to PIO
238 pio_gpio_init(pio, V24_RXD);
239 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_RXD, 1, false);
240 pio_gpio_init(pio, V24_RXC);
241 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_RXC, 1, false);
242
243 // Configure state machine
244 pio_sm_config config = rck_rxd_program_get_default_config(offset);
245 sm_config_set_jmp_pin(&config, V24_RXC);
246 sm_config_set_in_pins(&config, V24_RXD);
247 sm_config_set_in_shift(&config, true, true, CHAR_BIT);
248 // External clock can keep streaming continuously; use joined RX FIFO for more headroom.
249 sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_RX);
250 pio_sm_init(pio, pio_sm, offset, &config);
251
252 // This implicitly enables the sm
253 rx_clock_update_settings(polarities);
254 LOG_INFO("RXC: enabled\r\n");
255}
256
257bool tx_poll(void)
258{
259 static bool deassert_pending = false;
260 static uint64_t deassert_deadline_us = 0;
261
262 if (!v24_runtime.rts_set)
263 {
264 deassert_pending = false;
265 return true;
266 }
267
268 bool fifo_empty = pio_sm_is_tx_fifo_empty(v24_runtime.tx_pio, v24_runtime.tx_sm);
269 bool stalled =
270 (v24_runtime.tx_pio->fdebug & (1U << (PIO_FDEBUG_TXSTALL_LSB + v24_runtime.tx_sm))) != 0;
271 bool ready_to_deassert = (fifo_empty || stalled);
272
273 uint64_t now_us = to_us_since_boot(get_absolute_time());
274
275 if (!ready_to_deassert)
276 {
277 deassert_pending = false;
278 return false;
279 }
280
281 if (!deassert_pending)
282 {
283 deassert_pending = true;
284 deassert_deadline_us = now_us + v24_runtime.tx_rts_holdoff_us;
285 return false;
286 }
287
288 if (now_us < deassert_deadline_us)
289 {
290 return false;
291 }
292
293 gpio_put(V24_RTS, 0);
294 v24_runtime.rts_set = false;
295 deassert_pending = false;
296 return true;
297}
298
299bool tx_put(uint8_t data)
300{
301 assert(v24_runtime.tx_pio != NULL);
302 if (v24_runtime.tx_pio == NULL)
303 {
304 return false;
305 }
306 if (!v24_runtime.rts_set)
307 {
308 // Indicate ready to send by setting RTS
309 v24_runtime.tx_pio->fdebug = (1U << (PIO_FDEBUG_TXSTALL_LSB + v24_runtime.tx_sm));
310 gpio_set_dir(V24_RTS, GPIO_OUT);
311 gpio_put(V24_RTS, 1);
312 v24_runtime.rts_set = true;
313 }
314 if (pio_sm_is_tx_fifo_full(v24_runtime.tx_pio, v24_runtime.tx_sm))
315 {
316 return false;
317 }
318 pio_sm_put(v24_runtime.tx_pio, v24_runtime.tx_sm, data);
319 return true;
320}
321
323{
324 if (v24_runtime.tx_pio == NULL)
325 {
326 return;
327 }
328
329 pio_sm_set_enabled(v24_runtime.tx_pio, v24_runtime.tx_sm, false);
330 pio_sm_clear_fifos(v24_runtime.tx_pio, v24_runtime.tx_sm);
331 pio_sm_restart(v24_runtime.tx_pio, v24_runtime.tx_sm);
332 pio_sm_clkdiv_restart(v24_runtime.tx_pio, v24_runtime.tx_sm);
333 v24_runtime.tx_pio->fdebug = (1U << (PIO_FDEBUG_TXSTALL_LSB + v24_runtime.tx_sm));
334 pio_sm_set_enabled(v24_runtime.tx_pio, v24_runtime.tx_sm, true);
335}
336
338{
339 assert(v24_runtime.tx_pio != NULL);
340 float clkdiv = baud_to_clockdiv(config->baudrate);
341
342 pio_sm_set_enabled(v24_runtime.tx_pio, v24_runtime.tx_sm, false);
343
344 V24_TX_POLARITIES_T* polarities = &(config->polarities.tx_polarities);
345
346 if (config->external_clock)
347 {
348 gpio_set_inover(V24_TXC_DCE,
349 polarities->txc_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
350 }
351 else
352 {
353 gpio_set_outover(V24_TXC_DTE,
354 polarities->txc_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
355 }
356 gpio_set_outover(V24_TXD,
357 polarities->txd_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
358 gpio_set_outover(V24_DTR,
359 polarities->dtr_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
360 gpio_set_outover(V24_RTS,
361 polarities->rts_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
362 gpio_set_inover(V24_CTS,
363 polarities->cts_inverted ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
364
365 if (!config->external_clock)
366 {
367 pio_sm_set_clkdiv(v24_runtime.tx_pio, v24_runtime.tx_sm, clkdiv);
368 }
369 else
370 {
371 // pio_sm_set_clkdiv(v24_runtime.tx_pio, v24_runtime.tx_sm, 1.0f);
372 }
373 pio_sm_set_enabled(v24_runtime.tx_pio, v24_runtime.tx_sm, true);
374}
375
376static void tx_clock_init_tck(PIO pio, uint pio_sm, V24_CONFIG_T* config)
377{
378 float clkdiv = baud_to_clockdiv(config->baudrate);
379
380 LOG_INFO("Initializing internal clock\r\n");
381 LOG_DEBUG("TXC: init pio%u sm%u pin%u baud=%u clkdiv=%.6f\r\n", (unsigned)pio_get_index(pio),
382 (unsigned)pio_sm, (unsigned)V24_TXC_DTE, (unsigned)config->baudrate, (double)clkdiv);
383
384 pio_sm_claim(pio, pio_sm);
385 v24_runtime.tx_pio = pio;
386 v24_runtime.tx_sm = pio_sm;
387
388 // Load PIO program
389 uint offset = pio_add_program(pio, &tck_txd_program);
390 LOG_DEBUG("TXC: program offset=%u\r\n", (unsigned)offset);
391
392 // Route GPIO to PIO
393 pio_gpio_init(pio, V24_TXC_DTE);
394 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_TXC_DTE, 1, true);
395 pio_gpio_init(pio, V24_TXD);
396 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_TXD, 1, true);
397 pio_gpio_init(pio, V24_CTS);
398 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_CTS, 1, false);
399
400 // Configure state machine
401 pio_sm_config sm_config = tck_txd_program_get_default_config(offset);
402 sm_config_set_sideset_pins(&sm_config, V24_TXC_DTE);
403 sm_config_set_out_pins(&sm_config, V24_TXD, 1);
404 sm_config_set_jmp_pin(&sm_config, V24_CTS);
405 sm_config_set_out_shift(&sm_config, true, true, CHAR_BIT);
406
407 pio_sm_init(pio, pio_sm, offset, &sm_config);
408
409 // This implicitly activates the sm
411 LOG_INFO("Setting RTS Holdoff to %u us\r\n", (unsigned)v24_runtime.tx_rts_holdoff_us);
412 LOG_INFO("TXC: enabled\r\n");
413}
414
415static void tx_clock_init_xck(PIO pio, uint pio_sm, V24_CONFIG_T* config)
416{
417 LOG_INFO("Initializing external clock\r\n");
418 LOG_DEBUG("TXC: init pio%u sm%u pin%u\r\n", (unsigned)pio_get_index(pio), (unsigned)pio_sm,
419 (unsigned)V24_TXC_DCE);
420
421 pio_sm_claim(pio, pio_sm);
422 v24_runtime.tx_pio = pio;
423 v24_runtime.tx_sm = pio_sm;
424
425 // Load PIO program
426 uint offset = pio_add_program(pio, &xck_txd_program);
427 LOG_DEBUG("XCK: program offset=%u\r\n", (unsigned)offset);
428
429 // Route GPIO to PIO
430 pio_gpio_init(pio, V24_TXC_DCE);
431 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_TXC_DCE, 1, false);
432 pio_gpio_init(pio, V24_TXD);
433 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_TXD, 1, true);
434 pio_gpio_init(pio, V24_CTS);
435 pio_sm_set_consecutive_pindirs(pio, pio_sm, V24_CTS, 1, false);
436
437 // Configure state machine
438 pio_sm_config sm_config = xck_txd_program_get_default_config(offset);
439 sm_config_set_in_pins(&sm_config, V24_TXC_DCE);
440 sm_config_set_out_pins(&sm_config, V24_TXD, 1);
441 sm_config_set_jmp_pin(&sm_config, V24_CTS);
442 // xck_txd.pio uses explicit `pull block` and emits exactly 8 bits/byte.
443 sm_config_set_out_shift(&sm_config, true, false, CHAR_BIT);
444
445 pio_sm_init(pio, pio_sm, offset, &sm_config);
446
447 // This implicitly activates the sm
449 LOG_INFO("Setting RTS Holdoff to %u us\r\n", (unsigned)v24_runtime.tx_rts_holdoff_us);
450 LOG_INFO("XCK: enabled\r\n");
451}
452
453void tx_clock_init(PIO pio, uint pio_sm, V24_CONFIG_T* config)
454{
455 config->external_clock ? tx_clock_init_xck(pio, pio_sm, config)
456 : tx_clock_init_tck(pio, pio_sm, config);
457 cts_irq_init();
458}
#define TX_PIO_CYCLES_PER_BIT
Number of TX PIO instructions executed per serialized bit.
Definition common.h:47
#define RX_SHIFT_TO_LSB
Bit shift used to normalize RX byte ordering to LSB position.
Definition common.h:37
#define LOG_INFO(...)
Definition common.h:164
#define LOG_DEBUG(...)
Definition common.h:165
#define US_PER_SECOND
The number of microseconds per second.
Definition common.h:52
#define LOG_ERROR(...)
Definition common.h:163
V24_POLARITIES_T init_polarities(void)
Build default V.24 polarity configuration.
Definition gpio_driver.c:35
#define V24_RXC
Definition pinmap.h:28
#define V24_DCD
Definition pinmap.h:30
#define V24_DTR
Definition pinmap.h:37
#define V24_TXD
Definition pinmap.h:35
#define V24_RTS
Definition pinmap.h:34
#define V24_TXC_DTE
Definition pinmap.h:29
#define V24_STATUS_LED
Definition pinmap.h:39
#define V24_RXD
Definition pinmap.h:33
#define V24_CTS
Definition pinmap.h:32
#define V24_TXC_DCE
Definition pinmap.h:27
bool tx_put(uint8_t data)
Queue one byte to the TX PIO FIFO.
void tx_clock_init(PIO pio, uint pio_sm, V24_CONFIG_T *config)
Initialize TX clock/data PIO state machine.
bool tx_poll(void)
Poll TX completion/holdoff state and manage RTS release.
static void cts_raw_irq_handler(void)
bool rx_get(uint8_t *data)
Read one received byte from RX PIO FIFO.
bool rx_clock_poll_stall(void)
Poll RX FIFO stall events and update the provided event counter.
void reinit_v24_config(V24_CONFIG_T *config, V24_BAUDRATE_T baudrate)
Reinitialize V.24 runtime configuration and derived timing values.
static float baud_to_clockdiv(V24_BAUDRATE_T baudrate)
void tx_clock_hard_reset(void)
Hard-reset TX SM state to discard queued/in-flight bytes.
const v24_runtime_t * get_v24_runtime(void)
Returns a pointer to the v24 runtime.
v24_runtime_t v24_runtime
uint32_t tx_clock_get_cts_toggle_seq(void)
Read current CTS edge sequence counter.
#define V24_RTS_HOLDOFF_MARGIN
RTS holdoff multiplier in bit-times. The final holdoff is: tx_rts_holdoff_us = margin * t_bit_us....
static void tx_clock_init_tck(PIO pio, uint pio_sm, V24_CONFIG_T *config)
static void tx_clock_init_xck(PIO pio, uint pio_sm, V24_CONFIG_T *config)
void led_mirror_init(void)
Initialize optional LED activity mirror PIO program.
void rx_clock_hard_reset(void)
Disable RX Path, clear fifos, restart SMs and CLKDIV and enable again.
void rx_clock_update_settings(V24_RX_POLARITIES_T *polarities)
Apply RX runtime settings to an already configured RX PIO SM. Take note that this function relies on ...
void tx_clock_update_settings(V24_CONFIG_T *config)
Apply TX runtime settings to an already configured TX PIO SM. Take note that this function relies on ...
void rx_clock_init(PIO pio, uint pio_sm, V24_RX_POLARITIES_T *polarities)
Initialize RX clock/data PIO state machine.
static gpio_function_t pio_gpio_func(PIO pio)
static void cts_irq_init(void)
void init_v24_config(V24_CONFIG_T *config, V24_BAUDRATE_T baudrate)
Initialize V.24 runtime configuration structure.
#define LED_MIRROR_PIO
#define V24_RTS_MIN_HOLDOFF
Minimum RTS holdoff in microseconds used as a safety floor.
Runtime V.24 configuration and TX holdoff state.
Definition v24_config.h:87
V24_POLARITIES_T polarities
Definition v24_config.h:91
V24_BAUDRATE_T baudrate
Definition v24_config.h:89
bool external_clock
Definition v24_config.h:97
V24_TX_POLARITIES_T tx_polarities
Definition v24_config.h:79
RX/input polarity configuration.
Definition v24_config.h:65
TX/control output polarity configuration.
Definition v24_config.h:48
volatile uint32_t cts_toggle_seq
uint32_t tx_rts_holdoff_us
V24_BAUDRATE_T
Supported synchronous V.24 baudrates.
Definition v24_config.h:32
@ V24_BAUD_1200
Definition v24_config.h:33