资讯动态

STM32F103 PWM Timer

发布时间:2026/10/4 7:32:12 来源:尧图企业网站定制
在具有惯性的系统中可以通过对一系列脉冲的宽度进行调制来等效地获得所需要的模拟参量常应用于电机控速等领域。PWM参数频率 1 / TS 占空比 TON / TS 分辨率 占空比变化步距对于普通的开漏/推挽输出引脚的控制权来自于输出数据寄存器要让定时器来控制引脚需要使用复用开漏推挽模式GPIO_Mode_AF_PP输出模式控制器的比较逻辑模式描述冻结CNTCCR时REF保持为原状态匹配时置有效电平CNTCCR时REF置有效电平匹配时置无效电平CNTCCR时REF置无效电平匹配时电平翻转CNTCCR时REF电平翻转强制为无效电平CNT与CCR无效REF强制为无效电平强制为有效电平CNT与CCR无效REF强制为有效电平PWM模式1向上计数CNTCCR时REF置有效电平CNT≥CCR时REF置无效电平 向下计数CNTCCR时REF置无效电平CNT≤CCR时REF置有效电平PWM模式2向上计数CNTCCR时REF置无效电平CNT≥CCR时REF置有效电平 向下计数CNTCCR时REF置有效电平CNT≤CCR时REF置无效电平一、PWM基本结构CNT(蓝)向上计数ARR(黄)CCR(红)。占空比受CCR控制定时器决定频率捕获比较器决定占空比PWM频率 Freq CK_CNT / (ARR 1) CK_PSC / (PSC 1) / (ARR 1)PWM占空比 Duty CCR / (ARR 1)PWM分辨率 Reso 1 / (ARR 1) 占空比最小的变化步距二、具体实现步骤这里 要用到PA6引脚对应TIM3_CH1在stm32f10x_tim.h文件中OC Output Comparevoid TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); ​ void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);//结构体初始化 { /* Set the default configuration */ TIM_OCInitStruct-TIM_OCMode TIM_OCMode_Timing;//输出比较模式 TIM_OCInitStruct-TIM_OutputState TIM_OutputState_Disable;//输出状态 使能/不使能 TIM_OCInitStruct-TIM_OutputNState TIM_OutputNState_Disable; TIM_OCInitStruct-TIM_Pulse 0x0000;//设置CCR寄存器的值 between 0x0000 and 0xFFFF TIM_OCInitStruct-TIM_OCPolarity TIM_OCPolarity_High;//输出极性 高/低 TIM_OCInitStruct-TIM_OCNPolarity TIM_OCPolarity_High; TIM_OCInitStruct-TIM_OCIdleState TIM_OCIdleState_Reset; TIM_OCInitStruct-TIM_OCNIdleState TIM_OCNIdleState_Reset; } ​ //更改占空比的时候会用到 void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);单独更改CH1通道CCR的值 void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2); void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3); void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);#include stm32f10x.h // Device header void PWM_Init() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); ​ //配置时钟源时基单元 TIM_InternalClockConfig(TIM3); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period 100-1;// ARR 1ms 72M/720001000hz TIM_TimeBaseInitStruct.TIM_Prescaler 720-1;//PSC TIM_TimeBaseInitStruct.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStruct); //配置输出比较单元 TIM_OCInitTypeDef TIM_OCInitStruct; TIM_OCStructInit(TIM_OCInitStruct);//结构体完整的初始化 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1;//下面用到什么改什么 TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_Pulse 50;//CCR 占空比50% TIM_OC1Init(TIM3, TIM_OCInitStruct); //配置GPIO GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Pin GPIO_Pin_6; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); //启动定时器 TIM_Cmd(TIM3,ENABLE); } //如果想让PWM实现不断调速的功能就需要在程序运行中不断更改CCR的值 void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM3, Compare); } ​ #ifndef __PWM_H #define __PWM_H ​ void PWM_Init(void); void PWM_SetCompare1(uint16_t Compare); #endif如果要产生一个 频率为1KHz占空比为50%分辨率为1%的PWM波形。直接再main()函数中调用初始化函数PWM_Init();即可解1/(ARR1)1% ARR100-1 CCR50 PSC 720-1//这个程序的效果类似呼吸灯占空比从0开始增加到Max之后再降为0 #include stm32f10x.h // Device header #include PWM.h #include delay.h uint8_t i; int main() { PWM_Init(); while(1) { for(i0;i100;i) { PWM_SetCompare1(i); Delay_ms(10); } for(i0;i100;i) { PWM_SetCompare1(100-i); Delay_ms(10); } } ​ }这里借用舵机模块来观察PWM的波形。第一次点击运行后可以看到波形输出窗口要是关闭后就找不到了。在哪里可以找回来呢点击运行后在“Debug”下找到“Digital Oscilloscope”就可以找到啦三、引脚重映射引脚重映射功能TIM3_CH1引脚可以从PA6 挪到 PB4这时候需要用到AFIORCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);配置AFIO参考手册 这里选择 部分重映射GPIO_PartialRemap_TIM3GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);重映射类型特点适用场景部分重映射仅改变部分外设引脚需要保留某些默认功能完全重映射全部外设引脚重新分配需要完全改变引脚布局但是要注意 PA14、PA15、PB3、PB4引脚上电后默认复用了调试端口PB4默认复用了JTRST* arg GPIO_Remap_SWJ_NoJTRST : Full SWJ Enabled (JTAG-DP SW-DP) but without JTRST * arg GPIO_Remap_SWJ_JTAGDisable : JTAG-DP Disabled and SW-DP Enabled * arg GPIO_Remap_SWJ_Disable : Full SWJ Disabled (JTAG-DP SW-DP)要让PB4作为普通GPIO或者复用定时器的通道需要先关闭调试端口的复用GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST,ENABLE);#include stm32f10x.h // Device header void PWM_Init() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//端口改 //引脚重映射 AFIO RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST,ENABLE); GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //配置时钟源时基单元 TIM_InternalClockConfig(TIM3); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period 100-1;// ARR 1ms 72M/720001000hz 1/10001ms TIM_TimeBaseInitStruct.TIM_Prescaler 720-1;//PSC TIM_TimeBaseInitStruct.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStruct); //配置输出比较单元 TIM_OCInitTypeDef TIM_OCInitStruct; TIM_OCStructInit(TIM_OCInitStruct);//结构体完整的初始化 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1;//下面用到什么改什么 TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_Pulse 50;//CCR TIM_OC1Init(TIM3, TIM_OCInitStruct); //配置GPIO GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Pin GPIO_Pin_4;//引脚重映射 这里也要改 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); //启动定时器 TIM_Cmd(TIM3,ENABLE); } void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM3, Compare); } ​Proteus作为流行的电路仿真软件在某些情况下对STM32的引脚重映射支持有限这可能导致仿真结果与实际情况不符。重映射后的PWM输出在Proteus中无信号只能观察到默认引脚(PA6)的PWM波形仿真时不响应PB4引脚的PWM变化用实物的话是可以正常重映射的。四、PWM的应用1.舵机【Proteus仿真】STM32F103C8T6核心板 Proteus小白食用_proteus 8 仿真f103c8-CSDN博客【Proteus仿真】51单片机PWM信号控制舵机_proteus 舵机-CSDN博客Stm32f103c8t6(proteus仿真)学习——7-2.PWM驱动SG90舵机_proteus舵机-CSDN博客这里的仿真借鉴的是上面两个博客。舵机是一种根据输入PWM信号占空比来控制输出角度的装置输入PWM信号要求周期为20ms高电平宽度为0.5ms~2.5msFreq 50Hz 72M/PSC1/(ARR1) 50先假定 PSC 72-1 ARR 20K-1占空比范围 2.5%~12.5% CCR 范围500~2500脉冲宽度0.5ms1ms1.5ms2ms2.5ms转动角度-90°-45°0°34°90°重定义转角0°45°90°135°180°任意角脉宽0.5ms 角度值 *2ms/180°CCR 500 角度值 / 180° * 2000舵机参数配置下面再PWM模块的基础上新增舵机模块的代码#include stm32f10x.h // Device header #include PWM.h void Servo_Init() { PWM_Init(); } void Servo_SetAngle(float angle) { PWM_SetCompare1(500angle/180*2000); } #ifndef __SERVO_H #define __SERVO_H void Servo_Init(void); void Servo_SetAngle(float angle); #endif舵机模块功能描述按键PB1 每按下一次按键舵机角度增加30°按键PB11 每按下一次按键舵机角度减少30°#include stm32f10x.h // Device header #include servo.h #include timer.h #include key.h uint8_t key_val,key_old,key_down,key_up; uint8_t key_clock; float angle50; void Key_Proc() { if(key_clock) return; key_clock1; key_val Key_Read(); key_down key_val (key_old ^ key_val); key_up ~key_val (key_old ^ key_val); key_old key_val; switch(key_down) { case 1:{ angle30; if(angle180)angle180; Servo_SetAngle(angle); }break; case 11: { angle-30; if(angle0)angle0; Servo_SetAngle(angle); }break; } } int main() { Timer_Init(); KEY_Init(); Servo_Init(); Servo_SetAngle(angle); while(1) { Key_Proc(); } } void TIM2_IRQHandler(void) { if(SET TIM_GetITStatus(TIM2,TIM_IT_Update))//查看更新标志位的状态 { key_clock; if(key_clock 20) key_clock0; TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } }可通过按键观察到舵机角度以及占空比的变化。2.直流电机Proteus基于51单片机通过PWM脉冲调制控制电机转速_按键与串口控制转速_电机转速可测_motor-encoder-CSDN博客L298N 电机驱动板 - 详细介绍 知乎直流电机是一种将电能转换为机械能的装置有两个电极当电极正接时电机正转当电极反接时电机反转。直流电机属于大功率器件GPIO口无法直接驱动需要配合电机驱动电路来操作。TB6612是一款双路H桥型的直流电机驱动芯片可以驱动两个直流电机并且控制其转速和方向。IN1和IN2 电平相同 电机停止IN1和IN2 电平相反看PWM的状态高转 低不转如果PWM频率足够快那么电机就可以稳定的转动PWM模块 TIM3的通道1、通道2对应的IO口分别是PA6和PA7。PA7主要是通过调CH2通道的 CCR来控制占空比从而实现调节直流电机速度的效果。对于同一个定时器的不同通道 共有同一个计数器他们的频率是一样的但是他们的占空比由各自的CCR决定占空比可以各自设定相位同步。电机这块的占空比没有要求 CRR范围可以是 0 ~ 20K-1功能PB1按键 每按下一次按键舵机角度增加30° 直流电机速度增加10% 最大速度按15KPB11按键 每按下一次按键舵机角度减少30° 直流电机速度减少10%PB12按键 控制电机正反转#include stm32f10x.h // Device header void PWM_Init() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //配置时钟源时基单元 TIM_InternalClockConfig(TIM3); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period 20000-1;// ARR TIM_TimeBaseInitStruct.TIM_Prescaler 72-1;//PSC TIM_TimeBaseInitStruct.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStruct); //配置输出比较单元 TIM_OCInitTypeDef TIM_OCInitStruct; TIM_OCStructInit(TIM_OCInitStruct);//结构体完整的初始化 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; //TIM3 CH1 CCR1 1000 TIM_OCInitStruct.TIM_Pulse 1000; TIM_OC1Init(TIM3, TIM_OCInitStruct);//TIM3 CH1 初始化 //TIM3 CH2 CCR2 1500 TIM_OCInitStruct.TIM_Pulse 1500; TIM_OC2Init(TIM3, TIM_OCInitStruct);//TIM3 CH2 初始化 //配置GPIO GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Pin GPIO_Pin_6|GPIO_Pin_7; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); //启动定时器 TIM_Cmd(TIM3,ENABLE); } //以下函数是设置TIM3 CHX通道 void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM3, Compare); } void PWM_SetCompare2(uint16_t Compare) { TIM_SetCompare2(TIM3, Compare); }新增motor.c/.h#include stm32f10x.h // Device header #include PWM.h void Motor_Init(void) { PWM_Init(); //IN1 IN2 初始化 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP;//stm32引脚输出 来控制L298输入 IN1 IN2 GPIO_InitStruct.GPIO_Pin GPIO_Pin_3|GPIO_Pin_4; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB,GPIO_InitStruct); } void Motor_SetSpeed(_Bool flag,uint8_t speed) { //speed取值 0-100 //电机这块的占空比没有要求 CRR范围0 ~ 20K-1 这里规定最高按15K //15K*speed/100150*speed if(flag)//正转 { GPIO_SetBits(GPIOB,GPIO_Pin_3); GPIO_ResetBits(GPIOB,GPIO_Pin_4); PWM_SetCompare2(150*speed); } else { GPIO_SetBits(GPIOB,GPIO_Pin_4); GPIO_ResetBits(GPIOB,GPIO_Pin_3); PWM_SetCompare2(150*speed); } } #ifndef __MOTOR_H #define __MOTOR_H void Motor_Init(void); void Motor_SetSpeed(_Bool flag,uint8_t speed); #endifmain.c#include stm32f10x.h // Device header #include timer.h #include key.h #include motor.h #include servo.h uint8_t key_val,key_old,key_down,key_up; uint8_t key_clock; float angle80;//范围0~180 uint8_t speed50;//范围0~100 _Bool flag1; void Key_Proc() { if(key_clock) return; key_clock1; key_val Key_Read(); key_down key_val (key_old ^ key_val); key_up ~key_val (key_old ^ key_val); key_old key_val; switch(key_down) { case 1:{ angle30; if(angle180)angle180; Servo_SetAngle(angle); speed10; if(speed100) speed100; Motor_SetSpeed(flag,speed); }break; case 11: { angle-30; if(angle0)angle0; Servo_SetAngle(angle); speed-10; if(speed0) speed0; Motor_SetSpeed(flag,speed); }break; case 12: { flag ^ 1; Motor_SetSpeed(flag,speed); }break; } } int main() { Timer_Init(); KEY_Init(); Servo_Init(); Motor_Init(); Servo_SetAngle(angle); Motor_SetSpeed(1,speed); while(1) { Key_Proc(); } } void TIM2_IRQHandler(void) { if(SET TIM_GetITStatus(TIM2,TIM_IT_Update))//查看更新标志位的状态 { key_clock; if(key_clock 20) key_clock0; TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑