조이스틱을 통한 DC 모터 제어 도중 비상 버튼 인터럽트 발생시 즉시 동작중단.
Toggle 버튼을 활용하여, 다시 시작 가능하다.


osThreadId_t joystickTaskHandle;
const osThreadAttr_t joystickTaskAttr = {
.name = "joystickTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
osKernelInitialize();
joystickTaskHandle = osThreadNew(JoystickTask, NULL, &joystickTaskAttr);
osKernelStart();
void Motor_Init()
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
}
void Motor_Forward(uint16_t speed)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, speed);
}
void Motor_Backward(uint16_t speed)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); // IN2 = 1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // IN1 = 0
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, speed);
}
void Motor_Stop()
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 0); // PWM = 0
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); // IN2 = 0
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // IN1 = 0
}
uint16_t Read_Joystick()
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
return HAL_ADC_GetValue(&hadc1);
}
void JoystickTask(void *argument)
{
uint16_t x_val;
for(;;)
{
if (!stopFlag)
{
x_val = Read_Joystick();
if (x_val > 2300)
Motor_Forward((x_val - 2048) * 2);
else if (x_val < 1800)
Motor_Backward((2048 - x_val) * 2);
else
Motor_Stop();
}
else
{
Motor_Stop(); // 멈춘 후 반복 중지
}
osDelay(10);
}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13) // 블루버튼 기준 (PA13 또는 PC13)
{
stopFlag ^= 1; // toggle
Motor_Stop(); // 즉시 멈춤
}
}
이번 프로젝트의 목적은 단순한 GPIO 제어를 넘어서, FreeRTOS의 Task 관리 흐름을 직접 구성하며 시스템 제어를 구조화하는 것이었다.
초기 설계에서는 EXTI 인터럽트 → RTOS Task 통신 구조를 염두에 두고,
인터럽트에서 osThreadFlagsSet()이나 vTaskNotifyGiveFromISR() 등을 활용해 RTOS 스케줄러로 작업을 넘길 계획이었다.
그러나 구현 과정에서 버튼 인터럽트에 의한 로직이 너무 단순하고 짧다는 사실을 확인했고,
이러한 작업까지 굳이 Task로 분리하게 되면 오히려 컨텍스트 스위칭 비용, Task wake-up latency 등으로 인해 실시간성이 손해를 본다는 점을 체감했다.
결국, 인터럽트 콜백 내에서 직접 Motor_Stop()을 호출하고,
stopFlag 제어도 Task 외부에서 수행하는 방식으로 설계를 간소화했다.
이번 구현은 Task가 단 하나뿐이고, 인터럽트 핸들링도 매우 가볍다.
이런 상황에서는 FreeRTOS를 굳이 사용할 필요가 없으며,
Bare-metal 방식이 더 경량이며 실시간성도 보장되는 구조다.
하지만 RTOS의 구조적 장점은 향후 시스템 확장 시 명확히 드러난다.
예를 들어:
지금은 RTOS가 과한 설계였지만, "왜 과했는가"를 직접 구현하며 체득한 경험 자체가 중요했고,
앞으로 시스템 규모에 따라 적절한 구조와 기술스택을 선택하는 안목을 키우는 데 큰 도움이 되었다.
인터럽트 핸들링은 무조건 RTOS Task로 넘긴다고 좋은 게 아니다.
실시간성 보장, 실행 시간, 컨텍스트 전환 비용을 반드시 고려해야 한다.
Task는 "병렬성"이 아니라 "역할 분리"가 있을 때만 도입해야 의미가 있다.
기술을 쓰는 것보다, "왜 안 쓰는지 설명할 수 있을 때" 더 잘 이해한 것이다.