이번 강좌에서는 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) 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. 코드 작성 및 컴파일
{
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;
}
}
}
컴파일 에러가 날 경우 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
그리고 다시 빌드를 하면
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 |
================== 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) Programming Completed Successfully! |
코드가 실행되면 36KHz의 duty cycle 50%를 가지는 PWM이 출력되며, 0, 1, 2 번키를 Terminal에서 입력함에 따라 각각 duty cycle이 0, 12.5, 50%로 변경됨을 확인할 수 있습니다.
cf) PWM확인은 스코프를 통해 파형을 확인하여야 합니다.
기본적인 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 파형을 출력하도록 하였다.
아울러, 지금까지의 예제에서 사용된 API들의 조합으로 Arduino에서 제공하는 Digital/Analog Read/Write 함수로 구성하면, 조금 더 쉬운 컨셉으로 STM32F10x Chipset을 구동할 수도 있을 것이다.
[참고자료]