STM32 PWM学习
·
输出波形:

定时器波形步骤
第一步:CNT(计数器)与CCR(捕获比较寄存器)进行比较,如果CNT大于or等于CCR,就会给输出模式控制器传一个信号;
第二步:输出模式控制器就会改变它输出OC1REF的高低电平,REF信号实际上就是信号的高低电平(REF:reference,参考信号);
第三步:CC1P:极性选择寄存器,给0,信号就会走对应的0那一路,信号不变;给1,信号会经过一个非门,极性翻转;
第四步:信号从OC1输出。
控制输出模式
PC1M寄存器:控制输出模式

CNT是计数器(预先设定的) CCR是输入捕获
REF 是 “Reference Signal” 的缩写,它不是芯片上的一个物理引脚,而是定时器内部的一路信号线。
在PWM或输出比较模式下,定时器会根据 CNT(当前计数值)和 CCR(比较值)的比较结果,动态改变 REF 的电平。最后,REF 被送到输出引脚(如 PA0、PA1)上,就变成了你在示波器上看到的那个波形。
PWM实现流程

注:
黄色线:计算器溢出频率(ARR),也是PWM更新频率;
红色线:捕获比较寄存器(CCR)的值;
蓝色线:计数器(CNT)的值。
下图可以看到 时基单元的ARR CNT 还有四个输出比较单元的 CCR之间的关系

PWM的各个寄存器和计算
ARR(Auto-Reload Register,自动重装寄存器)是 STM32 定时器的核心基础寄存器,本质是为定时器计数器(CNT)设定计数上限 / 下限,当计数器值与 ARR 值匹配时,会触发计数器自动复位+更新事件,是实现定时、PWM、脉冲生成等所有定时器功能的时间基准核心,没有 ARR 的定界,计数器会无限制计数,定时器无法实现可控的时序功能。
STM32 定时器的计数器(CNT)默认采用向上计数模式(最常用),结合 ARR 的工作逻辑如下:
- 计数器从0开始,按定时器的计数时钟频率向上递增;
- 当CNT == ARR时,触发更新事件(Update Event),同时计数器自动清零,重新从 0 开始计数;
- 上述过程循环往复,形成固定的计数周期,这一周期就是定时器的基础定时周期。
CCR(捕获/比较值CCR(捕获比较寄存器)):决定高电平持续的时间。在一个周期内,计数器从0数到 CCR 之前,输出的是高电平。
PSC:决定了时基单元的计数时间有多久
PWM 占空比 = CCR 值 / (ARR 值 + 1) × 100%(向上计数模式),这是 PWM 应用中最核心的公式。表示在一个完整的PWM周期里,高电平占了多少比
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
例:
假设你配置了 ARR = 999,CCR = 300(向上计数模式):
- 总步数:999 + 1 = 1000 步(从0到999)。
- 高电平持续时间:从 CNT = 0 到 CNT = 299,也就是 300 步。
- 占空比 = 300 / 1000 = 30%。
假设:PSC = 71,ARR = 99,机器频率 = 72MHz
- 机器频率:72MHz
- 计数频率:72MHz / (71 + 1) = 1MHz(也就是每1微秒数一次)
- 一个PWM周期:99 + 1 = 100次计数
- PWM周期时间:100次 × 1微秒 = 100微秒
- PWM频率:1 / 100微秒 = 10kHz
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint16_t i; //定义for循环的变量
int main(void)
{
OLED_Init();
PWM_Init();
while (1)
{
for (i = 0; i <= 100; i++)
{
/*LED的亮度随占空比线性增加,产生渐亮的效果。
PA0被配成了复用推挽输出(AF_PP),它输出的不是“高”或“低”的静态电平,而是一个“高频率、高电平时间可调”的方波。*/
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(10);
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(10);
}
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
/*GPIO_Mode_AF_PP :引脚的电平不再由CPU控制,而是交给芯片内部的外设(比如TIM2的PWM输出通道)。
让TIM2的PWM波形输出到引脚,或者让USART、I2C、SPI等外设的通信信号输出到引脚。
需要把PA0这个引脚的控制权交给TIM2,让它能把内部产生的PWM信号输出到外部引脚。GPIO_Mode_AF_PP 的作用就是把引脚的控制权交给片上外设。*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);
/*PSC=720 ARR=100 72MHZ/720=0.1MHZ=10us 一个PWM周期:10us * ARR=1000us=1ms 频率 = 1kHz
CCR在下面的OCInit初始化为0 由void PWM_SetCompare1(uint16_t Compare) 设置,并且在main中不断调节*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 720- 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
/*OC:Output Compare (输出比较)*/
TIM_OCInitTypeDef TIM_OCInitStructure;
/*结构体初始化,若结构体没有完整赋值 ,则最好执行此函数,给结构体所有成员都赋一个默认值*/
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; //输出极性,选择为高
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
/*初始的CCR设置为0 ,呼吸灯是通过函数 void PWM_SetCompare1(uint16_t Compare) 来调节占空比 */
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC1Init(TIM2, &TIM_OCInitStructure); ////将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR的值
}
更多推荐




所有评论(0)