尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
一篇理解“PWM调制占空比技术”(源码)
一、简介PWM调制占空比技术是我们常用的一种用于调节输出信号平均功率的技术。它通过改变脉冲的宽度来控制输出到负载的能量而保持脉冲频率恒定或在一定范围内变化常见用于电机的输出功率控制音频处理控制等。二、原理假设一个方波周期高电平与低电平各占整个周期的一半我们就说占空比为50%在快速的脉冲开关我们就能够控制设备输出的模拟电压假设我们控制的设备输出是5V周期是20ms现在我们使用单片机定时器PWM模式把高电平占空比调制到25%也就是周期为20ms,周期内高电平时间为5ms周期内低电平时间为15ms高电平与周期的比值就是占空比 5/2025% 最终调制得到的设备输出电压为 5V * 25% 1.25V模拟输出电压。三、案例演示3.1 简介我们以控制电机输出电压为例当占空比100%也就是周期全是高电平时电机输出的速度是全速反之占空比越小速度则越慢因为输出控制电压设备输出电压*占空比。3.2 电机参数笔者使用手上的一款24直流减速电机带编码器理论满速为1400转/分钟左右编码反馈一圈11个信号减速比4.4。3.3 驱动器想要驱动此电机肯定是要借助驱动器的这里我们选择一个比较便宜的驱动器。四、代码示例按照驱动器说明我们搭建好电路即可编写代码注意上面的驱动器说明了频率范围我们需要按照范围来进行配置。4.1 定时器与编码器配置TIM_HandleTypeDef TIM_TimeBaseStructure; //定时器结构体声明 void Motor_TIM_Init(void); //声明函数 TIM_HandleTypeDef g_timx_chy_handle; /* 定时器x句柄 */ TIM_Encoder_InitTypeDef g_timx_encoder_chy_handle; /* 定时器编码器句柄 */ void Motor_BM_TIM_Init(void); /*NVIC中断向量控制器函数*/ static void TIMx_BM_NVIC_Configuration(void) { HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 0, 1); HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn); } /*这是一个用于初始化GPIO的函数*/ void Motor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; //GPIO初始化结构体声明 MOTOR_TBPUL_CLK(); GPIO_InitStructure.Mode GPIO_MODE_AF_PP; //配置模式为复用推挽输出 GPIO_InitStructure.Speed GPIO_SPEED_FREQ_VERY_HIGH; //引脚速率 GPIO_InitStructure.Alternate MOTOR_PUL_GPIO_AF; //设置为复用 GPIO_InitStructure.Pull GPIO_PULLUP; //内部上拉 GPIO_InitStructure.Pin MOTOR_TBPUL_PIN; //配置脉冲引脚 HAL_GPIO_Init(MOTOR_TBPUL_PORT, GPIO_InitStructure); //初始化 Motor_TIM_Init(); //定时器初始化 Motor_BM_TIM_Init(); //编码器初始化 MOTOR_CHA_CLK(); //编码器反馈IO时钟 MOTOR_CHB_CLK(); //编码器反馈IO时钟 /*使能编码器复用通道引脚*/ GPIO_InitStructure.PinMOTOR_CHA_PIN|MOTOR_CHB_PIN; //配置反馈线 GPIO_InitStructure.ModeGPIO_MODE_AF_PP; //配置模式 GPIO_InitStructure.PullGPIO_NOPULL; //配置无上下拉 GPIO_InitStructure.AlternateMOTOR_CHB_GPIO_AF; GPIO_InitStructure.SpeedGPIO_SPEED_FREQ_VERY_HIGH; //配置翻转速度 HAL_GPIO_Init(MOTOR_CHB_PORT, GPIO_InitStructure); //初始化结构体 } /*这是一个用于初始化PWM输出定时器的函数*/ void Motor_TIM_Init(void) { TIM_OC_InitTypeDef TIM_OCInitStructure; GENERAL_TIM_CLK_ENABLE(); //打开定时器时钟 TIM_TimeBaseStructure.InstanceGENERAL_TIM; //配置定时器号 TIM_TimeBaseStructure.Init.Period100-1; //ARR重装载值 TIM_TimeBaseStructure.Init.Prescaler180-1; //定时器分频器 1M TIM_TimeBaseStructure.Init.ClockDivisionTIM_CLOCKDIVISION_DIV1; //频率为1分频 TIM_TimeBaseStructure.Init.CounterModeTIM_COUNTERMODE_UP; //向上计数 HAL_TIM_PWM_Init(TIM_TimeBaseStructure); //初始化结构体 TIM_OCInitStructure.OCModeTIM_OCMODE_PWM1; //PWM模式配置这里为PWM1 TIM_OCInitStructure.Pulse0; //默认占空比为0 TIM_OCInitStructure.OCFastMode TIM_OCFAST_DISABLE; /*当定时器计数值小于CCR1_Val时为高电平*/ TIM_OCInitStructure.OCPolarity TIM_OCPOLARITY_HIGH; /*配置PWM通道*/ HAL_TIM_PWM_ConfigChannel(TIM_TimeBaseStructure, TIM_OCInitStructure, MOTOR_PUL_CHANNEL_x); HAL_TIM_PWM_Start(TIM_TimeBaseStructure,MOTOR_PUL_CHANNEL_x); //开启PWM通道1 } /*这是一个用于初始化用于编码器定时器的函数*/ void Motor_BM_TIM_Init(void) __TIM8_CLK_ENABLE(); //打开定时器时钟 g_timx_chy_handle.InstanceTIM8; //定时器8 g_timx_chy_handle.Init.Prescaler0; //自动加一就是1分频 g_timx_chy_handle.Init.Period65535; //自动重装载值 g_timx_chy_handle.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; //不分频 g_timx_encoder_chy_handle.EncoderMode TIM_ENCODERMODE_TI12; /* TI1,TI2都检测 011 4倍频*/ g_timx_encoder_chy_handle.IC1Polarity TIM_ICPOLARITY_RISING; /* 边沿检测器设置非反向 */ g_timx_encoder_chy_handle.IC1Selection TIM_ICSELECTION_DIRECTTI; /* 通道1映射到IC1*/ g_timx_encoder_chy_handle.IC1Prescaler TIM_ICPSC_DIV1; /* 不分频 */ g_timx_encoder_chy_handle.IC1Filter 10; /* 滤波器设置 */ g_timx_encoder_chy_handle.IC2Polarity TIM_ICPOLARITY_RISING; /* 边沿检测器设置非反向 */ g_timx_encoder_chy_handle.IC2Selection TIM_ICSELECTION_DIRECTTI; /* 通道2映射到IC2 */ g_timx_encoder_chy_handle.IC2Prescaler TIM_ICPSC_DIV1; /* 一分频 */ g_timx_encoder_chy_handle.IC2Filter 10; /* 滤波器设置 */ HAL_TIM_Encoder_Init(g_timx_chy_handle, g_timx_encoder_chy_handle); /*句柄初始化*/ HAL_TIM_Encoder_Start(g_timx_chy_handle,TIM_CHANNEL_1); /* 开启编码器通道1 */ HAL_TIM_Encoder_Start(g_timx_chy_handle,TIM_CHANNEL_2); /* 开启编码器通道2 */ TIMx_BM_NVIC_Configuration(); //初始化NVIC __HAL_TIM_CLEAR_FLAG(g_timx_chy_handle,TIM_IT_UPDATE); /* 清除更新中断 */ __HAL_TIM_ENABLE_IT(g_timx_chy_handle,TIM_IT_UPDATE); /* 开启更新中断 */ } /*这是一个中断服务函数将句柄放入绑定回调函数*/ void TIM8_UP_TIM13_IRQHandler(void) { HAL_TIM_IRQHandler(g_timx_chy_handle); } volatile int g_timx_encode_count 0; /* 用于记录溢出次数向下溢出就-1向上溢出就1 */ /*这是一个中断回调函数*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(__HAL_TIM_IS_TIM_COUNTING_DOWN(g_timx_chy_handle)) /* 读取寄存器位判断CR1的DIR位 */ { g_timx_encode_count--; /* DIR位为1也就是递减计数 */ } else { g_timx_encode_count; /* DIR位为0也就是递增计数 */ } } /*这是一个获取编码器值的函数*/ int32_t gtim_get_encode(void) { // printf(编码器原式步数%d\r\n,__HAL_TIM_GET_COUNTER(g_timx_chy_handle)); /* 计算当前总计数值当前总计数值 计数器当前值 溢出次数*65536 */ return ( int32_t )(__HAL_TIM_GET_COUNTER(g_timx_chy_handle) g_timx_encode_count * 65536); } int32_t encode_old,encode_now,var; /* 计算1分钟间隔的计数变化量原理 第一步外部延时200ms调用一次函数var*5*200ms*601分钟 第二步计算1分钟间隔编码器的输出脉冲个数var*5*200ms*60/4脉冲个数4为因为使用了4倍频 第三步计算1分钟间隔编码器转过的圈数var*5*200ms*60/4/11分辨率 第四步除去减速齿轮比得到真正的转速var*5*200ms*60/4/11/4 */ int32_t speed_computer(void) { encode_now gtim_get_encode(); /* 获取当前计数值 */ var encode_now - encode_old; /* 计算计数变化量200ms间隔 */ encode_old encode_now; /* 保存这一次的计数值 */ return ( int32_t )( var * 5 * 60 / 4 / 11 / 4.4 ); /* 返回电机速度值 */ }4.2 控制占空比大小逻辑/*旋转编码器控制逻辑 (外部中断扫描方式)*/ void xz_can(void) { if (SW_Scan() KEY_ON) { //SW按钮触发 ; } if (encoderCount ! 0) { //旋转编码器被旋转 if (encoderCount precision) { //大于精度阈值代表被正转 encoderCount 0; //清零方便下次判断 if ((arr2 0) (arr2 50)) { arr2; OLED_Show_Humi(40, 2, arr2 * 2); //更新数字内容 2倍显示速度 __HAL_TIM_SET_COMPARE(TIM_TimeBaseStructure, MOTOR_PUL_CHANNEL_x, arr2*2); //修改CCRx寄存器值 } } else if (encoderCount -precision) { //小于负的精度阈值代表反转 encoderCount 0; if ((arr2 0) (arr2 50)) { arr2--; OLED_Show_Humi(40, 2, arr2 * 2); __HAL_TIM_SET_COMPARE(TIM_TimeBaseStructure, MOTOR_PUL_CHANNEL_x, arr2*2); //修改CCRx寄存器值 } } } }五、实验示例使用旋转编码器作为占空比的调节器并通过OLED显示占空比调节情况可见电机的运行速度随着占空比的大小而变化。六、总结可见只要弄懂单片机定时器的PWM模式相关知识点实现电机控制呼吸灯控制亮度控制等等简直‌信手拈来需要完整源码请留下邮箱。
RELATED

相关推荐

RDP Wrapper终极指南:解锁Windows远程桌面无限连接的专业解决方案

RDP Wrapper终极指南:解锁Windows远程桌面无限连接的专业解决方案

RDP Wrapper终极指南:解锁Windows远程桌面无限连接的专业解决方案 【免费下载链接】rdpwrap RDP Wrapper Library 项目地址: https://gitcode.com/gh_mirrors/rd/rdpwrap RDP Wrapper Library是一个革命性的开源项目,它通过创新的中间层封装技术&…

📅 2026/7/21 7:08:45
提升用户体验:vue-timeago自动更新功能让时间显示更精准

提升用户体验:vue-timeago自动更新功能让时间显示更精准

提升用户体验:vue-timeago自动更新功能让时间显示更精准 【免费下载链接】vue-timeago A timeago component for Vue. 项目地址: https://gitcode.com/gh_mirrors/vu/vue-timeago 在Vue.js应用开发中,时间显示是一个常见但容易被忽视的细节。传统…

📅 2026/7/21 0:18:30
3DTopia部署指南:如何在云端服务器运行AI 3D生成服务

3DTopia部署指南:如何在云端服务器运行AI 3D生成服务

3DTopia部署指南:如何在云端服务器运行AI 3D生成服务 【免费下载链接】3DTopia Text-to-3D Generation within 5 Minutes 项目地址: https://gitcode.com/gh_mirrors/3d/3DTopia 想要在云端服务器上快速部署AI 3D生成服务吗?3DTopia是一个强大的文…

📅 2026/7/31 13:18:06
MORE NEWS

更多资讯

📰

为什么硬盘越来越小:Czkawka 免费重复文件清理工具完整指南

为什么硬盘越来越小:Czkawka 免费重复文件清理工具完整指南 【免费下载链接】czkawka Multi functional app to find duplicates, empty folders, similar images etc. 项目地址: https://gitcode.com/GitHub_Trending/cz/czkawka 硬盘的"剩余空间"…

📰

Android OTA升级包下载避坑指南:选型、断点续传与校验

简介:面向 Android 系统开发者与 ROM 定制工程师,这份资源围绕 OTA 在线升级中的升级包下载环节,提供可直接参考的 DownloadFile 工程实现。工程内部按标准 Android 项目组织,包含下载任务的文件获取、校验与落盘逻辑,…

📰

OpenCore Legacy Patcher 完整指南:三步让老 Mac 跑上最新 macOS

OpenCore Legacy Patcher 完整指南:三步让老 Mac 跑上最新 macOS 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher Apple 每发布一代新 macOS&#…

📰

STM32F103移植FreeRTOS通用模板实践与原理详解

简介:面向正点原子精英板用户的STM32F103 FreeRTOS移植通用模板,专为希望快速上手实时操作系统开发的嵌入式工程师与学习者设计,覆盖从工程搭建到任务运行的全流程。工程已完成FreeRTOS与STM32标准外设库的整合,任务创建、调度器启…

📰

51单片机扫地吸尘智能车设计与制作:从硬件选型到代码调试

1. 项目整体认识:一台51单片机小车能做什么把“51扫地吸尘智能车机器人”拆开看,它其实是三件事的组合:一辆能动的智能小车、一套能吸尘的清扫机构、一颗用51单片机驱动一切的“大脑”。这是个非常典型又非常完整的单片机课程设计级项目&…

📰

盘点无人机飞手接单平台有哪些?

寻找一个强大的专业的无人机飞手接单平台,是众多无人机操作员和项目方共同关注的问题。随着无人机技术的不断普及,越来越多的行业开始依赖无人机进行测绘、巡检、航拍等任务。而在这个过程中,如何快速找到合适的飞手,成为了一个关…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬