Ich habe einen Interrupt, sagen wir von UART, um ein echtes Beispiel zu geben:
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';
}
}
}
Der Interrupt-Code sollte jedoch so schnell wie möglich ausgeführt werden. Und hier haben wir einige zeitraubende Funktionen drin.
Die Frage ist: Wie werden Interrupts richtig implementiert, die zeitaufwändige Funktionen aufrufen?
Eine meiner Ideen ist es, Flags Buffer und Flags in Interrupt zu erstellen. Und verarbeiten Sie den Flag-Puffer in der Hauptschleife, indem Sie die entsprechenden Funktionen aufrufen. Ist es richtig?
UART ist in der Tat ein ziemlich typischer Fall, da viele Anwendungen erfordern, dass eine Verarbeitung als Reaktion auf Befehle/Datum erfolgt, die über die serielle Schnittstelle empfangen werden. Wenn die Anwendung um eine unendliche Verarbeitungsschleife herum aufgebaut ist, was oft der Fall ist, besteht eine gute Möglichkeit darin, empfangene Bytes mit DMA in einen kleinen Puffer zu übertragen und diesen Puffer bei jedem Schleifendurchlauf zu verarbeiten. Der folgende Beispielcode veranschaulicht dies:
#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...
}
Was dieser Code tut, um zuerst einen DMA-Kanal einzurichten, um von USART2 zu lesen. Der richtige DMA-Controller, Stream und Kanal hängt davon ab, welchen USART Sie verwenden (sehen Sie im STM32-Referenzhandbuch nach, um herauszufinden, welche Kombination für einen bestimmten USART-Port erforderlich ist). Dann tritt der Code in die Hauptendlosschleife ein. Bei jeder Schleife prüft der Code, ob etwas (über DMA) in geschrieben wurde inputBuffer
. Wenn dies der Fall ist, werden diese Daten von verarbeitet processByte
, was Sie ähnlich wie Ihren ursprünglichen IRQ-Handler implementieren sollten.
Das Schöne an diesem Setup ist, dass es keinen Interrupt-Code gibt – alles läuft synchron. Dank DMA erscheinen empfangene Daten einfach "magisch" in inputBuffer
. Die Größe inputBuffer
sollte jedoch sorgfältig bestimmt werden. Es sollte groß genug sein, um alle Daten zu enthalten, die Sie möglicherweise während einer Schleifeniteration erhalten können. Beispielsweise sollte bei einer Baudrate von 115200 (ca. 11 KB/s) und einer maximalen Schleifenzeit von 50 ms die Puffergröße mindestens 11 KB/s * 50 ms = 550 Bytes betragen.
Es kommt wirklich darauf an. Wenn es wichtig ist, dass der Code in Ihrem Handler "sofort" verarbeitet wird, dann gibt es kaum eine Möglichkeit, dies zu umgehen, außer kostspielige externe Funktionsaufrufe zu vermeiden (dh die Funktionalität der aufgerufenen Funktion innerhalb des Handlers zu implementieren). Wenn Sie sich nur Sorgen machen, die eingehenden Daten von Ihrem USART zu lesen, aber die Daten selbst später "bearbeitet" werden können, verwenden Sie besser einen sehr einfachen ISR oder noch besser den DMA und einen externen Puffer das die eingehenden Daten vorübergehend halten kann. ST hat einen netten Anwendungshinweis AN3109 , der zeigt, wie das geht.
Meiner Erfahrung nach ist die allgemeinste Methode, die Embedded-Entwicklern zur Verfügung steht, Nachrichtenwarteschlangen, die von den meisten RTOS da draußen bereitgestellt werden. Der Interrupt-Handler platziert empfangene Daten in der Warteschlange und die Handler-Task (die mit der „Thread“-Priorität läuft, um einen Cortex-M-Term zu verwenden) empfängt und verarbeitet die Daten in einer Schleife. Dadurch werden Flags, Locks, Semaphoren etc. vermieden, die eine ständige Fehlerquelle darstellen. Diese Methode hat natürlich ihre Nachteile, zum Beispiel eine ziemlich hohe RAM-Nutzung und die Notwendigkeit eines RTOS. Dennoch finde ich es durchaus gerechtfertigt, wenn die zu implementierende Logik komplex genug ist (und der verfügbare Arbeitsspeicher nicht zu begrenzt ist).
Auf diese Weise können Sie ein ziemlich generisches Ereignisbehandlungssystem erstellen. Es folgt ein FreeRTOS / Cortex-M-Skelettbeispiel.
#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
Jippie
krzych