编码器接口测速接线部分
接线图:
这里PA6和PA7两个引脚可以交换一下,A相接PA6,B相接PA7,或者A相接PA7,B相接PA6,都是可以的,就是正转和反转的极性不一样而已,但是PA6和PA7这两个引脚不能随便更换
PA6和PA7是TIM3的通道1和通道2,我们计划用TIM3接编码器,所以需要接在PA6和PA7这两个引脚
代码部分
第一步,RCC开启时钟,开启GPIO和定时器的时钟
c
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
第二步, 配置GPIO,这里需要把PA6和PA7配置成输入模式
c
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);
第三步,配置时基单元,这里预分频器我们一般选择不分频,自动重装,一般给最大65535, 只需要个CNT执行计数就行了
c
//TIM_InternalClockConfig(TIM3);这一行就不需要了,因为编码器接口会托管时钟,编码器接口就是一个带方向控制的外部时钟,所以这个内部时钟就没有用了
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
第四步, 配置输入捕获单元,不过这里输入捕获单元只有滤波器和极性这两个参数有用,后面的参数没有用到,与编码器无关
c
//看流程图发现,输入捕获单元并没有全部使用到,编码器接口只使用了通道1和通道2的滤波器和极性选择,所以我们只需要配置这两部分的参数即可,那在代码这里,就是后面这两个参数(上节代码部分),与编码器无关,删掉,或者你留看也行,只是它们目前没有作用,那删掉之后,目前结构体的配置是不完整的,为了防止结构体中出现不确定值可能会造成的问题,我们最好用structinit给结构体赋一个初始值,加个Structlnit也是提醒一下我们,结构体并没有配置完整
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //通道为1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //滤波器为0xF
//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //电平极性在默认赋值里面就是上升沿,上一小节我们说过,这里的上升沿并不代表上升沿有效,因为编码器接口始终都是上升沿、下降沿都有效的,这里的上升沿参数代表的是高低电平极性不反转,也就是我们PPT这里演示的TI1,TI2是否反相,对应通道,给上升沿,就是不反相,给下降沿 就是反相,其实这里的这个极性参数,等会儿我们配置编码器接口的时候也有,属于重复配置了,这里这个其实也可以删掉
TIM_ICInit(TIM3, &TIM_ICInitStructure); //最后传给ICInit,这些参数写到通道1,调用ICInit函数之后,就写入到硬件的寄存器了,所以Init之后,这个结构体我们可以换个值继续使用,不需要重新定义新的结构体了
//下面通道2
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
第五步,配置编码器接口模式,这个直接调用一个库函数就可以了
c
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //这后两个参数和上面代码极性选择部分是一样的,实际效果确实一样,这两个地方的参数,其实都配置的是同一个寄存器,属于重复配置了,后配置的参数,会覆盖前面的参数,所以我们可以把这前面那个极性的参数也删掉,只使用这个函数来配置极性,不过要注意,这时一定要保证,这个Encoder函数位于ICInit函数的下面,否则的话,就是ICInit覆盖Encoder函数的配置了
c
/**
* @brief Configures the TIMx Encoder Interface.
* @param TIMx: where x can be 1, 2, 3, 4, 5 or 8 to select the TIM peripheral.
* @param TIM_EncoderMode: specifies the TIMx Encoder Mode. 选择编码器模式
* This parameter can be one of the following values:
* @arg TIM_EncoderMode_TI1: Counter counts on TI1FP1 edge depending on TI2FP2 level.仅在TI1计数
* @arg TIM_EncoderMode_TI2: Counter counts on TI2FP2 edge depending on TI1FP1 level.仅在TI2计数
* @arg TIM_EncoderMode_TI12: Counter counts on both TI1FP1 and TI2FP2 edges depending 在TI1和TI2都计数
* on the level of the other input.
* @param TIM_IC1Polarity: specifies the IC1 Polarity 选择IC1的极性
* This parameter can be one of the following values:
* @arg TIM_ICPolarity_Falling: IC Falling edge. 选择Falling就是这个通道反相
* @arg TIM_ICPolarity_Rising: IC Rising edge. 选择Rising就是这个通道不反相
* @param TIM_IC2Polarity: specifies the IC2 Polarity 选择IC2的极性
* This parameter can be one of the following values:
* @arg TIM_ICPolarity_Falling: IC Falling edge.
* @arg TIM_ICPolarity_Rising: IC Rising edge.
* @retval None
*/
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode, uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity)
最后,调用TIM_Cmd,启动定时器, 就完事了
c
TIM_Cmd(TIM3, ENABLE);
电路初始化完成之后,CNT就会随着编码器旋转而自增自减,如果想要测量编码器的位置,直接读出CNT的值就行了,如果想测量编码器的速度和方向,那就需要每隔一段固定的闸门时间,取出一次CNT, 然后再把CNT清零,这样就是测频法测量速度了
流程看完, 我们来看一下库函数
c
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode, uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity); //定时器编码器接口配置,第二个参数选择编码器模式,然后后面两个参数 分别选择通道1和通道2的电平极性
调用一下这个Encoder_Init函数,编码器旋转就能控制CNT自增自减了
再写一个Get函数
c
int16_t Encoder_Get(void)
{
int16_t Temp; //为了显示负数,就直接把uint16_t类型强制转换成int16_t就行了
Temp = TIM_GetCounter(TIM3); //得到CNT的值
TIM_SetCounter(TIM3, 0); //为了测速度,读完后清零CNT
return Temp;
}
编码器每转一格(段落感)
A相提前还是滞后90度,取决于正转还是反转
那可以看出,现在编码器转动一格,A、B相各出现了一个下降沿和上升沿,所以计次总共加了4次,当然如果你是电机的编码器,那就不会有这个段落感了
main.c
c
#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();
Timer_Init();
Encoder_Init();
OLED_ShowString(1, 1, "Speed:");
while (1)
{
OLED_ShowSignedNum(1, 7, Speed, 5); //这个函数可以显示负数
//Delay_ms(1000);
//可以这个地方加delay函数,测这个delay时间段内计次的个数,如果你是编码电机飞速旋转的话,闸门时间就可以给短点,这样可以提高速度刷新的频率,而且防止计数器溢出,最好不要在主循环加入过长的Delay,这样会阻塞主循环的执行,那比较好的方法,就是用定时中断
}
}
void TIM2_IRQHandler(void) //这里定时中断自前是每隔1s执行一次,你可以修改定时中断的时间 来调整闸门时间
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Speed = Encoder_Get(); //每隔1s读取一下速度,存在Speed变量里,然后主循环,就可以快速刷新显示speed了
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
研究一下极性问题
自前向右转是增,向左转是减,如果这个方向和你想要的不一致的话,可以修改一下极性
在硬件层面,我们可以这样,把A、B相两根线换一下
在硬件层面,我们可以修改这里的(TIM_EncoderInterfaceConfig
), 两个输入通道的极性,把任意一个极性反转一下, 方向就会反过来,如果两个极性都反转,那极性还是保持不变