Unexpected task behav when blocked on a queue

cmp1104 wrote on Monday, October 01, 2012:

I am using FreeRTOS 7.1.1 on an EFM32 (ARM) uC.  I have three tasks: Idle (lowest priority), Task1 (highest priority), and Task 2 (middle priority).  At power-up I initialize Task 1 and Task 2.  Task 2 is set to block forever on a queue.  Task 1 checks to see if a global flag is set.  If it is not set (which it never is on powerup), Task 1 is suspended (see code below).  As my idle task is running I execute an external interrupt.  The external interrupt ISR posts to the queue which unblocks Task 1.  Task 1 then starts a uC based timer which has its own ISR.  After 1 ms the timer ISR resumes Task 2  (which is suspended) using xTaskResumeFromISR(). 

My problem is this: I am creating an interrupt by sending a double pulse to the uC.  The rising edges of the two pulses are far apart (200 us).  On the first pulse Task 1 is successfully unblocked and performs the necessary actions.  On the second pulse, Task 1 does not do anything.  However when the ISR for the the timer tells Task 2 to resume, Task 1 executes and then Task 2 resumes.  I have checked the time for Task 1 to execute and it is << 200 us.  I don’t understand why Task 1 does not begin executing immediately after the second interrupt.  Is the RTOS is an unknown state in between the first and second interrupts?

Thanks.

TASK 1:
for( ;; )
{                        
    //Try to get queue contents.  If nothing is available block until something is ready.  
    if(xQueueReceive(BlockingQueue, &queueContents, WAIT_FOREVER) == SEMAPHORE_AVAILABLE)
    {
        /*if this is the first interrupt -> start timer*/
      
       if(xSemaphoreTake(dataMutex, DO_NOT_WAIT) == APP_SEMAPHORE_AVAILABLE)
       {
           //Do some actions
           xSemaphoreGive(dataMutex);
        }
}//Loop back to be blocked again
        
TASK 2:
for(;:wink:
{
    if(An Interrupt has occurred)
   {
       if(xSemaphoreTake(dataMutex, WAIT_FOREVER) == SEMAPHORE_AVAILABLE)
      {
          //Do some actions
         xSemaphoreGive(dataMutex);
       }
   }
   else
   {
       vTaskSuspend(NULL);
   }
}
  

anonymous wrote on Monday, October 01, 2012:

Im still a FreeRTOS newbie myself, but are you using the portEND_SWITCHING_ISR macro at the end of your ISRs?  I would think this could cause something like this to happen.

anonymous wrote on Monday, October 01, 2012:

To clarify a bit, I think if you dont use that macro, freeRTOS waits until the next “tick” to switch tasks, which could be why your task 1 isnt running right away.  I believe it needs to be there to get the results you are looking for.

rtel wrote on Monday, October 01, 2012:

I have to admit I am a little confused by your explanation, and the source code, but here are some general comments that might help.

Mr google.com/accounts is correct.  If the interrupt unblocks a task the task is moved to the Ready state, and if the task is then the highest priority Ready state task it will be selected to run the next time the scheduler executes.  The scheduler will execute immediately (inside the ISR) if you call portEND_SWITCHING_ISR() with a non zero parameter.  When that is done, the interrupt will return to the task that was unblocked, not the task that it originally interrupted.  If you don’t call portEND_SWITCHING_ISR(), then the scheduler will run the next time the tick interrupt executes, so that is when the task that was unblocked will run.

Generally speaking, using vTaskResumeFromISR() can have results that you might not expect.  This is because calls to vTaskResumeFromISR() does not latch events.  In your example, you have two interrupt occurring in quick succession.  The first will resume the suspended task, the second will do nothing unless the task has executed far enough to suspend itself again.  Contrast that to blocking on a semaphore instead of suspending.  If the task blocks on a semaphore, the first interrupt will result in the semaphore being available, and the task leaving the Blocked state leaving the semaphore empty again.  The second interrupt will give the semaphore again, making it available again, even though the task has not executed far enough to return to blocking on the semaphore.  When the task eventually does try to take the semaphore, the call to xSemaphoreTake() returns immediately because the semaphore is already available.

If that last paragraph is too complex, then, put more simply, the two interrupts will cause the task to execute twice if the task is blocking on a semaphore, but only once if the task is suspending itself.

Regards.

cmp1104 wrote on Monday, October 01, 2012:

Re-reading the explanation of my problem I realized it wasn’t particularly clear. 

Task 1 is is successfully unblocked on the first external interrupt and performs the actions I expect.  However it does not appear to go back into a blocked state.  The time between external interrupts is sufficient so it should return to being blocked on the queue.  However Task 1 does not appear to re-block.  Instead it appears to do nothing until the timer interrupt handler executes.  When the timer inteterrupt executes, and tells Task 2 to resume, Task 1 does what it should have done for the second external interrupt.  Task 2 then resumes as expected.

The time delay bewtween when the first external interrupt occurs and Task 1 becomes unblocked is fine (few microseconds).  The problem is only with second external interrupt.  I have tried using  portEND_SWITCHING_ISR() in the past and the behavior did not change.

anonymous wrote on Monday, October 01, 2012:

Can you post the code from your interrupts?

rtel wrote on Monday, October 01, 2012:

From your original post:

Task 1 checks to see if a global flag is set.  If it is not set (which it never is on powerup), Task 1 is suspended (see code below).

The behaviour you describe does not seem consistent with the code you have posted for Task 1.  It does seem consistent with the code you have posted for Task 2 though.

       if(xSemaphoreTake(dataMutex, WAIT_FOREVER) == SEMAPHORE_AVAILABLE)
      {
          //Do some actions
         xSemaphoreGive(dataMutex);
       }

In this code, the task originally blocks on dataMutex, then just loops giving and taking dataMutex to itself, in the following short sequence:

1) It calls xSemaphoreTake( dataMutex ) to take dataMutex.
2) It calls xSemaphoreGive( dataMutex ) to give the mutex back again.
3) It calls xSemaphoreTake( dataMutex ), but this time it does not block because the mutex is already available - it just gave it to itself.

… then back to step 2, and so on for ever.

Regards.

cmp1104 wrote on Monday, October 01, 2012:

My uC has two interrupt vectors for external interrupts but right now I am just using one.  There is not much to the ISR.  I have also confirmed that the priority of interrupt vector is lower than configMAX_SYSCALL_INTERRUPT_PRIORITY.

void GPIO_ODD_IRQHandler(void)
{
   //The current value of the interrupt flag register
   uint16 currInt = 0;
  
 
   /*Safely disable and enable interrupts at cpu level.     
   INT_Disable();
   currInt = GPIO_IntGetEnabled();     
   
   ISR_IRQHandler(&currInt);
   GPIO_IntClear(currInt);
  
  INT_Enable();  
 
}

void ISR_IRQHandler(uint16* pCurrInt)
{
    int32 queueReturn = 0;
    if((xQueueSendToBackFromISR(dataQueue, pCurrInt, &queueReturn)) == QUEUE_NO_ERROR)
   {
      return(NO_ERROR);
   }
   else
   {
         return(ERROR);
   }
}

rtel wrote on Monday, October 01, 2012:

Note I posted a reply just before your last post.

I’m not sure why you interrupt handler is returning a value, but it looks like it is not called directly because it is also taking a parameter.  This is not normal for a Cortex-M3.  You are not calling portEND_SWITCHING_ISR() either, as per the Google guy’s recommendation, so that will mess up the sequencing too.  You could add

portEND_SWITCHING_ISR( queueReturn );

to the end of your ISR.

Regards.

cmp1104 wrote on Monday, October 01, 2012:

Richard - Sorry, that was a typo on my part. I meant to say that Task 2 checks for a global flag and if it is not set it gets suspended.  Task 1 is blocked on the queue.  (Task 1 also sets the global flag.)

Thanks for the catch.

cmp1104 wrote on Monday, October 01, 2012:

In an earlier version of my code I had relatively elaborate GPIO_ODD and GPIO_EVEN ISRs and had to perform identical actions  As part of the revision I am doing I am having the ODD and EVEN functions call one function (ISR_IRQHandler) so any code edits are made in only one place.  The return from ISR_IRQHandler is just there for completeness in case there was  problem writing to the queue. 

I tried putting portEND_SWITCHING_ISR( queueReturn ); previously and there was no change in the behavior.  I have also just recently put everything in my GPIO_ODD function (i.e. posting to the queue from GPIO_ODD and cutting out the ISR_IRQHandler function) and again there was no change. 

cmp1104 wrote on Monday, October 01, 2012:

One other finding: The time between external interrupts seems to make a difference.  I can cut the time in half (from 200 us to 100 us) and the system behaves as expected.  I can double the time (from 200 us to 400 us) and system behaves as expected.  However if I am around 200 us I run into the issue I describe above.

anonymous wrote on Monday, October 01, 2012:

What is your tick frequency?

anonymous wrote on Monday, October 01, 2012:

Also, what is configUSE_PREMPTION set to?

rtel wrote on Tuesday, October 02, 2012:

Can you comment on post 7 in this thread.

Regards.

cmp1104 wrote on Tuesday, October 02, 2012:

My tick frequency is set to 1.  I have tried a higher frequency (128) and got the same results.  My configUSE_PREEMPTION setting is 1. 

Post #9 contains my interrupt handling code.  I am using the output of a function generator to make the interrupts and this is independent of the uC.

rtel wrote on Tuesday, October 02, 2012:

Post #9 contains my interrupt handling code.

Actually, post 9 is from me.  The numbers are above the post, not below it, but I was asking for a reply to post 7, which is a comment on your source code.  The post I am asking about starts “From your original post:”.

Regards.

cmp1104 wrote on Tuesday, October 02, 2012:

Sorry again.  A fuller version of Task 2 is below.  I use the global flag to force Task 2 to be suspended immediately after it is initialized.  When my timer ISR resumes Task 2, the task should immediate check if the flag is set (and if we get to this point the flag should have been set by Task 1).  Since the flag is set, Task 2 then tries to take the mutex.  I do this in case the timer ISR asserts itself and resumes Task 2 before Task 1 has finished processing data.  Once Task 2 has finished its work it leaves the conditional statement and the task is suspended again. 

I am running my code in debug mode and if I pause while the Idle task is running, Task 1 is blocked (as expected) and Task 2 is suspended (as expected). 

TASK 2:
for(;:wink:
{
    if(irqCount)  //Check of value of global flag
   {
       if(xSemaphoreTake(dataMutex, WAIT_FOREVER) == SEMAPHORE_AVAILABLE)  //
      {
          //Do some actions
         irqCount = 0;   //Clear global flag
         xSemaphoreGive(dataMutex);
       }
   }  //end of  if(irqCount)
  //Global flag is not set.  Suspend task until resumed by timer ISR
   vTaskSuspend(NULL);
  
}

cmp1104 wrote on Monday, October 08, 2012:

Just to bring this issue to a point of closure, I think I found the issue.  It appears that my second interrupt is occuring in a narrow window in which the RTOS has completed its status checks and is about to go to sleep.  I found that the time required for the program to go from blocking on the queue to going to sleep is about 200 us.  Just prior to going to sleep the tick interrupt timer is set to its maximum value so the RTOS thinks it is okay to go to sleep for a long period of time and is only woken up by the 1 ms timer interrupt going off.  I think this is why interrupt intervals > ~200 us are okay, the are seen as separate events and shorter interrupt intervals are also okay (< ~150 us), those are caught when the RTOS is doing its final status check prior to sleep.  It appears that I have hit a bit of a hole timing wise.

In addition I am using the RTOS  configuration for EFM32 microcontrollers that helps to take advantage of the EFM32 power saving modes.  There is a basic check just prior to going to sleep but it does not catch the fact that a second interrupt occurred.  Besides explicitly doing a check if something is in the queue is there anything I can do so I don’t fall into the hole?

Thanks.

edwards3 wrote on Monday, October 08, 2012:

I don’t understand this. It sounds like you are using global variables and the suspend/resume mechanism to sequence two tasks. Both these methods are prone to race conditions. Queues and mutexes can be used for synchronization without race condition.

It appears that my second interrupt is occuring in a narrow window in which the RTOS has completed its status checks

What status checks?

and is about to go to sleep

The RTOS doesn’t go to sleep by itself.

I found that the time required for the program to go from blocking on the queue to going to sleep is about 200 us

As far as the RTOS is concerned, that is an atomic operation, unless you have the interrupt priorities set wrong.

ust prior to going to sleep the tick interrupt timer is set to its maximum value so the RTOS thinks it is okay to go to sleep for a long period of time and is only woken up by the 1 ms timer interrupt going off.

Nope. Lost me.