저번 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 기반 코드 구현

  1. TIM9 레지스터와 연결된 버스 확인 (클럭 활성화)

APB2와 연결된 클럭을 ON해줄 것이다.

  1. RCC APB2 periphral clock enable register
연결된 버스에 해당되는 RCC 레지스터 비트를 1로 설정해준다.  
RCC->APB2ENR = 0x1<<16; (여러 비트 활성화 시 or연산)
  1. 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 : 업데이트 인터럽트 활성화
  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 등)를 업데이트 이벤트로 갱신

 

5.TIM9/12 capture/compare mode register 1

 

PWM 모드 1로 설정할 것이다.

  1. TIM9/12 capture/compare enable register

 

카운트

  1. TIM9/12 prescaler

TIM6글에 프리스케일에 대한 간단한 이용 설명이 있습니다.

  1. 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 체크 필요

+ Recent posts