UART only works for 8N1; API exposes functions that mishandle UARTLCR #548
Labels
No labels
bazel
bluetooth
board settings
boot_stage2
bug
build
c++
can't reproduce
clang
clib-interface
cmsis
code cleanliness
code size
compiler version issues
cyw43_driver
debug
discussion
documentation
duplicate
dynamic_clock
elf2uf2
enhancement
errata
filesystem
flash layout
floating point
FreeRTOS
hardware_adc
hardware_claim
hardware_clocks
hardware_dma
hardware_exception
hardware_flash
hardware_gpio
hardware_i2c
hardware_irq
hardware_pio
hardware_pwm
hardware_rcp
hardware_rtc
hardware_spi
hardware_sync
hardware_uart
hardware_vreg
hardware_watchdog
hardware_xosc
help wanted
host
IAR
installation
invalid
linker scripts
lto
lwip
mbedtls
missing API
needs reporter detail
networking
newlib
newlib-multicore
non-c
non-cmake
non-gcc
openocd
orphan PR
OS integration
pico_async_context
pico_base
pico_binary_info
pico_flash
picolibc
pico_lwip
pico_malloc
pico_multicore
pico_printf
pico_runtime
pico_stdio
pico_stdio_usb
pico_sync
pico_time
pico_util
pioasm
port
question
review
review2
rtos interop
runtime init
static library
svd
test
tinyusb
vscode
wifi integration
wontfix
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-raspberrypi/pico-sdk#548
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The documented Hardware UART API exposes useful-sounding functions such as
uart_set_baudrate()anduart_set_format()(and others) that write to the UARTLCR registers. This can only be done safely if the UART is not enabled, and then only if the UART is not currently busy transmitting or receiving a character, as documented in ARM's PrimeCell UART (PL011) Technical Reference Manual, in the section on the UARTLCR_H register. These functions make no attempt to ensure this is the case, and there is no warning in the API documentation.In fact there is no safe way to call these functions through the API, because the API does not expose the UART enable separately, but only through uart_init() and uart_deinit(). The programmer can test the enable with uart_is_enabled(), but cannot control it. If the programmer tries to comply with the restriction on writing UARTLCR by first calling uart_deinit(), then calling (say) uart_set_format(), and finally calling uart_init(), their work is immediately undone by uart_init(). The uart_init() functions HARD CODES the format value to 8 bits, 1 stop bit, and no parity! The result is that any attempt to use word size, stop bits, or parity values other than 8, 1, and none is doomed to failure.
Changing baud rate on the fly (by just calling uart_set_baudrate()) does seem to work empirically, but it still violates the requirements set out in the PL011 manual, so there may be cases I didn't try where it fails.
Changing the stop bits, word width, or parity on the fly (by just calling uart_set_format()) seems to work sometimes and fail sometimes. My current project requires changing all those values quite frequently (to exercise another device's serial port implementation). Sometimes it transmits with the wrong parameters, and other times it fails to transmit at all after a parameter change.
I did not investigate whether other settings in UARTLCR work or not. FIFO enable, for instance, is in UARTLCR_H, and uart_init() has a hard-coded value for that one, too. However, uart_init() erroneously sets the FIFO enable bit AFTER enabling the UART, so it is always in violation of the rule from the PL011 manual. The API function for this, uart_set_fifo_enabled(), writes UARTLCR_H without any checks, just as uart_set_format() does.
It's great that the API presents a simplified model of the UART, hiding all these weird rules from the programmer. I'm not suggesting that more of the details be exposed. In fact, I'd remove the
uart_is_enabled()function. I'm suggesting that the implementation of the API should do the necessary magic to make the simplified API work as expected, reliably. It should document any weird side effects caused by the magic (such as: the UART goes dead briefly to change parameters). The typical user should be able to get reliable performance out of the UART without worrying about these details.The Linux kernel has a surprisingly complicated-looking driver for PL011 UARTs, written originally by ARM and updated by other ARM-licensed manufacturers. It might be valuable to go through that driver and see if there are other hard-learned tricks that need to be included in the Pico SDK's UART APIs.
There is an issue in uart_init possibly which is related to this. It enables the module then attempts to enable the FIFO which is forbidden in PrimeCell UART manual. Loopback logic may not be supported by SDK. See this conversation for more information: https://www.raspberrypi.org/forums/viewtopic.php?f=144&t=318787
Recommend to changing this to Update SDK to implement PrimeCell UART. Also RP2040 datasheet lacks most of this, which is very strange.
Hardware_uart may need to be refactored. It looks like it currently only supports what is needed by CMake printf.
what is "CMake printf"?!
PICO_STDIO_UART, PICO_UART_DEFAULT_CRLF, PICO_DEFAULT_UART, PICO_DEFAULT_UART_BAUD_RATE, etc.
This
https://raspberrypi.github.io/pico-sdk-doxygen/group__pico__stdio.html
Looks like this:
https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__uart.html
Not like PL011
Thanks, this is definitely a bug. Hopefully we can keep the fixes cleanly contained on the implementation side.
There is a WIP pull request, currently only worth discussing, not running.
I'd like to preserve the current API. There is code added to enable/disable the UART around LCR updates, which may lead to loss of data, but this isn't such a big deal as different UART data formats are mutually unintelligible anyway.
Unfortunately the approach is a bit of a hack -- see comments at
github.com/raspberrypi/pico-sdk@25b98942ce/src/rp2_common/hardware_uart/uart.c (L73-L92). The PL011 doesn't seem to have the necessary status bits for you to poll in order to wait for the current TX/RX characters to complete like it asks you to do. This code just adds a 15 baud period busy wait after disabling the UART, to make sure in-progress characters have finished.I did have a look at the kernel driver but it's odd, doesn't seem to line up with the information in the reference manual. I have no idea where the 10-PCLK delay in
pl011_set_termioscomes from for example.This is not true. This UART module is complete with busy and FIFO flags. You can block. Note you will need to block on DMA? There needs to be warnings in the documentation for hardware_uart.
Taking enable out of uart_init and creating uart_enable is possible. However you could also create structure for uart_init. This is kind of bad. However baud change will now block and call deinit update current struct then proceed with init. Structure will need to be preserved.
MIcrochip's PLIB HAL is known for creating an enable and init routine which allowed reconfiguration then locking it in with enable. That is another approach here. Init would do most of the initialization but a few other things were also possible. This will need to be documented. Avoiding this for this issue here is possible with existing API.
Edit: Continued here - https://github.com/raspberrypi/pico-sdk/pull/556#issuecomment-915238869
There is a TX BUSY flag but no RX BUSY. Even if you poll for TX to completely drain, an RX can begin arbitrarily close to the point where you disable the UART, and you have no flag to precisely wait this out. You can't modify the LCR whilst that RX is still in progress. What am I missing?
Application logic problem. UART is not like SPI and I2C in that it lacks state and is async like. The SDK does not have enough information but does provide a means to solve this. I would recommend ignoring this. It is a both a valid and invalid problem.
The solution of blocking for it to arrive is potentially valid. If the application need instant frequency change after last byte this could be justified. However I think you could also assume that it is forced to be idle at the time of the call. The issue with delay is potentially overhead. The issue with and without it is confusion.
I spent some time reading the PL011 source to understand the actual reason for the requirement in the PL011 manual.
I can't guess why Arm added that check -- maybe they were being defensive against the TX/RX state machines reaching bad states when the format changes part way.
So based on this, I think:
The delay is ugly, so I am tempted to leave the baud rate change unpatched (as it should work fine based on reading the implementation, and empirically does work), add a new method to initialise the UART without enabling, and add a new requirement to the format change functions that they can only be called before the UART is enabled.
I can see why it's attractive to just reset the hardware with each configuration change. PL011 doesn't make this easy.
I had a closer look at this and it seems unrelated. They are getting a 0-pulse on the RX input because the GPIO is unconfigured, and at certain baud rates this appears as a 0-character prepended to their data.
I have a couple of additional observations from a simple test program and a logic analyser that may or may not be helpful. This is in the context of making changes whilst data is being sent:
uart_set_formatimmediately afteruart_set_baudratecauses the new baudrate to be ignored (but the IBRD and FBRD registers will still indicate the new baudrate)For completeness, here is the test program I used as a basis:
Is there any update on this? My project requires that I change UART parameters frequently as I am using a MUX to communicate with multiple different devices. Many of these devices are not 115200 + 8N1. I need to be able to change both baud rate (which appears to work with the caveats mentioned by @rhulme) as well as word size, parity, and stop bits.
For example, one protocol I will be using is SDI-12, where I will need 1200 baud, 7 bit words, Even parity, and 1 stop bit. Another is Modbus, which typically uses 8E1 frames.
None of these use cases seem to be currently supported by the hardware_uart API. I can try using the draft PR mentioned, but I would much prefer if this were "officially" fixed and released as part of the SDK. Seems the PR above has been sitting untouched for around a year. I can dig into using the hardware registers and attempting to write the functionality myself, but that seems a little silly given that this API already exists.
Edit: As a temporary workaround, I run the following after calling
uart_init(uart1, Baud);:This does not check if the UART is busy though, so still not a great solution.
MicroPython v1.19.1-879-g294098d28 on 2023-02-20; Raspberry Pi Pico with RP2040
Had perhaps the same problem in Python on Pico, also for modbus 8E1 Even Pairty
main.py
from machine import UART, Pin
uart0 = UART(0,baudrate=57600, bits=8, parity = 0, stop=1, rx=Pin(1),tx=Pin(0))
uart0.write("AAA")
When executing the code from "Thonny" it works
Receiving in my terminal, and this is correct.
01000001 01000001 01000001
When unplugging the USB port and reconnect so that it runs without thonny
Receiving this
01000001 01000001 11010000
Last byte is garbage..
This isue has been fixed 9 Mar 2023
https://github.com/micropython/micropython/issues/10976
Best regrads
Jørgen Johannessen
fix merged into develop