diff --git a/src/rp2_common/hardware_uart/include/hardware/uart.h b/src/rp2_common/hardware_uart/include/hardware/uart.h index 1599098a..6d3a3feb 100644 --- a/src/rp2_common/hardware_uart/include/hardware/uart.h +++ b/src/rp2_common/hardware_uart/include/hardware/uart.h @@ -130,6 +130,13 @@ typedef enum { * Put the UART into a known state, and enable it. Must be called before other * functions. * + * This function always enables the FIFOs, and configures the UART for the + * following default line format: + * + * - 8 data bits + * - No parity bit + * - One stop bit + * * \note There is no guarantee that the baudrate requested will be possible, the nearest will be chosen, * and this function will return the configured baud rate. * @@ -153,6 +160,17 @@ void uart_deinit(uart_inst_t *uart); * \ingroup hardware_uart * * Set baud rate as close as possible to requested, and return actual rate selected. + * + * The UART is paused for around two character periods whilst the settings are + * changed. Data received during this time may be dropped by the UART. + * + * Any characters still in the transmit buffer will be sent using the new + * updated baud rate. uart_tx_wait_blocking() can be called before this + * function to ensure all characters at the old baud rate have been sent + * before the rate is changed. + * + * This function should not be called from an interrupt context, and the UART + * interrupt should be disabled before calling this function. * * \param uart UART instance. \ref uart0 or \ref uart1 * \param baudrate Baudrate in Hz @@ -176,27 +194,25 @@ static inline void uart_set_hw_flow(uart_inst_t *uart, bool cts, bool rts) { /*! \brief Set UART data format * \ingroup hardware_uart * - * Configure the data format (bits etc() for the UART - * + * Configure the data format (bits etc) for the UART. + * + * The UART is paused for around two character periods whilst the settings are + * changed. Data received during this time may be dropped by the UART. + * + * Any characters still in the transmit buffer will be sent using the new + * updated data format. uart_tx_wait_blocking() can be called before this + * function to ensure all characters needing the old format have been sent + * before the format is changed. + * + * This function should not be called from an interrupt context, and the UART + * interrupt should be disabled before calling this function. + * * \param uart UART instance. \ref uart0 or \ref uart1 * \param data_bits Number of bits of data. 5..8 * \param stop_bits Number of stop bits 1..2 * \param parity Parity option. */ -static inline void uart_set_format(uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity) { - invalid_params_if(UART, data_bits < 5 || data_bits > 8); - invalid_params_if(UART, stop_bits != 1 && stop_bits != 2); - invalid_params_if(UART, parity != UART_PARITY_NONE && parity != UART_PARITY_EVEN && parity != UART_PARITY_ODD); - hw_write_masked(&uart_get_hw(uart)->lcr_h, - ((data_bits - 5u) << UART_UARTLCR_H_WLEN_LSB) | - ((stop_bits - 1u) << UART_UARTLCR_H_STP2_LSB) | - (bool_to_bit(parity != UART_PARITY_NONE) << UART_UARTLCR_H_PEN_LSB) | - (bool_to_bit(parity == UART_PARITY_EVEN) << UART_UARTLCR_H_EPS_LSB), - UART_UARTLCR_H_WLEN_BITS | - UART_UARTLCR_H_STP2_BITS | - UART_UARTLCR_H_PEN_BITS | - UART_UARTLCR_H_EPS_BITS); -} +void uart_set_format(uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity); /*! \brief Setup UART interrupts * \ingroup hardware_uart @@ -241,15 +257,20 @@ static inline bool uart_is_enabled(uart_inst_t *uart) { /*! \brief Enable/Disable the FIFOs on specified UART * \ingroup hardware_uart * + * The UART is paused for around two character periods whilst the settings are + * changed. Data received during this time may be dropped by the UART. + * + * Any characters still in the transmit FIFO will be lost if the FIFO is + * disabled. uart_tx_wait_blocking() can be called before this + * function to avoid this. + * + * This function should not be called from an interrupt context, and the UART + * interrupt should be disabled when calling this function. + * * \param uart UART instance. \ref uart0 or \ref uart1 * \param enabled true to enable FIFO (default), false to disable */ -static inline void uart_set_fifo_enabled(uart_inst_t *uart, bool enabled) { - hw_write_masked(&uart_get_hw(uart)->lcr_h, - (bool_to_bit(enabled) << UART_UARTLCR_H_FEN_LSB), - UART_UARTLCR_H_FEN_BITS); -} - +void uart_set_fifo_enabled(uart_inst_t *uart, bool enabled); // ---------------------------------------------------------------------------- // Generic input/output @@ -397,12 +418,7 @@ static inline char uart_getc(uart_inst_t *uart) { * \param uart UART instance. \ref uart0 or \ref uart1 * \param en Assert break condition (TX held low) if true. Clear break condition if false. */ -static inline void uart_set_break(uart_inst_t *uart, bool en) { - if (en) - hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_BRK_BITS); - else - hw_clear_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_BRK_BITS); -} +void uart_set_break(uart_inst_t *uart, bool en); /*! \brief Set CR/LF conversion on UART * \ingroup hardware_uart diff --git a/src/rp2_common/hardware_uart/uart.c b/src/rp2_common/hardware_uart/uart.c index ba48e87b..2a95d13d 100644 --- a/src/rp2_common/hardware_uart/uart.c +++ b/src/rp2_common/hardware_uart/uart.c @@ -53,10 +53,10 @@ uint uart_init(uart_inst_t *uart, uint baudrate) { uint baud = uart_set_baudrate(uart, baudrate); uart_set_format(uart, 8, 1, UART_PARITY_NONE); + // Enable FIFOs (must be before setting UARTEN, as this is an LCR access) + hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_FEN_BITS); // Enable the UART, both TX and RX uart_get_hw(uart)->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS; - // Enable FIFOs - hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_FEN_BITS); // Always enable DREQ signals -- no harm in this if DMA is not listening uart_get_hw(uart)->dmacr = UART_UARTDMACR_TXDMAE_BITS | UART_UARTDMACR_RXDMAE_BITS; @@ -69,6 +69,42 @@ void uart_deinit(uart_inst_t *uart) { uart_reset(uart); } +uint32_t uart_disable_before_lcr_write(uart_inst_t *uart) { + // Notes from PL011 reference manual: + // + // - Before writing LCR, must disable UART and wait for current TX + RX + // char to finish + // + // - There is a BUSY flag which waits for the current TX char, but this is + // OR'd with TX FIFO !FULL, so not usable when FIFOs are enabled and + // potentially nonempty + // + // - FIFOs can't be set to disabled whilst a character is in progress + // (else "FIFO integrity is not guaranteed") + // + // Combination of these means there is no general way to halt and poll for + // end of TX character, if FIFOs may be enabled. Either way, there is no + // way to poll for end of RX character. + // + // So... we're going to insert a 15 baud period delay before changing + // settings (comfortably higher than max data + start + stop + parity). + // Anything else would require API changes to permit a non-enabled UART + // state after init() where settings can be changed safely. + + uint32_t cr_save = uart_get_hw(uart)->cr; + hw_clear_bits(&uart_get_hw(uart)->cr, + UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS); + + uint32_t current_ibrd = uart_get_hw(uart)->ibrd; + uint32_t current_fbrd = uart_get_hw(uart)->fbrd; + uint64_t baud_period_usec = 1u + + ((uint64_t)64 * current_ibrd + current_fbrd) / + (4u * clock_get_hz(clk_peri)); + + busy_wait_us(15 * baud_period_usec); + return cr_save; +} + /// \tag::uart_set_baudrate[] uint uart_set_baudrate(uart_inst_t *uart, uint baudrate) { invalid_params_if(UART, baudrate == 0); @@ -86,19 +122,80 @@ uint uart_set_baudrate(uart_inst_t *uart, uint baudrate) { baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2; } - // Load PL011's baud divisor registers + // Need to cleanly disable UART before touching LCR + bool was_enabled = uart_is_enabled(uart); + uint32_t cr_save; + if (was_enabled) + cr_save = uart_disable_before_lcr_write(uart); + uart_get_hw(uart)->ibrd = baud_ibrd; uart_get_hw(uart)->fbrd = baud_fbrd; - - // PL011 needs a (dummy) line control register write to latch in the - // divisors. We don't want to actually change LCR contents here. + // PL011 needs a (dummy) LCR_H write to latch in the divisors. We don't + // want to actually change LCR_H contents here. hw_set_bits(&uart_get_hw(uart)->lcr_h, 0); + // Re-enable using saved control register value + if (was_enabled) + uart_get_hw(uart)->cr = cr_save; + // See datasheet return (4 * clock_get_hz(clk_peri)) / (64 * baud_ibrd + baud_fbrd); } /// \end::uart_set_baudrate[] +void uart_set_format(uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity) { + invalid_params_if(UART, data_bits < 5 || data_bits > 8); + invalid_params_if(UART, stop_bits != 1 && stop_bits != 2); + invalid_params_if(UART, parity != UART_PARITY_NONE && parity != UART_PARITY_EVEN && parity != UART_PARITY_ODD); + + bool was_enabled = uart_is_enabled(uart); + uint32_t cr_save; + if (was_enabled) + cr_save = uart_disable_before_lcr_write(uart); + + hw_write_masked(&uart_get_hw(uart)->lcr_h, + ((data_bits - 5u) << UART_UARTLCR_H_WLEN_LSB) | + ((stop_bits - 1u) << UART_UARTLCR_H_STP2_LSB) | + (bool_to_bit(parity != UART_PARITY_NONE) << UART_UARTLCR_H_PEN_LSB) | + (bool_to_bit(parity == UART_PARITY_EVEN) << UART_UARTLCR_H_EPS_LSB), + UART_UARTLCR_H_WLEN_BITS | + UART_UARTLCR_H_STP2_BITS | + UART_UARTLCR_H_PEN_BITS | + UART_UARTLCR_H_EPS_BITS); + + if (was_enabled) + uart_get_hw(uart)->cr = cr_save; +} + +void uart_set_fifo_enabled(uart_inst_t *uart, bool enabled) { + bool was_enabled = uart_is_enabled(uart); + uint32_t cr_save; + if (was_enabled) + cr_save = uart_disable_before_lcr_write(uart); + + hw_write_masked(&uart_get_hw(uart)->lcr_h, + (bool_to_bit(enabled) << UART_UARTLCR_H_FEN_LSB), + UART_UARTLCR_H_FEN_BITS); + + if (was_enabled) + uart_get_hw(uart)->cr = cr_save; +} + +void uart_set_break(uart_inst_t *uart, bool en) { + bool was_enabled = uart_is_enabled(uart); + uint32_t cr_save; + if (was_enabled) + cr_save = uart_disable_before_lcr_write(uart); + + if (en) + hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_BRK_BITS); + else + hw_clear_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_BRK_BITS); + + if (was_enabled) + uart_get_hw(uart)->cr = cr_save; +} + void uart_set_translate_crlf(uart_inst_t *uart, bool crlf) { #if PICO_UART_ENABLE_CRLF_SUPPORT uart_char_to_line_feed[uart_get_index(uart)] = crlf ? '\n' : 0x100;