一、实验目标
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;}四、核心知识点
- 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
- 按键消抖原理
机械按键在按下和松开的瞬间,由于金属弹片的物理抖动,电平信号会在短时间内产生多次不稳定的跳变(通常持续 5~10ms)。如果不做处理,一次按键可能会被单片机误判为多次触发。本实验采用软件延时消抖的方式,在Key_GetNum()函数中实现。
消抖流程:
首次检测:读取 PA9 引脚电平,若为低电平(按键按下,上拉输入默认高电平),进入下一步;
延时确认:延时 10ms 跳过抖动期,再次读取 PA9,若仍为低电平,则确认按键确实被按下;
等待释放:while循环等待按键松开(电平恢复高电平),防止长按导致重复触发;
再次延时:松开后再延时 10ms,跳过松开瞬间的抖动;
返回按键值:返回 1,主函数中据此执行speed += 30等操作。
小结:这种「检测 → 延时 → 再检测 → 等待释放 → 再延时」的软件消抖方法简单可靠,不依赖额外硬件,且可方便地移植到其他按键工程中。
占空比 = CCR / 100 × 100%,通过修改 CCR(即pwm_setcompare传入的speed值)即可调节 LED 亮度或电机转速。
五、实验总结
本实验围绕 STM32F103 的 PWM 输出与按键控制,完整走通了「定时器配置 → PWM 波形生成 → 电机驱动 → 按键交互」的嵌入式开发链路,主要收获如下:
深入理解 PWM 原理:掌握了占空比、频率、预分频系数(PSC)、自动重装载值(ARR)和比较值(CCR)等核心概念,并能通过公式
PWM 频率 = 定时器时钟 /((ARR + 1)×(PSC + 1))和占空比 = CCR /(ARR + 1)× 100%独立计算波形参数。本实验配置 ARR = 99、PSC = 35,得到 20kHz 的 PWM 频率,满足电机与 LED 的驱动需求。掌握定时器 PWM 输出配置流程:通过 TIM2 的通道 3 和 TIM3 的通道 4 分别输出两路 PWM,熟悉了 GPIO 复用推挽模式、时基初始化、输出比较初始化以及
TIM_SetCompare动态修改占空比的方法,为后续多路电机控制打下基础。学会按键读取与软件消抖:采用「检测 → 延时 → 再检测 → 等待释放 → 再延时」的软件消抖方案,有效避免了机械按键抖动带来的误触发,代码简单可靠且易于移植到其他工程。
掌握 TB6612 电机驱动模块的使用:理解了 AIN1/AIN2、BIN1/BIN2 控制电机方向、PWMA/PWMB 控制转速的接线方式,并通过
motor_speed()中正负速度判断实现正反转控制。完成系统联调:主函数中通过按键每按一次
speed += 30,超过 180 后归零,实现电机转速的循环调节,并在 OLED 上实时显示当前速度,验证了 PWM 调速与按键交互的完整闭环。
不足与改进方向:当前按键消抖采用阻塞式延时,会占用 CPU 时间,后续可改用定时器扫描或外部中断 + 状态机的方式提升实时性;同时可将 PWM 频率与占空比参数宏定义化,提高代码的可读性与可维护性。