当前位置: 首页 > news >正文

STM32 PWM驱动设计

单片机学习!

目录

文章目录

前言

一、PWM驱动配置步骤

二、代码示例及注意事项

2.1 RCC开启时钟

2.2 配置时基单元

2.3 配置输出比较单元

2.4 配置GPIO

2.5 运行控制

三、PWM周期和占空比计算

总结


前言

        PWM本质是利用面积等效原理来改变波形的有效值。


一、PWM驱动配置步骤

第一步、RCC开启时钟,将需要的TIM外设和GPIO外设的时钟打开。

第二步、配置时基单元,包括时钟源选择的配置。

第三步、配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能这些参数。(在库函数中也是用结构体统一来配置)。

第四步、配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置。

第五步、运行控制,启动计数器就可以输出PWM了。

二、代码示例及注意事项

2.1 RCC开启时钟

        将需要的TIM外设GPIO外设的时钟打开。打开时钟后定时器的基准时钟和整个外设的工作时钟就会同时打开。

代码示例:

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使用APB1的开启时钟函数,因为TIM2是APB1总线的外设。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

2.2 配置时基单元

        配置时基单元,包括时钟源选择的配置。

  1. 时基单元的选择时钟源。对于定时中断可选择内部时钟源。
  2. 配置时基单元,包括预分频器、自动重装器、计数模式等,这些参数可用结构体配置。

代码示例:

	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;//ARR自动重装器的值TIM_TimeBaseInitStructure.TIM_Prescaler= 720-1;//PSC预分频器的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0;//重复计数器的值TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

结构体成员:

  • TIM_ClockDivision:指定时钟分频,用于信号经过滤波器时的滤波采样频率。
  • TIM_CounterMode:计数器模式,这里选择向上计数模式TIM_CounterMode_Up。
  • TIM_Period :ARR自动重装器的值。
  • TIM_Prescaler:PSC预分频器的值。
  • TIM_RepetitionCounter:重复计数器的值,只有高级定时器才有,本文初始化通用寄存器,所以值给0。

        时基单元中关键寄存器参数ARR、PSC都有设置,但是这里没有CNT计数器的参数,CNT参数的配置可根据需要在函数 TIM_SetCounter 和函数 TIM_GetCounter 中操作。

        决定定时时间的参数是 TIM_Period 和 TIM_Prescaler 。定时时间可用计数器溢出频率公式计算,定时频率=72M/(PSC+1)/(ARR+1)。

2.3 配置输出比较单元

        配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能这些参数。(在库函数中也是用结构体统一来配置)。

void 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);

        这里4个初始化函数对应4个输出比较通道(单元),不同的通道对应的GPIO口也不一样,函数的选择需要根据GPIO口的配置选择。代码示例中使用PA0口,对应第一个输出比较通道,选择TIM_OC1Init函数。

代码示例:

	TIM_OCInitTypeDef TIM_OCInitStructture;TIM_OCStructInit(&TIM_OCInitStructture);TIM_OCInitStructture.TIM_OCMode=TIM_OCMode_PWM1; //设置输出比较的模式。TIM_OCInitStructture.TIM_OCPolarity=TIM_OCPolarity_High; //设置输出比较的极性TIM_OCInitStructture.TIM_OutputState=TIM_OutputState_Enable; //设置输出使能TIM_OCInitStructture.TIM_Pulse=50; //设置CCRTIM_OC1Init(TIM2,&TIM_OCInitStructture);   

        代码示例中的程序并没有给结构体的所有成员赋值,而是用TIM_OCStructInit函数先给结构体成员赋一个初始值,再修改部分的结构体成员。这个结构体变量是局部变量,若不给成员赋初始值,它成员的值就是不确定的,这会导致一些问题。比如当你想把高级定时器当作通用定时器输出PWM时,只配置了通用定时器需要的结构体成员,剩下这些没赋值成员就会导致高级定时器输出PWM出现一些奇怪的问题。

        TIM_OCStructInit函数需要把结构体变量的地址传进去,才能给结构体赋初始值。

1. TIM_OCMode设置输出比较的模式。

        TIM_OCMode输出比较模式参数对应含义:

  • TIM_OCMode_Timing 冻结模式                
  • TIM_OCMode_Active 相等时置有效电平                 
  • TIM_OCMode_Inactive 相等时置无效电平               
  • TIM_OCMode_Toggle 相等时电平翻转                 
  • TIM_OCMode_PWM1 PWM模式1                   
  • TIM_OCMode_PWM2 PWM模式2                   
  • TIM_ForcedAction_Active 强制输出有效电平
  • TIM_ForcedAction_InActive 强制输出无效电平

        注:强制输出两种模式的参数不可以在初始化时使用

2. TIM_OCPolarity设置输出比较的极性。

  • TIM_OCPolarity_High 高级性,是极性不翻转,REF波形直接输出。意思是有效电平是高电平,REF有效时,输出高电平。
  • TIM_OCPolarity_Low  低级性,REF电平取反,意思是有效电平为低电平。

3. TIM_OutputState设置输出使能。

4.TIM_Pulse置CCR。 ARR,PSC,CCR共同决定PWM的周期和占空比
 

        以上配置已经把输出比较通道初始化好了,在TIM2的OC1通道上就可以输出PWM波形了,最终这个波形需要通过GPIO口才可以输出。那TIM2的OC1通道是借用哪个GPIO口呢?下文来选择并配置GPIO。


注:TIM_OC1Init是通道1的初始化函数,若通道1、2、3、4都需要的话可以直接在后面加TIM_OC2Init、TIM_OC3Init、TIM_OC4Init,这样就可以同时使用4个通道来输出4个PWM了。

代码示例:

	TIM_OCInitTypeDef TIM_OCInitStructture;TIM_OCStructInit(&TIM_OCInitStructture);TIM_OCInitStructture.TIM_OCMode=TIM_OCMode_PWM1; //设置输出比较的模式。TIM_OCInitStructture.TIM_OCPolarity=TIM_OCPolarity_High; //设置输出比较的极性TIM_OCInitStructture.TIM_OutputState=TIM_OutputState_Enable; //设置输出使能TIM_OCInitStructture.TIM_Pulse=50; //设置CCRTIM_OC1Init(TIM2,&TIM_OCInitStructture);TIM_OC2Init(TIM2,&TIM_OCInitStructture);TIM_OC3Init(TIM2,&TIM_OCInitStructture);TIM_OC4Init(TIM2,&TIM_OCInitStructture);   

        同一个定时器的不同通道输出的PWM,因为不同通道是共用一个计数器的,所以它们的频率必须是一样的;它们的占空比由各自的CCR决定,所以占空比可以各自设定;他们的相位由于计数器更新,所有PWM同时跳变,所以它们的相位是同步的。

        这就是同一个定时器不同通道输出PWM的特点。如果使用多个设备如电机或者舵机,那使用同一个定时器不同通道的PWM就完全可以。


2.4 配置GPIO

        输出比较通道借用GPIO口可以在引脚定义表中查看:

        默认复用功能这一列就是片上外设的端口和GPIO的连接关系。可以找到 TIM2_CH1_ETR 在 PA0 这一行,这说明 TIM2 的 ETR 引脚和通道1的引脚都是借用了 PA0 这个引脚位置,就是TIM2的引脚复用在了PA0引脚上。所以使用TIM2的OC1通道也就是CH1通道输出PWM,就只能在PA0引脚上输出,而不能任意选择引脚输出。

同理:

  • 使用TIM2_CH2只能在PA1端口输出
  • 使用TIM2_CH3只能在PA2端口输出
  • 使用TIM2_CH4只能在PA3端口输出

表中其他外设也是同理:如使用SPI1_MISO只能在PA6端口输出。虽然引脚与外设都是规定好的,但是还可以根据情况做一次改动。在引脚定义表重定义(重映射)这一列,还可以对应更改。如既要使用 USART2_TX 引脚又要使用 TIM2_CH3 通道,但是它俩都在PA2端口输出,这就冲突了没法同时使用。这时可以在重定义列表里找一下,有 TIM2_CH3 ,那么 TIM2_CH3 就可以从PA2端口输出换为从PB10端口输出。这样就避免了两个外设引脚的冲突。但是如果重映射的列表里找不到,那外设复用的GPIO就不能挪位置。配置重映射需要用AFIO来完成。

        配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置。

代码示例:

	GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//这里选择复用推挽输出。GPIO_InitStruct.GPIO_Pin= GPIO_Pin_0;GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);

        这里选择复用推挽输出。
        原因:对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,定时器控制引脚需要使用复用开漏/推挽输出的模式,复用开漏/推挽输出模式中输出数据寄存器将被断开,输出控制权将转移给片上外设,通过引脚定义表可知,这里片上外设引脚连接的就是TIM2的CH1通道。所以只有把GPIO设置为复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出。
  

2.5 运行控制

        运行控制:整个模块配置完成后,还需要使能一下计数器,PWM波形就能通过PA0输出了。

代码示例:

    TIM_Cmd(TIM2,ENABLE);

三、PWM周期和占空比计算

计算公式:

  • PWM频率:    Freq = CK_PSC / (PSC + 1) / (ARR + 1)      PWM频率等于计数器更新频率。
  • PWM占空比:    Duty = CCR / (ARR + 1)
  • PWM分辨率:    Reso = 1 / (ARR + 1)

        ARR、PSC、CCR共同决定PWM的周期和占空比

代码示例中产生的是一个频率为1KHHz,占空比为50%,分辨率为1%的PWM波形。

代入公式:

  • 72M/(PSC+1)/(ARR+1)=1000
  • CCR/(ARR+1)=50%
  • 1/(ARR+1)=1%

得出:

  • (ARR+1)=100
  • CCR=50
  • (PSC+1)=720

对应代码中:

  • ARR 给 100-1;
  • PSC 给 720-1;
  • CCR 给 50。

        代码运行时,PWM占空比也可更改,可选择使用TIM_SetCompare1、TIM_SetCompare2、TIM_SetCompare3、TIM_SetCompare4 函数改变CCR寄存器的值来调节PWM占空比。但是PWM的占空比是由ARR、PSC、CCR共同决定。


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了PWM驱动配置。

相关文章:

  • OJ_阶乘的和
  • 【重点问题】攻击面发现及管理
  • SpringBoot 整合RabbitMQ 之延迟队列实验
  • Jenkins上跑自动化项目,case出现错误时,导致项目运行时间过长,该如何处理?
  • diffusion 和 gan 的优缺点对比
  • Python系列(9)—— 比较运算符
  • 知识笔记(九十七)———什么是实体符???
  • 【算法专题】动态规划之回文子串问题
  • c#定义特性,通过反射获取特性
  • 基于SSM的网络办公系统(有报告)。Javaee项目。ssm项目。
  • 探索Gin框架:快速构建高性能的Golang Web应用
  • Flutter App 生命周期观察监听
  • 爬虫(一)
  • SpringBoot项目配置SSL后,WebSocket连接失败的解决方案
  • FIR数字滤波器设计
  • @angular/forms 源码解析之双向绑定
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • ➹使用webpack配置多页面应用(MPA)
  • 03Go 类型总结
  • es6
  • exif信息对照
  • javascript 总结(常用工具类的封装)
  • JavaScript实现分页效果
  • miaov-React 最佳入门
  • rc-form之最单纯情况
  • React-Native - 收藏集 - 掘金
  • Spring Cloud Feign的两种使用姿势
  • 代理模式
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 普通函数和构造函数的区别
  • 如何设计一个比特币钱包服务
  • 网络应用优化——时延与带宽
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 小而合理的前端理论:rscss和rsjs
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • ​configparser --- 配置文件解析器​
  • #QT项目实战(天气预报)
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (4.10~4.16)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (LeetCode 49)Anagrams
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (python)数据结构---字典
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (黑马C++)L06 重载与继承
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (转载)(官方)UE4--图像编程----着色器开发
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • .gitignore文件设置了忽略但不生效
  • .net core 依赖注入的基本用发
  • .net 反编译_.net反编译的相关问题