51单片机编码器测速系统:A/B相方向判别与实现

发布时间:2026/9/17 1:47:27
51单片机编码器测速系统:A/B相方向判别与实现 简介面向51单片机初学者的编码器测速与测方向完整方案包含原理图、流程图、物料清单、仿真图和Keil源码可直接用于课程设计或项目复现。项目以电机带动编码器产生脉冲通过单片机测量实现转速与旋转方向判断并用LCD1602显示结果仿真中使用带编码器的电机模型模拟机械式编码器便于无硬件时开展实验也解决了编码器仿真模型难以获取的问题。资源共37个文件压缩包约661KB核心内容包括C程序源码、Keil工程与hex烧录文件、Proteus仿真的DSN工程、AD原理图及PDF预览、元件清单Excel、流程图BMP等覆盖从设计到调试的主要环节。目前已有236人学习下载。整体适合课程设计或毕业设计参考可结合物料清单搭建实物也可对照源码与仿真理解编码器脉冲计数、方向判别和LCD显示的实现思路对入门51单片机的外设编程很有帮助。1. 基于51单片机的编码器测速系统先把“方向”从脉冲里读出来增量式编码器在51单片机场景里常被当成“转一圈能输出多少个脉冲的传感器”但真正坑人的不是数脉冲而是方向怎么判。电机轴带编码器转动时会从A、B两个通道输出相位差90°的方波转速藏在脉冲频率里旋转方向则藏在这一路相对另一路的相位关系里。只盯着单路脉冲看永远读不出方向。这套基于51单片机的编码器速度、方向测量项目把编码器接上电机轴通过外部中断加定时器采集A/B相信号最后用LCD1602显示实时转速和正反转状态配套原理图、流程图、物料清单、Proteus仿真图和C51源码一应俱全。适合刚做完定时器和中断实验、准备切入电机控制的开发者下文先讲清楚编码器信号本身再落到原理图选型、源代码实现和仿真联调。2. 编码器A/B相正交信号与方向判别从时序到鉴相逻辑2.1 A/B相输出为什么能同时提供速度和方向增量式编码器内部码盘转动时会从OUT_A和OUT_B两个通道输出相位差90°的方波。转一圈输出的脉冲数就是编码器线数PPR转速越高脉冲频率越高所以速度信息实际在脉冲频率里方向信息则藏在A、B两路的相位关系里。若A相超前B相90°约定为正转那么B相超前A相90°就代表反转。只看某一路脉冲本身得不到方向必须同时监测两路信号这正是原理图里要把A、B两路都接进51单片机的原因。从波形上看正转时A相上升沿附近B相是低电平反转时同一上升沿附近B相是高电平。基于这个约定方向判断可以只盯住沿触发那一瞬间另一路电平的高低。具体对应关系如下表。A相边沿B相电平判断方向上升沿低正向上升沿高反向下降沿高正向下降沿低反向这里的方向约定取决于编码器安装时A/B哪路在前实物装反了会出现“明明是正转却显示反转”处理办法是交换A/B输入或者在软件里对方向标志取反。仿真里用的MOTOR-ENCODER同理先把两个输出通道按顺序接好观察一遍波形再写代码最省事。2.2 外部中断里判方向的代码模型方向判断在51上最常见的做法是A相接INT0P3.2B相接普通IO比如P3.3INT0配置为边沿触发。进入中断后读一次B相电平按上面的真值表给出方向。机械编码器低速时触点会抖动可能导致同一边沿触发多次脉冲计数和方向都会出错所以抖动处理要放在中断入口处。sbit ENC_A P3^2; /* A相接外部中断0 */ sbit ENC_B P3^3; /* B相接普通IO */ unsigned char enc_dir 0; /* 0:正转 1:反转 */ unsigned int enc_count 0; /* 正转累加, 反转递减 */ void INT0_ISR(void) interrupt 0 { if (ENC_B 0) /* A上升沿时B为低 正转 */ { enc_dir 0; enc_count; } else /* A上升沿时B为高 反转 */ { enc_dir 1; enc_count--; } }外层模块每100ms读取一次enc_count差值来计算速度。enc_count按有符号语义维护反转时减小这样即使方向频繁切换速度计算也不会出现“正转累加、反转也累加”的错误。中断里要尽量减少耗时操作LCD刷新这类动作放在主循环里做避免在中断里调用延时函数。除了在A相上升沿判方向还可以在A相上下沿都触发或者A/B两路都参与判向分别对应2倍频和4倍频。4倍频下分辨率提高4倍但中断频率也变成4倍12MHz晶振、低速电机场景下51完全扛得住。源码main.c里用的是单倍频加方向修正想要更高分辨率就把INT0和INT1都利用起来A/B相各接一路外部中断两边都做鉴相计数频率翻倍。跨入4倍频后计数变量要换成长整型防止高转速下溢出。2.3 为什么仿真里没有机械编码器Proteus元器件库里没有单独的机械编码器元件这类编码器仿真通常都是用MOTOR-ENCODER这个带编码器的直流电机实现的。它的电机轴转动后会输出两路脉冲外部看起来就是一个完整的编码器信号源还能通过调节电机电压改变转速比纯脉冲发生器更接近真实场景。这也是仿真图里用电机自带编码器的原因把电机转起来就等于编码器在输出A/B相脉冲后面所有代码逻辑不用改就能直接验证。3. 原理图与物料清单落地STC89C52最小系统加编码器接口设计3.1 从Sheet1.SchDoc看电路结构原理图用Altium Designer打开Sheet1.SchDoc查看结构上分四块以STC89C52RC为核心的最小系统、LCD1602显示电路、编码器接口电路和电源电路。最小系统里晶振选12MHz两个33pF负载电容接地复位电路用10k电阻加10uF电容电源处放一个100uF电解电容加0.1uF瓷片电容做去耦。12MHz晶振的定时器初值好算100ms时基下没有明显累积误差这个项目纯LCD显示不需要纠结11.0592MHz的串口波特率12MHz是更省心的选择。编码器接口在原理图上体现为排针或电机端子A相接P3.2B相接P3.3两个引脚各接一个10k上拉电阻。真实增量式编码器很多是开集电极输出没有上拉时51读不到稳定电平上拉电阻属于必须项仿真里MOTOR-ENCODER输出已经接近TTL电平不加上拉也能跑但接上更贴近实物。上拉阻值选10k是通用值上拉能力够、功耗也低。提示如果实际编码器线缆超过1米建议在A/B进入单片机前加一级施密特触发缓冲比如74HC14或者至少加RC低通滤波否则电机启停瞬间的电磁干扰会让中断反复触发。3.2 LCD1602接口与对比度LCD1602接线用标准4位模式D4-D7接P0.4-P0.7RS接P1.0EN接P1.1RW直接接地。P0口内部没有上拉电阻D4-D7这一组必须接10k排阻到VCC否则LCD收到的数据全是低电平显示出来永远是黑块。Vo脚接10k电位器中间抽头电位器两端接VCC和GND用来调对比度。仿真里对比度调不好最常见的问题就是屏幕有字但特别浅或者满屏黑格。3.3 物料清单怎么核对资料里的元件清单.xlsx列出了完整BOM把原理图所有器件整理成表。核对时建议按电路模块走不要按元件类型走最容易漏的是复位电容和LCD对比度电位器。下面简化表以实际xlsx为准。模块器件参数/型号数量备注最小系统单片机STC89C52RC140pin DIP或LQFP最小系统晶振12MHz1测速用整数好算最小系统负载电容33pF2晶振对地最小系统复位电容/电阻10uF/10k各1高电平复位电源去耦电容100uF0.1uF各1电源入口处显示LCD160216x214位数据模式显示排阻10k1P0口上拉显示电位器10k1对比度调节编码器电机编码器MOTOR-ENCODER1仿真中替代机械编码器编码器上拉电阻10k2A/B相上拉核对物料时要特别注意编码器的供电电压。仿真里MOTOR-ENCODER的电机部分和编码器部分供电电压可以分开电机接可调直流电源编码器输出端直接进单片机即可。实物编码器则要看手册常见5V供电与51系统共地是必须的。3.4 接线顺序与上电检查搭电路时建议按最小系统、显示、编码器的顺序接线。最小系统焊完先烧一个LED闪烁程序确认晶振起振再焊接LCD1602最后接编码器。如果LCD一上电就满屏黑格先查Vo电位器和P0上拉如果上电后方向显示不对先交换A/B两根线而不是改代码这样可以快速确认是接线问题还是程序问题。测速必须共用一条GND回路实测中发现编码器地和单片机地分开接脉冲经常乱跳。4. C51程序设计定时器计数、方向判断与LCD1602显示4.1 软件流程对应流程图.bmp流程图.bmp给出了完整程序结构核心是三个任务主循环、定时器中断、外部中断0。主流程上电后先做LCD1602初始化然后进入while循环循环里每100ms从标志位判断是否到了采样时刻到了就取enc_count差值乘上系数算转速再调用LCD刷新函数显示速度值和方向字符串。外部中断0负责在A相每个上升沿记录一次方向并更新计数定时器1负责产生100ms时基置采样标志位。流程图分层就是这句话的图形化中断里只干中断该干的事显示和计算全部放主循环。4.2 定时器和测速的完整代码定时器1采用方式116位计数。12MHz晶振下机器周期1us100ms需要100000个机器周期16位定时器最大计数65536装不下100ms。工程里的做法是把时基拆开定时器初值设为0x3CB0对应50ms中断再用一个软计数变量累加两次得到100ms中断周期。这样定时器中断50ms进一次软计数到2才置采样标志主循环按100ms的周期刷新一次数据。#include reg52.h #include stdio.h #include lcd1602.h #define PPR 20 /* 编码器线数, 仿真电机默认每转输出20个脉冲, 按实际改 */ sbit ENC_A P3^2; sbit ENC_B P3^3; unsigned int enc_count 0; bit enc_dir 0; /* 0正向 1反向 */ bit sample_flag 0; /* 100ms采样标志 */ unsigned char timer_soft 0; unsigned int last_count 0; /* 定时器1初始化: 50ms中断, 软计数2次得到100ms */ void Timer1_Init(void) { TMOD 0x0F; /* 只改T1部分, 保留T0配置 */ TMOD | 0x10; /* 定时器1, 方式1, 16位 */ TH1 0x3C; /* 初值高8位 */ TL1 0xB0; /* 初值低8位, 65536-50000155360x3CB0 */ ET1 1; EA 1; TR1 1; } void Timer1_ISR(void) interrupt 3 { TH1 0x3C; /* 重装初值 */ TL1 0xB0; timer_soft; if (timer_soft 2) { timer_soft 0; sample_flag 1; /* 通知主循环采样 */ } } void INT0_ISR(void) interrupt 0 { if (ENC_B 0) /* A上升沿时B低 正转 */ { enc_dir 0; enc_count; } else { enc_dir 1; enc_count--; } } void main(void) { unsigned int curr_count; unsigned int pulse_cnt; unsigned int speed; unsigned char str[16]; LCD_Init(); /* 4位模式初始化LCD */ Timer1_Init(); IT0 1; /* INT0下降沿触发 */ EX0 1; LCD_ShowString(1, 0, Speed: 0 r/min); LCD_ShowString(2, 0, Dir: CW); while (1) { if (sample_flag) { sample_flag 0; curr_count enc_count; /* 100ms内的脉冲数, 注意方向变化时enc_count会减少 */ pulse_cnt (curr_count last_count) ? (curr_count - last_count) : (last_count - curr_count); last_count curr_count; /* 转速 脉冲数 / (线数 * 时间s) * 60 */ speed pulse_cnt * 600UL / PPR; /* 100ms, 即0.1s: 600 60 / 0.1 */ sprintf(str, Speed:%3d r/min, speed); LCD_ShowString(1, 0, str); if (enc_dir 0) LCD_ShowString(2, 0, Dir: CW ); else LCD_ShowString(2, 0, Dir: CCW); } } }代码里几个点需要单独说明pulse_cnt用绝对值算防止方向在采样间隔内翻转过一次导致差值出现负数。enc_count是带符号语义的无符号变量正转加、反转减只要在采样间隔内方向翻转次数远小于脉冲总数速度计算仍正确。sprintf格式化方便但会占用较多代码空间STC89C52RC有8K Flash放得下。lcd1602.h里提供的LCD_ShowString第一个参数是行号第二个是列号刷新前先组装好整段字符串可以避免LCD刷新时出现闪烁残影。4.3 测速公式怎么来的转速计算用M法也就是测频法。转速r/min 单位时间脉冲数 / PPR × 60。100ms采样一次那么一分钟有600个采样周期脉冲数×600再除以PPR就是每分钟转数。PPR是关键参数仿真MOTOR-ENCODER每转输出脉冲数在器件属性里能看到换成实物编码器时用编码器铭牌值替换。如果用4倍频PPR要乘4否则转速会显示成实际的4倍。4.4 工程编译与hex生成Keil工程文件是main_uvproj打开后在Options for Target里确认晶振填12MHz勾选Create HEX File编译后生成main.hex。目录里的main.OBJ、main.LST和main.M51是编译中间产物烧录只用main.hex。STC系列下载时波特率选低一点更稳下载完按复位键程序开始跑。如果LCD上一直没有显示优先确认hex是否生成成功而不是反复改代码——Keil里漏勾Create HEX File是初学者最常踩的坑。5. Proteus仿真电机编码器联调DSN搭建、波形验证与常见排错5.1 仿真工程怎么搭打开资料里的仿真.DSN里面已经放好了STC89C52、MOTOR-ENCODER、LCD1602、电阻电容和电源。电机编码器要转起来必须给电机部分接独立电源把直流电源的电压从0V慢慢调到5V左右电机转速跟着电压变化编码器A/B输出频率也随之改变。MOTOR-ENCODER的脉冲输出引脚在DSN底部走线对应接到了INT0和INT1。接好后在单片机属性里加载main.hex点击运行LCD1602上就能看到转速和方向。需要注意DSN里单片机型号选的是STC89C52还是AT89C52会直接影响程序是否跑得动。仿真里没有STC的精确时序模型时指定为AT89C52更保险引脚兼容源码不用改。晶振频率在单片机属性里也写成12MHz和Keil工程设置保持一致否则定时器初值对应的时基就不对了。5.2 常见仿真排错现象检查点处理办法电机不转电机电源给MOTOR-ENCODER电压端接直流电源并调高电压LCD黑格对比度/Vo调Vo电位器确认P0口上拉排阻存在固定字符不刷新定时器中断确认晶振频率12MHzTR1已置1转速一直为0PPR与实际不符示波器量A相频率反推实际PPR方向显示相反A/B接反交换A/B输入或软件取反方向标志加了电源、加载了hexLCD还是没反应按表里顺序排查。电机不转说明电源没接好或电压为0LCD有固定字符但数字不刷新检查定时器中断有没有触发可以临时把speed位置写死一个值来判断是显示问题还是计算问题转速显示一直是0但电机在转大概率是PPR和实际脉冲频率对应不上用示波器量一下A相频率换算就知道当前转速。5.3 用示波器验证A/B相波形Proteus里在MOTOR-ENCODER的两个脉冲输出脚挂虚拟示波器运行后能看到两路占空比约50%的方波相位差约90度。把示波器的显示频率换算成转速和LCD显示值对比假如A相频率是30Hz编码器每转20个脉冲转速应该是30/20×6090 r/min。如果LCD显示180说明PPR写成了10或者电路里用了2倍频。这个验证动作是整套工程里性价比最高的一步比肉眼盯着数字变化可靠得多。提示仿真走完不代表实物一定能跑。MOTOR-ENCODER输出是理想方波实物编码器输出有毛刺、有抖动、有电平偏移实物调试时要保留原理图中的上拉电阻必要时再软件滤波。6. 测速精度提升与验证M法低速误差、滤波与标定技巧6.1 M法低速误差在哪M法固定100ms采样窗低速时窗内可能只有几个脉冲量化误差会被放大。比如60 r/min、PPR 20时每秒20个脉冲100ms只有2个脉冲转速计算结果要么0、要么30的倍数跳变。这是51单片机教程里常说的低速测不准解决思路是拉长采样窗或改用T法。T法测的是相邻两个脉冲的时间间隔低速时间隔长、定时器计数多分辨率反而高。也可以M/T混合高速用M法低速自动切T法切换点通常取每秒脉冲数低于20时。6.2 软件滤波代码对转速做滑动平均是最实用的手段。窗口取4或8每次采样把历史值求和取平均响应时间和平滑程度折中。#define FILTER_N 4 unsigned int speed_buf[FILTER_N]; unsigned char speed_idx 0; unsigned long speed_sum 0; unsigned int Speed_Filter(unsigned int new_val) { speed_sum - speed_buf[speed_idx]; /* 去掉最老的一个值 */ speed_buf[speed_idx] new_val; /* 存入新值 */ speed_sum new_val; speed_idx (speed_idx 1) % FILTER_N; return (unsigned int)(speed_sum / FILTER_N); }这个函数放入主循环替换直接显示speed的地方算出来的数值更平滑。窗口越大越平但延迟越明显如果电机加减速很快窗口超过8会感觉显示跟不上实际转速。仿真里电机转速稳定看不出太大差别实物里这个滤波代码基本是保留项。6.3 用频率计反推PPR完成标定标定的目的是确认PPR到底是多少。仿真里做法简单示波器读A相频率fLCD转速为n时PPR f / n × 60。实物里用频率计或示波器量A相频率再配合一个已知转速的场景把显示的转速和频率换算值对齐PPR就定下来了。标定完成后把PPR以宏定义形式放在main.c顶部后续所有改动都围绕这个常量展开没必要再动测速公式。用示波器确认两路脉冲频率一致时就可以用这个PPR值直接套用公式算转速。本文还有配套的精品资源点击获取