A相输出接到PA6,B相输出接到PA7,也可以AB相可以互换,但PA6,PA7引脚不能随便更换。
PA6,PA7是TM3的通道1和通道2。
第一步,RCC开启时钟,开启GPIO和定时器的时钟
第二步,配置GPIO,需将PA6和PA7配置成输入模式
第三步,配置时基单元,预分频器一般选择不分频,ARR一般给最大值655535,只需要CNT执行计数就行了。
第四步,配置输入捕获单元,这里只有滤波器和极性两个参数有用,后面的参数没有用到,与编码器无关。
第五步,配置编码器接口模式,直接调用一个库函数。
最后,调用TIM_Cmd,启动定时器。
定时器编码器接口配置
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);//TIMx第一个参数选择定时器,TIM_EncoderMode第二个参数选择编码器模式,TIM_IC1Polarity,TIM_IC2Polarity,分别选择通道1,通道2的电平极性。
- 编码器向右旋转,CNT自增,向左旋转,自减。
向左转到0之后在左转现象,0在自减,计数器反向溢出了,回到自动重装载值65535,然后继续往下减。 这就是使用uint16_t的现象。
如果想0之后变为-1就要把uint16_t强制转换成int16_t。 - 如想修改极性,在硬件层面,把A,B相两线对调一下,软件层面,可以修改软件通道的极性,把任意一个极性反转,方向就会反转,如两个极性都反转,极性保持不变。
如想测速的话,在固定的匝门时间读一次CNT然后把CNT清零,CNT的值代表速度,单位是脉冲个数/s(每秒)例:
int16_t Encoder_Get(void) //直接返回CNT的值
{
//测速,在固定的匝门时间读一次CNT然后把CNT清零
int16_t Temp; //定义一个临时变量Temp,因为要先读取CNT再清零,所以要用temp缓存一下
Temp = TIM_GetCounter(TIM3); //读取CNT
TIM_SetCounter(TIM3, 0); //CNT清零
return Temp;
}
main.c
程序最终现象是:向右旋转,正速度,向左旋转,负速度,停止速度为0。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"
int16_t Speed; //定义一个全局变量Speed
int main(void)
{
OLED_Init();
Timer_Init();
Encoder_Init();
OLED_ShowString(1, 1, "Speed:");
while (1)
{
OLED_ShowSignedNum(1, 7, Speed, 5); //主循环快速刷新显示Speed
}
}
//定时器2中断函数放在使用中断的main.c文件中,在startup文件中;定时中断每隔1s执行一次
void TIM2_IRQHandler(void)
{
//检查中断标志位
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Speed = Encoder_Get(); //定时器每隔1s读取一下速度,存在speed变量里
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除标志位
}
}
Encoder.c
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
//1.打开时钟,选择内部时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//2.初始化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_InternalClockConfig(TIM3);
//3.配置时基单元
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 65536 - 1是满量程计数,这样计数的范围是最大的而且方便换算成负数
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC 1-1=0,预分频器,不分频,编码器的时钟直接驱动计数器
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //初始化TIM3
//4.配置输入捕获单元(通道),编码器接口只使用了通道1和2的滤波器和极性选择,只配置这两部分代码
//结构体的配置不完整,需先定义结构体变量,然后StructInit给结构体赋一个初始值,再部分修改我们想要的参数,调用ICInit配置一遍电路,结构体变量的配置在调用ICInit函数之后就写入到硬件的寄存器了,所以ICInit之后这个结构体我们可以换个值继续使用、不需要重新定义新的结构体
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure); //结构体初始化,防止结构体中出现不确定值可能造成问题,最好用StructInit给结构体赋一个初始值
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //滤波器为0xF
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //通道2
TIM_ICInitStructure.TIM_ICFilter = 0xF; //滤波器为0x
//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //电平极性为上升沿,这里的上升沿参数代表的是高低电平极性不反转;等会配置编码器接口的时候也有极性配置,属于重复配置,这个其实可以删掉;这里的上升沿并不代表上升沿有效,因为编码器接口始终都是上升沿、下降沿都有效
TIM_ICInit(TIM3, &TIM_ICInitStructure);
//5.配置编码器接口,只需调用一个函数就行了;需保证TIM_EncoderInterfaceConfig在TIM_ICInit函数后面,否则TIM_ICInit覆盖TIM_EncoderInterfaceConfig函数的配置
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //TI12是TI1和TI2都计数,选择Rising是通道不反相,选择Falling是通道反相;重复配置TIM_ICPolarity_Rising,后面的参数会覆盖前面的参数配置,所以前面注释掉
//6.启动定时器
TIM_Cmd(TIM3, ENABLE);
}
int16_t Encoder_Get(void) //Encoder_Get 函数是用于测速的函数,主要实现了在固定的时间间隔内读取一次计数器的值,并清零计数器。函数返回了测量得到的编码器的变化值,即速度信息
{
//测速,在固定的匝门时间读一次CNT然后把CNT清零
int16_t Temp; //定义一个临时变量Temp,因为要先读取CNT再清零,所以要用temp缓存一下
Temp = TIM_GetCounter(TIM3); //读取CNT
TIM_SetCounter(TIM3, 0); //CNT清零
return Temp;
}
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif