
타이머 인터럽트는 정확한 주기로 함수를 실행할 수 있게 해주는 핵심 기능입니다. HAL_Delay()와 달리 CPU를 차단하지 않으며, 백그라운드에서 정확한 시간 간격으로 작업을 수행할 수 있습니다.
타이머 인터럽트의 동작
Timer Counter: 0 → 1 → 2 → ... → ARR
↓
Update Event 발생
↓
인터럽트 요청 (IRQ)
↓
NVIC가 처리
↓
ISR(Interrupt Service Routine) 실행
↓
HAL_TIM_PeriodElapsedCallback() 호출
HAL_Delay()와의 차이
// HAL_Delay() - 블로킹 방식
HAL_Delay(1000); // 1초 동안 CPU 정지, 다른 작업 불가
// 타이머 인터럽트 - 논블로킹 방식
HAL_TIM_Base_Start_IT(&htim6); // 타이머 시작
// CPU는 계속 다른 작업 수행 가능
// 1초마다 자동으로 콜백 함수 호출됨
타이머 인터럽트는 정확한 주기가 필요하거나 여러 작업을 동시에 수행해야 할 때 필수적입니다.
| 시나리오 | 주기 | 용도 |
|---|---|---|
| 시스템 틱 | 1ms | 시간 측정, 타임아웃 |
| LED 깜박임 | 100ms ~ 1s | 상태 표시 |
| 센서 읽기 | 10ms ~ 1s | 주기적 샘플링 |
| 디스플레이 갱신 | 16ms (60Hz) | 화면 업데이트 |
| 모터 제어 | 1ms ~ 10ms | PID 제어 루프 |
| 통신 타임아웃 | 가변 | 통신 오류 감지 |
STM32CubeMX에서 타이머 인터럽트를 설정하는 과정은 간단하지만, Prescaler와 Period 값을 정확히 계산하는 것이 중요합니다.
Step 1: 타이머 선택
Timers → TIM6 선택
Mode: Activated (체크)
참고: TIM6, TIM7은 Basic Timer로 인터럽트 전용
Step 2: 파라미터 설정 (1ms 주기 예제)
Configuration → TIM6
Parameter Settings:
Prescaler (PSC): 83
Counter Mode: Up
Counter Period (ARR): 999
Auto-reload preload: Enable
계산:
Timer Clock = 84MHz (TIM6는 APB1)
Counter Clock = 84MHz / (83 + 1) = 1MHz
Update Freq = 1MHz / (999 + 1) = 1kHz
Period = 1 / 1kHz = 1ms ✓
Step 3: NVIC 설정
NVIC Settings:
TIM6 global interrupt: Enabled
Preemption Priority: 0 (높음)
Sub Priority: 0
Step 4: 코드 생성
Project Manager → Generate Code
원하는 주기에 따라 Prescaler와 ARR을 조절합니다. 일반적으로 Prescaler로 기본 속도를 맞추고, ARR로 정밀한 주기를 설정합니다.
1ms 주기 (1kHz)
Timer Clock: 84MHz
PSC: 83 → Counter Clock: 1MHz
ARR: 999 → Update: 1kHz (1ms)
10ms 주기 (100Hz)
방법 1: PSC = 83, ARR = 9999
→ 84MHz / 84 / 10000 = 100Hz
방법 2: PSC = 839, ARR = 999
→ 84MHz / 840 / 1000 = 100Hz (권장)
100ms 주기 (10Hz)
PSC = 8399, ARR = 999
→ 84MHz / 8400 / 1000 = 10Hz
1초 주기 (1Hz)
PSC = 8399, ARR = 9999
→ 84MHz / 8400 / 10000 = 1Hz
계산 공식
Update Frequency = Timer_Clock / ((PSC + 1) × (ARR + 1))
Period (초) = 1 / Update_Frequency
역산:
(PSC + 1) × (ARR + 1) = Timer_Clock / Update_Frequency
CubeMX가 생성한 코드를 이해하면 수동으로 타이머를 설정할 때도 도움이 됩니다.
/* TIM6 init function */
void MX_TIM6_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim6.Instance = TIM6;
htim6.Init.Prescaler = 83;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 999;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
인터럽트 핸들러 (stm32f4xx_it.c)
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
타이머를 시작하고 콜백 함수에서 주기적인 작업을 처리합니다.
/* USER CODE BEGIN 2 */
// 타이머 인터럽트 시작
if (HAL_TIM_Base_Start_IT(&htim6) != HAL_OK)
{
Error_Handler();
}
printf("Timer started: 1ms period\r\n");
/* USER CODE END 2 */
콜백 함수는 인터럽트 컨텍스트에서 실행되므로 가능한 짧고 빠르게 작성해야 합니다.
기본 예제: LED 토글
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 1ms마다 호출됨
static uint16_t counter = 0;
counter++;
// 500ms마다 LED 토글 (500 × 1ms)
if (counter >= 500)
{
counter = 0;
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
}
}
/* USER CODE END 0 */
1초 카운터
/* USER CODE BEGIN 0 */
volatile uint32_t seconds_counter = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 1ms마다 호출
static uint16_t ms_counter = 0;
ms_counter++;
if (ms_counter >= 1000)
{
ms_counter = 0;
seconds_counter++;
printf("Uptime: %lu seconds\r\n", seconds_counter);
}
}
}
/* USER CODE END 0 */
// 타이머 정지
HAL_TIM_Base_Stop_IT(&htim6);
// 타이머 재시작
HAL_TIM_Base_Start_IT(&htim6);
// 카운터 리셋
__HAL_TIM_SET_COUNTER(&htim6, 0);
타이머 인터럽트를 활용한 실제 응용 사례들입니다.
1ms 타이머로 시스템 시간을 관리하고, 타임아웃 기능을 구현합니다.
/* USER CODE BEGIN 0 */
volatile uint32_t system_tick_ms = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
system_tick_ms++;
}
}
// 타임아웃 함수
uint8_t wait_with_timeout(uint32_t timeout_ms)
{
uint32_t start_tick = system_tick_ms;
while ((system_tick_ms - start_tick) < timeout_ms)
{
// 조건 확인
if (condition_met())
{
return 1; // 성공
}
}
return 0; // 타임아웃
}
// 경과 시간 측정
uint32_t measure_execution_time(void)
{
uint32_t start = system_tick_ms;
// 작업 수행
some_function();
uint32_t elapsed = system_tick_ms - start;
printf("Execution time: %lu ms\r\n", elapsed);
return elapsed;
}
/* USER CODE END 0 */
일정 주기로 센서 값을 읽어 처리합니다.
/* USER CODE BEGIN 0 */
#define SAMPLE_INTERVAL_MS 100 // 100ms마다 샘플링
uint16_t sensor_buffer[10];
uint8_t buffer_index = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
static uint16_t ms_counter = 0;
ms_counter++;
// 100ms마다 센서 읽기
if (ms_counter >= SAMPLE_INTERVAL_MS)
{
ms_counter = 0;
// ADC 읽기 (예시)
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
uint16_t adc_value = HAL_ADC_GetValue(&hadc1);
// 버퍼에 저장
sensor_buffer[buffer_index] = adc_value;
buffer_index = (buffer_index + 1) % 10;
// 평균 계산
uint32_t sum = 0;
for (int i = 0; i < 10; i++)
{
sum += sensor_buffer[i];
}
uint16_t average = sum / 10;
// 결과 처리
process_sensor_data(average);
}
}
}
/* USER CODE END 0 */
1초 타이머로 시, 분, 초를 카운트하는 디지털 시계를 구현합니다.
/* USER CODE BEGIN 0 */
typedef struct {
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
} Clock_t;
Clock_t clock = {0, 0, 0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 1초마다 호출 (PSC=8399, ARR=9999)
clock.seconds++;
if (clock.seconds >= 60)
{
clock.seconds = 0;
clock.minutes++;
if (clock.minutes >= 60)
{
clock.minutes = 0;
clock.hours++;
if (clock.hours >= 24)
{
clock.hours = 0;
}
}
}
}
}
void print_clock(void)
{
printf("%02d:%02d:%02d\r\n",
clock.hours, clock.minutes, clock.seconds);
}
void set_time(uint8_t h, uint8_t m, uint8_t s)
{
clock.hours = h;
clock.minutes = m;
clock.seconds = s;
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
// TIM6 설정: 1초 주기 (PSC=8399, ARR=9999)
set_time(12, 0, 0); // 12:00:00으로 설정
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
while (1)
{
print_clock();
HAL_Delay(1000);
}
/* USER CODE END 3 */
하나의 하드웨어 타이머로 여러 개의 소프트웨어 타이머를 구현합니다.
/* USER CODE BEGIN 0 */
#define MAX_SW_TIMERS 5
typedef struct {
uint32_t interval_ms;
uint32_t last_tick;
void (*callback)(void);
uint8_t enabled;
} SoftwareTimer_t;
SoftwareTimer_t sw_timers[MAX_SW_TIMERS];
volatile uint32_t system_tick = 0;
void sw_timer_init(uint8_t id, uint32_t interval_ms, void (*callback)(void))
{
if (id < MAX_SW_TIMERS)
{
sw_timers[id].interval_ms = interval_ms;
sw_timers[id].last_tick = 0;
sw_timers[id].callback = callback;
sw_timers[id].enabled = 1;
}
}
void sw_timer_enable(uint8_t id)
{
if (id < MAX_SW_TIMERS)
{
sw_timers[id].enabled = 1;
sw_timers[id].last_tick = system_tick;
}
}
void sw_timer_disable(uint8_t id)
{
if (id < MAX_SW_TIMERS)
{
sw_timers[id].enabled = 0;
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
system_tick++;
// 모든 소프트웨어 타이머 확인
for (uint8_t i = 0; i < MAX_SW_TIMERS; i++)
{
if (sw_timers[i].enabled && sw_timers[i].callback != NULL)
{
if ((system_tick - sw_timers[i].last_tick) >= sw_timers[i].interval_ms)
{
sw_timers[i].last_tick = system_tick;
sw_timers[i].callback();
}
}
}
}
}
// 태스크 함수들
void task_led1(void)
{
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
void task_led2(void)
{
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
}
void task_sensor_read(void)
{
printf("Reading sensor...\r\n");
// 센서 읽기 코드
}
void task_display_update(void)
{
printf("Updating display...\r\n");
// 디스플레이 업데이트
}
void task_heartbeat(void)
{
static uint32_t count = 0;
count++;
printf("Heartbeat: %lu\r\n", count);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
// 소프트웨어 타이머 초기화
sw_timer_init(0, 500, task_led1); // 500ms마다 LED1 토글
sw_timer_init(1, 1000, task_led2); // 1초마다 LED2 토글
sw_timer_init(2, 100, task_sensor_read); // 100ms마다 센서 읽기
sw_timer_init(3, 50, task_display_update); // 50ms마다 디스플레이 업데이트
sw_timer_init(4, 2000, task_heartbeat); // 2초마다 하트비트
// 하드웨어 타이머 시작 (1ms)
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
서로 다른 주기의 작업을 위해 여러 타이머를 동시에 사용할 수 있습니다.
TIM6: 1ms 타이머 (고속 작업)
PSC: 83
ARR: 999
Priority: 0 (높음)
TIM7: 100ms 타이머 (중속 작업)
PSC: 8399
ARR: 999
Priority: 1 (중간)
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 1ms 타이머: 정밀한 시간 측정
system_tick_ms++;
// 빠른 응답이 필요한 작업
check_critical_sensors();
}
else if (htim->Instance == TIM7)
{
// 100ms 타이머: 일반 주기 작업
static uint8_t count_100ms = 0;
count_100ms++;
// 센서 읽기
read_sensors();
// 1초마다 (100ms × 10)
if (count_100ms >= 10)
{
count_100ms = 0;
update_display();
send_data_to_pc();
}
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
// 두 타이머 모두 시작
HAL_TIM_Base_Start_IT(&htim6); // 1ms
HAL_TIM_Base_Start_IT(&htim7); // 100ms
/* USER CODE END 2 */
인터럽트 우선순위를 적절히 설정하여 중요한 작업이 먼저 처리되도록 합니다.
/* USER CODE BEGIN 2 */
// TIM6: 최고 우선순위 (긴급)
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
// TIM7: 중간 우선순위 (일반)
HAL_NVIC_SetPriority(TIM7_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM7_IRQn);
// SysTick: 낮은 우선순위 (HAL_Delay 등)
HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
/* USER CODE END 2 */
우선순위 시나리오
TIM7 ISR 실행 중...
↓
TIM6 인터럽트 발생! (우선순위 높음)
↓
TIM7 ISR 일시 정지
↓
TIM6 ISR 실행
↓
TIM6 ISR 완료
↓
TIM7 ISR 재개
↓
TIM7 ISR 완료
인터럽트 핸들러의 실행 시간을 최소화하여 시스템 성능을 향상시킵니다.
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// GPIO를 HIGH로 (측정 시작)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);
// 실제 작업
perform_task();
// GPIO를 LOW로 (측정 종료)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
}
}
/* USER CODE END 0 */
오실로스코프로 PD15를 측정하면 ISR 실행 시간을 정확히 알 수 있습니다.
ISR에서는 플래그만 설정하고, 실제 작업은 메인 루프에서 처리합니다.
/* USER CODE BEGIN 0 */
// 나쁜 예: ISR에서 직접 처리
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 느린 작업 (위험!)
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
uint16_t value = HAL_ADC_GetValue(&hadc1);
// 복잡한 계산
float result = complex_calculation(value);
// UART 출력 (매우 느림!)
printf("Value: %.2f\r\n", result);
}
}
// 좋은 예: 플래그만 설정
volatile uint8_t sensor_read_flag = 0;
volatile uint16_t sensor_value = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 빠른 읽기
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 1) == HAL_OK)
{
sensor_value = HAL_ADC_GetValue(&hadc1);
sensor_read_flag = 1; // 플래그만 설정
}
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 3 */
while (1)
{
if (sensor_read_flag)
{
sensor_read_flag = 0;
// 메인 루프에서 처리 (ISR 차단 안 됨)
float result = complex_calculation(sensor_value);
printf("Sensor: %.2f\r\n", result);
}
// 다른 작업도 수행 가능
other_tasks();
}
/* USER CODE END 3 */
우선순위가 낮은 ISR에서 HAL_Delay()를 사용하면 SysTick이 차단되어 시스템이 멈출 수 있습니다.
// 위험한 예
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM7) // Priority: 5
{
HAL_Delay(100); // 위험! SysTick(Priority: 15)이 차단됨
}
}
// 안전한 예
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM7)
{
// 카운터 사용
static uint8_t delay_counter = 0;
delay_counter++;
if (delay_counter >= 10) // 100ms × 10 = 1초
{
delay_counter = 0;
// 작업 수행
}
}
}
타이머 인터럽트로 간단한 협력형 스케줄러를 구현합니다.
/* USER CODE BEGIN 0 */
#define MAX_TASKS 8
typedef enum {
TASK_STATE_READY,
TASK_STATE_RUNNING,
TASK_STATE_BLOCKED,
TASK_STATE_SUSPENDED
} TaskState_t;
typedef struct {
void (*function)(void);
uint32_t period_ms;
uint32_t last_run;
TaskState_t state;
const char* name;
} Task_t;
Task_t task_list[MAX_TASKS];
uint8_t task_count = 0;
volatile uint32_t scheduler_tick = 0;
/* USER CODE END 0 */
/* USER CODE BEGIN 0 */
void scheduler_add_task(void (*function)(void),
uint32_t period_ms,
const char* name)
{
if (task_count < MAX_TASKS)
{
task_list[task_count].function = function;
task_list[task_count].period_ms = period_ms;
task_list[task_count].last_run = 0;
task_list[task_count].state = TASK_STATE_READY;
task_list[task_count].name = name;
task_count++;
printf("Task added: %s (Period: %lu ms)\r\n", name, period_ms);
}
}
void scheduler_suspend_task(uint8_t task_id)
{
if (task_id < task_count)
{
task_list[task_id].state = TASK_STATE_SUSPENDED;
}
}
void scheduler_resume_task(uint8_t task_id)
{
if (task_id < task_count)
{
task_list[task_id].state = TASK_STATE_READY;
task_list[task_id].last_run = scheduler_tick;
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
scheduler_tick++;
// 모든 태스크 확인
for (uint8_t i = 0; i < task_count; i++)
{
if (task_list[i].state == TASK_STATE_READY)
{
if ((scheduler_tick - task_list[i].last_run) >= task_list[i].period_ms)
{
task_list[i].last_run = scheduler_tick;
// 태스크 실행
if (task_list[i].function != NULL)
{
task_list[i].state = TASK_STATE_RUNNING;
task_list[i].function();
task_list[i].state = TASK_STATE_READY;
}
}
}
}
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 0 */
// 태스크 함수들
void task_blink_led(void)
{
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
void task_read_sensor(void)
{
static uint16_t sample_count = 0;
sample_count++;
// 센서 읽기
printf("Sensor reading #%d\r\n", sample_count);
}
void task_update_display(void)
{
// 디스플레이 업데이트
printf("Display updated at %lu ms\r\n", scheduler_tick);
}
void task_send_data(void)
{
// 데이터 전송
printf("Data sent at %lu ms\r\n", scheduler_tick);
}
void task_watchdog(void)
{
// Watchdog kick
static uint32_t wdt_count = 0;
wdt_count++;
printf("WDT kicked: %lu\r\n", wdt_count);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
// 태스크 등록
scheduler_add_task(task_blink_led, 500, "LED Blink");
scheduler_add_task(task_read_sensor, 100, "Sensor Read");
scheduler_add_task(task_update_display, 200, "Display Update");
scheduler_add_task(task_send_data, 1000, "Data Send");
scheduler_add_task(task_watchdog, 5000, "Watchdog");
// 스케줄러 시작 (1ms 타이머)
HAL_TIM_Base_Start_IT(&htim6);
printf("\r\nScheduler started with %d tasks\r\n\n", task_count);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
while (1)
{
// 메인 루프는 idle 상태
// 또는 우선순위 낮은 작업 수행
__WFI(); // Wait For Interrupt (저전력)
}
/* USER CODE END 3 */
1ms 타이머를 사용하여 정밀한 스톱워치를 구현하세요.
요구사항
힌트
typedef struct {
uint16_t minutes;
uint8_t seconds;
uint16_t milliseconds;
uint8_t running;
} Stopwatch_t;
Stopwatch_t stopwatch = {0};
uint32_t lap_times[10];
uint8_t lap_count = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6 && stopwatch.running)
{
stopwatch.milliseconds++;
if (stopwatch.milliseconds >= 1000)
{
stopwatch.milliseconds = 0;
stopwatch.seconds++;
if (stopwatch.seconds >= 60)
{
stopwatch.seconds = 0;
stopwatch.minutes++;
}
}
}
}
100ms마다 센서 값을 읽어 배열에 저장하고, 1초마다 평균값을 출력하세요.
요구사항
힌트
#define BUFFER_SIZE 10
uint16_t adc_buffer[BUFFER_SIZE];
uint8_t buffer_index = 0;
uint8_t buffer_full = 0;
void calculate_statistics(void)
{
uint32_t sum = 0;
uint16_t max = 0;
uint16_t min = 4095;
uint8_t count = buffer_full ? BUFFER_SIZE : buffer_index;
for (uint8_t i = 0; i < count; i++)
{
sum += adc_buffer[i];
if (adc_buffer[i] > max) max = adc_buffer[i];
if (adc_buffer[i] < min) min = adc_buffer[i];
}
uint16_t avg = sum / count;
printf("Avg: %d, Min: %d, Max: %d\r\n", avg, min, max);
}
여러 타이머를 사용하여 4개의 LED가 각각 다른 패턴으로 동작하도록 구현하세요.
요구사항
타이머 인터럽트가 전혀 발생하지 않는 경우입니다.
증상
콜백 함수가 호출되지 않음
원인 및 해결
// 1. 타이머 시작 확인
HAL_TIM_Base_Start_IT(&htim6); // 호출했는지 확인
// 2. NVIC 활성화 확인 (CubeMX)
// NVIC Settings → TIM6 global interrupt: Enabled
// 3. 인터럽트 핸들러 존재 확인
// stm32f4xx_it.c에 TIM6_DAC_IRQHandler() 있는지 확인
// 4. 클럭 활성화 확인
__HAL_RCC_TIM6_CLK_ENABLE();
설정한 주기와 실제 주기가 다른 경우입니다.
증상
1ms로 설정했는데 실제로는 1.2ms마다 발생
원인 및 해결
// 1. 타이머 클럭 확인
// TIM6는 APB1 Timer Clock 사용
// CubeMX에서 Clock Configuration 확인
// APB1 Timer clocks = 84MHz인지 확인
// 2. Prescaler 및 ARR 재계산
uint32_t timer_clock = 84000000; // 84MHz
uint32_t target_freq = 1000; // 1kHz (1ms)
uint32_t total_division = timer_clock / target_freq; // 84000
// PSC = 83, ARR = 999
// 확인: (83+1) × (999+1) = 84000 ✓
// 3. Auto-reload preload 활성화
// CubeMX: Auto-reload preload: Enable
인터럽트로 인해 시스템 전체가 느려지는 경우입니다.
증상
메인 루프가 거의 실행되지 않음
UART 출력이 느림
원인
ISR 실행 시간이 너무 김
ISR 주기가 너무 짧음
해결
// 1. ISR 실행 시간 줄이기
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 나쁜 예: 느린 작업
// printf("Timer\r\n"); // 제거!
// HAL_Delay(10); // 절대 안 됨!
// 좋은 예: 빠른 작업
flag = 1;
counter++;
}
}
// 2. 주기 늘리기
// 1ms → 10ms로 변경
// PSC: 839, ARR: 999
// 3. 우선순위 낮추기
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 10, 0); // 우선순위 낮춤
여러 타이머를 사용할 때 예상치 못한 동작이 발생합니다.
증상
한 타이머의 콜백만 호출됨
원인 및 해결
// 콜백에서 타이머 구분 확인
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// 잘못된 예
if (htim == &htim6) // 포인터 비교 (위험)
{
// ...
}
// 올바른 예
if (htim->Instance == TIM6) // 인스턴스 비교
{
// TIM6 처리
}
else if (htim->Instance == TIM7)
{
// TIM7 처리
}
}
// 두 타이머 모두 시작했는지 확인
HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);
1. 타이머 주기 계산
Update Frequency = Timer_Clock / ((PSC + 1) × (ARR + 1))
Period = 1 / Update_Frequency
예: 1ms 주기
84MHz / (84 × 1000) = 1kHz = 1ms
2. 콜백 함수 작성 원칙
✓ 짧고 빠르게
✓ volatile 변수 사용
✓ 플래그 설정 후 메인 루프에서 처리
✗ HAL_Delay() 사용 금지
✗ printf() 최소화
✗ 복잡한 계산 금지
3. 타이머 선택 가이드
| 타이머 | 용도 | 특징 |
|--------|------|------|
| TIM6, TIM7 | 인터럽트 전용 | Basic Timer, 간단 |
| TIM2, TIM5 | 긴 시간 측정 | 32비트 카운터 |
| TIM3, TIM4 | 범용 | 16비트, PWM 가능 |
| 주기 | PSC | ARR | 주파수 | 용도 |
|---|---|---|---|---|
| 1ms | 83 | 999 | 1kHz | 시스템 틱 |
| 10ms | 839 | 999 | 100Hz | 센서 읽기 |
| 100ms | 8399 | 999 | 10Hz | 디스플레이 |
| 1s | 8399 | 9999 | 1Hz | 시계 |