We use some essential cookies to make our website work.

We use optional cookies, as detailed in our cookie policy, to remember your settings and understand how you use our website.

jimseng
Posts: 167
Joined: Mon Jul 02, 2012 11:33 am

Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 9:59 am

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.

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)

Lobo-T
Posts: 168
Joined: Fri Jan 22, 2021 10:52 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 10:38 am

Are you using an old version of Micropython? I think this was fixed quite some time ago.

B.Goode
Posts: 18782
Joined: Mon Sep 01, 2014 4:03 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 10:41 am

jimseng wrote:
Thu Nov 06, 2025 9:59 am
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.

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

scotty101
Posts: 4585
Joined: Fri Jun 08, 2012 6:03 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 10:42 am

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
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

Lobo-T
Posts: 168
Joined: Fri Jan 22, 2021 10:52 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 10:45 am

Lobo-T wrote:
Thu Nov 06, 2025 10:38 am
Are you using an old version of Micropython? I think this was fixed quite some time ago.
Found it: https://github.com/micropython/micropython/pull/16052

Lobo-T
Posts: 168
Joined: Fri Jan 22, 2021 10:52 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 10:47 am

scotty101 wrote:
Thu Nov 06, 2025 10:42 am
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
flush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685

jimseng
Posts: 167
Joined: Mon Jul 02, 2012 11:33 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 11:40 am

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.

gmx
Posts: 1845
Joined: Thu Aug 29, 2024 8:51 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 3:20 pm

What RS485 module is it?
Might be that the module needs a little bit of hold-off, or the network itself.

Lobo-T
Posts: 168
Joined: Fri Jan 22, 2021 10:52 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 4:42 pm

Lobo-T wrote:
Thu Nov 06, 2025 10:47 am
flush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685
After looking a bit closer it looks like it just checks txdone(), and if not wait long enough to transmit the buffer twice.
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?

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 4:43 pm

Lobo-T wrote:
Thu Nov 06, 2025 10:45 am
Lobo-T wrote:
Thu Nov 06, 2025 10:38 am
Are you using an old version of Micropython? I think this was fixed quite some time ago.
Found it: https://github.com/micropython/micropython/pull/16052
Lobo-T wrote:
Thu Nov 06, 2025 10:47 am
flush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685
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.

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")
I would expect that to never timeout but worth checking to discount that possibility.

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.

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 5:20 pm

Lobo-T wrote:
Thu Nov 06, 2025 4:42 pm
Lobo-T wrote:
Thu Nov 06, 2025 10:47 am
flush() calls txdone() internally:
https://github.com/micropython/micropyt ... art.c#L685
After looking a bit closer it looks like it just checks txdone(), and if not wait long enough to transmit the buffer twice.
If anything it ought to be a problem with being to slow. And looping over txdone might be quicker.
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 -

Code: Select all

timeout_microseconds = int((33 + tx_buffer_size) * 13000000 * 2 / baud_rate)
The timeout appears to me to be at least 2 times longer than it would take to actually send the bytes, more with smaller buffers. So it should never timeout, hard to see how it would.

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;
}
To me that looks like it's checking there are no available bytes in the TX Buffer, that is, it's full rather than empty ?
Last edited by hippy on Thu Nov 06, 2025 5:36 pm, edited 1 time in total.

Lobo-T
Posts: 168
Joined: Fri Jan 22, 2021 10:52 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 5:36 pm

hippy wrote:
Thu Nov 06, 2025 5:20 pm
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 -
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.

gmx
Posts: 1845
Joined: Thu Aug 29, 2024 8:51 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 5:38 pm

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:

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.

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 5:40 pm

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.

gmx
Posts: 1845
Joined: Thu Aug 29, 2024 8:51 pm

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 5:46 pm

Logic seems sound to me. They were only checking that TX FIFO was empty, hadn't considered any transmission in progress. They now do.

Code: Select all

&& (uart_get_hw(self->uart)->fr & (UART_UARTFR_TXFE_BITS | UART_UARTFR_BUSY_BITS)) == UART_UARTFR_TXFE_BITS;
is just an optimised

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)

gmx
Posts: 1845
Joined: Thu Aug 29, 2024 8:51 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 6:10 pm

Still ugly, just undermining any confidence.

Maybe a logic analyzer is more useful than analyzing such twisted logic. :)

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 7:33 pm

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.

jimseng
Posts: 167
Joined: Mon Jul 02, 2012 11:33 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 7:36 pm

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.
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?
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.
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.
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.
Can you expand on that a little? I'm not sure what that means exactly.
Thanks for all the help.

Lobo-T
Posts: 168
Joined: Fri Jan 22, 2021 10:52 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 8:02 pm

jimseng wrote:
Thu Nov 06, 2025 7:36 pm
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.
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.

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 8:13 pm

jimseng wrote:
Thu Nov 06, 2025 7:36 pm
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.
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 ?

jimseng wrote:
Thu Nov 06, 2025 7:36 pm
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.
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.

jimseng wrote:
Thu Nov 06, 2025 7:36 pm
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.
Can you expand on that a little? I'm not sure what that means exactly.
Thanks for all the help.
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 -

Code: Select all

 .--. 0 1 2 3 4 5 6 7 .---.-.
Data TX ---' |___:___:___:___:___:___:___:___:___| `-------
 _________
TX Done ______________________________________________|
 ____________________________________________
TX Enable ___| |_______
By using two stop bits that will delay the TX Done signal, which will delay your clearing the TX Enable line as if you had added a short sleep -

Code: Select all

 .--. 0 1 2 3 4 5 6 7 .---.---.-.
Data TX ---' |___:___:___:___:___:___:___:___:___| `---
 _____
TX Done __________________________________________________|
 ________________________________________________
TX Enable ___| |___
To set two stop bits I believe you need to add a 'stop' parameter to your UART creation -

Code: Select all

uart1 = UART( .... , stop=2)
You should still keep using one stop bit on the receiver, but you likely won't be able to send data back without errors.

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.

jimseng
Posts: 167
Joined: Mon Jul 02, 2012 11:33 am

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 8:56 pm

Oh, wait. Is this one of those that don't have the data_enable line exposed to the microcontroller side?
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.

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.
That is what I assumed but it doesn't seem to be happening correctly.

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 ?
Yes, 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. With time.sleep(0.001) I get absolutely no errors at all.
I have run out of time tonight but I'll have another look tomorrow to see if I can establish something more concrete.

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 9:10 pm

Lobo-T wrote:
Thu Nov 06, 2025 5:36 pm
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.
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)
Always returns 289 which suggests to me a 256 byte TX Buffer plus 33.

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.

hippy
Posts: 19831
Joined: Fri Sep 09, 2011 10:34 pm

Re: Uart.flush() not enough for reliable transmission of last byte

Thu Nov 06, 2025 9:49 pm

jimseng wrote:
Thu Nov 06, 2025 8:56 pm
Yes, 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.
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.

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.

gmx
Posts: 1845
Joined: Thu Aug 29, 2024 8:51 pm

Return to "MicroPython"

AltStyle によって変換されたページ (->オリジナル) /