☰
嵌入式入门第一周:为什么我选Arduino而不是STM32
2026/10/4 14:51:57 网站建设 项目流程

1. 从零上手嵌入式:为什么第一周我选了Arduino而不是STM32

很多人一提到嵌入式入门,第一反应就是买块STM32开发板,装Keil,啃寄存器手册。我身边不少朋友就是这么干的,结果第一周卡在环境配置上,第二周卡在时钟树配置上,第三周直接放弃。这不是能力问题,是路径选择的问题。

我这次给自己定的目标是:第一周必须把“代码能跑起来”这件事跑通,建立正反馈,再谈深入。所以选了Arduino Uno作为起点。原因很直接——它把工具链、烧录、基础外设驱动全部封装好了,你写digitalWrite(13, HIGH)就能点亮板载LED,不需要查任何寄存器手册。对于刚接触嵌入式的人来说,先建立“输入代码→看到现象”的闭环,比什么都重要。

这一周的时间跨度是2026年9月21日到9月27日,实际投入大概每天两到三小时。最终完成的内容包括:开发环境搭建、LED闪灯、按钮控制LED、串口通信与串口监视器调试、以及一个按钮+串口+LED的综合小项目。下面按实际学习顺序展开,每一步都附上我踩过的坑和验证过的操作细节。

注意:Arduino不是终点,它是跳板。它的抽象层帮你跳过大量底层细节,但到了第二、第三周,你迟早要面对寄存器、中断、DMA这些东西。第一周的任务是“跑通”,不是“学完”。

2. 开发环境搭建:Arduino IDE安装与板子识别的那些坑

2.1 IDE下载与安装:别从第三方站点拿安装包

Arduino IDE目前有两个大版本:经典版(1.8.x)和新版(2.x)。我建议新手直接用2.x版本,界面更现代,串口监视器和绘图器集成得更好。下载渠道只有一个——官网。第三方站点经常夹带旧版本或者捆绑软件,我有个朋友从某下载站拿了个“汉化版”,结果串口驱动死活装不上,折腾了一晚上。

安装过程本身没什么好说的,一路下一步就行。但有一个细节:Windows系统在安装过程中会弹出驱动安装提示,一定要点“允许”或“安装”。这个驱动是Arduino Uno板载USB转串口芯片的驱动,不装的话后面电脑识别不到板子。

2.2 板子连接后识别不到?先排查这三件事

插上USB线之后,打开IDE,在“工具→端口”菜单里应该能看到一个类似COM3或COM4的选项。如果看不到,按以下顺序排查:

  1. 换一根USB线。这是最常见的问题。很多USB线是“只供电不传数据”的,尤其是从充电宝、小风扇之类设备上随手拿的那种。我第一晚就栽在这上面,换了三根线才找到一根能传数据的。
  2. 检查驱动。在Windows设备管理器里看“端口”分类下有没有黄色感叹号。如果有,右键更新驱动,手动指向Arduino安装目录下的drivers文件夹。
  3. 换USB口。有些台式机前面的USB口供电不足或者接触不良,换到主板后面的口试试。

2.3 板子型号和端口的选择逻辑

在“工具→开发板”里选Arduino Uno。如果你用的是其他型号(比如Nano、ESP32),选对应的。这里有个容易忽略的点:端口号不是固定的。你今天插上去是COM3,明天换个USB口可能就变成COM5了。所以每次打开IDE准备烧录之前,先确认一下端口选对了没有。烧录失败十次里有三次是端口选错。

提示:如果你同时插了多块板子或者多个串口设备,端口菜单里会出现多个选项。不确定哪个是Arduino的话,拔掉板子看哪个选项消失了,再插上看哪个选项出现了,那个就是。

3. 第一个程序:LED闪灯背后的电路逻辑与代码结构

3.1 板载LED和外部LED的区别

Arduino Uno板上有一颗贴片LED,标注为“L”,连接在数字引脚13上。所以你写digitalWrite(13, HIGH),这颗灯就会亮。但如果你要外接LED,就需要额外的元件:LED灯珠、限流电阻、杜邦线。

限流电阻怎么选?这是新手最容易忽略的地方。LED的正向电压一般在1.8V到2.2V之间(红色),工作电流5mA到20mA。Arduino的数字引脚输出5V。如果直接接LED不加电阻,电流会远超20mA,轻则烧LED,重则烧引脚。计算方式很简单:

R = (5V - 2V) / 10mA = 300Ω

所以选一个220Ω到330Ω之间的电阻都行。我手头只有220Ω的,实测电流在13mA左右,LED亮度足够,也不会过热。

3.2 面包板接线:正极长脚,负极短脚

LED有两个引脚,长脚是正极(阳极),短脚是负极(阴极)。接线顺序是:Arduino数字引脚8 → 电阻一端 → 电阻另一端 → LED长脚 → LED短脚 → Arduino GND。面包板上的孔是横向连通的,中间有一条凹槽隔断,凹槽两边的孔不连通。这个结构决定了你的元件怎么跨接。

我第一次接线的时候把LED两个脚插在了同一列,结果短路了,灯不亮。后来才明白面包板的连通规则。建议新手拿到面包板之后,先用万用表的通断档测一下哪些孔是连通的,花五分钟搞清楚结构,后面接线会快很多。

3.3 代码结构:setup和loop的分工

Arduino程序的核心结构就两个函数:

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

setup()里的代码只执行一次,用来做初始化。loop()里的代码会无限循环执行。pinMode(8, OUTPUT)是把引脚8配置为输出模式。digitalWrite是设置引脚电平,HIGH是5V,LOW是0V。delay(1000)是延时1000毫秒,也就是1秒。

这里有个细节:delay()是阻塞式的。在延时期间,程序什么都做不了。如果你后面要做按钮检测或者串口接收,delay()会严重影响响应速度。第一周先用delay()建立概念,后面要换成millis()的非阻塞方式。

4. 按钮控制LED:上拉电阻、抖动与状态读取

4.1 按钮为什么需要上拉或下拉电阻

按钮的本质是一个开关。按下时两端导通,松开时两端断开。如果你把按钮一端接5V,另一端接数字引脚,按下时引脚读到5V(HIGH),松开时引脚悬空——悬空的引脚电平是不确定的,可能读到HIGH也可能读到LOW,这叫“浮空”。

解决方案有两种:外接下拉电阻(按钮接5V和引脚,引脚再通过10kΩ电阻接地)或者使用Arduino内部上拉电阻(pinMode(2, INPUT_PULLUP))。我推荐用内部上拉,省一个电阻,接线也简单。用了内部上拉之后,逻辑是反的:松开时读到HIGH,按下时读到LOW。

4.2 按钮抖动:为什么按一次会触发多次

机械按钮在按下和松开的瞬间,金属触点会有几毫秒的弹跳,导致电平快速变化多次。如果你在代码里直接判断if (digitalRead(2) == LOW),一次按下可能会被识别成好几次。

解决方法有两种:硬件消抖(在按钮两端并联一个0.1μF电容)和软件消抖(检测到电平变化后延时20毫秒再读一次)。第一周我用的软件消抖,代码简单,效果也够用:

void loop() { if (digitalRead(2) == LOW) { delay(20); if (digitalRead(2) == LOW) { digitalWrite(8, !digitalRead(8)); while (digitalRead(2) == LOW); } } }

最后那句while (digitalRead(2) == LOW);是等待按钮松开,防止按住不放时LED疯狂翻转。

4.3 状态翻转的写法与常见错误

digitalWrite(8, !digitalRead(8))这行代码的意思是:读取引脚8当前的电平,取反,再写回去。这样就实现了“按一次亮,再按一次灭”的效果。

常见错误是写成digitalWrite(8, !digitalRead(8))之前先digitalWrite(8, LOW),那就永远只亮不灭了。还有一个错误是忘记在setup里把按钮引脚设为INPUT_PULLUP,导致读数不稳定。

注意:按钮接内部上拉时,按钮的一端接引脚,另一端接GND,不要接5V。接错了会一直读到LOW或者烧引脚。

5. 串口通信:从Serial.print到串口监视器的完整链路

5.1 串口是嵌入式中最重要的调试手段

没有串口,你只能靠LED判断程序状态,信息量极其有限。有了串口,你可以打印变量值、打印状态信息、接收上位机指令。Arduino Uno的串口通过USB线连接到电脑,在IDE里打开“串口监视器”就能看到板子发来的数据。

初始化串口用Serial.begin(9600),参数是波特率。板子和监视器的波特率必须一致,否则看到的是乱码。9600是最常用的,也够稳定。如果你想传大量数据,可以调到115200。

5.2 Serial.print和Serial.println的区别

Serial.print打印完不换行,Serial.println打印完自动换行。调试信息一般用println,方便阅读。如果要打印多个变量拼成一行,可以这样:

Serial.print("Button state: "); Serial.println(digitalRead(2));

输出结果是Button state: 1然后换行。

5.3 串口监视器的几个实用设置

串口监视器右上角有个波特率下拉菜单,必须和代码里的Serial.begin一致。下面有个输入框,可以发送数据给板子。发送格式有“无结束符”“换行”“回车”“回车换行”等选项。如果你在代码里用Serial.readString()接收,建议选“换行”或“回车换行”,这样能明确知道一条指令什么时候结束。

我第一周用串口做了一个小功能:电脑发送“1”点亮LED,发送“0”熄灭LED。代码逻辑是:

void loop() { if (Serial.available() > 0) { char cmd = Serial.read(); if (cmd == '1') { digitalWrite(8, HIGH); Serial.println("LED ON"); } else if (cmd == '0') { digitalWrite(8, LOW); Serial.println("LED OFF"); } } }

这个功能虽然简单,但它是后面做“上位机控制下位机”的基础模型。

6. 综合练习:按钮+串口+LED三合一项目

6.1 项目需求拆解

把前面学的东西串起来:一个按钮控制LED亮灭,同时每次状态变化都通过串口打印出来。另外,电脑也可以通过串口发送指令直接控制LED。这样既有本地控制,又有远程控制,还有状态反馈。

需求拆解成三条:

  • 按钮按下时,LED状态翻转,串口打印当前状态。
  • 串口收到“1”时,LED点亮,打印“LED ON”。
  • 串口收到“0”时,LED熄灭,打印“LED OFF”。

6.2 完整代码与逐段解释

const int LED_PIN = 8; const int BUTTON_PIN = 2; bool ledState = false; void setup() { pinMode(LED_PIN, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); Serial.begin(9600); Serial.println("System ready"); } void loop() { // 按钮检测 if (digitalRead(BUTTON_PIN) == LOW) { delay(20); if (digitalRead(BUTTON_PIN) == LOW) { ledState = !ledState; digitalWrite(LED_PIN, ledState); Serial.print("Button pressed. LED: "); Serial.println(ledState ? "ON" : "OFF"); while (digitalRead(BUTTON_PIN) == LOW); } } // 串口指令检测 if (Serial.available() > 0) { char cmd = Serial.read(); if (cmd == '1') { ledState = true; digitalWrite(LED_PIN, HIGH); Serial.println("Command: LED ON"); } else if (cmd == '0') { ledState = false; digitalWrite(LED_PIN, LOW); Serial.println("Command: LED OFF"); } } }

用了一个bool ledState变量来记录当前LED状态,这样按钮和串口两条控制路径不会互相冲突。如果不用变量记录,按钮翻转之后串口再发指令,状态就乱了。

6.3 实测中遇到的三个问题

问题一:按钮按下后串口打印了多次。原因是while (digitalRead(BUTTON_PIN) == LOW);这行等待松开的代码在某些劣质按钮上会卡住,因为抖动导致电平在LOW和HIGH之间跳变。解决办法是在while循环里加一个超时,或者改用更完善的消抖逻辑。

问题二:串口发送“1”之后LED亮了,但按钮再按一次变成灭,串口打印的状态和实际不一致。这是因为按钮翻转用的是ledState = !ledState,而串口设置的是ledState = true,两者都更新了同一个变量,逻辑是对的。问题出在我一开始用digitalRead(LED_PIN)来读取LED状态,但输出引脚的digitalRead返回值不一定等于digitalWrite设置的值。改成用变量记录就解决了。

问题三:同时按按钮和发串口指令时,偶尔会丢指令。因为delay(20)和while等待松开期间,串口数据可能已经到达但没被读取。这是阻塞式代码的固有问题,后面学了中断和millis()之后可以解决。

7. 第一周踩过的坑与验证过的经验

7.1 硬件方面的坑

坑一:LED不亮,以为是代码问题,结果是LED插反了。长脚正极短脚负极,这个一定要记住。如果不确定,用万用表的二极管档测一下,能微亮的就是正向。

坑二:按钮接线接成了5V和引脚之间,用了内部上拉之后一直读到HIGH。内部上拉是把引脚通过电阻拉到5V,按钮应该接在引脚和GND之间,按下时把引脚拉到GND,读到LOW。

坑三:面包板电源轨没连通。有些面包板的电源轨是分段式的,中间断开。如果你从一端供电,另一端的元件可能没电。用万用表测一下通断,或者直接用跳线把两端连起来。

7.2 软件方面的坑

坑一:串口监视器波特率选错,看到一堆乱码。代码里是9600,监视器里选了115200,必乱。每次打开监视器先确认波特率。

坑二:烧录时忘记选端口,IDE报错“avrdude: ser_open(): can't open device”。这个错误九成是端口没选对,或者板子没插好。

坑三:delay()用太多导致按钮响应迟钝。如果loop里有delay(1000),按钮按下去要等一秒才被检测到。第一周先用delay建立概念,但要知道它的局限性。

7.3 学习节奏方面的体会

第一周不要贪多。我见过有人第一天就想着驱动OLED屏幕、第二天就想连WiFi模块,结果每个都只跑了个Demo,什么都没深入。把LED、按钮、串口这三个东西吃透,比囫囵吞枣跑十个例程有价值得多。

具体来说,“吃透”的标准是:你能不看教程,自己从零接线、写代码、调试通过。遇到问题能自己排查,而不是第一时间去群里问。这一周我大概烧录了三十多次,每次失败都记下来原因,后面翻看的时候发现大部分问题都是重复的。

8. 下周计划:从Arduino抽象层走向寄存器操作

第一周用Arduino把基本概念跑通了,但心里要清楚:digitalWrite背后是一堆寄存器操作,Serial.begin背后是USART配置。这些封装让你快速上手,但也屏蔽了底层细节。

下周我打算做两件事:一是用Wokwi仿真平台把同样的电路在浏览器里复现一遍,验证自己对电路的理解是否准确;二是开始看ATmega328P的数据手册,搞清楚pinMode和digitalWrite到底操作了哪些寄存器。从Arduino到寄存器的过渡是嵌入式学习的关键一步,这一步走稳了,后面上STM32或者ESP32都会顺很多。

如果你也在第一周,我的建议是:别急着换板子,别急着买一堆模块。把手头这块板子的每一个引脚、每一个基础函数都摸清楚,比什么都强。我第一周最大的收获不是学会了多少函数,而是建立了一套“遇到问题→查资料→动手验证→记录总结”的循环。这套循环后面会一直用下去。

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

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

立即咨询