8000 stm32: Implement Asynchronous UART TX · Issue #1642 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

stm32: Implement Asynchronous UART TX #1642

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

Open
ryannathans opened this issue Nov 24, 2015 · 9 comments
Open

stm32: Implement Asynchronous UART TX #1642

ryannathans opened this issue Nov 24, 2015 · 9 comments
Labels
enhancement Feature requests, new feature implementations port-stm32

Comments

@ryannathans
Copy link
Contributor

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...

@blmorris
Copy link
Contributor

DMA can certainly be used to implement non-blocking transfers, and to expand a bit on your reference to #1422, I have since managed to get it to work that way for I2S - see #1361. However, while my I2S fork does work (sort of…) it still has some issues which keep it from being ready to merge upstream just yet.
It's funny that I saw your message just as I was sitting down to write a bit on the status of that PR.
I am hoping to help advance the case for non-blocking DMA transfers in other parts of the code once I can show it working well for I2S.

@ryannathans
Copy link
Contributor Author

@blmorris once that's merged... I am starting to see a future where micropython supports asynchronous writes for UART and SPI. Do you think this is far off from becoming a reality? (I haven't looked at your commit/changes yet)

EDIT:
Related: #1124
and
#1118

@danicampora
Copy link
Member

Hi,

Regarding asynchronous writes, this has been considered before, and it's somewhat planned to be added to the new hardware API. For the UART, it might be as follows:

uart.write(buf, async=True)

and if a callback notification is desired, previously you should have done:

uart.irq(handler=some_handler, trigger=UART.TX_DONE, priority=...)

@ryannathans
Copy link
Contributor Author

@danicampora That's excellent! Is there a documented proposed spec I can read?

EDIT: Found it: https://github.com/micropython/micropython/wiki/Hardware-API

@neilh10
Copy link
Contributor
neilh10 commented Nov 25, 2015

I found this same issue for "debug printf" in a stm32 project and the same issue applies for the print output to the UART in MP
The useage of DMA to feed the hardware is exciting, but intricate and I passed on it to do an interrupt driven.
Here is a detailed DMA tutorial ....
http://www.emblocks.org/wiki/tutorials/f401_f429_disco/serial

For a non-blocking wrap around serial buffer called by a "debug printf" function I've reused some code I wrote a long time ago It is still rough from a stm32 project context.
I can't post it here so, started a thread here
http://forum.micropython.org/viewtopic.php?f=6&t=1182

@dpgeorge dpgeorge changed the title Implement Asynchronous UART TX stm32: Implement Asynchronous UART TX Oct 24, 2017
@dpgeorge dpgeorge added port-stm32 enhancement Feature requests, new feature implementations labels Oct 24, 2017
@tobbad
Copy link
Contributor
tobbad commented Feb 5, 2018

I have a non blocking DMA driven UART TX transfer working on a local branch for UART1 and 2 on STM32L4/F4. However with the proposal I read here relies on the stream interface forwarding the async=True parameter to the UART stream write which is not implemented. So what I did is to implement the setting the timeout on the ioctrl of the stream interface to 0 and then using the DMA to transfer the data from RAM to the wire.

Further I implemented a possibility to register a callback for a certain OR-ed IRQ sources. And I implemented the IRQ on RX IDLE (that means if after a gap less RX of serial bytes there is for the duration of one byte no traffic on the wire).

Going forward to push this code I have the following questions:

  • Is the IRQ already implemented as described above. My implementation registers on an instance method the callback to be called. Further I have a method returning the reason for calling the callback.
  • How should the dma structure be handled? This whole structure needs about 70 Bytes of RAM - so storing it in the _pyb_uart_obj_t is not an option. For the current blocking DMA transfer in DAC, I2C, SPI... the structure is locally allocated on the stack which is not an option for non blocking call.

For my proof of concept I created one static variable in the uart.c and if on this structure the instance is set the dma is in use and therefore another DMA transfer can not be started which is not a good solution. In my opinion there should be some kind of dma structure memory pool inside dma.c. One such structure is obtained from the dma module by the eg. UART or SPI... module. The structure is setup, the transfer is started and in the dma IRQ callback the structure is returned to the dma-struct mem pool.

I would like to get some feedback on my thoughts to push a proper solution to the Repo.

Discussion about this and similar topics are:
#1361 #1422 #1430 #1533 and the HardwareAPI.

@peterhinch
Copy link
Contributor

I appreciate your solution is more general and suited to high baudrates. But for many use-cases uasyncio supports asynchronous UART I/O. This does concurrent input and output as can be demonstrated with an external loopback:

import uasyncio as asyncio
from pyb import UART
uart = UART(4, 9600)

async def sender():
    swriter = asyncio.StreamWriter(uart, {})
    while True:
        await swriter.awrite('Hello uart\n')
        await asyncio.sleep(2)

async def receiver():
    sreader = asyncio.StreamReader(uart)
    while True:
        res = await sreader.readline()
        print('Recieved', res)

loop = asyncio.get_event_loop()
loop.create_task(sender())
loop.create_task(receiver())
loop.run_forever()

@hoihu
Copy link
Contributor
hoihu commented Feb 16, 2018

Since I'm a working mate of @tobbad and partly responsible for his work on the uPy UART a couple of comments.

As @tobbad mentioned - the callback that can be registered with RX_IDLE is called if a byte-timeout occurs on the wire. The advantage is: You don't need a timeout timer or anything similar on the C-level. It's a HW feature of the STM32 uart.

This allows you to respond to a received UART frame very fast and in a deterministic way. This is convinient not only in fast baudrate scenarios, but also if a lot of other activities are processed (lets say I2C, SPI etc). It also helps to create low power applications, since the callback is only called once, at the the end of the frame and you don't need to poll the interface for incoming chars all the time.

I'm not much of an expert on uasyncio, but as far as I know, the scheduler of uasyncio will hop from one coro to the other in a round robin fashion? This makes generating a timely and precise response to an incoming frame difficult. I guess in such a scenario, you'll need to poll the uart fast - this generates unecessary overhead and restricts the use of coros which take a bit longer to yield.

I agree though if you don't need this, then asyncio will probably be sufficient.

RX_IDLE, together with the UART TX non-blocking DMA transfer allows you to implement an efficient, low overhead and fast communication RX-TX cycle to the peripheral that basically run in the "background" of uPy:

  1. set up RX_IDLE callback
  2. within the callback, delegate to soft_irq handling, using micropython.schedule
  3. process the received frame and setup response.
  4. send response over TX DMA (non blocking).

Siemens Building Technologies (the company I work for) is sponsoring work on uPy development. Our interest is to give back some of the staff we are doing to the community. We would very much look forward to feedback / opinions / suggestions to the questions asked by @tobbad (with the goal that we could provide a proper PR).

Thanks.

@peterhinch
Copy link
Contributor

I agree - there are use-cases for an RX_IDLE callback where a cooperative scheduler like uasyncio is too slow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature requests, new feature implementations port-stm32
Projects
None yet
Development

No branches or pull requests

8 participants
0