STM32
目录
1.前言:
2.编码器接口简介:
编码器接口工作流程:
3.正交编码器
3.1正交信号计次和区分方向
3.2编码器设计逻辑:
4.编码器接口电路结构
5.编码器接口基本结构
5.1补码:
6.工作模式(编码器接口的工作逻辑)
6.1三种工作方式(模式):
7.实例(均不反相)
8.实例(TI1反相)
9.编码器接口测速
9.1编码器结构初始化
9.2 有关编码器库函数:
9.3代码编写
9.3.1Encoder.c
9.3.2注意点:
9.3.3encoder.h
9.3.4main.c
9.4编码器接口测速
9.4.1main.c(部分修改)
9.4.2encoder.c(部分修改)
9.4.3定时器中断
#TIM编码器接口#
1.前言:
使用编码器接口计次主要为了节约软件资源
对于频繁执行,操作简单的任务,设计一个硬件电路模块,自动完成
自动给编码器计次,若每隔一段时间取一次计次值,可得到编码器旋转速度
使用定时器编码器接口,再配合编码器,可测量旋转速度和旋转方向
编码器测速一般应用在电机控制项目上(eg.PWM驱动电机+编码器测量电机速度+PID算法进行闭环控制)
一般电机旋转速度比较高,会使用无接触式的霍尔传感器或者光栅进行减速
编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到 TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计 数)。所以在开始计数之前必须配置TIMx_ARR;同样,捕获器、比较器、预分频器、触发输出 特性等仍工作如常。
2.编码器接口简介:
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度(接收正交信号,自动执行CNT的自增或自减)
编码器接口工作流程:
编码器(输出A相B相)接入STM32的定时器的编码器接口(自动控制定时器时基单元中CNT计数器,进行自增和自减)
初始化后,CNT初始值为0,编码器右转,CNT++,右转产生一个脉冲,CNT+1,如果右转产生十个脉冲后停下来,CNT由0自增到10。编码器左转CNT--,左转产生一个脉冲,CNT-1,如果编码器左转产生五个脉冲,CNT在原来10的基础上自减5。
编码器接口相当于一个带有方向控制的外部时钟。它同时控制着CNT的计数时钟和计数方向,CNT的值就表示了编码器的位置。
每隔一段时间取一次CNT的值,再CNT清零,每次取出来的CNT值表示编码器的速度。
编码器测速本质是测频法测正交脉冲的频率。
CNT计次,每隔一段时间取值一次,这就是测频法的思路。
- 每个高级定时器和通用定时器都拥有1个编码器接口
若一个定时器配置成编码器接口模式,只能作用于这个模式,其他模式完成不了。C8T6芯片只有4个定时器,因此最多只能接四个编码器,并且接完四个编码器,无其他定时器定时器可以用。
若编码器比较多,需要考虑资源充不充足。也可以用外部中断来接编码器(用软件资源补硬件资源)
硬件资源和软件资源是互补的。硬件资源越多,软件就会越轻松,硬件不够,就软件弥补。
但优先选择硬件资源,可节约软件资源去作用更多事情。
- 两个输入引脚借用了输入捕获的通道1和通道2
编码器的两个输入引脚就是每个定时器的CH1和CH2引脚
3.正交编码器
测量位置和带有方向的速度值
输出俩个方波信号,相位相差90°,正转:超前90°,反转:滞后90°
转的越快,方波频率越高。因此方波的频率代表速度。
取出任意一相的信号来测频率,可知旋转速度。
但是只有一相的信号无法测量旋转方向。因为无论正转还是反转,都是相同的方波,想要测量方向,必须要有另一根线的辅助。
方法:
①不要B相输出,再定义一个方向输出脚,正转至高电平,反转至低电平。(不是正交信号)
②正转时,A相提前B相90°;反转时,A相滞后B相90°(正反转是相对的,A相提前滞后不是绝对的)。(正交信号)
使用正交信号相比较单独定义一个方向引角的优势:
①正交信号精度更高(AB相都可计次,相当于计次频率提高一倍)
②正交信号可以抗噪声(俩信号交替跳变,可设计一个抗噪声电路)
3.1正交信号计次和区分方向

边沿 | 另一相状态 |
A相↑ | B相低电平 |
A相↓ | B相高电平 |
B相↑ | A相高电平 |
B相↓ | A相低电平 |

边沿 | 另一相状态 |
A相↑ | B相高电平 |
A相↓ | B相低电平 |
B相↑ | A相低电平 |
B相↓ | A相高电平 |
3.2编码器设计逻辑:
先将A相B相所有边沿作为计数器的计数时钟。出现边沿信号时就计数自增或自减。
编码器接口执行逻辑:计数的方向由另一相的状态来确定。
当出现某个边沿时,判断另一相的高低电平。如果对应另一相的状态出现在正转模式表格,就是正转,自增;反之,另一相的状态出现在反转模式表格,就是反转,自减。
4.编码器接口电路结构
编码器接口在定时器中的位置👇
编码器的输入引脚是定时器的CH1和CH2这两个引脚
编码器接口的输出部分,相当于从模式控制器,控制CNT的计数时钟和计数方向。
输出流程:
若出现边沿信号,且对应的另一相状态是正转,CNT++,反之,CNT--。
注意:
①不使用内部时钟72MHz
②不使用时基单元初始化时设置的计数方向
(计数时钟和计数方向都处于编码器接口托管状态,计数器的自增和自减受编码器控制)
5.编码器接口基本结构
输入捕获1通道和2通道通过GPIO口接入编码器A相和B相,经过滤波器和边沿选择产生TI1FP1和TI2FP2,通向编码器接口,编码器接口通过与分屏器控制CNT计数器时钟,同时,编码器接口根据编码器的旋转方向,控制CNT的计数方向(正转CNT+,反转CNT-)。ARR一般为65535(最大量程,可根据补码特性,易得到负数)
5.1补码:
- 补码是计算机存储有符号整数(int8, int16, int32等)的标准方式。
- 正数和零: 和原码一样。最高位是 0,后面是数值本身的二进制。
- 负数: 最高位是 1。计算方法是 “原码取反加一” (这里的原码指该负数绝对值对应的原码)。
步骤:
①取绝对值 X 的原码: 先写出 +X 的二进制表示(符号位为0)。
②按位取反(得到反码): 把 +X 原码的每一位 0 变 1,1 变 0(包括符号位)。
③加 1: 在反码的最低位加 1。注意二进制加法,可能需要进位。
- 例子:求 -5 的补码(8位)
对值 5 的原码: 0000 0101
按位取反(反码): 1111 1010
加 1: 1111 1010 + 1 = 1111 1011
所以 -5 的补码 = 1111 1011
- 溢出条件(加法):
正溢出:正数 + 正数 = 负数(符号位从 0+0 变成 1)。
负溢出:负数 + 负数 = 正数(符号位从 1+1 变成 0)。
- 例子(8位):
127 + 1 = 0111 1111 + 0000 0001 = 1000 0000(应为 128,实际是 -128 → 正溢出)。
-128 + (-1) = 1000 0000 + 1111 1111 = 0111 1111(应为 -129,实际是 127 → 负溢出)。
补码特性: …… 65534 65535 0 1 2 3 4……
初始化CNT,正转+,反转-到0后为65535,将16位无符号数转换成16位有符号数
其中,65535=-1 65534=-2……
6.工作模式(编码器接口的工作逻辑)
相对电平信号就是另一个电平信号的状态
正转:向上计数 反转:向下计数
6.1三种工作方式(模式):
STM32 的编码器接口很灵活,可以根据编码器的类型或你的需求,选择让计数器在什么情况下计数:
- 模式 1(Encoder Mode TI1):
- 触发条件: 计数器只在 TI1 的边沿(上升沿或下降沿,可配置)发生变化时,才可能计数。
- 计数方向: 当 TI1 发生有效边沿时,硬件会看一眼当前 TI2 的电平状态:
假设TI1为TI1FP1,TI2为TI2FP2
- 如果 TI2 = 低电平 (0),计数器 +1(正转)。(A相↑,B相低电平)
- 如果 TI2 = 高电平 (1),计数器 -1(反转)。(A相↑,B相高电平)
- 通俗说: “老大” TI1 动的时候,“小弟” TI2 的状态告诉我们是加还是减。
- 分辨率: 每转一圈(或编码器的一个周期),计数器最多变化 N 次(N 是编码器线数 * 4? 不对!模式1下,通常每个周期计数 2 次。适合只需要中等分辨率的场合。
- 模式 2(Encoder Mode TI2):
- 触发条件: 计数器只在 TI2 的边沿(上升沿或下降沿,可配置)发生变化时,才可能计数。
- 计数方向: 当 TI2 发生有效边沿时,硬件会看一眼当前 TI1 的电平状态:
假设TI1为TI1FP1,TI2为TI2FP2
- 如果 TI1 = 高电平 (1),计数器 +1(正转)。(B相↑,A相高电平)
- 如果 TI1 = 低电平 (0),计数器 -1(反转)。(B相↑,A相低电平)
- 通俗说: 跟模式1反过来,“老大”换成 TI2,看“小弟” TI1 的状态决定加减。
- 分辨率: 与模式1相同,通常每个编码周期计数 2 次。
- 模式 3(Encoder Mode TI1 and TI2)(最灵敏):
- 触发条件: 计数器在 TI1 或 TI2 的任意一个有效边沿(上升沿/下降沿,可配置)发生变化时,都可能计数!这是最灵敏的模式。
- 计数方向: 无论哪个信号边沿到来,硬件都会根据 TI1 和 TI2 当前的相对相位关系(谁领先谁)来决定是 +1 还是 -1。
- 通俗说: 两个信号线上的任何风吹草动(边沿)都会让计数器动一下,而且方向判断总是基于它俩最新的“位置关系”。
- 分辨率: 最高! 通常每个编码周期计数 4 次(分别在 A 上升、B 上升、A 下降、B 下降时计数)。适合需要最高精度的场合(比如高精度位置控制),但对信号质量和 MCU 速度要求也最高。
7.实例(均不反相)
抗噪声原理:一个引脚不变另一个引脚多次变化的毛刺信号,计数器回+-来回摆动,最终数字还是原本的数字
8.实例(TI1反相)
TI反相:高低电平反转,CNT计数时,TI1的信号都是反相)极性的变化对计数的影响
若CNT计数自增自减错误,解决方法:
①可调整极性,将任意一个引脚反相,就可反转计数方向
②AB相转换
9.编码器接口测速
9.1编码器结构初始化
编码器结构初始化
①RCC开启时钟,开启GPIO和定时器的时钟
②配置GPIO,将PA6和PA7配置成输入模式
③配置时基单元,预分频器不分频,ARR65535,只需CNT计数即可
④配置输入捕获单元(滤波器和极性选择俩个参数)
⑤配置编码器接口模式
⑥启动定时器
CNT随编码器旋转自增自减
结果:
①测量编码器的位置,读出CNT值
②测量编码器的速度和方向,需每隔一段固定的闸门时间,取出CNT并把CNT值清零
9.2 有关编码器库函数:
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);
配置编码器接口函数第一个参数选择定时器,第二个参数选择编码器模式,后俩个参数选择通道1和通道2的电平极性
9.3代码编写
9.3.1Encoder.c
编码器接口测量位置
#include "stm32f10x.h" // Device headervoid Encoder_init(void)
{//①RCC开启时钟,开启GPIO和定时器的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//开启TIM3时钟(APB1)//②配置GPIO,将PA6和PA7配置成输入模式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);/*定时器内部时钟配置不需要,因为编码器接口回托管时钟,编码器接口就是一个带方向控制的外部时钟*///③配置时基单元,预分频器不分频,ARR=65535,只需CNT计数即可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_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(&TIM_ICInitStructure);//结构体初始化TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;//输入捕获通道1TIM_ICInitStructure.TIM_ICFilter=0xF;//滤波器,若有噪声,可增加滤波器参数,有效避免干扰TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//边沿检测极性选择/*上升沿并不代表上升沿有效,编码器接口始终都是上升沿和下降沿都有效的这里的上升沿表示高低电平极性不反转——上升沿,不反相;下降沿,反相*/TIM_ICInit(TIM3,&TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel=TIM_Channel_2;//输入捕获通道2TIM_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);/*@arg TIM_ICPolarity_Falling: IC Falling edge.通道反相@arg TIM_ICPolarity_Rising: IC Rising edge.通道不反相可根据实际情况配置这俩个参数根上面边沿检测极性选择配置同一个寄存器且效果相同,后配置参数回覆盖前面参数,可删除边沿检测参数*///⑥启动定时器TIM_Cmd(TIM3,ENABLE);
}//获取CNT值
/*
OLED显示为65534 65535 0 1 2 3 ……
uint16_t Encoder_Get(void)
{return TIM_GetCounter(TIM3);
}
*/
//OLED显示为-2 -1 0 1 2 3……
int16_t Encoder_Get(void)
{return TIM_GetCounter(TIM3);
}
9.3.2注意点:
1.配置GPIO输入模式:
上拉下拉如何选择?
看接在引脚处的外部模块输出的默认电平,与其状态保持一致,防止默认电平打架
①若外部模块空闲默认输出高电平,选择上拉输入,默认输入高电平
②若外部模块空闲默认输出低电平,选择下拉输入,默认输入低电平
③若不确定外部魔魁啊输出默认状态,或者外部信号输出功率非常小,选择浮空输入(当引悬空,没有默认电平,输入就会受噪声干扰,不断跳变)2.时钟配置不需要
定时器内部时钟配置不需要,因为编码器接口回托管时钟,编码器接口就是一个带方向控制的外部时钟
3.编码器接口库函数@arg TIM_ICPolarity_Falling: IC Falling edge.通道反相
@arg TIM_ICPolarity_Rising: IC Rising edge.通道不反相
可根据实际情况配置
这俩个参数根上面边沿检测极性选择配置同一个寄存器且效果相同,后配置参数回覆盖前面参数,可删除边沿检测参数
4.CNT的值
uint16_t 与int16_t的区别
无符号数>0 有符号数-~+
5.极性修改
硬件上:AB相输入调换引脚
软件上:在编码器接口函数内,任意一个通道的极性反相即可
9.3.3encoder.h
#ifndef __ENCODER_H_
#define __ENCODER_H_
void Encoder_init(void);
int16_t Encoder_Get(void);#endif
9.3.4main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "encoder.h"int main(void)
{ OLED_Init();Encoder_init();
// Timer_Init();OLED_ShowString(1,1,"CNT:");while(1){//OLED_ShowNum(2,3,Encoder_Get(),5);OLED_ShowSignedNum(2,3,Encoder_Get(),5);//显示负数}
}
9.4编码器接口测速
9.4.1main.c(部分修改)
while(1){//OLED_ShowNum(2,3,Encoder_Get(),5);OLED_ShowSignedNum(2,3,Encoder_Get(),5);//显示负数Delay_s(1);}
}
9.4.2encoder.c(部分修改)
int16_t Encoder_Get(void)
{int16_t temp;temp =TIM_GetCounter(TIM3);//清零CNTTIM_SetCounter(TIM3,0);return temp;
}
注意:闸门时间短一点,防止计数器计数溢出
Delay函数占用空间,可用定时器中断进行
9.4.3定时器中断
//⑦配置中断函数
void TIM2_IRQHandler(void)
{//检查中断标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){speed=Encoder_Get(); //清楚标志位TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}}