【STM32学习】——定时器的编码器接口&正交编码器&编码器接口测速代码实操


声明:学习笔记根据b站江科大自化协stm32入门教程编辑,仅供学习交流使用!

前言

引入:本实操案例与之前学习外部中断时写的旋转编码器计次的代码实现的功能基本一样,只不过之前的是通过触发外部中断,在中断函数中手动(软件++)进行计次;本代码是通过定时器的旋转编码器接口来自动计次的,使用编码器接口的好处是节约软件资源,避免频繁进中断而只是执行简单的++操作(极大浪费软件资源)。
应用场景:编码器测速一般应用在电机控制的项目上,使用PWM波驱动电机,再使用编码器测量电机速度,然后再用PID算法进行闭环控制!
注意:一般电机旋转速度比较高,会使用无接触式的霍尔传感器或光栅进行测速,本案例为了方便就使用触点式旋扭编码器来演示,电机旋转用手拧来模拟,效果一样!

一、编码器接口

芯片手册14.3.12

1.简介

Encoder Interface 编码器接口
1、编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置旋转方向旋转速度
2、每个高级定时器和通用定时器都拥有1个编码器接口。
3、编码器接口其实就相当于是一个带有方向控制的外部时钟,同时控制着CNT的计数时钟和计数方向。借用上一节输入捕获中测周法和测频法的知识点,编码器测速实际就是测频法测正交脉冲的频率,只不过编码器计次更高级:能根据旋转方向不仅能自增也可自减,是一个带方向的测速。
在这里插入图片描述
如上图绿框:
1、编码器有两个输入端,分别接到正交编码器的A、B两相引脚,两个输入引脚借用了输入捕获的通道1通道2,即TIFP1与TI2FP2与下面输入捕获通道的TIFP1与TI2FP2是连接在一起的。所以编码器最外面的输入引脚就是图左下的定TIMx_CH1和CH2两个引脚,信号从这两个引脚通过通道1或2到TIFP1与TI2FP2再到编码器接口。CH3与CH4与编码器接口无关。(红线)
2、编码器的输出部分,相当于从模式控制器,去控制CNT的计数时钟和计数方向。输出执行流程为:出现边沿信号时,并且根据下表判断为正转,则控制CNT自增,反之自减。注意之前一直使用的72MHz内部时钟CK_PSC和时基单元初始化时的计数方向+/-并不会使用,因为此时计数时钟和计数方向都处于编码器接口托管的状态,CNT的自增或自减由编码器控制。(绿线)
单一把编码器部分拿出来:
在这里插入图片描述
工作模式:(该表与下面正交编码器部分对应,正转向上计数,反转向下)
在这里插入图片描述
仅在TI1计数表示仅统计A相边沿时B相的状态,精度比在A、B边沿都都统计另一相状态要低。
实例(均不反相):(毛刺部分为抗噪声功能,上下摆动不计次)
所谓反相,即两个边沿检测与极性选择处选择下降沿参数,信号通过一个非门高低电平翻转。
在这里插入图片描述
实例(TI1反相):(极性变化对计数的影响)
TI1反相,即下面的TI1数字波需要高低反转下再对应查表,判断向下还是向上计数。反相功能的作用:比如实际需要的正反转(因为是相对的根据需要)与CNT自增自减不对应,就可反相一下。
在这里插入图片描述

2.正交编码器

正交:A、B两相位相差90°,用两相可以表示出方向(正反转是相对的):
在这里插入图片描述
出现边沿信号时就计数自增或自减,到底是自增还是自减由另一相的状态决定,这样就可以有方向计数。

二、实操案例(编码器接口测速)

在这里插入图片描述

//Encoder.c
#include "stm32f10x.h"                  // Device header
/*第一步:RCC开启时钟,开启GPIO和定时器的时钟
  第二步:配置GPIO,需要把PA6和PA7配置成输入模式
  第三步:配置时基单元,其中prescaler一般选择不分频,自动重装ARR一般给最大65535,只需要个CNT执行计数就可
  第四步:配置捕获单元,这里只需要滤波器和极性两个参数,其他参数与编码器无关
  第五步:配置编码器接口模式,一个库函数即可
  最后:TIM_Cmd启动定时器
电路初始化完成后,CNT会随着编码器旋转而自增自减。
若想测编码器的位置,直接读出CNT的值即可;若想测编码器的速度和方向,就需要每隔一段固定的闸门时间取出一次CNT,然后再把CNT清零(测频法测速度)
*/
void Encoder_Init(){
    
    
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE );
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	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 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;//不分频,编码器时钟直接驱动计数器CNT
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICStructInit(&TIM_ICInitStructure);//保证结构体配置完整,无用参数为初始化默认值
	
	TIM_ICInitStructure.TIM_Channel= TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter= 0xF;//0x0~0xF数越大滤波效果越好
	TIM_ICInitStructure.TIM_ICPolarity= TIM_ICPolarity_Rising;//边沿检测和极性选择
	//这里的上升沿与之前不同,不代表上升沿有效,因为编码器始终时上升下降沿都有效,这里表示高低电平极性不翻转
	//TIM_ICInitStructure.TIM_ICPrescaler= TIM_ICPSC_DIV1;//无效参数可不要,但要保证结构体完整加TIM_ICStructInit()函数
	//TIM_ICInitStructure.TIM_ICSelection= TIM_ICSelection_DirectTI;//无效参数可不要
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_Channel= TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICFilter= 0xF;
	TIM_ICInitStructure.TIM_ICPolarity= TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	//后两个参数与上面配置通道1、2时的TIM_ICPolarity_Rising一样,所以上面的可以删掉
	
	TIM_Cmd(TIM3,ENABLE);
}

int16_t Encoder_Get(void){
    
    
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);//读取CNT
	TIM_SetCounter(TIM3,0);//清零CNT
	return Temp;
	
}
//Timer.c
//在6-1定时器定时中断工程上更改

#include "stm32f10x.h" 
#include "Encoder.h"
extern int16_t Speed;//extern表示跨文件使用变量

void Timer_Init(void){
    
    
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );
	TIM_InternalClockConfig(TIM2);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;//1倍时钟分频
	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;//向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period= 10000-1;//ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler= 7200-1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;//重复计数器的值,高级定时器才有,赋0
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//时基单元初始化
	
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel= TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd= ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority= 1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM2,ENABLE);
}

void TIM2_IRQHandler (void){
    
    
	if (TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
    
    
		Speed=Encoder_Get();//每隔一秒读取一下速度,存在Speed里
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//最后别忘清除标志位
	}
	
}
//main.c
#include "stm32f10x.h"   // Device header
#include "Delay.h"   
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

uint16_t Speed;
int main(void){
    
    
	OLED_Init();
	//Timer_Init();
	Encoder_Init();
	
	OLED_ShowString(1,1,"Speed:");
	
	while(1){
    
    
		OLED_ShowSignedNum(1,7,Speed,5);//可显示正数或负数
		//Delay_ms(1000);
		//因为手转比较慢,闸门时间就给1s。如果电机飞速旋转可给短点,可提高速度刷新频率,且防止计数器溢出
		//Delay实现闸门时间会阻塞程序;可用Timer.c文件的TIM2_IRQHandler ()中断函数实现闸门时间
	}
}

总结心得

软硬件资源是可以互补的!硬件不够软件来凑,软件太累硬件代替!比如PWM可以直接用个定时中断,然后在中断里手动计数,手动反转电平;比如输入捕获可以来个外部中断,在中断里手动把CNT取出来放在变量里;比如本案例编码器接口也可用外部中断实现。但这样会浪费软件资源,一般有硬件资源的情况下应该优先使用硬件资源,节约下的软件资源可以去做更重要的事
在这里插入图片描述往期精彩:
STM32定时器输入捕获(IC)
STM32定时器输出比较(PWM波)
STM32定时中断
STM32外部中断
STM32GPIO精讲

猜你喜欢

转载自blog.csdn.net/weixin_51658186/article/details/129632478