
一、实验目标1.学习调节PWM的定义和原理理解占空比和频率的计算原理理解预分频系数的意义2. 配置STM32通用定时器TIM2和TIM3输出PWM波形驱动LED实现亮度调节3. 熟悉按键的读取数据和消抖功能能移植到其他的工程上4. 学会电机驱动模块TB6612模块。二、硬件接线说明TB6612模块:PWMA和PWMB接定时器23的输出口A2和B1;AIN1,ANI2接A4和A5;BIN1,BIN2接A0和A1VM接5V供电口VCC接3.3伏正极GND接共同的负极。按键接A9采取上拉输入。三、代码块main.h#includestm32f10x.h// Device header#includedelay.h#includeOLED.h#includemotor.h#includemotor2.h#includeKey.huint16_tspeed0;intmain(){OLED_Init();motor_init();motor2_init();Key__init();OLED_ShowString(1,1,speed:);while(1){if(Key_GetNum()1){speed30;if(speed180){speed0;}}motor_speed(speed);motor2_speed(speed);OLED_ShowNum(1,7,speed,3);Delay_ms(500);}}pwm.c#includestm32f10x.h// Device headervoidpwm_init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_PinGPIO_Pin_2;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);TIM_InternalClockConfig(TIM2);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period100-1;//ARRTIM_TimeBaseInitStructure.TIM_Prescaler36-1;//PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter0;TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(TIM_OCInitStructure);TIM_OCInitStructure.TIM_OCModeTIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OCPolarityTIM_OCPolarity_High;TIM_OCInitStructure.TIM_OutputStateTIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse50;//CCRTIM_OC3Init(TIM2,TIM_OCInitStructure);TIM_Cmd(TIM2,ENABLE);}voidpwm_setcompare(uint16_tnum){TIM_SetCompare3(TIM2,num);}pwm2.c#includestm32f10x.h// Device headervoidpwm2_init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_PinGPIO_Pin_1;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOB,GPIO_InitStructure);TIM_InternalClockConfig(TIM3);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period100-1;//ARRTIM_TimeBaseInitStructure.TIM_Prescaler36-1;//PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter0;TIM_TimeBaseInit(TIM3,TIM_TimeBaseInitStructure);TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(TIM_OCInitStructure);TIM_OCInitStructure.TIM_OCModeTIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OCPolarityTIM_OCPolarity_High;TIM_OCInitStructure.TIM_OutputStateTIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse50;//CCRTIM_OC4Init(TIM3,TIM_OCInitStructure);TIM_Cmd(TIM3,ENABLE);}voidpwm2_setcompare(uint16_tnum){TIM_SetCompare4(TIM3,num);}motor.c#includestm32f10x.h// Device header#includepwm.hvoidmotor_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct1;GPIO_InitStruct1.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStruct1.GPIO_PinGPIO_Pin_4|GPIO_Pin_5;GPIO_InitStruct1.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStruct1);pwm_init();}voidmotor_speed(int8_tspeed){if(speed0){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#includestm32f10x.h// Device header#includepwm2.hvoidmotor2_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct1;GPIO_InitStruct1.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStruct1.GPIO_PinGPIO_Pin_0|GPIO_Pin_1;GPIO_InitStruct1.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStruct1);pwm2_init();}voidmotor2_speed(int8_tspeed){if(speed0){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#includestm32f10x.h// Device header#includeDelay.hvoidKey__init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_ModeGPIO_Mode_IPU;GPIO_InitStruct.GPIO_PinGPIO_Pin_9;GPIO_InitStruct.GPIO_SpeedGPIO_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脉冲宽度调制原理PWMPulse 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定时器时钟为 72MHzARR 99PSC 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 频率与占空比参数宏定义化提高代码的可读性与可维护性。