[STM32] PWM

myblack·2024년 8월 13일
0

STM32

목록 보기
5/8

PWM

PWM 이란

PWM (Pulse Width Modulation) 의 약자
정보에 따라 펄스의 폭을 변화시켜 전달하는 방식이다.

신호의 크기가 크면 펄스 폭을 크게 하고 신호의 크기가 작으면 펄스 폭을 작게 하여 정보를 전달한다.
펄스 폭의 크기에 따라 평균 파워를 변화시킬 수 있다.
LED ON/OFF 제어, 밝기 제어, DC 서보 모터 등에 사용된다.

간단히 설명하자면, 일정한 주기 내에서 Duty비를 변화 시켜서 평균 전압을 제어하는 방법!

  • 기본은 Timer 인터럽트에 배경을 두고 있음

  • Prescale을 이용하여 HCLK를 일정 줄여줌

  • AutoReload Register를 이용하여 해당 값에 도달하면 인터럽트가 발생하며 다시 처음으로 돌아감.

  • 타이머 인터럽트 주기 만큼 PWM의 주기가 됨.

  • 아래 그림과 같이 타이머가 Up Count 하는 동안
    CCR(Capture Compare Register)을 통해 Duty를 조절함.

Duty (듀티) 란

디지털신호는 0과 1로 표현됩니다. 0과 1이 반복되는 한주기 신호중
0의 지속시간과 1의 지속시간의 비율을 나타내는 단어입니다.

(LOW와 HIGH신호의 비율)

예를 들면, 디지털신호의 한주기의 시간을 100이라고 가정할때, 0의 지속시간이 딱 절반(50)이고 1의 지속시간이 딱 절반(50)이라면

(50/100) x 100% = 50%, 듀티는 50%가 되는 것입니다.

TIMx의 Parameter Settings에서 Prescaler와 Counter Period를 설정해준다.

  • Counter Register가 1증가하는데 걸리는 시간은 System Clock에 의해 결정된다.

  • System Clock이 90MHz라면 1증가하는데 걸리는 시간은 1 / 90,000,000 (sec)

  • Prescaler=20 이므로 1 / 4,500,000 (sec)이 걸릴 것이다.

  • Period=45,000 이므로 45,000 / 4,500,000 = 1/100 이다.

  • 따라서 한 주기는 1 / 100 (sec)이므로 100Hz임을 알 수 있다.

PWM 타이머 설정

Example)

  • Prescaler = 1+1, Counter Period(AutoReload Register) = 35999 + 1.

  • HCLK(72,000,000) ÷ Prescaler(1+1) ÷ AutoReload Register(35999+1) = 0.001.

  • 즉 1번 펄스가 발생할 때 0.001초 걸리므로 1초면 1000번 발생하므로 1000 Hz 임을 알 수 있음.

  • Pulse(35999+1) 이므로 현재 Duty Rate는 100% 임을 알수 있음.

소스코드

  • PWM의 시작
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);

  • PWM의 duty 조절
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, nduty);

  • PWM의 정지
    HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_2);

PWM parameter

  // PWM 주기
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 10-1;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 42000-1;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  
  // PWM 펄스폭
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 21000-1;  // PWM 1번
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  // 카운터 레지스터 일치시 HIGH
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  
  sConfigOC.Pulse = 10500-1; // PWM 2번
  
  sConfigOC.Pulse = 5250-1; // PWM 3번
  

Main code

  //PWM 시작하는 함수
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);

  HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);
  
  uint8_t ccr=0; // ccr 선언, 초기화
 while (1)
  {
	  TIM4->CCR1 = ccr;		// CCR1값 변경시켜 Duty rate 변경
      // 듀티값 설정하는 함수
	  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,ccr);
	  ccr+=1000;
      
	  if(ccr>TIM4->ARR) ccr=0;
	  HAL_Delay(50);
  }

예제코드

#include "main.h"  // 메인 헤더 파일 포함
#include "tim.h"   // 타이머 관련 헤더 파일 포함
#include "gpio.h"  // GPIO 관련 헤더 파일 포함

void SystemClock_Config(void);  // 시스템 클럭 설정 함수의 프로토타입 선언

int main(void)
{
  HAL_Init();  // HAL 라이브러리 초기화
  SystemClock_Config();  // 시스템 클럭 설정

  MX_GPIO_Init();  // GPIO 초기화
  MX_TIM1_Init();  // TIM1 타이머 초기화

  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // TIM1 타이머의 PWM 신호 생성 시작

  duty = HAL_TIM_ReadCapturedValue(&htim1, TIM_CHANNEL_1); // 현재 PWM 듀티 사이클 값 읽기
  autoReload = __HAL_TIM_GET_AUTORELOAD(&htim1);  // TIM1의 오토 리로드 레지스터 값 읽기

  while (1)  // 무한 루프
  {
      while(duty < autoReload)  // 듀티 사이클이 autoReload 값보다 작을 때
    {
          __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ++duty);  // 듀티 사이클을 1씩 증가시킴
          HAL_Delay(1);  // 1ms 딜레이
      }
      while(duty > 0)  // 듀티 사이클이 0보다 클 때
    {
          __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, --duty);  // 듀티 사이클을 1씩 감소시킴
          HAL_Delay(1);  // 1ms 딜레이
      }
      HAL_Delay(1000);  // 1초 딜레이 후 다음 사이클 진행
  }
}

PWM 구동

서보모터

SG90

  • 구동전원 : 3.0~7.2V
  • 펄스주기 : 50Hz (20ms)
  • 펄스폭
    최소 0.2ms -> CW끝
    최대 3ms -> CCW끝

※ CW는 시계방향의 회전, CCW는 시계반대방향의 회전

  • 갈색 : GND / 빨간색 : VCC (+5V) / 주황선 : PWM입력

Sys_clk = 168MHz
168000000HZ / 50Hz = 336 * 10000
50HZ 를 1000번 count하면 20ms
Period 168000000HZ / 50
PWM : 500 ==> PWM Duty 비 50%

설정
PSC 336-1
ARR 10000-1
Pulse 100

최소 펄스폭 = 0.2ms
DutyCycle = (Pulse)/(Period)

1) 20ms / 10000(arr) = 0.002
0.002 * 100 = 0.2ms

2) 0.2ms / 20ms = 0.01 -> 1%
1% * 10000 = 100

소스코드
HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1);
TIM10->CCR1 = 1500;  // 0.2ms -> 3ms : 100*15

예제코드

/* USER CODE BEGIN PV */
uint8_t FLAG=0;  // FLAG 변수 선언 및 초기화 (0으로 초기화)
uint8_t deg[]={0,90,180, 30, 120, 60, 180};  // 서보 모터 각도를 저장하는 배열
uint8_t count;  // 서보 모터 각도 배열의 인덱스를 저장하는 변수
uint16_t dg;  // 현재 서보 모터 각도를 저장하는 변수
/* USER CODE END PV */

void SERVO_Angle(float Angle)
{
    uint16_t PWM_Pulse = 0;  // PWM 펄스 폭을 저장하는 변수
    uint16_t Period_Max = 240;  // PWM 최대값 (서보 모터의 최대 각도에 해당)
    uint16_t Period_Min = 60;  // PWM 최소값 (서보 모터의 최소 각도에 해당)

    // 주어진 각도(Angle)에 따라 PWM 펄스 폭을 계산
    PWM_Pulse = ((Angle*(Period_Max-Period_Min))/180.0)+Period_Min;
    TIM2->CCR1 = PWM_Pulse;  // 계산된 PWM 펄스 폭을 타이머 2의 채널 1 비교 레지스터에 저장
}

void HAL_IncTick(void) // HAL_getTick() 함수의 역할을 수행
{
    uwTick += uwTickFreq;  // 시스템의 Tick 카운터를 증가시킴
    if((uwTick % 2000)==0) // 2000ms(2초)마다 실행
    {
        FLAG=1;  // FLAG 변수를 1로 설정
        count +=1;  // 각도 배열의 인덱스를 증가시킴
        if(count>=7)  // 인덱스가 배열의 크기를 넘으면
            count = 0;  // 인덱스를 0으로 리셋 (배열의 처음으로 돌아감)
    }
}

main

int main(void)
{

  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);  // 타이머 2의 채널 1에서 PWM 신호 생성 시작

  while (1)  // 무한 루프
  {
    /* USER CODE BEGIN 3 */
      if(FLAG==1){  // FLAG가 1이면 (2초마다 발생)
          FLAG = 0;  // FLAG를 다시 0으로 리셋
          dg = deg[count];  // 현재 인덱스(count)에 해당하는 각도를 dg에 저장
          SERVO_Angle(dg);  // 해당 각도로 서보 모터를 이동시킴
          HAL_Delay(1000+dg);  // 1000ms + 각도 값만큼 추가로 딜레이
      }
      __NOP();  // 아무 작업도 하지 않는 NOP(아무 작업 없음) 명령어
  }
  /* USER CODE END 3 */
}
MX_TIM10_Init(); //타이머 10번 초기화

HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1); //PWM 출력

TIM10->CCR1 = 1500; //100넣으면 0.12 -> 1500 = 0.3

Passive 부저

부저 수동형 / 능동형 구분

능동형 타입 : 전원 인가시 정해져 있는 주파수의 비프음
패시브 타입 : 입력된 신호의 주파수에 따라 해당하는 비프음 출력

구동전원 : 3.3V ~ 5V
펄스주기 : 음계주파수
펄스폭 : 항상 50%

설정 (500Hz)
CLK = 84Mhz -> 84Mhz / 500 = 168000
PSC 168-1
ARR 1000-1
pulse 500 (50%)

PWM 주기변경
TIM2->ARR = 500;	//주파수 500Hz 설정
TIM2->CCR1 = TIM2->ARR / 2;		//Duty비 50%

예제코드

/* USER CODE BEGIN PV */
// 음계에 해당하는 주파수 값을 정의하는 열거형(enum)입니다.
// 각 음계는 특정 주파수 값으로 표현됩니다.
// 이 값들은 타이머의 ARR(오토 리로드 레지스터)에 사용될 주기를 결정합니다.
enum notes {
    C = 956,  // C 음에 해당하는 주기 값 (주파수에 따른 계산된 값)
    D = 852,  // D 음에 해당하는 주기 값
    E = 758,  // E 음에 해당하는 주기 값
    F = 716,  // F 음에 해당하는 주기 값
    G = 638   // G 음에 해당하는 주기 값
};
/* USER CODE END PV */

/* USER CODE BEGIN 2 */
// 멜로디를 저장하는 배열입니다. 배열의 각 요소는 위에서 정의한 음계에 해당합니다.
// 예: 첫 번째 음은 G, 두 번째 음은 E, 세 번째 음도 E입니다.
uint16_t bell[] = {G, E, E, F, D, D, C, D, E, F, G, G, G, G, E, E, E, F, D, D, C, E, G, G, E, E, E,
                  D, D, D, D, D, E, F, E, E, E, E, E, F, G, G, E, E, E, F, D, D, C, E, G, G, E, E, E};

// 멜로디의 길이를 계산하여 저장합니다. bell 배열의 총 요소 수를 의미합니다.
uint8_t bell_length = sizeof(bell)/sizeof(uint16_t);

// 각 음 사이의 간격(지연 시간)을 정의하는 배열입니다.
// 값은 ms 단위의 시간으로, 각 음 사이의 지연 시간을 나타냅니다.
uint8_t interval[] = {10, 10, 100, 10, 10, 10, 10, 10, 10, 10, 10, 10, 244,
          10, 10, 10, 10, 10, 10, 150, 10, 10, 10, 10, 10, 10, 244,
          10, 10, 10, 10, 10, 10, 244,
          10, 10, 10, 10, 10, 10, 244,
          10, 10, 10, 10, 10, 10, 150, 10, 10, 10, 10, 10, 10, 244};

// 타이머 2의 채널 4에서 PWM 신호 출력을 시작합니다.
// PWM 신호는 음을 생성하는 데 사용됩니다.
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
/* USER CODE END 2 */
while (1)
{
    // 멜로디의 각 음을 순서대로 재생하는 루프입니다.
    for(int i = 0; i < bell_length; i++) {
        // 타이머 2의 ARR(오토 리로드 레지스터)에 현재 음에 해당하는 주기를 설정합니다.
        TIM2->ARR = bell[i];
        
        // CCR4 레지스터를 ARR 값의 절반으로 설정하여 PWM 듀티 사이클을 50%로 만듭니다.
        // 이는 음을 출력하기 위해서입니다.
        TIM2->CCR4 = TIM2->ARR / 2;
        
        // 음을 재생하기 위해 500ms 동안 대기합니다.
        HAL_Delay(500);
        
        // PWM 신호를 중지하기 위해 CCR4 값을 0으로 설정합니다.
        TIM2->CCR4 = 0;
        
        // 다음 음 사이의 간격만큼 대기합니다.
        HAL_Delay(interval[i]);
        
        // 다음 음을 위해 다시 PWM 신호를 켭니다.
        TIM2->CCR4 = TIM2->ARR / 2;
    }
}

DC모터 구동

구동전원 : 4.5V ~ 15V
펄스주기 : 특성 없음
펄스폭 : 0~100% 모터속도비례.
특징 : 입력신호에 따라 모터 회전방향 결정

3pin(Ain) H -> 2pin(Bin) L - 정방향
3pin(Ain) L -> 2pin(Bin) H - 역방향
3pin(Ain) H -> 2pin(Bin) H - 모터정지
3pin(Ain) L -> 2pin(Bin) L - 모터오픈

설정 (100회)
CLK = 84Mhz -> 84Mhz / 100 = 840000
PSC 84-1
ARR 10000-1
PWM Genaration Channel 1
pulse 5000 (50%)
PWM Genaration Channel 1
pulse 0 (0%)

실시간 PWM 주기 변경

arr이 0부터 2500까지 1씩 증가
arr 2501에서 1000까지 1씩 감소

  uint16_t ccr = 0;
  uint16_t arr = 1000;
  uint8_t ud_flag = 0;


  while (1)
  {
	  if(ud_flag ==0)
	  {
		  arr++;
		  if(arr >= 2500) ud_flag =1;
	  }
	  else
	  {
		  arr--;
		  if(arr<=1000) ud_flag =0;
	  }
	  TIM2->ARR = arr;
	  TIM2->CCR1 = TIM2->ARR / 2;

psc가 0부터 2500까지 1씩 증가.
psc 2501에서 1000까지 1씩 감소.
단 펄스폭 resoultion 168배 감소

  uint16_t ccr = 0;
  uint16_t psc = 1000;
  uint8_t ud_flag = 0;


  while (1)
  {
	  if(ud_flag ==0)
	  {
		  psc++;
		  if(psc >= 2500) ud_flag =1;
	  }
	  else
	  {
		  psc--;
		  if(psc<=1000) ud_flag =0;
	  }
	  TIM2->PSC = psc;
	  TIM2->CCR1 = TIM2->PSC / 2;

Ref
https://louie0724.tistory.com/377
https://velog.io/@chaeyoonl/STM32-PWM
https://deepbluembedded.com/stm32-pwm-example-timer-pwm-mode-tutorial/#stm32-pwm-introduction
https://velog.io/@sang_yun_911/STM32-PWM
https://pineenergy.tistory.com/51
https://www.youtube.com/watch?v=haWC4s2s90c&list=PLUaCOzp6U-RqMo-QEJQOkVOl1Us8BNgXk&index=11
https://m.blog.naver.com/sikwon1/222072294829

profile
기록과 분석, 이해

0개의 댓글