1. 项目概述:从一块板子到一辆智能遥控车
最近在创客圈子里,行空板的热度一直不减。这块集成了Wi-Fi、蓝牙、触摸屏和丰富接口的国产开源硬件,让很多原本复杂的物联网和机器人项目变得触手可及。但很多朋友拿到手后,除了跑跑官方例程,往往不知道如何深入,让它真正“动”起来,去控制一个实实在在的物理设备。这正是我们这次项目的出发点:用行空板作为大脑,亲手打造一块扩展板作为“神经中枢”和“肌肉驱动”,最终驱动一辆可以安全遥控的小车。
这个项目远不止是简单的连线游戏。它涉及到嵌入式系统的核心工作流程:从主控板的GPIO(通用输入输出接口)资源评估与规划,到扩展板电路的设计与安全隔离,再到电机驱动、电源管理的选型与实现,最后通过无线通信协议完成实时控制。整个过程,是对硬件设计、固件编程和系统集成能力的一次综合锻炼。最终,你将得到的不仅是一辆能跑能停的遥控车,更是一套可复用的、安全的低功耗电机控制方案,这套方案稍加修改,就能应用到智能窗帘、小型机器人、自动导引车(AGV)模型等众多场景中。
2. 核心设计思路与方案选型
2.1 为什么选择“主控板+扩展板”的架构?
直接使用行空板驱动电机行不行?理论上,行空板的I/O口可以输出PWM信号,似乎能直接控制电机转速。但这里存在几个关键问题:
- 驱动能力不足:行空板的GPIO口输出电流通常只有几十毫安,而驱动小车电机(哪怕是N20这类微型减速电机)瞬间电流可能达到数百毫安甚至更高,直接连接会损坏行空板。
- 电源管理复杂:电机启动和堵转时会产生很大的反向电动势和电流尖峰,对电源系统造成干扰,可能导致行空板重启或工作不稳定。
- 接口与功能单一:直接驱动占用了宝贵的GPIO资源,使得后续添加超声波避障、巡线传感器等功能变得捉襟见肘。
因此,引入一块扩展板是专业且必要的选择。它的核心作用可以概括为“承上启下,隔离保护”:
- 承上:通过规范的排针接口与行空板连接,获取控制信号和电源。
- 启下:集成电机驱动芯片、电源稳压模块、传感器接口等,提供强大、稳定、安全的驱动能力。
- 隔离保护:利用电机驱动芯片的内部H桥电路和续流二极管,将电机回路与主控板数字电路隔离开,确保主控板的安全。
2.2 核心器件选型背后的逻辑
电机驱动芯片:DRV8833 vs L298N vs TB6612FNG这是扩展板最核心的部件。我们最终选择DRV8833,理由如下:
- 低功耗与高效率:DRV8833采用MOSFET工艺,导通电阻小,自身发热量远小于老旧的L298N(双极型晶体管工艺),非常适合电池供电的移动设备。
- 集成度高与保护完善:单芯片可驱动两个直流电机或一个步进电机,集成了过流保护、过热保护和欠压锁定保护。相比之下,L298N需要外接大量续流二极管和散热片。
- 控制方式简单:采用IN/IN输入+PWM调速的模式,逻辑清晰,编程简单。TB6612FNG性能类似,但DRV8833在价格和易用性上通常更有优势。
电源方案:双路供电与稳压安全遥控车的“安全”,很大一部分体现在电源系统上。我们采用双路独立供电方案:
- 主控电源:由一块独立的锂电池(如7.4V)通过低压差线性稳压器(LDO,如AMS1117-5.0/3.3)为行空板提供稳定的5V或3.3V电压。LDO噪音低,能为主控数字电路提供洁净的电源。
- 电机电源:直接使用另一块电池(或同一电池的未经稳压的端口)为DRV8833的VM引脚供电。电机电源地与主控电源地必须在扩展板上单点连接(“共地”),以确保信号参考电位一致。
这种分离设计避免了电机大电流波动通过电源线传导至主控板,极大提高了系统稳定性。
通信协议:为何是WebSocket而非简单的HTTP?行空板内置Wi-Fi,我们可以通过网页来遥控。这里不用简单的HTTP表单提交,而选择WebSocket协议,是为了实现全双工、低延迟的实时通信。
- HTTP请求:你点击“前进”按钮,浏览器发送一个请求,服务器(行空板)响应后连接就关闭。要持续控制,需要不断发起新请求,延迟高且繁琐。
- WebSocket连接:一旦网页打开,就与行空板建立一条持久的通信通道。你的每一个操作指令(如摇杆位置变化)都能以毫秒级延迟实时推送过去,行空板也能实时回传传感器数据(如电池电压),实现如游戏手柄般的流畅操控体验。
3. 扩展板电路设计与核心细节解析
3.1 扩展板电路原理图设计要点
设计扩展板,首先要在EDA软件(如立创EDA、KiCad)中绘制原理图。以下是几个关键部分的设计考量:
1. 接口定义与防呆设计行空板的引脚排列是固定的。我们需要在扩展板上设计一个与之匹配的排母接口。务必仔细核对行空板官方引脚图,将用到的I/O口、电源(5V、3.3V)、地(GND)正确引出。可以在接口旁丝印层清晰标注引脚功能(如M1_PWM,M1_IN2),并在物理结构上设计防呆口(如缺口),防止插反。
2. 电机驱动电路布局以驱动两个直流电机为例,DRV8833的典型连接如下:
VM:接电机电源正极(如7.4V)。GND:接电机电源地,并与主控地相连。AOUT1/AOUT2:输出A,接电机A的两根线。BOUT1/BOUT2:输出B,接电机B的两根线。AIN1/AIN2, BIN1/BIN2:控制逻辑输入,分别接行空板的四个GPIO。A_PHASE/B_PHASE:PWM调速输入,接行空板的两个支持PWM的GPIO。nSLEEP:使能引脚,接高电平(VCC)或通过一个GPIO控制,方便软件断电。nFAULT:故障指示引脚,可以接一个LED或上拉到VCC并通过GPIO读取,用于诊断。
3. 电源滤波与去耦在DRV8833的VM引脚附近,一定要放置一个大容量(如100uF)的电解电容和一个小容量(如0.1uF)的陶瓷电容并联到地。大电容应对电机启动时的大电流需求,小电容滤除高频噪声。同样,在给行空板供电的LDO输入输出端,也应放置合适的去耦电容(通常10uF+0.1uF),这是保证系统稳定运行的基石,绝不能省略。
4. 扩展接口预留除了电机驱动,应在板子边缘预留一些排针,将行空板剩余的I/O口、I2C、UART等接口引出,方便后续连接超声波模块、陀螺仪等传感器。
实操心得:在画原理图时,我习惯为每一个重要的电源网络(如5V、3.3V、VMOTOR)都添加一个测试点(Test Point)。在焊接调试阶段,用万用表或示波器钩住测试点测量电压波形,比去测芯片引脚方便安全得多。
3.2 PCB布局与布线的核心技巧
原理图正确只是第一步,PCB布局布线决定了扩展板的最终性能和抗干扰能力。
1. 电源路径优先、尽量粗短电机驱动电流大,VM到DRV8833再到电机接口的走线,必须足够宽。一个简单的估算方法是:1盎司铜厚下,1mm线宽大约能承载1A电流。如果电机堵转电流可能达到2A,那么这条电源线宽度至少应在2mm以上。同样,地线也要尽可能宽,最好采用铺铜方式形成完整的地平面。
2. 信号与电源分离将大电流的电机电源走线和小电流的数字信号走线(如GPIO、PWM)分开放置在PCB的不同区域,避免平行长距离走线,以减少耦合干扰。如果空间允许,可以在中间用地线进行隔离。
3. 去耦电容务必靠近芯片引脚给DRV8833和LDO芯片放置的去耦电容,其接地端到芯片电源引脚的环路面积要尽可能小。这意味着电容必须紧挨着芯片的VCC和GND引脚放置,否则高频去耦效果会大打折扣。
4. 散热考虑虽然DRV8833效率高,但在驱动负荷较重或堵转时仍会发热。PCB设计时,可以在芯片底部(对应封装)预留一个露铜的散热焊盘,并通过多个过孔连接到PCB背面的地铜层,利用整个PCB来辅助散热。
4. 固件编程:让行空板“思考”与“响应”
4.1 开发环境搭建与基础驱动编写
行空板支持Python编程,我们使用其内置的Pinpong库(兼容MicroPython和标准Python语法)来控制GPIO。
首先,需要初始化与电机驱动芯片相关的引脚:
from pinpong.board import Board, Pin from pinpong.extension.unihiker import * import time # 初始化行空板 Board().begin() # 定义电机控制引脚 # 假设AIN1/AIN2接P21/P22, A_PHASE接P23 M1_IN1 = Pin(Pin.P21, Pin.OUT) M1_IN2 = Pin(Pin.P22, Pin.OUT) M1_PWM = Pin(Pin.P23, Pin.OUT) # 初始化PWM对象,频率设为1kHz(对于DRV8833,常用范围是10kHz-50kHz,这里1kHz简化演示) pwm1 = PWM(Pin(Pin.P23)) pwm1.freq(1000) # 设置频率接下来,编写基础的电机控制函数。DRV8833的控制逻辑如下表所示:
| AIN1 | AIN2 | A_PHASE (PWM) | 电机A状态 |
|---|---|---|---|
| 0 | 0 | X | 停止/惯性滑行 |
| 1 | 0 | 有效占空比 | 正转(速度由PWM决定) |
| 0 | 1 | 有效占空比 | 反转(速度由PWM决定) |
| 1 | 1 | X | 刹车(快速停止) |
根据此表,我们可以封装函数:
def motor_a_control(direction, speed): """控制电机A Args: direction: 1-正转, -1-反转, 0-刹车 speed: 0-100之间的整数,表示速度百分比 """ if direction == 0: # 刹车 M1_IN1.write_digital(1) M1_IN2.write_digital(1) pwm1.duty(0) # PWM占空比设为0 elif direction == 1: # 正转 M1_IN1.write_digital(1) M1_IN2.write_digital(0) pwm1.duty(int(speed * 1023 / 100)) # 将百分比转换为0-1023的占空比值 elif direction == -1: # 反转 M1_IN1.write_digital(0) M1_IN2.write_digital(1) pwm1.duty(int(speed * 1023 / 100)) else: # 停止/滑行 M1_IN1.write_digital(0) M1_IN2.write_digital(0) pwm1.duty(0)4.2 构建实时WebSocket遥控服务器
行空板运行一个基于asyncio和websockets库的服务器,用于接收网页指令并控制电机。
import asyncio import websockets import json # 电机控制函数(同上,略) async def handle_websocket(websocket, path): """处理WebSocket连接""" print("客户端连接成功") try: async for message in websocket: # 解析从网页发来的JSON指令,例如 {"cmd": "move", "left": 80, "right": 80} data = json.loads(message) if data['cmd'] == 'move': left_speed = data.get('left', 0) right_speed = data.get('right', 0) # 根据左右轮速度差,计算差速转向 # 这里假设两个电机:M1左轮,M2右轮 # 简化处理:速度值为正前进,负后退 if left_speed >= 0: motor_a_control(1, abs(left_speed)) else: motor_a_control(-1, abs(left_speed)) # ... 同理控制右轮电机M2 ... print(f"设置速度: 左轮{left_speed}, 右轮{right_speed}") elif data['cmd'] == 'stop': motor_a_control(0, 0) # ... 停止右轮 ... print("紧急停止") except websockets.exceptions.ConnectionClosed: print("客户端断开连接") async def main(): # 启动WebSocket服务器,监听本机所有IP的8765端口 async with websockets.serve(handle_websocket, "0.0.0.0", 8765): print("WebSocket遥控服务器已启动,等待连接...") await asyncio.Future() # 永久运行 # 在主线程中运行异步事件循环 asyncio.run(main())4.3 创建低延迟的网页遥控界面
在行空板内部存储或通过HTTP服务提供一个简单的HTML页面。这个页面包含虚拟摇杆(可以使用nipple.js或joystick库)或方向按钮,并通过JavaScript建立WebSocket连接,实时发送控制指令。
<!DOCTYPE html> <html> <head> <title>行空板遥控车控制端</title> <script src="https://cdn.jsdelivr.net/npm/nipplejs@0.9.1/dist/nipplejs.min.js"></script> <style> /* 样式:居中显示摇杆区域 */ #joystick_zone { width: 300px; height: 300px; margin: 50px auto; background-color: #f0f0f0; position: relative; } #status { text-align: center; margin-top: 20px; } </style> </head> <body> <h2 style="text-align:center;">安全遥控车控制面板</h2> <div id="joystick_zone"></div> <div id="status">连接状态:<span id="conn_status">未连接</span></div> <script> const wsUrl = `ws://${window.location.hostname}:8765`; // 假设页面由行空板HTTP服务提供 let socket = null; function connectWebSocket() { socket = new WebSocket(wsUrl); socket.onopen = () => { document.getElementById('conn_status').textContent = '已连接'; console.log('WebSocket连接成功'); }; socket.onclose = () => { document.getElementById('conn_status').textContent = '连接断开,正在重连...'; console.log('WebSocket连接断开'); setTimeout(connectWebSocket, 2000); // 2秒后重连 }; socket.onerror = (error) => { console.error('WebSocket错误:', error); }; } // 初始化虚拟摇杆 const joystickZone = document.getElementById('joystick_zone'); const joystick = nipplejs.create({ zone: joystickZone, mode: 'static', position: { left: '50%', top: '50%' }, color: 'blue', size: 150 }); joystick.on('move', (evt, data) => { if (!socket || socket.readyState !== WebSocket.OPEN) return; // data.vector.x 和 data.vector.y 范围是[-1, 1] const x = data.vector.x; const y = data.vector.y; // 将摇杆坐标转换为左右轮速度(差速模型) // 这是一个简化模型:前进/后退由y控制,转向由x控制 const maxSpeed = 100; let leftSpeed = Math.round((y + x) * maxSpeed); let rightSpeed = Math.round((y - x) * maxSpeed); // 限制速度在[-100, 100]之间 leftSpeed = Math.max(-100, Math.min(100, leftSpeed)); rightSpeed = Math.max(-100, Math.min(100, rightSpeed)); const cmd = { cmd: 'move', left: leftSpeed, right: rightSpeed }; socket.send(JSON.stringify(cmd)); }); joystick.on('end', () => { if (!socket || socket.readyState !== WebSocket.OPEN) return; // 摇杆松开,发送停止指令 const cmd = { cmd: 'stop' }; socket.send(JSON.stringify(cmd)); }); // 初始化连接 connectWebSocket(); </script> </body> </html>5. 系统集成、组装与调试实录
5.1 硬件焊接与组装注意事项
拿到打样回来的PCB后,焊接是第一步。顺序建议是“先矮后高,先贴片后直插”:
- 焊接贴片元件:如电阻、电容、DRV8833芯片。使用烙铁和焊锡丝,配合助焊剂,确保焊点饱满光亮,无虚焊、桥接。焊接芯片时,可以先对齐固定一个脚,再焊接对角,最后处理其余引脚。
- 焊接直插元件:如电源端子、排针、排母、LED。注意排母的方向要与行空板对应。
- 焊接电机接口:建议使用XT30、T插等专用连接器,或者焊接导线后热缩管绝缘。确保连接牢固,能承受拉扯。
组装小车底盘时,注意电机安装牢固,轮子与电机轴之间最好使用联轴器或紧定螺丝固定,避免打滑。将扩展板通过排母插到行空板上方,注意对准方向。最后,用扎带或螺丝将电池妥善固定在小车底盘上。
踩坑记录:第一次组装时,我把电机驱动芯片的散热焊盘忘了接地。结果小车一跑起来,芯片烫得厉害,没多久就保护停机了。后来用万用表检查才发现,散热焊盘是悬空的。重新加焊,用多个过孔连接到背面地铜皮,温度立刻降了下来。所以,芯片数据手册里的每一个细节都不能放过。
5.2 上电测试与基础功能验证
组装完成后,不要急于上电跑程序。遵循以下安全测试流程:
- 目视检查:检查有无焊锡短路、元件焊反、极性接反(如电容、二极管)。
- 静态阻抗测试:断开所有电源,用万用表蜂鸣档测量:
VM与GND之间不应短路。- 行空板的
5V/3.3V与GND之间不应短路。
- 分级上电:
- 第一步:只连接主控电源(给行空板供电),不接电机电源。观察行空板能否正常启动,扩展板上的电源指示灯(如果有)是否亮起。用万用表测量扩展板上各电压点(5V, 3.3V)是否正常。
- 第二步:连接电机电源。此时先不要编程,手动短接扩展板上电机驱动的输入引脚(例如,用杜邦线将
AIN1接高电平,AIN2接地),听电机是否有反应。注意:手动测试时,电机最好悬空,避免小车突然窜出。
- 软件基础测试:编写一个简单的测试程序,让单个电机以不同方向、不同速度转几秒。观察电机转动是否顺畅,有无异响,驱动芯片温度是否正常。
5.3 无线连接与实时控制联调
确保基础硬件功能正常后,开始进行软件联调:
- 启动服务器:在行空板上运行写好的WebSocket服务器程序。查看输出,确认服务器已启动并监听在
0.0.0.0:8765。 - 获取IP地址:在行空板屏幕上或通过串口查看其Wi-Fi IP地址。
- 网页访问:在同一局域网下的电脑或手机浏览器中,输入
http://[行空板IP地址](如果托管了HTML页面)或使用单独的HTML文件,但将JS代码中的ws://${window.location.hostname}:8765改为ws://[行空板IP地址]:8765。 - 操控测试:打开网页,操作摇杆。同时观察:
- 浏览器开发者工具(F12)的“网络”或“控制台”标签,查看WebSocket消息是否正常发送。
- 行空板服务器的打印输出,是否收到指令并解析成功。
- 小车电机的实际响应是否与指令一致,有无延迟或卡顿。
6. 常见问题排查与性能优化技巧
6.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 上电后行空板不启动或反复重启 | 1. 电源电压不足或电流不够。 2. 扩展板短路。 3. 电机电源干扰串入主控电源。 | 1. 用万用表测量行空板供电入口电压,确保在4.8V-5.2V之间。 2. 断开扩展板,单独给行空板供电测试。 3. 检查扩展板 5V/GND间阻抗,排除短路。4. 确保主控电源与电机电源地线单点连接良好,且电机电源滤波电容足够。 |
| 电机不转或单向转动 | 1. 控制信号线连接错误或虚焊。 2. DRV8833使能引脚 nSLEEP未拉高。3. 电机本身损坏或接线断路。 | 1. 用万用表或逻辑分析仪检查AIN1/AIN2和PWM引脚在控制时的电平变化。2. 检查 nSLEEP引脚是否接到高电平(VCC)。3. 直接将电机接到电池上(短暂测试),看是否转动。 |
| 电机转动无力或速度慢 | 1. 电机供电电压不足。 2. PWM频率设置不当。 3. DRV8833驱动电流不足或过热保护。 | 1. 测量电机电源VM端电压,带载时是否跌落严重。2. 尝试调整PWM频率(通常在10kHz-50kHz),频率太低电机可能有噪音,太高可能导致驱动芯片开关损耗增加。 3. 触摸DRV8833是否发烫,检查散热。 |
| WebSocket连接失败 | 1. 行空板与遥控设备不在同一局域网。 2. 防火墙或路由器设置阻止了8765端口。 3. 服务器程序未成功运行。 | 1. 确认两者连接的是同一个Wi-Fi。 2. 尝试在行空板上 ping一下遥控设备的IP。3. 检查行空板Python程序是否有报错,是否正常监听端口(可用 netstat命令查看)。4. 尝试更换一个端口号(如8080)。 |
| 遥控延迟高、卡顿 | 1. 网络信号差。 2. 网页端JS代码发送指令过于频繁或处理阻塞。 3. 行空板性能瓶颈(同时处理太多任务)。 | 1. 将行空板和遥控设备靠近路由器。 2. 在JS摇杆 move事件中,可以加入节流(throttle)处理,比如每50ms发送一次指令,而不是每个事件都发送。3. 优化行空板Python代码,确保 asyncio事件循环不被阻塞(如避免在回调中使用time.sleep)。 |
| 小车直线跑偏 | 1. 左右轮电机/轮胎存在细微差异。 2. 左右轮负载不均。 3. 地面不平。 | 1. 这是开源硬件的通病。可以在软件中加入“校准系数”。例如,发现小车总是右偏,则在发送速度指令时,将右轮速度乘以一个略小于1的系数(如0.95),通过微调使小车走直线。 |
6.2 进阶优化与功能扩展思路
当基础功能稳定后,可以考虑以下优化和扩展,让项目更具挑战性和实用性:
1. 增加电池电压监测通过行空板的ADC(模数转换)引脚,连接一个电阻分压电路到电池正极,实时监测电池电压。当电压低于阈值时,在网页控制界面上显示警告,或自动控制小车减速、返回充电。
2. 实现速度闭环控制目前的控制是开环的,即给定PWM占空比,但实际转速会因负载、电量变化而波动。可以给电机加装编码器,通过行空板的中断引脚计数脉冲,计算出实际转速,并使用PID算法来调节PWM输出,使小车能保持精确的设定速度运行。
3. 融合传感器实现半自主
- 超声波避障:在扩展板预留的I/O口上连接HC-SR04模块。在WebSocket服务器代码中,周期性读取距离数据。当距离小于安全值时,自动覆盖遥控指令,执行刹车或绕行逻辑,实现“防撞”安全模式。
- 惯性导航:接入MPU6050等六轴传感器,通过行空板的I2C接口读取数据。结合算法(如互补滤波),可以估算小车的姿态和位移,实现更复杂的运动控制,比如保持直线行驶、完成特定角度的转弯。
4. 优化控制协议与界面
- 协议优化:将控制指令从简单的
{left, right},升级为更专业的{v, w}(线速度和角速度)模型,使控制更符合运动学,便于后续加入导航算法。 - 界面美化:使用Vue.js或React等框架重构网页,加入更酷炫的虚拟摇杆、实时视频流显示(如果扩展摄像头)、传感器数据仪表盘等,打造一个专业的遥控终端。
从一块小小的行空板扩展板出发,到一辆响应迅速、运行稳定的无线遥控车,这个项目贯穿了硬件设计、嵌入式编程、网络通信和系统调试的全流程。每一个环节遇到的问题和解决的思路,都是宝贵的实践经验。最重要的是,这个框架是活的,你可以随时为它添加新的传感器、新的算法,让它从一辆简单的遥控车,进化成能避障、能巡线、甚至能SLAM建图的智能移动平台。