基于Arduino Uno的自动导航小车:编码器测速与PID双闭环控制实现
这一章我们把小车从“按键能跑但每次都跑歪”改成真正意义上有“感知、决策、执行”的自动导航小车。板上钉钉的是这块 Arduino Uno R3ATmega328P16MHz 主频2KB SRAM放现在连玩具都算不上高性能但把一个完整的“编码器测速 舵机云台超声波避障 PID 双闭环控制 状态机导航”工程塞进去依然绰绰有余。前提是你得把代码结构理清楚别到处写 delay别让慢速传感器去打断快速控制环。在开始正文之前先亮明核心结论能不用换主控Uno R3 足够跑完整套控制算法编码器测量轮速必须用中断不能靠 while 轮询只调开环 PWM小车一定走不直必须上 PID 速度环超声波不适合“实时连续控制”适合在状态机里做低频避障决策位置控制不是靠累计 PWM 时间而是靠编码器脉冲闭合回来本文会把自动避障状态机、速度规划、双闭环控制全部串到一个 main-loop 里给出一套可运行框架。全文是 CSDN 技术教程写法不是 PPT 课程建议打开 Arduino IDE屏幕分成左右两边左边读代码右边对照接线表然后一步一步把代码烧进去验证。1. 核心能力速览先说本套方案的能力边界和数据通路这样你能快速判断这个方案是否符合预期。能力项说明主控平台Arduino Uno R3ATmega328P16MHz开发环境Arduino IDE 1.8.x 或 2.x运动底盘两轮差速驱动左右电机均为编码器减速电机测速传感器霍尔/光电编码器AB 相输出A 相接外部中断外部中断使用左电机 A 相接 D2右电机 A 相接 D3测距传感器HC-SR04 超声波模块Trig 和 Echo 各占用 1 个 IO角度扫描机构SG90 或 MG90 舵机用于超声波左右扫描电机控制算法PID 速度环PID 位置外环 速度内环滤波方案一阶低通滤波 / 滑动窗口中值滤波根据数据性质选择行为控制有限状态机 FSM前进、扫描、转向、停车任务调度方式millis() 非阻塞调度速度环周期 5ms测距 20ms 级调试手段串口监视器 Arduino IDE 串口绘图器需要特别说明一点下面所有代码我按“最常见的两轮差速小车”来写也就是两个驱动轮带编码器外加一个万向轮。如果你的车是四轮独立驱动、四路编码器都要测那需要把中断部分换成 Pin Change Interrupt或者换 Mega / ESP32 这类中断资源更多的板子代码的主干逻辑不变。2. 先想清楚小车自动导航到底是谁在指挥很多新手拿到这种项目第一个误区就是以为“导航”是一个大 while 循环里塞满 if else。真这么写代码会在超声波测距的时候卡死也会在舵机扫描的时候把速度控制全部停掉。控制小车自动跑本质是三层任务在同时工作感知层编码器脉冲、超声波距离、舵机当前角度控制层测速、速度环 PID、位置外环 PID、PWM 输出决策层根据当前任务目标决定是前进、转弯、扫描还是停车。三层任务对实时性的要求完全不同层数据典型周期用途感知编码器脉冲每 1~10ms速度环反馈控制PID 输出每 5~20ms保持轮速稳定感知超声波距离每 20~100ms避障判断决策状态机切换每 50~200ms判断下一步动作如果你用 delay() 把所有任务串起来超声波等待回波默认最长能到几十毫秒舵机转动又要几十毫秒速度控制早乱了。这章代码最终会收敛成一段runTaskByTime()调度框架。这个框架比具体的“避障三行判断”更重要因为后面你要加蓝牙遥控、陀螺仪、视觉巡线能复用的是这个调度结构而不是那几行避障 if。3. 编码器测速与外部中断3.1 编码器为什么必须搭配中断现在市面上的 Arduino 学习小车电机后面通常带着一个霍尔编码器。电机转动时编码器输出 A、B 两路方波两路信号相差 90 度通过 A/B 相可以知道轮子是否在转、转了多少、朝哪个方向转。一个典型问题能不能直接在 main-loop 里用 digitalRead() 去读 A 相脉冲答案是不能。如果你主循环里有超声波测距、PID 运算、串口打印这些函数执行时间加起来很容易超过 1ms轮子高速旋转时脉冲早丢了一大截。编码器测速必须用“边沿触发中断”A 相每产生一个上升沿MCU 立刻跳进中断服务函数在中断里累加计数。只要中断服务函数足够短就不会丢脉冲。3.2 最小可用的编码器计数代码下面这段代码按照两轮差速小车接线A 相接在 Uno 的外部中断引脚 D2 和 D3B 相接普通 IO用于判断方向。#define ENC_L_A 2 #define ENC_L_B 4 #define ENC_R_A 3 #define ENC_R_B 7 volatile long encCountL 0; volatile long encCountR 0; // 左电机 A 相上升沿中断 void encLeftISR() { if (digitalRead(ENC_L_B)) { encCountL; // B 相高电平默认正转 } else { encCountL--; // B 相低电平默认反转 } } // 右电机 A 相上升沿中断 void encRightISR() { if (digitalRead(ENC_R_B)) { encCountR; } else { encCountR--; } } void setupEncoders() { pinMode(ENC_L_A, INPUT_PULLUP); pinMode(ENC_L_B, INPUT_PULLUP); pinMode(ENC_R_A, INPUT_PULLUP); pinMode(ENC_R_B, INPUT_PULLUP); // 在 A 相上升沿触发中断 attachInterrupt(digitalPinToInterrupt(ENC_L_A), encLeftISR, RISING); attachInterrupt(digitalPinToInterrupt(ENC_R_A), encRightISR, RISING); }注意三点encCountL、encCountR声明成volatile因为这两个变量会被中断修改编译器优化时必须保证每次都从内存重读。示例是“RISING 上升沿单倍频”计数。若想要更高分辨率可以把模式改成CHANGE一周期计两次精度翻倍但中断频率也翻倍。digitalRead()在中断服务函数里速度不算快但只有 4 个数字引脚读取单次中断开销极小实测不会拖垮 16MHz 主控。更讲究的做法是用端口寄存器直读课程阶段不必过度优化。3.3 从脉冲数换算成转速编码器脉冲要换算成轮子实际转速必须先知道产品的两个参数编码器本体每转一圈输出多少脉冲减速箱减速比是多少。例如电机规格书上写着“编码器 20 PPR减速比 1:30”那么电机输出轴转一圈编码器累计脉冲大约是PULSES_PER_REV 20 * 30 600也就是编码器计数从 0 变到 600说明车轮实际转了一圈。取速度的时候不需要读瞬时脉冲只需要在上一次控制周期结束时把encCountL和现在的encCountL相减得到当前周期内的脉冲增量。const float ENCODER_PPR 20.0f; const float GEAR_RATIO 30.0f; const float PULSES_PER_REV ENCODER_PPR * GEAR_RATIO; const float TICK_TO_RPM_L 60.0f / PULSES_PER_REV; // 每隔 5ms 调用一次 float calcSpeedRPM(long currentCount, long *lastCount) { long delta currentCount - *lastCount; *lastCount currentCount; // 把 5ms 内的脉冲增量换算成“每分钟转数” float revPerSecond (float)delta / PULSES_PER_REV / 0.005f; return revPerSecond * 60.0f; }这里有个工程细节速度环周期越短测速“分辨率”越低。如果周期是 5ms但电机转速较低5ms 内只有 1~2 个脉冲那速度反馈就会一跳一跳PID 输出也会跟着抖。所以主控能力允许时把速度环周期选在 5~20ms 比较合理如果是低速高精度场景则需要改用“定时器捕获单脉冲周期”的测速方式也叫 T 法测速。4. 超声波测距、舵机扫描与数据滤波

相关新闻

Echarts模板工程化:从可复用组件到可视化资产库

Echarts模板工程化:从可复用组件到可视化资产库

简介:本资源是一套面向数据分析、前端开发与课程设计场景的ECharts大数据可视化实战模板集,适用于高校学生课程设计、毕业设计、数据大屏项目快速搭建及可视化工程师日常开发参考。压缩包内含100套风格多样、交互丰富的可视化模板,覆盖地理热…

2026/11/16 6:44:59 阅读更多 →
Gopeed 下载唤醒失败?3 类常见故障快速定位,5 分钟让磁力链接恢复可用

Gopeed 下载唤醒失败?3 类常见故障快速定位,5 分钟让磁力链接恢复可用

Gopeed 下载唤醒失败?3 类常见故障快速定位,5 分钟让磁力链接恢复可用 【免费下载链接】gopeed A fast, modern download manager for HTTP, BitTorrent, Magnet, and ed2k. Cross-platform, built with Golang and Flutter. 项目地址: https://gitco…

2026/11/16 6:44:59 阅读更多 →
kotaemon 文档问答报错修复指南:本地部署后 4 步自查定位法

kotaemon 文档问答报错修复指南:本地部署后 4 步自查定位法

kotaemon 文档问答报错修复指南:本地部署后 4 步自查定位法 【免费下载链接】kotaemon An open-source RAG-based tool for chatting with your documents. 项目地址: https://gitcode.com/GitHub_Trending/kot/kotaemon kotaemon 是一个开源的本地部署文档问…

2026/10/24 22:21:49 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/15 5:13:35 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/16 6:41:34 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/15 5:13:35 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/15 3:37:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/15 4:18:38 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/15 5:13:35 阅读更多 →

日新闻

周新闻

月新闻