1. 项目概述:当行空板遇上RC车,一场开源硬件的降维打击
最近在创客圈和硬件爱好者社群里,一个词被反复提及:“行空板”。这玩意儿乍一听有点玄乎,其实它是一款面向青少年编程教育和创客开发的国产开源硬件,核心是一块集成了Wi-Fi、蓝牙、麦克风、扬声器、屏幕和丰富传感器接口的单板计算机。而我这次折腾的项目,就是把这块功能强大的“大脑”,塞进一辆传统的RC(Radio Control,无线电遥控)智能车底盘里,打造一台能编程、能联网、能感知环境的“超级小车”。这听起来像是“旧手机DIY”的硬件升级版,但玩法深度和可扩展性完全不是一个量级。
为什么这么做?传统的RC车,乐趣在于操控,核心是遥控器和接收机之间的通讯。而行空板带来的,是“智能化”的可能。你可以让它脱离遥控器,用Python写几行代码,实现自动避障、巡线、甚至通过Wi-Fi用网页或手机App来控制。它就像给一辆普通汽车装上了特斯拉的Autopilot系统(当然,是极度简化版),从纯粹的“遥控玩具”升级为可编程、可学习的“智能机器人平台”。这个项目非常适合有一定Python基础,想从软件世界踏入硬件实操的开发者,或者是对智能硬件、机器人感兴趣的学生和爱好者。你不需要从零设计电路和结构,而是站在RC车这个成熟“躯体”的肩膀上,专注于“大脑”和“灵魂”的赋予,成就感来得更快,学习曲线也更平滑。
2. 核心思路与方案选型:为什么是行空板+RC车?
在决定动手之前,我花了些时间评估了几种主流方案。比如用树莓派,性能强大但功耗和体积是问题;用Arduino,虽然小巧但计算和联网能力有限,需要额外扩展。而行空板,恰恰在这个夹缝中找到了一个完美的平衡点。
2.1 行空板的独特优势解析
行空板的核心是一颗高性能的嵌入式处理器,运行着完整的Linux系统。这意味着你可以用SSH登录进去,像操作一台微型电脑一样安装软件、运行后台服务。它原生支持Python,并且官方提供了极其友好的pinpong库,让控制GPIO(通用输入输出引脚)像在Arduino上使用digitalWrite一样简单,但背后是更强大的Python生态。对于这个项目,行空板的几个特性是关键:
- 内置屏幕与交互:一块小屏幕可以直接显示小车状态(速度、传感器数据、IP地址)、调试信息,甚至能运行简单的图形界面。这比外接屏幕或全靠串口打印优雅得多。
- 一体化无线模块:板载Wi-Fi和蓝牙,省去了额外的USB适配器或模块,让小车可以轻松接入局域网,实现网页控制、数据回传或与手机App通信。
- 丰富的传感器接口:除了常见的数字/模拟IO口,还直接集成了光线传感器、麦克风等,为环境感知提供了便利。更重要的是,其扩展接口兼容Gravity-I2C/UART等标准,市面上大量的传感器(如超声波、巡线、陀螺仪)可以即插即用。
- 供电与驱动简化:行空板可以通过Type-C口供电,同时其GPIO口可以提供足够的电流来驱动一些小型的舵机和电机驱动芯片,简化了电源系统的设计。
2.2 RC车底盘的选择与改造考量
RC智能车底盘是整个项目的“躯体”。我的选择标准是:结构坚固、差速转向(便于精确控制)、有足够的空间安装行空板和扩展板、电机最好是有刷直流电机(驱动简单)。市面上常见的智能小车底盘套件(通常是亚克力或金属结构)就很合适。你需要关注以下几点:
- 电机与驱动:底盘一般自带减速电机。你需要一个电机驱动模块(如L298N、TB6612FNG)来接收行空板的控制信号,并输出足够的电流给电机。TB6612FNG效率更高、发热更小,是我的首选。
- 供电系统:这是最容易踩坑的地方。行空板需要稳定的5V供电(可通过Type-C),而电机驱动和电机则需要更高的电压(如7.4V或11.1V的锂电池组)来获得足够的扭矩和速度。绝对不能用一个电源同时给行空板和电机供电,电机启停时产生的电压波动和电流尖峰极易导致行空板死机或重启。因此,必须采用双电源方案:一块大容量锂电池(如2S 7.4V)给电机驱动供电;另一块小容量电池或充电宝(输出5V/2A以上)单独给行空板供电。两者共地即可。
- 空间布局:规划好行空板、电机驱动板、电池、可能用到的传感器(如超声波模块)的摆放位置,确保重心平衡,且不影响车轮转向。使用尼龙柱、螺丝和扎带进行固定。
注意:在选购RC车底盘时,避免选择集成了一体化接收机/电调/舵机控制器的“玩具级”遥控车。我们需要的是纯粹的“底盘”:只有电机、车轮和车架,所有的控制逻辑都将由行空板接管。
3. 硬件连接与系统搭建:从零开始的接线指南
硬件连接是项目的基础,务必耐心细致。错误的接线轻则功能失常,重则损坏设备。
3.1 核心部件清单与作用
| 部件名称 | 推荐型号/规格 | 在项目中的作用 |
|---|---|---|
| 主控 | 行空板 (UNIHIKER) | 项目的大脑,运行Python程序,处理逻辑,连接网络。 |
| 车体底盘 | 四轮差速RC智能车底盘套件 | 提供移动平台,包含电机、车轮、车架。 |
| 电机驱动模块 | TB6612FNG 双路电机驱动板 | 将行空板微弱的控制信号放大,驱动大电流电机正反转及调速。 |
| 主电源 | 2S 7.4V 锂电池组 (带XT60或T插) | 为电机驱动模块供电,提供小车动力。 |
| 控制电源 | 大容量充电宝 (5V/2A+) 或 小容量锂电池+降压模块 | 单独为行空板提供稳定、干净的5V电源。 |
| 杜邦线 | 公对公、公对母、母对母若干 | 连接各模块之间的信号与电源。 |
| 固定材料 | M3尼龙柱、螺丝、螺母、扎带 | 将各部件牢固地安装在车架上。 |
3.2 电路连接详解与避坑点
连接遵循“电源分离,信号互通”的原则。下图是核心的连接逻辑示意图(文字描述):
- 电机驱动供电:将7.4V锂电池的正负极分别连接到TB6612FNG驱动板的
VM(电机电源正极)和GND(地线)端子。 - 行空板供电:使用一根Type-C数据线,将充电宝与行空板连接。确保充电宝能提供至少2A的电流。
- 共地操作:这是关键一步!用一根杜邦线,将TB6612FNG驱动板上的
GND(与电机电源共地),与行空板上任意一个GND引脚连接起来。这样,两个独立的电源系统就有了共同的电压参考点,信号才能正确传输。 - 控制信号连接:将行空板的GPIO引脚与TB6612FNG的信号输入端连接。通常控制两个电机需要6个GPIO口:
AIN1,AIN2,PWMA:控制左侧电机的方向(正/反/停)和速度(PWM)。BIN1,BIN2,PWMB:控制右侧电机。 以左侧电机为例,将行空板的三个GPIO口(例如P21,P22,P23)分别连接到驱动板的AIN1,AIN2,PWMA。
- 电机输出连接:将底盘左侧电机的两根线接入驱动板的
A01和A02端子;右侧电机接入B01和B02。如果电机转动方向与预期相反,交换这两个端子的接线即可。
实操心得:电源隔离是生命线我曾尝试用一块电池通过降压模块给行空板供电,省去一个充电宝。但在电机启动瞬间,行空板频繁重启。用示波器观察降压模块输出,能看到明显的电压跌落。这就是电机负载突变对电源系统的“冲击”。最终回归双电源方案,问题彻底解决。多一块电池,换来的是整个系统无与伦比的稳定性。
3.3 行空板基础环境配置
硬件连接好后,给行空板上电。首次使用需要通过USB连接电脑,进行网络配置。
- 连接与登录:用USB线连接行空板和电脑。电脑上会识别出一个网络适配器(如“RNDIS”)。将电脑的IP设置为同一网段(如
192.168.137.2),子网掩码255.255.255.0。在浏览器打开http://192.168.137.1,进入行空板管理界面。 - 配置Wi-Fi:在管理界面中,配置行空板连接到你家的Wi-Fi网络。成功后,记下行空板获取到的IP地址(如
192.168.31.xxx)。此后,你就可以拔掉USB线,通过这个IP地址用SSH(如PuTTY)远程登录行空板,或者直接在浏览器访问该IP来使用其内置的Web IDE(Jupyter Notebook)进行编程。 - 安装必要库:通过SSH登录(用户名
pi,密码unihiker),首先更新软件源并安装Python库的管理工具。行空板官方推荐使用pinpong库来控制硬件,它已经预装。但我们可能还需要其他库,例如用于Web开发的flask。sudo apt update sudo apt install python3-pip pip3 install flask -i https://pypi.tuna.tsinghua.edu.cn/simple
4. 核心控制程序编写:让小车“动”起来
一切就绪,现在开始编写“灵魂”。我们将从最基础的电机控制开始,逐步增加功能。
4.1 基础电机驱动与PWM调速
首先,在行空板的Web IDE(Jupyter)中创建一个新的Python笔记本。我们使用pinpong库来操作GPIO。
from pinpong.board import Board, Pin, PWM import time # 初始化行空板 Board().begin() # 定义引脚映射 (根据你的实际接线修改) # 左侧电机 AIN1 = Pin(Pin.P21, Pin.OUT) # 方向1 AIN2 = Pin(Pin.P22, Pin.OUT) # 方向2 PWMA = PWM(Pin.P23)) # 速度控制 (PWM) # 右侧电机 BIN1 = Pin(Pin.P24, Pin.OUT) BIN2 = Pin(Pin.P25, Pin.OUT) PWMB = PWM(Pin.P26)) # 设置PWM频率,对于电机控制,通常几百Hz到几千Hz,这里设1000Hz PWMA.freq(1000) PWMB.freq(1000) def motor_left(speed): """控制左侧电机,speed范围-100~100,负值为反转""" if speed > 0: AIN1.write_digital(1) AIN2.write_digital(0) PWMA.duty(int(speed * 10.23)) # 将-100~100映射到0~1023的占空比 elif speed < 0: AIN1.write_digital(0) AIN2.write_digital(1) PWMA.duty(int(-speed * 10.23)) else: AIN1.write_digital(0) AIN2.write_digital(0) PWMA.duty(0) def motor_right(speed): """控制右侧电机""" if speed > 0: BIN1.write_digital(1) BIN2.write_digital(0) PWMB.duty(int(speed * 10.23)) elif speed < 0: BIN1.write_digital(0) BIN2.write_digital(1) PWMB.duty(int(-speed * 10.23)) else: BIN1.write_digital(0) BIN2.write_digital(0) PWMB.duty(0) def car_stop(): """停车""" motor_left(0) motor_right(0) def car_forward(speed): """前进""" motor_left(speed) motor_right(speed) def car_backward(speed): """后退""" motor_left(-speed) motor_right(-speed) def car_turn_left(speed): """原地左转(差速)""" motor_left(-speed) motor_right(speed) def car_turn_right(speed): """原地右转""" motor_left(speed) motor_right(-speed) # 测试代码 if __name__ == '__main__': print("前进2秒") car_forward(50) # 50%速度前进 time.sleep(2) car_stop() time.sleep(1) print("右转1秒") car_turn_right(30) time.sleep(1) car_stop() print("测试结束")运行这段代码,你的小车应该能完成前进、转弯和停止的基本动作。PWM.duty()函数中的映射计算是关键,它把直观的百分比速度(-100到100)转换成了0-1023的占空比数值(行空板PWM分辨率是10位,即1024级)。
4.2 构建Web遥控器:用网页控制小车
让小车动起来只是第一步,接下来我们让它联网,并通过浏览器远程控制。这里使用轻量级的Flask框架来创建一个简单的Web服务器。
from flask import Flask, render_template_string, request import threading # ... (保留上面的电机控制函数和引脚定义) ... app = Flask(__name__) current_speed = 0 current_turn = 0 # -100左转, 0直行, 100右转 def car_control_loop(): """一个简单的控制循环,将速度和转向量转换为左右电机速度""" global current_speed, current_turn while True: left_speed = current_speed - current_turn * 0.5 # 转向时内侧轮减速 right_speed = current_speed + current_turn * 0.5 # 限制速度范围 left_speed = max(-100, min(100, left_speed)) right_speed = max(-100, min(100, right_speed)) motor_left(left_speed) motor_right(right_speed) time.sleep(0.05) # 20Hz更新频率 # 启动控制线程 control_thread = threading.Thread(target=car_control_loop, daemon=True) control_thread.start() # 定义一个简单的HTML页面,包含控制按钮和键盘事件 HTML_PAGE = ''' <!DOCTYPE html> <html> <head> <title>行空板RC车遥控器</title> <style> body { text-align: center; font-family: Arial; margin-top: 50px; } .control { margin: 20px; } button { font-size: 24px; padding: 15px 30px; margin: 5px; } #status { margin-top: 20px; font-size: 18px; color: #666; } </style> </head> <body> <h1>行空板RC智能车遥控器</h1> <div class="control"> <button onmousedown="sendCmd('forward')" ontouchstart="sendCmd('forward')" onmouseup="sendCmd('stop')" ontouchend="sendCmd('stop')">前进</button><br> <button onmousedown="sendCmd('left')" ontouchstart="sendCmd('left')" onmouseup="sendCmd('stop_turn')" ontouchend="sendCmd('stop_turn')">左转</button> <button onmousedown="sendCmd('backward')" ontouchstart="sendCmd('backward')" onmouseup="sendCmd('stop')" ontouchend="sendCmd('stop')">后退</button> <button onmousedown="sendCmd('right')" ontouchstart="sendCmd('right')" onmouseup="sendCmd('stop_turn')" ontouchend="sendCmd('stop_turn')">右转</button> </div> <p>也可以使用键盘方向键控制 (点击页面后按)</p> <div id="status">状态: 停止</div> <script> let statusDiv = document.getElementById('status'); document.addEventListener('keydown', (e) => { switch(e.key) { case 'ArrowUp': sendCmd('forward'); break; case 'ArrowDown': sendCmd('backward'); break; case 'ArrowLeft': sendCmd('left'); break; case 'ArrowRight': sendCmd('right'); break; } }); document.addEventListener('keyup', (e) => { if (['ArrowUp', 'ArrowDown'].includes(e.key)) sendCmd('stop'); if (['ArrowLeft', 'ArrowRight'].includes(e.key)) sendCmd('stop_turn'); }); function sendCmd(cmd) { fetch('/cmd/' + cmd) .then(response => response.text()) .then(text => statusDiv.innerText = '状态: ' + text); } // 页面加载后自动聚焦,便于键盘控制 window.onload = () => document.body.focus(); </script> </body> </html> ''' @app.route('/') def index(): return render_template_string(HTML_PAGE) @app.route('/cmd/<command>') def command(command): global current_speed, current_turn if command == 'forward': current_speed = 60 current_turn = 0 elif command == 'backward': current_speed = -60 current_turn = 0 elif command == 'left': current_turn = -80 elif command == 'right': current_turn = 80 elif command == 'stop': current_speed = 0 elif command == 'stop_turn': current_turn = 0 return f'{command}' if __name__ == '__main__': # 注意:host='0.0.0.0' 使得服务器可以从网络中的任何IP访问 # 在行空板上运行,通常使用5000端口 app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)将这段代码保存为web_control.py,并在行空板上运行(python3 web_control.py)。然后在同一局域网内的电脑或手机浏览器中,输入http://[行空板IP]:5000(例如http://192.168.31.123:5000),你就能看到一个简单的网页遥控器了!点击按钮或使用键盘方向键,就能实时控制小车的移动。这个方案比传统的专用遥控器更灵活,你可以在任何有浏览器的设备上控制它,甚至可以实现更复杂的UI和功能。
5. 功能进阶与传感器集成:从遥控到智能
有了基础的移动和遥控能力,我们可以向“智能车”迈进,为其添加“眼睛”和“耳朵”。
5.1 超声波避障功能实现
让小车具备基础的自主避障能力,是迈向自动化的第一步。我们需要一个HC-SR04超声波模块。
- 硬件连接:将超声波模块的
Vcc接行空板5V,Gnd接GND,Trig(触发)接一个GPIO口(如P15),Echo(回声)接另一个GPIO口(如P16)。 - 避障逻辑编程:我们修改控制循环,加入超声波测距和简单的决策逻辑。
from pinpong.board import Board, Pin import time, threading # ... (之前的引脚定义、电机控制函数、Flask app定义保留) ... # 超声波引脚定义 TRIG_PIN = Pin(Pin.P15, Pin.OUT) ECHO_PIN = Pin(Pin.P16, Pin.IN) def get_distance(): """获取超声波测距结果,单位厘米""" TRIG_PIN.write_digital(0) time.sleep(0.000002) # 2微秒 TRIG_PIN.write_digital(1) time.sleep(0.00001) # 10微秒高电平触发 TRIG_PIN.write_digital(0) # 等待回声引脚变高,记录开始时间 while ECHO_PIN.read_digital() == 0: pass pulse_start = time.time() # 等待回声引脚变低,记录结束时间 while ECHO_PIN.read_digital() == 1: pass pulse_end = time.time() pulse_duration = pulse_end - pulse_start # 声音速度约343米/秒,除以2(往返距离) distance = (pulse_duration * 34300) / 2 return distance def auto_avoid_loop(): """自动避障循环,与主控制循环并行运行""" global current_speed, current_turn avoid_mode = False # 是否开启自动避障模式 while True: if avoid_mode: dist = get_distance() if dist < 20: # 如果前方20厘米内有障碍物 car_stop() time.sleep(0.2) car_backward(40) # 先后退 time.sleep(0.5) car_turn_right(60) # 再右转 time.sleep(0.5) car_stop() else: car_forward(40) # 否则继续前进 time.sleep(0.1) # 每100ms检测一次 # 在Flask中添加一个路由来切换自动/手动模式 @app.route('/mode/<mode>') def set_mode(mode): global avoid_mode if mode == 'auto': avoid_mode = True return '自动避障模式开启' else: avoid_mode = False car_stop() return '手动遥控模式' # ... (原有的Flask启动代码) ... # 启动自动避障线程 avoid_thread = threading.Thread(target=auto_avoid_loop, daemon=True) avoid_thread.start()然后在之前的Web页面里增加两个按钮,分别调用/mode/auto和/mode/manual,就可以在网页上切换手动遥控和自动避障模式了。小车在自动模式下会像一个简单的扫地机器人一样,遇到障碍就后退转向。
5.2 巡线功能拓展
巡线是智能小车比赛的经典项目。你需要购买一个灰度传感器或巡线传感器模块(通常有多个红外对管)。这里以常见的4路巡线传感器为例。
- 硬件连接:传感器输出通常是数字信号(黑线为0,白底为1)。将传感器的
Vcc和GND连接好,四个输出引脚分别接到行空板的四个数字输入GPIO口(例如P11,P12,P13,P14)。 - 巡线算法(PID简化版):最简单的可以是“开关量控制”,更高级的可以用PID算法让行驶更平滑。这里展示一个简单的比例控制。
# 定义巡线传感器引脚 line_sensor_pins = [Pin(Pin.P11, Pin.IN), Pin(Pin.P12, Pin.IN), Pin(Pin.P13, Pin.IN), Pin(Pin.P14, Pin.IN)] def line_following_loop(): """巡线控制循环""" global current_speed, current_turn line_mode = False base_speed = 40 while True: if line_mode: sensor_values = [pin.read_digital() for pin in line_sensor_pins] # 假设传感器从左到右排列,值为0表示检测到黑线 # 计算偏差:例如,[-2, -1, 1, 2] 对应四个传感器的权重 error = 0 weights = [-2, -1, 1, 2] for i in range(4): if sensor_values[i] == 0: # 检测到黑线 error += weights[i] # 简单的比例控制:转向量与误差成正比 turn = error * 15 # 比例系数Kp,需要根据实际调整 current_turn = max(-100, min(100, turn)) current_speed = base_speed time.sleep(0.02) # 50Hz高速检测 # 同样,在Flask中添加切换巡线模式的路由巡线功能的调试关键在于调整传感器的安装高度(离地面距离)以及PID算法中的比例系数Kp。你需要反复测试,让小车能平滑地沿着黑线行驶,而不是剧烈地左右摇摆。
6. 常见问题排查与性能优化实录
在项目搭建和调试过程中,我遇到了不少问题,这里把典型的坑和解决方案记录下来。
6.1 硬件连接与电源问题
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 行空板频繁重启或死机 | 1. 电机电源干扰控制电源。 2. 电机驱动板与行空板共地不良。 3. 电机瞬时电流过大。 | 1.坚持使用双电源方案,确保行空板由独立的5V/2A以上电源供电。 2. 检查并确保电机驱动板的GND与行空板的GND用导线可靠连接。 3. 在电机电源输入端并联一个大容量电解电容(如470uF-1000uF/16V),可以吸收电机启停时的电流冲击。 |
| 电机不转或只振动 | 1. 电机驱动板供电不足或未使能。 2. PWM频率不合适。 3. 程序逻辑错误,AIN1/AIN2同时为高或低。 | 1. 用万用表测量驱动板VM电压是否正常(7.4V左右)。检查驱动板使能引脚(如果有)是否接高电平。 2. 尝试调整PWM频率,对于有刷直流电机,通常500Hz-2kHz比较合适。频率太低会听到啸叫,太高可能驱动不了。 3. 检查代码,确保控制电机方向的两个引脚(如AIN1和AIN2)永远是相反的电平(一个1一个0),停车时则都为0。 |
| 网页遥控延迟高或卡顿 | 1. 行空板Wi-Fi信号弱。 2. Flask应用处理阻塞或未使用线程。 3. 浏览器与行空板不在同一局域网。 | 1. 将行空板尽量靠近路由器,或使用手机热点测试。 2. 确保Flask使用 threaded=True运行,避免一个请求阻塞其他请求。控制循环必须放在单独的线程中。3. 确认手机/电脑的IP与行空板IP前三位相同(如都是192.168.31.xxx)。 |
6.2 软件与网络问题
- Python库导入错误:确保在行空板终端中使用
pip3安装库,而不是pip。使用国内镜像源加速下载。 - Flask服务无法远程访问:检查
app.run()是否设置了host='0.0.0.0'。行空板防火墙可能阻止端口,可以暂时关闭或添加规则(sudo ufw allow 5000)。 - 控制响应不跟手:优化Web前端的控制指令发送频率。上面的示例代码在按键按下时持续发送命令,已经足够实时。如果仍有延迟,可以考虑使用WebSocket协议替代HTTP轮询,实现双向实时通信,但这需要更复杂的前后端代码。
6.3 性能优化与小技巧
- 多线程的必要性:正如代码所示,必须将传感器数据读取循环、自动控制决策循环和Flask Web服务放在不同的线程中。否则,
time.sleep()或网络请求会阻塞整个程序,导致控制失灵。Python的threading模块简单易用,适合这种I/O密集型任务。 - 电源管理:长时间运行后,触摸电机驱动芯片和行空板,如果异常发烫,需要检查负载是否过大,或者考虑加装小型散热片。锂电池务必使用平衡充电器充电,并避免过放。
- 结构化你的代码:随着功能增加,代码会变得混乱。建议将电机驱动、传感器读取、Web服务、核心控制逻辑分别写成独立的类或模块文件,通过全局变量或队列进行通信,这样调试和维护会清晰很多。
- 利用行空板屏幕:在屏幕上实时显示IP地址、当前模式(手动/自动)、超声波距离、电池电压(需通过ADC引脚测量)等信息,会极大方便调试和展示。
这个项目的魅力在于,它只是一个起点。你可以继续集成摄像头(通过USB摄像头和OpenCV)实现视觉巡线或物体跟踪,添加语音模块实现语音控制,或者通过MQTT协议接入物联网平台,实现远程监控和调度。它就像一块硬件领域的“三国杀DIY卡牌制作器”,有限的组件提供了无限的创意组合可能。而整个过程,从硬件拼装、软件编程到问题调试,所获得的动手能力和系统思维,远比最终那个能跑的小车本身更有价值。