Uart.flush() not enough for reliable transmission of last byte
Hell.
I am using an RS485 module to send data between RP2040s at a baud rate of 115200. My RS485 module requires an enable line to be taken high for transmitting data and low for receiving data so I include that in my sending routine. I had always thought that a uart0.flsuh() would allow the last byte to be sent before the enable line is taken low but it seems that often the last byte is incorrect/corrupt and I have to use a time.sleep() to make the data transmission reliable. I am only sending up to 19 bytes at a time. It works but for my own education is there a better way of doing this rather than just guessing at a sleep value? I'd like to know what my misunderstanding of the point of .flsuh() is.
I am using an RS485 module to send data between RP2040s at a baud rate of 115200. My RS485 module requires an enable line to be taken high for transmitting data and low for receiving data so I include that in my sending routine. I had always thought that a uart0.flsuh() would allow the last byte to be sent before the enable line is taken low but it seems that often the last byte is incorrect/corrupt and I have to use a time.sleep() to make the data transmission reliable. I am only sending up to 19 bytes at a time. It works but for my own education is there a better way of doing this rather than just guessing at a sleep value? I'd like to know what my misunderstanding of the point of .flsuh() is.
Code: Select all
def send485(d):
#print("Send", d.hex(":"))
tx_en.value(1)
uart0.write(d)
uart0.flush()
time.sleep(0.001) #this is just a guess but it seems to work
tx_en.value(0)Re: Uart.flush() not enough for reliable transmission of last byte
Are you using an old version of Micropython? I think this was fixed quite some time ago.
Re: Uart.flush() not enough for reliable transmission of last byte
jimseng wrote: ↑Thu Nov 06, 2025 9:59 amHell.
I am using an RS485 module to send data between RP2040s at a baud rate of 115200. My RS485 module requires an enable line to be taken high for transmitting data and low for receiving data so I include that in my sending routine. I had always thought that a uart0.flsuh() would allow the last byte to be sent before the enable line is taken low but it seems that often the last byte is incorrect/corrupt and I have to use a time.sleep() to make the data transmission reliable. I am only sending up to 19 bytes at a time. It works but for my own education is there a better way of doing this rather than just guessing at a sleep value? I'd like to know what my misunderstanding of the point of .flsuh() is.
Code: Select all
def send485(d): #print("Send", d.hex(":")) tx_en.value(1) uart0.write(d) uart0.flush() time.sleep(0.001) #this is just a guess but it seems to work tx_en.value(0)
Not attempting to block your discussion of this, but perhaps it is an implementation issue that would be better directed to the maintainers/developers of MicroPython.
Perhaps via https://github.com/orgs/micropython/discussions or https://github.com/micropython/micropython/issues
Beware of the Leopard
Re: Uart.flush() not enough for reliable transmission of last byte
Could always use the .txdone() method to determine whether the buffer is empty or not
Or the irq() to request a callback to clear the tx enable once the buffer is empty.
https://docs.micropython.org/en/latest/ ... .UART.html
Or the irq() to request a callback to clear the tx enable once the buffer is empty.
https://docs.micropython.org/en/latest/ ... .UART.html
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter
Pi Interests: Home Automation, IOT, Python and Tkinter
Re: Uart.flush() not enough for reliable transmission of last byte
flush() calls txdone() internally:scotty101 wrote: ↑Thu Nov 06, 2025 10:42 amCould always use the .txdone() method to determine whether the buffer is empty or not
Or the irq() to request a callback to clear the tx enable once the buffer is empty.
https://docs.micropython.org/en/latest/ ... .UART.html
https://github.com/micropython/micropyt ... art.c#L685
Re: Uart.flush() not enough for reliable transmission of last byte
I'm pretty sure I am using a very recent uf2 for the Pico but I'll check and make sure.
I thought perhaps I had misunderstood what flush() is supposed to achieve but maybe not.
I thought perhaps I had misunderstood what flush() is supposed to achieve but maybe not.
Re: Uart.flush() not enough for reliable transmission of last byte
What RS485 module is it?
Might be that the module needs a little bit of hold-off, or the network itself.
Might be that the module needs a little bit of hold-off, or the network itself.
Re: Uart.flush() not enough for reliable transmission of last byte
After looking a bit closer it looks like it just checks txdone(), and if not wait long enough to transmit the buffer twice.Lobo-T wrote: ↑Thu Nov 06, 2025 10:47 amflush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685
If anything it ought to be a problem with being to slow. And looping over txdone might be quicker.
How does your receiving end look like?
Is it waiting for a specific number of bytes? Could it be a erronous first byte in the buffer that makes it not read the last?
Re: Uart.flush() not enough for reliable transmission of last byte
Lobo-T wrote: ↑Thu Nov 06, 2025 10:45 amFound it: https://github.com/micropython/micropython/pull/16052
Any version from 1.24 onwards should have had the fix applied and I can't see why it wouldn't work, why there would need to be a delay after 'flush' to lowering the TX Enabled line. There would be some instruction cycles anyway between the firmware determining everything, including stop bits, had been sent and that physical line being set low.Lobo-T wrote: ↑Thu Nov 06, 2025 10:47 amflush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685
First thing I would do is change to -
Code: Select all
tx_en.value(1)
uart0.write(d)
result = uart0.flush()
tx_en.value(0
if result != 0:
print("uart0.flush() timed-out")
Second thing I would try is configuring the UART to send two stop bits, to see if that improves things. That will give an idea of what sort of delay may need adding. I would also replace the inserted 'time.sleep(0.001)' fix with 'pass', for two stop bits, then one, to see if that is a good enough fix if it turns out we need one.
I'd also look at what's being sent on a scope or logic analyser.
The issue may not be with transmission but on the receive side - Though I am struggling to see how that could be.
It would be worth posting your receive-side code, and detailing the nature of the corruption you are seeing.
Re: Uart.flush() not enough for reliable transmission of last byte
I don't understand why it even has a timeout when there's no expectation the 'txdone' would ever fail. Or why they calculate the timeout in such a complicated manner, what the 33 is for -Lobo-T wrote: ↑Thu Nov 06, 2025 4:42 pmAfter looking a bit closer it looks like it just checks txdone(), and if not wait long enough to transmit the buffer twice.Lobo-T wrote: ↑Thu Nov 06, 2025 10:47 amflush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685
If anything it ought to be a problem with being to slow. And looping over txdone might be quicker.
Code: Select all
timeout_microseconds = int((33 + tx_buffer_size) * 13000000 * 2 / baud_rate)
But there is a scenario when 'txdone' could prematurely indicate everything has been sent when it hasn't been. That would depend on how bytes are transferred from the TX Buffer to the actual UART. If the UART FIFO is emptied, the last byte has been transmitted, 'txdone' gets asserted, but there might still be bytes in the TX Buffer which haven't been transferred to the UART FIFO.
I haven't looked into that, don't know what MicroPython does. I would normally expect an interrupt to occur whenever a byte was placed in the UART Transmit Buffer, the UART TX FIFO then has space for another byte, the TX Buffer has a byte taken from it and placed into TX FIFO. But, if the interrupts are deferred, or scheduled for handling, it may be possible 'txdone' gets asserted before that interrupt is handled. As said, I haven't looked at it.
Added
Then I thought 'txdone' might be checking if TX BUffer is empty which would resolve the above. And it seems it may be intending to do that, but I am not convinced it does -
https://github.com/micropython/micropython/blob/master/ports/rp2/machine_uart.c#L510
Code: Select all
static bool mp_machine_uart_txdone(machine_uart_obj_t *self) {
// TX is done when: nothing in the ringbuf, TX FIFO is empty, TX output is not busy.
return ringbuf_avail(&self->write_buffer) == 0
&& (uart_get_hw(self->uart)->fr & (UART_UARTFR_TXFE_BITS | UART_UARTFR_BUSY_BITS)) == UART_UARTFR_TXFE_BITS;
}
Last edited by hippy on Thu Nov 06, 2025 5:36 pm, edited 1 time in total.
Re: Uart.flush() not enough for reliable transmission of last byte
I can't really understand it either.
The pico-sdk's uart_tx_wait_blocking() only checks UART_UARTFR_BUSY_BITS
https://github.com/raspberrypi/pico-sdk ... art.h#L431
And I'm quite certain that works, because I have an RP2040 running as a Profibus slave at 1.5Mbps. It takes pretty much the whole of core1 to keep up with Profibus at that speed, but it does.
The 33 caught my eye aswell. Because it's the Profibus protocols tSyn. Minimum bit time a slave must wait before answering the master. Maybe some copy-paste programming.
Re: Uart.flush() not enough for reliable transmission of last byte
Looking at the patch (diif) https://github.com/micropython/micropyt ... c9c51L494
it's a little bit of a strange logic change, not sure if it solved the problem:
it's a little bit of a strange logic change, not sure if it solved the problem:
Code: Select all
return ringbuf_avail(&self->write_buffer) == 0
- && (uart_get_hw(self->uart)->fr & UART_UARTFR_TXFE_BITS);
+ && (uart_get_hw(self->uart)->fr & (UART_UARTFR_TXFE_BITS | UART_UARTFR_BUSY_BITS)) == UART_UARTFR_TXFE_BITS;
Last edited by gmx on Thu Nov 06, 2025 5:41 pm, edited 1 time in total.
Re: Uart.flush() not enough for reliable transmission of last byte
We cross-posted so you may have missed my edit - What do you think of 'ringbuf_avail(&self->write_buffer) == 0' ?
The second part I am happy with; check TXFE and BUSY bits, and done when TXFE=1, BUSY=0.
The second part I am happy with; check TXFE and BUSY bits, and done when TXFE=1, BUSY=0.
Re: Uart.flush() not enough for reliable transmission of last byte
I meant how it was changed, hard to grasp the logic of it.
Re: Uart.flush() not enough for reliable transmission of last byte
Logic seems sound to me. They were only checking that TX FIFO was empty, hadn't considered any transmission in progress. They now do.
is just an optimised
Code: Select all
&& (uart_get_hw(self->uart)->fr & (UART_UARTFR_TXFE_BITS | UART_UARTFR_BUSY_BITS)) == UART_UARTFR_TXFE_BITS;
Code: Select all
&& ((uart_get_hw(self->uart)->fr & UART_UARTFR_TXFE_BITS) != 0)
&& ((uart_get_hw(self->uart)->fr & UART_UARTFR_BUSY_BITS) == 0)
Re: Uart.flush() not enough for reliable transmission of last byte
Still ugly, just undermining any confidence.
Maybe a logic analyzer is more useful than analyzing such twisted logic. :)
Maybe a logic analyzer is more useful than analyzing such twisted logic. :)
Re: Uart.flush() not enough for reliable transmission of last byte
I really don't see how you can call it "twisted logic". The routine is intended to indicate transmission is done when -
1) There is nothing in the transmit buffer, and
2) The UART TX FIFO is empty (TXFE=1), and
3) Nothing is being transmitted by the UART (BUSY=0)
I am not sure what other logic one would use.
1) There is nothing in the transmit buffer, and
2) The UART TX FIFO is empty (TXFE=1), and
3) Nothing is being transmitted by the UART (BUSY=0)
I am not sure what other logic one would use.
Re: Uart.flush() not enough for reliable transmission of last byte
wow. Lots of stuff to digest. I wish I had a scope but I can't justify the cost at this stage of my hobbies level.
So for instance I might send: 06:01:01:00:13:1B and often the 1B is some other value. Not always but with the time.sleep(0.001) it is always correct.
I'm using a very cheap RS485 to ttl module from Aliexpress. My guess is that the tx_enable line goes low while the last byte is still on the way out of the module. It's not really an issue but I thought flush() would work in this instance.
Thanks for all the help.
My first byte is the number of bytes in the frame and the last byte is a checksum. Without the time.sleep(0.001) the checksum is often simply the wrong value, especially as it gets higher in value.How does your receiving end look like?
Is it waiting for a specific number of bytes? Could it be a erronous first byte in the buffer that makes it not read the last?
So for instance I might send: 06:01:01:00:13:1B and often the 1B is some other value. Not always but with the time.sleep(0.001) it is always correct.
I'm using a very cheap RS485 to ttl module from Aliexpress. My guess is that the tx_enable line goes low while the last byte is still on the way out of the module. It's not really an issue but I thought flush() would work in this instance.
Can you expand on that a little? I'm not sure what that means exactly.Second thing I would try is configuring the UART to send two stop bits, to see if that improves things. That will give an idea of what sort of delay may need adding. I would also replace the inserted 'time.sleep(0.001)' fix with 'pass', for two stop bits, then one, to see if that is a good enough fix if it turns out we need one.
Thanks for all the help.
Re: Uart.flush() not enough for reliable transmission of last byte
Oh, wait. Is this one of those that don't have the data_enable line exposed to the microcontroller side?
Then there is your problem. Others have talked about these. I haven't seen a schematic for them, but I suppose they must just have an RC delay after tx going low. Or just based on the fact that idle is 1 and only activating enable on tx low.
Does it work better at lower speeds?
What is you termination scheme? If nothing drives the bus and no pull up and pull down, I think an auto-direction RS-485 driver might have problems.
Re: Uart.flush() not enough for reliable transmission of last byte
Have you checked the receiver is receiving the correct number of bytes, that it is the checksum which is corrupted, not the last byte being treated as a checksum when it isn't ?
It's a plausible theory but the 'uart.flush()' should have ensured that the byte and stop bits have been transmitted before it allows your code to continue and set your enabled line to zero.
The receiver should therefore have received the full and correctly framed byte, before the line is set to zero.
With a single stop bit and no 'time.sleeep()' the enable line should be set to zero soon after the last bit of data has been sent -jimseng wrote: ↑Thu Nov 06, 2025 7:36 pmCan you expand on that a little? I'm not sure what that means exactly.Second thing I would try is configuring the UART to send two stop bits, to see if that improves things. That will give an idea of what sort of delay may need adding. I would also replace the inserted 'time.sleep(0.001)' fix with 'pass', for two stop bits, then one, to see if that is a good enough fix if it turns out we need one.
Thanks for all the help.
Code: Select all
.--. 0 1 2 3 4 5 6 7 .---.-.
Data TX ---' |___:___:___:___:___:___:___:___:___| `-------
_________
TX Done ______________________________________________|
____________________________________________
TX Enable ___| |_______
Code: Select all
.--. 0 1 2 3 4 5 6 7 .---.---.-.
Data TX ---' |___:___:___:___:___:___:___:___:___| `---
_____
TX Done __________________________________________________|
________________________________________________
TX Enable ___| |___
Code: Select all
uart1 = UART( .... , stop=2)
We know a 1 millisecond 'time.sleep(0.001)' solves the problem. It would be interesting and I think useful to know if a less than 1 millisecond 'pass' or an 8.68 microsecond extra stop bit also resolves the problem.
Re: Uart.flush() not enough for reliable transmission of last byte
If I understand you correctly then no, the enable line is available to the Pico and I use a gpio to pull it low for receiving and send it high to transmit, which is where I am having the issue. It seems that the enable line is going low and ceasing transmission before the last byte has been completed.Oh, wait. Is this one of those that don't have the data_enable line exposed to the microcontroller side?
Code: Select all
It's a plausible theory but the 'uart.flush()' should have ensured that the byte and stop bits have been transmitted before it allows your code to continue and set your enabled line to zero.Code: Select all
Have you checked the receiver is receiving the correct number of bytes, that it is the checksum which is corrupted, not the last byte being treated as a checksum when it isn't ?I have run out of time tonight but I'll have another look tomorrow to see if I can establish something more concrete.
Re: Uart.flush() not enough for reliable transmission of last byte
I am now thinking it's the number of bytes that could be in the UART, FIFO + being transmitted, added to the TX Buffer size. Using -
Code: Select all
sent = uart.write("U" * 1000)
I think I have resolved the 'ringbuf_avail()' mystery. That seems to be 'how many bytes are available for taking from the buffer' so would be zero when nothing is in the buffer.
As to why they consider the symbol size to be 13 bits when 'start + data + parity + stop' bits is 12 maximum, that does seem to be 'just to be safe' as stated elsewhere in 'machine_uart.c'. Though weirdly 'print(uart)' always shows 'timeout_char=11' no matter what the actual configuration is.
Re: Uart.flush() not enough for reliable transmission of last byte
Thanks for the update, and it seems there's no need to faff about with two stop bits or 'pass' as it seems that's not going to solve the issue.jimseng wrote: ↑Thu Nov 06, 2025 8:56 pmYes, I have compared the sent bytes with the received bytes and they are the correct length (in my tests only 6 bytes including the checksum). The first 5 bytes are always correct. With a time.sleep(0) the last byte is often incorrect. With a time.sleep(0.001) it is always correct. I have tried shorter time periods but anything less than 0.001 gives me errors.
Interesting that anything less than a 1 millisecond delay seems to cause an issue. Even a 100 microsecond delay would be long after the last byte had been sent and presumably received. But there does seem to be an issue with 'time.sleep()' so how long the delay actually is becomes questionable - I'll start a separate thread on that.
Re: Uart.flush() not enough for reliable transmission of last byte
Try to use time.sleep_ms(ms) and time.sleep_us(us)
Jump to
- Community
- General discussion
- Announcements
- Other languages
- Deutsch
- Español
- Français
- Italiano
- Nederlands
- 日本語
- Polski
- Português
- Русский
- Türkçe
- User groups and events
- Raspberry Pi Official Magazine
- Using the Raspberry Pi
- Beginners
- Troubleshooting
- Advanced users
- Assistive technology and accessibility
- Education
- Picademy
- Teaching and learning resources
- Staffroom, classroom and projects
- Astro Pi
- Mathematica
- High Altitude Balloon
- Weather station
- Programming
- C/C++
- Java
- Python
- Scratch
- Other programming languages
- Windows 10 for IoT
- Wolfram Language
- Bare metal, Assembly language
- Graphics programming
- OpenGLES
- OpenVG
- OpenMAX
- General programming discussion
- Projects
- Networking and servers
- Automation, sensing and robotics
- Graphics, sound and multimedia
- Other projects
- Media centres
- Gaming
- AIY Projects
- Hardware and peripherals
- Camera board
- Compute Module
- Official Display
- HATs and other add-ons
- Device Tree
- Interfacing (DSI, CSI, I2C, etc.)
- Keyboard computers (400, 500, 500+)
- Raspberry Pi Pico
- General
- SDK
- MicroPython
- Other RP2040 boards
- Zephyr
- Rust
- AI Accelerator
- AI Camera - IMX500
- Hailo
- Software
- Raspberry Pi OS
- Raspberry Pi Connect
- Raspberry Pi Desktop for PC and Mac
- Beta testing
- Other
- Android
- Debian
- FreeBSD
- Gentoo
- Linux Kernel
- NetBSD
- openSUSE
- Plan 9
- Puppy
- Arch
- Pidora / Fedora
- RISCOS
- Ubuntu
- Ye Olde Pi Shoppe
- For sale
- Wanted
- Off topic
- Off topic discussion