ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Arduino遥控车实战:用GPIO、PWM、UART与状态机搭建蓝牙小车

Arduino遥控车实战:用GPIO、PWM、UART与状态机搭建蓝牙小车 直接上一台几十块钱的Arduino Nano搭配两个直流减速电机、一块TB6612电机驱动模块、一个HC-05蓝牙模块和一块7.4V锂电池就能把GPIO、PWM、UART和状态机这四个嵌入式开发里最基础也最核心的知识点全部串起来。很多初学者会被这四个名词吓到觉得它们是四个独立的高深概念但实际上在一个遥控车项目里它们就是四个配合默契的零件——GPIO负责让主控“摸到”外设PWM负责“控制力度”UART负责“接收指令”状态机负责让程序“有条理地干活”。这篇文章就把整个项目的拆解过程、代码逻辑、踩坑记录全部写出来照着做一遍比刷十篇理论教程都管用。1. 项目整体思路与硬件选型1.1 为什么选遥控车作为入门载体电子行业里有个老说法点灯是Hello World小车是第一个完整系统。遥控车项目麻雀虽小五脏俱全它同时包含了输入遥控指令、运算状态解析与决策、输出电机与舵机动作这三个完整闭环而且每个环节都能直观看到效果。如果一个新手能独立把一台遥控车从零攒到跑起来那他对嵌入式开发的整体认知就不再是一堆零散的概念而是一条完整的链路。更重要的是遥控车项目能够自然引出嵌入式开发中最核心的几个主题GPIO管脚配置决定了你能否正确驱动外设PWM决定电机转速和舵机转向是否顺滑UART决定了主控能否理解手机或电脑发来的指令状态机则决定了整个程序在复杂操作下是否稳定不崩溃。这四个主题单独拿出来都能写好几篇文章但在小车项目里它们有机地组合在一起每个知识点都有一个“为什么要学它”的明确答案。1.2 硬件清单与选型理由以Arduino Uno/Nano为控制核心硬件清单如下主控板Arduino Uno或Nano价格便宜资料多GPIO、PWM、UART引脚齐全新手友好电机TT直流减速电机带轮子套装工作电压3-6V自带减速箱扭矩足够小车跑动电机驱动TB6612FNG模块比L298N体积小、压降低、效率高支持PWM调速供电两节18650锂电池串联7.4V或一块3.7V锂电池升压到5V根据电机和主控需求选择通信模块HC-05蓝牙串口模块通过UART与Arduino通信手机APP即可发送指令舵机可选SG90 9g舵机用于控制云台或超声波传感器方向辅助材料面包板、杜邦线、电源开关、铜柱、亚克力底盘或3D打印底盘这里重点说下TB6612的选择理由。很多人入门时喜欢用L298N但L298N的压降太大电源电压7.4V输入到电机端可能只剩6V不到而且模块本身发热严重。TB6612FNG的内阻只有0.5欧姆左右同样电压下电机能获得更高功率发热量小得多还能直接输入PWM信号调速整体效率高出不少。虽然价格贵几块钱但从实际使用体验来说非常值得。1.3 系统架构与数据流拆解整个系统的数据流可以画成一条清晰的主线手机APP蓝牙调试助手→ HC-05蓝牙模块 → Arduino主控UART接收数据→ 状态机解析指令 → GPIO控制方向引脚 PWM控制电机转速 → 电机/舵机执行动作这条链路里每个环节都对应一个核心知识点。手机通过蓝牙把指令包比如F100表示前进、速度100发出去HC-05收到后通过UART把数据传给ArduinoArduino的串口接收中断或轮询把数据存入缓冲区状态机根据当前状态和收到的指令决定下一步动作最终通过GPIO输出高低电平控制电机的正反转方向通过PWM输出不同占空比的方波控制电机的转速。整个过程环环相扣任何一个环节出问题小车都跑不起来。2. GPIO让主控“摸到”外部世界2.1 GPIO的八种工作模式与选型逻辑GPIOGeneral Purpose Input Output通用输入输出是单片机最基础的引脚功能。很多初学者以为GPIO就是简单的“输出高电平/低电平”但实际上GPIO有更丰富的模式。在STM32中GPIO有八种工作模式包括四种输入模式浮空输入、上拉输入、下拉输入、模拟输入和四种输出模式开漏输出、开漏复用功能、推挽输出、推挽复用功能。Arduino虽然没有暴露这么细的模式配置接口但底层原理是相通的理解这八种模式对后续深入非常有帮助。在遥控车项目里GPIO的使用场景分为两类输入和输出。输入场景典型的是按键检测。比如小车上有两个按键一个用于切换“手动模式/自动模式”一个用于急停。如果按键一端接地、另一端接GPIO那GPIO就需要配置为输入上拉模式这样按键未按下时引脚读到高电平按下时读到低电平。如果配置成输入浮空模式引脚悬空时电平会随机跳动状态机就可能收到错误的触发信号。输出场景是驱动外部设备。比如TB6612的AIN1、AIN2、BIN1、BIN2这四个方向控制引脚需要配置为推挽输出模式——高电平时能输出足够大的电流驱动模块内部逻辑低电平时能稳定拉低。LED指示灯也同样用推挽输出。2.2 TB6612方向控制引脚接线与代码细节TB6612模块控制一个电机需要三个引脚一个PWM引脚和两个方向引脚。以下是一组完整的接线方案AIN1 → Arduino D5AIN2 → Arduino D6PWMA → Arduino D9支持PWM输出BIN1 → Arduino D7BIN2 → Arduino D8PWMB → Arduino D10支持PWM输出STBY → Arduino D4待机控制必须拉高才能工作VM → 电池正极7.4VVCC → Arduino 5V输出GND → Arduino GND这里有个特别容易踩坑的点STBY引脚。TB6612模块上有一个STBYStandby待机引脚这个引脚必须接高电平驱动模块才会正常工作。如果不接或者直接悬空模块会一直处于待机状态无论你怎么给PWM信号电机都不会转。我见过太多新手把时间浪费在查方向上最后发现只是STBY没接。方向控制的逻辑也很简单以A通道为例AIN1HIGHAIN2LOW电机正转前进AIN1LOWAIN2HIGH电机反转后退AIN1LOWAIN2LOW电机刹车短路制动AIN1HIGHAIN2HIGH电机惰行停止自由转动代码里可以用一个函数封装这个逻辑void setMotorA(int dir, int speed) { if (dir 1) { digitalWrite(AIN1, HIGH); digitalWrite(AIN2, LOW); } else if (dir -1) { digitalWrite(AIN1, LOW); digitalWrite(AIN2, HIGH); } else { digitalWrite(AIN1, LOW); digitalWrite(AIN2, LOW); } analogWrite(PWMA, speed); }这段代码把“方向”和“速度”两个维度分开处理方向用GPIO数字输出速度用analogWrite底层就是PWM。这样逻辑清晰后续状态机调用时只需要告诉它“左轮前进50%速度”这样的语义化指令即可。2.3 GPIO防抖与按键检测的经验小车上的按键如果不做防抖处理会产生一个非常奇怪的现象按一下按键小车会切换两次模式或者急停后立刻又恢复运行。原因是机械按键在按下和释放的瞬间金属触点会发生多次微小回弹导致电平在几十毫秒内快速跳变。程序如果只检测到一次低电平就触发事件实际上可能被触发两三次。常用解决方案是软件延时消抖const int buttonPin 2; int lastButtonState HIGH; unsigned long lastDebounceTime 0; const unsigned long debounceDelay 50; void loop() { int reading digitalRead(buttonPin); if (reading ! lastButtonState) { lastDebounceTime millis(); } if ((millis() - lastDebounceTime) debounceDelay) { if (reading ! buttonState) { buttonState reading; if (buttonState LOW) { // 处理按键按下事件 } } } lastButtonState reading; }这个代码的逻辑是只有当电平变化后持续稳定超过50毫秒才认为是一次有效的按键事件。实际测试下来50ms的延时既能过滤掉触点回弹又不会让人感觉按键“迟钝”。如果按键还要接外部中断那可以在中断服务函数里只设置一个标志位然后在主循环里处理防抖和业务逻辑避免在中断里做耗时操作。3. PWM用占空比精确控制电机与舵机3.1 PWM调速的核心原理PWMPulse Width Modulation脉冲宽度调制本质上是一种数字信号模拟模拟输出的技术。它通过在一个固定频率周期内改变高电平所占的时间比例即占空比来改变输出的等效电压。占空比0%时输出电压为0占空比100%时输出电压等于电源电压占空比50%时等效输出电压约为电源电压的一半。直流电机的转速与两端电压大致成正比因此通过调节PWM占空比就能调节电机转速。但这里有个容易被忽视的物理细节电机不是纯电阻负载它带有电感和反电动势。PWM频率太低时电流会随着占空比的变化产生明显波动导致电机转速抖动、噪音增大频率太高时电机驱动芯片的开关损耗会增加甚至驱动能力下降。实际经验是直流减速电机的PWM频率选在100Hz到1kHz之间比较合适。Arduino的analogWrite默认频率约为490HzD3、D9、D10、D11引脚或980HzD5、D6引脚实测在这个频率范围内电机运行平稳、噪音可以接受。如果你用的是ESP32或其他主控可以通过配置寄存器实现更高精度的PWM频率调整。3.2 电机调速代码与加速曲线前面TB6612的驱动代码里已经使用analogWrite(PWMA, speed)来设置速度这个speed是一个0到255之间的整数。取值0时占空比0%电机不转取值255时占空比100%电机全速取值128时占空比约50%电机半速。但在实际控制小车时不建议直接把目标速度一步到位地赋给电机。因为如果从0突然跳到255电机会猛加速车身会有一个明显的“点头”动作而且齿轮组也会承受很大的冲击力。更平滑的做法是使用梯形加速曲线void setMotorSpeedWithRamp(int motorPin, int targetSpeed, int stepDelay) { static int currentSpeed 0; if (targetSpeed currentSpeed) { currentSpeed; if (currentSpeed targetSpeed) currentSpeed targetSpeed; } else if (targetSpeed currentSpeed) { currentSpeed--; if (currentSpeed targetSpeed) currentSpeed targetSpeed; } analogWrite(motorPin, currentSpeed); }在主循环里每隔20到30毫秒调用一次这个函数速度和目标值之间的差值会被一步步抹平电机加速过程会变得非常自然车子的稳定性也会好很多。这个“ramp”斜坡逻辑在真实的工业伺服系统里也被广泛使用可以说是从小车走向工程实践的一个小缩影。3.3 舵机PWM控制50Hz频率的约定如果小车上装了SG90舵机用于控制摄像头云台或超声波传感器方向PWM的使用又会多一个层次。舵机对PWM信号有严格的要求周期固定为20ms即频率50Hz高电平脉宽在0.5ms到2.5ms之间对应舵机输出轴从0度转到180度。Arduino中要精确生成50Hz、高电平脉宽可调的PWM信号直接用analogWrite默认490Hz是行不通的更靠谱的方案是使用Servo库。Servo库底层使用定时器来精确控制脉冲宽度代码非常简单#include Servo.h Servo myservo; int pos 0; void setup() { myservo.attach(9); // 舵机信号线接D9 } void loop() { for (pos 0; pos 180; pos) { myservo.write(pos); delay(15); } }这里有几个注意点。第一Servo库使用Timer1在Uno上来产生PWM信号因此D9和D10引脚的analogWrite会受影响。如果你同时用D9驱动舵机、D10驱动电机就会发现电机调速失灵。解决办法是把舵机信号接到D9电机PWM避开D9和D10改用D5、D6、D3等引脚。第二舵机需要独立的5V供电如果直接接Arduino的5V引脚舵机堵转时电流可达几百毫安甚至更大容易把板载稳压芯片烧掉。用外部5V电源给舵机供电地线跟Arduino共地即可。3.4 PWM故障排查电机不转或转速异常电机不转是最常见的问题按照以下顺序排查能最快定位检查STBY引脚是否接高电平检查PWM引脚是否正确用analogWrite输出255再用万用表测引脚对地电压接近5V说明PWM输出正常检查TB6612的VM是否接到电池正极、GND是否与Arduino共地如果不共地PWM信号和电机驱动电路之间没有共同参考点必然无法正常工作检查电机本身是否完好直接给电机接电池正负极看是否转动检查电池电压是否足够TB6612模块的VM最低工作电压约为2.5V但电机堵转时电流很大电池电压会被拉低供电不足时电机可能一抖一抖地转不动我实测遇到过一种隐蔽故障杜邦线接触不良。TB6612的PWMA引脚插在面包板上震动几次后接触电阻变大PWM信号幅度被衰减电机表现为转速忽高忽低。排查时用手轻轻拨动每根线同时观察电机反应这种方式往往能快速定位问题。4. UART打通手机与小车之间的指令通道4.1 UART通信协议与帧格式设计UARTUniversal Asynchronous Receiver/Transmitter通用异步收发器是最简单的串行通信协议只需要两根数据线TX发送和RX接收。没有时钟线所以通信双方必须预先约定好波特率每秒传输的比特数。Arduino Uno/Nano的硬件串口支持常见波特率9600、19200、38400、57600、115200等。蓝牙模块HC-05默认波特率常见为9600或38400需要在AT命令模式下设置与Arduino一致的波特率。这里有个很重要的概念AT命令模式。HC-05上电前按住模块上的按键再上电模块就进入AT模式此时可以通过串口发送AT命令配置名称、密码、波特率等参数。记住AT模式下模块的波特率通常是38400而正常通信模式下是你自己设置的那个值。在遥控车项目中直接在串口里发单个字符比如’F’表示前进、’B’表示后退是最简单的方案但在实际写代码时还是建议设计一个最小可行的帧格式。比如帧头0xAA数据长度1字节数据域指令类型1字节 速度值1字节校验和前面所有字节累加取低8位这样一个指令帧就是AA 02 01 64 670xAA 长度2 指令01前进 速度100 校验和0x67。设计帧格式的意义在于当指令越来越复杂比如加入灯控、蜂鸣器、云台角度等时程序不会变成一堆散乱的字符判断而是可以通过统一的数据解析函数来分派指令。这在状态机中是关键的基础数据层。4.2 Arduino端串口数据接收与解析Arduino端的串口接收有两种方式轮询接收和中断接收。在Uno中串口数据接收到后会自动存入硬件缓冲区默认64字节程序在loop里通过Serial.available()检查缓冲区中是否有数据然后逐个读取。这种方式对遥控车这种低速控制场景完全够用。完整的串口解析代码可以这样写byte rxBuffer[32]; int rxIndex 0; bool frameReady false; void parseSerial() { while (Serial.available() 0) { byte b Serial.read(); if (rxIndex 0 b ! 0xAA) { // 没收到帧头丢弃 continue; } rxBuffer[rxIndex] b; // 最少需要4个字节才能判断一帧是否完整帧头长度2字节数据 if (rxIndex 2) { frameLen rxBuffer[1] 3; // 长度字段是数据域长度加上帧头和长度字段自身 } if (rxIndex sizeof(rxBuffer)) { rxIndex 0; } if (rxIndex frameLen) { frameReady true; break; } } } void processCommand() { if (!frameReady) return; byte checksum 0; for (int i 0; i frameLen - 1; i) { checksum rxBuffer[i]; } if (checksum ! rxBuffer[frameLen - 1]) { // 校验失败丢弃 rxIndex 0; frameReady false; return; } byte cmd rxBuffer[2]; byte value rxBuffer[3]; if (cmd 0x01) { // 前进 setMotorA(1, map(value, 0, 255, 0, 255)); setMotorB(1, map(value, 0, 255, 0, 255)); } else if (cmd 0x02) { // 后退 setMotorA(-1, map(value, 0, 255, 0, 255)); setMotorB(-1, map(value, 0, 255, 0, 255)); } rxIndex 0; frameReady false; }这个解析过程的核心在于“按状态推进”先找帧头再收长度再收完整数据最后校验。这个思路本身就是一个小型状态机想想看是不是很妙——你在写串口协议的同时已经在不知不觉中应用了状态机思想。4.3 串口调试的实战技巧串口调试最常见的问题是数据乱码和收不到数据。乱码的原因基本可以锁定为波特率不匹配——发送端和接收端必须使用完全相同的波特率即使误差超过3%都可能产生乱码。在Arduino IDE的串口监视器里选择正确的波特率是最基本的操作。同时要注意Arduino的串口监视器默认识别的是字符如果用十六进制发送帧记得在设置里选择“不进行编码转换”或者使用十六进制显示模式。收不到数据的情况优先检查TX和RX接线是否交叉。Arduino的RX必须接HC-05的TXArduino的TX必须接HC-05的RX。很多人拿两根公对母杜邦线直接对接或者连错了针脚结果怎么调都不通。还有个容易被忽略的点Arduino Uno的硬件串口D0和D1同时也是USB转串口的通道。如果你在程序里用了Serial即硬件串口同时又把HC-05接在D0/D1上上传程序时可能会遇到“上传失败”或“板卡被占用”的问题。解决办法是上传程序时断开 HC-05 的接线上传成功后再接回。如果不用USB调试也可以在程序里用SoftwareSerial库在任意两个数字引脚上模拟串口比如D2做RX、D3做TX这样硬件串口留给USB调试软件串口跟蓝牙模块通信两边互不干扰是嵌入式开发里很实用的小技巧。5. 状态机让遥控车程序脱胎换骨的编程范式5.1 为什么需要状态机从混乱到有序当遥控车的功能只有“前进”和“后退”时用一个while循环就能搞定。但当功能增加到手动模式、自动避障模式、循线模式、急停、低速巡航、高速冲刺、指示灯状态切换……如果所有逻辑都堆在loop()里用if-else嵌套代码很快就会变成一团乱麻。状态机的核心价值在于把系统中的“状态”显式建模把“状态之间的迁移条件”清晰定义。程序在任何时刻都处于且仅处于一个状态当外部事件满足某个状态的迁移条件时才转移到下一个状态。这个思想跟现实世界是高度一致的小车不会同时处于“前进”和“后退”两个矛盾状态它一定是在某一时刻处于某个确定状态然后根据条件切换。5.2 一段式、二段式与三段式状态机的区别在FPGA/Verilog开发里状态机的写法被分为一段式、两段式、三段式。这个分类思路对嵌入式C语言编程同样有借鉴意义。一段式所有逻辑状态转移输出都写在一个代码块里。简单直观但代码耦合度高状态多时难以维护。二段式把状态转移和输出分成两个部分。第一部分只负责根据当前状态和输入条件计算下一个状态第二部分根据当前状态执行对应的输出动作。这种写法让“状态切换”和“行为执行”分离是嵌入式C语言中最推荐的做法。三段式在二段式基础上再进一步把状态转移逻辑和状态寄存器更新分开。当前状态、次态计算、输出逻辑三者各自独立代码更清晰调试更方便。对于Arduino遥控车项目推荐使用二段式状态机一个函数负责执行当前状态对应的动作动作函数一个函数负责判断是否需要切换状态转移函数。结构如下typedef enum { STATE_IDLE, STATE_FORWARD, STATE_BACKWARD, STATE_LEFT, STATE_RIGHT, STATE_STOP } CarState; CarState currentState STATE_IDLE; // 状态转移逻辑 CarState getNextState(CarState current, int cmd) { switch (current) { case STATE_IDLE: if (cmd CMD_FORWARD) return STATE_FORWARD; if (cmd CMD_BACKWARD) return STATE_BACKWARD; break; case STATE_FORWARD: if (cmd CMD_STOP) return STATE_STOP; if (cmd CMD_LEFT) return STATE_LEFT; break; // 其他状态类似 } return current; } // 状态执行逻辑 void executeState(CarState state) { switch (state) { case STATE_IDLE: setMotorSpeedWithRamp(PWMA, 0, 20); setMotorSpeedWithRamp(PWMB, 0, 20); break; case STATE_FORWARD: setMotorA(1, currentSpeed); setMotorB(1, currentSpeed); break; // 其他状态执行动作 } }主循环里只需要两行核心调用void loop() { currentState getNextState(currentState, lastCommand); executeState(currentState); }5.3 状态机的实际应用手动/自动双模式切换一个体现状态机威力的场景是“手动模式”和“自动模式”切换。手动模式下小车根据手机指令运动自动模式下小车根据超声波传感器检测到的距离自动避障。如果不用状态机你可能会写出一个巨大的if-else块里面还要管理“当前是哪个模式”“模式切换时电机该不该停”“自动模式下收到手机指令该怎么响应”等各种问题逻辑很容易出错。用了状态机之后一切变得清晰状态定义MODE_MANUAL、MODE_AUTO、MODE_EMERGENCY_STOP状态迁移条件按下模式键 → 在MANUAL和AUTO之间切换手动模式下收到急停指令 → 进入EMERGENCY_STOPAUTO模式下超声波距离小于10cm → 进入EMERGENCY_STOP自动停车在EMERGENCY_STOP状态下收到恢复指令 → 返回之前的模式在AUTO模式下还有一个内置的子状态机直行、左转避障、右转避障、倒车。子状态机让自动避障的流程变得更加可控typedef enum { AUTO_FORWARD, AUTO_LEFT, AUTO_RIGHT, AUTO_REVERSE } AutoState; AutoState autoState AUTO_FORWARD; void updateAutoState() { float dist readUltrasonicDistance(); switch (autoState) { case AUTO_FORWARD: if (dist 15) { autoState AUTO_REVERSE; } else if (dist 30) { autoState distRandom() ? AUTO_LEFT : AUTO_RIGHT; } break; case AUTO_REVERSE: if (dist 20) autoState AUTO_LEFT; break; // ... } }这种分层状态机的设计思路在大型嵌入式系统里同样适用系统级状态机管控整体运行模式子状态机管控具体功能流程两者互不干扰。6. 常见问题与排查技巧实录6.1 问题速查表把实际调试中踩过的坑整理成一个速查表供快速定位问题现象可能原因排查方法蓝牙连不上手机HC-05未进入配对模式/密码错误按住模块按键上电进入AT模式设置密码和名称手机能连蓝牙但小车无反应串口波特率不匹配确认HC-05与Arduino程序里的Serial.begin波特率一致电机不转TB6612 STBY引脚未接高电平检查STBY是否接5V或数字输出HIGH电机转速抖动电池电压不足测量VM引脚电压低于6V时及时充电或更换电池舵机乱转与电机PWM引脚冲突换用不冲突的引脚舵机单独供电串口输出乱码波特率不一致统一两端波特率检查TX/RX是否错接程序上传失败HC-05占用D0/D1上传时拔掉HC-05接线小车加速冲击大无加速曲线添加梯形加速/减速逻辑6.2 从定位问题到修复的三步法如果小车出了问题建议按照“观察现象 → 定位范围 → 单点测试”的顺序排查。先别急着改代码仔细确认现象是什么是电机完全不动还是动了但不对是只有左轮不对还是两个轮子都不对是用手机控制不行还是直接用串口发指令也不行这个确认过程往往就能缩小一半的排查范围。比如遇到“手机发前进指令小车右转”的现象先判断是软件逻辑问题还是硬件接线问题。把手机指令换成串口直接发送同一帧数据如果现象相同说明不是蓝牙通信的问题。接着把两个电机的PWM信号互换软件里交换PWMA和PWMB的赋值如果“右转”变成了“左转”说明电机的接线方向反了把对应的方向引脚对调即可。这种二分法定位问题的方式在工程调试中非常高效。6.3 电源安全与稳定性电源问题在遥控车项目里是隐藏的重灾区。TT直流减速电机堵转电流可能达到1A以上当电流从电池流向电机时会引起整个供电网络电压跌落。如果Arduino和传感器也挂在同一块电池上电压跌落轻则造成传感器读数漂移重则导致主控重启——而主控一重启GPIO引脚全部回到默认状态电机驱动瞬间失效小车就会突然失控。推荐的供电方案是电池正极先给TB6612的VM供电同时通过一个AMS1117-5V或DC-DC降压模块给Arduino的5V供电舵机使用独立5V电源并共地。在电源输入端并联一个470uF电解电容和一个100nF陶瓷电容可以有效吸收电机带来的电源噪声。这个小细节能让整个系统稳定性上一个台阶。7. 项目扩展方向与个人体会做完遥控车基础功能之后可以尝试几个性价比极高的扩展方向。第一个是加装超声波避障用HC-SR04模块通过GPIO的输入捕获功能测量距离在自动模式下实现避障。第二个是加装红外循线模块两个红外传感器贴在底盘下方通过检测黑白线的反射差异来控制方向这里会用到更多的GPIO输入处理和状态判断。第三个是把ESP32替换掉Arduino UNO作为主控利用ESP32自带的WiFi/蓝牙功能实现网页端控制同时还能用ADC读取电池电压并通过MQTT上报。我个人在实际操作中最深的体会是很多知识点在单独学习时觉得简单但放到一起就会出现各种奇奇怪怪的联动问题。比如PWM和舵机抢定时器的问题、串口接收缓冲区被占满的问题、电源电压不稳定导致重启的问题——这些问题没有一个能在书本里学到只能在真正动手做项目时踩一遍才能理解。Arduino遥控车项目提供了最经济、最直观的试错环境如果能把这里面的坑都趟平再去学STM32、ESP32甚至Linux驱动开发都会顺畅得多。最后再分享一个小技巧项目做到后期不要怕改代码要善用Git做版本管理。每次改动前先提交一个版本改动之后如果跑不起来可以快速回退对比定位问题会快很多。不要觉得Arduino项目小就不值得用Git我用下来最大的感受是有了版本管理你才敢大胆做实验而敢做实验才是进步最快的学习方式。
返回列表