4 votos

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

Tengo un poco de interrupción, digamos de 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] = '\0'; 
        }
    }
}
 

Pero el código de interrupción debe 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?

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

5voto

l1feh4ck3r Puntos 81

Realmente depende de la situación. Si es importante que el código dentro de su controlador, es procesado "inmediatamente", a continuación, poco hay forma de evitar esto, aparte de evitar costosas llamadas a funciones externas (es decir, implementar la funcionalidad de la función llamada en el interior del controlador). Si lo único que le preocupa es la lectura de los datos entrantes de su USART, pero los datos sí puede ser "tratados" más tarde, es mejor que use un muy simple ISR, o mejor aún la DMA, y algunos externos de búfer que puede contener temporalmente los datos entrantes. ST tiene una bonita nota de aplicación AN3109 que muestra cómo hacer esto.

5voto

jarrodn Puntos 99

UART es de hecho bastante típico caso porque muchas aplicaciones que requieren algún procesamiento se realiza en respuesta al comando/fecha de recibido a través del puerto serie. Si la aplicación es architectured alrededor de un infinito bucle de procesamiento, como es a menudo el caso, una buena manera es utilizar DMA para la transferencia de bytes recibidos en un buffer pequeño y proceso de este búfer en cada iteración del bucle. El código de ejemplo siguiente 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 este código hace a la primera instalación de un canal de DMA para leer USART2. La correcta controlador de DMA, la corriente y el canal es la persona a cargo en el que USART utiliza (consulte el STM32 manual de referencia para averiguar qué combinación es necesaria para un determinado puerto USART). A continuación, el código entra en el principal bucle infinito. En cada bucle, el código comprueba si algo ha sido escrito (a través de la DMA) en inputBuffer. Si es así, esta información es procesada por processByte, que se debe implementar de una manera que es similar a la original IRQ controlador.

Lo bueno de esta configuración es que no hay ningún código de interrupción, todo lo que se ejecuta de forma sincrónica. Gracias a la DMA, los datos recibidos sólo "por arte de magia" aparece en inputBuffer. El tamaño de inputBuffer debe ser cuidadosamente determinada, aunque. Debe ser lo suficientemente grande para contener todos los datos que usted posiblemente puede recibir durante una iteración del bucle. Por ejemplo, con una tasa de 115200 baudios (sobre 11KB/s) y un máximo de bucle de tiempo de 50 ms, tamaño del buffer debe ser de al menos 11KB/s * 50 ms = 550 bytes.

3voto

Alex Angas Puntos 20408

En mi experiencia la mayoría de los generales método disponible para desarrolladores de embebido es de las colas de mensajes que son proporcionados por la mayoría de RTOSes por ahí. El controlador de interrupción tendrá lugar los datos recibidos en la cola y el controlador de tareas (que se ejecuta en el "hilo" de prioridad para el uso de un Cortex-M plazo) recibir y procesar los datos en un bucle. Esto evita las banderas, los bloqueos, los semáforos, etc. que son una fuente constante de errores. Este método, por supuesto, tiene sus desventajas, por ejemplo, en lugar alto consumo de RAM y la necesidad de un RTOS. Todavía me parece más que justificado si la lógica para ser implementado es bastante complejo (y la memoria RAM disponible no es demasiado limitado).

Usted puede crear una forma bastante genérica sistema de manejo de esta manera. Un FreeRTOS / Cortex-M esqueleto ejemplo es el siguiente.

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

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X