본문 바로가기
Robot/Device Control

[STM32F10x-StdPeriph] 5. SmartRobot Board 실습 - Analog Write (PWM)

by lifeseed 2013. 11. 25.
0. 들어가기 전에

 이번 강좌에서는 PWM Wave를 SmartRobot Pin을 통해서 출력하는 예제를 진행합니다. STM32F의 TIM Peripheral에서 제공하는 PWM out 기능을 이용하게 되구요, 이를 컨트롤하기 위해 ST에서 제공하는 Peripheral Device Library 코드를 이용하게됩니다.

 Base Code는 http://lifeseed.tistory.com/78 게시물의 첨부파일인 srbd_bsp_printf_ex.zip 파일을 이용하며, 본예제에서 사용되는 출력핀은 SmartRobot Board의 J8-12 번핀인 PB8로써 TIM4 Channel 3으로 할당되어 있습니다.(CD00161566_product_spec.pdf 참조)

 

1. TIM Peripheral for PWM

1) about TIM 

 STM32F10x에서는 Cortex-M Core가 기본적으로 제공하는 Sytem Timer외에도 TIM 이라는 Timer를 제공합니다.

Register Manual을 살펴보면 TIM은 Chapter 14 ~ 17 장까지 네개의 장에서 설명하고 있으며, 각각

- Advanced-control timers (TIM1 & TIM8)

- General-purpose timers (TIM2 ~ TIM5)

- General-purpose timers (TIM9 ~ TIm14)

- Basic timers (TIM6 & TIM7)

으로 구분되어 설명하고 있으며, 이 강좌에서는 TIM의 사용법에 대한 구체적인 내용보다, TIM4번을 PWM output으로 사용하기위해 사용되는 API들을 어떻게 구성하는가에 대해 설명하고자 한다.

 

2) List of ST Library for PWM Out Control

 PWM output을 설정하기 위해 사용되는 API는 크게 Pin 속성 설정, TIM Port 속성 설정, 그리고 Channel 설정 3가지로 분류할 수 있다.

 먼저 Pin 속성에 관하여서는 가장 먼저 해당 Peripheral을 사용하기 위해서는 Clock이 인가되어야 하며, 또한 앞서 사용된 예제들 처럼 입,출력 및 기본 혹은 특수 기능을 담당하도록 설정해주어야 한다. TIM4는 PB8에 해당하는 핀의 Alternative Function Output 모드에서 동작된다.

 아래는 PB8 핀을 PWM output으로 설정하기 위해 사용되는 ST에서 제공된 API List들이다.

- RCC_APB1PeriphClockCmd

  : APB1 에 할당된 Peripheral에 Clock을 인가하도록 설정 가능한 API로서, APB1에 할당된 TIM4에 Clock을 할당한다.

 

- GPIO_Init

  : 각 포트의 속성을 정의하는 API로, PB8 번 포트의 속성을 TIM4 Channel3 으로 설정한다.

 

- TIM_TimeBaseInit

 : Counter Clock, Mode등, TIM 블럭의 기본적인 속성을 설정한다.

 

- TIM_Cmd

 : TIM 블럭을 구동시키는 명령을 포함한다.

 

- TIM_OC3Init

 : TIM블럭의 3번 채널에 대한 출력 속성을 설정한다.

 

- TIM_OC3PreloadConfig

 : TIM블럭의 3번 채널의 Preload 속성을 설정한다. 

 

2. 코드 작성 및 컴파일

void setup(void)
{
 GPIO_InitTypeDef GPIO_InitStructure;

 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 TIM_OCInitTypeDef  TIM_OCInitStructure; 

 

 // RCC Configuration
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE );

 

 // TIM4 Ch3
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOB, &GPIO_InitStructure);

 

 /* Time base configuration */
 TIM_TimeBaseStructure.TIM_Period = 665;             // set to 36KHz PWM Clock

// set to 24MHz Counter Clock
 TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t) (SystemCoreClock / 24000000) - 1; 
 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

 

 /* PWM1 Mode configuration: Channel3 */
 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
 TIM_OCInitStructure.TIM_Pulse = 333;                  // 50 % duty cycle value
 TIM_OC3Init(TIM4, &TIM_OCInitStructure);
 TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);

 

 TIM_ARRPreloadConfig(TIM4, ENABLE);

 TIM_Cmd(TIM4, ENABLE);

}

 

void loop(void)
{
 char c;

 TIM_OCInitTypeDef  TIM_OCInitStructure; 

 

 if(GetKey(&c)==1)
 {
  switch(c)
  {
  case '0':

   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
   TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

   TIM_OCInitStructure.TIM_Pulse = 0;

   TIM_OC3Init(TIM4, &TIM_OCInitStructure);

   Lb_printf("Duty cycle is set to 0%\r\n");

   break;
  case '1':

   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
   TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

   TIM_OCInitStructure.TIM_Pulse = 83;

   TIM_OC3Init(TIM4, &TIM_OCInitStructure);
   Lb_printf("Duty cycle is set to 12.5%\r\n");
   break;
  case '2':

   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
   TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

   TIM_OCInitStructure.TIM_Pulse = 333;

   TIM_OC3Init(TIM4, &TIM_OCInitStructure);
   Lb_printf("Duty cycle is set to 50%\r\n");
   break;
  default:
   Lb_printf("%c", c);
   break;
  }
 }
}

 

arch/main.c 의 setup 및 loop함수에 위와 같이 코딩하고 cs-make를 통하여 Build합니다.

 

컴파일 에러가 날 경우 adc library를 include해야 합니다.

1) arch/include/stm32f10x_conf.h 의 46라인의 주석을 풀어줍니다.

 

/*  #include "stm32f10x_tim.h" */ 

=>

#include "stm32f10x_tim.h" 

 

 

2) arch/STM32F10x_StdPeriph_Driver/src/Makefile.inc 의 26번째 라인의 주석을 풀어 줍니다.

 

#STM32F10X_DRV_SRC  += stm32f10x_tim.c

=>

STM32F10X_DRV_SRC  += stm32f10x_tim.c

  

 

그리고 다시 빌드를 하면  

stm32f103cb.bin 파일이 생성됩니다.
 arm-none-eabi-objcopy -O binary stm32f103cb.elf stm32f103cb.bin
arm-none-eabi-objdump -h -S -C stm32f103cb.elf > stm32f103cb.lss
arm-none-eabi-nm -n stm32f103cb.elf > stm32f103cb.sym
arm-none-eabi-size -A stm32f103cb.elf
stm32f103cb.elf  :
section            size        addr
.text             15112   134234112
.data                40   536870912
.heap              3072   536870952
.stack_dummy       1024   536870952
.ARM.attributes      41           0
.comment             48           0
.debug_info       25307           0
.debug_abbrev      4423           0
.debug_aranges      328           0
.debug_line        7807           0
.debug_str         8875           0
.debug_frame       4456           0
.debug_loc        21204           0
.debug_ranges       768           0
Total             92505
 
3. Download 및 실행
SW3을 누른채로 Reset하여 부트모드로 진입후 1번항목을 선택하고 TeraTerm등의 Terminal 창에서 Y-Modem으로 빌드된 stm32f103cb.bin을 전송합니다.
자세한 다운로드 방법은 http://lifeseed.tistory.com/73 의 3번 항목을 참조하시면 됩니다.
(저는 Teraterm V4.79를 사용하고 있습니다.)

 ================== Main Menu ============================

  Download Image To Flash ------- 1

  Upload Image From Flash ------- 2

  Execute ------------------------------ 3

==========================================================

Waiting for the file to be sent ... (press 'a' to abort)
C

 Programming Completed Successfully!
--------------------------------
 Name: stm32f103cb.bin
 Size: 15152      Bytes
-------------------

다운로드가 완료되면 Reset버튼을 눌러 코드를 실행합니다.

코드가 실행되면 36KHz의 duty cycle 50%를 가지는 PWM이 출력되며, 0, 1, 2 번키를 Terminal에서 입력함에 따라 각각 duty cycle이 0, 12.5, 50%로 변경됨을 확인할 수 있습니다.

cf) PWM확인은 스코프를 통해 파형을 확인하여야 합니다.


 

4. PWM설정 Code 설명

기본적인 API의 설명은 그동안의 예제와, 1.2) List of ST Library for PWM Out Control  에서의 설명으로 대체하고, 이 장에서는 Timer API 호출시 사용되는 변수 값들 및 Clock 설정에 대한 설명을 진행한다.

1) Timer Port 속성 설정

 /* Time base configuration */
 TIM_TimeBaseStructure.TIM_Period = 665;             // set to 36KHz PWM Clock

// set to 24MHz Counter Clock
 TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t) (SystemCoreClock / 24000000) - 1; 
 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

 

이 예제에서는 Up Counter Mode로 Timer의 Counter Clock을 TIM_Prescaler를 이용하여 24MHz로 설정하고, TIM_Period 값을 이용하여 최종적으로 36KHz의 PWM 파형을 출력하도록 합니다.

 

TIM_Prescaler 및 TIM4 Frequency를 구하는 공식은 다음과 같습니다.

 

 - TIM4 counter clock  24MHz

        Prescaler = (TIM4CLK / TIM4 counter clock) - 1

 - TIM4 is running at 36 KHz

        TIM4 Frequency = TIM4 counter clock/(TIM_Period+ 1)
                               = 24 MHz / 666 = 36 KHz 
  

 

2) Channel 3 속성 설정

TIM의 Capture Compare의 Output 설정을 이용하여 PWM 출력모드로 설정하게 되며, 이에 대한 세부적인 설명은 여기서는 생략한다.

아래와 같이 코드를 구성함으로써 PWM  출력이 가능하다.

 /* PWM1 Mode configuration: Channel3 */
 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
 TIM_OCInitStructure.TIM_Pulse = 333;                  // 50 % duty cycle value
 TIM_OC3Init(TIM4, &TIM_OCInitStructure);
 TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);

 

 TIM_ARRPreloadConfig(TIM4, ENABLE); 

 

여기서 하나 짚고 넘어갈 부분은 PWM의 duty cycle을 설정하는 부분이다.

 TIM4 Channel1 duty cycle = (TIM_Pulse/ (TIM_Period+1))* 100   

 

본 예제에서는 키 입력에 따라 0, 83, 333 이라는 값을 사용함으로써 각각 0%, 12.5%, 50%의 duty cycle을 가지는 PWM 파형을 출력하도록 하였다.

 
5. 마무리 하며
 지금까지 강좌의 주 목적은 ST에서 제공하는 Standard Library API를 이용하여, 각 포트의 입, 출력 속성을 설정하고, 이를 컨트롤 해 보는 것이다. 코드 분석 능력이 추가 된다면, API를 분석함으로써 STM32F10x 칩에 대한 Register Control까지 이해가 가능할 것이며, 더 나아가 사용된 API를 본인이 직접 Register를 Control하는 API로 변경하면 본인만의 Device Driver API를 생성할 수도 있다.

 아울러, 지금까지의 예제에서 사용된 API들의 조합으로 Arduino에서 제공하는 Digital/Analog Read/Write 함수로 구성하면, 조금 더 쉬운 컨셉으로 STM32F10x Chipset을 구동할 수도 있을 것이다.

 

 

[참고자료]


[1] CD00171190 STM32F1xx Reference Manual
[2] CD00161566 STM32F1xx Product Spec
[3] http://arduino.cc/en/Reference/AnalogWrite