3116 views|10 replies

5213

Posts

239

Resources
The OP
 

[FreeRTOS check-in station 5 opens] Interrupts and task switching, closing time is August 26 [Copy link]

Activity Overview: Click here to view (including activity encouragement and activity learning content)

Check-in start and end time for this site: August 24-August 26 (3 days)

Check-in tasks :

1. Read Cruelfox's fifth article: FreeRTOS Learning Notes (5) Interrupts and Task Switching

2. Please briefly describe the function of the portYIELD_FROM_ISR() macro call. ( Reading the FreeRTOS official documentation and viewing the implementation of this macro in the source code will help you understand it.)

FreeRTOS Learning Notes (5) Interrupts and Task Switching

For your convenience, I copied cruelfox's " FreeRTOS Learning Notes (5) Interrupts and Task Switching " here~

After FreeRTOS has the task's memory resource - stack management mechanism, can perform context switching of CPU execution according to task status and priority, and provides inter-task communication channels to achieve necessary task synchronization and mutual exclusion, multiple tasks can work together. However, since it is called a Real-Time operating system, it also needs to be able to respond quickly to external (hardware) events. Especially for applications on microcontrollers, after a hardware interrupt (IRQ) is generated, it is necessary for the operating system to immediately wake up a task to handle this event. From the perspective of tasks, there are actually many tasks that need to be scheduled according to hardware events (such as transmission completion, device ready, data received, etc.), otherwise it will keep testing the hardware device status register flag bit, wasting CPU time.


  The time slice management of FreeRTOS actually borrows the timer interrupt behind the scenes. Otherwise, it is impossible for a task to be interrupted without applying for scheduling and execute other tasks of the same priority. Similarly, any hardware interrupt will execute the corresponding interrupt service routine (ISR, also called IRQ Handler). After the ISR is executed, will it return to the current task or schedule other tasks? This is entirely determined by the ISR.

1. ISR is independent of all tasks
  Although in effect, ISR, that is, interrupt service routine, serves the function of a certain task, it must be emphasized first: ISR code does not belong to any part of FreeRTOS task code. Each ISR is a C language function, but it is not a task and will not be called by any task.


  ISR uses the stack differently from tasks. As mentioned in the previous series, FreeRTOS allocates independent stack space for each task to save local variables of functions, etc. When an interrupt occurs, some registers of the CPU will be saved to the current stack (rather than the stack of a specified task), and then the ISR program will be executed. If the code of a task is currently being executed, the stack of the task will be occupied; if the code of another ISR is currently being executed, that is, interrupt nesting occurs, then the stack of the task that was interrupted earlier may continue to be used (Note: This is platform-related. For the implementation on the ARM Cortex-m series platform, FreeRTOS allows the task to run in thread mode, using PSP as the stack pointer, and the ISR will switch to handler mode, using MSP as the stack pointer, so all ISRs will share a stack).

  The execution of the ISR can be independent of the FreeRTOS kernel. As long as the FreeRTOS API is not used in the ISR, FreeRTOS will not know that the interrupt has occurred, because it can save the scene and restore the scene after execution regardless of where the current stack is. Similarly, the execution of the ISR itself will not cause any task switching. When the FreeRTOS code is introduced into an existing project, the original ISR can still operate without modification.

  The ISR does not change the state of the current task. Although the execution of the currently running task is suspended after the IRQ occurs, and the CPU switches to execute the ISR code, the state of the current task is still Running, and it does not change to other states - this is obviously different from the task being preempted. Even if the FreeRTOS API is called in the ISR to wake up other tasks with higher priority than the current task (change to the Ready state), the task switching operation will be performed after the ISR returns, and the running task will be reselected. In fact, the ISR does not know what the currently running task is, and it is meaningless to actively change the current task state.

2. Critical Section Concept
  When I analyzed the FreeRTOS implementation details earlier, I encountered the two calls taskENTER_CRITICAL() and taskEXIT_CRITICAL() many times. From the name, it means that a very important operation is to be performed at this time, which is not allowed to be interrupted, such as accessing the task status list. If it is not handled in this way, the data to be accessed may be overwritten in the middle, or the data modification is not completed and is accessed by other tasks or the FreeRTOS kernel, which will cause erroneous results. Therefore, a section of code is defined as a critical section, and taskENTER_CRITICAL() and taskEXIT_CRITICAL() are used to protect it, prohibiting task scheduling and prohibiting other interrupt ISRs from accessing FreeRTOS core data.
  After this treatment, this section of code is temporarily given a very high priority, regardless of the priority of the current task. Guess, first mask the interrupt, and then allow it after execution, isn't it? In fact, it is not that simple. Let's take a look at how FreeRTOS defines these two operations.

  There are two macro definitions in the task.h header file:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()

  Next, in the portmacro.h file (CM3 platform), it is defined as
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()

  Find the implementation of vPortEnterCritical() and vPortExitCritical() functions in the port.c file:

  1. void vPortEnterCritical(void)
  2. {
  3. portDISABLE_INTERRUPTS();
  4. uxCriticalNesting++;
  5. if( uxCriticalNesting == 1 )
  6. {
  7. configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
  8. }
  9. }
  10. void vPortExitCritical(void)
  11. {
  12. configASSERT(uxCriticalNesting);
  13. uxCriticalNesting--;
  14. if( uxCriticalNesting == 0 )
  15. {
  16. portENABLE_INTERRUPTS();
  17. }
  18. }

Copy code


  There is a little more operation than disabling interrupts: a counting variable is used. The configASSERT() code can be removed and ignored. So why do we need to count? The answer is to nest calls. After a certain number of vPortEnterCritical() calls, the same number of vPortExitCritical() calls are required to allow interrupts.

  Let's take a look at how to disable interrupts on the Cortex-m3 platform:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)

  Take a closer look at the functions implemented in the assembly code

  1. portFORCE_INLINE static void vPortRaiseBASEPRI( void )
  2. {
  3. uint32_t ulNewBASEPRI;
  4. __asm volatile
  5. (
  6. " mov %0, %1 undefined" \
  7. " msr basepri, %0 undefined" \
  8. " isb undefined" \
  9. "dsb undefined" \
  10. :"=r" (ulNewBASEPRI) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
  11. );
  12. }

Copy code


  This operation modifies the BASEPRI register and masks some hardware interrupts: interrupts with a priority equal to or lower than configMAX_SYSCALL_INTERRUPT_PRIORITY. Why is it only partially masked? Because if an interrupt ISR does not access FreeRTOS core data or call any FreeRTOS API, then its interruption is harmless. However, partially masking interrupts requires hardware support. For example, there is no BASEPRI register on the ARM Cortex-m0 platform, so the corresponding implementation code is simple:
#define portDISABLE_INTERRUPTS() __asm volatile (" cpsid i ")
#define portENABLE_INTERRUPTS() __asm volatile (" cpsie i ")

  There can also be a critical section in the ISR, but it is necessary to call taskENTER_CRITICAL_FROM_ISR() and taskEXIT_CRITICAL_FROM_ISR(), whose parameters and return values are different, and the current interrupt level status needs to be saved and restored. On the Cortex-m3 platform, the corresponding value is to save and restore the BASEPRI register.
  The meaning of the value of configMAX_SYSCALL_INTERRUPT_PRIORITY is that only ISRs with a priority not higher than this one are allowed to call the FreeRTOS API, that is, just because they have the opportunity to call the API, they must be blocked when entering the critical section. As for the higher the interrupt priority, whether the value is larger or smaller depends on the hardware platform. Be sure not to confuse the interrupt priority (a hardware concept) with the task priority of FreeRTOS.

3. FreeRTOS API functions that can be used in ISR
  In the FreeRTOS documentation, it has always been emphasized that in ISR, API functions ending with FromISR must be called , and regular APIs cannot be called. This is because the execution environment and tasks of ISR are different. In addition to considering efficiency, some APIs have to be distinguished. The

  API called in the ISR requires a quick return and does not allow waiting. The system does not allow interrupt processing to take up too much time, let alone waiting for other interrupts to occur. Some APIs cannot be used in ISRs because they have blocking functions, or the functions are changed, including parameter passing requirements.

  Task scheduling is optional in ISRs. For example, the operation of the communication object may wake up other tasks with higher priority than the current task; if it is performed in a task, it will immediately cause a task switch. However, in ISRs, it may not be necessary to switch tasks so frequently, and it is beneficial to operating efficiency to treat it as a freely selectable operation. This xxxxFromISR() API will have a BaseType_t *pxHigherPriorityTaskWoken parameter to determine whether a higher priority task has been awakened, and then the ISR will decide whether to switch tasks.

  I have extracted the ISR-specific API functions from the manual and their corresponding ordinary API versions, and listed them in the table below. Some ordinary versions of APIs have a parameter to specify the waiting time, which is cancelled in the ISR version.

ISR dedicated function name Conventional API correspondence Other Features
xTaskGetTickCountFromISR xTaskGetTickCount
xTaskNotifyFromISR xTaskNotify Additional parameters
xTaskNotifyAndQueryFromISR xTaskNotifyAndQuery Additional parameters
vTaskNotifyGiveFromISR xTaskNotifyGive Additional parameters
xTaskResumeFromISR vTaskResume Return Value
xQueueIsQueueEmptyFromISR ---
xQueueIsQueueFullFromISR ---
uxQueueMessagesWaitingFromISR uxQueueMessagesWaiting
xQueueOverwriteFromISR xQueueOverwrite Additional parameters
xQueuePeekFromISR xQueuePeek Cancel Wait
xQueueReceiveFromISR xQueueReceive Additional parameters, cancel waiting
xQueueSelectFromSetFromISR xQueueSelectFromSet Cancel Wait
xQueueSendFromISR xQueueSend Additional parameters, cancel waiting
xQueueSendToBackFromISR xQueueSendToBack Additional parameters
xQueueSendToFrontFromISR xQueueSendToFront Additional parameters
xSemaphoreGiveFromISR xSemaphoreGive Additional parameters
xSemaphoreTakeFromISR xSemaphoreTake Additional parameters, cancel waiting
xTimerChangePeriodFromISR xTimerChangePeriod Additional parameters, cancel waiting
xTimerPendFunctionCallFromISR xTimerPendFunctionCall Additional parameters, cancel waiting
xTimerResetFromISR xTimerReset Additional parameters, cancel waiting
xTimerStartFromISR xTimerStart Additional parameters, cancel waiting
xTimerStopFromISR xTimerStop Additional parameters, cancel waiting
xEventGroupClearBitsFromISR xEventGroupClearBits Executed in Daemon Task
xEventGroupGetBitsFromISR xEventGroupGetBits Executed in Daemon Task
xEventGroupSetBitsFromISR xEventGroupSetBits Additional parameters, executed in Daemon Task


  When the ISR needs task scheduling (for example, when an API returns *pxHigherPriorityTaskWoken equal to pdTRUE), portYIELD_FROM_ISR(pdTRUE) should be executed before the ISR returns to let the scheduler switch tasks. For the Cortex-m3 platform, portYIELD_FROM_ISR() implements scheduling in the same way as portYIELD(), except for checking whether the parameter is true, which is to set the PendSV bit in the NVIC (interrupt controller). In this way, after all hardware interrupt request ISRs return, the ISR of the PendSV interrupt is executed and the scheduler performs task switching. (See my previous post "FreeRTOS Learning Notes (3) Task Status and Switching")

  Using ISR to trigger task scheduling logically assigns part of the processing of external interrupt events to a certain task (or some tasks), and only performs some urgent and time-saving processing in the ISR (such as reading the registers of hardware devices, clearing flags, transferring buffer data, etc.). The remaining work handled by the task is then managed by the FreeRTOS scheduler according to the task priority. From the software's point of view, it is as if the task is waiting for an interrupt to occur and then immediately handles it.

4. Daemon Task
  It is natural to assign the more complex and time-consuming work of hardware interrupt processing to a separate task, but FreeRTOS also provides a mechanism to avoid creating a separate task. This is to use the system's Daemon Task.

  The xTimerPendFunctionCallFromISR() function "submits" a normal function as a parameter to the system service, allowing the system's own Daemon Task to execute this function. When submitting, specify two parameters to pass to this function. Daemon Task is managed by the scheduler, and its task priority is specified by configTIMER_TASK_PRIORITY. When Daemon Task executes the submitted function, it depends on whether the system is idle. When it gets the execution opportunity, it will take out the function entry address and parameters to be executed from the command queue to execute. Borrowing a picture from the manual:



  The FreeRTOS Event Group implementation borrows the Daemon Task to handle operations in the ISR, such as the xEventGroupSetBitsFromISR() call listed in the table above. The reason described in the manual is that this is not a "deterministic operation" (it may take too long). In event_groups.h, #define
xEventGroupClearBitsFromISR(xEventGroup, uxBitsToClear) \
xTimerPendFunctionCallFromISR(vEventGroupClearBitsCallback, \
(void *) xEventGroup, (uint32_t)uxBitsToClear, NULL)

is defined , thus delaying a FromISR call to the Daemon Task to execute the normal version call.

  The main body of the Daemon Task is as follows:

  1. static void prvTimerTask( void *pvParameters )
  2. {
  3. TickType_t xNextExpireTime;
  4. BaseType_t xListWasEmpty;
  5. #if( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 )
  6. {
  7. extern void vApplicationDaemonTaskStartupHook( void );
  8. }
  9. #endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */
  10. for( ;; )
  11. {
  12. xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
  13. prvProcessTimerOrBlockTask(xNextExpireTime, xListWasEmpty);
  14. prvProcessReceivedCommands();
  15. }
  16. }

Copy code


  The loop is processing software timer events, sorting them by expiration time (executing the corresponding functions). This involves the function of software timer - FreeRTOS, which will be studied later. In order to understand how the function submitted from ISR is executed, let's first look at what xTimerPendFunctionCallFromISR() does:

  1. BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken )
  2. {
  3. DaemonTaskMessage_t xMessage;
  4. BaseType_t xReturn;
  5. xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR;
  6. xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend;
  7. xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1;
  8. xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2;
  9. xReturn = xQueueSendFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
  10. tracePEND_FUNC_CALL_FROM_ISR( xFunctionToPend, pvParameter1, ulParameter2, xReturn );
  11. return xReturn;
  12. }

Copy code


  It is easy to understand. Fill in the function address and parameters to be executed in the DaemonTaskMessage_t data structure and add it to the xTimerQueue queue. In the prvProcessTimerOrBlockTask() function in the task loop above, there is a call (the complete code is not listed here):
vQueueWaitForMessageRestricted(xTimerQueue, (xNextExpireTime - xTimeNow), xListWasEmpty);
that is, wait for messages in the xTimerQueue queue until the next software timer expires. Therefore, when the Daemon Task receives the message sent from the ISR, it will execute the command (function call) specified by the message.

  Summary
  In order to support real-time response to hardware events, the interrupt service routine (ISR) must be executed as early as possible. Because the system may have multiple interrupts, the ISR needs to be written as short as possible and return after executing the key operations to allow other interrupts to be processed. FreeRTOS provides a series of mechanisms to allow ISR to hand over operations that need to be processed but are not so urgent to tasks to complete, and reasonably allocate CPU resources.

Welcome to reply to this post and the question cruelfox left for everyone: Please briefly describe the function of the portYIELD_FROM_ISR() macro call. ( Reading the FreeRTOS official documentation and viewing the implementation of this macro in the source code will help you understand it.)

This post is from MCU
Add and join groups EEWorld service account EEWorld subscription account Automotive development circle

Latest reply

  Details Published on 2020-8-27 08:18
 

1w

Posts

16

Resources
2
 

portYIELD_FROM_ISR(pdTRUE) allows the scheduler to switch tasks. For the Cortex-m3 platform, portYIELD_FROM_ISR() implements scheduling in exactly the same way as portYIELD(), except for checking whether the parameter is true, which is to set the PendSV bit in the NVIC (interrupt controller). In this way, after all hardware interrupt request ISRs return, the ISR of the PendSV interrupt is executed and the scheduler performs task switching.

This post is from MCU

Comments

This is copy and paste.  Details Published on 2020-8-25 11:51
 
Personal signaturehttp://shop34182318.taobao.com/
https://shop436095304.taobao.com/?spm=a230r.7195193.1997079397.37.69fe60dfT705yr
 

122

Posts

0

Resources
3
 
ddllxxrr posted on 2020-8-24 19:53 portYIELD_FROM_ISR(pdTRUE) allows the scheduler to switch tasks. For the Cortex-m3 platform, portYIELD_FROM_ISR() not only checks...

This is just copy and paste, the standard answer?

This post is from MCU
 
 
 

144

Posts

0

Resources
4
 

Execute system calls. For example, normal tasks can use taskYIELD() to force task switching, and interrupt service routines can use portYIELD_FROM_ISR() to force task switching.

If the parameter of the portYIELD_FROM_ISR function is pdTRUE, the exit interrupt will switch to the high priority task. That is because the portYIELD_FROM_ISR function will call the portYIELD() function to force context switching when the parameter is pdTRUE. If it is pdFALSE, the context switch will not be performed.

This post is from MCU
 
 
 

122

Posts

0

Resources
5
 

Macro definition in source code

#define portNVIC_INT_CTRL_REG  ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT  ( 1UL << 28UL )
#define portYIELD()     vPortYield()
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired ) portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )

It can be seen that the register address is set to 0xe000ed04

View Register Table

Interrupt Control Status Register

The interrupt control status register is used to:

  • Set a pending NMI
  • Set or clear a pending SVC
  • Set or clear a pending SysTick
  • Finding Hang Exceptions
  • Find the vector number of the highest priority pending exception
  • Find the vector number that activates the exception

Register address, access type and reset state:

Address 0xE000ED04

Access Type Read/Write or Read Only

Reset status 0x00000000

The bit allocation of the interrupt control status register is shown in the figure.

From the source code macro definition, we can see that this macro definition is mainly to suspend pendSV

The typical use case for PendSV is during context switching (switching between different tasks).

Operating system, context switch example:

Scenario assumption: There are two ready tasks (task A and task B) in a system (a system scheduled by time slice rotation).
The context switch can be triggered when:

  • Execute a system call
  • System tick timer (SYSTICK) interrupt, (required in round-robin scheduling)

Two ready tasks A and B start context switching through SysTick exception. As shown in Figure 7.15.


The above figure is a schematic diagram of the round-robin scheduling of two tasks. However, if an interrupt is being responded to when the SysTick exception is generated, the SysTick exception will preempt its ISR.
In this case, the operating system cannot perform context switching, otherwise the interrupt request will be delayed, and the delay time is often unpredictable in the real system - any system with a little real-time requirement will never tolerate this. Therefore, it is strictly prohibited in CM3 - if the operating system tries to switch to thread mode when an interrupt is active, it will violate the usage fault exception.

To solve this problem, most early operating systems will detect whether there is an interrupt currently active in the SysTick exception, and only when there is no interrupt to respond, will they perform context switching (interrupts cannot be responded to during switching).
However, the disadvantage of this method is that it may delay the task switching action for a long time (because if the IRQ is preempted, the current SysTick cannot perform context switching after execution and can only wait for the next SysTick exception), especially when the frequency of a certain interrupt source is close to the frequency of the SysTick exception, "resonance" will occur. Now, PendSV can perfectly solve this problem (when a SysTick exception is generated, an interrupt is being responded to, and the SysTick exception will preempt its ISR. At this time, the operating system cannot perform context switching, otherwise the interrupt request will be delayed):
Program PendSV as the lowest priority exception, and the PendSV exception will automatically delay the context switching request until other ISRs have completed processing. If the operating system detects that an IRQ is active and preempted by SysTick, it will suspend a PendSV exception to postpone the execution of the context switch. As shown in Figure 7.17

The logbook is as follows:
1. Task A calls SVC to request a task switch (e.g., to wait for some work to be completed)
2. The OS receives the request, prepares for the context switch, and pends a PendSV exception.
3. When the CPU exits SVC, it immediately enters PendSV to perform the context switch.
4. When PendSV is completed, it returns to Task B and enters Thread mode.
5. An interrupt occurs and the Interrupt Service Routine begins to execute
6. During the execution of the ISR, the SysTick exception occurs and the ISR is preempted.
7. The OS performs the necessary operations and then pends a PendSV exception to prepare for the context switch.
8. When SysTick exits, it returns to the previously preempted ISR, and the ISR continues to execute
. 9. After the ISR completes execution and exits, the PendSV service routine begins execution and performs context switching in it.
10. When PendSV completes execution, it returns to task A and the system enters thread mode again.

Some sources: Internet

This post is from MCU
 
 
 

493

Posts

1

Resources
6
 

Let the scheduler switch tasks

This post is from MCU
 
 
 

5

Posts

0

Resources
7
 

According to the official tutorial

portYIELD_FROM_ISR() is a macro for an ISR to request a task switch, and portEND_SWITCHING_ISR() is also provided in newer ports.

If the parameter of this macro is pdFALSE (zero), then task switching will not be requested. If it is pdTURE, task switching is requested, and the task in the running state may switch. The interrupt will always return to the task in the running state, even if the task in the running state is switched when the interrupt is executed.

This post is from MCU
 
 
 

27

Posts

0

Resources
8
 

portYIELD_FROM_ISR() requests context switching in a task. It is used to perform a task switch after the interrupt task is executed.

This post is from MCU
 
 
 

419

Posts

1

Resources
9
 

This macro function is mainly used for context switching applications. In essence, it implements the PendSV exception by operating the relevant positions of the Interrupt Control and Status Register (ICSR); then sets the context return of other tasks in the PendSV exception, so that after the high-priority exception is handled, it will immediately enter the PendSV exception, which will start a new task

This post is from MCU
 
Personal signature君应有语,渺万里层云,千山暮雪,知向谁边?
 
 

27

Posts

0

Resources
10
 
abc9981 posted on 2020-8-26 21:39 Macro definition in source code #define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) ) #defi ...

This post is from MCU
 
 
 

5213

Posts

239

Resources
11
 

Related interpretation by cruelfox: https://en.eeworld.com/bbs/thread-1141050-1-1.html

This post is from MCU
Add and join groups EEWorld service account EEWorld subscription account Automotive development circle
 
 
 

Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list