New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stm32/uart: Add irq handler support for RXNE (character received). #4599
Conversation
Just wondering, did you have a need for this? And did you find that you could respond to these IRQs fast enough (I guess it depends on the baudrate though)? |
This isn't replacing the existing C interrupt handler, so it's still moving data into the rx buffer at full speed. This RXNE interrupt just gives a higher update rate than the IDLE one which I'm using to pass incoming data on to a library for further processing. Actually thinking about it now, the problem may just be a race condition in the existing uart_irq_handler() between when it handles incoming data and clears interrupt flags. It should probably snapshot and clear all flags at the start of the function and work on the snapshot, rather than checking and clearing as it goes down. Since pushing this I've found I also need a TX Complete interrupt for the same library, along with a modified uart_tx_data that doesn't block until complete. |
Thanks for the info.
Maybe. In any case, that should be fixed separately.
It sounds like the TXC IRQ is independent to the non-blocking write, true? If they are separate commits they can go in this same PR. |
b762087
to
e80f077
Compare
I've replaced the original commit with two separate ones. Firstly, by moving the IDLE flag check to the top of the interrupt handler my unreliability in it has disappeared. The second one, rather than just adding explicit support for RXNE, I've removed the restrictions entirely. |
The aim of not touching the RXNE was, to not interfere with the REPL. In my opinion if you deactivate the RXNE (which will be allowed if you remove the black/white list) your interactive REPL is gone and there is no communication possible with the device any more. Further my impression is, that it is rather dangerous to allow the user to activate such high frequency IRQs which will come in at a rate which MP just can not handle. For RX Idle: As you probably noticed, you only get that IRQ if you have a gap in your input data stream on the RX pin for at least one character. Therefore if you have an endless stream you would probably get a overwrite in the ring buffer and never an RX Idle IRQ - as RX is never idle. For your application I would rather vote for a buffer half or 3/4 full/empty callback from the interrupt. The callback would be called from the IRQ context if the conditions are full filled and you'll have larger datablocks to be processed in MP. For short: I think to process single chars (RXNE/TXNE IRQs) IRQ driven in MP is not that great and would stall the normal MP processing. Try to process larger blocks of data at lower frequency better suits - at least in my opinion - the "spirit" of Micropython. |
@tobbad Thanks for the clarification. That being said, preventing access to single char interrupts just in case a user's application can't keep up is quite limiting. I'd prefer to perhaps document that it may be too slow to keep up, but it depends on the baud rate and the cpu speed (200Mhz stm32's can keep up with a lot even via the python vm). Regarding turning RXNE off, I can't see where it's currently being switched on? Either way, with the re-ordering of how the user interrupt flags is set in the interrupt handler, my IDLE interrupt now seems to be working reliably without needing RXNE so my arguments there are purely academic. |
In the top of uart.c there are some #defines which de-/activate the RXNE IRQ. These Macros are used in uart_set_rxbuf to activate the IRQ if a buffer is given to the MP UART constructor helper in uart_machine (Approx line# 312, source as today) |
Ah, I see. Yes my changes wont have affected this, so RXNE will always be enabled by default. Even if it gets disabled, it's re-enabled in the interrupt handler if anything else triggers that and the hardware rxne flag is set. |
ports/stm32/uart.c
Outdated
self->mp_irq_flags = self->uartx->SR; | ||
if (self->uartx->SR & USART_SR_IDLE) { | ||
(void)self->uartx->SR; | ||
(void)self->uartx->DR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this will break the RXNE handler below because it'll read the current incoming data (from DR) and clear the RXNE flag. Probably best to cache the SR and DR values here, or similar. It's a bit tricky, and is different on F4 vs other MCUs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know why DR was ever being read/cleared by this block?
The way I read it, if the rx buffer was already full (above) DR would not have been read into the buffer, but then this would clear / throw it away. Perhaps it's better to just clear SR and delete the DR line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok ignore that, the ref manual does list this as the only way to clean idle flag.
In general having less restrictions on what the user can do, the better. In the case here, it still might be useful in some scenarios to have access to RXNE from Python. For low baudrates it would work fine, and for UARTs not attached to the REPL it wouldn't interfere with the REPL. So I think it's a good change to remove the restriction. |
e80f077
to
42e548c
Compare
@andrewleech I see you pushed some commits. Is this ready for re-review? But note, there is still a compile failure on Travis. |
921374d
to
5620c33
Compare
@dpgeorge I've finally fixed the build issues on F4 in the latest push. I've run the change on my stm32F429 discovery board which has a uart repl to PC via the onboard debugger interface. I tried a flood test by sending a large amount of data in one go (3700 bytes) and it misses a lot of them (only ~3400 show up on screen, starts missing every few chars part way though the block). |
Thanks for updating. But I think there are still issues on the F4: since DR is read if IDLE is set it may be that a character is lost if the RX buffer is already full, or if RX buffering is disabled. This is because the DR value that is read at the start of the IRQ handler is only used if buffering is enabled and not full, otherwise it's discarded. I think the way around this would be to have a special 1-character buffer in the uart object to cache it. This would require a lot of testing to make sure it works, so I understand if you don't have time to do that (maybe I will get to it...). Also, doesn't there need to be a new constant like |
That's something I'd missed, however I think that bug might exist in the current implementation already. If the buffer is already full, #if defined(STM32F4)
if (self->uartx->SR & USART_SR_IDLE) {
(void)self->uartx->SR;
(void)self->uartx->DR;
self->mp_irq_flags |= UART_FLAG_IDLE;
} which will read/clear the DR register regardless of whether it was copied into buffer earlier or not. I think it would likely be better to never run |
60a5c70
to
5b13788
Compare
5b13788
to
c8e20dd
Compare
@andrewleech what's the status of this, is it ready for re-review? |
I believe so, though my previous comment still stands. I think there's an issue in the F4 implementation still that already exists in current code regarding the case where the buffer is already full. |
#if defined(STM32F4) | ||
if (self->uartx->SR & USART_SR_IDLE) { | ||
(void)self->uartx->SR; | ||
(void)self->uartx->DR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is a bug in the existing implementation, which still occurs in my changes. In the case where the buffer is already full, the data in DR
will not have been read above and will still be sitting there. That data will now be cleared/lost here if the IDLE flag is also set.
I guess the reality in any uart system is if the buffer isn't read fast enough data is being lost, so this is no different. It might be nice to detect this and set a flag the user can see though?
Other than this, I do believe this PR is good for final review, I've been using it unchanged for a long time now on our project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it was broken before this PR! Eg if rxbuf=0
then there is no buffer and it is supposed to allow 1 character reads, but with irqs enabled all chars disappear.
c8e20dd
to
2e4bab9
Compare
2e4bab9
to
2385d25
Compare
2385d25
to
31bd679
Compare
1a3e577
to
9c31627
Compare
9c31627
to
de3673c
Compare
de3673c
to
3502983
Compare
It looks like this change has now broken this PR completely on F4 MCUs.... Testing on a PYBv1.0 the IRQ_RXIDLE callback now locks up the device (even with a large rx buffer). This is because when the IDLE IRQ comes in this flag is never cleared, the SR register is read but DR is never read so it never clears the flag and the IRQ is called continuously. |
See #6082 for an attempt to try and fix the issues here in a different way. |
…orage Turn storage back on for NeoPixel Trinkey
The idea is to try and make the IRQ handler more atomic in the operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the code will disable interrupts if the DR is full and wait for the user to read it before reenabling the interrupts. Fixes issue mentioned in micropython#4599.
Prior to this commit IRQs on STM32F4 could be lost because SR is cleared by reading SR then reading DR. For example, if both RXNE and IDLE IRQs were active upon entry to the IRQ handler, then IDLE is lost because the code that handles RXNE comes first and accidentally clears SR (by reading SR then DR to get the incoming character). This commit fixes this problem by making the IRQ handler more atomic in the following operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the new code disables interrupts if the DR is full and waits for the user to read it before reenabling the interrupts. Fixes issue mentioned in micropython#4599 and micropython#6082.
Prior to this commit IRQs on STM32F4 could be lost because SR is cleared by reading SR then reading DR. For example, if both RXNE and IDLE IRQs were active upon entry to the IRQ handler, then IDLE is lost because the code that handles RXNE comes first and accidentally clears SR (by reading SR then DR to get the incoming character). This commit fixes this problem by making the IRQ handler more atomic in the following operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the new code disables interrupts if the DR is full and waits for the user to read it before reenabling the interrupts. Fixes issue mentioned in micropython#4599 and micropython#6082.
Prior to this commit IRQs on STM32F4 could be lost because SR is cleared by reading SR then reading DR. For example, if both RXNE and IDLE IRQs were active upon entry to the IRQ handler, then IDLE is lost because the code that handles RXNE comes first and accidentally clears SR (by reading SR then DR to get the incoming character). This commit fixes this problem by making the IRQ handler more atomic in the following operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the new code disables interrupts if the DR is full and waits for the user to read it before reenabling the interrupts. Fixes issue mentioned in micropython#4599 and micropython#6082. Signed-off-by: Damien George <damien@micropython.org>
Missing IDLE IRQ is fixed by 83827e8 |
Prior to this commit IRQs on STM32F4 could be lost because SR is cleared by reading SR then reading DR. For example, if both RXNE and IDLE IRQs were active upon entry to the IRQ handler, then IDLE is lost because the code that handles RXNE comes first and accidentally clears SR (by reading SR then DR to get the incoming character). This commit fixes this problem by making the IRQ handler more atomic in the following operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the new code disables interrupts if the DR is full and waits for the user to read it before reenabling the interrupts. Fixes issue mentioned in micropython#4599 and micropython#6082. Signed-off-by: Damien George <damien@micropython.org>
Prior to this commit IRQs on STM32F4 could be lost because SR is cleared by reading SR then reading DR. For example, if both RXNE and IDLE IRQs were active upon entry to the IRQ handler, then IDLE is lost because the code that handles RXNE comes first and accidentally clears SR (by reading SR then DR to get the incoming character). This commit fixes this problem by making the IRQ handler more atomic in the following operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the new code disables interrupts if the DR is full and waits for the user to read it before reenabling the interrupts. Fixes issue mentioned in micropython#4599 and micropython#6082. Signed-off-by: Damien George <damien@micropython.org>
This extends the uart irq support from 372e7a4 / #3971 to allow running the irq handler for
UART_FLAG_RXNE
, ie for every character received.