Skip to content

Problems with pico-sdk UART transmission: blocking exits early, and (possibly PL011) 2nd stop bit missing #1274

@sdbbs

Description

@sdbbs

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:

  1. uart_write_blocking does block, but returns early: when it returns, two bytes (in this test) still remain to be sent
    • The documentation for uart_write_blocking explicitly 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.

  2. 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_format explicitly 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_stdlib and builds only against pico_runtime; also stdio_usb and stdio_uart are 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_buffer with some random binary content
  • After setup, main goes into the infinite while loop, where:
    • it sleeps for 250 ms
    • then it sets debug pin HIGH, executes uart_write_blocking with the intent to send/TX all 24 bytes of tx_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:

build_1152008N1_decode_1152008N1_OK

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:

build_1152008N2_decode_1152008N2_bad

... 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:

build_1152008N2_decode_1152008N1_OK

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:

build_1152008N2_decode_1152008N1_blocking

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

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions