定时器的编码器接口

news2024/11/19 5:31:12

对应手册编码器接口14.3.12

实现代码

实现旋转编码器计次,与之前的在定时器中断时实现的旋转编码器计次实现内容相同,但是方式不同,之前的是通过触发外部中断,通过中断函数来实现手动计次加一这次不同,是通过定时器的编码器接口来自动计次,可以节约软件资源。

之前使用外部中断来实现旋转编码器计次,当电机高速旋转时,编码器每秒产生成千上万个脉冲,会频繁进入中断,而完成的内容却是简单的+1,这导致软件资源被这种简单而又低级的工作占用了,对于这种简单且需要频繁执行的任务,我们可以交给定时器的编码器接口来自动计次。

每隔一段时间来获取旋转编码器的计次值,就可以得到旋转编码器旋转的速度了

简介

Encoder Interface 编码器接口
编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
每个高级定时器和通用定时器都拥有 1 个编码器接口(定时器使用为编码器后,就不能做其他事情了,我们的STM32只有4个定时器,资源紧张的情况下,可以使用外部中断来接编码器,使用软件资源和硬件资源互补)
两个输入引脚借用了输入捕获的通道 1 和通道 2

 使用编码器接口计次旋转编码器的速度实质上就是上一节所说的测频法的思路

正交编码器

当编码器的旋转轴转起来时,编码器就会输出如图的正交波,旋转得越快,方波频率就越高,则波的频率即是旋转的速度,A,B中任意一相就可测出旋转的速度,两相的目的是用于确定旋转的方向,当旋转为正向时,B相会滞后90°;反向旋转时,B相则会提前90°。这些并不是绝对的,这些是极性问题,正转和反转也是相对的。

双相都输出为正交信号有什么好处呢?首先是正交信号精度更高,因为AB相都是正交信号,都可以计次,计次频率提高了一倍;其次是正交信号可以抗噪声,比如A相信号不变,B相信号却发生很大的波动,连续跳变,会被判定为噪声被剔除掉,这时计次值是不会变化的。

设计逻辑:把AB相的所有边沿都作为计数器的计数时钟,出现边沿信号时,就计数自增或者自减,计数的方向由另一相的状态来决定。

电路结构

可见编码器接口接在CH1和CH2,同时也使用了输入滤波器和边沿检测器 

编码器接口的输出部分,相当于从模式控制器了,去控制CNT的计数时钟和计数方向此时这里不会用到72MHz内部时钟和在时基单元初始化时设置的计数方向,计数时钟和计数方向完全被编码器接口托管,由其决定计数方向

编码器接口基本结构

        如图所示, GPIO口接入编码器的AB相,然后通过滤波器和边沿检测极性选择,在分别通往编码器接口,编码器接口通过预分频器控制CNT计数器的时钟,同时编码器接口还会根据编码器的旋转方向控制CNT的计数方向。

        一般我们设置ARR为最大值65535,这样可以利用补码的特性,容易得到负数。

       (反转会导致CNT自减,0下一个数时65535,而不是负数,所以我们会做一个操作,直接把这个16位无符号数转换为16位有符号数,其中65535对应-1)

 

工作模式

如图,检测到TI1FP1正处于上升沿,则会继续检测TI1FP2是处于高电平还是低电平,若处于高电平,则向下计数,即计数器自减;若处于低电平,则向上计数,即计数器自增。

 实例

(均不反相)

 对抗噪声,如图所示,不符合规范的波会导致计数器自增一下自减一下,即不影响结果

TI1反相

反相,即极性选择中选择反相,TI1反相即把高低电平置换,会得出与不反相刚好相反的计数器计数,这种情况用于调整计数方向,当我们发觉计数方向和自己的需求不同时,采用这种方法得到自己想要的计数方向

代码实操

计划用TIM的CH1和CH2

我们需要新学的函数有

定时器编码器接口配置(定时器,编码器模式,CH1的极性,CH2的极性)

void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
                                uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

1、开启GPIO和定时器的时钟

	//开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

2、配置GPIO口,需要把PA6和PA7配置为输入模式

	//配置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);

3、配置时基单元(不分频)

	//时基单元初始化
	TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
	//指定时钟分频(与本次操作没太大关系)
	TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式(编码器接手托管,与本次操作无关)
	TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	//ARR(防止测量的频率太小,导致计数溢出)
	TimeBaseInitStructure.TIM_Period = 65536 - 1;
	//PSC(给0,不分频,编码器的时钟直接驱动计数器)
	TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
	//重复计数器的值(高级计数器特有的,我们没有直接赋0)
	TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TimeBaseInitStructure);

4、配置输入捕获单元

    //配置输入捕获单元的滤波器和边沿检测极性选择
	//用不到的参数由结构体初始化函数配置初始值
	//CH1
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	//边沿检测,极性选择(极性不反转)
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	//CH2
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	//边沿检测,极性选择(极性不反转)
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);

5、配置编码器接口模式

//配置编码器接口
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

在这我们会发现这里也定义了一边CH1和CH2引脚的极性选择,所以我们在配置输入捕获单元时,可以不用配置CH1和CH2的极性选择,在配置编码器接口模式中陪置即可 

6、启动定时器

	TIM_Cmd(TIM3, ENABLE);

总体(还加了一个获取CNT值的函数)

#include "stm32f10x.h"                  // Device header

void Encoder_Init(void)
{
	//开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//配置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);
	
	//编码器会托管时钟,所以就不用配置内部时钟了
	
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
	//指定时钟分频(与本次操作没太大关系)
	TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式(编码器接手托管,与本次操作无关)
	TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	//ARR(防止测量的频率太小,导致计数溢出)
	TimeBaseInitStructure.TIM_Period = 65536 - 1;
	//PSC(给0,不分频,编码器的时钟直接驱动计数器)
	TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
	//重复计数器的值(高级计数器特有的,我们没有直接赋0)
	TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TimeBaseInitStructure);

	//配置输入捕获单元的滤波器和边沿检测极性选择
	//用不到的参数由结构体初始化函数配置初始值
	//CH1
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	//CH2
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);

	//配置编码器接口
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	
	TIM_Cmd(TIM3, ENABLE);
}

uint16_t Encoder_Get(void)
{
	return TIM_GetCounter(TIM3);
}

在主函数中调用一下,看看CNT是否准确

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

int main(void)
{
	OLED_Init();
	Encoder_Init();
	OLED_ShowString(1,1,"CNT:");
	while(1)
	{
		OLED_ShowNum(1, 5, Encoder_Get(), 5);
	}
}

可以看到结果正确

但是反转自减从0开始会变65535,我们只需强制转换类型即可

int16_t Encoder_Get(void)
{
	return TIM_GetCounter(TIM3);
}
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

uint16_t Num;

int main(void)
{
	OLED_Init();
	Encoder_Init();
	//Timer_Init();
	
	OLED_ShowString(1,1,"CNT:");
	while(1)
	{
		OLED_ShowSignedNum(1, 5, Encoder_Get(), 5);
	}
}

这样反转就可以显示负数了

拓展

如果目前的正转方向和我们想要的不一样,我们有两种方法来修改正转的定义

1、直接更改接线,把旋转编码器的两根线调换即可

2、修改代码,在配置编码器接口时,我们定义了两个端口的极性选择,我们只需把其中的一个改为与其之前相反的极性即可

//原
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
//修改极性后
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);

使用编码器接口测速

要实现测量旋转编码器的速度,就需要设定一个闸门时间,在这段时间内旋转多少,再除以时间,即是速度。

首先每过一次闸门时间,CNT的计数都需要清零,由此我们需要改编获取CNT值的函数

int16_t Encoder_Get(void)
{
	int16_t temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return temp;
}

main.c(使用Delay的方法,每过1s,获取一次CNT的值,即Speed=CNT/1)

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

uint16_t Num;

int main(void)
{
	OLED_Init();
	Encoder_Init();
	
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		OLED_ShowSignedNum(1, 7, Encoder_Get(), 5);
		Delay_ms(1000);
	}
}

这样做不太妥当,假如主函数中还有其他的程序需要运行,使用Delay函数可能会阻塞主循环的运行,所以我们可以通过定时中断来实现每隔一秒取一次CNT的值。

在定时中断中我们就已经设置好了是每个1s执行一次中断

#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();
	Encoder_Init();
	Timer_Init();
	
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		OLED_ShowSignedNum(1, 7, Speed, 5);
		Delay_ms(1000);
	}
}

//中断函数
void TIM2_IRQHandler(void)
{	
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//判断是否是指定端口产生的中断
	{//第二个参数是想看哪个中断的标志位
		//内容写在这
		
		Speed = Encoder_Get();
		//清除中断标志位,回归主函数,以防一直卡在中断函数中
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
	
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑版剪映怎么倒放?

1.打开一个素材 2.添加到时间轨道 3.右击轨道素材 弹出的选项钟选择,基础编辑》倒放!

【Java开发】Redis位图实现统计日活周活月活

最近研究了使用 Redis 的位图功能统计日活周活等数据,特来和大家分享下,Redis 位图还可用于记录用户签到情况、判断某个元素是否存在于集合中等。 1 Redis 位图介绍 Redis 位图是一种特殊的数据结构,它由一系列位组成,每个位只能…

Spring Cloud Alibaba Gateway 全链路跟踪TraceId日志

前言 凡是文中需要注册到nacos的都需要这个jar包 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>凡是使用config jar包的都需要写bootstrap.prop…

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基…

Tuya MQTT 标准协议是什么?

TuyaLink 协议是涂鸦 IoT 开发平台面向物联网开发领域设计的一种数据交换规范&#xff0c;数据格式为 JSON&#xff0c;主要用于设备端和涂鸦 IoT 开发平台的双向通信&#xff0c;更便捷地实现了设备端和平台之间的业务数据交互。 设备的通信方式也是多种多样的。无线通信方式…

什么是Redux?它的核心概念有哪些?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是Redux&#xff1f;⭐ 它的核心概念有哪些&#xff1f;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发…

【Go】rsrc不是内部或外部命令、无法将“rsrc”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 解决方法

前言 想尝试用go创建一个桌面应用程序&#xff0c;然后查了下决定用 walk。 我们要先下载walk&#xff0c;这里 官方链接 按照官方文档&#xff0c;我们先用go get命令下载。 go get github.com/lxn/walk然后分别创建好了 main.go、main.manifest 文件&#xff0c;代码如下…

Web自动化框架中验证码识别处理全攻略,让测试更得心应手!

前言&#xff1a; 随着Web应用程序的不断发展&#xff0c;自动化测试已成为项目开发中必不可少的一环。然而&#xff0c;验证码的出现却经常会使自动化测试变得更具挑战性。为了解决这个问题&#xff0c;我们需要一种方法来自动识别和处理验证码&#xff0c;从而提高自动化测试…

Spring面试题11:什么是Spring的依赖注入

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说Spring的依赖注入 依赖注入(Dependency Injection)是Spring框架的一个核心特性,它是指通过外部容器将对象的依赖关系注入到对象中,从而…

在大厂做外包的软件测试工程师,如今发展怎么样了?

前言 不久前&#xff0c;许久没联系的大学室友出差来了魔都&#xff0c;趁此机会叫上了昔日的两三好友&#xff0c;一起小酌了一杯&#xff0c;觥筹交错中&#xff0c;得知了大学室友今年开始和朋友一同创业&#xff0c;时隔多年终于从外包公司&#xff0c;成为了一名“正式员…

RDMA编程杂记

目录 编程杂记什么是P_Key建链基于Socket API的建链基于CM API的建链 编程杂记 什么是P_Key P_Key&#xff08;Partition Key&#xff09;用于提供InfiniBand网络的隔离机制&#xff0c;只有在一个分区内的节点可以互相通信。 P_Key是一个16位的值&#xff0c;有两部分 msb…

第五章React路由

文章目录 一、React的基本使用1-1、react-router-dom1-1-1、react-router-dom基本使用 1-2、一般组件与路由组件1-3、NavLink1-4、封装自己的组件--MyNavLink1-5、Switch的使用1-6、多级地址样式失效问题1-7、模糊匹配1-8、严格匹配1-9、Redirect重定向1-10、嵌套路由1-11、向路…

Spring学习笔记3 Spring对IOC的实现

Spring学习笔记2 Spring的入门程序_biubiubiu0706的博客-CSDN博客 控制反转是一种思想 控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP(开闭原则)原则,达到DIP(依赖倒置原则)原则 控制反转,反转的是什么? 1.将对象的创建权力交出去,交给第三方容器负责 2.对象与对象…

1990-2021年全国31个省人口相关统计面板数据

1990-2021年全国31个省人口相关统计面板数据 1、时间&#xff1a;1990-2021年 2、来源&#xff1a;各省统计年鉴 3、指标&#xff1a;省份代码、年份、省份名称、省份名称、常住人口数、年末常住人口 0-14岁人口数、15-64岁人口数、65岁及以上人口数、6岁及以上人口&#x…

【精品资源】Java毕业设计攻略:从选题到答辩,一站式指南

导读&#xff1a; Java毕业设计是计算机科学与技术专业学生展示其编程能力、问题解决能力和创新思维的重要环节。这篇博客将为您提供一站式的Java毕业设计攻略&#xff0c;帮助您从选题到答辩&#xff0c;顺利完成毕业设计。 一、选题阶段 寻找灵感&#xff1a; 探讨热门技术如…

【Linux】Linux环境基础开发工具使用

Linux安装软件: 源代码安装rpm安装-- Linux安装包yum安装–解决安装源&#xff0c;安装版本&#xff0c;安装依赖 目录 一、Linux 软件包管理器 yum1.查看安装包2.安装软件3.删除软件一些有趣的软件的安装&#x1f47b;1.sl 二、vim编辑器1.使用vim1.模式转换2.命令模式2.底行模…

软件设计模式系列之十三——享元模式

1 模式的定义 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它旨在减少内存占用或计算开销&#xff0c;通过共享大量细粒度对象来提高系统的性能。这种模式适用于存在大量相似对象实例&#xff0c;但它们的状态可以外部化&#xff08;e…

人工神经网络ANN:数学总结

一、内容 径向基函数&#xff08;Radial basis function&#xff0c;RBF&#xff09;&#xff1a;一个取值仅依赖于到原点距离的实值函数&#xff0c;即。此外&#xff0c;也可以按到某一中心点c的距离来定义&#xff0c;即。 可以用于许多向函基数的和来逼近某一给定的函数&a…

SpringBoot使用@Async异步注解

首先&#xff0c;想一想为什么使用异步线程? 举个例子: 当我们请求这个接口的时候,在接口调用了method这个方法 然而被调用的方法执行了一个线程睡眠三秒 因为method方法睡眠了三秒钟,所以这个接口响应的时间肯定是大于三秒。因为接口是从上往下执行的,首先会在控制台输出一…

微信小程序python+nodejs+php+springboot+vue 讲座预约系统

讲座预约管理系统的用户是系统最根本使用者&#xff0c;按需要分析系统包括用户&#xff1a;学生、管理员。 管理员通过后台的登录页面&#xff0c;选择管理员权限后进行登录&#xff0c;管理员的权限包括学生信息管理和文章公告管理。讲座公告管理&#xff0c;添加讲座公告信息…