mirror of
https://github.com/apache/nuttx.git
synced 2026-10-04 04:38:10 +00:00
arch/xtensa/esp32s3: Release RS-485 DIR on TX_DONE.
In RS-485 mode the driver released the direction (DE) pin only on TX_BRK_IDLE_DONE. That interrupt belongs to the break feature (UART_TXD_BRK), which this driver never enables, so it never fired and DIR stayed asserted after the first transmit. The board then kept driving the bus and collided with every reply. The interrupt was also never cleared, so had it fired, the handler would have re-entered forever. TX_DONE cannot simply replace it: the upper half calls txint(false) as soon as its software buffer drains, which disabled TX_DONE while the last bytes were still in the FIFO (see #15888). * Keep TX_DONE enabled in txint(false) while in RS-485 mode. * In the handler, on TX_DONE with the software buffer and TX FIFO empty, wait (bounded) for the transmitter FSM to go idle so the last stop bit is not clipped, release DIR and disable TX_DONE. This is how ESP-IDF's RS-485 half-duplex mode handles it. * Make txempty() use FIFO count and FSM state, as ESP-IDF's uart_ll_is_tx_idle() does. The raw TX_DONE bit reads 0 before the first transmission and is now cleared by the handler, which would make tcdrain() wait for its full timeout. Tested on an ESP32-S3 board with an SP3485 transceiver (DE/RE on GPIO21) at 115200 baud, doing Modbus RTU reads against a servo drive: without the patch every request timed out; with it DIR drops right after the last stop bit and all reads succeed. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Max Kriegleder <max.kriegleder@gmail.com>
This commit is contained in:
parent
3297bbec44
commit
fe7eff404e
1 changed files with 47 additions and 22 deletions
|
|
@ -321,13 +321,31 @@ static int uart_handler(int irq, void *context, void *arg)
|
|||
int_status = getreg32(UART_INT_ST_REG(priv->id));
|
||||
|
||||
#ifdef HAVE_RS485
|
||||
if ((int_status & UART_TX_BRK_IDLE_DONE_INT_ST_M) != 0 &&
|
||||
esp32s3_txempty(dev))
|
||||
/* Release the RS-485 driver once the frame has physically left the
|
||||
* transmitter. TX_DONE stays enabled while DIR is asserted (see
|
||||
* esp32s3_txint()): the upper half disables TX interrupts as soon as its
|
||||
* software buffer drains, while the last bytes are still in the FIFO.
|
||||
* TX_BRK_IDLE_DONE is not usable here: it belongs to the break feature
|
||||
* (UART_TXD_BRK), which this driver never enables.
|
||||
*/
|
||||
|
||||
if (priv->rs485_dir_gpio != 0 &&
|
||||
(int_status & UART_TX_DONE_INT_ST_M) != 0 &&
|
||||
dev->xmit.tail == dev->xmit.head &&
|
||||
REG_MASK(getreg32(UART_STATUS_REG(priv->id)), UART_TXFIFO_CNT) == 0)
|
||||
{
|
||||
if (dev->xmit.tail == dev->xmit.head)
|
||||
/* TX_DONE can precede the transmitter FSM returning to idle by the
|
||||
* tail of the last stop bit; wait for it (bounded) so the stop bit
|
||||
* is not clipped.
|
||||
*/
|
||||
|
||||
for (int i = 0; i < 1000 && !esp32s3_txempty(dev); i++)
|
||||
{
|
||||
esp_gpiowrite(priv->rs485_dir_gpio, !priv->rs485_dir_polarity);
|
||||
up_udelay(1);
|
||||
}
|
||||
|
||||
esp_gpiowrite(priv->rs485_dir_gpio, !priv->rs485_dir_polarity);
|
||||
modifyreg32(UART_INT_ENA_REG(priv->id), UART_TX_DONE_INT_ENA_M, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -616,18 +634,6 @@ static void esp32s3_txint(struct uart_dev_s *dev, bool enable)
|
|||
|
||||
if (enable)
|
||||
{
|
||||
/* After all bytes physically transmitted in the RS485 bus
|
||||
* the TX_BRK_IDLE will indicate we can disable the TX pin.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_RS485
|
||||
if (priv->rs485_dir_gpio != 0)
|
||||
{
|
||||
modifyreg32(UART_INT_ENA_REG(priv->id),
|
||||
0, UART_TX_BRK_IDLE_DONE_INT_ENA);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set to receive an interrupt when the TX holding register register
|
||||
* is empty
|
||||
*/
|
||||
|
|
@ -638,9 +644,22 @@ static void esp32s3_txint(struct uart_dev_s *dev, bool enable)
|
|||
}
|
||||
else
|
||||
{
|
||||
/* Disable the TX interrupt */
|
||||
/* Disable the TX interrupt. In RS-485 mode keep TX_DONE enabled:
|
||||
* uart_handler() needs it to release DIR once the FIFO has drained,
|
||||
* and disables it itself when it does.
|
||||
*/
|
||||
|
||||
modifyreg32(UART_INT_ENA_REG(priv->id), ints_mask, 0);
|
||||
#ifdef HAVE_RS485
|
||||
if (priv->rs485_dir_gpio != 0)
|
||||
{
|
||||
modifyreg32(UART_INT_ENA_REG(priv->id),
|
||||
UART_TXFIFO_EMPTY_INT_ENA_M, 0);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
modifyreg32(UART_INT_ENA_REG(priv->id), ints_mask, 0);
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
|
@ -755,13 +774,19 @@ static bool esp32s3_txready(struct uart_dev_s *dev)
|
|||
|
||||
static bool esp32s3_txempty(struct uart_dev_s *dev)
|
||||
{
|
||||
uint32_t reg;
|
||||
struct esp32s3_uart_s *priv = dev->priv;
|
||||
|
||||
reg = getreg32(UART_INT_RAW_REG(priv->id));
|
||||
reg = REG_MASK(reg, UART_TX_DONE_INT_RAW);
|
||||
/* Same test as ESP-IDF's uart_ll_is_tx_idle(): FIFO drained and the
|
||||
* transmitter FSM idle. The raw TX_DONE bit is not usable: it reads 0
|
||||
* before the first transmission, and uart_handler() clears it whenever
|
||||
* the TX_DONE interrupt is enabled -- in RS-485 mode, until DIR has been
|
||||
* released after each frame.
|
||||
*/
|
||||
|
||||
return reg > 0;
|
||||
return REG_MASK(getreg32(UART_STATUS_REG(priv->id)),
|
||||
UART_TXFIFO_CNT) == 0 &&
|
||||
REG_MASK(getreg32(UART_FSM_STATUS_REG(priv->id)),
|
||||
UART_ST_UTX_OUT) == 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue