Confusing context switching on ATmega128

Hi, I am using an ATmega128L with an 8MHz clock. FreeRTOS is configured to for a tick rate of 4KHz. I have three tasks. Task0 and Task1 are IDLE priority tasks that use vTaskDelayUntil(…) to wake up every 2000 and 1000 ticks, respectively. Everytime these two tasks run, they simply toggle a LED each. There is an ISR that uses timer/counter0 to trigger an overflow interrupt every 1ms (1KHz rate). Each time, the ISR sends a message to Task2 containing a pointer to a CHAR buffer using cQueueSendFromISR(…). The ISR sets a LED when it enters and clears the LED when it exits. Task2 is a higher priority ISR (idle+1) that blocks for 8000 ticks on a message from ISR. When it receives a message it performs a simple loop through the message. Task2 also sets a LED when it has successfully received a message and clears the LED when it has finished its work. I would have expected the following behavior from the LEDS on the ISR and Task2: T2:  xxxxOOOxxxxOOOxxxxOOOxxxxOOO.. ISR:xxOOxxxxxxOOxxxxxOOxxxxxOO… But instead I see, T2:  xxxxOOxxxxxxxOOxxxxxxOOxxxxxxOO… ISR:xxxOOOOxxxxOOOOxxxOOOOxxxOOOO.. How can T2 execute before ISR has finished executing? Is it possible that there is a context switch occuring inside the ISR before it has even finished executing (because after all the FreeRTOS tick rate is 4KHz where as the ISR rate is only 1KHz). I would appreciate any help on the subject. I have included my code in case it helps.  If you need the makefile, I have that too, I can email the whole thing. ******* CODE ******* /************************************************************************************** ** ** ** Tasks: ** 1. Task0: Low priority task that toggles a led every few ticks. ** 2. Task1: Another low priority task that toggle another led every few ticks. ** 3. Task2: The high priority task that’s waiting for a message containing a CHAR **           buffer from ISR. ** ** ISR: Sends a CHAR buffer to Task2 every 1ms (1KHz) ** ** Diagnostics: ** Task0 and Task1 toggle LEDS everytime they execute. ** ISR sets a led when it is entered, and clears the led when it exits. ** Task2 sets a led when cQueueReceive is successfull and clears a led when it’s done. ** ** Problems: ** ** ***************************************************************************************/ #include <stdlib.h> #include "projdefs.h" #include "portable.h" #include "task.h" #include "queue.h" /* This is just because the program used to consists of many files but now combined into one. */ #define _SINGLE_FILE_ #if defined (_SINGLE_FILE_)     #include <avr/signal.h>     #include <avr/interrupt.h> #else     #include "types.h"     #include "task0.h"     #include "task1.h"     #include "task2.h"     #include "ISR.h" #endif #define TASK0_PRIORITY        ( tskIDLE_PRIORITY ) #define TASK1_PRIORITY        ( tskIDLE_PRIORITY ) #define TASK2_PRIORITY        ( tskIDLE_PRIORITY + ( unsigned portCHAR ) 1 ) #if defined (_SINGLE_FILE_)     #define SET_STATUS_LED()         PORTB |= 0x80;     #define CLEAR_STATUS_LED()         PORTB &= 0x7F;     #define TOGGLE_STATUS_LED()         PORTB ^= 0x80;     #define TOGGLE_TEST1_LED()         PORTE ^= 0x08;     #define SET_TEST1_LED()         PORTE |= 0x08;     #define CLEAR_TEST1_LED()         PORTE &= 0xF7;     #define TOGGLE_TEST2_LED()         PORTB ^= 0x10;     #define TOGGLE_TEST3_LED()         PORTE ^= 0x10;     #define SET_TEST3_LED()         PORTE |= 0x10;     #define CLEAR_TEST3_LED()         PORTE &= 0xEF;     #define MAX_MESSAGE_SIZE        ( ( unsigned portCHAR ) 5 )     #define BLOCK_TIME                ( ( portTickType ) 8000 )     typedef struct     {         xQueueHandle xInputQueue;         portTickType xBlockTime;     } xTASK2_PARAMS;     typedef struct     {         unsigned portCHAR ucPacket[MAX_MESSAGE_SIZE];     } xRX_BUFFER;     xRX_BUFFER * pxRxBuffer; #endif static xQueueHandle pxQueueHandle; /************************************************************************************** ** **                                    PROTOTYPES ** ***************************************************************************************/ #if defined (_SINGLE_FILE_)     void vTask0Init( unsigned portCHAR ucPriority );     void vTask0Code( void * pvParameters );     void vTask1Init( unsigned portCHAR ucPriority );     void vTask1Code( void * pvParameters );     void vTask2Init( unsigned portCHAR ucPriority );     void vTask2Code( void * pvParameters );     void vInitializeISR( void ); #endif /************************************************************************************** ** ** MAIN ** ***************************************************************************************/ portSHORT main( void ) {     /*     ** Set ports D abd E as outputs so we can flash LEDS.     */     DDRB = 0xFF;     PORTB = 0x00;     DDRE = 0xFF;     PORTE = 0x00;     /*     ** Allocate memory all queues.     */     const unsigned portCHAR ucQueueSize = 1;     pxQueueHandle = xQueueCreate( ucQueueSize, ( unsigned portCHAR ) (sizeof ( xRX_BUFFER * )) );     /*     ** Initialize the RX BUFFER storage area.     */     pxRxBuffer = ( xRX_BUFFER * ) pvPortMalloc( sizeof( xRX_BUFFER ) );     /* Create Task0 – LOW PRIORITY. */     vTask0Init( TASK0_PRIORITY );     /* Create Task1 – LOW PRIORITY. */     vTask1Init( TASK1_PRIORITY );     #if defined (_SINGLE_FILE_)         /* Create Task2 – HIGH PRIORITY. */         vTask2Init( TASK2_PRIORITY );         /* Set up ISR. */         vInitializeISR();     #else         /* Create Task2 – HIGH PRIORITY. */         vTask2Init( TASK2_PRIORITY, pxQueueHandle );         /* Set up ISR. */         vInitializeISR(pxQueueHandle);     #endif     /* Start the scheduler. */     vTaskStartScheduler( portUSE_PREEMPTION );     while(1);     return 0; } #if defined (_SINGLE_FILE_) /************************************************************************************** ** ** TASK 0: Low priority task that toggles TEST2 LED every few ticks. ** ***************************************************************************************/ /* Task initialization. */ void vTask0Init( unsigned portCHAR ucPriority ) {     /* Create task. */     sTaskCreate( vTask0Code, "TASK0", portMINIMAL_STACK_SIZE, NULL, ucPriority, NULL ); } /* Task code. */ void vTask0Code( void * pvParameters ) {     /* This task will execute at 4K/2K Hz when portTICK_RATE_HZ = 4K. */     const portTickType xFrequency = 2000;     portTickType xLastWakeTime;     xLastWakeTime = xTaskGetTickCount();     for (;;)     {         vTaskDelayUntil( &xLastWakeTime, xFrequency );         //vTaskDelay(2000);         TOGGLE_TEST2_LED();     } } /************************************************************************************** ** ** TASK 1: Low priority task that toggles STATUS LED every few ticks. ** ***************************************************************************************/ /* Task initialization. */ void vTask1Init( unsigned portCHAR ucPriority ) {     /* Create task. */     sTaskCreate( vTask1Code, "TASK1", portMINIMAL_STACK_SIZE, NULL, ucPriority, NULL ); } /* Task code. */ void vTask1Code( void * pvParameters ) {     /* This task will execute at 4K/1K Hz when portTICK_RATE_HZ = 4K. */     const portTickType xFrequency = 1000;     portTickType xLastWakeTime;     xLastWakeTime = xTaskGetTickCount();     for (;;)     {         vTaskDelayUntil( &xLastWakeTime, xFrequency );         //vTaskDelay(1000);         TOGGLE_STATUS_LED();     } } /************************************************************************************** ** ** TASK 2: Highest priority task that blocks until it receives a CHAR buffer from the **            ISR. ** ***************************************************************************************/ /* ** Initialize Task2 parameter structure, including block time and queue handle. */ void vTask2Init( unsigned portCHAR ucPriority ) {     /* The pointer to parameters for this task. */     xTASK2_PARAMS *pxTask2Params;     pxTask2Params = ( xTASK2_PARAMS * ) pvPortMalloc( sizeof( xTASK2_PARAMS ) );     pxTask2Params->xInputQueue = pxQueueHandle;     pxTask2Params->xBlockTime = BLOCK_TIME;     /* Create task. */     sTaskCreate( vTask2Code, "TASK2", portMINIMAL_STACK_SIZE, pxTask2Params, ucPriority, NULL ); } /* ** The actual Task2 code. */ void vTask2Code( void * pvParameters ) {     /* Declare message handles used in this task. */     xRX_BUFFER * ucReceivedBuffer;     /* Cast the task parameters. */     xTASK2_PARAMS * pxParameters = ( xTASK2_PARAMS * ) pvParameters;     /* The actual work. */     for (;;)     {         /* Block on message available from ISR, and log the received packet. */         if( cQueueReceive( pxParameters->xInputQueue, &(ucReceivedBuffer), pxParameters->xBlockTime ) == pdPASS )         {             /* Set TEST3 LED to indicate we have received a CHAR buffer from ISR. */             SET_TEST3_LED();         }         /* Do something with CHAR buffer. */         for ( unsigned portCHAR ucCount=0; ucCount < MAX_MESSAGE_SIZE; ucCount++ )         {             ucReceivedBuffer->ucPacket[ucCount] = 0xA1;         }         /* Clear TEST3 LED to indicate we are done and waiting for the next CHAR buffer. */         CLEAR_TEST3_LED();     } } /************************************************************************************** ** ** ISR: This ISR uses timer/counter0 to generate a (~)1KHz overflow interrupt. ** ***************************************************************************************/ /* **    Set up timer/counter0 to generate the interrupt. Initialize the CHAR buffer that **     will be sent to Task2. */ void vInitializeISR( void ) {     /*     ** Generate a 1KHz interrupt using overflow on timer/counter0.     */     /* TimerSetT0Prescaler to 32 */     TCCR0 &= 0xF8;     TCCR0 |= ( ( unsigned portCHAR ) 3 );     /* TimerClearT0PendingInterrupts */     TIFR |= _BV (TOV0);     TIFR |= _BV (OCF0);     /* TimerEnableT0OverflowEvent */     TCCR0 &= 0xB7;    /* clear */     TCCR0 |= 0x00;    /* set */     /* TimerEnableT0OverflowInterrupt */     TIMSK |= _BV (TOIE0);     /* Initialize the contents of the buffer. */     for ( unsigned portCHAR ucCount=0; ucCount < MAX_MESSAGE_SIZE; ucCount++ )     {         pxRxBuffer->ucPacket[ucCount] = 0xA5;     } } /* ** The ISR sends a pointer to the CHAR buffer to Task2. */ SIGNAL( SIG_OVERFLOW0 ) {     portCHAR cTaskWokenByPost = pdFALSE;     /* Set TEST1 LED to indicate we are in this ISR. */     SET_TEST1_LED();     /* Send the current message to Task2. Note we send the address of the pointer to the RX buffer. */     cTaskWokenByPost = cQueueSendFromISR( pxQueueHandle, &pxRxBuffer, cTaskWokenByPost );     /* If a higher priority task is woken by this message then yield the current task. */     if ( cTaskWokenByPost )     {         taskYIELD();     }     /* Clear TEST1 LED to indicate we are leaving this ISR. */     CLEAR_TEST1_LED(); } #endif

Confusing context switching on ATmega128

if ( cTaskWokenByPost ) { taskYIELD(); } Context switch occurs on Yield call within your ISR – so yes a context switch is occurring before you turn off the ISR LED.  This is ok as each task maintains it’s own interrupt mask state.  The task you switch to will have interrupts enabled, then when you switch back to the interrupted task the ISR will complete.  If you are going to do this it is best to have the yield as the last instruction in the ISR otherwise you could get into stack problems if the timer ticks again before you complete the original ISR.  Also make sure the interrupt is cleared before you switch!  This might happen automatically when the ISR is serviced on the AVR, not sure. 4KHz is very fast tick – can the AVR cope?

Confusing context switching on ATmega128

Thanks for the reply. You said: "…This is ok as each task maintains it’s own interrupt mask state. The task you switch to will have interrupts enabled, then when you switch back to the interrupted task the ISR will complete." So effectively, the ISR sends the message to the queue and then calls taskYIELD(). This means I switch context to Task2 and the original task (probably IDLE), that was interrupted by the ISR, is paused. Then Task2 finishes it job and starts blocking for another message, I go back to the original interrupted task which remembers that it was in the middle of an ISR and so let’s it finish? Wow, now I get it. That explains a lot of the behaviour I couldn’t understand. Thanks. By the way, the AVR seems to cope quite well with the 4KHz tick. However, my next step is to profile the tasks to check that this rate is ok.

Confusing context switching on ATmega128

Thats it exactly.  You can see why it is important to only call yield at the very end of the ISR, and once the interrupt has been cleared.  The queue/semaphore functions with "FromISR" on the end return a value that lets you know whether a context switch is required, rather than performing the switch themselves like their counterparts without the "FromISR".  This enables you to delay the switch to the end of the ISR.