输出波形:

定时器波形步骤

第一步: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 的工作逻辑如下:
  1. 计数器从0开始,按定时器的计数时钟频率向上递增;
  2. CNT == ARR时,触发更新事件(Update Event),同时计数器自动清零,重新从 0 开始计数;
  3. 上述过程循环往复,形成固定的计数周期,这一周期就是定时器的基础定时周期

CCR捕获/比较值CCR(捕获比较寄存器)):决定高电平持续的时间。在一个周期内,计数器从0数到 CCR 之前,输出的是高电平。

PSC:决定了时基单元的计数时间有多久

PWM 占空比  = CCR 值 / (ARR 值 + 1) × 100%(向上计数模式),这是 PWM 应用中最核心的公式。表示在一个完整的PWM周期里,高电平占了多少比

PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1)

例:

假设你配置了 ARR = 999CCR = 300(向上计数模式):
  • 总步数999 + 1 = 1000 步(从0到999)。
  • 高电平持续时间:从 CNT = 0 到 CNT = 299,也就是 300 步
  • 占空比 = 300 / 1000 = 30%

假设:PSC = 71ARR = 99,机器频率 = 72MHz
  1. 机器频率:72MHz
  2. 计数频率:72MHz / (71 + 1) = 1MHz(也就是每1微秒数一次)
  3. 一个PWM周期:99 + 1 = 100次计数
  4. PWM周期时间:100次 × 1微秒 = 100微秒
  5. 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的值
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐