1. 项目缘起:从一块“古董”开发板开始的智能硬件之旅
最近在整理工作室的物料箱,翻出来一块几年前入手的 Intel Curie Nano 开发板。这板子当年也算是 Intel 进军物联网和可穿戴领域的一个标志性产品,内置了 Intel Curie 模组,集成了低功耗的 Quark SE 微控制器和一个 32 位的 ARC 架构传感器协处理器,还有 6 轴 IMU 和蓝牙。不过后来 Intel 在物联网硬件上战略收缩,这板子也就渐渐淡出了主流视野,成了我们这些硬件爱好者手里的“收藏品”。看着它静静地躺在防静电袋里,总觉得有点可惜,毕竟硬件底子还是不错的。正好手头有个一直想做的项目——一个能自动分类、压缩甚至提醒你倒垃圾的“智能垃圾桶”,不如就用它来当主控,也算是让这块板子发挥一下余热。
这个“智能垃圾桶”项目我打算分成几期来做,从易到难,从基础到复杂。第一期,我们就从最根本的“底盘”开始。为什么是底盘?因为一个好的移动平台是整个项目稳定运行的基础。想象一下,你的垃圾桶需要自主移动到指定位置,或者根据指令调整朝向,如果底盘不稳、控制不灵,后面加上再多的传感器和智能算法都是白搭。这就像盖房子,地基没打好,上面装修得再漂亮也住不踏实。
本期“底盘篇”的核心目标很明确:利用 Intel Curie Nano 作为主控,搭配常见的电机驱动模块和轮子,搭建一个稳定、可控的双轮差分底盘。我们会深入探讨如何通过 Arduino 环境为这块相对小众的板子编程,如何通过 SPI 总线与外围设备高效通信(比如读取更高精度的编码器数据),并解决在驱动和控制中可能遇到的实际问题。无论你是对 Intel Curie 这块板子感兴趣,还是想学习如何从零搭建一个机器人底盘,亦或是想了解 SPI 通信在电机控制中的实际应用,这篇内容都会给你带来一次扎实的实战体验。
2. 硬件选型与核心组件解析
搭建一个差分底盘,硬件选型是第一步,它直接决定了后续开发的难度和最终性能的上限。我的选型原则是在满足基本功能的前提下,尽量利用手头已有的物料,同时兼顾学习的价值。
2.1 主控:Intel Curie Nano 的再审视
首先是我们今天的主角,Intel Curie Nano。它本质上是一个基于 Intel Quark SE 微控制器的 Arduino 兼容板。其核心优势在于极低的功耗和内置的传感器融合引擎,非常适合可穿戴设备。但对于机器人底盘来说,我们更关注以下几点:
- GPIO 与 PWM:它提供了足够数量的数字 IO 和 PWM 输出,足以驱动两个电机并连接必要的传感器。
- 通信接口:具备 I2C 和 SPI 接口,这对于连接现代传感器(如 SPI 接口的磁编码器)至关重要。
- 开发环境:兼容 Arduino IDE,极大地降低了开发门槛。虽然 Intel 官方对其的支持已减弱,但社区的基础库和示例仍然可用。
它的局限性也很明显:主频较低(32MHz),内存有限(SRAM 仅 24KB),处理复杂的控制算法(如 PID 调参过程)或大量数据时会有些吃力。但这对于本期实现底盘的基础移动和速度控制来说,是完全足够的。选择它,正是为了在资源受限的环境下实践高效编程。
2.2 动力与驱动:电机、驱动模块与电源
- 电机:我选择了两个常见的 N20 微型减速电机。它们体积小、扭矩足,自带减速箱,非常适合小型底盘。关键是,我手头有配套的增量式光电编码器(AB 相输出),可以用于测量电机转速,实现闭环速度控制。
- 电机驱动模块:这是连接弱电(主控)和强电(电机)的关键。我选用的是 TB6612FNG 双路电机驱动芯片模块。相比于经典的 L298N,TB6612FNG 效率更高、发热更少,支持 PWM 调速和正反转控制,逻辑电压与 Curie Nano 的 3.3V 完美兼容,无需电平转换。
- 电源:整个系统需要两组电源:一是为 Curie Nano 和逻辑电路供电的 3.3V/5V;二是为电机供电的功率电源。我使用一块 7.4V 的 2S 锂聚合物电池。通过一个降压模块(如 MP1584EN)将电池电压降至 5V 给 Curie Nano 的 VIN 引脚供电,同时电池电压直接接入 TB6612FNG 的 VMOT 引脚驱动电机。这里有个重要细节:必须确保逻辑部分(Curie Nano, TB6612)和电机部分的地(GND)是共用的,即整个系统只有一个共同的参考地,否则控制信号会紊乱。
2.3 感知与反馈:编码器的选择与 SPI 的引入
要让底盘走直线、转特定的角度,开环控制(只给 PWM,不管实际转速)是绝对不行的,漂移会非常严重。因此,我们需要电机编码器来提供速度反馈。
我手头的光电编码器是增量式的,通过 AB 相输出脉冲。Curie Nano 可以利用外部中断引脚来捕获这些脉冲,计算转速。但这里我想引入本期的一个技术重点:SPI 通信。
为什么考虑 SPI?增量式编码器在高速或需要绝对位置时,对主控的中断响应和计数能力要求高,容易丢失脉冲。而一些更先进的磁编码器(如 AS5047P、MA730)则通过 SPI 接口直接输出一个绝对角度值(比如 14 位,0-16383)。主控只需定期通过 SPI 读取这个值,通过计算角度差就能得到精确的速度和位置信息,无需担心中断丢失,抗干扰能力也更强。这为后续实现更精准的位置控制打下了基础。
因此,在硬件连接上,我预留了 SPI 接口(Curie Nano 的引脚 10-SS, 11-MOSI, 12-MISO, 13-SCK),为未来升级为 SPI 编码器做好准备。本期我们先使用中断方式读取增量编码器,但会详细讲解 SPI 的配置,为“第二期”做好铺垫。
2.4 结构件与杂项
底盘结构我用的是激光切割的亚克力板,轻便且易于加工。轮子、万向轮(或从动球)、电机固定支架、电池仓等都是模型用的标准件。连接线建议使用杜邦线进行原型搭建,但为了可靠性,后期最好焊接或使用更可靠的连接器。
下表总结了本期核心硬件清单及连接要点:
| 组件 | 型号/规格 | 关键接口/参数 | 与 Curie Nano 连接要点 |
|---|---|---|---|
| 主控 | Intel Curie Nano | 3.3V 逻辑, Arduino 兼容 | 核心控制中心 |
| 电机驱动 | TB6612FNG 模块 | 双路,逻辑电压 3.3V-5V | PWMA/B接 PWM 引脚,AIN1/AIN2, BIN1/BIN2接数字 IO |
| 电机 | N20 减速电机 (带编码器) | 工作电压 6V,减速比 1:30 | 电机线接驱动模块输出,编码器线接中断引脚 |
| 编码器 | 增量式光电编码器 (AB相) | 每转脉冲数 (PPR) | A相接外部中断引脚,B相接普通数字输入引脚 |
| 电源 | 7.4V 2S 锂聚合物电池 | - | 经降压模块至 5V 接 Curie VIN,电池直连驱动 VMOT |
| 结构 | 亚克力底盘、轮子、万向轮 | - | 确保电机安装牢固,底盘重心平衡 |
3. 软件环境搭建与 Curie Nano 的 Arduino 配置
虽然 Curie Nano 相对小众,但在 Arduino IDE 中配置它并不复杂。关键在于找到正确的板卡支持包。
3.1 安装 Arduino IDE 与板卡支持
首先,确保你安装了最新版的 Arduino IDE(1.8.x 或 2.0+均可)。打开 IDE,进入“文件”->“首选项”。在“附加开发板管理器网址”中,添加以下 URL:
https://github.com/01org/corelibs-arduino101/raw/master/package_index.json这个链接包含了 Intel Curie 系列开发板的支持包。添加后,点击“确定”。
接着,打开“工具”->“开发板”->“开发板管理器”。在搜索框中输入“Curie”。你应该能看到一个名为“Intel Curie Boards by Intel”的包。选择它并点击“安装”。安装过程可能需要几分钟,请保持网络通畅。
安装完成后,你就可以在“工具”->“开发板”列表中看到“Intel Curie Nano”的选项了,选择它。
3.2 关键设置与端口选择
选择好开发板后,还需要注意“工具”菜单下的几个选项:
- 端口:将 Curie Nano 通过 Micro-USB 线连接到电脑,这里会出现对应的串口(如 COM3 或 /dev/ttyACM0),选择它。
- 编程器:保持默认的“AVRISP mkII”即可。
- 其他选项:如 CPU 频率等,本期项目保持默认设置。
3.3 验证与基础程序上传
为了测试环境是否搭建成功,我们可以上传一个最简单的 Blink 程序。但 Curie Nano 的板载 LED 连接在引脚 13,而这个引脚同时也是 SPI 的 SCK 引脚。为了避免冲突,我们可以用另一个数字引脚(比如引脚 4)接一个外置 LED 来测试。
void setup() { pinMode(4, OUTPUT); // 设置引脚4为输出 } void loop() { digitalWrite(4, HIGH); // 点亮LED delay(1000); // 等待1秒 digitalWrite(4, LOW); // 熄灭LED delay(1000); // 等待1秒 }点击上传按钮,如果一切顺利,程序将被编译并上传到 Curie Nano,外接在引脚 4 的 LED 会开始闪烁。这证明你的开发环境已经就绪。
注意:有时在上传过程中可能会遇到错误,比如“下载超时”或“找不到设备”。可以尝试以下步骤:1) 按一下板子上的“MASTER RESET”按钮再重新上传;2) 更换一条质量好的 USB 数据线;3) 检查电脑的端口是否被其他软件占用。
4. 底盘基础驱动:PWM 与电机控制逻辑
底盘要动起来,最核心的就是控制两个电机的转速和方向。我们将通过 PWM 信号来控制电机速度,通过两个 GPIO 的电平组合来控制方向。
4.1 TB6612FNG 驱动逻辑详解
TB6612FNG 每一路电机控制需要三个信号:AIN1/AIN2(或BIN1/BIN2)用于方向,PWMA/PWMB用于速度。
其真值表如下:
| AIN1 | AIN2 | PWMA | 电机A状态 |
|---|---|---|---|
| 0 | 0 | X | 停止(刹车) |
| 1 | 0 | PWM | 正转(速度由 PWM 占空比决定) |
| 0 | 1 | PWM | 反转(速度由 PWM 占空比决定) |
| 1 | 1 | X | 停止(刹车) |
“刹车”状态相当于将电机两端短路,能产生一定的制动效果,比单纯断开电源停止得更快。
4.2 Curie Nano 的 PWM 配置与代码实现
在 Arduino 中,我们使用analogWrite(pin, value)函数来输出 PWM。其中value范围是 0-255,对应占空比 0%-100%。Curie Nano 的特定引脚支持 PWM,我们需要查看其引脚定义图。通常数字引脚 3, 5, 6, 9, 10, 11 是支持 PWM 的。
假设我们将电机 A 的PWMA接 Curie Nano 的引脚 5,AIN1接引脚 7,AIN2接引脚 8。电机 B 同理接在引脚 6, 9, 10 上。
下面是一个基础的电机控制函数封装:
// 电机引脚定义 #define MOTOR_A_PWM 5 #define MOTOR_A_IN1 7 #define MOTOR_A_IN2 8 #define MOTOR_B_PWM 6 #define MOTOR_B_IN1 9 #define MOTOR_B_IN2 10 void setup() { // 初始化所有电机控制引脚为输出模式 pinMode(MOTOR_A_PWM, OUTPUT); pinMode(MOTOR_A_IN1, OUTPUT); pinMode(MOTOR_A_IN2, OUTPUT); pinMode(MOTOR_B_PWM, OUTPUT); pinMode(MOTOR_B_IN1, OUTPUT); pinMode(MOTOR_B_IN2, OUTPUT); } // 控制单个电机的函数 // motor: 0 代表电机A, 1 代表电机B // speed: -255 到 255,负数为反转 void setMotor(int motor, int speed) { speed = constrain(speed, -255, 255); // 限制速度范围 int pwmPin, in1Pin, in2Pin; if (motor == 0) { pwmPin = MOTOR_A_PWM; in1Pin = MOTOR_A_IN1; in2Pin = MOTOR_A_IN2; } else { pwmPin = MOTOR_B_PWM; in1Pin = MOTOR_B_IN1; in2Pin = MOTOR_B_IN2; } if (speed > 0) { // 正转 digitalWrite(in1Pin, HIGH); digitalWrite(in2Pin, LOW); analogWrite(pwmPin, speed); } else if (speed < 0) { // 反转 digitalWrite(in1Pin, LOW); digitalWrite(in2Pin, HIGH); analogWrite(pwmPin, -speed); // 取绝对值 } else { // 停止(刹车) digitalWrite(in1Pin, LOW); digitalWrite(in2Pin, LOW); analogWrite(pwmPin, 0); } } void loop() { // 测试:让底盘前进2秒,原地右转2秒,停止1秒 setMotor(0, 200); // 电机A正转,速度200 setMotor(1, 200); // 电机B正转,速度200 delay(2000); setMotor(0, 150); // 电机A正转,速度150 setMotor(1, -150); // 电机B反转,速度150 -> 差分转向 delay(2000); setMotor(0, 0); // 停止 setMotor(1, 0); delay(1000); }上传这段代码,你的底盘应该能执行一个简单的前进-右转-停止的动作序列。如果电机转向与预期相反,只需交换对应电机的IN1和IN2接线即可。
5. 速度闭环控制:增量式编码器与中断计数
开环控制下,即使给两个电机相同的 PWM 值,由于电机特性、电池电压波动、轮子摩擦等因素,底盘也几乎不可能走直线。我们必须引入编码器反馈,构成闭环。
5.1 编码器计数原理与接口
增量式编码器输出两路相位差 90 度的方波(A 相和 B 相)。通过检测 A 相的上升沿和下降沿,并结合 B 相的电平状态,可以在一个脉冲周期内进行 4 次计数(即 4 倍频),大大提高分辨率。例如,我的编码器电机标称是每转 11 个脉冲(PPR),经过 4 倍频后,每转的计数就是 44。
我们将编码器 A 相接 Curie Nano 的外部中断引脚(如引脚 2 或 3),B 相接普通数字输入引脚(如引脚 4)。
5.2 使用外部中断实现高速计数
Curie Nano 的 Arduino 核心支持attachInterrupt()函数。我们需要在中断服务函数中,根据 A、B 相的状态判断是加计数还是减计数。
// 编码器引脚定义 #define ENCODER_A_A 2 // 中断引脚,接编码器A相 #define ENCODER_A_B 4 // 普通输入,接编码器B相 #define ENCODER_B_A 3 // 中断引脚,接另一个编码器A相 #define ENCODER_B_B 12 // 普通输入,接另一个编码器B相 // 编码器计数变量,使用 volatile 修饰,确保在中断内修改能被主循环正确读取 volatile long encoderCountA = 0; volatile long encoderCountB = 0; // 电机A的编码器中断服务函数 void handleEncoderA() { int a = digitalRead(ENCODER_A_A); int b = digitalRead(ENCODER_A_B); // 根据A、B相状态判断方向。此逻辑取决于你的编码器相位和接线,可能需要调整 if (a == b) { encoderCountA--; } else { encoderCountA++; } } // 电机B的编码器中断服务函数 void handleEncoderB() { int a = digitalRead(ENCODER_B_A); int b = digitalRead(ENCODER_B_B); if (a == b) { encoderCountB++; } else { encoderCountB--; } } void setup() { Serial.begin(115200); // 初始化串口用于调试输出 // 初始化编码器引脚 pinMode(ENCODER_A_A, INPUT_PULLUP); pinMode(ENCODER_A_B, INPUT_PULLUP); pinMode(ENCODER_B_A, INPUT_PULLUP); pinMode(ENCODER_B_B, INPUT_PULLUP); // 附着中断,在ENCODER_A_A引脚变化(上升沿或下降沿)时触发中断函数 attachInterrupt(digitalPinToInterrupt(ENCODER_A_A), handleEncoderA, CHANGE); attachInterrupt(digitalPinToInterrupt(ENCODER_B_A), handleEncoderB, CHANGE); // ... 电机引脚初始化代码(同上) } long lastPrintTime = 0; long lastCountA = 0, lastCountB = 0; void loop() { // 每100毫秒计算一次速度(脉冲数/时间) if (millis() - lastPrintTime > 100) { long currentCountA = encoderCountA; long currentCountB = encoderCountB; long deltaA = currentCountA - lastCountA; long deltaB = currentCountB - lastCountB; // 计算转速(脉冲数/100ms),你可以将其转换为 RPM(转/分钟) float speedA = deltaA; // 这里只是脉冲数,需要根据PPR和倍频换算成实际转速 float speedB = deltaB; Serial.print("Speed A: "); Serial.print(speedA); Serial.print(" | Speed B: "); Serial.println(speedB); lastCountA = currentCountA; lastCountB = currentCountB; lastPrintTime = millis(); } // 这里可以加入PID控制逻辑,根据目标速度和当前速度(speedA/B)调整PWM输出 // setMotor(0, pidA.update(targetSpeed, speedA)); // setMotor(1, pidB.update(targetSpeed, speedB)); }这段代码实现了编码器脉冲的计数和速度的粗略计算。打开 Arduino IDE 的串口监视器,设置波特率为 115200,手动转动电机轮子,你应该能看到速度值的变化。
重要经验:中断服务函数
handleEncoder必须尽可能短小,只做最简单的计数操作。避免在中断中使用Serial.print()、delay()等耗时函数,否则会导致系统响应异常甚至崩溃。所有复杂计算(如 PID)和输出都应放在主循环loop()中。
5.3 差分底盘的运动学模型
要控制底盘走直线或旋转,需要理解差分底盘的运动学。两个轮子的速度差决定了底盘的角速度。假设两个轮子间距为L,轮子半径为r,左轮转速为v_left,右轮转速为v_right(单位:脉冲数/时间)。
- 线速度
V = (v_right + v_left) * r / 2 - 角速度
ω = (v_right - v_left) * r / L
我们的速度闭环控制目标,就是通过 PID 等算法,让两个轮子的实际转速v_left和v_right精确地跟踪我们根据上述模型计算出的目标转速。由于篇幅所限,完整的 PID 实现和调参将是一个独立的话题,但有了精确的编码器反馈数据,我们已经迈出了最关键的一步。
6. SPI 通信基础与为未来升级做准备
尽管本期我们使用的是中断读取增量编码器,但为了项目的可扩展性,以及应对更高精度的需求,预先配置好 SPI 接口是很有必要的。未来我们可以无缝地将编码器更换为 SPI 接口的绝对式磁编码器。
6.1 SPI 在 Arduino 中的使用
Arduino 提供了SPI库来简化 SPI 操作。Curie Nano 的 SPI 引脚是固定的:
- SS (Slave Select):引脚 10 (但可以作为普通数字 IO 使用)
- MOSI (Master Out Slave In):引脚 11
- MISO (Master In Slave Out):引脚 12
- SCK (Serial Clock):引脚 13
我们需要在代码中引入SPI.h头文件,并进行初始化。
#include <SPI.h> // 假设我们未来连接一个 SPI 编码器,其片选引脚接 Curie Nano 的引脚 1 #define ENCODER_SPI_CS 1 void setup() { Serial.begin(115200); SPI.begin(); // 初始化 SPI 总线 pinMode(ENCODER_SPI_CS, OUTPUT); digitalWrite(ENCODER_SPI_CS, HIGH); // 默认不选中从设备 // ... 其他初始化代码 } // 一个模拟读取 SPI 编码器的函数(以 AS5047P 为例) unsigned int readSPIEncoder() { unsigned int data = 0; digitalWrite(ENCODER_SPI_CS, LOW); // 选中编码器 delayMicroseconds(1); // 等待片选稳定 // SPI 传输:先发送一个读取命令(例如 0xFFFF),同时接收高字节 data = SPI.transfer16(0xFFFF); // transfer16 用于16位数据交换 digitalWrite(ENCODER_SPI_CS, HIGH); // 取消选中 // AS5047P 返回的14位数据在最高位,需要处理 data = (data & 0x3FFF); // 取低14位为角度值 return data; } void loop() { // 定期读取 SPI 编码器 unsigned int angle = readSPIEncoder(); Serial.print("SPI Encoder Angle: "); Serial.println(angle); delay(100); // 每100ms读取一次 }这段代码展示了 SPI 通信的基本流程:初始化总线、控制片选引脚、使用SPI.transfer()或SPI.transfer16()进行数据交换。不同的 SPI 设备(编码器)其通信协议(命令字、数据格式、时钟极性 CPHA 和相位 CPOL)可能不同,需要查阅具体的数据手册。在SPI.begin()后,有时还需要用SPI.setBitOrder(MSBFIRST);、SPI.setDataMode(SPI_MODE1);等函数来配置通信模式,这必须与从设备严格匹配。
6.2 中断与 SPI 的权衡
使用中断方式读取增量编码器,优点是响应实时性极高,几乎无延迟,适合高速计数。缺点是需要占用两个中断引脚,并且在高转速或代码复杂时,频繁中断可能影响主程序运行。
使用 SPI 读取绝对编码器,优点是不占用中断资源,通过轮询方式获取绝对位置,数据更可靠,抗噪声能力强,且能获得绝对角度信息。缺点是读取速度受 SPI 时钟频率和轮询间隔限制,存在一定的延迟。
对于智能垃圾桶底盘这种对实时性要求不是极端高的应用,SPI 轮询方式通常足够可靠,且为未来增加更多 SPI 设备(如 SPI 接口的显示屏、Flash 存储等)提供了便利。这也是我倾向于为未来升级预留 SPI 接口的原因。
7. 系统集成、调试与常见问题排查
将电机驱动、编码器计数、电源管理集成到一起,并让底盘稳定受控地运行,是本期最后也是最考验耐心的一步。
7.1 完整系统接线与供电检查
在将所有模块焊接或牢固连接之前,强烈建议先在面包板上搭建整个系统,并逐一测试每个功能。完整的接线示意图如下(文字描述):
- 电源部分:7.4V 电池正极接降压模块输入正,输出 5V 接 Curie Nano 的 VIN 和 TB6612 的 VCC(逻辑电源)。电池正极同时接 TB6612 的 VMOT(电机电源)。所有模块的 GND 连接到一起,最后接到电池负极。
- 控制部分:Curie Nano 的 PWM 引脚 (5,6) 接 TB6612 的 PWMA/B;数字 IO (7,8,9,10) 接 TB6612 的 AIN1, AIN2, BIN1, BIN2。
- 反馈部分:编码器 A 相接中断引脚 (2,3),B 相接普通数字输入引脚 (4,12)。编码器电源接 3.3V 或 5V(需确认编码器工作电压),地接共同 GND。
上电前,务必用万用表检查电源线路,确保无短路。先单独给 Curie Nano 上电,测试程序能否下载和运行。然后再连接电机驱动和电机。
7.2 典型问题与排查思路
问题一:电机不转或只振动。
- 排查:首先检查电机驱动模块的 VMOT 电压是否正常(接近电池电压)。然后检查 STBY(待机)引脚是否已接高电平(如果模块有的话)。用
analogWrite给一个较小的 PWM 值(如 100),同时用digitalWrite设置正确的方向引脚,用万用表测量驱动模块输出端是否有变化的电压。如果驱动模块输出正常,则检查电机本身和接线。
- 排查:首先检查电机驱动模块的 VMOT 电压是否正常(接近电池电压)。然后检查 STBY(待机)引脚是否已接高电平(如果模块有的话)。用
问题二:编码器计数不准或为0。
- 排查:首先确认编码器供电正常。在中断服务函数中,除了计数,可以尝试简单翻转一个 LED 的状态,观察在转动电机时 LED 是否闪烁,以确认中断是否被触发。如果触发但计数方向错误,调整中断服务函数中的方向判断逻辑(交换
a==b的判断结果)。如果完全不触发,检查接线是否正确,特别是中断引脚是否连接到了编码器的 A 相。另外,机械编码器有时需要上拉电阻,如果模块没有内置,需要在信号线和电源之间接一个 10kΩ 的上拉电阻。Curie Nano 的INPUT_PULLUP模式内部提供了上拉,通常可以省去外接电阻。
- 排查:首先确认编码器供电正常。在中断服务函数中,除了计数,可以尝试简单翻转一个 LED 的状态,观察在转动电机时 LED 是否闪烁,以确认中断是否被触发。如果触发但计数方向错误,调整中断服务函数中的方向判断逻辑(交换
问题三:底盘严重跑偏或无法走直线。
- 排查:这是开环控制的必然结果。首先,即使不给 PWM,用手推动底盘,检查两个轮子转动是否一样顺畅,机械结构是否对称。然后,在开环下,给两个电机相同的 PWM 值,用编码器计数功能查看两个轮子的实际转速是否一致。通常会发现差异。这就是我们需要闭环控制的原因。在引入 PID 之前,可以尝试手动微调两个电机的 PWM 补偿值,形成一个简单的“校准表”。
问题四:系统运行时 Curie Nano 意外复位。
- 排查:最可能的原因是电源问题。电机启动瞬间电流很大,可能导致电压骤降,使得微控制器复位。确保电池电量充足,连接线足够粗。可以在 Curie Nano 的 VIN 和 GND 之间并联一个较大容量的电解电容(如 100uF-470uF)作为储能缓冲。另外,检查代码中是否有数组越界、死循环或堆栈溢出等问题。
7.3 初步调试与功能验证
在确保各部分硬件工作正常后,可以编写一个综合测试程序:
- 让底盘以固定 PWM 值前进 5 秒,通过编码器数据计算实际走过的脉冲数,估算距离。
- 让底盘原地旋转 360 度(通过给两个电机相同大小、相反方向的 PWM),检查编码器计数是否对称,旋转是否准确。
- 尝试实现一个简单的 P 控制器(比例控制):设定一个目标速度(脉冲数/100ms),根据当前速度与目标的误差,调整 PWM 输出。
newPWM = oldPWM + Kp * (targetSpeed - currentSpeed)。从小Kp值开始试,观察速度是否能稳定在目标值附近。
这个过程可能会反复多次,需要耐心记录数据、调整参数。当你能通过串口指令让底盘以相对稳定的速度前进、后退、转弯时,本期“底盘篇”的核心目标就圆满达成了。我们得到了一个“听话”的、具备基本感知(速度反馈)能力的移动平台,这为下一期集成超声波/红外避障传感器、摄像头进行目标识别,乃至最终实现垃圾桶的智能功能,奠定了无比坚实的物理基础。