Arduino UNO三轮智能小车例程:从差速运动学到循迹避障
简介智能小车是嵌入式系统与机器人控制的经典入门项目其运动控制核心在于差速驱动模型通过左右驱动轮的转速差实现前进、转弯与原地旋转而万向轮仅作支撑。Arduino UNO作为主控平台负责读取循迹传感器、超声波或蓝牙指令并输出PWM信号驱动电机从而完成预期的轨迹运动。理解差速运动学的基本公式、电机驱动板的选型与引脚映射是让小车稳定行走的前提。在此之上循迹避障、蓝牙遥控等工程实践进一步将底层控制原语与状态机结合形成可复用的软件架构。本文从三轮底盘的运动学原理出发解析基于Arduino UNO的例程设计思路并给出硬件接线、参数标定与调试排查方法帮助初学者快速掌握从电机驱动到智能控制的完整链路。1. Arduino UNO 3轮智能小车例程先把差速运动学想清楚毕设文件包里放着一个基于arduino UNO 3轮智能小车例程.zip多数人第一件事是解压烧录结果电机一边转一边不转。三轮小车的本质是差速运动学两个驱动轮承担牵引一只万向轮只做支撑前进、后退、转弯全部靠左右轮转速差实现。Arduino UNO 在这里是主控负责读取循迹传感器、超声波或手机蓝牙下发的指令再用两路 PWM 控制电机驱动板。这套例程要解决的是从“电机能转”到“小车按期望轨迹走”之间的工程问题引脚映射、驱动使能、差速换算和阈值标定。适合刚接触 Arduino 的课设生也适合答辩前要把每个参数讲出依据的人。2. 三轮小车的底盘选型与 Arduino UNO 接线解压 zip 之后不要急着打开 .ino。先把硬件清单一字排开底盘、两个直流减速电机、一块电机驱动板、Arduino UNO、5V 供电和电池。例程包的文件一般按电机、循迹、避障、蓝牙拆成独立目录或单独 .ino接线表在注释里写得最全先按 2.3 的表接完板子再看代码才有意义。2.1 为什么课设三轮比四轮更好写四轮车如果四个轮子都做驱动转向要么引入阿克曼梯形要么靠一侧打滑实现滑移转向两种方案对底盘精度和程序复杂度要求都高。三轮差速底盘只有两个驱动轮加一只万向轮转向中心落在两驱动轮连线中点附近运动模型就一组公式线速度 v(vLvR)/2角速度 ω(vR−vL)/W转动半径 RW(vLvR)/(2(vR−vL))其中 W 是左右轮轮距。例程里所有转向函数最后都是在往这组公式里填数答辩时老师追问的往往也是这个而不是“用了什么高级算法”。万向轮要选带滚珠的转向轮不要用固定硬脚。三轮车重心落在两驱动轮轴线附近急加速时前端万向轮阻力一大车头就打横循迹表现为过弯甩尾。常见配法是 6.5cm 尼龙万向轮加 TT 电机或者 GA12-N20 减速电机加定制支架后者扭矩大、虚位小适合跑直线。2.2 电机驱动板对照L298N、TB6612 与 DRV8833驱动板是接线最容易出错的一环。三块常见板子的对比驱动板每路持续电流逻辑电压供电范围特点课设适用度L298N2A 标称1A 以下稳妥5V6~12V便宜、压降约 2V、重、发热大一般TB6612FNG1.2A峰值 3A3.3/5V2.7~13.5VMOSFET 桥、压降小、带 STBY 使能高DRV88331.5A2.7~5.5V2.7~10.8V体积小、逻辑电压低中Arduino UNO 的 5V 引脚来自 USB 或 Vin 上的板载稳压器容量只够传感器和逻辑电路用电机堵转电流动辄 1A 以上绝不能从 5V 引脚取电。驱动板单独接电池然后与 Arduino 共地。课设我一般选 TB6612逻辑接口兼容 5V压降低两节 18650 串联的 7.4V 输入到电机端还能剩 7V 左右同样电压下 L298N 因为功率管压降大转速明显偏低例程里标定好的 base 值要整体往上调。2.3 以 TB6612 为例的引脚映射与接线表这套例程我习惯把两路 PWM 放在 UNO 的 D5、D6方向信号放 D7~D10STBY 放 D4为循迹、超声波、蓝牙把 D2、D3、D11、D12、A0、A1 都留出来。Arduino 引脚TB6612 / 外设说明D5PWMA左电机调速PWM约 976HzD6PWMB右电机调速PWM约 976HzD7 / D8AIN1 / AIN2左电机方向D9 / D10BIN1 / BIN2右电机方向D4STBY使能必须为 HIGH5V / GNDVM / GND驱动板逻辑供电与共地选 D5、D6 不是顺手写的。UNO 的 PWM 引脚分布在三个定时器上D5、D6 属于 timer0默认约 976HzD9、D10 属于 timer1约 490Hz。两个电机用同一频率起步和微调的声感一致直行更均匀如果一个在 timer0 一个在 timer1会听出明显的“双频电机啸叫”。const int AIN1 7; // 左电机方向信号 A const int AIN2 8; // 左电机方向信号 B const int PWMA 5; // 左电机调速timer0 通道 const int BIN1 9; // 右电机方向信号 A const int BIN2 10; // 右电机方向信号 B const int PWMB 6; // 右电机调速timer0 通道 const int STBY 4; // TB6612 使能引脚 void setup() { pinMode(AIN1, OUTPUT); // 方向信号按数字输出配置 pinMode(AIN2, OUTPUT); pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT); pinMode(STBY, OUTPUT); digitalWrite(STBY, HIGH); // 使能必须优先于任何 PWM 输出 analogWrite(PWMA, 0); // PWM 归零防上电窜动 analogWrite(PWMB, 0); } void loop() { digitalWrite(AIN1, HIGH); // 左轮正转 digitalWrite(AIN2, LOW); analogWrite(PWMA, 150); // 占空比 150/255约 59% digitalWrite(BIN1, HIGH); // 右轮正转同速即直行 digitalWrite(BIN2, LOW); analogWrite(PWMB, 150); delay(2000); digitalWrite(AIN1, LOW); // 左轮反转 digitalWrite(AIN2, HIGH); digitalWrite(BIN1, HIGH); // 右轮仍正转形成原地旋转 digitalWrite(BIN2, LOW); delay(2000); }这段程序只验证一件事电机接线和驱动板工作正常。先直行 2 秒再原地差速转 2 秒如果有一个轮子不转交换该电机在驱动板上的 OUT 接线重新测能区分是板子通道坏还是电机坏。analogWrite第二参数是占空比不是电压150 表示 150/255。TB6612 的 AIN1/AIN2 同时为低输出刹车同时为高是急停理解这点能避开“电机嗡嗡响但不转”的排查方向。注意STBY 同时控制左右两路忘记拉高是“烧录成功但轮子毫无反应”的头号原因。示例把它接在 D4也可以直接接 5V但保留引脚能在软件里做总开关。之后若用 TCCR 寄存器重映射 PWM 频率不要动 timer0D5/D6否则 millis() 和 delay() 的时间基准会被一起改掉。3. 用最小例程把 Arduino UNO 的电机控制封装成一个原语上一章的 setup 只做硬件验证。真正进入例程结构第一步是写一个统一的电机输出函数后面循迹、避障、蓝牙全部复用这一层业务代码里不要再出现裸的 digitalWrite 和 analogWrite。3.1 run(left, right)把方向与速度合并void motorWrite(int in1, int in2, int pwmPin, int speed) { speed constrain(speed, -255, 255); // 越界值截断到合法区间 if (speed 0) { // 正数正转 digitalWrite(in1, HIGH); digitalWrite(in2, LOW); } else { // 负数反转取绝对值输出 digitalWrite(in1, LOW); digitalWrite(in2, HIGH); speed -speed; } analogWrite(pwmPin, speed); } void run(int left, int right) { motorWrite(AIN1, AIN2, PWMA, left); // 左电机 motorWrite(BIN1, BIN2, PWMB, right); // 右电机 } void setup() { pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT); pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT); pinMode(STBY, OUTPUT); digitalWrite(STBY, HIGH); // 使能一次到位 Serial.begin(9600); // 调试串口 } void loop() { run(150, 150); // 直行 800ms delay(800); run(80, 150); // 左转左慢右快 delay(800); run(-150, 150); // 原地旋转左右反号 delay(800); run(0, 0); // 刹车 Serial.println(cycle done); // 标记一次周期完成 delay(1000); }constrain不是可选项。遥控或导航计算一旦出现越界速度未限幅直接输出会让电机瞬间满转小车窜出去撞墙。run(-150,150)表示左右轮反方向等速车围绕两轮连线中点原地旋转这是差速底盘转弯半径最小的动作。串口打印放在 loop 末尾串口监视器里看不到 “cycle done” 时说明代码根本没跑到那个位置先查前一行。3.2 速度怎么给base、turn 与最小转弯半径例程里最常见的写法是定义一个基准速度 base 和一个转向增量 turn业务层做组合int base 130; // 直行基准占空比约 51% int turn 70; // 转向增量决定转弯半径 // 左转左轮减速、右轮加速 run(base - turn, base turn); // 右转右轮加速、左轮减速 run(base turn, base - turn);参数有三个约束baseturn 不能超过 255否则高速轮丢速度base−turn 不能低于 30 左右否则低占空比下启动转矩不足小车直线变画龙turn 是最直接的转弯半径旋钮。把左转参数代入 2.1 的公式vLbase−turn、vRbaseturnvR−vL2·turn得到 RW·base/(2·turn)。轮距 W 取 14cmbase130、turn70 时 R≈13cmturn 降到 40R≈23cm。循迹赛道弯道变缓优先降 turn 而不是降 base速度损失更小。还要留意占空比与转速不是线性关系。TT 电机在中低占空比存在死区0~40 之间可能不转40~120 转速爬得快120 以上才进入接近线性的工作段。所以 base 的经验区间是 120~160低于这个区间先考虑垫高工作点而不是急着重写 PID。3.3 烧录验证IDE 设置与最省事的检查顺序Arduino IDE 里开发板选 “Arduino Uno”ATmega328P端口选 CH340 或 USB-ACM 对应的串口号Windows 设备管理器看不到端口一般是 CH340 驱动没装。用 VSCode 加 Arduino 扩展编译也没问题但板型和端口选择逻辑与 IDE 一样报错第一反应还是查这两项。烧录时若提示 avrdude stk500_recv 通信失败把电机和驱动板的信号线拔掉再烧UNO 在串口握手阶段被外部灌电流时会失败。注意验证顺序固定为 直行 → 原地转 → 挂传感器。直行跑偏先查左右轮胎压、万向轮阻力和两个电机是否同减速比原地转画圈半径大则查左右轮 PWM 是否同频。例程在这个阶段就能演示“模块化封装”答辩时把 run 的源码和这轮测试数据一起放出来比只贴 main 里的 if/else 有说服力。4. 循迹、避障与蓝牙遥控智能小车例程三件套的参数调优毕设最常见的要求就是把循迹、避障、遥控三件事凑齐。这三部分共用一套引脚分配先看下面的接线总表再逐个调参。外设引脚说明左循迹 TCRT5000D11D0 输出黑线上为 1以模块实测为准右循迹 TCRT5000D12D0 输出超声波 HC-SR04A0 / A1TRIG / ECHO蓝牙 HC-05D3 / D2RX / TX软件串口舵机可选D13超声波云台4.1 TCRT5000 循迹例程先把阈值读进串口再写逻辑循迹模块的核心是 TCRT5000 反射式光电传感器加 LM393 比较器输出一路数字电平。白纸反光强、黑线吸光弱比较器翻转点靠板上电位器调。第一次接线不要凭感觉写逻辑先把两个头的原始电平打出来看const int TRACK_L 11; // 左循迹模块数字输出 const int TRACK_R 12; // 右循迹模块数字输出 void setup() { Serial.begin(9600); pinMode(TRACK_L, INPUT); pinMode(TRACK_R, INPUT); } void loop() { Serial.print(L); Serial.print(digitalRead(TRACK_L)); // 打印原始电平 Serial.print( R); Serial.println(digitalRead(TRACK_R)); delay(100); }模块悬在纸面上方 0.5~1.5cm黑线上读到 1、白纸上读到 0部分模块极性相反取反即可。电位器调到白纸和黑线都能稳定输出、不在临界点抖动的程度。循迹控制的四分支状态机是例程的标准答案void trackRun() { int l digitalRead(TRACK_L); // 1 表示压到黑线 int r digitalRead(TRACK_R); if (!l !r) run(130, 130); // 都在白纸上直线段直行 else if (l !r) run(85, 150); // 左轮压线车头偏右左轮降速 else if (!l r) run(150, 85); // 右轮压线车头偏左右轮降速 else run(130, 130); // 双线十字/断点保持直行冲过 }两路循迹只能纠偏不能预测turn 要调得保守缓弯道 turn 40~60连续 S 弯 turn 给到 80 并同步降 base 到 110。过直角弯两路会同时出线else 分支保持直行 100~150ms 让它冲过弯心时间太长会冲出赛道。4.2 HC-SR04 避障例程pulseIn 超时与三段距离策略避障逻辑比循迹更固定。Trig 接 A0、Echo 接 A1测距函数const int TRIG A0; // 触发引脚 const int ECHO A1; // 回波引脚 float measureDistance() { digitalWrite(TRIG, LOW); delayMicroseconds(2); // 拉低稳定电平 digitalWrite(TRIG, HIGH); delayMicroseconds(10); // 至少 10us 触发脉冲 digitalWrite(TRIG, LOW); long us pulseIn(ECHO, HIGH, 30000); // 30ms 超时 if (us 0) return 999; // 超时按无障碍处理 return us * 0.034f / 2.0f; // 距离 声速 × 时间 / 2 }pulseIn 第三个参数是超时值单位微秒。不加超时Echo 悬空会让主循环卡死在等待回波里小车直接停摆。30ms 对应约 5m 量程避障用不完但留足余量能避免近距离强反射丢波。0.034 是声速折半再换算到厘米的系数气温低声速略降测距差 1~2cm 属正常。避障例程常见做法是三段式大于 25cm 直行15~25cm 减速转向小于 15cm 后退转向。void avoidRun() { float d measureDistance(); if (d 25) run(150, 150); // 安全区直行 else if (d 15) run(60, 150); // 警戒区右轮加速左转绕过 else run(-100, 150); // 危险区后退并左转 delay(50); // 每个周期留 50ms防转向过冲 }超声波有约 2cm 盲区和 30° 波束角单探头对斜前方的障碍容易漏检。例程常见的升级是给探头加舵机云台左右各扫 90° 取最小距离用servo.write()控制角度按角度记忆距离数组。这是从例程自然地长出“创新点”的位置但舵机瞬时电流 500mA 以上不能从 UNO 5V 引脚取电单独一路 5V 共地。4.3 蓝牙遥控例程HC-05 波特率与串口指令解析蓝牙走软件串口把硬件串口留给 USB 调试是最省事的做法。HC-05 的 RX 逻辑是 3.3VUNO 的 TX 是 5V中间串一个 1kΩ 电阻再进模块 RX防止烧引脚。#include SoftwareSerial.h SoftwareSerial bt(2, 3); // RXD2, TXD3对应 HC-05 的 TX/RX void setup() { Serial.begin(9600); // USB 调试串口 bt.begin(9600); // 蓝牙串口波特率与模块保持一致 pinMode(2, INPUT); // 软件串口 RX 脚 pinMode(3, OUTPUT); } void handleCmd() { if (bt.available() 0) return; // 无数据直接返回 char c bt.read(); // 读取一字节指令 while (bt.available()) bt.read(); // 丢弃本帧剩余字节防粘包 switch (c) { case F: run(160, 160); break; // 前进 case B: run(-160, -160); break; // 后退 case L: run(90, 160); break; // 左转 case R: run(160, 90); break; // 右转 case S: run(0, 0); break; // 停止 default: break; } } void loop() { handleCmd(); delay(20); // 20ms 轮询约 50Hz 指令更新率 }HC-05 出厂常在 38400HC-06 出厂常见 9600。模块上电前按住板载按键再上电进入 AT 模式发ATUART9600,0,0把波特率固定到 9600 后再接线跑例程。Serial.read()读到的是 ASCII 码switch 里必须写F而不是F这是编译期最容易栽的坑。模式切换用一个全局变量 mMode1 切循迹、2 切避障、3 切遥控主循环按 mMode 调用 trackRun、avoidRun 或 handleCmd三个状态机之间不互相抢电机答辩时也讲得清。这套“底层驱动原语 三个模式状态机”的结构以后换 STM32 或 ESP32 也只需要重写电机驱动层业务逻辑原样平移。5. 仿真验证与供电排错让例程直接变成毕设作品5.1 用 Wokwi 做第一轮逻辑验证没有硬件或者硬件在别人手里时Wokwi 这类浏览器仿真平台可以先跑逻辑。新建 Arduino Uno 项目把代码原样贴进去电机选直流电机组件循迹信号用按键模拟电平翻转超声波用虚拟距离组件改变回波蓝牙直接靠仿真串口发字符。仿真验证的不是电压和转速而是状态机分支按键模拟压线时两个电机的 PWM 值对不对串口发 L 时左轮是不是真的减速。仿真通过后再烧真实板子调试时间能砍掉大半。Wokwi 的串口监视器同样能看到 “cycle done” 这类标记。要注意仿真电机模型是理想线性的真实电机有死区和负载特性仿真里标定的 base 值上板后必须重新调一轮仿真最大的价值是帮你确认引脚冲突和逻辑分支而不是替代实物标定。5.2 供电拓扑与两道检查清单供电是例程上板后问题最多的环节。推荐拓扑两节 18650 串联 7.4V 进驱动板 VM驱动板 GND 与 UNO GND 共地UNO 用 USB 或 Vin 单独供电舵机云台另接一路 5V不要并接在 UNO 5V 引脚。电池端并联 470μF 电解电容吸收电机换向的反电动势尖峰能明显减少转弯瞬间复位。检查顺序固定为先硬件后软件万用表量驱动板 VM 与 GND 确认电压范围再量 STBY 引脚电平不到 3.3V 以上就先查使能。软件侧建议把调试输出统一成定长帧例如Serial.println(L130 R70 MTRACK)串口监视器里一眼看到当前模式、左右轮目标速度和状态机是否跳对了比零散 print 好排查得多。最后记得 loop() 里不要放长 delay 阻塞循迹与避障共用控制周期时一次超过 100ms 的阻塞就会让小车丢响应。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →