平衡小车之编码器的使用(深夜学习——单片机)
一、平衡小车要编码器干什么?
采集电机的转速,并转化成脉冲信号并发送给单片机,最后计算出小车的速度。
二、编码器原理:(以常用的增量式为例)
-
霍尔编码器:
(1)组成:
霍尔编码器主要包括两个部分:霍尔码盘,霍尔元件组成。
霍尔码盘:由多个NS极间隔的圆形磁体,其中你买电机看到xx线,就是指有多少组NS极
霍尔元件:一种基于霍尔效应,能检测磁场的元件(如果不懂霍尔效应,建议先去了解一下),当N极靠近就产生高电平;当S极靠近就产生低电平
(3)工作原理:
假设初始位置如上图所示,则A、B输出的电压如图:
当主轴顺时针旋转时,输出脉冲A通道信号位于B通道之前;
当主轴逆时针旋转时,输出脉冲A通道信号位于B通道之后。
(4)由输出信号得出转速:
假设编码器为13线,也就是每转一圈产生13个脉冲信号,我们只需要在单位时间内对产生的脉冲信号进行计数,再将总数除以13和计数时间,就可以得出转速。
(5)由AB确定转动方向:
通过观察上图可以知道,顺时针转动时,A相会先变化,导致A相上升沿,B相处于高电平;A相下降沿,B相处于低电平。逆时针时,B相会先变化,导致A相上升沿,B相处于低电平,A相下降沿,B相处于低电平。
三、计算方法:
-
倍频:
(1)LSB最低有效位:
类似于一个长10cm的尺子,假设平均分为100格,那么最低有效位就是0.1cm
(2)编码器的LSB:
编码器的LSB主要由线数决定,我们知道每经过一组NS极,都会产生一个脉冲信号,而上升沿和下降沿都可以触发单片机的中断,我们只需要在此时计数,就可以将转一圈分为13份(假设为13线)
(3)编码器的倍频:
不知道你们有没有用很多个点拟合一个正弦函数,我们会发现缩小了看,可以看到十分平滑曲线,但是逐渐放大看局部,就会发现崎岖无比的曲线。
推广到编码器的计数,如果我们可以尽可能将转动一圈分为更多份,那么局部的有害干扰就对整体的结果产生的影响就显得微乎其微了
(4)二倍频:
我们知道每经过NS的边界,霍尔传感器就会上升沿和下降沿,而这个变化之间的距离又是相同的,所以我们让单片机对A或B相的上升沿和下降沿都计数,也可以看成输出频率加倍了,也就是二倍频。
(5)四倍频:
如果将一个NS极的角度叫做一个周期的角度,而A、B相之间的角度被设计为相差四分之一个周期的角度。综合起来看,也就是A,B相的上升沿和下降沿之前相差的距离是相同的,如果对A、B相的上升沿和下降沿都进行计数,就能实现四倍频
-
电机转速计算:
(1)“13线”:码盘中缺口的个数,每经过一个缺口就会产生一个脉冲信号,也就是说每产生13个脉冲信号相当于码盘转一圈
(2)”减速比1:30“:轮子转一圈,码盘转三十圈
(3)轮子转速 =码盘转速/减速比
码盘转速 = 一定时间内计数值/转一圈的计数值/计数时间
四、参考代码:(以二倍频,STC15系列为例)
-
主函数:
#ifndef PUBLIC_H
#define PUBLIC_H#include <STC15F2K60S2.H>
#define u8 unsigned char
#define u16 unsigned int
#endif
#include "public.h"
#include "uart.h"
#include "stdio.h"
#include "encoder.h"
#include "intrins.h"
#include "timer.h"
#include "pwm.h"u8 sign_5ms;
float rpm;//单位:转每分钟
int count_enco,cacl_temp;void main()
{UartInit();E_IT1_Init();Timer0_Init();//驱动电机转动PMW_Init(); AIN1 = 1;AIN2 = 0;BIN1 = 1;BIN2 = 0; while(1){//每5ms计算一次if(sign_5ms == 1){printf("cacl_temp:%d\\r\\n",cacl_temp);//轮子转速,单位:转/分钟rpm = cacl_temp/26.0/5*1000*60/30;printf("rpm:%f\\r\\n",rpm);sign_5ms = 0;}}
} void E_IT1() interrupt 2
{//相异为正,相同为负if(Encoder1_A ^ Encoder1_B)count_enco++;elsecount_enco--;//指示灯,检测是否正常工作P00 ^= 1;
}
void Timer0_Isr(void) interrupt 1
{sign_5ms++;cacl_temp = count_enco;count_enco = 0;
}
-
编码器初始化:
#ifndef ENCODER_H
#define ENCODER_H#include "public.h"sbit Encoder1_A = P3^3;
sbit Encoder1_B = P2^5;
void E_IT1_Init();#endif
#include "encoder.h"/*外部中断0初始化
*/
void E_IT1_Init()
{//跳变沿触发IT1 = 0;//开启中断EX1 = 1;EA = 1;
}
-
PWM初始化:
#ifndef PWM_H
#define PWM_H#include "public.h"sbit pwm_A = P3^6;
sbit AIN1 = P2^0;
sbit AIN2 = P2^1;sbit pwm_B = P3^7;
sbit BIN1 = P2^2;
sbit BIN2 = P2^3;void PMW_Init(void);
#endif
#include "pwm.h"void PMW_Init(void)
{//CCP1:P36 CCP2:P37P_SW1 = P_SW1 & 0xcf | (0x02<<4);CL = CH = 0;CMOD = 0x08;//CCP1: 占空比:70%PCA_PWM1 = 0x00;CCAP1H = CCAP1L = 0;CCAPM1 = 0X42;//CCP2: 占空比:70%PCA_PWM2 = 0x00;CCAP2H = CCAP2L = 0;CCAPM2 = 0X42;//CCON = 0X40;
}
-
定时器初始化:
#ifndef TIMER_H
#define TIMER_H#include "public.h"void Timer0_Init(void);#endif
#include "timer.h"void Timer0_Init(void) //5毫秒@12MHz
{AUXR |= 0x80; //定时器时钟1T模式TMOD &= 0xF0; //设置定时器模式TL0 = 0xA0; //设置定时初始值TH0 = 0x15; //设置定时初始值TF0 = 0; //清除TF0标志TR0 = 1; //定时器0开始计时ET0 = 1; //使能定时器0中断EA = 1;
}
-
串口通信:
#ifndef UART_H
#define UART_H#include "public.h"void UartInit(void);#endif
#include "uart.h"
#include <stdio.h>
void UartInit(void) //9600bps@12MHz
{SCON = 0x50; //8位数据,可变波特率AUXR |= 0x40; //定时器时钟1T模式AUXR &= 0xFE; //串口1选择定时器1为波特率发生器TMOD &= 0x0F; //设置定时器模式TL1 = 0xC7; //设置定时初始值TH1 = 0xFE; //设置定时初始值ET1 = 0; //禁止定时器中断TR1 = 1; //定时器1开始计时
}/*重定义putchar函数
*/
char putchar (char c)
{SBUF = c;while(!TI);TI = 0;return c;
}