news 2026/9/4 7:28:22

STM32 12 PWM驱动双电机

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 12 PWM驱动双电机

一、实验目标
1.学习调节PWM的定义和原理,理解占空比和频率的计算原理,理解预分频系数的意义;
2. 配置STM32通用定时器(TIM2)和(TIM3)输出PWM波形,驱动LED实现亮度调节;
3. 熟悉按键的读取数据和消抖功能,能移植到其他的工程上;
4. 学会电机驱动模块TB6612模块。

二、硬件接线说明
TB6612模块:

PWMA和PWMB接定时器2,3的输出口A2和B1;AIN1,ANI2接A4和A5;BIN1,BIN2接A0和A1;VM接5V供电口;VCC接3.3伏正极;GND接共同的负极。

按键接A9,采取上拉输入。

三、代码块
main.h

#include"stm32f10x.h"// Device header#include"delay.h"#include"OLED.h"#include"motor.h"#include"motor2.h"#include"Key.h"uint16_tspeed=0;intmain(){OLED_Init();motor_init();motor2_init();Key__init();OLED_ShowString(1,1,"speed:");while(1){if(Key_GetNum()==1){speed+=30;if(speed>180){speed=0;}}motor_speed(speed);motor2_speed(speed);OLED_ShowNum(1,7,speed,3);Delay_ms(500);}}

pwm.c

#include"stm32f10x.h"// Device headervoidpwm_init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);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);TIM_InternalClockConfig(TIM2);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period=100-1;//ARRTIM_TimeBaseInitStructure.TIM_Prescaler=36-1;//PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);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=50;//CCRTIM_OC3Init(TIM2,&TIM_OCInitStructure);TIM_Cmd(TIM2,ENABLE);}voidpwm_setcompare(uint16_tnum){TIM_SetCompare3(TIM2,num);}

pwm2.c

#include"stm32f10x.h"// Device headervoidpwm2_init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,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(GPIOB,&GPIO_InitStructure);TIM_InternalClockConfig(TIM3);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period=100-1;//ARRTIM_TimeBaseInitStructure.TIM_Prescaler=36-1;//PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);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=50;//CCRTIM_OC4Init(TIM3,&TIM_OCInitStructure);TIM_Cmd(TIM3,ENABLE);}voidpwm2_setcompare(uint16_tnum){TIM_SetCompare4(TIM3,num);}

motor.c

#include"stm32f10x.h"// Device header#include"pwm.h"voidmotor_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct1;GPIO_InitStruct1.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStruct1.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5;GPIO_InitStruct1.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct1);pwm_init();}voidmotor_speed(int8_tspeed){if(speed>=0){GPIO_SetBits(GPIOA,GPIO_Pin_4);GPIO_ResetBits(GPIOA,GPIO_Pin_5);pwm_setcompare(speed);}else{GPIO_SetBits(GPIOA,GPIO_Pin_5);GPIO_ResetBits(GPIOA,GPIO_Pin_4);pwm_setcompare(-speed);}}

motor2.c

#include"stm32f10x.h"// Device header#include"pwm2.h"voidmotor2_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct1;GPIO_InitStruct1.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStruct1.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;GPIO_InitStruct1.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct1);pwm2_init();}voidmotor2_speed(int8_tspeed){if(speed>=0){GPIO_SetBits(GPIOA,GPIO_Pin_1);GPIO_ResetBits(GPIOA,GPIO_Pin_0);pwm2_setcompare(speed);}else{GPIO_SetBits(GPIOA,GPIO_Pin_0);GPIO_ResetBits(GPIOA,GPIO_Pin_1);pwm2_setcompare(-speed);}}

key.c

#include"stm32f10x.h"// Device header#include"Delay.h"voidKey__init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);}uint8_tKey_GetNum(void){if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9)==0){Delay_ms(10);if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9)==0){while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9)==0);Delay_ms(10);return1;}}return0;}

四、核心知识点

  1. PWM(脉冲宽度调制)原理

PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过改变高电平在一个周期内所占的比例(即占空比)来模拟输出不同大小电压或功率的数字控制技术。它利用微控制器的高速开关特性,以固定频率输出方波,通过调节高电平的持续时间(脉宽)来控制负载的平均功率。

核心概念:

占空比(Duty Cycle):高电平时间占整个周期的百分比。占空比越大,平均输出电压越高。例如占空比为 50% 时,平均电压为电源电压的一半。
频率(Frequency):单位时间内 PWM 波形的周期数,频率 = 1 / 周期。频率越高,控制越平滑,但过高会增加开关损耗。
预分频系数(PSC):对定时器时钟进行分频,决定计数器的计数频率。预分频系数越大,计数越慢,PWM 频率越低。
自动重装载值(ARR:决定计数器的计数上限,即 PWM 周期长度。
比较值(CCR):与计数器值比较,决定高电平的持续时间,即占空比。

计算公式:
PWM 频率 = 定时器时钟频率 /((ARR + 1)×(PSC + 1))
占空比 = CCR /(ARR + 1)× 100%

本实验参数:

本实验使用 STM32F103 的 TIM2 和 TIM3,定时器时钟为 72MHz,ARR = 99,PSC = 35,因此:

  • PWM 频率 = 72MHz /(100 × 36)= 20kHz
  1. 按键消抖原理

机械按键在按下和松开的瞬间,由于金属弹片的物理抖动,电平信号会在短时间内产生多次不稳定的跳变(通常持续 5~10ms)。如果不做处理,一次按键可能会被单片机误判为多次触发。本实验采用软件延时消抖的方式,在Key_GetNum()函数中实现。

消抖流程:

首次检测:读取 PA9 引脚电平,若为低电平(按键按下,上拉输入默认高电平),进入下一步;
延时确认:延时 10ms 跳过抖动期,再次读取 PA9,若仍为低电平,则确认按键确实被按下;
等待释放:while循环等待按键松开(电平恢复高电平),防止长按导致重复触发;
再次延时:松开后再延时 10ms,跳过松开瞬间的抖动;
返回按键值:返回 1,主函数中据此执行speed += 30等操作。

小结:这种「检测 → 延时 → 再检测 → 等待释放 → 再延时」的软件消抖方法简单可靠,不依赖额外硬件,且可方便地移植到其他按键工程中。

占空比 = CCR / 100 × 100%,通过修改 CCR(即pwm_setcompare传入的speed值)即可调节 LED 亮度或电机转速。

五、实验总结

本实验围绕 STM32F103 的 PWM 输出与按键控制,完整走通了「定时器配置 → PWM 波形生成 → 电机驱动 → 按键交互」的嵌入式开发链路,主要收获如下:

  1. 深入理解 PWM 原理:掌握了占空比、频率、预分频系数(PSC)、自动重装载值(ARR)和比较值(CCR)等核心概念,并能通过公式PWM 频率 = 定时器时钟 /((ARR + 1)×(PSC + 1))占空比 = CCR /(ARR + 1)× 100%独立计算波形参数。本实验配置 ARR = 99、PSC = 35,得到 20kHz 的 PWM 频率,满足电机与 LED 的驱动需求。

  2. 掌握定时器 PWM 输出配置流程:通过 TIM2 的通道 3 和 TIM3 的通道 4 分别输出两路 PWM,熟悉了 GPIO 复用推挽模式、时基初始化、输出比较初始化以及TIM_SetCompare动态修改占空比的方法,为后续多路电机控制打下基础。

  3. 学会按键读取与软件消抖:采用「检测 → 延时 → 再检测 → 等待释放 → 再延时」的软件消抖方案,有效避免了机械按键抖动带来的误触发,代码简单可靠且易于移植到其他工程。

  4. 掌握 TB6612 电机驱动模块的使用:理解了 AIN1/AIN2、BIN1/BIN2 控制电机方向、PWMA/PWMB 控制转速的接线方式,并通过motor_speed()中正负速度判断实现正反转控制。

  5. 完成系统联调:主函数中通过按键每按一次speed += 30,超过 180 后归零,实现电机转速的循环调节,并在 OLED 上实时显示当前速度,验证了 PWM 调速与按键交互的完整闭环。

不足与改进方向:当前按键消抖采用阻塞式延时,会占用 CPU 时间,后续可改用定时器扫描或外部中断 + 状态机的方式提升实时性;同时可将 PWM 频率与占空比参数宏定义化,提高代码的可读性与可维护性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 7:27:56

STM32在线升级BootLoader设计:从内存分区到安全跳转的工业级实现

简介:本资源是一套面向嵌入式开发工程师与STM32进阶学习者的在线升级BootLoader完整实现方案,聚焦解决固件远程安全更新这一工业级产品迭代核心需求。压缩包共995个文件,涵盖117个C源文件(含BootLoader主逻辑与Flash擦写驱动&…

作者头像 李华
网站建设 2026/9/4 7:26:50

热门八股-Kafka

Kafka基础与架构1.Kafka是什么?核心定位与核心价值是什么?Kafka是一个分布式消息队列,也可以叫分布式事件流平台。它最常见的用途是做系统解耦、异步处理、削峰填谷、日志采集和实时数据流转。Kafka的核心定位不是“简单发一条消息给消费者”…

作者头像 李华
网站建设 2026/9/4 7:24:48

基于微信小程序的选修课管理系统的设计与实现源码+文档+讲解视频

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/4 7:23:48

向量数据库分片与副本:海量数据下的高可用集群拓扑

向量数据库分片与副本:海量数据下的高可用集群拓扑 当企业知识库与 Agent 长期记忆系统的数据规模从数十万条增长到数千万甚至数亿条高维向量时,单机部署的向量数据库(如单机版 Milvus、Qdrant、Chroma)会不可避免地撞上单机物理内…

作者头像 李华
网站建设 2026/9/4 7:22:47

基于SpringBoot的校园二手物品交易系统(源码+文档+讲解视频)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/4 7:22:21

Claude Code实战:从环境搭建到Skill工具,重构AI编程协作流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华