平衡小车之编码器的使用(深夜学习——单片机)
一、平衡小车要编码器干什么?
采集电机的转速,并转化成脉冲信号并发送给单片机,最后计算出小车的速度。
二、编码器原理:(以常用的增量式为例)
霍尔编码器:
(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++;
else
count_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:P37
P_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;
}