前言
编码器接口测速与之前的旋转编码器计次代码实现的功能基本一样, 只不过这个代码是通过定时器的编码器接口自动计次的,之前的的代码时通过触发外部中断,然后在外部中断里手动计次。
旋转编码器计次可参考:{STM32} 江科大学习笔记-EXTI外部中断(下)_江科大stm32笔记exti-CSDN博客
编码器接口简介
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
- 每个高级定时器和通用定时器都拥有1个编码器接口
- 两个输入引脚借用了输入捕获的通道1和通道2
旋转编码器简介可参考下面这篇文章旋转编码器部分,里面也涉及到正交波形介绍
[STM32] 江科大-EXTI外部中断(学习笔记-上)_江科大stm32中断笔记-CSDN博客
正交编码器
正交编码器(Quadrature Encoder)是一种常用的位置传感器,用于检测旋转运动或线性运动的位置、速度和方向。正交编码器由两个光电传感器组成,这两个传感器输出的信号相位差为90度,因此称为正交编码器。通过检测两个传感器的信号脉冲,可以确定物体的准确位置和移动方向。
编码器接口的设计逻辑:首先把A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,计数器就自增或者自减,到底是增还是减呢?这个计数的方向由另一相状态来决定,当出现某个边沿时,我们就判断另一相的高低电平,如果对应另一相的状态出现在上面的表格里,那就是正转,计数自增;反之,另一相的状态出现在下面的表格里,就是反转,计数自减;这样就能实现编码器接口的功能了,这也是我们STM32定时器编码器接口的执行逻辑。
正转 (上)
反转(上)
编码器接口工作流程:书面解释
拓展知识
使用编码器接口的好处: 使用编码器接口可以带来更灵活、可扩展、互操作、高效和可靠的系统,有利于降低成本,提高生产效率和产品质量。
编码器接口测速通常应用在需要实时监测物体运动速度和位置的场合,如:
机器人控制系统:用于测量机器人关节的转动速度,实现精确的轴控制和路径规划。
电机控制系统:连接到电机轴上,测量电机转速和位置,用于闭环控制系统,提高电机运动的准确性和稳定性。
CNC数控机床:用于测量工件或刀具的运动速度和位置,确保加工操作的精确性。
输送系统:连接到输送带上,测量物料在输送带上的速度,实现自动输送和分拣。
速度调节控制器:用于测量车辆或机械设备的速度,控制电机或液压系统的输出力,实现速度调节和动力平衡。
包装机械:连接到包装机械上,测量机械的速度和位置,确保包装过程的精准性和稳定性。
总的来说,编码器接口测速在需要精确控制物体位置和速度的自动化系统和运动控制系统中具有重要应用。
本节代码功能:使用定时器的编码器接口,再配合编码器,就可以测量旋转时间和旋转方向。
定时器结构框图
输入
输出
输出部分就相当于从模式控制器,去控制计数器的计数时钟和计数方向, 简单来说就是出现边沿信号,正转自增,反转自减。
注意:这里我们之前一直使用的72MHz内部时钟和时基单元初始化设置的计数方向并不会使用,因为此时计数时钟和计数方向都处于编码器接口的托管状态,计数器的增减受编码器控制。
编码器接口基本结构
基本结构框图
编码器接口工作模式
编码器接口三种工作模式(上)
写代码时配置模式(下)
正转的状态都向上计数,反向的状态都向下计数。
实例图(均不反相)

实例(TI1反相)极性变化对计数影响

实列讲解:22:38
看手册:25:12
编码器接口测速
接线图: 这里接旋转编码器的PA6和PA7可以交换连接,就是正转和反转的极性不一样

但是PA6和PA7两个引脚不能随便换,因为引脚定义表有规定
计划用TIM3定时器的通道1和通道2接编码器,所有不能随便改。
编码器接口初始化
步骤:
①RCC开启时钟(把要用的TIM外设和GPIO外设时钟都打开)
②GPIO初始化,把PA6和PA7配置为输入模式 ,一般选择上拉下拉或者浮空输入模式
③配置时基单元,预分频器不分频,自动重装载值,给最大65536,
④配置输入捕获单元,这里只有滤波器、极性选择有用
⑤配置编码器接口模式
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity)//配置TIMx编码器接口
|-----------------|---------------------------|
| 参数 | 说明 |
| TIMx | 其中x可以为1、2、3、4、5或8,选择TIM外设 |
| TIM_EncoderMode | 指定TIMx编码器模式 |
| TIM_IC1Polarity | 指定IC1极性 |
| TIM_IC2Polarity | 指定IC2极性 |
具体参选选择
⑥最后调用TIM_Cmd() 启动定时器
具体配置:
cs
void Encoder_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode =TIM_CounterMode_Up;//这里不起作用,编码器托管
TIM_TimeBaseInitStruct.TIM_Period =65536 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler =1 - 1 ;//不分频,编码器时钟直接驱动计数器
TIM_TimeBaseInitStruct.TIM_RepetitionCounter =0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICStructInit(&TIM_ICInitStruct); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_ICInitStruct.TIM_Channel =TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter =0xF;
TIM_ICInit(TIM3,&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel =TIM_Channel_2;
TIM_ICInitStruct.TIM_ICFilter =0xF;
TIM_ICInit(TIM3,&TIM_ICInitStruct);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
//配置编码器模式以及两个输入通道是否反相
//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
TIM_Cmd(TIM3,ENABLE);
}
上拉和下拉如何选择?
一般可以看一下接在这个引脚的外部模块输出的默认电平,如果外部模块空闲默认输出高电平,我们就选择上拉输入,默认输入高电平。如果外部模块空闲默认输出低电平,我们就选择下拉输入,默认输入低电平。和外部模块保持默认状态一致,防止默认电平打架。一般来说默认高电平,所以上拉输入用的比较多。如果不确定外部模块输出默认状态,或者外部信号输出功率非常小,这是就尽量选择浮空输入,浮空输入没有上拉电阻和下拉电阻去影响外部信号,但是缺点就是当引脚悬空时,没有默认电平,输入就会受噪声干扰,来回不断跳变。
内部时钟不需要配置,因为编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟
小提示:

获取编码器增量值
cs
/**
* 函 数:获取编码器的增量值
* 参 数:无
* 返 回 值:自上此调用此函数后,编码器的增量值
*/
int16_t Encoder_Get(void)
{
/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
int16_t Temp;
Temp = TIM_GetCounter(TIM3);//获取TIMx计数器值
TIM_SetCounter(TIM3,0);//设置TIMx计数器寄存器值 清零
return Temp;
}
编码器每转一格输出波形其实是这样的:A相产生一个低电平脉冲,B相产生一个相位差90°的脉冲,波形提前还是滞后取决于正转还是反转,如果连续转动就跟前面介绍的正交编码器的波形一样。
波形:上A下B
转一格计数4个

用定时中断计数
cs
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更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
右转自增,左转自减,停止不转数值归零。 增减数值大小由转动速度控制。