STM32——PWM驱动舵机和直流电机、测量PWM频率和占空比、旋转编码器测速(十二)

一、PWM驱动舵机

通过按键控制舵机的旋转角度,现象是按一下按键舵机旋转30°,就是通过按键去变化PWM的占空比。

接线图
配置PWM波形
复制代码
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	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);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	TIM_InternalClockConfig(TIM2);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;//自动重装ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//预分频
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器,高级定时器独有。PSC
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清断中断标记,以解决TIM_TimeBaseInit带来的中断
	
	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);

整体流程遵循时钟使能 → 外设引脚初始化 → 定时器基础配置 → PWM 输出通道配置 → 定时器使能的 STM32 外设配置通用逻辑,每一步的作用和关联关系如下:

  1. 使能 GPIOA 时钟,为 PA1 引脚的功能配置做准备;
  2. 初始化 GPIOA_Pin1 ,配置为复用推挽输出模式(AF_PP),因为定时器 PWM 属于 GPIO 复用功能,推挽模式保证输出驱动能力,同时配置 50MHz 输出速度;
  3. 使能 TIM2 定时器时钟(TIM2 挂在 APB1 总线,需单独使能);
  4. 配置 TIM2 时钟源 ,使用内部时钟(STM32 通用定时器默认也是内部时钟,此句可省略,但显式配置更规范);
  5. 初始化 TIM2 时基单元 ,配置预分频、自动重装值、计数模式等核心参数,决定 PWM 的频率
  6. 清除 TIM2 更新标志位,避免时基初始化时产生的更新中断误触发(若开启了更新中断,此步必须有);
  7. 初始化 TIM2_OC2(通道 2) ,先通过TIM_OCStructInit初始化 PWM 结构体默认值,再配置 PWM 模式、输出极性、输出使能、占空比寄存器值,决定 PWM 的占空比和输出特性;
  8. 使能 TIM2 定时器,定时器开始计数,PA1 引脚正式输出 PWM 波。

引脚对应表

快速修改 TIM2 通道 2 的 CCR2 寄存器值

void PWM_SetCompare2(uint16_t Compare)

{

TIM_SetCompare2(TIM2,Compare);

}

然后把0~180° 的角度值转换成了舵机对应的 PWM 占空比参数(CCR2 值)

void Servo_SetAngle(float Angle)

{

PWM_SetCompare2(Angle / 180 * 2000 + 500);

}

这里的0°就是上面的-90°

CCR/ARR == 占空比

按键控制舵机的旋转角度

通过按键触发舵机角度每次 + 30°,超过 180° 则重置为 0°

复制代码
while(1)
	{
		keyNum = Key_GetNum();
		if(keyNum == 1)
		{
			Angle += 30;
			if(Angle > 180)
			{
				Angle = 0;
			}
		}
		Servo_SetAngle(Angle);
		OLED_ShowNum(1,7,Angle,3);
	}	

二、PWM驱动直流电机

和上面一样通过按键控制PWM占空比。

接线图
配置PWM

基本和上面一样,改用TIM2的通道3,对应的GPIO口是PA2,然后是为了方便配置占空比,直接将ARR配置为100。

复制代码
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	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);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	TIM_InternalClockConfig(TIM2);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//自动重装ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;//预分频
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器,高级定时器独有。PSC
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清断中断标记,以解决TIM_TimeBaseInit带来的中断
	
	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);
配置驱动模块
复制代码
    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();

把 PA4、PA5 配置成通用推挽输出,作为电机驱动模块的方向控制引脚。将PWM的驱动函数放在最后,调用时就不需要单独调用。

电机转动方向及转速
复制代码
void Motor_SetSpeed(int8_t Speed)
{
	if(Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(-Speed);
	}
		
}
  • 正数(Speed > 0) :执行正转电平配置(PA4 高、PA5 低),直接用正数设置 PWM 占空比(转速);
  • 负数(Speed < 0) :执行反转 电平配置(PA4 低、PA5 高),取负数的绝对值-Speed设置 PWM 占空比(保证占空比为正);
  • 零(Speed = 0) :进入正数分支,电平为 PA4 高、PA5 低,但 PWM 占空比为 0,电机停止转动(刹车)。

调用

复制代码
int main(void)
{
	OLED_Init();
	Motor_Init();
	key_Init();
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		keyNum = key_GetNum();
		if(keyNum == 1)
		{
			Speed += 20;
			if(Speed > 100)
			{
				Speed = -100;
			}	
			Motor_SetSpeed(Speed);
			OLED_ShowSignedNum(1,7,Speed,3);
		}
	}	
}

三、测量PWM频率、占空比

接线图
配置PWM

让输出的PWM的频率为1MHZ。

复制代码
    TIM_InternalClockConfig(TIM2);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//自动重装ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//预分频
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器,高级定时器独有。PSC

使用TIM_PrescalerConfig函数去修改PSC,以改变PWM的频率。

复制代码
void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);
}
配置输入捕获
复制代码
void IC_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	TIM_InternalClockConfig(TIM3);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//自动重装ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//预分频psc
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器,高级定时器独有。PSC
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter = 0XF;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	
	TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);	//此函数会自动把剩下的一个通道初始化为相反的配置,此函数仅支持通道1和通道2
			
	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
	
	TIM_Cmd(TIM3, ENABLE);
}

选择内部时钟,用TIM3。将PSC配置为72,将计数频率配置为1MHZ。

为 TIM3 通道 1 设置捕获规则,所有配置围绕「精准、抗干扰捕获外部上升沿」设计;

关键:TIM_ICFilter=0XF是最大滤波,适合工业环境 / 杂波多的场景,若外部 PWM 信号干净,可减小滤波值(如 0x4)提升响应速度。

PWMI 模式开启(代码灵魂,自动配置双通道)

复制代码
TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);

如果是只测量频率,不用进行这一步。这个的作用是基于通道 1 的配置,自动初始化通道 2 为相反捕获配置,无需手动写通道 2 的代码:

  • 通道 1(TIM3_Channel1):保持配置 → 捕获上升沿(记录 PWM 周期的起始 / 结束);
  • 通道 2(TIM3_Channel2):自动配置 → 捕获下降沿(记录 PWM 高电平的结束);
  • 硬件特性:两个通道的捕获值会自动存入CCR1CCR2寄存器,CPU 只需按需读取,无需干预;
  • 限制:STM32 硬件仅支持通道 1/2组成 PWMI 配对,通道 3/4 不支持。

从模式

TIM_TS_TI1FP1:指定「TIM3 通道 1 滤波后的上升沿」作为定时器的触发源;

TIM_SlaveMode_Reset:复位从模式 → 每当检测到上述触发源(上升沿),定时器的计数器 CNT 会被硬件自动复位为 0,并同时将当前 CNT 值存入 CCR1 寄存器

转换逻辑

这个使用的是测周法,计数频率是1MHZ,除以CNT1就是PWM的频率。

uint32_t IC_GetFreq(void)

{

return 1000000 / (TIM_GetCapture1(TIM3) + 1);

}

占空比 =(CNT2 + 1)/(CNT2 + 1)

uint32_t IC_GetDuty(void)

{

return (TIM_GetCapture2(TIM3) + 1)*100 / (TIM_GetCapture1(TIM3) + 1);

}

调用
复制代码
int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1,1,"Freq:00000Hz");
	OLED_ShowString(2,1,"Duty:00%");
	
	PWM_SetPrescaler(72 - 1);		//Freq = 72M / (psc + 1) / 100
	PWM_SetCompare1(20);			//Duty = ccr / 100
		
	while(1)
	{
		OLED_ShowNum(1,6,IC_GetFreq(),5);
		OLED_ShowNum(2,6,IC_GetDuty(),2);
	}	
}

四、旋转编码器测速

接线图
配置GPIO

将PA6、PA7配置为上拉输入。

复制代码
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM3 时钟使能 + 时基单元配置

将PSC配置为0,不要分频。

复制代码
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//自动重装ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//预分频psc
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器,高级定时器独有。PSC
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM3 双通道输入捕获配置
复制代码
    TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter = 0XF;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICFilter = 0XF;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
  • 核心:为 TIM3 的两个通道分别配置输入捕获规则,作为编码器模式的硬件基础(编码器模式本质是输入捕获的特殊应用);
  • 关键:TIM_ICStructInit(&TIM_ICInitStructure):初始化捕获结构体为默认值,避免未赋值的成员出现脏数据,导致配置失败;
  • 滤波 & 极性:两个通道均设最大滤波(抗编码器杂波)、上升沿捕获,后续编码器模式会基于此极性实现边沿检测。
编码器接口模式配置(代码灵魂,开启硬件计数)
复制代码
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

这个的作用是将 TIM3 从普通定时器转换为编码器专用计数器

TIM_EncoderMode_TI12编码器模式 1同时检测 TI1(A 相)和 TI2(B 相)的边沿,A/B 相每一个有效边沿都会触发计数器加 / 减 1,计数精度最高(编码器最常用模式)

使能 TIM3,开始编码器计数
复制代码
TIM_Cmd(TIM3, ENABLE)
读取CNT

将读取到的CNT值放在Temp,然后将CNT清零,最后将Temp返回。

复制代码
int16_t Encoder_Get(void)
{
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3,0);
	return Temp;
}
调用

将读取CNT的函数放在定时中断函数里,定时去更新CNT的数值,就是在1内旋转编码器旋转了多少,这个数值和速度相同。

复制代码
int main(void)
{
	OLED_Init();
	Encoder_Init();
	Timer_Init();

	OLED_ShowString(1, 1, "Speed:");
	
	while(1)
	{
		OLED_ShowSignedNum(1,7,Speed,5);
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		Speed = Encoder_Get();
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
相关推荐
VekiSon2 小时前
Linux内核驱动——Ubuntu 网络启动环境配置与操作
linux·arm开发·嵌入式硬件·ubuntu
范纹杉想快点毕业2 小时前
嵌入式实时系统架构设计:基于STM32与Zynq的中断、状态机与FIFO架构工程实战指南,基于Kimi设计
c语言·c++·单片机·嵌入式硬件·算法·架构·mfc
恒锐丰小吕2 小时前
屹晶微 EG2003 中压200V半桥驱动芯片技术解析
嵌入式硬件·硬件工程
wypywyp2 小时前
7.stm32 江协科技笔记2
笔记·科技·stm32
神一样的老师2 小时前
【RT-Thread Titan Board 开发板】RGB LCD屏测试
单片机·嵌入式硬件
周周记笔记3 小时前
ESP32-S3:工程配置(十二)
单片机·嵌入式硬件
想放学的刺客3 小时前
单片机嵌入式系统试题(第28期)flash芯片各引脚作用?低功耗设计估算电池续航时间是多少?如何优化低功耗等项目经验
stm32·单片机·嵌入式硬件·mcu·物联网·51单片机
WuLaHH3 小时前
可靠UDP协议RUDP
单片机·网络协议·udp
宵时待雨4 小时前
STM32笔记归纳5:SPI
笔记·stm32·嵌入式硬件