이번 시간에는 지난 시간 만든 1ms Timer Interrupt를 활용해서 Scheduler를 만들어 봅시다. 임베디드에서는 Scheduler가 심장이라고 해도 과언이 아닙니다. 임베디드 위에서 올라가있는 모든 로직들의 일정을 관리해주니까요. 우리가 여태까지 아두이노를 통해 접해왔던 코드로 만들어진 결과물은 OS라 보기 어려웠습니다. 왜냐면 아주 단순하게 setup()이라는 함수를 보드가 처음 켜질때 1회 실행시키고 나서 loop()안에서만 빙빙 돌게 되니까요. 이건 아주 단순한 Firmware라고 봐야할 것 같아요. 우리가 OS라고 했을때 떠오르는 것은 윈도우, 맥, 리눅스, 안드로이드, IOS 등등 이니까요.
우리가 알고 있는 것 이외에도 RTOS라 해서 Real-Time OS가 존재합니다. 대부분 임베디드 시스템 위에 올라가 실시간으로 Hard-Time 제어(수행 시간에 민감한)의 목적으로 사용됩니다. 가장 대표적으로 MicroC/OS-II, FreeRTOS등이 있습니다. 이 OS들은 우리가 사용하는 전자기기들에 들어가기 때문에 매우 생소할거에요. 이런 RTOS들은 특정 목적을 가지고 각종 제어 로직의 일정을 관리해 주는 스케줄러입니다. (물론 스케줄러의 역할 외에도 자원관리와 같은 중요한 일들을 해주고 있습니다)
오늘 구현해볼 스케줄러는 위에서 설명한 OS들에 비해 매우 단순하지만 어떠한 느낌으로 흘러가는지 이해하기 좋을거에요. 우리의 스케줄러는 앞에서 말했던 것처럼 Hard-Time Scheduler가 될 겁니다. 왜냐면 자동차에 들어가는 제어 로직은 실시간으로 센싱하고 판단하여 동작해야 하니까요!
우리가 구현할 스케줄러의 가장 최소 시간 단위는 1ms가 될겁니다. 이 이하로 낮출수는 있지만 그렇게 된다면 우리가 스케줄러를 위해 만들어둔 Timer Interrupt 부터 손봐야 합니다. 그 이외에도 현재 우리가 개발하고 있는 보드는 84MHz로 동작하고 잇는데, 1 Clock에 약 0.0119us(1/84,000,000)가 소요됩니다. 그렇다는 이야기는 1ms에 84,033의 Clock이 뛰면서 명령어들을 수행하게 될건데, 이 이하로 최소 수행시간을 낮추게 되면 버벅거릴 수도 있습니다. 사실 다양한 Interrupt를 통해 필요하다면 추가 구현하면 되기도 하고 1ms 이하의 상황의 제어가 필요한 경우가 많이 없습니다.
이제 본론으로 돌아와서 스케줄러의 동작 순서를 설계해 봅시다. 가장 먼저 우리는 1ms마다 대기중인 Task들의 일정을 체크할 겁니다. 대기중인 Task는 본인이 수행되어야할 정보들을 담고 있습니다. (주기 / 수행 주기 Offset / 현재 대기 시간 / 수행할 Application / 활성화 여부 등등..) 이러한 정보들은 구조체로 만들어두면 관리하기가 편합니다. 아래와 같이 말이죠.
typedef struct{
uint16_t period_ms; // Task 주기
uint16_t offset_ms; // Task 주기 Offset
uint16_t curtick_ms; // Task가 대기한 시간
void (*func_ptr)(void); // Task가 연결된 Application 함수
bool activation; // Task 활성화 여부
}typSchTask;
이러한 정보들을 담고있는 Task는 1ms마다 대기 시간이 증가하게 되며, Task가 대기한 시간이 Task의 Period와 같아지게 되면 활성화를 시켜 둡니다.
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance == TIM2){
SchTask_TimeManager();
}
}
/* USER CODE END 4 */
void SchTask_TimeManager(void){
uint8_t i;
for(i = 0;i < SCHTASK_NUM;i++){
if(vSchTask_Info[i].offset_ms > 0){
vSchTask_Info[i].offset_ms--;
} // Wait for Offset Time
else{
vSchTask_Info[i].curtick_ms++;
if(vSchTask_Info[i].curtick_ms == vSchTask_Info[i].period_ms){
vSchTask_Info[i].activation = TRUE;
vSchTask_Info[i].curtick_ms = 0;
}
else{
// Do Nothing
}
}
}
}
1ms마다 스케줄러가 활성화 되어야 할 친구들을 Check하고 있으면, 그 뒤에서 Background 함수가 보조해 줄 겁니다. Background 함수는 우리가 익숙한 loop()문인 main문의 while(1)에서 스케줄러가 체크해서 던저준 Task들을 확인하고 Task에 연결되어 있는 Application 함수를 꺼내서 수행시켜주는 것이죠.
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
SchTask_Init();
HAL_TIM_Base_Start_IT(&htim2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
SchTask_Background();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
void SchTask_Background(void){
uint8_t i;
for(i = 0;i < SCHTASK_NUM;i++){
if(vSchTask_Info[i].activation == TRUE){
vSchTask_Info[i].func_ptr();
vSchTask_Info[i].activation = FALSE;
}
else{
// Do Nothing
}
}
}
이러한 방식을 통해서 우리는 각 Task를 서로 다른 주기로 동작시킬 수 있으며, 필요에 따라서 우선순위를 부여할 수 있게 되는 것이죠. 물론 앞에서 언급했던 것처럼 기존에 배포되고있는 OS, RTOS는 이것보다 훨씬 복잡하게 구현이 되어있습니다. 하지만 이렇게 비슷한 결로 흘러간다는것만 이해하고 계시면 됩니다.
이제 이렇게 스케줄러와 빈 껍데기의 Task & Application을 만들어 두었으니 다음 시간부터는 본격적으로 기능을 집어넣어 구현해봅시다.
(참고로 지난시간 1ms Timer Interrupt에서 1000번씩 Count해서 LED를 깜빡이던 로직은 1sec Task를 만들어서 이사해줬습니다! 이전과 동일하게 1초마다 잘 깜빡이고 있어요!)
Project Github 주소: https://github.com/DongjinJ/Whale_Mk1
GitHub - DongjinJ/Whale_Mk1
Contribute to DongjinJ/Whale_Mk1 development by creating an account on GitHub.
github.com
유튜브 주소: https://youtu.be/L6xtoKXyfWk
'프로젝트 일지 > Whale Car' 카테고리의 다른 글
[Whale Car] STM32H745ZIQ 보드 연결 (0) | 2024.10.03 |
---|---|
[Whale Car] 개발 보드 변경 (2) | 2024.09.19 |
[Whale Car] Serial 통신 뚫기 (1) | 2024.04.08 |
[Whale Car] 1ms Timer 만들기 (0) | 2023.05.29 |
[Whale Car] RC카 만드는 이야기 (0) | 2023.05.29 |