stm32: Implement Asynchronous UART TX
I am attempting to write large blocks (eg: 2048 bytes) of data to the UART at low baudrates whilst attempting to execute other code. I've discovered that the UART TX is actually blocking until the write is completed. This is a bit of a pain...
This issue is for creating an asynchronous write option for UART. Related to #1533
Probably either interrupt or DMA driven. Should ideally behave like the UART RX does, with a definable buffer in python code, or block as it currently does.
Reading related issues it seems like #1422 has had some success using DMA. Personally, I don't require the callback as I'm not streaming from a file, just an existing bytearray in memory.
I haven't had a chance to read the huge threads on some of the issues I've found so I'm not 100% up to date yet...
[ports/stm32]: Added generic MP IRQ callback and example for UART.
Reworked see #3971
Aim of PR
At several locations in the stm32/port (maybe in other ports too) there is a call from the interrupt context to a user defined micropython routine/method. As a result we have some duplicated code (see Handle_EXTI_Irq, timer_handle_irq_channel) and some ToDo (se eg. pin_irq method). Further the way in timer the callback is set-up should be unified to the way it is done elsewhere.
There are some discussions in the forum on this topic(Event/IRQ callback):
https://forum.micropython.org/viewtopic.php?f=2&t=1448
https://forum.micropython.org/viewtopic.php?f=6&t=4414
https://forum.micropython.org/viewtopic.php?f=6&t=1312
https://forum.micropython.org/viewtopic.php?f=2&t=194
Issues #1642 and the discussion in the Hardware API (https://github.com/micropython/micropython/wiki/Hardware-API) on IRQ
My proposed MP irq module tries to keep close to the API of pin.irq.
Example implementation of IDLE IRQ for UART
See implementation in the 2nd commit. To use this feature create a class:
import micropython
class UART_IDLE:
def __init__(self, uart):
self._uart = uart
self._uart.write("Hi there\n\r")
self._buffer = bytearray(64)
self._answer = b"Ready \r\n"
self._normal_context_ref = self.normal_context
def set_up_cb(self):
self._uart.irq(self.irq_idle_callback, self._uart.RX_IDLE_IRQ)
def irq_idle_callback(self, other):
print("IRQ ", self._uart.irq().flags())
micropython.schedule(self._normal_context_ref, 0)
def normal_context(self, value):
print("Get UART data in normal context")
cnt=self._uart.readinto(self._buffer)
data = "".join([chr(i) if 32<=i<128 else "." for i in self._buffer[:cnt] ])
self._uart.write("Received: \"%s\"\r\n" % data)
and instantiate it:
uart = pyb.UART(2, baudrate = 115200, timeout=1)
dut=UART_IDLE(uart)
dut.set_up_cb()
callback= uart.irq()
callback() # call the callback
print("Trigger flag %d " % uart.irq().trigger()) # Print active set triggers
The idle callback is fired on my L476Disco every time when after the start of a transfer there is no data on the UART RX for one character.
Cons
I know that @Damien once said he does not like the idea (https://forum.micropython.org/viewtopic.php?f=17&t=3747&p=21669&hilit=Event+callback#p21669). However we would like to have a fast reacting system which returns within a few ms an answer on the UART. I could imaging as well to automatically schedule the callback in the normal context (as in the class shown with micropython.schedule) but within the C code (Don't know yet how to do it, but it is probably possible).
@dhylands (https://forum.micropython.org/viewtopic.php?f=2&t=194&p=825&hilit=IRQ+callback#p925)
I would be happy for any feedback to improve this PR to make it merge-able.
Further steps
I would be glad to adapt other (can, timer .. which?) modules with this modification.