Electronic – How to cut interrupt code to minimum

cinterruptsstm32

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?

Best Answer

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.