1 项目功能
通过光电编码器进行转速测量
2 光电编码器工作原理
光电编码器,是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。这是应用最多的传感器,光电编码器是由光源、光码盘和光敏元件组成。光栅盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,光栅盘与电动机同速旋转,经发光二极管等电子元件组成的检测装置检测输出若干脉冲信号,通过计算每秒光电编码器输出脉冲的个数就能反映当前电动机的转速。此外,为判断旋转方向,码盘还可提供相位相差90º的两路脉冲信号。
下图为光电编码器的工作原理图:

合理安排光栏板和光电元件的位置,每转一圈会发出固定个数的脉冲,A相和B相脉冲相差90度,可以用来区分旋转方向,有些编码器每转一圈会发出一个Z相脉冲。
编码器有增量式和绝对式两种,我们采用增量式编码器进行转速测量。
3 AT32编码器接口


本例采用欧姆龙omron的E6B2-CWZ6C型光电编码器,分辨率为1000脉冲/秒。

AT32F407系列的通用定时器(TMR2 到TMR5)支持编码器输入。AT32F407编码器模式配置,需将SMSEL[2:0]配置为3’b001/3’b010/3’b011可开启编码模式,编码模式下需提供两个输入(C1IN/C2IN),根据一个输入的电平值,计数器将在另一个输入的边沿向上或向下计数。计数方向将由OWCDIR值指示。编码模式下计数器计数方向如下表所示:

如果采用在C1IN和C2IN上计数,计数值为脉冲数的4倍,AT32的编码器模式具备干扰脉冲自动滤除功能。需要注意的是,如果采用计数器溢出中断,AT32在某种特定条件下不会触发该中断。高级用户可能考官方给出的参考解决方案,见《FAQ0121_TMR在编码器模式下的Overflow事件_V2.0.0》,官方搜索即可。
4 掌上实验室与编码器连接电路图



图中右上脚为扩展接口,接法为:1脚-A相(黑);2脚-B相(白);12脚(GND)-0V(蓝);16脚(5V)-电源(褐)。
连接好编码器之后,试着转动编码器,可以看到LED2红色闪烁,是不是有意外收获?实际上《掌上实验室V8》PA1有复用,电路如下:

还有一点需要注意,编码器是NPN集电极开路输出(可以方便和不同电平的电路或单片机接口),接收电路需要增加上拉电阻。也可以用单片机内部的上拉电阻,需要程序配置。
5 程序设计
示例程序如下:
#include "at32f403a_407_conf.h"
void delay_us(int us)
{
SysTick->LOAD = system_core_clock/1000000 * us;
SysTick->VAL = 0;
SysTick->CTRL = 0x5; //Systick采用系统时钟为时钟源,并启动
while((SysTick->CTRL & (1<<16)) == 0);
SysTick->CTRL = 0; //停止Systick
}
void delay_ms(int ms)
{
for(;ms>0;ms--)
delay_us(1000);
}
//端口初始化
void gpio_pins_init(void)
{
//打开GPIO时钟
crm_periph_clock_enable(CRM_GPIOE_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOD_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
//配置PD2~PD5, PE0~PE7为输出
gpio_init_type gpio_init_struct;
gpio_init_struct.gpio_pins = GPIO_PINS_0 | GPIO_PINS_1 | GPIO_PINS_2 | GPIO_PINS_3 | GPIO_PINS_4 | GPIO_PINS_5 | GPIO_PINS_6 | GPIO_PINS_7;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init(GPIOE, &gpio_init_struct);
gpio_init_struct.gpio_pins = GPIO_PINS_2 | GPIO_PINS_3 | GPIO_PINS_4 | GPIO_PINS_5;
gpio_init(GPIOD, &gpio_init_struct);
//蜂鸣器
gpio_init_struct.gpio_pins = GPIO_PINS_2;
gpio_init(GPIOB, &gpio_init_struct);
//按键上拉输入
gpio_init_struct.gpio_pins = GPIO_PINS_12 | GPIO_PINS_13 | GPIO_PINS_14 | GPIO_PINS_15;
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init(GPIOD, &gpio_init_struct);
//编码器输入
gpio_init_struct.gpio_pins = GPIO_PINS_0 | GPIO_PINS_1;
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_pull = GPIO_PULL_UP; //编码器为集电极开路输出,需上拉
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init(GPIOA, &gpio_init_struct);
}
//显示缓冲区定义
uint8_t disp_buf[4];
//显示整数
void display_dec_int(int num)
{
static uint8_t tab[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
disp_buf[0] = tab[num/1000%10];
disp_buf[1] = tab[num/100%10];
disp_buf[2] = tab[num/10%10];
disp_buf[3] = tab[num%10];
}
void display_minus()
{
disp_buf[0] = 0xbf;
disp_buf[1] = 0xbf & 0x7F;
disp_buf[2] = 0xbf;
disp_buf[3] = 0xbf;
}
//扫描刷新
void display_scan()
{
static int cur_digit = 0;
//全关
gpio_bits_set(GPIOD, GPIO_PINS_2 | GPIO_PINS_3 | GPIO_PINS_4 | GPIO_PINS_5);
//输出字形码
gpio_bits_reset(GPIOE, 0xff); //PE0~PE7 = 0
gpio_bits_set(GPIOE, disp_buf[cur_digit]);
//打开对应位开关
gpio_bits_reset(GPIOD, GPIO_PINS_2 << cur_digit);
//更新cur_digit, 准备下一次扫描
cur_digit = (cur_digit + 1) % 4;
}
void tmr6_init(void)
{
/* enable tmr6 clock */
crm_periph_clock_enable(CRM_TMR6_PERIPH_CLOCK, TRUE);
/* 64000000/64/1000 = 1000Hz*/
/* tmr_base_init(TMR6, 1000 - 1, 64 - 1); */
tmr_base_init(TMR6, 1000 - 1, system_core_clock/1000000 - 1);
tmr_cnt_dir_set(TMR6, TMR_COUNT_UP);
/* overflow interrupt enable */
tmr_interrupt_enable(TMR6, TMR_OVF_INT, TRUE);
/* tmr1 overflow interrupt nvic init */ nvic_priority_group_config(NVIC_PRIORITY_GROUP_4); nvic_irq_enable(TMR6_GLOBAL_IRQn, 0, 0);
/* enable tmr1 */
tmr_counter_enable(TMR6, TRUE);
}
void TMR6_GLOBAL_IRQHandler(void)
{
if(tmr_flag_get(TMR6, TMR_OVF_FLAG) != RESET)
{
tmr_flag_clear(TMR6, TMR_OVF_FLAG);
//gSysTick++;
display_scan();
}
}
int getKey()
{
if(gpio_input_data_bit_read(GPIOD,GPIO_PINS_12)==RESET){
delay_ms(20);
if(gpio_input_data_bit_read(GPIOD,GPIO_PINS_12)==RESET){
while(gpio_input_data_bit_read(GPIOD,GPIO_PINS_12)==RESET);
return 1;
}
}
return 0;
}
void buzzer_ctrl(int on)
{
if(on)
gpio_bits_set(GPIOB, GPIO_PINS_2);
else
gpio_bits_reset(GPIOB, GPIO_PINS_2);
}
void tmr2_encoder_init(){
/* enable tmr2/gpioa clock */
crm_periph_clock_enable(CRM_TMR2_PERIPH_CLOCK, TRUE);
/* tmr2 encoder mode configuration
tmr2 ti1pf1 ,ti2fp2 as encoder input pin, tmr2 counter
changed each signal edge. */
/* enable tmr2 32bit function */
tmr_32_bit_function_enable(TMR2, TRUE);
tmr_base_init(TMR2, 0xFFFFFFFF, 0);
tmr_cnt_dir_set(TMR2, TMR_COUNT_UP);
/* config encoder mode */
tmr_encoder_mode_config(TMR2, TMR_ENCODER_MODE_C, TMR_INPUT_BOTH_EDGE, TMR_INPUT_BOTH_EDGE);
/* enable tmr2 */
tmr_counter_enable(TMR2, TRUE);
}
int main(void)
{
gpio_pins_init();
tmr6_init();
tmr2_encoder_init();
uint32_t counter_1, counter_2;
int32_t diff;
float rpm;
for(;;){
counter_1 = tmr_counter_value_get(TMR2);
delay_ms(1000);
counter_2 = tmr_counter_value_get(TMR2);
diff = counter_2 - counter_1;
rpm = diff*60/4/1000.0;
if(rpm<0)
rpm = -rpm;
display_dec_int(rpm);
}
}
本例采用AB相脉冲上升沿和下降沿全部计数的方法,这样可以滤除干扰脉冲,所以实际的分辨率细分4倍,达到4000脉冲/转。程序每秒钟测获取一次编码器值,其差值就是1秒钟编码器产生的脉冲值,乘上60就换算成了转速,单位为转/分钟。
程序中counter_1, counter_2, diff分别定义为32位无符号和有符号整数,如不清楚C语言处理数据的相关机制,请自行了解和测试。
如果编码器带了转轮,还要考虑机械结构的转速转换系数。
可以进一步改进的地方,秒延时精度,增加方向和小数点的显示。