Timer 6의 구조와 활용

  1. polling 하며 일정 주기마다 uart 메시지 출력,
  2. 익셉션핸들러(인터럽트) 이용으로 uart 메시지 출력을 해본다.

다음글 - GP(General Purpose)Timer인 Timer 9의 구조와 활용

 

STM32F4XX reference manual에 TIM6에 대해 나와있다.

  • 16비트 업카운터로, 0부터 설정값까지 카운트한다.
  • 동작 흐름 요약
    1. RCC로부터 APB1버스를 통해 TIM6에 클럭이 공급
    2. PSC(분주기) 작동
      • TIM6_PSC의 값만큼 클럭을 나눔
    3. CNT(카운터) 증가
      • PSC에서 분주된 클럭(CK_CNT)에 따라 CNT 값이 0부터 증가
    4. ARR 도달 시 업데이트 이벤트(U event) 발생
      • CNT == ARR 일때 발생
      • 인터럽트, TRGO 트리거 출력, CNT 초기화 가능

STM32F4xx 매뉴얼에 보면 Tim6엔 다양한 설정에 따른 클럭 다이어그램이 있다.
간단한 Counting Mode를 보면

internal clock을 2로 나눈 상황

  1. CNT_EN : Counter 활성화
  2. CK_CNT : CNT_EN 활성화 다음 클럭부터 Clock counter 동작
    internal clock을 2로 나눈 주기로 실행하기 때문에, CK_INT(내부 클럭)가 2번 발생 시 CK_CNT가 1번 동작
  3. Counter overflow : Counter register 값이 설정한 주기(ARR) 값을 넘으면 오버플로우 발생
  4. Update event(UE) : 오버플로우 발생함에 따라 Update Event 발생
  5. Update interrupt flag(UIF) : Update event가 발생함에 따라 Update interrupt flag 활성화(flag가 다시 0으로 되도록 clear 동작이 필요)

기본 동작 순서를 잘 읽어보면, 클럭 다이어그램을 이해할 수 있다.

다음은 prescaler를 사용할때의 다이어그램이다. 보통 시스템 클럭값으로부터 타이머 카운트값이나, Baud rate로 사용할때 원하는 값으로 줄여주기 위해, 스케일링해주는 prescaler이다.

타이머의 입력 클럭이 너무 빠르면, 카운터가 너무 빨리 증가해서 원하는 시간 제어(예: 1초 간격 인터럽트)가 어렵다.
그래서 Prescaler(전치분주기) 를 사용해 타이머 입력 클럭을 나누어 느리게 만들어 준다.

예로는 168000000(168MHz)의 클럭값이 있을때, 오버플로우를 방지하기도하고, 간단한 수로 만들고 사용하고자할때 500000으로 스케일링 해놓고 사용한다.

  • APB1 Timer Clock: 168 MHz
  • Prescaler (PSC): 335
  • ARR (Auto-Reload Register): 999

동작 계산

  1. Prescaler 설정 (PSC = 335)
    → 타이머 입력 클럭 = 168 MHz / (PSC + 1) = 168,000,000 / 336 = 500,000 Hz
  2. ARR 설정 (ARR = 999)
    → 카운터가 0 ~ 999까지 세고 오버플로우 발생 (1000번 count)
  3. 오버플로우 주기
    → 오버플로우 시간 = (ARR + 1) / 타이머 클럭
    → 1000 / 500,000 = 0.002초 (2ms)

=>2ms마다 타이머 인터럽트가 발생.

 

prescaler 값이 1 → 2 가 되는 상황

  1. CNT_EN : count를 활성화하여 다음 클럭부터 clock count 동작
  2. prescaler = 1 이므로 Prescaler buffer는 0, Prescaler counter는 0으로 유지
  3. TIMx_PSC 레지스터를 통해 새로운 prescaler 값을 설정
  4. 다음 update event에서 prescaler buffer= 1 이 되며, Prescaler counter는 2단위(0, 1)로 동작
  5. Prescaler counter에 맞춰 PSC counter가 0일 때 clock count 동작

4.2.2. 관련 레지스터 분석 및 설정 값 이해

  • register boundary address

 

  • RCC_APB1ENR
  •  

 

  • TIMx_CR1
    • Basic Timer의 동작을 설정하는 레지스터

 

  • TIMx_CR2
    Trigger 트리거 관련 레지스터

 

  • TIMx_DIER
    -인터럽트 활성화 레지스터
  • UIE (0번 비트)
    • 0 : 업데이트 인터럽트 비활성화
    • 1 : 업데이트 인터럽트 활성화
  • UDE (8번 비트)
    • 0 : 업데이트 DMA 요청 비활성화
    • 1 : 업데이트 DMA 요청 활성화
  • TIMx_SR
    -status 레지스터(인터럽트 flag 확인)bit 0 : UIF : 업데이트 인터럽트 플래그 (업데이트 이벤트 발생 시 하드웨어에 의해 flag 1로 설정됨 → clear (0으로) 해주어야 한다.

 

  • TIMx_CNT (counter)
    -현재 카운터 값 저장
  •  

 

  • TIMx_PSC (prescaler)

Bits 15:0 PSC : 타이머의 주기 결정

타이머의 입력 클럭을 나누는 역할 (타이머 클럭 분주)

실제 분주 값 = PSC + 1 (0을 포함해서 카운트하기때문)

 

  • STM32에서 TIM6의 기본 클럭은 84MHz (1초당 84M번의 tick)
  • // 예시 TIM6->PSC = 8400-1; // 84,000,000Hz / (8400-1)+1 = 10kHz로 분주

 

  • TIMx_ARR (auto-reload register)
    • reload value 설정 레지스터
  • 0xFFFF 초과하면 오버플로우
  • 카운터는 0부터 ARR까지 카운팅, 다시 0으로 리셋
// 예시

TIM6->ARR = 5000-1;            // 4999 = 500ms

 

 

  • 타이머 설정 방법(500ms 주기마다 출력)
  • The counter clock frequency CK_CNT is equal to fCK_PSC / (PSC[15:0] + 1).(p.709)
 Prescaler = 8399
 ARR       = 4999

    ⇒ 84,000,000 / (8399 + 1) = 10,000 Hz
    ⇒ 타이머가 0~4999까지 센 후 오버플로우 → 5000 / 10000 = 0.5초 (500ms)

코드 구현-1 Polling으로 500mS 마다 Uart로 ‘.’ 하나씩 출력

void uart3_init(void){
//1.clock 활성화
    RCC->AHB1ENR |= 1<<3;//GPIOD
    RCC->APB1ENR |= 1<<18;//USART3

//2.PD8,9핀 Alternative Function으로 설정
    GPIOD->MODER &= ~((0x3<< (2*8))| (0x3<<(2*9)));
    GPIOD->MODER |=((0x2<<(2*8)) | (0x2<<(2*9)));

//3.MUX AFR uart tx,rx핀으로 설정.
    GPIOD->AFR[1]&= ~(0xf<<4 | 0xf<<0);//MUX PD8,9(AFRH 8~15)
    GPIOD->AFR[1] |= (0x7<<4 | 0x7<<0);//uart3(AF7)

//4.uart 레지스터 설정
    //oversampling by 16(bit 15),UE,wordlength8bit+stop1bit(bit12),parityX(bit10),TE,RE
    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();

//TIM6 clock 활성화
 RCC->APB1ENR |= 1<<4;

//APB1 : 84MHz
int msec=500,pclk=84000000; 

//(7:ARR preload enable,2:update request sources, 1: update enable, 0 : counter reset)
TIM6->CR1=(0x1<<7 | 0x1<<2 | 0x0<<1 | 0x0<<0);

//clk=84000000, PSC(8400-1)+1, CK_CNT=10000, 목표값 : 500ms, 주기 2
TIM6->PSC=8400-1;//prescaler 84000000->10kHz=10000

TIM6->ARR=5000-1;//5000=2Hz (int)(pclk/8400)/2)-1 reload
//TIM6->ARR=10*msec-1; // msec으로 표현
TIM6->CNT=0;//0부터count시작

//count start
TIM6->CR1 |= 0x1<<0;

char msg[100];

  while (1)
  {

      if(TIM6->SR & 0x1){//(UIF)Update interrupt flag
          uart3_printf(".");
          TIM6->SR =0x0;
      }
      /*
      sprintf(msg,"%d ",TIM6->CNT);//count출력
      uart3_printf(msg);
*/
  }

}

 

 

코드 구현-2 Exception(Interrupt) 활용하여 500mS 마다 Uart로 ‘#’ 하나씩 출력

startup 벡터 테이블에 등록되어 있는 TIM6_DAC_IRQHandler 이름을 사용해야 함.

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++);
    }
}

char msg[100];

void TIM6_DAC_IRQHandler(void)
{

    if (TIM6->SR & (1 << 0)) {
        uart3_printf("#");
        TIM6->SR = ~(1 << 0); // UIF 비트 리셋
    }

}

int main(void)
{
 HAL_Init();
 uart3_init();
 SystemClock_Config();

  RCC->APB1ENR |= 1<<4; //TIM6 clock

  int msec=300,pclk=84000000; //APB1 : 84MHz
  //(7:ARR preload enable,2:update request sources, 1: update enable, 0 : counter reset)
  TIM6->CR1=(0x1<<7 | 0x1<<2 | 0x0<<1 | 0x0<<0);

  //clk=84000000, PSC(8400-1)+1, CK_CNT=10000, 목표값 : 500ms, 주기 2
  TIM6->PSC=8400-1;//prescaler 84000000->10kHz=10000

  TIM6->ARR=5000-1;//5000=2Hz

  TIM6->CNT=0;//start value

  TIM6->DIER=0x1<<0;//Interrupt enable

  //NVIC에 TIM6_DAC_IRQ 번호 등록하기
  NVIC->ISER[1]|=0x1<<22; //54번 IRQ. ISER[1] 32~63

  //count start
  TIM6->CR1 |= 0x1<<0;

  while (1)
  {

      if(TIM6->SR & 0x1){//(UIF)Update interrupt flag
          uart3_printf(".");
           TIM6->SR &= ~(1 << 0);
      }
  }
  /* USER CODE END 3 */
}

+ Recent posts