PWM (Pulse Width Modulation) 의 약자
정보에 따라 펄스의 폭을 변화시켜 전달하는 방식이다.
신호의 크기가 크면 펄스 폭을 크게 하고 신호의 크기가 작으면 펄스 폭을 작게 하여 정보를 전달한다.
펄스 폭의 크기에 따라 평균 파워를 변화시킬 수 있다.
LED ON/OFF 제어, 밝기 제어, DC 서보 모터 등에 사용된다.
간단히 설명하자면, 일정한 주기 내에서 Duty비를 변화 시켜서 평균 전압을 제어하는 방법!
기본은 Timer 인터럽트에 배경을 두고 있음
Prescale을 이용하여 HCLK를 일정 줄여줌
AutoReload Register를 이용하여 해당 값에 도달하면 인터럽트가 발생하며 다시 처음으로 돌아감.
타이머 인터럽트 주기 만큼 PWM의 주기가 됨.
아래 그림과 같이 타이머가 Up Count 하는 동안
CCR(Capture Compare Register)을 통해 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임을 알 수 있다.
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 주기
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번
//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초 딜레이 후 다음 사이클 진행
}
}
SG90

※ CW는 시계방향의 회전, CCW는 시계반대방향의 회전
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
부저 수동형 / 능동형 구분
능동형 타입 : 전원 인가시 정해져 있는 주파수의 비프음
패시브 타입 : 입력된 신호의 주파수에 따라 해당하는 비프음 출력
구동전원 : 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;
}
}
구동전원 : 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%)
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