이전 4번 게시글 코드를 이용해, LED 만 부저로 바꿔 연결하여 구현합니다.

Key가 눌리면, 도->레->미->...->도로 변경과 동시에 'do','re'와 같이 어떤 음으로 바뀌는지 uart 시리얼 포트로 출력합니다.

음계 말고 이하 동일입니다.

음계 설정 하기

HCLK : 168000000Hz

1.Timer 9의 PSC,ARR,CCR1 설정
ARR값을 계속 변경하며 사용하니,
PSC로 식을 만들기 간단하도록 500000으로 스케일링 할 것입니다.

-예를 들어 계이름 '도'(523Hz)를 만든다면.

1) 168000000/(PSC+1)=500000, PSC=>336-1
2) 500000/(ARR+1)=523, ARR=>956-1

=>식으로 여러 음 번갈아 넣으려면
ARR=500000/((해당음의 Hz)-1);
으로 코드 작성합니다.

  1. CCR은 항상 50%로 합니다. ARR/2

계이름을 참고해 int buzzerHZ[8]={130,146,164,174,195,220,246,261};
도레미파솔라시도 리스트를 만들어 사용합니다.

Key가 눌리면, 도->레->미->...->도로 변경과 동시에 'do','re'와 같이 어떤 음으로 바뀌는지 uart 시리얼 포트로 출력합니다.

void USART3_Init(void)
{
     RCC->AHB1ENR |= 1<<3;// GPIOD 클럭 활성화
     RCC->APB1ENR |= 1<<18;//USART3

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

     //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)

    //uart 레지스터 설정
     USART3->CR1 = (1<<13 | 1<<3 | 1<<2);//UE,TE,RE
     USART3->BRR = (22 << 4) | 13; //115200bps,42MHZ =356

}

void UART3_Send(char c){
    while(!(USART3->SR>>7 & 0x1));
    USART3->DR=c;
}

void UART3_Print(const char* str){
    while(*str){
        UART3_Send(*str++);
    }
}

void EXTI_KEY_Init(void){
    RCC->AHB1ENR |= (0x1 << 2); // GPIOC
    GPIOC->MODER &= ~(0x3 << (2 * 13)); // PC13: 입력 모드

    RCC->APB2ENR |= (1<<14);//SYSCFG

    SYSCFG->EXTICR[3]&= ~(0xF <<4);
    SYSCFG->EXTICR[3]|=(0x2<<4);//SYSCFG_EXTICR4(Port C)

    EXTI->IMR |=(1<<13); //line13인터럽트 마스크
    EXTI->RTSR |=(1<<13);//Rising edge

    EXTI->PR = (1<<13);//pending (인터럽트 플래그 리셋.)
    NVIC->ISER[1]|=0x1<<8;


}
int cnt=0;
int id_cnt=0;
//int ledtime[4]={20000, 10000, 5000, 2500};//1 0.5 0.25 0.125

int buzzerHZ[8]={130,146,164,174,195,220,246,261};
char msg[100];
char* HZname[8]={"do","re","mi","pa","sol","ra","si","do"};
//523:도,130:도

void EXTI15_10_IRQHandler(void)
{
    if(EXTI->PR & (0x1<<13)){
          EXTI->PR = (0x1<<13);//pending bit 클리어
          ++cnt;
          if(cnt%5==0){

         //      TIM9->ARR=ledtime[(++id_cnt)%4]-1;
              TIM9->ARR=50000/(buzzerHZ[(++id_cnt)%8])-1;
               TIM9->CCR1=(TIM9->ARR+1)/2;
               sprintf(msg,"%s\r\n",HZname[id_cnt%8]);
               UART3_Print(msg);



           }}

}



void TIM9_Init(void){
    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

    RCC->APB2ENR |= 1<<16;//TIM9 clock
    TIM9->CR1= 0x0<<0;

    TIM9->PSC=336-1;//50000
    TIM9->ARR=956-1;//523
    TIM9->CCR1 = (TIM9->ARR)/2; // duty : 50%

    TIM9->CCMR1 |= 0x6<<4;//pwm 1 mode
    TIM9->CCER|=0x1;//output channel1

    // 타이머 시작
    TIM9->CR1 |= (1 << 0);
}

int main(void)
{

  HAL_Init();

  EXTI_KEY_Init();
  USART3_Init();
  TIM9_Init();
  SystemClock_Config();


  while (1);

}

이번엔 TIM9와 KEY 인터럽트를 이용해 GPIO 응용을 연습한 코드입니다.

PWM 출력으로 LED(커넥터에 점프선으로 직접 배선) 1초 주기로 on/off

  • 키 누른 회수가 5의 배수일 때마다 절반씩 주기 줄이며 돌아가며 변경.
    1초->0.5초->0.25->0.125초->1초..

*그리고 5의 배수나, 일정 카운트 이상일때 key 입력을 처리하는 것은 채터링 방지 효과가 있습니다.

해야할 것

아래 내용 모두 이전 포스팅에 이용 방법, 구조 설명 등 있습니다

  1. PE5와 GND -> LED를 꽂기.(긴 쪽 다리 +, 짧은 다리는 GND)

  2. key 익셉션 핸들러로 인터럽트 구현
  3. TIM9도 핸들러 구현
  4. (토글 주기)ARR값 변경할 값을 저장한 list만들기
  5. main안에 while문 동작 구현
void EXTI_KEY_Init(void){
    RCC->AHB1ENR |= (0x1 << 2); // GPIOC
    GPIOC->MODER &= ~(0x3 << (2 * 13)); // PC13: 입력 모드

    RCC->APB2ENR |= (1<<14);//SYSCFG

    SYSCFG->EXTICR[3]&= ~(0xF <<4);
    SYSCFG->EXTICR[3]|=(0x2<<4);//SYSCFG_EXTICR4(Port C)

    EXTI->IMR |=(1<<13); //line13인터럽트 마스크
    EXTI->RTSR |=(1<<13);//Rising edge

    EXTI->PR = (1<<13);//pending (인터럽트 플래그 리셋.)
    NVIC->ISER[1]|=0x1<<8;


}
int cnt=0;
int id_cnt=0;
int ledtime[4]={20000, 10000, 5000, 2500};//1 0.5 0.25 0.125


void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */
    if(EXTI->PR & (0x1<<13)){
          EXTI->PR = (0x1<<13);//pending bit 클리어
          ++cnt;
          if(cnt%5==0){
               TIM9->ARR=ledtime[(++id_cnt)%4]-1;
               TIM9->CCR1=(TIM9->ARR+1)/2;
           }}
  /* USER CODE END EXTI15_10_IRQn 0 */
 // HAL_GPIO_EXTI_IRQHandler(USER_Btn_Pin);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  /* USER CODE END EXTI15_10_IRQn 1 */
}


char msg[100];
volatile int id = 0;
volatile int sended = 0;



void TIM9_Init(void){
    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

    RCC->APB2ENR |= 1<<16;//TIM9 clock
    TIM9->CR1= 0x0<<0;
      //168MHz
    TIM9->PSC=8400-1;    // 20KHz
    TIM9->ARR=10000-1;    //  1KHz
    TIM9->CCR1 = (TIM9->ARR)/2; // duty : 50%

    TIM9->CCMR1 |= 0x6<<4;//pwm 1 mode
    TIM9->CCER|=0x1;//output channel1

    // 타이머 시작
    TIM9->CR1 |= (1 << 0);
}


/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  EXTI_KEY_Init();

   TIM9_Init();
    //TIM6_Init();
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
 /* MX_GPIO_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_TIM9_Init();*/
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  while (1)
  {



    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

이전 펌웨어 기본 동작 구현 2 에서

Timer 6을 이용해 LED 토글했었던 것을 이어서,

@+'1'~'4'를 입력하면 LED 주기를 (2초 1초 0.5초 0.25초..등) 변경해보는 것을 구현해봅니다.

시리얼 포트로 해당 문장도 같이 출력합니다.

해야할 것

  1. 주변 장치 타이머,gpio Init
    TIM6 Init
    LED Init
    UART Init
    주변 peripheral->stmf4xxxx_reference manual.pdf
  2. 인터럽트로 입력 받을 UART 핸들러 구현
    ->stmf4xxxx_reference manual.pdf
    ->programming manual.pdf
    이전 글 참고 및 필요한 문서들은 공지사항에 첨부해두었습니다.
  3. LED 주기마다 토글시킬 TIM6 핸들러 구현
  4. main에 while문안에 인터럽트 이후 동작 구현

인터럽트로 구현 - UART 시리얼 포트로 @+1~4 입력 시 LED 주기 변경

void LED_Init(void){
    RCC->AHB1ENR |= (0x1<<1);//GPIOB

    GPIOB->MODER &= ~(0x3<<7*2);//LED output mode
    GPIOB->MODER |= (0x01 <<7*2);

    GPIOB->BSRR=(0x01<<(7+16));
}

void TIM6_Init(void){
    //TIM6 clock 활성화
     RCC->APB1ENR |= 1<<4;

     //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);
     //84000000->
     TIM6->PSC=8400-1;//10000 10kHz
    // TIM6->ARR=10-1;//
     TIM6->ARR=5000-1;//10000/5000=2Hz 0.5s

     TIM6->CNT=0;//0부터 시작

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


}

void Change_LEDTime(int t){
    switch(t){
        case 1:
            TIM6->ARR=5000-1;//0.5s
            break;
        case 2:
            TIM6->ARR=10000-1;//1s
            break;
        case 3:
            TIM6->ARR=20000-1;//2s
            break;
        case 4:
            TIM6->ARR=30000-1;//3s
            break;
        default:
            TIM6->ARR=5000-1;
    }

}

void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */
 if(TIM6->SR & (0x1)){
     TIM6->SR = ~(0x1);//rc_w0
     GPIOB->ODR ^=(0x1<<7);

 }}
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 =(1<<13 |1<<5| 1<<3 | 1<<2);//UE,TE,RE
    USART3->BRR=(22 << 4) | 13;//115200bps,42MHZ =356
      NVIC->ISER[1]|=0x1<<7; //39번 IRQ. ISER[1] 32~63

}

void UART3_Send(char c){
    while(!((USART3->SR>>7)&(0x1)));
    USART3->DR=c;
}
char UART3_Receive(void)
{
    while (!(USART3->SR & USART_SR_RXNE));
    return (char)(USART3->DR);
}

void UART3_Print(const char* str){
    while(*str){
        UART3_Send(*str++);
    }
}

char msg[100];
volatile int id = 0;
volatile int sended = 0;

void USART3_IRQHandler(void)
{

    if (USART3->SR & USART_SR_RXNE) {
        char c = (char)USART3->DR;

        if (c == '\r' || c == '\n') {
            msg[id] = '\0';
            sended = 1;
            id = 0;
            } 
        else if (id < 99) {
            msg[id++] = c;
        }
    }

}



int main(void)
{

  HAL_Init();
  UART3_Init();
   LED_Init();
   TIM6_Init();

  SystemClock_Config();

  while (1)
  {
  if(sended==1){
      sended=0;
      UART3_Print(msg);

    for(int i=0;msg[i]!='\0';++i){
             if(msg[i]=='@'){
                  int t=(int)(msg[i+1]-'0');
                  Change_LEDTime(t);
                  break;

               }}
    UART3_Print(msg);

  }

  }

}

TIM6 타이머 -polling, 익셉션 핸들러(인터럽트) 이용 | STMF429ZI :: skylarvv 님의 블로그

이번 글에서는 systick,TIM6,TIM9 타이머 중 TIM6을 이용해 LED를 일정 주기를 주어 ON/OFF하는 코드를 구현해봅니다.

위 링크에 TIM6 기본 구조와 활용에 대해 필기해두었습니다.

TIM6의 카운트 인터럽트 발생으로, LED를 토글시키는 동작입니다.
이번 글에서는 추가로 아래 구현이 필요합니다.

  • GPIO, prepherial 관련 레지스터 설정과
  • 해당 입력장치, timer 등이 어떤 핸들러에 해당하는지 찾으려면 (p.377)

STM32F4xx Reference Manual.zip
16.47MB

  • TIM6 핸들러 찾아 NVIC 테이블에 우선순위 등록, IRQ 핀 활성화하기.

pm0214-stm32-cortexm4-mcus-and-mpus-programming-manual-stmicroelectronics.pdf
2.61MB

 

ARR,Prescale 설정은 TIM6,systick 관련 이전 글들에 간단 설명 적어두었습니다.

void LED_Init(void){
    RCC->AHB1ENR |= (0x1<<1);//GPIOB

    GPIOB->MODER &= ~(0x3<<7*2);//LED output mode
    GPIOB->MODER |= (0x01 <<7*2);

    GPIOB->BSRR=(0x01<<(7+16));
}

void TIM6_Init(void){
    //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);
     //84000000->
     TIM6->PSC=8400-1;//10000 10kHz
    // TIM6->ARR=10-1;//
     TIM6->ARR=5000-1;//10000/5000=2Hz 0.5s

     TIM6->CNT=0;//0부터 시작

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

}

void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */
 if(TIM6->SR & (0x1)){
     TIM6->SR = ~(0x1);//rc_w0
     GPIOB->ODR ^=(0x1<<7);

 }
  /* USER CODE END TIM6_DAC_IRQn 0 */
  //HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */

  /* USER CODE END TIM6_DAC_IRQn 1 */
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  LED_Init();
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  TIM6_Init();

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

  TIM6->CR1 |= 0x1<<0;
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  /*MX_GPIO_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_TIM6_Init();*/
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

button Key 인터럽트로 LED 토글하기(레지스터제어) | STM32cubeIDE(32F429ZI) :: skylarvv 님의 블로그

Key 인터럽트 활용 관해서 전에 필기해두었습니다.

Nucleo-STM32F429ZI 보드를 사용했으며

이번엔 Key가 눌리면

%d th Key pressed로 몇번째 출력인지, UART로 출력하는 코드 구현 연습입니다.(Tera Term으로 메세지 확인)

사용한 UART, GPIO, Key 등 구조와 레지스터 사용에 대해서는 이전에 작성한 글들이 있습니다.

void USART3_Init(void)
{
     RCC->AHB1ENR |= 1<<3;// GPIOD 클럭 활성화
     RCC->APB1ENR |= 1<<18;//USART3

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

     //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)

    //uart 레지스터 설정
     USART3->CR1 = (1<<13 | 1<<3 | 1<<2);//UE,TE,RE
     USART3->BRR = (22 << 4) | 13; //115200bps,42MHZ =356

}

void UART3_Send(char c){
    while(!(USART3->SR>>7 & 0x1));
    USART3->DR=c;
}

void UART3_Print(const char* str){
    while(*str){
        UART3_Send(*str++);
    }
}

void EXTI_KEY_Init(void){
    RCC->AHB1ENR |= (0x1 << 2); // GPIOC
    GPIOC->MODER &= ~(0x3 << (2 * 13)); // PC13: 입력 모드

    RCC->APB2ENR |= (1<<14);//SYSCFG

    SYSCFG->EXTICR[3]&= ~(0xF <<4);
    SYSCFG->EXTICR[3]|=(0x2<<4);//SYSCFG_EXTICR4(Port C)

    EXTI->IMR |=(1<<13); //line13인터럽트 마스크
    EXTI->RTSR |=(1<<13);//Rising edge

    EXTI->PR = (1<<13);//pending (인터럽트 플래그 리셋.)

}

char msg[100];
int cnt=0;

void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */

  if(EXTI->PR & (0x1<<13)){
      EXTI->PR = (1<<13);//pending bit 클리어
      ++cnt;
      sprintf(msg,"%d th Key pressed ",cnt);
      UART3_Print(msg);

  }
  /* USER CODE END EXTI15_10_IRQn 0 */
  //HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  /* USER CODE END EXTI15_10_IRQn 1 */
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  USART3_Init();

  NVIC->ISER[1]|=0x1<<8;//EXTI15_10->40번
  EXTI_KEY_Init();

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
 /* MX_GPIO_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();*/
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

저번 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 체크 필요

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 */
}

NUCLEO-STM32F429ZI 보드를 사용했습니다.
(STM32F429 discovery 보드는 핀 번호가 달라서 핀 번호 설정만 바꿔서 하면 된다.)



IWDG 이해


iwdg은 카운트 다운을 하며, 일정신호를 주기적으로 보내는 정상 작동이 아닌 경우, 카운트 값을 갱신하지 않고 카운팅을 계속하여 0이 되면 시스템을 리셋한다.

iwdg은 내부의 LSI클럭을 이용하는 독립된 구조이다. RCC와 연결된 CSR이라는 레지스터로 LSI클럭을 onoff하는 비트가 있는데, 이걸로 IWDG의 실행에 영향을 주지 않는다.
iwdg는 한 번 CCCC로 enable되면, 끌 수 없다.
ST의 다른 칩들에서는 desable할 수도 있는데 이 보드는 끌 수없도록 설계되었다고 볼 수 있다.

WWDG와 다른점은 메인 클럭이 고장나도 독립적으로 작동을 유지한다는 것이다.

iwdg 레지스터제어로 구현해보기


다이어그램에 있는 iwdg의 작동을 제어하는 레지스터를 레퍼런스에서 찾아본다.

1. IWDG->KR(key register)


IWDR->KR에 5555값을 주면 PR(prescale),RLR(reload)레지스터에 접근할 수 있다.
왜 이런 방식으로 접근하는 이유는 실수로 watchdog 제어에 관련된 레지스터에 접근하는 것을 방지하는 것이다.
CCCC : iwdg 카운트 다운 시작.
AAAA : iwdg의 reload(카운트값)을 갱신. (정상작동 시 리셋되지않도록 계속 갱신해준다)





2. IWDG->PR(Prescaler register)


LSI클럭을 사용하는 iwdg의 클럭 값을 prescale로 divide해줄 수 있다. 표에 맞게 설정하면 된다.



3. IWDG->RLR(Reload register)


카운트 값을 12비트, 즉 4095내로 넣어준다.
그럼 0까지 down count한다.



4. RCC->CSR(clock control, status register)

 


iwdg reset에 의해 flag되는 29번 비트를 24번 remove비트로 지워줄 수 있다. 이 두 개를 사용해서 코드를 구현해본다.


근데 LSI osc와 관련된 비트가 있는데 이걸 해봤더니, 소용이 없다. 왜냐면 앞에서 레퍼런스에 나와있듯, iwdg이 한 번 실행되면 LSI클럭을 끌 수 없다. 그리고 이 레지스터에서의 0번 비트의 reset value도 0인데, 1로 on해주지 않고도 iwdg카운트를 시작해도 된다.
처음에 레지스터에 이 비트때문에 클럭 on해줘야하는건지 헷갈렸다. iwdg은 RCC의 경로와 다른 내부의 LSI클럭을 가지는 독립된 구조이다.

RCC→ CSR에 LSI클럭을 onoff하는 비트가 있지만, iwdg의 경우, 클럭 on off에 영향 받지 않고 동작한다. Iwdg은 시스템의 안전을 위한 장치이기 때문에, 소프트웨어 설정으로 멈출 수 없고, 한 번 시작되면 오직 reset에 의해서만 멈춘다.

코드 구현

iwdg에 의한 리셋을 확인해보는 코드이다.
uart3으로 Tera Term으로 메세지 출력해서 확인해보았다.

//처음엔 first try 출력, 두번째부터 reset by IWDG2 출력
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 iwdg_init(void){
    IWDG->KR=0x5555;//enable access to PR,RLR
    IWDG->PR |=0x3; //divided by 32
    IWDG->RLR= 4096-1; //count load
    IWDG->KR=0xAAAA;//update
    IWDG->KR=0xCCCC;//start watchdog
    //Timeout = (4095 + 1) * 32 / 32000 = 4.096 sec
}

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

  if((RCC->CSR)&(0x1<<29)){
                  sprintf(msg,"%s","reset by IWDG2 ");
                  uart3_printf(msg);
                  RCC->CSR |= (0x1<<24);//리셋 Flag clear
                   while(1);
              }else{
                  sprintf(msg,"%s","first try");//IWDG 실행 전 리셋이 아직 되지않았을 때를 출력해서 확인.
                  uart3_printf(msg);
              }


  iwdg_init();
  while (1);//기다리다 타임 아웃 후 리셋되면 main다시 실행



}

보드에 있는 두 버튼 중 리셋 버튼을 누르면서 테스트 해본다. 보드 전원 연결(usb)을 해제했다가 다시 로드하고 해도된다.

STM32cubeIDE에서 NUCLEO-STM32F429ZI 보드를 사용했습니다.

KEY 버튼을 누를 때마다 LED를 ON OFF 번갈아 깜박거리도록 코드 구현하기.
->GPIO INPUT으로 설정하기 다음으로, 이번엔 Key입력을 GPIO와 연결된 External Interrupt로 구현하기입니다.

1. Key 버튼 위치 확인

)


입력받을 KEY의 핀 위치는 Port C 13번이며 peripheral configuration에서
외부 인터럽트로 사용함으로 EXTI13을 선택한다.

 

2. EXTI ,SYSCFG 연결


SYSCFG(System config)를 통해 GPIO 해당 포트와 EXTI라인을 연결할 수 있다.
레퍼런스의 mapping 그림을 보면 GPIO핀에 연결 가능한 EXTI0~15개의 16개 라인과 특수 기능용 내부 이벤트에 사용하는 나머지 7개로 총 23개 라인이 있다.

GPIO C 13번 key 핀으로 인한 외부 인터럽트는 EXTI13라인과 연결되어있다.(데이터시트 확인가능)



3.External interrupt 처리 흐름

 

  1. 외부 입력 핀의 Rising,Falling edge 상태 변화를 감지한다.
  2. masking bit로 enable인지 확인 후, pending bit가 1로 설정된다.
    • masking register : 인터럽트를 enable한다.
    • pending register : 외부 인터럽트가 발생했음을 플래그.(rc_w1)
      • 읽기 : 1이면 인터럽트 발생.
      • 쓰기 : 1을 쓰면 0으로 클리어하며 인터럽트 처리 완료.
  1. NVIC에 등록한 IRQ인터럽트 핸들러의 우선순위와 해당 인터럽트를 CPU에 전달하고 ISR(service routine)에 진입한다.
  2. ISR 끝에서 pending 비트를 1로 써서 클리어해주고 조건을 구현한다.

4. 사용 레지스터

4-1 SYSCFG 클럭 인가

4-2 SYSCFG로 GPIO C포트와 EXTI13라인 연결

4-3 인터럽트 마스킹

4-4 Rising/Falling trigger 설정

)

버튼 핀은 R58 pull down 저항이 있는 회로로,

누르면 High(1)→Rising edge

떼면 Low(0)→Falling edge로,

누를때마다 LED 토글을 하는 것이면 Rising/Falling 둘 중 하나 감지해도 된다.

4-5 NVIC에 해당 IRQ 핸들러 등록하기


EXTI10 - 15을 처리하는 40번 IRQ 핸들러를 사용한다.



NVIC에 이 EXTI15_10 핸들러를 등록하려면
32 ~63 범위인 ISER[1]에 40-32 =8번째 비트에 써서 등록한다.

4-6 Pending bit로 인터럽트 상태 확인+조건 구현


인터럽트가 발생 중이면 1을 읽을 수 있다.

1을 확인 하면

다시 1을 써주어 0으로 클리어해 인터럽트 처리완료와 LED 토글동작을 수행한다.

처음 설정 마무리 시에도 PR을 1로 써주어 리셋하고 while문을 실행한다.

 

rc_w1관련 의미

비트 처리 방식이 rc_w1인 pending 레지스터의 비트 설정 시 주의사항
예를 들어 인터럽트를 하나 처리하고자
EXTI->PR |= 0x1<<3 이렇게 했을 때, 두 개 이상의 다른 인터럽트도 발생했다면
모두 처리해버리는 문제가 생긴다.
EXTI->PR =EXTI->PR | (0x1<<3)으로 3번 비트에 연결된 하나의 인터럽트를 처리하고자 했을때,
or 연산이여서 1인 플래그가 두 개이다.
이걸 그대로 EXTI->PR에 다시 넣어주게 되면 rc_w1이라 다른 인터럽트의 비트에도 1을 써주는 동작이 된다. 그래서 처리하고자 하지 않았던 비트도 모두 0으로 클리어된다.

EXTI->PR = 0x1<<3 으로 하나의 비트에만 넣어주는 것으로 한다.

5. 위 레지스터 제어로 코드 구현

void led_gpio_init(void){
    RCC->AHB1ENR |= (0x1<<1);//GPIOB

    GPIOB->MODER &= ~(0x3<<7*2);//LED output mode
    GPIOB->MODER |= (0x01 <<7*2);

    GPIOB->BSRR=(0x01<<(7+16));

    RCC->AHB1ENR |= (0x1 << 2); // GPIOC
    GPIOC->MODER &= ~(0x3 << (2 * 13)); // PC13: 입력 모드

}

void exti_init(void){

    RCC->APB2ENR |= (1 << 14); // SYSCFG

    SYSCFG->EXTICR[3] &= ~(0xF << 4); // EXTI13 위치는 EXTICR[3]의 4~7bit
    SYSCFG->EXTICR[3] |= (0x2 << 4);  //Port C = 0010


    EXTI->IMR |= (1 << 13);    // 인터럽트 마스크 (Unmask)
    EXTI->RTSR |= (1 << 13);   // 버튼누르면 High->rising edge
     //Falling edge로 FTSR로 설정해도 토글 동작함.
     ///누르면 high(rising), 떼면 Low(falling) 어느 엣지든 감지하도록 함.

    EXTI->PR = (1 << 13);//인터럽트 플래그 리셋 
}

void EXTI15_10_IRQHandler(void)
{

if(EXTI->PR & (0x1<<13)){
  EXTI->PR = (1 << 13);//클리어
    GPIOB->ODR^=0x1<<7;
}
}

int main(void)
{

  HAL_Init();
  led_gpio_init();
  NVIC->ISER[1]|=0x1<<8;//EXTI15_10->40번

  exti_init();

  SystemClock_Config();


  while (1);

}

System timer


register + ALU +Control unit => CPU
CPU 참고 -> ARMxx.pdf

CPU + 성능을 위한 장치(cache,MPU,FPU,systick..)=>ARM processor(Cortex M4)
Programming manual, TRM.pdf 참고

ARM processor + perpherial,I/O(GPIO,UART,TIMER,SPI,..)=>MCU(SoC) stm32f429××
UM,Reference.pdf 참고

stm32f429xx + 입출력장치. LED,key,gps+MEM => Nucleo 실습 보드

SysTick에 대한 설명은 programming manual.pdf를 참고해야한다.

 

ARM core 주변 장치 중 하나이다.
AHB와 연결된 클럭을 이용하며, ARM processor 에 내장된 24비트 다운카운터로, 주로 주기적인 인터럽트를 발생시켜 타이머 기반 작업을 처리하는 데 사용한다. 

1. 레지스터 제어로 500ms마다 polling으로 '.'출력하기

2. 500ms마다 익셉션 인터럽트로 출력하기

 

SysTick timer 레지스터

1. SysTick control and status register(STK_CTRL)

  • COUNTFLAG : 타이머가 0까지 카운트되면 1을 반환
  • CLKSOURCE : 클럭 소스 선택 비트
  • 0 → AHB/8 1 → AHB
  • TICKINT : SysTick 익셉션 요청 발생 여부
  • 0 → 익셉션 요청 X 1 → 0까지 카운트 되었을 때 예외 요청 발생
  • ENABLE : 카운터 동작 제어 비트
  • 0 → 카운터 비활성화 1 → 카운터 활성화 활성화 시, LOAD 레지스터의 값을 로드하고 카운트가 0이 되면 COUNTFLAG=1 설정 및 선택에 따라 예외 요청 발생

 

2. SysTick reload value register(STK_LOAD)


카운팅 값(reload 값)을 설정한다.

  • 카운터가 활성화될 때와 카운터가 0에 도달했을 때 STK_VAL 에 로드될 시작값을 지정해줌
  • 주기가 N 프로세서 클럭 사이클인 주기적 인터럽트를 만들려면 → RELOAD = N - 1
  • N 프로세서 클럭 사이클 뒤에 한 번만 인터럽트를 발생시키려면 RELOAD = N

3. SysTick current value register(STK_VAL)


SysTick 카운터의 현재 값을 담고 있음

이 레지스터에 어떤 값을 쓰더라도 필드는 0으로 클리어되고, 동시에 STK_CTRL의 COUNTFLAG 비트도 0으로 리셋된다.

 

Polling (500ms마다 . 찍기)

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(15bit),UE,wordlength8bit+stop1bit(12bit),parityX(10bit),TE,RE
    USART3->CR1 =(0<<15 |1<<13 | 0<<12 |0<<10 | 1<<3 | 1<<2);
    //USART3->CR2 &=~(0x3<<12);//1 stopbit, reset value 0이라서 필수X
    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();

int msec=500, hclk=168000000;
SysTick->CTRL=(0<<2 | 0<<1 |0<<0);//reset
SysTick->LOAD=(int)((hclk/(8.0*1000)*msec)+0.5); //msec 1000은 0xFFFFFF 초과해 오버플로우

SysTick->CTRL|=1<<0; //count start

  while (1)
  {
      if((SysTick->CTRL >>16) &0x1){//카운트 0될때마다 Flag 1
          uart3_printf(".");
      }
  }
}

 

Exception으로 500ms마다 출력하기

//exception 두 번할 때 polling 한번 출력:@@. @@. @@. @@. @@. 
//(polling :"."출력,exception :"@"출력(systick타이머500ms)))
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(15bit),UE,wordlength(12bit),parityX(10bit),TE,RE
    USART3->CR1 =(0<<15 |1<<13 | 0<<12 |0<<10 | 1<<3 | 1<<2);
    USART3->CR2 &=~(0x3<<12);//1 stopbit, reset value 0이라서 필수는 X
    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++);
    }
}

extern volatile uint32_t sys_tick;

int main(void)
{
  HAL_Init();
  uart3_init();
  int poll_tick=0;

  SystemClock_Config();

int msec=500, hclk=168000000;
SysTick->CTRL=(0<<2 | 1<<1 |0<<0);//Exception 발생
SysTick->LOAD=(int)((hclk/(8.0*1000)*msec)+0.5);
SysTick->CTRL|=1<<0;//counter start

  while (1)
  {
      if(((sys_tick-poll_tick)>=2)&&((SysTick->CTRL >>16)&0x1)){//check count flag
          uart3_printf(".");
          poll_tick=sys_tick;

      }

  }

}

<stm32f4xx_it.c 파일>에 핸들러함수가 생성되어서 여기에 두고 사용했는데, 핸들러 함수를 main.c로 이동함. stm32f4xx_it.c에 두고 extern선언해 사용해도 된다

_____________________________________________________
//stm32f4xx_it.c

volatile uint32_t sys_tick=0;

void SysTick_Handler(void)
{

    uart3_printf("@");
    ++(sys_tick);

  HAL_IncTick();

}

NUCLEO-STM32F429ZI 보드를 사용했습니다.

UART 시리얼 통신

UART와 USART의 차이는 동기화 Clock 소스의 유무이다.
1. USART는 동기화 Clock에 따라 데이터를 전송한다.
2. UART는 협의된 baudrate에 따라 T1bit 간격으로 샘플링된 값을 데이터로 수신하며, START비트와 STOP비트는 1.5bit의 길이를 가진다. (RS-232 통신 프로토콜과 같은 구조)
3. “hello” → logic analyzer로 파형 측정해보기

-구조 : start 1bit → data 8 bit → stop 1bit

  • data bit를 보낼 때 LSB를 먼저 보내기 때문에 파형의 오른쪽부터 반대로 읽어야 한다.
  • ‘h’ (hex값 0x68) →0110 1000

보드의 RX핀과 로직 애널라이저를 연결하여 파형을 확인할 것이다. GND까지 애널라이저의 집게핀과 연결했음을 꼭 확인한 뒤에 pc에 보드와 애널라이저 각각 usb연결을 해야한다(포트 2개필요)



UART 회로 및 내부 구조의 대략적인 이해

Nucleo-F429ZI 보드와 PC UART 연결 (USB 인터페이스 연결)

ST-Link 파트

STM32F103 ←[USB]→ PC

MCU 파트

STM32F429 ←[UART]→ STM32F103

USART3 인터페이스를 이용해 STLK_TX, STLK_RX의 라인으로 연결한다.

 

예시 이미지

  • DCE(Data Communication Equipment): 데이터의 회선 종단 장치 (모뎀, 휴대폰, 세터박스 등)
  • DTE(Data Terminal Equipment): 데이터를 생산, 수신하는 장치 (PC, 사람 등)

ST-Link 파트의 STM32F103



MCU 파트의 STM32F249ZI

 

UART 블록 다이어그램

8개의 외부핀이 있다.
TX : 송신핀
RX : 수신핀
IRDA_IN / IRDA_OUT : 적외선 통신 입출력 핀
RTS : Request to send(수신 가능 상태 시 Low로 signal 변환)
CTS : Clear to send(CTS 신호가 Low 시에 데이터 전송)
CK : USART 시 사용하는 동기 통신 클럭 핀



RCC 구조 대략적 이해

RCC (Reset clock controller)

  • STM32의 모든 장치를 동작하게 하는 타이밍(클럭 신호)을 제공한다.
  • 클럭 신호는 Low(0), High(1)의 값을 갖는 방형파(square wave)로 표현된다.
  • 클럭 소스는 다음과 같이 3가지가 있다.
    1. HSI(High speed internal) : 내부에서 생성하는 16MHz의 클럭이다. (내부 16MHz RC 발진기)
    2. HSE(external) : 외부 클럭을 HSEBYPASS로 그대로 사용하거나, (외부 크리스탈 공진기, 외부 클럭)
    3. PLLCLK : HSE 외부 클럭을 PLL회로를 거쳐 시스템 클럭을 구하고, 스케일된 HCLK를 Cortex system timer, APB,AHB 등 peripheral 클럭으로 사용한다.

-USART3은 APB1 Bus와 연결된다.


그래서 42MHz를 이용할 수 있는데 이 시스템 클럭을 바탕으로 baud rate을 설정할 것이다.



UART 관련 레지스터

① RCC_APB1ENR : USART3에 연결된 APB 클럭을 인가한다

RCC->APB1 |= 0x1 << 18; // enable USART3

 

② GPIOx_MODER : AF 모드로 비트 10 으로 마스크해준다.

GPIOD->MODER &= ~(0x3 << 16 | 0x3 << 18); // 초기화
GPIOD->MODER |= (0x2 << 16) | (0x2 << 18); // 10 10

 

③ GPIOx_AFRH : USART3와 연결된 GPIO핀과 맵핑해준다.

  • GPIOx_AFRL : 포트x의 0~7번 핀
  • GPIOx_AFRH : 8~15번 핀
  • AFRL,AFRH는 32bit 레지스터이다.
  • 한 핀당 4bit 사용으로 AFRL, AFRH 각각 32비트로 각각 8개 GPIO핀을 설정할 수 있다.

  • PD8 : AFRH8[3:0], PD9 : AFRH9[3:0]
      GPIOD→AFR[1] &= ~(0xF << 0 | 0xF << 4); // 초기화
    GPIOD→AFR[1] |= (0x7 << 0) | (0x7 <<4); // 0111 0111
  • →GPIOD→AFR[1] |= (0x7 <<0) | (0x7 <<4);

④ USART_SR : (TXE가 1이 되어 새 데이터를 버퍼에 쓸 수 있는지 확인한다)

Bit 이름 설명 클리어 방법
9 CTS CTS 핀의 상태 변화 감지 소프트웨어가 0을 써서 클리어 (rc_w0)
7 TXE 송신 데이터 레지스터가 비었는지 여부 : TDR레지스터 다 shift되면→ 1 USART_DR에 쓰기로 클리어(r)
6 TC 송신 완료 여부 USART_SR 읽기 후 USART_DR 쓰기 또는 0 쓰기(rc_w0)
5 RXNE 수신 데이터 존재 여부 : 1→ 데이터를 읽으면 자동 클리어. USART_DR 읽기 또는 0 쓰기(r)
       

rc_w0
0을 write해서 clear한다.(or연산 주의. 직접 연산없이 대입하는게 다른 비트에 안전)

 

⑤ USART_DR : Data 레지스터.

[송신 흐름]
코드: USART->DR = 'H';  DR은 데이터 버퍼 역할.
→ DR = 0x48 (ASCII 'H')  
→ 내부 시프트 레지스터로 전송  
→ TX 핀으로 직렬 송신

[수신 흐름]
외부에서 'H' 수신  
→ 내부 시프트 레지스터 → DR = 0x48  
→ RXNE = 1  
→ 코드에서 USART->DR 읽기  
→ c = 'H', RXNE = 0 (자동 클리어)

** 수신 데이터 읽기 **

UART3으로 데이터를 수신하면, 수신한 데이터는 USART→DR 레지스터에 저장되어 읽어들일 수 있다.

  • DR레지스터에서 8비트 값 읽기
    uint8_t received_data = UART3->DR;

** 전송 데이터 쓰기 **

UART3으로 전송을 하려면, 먼저 전송할 데이터를 DR 레지스터에 쓴다.
USART3->DR = (uint8_t)(*pdata8bits);



⑥ USART_BRR

)

  • baud rate 계산
- DIV_Mantissa[ 11 : 0 ] : USARTDIV의 정수 부분 (소수점 앞의 값)
- DIV_Fraction[ 3 : 0 ] :USARTDIV의 소수점 이하 값 (소수점 뒤의 값 × 16)

[16MHz, 115200bps]      내부 클럭 사용시
//[42MHz, 115200bps]     // 외부 클럭.APB1의 PCLK 사용시

USARTDIV=f_CLK/(oversampling* Baudrate)
=16000000/(16*115200)
=약 8.6806

DIV_Mantissa=8
DIV_Fraction=0.6806*16= 약10.89 =>11

USART_BRR = (DIV_Mantissa << 4) | DIV_Fraction
          = (8 << 4) | 11
          = 0x80 | 0x0B
          = 0x8B



⑦USART_CR1 : USART의 동작 모드와 주요 기능들을 켜고 끄는 역할.

)



Hello world 출력

#include "stm32f4xx.h"

void USART3_Init(void) {
    // 1. GPIOD 클럭 활성화
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

    // 2. PD8(RX), PD9(TX) 를 AF 모드로 설정
    GPIOD->MODER &= ~((0x3 << (2 * 8)) | (0x3 << (2 * 9)));  // 클리어
    GPIOD->MODER |= ((0x2 << (2 * 8)) | (0x2 << (2 * 9)));   // AF 모드

    // 3. AF7(USART3) 설정
    GPIOD->AFR[1] &= ~((0xF << (4 * 0)) | (0xF << (4 * 1)));  // 클리어
    GPIOD->AFR[1] |=  ((0x7 << (4 * 0)) | (0x7 << (4 * 1)));  // AF7

    // 4. USART3 클럭 활성화
    RCC->APB1ENR |= RCC_APB1ENR_USART3EN;

    // 5. Baud rate 설정 (115200 baud,16mHz)
    USART3->BRR = (8 << 4) | 11;  // 0x8B

    // SystemClock_Config 호출과 함께 이 값을 설정 시->CPU 클럭이 빨라진다.
    //당연히 tera term에 나오는 출력도 빨라짐
    // USART3->BRR = (22 << 4) | 13;

    // 6. 송신 활성화, USART 활성화
    USART3->CR1 |= USART_CR1_TE;
    USART3->CR1 |= USART_CR1_UE;
}

void USART3_SendChar(char c) {
    while (!(USART3->SR & USART_SR_TXE));
    USART3->DR = c;
}

void USART3_SendString(const char* str) {
    while (*str) {
        USART3_SendChar(*str++);
    }
}

int main(void) {
  HAL_Init();
  SystemClock_Config();
    USART3_Init(); // 초기화
while (1)
{
    USART3_SendString("Hello, World ");
    for (volatile uint32_t i = 0; i < 1000000; i++) {
        }
    }
}



Tera Term에서 echo back 출력해보기

void GPIO_Init(void)
{
    // GPIOD 클럭 활성화
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

    // PD8 (TX) 설정
    GPIOD->MODER &= ~((0x3 << (2 * 8)) | (0x3 << (2 * 9)));  // 클리어
    GPIOD->MODER |= ((0x2 << (2 * 8)) | (0x2 << (2 * 9)));   // AF 모드

    // 3. AF7(USART3) 설정
    GPIOD->AFR[1] &= ~((0xF << (4 * 0)) | (0xF << (4 * 1)));  // 클리어
    GPIOD->AFR[1] |=  ((0x7 << (4 * 0)) | (0x7 << (4 * 1)));  // AF7
}

void USART3_Init(void)
{
     RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
     // 5. Baud rate 설정 (115200 baud)
    USART3->BRR = (8 << 4) | 11;  // 0x8B;  

    // 6. 송신 활성화, USART 활성화
     USART3->CR1 |= USART_CR1_TE;
     USART3->CR1 |= USART_CR1_RE;
     USART3->CR1 |= USART_CR1_UE;
}

void USART3_SendChar(char c)
{ 
    while (!(USART3->SR & USART_SR_TXE));
    USART3->DR = c;  // TDR 레지스터에 문자 전송
}

char USART3_ReceiveChar(void)
{
    while (!(USART3->SR & USART_SR_RXNE));
    return (char)(USART3->DR & 0xFF);
}

int main(void)
{
     HAL_Init();
    GPIO_Init();
  USART3_Init();
    while (1)
  {
      char c = USART3_ReceiveChar();  // 문자를 하나 수신
      USART3_SendChar(c);
    }
}

Tera Term 에서 문자하나를 터미널에 입력하면, 바로 뒤에 입력한 문자가 나타난다.
->1번코드 실행과 달리 두 번씩 쓰여지는 것이 보이면 echo back이 된것이다.
(따로 echo back모드 설정 필요없음)

sprintf로 문자열 출력

void GPIO_Init(void)
{
    // GPIOD 클럭 활성화
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

    // PD8 (TX) 설정
    GPIOD->MODER &= ~((0x3 << (2 * 8)) | (0x3 << (2 * 9)));  // 클리어
    GPIOD->MODER |= ((0x2 << (2 * 8)) | (0x2 << (2 * 9)));   // AF 모드

    // 3. AF7(USART3) 설정
    GPIOD->AFR[1] &= ~((0xF << (4 * 0)) | (0xF << (4 * 1)));  // 클리어
    GPIOD->AFR[1] |=  ((0x7 << (4 * 0)) | (0x7 << (4 * 1)));  // AF7
}

void USART3_Init(void)
{
     RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
     // 5. Baud rate 설정 (115200 baud)
    USART3->BRR = (22 << 4) | 13;  //42MHZ 클럭 이용

    // 6. 송신 활성화, USART 활성화
     USART3->CR1 |= USART_CR1_TE;
     USART3->CR1 |= USART_CR1_RE;
     USART3->CR1 |= USART_CR1_UE;
}

char msg[100];

void sendchar(char c){
      while(!(USART3->SR & USART_SR_TXE));
      USART3->DR =c;
  }

void sendstring(const char* str){
      while(*str){
          sendchar(*str++);
      }
  }

int main(void){
 HAL_Init();
 SystemClock_Config();
 GPIO_Init();
 USART3_Init();
while (1)
{
sprintf(msg, "%d", "uart test ");
      sendstring(msg);
      HAL_Delay(1000);
      }
      }

tera term 으로 baud rate을 동일하게 맞춰주고, 출력확인해본다.

+ Recent posts