-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
The documented Hardware UART API exposes useful-sounding functions such as uart_set_baudrate() and uart_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.