5
\$\begingroup\$

I have some interrupt let's say from UART to make a real example:

void USART2_IRQHandler(void)
{
    int i = 0;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        static uint8_t cnt = 0;
        char t = USART_ReceiveData(USART2);
        if((t!='!')&&(cnt < MAX_STRLEN))
        {
            received_string[cnt] = t;
            cnt++;
        }
        else
        {
            cnt = 0;
            if(strncmp(received_string,"connection",10) == 0)
            {
                USART2_SendText("connection ok");
            }
            else if(strncmp(received_string,"sine",4) == 0)
            {
                DAC_DeInit();
                DAC_Ch2SineWaveConfig();
                USART2_SendText("generating sine");
            }
            else
            {
                USART2_SendText("unknown commmand: ");
                USART2_SendText(received_string);
            }
            for (i = 0; i <= MAX_STRLEN+1; i++)         // flush buffer
                received_string[i] = '\0'; 
        }
    }
}

But interrupt code should run as fast as possible. And here we have some time consuming functions inside.

The question is: What is the correct way to implement interrupts which call time consuming functions?

One of my ideas is to create flags buffer and flags in interrupt. And process flag buffer in main loop calling appropriate functions. Is it correct?

\$\endgroup\$
3
  • 3
    \$\begingroup\$ The basic answer is to architect the system properly from the start. Explain what your system needs to accomplish, not how you think it should be accomplished, and we may be able to suggest a suitable architecture. Without some sort of spec, this is no question at all or way too open ended. \$\endgroup\$ Commented Jun 2, 2013 at 19:02
  • 3
    \$\begingroup\$ Do the timing critical stuff in the interrupt routine and use flags that will be handled in main for all other things. Where time critical is in the order of couple instruction cycles accurate. \$\endgroup\$
    – jippie
    Commented Jun 2, 2013 at 19:03
  • \$\begingroup\$ @Olin Lathrop : This example came from signal generator controlled by PC software. I send commands via UART and they should change generated signal, parameters etc. But I aimed to make this question general to know what are good styles and design patterns when implementing interrupts. \$\endgroup\$
    – krzych
    Commented Jun 2, 2013 at 19:09

3 Answers 3

6
\$\begingroup\$

UART is indeed a pretty typical case because many applications require that some processing is done in response to command/date received via the serial port. If the application is architectured around an infinite processing loop, as it is often the case, one good way is to use DMA to transfer received bytes into a small buffer and process this buffer at each loop iteration. The following example code illustrates this:

#define BUFFER_SIZE 1000
uint8_t inputBuffer[BUFFER_SIZE];
uint16_t inputBufferPosition = 0;    

// setup DMA reception USART2 RX => DMA1, Stream 6, Channel 4
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
DMA_InitTypeDef dmaInit;
DMA_StructInit(&dmaInit);
dmaInit.DMA_Channel = DMA_Channel_4;
dmaInit.DMA_PeripheralBaseAddr = ((uint32_t) USART2 + 0x04);
dmaInit.DMA_Memory0BaseAddr = (uint32_t) inputBuffer;
dmaInit.DMA_DIR = DMA_DIR_PeripheralToMemory;
dmaInit.DMA_BufferSize = BUFFER_SIZE;
dmaInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dmaInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
dmaInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
dmaInit.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
dmaInit.DMA_Mode = DMA_Mode_Circular;
dmaInit.DMA_Priority = DMA_Priority_Medium;
dmaInit.DMA_FIFOMode = DMA_FIFOMode_Disable;
dmaInit.DMA_MemoryBurst = DMA_MemoryBurst_Single;
dmaInit.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA1_Stream5, &dmaInit);
USART_DMACmd(port, USART_DMAReq_Rx, ENABLE);

// loop infinitely
while(true)
{
    // read out from the DMA buffer
    uint16_t dataCounter = DMA_GetCurrDataCounter(DMA1_Stream5);
    uint16_t bufferPos = BUFFER_SIZE - dataCounter;

    // if we wrapped, we consume everything to the end of the buffer
    if (bufferPos < inputBufferPosition)
    {
        while (inputBufferPosition < BUFFER_SIZE)
            processByte(inputBuffer[inputBufferPosition++]);
        inputBufferPosition = 0;
    }

    // consume the beginning of the buffer
    while (inputBufferPosition < bufferPos)
        processByte(inputBuffer[inputBufferPosition++]);

    // do other things...
}

What this code does it to first setup a DMA channel to read from USART2. The correct DMA controller, stream and channel is dependant on which USART you use (check the STM32 reference manual to figure out which combination is needed for a given USART port). Then the code enters the main infinite loop. At each loop, the code checks whether something has been written (through DMA) in inputBuffer. If so, this data is processed by processByte, which you should implement in a way that is similar to your original IRQ handler.

What's nice in this setup is that there is no interrupt code -- everything runs synchronously. Thanks to DMA, received data just "magically" appears in inputBuffer. The size of inputBuffer should be carefully determined though. It should be large enough to contain all the data you can possibly receive during a loop iteration. For example, with a baud rate of 115200 (about 11KB/s) and a maximum loop time of 50 ms, the buffer size should be at least 11KB/s * 50 ms = 550 bytes.

\$\endgroup\$
3
  • \$\begingroup\$ It would be nice if a DMA channel could be configured so as to act as a continuous FIFO, especially if the DMA controller could handle handshaking in some reasonable fashion. Conceptually, it shouldn't be too hard. Unfortunately, none of the DMA controllers I'm familiar with provide any such facility; does the one on STM32? \$\endgroup\$
    – supercat
    Commented Jul 10, 2013 at 18:41
  • \$\begingroup\$ @supercat: it would be nice indeed but I don't think it can do that. \$\endgroup\$
    – abey
    Commented Jul 11, 2013 at 8:23
  • \$\begingroup\$ I wonder how much it would cost to add some handshaking ability? I know I've seen DMA buffers with "wrap" registers. Basically all that should be needed for a full FIFO would be a "stop address" register; the DMA controller should pause any time the next request would be at that address. When the CPU either adds data to the buffer or reads data (making space available for new incoming data) it updates the stop-address register appropriately--an operation which could safely be performed even while operations were in progress. \$\endgroup\$
    – supercat
    Commented Jul 11, 2013 at 14:59
5
\$\begingroup\$

It really depends. If it's important that the code inside your handler is processed "immediately" then there's little way around this, other than avoiding costly external function calls (i.e. implement the functionality of the called function inside the handler). If all you're worried about is reading the incoming data from your USART, but the data itself can then be "dealt with" later, you're better off using a very simple ISR, or better yet the DMA, and some external buffer that can temporarily hold the incoming data. ST has a nice application note AN3109 that shows how to do this.

\$\endgroup\$
2
  • \$\begingroup\$ Inlining not only avoids call overhead but also allows code specialization (strncmp is a rather general function) and instruction scheduling across code that would have been in separate calls. Not using strncmp might also allow one to avoid flushing the buffer since zero termination might no longer be necessary. Similarly, if USART2_SendText() is slow, concatenating the strings rather than using two calls might improve WCET; placing "unknown command: " immediately before received_string might make this free. \$\endgroup\$
    – user15426
    Commented Jun 3, 2013 at 13:32
  • \$\begingroup\$ Incidentally, the compiler might not be smart enough to extract the conditional calls by conditionally setting a string pointer. Such would allow use of conditional moves in place of two of the branches (possibly helping WCET) and reduce code size. \$\endgroup\$
    – user15426
    Commented Jun 3, 2013 at 13:38
4
\$\begingroup\$

In my experience the most general method available to embedded developers is message queues which are provided by most RTOSes out there. The interrupt handler will place received data into the queue and the handler task (running at the "thread" priority to use a Cortex-M term) will receive and process the data in a loop. This avoids flags, locks, semaphores etc. which are a constant source of bugs. This method of course has its disadvantages, for example rather high RAM usage and the need for a RTOS. Still I find it quite justified if the logic to be implemented is complex enough (and available RAM is not too constrained).

You can create a fairly generic event handling system this way. A FreeRTOS / Cortex-M skeleton example follows.

#define EVENT_QUEUE_SIZE 32 // could be tricky to get right
xQueueHandle event_queue;

void Some_IRQHandler(void)
{
    // reset the interrupt pending bit

    event_t event;
    event.type = FOO; // if event_t is a tagged union
    event.foo = ...; // fill the structure with data from the peripheral

    // place into t he queue
    portBASE_TYPE task_woken = pdFALSE;
    xQueueSendFromISR(event_queue, &event, &task_woken);
}

void Other_IRQHandler(void)
{
    // same except
    event.type = BAR;
}


void handler_task(void *pvParameters)
{
    while(true) {
        event_t event;
        if(!xQueueReceive(event_queue, &event, portMAX_DELAY))
            continue;

        // process the event
        switch(event.type) {
            case FOO:
               ...
            break;

            ...
        }
    }
}

int main()
{
    // create the queue
    event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(event_t));

    // create handler task
    xTaskCreate(handler_task, ...);

    // enable interrupts, start the scheduler
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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