stm32学习笔记---TIM编码器接口(代码部分)编码器接口测速

目录

编码器接口的初始化步骤

Tim.h库函数

TIM_EncoderInterfaceConfig

代码实现

Encoder.c

初始化

三种输入模式的选择原则

获取编码器的增量值的函数

调整极性

闸门时间调整

Encoder.h

main.c


声明:本专栏是本人跟着B站江科大的视频的学习过程中记录下来的笔记,我之所以记录下来是为了方便自己日后复习。如果你也是跟着江科大的视频学习的,可以配套本专栏食用,如有问题可以QQ交流群:963138186

接下来我们就来写一下编码及接口测速的代码

编码器接口测速

接线图:

A相可以接PA6或者PA7,这个可以随便交换,但是必须用PA6和PA7这两个引脚,因为我们计划用定时器3来接编码器,PA6和PA7对应的是定时器3的通道一和通道二。

复制定时器定时中断节的工程并改名

现在来封装一下编码器接口的代码

编码器接口的初始化步骤

编码器接口的初始化步骤看这个结构图来配置

第一步,RCC开启时钟,开启GPIO和定时器的时钟。

第二步,配置GPIO,这里需要把PA6和PA7配置成输入模式。

第三步,配置时基单元,这里预分频器,一般选择不分频。自动重装一般给最大65535,只需要个CNT执行计数就行了。

第四步,配置输入捕获单元,不过这里输入捕获单元只有滤波器和极性这两个参数有用,后面的参数没有用到,与编码器无关。

第五步,配置编码器接口模式,这个直接调用个库函数就可以了。

第六步,启动定时器,调用TIM_Cmd启动定时器。

电路初始化完成之后,CNT就会随着编码器旋转而自增自减。如果想要测量编码器的位置直接读出CNT的值就行了。如果想测量编码器的速度和方向就需要每隔一段固定的闸门时间取出一次CNT,然后再把CNT清零这样就是测频法测量速度了。

Tim.h库函数

在这里找一下库函数,本节我们需要新学习的扩函数比较少,只有这一个。

TIM_EncoderInterfaceConfig

TIM_EncoderInterfaceConfig配置编码器接口的函数。第一个参数选择定时器,第二个参数选择编码器模式,后面两个参数分别选择通道一和通道二的电平极性。

代码实现

Encoder.c

初始化

首先前面几步和之前的代码基本一样,我们直接复制输入捕获的代码,然后修改一下。

GPIO初始化,我们使用的是PA6和PA7,把PA6和PA7配置成上拉输入模式。

上拉和下拉如何选择

三种输入模式的选择原则

我们一般可以看一下接在这个引脚的外部模块输出的默认电平。如果外部模块空闲默认输出高电平,我们就选择上拉输入,默认输入高电平。如果外部模块默认输出低电平,我们配置下拉输入,默认输入低电平,和外部模块保持默认状态一致,防止默认电平打架,这是上拉和下拉的选择原则。

不过一般来说默认高电平是一个习惯的状态所以一般上拉输入用的比较多。

然后如果不确定外部模块输出的默认状态或者外部信号输出功率非常小这时就尽量选择浮空输入浮空输入没有上拉电阻和下拉电阻去影响外部信号。但是缺点就是当引脚悬空时没有默认的电平了,输入就会受噪声干扰,来回不断的跳变。

这就是三种输入模式的选择原则。

这一行就不需要了

因为编码器接口会托管时钟,编码器接口就是一个带方相控制的外部时钟,所以这个内部时钟就没有用了。

接下来是时基单元配置。

计数器模式这个参数目前也是没有作用的

因为计数方相也是被编码器接口托管的。

自动重装值还是给65536-1,也就是满量程计数,这样计数的范围是最大的,而且方便换算为负数。

预分频器这里改成1-1,预分频给零,就是不分频,编码器的时钟直接驱动计数器

这就是时基部分的配置。

下一步就是输入捕获单元的配置

后面这两个参数与编码器无关,我们可以直接删掉。

删掉之后,目前结构体的配置是不完整的。为了防止结构体中出现不确定值可能会造成的问题,我们最好用StructInit给结构体赋一个初始值,也是提醒一下我们结构体并没有配置完整。在定义结构体定义好之后,我们来一个结构体初始化,把这个结构体的地址传进去,赋一个初始值。

接着看,电平极性为上升沿,上节我们说过,这里的上升沿并不代表上升沿有效。因为编码器接口时钟都是上升沿,下降沿都有效的。这里的上升沿参数代表的是高低电平极性不反转。对应通道,给上升沿就是不反相,给下降沿就是反相这个意思。

其实这里的这个极性参数,等会儿我们配置编码器接口的时候也有,属于重复配置的。这里这个其实也可以删掉。

我们可以等会儿对比一下之后再删。

通道一的我们配置好了,然后还有通道二,我们复制修改一下,把通道1改成通道二。

到这里两个通道的滤波器和极性就都配置好了。

下一步配置编码器。

我们只需要调用一个函数就行了,到tim.h里复制这个函数。

第一个参数是TIM3。

第二个编码性模式,可以选择下面三个参数之一。

第一个TI1就是仅在TI1计数,第二个TI2就是仅在TI2计数。第三个TI12就是TI1和TI2都计数。对应这三个模式。

我们一般使用TI1和TI2都计数。

第三和第四个参数就是IC1的极性和IC2的极性,取值列表一样

rising就是这个通道不反相选择,falling就是这个通道反相。这个可以根据实际情况来配置,我们目前两个参数都可以先选择rising。

这样参数就配置好了。

注意:这里后两个参数和上面输入捕获单元的两个参数是一样的。

实际的效果确实就是一样的,这两个地方的参数其实都配置的是同一个寄存器,属于重复配置的,后配置的参数会覆盖前面的参数。所以可以把这前面输入捕获单元的极性参数也删掉,只使用后面这个函数来配置极性。

不过要注意,这时一定要保证这个编码器接口配置的函数位于输入捕获初始化函数的下面,否则的话就是TIM_ICInit覆盖TIM_EncoderInterfaceConfig函数的配置。

到这里,我们整个电路就配置完成了,

最后我们再调用一个TIM_Cmd开启定时器。

这样初始化配置就结束了。

调用一下TIM_EncoderInterfaceConfig函数,编码器旋转就能控制CNT自增自减了。

获取编码器的增量值的函数

我们接下来再写个获取编码器的增量值的函数

在里面,我们暂时先直接返回CNT的值测试一下看看

测试结果就是右转一下编码器可以看到值变成4。这个编码器是有这个段落感的(如果是电机的编码器就不会有段落感),每转一格它输出的波形其实是。a相产生一个低电平脉冲,b相产生一个相位差九十度的脉冲。

提前还是滞后取决于正转还是反转。

编码器转动一格,ab相各出现了一个下降沿和上升沿。所以计次总共加了4次。

然后我们继续往右转CNT就继续自增,往左转CNT就自减。

向左一直转到零之后是什么情况?

我们转到零,再继续转可以看到零再自减,计数器溢出,回到自动重装值65535,然后继续往下减。

这就是目前我们使用uint16_t数据的现象。

如果我们想要0之后变为-1,就直接把uint16_t类型强制转换成int16_t就行了。

这里函数的返回值,直接换成int16_t。

然后函数声明也别忘了改一下,在主循环里改成

这样就能显示负数了。

现在是0

向左转数值变成负数

这就是借用补码的特性快速完成负数转换的小技巧。

我们再来研究一下极性的问题。

调整极性

如果方向和你想要的不一致的话,可以修改一下极性。

在硬件上面,我们可以把ab相两根线换一下。

在软件层面,我们可以修改这里的两个输入通道的极性:

把任意一个极性反转一下,方向就会反过来。如果两个极性都反转,极性还是保持不变。

这就是极性的问题。

目前我们这个代码是编码器测量位置,如果需要测量位置的话,就这样直接get counter就行了

如果我们想用这个编码器来测速的话就可以在固定的闸门时间读一次CNT,然后把CNT清零。

我们修改一下这个Encoder_Get函数,要求读完后清零CNT,所以我们要定义一个临时变量temp。

要先读取后清零,所以需要用temp缓存一下。

在主循环里每隔一段时间get一次。所以下面可以给一个delay一千毫秒。因为我们人手转比较慢,所以闸门时间就给一秒。

如果是电机飞速旋转的话,闸门时间就可以给短的,这样可以提高速度刷新的频率,而且防止计数器溢出。

这样每一秒都会清零,CNT最多是一秒内的值,就是速度。也即CNT的值就代表速度单位是脉冲个数每秒

结果就是我们向右慢速转,速度是正数比较小。快速转,正数比较大。向左慢速转,速度负数比较小。快速转负数比较大。

当然我们目前只是用这个旋钮模拟的测速。如果你有编码电机的话,可以实际接电机的编码器试试看现象都是一样的。不过要注意把这个闸门时间弄短点,防止计数器溢出。

闸门时间调整

目前我们是直接通过delay实现的闸门时间,如果主程序没有其他东西的话,可以这样来做。但是如果有其他东西的话,最好就不要在主循环加入过长的delay,这样会阻塞主循环的执行。

比较好的方法就是用定时中断了。

目前是每隔一秒执行一次,可以通过修改定时中断的时间,来调整闸门时间。

之后再定义一个全局变量

然后在定时中断里执行每隔一秒读取一下速度,存在speed的变量里,然后主循环就可以快速刷新显示speed。这样delay可以删掉,这样就不会阻塞主循环了。

Encoder.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:编码器初始化
  * 参    数:无
  * 返 回 值:无
  */
void Encoder_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6和PA7引脚初始化为上拉输入
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;                //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*输入捕获初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICStructInit(&TIM_ICInitStructure);							//结构体初始化,若结构体没有完整赋值
																	//则最好执行此函数,给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;				//选择配置定时器通道2
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	
	/*编码器接口配置*/
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
																	//配置编码器模式以及两个输入通道是否反相
																	//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
																	//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

/**
  * 函    数:获取编码器的增量值
  * 参    数:无
  * 返 回 值:自上此调用此函数后,编码器的增量值
  */
int16_t Encoder_Get(void)
{
	/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return Temp;
}

Encoder.h

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

int16_t Speed;			//定义速度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时器初始化
	Encoder_Init();		//编码器初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
	
	while (1)
	{
		OLED_ShowSignedNum(1, 7, Speed, 5);	//不断刷新显示编码器测得的最新速度
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Speed = Encoder_Get();								//每隔固定时间段读取一次编码器计数增量值,即为速度值
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

运行结果:

STM32-编码器接口测速

到这里,编码器接口测速的代码就写完了,下节继续!

QQ交流群:963138186

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/752697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文本分类-RNN-LSTM

1.前言 本节介绍RNN和LSTM,并采用它们在电影评论数据集上实现文本分类,会涉及以下几个知识点。 1. 词表构建:包括数据清洗,词频统计,词频截断,词表构建。 2. 预训练词向量应用:下载并加载Glove的…

Terraform基础概念一

Terraform基础概念一 1.Infrastructure-as-Code(IaC)概念1.1 IaC优势1.2 IaC工具1.3 IaC的两种方式 2.Terraform基础概念2.1 Terraform工作原理2.2 Terraform 工作流 3.总结 1.Infrastructure-as-Code(IaC)概念 基础设施即代码(Infrastructure-as-Code,…

告别 “屎山” 代码,务必掌握这14 个 SpringBoot 优化小妙招

插: AI时代,程序员或多或少要了解些人工智能,前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家(前言 – 人工智能教程 ) 坚持不懈,越努力越幸运,大家…

flash-Attention2安装和使用

flash-Attention2安装和使用 文章目录 flash-Attention2安装和使用写在前面解决方案 写在前面 就怕你不知道怎么查 pytorch、cuda 的版本 配置cuda:vim ~/.bashrc export CUDA_HOME/usr/local/cuda/ export PATH$PATH:$CUDA_HOME/bin export LD_LIBRARY_PATH$LD_LIB…

鉴源实验室·基于MQTT协议的模糊测试研究

作者 | 张渊策 上海控安可信软件创新研究院工控网络安全组 来源 | 鉴源实验室 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 随着物联网技术的快速发展,越来越多的设备加入到互联网中,形成了庞大的物联网系统。这些设备之间的通信…

【Sklearn-线性回归驯化】史上最为全面的预测分析的基石-线性回归大全

【Sklearn-驯化】史上最为全面的预测分析的基石-线性回归大全 本次修炼方法请往下查看 🌈 欢迎莅临我的个人主页 👈这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合,智慧小天地! 🎇 免费获取相关内容文档关注&…

Java8新特性stream的原理和使用

这是一种流式惰性计算&#xff0c;整体过程是&#xff1a; stream的使用也异常方便&#xff0c;可以对比如List、Set之类的对象进行流式计算&#xff0c;挑出最终想要的结果&#xff1a; List<Timestamp> laterTimes allRecords.stream().map(Record::getTime).filter…

电脑音频剪辑怎么操作?分享六个简单的音频剪辑技巧【常用】

音频剪辑的需求越来越多&#xff0c;大多数短视频中的音乐都是大家后期制作的&#xff0c;主要目的就就是让视频观看起来更有趣。音频剪辑的方法有很多&#xff0c;比较好用的可以借助第三方音频剪辑软件。操作简单&#xff0c;对没有任何剪辑经验的小白用户来说十分友好。 本文…

java简易计算器(多种方法)

parseDouble() 方法属于 java.lang.Double 类。它接收一个字符串参数&#xff0c;其中包含要转换的数字表示。如果字符串表示一个有效的 double&#xff0c;它将返回一个 double 值。 应用场景 parseDouble() 方法在以下场景中非常有用&#xff1a; 从用户输入中获取数字&a…

VUE大屏的开发过程(纯前端)

写在前面&#xff0c;博主是个在北京打拼的码农&#xff0c;工作多年做过各类项目&#xff0c;最近心血来潮在这儿写点东西&#xff0c;欢迎大家多多指教。 对于文章中出现的任何错误请大家批评指出&#xff0c;一定及时修改。有任何想要讨论和学习的问题可联系我&#xff1a;1…

2.4G无线通信芯片数据手册解读:Ci24R1南京中科微

今天&#xff0c;我非常荣幸地向您介绍这款引领行业潮流的2.4G射频芯片&#xff1a;Ci24R1。这款芯片&#xff0c;不仅是我们技术的结晶&#xff0c;更是未来无线通信的璀璨明星。 首先&#xff0c;让我们来谈谈Ci24R1的“速度”。2.4G射频芯片&#xff0c;凭借其卓越的数据传输…

Python基于逻辑回归分类模型、决策树分类模型、随机森林分类模型和XGBoost分类模型实现乳腺癌分类预测项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 在当今医疗健康领域&#xff0c;乳腺癌作为威胁女性健康的主要恶性肿瘤之一&#xff0c;其早期诊断与精…

OpenHarmony开发实战:HDF驱动开发流程

概述 HDF&#xff08;Hardware Driver Foundation&#xff09;驱动框架&#xff0c;为驱动开发者提供驱动框架能力&#xff0c;包括驱动加载、驱动服务管理、驱动消息机制和配置管理。并以组件化驱动模型作为核心设计思路&#xff0c;让驱动开发和部署更加规范&#xff0c;旨在…

Redis-Bitmap位图及其常用命令详解

1.Redis概述 2.Bitmap Bitmap 是 Redis 中的一种数据结构&#xff0c;用于表示位图&#xff08;bit array&#xff09;。 它通常用于处理大规模数据集中每个元素的状态&#xff0c;比如用户的在线/离线状态&#xff08;每个用户对应一个位&#xff0c;表示在线&#xff08;1&a…

[数据结构】——七种常见排序

文章目录 前言 一.冒泡排序二.选择排序三.插入排序四.希尔排序五.堆排序六.快速排序hoare挖坑法前后指针快排递归实现&#xff1a;快排非递归实现&#xff1a; 七、归并排序归并递归实现&#xff1a;归并非递归实现&#xff1a; 八、各个排序的对比图 前言 排序&#xff1a;所谓…

Mac中的xshell、xftp

ROYAL TSX 插件式支持远程连接linux、支持命令行、支持ftp、支持远程windows桌面。 免费版就足够使用了。&#xff08;支持维护一个Connections文件夹&#xff09; 需要在本地创建一个文件夹&#xff0c;用以保存链接信息 使用方法

Bytebase 2.20.0 - 支持为工单事件配置飞书个人通知

&#x1f680; 新功能 支持 Databricks。支持 SQL Server 的 TLS/SSL 连接。支持为工单事件配置飞书个人通知。支持限制用户注册的邮箱域名。 &#x1f514; 重大变更 将分类分级同步设置从数据库配置移至工作空间的全局配置。 SQL 编辑器只读模式下只允许执行 Redis 的只读…

抖音外卖服务商申请全域外卖系统源码部署,如何保证竞争力?

随着本地生活市场规模的逐渐扩大&#xff0c;多家互联网公司在加大投入力度的同时&#xff0c;也在不断调整其市场竞争策略&#xff0c;作为国内头部社交平台的抖音也不例外。就在近日&#xff0c;抖音发布了关于新增《【到家外卖】内容服务商开放准入公告》的意见征集通知&…

OSI七层模型TCP/IP四层面试高频考点

OSI七层模型&TCP/IP四层&面试高频考点 1 OSI七层模型 1. 物理层&#xff1a;透明地传输比特流 在物理媒介上传输原始比特流&#xff0c;定义了连接主机的硬件设备和传输媒介的规范。它确保比特流能够在网络中准确地传输&#xff0c;例如通过以太网、光纤和无线电波等媒…

SCI二区复现|体育场观众优化算法(SSO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;M Nemati受到体育场观众的行为对比赛中球员行为的影响启发&#xff0c;提出了体育场观众优化算法&#xff08;Stadium Spectators Optimizer, SSO&#xff09;。 2.算法…