-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Hi all,
I have prepared a small pico-sdk project, rp2040_uart_err_test, an RP2040 UART TX test, in this gist: rp2040_uart_err_test
I believe that it demonstrates the following two problems:
uart_write_blockingdoes block, but returns early: when it returns, two bytes (in this test) still remain to be sent-
The documentation for
uart_write_blockingexplicitly says:"This function will block until all the data has been sent to the UART"
... so I would expect either this function to indeed block until all data has been transmitted - or the help text needs to be more precise about what should be expected, otherwise it is misleading as it is.
-
- If UART is set up for 2 stop bits, then when transmitting (TX) over UART, 2nd stop bit is never transmitted
-
The documentation for
uart_set_formatexplicitly says:"stop_bits Number of stop bits 1..2 "
... so I would expect that two stop bits are sent, if I have configured the UART for two stop bits.
-
Few words about the UART TX test project, rp2040_uart_err_test:
- I have tried to make it as "low-level" as possible, to avoid possible setup interference with convenience features of "vanilla" projects; so the project avoids
pico_stdliband builds only againstpico_runtime; alsostdio_usbandstdio_uartare disabled - There is a setup for a UART RX interrupt service routine,
on_uart_rx_isr, but it is there simply to go through the setup motions - it is otherwise not expected to fire or do anything in this test - Clock is at 133 MHz
So, the main functionality is in the main function:
- There is a 24-byte global array
tx_bufferwith some random binary content - After setup,
maingoes into the infinitewhileloop, where:- it sleeps for 250 ms
- then it sets debug pin HIGH, executes
uart_write_blockingwith the intent to send/TX all 24 bytes oftx_buffer(via reference variables), and sets debug pin LOW
This would allow for the debug pin indicating the duration of uart_write_blocking. Here is the while loop:
// ...
static volatile bool mindicator;
while (1) {
sleep_ms(250);
mindicator = true;
gpio_put(DEBUG_PIN_A, mindicator);
volatile uint8_t* tx_buffer_p = NULL;
volatile size_t bytes_to_send = 0;
tx_buffer_p = (uint8_t*)&tx_buffer;
bytes_to_send = sizeof(tx_buffer);
uart_write_blocking(UART_ID, (const uint8_t *)tx_buffer_p, bytes_to_send);
mindicator = false;
gpio_put(DEBUG_PIN_A, mindicator);
}
// ...I have tried to use volatile variables to hint to the compiler not to reorder statements; looking at the output of arm-none-eabi-objdump -S rp2040_uart_err_test.elf that is not obvious at first, because there are branching jumps - but I've tried to follow the jumps, and it does seem the statements are in order.
Finally, as output of the test program, I am capturing the debug pin and the UART TX pin with Saleae Logic.
Let's look at the stop bit problem first. First, let's recall, that for a 115200 baud traffic, the bit period is 1/115200 ≈ 8.68 μs
The rp2040_uart_err_test code as submitted, sets up the UART (via #defines) for 115200 baud, 8N2 traffic. However, at first I tested with a 8N1 build, which worked great:
Note that I've activated the Async Serial decoder from Saleae Logic on the channel with the digital capture of the UART TX signal, and it decodes the right bytes; also, the duration of a byte in this case is measured at 87.68 μs - quite close to 10 bits (start + 8 data + stop) times the bit period 8.68 μs.
So, then I just change the STOP_BITS define to 2, rebuild, capture again, try to capture with Saleae Logic again - and set up the Async Serial decoder accordingly, with stop bits to 2:
... and I can see the Async Serial decoder report framing errors; but if I change back the settings of the Async Serial decoder to 8N1, it again decodes no errors:
Here we can see that even if I had specified 8N2, a byte with a duration 87.2 μs has been sent, quite close to the previous duration 87.68 μs - whereas 8N2 would require 1+8+1+1 = 11 bits, times expected duration 8.68 μs, that is around 95.48 μs for the full duration.
Conclusion: second stop bit had not been sent, even if I specified 8N2.
To be frank, I don't think this is pico-sdk problem; the PrimeCell® UART (PL011) Technical Reference Manual mentions:
3.3.7 Line Control Register, UARTLCR_H
...
3 STP2 Two stop bits select. If this bit is set to 1, two stop bits are transmitted at the end of the frame. The receive logic does not check for two stop bits being received.
... and uart_set_format in uart.h does indeed set those bits; and I even have a function get_uart_lcr_h in my code, so I can confirm in gdb (also note that actual_baud_rate is 115176, slightly less than requested 115200):
(gdb) p actual_baud_rate
$1 = 115176
(gdb) p actual_clock_sys_hz
$2 = 133000000
(gdb) p /t get_uart_lcr_h()
$3 = 1101000
# (6543210)
... and it's clear that at runtime, the STP2 (3rd) bit of UART_LCR_H register is indeed enabled - and yet, I've obtained the scope traces seen above, where there is no second stop bit.
And for the first problem: let's look at debug pin (which indicates the duration of uart_write_blocking) versus entire UART TX packet -- where for ease of perception, Async Serial decoder is set up for 8N1:
It is visible that right after the debug pin goes low (where supposedly uart_write_blocking stopped blocking and returned), another two bytes are being sent on wire.
Are there any solutions or workarounds for these issues?
EDIT: also related: UART only works for 8N1; API exposes functions that mishandle UARTLCR · Issue #548 · raspberrypi/pico-sdk



