Skip to content
Snippets Groups Projects
Commit 25b98942 authored by Luke Wren's avatar Luke Wren
Browse files

Disable UART when writing to LCR

parent c08136b6
No related tags found
No related merge requests found
......@@ -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.
*
......@@ -154,6 +161,17 @@ void uart_deinit(uart_inst_t *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
* \return Actual set baudrate
......@@ -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
......
......@@ -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;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment