저번 TIM6에 이어
GP (General purpsoe)타이머 TIM9의 구조와 활용에 대해 알아본다.
1. 타이머 구조 및 레지스터 설정 값 이해
2. polling으로 500ms마다 Uart로 메시지 출력 및 익셉션 활용으로 Uart메시지 출력 구현
3. PWM활용하여 MCU pin으로 1KHz,2KHz 등 의 주파수 및 duty로 파형 출력 구현
타이머 구조 이해

TIM6과 같은 16bit의 업카운팅 타이머이다.

- 16 bit Auto-Reload Upcounter
- : 지정한 값(ARR)에 도달하면 카운터가 0으로 되돌아가며 반복 업카운팅을 수행
- 16 bit Prescaler
- : 내부 클럭 주파수를 나누어, 카운터 입력 클럭을 조절
TIM9의 기능은 GPIO핀으로 PWM(Pulse-Width Modulation)를 출력가능하다는 것인데,
이번 글에서는 PE5핀을 통해 PWM 파형 출력하여 로직 애널라이저로 확인해 볼 것이다.
두 개의 채널로 두 파형의 입출력, 비교가 가능한데, 하나의 파형만 출력해볼 것이라 Channel1만 사용한다.


CH1 하나만 쓸것이고, PA2와 PE5가 가능한데 Nucleo-STMF429ZI보드는 처음에 PA2는 납땜이 필요해서, PE5핀을 사용할 것이다.
schematic 문서에 가면 PE5의 정확한 핀 위치를 알 수 있다.


항상 로직 애널라이저,주변장치등과 연결 시 GND와 출력 핀 두 곳을 같이 연결 후 PC와 USB전원 연결을 하도록 한다.
Register 기반 코드 구현
- TIM9 레지스터와 연결된 버스 확인 (클럭 활성화)

APB2와 연결된 클럭을 ON해줄 것이다.
- RCC APB2 periphral clock enable register

연결된 버스에 해당되는 RCC 레지스터 비트를 1로 설정해준다.
RCC->APB2ENR = 0x1<<16; (여러 비트 활성화 시 or연산)
- TIM9/12 control register


- Bit 3: OPM (One-pulse mode)
- 0 : 업데이트 이벤트 발생 후에도 카운터 계속 동작
- 1 : 다음 업데이트 이벤트에서 카운터 정지 (CEN 비트 자동 클리어)
- Bit 2: URS (Update request source)
- 0 : 카운터 오버플로우/언더플로우 또는 UG 비트 설정 시 인터럽트 발생
- 1 : 오직 카운터 오버플로우/언더플로우에서만 인터럽트 발생
- UEV(Update Event) 발생 시 인터럽트 요청 소스를 선택
- Bit 1: UDIS (Update disable)
- 0 : UEV 활성화 (오버플로우/UG 비트 설정 시 섀도 레지스터 로드 및 인터럽트 발생)
- 1 : UEV 비활성화 (이벤트 발생 무시, 섀도 레지스터 유지; 단 UG 설정 시 카운터·프리스케일러 재초기화)
- UEV 이벤트 생성을 활성/비활성화
- Bit 0: CEN (Counter enable)
- 0 : 카운터 비활성화
- 1 : 카운터 활성화
원펄스 모드에서는 업데이트 이벤트 발생 시 CEN이 자동으로 클리어되어 카운터가 멈춤
4.TIM9/12 Interrupt enable register


- Bit 0: UIE (Update interrupt enable)
- 0 : 업데이트 인터럽트 비활성화
- 1 : 업데이트 인터럽트 활성화
- TIM9/12 status register
)
- Bit 0: UIF (Update Interrupt Flag)
- 업데이트 이벤트(오버플로우, UG 비트 설정, 슬레이브 리셋 등으로 레지스터 갱신) 발생하면 하드웨어에 의해 set되고 소프트웨어에 의해 clear됨
- 0 : 업데이트 발생하지 않음
- 1 : 업데이트 인터럽트 대기 중
- 레지스터가 업데이트 될 때 하드웨어에 의해 set :
- 오버플로우이고 TIMx_CR1 레지스터의 UDIS = ‘0’일 때
- TIMx_EGR 레지스터의 UG 비트를 사용하여 소프트웨어에 의해 CNT가 재초기화될 때, TIMx_CR1 레지스터의 URS, UDIS 비트가 모두 0일 때
5. TIM9/12 event generation register

- Bit 0 UG: Update generation
- 소프트웨어가 ‘1’로 설정하면 카운터와 프리스케일러 카운터를 재초기화(0으로 클리어)하고, 레지스터 업데이트 이벤트(UEV)를 생성하며, 하드웨어가 자동으로 ‘0’으로 클리어합니다.
- 0 : 동작 없음
- 1 : 카운터 및 프리스케일러를 0으로 리셋, 자동리로드 레지스터(ARR 등)를 업데이트 이벤트로 갱신
- 소프트웨어가 ‘1’로 설정하면 카운터와 프리스케일러 카운터를 재초기화(0으로 클리어)하고, 레지스터 업데이트 이벤트(UEV)를 생성하며, 하드웨어가 자동으로 ‘0’으로 클리어합니다.
5.TIM9/12 capture/compare mode register 1


PWM 모드 1로 설정할 것이다.
- TIM9/12 capture/compare enable register


카운트
- TIM9/12 prescaler

TIM6글에 프리스케일에 대한 간단한 이용 설명이 있습니다.
- TIM9/12 auto-reload register

Polling으로 500mS 마다 Uart로 ‘.’ 하나씩 출력 및 인터럽트로 ‘#’ 출력
void uart3_init(void){
RCC->AHB1ENR |= 1<<3;//GPIOD
RCC->APB1ENR |= 1<<18;//USART3
//PD8,9 AF모드
GPIOD->MODER &= ~((0x3<< (2*8))| (0x3<<(2*9)));
GPIOD->MODER |=((0x2<<(2*8)) | (0x2<<(2*9)));
GPIOD->AFR[1]&= ~(0xf<<4 | 0xf<<0);//MUX PD8,9(AFRH 8~15)
GPIOD->AFR[1] |= (0x7<<4 | 0x7<<0);//uart3
USART3->CR1 =(0<<15 |1<<13 | 0<<12 |0<<10 | 1<<3 | 1<<2);
USART3->BRR=(22 << 4) | 13;//115200bps,42MHZ =356
}
void uart3_send(char c){
while(!(USART3->SR>>7 &0x1));
USART3->DR=c;
}
void uart3_printf(const char* str){
while(*str){
uart3_send(*str++);
}
}
int main(void)
{
HAL_Init();
uart3_init();
SystemClock_Config();
//TIM9 클럭 활성화(APB2에 연결)
RCC->APB2ENR |= 1<<16;//TIM9 clock
//기본 타이머 설정 APB2->클럭 168000000Hz
TIM9->PSC=16800-1; //168000000/16800 = 10000
TIM9->ARR=10000-1;// 10000/(ARR+1)=1HZ 1sec으로 맞추려면 ARR=10000-1
//(7:ARR preload enable,2:update request sources, 1: update enable, 0 : counter reset)
TIM9->CR1=(0x1<<7 | 0x1<<2 | 0x0<<1 | 0x0<<0);
//필요에 따라 선택 가능 여기서 CR1은 설정해주지 않아도됨.
//count start
TIM9->CR1 |= 0x1<<0;
while (1)
{
if(TIM9->SR & 0x1){//(UIF)Update interrupt flag
TIM9->SR = ~(0x1<<0);
uart3_printf(".");
}
}
}
CR1레지스터에서 preload를 설정해보았는데 preload하면 새 ARR값이 이전 카운트가 끝나는 즉시 갱신되면 error가 생길 가능성이 있어서, 미리 load해 놓는 그림이다.그러고, 다음 카운트를 시작할때 새로 담긴 값으로 시작한다.


타이머9로 들어오는 클럭은 168MHz이다.
Prescale : 일단 16비트에 맞는 값을 구하기 전에 간단한 값 10000으로 스케일링 해준다.
168000000/x=10000 , x=16800 = Prescale값.(0부터 세기때문에 실제는 +1더센다. 그래서 16800-1으로 1을 빼준값으로 넣어준다)
ARR : 1초를 맞추려면 1Hz, 1ms면 1KHz, 0.5ms=> 2KHz.
스케일 된 값 10000/(ARR+1)=1(Hz)
ARR 은 10000-1 설정해준다.->1초가 카운트 로드값이 된다.
타이머 익셉션핸들러(인터럽트)활용 UART 메시지 출력
TIM6과 마찬가지로 startup 벡터 테이블에 등록된 핸들러가 있다.

핸들러 함수는 stm32f4xx_it.c였던 것 같은데, 이 파일에서 함수가 생성된다. 이걸 잘라내서 main.c에 붙여 사용해도 된다. 함수가 한 파일에서 한번만 생성되어있으면 된다.
그리고 핸들러를 사용하면, 우선순위 등록과, CPU와 연결된 IRQ(interrupt request)핀을 활성화해줘야한다.
NVIC_SetPriority(TIM1_BRK_TIM9_IRQn, 1); // 우선순위 설정
NVIC_EnableIRQ(TIM1_BRK_TIM9_IRQn); // NVIC에 TIM9 IRQ 활성화
내장 함수 이용해도 되고, NVIC 레지스터 이용해도 된다.
// main.c
void TIM1_BRK_TIM9_IRQHandler(void)
{
if (TIM9->SR & 0x01) // Update interrupt flag 확인
{
TIM9->SR &= ~(0x01); // 플래그 클리어
uart3_write_char('9');
}
}
// 인터럽트 TIM9 init
void TIM9_Timer_Init(void)
{
// 1. TIM9 클럭 활성화 (APB2에 연결)
RCC->APB2ENR |= (1 << 16);
// 2. 타이머 설정
TIM9->PSC = 16799; // 168MHz / 16800 = 10kHz (의미 : 1초당 10k번의 tick)
TIM9->ARR = 9999; // 10kHz(=1s)에서 (10k번의 tick이 발생하면 초기화)-> 1s
TIM9->CNT = 0; // 카운터 초기화
// 3. 인터럽트 설정
TIM9->DIER |= (1 << 0); // Update interrupt enable
NVIC_SetPriority(TIM1_BRK_TIM9_IRQn, 1); // 우선순위 설정
NVIC_EnableIRQ(TIM1_BRK_TIM9_IRQn); // NVIC에 TIM9 IRQ 활성화
// 4. 타이머 시작
TIM9->CR1 |= (1 << 0);
}
int main(void)
{
uart3_init();
SystemClock_Config();
TIM9_Timer_Init();
while (1);
/* USER CODE END 3 */
}

PWM을 활용하여 MCU pin으로 1KHz, 2KHz 등 정해진 주파수 및 duty 파형 출력
2KHz, duty : 50%
CCR1레지스터로 duty비를 설정할 수 있는데,
High(ON) 상태가 전체 주기에서 차지하는 비율이다.
duty=한 주기에서 값이 1인 구간 길이/한 주기 전체 길이
주기 값이 10일때 CCR이 3이면,
1,2,3,4..10 중 3이상부터 1을 출력한다.
그럼 duty비는 (10-3)/10 = 약 70%이다.
CUBEIDE에서 설정할땐 duty비 가 ccr1레지스터 설정값으로 들어간다.
TIM9->CCR1 = (TIM9->ARR)/2; // duty : 50%
TIM9->CCR1 = (TIM9->ARR)/5; // duty : 20%
duty값이 크면 출력은 밝고(LED), 빠르다(모터회전의경우).
int main(void)
{
HAL_Init();
RCC->AHB1ENR |= 1<<4;//GPIOE PE5
GPIOE->MODER &= ~(0x3<<(5*2));
GPIOE->MODER |= (0x2<<(5*2));//AF
GPIOE->AFR[0] &= ~(0xf<<20);//MUX PE5
GPIOE->AFR[0] |= (0x3<<20);//AF3
SystemClock_Config();
RCC->APB2ENR |= 1<<16;//TIM9 clock
// bit 0 : counter reset
TIM9->CR1= 0x0<<0;
//168MHz
TIM9->PSC=8400-1; // 20KHz
TIM9->ARR=10-1; // 2KHz
TIM9->CCMR1 |= 0x6<<4;//pwm 1 mode
//TIM9->CCMR1 |= 0x1<<3; //preload
TIM9->CCER |= 0x1; //enable
// 2KHz
TIM9->CCR1 = (TIM9->ARR)/2; // duty : 50%
//TIM9->CCR1 = (TIM9->ARR)/5; // duty : 20%
TIM9->CR1 |= 0x1<<0;
while (1);
}

1KHz, duty : 20%

로직 애널라이저를 확인할때 채널 번호 확인과,baud rate 체크 필요
'Cortex-M' 카테고리의 다른 글
| 펌웨어 기본 동작 구현2- Timer활용하여 1초 주기로 LED토글 (0) | 2025.05.25 |
|---|---|
| 펌웨어 기본 동작 구현1- Key 입력 시 눌린 순서 번호+문장 UART출력 | STM32CubeIDE ,STM32F429ZI (0) | 2025.05.25 |
| TIM6 타이머 -polling, 익셉션 핸들러(인터럽트) 이용 | STMF429ZI (0) | 2025.05.25 |
| IWDG Independent watch dog | 레지스터 제어로 코드 구현(STM32F429ZI) (0) | 2025.05.23 |
| button Key 인터럽트로 LED 토글하기(레지스터제어) | STM32cubeIDE(32F429ZI) (0) | 2025.05.23 |