STM32/STM32_RTOS

STM32 RTOS 알아보기 5편(portYIELD_FROM_ISR)

원원 2025. 2. 1. 17:43

안녕하세요. 오늘은 RTOS에서 사용하는 portYIELD_FROM_ISR에 대해 알아보겠습니다.

제가 업로드했던 글 기준으로  3편에서 portYIELD_FROM_ISR 함수를  사용했었습니다.


portYIELD_FROM_ISR 는 ISR종료 후 즉시 태스크 전환이 필요한 경우 Context Switching을 수행하게 해줍니다.
port : FreeRTOS가 다양한 하드웨어에서 동작할 수 있도록 CPU별 맞춤 구현을 제공하는 하드웨어 적응 계층입니다.
YIELD : (양보하다) 현재 실행중인 태스크가 CPU실행을 포기하고 다른 태스크에게 실행을 양보합니다.
FROM_ISR : 해당기능(매크로 또는 함수)이 ISR에서 호출될 수 있음을 의미합니다.



portYIELD_FROM_ISR의 코드를 보겠습니다. 매크로로 되어있고, 만약 x가 pdFALSE라면 아무동작도 안하게 됩니다.
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
#define
 portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD()

결국에는 0xe000ed04은 SCB->ICSR (Interrupt Control and State Register)이고 값을 (1UL<<28UL)로 세팅합니다.

PendSV(Pending Supervisor Call)는 주로 태스크 전환(Context Switching)을 수행할 때 사용됩니다.  
PendSV는 현재 실행 가능한(Ready 상태) 태스크 중에서 가장 높은 우선순위의 태스크를 실행하도록 합니다.  
예를 들어, ISR에서 `xQueueSendFromISR()` 함수를 호출하면 큐를 대기하고 있던 태스크가 Ready 상태로 변경됩니다.  
이후, `portYIELD_FROM_ISR()`을 호출하여 PendSV 인터럽트를 발생시키면, ISR 종료 후 즉시 FreeRTOS 스케줄러가 실행됩니다. 스케줄러는 가장 높은 우선순위의 Ready 상태 태스크를 선택하고, PendSV는 해당 태스크로 전환을 수행합니다.


이제 실제로 코드에서 위와같이 동작하는지 확인해보겠습니다.
task1 : 우선순위1
task2 : 우선순위2
task1동작 : "C"를 딜레이없이 계속 출력
task2동작 : 큐에 데이터가들어오면 "B"출력
ISR동작 : 큐에 데이터 보내고 portYIELD_FROM_ISR를 사용/미사용으로 테스트

#include <stdio.h>
#include "cmsis_os.h"
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#define USE_YIELD_FROM_ISR 1

void task1(void *pvParameters);
void task2(void *pvParameters);

QueueHandle_t xQueue;
uint8_t input;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    input = 1;
    if (xQueueSendFromISR(xQueue, &input, &xHigherPriorityTaskWoken) != pdPASS) {
        printf("[ISR] Queue full! Failed to send button press event.\n");
    }
#if USE_YIELD_FROM_ISR
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
#endif
    printf("A");
}

void myTask()
{
    xQueue = xQueueCreate(5, sizeof(uint8_t));
    xTaskCreate(task1, "Task1", 256, NULL, 1, NULL);
    xTaskCreate(task2, "Task2", 256, NULL, 2, NULL);
}

void task1(void *pvParameters)
{
    while(1)
    {
        printf("C");
    }
}

void task2(void *pvParameters)
{
    uint8_t receivedValue = 0;
    while(1)
    {
        if (xQueueReceive(xQueue, &receivedValue, portMAX_DELAY) == pdPASS)
        {
            printf("B");
        }
    }
}

테라텀으로 log를 저장해서 검색하는형식으로 찾았습니다.
portYIELD_FROM_ISR 사용 (USE_YIELD_FROM_ISR : 1)
ISR이후에 task1이 실행될때가 없었습니다.

1. 버튼 인터럽트 발생 -> HAL_GPIO_EXTI_Callback 실행하며 "A"출력
2. xQueueSendFromISR가 실행되며 task2가 Ready상태가 됨
3. portYIELD_FROM_ISR(xHigherPriorityTaskWoken)실행 : PendSV 인터럽트 발생
4. task2 실행하며 "B"출력
5. task2는  xQueueReceived에서 다시 대기(Blocked)
6. task1 실행 시작 -> "C" 계속출력
"AC"가 출력되는경우가 없음

portYIELD_FROM_ISR 미사용 (USE_YIELD_FROM_ISR : 0)
ISR이후에 task1가 바로 실행될때가 있었습니다.

1. 버튼 인터럽트 발생 -> HAL_GPIO_EXTI_Callback 실행하며 "A"출력
2. xQueueSendFromISR가 실행되며 task2가 Ready상태가 됨
3. task1이나 task2가 실행됨
4. (task1가 실행된 경우) "C"출력
"AC" 가 출력되는 경우가 존재함

예제를 통해서 portYIELD_FROM_ISR가 context Switching을 수행하게하는것을 확인했습니다.



예제에서 portYIELD_FROM_ISR(xHigherPriorityTaskWoken)를 보면 xHigherPriorityTaskWoken가 pdTRUE여야만 portYIELD_FROM_ISR가 수행되는데 xQueueSendFromISR를 호출했을때 xQueueReceive에 따라서 pdTRUE가 되는지 확인해보겠습니다.

xQueueReceive가 없는경우


xQueueReceive가 대기하고있는경우