介绍

常用于电机调速,用PWM信号控制电机速度,编码器测速,再通过PID算法去闭环控制

正交编码器

正转和反转波形中的四种状态都是不同的,由此可以判断方向,至于哪种是正转反转是可以极性选择的。

编码器接口内部结构

编码器接口的输入端连接了TI1FP1,TI2FP2,也就是占用了输入捕获的CH1引脚和CH2引脚的通道1和通道2,另外两个通道没有涉及;并且编码器接口也使用了输入捕获通道上的滤波器和边沿检测,之后的交叉通道、预分频器、CCR寄存器都没有涉及了。

而编码器接口的输出端则通过从模式控制了定时器,根据检测边沿和对应的高低电平来判断正反转,从而控制定时器中的CNT自增或自减。在这里的72MHZ内部时钟和初始化设置的计数方向都不起作用,因为此时定时器是由编码器接口控制的。

基本流程

外部正交编码器的A、B相输出信号分别输入CH1引脚和CH2引脚,也就是对应复用的GPIO口,接着通过滤波、边沿检测等形成TI1FP1,TI2FP2信号输入到编码器接口,编码器接口控制定时器CNT自增自减,PSC可以控制CNT的时钟;ARR是正向最大值,反相时本来0再减一就是65535,但需要的是-1,此时就可以用强制类型转换成有符号数就能得到-1,所以凭借ARR可以得到负值。

工作模式

向上计数和向下计数就是自增自减的意思

假设TI1FP1连接编码器A相,TI2FP2连接B相。只在TI1计数就是只看TI1FP1的边沿对应TI2FP2的高低电平,不看TI2FP2的边沿;只在TI2计数类似,这样测量精度会下降;

一般都使用在TI1,TI2均计数,二者的边沿都看,精度更高。

举例

不反相

前面说过输入信号会经过边沿检测极性选择,这里的边沿检测极性选择是针对信号高低电平是否反转,选择上升沿就是不反相,下降沿是反相;反相就是经过一个非门使信号高电平变低电平,低电平变高电平。

图中还表现了正交编码器的抗噪声能力,在因为噪声产生毛刺时,如图,TI2保持低电平,但TI1产生了多次电平跳变,经过增减增减后CNT的值保持不变。

反相

TI1信号反相,编码器输入进来的信号是图中黑线,但是经过极性选择,实际输入到编码器接口的信号是图中红线。由此也能看出,反相可以改变计数方向,当需要转换计数方向的时候就可以设置其中一个信号反相,或者直接交换AB相。

代码实现

配置编码器接口代码

使用编码器接口,先开启定时器和GPIO的RCC,再初始化GPIO口,接着配置时基单元,然后配置输入捕获单元(这里只会使用滤波器和边沿检测极性选择),再配置编码器接口,最后开启定时器

void encoder_init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//初始化GPIO口,这里使用TIM3,占用了它的CH1,CH2,这两个引脚复用在PA6,PA7
	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,设置最大,防止CNT溢出
	TIM_TimeBaseInitStructure.TIM_Prescaler=0;//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;//通道1
	TIM_ICInitStructure.TIM_ICFilter=0XF;//滤波
	TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//这里的上升沿表示不反相
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_Channel=TIM_Channel_2;//通道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);
//TIM3,TI1和TI2都计数,都不反相,这里和输入捕获极性配置是一样的,在保证编码器接口函数在输入捕获后面时可以把输入捕获极性选择去掉
	
	//开启定时器
	TIM_Cmd(TIM3,ENABLE);
}
测速
//然后取出CNT的值,因为需要负数,直接强制类型转换为有符号类型,测速需要规定一个闸门时间,速度就看这段时间产生了多少脉冲即CNT变化了多少,越快变化越大。隔这样一段时间就读取一次CNT的值,然后将其清零,以便下次计数
int16_t encoder_CNT(void)
{
	int16_t temp;
	temp=TIM_GetCounter(TIM3);//CNT的值先赋给temp
	TIM_SetCounter(TIM3,0);//将TIM3的CNT清零
	return temp;
}
主函数中
//使用主函数中延时的方法
//int main()
//{
//	OLED_Init();
//	encoder_init();
//	OLED_ShowString(1,1,"CNT:");
// while(1)
// {
//	 OLED_ShowSignedNum(1,5,encoder_CNT(),5);
//	 Delay_ms(1000);//闸门时间,主程序没什么任务可以用延时,但如果任务多就会阻塞主程序,此时就可以改用中断
// }
//}

//使用中断
int16_t speed;
int main()
{
	OLED_Init();
	encoder_init();
	timer_init();
	OLED_ShowString(1,1,"speed:");
 while(1)
 {
	 OLED_ShowSignedNum(1,7,speed,5);
 }
}
//在定时器初始化函数timer_init()已经配置好了ARR这些值,是一秒进入一次中断,相当于一秒读取一次TIM3中CNT的值
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		speed=encoder_CNT();
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

(GPIO输入模式的选择:如果外部信号默认输出高电平,GPIO就选择上拉输入,默认输入高电平;外部信号默认输出低电平,那GPIO就需要下拉输入,默认输入低电平,这样保证默认电平不冲突;当不确定外部信号默认电平或者外部功率低时,GPIO就选择浮空输入,只不过这时候当引脚浮空时容易受影响)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐