☰
嵌入式学习第一周:Arduino从点灯到超声波避障小车实战
2026/11/6 3:02:55 网站建设 项目流程

9月21日到9月27日,我把嵌入式学习的第一周完整交给了Arduino。这一个礼拜的收获比我想象中大得多:从完全不懂硬件,到能独立把一套超声波避障小车跑起来,中间踩了不少坑,也想明白了很多入门阶段容易卡住的问题。如果你正准备入坑嵌入式,或者刚拿到一块Arduino UNO不知道从哪下手,这篇周记应该能给你一份比较实在的路线参考。我尽量按我实际操作的顺序来写,不绕弯子,每一步都说明为什么这么做。

1. 学习目标与方案选型:为什么第一周选Arduino

1.1 嵌入式入门常见的三条路线

现在网上一搜“嵌入式学习路线”,基本就是三派:直接上STM32派、Linux+ARM派、还有Arduino/ESP32派。这三条路线都有人走通,但第一周的体感差别是巨大的。

直接上STM32的问题是,寄存器操作、时钟树、中断优先级这些东西对一个连LED正负极都分不清的新手来说,信息量太大。我见过太多人第一周就被各种CubeMX配置劝退,然后“从入门到放弃”。Linux+ARM那条路就更不用说,第一周连交叉编译工具链都未必能装明白。相比之下,Arduino把底层寄存器封装成了简单的函数,比如digitalWrite(13, HIGH)就是点亮13号引脚连接的LED,你不需要知道AVR芯片内部怎么翻转IO口,也能先把电路的逻辑跑通。所以我的选择很明确:嵌入式第一周,用Arduino UNO建立“硬件直觉”。

这不是说以后不学底层,恰恰相反,Arduino是通往底层的跳板。你用它理解了“引脚”“上拉下拉”“PWM”“串口”这些词,再去接触寄存器、看芯片手册,起码脑子里有一个具体的场景,而不是对着抽象的寄存器位定义发呆。第一周的核心目标我定为三条:

  • 能独立搭建Arduino开发环境,完成程序下载与串口调试
  • 掌握GPIO输入输出、模拟输入、PWM、串口通信这几个核心概念
  • 用一个综合小项目(智能小车)把传感器、执行器、控制逻辑串起来

1.2 一周学习计划的拆解

我给自己定的节奏是这样:前三天打基础,中间两天做功能模块实验,最后两天做综合项目。这里有个很重要的原则——不要第一天就想着做小车,先老老实实把每个零件单独“喂”一遍程序,否则车上任何一个模块出问题,你都不知道该查哪里。

参考计划如下:

时间段学习内容目标产出
第1-2天环境搭建、板卡认识、Blink点灯点亮板载LED,理解程序上传流程
第3天数字输入、按键控制、串口输出会用串口监视器观察数据
第4天模拟输入、光敏电阻/电位器、PWM呼吸灯理解analogRead和analogWrite的区别
第5天超声波传感器HC-SR04、舵机SG90能测距并在串口显示,能控制舵机角度
第6-7天超声波避障小车组装、调试小车能自主避障运行

说句实在话,这个计划看起来不难,但真正跑起来才发现,每一個环节都可能出幺蛾子:驱动跟IDE版本不匹配、串口号被占用、超声波测距数据乱跳、电机电源供电不足……这些坑我后面专门开了一章来写,因为它们是新手最消磨信心的地方。

2. 开发环境搭建与硬件准备

2.1 硬件清单与选型逻辑

如果你手里还没有任何硬件,第一周最低配的清单是这些:

  • Arduino UNO主控板一块(或者兼容板,兼容板性价比很高)
  • USB线一根(注意要带数据功能的,很多充电线无法传输数据)
  • 面包板、LED灯珠、220Ω电阻若干、按键、杜邦线
  • 电位器或者光敏电阻(理解模拟输入用)
  • HC-SR04超声波模块、SG90舵机
  • L298N电机驱动模块、直流电机、车轮、电池盒(做小车用)

为什么选UNO而不是ESP32或ESP8266?我承认ESP32性能强得多、还带WiFi,但UNO的5V逻辑电平对新手更友好,外设资源简单,出错面小。而且Arduino UNO的教程生态几乎是全网上最多的,遇到问题一搜就有答案。等第一周跑通UNO,后面玩ESP32、ESP8266其实是一通百通,因为Arduino框架的语法是一样的。

兼容板要注意一个问题:很多国产UNO用的是CH340串口芯片,而不是原版的ATmega16U2。这本身不是坏事,CH340驱动装好一样用,但MacOS和Windows的驱动安装方式不同,后面我会讲到。

2.2 Arduino IDE安装、驱动与开发板管理

IDE我直接装了最新的Arduino IDE 2.x版本,界面比1.8.x好看,编译上传速度也能接受。但这里有个插曲:我第一次启动IDE时,窗口打开后居然是空白的,等了很久才恢复。这个问题的根源是2.x版本首次启动会联网拉取一堆索引和平台信息,网络环境不好就卡住。

解决的办法有两个:

  1. 在IDE设置里关闭“检查更新”等联网选项,或直接使用离线安装包
  2. 需要ESP32等第三方开发板支持时,优先找阿里云镜像源配置,不要用默认的GitHub下载地址

配置第三方开发板地址的方法不难:文件→首选项→“附加开发板管理器网址”里粘贴镜像地址,然后在“开发板管理器”里搜索安装。千万别自己手动去下压缩包扔进硬件目录,版本依赖很容易搞乱。

驱动方面的血泪教训是:Windows下CH340驱动如果不识别,不要反复插拔USB,先打开设备管理器看端口(COM)和感叹号设备;macOS下如果提示“没有权限打开”,需要到系统设置→隐私与安全性里允许从App Store和被认可的开发者。我在这一环节花了大半天,后来发现就是一个驱动版本太老的问题,卸载重装新版驱动立刻解决。

板卡型号和串口端口的设置位置:工具→开发板→Arduino UNO,端口选择对应的COM口。上传程序后如果看到avrdude: stk500_recv(): programmer is not responding,大概率是端口选错了,或者板子没进入下载模式。老款UNO在下载时需要手动按一下复位键,时机是IDE提示“Connecting”的时候,这个技巧很多教程不会写。

2.3 没有硬件怎么办:wokwi仿真先跑通逻辑

如果你暂时没有买板子,或者快递还没到,完全可以先在线仿真。wokwi(wokwi.com)这个平台支持Arduino UNO、ESP32、ESP8266,而且自带面包板、LED、传感器、电机驱动等仿真件,在浏览器里就能搭电路、写代码、看串口输出。

我的建议是:

先用仿真把程序逻辑和接线逻辑跑一遍,再上手实物。这样可以显著减少实物调试的挫败感。

但仿真有个很大的局限:它不会真实模拟供电不足、传感器信号抖动、电机堵转这些问题。所以仿真通过之后,该买硬件还是得买。仿真平台适合“敢想”,实物适合“敢做”,两件事都要干。

3. 第一周核心学习内容拆解

3.1 从点亮一颗LED开始:理解GPIO输出

第一天的实验是大家都绕不开的Blink。代码只有几行,但背后值得琢磨的东西很多。

void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(1000); digitalWrite(LED_BUILTIN, LOW); delay(1000); }

这段代码里有两个基础概念:setup()是上电后执行一次的程序,用于初始化引脚模式;loop()是不断循环执行的程序。LED_BUILTIN是板载LED所在的13号引脚,digitalWrite把该引脚置为高电平,点亮LED。

如果你用一个外接LED插在面包板上,需要多做一个事:LED长脚接数字引脚,短脚串联一个220Ω电阻后再接GND。很多新手直接把LED两个脚插在面包板两端就完事,结果不是不亮就是烧灯珠。LED是有极性的,长脚是阳极,必须通过限流电阻接到正极,否则电流过大会损坏元件。220Ω电阻在5V供电下限制电流在20mA左右,这是常规LED的安全工作范围。

这个实验中我真正学到的东西不是“怎么点亮灯”,而是理解程序是在一块真实的芯片上跑——你写的每一行代码,最终都会变成引脚上实实在在的电平变化。这种从“程序语言”到“硬件动作”的跨越感,是有软件背景的人接触嵌入式时最容易忽略、也最需要建立的心理模型。

3.2 数字输入与超声波测距:让板子“感知”外界

点灯属于输出,那么传感器就属于输入。我用的第一个输入实验是按键控制LED:按键按下时读到的引脚电平变化,控制LED亮灭。

const int buttonPin = 2; const int ledPin = 13; int buttonState = 0; void setup() { pinMode(buttonPin, INPUT_PULLUP); pinMode(ledPin, OUTPUT); } void loop() { buttonState = digitalRead(buttonPin); if (buttonState == LOW) { digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } }

注意这里用了INPUT_PULLUP,这是新手最容易忽视的配置。按键一端接引脚、另一端接GND时,如果没有内部上拉电阻,引脚悬空状态下电平是不确定的,按键读取就会随机跳变。Arduino内部提供了上拉电阻,用INPUT_PULLUP就能省掉外部上拉电阻,而且逻辑是反的:按键按下时读到LOW,松开时读到HIGH。理解“为什么按下是高电平还是低电平”,比记住代码本身重要得多。

超声波传感器HC-SR04稍微复杂一点,它用TRIG引脚发送10微秒的高电平触发信号,然后等待ECHO引脚返回高电平时长,根据声速换算距离。它的代码是这样的:

const int trigPin = 9; const int echoPin = 10; void setup() { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); } void loop() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration = pulseIn(echoPin, HIGH); float distance = duration * 0.034 / 2; Serial.print("Distance: "); Serial.print(distance); Serial.println(" cm"); delay(500); }

这里的关键是pulseIn函数,它测量一个引脚高电平持续的时间,单位是微秒。声速大约是340m/s,换算成微秒每厘米就是0.034,除以2是因为超声波要走一个来回。我第一次把“除以2”忘记了,测出来的距离全部是实际值的两倍,这个错误后来成了我的口头禅:读代码之前先看单位换算。

串口监视器在这个实验里扮演了“眼睛”的作用。Serial.begin(9600)初始化串口通信,Serial.print把数据发到电脑端。如果串口监视器里出现乱码,第一嫌疑是波特率没选对——必须选和Serial.begin一致的数值。第二嫌疑是串口号选错了,连接了别的串口设备。

3.3 PWM与舵机控制:模拟量输出的真相

Arduino的analogWrite看起来像输出一个模拟电压,但真实原理是PWM(脉冲宽度调制)——快速开关引脚,调节高电平占空比来等效出不同的平均电压。UNO的PWM引脚是3、5、6、9、10、11,这一点一定要注意:不是所有数字引脚都支持analogWrite。

呼吸灯实验是理解PWM的经典案例:

int ledPin = 9; int brightness = 0; void setup() { pinMode(ledPin, OUTPUT); } void loop() { for (brightness = 0; brightness <= 255; brightness++) { analogWrite(ledPin, brightness); delay(10); } for (brightness = 255; brightness >= 0; brightness--) { analogWrite(ledPin, brightness); delay(10); } }

analogWrite的取值范围是0到255,也就是8位分辨率。这里的“占空比”可以这么理解:如果周期固定,高电平占的时间比例越大,LED的平均功率就越大,看起来越亮。很多新手以为函数名叫“analog”就是真的模拟输出,其实它只是快速开关数字引脚,原理层面并不是连续电压。

舵机SG90也是PWM的典型应用,但它需要的是20ms周期、1.5ms左右脉宽对应90度位置的信号。用Arduino自带的Servo库就不用关心这些细节:

#include <Servo.h> Servo myservo; int angle = 0; void setup() { myservo.attach(9); Serial.begin(9600); } void loop() { for (angle = 0; angle <= 180; angle++) { myservo.write(angle); delay(15); } for (angle = 180; angle >= 0; angle--) { myservo.write(angle); delay(15); } }

舵机的橙线接信号引脚、红线接5V、棕线接GND。如果舵机不动或者抖动,大概率是供电不足——舵机启动电流能到几百毫安,UNO板载的5V稳压有时候带不动,需要外加电源。我做实验时发现舵机只动了一下就停了,后来查资料才知道是电流不够,换了一个外接5V电源后恢复正常。这里有个通用原则:驱动电机类执行器,一定不要依赖开发板供电。

4. 综合小项目:从零搭一台超声波避障小车

4.1 项目设计:写给新手的“最小可行方案”

第一轮的传感器和执行器实验都通过后,我给自己定的综合项目是做一台超声波避障小车。这个项目的价值在于它把前面学的所有知识点组合到了一起:数字输出驱动电机、PWM控制速度、超声波传感器读数据、条件分支做决策。

我的方案尽量简化:两个直流电机负责驱动,一个超声波传感器装在车头正前方,用L298N电机驱动模块控制电机方向。当超声波检测到前方障碍物小于20cm时,小车后退并右转;否则一直前进。不做舵机云台,因为第一周把三线舵机、两路直流电机、超声波传感器、驱动模块、供电系统全部整合在一起,已经够一个新手消化了。

为什么要用L298N?因为它同时解决了两件事:隔离电机的大电流、提供正反转的逻辑控制。L298N模块的IN1~IN4控制两个电机的方向,ENA、ENB使能引脚可以用PWM调速,供电必须用电池盒(4节5号电池或者7.4V锂电池),同时把它的GND和Arduino的GND接在同一参考点,这是新手最容易漏掉的地方。

4.2 接线与代码实现

接线表如下:

L298N模块引脚Arduino UNO引脚
IN14
IN25
IN36
IN47
ENA9(PWM,速度控制)
ENB10(PWM,速度控制)
12V+/GND电池盒正极/GND(与Arduino共地)
5V输出可给Arduino供电(可选,我选择分别供电)

超声波模块的TRIG接2号引脚、ECHO接3号引脚,VCC接5V,GND接GND。注意电池盒供电给L298N的12V+/GND,Arduino仍然用USB供电,两块板之间必须用一根杜邦线把GND连起来,否则电平参考点不一致,通信信号会乱。

完整代码如下:

const int trigPin = 2; const int echoPin = 3; const int IN1 = 4; const int IN2 = 5; const int IN3 = 6; const int IN4 = 7; const int ENA = 9; const int ENB = 10; int speed = 150; void setup() { pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT); Serial.begin(9600); } float getDistance() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration = pulseIn(echoPin, HIGH); float distance = duration * 0.034 / 2; return distance; } void forward() { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, speed); analogWrite(ENB, speed); } void back() { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); analogWrite(ENA, speed); analogWrite(ENB, speed); } void turnRight() { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); analogWrite(ENA, speed); analogWrite(ENB, speed); } void stop() { digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); analogWrite(ENA, 0); analogWrite(ENB, 0); } void loop() { float distance = getDistance(); Serial.print("Distance: "); Serial.println(distance); if (distance < 20) { back(); delay(500); turnRight(); delay(400); } else { forward(); } delay(50); }

这里有几个关键点要说明。电机的正反转取决于IN1和IN2的电平组合:IN1高、IN2低时电机正转;反过来就反转。同侧的两个电机同时正转就是前进,同时反转就是后退。转弯的实现是两侧电机方向相反,比如左轮前进、右轮后退,车身向右转。如果你发现前进时小车是倒着跑的,只需要把IN1~IN4的接线互换,或者把代码里的HIGH和LOW反向即可。analogWrite(ENA, speed)中speed我设成了150,这个值要根据电池电压和电机特性现场调——太小电机不转,太大电流冲击会让小车起步太猛。

4.3 调试记录:从“跑不起来”到稳定运行

这个项目我折腾了整整两个下午,遇到的第一个问题是电机完全不动。排查思路很重要:我先用代码单独测试电机——把IN1、IN2、IN3、IN4都手动设置为前进状态,发现还是不动,说明问题不在Arduino代码,而在电机驱动或供电环节。用万用表量L298N的输出电压,发现OUT端口电压接近0V,再往前查,电池盒的电源线脱落了。重新焊牢电源线后,电机终于转动。

第二个问题是超声波数据跳变。小车在桌面上跑,串口监视器里显示距离在5cm到80cm之间乱跳。一开始怀疑是传感器坏了,后来发现是供电波动导致的——电机启动瞬间电流很大,电压跌落让超声波传感器工作不稳定。解决方案有两个:超声波传感器单独供电,或者在代码里给传感器加软件滤波(连续读三次取中间值)。我选了后者,因为改动最小。

float getStableDistance() { float d1 = getDistance(); delay(5); float d2 = getDistance(); delay(5); float d3 = getDistance(); if (d2 < d1 && d2 < d3) return d2; if (d1 < d2 && d1 < d3) return d1; return d3; }

这个办法能解决大部分瞬时干扰的问题。第三个问题更隐蔽:小车每次启动都先右转一下,原因是电机的两个轮子阻力不一样,同一个PWM占空比下,两侧转速不一致。我在代码里给左右电机分别定义了speedL和speedR两个变量,实测调整差值:

int speedL = 140; int speedR = 170;

左侧电机加了一点占空比,小车直线性能明显改善。这类调参技巧没法写进教程,只能靠实测。我的体会是:小车跑不直,先怀疑机械和供电,最后才是代码逻辑。

5. 常见问题与排查技巧实录

5.1 新手最容易踩的五个坑

这一周我把常见问题攒成了一个速查表,每一条都是我或者同群同学实际遇到过的。

现象原因解决办法
IDE打开是空白窗口2.x版本首次启动联网加载索引卡住等待或断网启动,配置镜像源
上传失败:stk500_recv错误端口选错或开发板未进入下载模式重新选择COM口,老版UNO按住复位键看时序
CH340驱动不识别驱动版本过老,或系统权限拦截卸载重装官网最新版,macOS检查安全设置
串口监视器乱码波特率不一致确保右下角波特率与Serial.begin值相同
LED不亮正负极接反、电阻短路、引脚没设置OUTPUT检查引脚顺序,万用表测电压

第5条听起来基础,但操作时真的很容易中招:LED插反了不亮你还会怀疑代码有问题;还有一次我忘了在setuo函数里pinMode,程序上传后灯就是不亮,后来才发现是漏了初始化。实际上digitalWrite在没有pinMode的引脚上也能输出电平,但行为不稳定,这个坑坑了不少人。

供电问题值得单独拎出来说。小车加舵机后,总电流会飙升,单靠Arduino USB供电根本扛不住。任何带电机、大功率LED、舵机的项目,我的建议是遵循“主板和控制板分开供电,最后共地”的原则。有的同学直接用L298N的5V输出给Arduino供电,结果L298N的稳压模块发热严重,小车跑几分钟就自动复位,这就是典型供电不足症状。

5.2 一套实用的排查方法论

排查的思维方式比记住具体故障重要得多。我这一周总结出一套“二分法”排查流程:

  1. 先看硬件还是软件问题:换一段能正常工作的示例代码(比如Blink)上传,如果正常,问题大概率在你的代码或接线;如果Blink都不行,就是板子、驱动、端口这些环境问题
  2. 再看单模块还是整体问题:把传感器、电机、舵机分开单独测,排除模块之间的干扰
  3. 最后看是逻辑还是参数问题:用串口监视器把关键变量的值打印出来,看数据是否符合预期

比如小车不走,就用Blink确认板子活着;然后单独测电机转不转;再测超声波读数;最后组合测试。一套流程走下来,绝大多数问题都能定位到具体环节。

这里想特别提一下wokwi在排查中的作用。我遇到超声波数据异常时,先在wokwi里跑了同样的代码,仿真结果完全正常,于是很确定是实物环境问题(供电),而不是程序逻辑问题。这个思路很实用:仿真环境是一个免费的“对照组”,当实物表现和仿真不一致时,问题往往出在硬件或环境层。

6. 一周心得与下一步方向

6.1 我对嵌入式的理解发生了哪些变化

开始之前,我理解的嵌入式就是“写代码控制硬件”,但一周实践下来,这个认知明显不够。真正的嵌入式开发,至少是三层能力的叠加:看得懂电路(知道为什么LED要串电阻、为什么电机要驱动模块)、写得对代码(处理好时序、初始化、状态判断)、调得了bug(会用万用表、示波器、串口助手定位故障点)。很多网上的争论,比如“应用层开发是不是嵌入式”,本质上是在讨论这套技能体系里你占比多少。第一周我的结论是:嵌入式工程师的护城河,恰恰是那些软件工程师不擅长的“硬件直觉”。

比如看原理图这件事。以前我觉得原理图是硬件工程师的活,但做小车时我发现,不看L298N模块的原理图,你根本不知道ENA引脚必须接PWM口才能调速。所以第二周我必须开始练“看图能力”。热词里经常提到的“嵌入式面试”,很多基础题翻来覆去也就是GPIO、PWM、UART、I2C这些,说明基础概念比花哨的项目更重要。

6.2 第二周的准备与学习路线规划

第二周我的计划是两条线并行:

  • 继续用Arduino跑一个小型环境监控项目,把DHT11温湿度传感器、OLED显示屏、蜂鸣器组合起来,进一步熟悉I2C通信,因为后面接触MPU6050这类传感器还会遇到
  • 同步开始看《嵌入式Linux应用开发》的入门章节,学习交叉编译环境的基本逻辑,偶尔也会在wokwi上玩一玩ESP32的在线仿真,为后面真正上手ESP32积累经验

技术方向上也给自己定了大框架:第一、二周Arduino建立硬件感知,第三、四周进入STM32标准库/寄存器层面,接触中断、定时器、DMA这些更底层的资源,第五周以后转向ARM-Linux方向,学嵌入式Linux的开发环境、设备树、驱动模型。这是个比较传统但稳定的路径,期间如果对哪块感兴趣就多深入一点,比如语音识别用ESP32就能做,和小车结合也算一个不错的方向。

第一周下来最大的体会是:嵌入式这东西,光看视频、记笔记是真的学不会。必须亲手接一次线、亲眼看到LED亮起、亲耳听到蜂鸣器响起来,那些概念才算真正长在你身上。如果你也想入门嵌入式,别等准备好了再开始,买一块UNO、点亮一颗LED,你的第一周就从那一刻开始了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询