【STM32】定时器 —— 输出比较&PWM

使用的单片机机型为STM32F103C8T6

文章目录

PWM

对于5V电路来说,输出只有高电平5V和低电平0V,控制LED灯就是点亮和熄灭,但如果想要控制其亮度呢?这就需要PWM

PWM

PWM (Pulse Width Modulation) 脉冲宽度调制

具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常用于电机控速等领域

拿LED灯举例

当输出高电平时,LED灯点亮;当输出低电平时,LED灯熄灭。但如果在一定周期内,比如1ms内,前100us高电平,后900us低电平,那么在人眼看来,LED灯就是很低的亮度。随着高电平所占时间增多,LED亮度就会增大

换成电机,由于惯性,从高电平转为低电平,电机并不会立刻停止,同样在较短时间内分配高低电平的比率就可以实现不同档位的电机


PWM参数

  • Ts:周期,如1ms
  • Ton:高电平时间,如400us
  • Toff:低电平时间,如600us

频率 = 1 / Ts = 1KHz
占空比 :高电平时间占周期的比率。Ton / Ts = 40%
分辨率:1 / (计数器最大值 + 1)


输出比较

定时器输出比较模块如下

OC (Output Compare) 输出比较

通过比较 CNT 和 CCR 寄存器值的关系,对输出电平置1、置0 或翻转的操作,用于输出一定频率和占空比的 PWM 波形

通用定时器和高级定时器都拥有4个输出比较通道,如上图的 TIMx_CH1 ~ TIMx_CH4

高级定时器的前3个通道额外拥有死去生成和互补输出的功能

输出部分电路如下:

通过 CNT 和 CCR比较控制输出使能电路

例如:当CNT < CRR 时,输出高电平,CNT >= CRR 时输出低电平。

配合时基单元 (参看【STM32】定时中断),即可输出PWM。

如定时器频率为1MHz,即每1微秒CNT计数加一,设置重装值ARR = 100,总周期为100us,比较器CRR = 40。

CNT计数前40us都输出高电平,后60us输出低电平。如此就输出了频率为10KHz,占空比为40%,分辨率为1%


输出比较模式

常用的为 PWM模式1 和 PWM模式2,二者效果相反,根据实际场景选择

编程实例

输出比较呼吸灯

  • 程序目标:通过定时器输出比较实现呼吸灯(亮度周期变化)
  • 程序原理:每20ms修改CRR值,输出不同PWM波形

接线图如下:

通过引脚定义得知,TIM2 的 CH1输出引脚为 A0

同时,将LED灯的正极接在输出引脚上,方便占空比的计算

PWM基本结构

PWM使用步骤:

  1. 开启相关外设时钟
c 复制代码
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  1. 定时器使用内部时钟源72MHz
c 复制代码
//时钟源使用内部时钟源,72MHz
TIM_InternalClockConfig(TIM2);
  1. 配置GPIO,注意引脚使用复用推挽输出
c 复制代码
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
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);
  1. 配置时基单元,频率为1KHz,分辨率为1%
c 复制代码
//时基单元
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_TimeBaseStructInit(&TIM_InitStructure);
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
TIM_InitStructure.TIM_Period = 100 - 1;						//重装值
TIM_InitStructure.TIM_Prescaler = 720 - 1;					//预分频
TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
  1. 配置输出比较,通过修改CCR值改变占空比
c 复制代码
//输入/比较器模块
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
//配置
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;								//比较值,即CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
  1. 启动定时器
c 复制代码
//开启时钟
TIM_Cmd(TIM2, ENABLE);

完整代码如下:

c 复制代码
/**
  * @brief		初始化PWM功能
  * @parm		无
  * @retval		无
  */
void PWM_Init(void)
{
	//开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//时钟源使用内部时钟源,72MHz
	TIM_InternalClockConfig(TIM2);
	
//	//引脚重定义
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);		//TIM2引脚重定向
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);	//关闭A15的调试接口,使其变成普通引脚使用
	
	//初始化GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	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_TimeBaseInitTypeDef TIM_InitStructure;
	TIM_TimeBaseStructInit(&TIM_InitStructure);
	TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
	TIM_InitStructure.TIM_Period = 100 - 1;						//重装值
	TIM_InitStructure.TIM_Prescaler = 720 - 1;					//预分频
	TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
	TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
	
	//输入/比较器模块
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
	//配置
	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;								//比较值,即CCR
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	//开启时钟
	TIM_Cmd(TIM2, ENABLE);
}

同时提供修改CRR值的函数

c 复制代码
/**
  * @brief		设置比较值
  * @parm		Compare:比较值  范围:0 ~ 255
  * @retval		无
  */
void PWM_SetCompare(uint8_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

主程序每隔20ms修改CRR值,实现LED灯由 熄灭 -> 亮度增大 -> 完全点亮 -> 亮度减小 -> 熄灭 的循环

c 复制代码
int main()
{
	PWM_Init();
	while(1)
	{
		for(uint8_t i = 0; i < 100; ++i)
		{
			PWM_SetCompare(i);
			Delay_ms(20);//延迟20ms
		}
		for(uint8_t i = 0; i < 100; ++i)
		{
			PWM_SetCompare(100 - i);
			Delay_ms(20);//延迟20ms
		}
	}
}

完整程序链接:【STM32】输出比较PWM

舵机转向

舵机是一种根据输入PWM信号占空比来控制输出角度的装置

输入PWM信号要求:周期为20ms,高电平宽度为0.5ms ~ 2.5ms

硬件电路如下:

接线图如下:舵机需要5V驱动,正极接STLINK

PWM初始化大抵相同,需要注意 ARR、PSC 的设置

c 复制代码
/**
  * @brief		初始化PWM功能
  * @parm		无
  * @retval		无
  */
void PWM_Init(void)
{
	//开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
	
	//初始化GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//注意使用复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//内部时钟
	TIM_InternalClockConfig(TIM2);
	
	//时基单元
	TIM_TimeBaseInitTypeDef TIM_InitStructure;
	TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
	TIM_InitStructure.TIM_Period = 20000 - 1;					//重装值,计数周期
	TIM_InitStructure.TIM_Prescaler = 72 - 1;					//预分频
	TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
	TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
	
	//输入/比较器模块
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
	//配置
	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;								//比较值,即CCR
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);
	
	//开启时钟
	TIM_Cmd(TIM2, ENABLE);
}

此处设置 PSC = 72,ARR = 20000

频率为50Hz,即周期为20ms

-90° 为0.5ms,对应CRR为500,-45°对应为1000...

c 复制代码
/**
  * @brief		设置舵机角度  -90为0.5ms,对应CRR为500,-45°对应为1000.....	
  * @parm		角度   范围:0 ~ 180
  * @retval		无
  */
void Servos_SetAngle(float Angle)
{
	PWM_SetCompare(Angle / 180 * 2000 + 500);
}

完整程序链接:

PWM控制直流电机

直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转

直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作

TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

硬件电路

PWMA 和 PWMB 接收PWM波形

AIN1、AIN2 和 BIN1、BIN2控制电机正反转

定义AIN1高电平,AIN2低电平为正转,则AIN1低电平,AIN2高电平为反转

接线图如下

PWM配置

c 复制代码
/**
  * @brief		初始化PWM功能
  * @parm		无
  * @retval		无
  */
void PWM_Init(void)
{
	//开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
	
	//初始化GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//注意使用复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//内部时钟
	TIM_InternalClockConfig(TIM2);
	
	//时基单元
	TIM_TimeBaseInitTypeDef TIM_InitStructure;
	TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
	TIM_InitStructure.TIM_Period = 100 - 1;						//重装值,计数周期
	TIM_InitStructure.TIM_Prescaler = 36 - 1;					//预分频
	TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
	TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
	
	//输入/比较器模块
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
	//配置
	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;								//比较值,即CCR
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);
	
	//开启时钟
	TIM_Cmd(TIM2, ENABLE);
}

还需要配置 A4 和 A5引脚控制电机正反转

c 复制代码
/**
  * @brief		舵机初始化
  * @parm		无
  * @retval		无
  */
void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	//初始化输出高低电平的引脚
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	PWM_Init();
}

/**
  * @brief		设置舵机角度  0为500,45为1000,90为1500....	
  * @parm		角度   范围:0 ~ 180
  * @retval		无
  */
void Motor_SetSpeed(int16_t Speed)
{
	if(Speed >= 0)//正转
	{
		//设置GPIO引脚高低电平
		GPIO_SetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		//设置占空比,输出PWM波形
		PWM_SetCompare(Speed);
	}
	else//反转
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare(-Speed);
	}
}

完整程序链接:


以上就是本篇博客的所有内容,感谢你的阅读

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。

相关推荐
工业甲酰苯胺1 小时前
C语言之输入输出
c语言·c++·算法
余额不足121383 小时前
C语言基础六:循环结构及面试上机题
c语言·开发语言
嗯? 嗯。3 小时前
工作bug,keil5编译器,理解int 类型函数返回值问题,详解!!!
c语言·return·keil5编译器·整数返回类型函数
legendary_1637 小时前
LDR6500:音频双C支持,数字与模拟的完美结合
c语言·开发语言·网络·计算机外设·电脑·音视频
一行玩python7 小时前
Xerces-C,一个成熟的 C++ XML 解析库!
xml·c语言·开发语言·c++
CHENWENFEIc7 小时前
基础排序算法详解:冒泡排序、选择排序与插入排序
c语言·数据结构·算法·排序算法·学习方法·冒泡排序
yangpipi-8 小时前
数据结构(C语言版)-4.树与二叉树
c语言·开发语言·数据结构
qystca9 小时前
洛谷 P8824 [传智杯 #3 初赛] 终端 C语言
c语言·开发语言
橘颂TA9 小时前
C语言:编译与链接
c语言·开发语言
炸鸡配泡面9 小时前
12.10 C语言作业3
c语言·c++·算法