Alternative Implementations of UART DMA Tx Driver

yevpator75 wrote on Monday, January 07, 2019:

Just wanted to hear opinions about my ideas of implementation of UART DMA Tx driver.
I want to use DMA to transmit data over UART. I want to create a task that will be pending on its queue and once a message arrives to the queue AND the DMA TX is NOT busy now, it will initiate DMA transfer.
That is to say we have 2 conditions in order to start: a new message and DMA is not busy. Assume we have only 1 TX driver implemented as a task that can access to this specific DMA.

I thought to implement that as following:

Method #1.
1 The DMA interrupt will tell to the driver task whether it is free by means of a binary semaphore of maybe event group with a single bit (BTW which one is more appropriate in this case?).
2. The task will be pending on the semaphore or the event group I mentioned in #1
3. Once the task wakes up, because DMA Tx is free, the program will be pending on a queue with messages to be transmitted.
4. If there is a new message in the queue, it i s pulled and DMA transfer is initiated
Disadvantage - I will wakeup the task on every DMA busy-to-free condition (“transfer done”), while I am looking for 2 conditions, as mentioned above: DMA is not busy AND we have what to transmit. This led me to think about another implementation.

Method #2

  1. I will create a Queue Set that will consist of the queue with the messages to be tranmsitted and a semaphore that will indicate if the DMA is busy or not.
  2. The task will wake up only if 2 conditions are met.
    **Is the solution feasible or maybe I missed something ? I saw in the “Mastering FreeRTOS …” the author recommends to use the feature only when the use is absolutely necesarry. I wonder if in my case there is a justification to use the feature?

Do you see other implementation options?
I am looking to to reduce tasks wakeups, if possible .

I will be gratefull for any idea or opinion !

hs2sf wrote on Monday, January 07, 2019:

I’d use method #1. In fact the internal TXREADY event (I’d use a light weight task notification) is an implementation detail and can be used to implement the simple state machine you described with a few lines of code. IMHO there is no significant disadvantage. I’d propose to avoid micro optimizing the rather short task wakeup, which is needed anyway if a message is already pending.

richarddamon wrote on Tuesday, January 08, 2019:

I would agree with Method #1, your dual conditions can be made serial. It might be a bit more efficient to wait on the queue first, so that you wait for the next message, and then check if the DMA is busy via the semaphore/event group/direct task notification (probably the best option). Assuming that most of the time, the new message comes after the previous is finished, then check for done will be very quick and not need to block and context switch. (if you expect new messages often come while still sending the previous, than wait for the DMA done first, and you might even have the DMA interrupt check the queue, and if there is a new message, switch to it in the ISR itself if this can be done quickly enough, and the task never needs to activate for chained messages.

yevpator75 wrote on Tuesday, January 08, 2019:

Hi Richard,
First, thanks for taking time to help me!

Assuming that most of the time, the new message comes after the previous is finished, then check for done will be very quick and not need to block and context switch.
I assumed exactly the opposite (-: and the order of 2 conditions was chosen by me intentionally. However, now I think I don’t know the statistics and therefore I have now enough inforrmation to decide, I may try with either approach and then using for example Percepio to determine what is more efficient way.

Now I am making the conditions more complicated. I have to provide to driver a method to drive 2 (or maybe even 3) UARTs with DMA. So the conditions to wakeup the task are

  1. UART1 DMA is ready and Message #1 (must be sent only through UART1) is pending
  2. UART2 DMA is ready and Message #2 (must be sent only through UART2) is pending

Waking up task on other combination is wasting time. In this situation I thought I have to divide the task into 2 parts: each one will behave like you suggested.
Alternatively I tried to think what would be an alterntive approach with one task and again thought about the Queue Set (2 queue for each UART) and found it to be less efficient for me than having 2 separate tasks. Do you see a better solution?

richarddamon wrote on Tuesday, January 08, 2019:

First, waking up the task to have it imediately block on another condition wastes very little time.

Second, for two devices, unless there is something that really links them together, I find two separate drivers tend to work better, or for something like this, 0 driver tasks, but a simple driver function which uses a semaphore for each device.

Each task that wants to print a message, rather than pushing the message on a queue, calls the driver function with a control block to indicate which device. Driver function takes (and waits on) the semaphore for that channel. When it has it, initiates the DMA transaction, and the ISR gives the semaphore back when the transfer is complete.