重新开始学stm32(5)PWM输出实验和正交编码器实验(1)-程序员宅基地

技术标签: stm32  stm32学习之路  嵌入式硬件  单片机  

今天继续我们的stm32回顾之路,今天的回顾的实验是PWM输出实验和正交编码器实验,为什么今天博主要把这两个实验放在一起讲呢?因为在应用中这两个实验紧密相连,而且今天我们讲的正交编码器实验和正点原子输入捕获实验的差不多意思的,我们用正交编码器实验取代输入捕获实验。这是博主根据这两个内容在实际项目中的联系和使用最终决定的。接下来我们进入正题,看看为什么把这两个实验放在一起,它们有什么联系?提前提醒一下,因为正交编码器实验相比于输入捕获实验是以输入捕获为基础的一种在现实小车项目里实际的用法,且难度相对来说更难一点,如果感觉正交编码器实验难以看明白,可以先去看一看正点原子的输入捕获实验。

实验目的

今天的实验主要是学习怎么使用定时器进行PWM输出和进行脉冲的输入捕获,这两个实验内容在实际应用中经常用到,学完这两个实验,我们对应定时器的理解和使用也差不多毕业了。

实验内容

(1)PWM输出实验

我们先来看看PWM输出实验,PWM的英文全称是Pulse Width Modulation,中文就是脉冲宽度调制,简单来说就是改变脉冲的高或低电平在一个脉冲周期的占比。在我们的stm32中除了基本定时器TIM6和TIM7不能输出PWM,或者更准确地说是不能设置成PWM输出模式输出PWM,毕竟还是可以用它们来模拟脉冲输出的。其他的通用定时器TIM2、TIM3、TIM4、TIM5和高级定时器TIM1、TIM8都是可以设置成PWM输出模式输出PWM,而且通用定时器可以同时产生4路,高级定时器可以产生7路PWM输出。

在这个实验,我们是想用定时器TIM3的CH2输出PWM来点亮并控制DS0的亮度。但是我们的TIM3的CH1默认是接在PA7上的,而我们的DS0是接在PB5上的,我们怎么把它们接起来,除了用杜邦线连接外,stm32还给我们提供了另一个解决方式,通过重映射把CH1映射到PB5上。

 我们可以对TIM3设置部分重映射,这时候TIM3的CH1和CH2就分别接在了PB4和PB5上了。

那么下面我们就开始来配置我们的定时器PWM输出。

因为我们要使用TIM3且TIM3_CH2通道重映射到PB5上,此时PB5是属于复用功能输出,所以我们要使能GPIOB的时钟、TIM3的时钟和AFIO的时钟。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

 将TIM3_CH2重映射到PB5上,我们需要用到GPIO_PinRemapConfig()函数,这里我们选的是GPIO_PartialRemap_TIM3,是TIM3的部分重映射。

GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

 然后是PB5的GPIO配置,这些配置我们已经非常熟悉了,这里就不再讲了,但是要注意这里的GPIO模式要选GPIO_Mode_AF_PP复用推挽输出

配置完GPIO后,还要配置TIM3。同样的还是使用TIM_TimeBaseInit()函数来配置TIM3。

TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

在PWM输出这里,最关键的配置参数是自动重装载值arr和预分频系数psc,它们共同决定着定时器的定时周期,这里的周期不宜太小,如果太小就能明显看到LED的闪烁了。 

然后,我们还要设置TIM_CH2的PWM输出模式,使能TIM3的CH2输出。在库函数里面,PWM通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置,分别对应通道1至4,这里我们设置TIM_CH2的PWM输出模式用的是TIM_OC2Init()。我们来看这个函数里用到的结构体TIM_OCInitTypeDef。

typedef struct
{
  uint16_t TIM_OCMode;
  uint16_t TIM_OutputState;
  uint16_t TIM_OutputNState; 
  uint16_t TIM_Pulse;
  uint16_t TIM_OCPolarity;
  uint16_t TIM_OCNPolarity;
  uint16_t TIM_OCIdleState;
  uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;

参数 TIM_OCMode 设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。
参数 TIM_OutputState 用来设置比较输出使能,也就是使能 PWM 输出到端口。
参数 TIM_OCPolarity 用来设置极性是高还是低。
其他的参数 TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是
高级定时器 TIM1 和 TIM8 才用到的,TIM_OutputNState用来设置互补通道比较输出使能,TIM_OCNPolarity用来设置互补通道极性是高还是低,TIM_OCIdleState 和 TIM_OCNIdleState用来设置相应通道空闲状态(即非捕获/比较状态)的输出电平。

下面是我们在此次实验的配置。

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;                //设置PWM模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;        //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure);

在配置完定时器后,我们还要使能定时器。这个操作在上一期定时器中断已经讲过了。

TIM_Cmd(TIM3, ENABLE);

最后,我们还要来学一个很重要的函数,就是用来设置比较值的TIM_SetCompare1()~TIM_SetCompare4()函数,分别对应输出通道。通过改变比较值就可以改变PWM的占空比了,在正点原子的实验里面是控制LED的亮度。除此之外,还可以使用在小车轮子转速的控制等地方,所以PWM实验的内容是非常常用非常重要的。

(2)正交编码器实验 

学了PWM输出实验,我们对定时器的使用又有了进一步的了解。其实定时器的编码器输入捕获实验和PWM输出实验是类似的,只有部分地方会有些许的不同。这里我们的正交编码器实验和正点原子的输入捕获是差不多原理的,都是对脉冲的获取,我们这里的正交编码器实验是根据在小车项目里面最常用的使用演化而来,只讲定时器的编码器模式。

在讲定时器的编码器模式之前,我们先来简单认识一下什么是编码器。

认识编码器

编码器就是一种可以将模拟信号转换成数字信号的设备,更直接点就是可以将角位移或角速度转变成串电脉冲信号的一种旋转式的传感器。根据其测量的是位置信息还是速度信息可以分为增量式编码器和绝对式编码器,根据其测量原理又可以分为光学式、磁式、感应式、电容式等类型的编码器。最常见的又光电编码器和霍尔编码器(也叫磁编码器)。由于本实验的重点是定时器编码器模式的使用,且编码器的工作原理我们并不要求掌握,所以在此实验就不讲解编码器的工作原理。

编码器接线说明

本实验我们将采用带霍尔编码器的电机进行实验对电机进行测速,所以我们先来看看带编码器的电机怎么接线。

 根据图片的信息,我们可以看到编码器上总共有6个接线口,其中电机的正负极线分别接电源的正负极,编码器的5V接口可以接电源或单片机的5V或3.3V接口都是没问题的,编码器的GND接口接电源或单片机的的GND,编码器的A、B相分别接定时器的输入通道CH1、CH2。

定时器编码器模式

首先我们来看看编码器模式下是怎么判断电机的旋转方向的。其实我们是根据编码器产生的两脉冲的相位来判断的,编码器旋转一圈A、B相输出的脉冲数是一样的,但是A、B相的脉冲之间是有先后顺序的,即我们通道CH1 、CH2接收到的脉冲是有相位差的,所以我们可以根据相位差来判断电机的旋转方向。下面通过图片可以清晰看到它们的关系。

我们再来看看编码器倍频功能,这项功能非常的有用,可以提高编码器的精度。什么是倍频呢?举个例子,比如我们这个电机,带动编码器旋转一圈可以产生N个脉冲信号,经过倍频之后,可以产生N*m个脉冲信号,其中m就是倍频数。

我们的stm32给我们提供了三种倍频选择,分别是1倍频、2倍频、4倍频。具体的工作原理如下。

知道倍频是怎么实现的之后,我们来看看在程序里面我们怎么设置倍频的。

在配置定时器的编码器模式之前,我们还要先配置好GPIO和TIM,配置方法和配置过程和上面的PWM实验几乎完全一样,不同的只有在PWM实验里我们只使用通道1(CH1)进行PWM输出,只用初始化一个IO口,而在编码器模式的输入捕获实验里面我们选择的是4倍频,所以使用通道1和通道2进行脉冲输入,要初始化两个IO口。

 其实定时器的编码器模式的设置超级简单,只需要一个函数,一句代码就可以完成了。定时器编码器模式的配置使用的是TIM_EncoderInterfaceConfig()这个函数,该函数第一个参数是选择定时器;第二个参数是选择计数的通道,分别是TI1计数、TI2 计数、TI1和TI2同时计数;

#define TIM_EncoderMode_TI1                ((uint16_t)0x0001)
#define TIM_EncoderMode_TI2                ((uint16_t)0x0002)
#define TIM_EncoderMode_TI12               ((uint16_t)0x0003)

第三个和第四个参数是选择通道极性。

#define  TIM_ICPolarity_Rising             ((uint16_t)0x0000)
#define  TIM_ICPolarity_Falling            ((uint16_t)0x0002)
#define  TIM_ICPolarity_BothEdge           ((uint16_t)0x000A)

在本实验中,我们的设置如下

TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

但是除此之外,我们还需要对我们的输入通道进行初始化,使用TIM_ICInit()这个函数对输入通道进行初始化。这个函数用到的结构体是TIM_ICInitTypeDef,来看看它的组成。

typedef struct
{
  uint16_t TIM_Channel;
  uint16_t TIM_ICPolarity;
  uint16_t TIM_ICSelection;
  uint16_t TIM_ICPrescaler;
  uint16_t TIM_ICFilter;
} TIM_ICInitTypeDef;

第一个成员变量是通道的选择,第二个成员变量是选择通道极性,第三个成员变量是选择输入捕获通道的触发源,第四个成员变量是预分频器的选择,前面的这些我们都不需要重点关注,只需要使用TIM_ICStructInit()函数对它们进行初始化就好,剩下的最后一个变量才需要我们去配置,就是滤波参数的设置,根据我们的需要进行配置,因为我们这个实验对滤波的需求不大,可以随便设置一个值。

TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_ICFilter=0;
TIM_ICInit(TIM3,&TIM_ICInitStruct);

最后使能我们的定时器就行了。

TIM_Cmd(TIM3,ENABLE);

定时器的配置完成后,还需要写一个获取脉冲数的函数,周期调用该函数获取脉冲数,就可以知道在这个周期里面电机旋转速度的多少了。下面我们来看看这个函数怎么写。

获取编码器产生的脉冲数有两种方法:一种是直接赋值的方法,把计数器CNT对应的结构体变量的数值直接赋值到我们创建的变量里;另一种是调用TIM_GetCounter()函数获取计数器CNT的数值存放在我们创建的变量里。注意每次获取了CNT的数值后要使用TIM_SetCounter()函数情空CNT的数值。下面是我写的获取CNT数值的函数。

static int read_encoder(void)
{
	int encodernum = 0;

	encodernum = (int)((int16_t)(TIM3->CNT));
    //encodernum = TIM_GetCounter(TIM3);

	TIM_SetCounter(TIM3, CNT_INIT);

	return encodernum;
}

最后顺便再提一下把获取的脉冲数转换为实际电机的行驶速度的函数吧。在转换中使用的公式是:

转速(1秒钟转多少圈)= 单位时间内的计数值 / 总分辨率 * 时间系数                                               总分辨率(定时器实际获得的脉冲数)= 编码器物理脉冲数 * 编码器倍频 * 电机减速比

下面是我写的脉冲数转换成实际行驶速度的函数。

void calc_motor_rotate_speed()
{
	int encoderNum = 0;
	float rotateSpeed = 0;
	
	encoderNum = read_encoder();
	//printf("%d\r\n",encoderNum);
	
	rotateSpeed = (float)encoderNum/TOTAL_RESOLUTION*10;
	
	printf("encoder: %d\t speed:%.2f rps\r\n",encoderNum,rotateSpeed);
}

 其中宏定义TOTAL_RESOLUTION就是总分辨率,定义如下。而时间系数是根据调用获取脉冲并计算转换为实际转速的周期来定的,比如这个周期是1秒那时间系数就为1,如果中国周期是0.1秒那时间系数就为10,以此类推。

#define ENCODER_RESOLUTION      11     //编码器一圈物理脉冲数
#define ENCODER_MULTIPLE        4      //设置的倍频数
#define MOTOR_REDUCTION_RATIO   30     //电机减速比

#define TOTAL_RESOLUTION    (ENCODER_RESOLUTION*ENCODER_MULTIPLE*MOTOR_REDUCTION_RATIO)

到这里,我们这个正交编码器的实验也差不多完成了,剩下的只需要小伙伴根据自己的需要补充扩展就行了。由于篇幅太多,所以这两个实验的联系应用放在下一期吧。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_73719155/article/details/130789437

智能推荐

EBS R12基本概念与应用基础-程序员宅基地

文章浏览阅读1.8k次。摘自: [ORACLE EBS 入门及供应链核心系统详解教程] (书籍)EBS基础功能架构(13个核心模块,业财一体化)业务运营管理,价值增值财务会计管理,价值实现应用架构Finance财务,资金流Accounting财务管理Bisuness业务,实物流核心业务,与财务高度集成;PUR、INV、制造、订单履行等间接业务,or专业业务,为核心业务提供支持;HR..._ebs r12

Java中Date和Timestamp的区别_java date timestamp区别-程序员宅基地

文章浏览阅读838次。转载:https://blog.csdn.net/ccecwg/article/details/39546307_java date timestamp区别

如何用原生js封装一个类似jq的选择器_原声js实现jq元素选择器-程序员宅基地

文章浏览阅读1.4k次。1、我们先了解一下原生js中的选择器ID选择器(在整个文档中获取id为xxx的元素)document.getElementId([ID]);类名选择器(在整个文档中或者在指定上下文中获取类名为xxx的元素)document.getElementsByClassName(' ');[context].getElementsByClassName(' ');标签名选择器(在整个文档中或者..._原声js实现jq元素选择器

Hive中partition by和distribute by区别_partition by distribute by-程序员宅基地

文章浏览阅读1.2k次,点赞3次,收藏4次。通常查询时会对整个数据库查询,而这带来了大量的开销,因此引入了partition的概念,在建表的时候通过设置partition的字段, 会根据该字段对数据分区存放,更具体的说是存放在不同的文件夹,这样通过指定设置Partition的字段条件查询时可以减少大量的开销。1)partition by [key..] order by [key..]只能在窗口函数中使用,而distribute by [key...] sort by [key...]在窗口函数和select中都可以使用。_partition by distribute by

游标(cursor )是什么?_c# cursor-程序员宅基地

文章浏览阅读7.3k次。Private SQL Area A private SQL area holds information about a parsed SQLstatement and other session-specific information for processing. When a serverprocess executes SQL or PL/SQL code, the process_c# cursor

listview使用的一些心得_listview的使用——购物商城实验心得-程序员宅基地

文章浏览阅读616次。近日在用ListView中的一些注意点,和公用代码,整理如下1.ListView.Items.Clear而不是ListView.Clear一般如果ListView是动态填充的,我们在填充之前都会先进行清理。但需要注意一下,我们是清理Items,如果去直接Clear整个ListView,就连原先定义好的列都没有了2.给ListView绑定数据ListView并不能直接_listview的使用——购物商城实验心得

随便推点

java 注解处理器的作用_深入理解Java:注解(Annotation)--注解处理器-程序员宅基地

文章浏览阅读110次。如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。注解处理器类库(java.lang.reflect.AnnotatedElement):Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口..._java注解处理器作用

全国职业技能大赛高职组(最新职业院校技能大赛_大数据应用开发2023国赛样题解析-模块C:实时数据处理-任务二:实时指标计算)_大数据 国赛 样题-程序员宅基地

文章浏览阅读1.8k次,点赞27次,收藏28次。全国职业技能大赛高职组(最新职业院校技能大赛_大数据应用开发样题解析-模块B:数据采集-任务一:离线数据采集-程序员宅基地。_大数据 国赛 样题

ssm+mysql+微信小程序疫情防控小程序-计算机毕业设计源码73691_ssm+微信小程序-程序员宅基地

文章浏览阅读926次。本系统分为管理员和注册用户两个角色,主要有疫情新闻、疫情案例介绍、健康信息申报、行程信息申报、就医流程介绍、举报、在线留言、用户管理、信息统计等模块。用户需要先注册成为会员,成功登录后,可以查看网站发布的疫情新闻,可以查看疫情相关病例介绍,有助于疫情防范,还可以查看网站发布的重大疫情案例,了解疫情的发展状况,出行时候好做好防护,同时通过网站可以上报健康信息,以及上报行程信息,方便社区了解自己的出行情况;网站还发布了疫情状态下的就医流程,方便大家就医时候做好准备;同时网站还提供了举报功能,如果发现外来人员或_ssm+微信小程序

Linux 操作系统 022-串口/U盘/共享文件夹-程序员宅基地

文章浏览阅读296次,点赞3次,收藏9次。本节关键字:Linux、centos、串口、U盘、共享文件夹本节相关指令:echo、cat、mkdir、mount

解密C++新特性:内联函数、auto和基于范围的for循环-程序员宅基地

文章浏览阅读1.3k次,点赞45次,收藏29次。本篇主题为: 解密C++新特性:内联函数、auto关键字和基于范围的for循环。

上岸整理:2023前端面试题-vue,小程序,js,css_今年的前端面试难不难-程序员宅基地

文章浏览阅读774次,点赞4次,收藏11次。1、浏览器常见的报错信息与含义2、304与204的区别,http缓存,强缓存,协商缓存3、浏览器从输入地址到渲染,经历了什么状态?4、vue的界面渲染,经过哪些过程(生命周期)5、三次握手,四次挥手6、重排与重绘7、用css实现一个三角形8、常见的flex布局,有哪些功能9、用css实现一个水平垂直居中10、null与undefined的区别11、虚拟dom12、深拷贝与浅拷贝13、es6新增的功能15、async await 与promise。_今年的前端面试难不难