Forum: Mikrocontroller und Digitale Elektronik Cortex-M0: FreeRTOS + UART-Interrupt = Problem.


von Michael B. (Gast)


Lesenswert?

Hallo!

Auf einem NXP LPC11U35 (Cortex M0) versuche ich seit nunmehr geraumer 
Zeit FreeRTOS (v9.0.0) und den UART-Interrupt zusammen ans Laufen zu 
bekommen.

Folgendes funktioniert einwandfrei:
- FreeRTOS Tasks (LED, Statistikausgabe)
- IdleHook (u.A. USB-basiertes serielle Befehlskonsole (dafür braucht es 
ja auch einen Int-Handler, testweise auch __wfi();)
- Stacküberwachung (StackOverflowHook) und MallocFailedHook sind 
implementiert und funktionieren, wenn provoziert, wie erwartet
- Stacks sind alle ausreichend groß (Überwacht+sehr großzügig Headroom, 
auch für den IdleTask und den ersten Stack vor dem Starten des 
Schedulers

Nun füge ich ein UART-ISR hinzu, und das OS läuft nach Empfang des 
ersten Zeichens sofort bedeutend langsamer!? Später soll das ISR über 
TaskNotifications natürlich den Task aufwecken, aber dies ist momentan 
nicht implementiert und das ISR kümmert sich selbst drum. Alleine der 
konfigurierte UART + das eingeschaltete ISR bremsen das OS massiv ein.

Im Debugger geprüft: SysTick und PendSV haben Wert "192", also Prio "3", 
was korrekt der niedrigsten Cortex-Prio entspricht. Analog setze ich 
auch Prio 3, was entsprechend im Debugger auch stimmt. Der Cortex M0 hat 
lt. CMSIS auch nur 2 Prio-Bits.

Wie bereits geschrieben, Stack und Heap laufen auch (sehr sicher) nicht 
über.

Stoppt man die Ausführung im Debugger, sieht man subjektiv, dass obwohl 
nur selten Zeichen empfangen werden sehr häufig das UART-ISR 
angesprungen wird/aktiv ist.
Deaktiviert man per Debugger das UART-ISR im NVIC läuft das FreeRTOS 
sofort normal weiter!

These: Irgendwie verändert das FreeRTOS den NVIC so, dass ich nicht 
sauber das UART-ISR bediene/lösche, bzw. evtl. wird nach jeder 
CriticalSection durch (Re-)Aktivieren der ISRs das UART-ISR getriggert. 
Ein Nesting kann bei gleicher Prio da ja eigentlich nicht vorkommen.

Wenn ich wiederum alle StackCreate und StartScheduler auskommentiere 
(und in der nachfolgenden for (;;) Schleife arbeite) funktioniert das 
UART-ISR einwandfrei...

Über Vorschläge würde ich mich sehr freuen!

Grüße
Michi
1
#include "board.h"
2
#include "cli.h"
3
4
#include "FreeRTOS.h"
5
#include "task.h"
6
7
#include "stdio.h"
8
#include "cdc_vcom.h"
9
10
extern ErrorCode_t USB_Init(void);
11
extern USBD_API_INIT_PARAM_T usb_param;
12
13
long ledStackHighWaterMark;
14
long uartStackHighWaterMark;
15
16
static TaskHandle_t xUARTTaskHandle = NULL;
17
18
static short rx_byte;
19
static char rx_status;
20
21
void UART_IRQHandler(void)
22
{
23
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
24
25
  uint8_t iirvalue = Chip_UART_ReadIntIDReg(LPC_USART) & UART_IIR_INTID_MASK;
26
  if ((iirvalue == UART_IIR_INTID_RLS) || (iirvalue == UART_IIR_INTID_RDA))
27
  {
28
    rx_status = Chip_UART_ReadLineStatus(LPC_USART) & (UART_LSR_OE | UART_LSR_PE | UART_LSR_BI | UART_LSR_FE | UART_LSR_RXFE);
29
    rx_byte = Chip_UART_ReadByte(LPC_USART);
30
  }
31
  else if (iirvalue == UART_IIR_INTID_CTI)  /* Character timeout indicator */
32
  {
33
//    if (UART_Character_Timeout)
34
//      UART_Character_Timeout();
35
  }
36
  else if (iirvalue == UART_IIR_INTID_THRE)  /* THRE, transmit holding register empty */
37
  {
38
    /* all data transmitted on UART disable UART_IER_THREINT */
39
    Chip_UART_IntDisable(LPC_USART, UART_IER_THREINT);
40
  }
41
  
42
  NVIC_ClearPendingIRQ(UART0_IRQn);
43
  
44
  /* Notify the task that the transmission is complete. */
45
//  vTaskNotifyGiveFromISR( xUARTTaskHandle, &xHigherPriorityTaskWoken );
46
47
  /* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
48
  should be performed to ensure the interrupt returns directly to the highest
49
  priority task.  The macro used for this purpose is dependent on the port in
50
  use and may be called portEND_SWITCHING_ISR(). */
51
//  portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
52
}
53
54
static void vUARTTask(void *pvParameters)
55
{
56
  uint32_t ulNotificationValue;
57
  const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 1000 );
58
59
  /* Store the handle of the calling task. */
60
  xUARTTaskHandle = xTaskGetCurrentTaskHandle();
61
62
  for (;;)
63
  {
64
    uartStackHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
65
66
    /* Wait for the transmission to complete. */
67
    ulNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );
68
69
    if( ulNotificationValue >= 1 )
70
    {
71
      /* The transmission ended as expected. */
72
      printf("ISR - ch = 0x%02x\r\n", rx_byte);      
73
    }
74
    else
75
    {
76
      /* The call to ulTaskNotifyTake() timed out. */
77
      printf("TO\r\n");
78
//      rx_byte = Chip_UART_ReadByte(LPC_USART);
79
//      printf("ch = %x\r\n", rx_byte);      
80
    }
81
  }
82
}
83
84
/* LED toggle thread */
85
static void vLEDTask(void *pvParameters)
86
{
87
  for (;;)
88
  {
89
    ACT_LED_TOGGLE();
90
    vTaskDelay(configTICK_RATE_HZ/2);
91
    ledStackHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
92
  }
93
}
94
95
/* STATS thread */
96
static void vStatsTask(void *pvParameters)
97
{
98
  for (;;)
99
  {
100
    vTaskDelay(configTICK_RATE_HZ*10);
101
    printf("\r\n");
102
    printf("Heap: %d (act) / %d (min) byte free\r\n", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize());
103
    printf("this stack: %ld entries free\r\n", uxTaskGetStackHighWaterMark( NULL ));
104
    printf("UART stack: %ld entries free\r\n", uartStackHighWaterMark);
105
    printf("LED stack: %ld entries free\r\n", ledStackHighWaterMark);
106
    printf("\r\n");
107
  }
108
}
109
110
uint8_t lower_heap[5*1024];
111
112
HeapRegion_t xHeapRegions[] = {
113
  { (uint8_t*)lower_heap, sizeof(lower_heap) },
114
  { NULL, 0 },
115
  { NULL, 0 }
116
};
117
118
/* Main Program */
119
int main (void)
120
{
121
  Board_Init();
122
  USB_Init();
123
  CLI_Init();
124
125
  /* Setup UART for 38.4K8E1 */
126
  Chip_UART_Init(LPC_USART);
127
  Chip_UART_SetBaud(LPC_USART, 38400);
128
  Chip_UART_ConfigData(LPC_USART, (UART_LCR_WLEN8 | UART_LCR_SBS_1BIT | UART_LCR_PARITY_EN | UART_LCR_PARITY_EVEN));
129
  Chip_UART_SetupFIFOS(LPC_USART, (UART_FCR_FIFO_EN | UART_FCR_TRG_LEV2));
130
  Chip_UART_TXEnable(LPC_USART);
131
132
  /* Enable receive data and line status interrupt */
133
  Chip_UART_IntEnable(LPC_USART, (UART_IER_RBRINT | UART_IER_RLSINT));
134
135
  /* prio = 3 */
136
  NVIC_SetPriority(UART0_IRQn, (1UL<<__NVIC_PRIO_BITS)-1UL); // only 2 bits used, so prio can be 0,1,2,3 where 0 is highest priority
137
  NVIC_EnableIRQ(UART0_IRQn);
138
139
  // use remaining USB RAM for RTOS heap
140
  xHeapRegions[1].pucStartAddress = (uint8_t*)usb_param.mem_base;
141
  xHeapRegions[1].xSizeInBytes = usb_param.mem_size;
142
143
  vPortDefineHeapRegions(xHeapRegions);
144
145
  /* UART thread */
146
  xTaskCreate(vUARTTask, "UART", 80, NULL, configMAX_PRIORITIES - 1, (xTaskHandle *) NULL);
147
148
  /* STATS thread */
149
  xTaskCreate(vStatsTask, "Stats", 80, NULL, tskIDLE_PRIORITY, (xTaskHandle *) NULL);
150
151
  /* LED toggle thread */
152
  xTaskCreate(vLEDTask, "LED", 32, NULL, tskIDLE_PRIORITY + 1, (xTaskHandle *) NULL);
153
154
  /* Start the scheduler */
155
  vTaskStartScheduler();
156
157
  for (;;)
158
  {
159
    if (rx_byte != -1)
160
    {
161
      printf("ISR - ch = 0x%02x\r\n", rx_byte);      
162
      rx_byte = -1;
163
    }
164
    CLI_Task();
165
  }
166
}
167
168
void vApplicationIdleHook( void )
169
{
170
  // must never been delayed by FreeRTOS function...
171
  CLI_Task(); // consume all input
172
//  __wfi();
173
}
174
/*-----------------------------------------------------------*/
175
176
void vApplicationMallocFailedHook( void )
177
{
178
  ACT_LED_ON();
179
  taskDISABLE_INTERRUPTS();
180
  for (;;) {;}
181
}
182
/*-----------------------------------------------------------*/
183
184
void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
185
{
186
  ( void ) pcTaskName;
187
  ( void ) pxTask;
188
189
  ACT_LED_ON();
190
  taskDISABLE_INTERRUPTS();
191
  for (;;) {;}
192
}

von Daniel B. (dbuergin)


Lesenswert?

Kleiner Tipp, häng den Code als File an, oder formatier ihn wenigstens 
richtig mit den Tags (c und \c) siehe auch Formatierung
Ansonsten vergraulst Du schon mal ein paar potenzielle Antwortgeber...


Beispiel:
1
void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
2
{
3
  ( void ) pcTaskName;
4
  ( void ) pxTask;
5
6
  ACT_LED_ON();
7
  taskDISABLE_INTERRUPTS();
8
  for (;;) {;}
9
}

von Pete K. (pete77)


Lesenswert?

Ich kenne mich mit RTOS nicht wirklich aus, aber ist es erlaubt in der 
Main noch eine For-Schleife, die auf etwas wartet hinzuzufügen?

Ansonsten vielleicht mal die globalen Variablen als volatile generieren.

von temp (Gast)


Lesenswert?

Das U0IIR Register sollte 0 sein wenn du den Interrupt verlässt, sonst 
läuft das Ding in einer Schleife. Versuch mal das in einer 
while-Schleife sicherzustellen. Bei den Cortexen kann auch mal eine 
gewisse Zeit vergehen bis die Interrupt-Flags so stehen wie gewünscht. 
Wird die Interrupt-Routine zu früh verlassen, löst das gleich den 
nächsten Interrupt aus. Hab ich bei den Timern schon erlebt.

so in etwa:
1
void UART_IRQHandler(void)
2
{
3
....
4
5
while (1) 
6
  {
7
  uint32_t iirvalue=Chip_UART_ReadIntIDReg(LPC_USART);
8
  if (iirvalue)
9
    {
10
    // Aktionen ausführen um die Flags zu löschen
11
    }
12
  else
13
    break;  
14
  } 
15
....
16
17
}

du hast in deinem Code iirvalue als uint8_t declariert, Sollte das nicht 
uint32_t sein?

von Michael B. (Gast)


Lesenswert?

@Daniel: Hast recht, hatte [ c ] leider übersehen...

Pete K. schrieb:
> Ich kenne mich mit RTOS nicht wirklich aus, aber ist es erlaubt in
> der
> Main noch eine For-Schleife, die auf etwas wartet hinzuzufügen?
>
> Ansonsten vielleicht mal die globalen Variablen als volatile generieren.

Hallo Pete, die For-Schleife wird wenn vTaskStartScheduler aufgerufen 
wird nicht mehr erreicht, da ich nie (alle) Tasks beende. Ist in dem 
Fall "non-reachable".

temp schrieb:
> Das U0IIR Register sollte 0 sein wenn du den Interrupt verlässt,
> sonst
> läuft das Ding in einer Schleife. Versuch mal das in einer
> while-Schleife sicherzustellen. Bei den Cortexen kann auch mal eine
> gewisse Zeit vergehen bis die Interrupt-Flags so stehen wie gewünscht.
> Wird die Interrupt-Routine zu früh verlassen, löst das gleich den
> nächsten Interrupt aus. Hab ich bei den Timern schon erlebt.
>
> so in etwa:
> void UART_IRQHandler(void)
> {
> ....
>
> while (1)
>   {
>   uint32_t iirvalue=Chip_UART_ReadIntIDReg(LPC_USART);
>   if (iirvalue)
>     {
>     // Aktionen ausführen um die Flags zu löschen
>     }
>   else
>     break;
>   }
> ....
>
> }
>
> du hast in deinem Code iirvalue als uint8_t declariert, Sollte das nicht
> uint32_t sein?

Hallo temp, ich werde das mal versuchen. Die Idee defensiv IIR 
abzufragen ist sicher sinnvoll. Alle in IIR relvanten Ints sind in den 
unteren 8 bit, trotzdem ist es natürlich unschön. Wie gesagt: Ohne RTOS 
funktioniert das alles aber. Werde es aber trotzdem fixen.

Grüße
Michi

von Michael B. (Gast)


Lesenswert?

Hallo!

Ich konnte dank Eurer Hinweise das Problem lokalisieren: Tatsächlich ist 
der Character Timeout (CTI) des UART gesetzt und wird auch von den 
Referenzimplementierungen nicht gelöscht.

Scheinbar bin ich nicht der Einzige: 
https://community.nxp.com/thread/419856

Als Lösungsmöglichkeit ergeben sich kurzfristig zwei Wege:
- bei CTI IMMER auch das RBR lesen (egal was RDA sagt)
- den FIFO nur 1 Byte tief machen

Ich werde mich nun noch mal genauer mit den Auswirkungen der einzelnen 
Lösungsansätze beschäftigen und das kleinere Übel auswählen.

Vielen Dank und Grüße
Michi

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.