STM32CubeIDE 에서 FreeRTOS 를 수동으로 통합하는 것까지 했고, SystemView 의 타겟 코드를 통합하는 작업을 했지만 실행해보니 특정 구간에서 오류가 발생하였다.
위와 동일한 사례라고 보면 된다.
""""
STM32에서 FreeRTOS와 Segger를 통합하려고 시도했을 때 configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue ) 오류가 발생했습니다.
제가 참조했던 자료를 바탕으로 "stm32f4xx_hal_msp.c" 파일 내의 HAL_MspInit() 함수에서 vInitPrioGroupValue()를 호출해 보았고, 또한 "stm32f4xx_hal_msp.c" 파일에 "FreeRTOS.h" 헤더 파일을 포함시켰습니다.
컴파일 시 vInitPrioGroupValue()가 정의되지 않았다는 오류가 발생합니다.
""""
오류는 FreeRTOS/portable/GCC/ARM_CM3/port.c 파일에서 발생한다.

configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue ) 이 부분에서 더 이상 넘어가지 않고 무한 루프에 빠지는 것을 볼 수 있다.
가장 큰 문제는 vInitPrioGroupValue 가 존재하지 않는다는 것이다. 누군가가 "그 기능은 Segger System Viewer 소스 코드의 패치를 통해 제공됩니다. 최신 패치에는 이 기능이 정의되어 있지 않습니다" 라고 답변을 달았는데, 실제로 단어 검색을 통해 확인해보니 프로젝트 내부에 vInitPrioGroupValue 라는 함수는 존재하지 않는 거 같았다.
이걸 생략하고 실행하면 위와 같은 문제가 발생하는 것이다. 무한 루프에 빠지게 된다.
그렇다면 몇가지 의문이 생긴다. 저 오류는 왜 발생하는 것이며, vInitPrioGroupValue는 원래 무슨 기능이었던 걸까? 하나씩 살펴보자.
일단 코드에 있는 주석 부분을 해석해봤다.
/* 우선순위 그룹화:
* 인터럽트 컨트롤러(NVIC)는 각 인터럽트의 우선순위를 정의하는 비트들을
* 선점 우선순위(pre-emption priority)를 정의하는 비트와
* 서브 우선순위(sub-priority)를 정의하는 비트로 나눌 수 있다.
* 단순화를 위해 모든 비트는 선점 우선순위 비트로 정의되어야 한다.
* 만약 일부 비트가 서브 우선순위를 나타낸다면,
* 아래의 assertion(검증)이 실패하게 된다.
*
* 애플리케이션이 인터럽트 설정을 위해 CMSIS 라이브러리만 사용한다면,
* 스케줄러를 시작하기 전에 NVIC_SetPriorityGrouping(0)을 호출하여
* 모든 Cortex-M 디바이스에서 올바른 설정을 할 수 있다.
* 하지만 일부 벤더 전용 주변장치 라이브러리는
* 0이 아닌 priority group 설정을 가정하고 있기 때문에,
* 이 경우 0을 사용하면 예측할 수 없는 동작이 발생할 수 있다.
*/
나는 우선순위 그룹을 "4 bits for pre-emption priority 0 bits for subpriority" 로 설정해놨다.

우선순위 그룹에는 크게 두 가지 부분으로 나뉜다. Pre-emption Priority (선점 우선순위)와 Subpriority (하위 우선순위) 로 나뉘는데 각각의 의미는 다음과 같다.
Pre-emption Priority가 4bit 라는 의미는 선점 우선순위가 0~15까지 총 16단계의 계급이 생긴다는 뜻이고, Subpriority가 0bit 라는 것은 동일한 계급 내에서는 누구부터 처리할지 정하는 순번을 지정하지 않겠다는 말이된다. 이 경우에는 하드웨어적인 번호(IRQ Number)가 빠른 쪽이 먼저 실행될 것이다.
정리하자면, 우선순위 그룹이란 인터럽트가 동시에 발생하거나 실행 중일 때, "누가 누구를 끊고 들어갈 수 있는지"와 "동시에 터졌을 때 누가 먼저 실행될지(순서)"를 결정하는 비트 배분 방식이다.
FreeRTOS 에서는 우선순위 그룹을 "4 bits for pre-emption priority 0 bits for subpriority"로 설정하는 것을 권장한다. 이유라고 한다면 커널이 인터럽트를 확실하게 제어하고 통제할 수 있는 환경을 만들기 위해서다.
자, 다음으로 넘어가자. 코드 주석 부분에서도 "단순화를 위해 모든 비트는 선점 우선순위 비트로 정의되어야 한다" 라고 써져있다. 만약 일부 비트가 서브 우선순위를 나타낸다면, assertion(검증)이 실패하게 된다.
음. 근데 좀 이상하다. 나는 분명 모든 비트를 선점 우선순위 비트로 설정했다. 코드를 살펴보면 HAL_Init의 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); 라고 되어있다.
주석을 좀 더 읽어보면 스케줄러를 시작하기 전에 NVIC_SetPriorityGrouping(0)을 호출하라. 단, 일부 벤더 전용 주변장치 라이브러리는 좀 다를 수 있다. 라고 되어있다.
HAL_NVIC_SetPriorityGrouping 함수를 살펴보자.
/**
* @brief Sets the priority grouping field (preemption priority and subpriority)
* using the required unlock sequence.
* @param PriorityGroup: The priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PRIORITYGROUP_0: 0 bits for preemption priority
* 4 bits for subpriority
* @arg NVIC_PRIORITYGROUP_1: 1 bits for preemption priority
* 3 bits for subpriority
* @arg NVIC_PRIORITYGROUP_2: 2 bits for preemption priority
* 2 bits for subpriority
* @arg NVIC_PRIORITYGROUP_3: 3 bits for preemption priority
* 1 bits for subpriority
* @arg NVIC_PRIORITYGROUP_4: 4 bits for preemption priority
* 0 bits for subpriority
* @note When the NVIC_PriorityGroup_0 is selected, IRQ preemption is no more possible.
* The pending IRQ priority will be managed only by the subpriority.
* @retval None
*/
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */
NVIC_SetPriorityGrouping(PriorityGroup);
}
결국 HAL_NVIC_SetPriorityGrouping 를 NVIC_SetPriorityGrouping를 호출하게 되어있다.
그리고 NVIC_PRIORITYGROUP_4 값을 살펴보면 "3" 이랑 동일하다.
/** @defgroup CORTEX_Preemption_Priority_Group CORTEX Preemption Priority Group
* @{
*/
#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2 0x00000005U /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3 0x00000004U /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority
0 bits for subpriority */
이렇게 생각해보면 주석에서 처럼 NVIC_SetPriorityGrouping(0)을 호출하면 음... 어떻게 되는거지? ㅋㅋ
찾아보니까 3 이하라면 NVIC_PRIORITYGROUP_4를 설정한 것과 동일한 결과가 나올거라고 하는데. 이게 팩트인지는 좀 더 확인이 필요해보임.
Cortex-M3 Devices Generic User Guide 문서를 보면 AIRCR 레지스터에 대한 설명이 있는데 여기서 우선순위 그룹을 설정하는 비트가 있다. [10:8] PRIGROUP
3 비트로 설정이 가능한데, 설정에 대한 의미는 다음과 같다.

만약에 3(011)을 설정한다고 하면 선점 우선순위 16개, 서브 우선순위 16개가 된다고 한다. 하지만 MCU마다 구현 차이가 있어서 Cortex-M은 priority 비트가 실제로는 3~4비트만 구현되어있다.
어쨌꺼나 나 같은 경우는 4비트 라고 볼 수 있고, 따라서 선점 우선순위를 4bit를 사용하면 서브 우선순위는 0bit를 사용할 수 밖에 없는 구조다.
결국 3 이하에서는 모두 동일하게 선점 우선순위 4bit, 서브 우선순위 0bit 가 될 것이다. (아하...)
검색하다보니 이런 흥미로운 글도 있었다. NVIC_PRIORITYGROUP_4(3)는 0은 효과가 동일한지 묻는 글이다. 마찬가지로 "0부터 3까지의 값(b'0xx)은 모두 동일한 효과를 나타냅니다." 라고 써있다.
NVIC_SetPriorityGrouping(0); 이렇게 설정하면 다른 MCU 를 사용한다고 했을때도 문제가 없을 것이다. 그니까 FreeRTOS에서는 선점 우선순위만 사용하고, 서브 우선순위를 사용하지 않는다는 것을 확실하게 보장하기 위해서 이런 설정을 권장하는 것이다.
결국 코드 주석에서처럼 NVIC_SetPriorityGrouping(0)을 사용하면 문제가 해결된다.
그러면 configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue ) 이 코드의 의미는 뭘까?
portAIRCR_REG는 SCB->AIRCR 레지스터를 말한다. 위에서 봤다. portPRIORITY_GROUP_MASK는 PRIGROUP 비트만 뽑아내는 마스크다. 따라서 이 둘을 & 하면 그룹 설정 비트만 나온다.
ulMaxPRIGROUPValue는 FreeRTOS가 허용하는 최대 PRIGROUP 값이다. “이 이상이면 안 된다”는 기준값이다. 이걸 체크하는 이유는 sub-priority 비트가 생기면 안 되기 때문이다. FreeRTOS가 아예 강제를 하고 있는 거네.
ulMaxPRIGROUPValue를 디버깅을 통해 값을 살펴보니 0으로 나온다. NVIC_SetPriorityGrouping(0)을 통해 0으로 설정하면 0 <= 0이 되므로 true가 되어 assert를 통과하게 되는것이다.
근데 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); 를 사용하면 값이 3이 설정되어서 3 <= 0 false가 되어서 assert를 통과하지 못해서 무한 루프에 빠지게 되는것이다.