2
\$\begingroup\$

I have a situation in which a microcontroller is to perform a large number of ADC conversions and format the results into commands (or data packages) and send these to a PC using the UART. In order to measure continuously while sending data, I created a circular buffer (queue/FIFO-buffer) to store the pending packages, and the microcontroller will then (ideally) empty this queue as fast as the PC will allow.

The microcontroller automatically sends the next command (if any) after an ACK-character (in this case a ‘!’) has been received. The UART Interrupt Handler is like this:

if (ch == '!') // ch is the received character
{
    cmd_rx_ack = '!';
    cmd_tx_pop_cmd();
}

The function cmd_tx_pop_cmd() is taking one package out of the circular buffer and putting the bytes int a cmd_tx[] array and the length of the package in cmd_tx_length.

In the main loop:

if (cmd_rx_ack == ‘!’)
{
    cmd_rx_ack = 0; // Reset ack status

    // Transmit the command
    if (cmd_tx_length > 0)
    {
        cmd_tx_transmit();
    }
}

The function cmd_tx_transmit() is simply transmitting the bytes in cmd_tx[] one by one (this is done in the main loop since it takes too long time to be done in the UART-handler).

This works fine, if the consumer of the queue (in this case the UART-handler) has higher priority than the producer (a timer that periodically makes the ADC convert and push a data package with the result onto the queue). Earlier I had concurrency problems (see Concurrency issues with circular buffer in embedded program) but now I have another problem:

I want the microcontroller to send the next package in the queue not only when an ACK has been received, but also in the situations listed below:

  1. The first package to be transmitted
  2. If the measurements are performed at a speed slower than the time it takes to send a package.

In the first situation, the package is never transmitted since no ACK is received after the package is pushed onto the queue.

In the second situation, the packages are never popped out of the queue for the same reason as in the first situation. (Assume the first package is ACKed and the UART-handler wants to transmit the next one, but the queue is empty at this point in time). In this case, the queue is just getting bigger and bigger and no packages are transmitted.

I can solve the first one by transmitting the first package manually (without the use of the queue) and put the remaining onto the queue. Not a pretty solution, though - but it works... I still need to solve the second problem.

So, I think that the UART-handler is not the right place to do the queue-popping since it cannot take account for the two situations I just mentioned. But I cannot put it in the main loop since it has lowest priority and then concurrency issues become a problem.

What would be a good way to implement this?

Thanks in advance :-)

BTW 1: An ACK is transmitted to the microcontroller if the PC accepted the transmitted package. I have made a special encoding of the packages so their length can be determined. This works just fine and cmd_tx_pop_cmd() will only pop the first package available in the queue. I have tested all this many, many times and it works perfectly, so please do not focus on this part as it is not the problem.

BTW 2: The microcontroller is a Tiva C series TM4C123GH6 as found on the Tiva C Series Launchpad. I am using gcc-arm-none-eabi.

\$\endgroup\$
  • \$\begingroup\$ A few questions: (1) what happens if the PC doesn't poll your device and the fifo overflows? (2) If the PC doesn't send an ACK (maybe the transmission gets lost) what does your device do? \$\endgroup\$ – Jon Oct 27 '15 at 22:03
  • \$\begingroup\$ (1) The FIFO will not overflow since measurements will be paused if there is not space for a new package in the buffer. The measurement timer will then start again once the queue is empty. This works fine as long as the FIFO still contain packages when an ACK is received. If not, I run into the problem described in my question. (2) The device will likely need to be reset in order to function again. I think I will implement a "communication reset" function that clears all TX and RX buffers after one second of no activity on the UART. This should be easy to implement as I have done that before. \$\endgroup\$ – pvh1987 Oct 27 '15 at 22:44
2
\$\begingroup\$

In general an interrupt-driven transmit protocol must always be started from somewhere outside the interrupt.

But do you realy need interrupts? This sounds like a fialy simple application: main loop checks for a few possible situations:

  • timer expired: start A/D conversion
  • A/D conversion completed: put result in queue
  • ! received: set transmitting allowed
  • transmitting allowed and not transmitting: if queue not empty : pop result; clear transitting allowed; set transmitting
  • uart buffer empty: transmit next character, or clear transmitting if none available

The transmit part might be cleared if expressed in an STD.

\$\endgroup\$
  • \$\begingroup\$ Thank you for your response. I have tried to send the first package by setting a boolean variable that my main loop polls. It will then pop the queue and transmit. It might solve the first situation. In the second situation, the UART handler and the main loop does not work well together (the UART handler might like to pop bytes from the queue while the main loop is also doing that). Also, the measurement timer will insert packages into the queue meanwhile. Keep in mind that my question is simplified - the real device is more complex. Thanks anyway :-) \$\endgroup\$ – pvh1987 Oct 27 '15 at 23:02
  • \$\begingroup\$ A solution might be to make a software interrupt that gets triggered by the UART-handler (when an ACK is received) and also get triggered after every package is pushed into the queue. This ISR will then have higher priority than the UART-handler and the measurement timer (and of course the main loop) and the only place where the queue is popped. The main loop still takes care of the actual transfer of bytes. I don't know about this (and do not know a lot about software interrupts). The idea is that the queue gets popped in either case, not only after an ACK. What do you think about this idea? \$\endgroup\$ – pvh1987 Oct 27 '15 at 23:09
0
\$\begingroup\$

After a bit of thinking, I came up with a solution myself. The idea is that only the UART-handler (the consumer) is going to pop the queue, since it has higher priority than the main loop and also higher priority than the timer-handler which does the measurements. However, in the two situations I listed in my question above, the UART-handler will not pop the queue because the queue is empty at the time the UART-handler received an ACK.

The solution I came up with is to let the producer (the measurement timer) know that the queue was empty at the last ACK and that the next package has to be transferred manually (without the use of the queue). This solved the second situation. The first situation is solved by always transmitting the first package manually.

I changed the UART-handler to

if (ch == '!')
{
    if (cmd_tx_queue.size == 0)
    {
        cmd_transmit_manually = 1;
        cmd_rx_ack = 0;
    }
    else
    {
        cmd_transmit_manually = 0;
        cmd_tx_pop_cmd();
        cmd_rx_ack = '!';
    }
}

The main loop remains as before:

if (cmd_rx_ack == ‘!’)
{
   cmd_rx_ack = 0; // Reset ack status

   // Transmit the command
   if (cmd_tx_length > 0)
   {
       cmd_tx_transmit();
   }
}

In the Timer ISR I just check if cmd_transmit_manually is set. If so, I clear cmd_transmit_manually, and put the package directly into the cmd_tx[] array and set cmd_rx_ack = '!'. Then the main loop will transfer this package when the Timer ISR returns.

If cmd_transmit_manually is 0, the package is pushed onto the queue instead.

After a little while, the UART handler will receive the ACK. If the queue is not empty, the next command will be popped into the cmd_tx[] array for the main loop to transfer. If it is empty, cmd_transmit_manually is set to 1. No package can be pushed into the queue while this check is done, since the UART-handler has higher priority than the timer-handler.

I have tested this code a lot of times now, with different time periods for the measurement. I have yet to see it fail. I have also verified that there are no missing packages in the transmission.

The next thing could be to make a cmd_tx_push_cmd() function that automatically transmits manually if cmd_transmit_manually is set and if not, pushes the package into the queue. In that way, the Timer ISR can be simplified and the code could be cleaned up a bit.

If you think this is not the best solution or if I have overlooked something important, please let me know. For now, it seems to work like it should - and quite a simple solution as well, I think :-)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.