Tengo alguna 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 internas que consumen mucho tiempo.
La pregunta es: ¿Cuál es la forma correcta de implementar interrupciones que llaman a funciones que consumen mucho tiempo?
Una de mis ideas es crear un búfer de banderas y banderas en interrupción. Y procesar el búfer de bandera en el bucle principal llamando a las funciones apropiadas. ¿Es correcto?
UART es, de hecho, un caso bastante típico porque muchas aplicaciones requieren que se realice algún procesamiento en respuesta al comando/fecha recibido a través del puerto serie. Si la arquitectura de la aplicación gira en torno a un ciclo de procesamiento infinito, como suele ser el caso, una buena manera es usar DMA para transferir los bytes recibidos a un búfer pequeño y procesar este búfer en cada iteración del ciclo. 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 es configurar primero un canal DMA para leer desde USART2. El controlador DMA, la transmisión y el canal correctos dependen del USART que utilice (consulte el manual de referencia de STM32 para averiguar qué combinación se necesita para un puerto USART determinado). Luego, el código ingresa al bucle infinito principal. En cada bucle, el código comprueba si se ha escrito algo (a través de DMA) en formato inputBuffer
. Si es así, estos datos son procesados por processByte
, que debe implementar de una manera similar a su controlador de IRQ original.
Lo bueno de esta configuración es que no hay código de interrupción: todo se ejecuta sincrónicamente. Gracias a DMA, los datos recibidos simplemente aparecen "mágicamente" en archivos inputBuffer
. Sin embargo, el tamaño 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 de transmisión de 115200 (alrededor de 11 KB/s) y un tiempo de bucle máximo de 50 ms, el tamaño del búfer debe ser de al menos 11 KB/s * 50 ms = 550 bytes.
Realmente depende. Si es importante que el código dentro de su controlador se procese "inmediatamente", entonces no hay forma de evitar esto, aparte de evitar costosas llamadas a funciones externas (es decir, implementar la funcionalidad de la función llamada dentro del controlador). Si todo lo que le preocupa es leer los datos entrantes de su USART, pero los datos en sí pueden "tratarse" más adelante, es mejor que use un ISR muy simple, o mejor aún, el DMA, y algún búfer externo. que puede contener temporalmente los datos entrantes. ST tiene una buena nota de aplicación AN3109 que muestra cómo hacer esto.
En mi experiencia, el método más general disponible para los desarrolladores integrados son las colas de mensajes que proporcionan la mayoría de los RTOS. El controlador de interrupciones colocará los datos recibidos en la cola y la tarea del controlador (que se ejecuta en la prioridad de "subproceso" para usar un término Cortex-M) recibirá y procesará los datos en un bucle. Esto evita banderas, bloqueos, semáforos, etc., que son una fuente constante de errores. Por supuesto, este método 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 a implementar es lo suficientemente compleja (y la RAM disponible no está demasiado restringida).
Puede 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
}
olin lathrop
yippie
krzych