¿Cómo reducir al mínimo el código de interrupción?

5

Tengo un poco de interrupción, digamos desde UART para hacer un ejemplo real:

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] = '
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] = '%pre%'; 
        }
    }
}
'; } } }

Pero el código de interrupción debería ejecutarse lo más rápido posible. Y aquí tenemos algunas funciones que llevan mucho tiempo dentro.

La pregunta es: ¿Cuál es la forma correcta de implementar interrupciones en las funciones que consumen tiempo de llamadas?

Una de mis ideas es crear búferes de indicadores e indicadores en interrupción. Y procesar el búfer de bandera en el bucle principal llamando a las funciones apropiadas. ¿Es correcto?

    
pregunta krzych

3 respuestas

6

UART es un caso bastante típico porque muchas aplicaciones requieren que se realice algún procesamiento en respuesta al comando / fecha recibida a través del puerto serie. Si la aplicación se diseña en torno a un bucle de procesamiento infinito, como suele ser el caso, una buena manera es usar DMA para transferir los bytes recibidos a un pequeño búfer y procesar este búfer en cada iteración de bucle. El siguiente código de ejemplo ilustra esto:

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

Lo que hace este código para configurar primero un canal DMA para leer desde USART2. El controlador, el flujo y el canal DMA correctos dependen de qué USART utilice (consulte el manual de referencia del STM32 para determinar qué combinación es necesaria para un puerto USART determinado). Entonces el código entra en el bucle infinito principal. En cada bucle, el código verifica si algo se ha escrito (a través de DMA) en inputBuffer . Si es así, estos datos se procesan mediante processByte , que debe implementar de manera similar a su controlador IRQ original.

Lo bueno de esta configuración es que no hay código de interrupción, todo se ejecuta de forma síncrona. Gracias a DMA, los datos recibidos simplemente "mágicamente" aparecen en inputBuffer . Sin embargo, el tamaño de inputBuffer debe determinarse cuidadosamente. Debe ser lo suficientemente grande como para contener todos los datos que posiblemente pueda recibir durante una iteración de bucle. Por ejemplo, con una velocidad en baudios de 115200 (aproximadamente 11KB / s) y un tiempo de bucle máximo de 50 ms, el tamaño del búfer debe ser de al menos 11KB / s * 50 ms = 550 bytes.

    
respondido por el abey
5

Realmente depende. Si es importante que el código dentro de su manejador se procese "inmediatamente", no hay mucho más que evitar esto, aparte de evitar costosas llamadas a funciones externas (es decir, implementar la funcionalidad de la función llamada dentro del manejador). Si todo lo que le preocupa es leer los datos entrantes de su USART, pero los datos en sí pueden "tratarse" más adelante, será mejor que utilice un ISR muy simple, o mejor aún, el DMA y algunos búferes externos. que puede contener temporalmente los datos entrantes. ST tiene una buena nota de aplicación AN3109 que muestra cómo hacer esto.

    
respondido por el fm_andreas
4

En mi experiencia, el método más general disponible para los desarrolladores integrados son las colas de mensajes que proporciona la mayoría de los RTOS. El manejador de interrupciones colocará los datos recibidos en la cola y la tarea del manejador (que se ejecuta en la prioridad de "subproceso" para usar un término de Cortex-M) recibirá y procesará los datos en un bucle. Esto evita banderas, bloqueos, semáforos, etc. que son una fuente constante de errores. Este método, por supuesto, tiene sus desventajas, por ejemplo, un uso bastante alto de RAM y la necesidad de un RTOS. Aún así, lo encuentro bastante justificado si la lógica que se implementa es lo suficientemente compleja (y la memoria RAM disponible no está demasiado limitada).

Puedes crear un sistema de manejo de eventos bastante genérico de esta manera. A continuación se muestra un ejemplo de esqueleto de FreeRTOS / Cortex-M.

#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
}
    
respondido por el Thorn

Lea otras preguntas en las etiquetas