UART with the same pin for both RX and TX doesn't work on ESP32 variants
Port, board and/or hardware
esp32 port
MicroPython version
MicroPython v1.23.0 on 2024-06-02; LOLIN_C3_MINI with ESP32-C3FH4
MicroPython v1.23.0 on 2024-06-02; LOLIN_S2_MINI with ESP32-S2FN4R2
MicroPython v1.23.0-602.g403401490d on 2024-10-25; LOLIN S3 MINI with ESP32S3
MicroPython v1.23.0 on 2024-06-02; Generic ESP32S3 module with ESP32S3
Reproduction
from micropython import const
from machine import Pin, UART, mem32
PIN_NUM = const(2)
PIN_FUNC_REG = const(0x60004554 + 4 * PIN_NUM) # GPIO_FUNCx_OUT_SEL_CFG_REG
UART_NUM = const(1)
UART_BAUDRATE = const(1000000)
pin = Pin(PIN_NUM)
# pass the same `pin` to `tx` and `rx` doesn't work!
uart = UART(UART_NUM, UART_BAUDRATE, tx=pin, rx=pin)
uart.write(b'test') # UART signal doesn't routed to `PIN_NUM `.
print(uart.read()) # nothing gets printed..
# pass only `tx` (to get the `UART_SIG` number)
uart = UART(UART_NUM, UART_BAUDRATE, tx=pin)
UART_SIG = mem32[PIN_FUNC_REG] # see "Peripheral Signals via GPIO Matrix" in datasheet
# now try again, this time overwrite `PIN_FUNC_REG` with previous value
uart = UART(UART_NUM, UART_BAUDRATE, tx=pin, rx=pin)
mem32[PIN_FUNC_REG] = UART_SIG # ESP32-C3 is 6, ESP32-S3 is 12...
uart.write(b'test')
print(uart.read()) # b'test' is printed!
Expected behaviour
I expected:
uart = UART(UART_NUM, UART_BAUDRATE, tx=pin, rx=pin)
to work- but is seems to overwrite the TX output configuration and only the RX pin is configured correctly.
My workaround is to re-write the TX output configuration after the UART initialization:
uart = UART(UART_NUM, UART_BAUDRATE, tx=pin, rx=pin)
mem32[PIN_FUNC_REG] = UART_SIG
I think the UART class needs to support this case of a single pin for both TX and RX.
I use it to control some single line smart servo motors with open-drain UART bus...
The ESP32 support this common UART configuration (like RS485),
it also support echo-cancellation that may also be a nice option for the UART class..
Observed behaviour
Tested the provided sample code on ESP32-S2, ESP32-S3, ESP32-C3 -
All have the same bug and the same suggested workaround fix.
Additional Information
No, I've provided everything above.
Code of Conduct
Yes, I agree
esp32/machine_uart: Allow passing -1 to specify pin will be unused.
Summary
The UART constructor previously required you to pass a TX and RX pin, else it would use the default pin numbers.
However, sometimes you don't want to allocate a pin for one of these functions, for example when you have a read-only or write-only UART. The workaround would be to pass an unused pin for the unused function, or to let some pin first be claimed by the UART and then steal it away for some other function.
The ESP-IDF supports leaving a pin unused by passing UART_PIN_NO_CHANGE (which equal -1) as the pin number.
machine_pin_get_id doesn't consider -1 a valid pin number (rightly so). So I used a small macro, inspired by the one used in machine_lan.c, that passes through a -1 as UART_PIN_NO_CHANGE.
(The RTS and CTS pins already default to UART_PIN_NO_CHANGE, so they were only changed for consistency with the other pin assignments.)
Testing
Tested it on a few ESP32-based boards where I have a read-only UART and no other free pins. When passing tx=-1 everything works as expected: The UART functions as normal, without trying to configure a pin for the TX function.
Generative AI
I did not use generative AI tools when creating this PR.