硬件
动手学Arduino
从 Arduino Uno 开始接触嵌入式系统,记录第一次 Build、Upload、点亮 LED,以及我开始理解代码如何真正作用于现实世界的过程。

最近开始正式学习 Arduino。
虽然之前已经接触过 C++,但这是我第一次真正把代码和现实世界连接起来。
以前写程序,大多数结果都停留在屏幕里:变量发生变化、程序打印结果、算法完成计算。它们当然是真实发生的,但总还是隔着一层。
这一次不一样。
我点击 Upload,把程序写入 Arduino Uno,然后看着 LED 按照我写下的代码开始闪烁。
那一刻其实挺奇妙的。
原来一行代码,最后真的可以变成一个电信号。
为什么开始学 Arduino
我并不是单纯为了“学会 Arduino”才开始折腾它。
真正吸引我的,是这样一条链路:
传感器
↓
微控制器
↓
数据处理
↓
通信
↓
服务器
↓
AI
↓
反馈
我希望以后能够逐渐把这条链真正搭起来。
Arduino 只是其中非常靠前的一站。
之后我还想继续接触 ESP32、各种传感器、毫米波雷达、无线通信、边缘计算和服务器,再进一步把这些东西和自己的专业方向联系起来。
比如生物医学工程、脑机接口、神经反馈、可穿戴设备,以及真正的实时闭环系统。
所以对我来说,Arduino 更像是进入硬件世界的一扇门。
我的第一块开发板
目前使用的是 Arduino Uno。
它的核心是一颗:
ATmega328P
一些最基本的参数是:
主频:16 MHz
SRAM:2 KB
Flash:约 32 KB
第一次看到 2 KB SRAM 的时候,我其实有点惊讶。
现在的电脑动辄就是 16 GB、32 GB 内存,而这样一颗微控制器只有几 KB 的 RAM,却仍然可以完成大量控制任务。
这也让我第一次比较明显地意识到:
嵌入式系统和普通电脑上的软件开发,是两个非常不同的世界。
在电脑上,有时候几 MB 的浪费根本不会被注意。
而在微控制器上,每一点存储空间、每一个引脚、每一个时钟周期,都开始变得具体。
我的开发环境
我没有直接使用 Arduino IDE。
目前使用的是:
- Arduino Uno
- VS Code
- PlatformIO
- C++
一个最基本的 PlatformIO 工程,大概是这样的:
project/
├── include/
├── lib/
├── src/
│ └── main.cpp
└── platformio.ini
真正写程序的地方主要是:
src/main.cpp
而 platformio.ini 则用来告诉 PlatformIO:
- 使用什么硬件平台
- 使用哪块开发板
- 使用什么开发框架
例如:
[env:uno]
platform = atmelavr
board = uno
framework = arduino
以前看到这种配置文件,我大概只会复制粘贴。
现在开始慢慢明白:
platform = atmelavr
表示使用 AVR 平台;
board = uno
表示目标开发板是 Arduino Uno;
framework = arduino
则表示使用 Arduino Framework。
PlatformIO 会根据这些信息选择对应的工具链、框架和上传方式。
很多原本隐藏在一个按钮背后的事情,开始逐渐变得具体。
第一次理解 Build 和 Upload
刚开始使用 PlatformIO 的时候,我其实没有真正分清:
Build
和:
Upload
有什么区别。
因为从使用者的角度看,好像都是“让程序跑起来”。
但它们实际上是两个完全不同的阶段。
Build
Build 大致经历的是:
C / C++ 源代码
↓
编译
↓
链接
↓
微控制器能够执行的程序
比如我写:
digitalWrite(LED_BUILTIN, HIGH);
ATmega328P 并不能直接理解这行 C++。
C++ 是给人写的。
最终,它必须经过编译,被转化成处理器真正能够执行的机器指令。
所以 Build 解决的问题是:
怎样把人写的程序变成机器可以执行的程序?
Upload
Upload 则是下一步:
编译好的程序
↓
USB
↓
Arduino Uno
↓
写入 Flash
也就是说:
Build 是生成程序。
Upload 是把程序真正送进开发板。
以前它们只是两个按钮。
现在开始知道,这两个按钮背后其实代表着程序从“源代码”到“真正运行在芯片上”的两个不同阶段。
第一次点亮 LED
Arduino 世界里最经典的程序大概就是 Blink。
#include <Arduino.h>
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
运行以后,效果非常简单:
亮 1 秒
↓
灭 1 秒
↓
亮 1 秒
↓
灭 1 秒
↓
……
但真正让我感兴趣的并不是:
“LED 会闪了。”
而是另外一个问题:
为什么这一行代码,真的能够让 LED 亮起来?
比如:
digitalWrite(LED_BUILTIN, HIGH);
从表面上看,只是调用了一个函数。
但是继续往下面追,就会发现整个过程大致是:
C++ 代码
↓
Arduino Framework
↓
机器指令
↓
微控制器寄存器
↓
GPIO
↓
引脚电平发生变化
↓
LED 发光
也就是说:
我们写下的抽象程序,最终真的会改变芯片内部的状态;
芯片内部状态发生变化之后,又会改变某个引脚的电压;
然后电流经过 LED;
最后我们看到它发光。
这一刻,“软件”和“硬件”第一次非常直观地连在了一起。
setup() 和 loop()
Arduino 程序里最常见的两个函数是:
void setup()
和:
void loop()
setup() 在设备启动之后运行一次。
所以通常会在里面进行初始化,例如:
pinMode(LED_BUILTIN, OUTPUT);
告诉微控制器:
这个引脚接下来要作为输出使用。
而 loop() 会不断重复运行。
可以先非常粗略地把它理解成:
setup();
while (true) {
loop();
}
也就是说:
启动
↓
初始化一次
↓
不断执行 loop()
↓
不断执行 loop()
↓
不断执行 loop()
↓
……
这也是很多嵌入式程序最基本的运行方式。
从普通 LED 到 RGB LED
在普通 LED 之后,我开始尝试 RGB LED。
RGB 分别代表:
Red
Green
Blue
一个 RGB LED 可以理解成在一个封装里放进了三个不同颜色的发光部分。
只要分别控制:
R
G
B
三个通道的亮度,就可以混合出不同的颜色。
例如:
R = 255
G = 0
B = 0
就是红色。
而:
R = 255
G = 255
B = 0
则可以混合出黄色。
也就是从这里开始,我遇到了一个很有意思的问题:
数字引脚明明只有 HIGH 和 LOW,为什么 LED 的亮度还可以发生变化?
于是就遇到了 PWM。
第一次理解 PWM
PWM 的全称是:
Pulse Width Modulation
中文叫:
脉冲宽度调制
例如在 Arduino Uno 中写:
analogWrite(pin, 128);
我一开始很自然地以为:
这是不是让引脚输出了一个介于 0 V 和 5 V 中间的模拟电压?
后来才发现,并不是。
Arduino Uno 的 analogWrite() 本质上仍然是在快速切换:
HIGH
LOW
HIGH
LOW
HIGH
LOW
……
它并没有真正产生一个连续变化的模拟电压。
真正改变的是:
HIGH 和 LOW 各自持续多长时间。
Duty Cycle:占空比
比如一个周期里:
HIGH 占 50%
LOW 占 50%
那么占空比就是:
50%
如果:
HIGH 占 75%
LOW 占 25%
那么占空比就是:
75%
Arduino Uno 的 analogWrite() 一般使用:
0 ~ 255
表示不同的 PWM 强度。
可以粗略理解成:
255 → 接近 100% HIGH
192 → 接近 75% HIGH
128 → 接近 50% HIGH
64 → 接近 25% HIGH
0 → 一直 LOW
于是:
analogWrite(pin, 255);
LED 会非常亮。
而:
analogWrite(pin, 64);
LED 看起来就会暗很多。
可它明明还是在闪
有趣的地方就在这里。
假设 PWM 一直在:
开
关
开
关
开
关
……
那么 LED 本质上仍然是在不断闪烁。
只是这个过程非常快。
快到人眼无法分辨每一次亮和灭。
所以我们最终感觉到的不是:
闪 → 闪 → 闪 → 闪
而是:
亮度发生了变化
这让我第一次比较直观地理解到:
一个只有 0 和 1 的数字系统,也可以利用时间,在宏观上制造出类似连续变化的效果。
我觉得这是一个很漂亮的思想。
RGB 渐变
之后还可以让两个颜色同时变化。
例如:
for (int i = 0; i <= 255; i++) {
analogWrite(RED_PIN, i);
analogWrite(GREEN_PIN, 255 - i);
delay(10);
}
这里:
Red
会不断增加:
0 → 255
而:
Green
会不断减少:
255 → 0
也就是:
R = i
G = 255 - i
同时满足:
R + G = 255
这样颜色就会逐渐从一个状态过渡到另一个状态。
在普通程序里:
i
255 - i
可能只是两个数字。
但是到了 Arduino 上:
数学关系
↓
程序
↓
PWM
↓
电信号
↓
LED
↓
颜色变化
数字第一次变成了一个真正可以看到的物理过程。
digitalWrite() 和 analogWrite()
到这里,也开始能够区分:
digitalWrite()
和:
analogWrite()
了。
digitalWrite()
控制的是:
HIGH
或者:
LOW
也就是最标准的数字输出。
例如:
digitalWrite(LED_PIN, HIGH);
和:
digitalWrite(LED_PIN, LOW);
analogWrite()
虽然名字里有 analog,但在 Arduino Uno 上,它通常并不意味着真正的模拟电压输出。
而是:
PWM
通过改变占空比,产生不同的平均效果。
所以:
digitalWrite()
更像是:
开 / 关
而:
analogWrite()
则可以利用 PWM 表现出:
亮
稍亮
较暗
很暗
灭
这样的变化。
为什么我开始觉得 Arduino 很有意思
一开始,我其实会觉得 Arduino 好像“太简单了”。
很多操作只是:
pinMode()
digitalWrite()
analogWrite()
delay()
几行代码。
但真正开始学习之后,我反而觉得:
Arduino 很厉害的一点,就是它把一部分复杂度暂时隐藏了起来,却没有把真实的硬件隐藏掉。
表面上是:
digitalWrite()
但继续向下其实是:
C++
↓
Arduino Framework
↓
AVR
↓
寄存器
↓
GPIO
↓
电路
初学的时候,可以先站在最上面。
以后再一层一层向下挖。
最终可以继续学习:
寄存器
中断
定时器
ADC
串口
I²C
SPI
PWM
内存
实时系统
这样很多原本散落在计算机、电子、电路和程序设计里的知识,就会逐渐串到一起。
从代码到现实
到目前为止,我觉得学习 Arduino 最重要的收获,其实不是学会了几个函数。
而是开始看到一条以前不够清楚的链:
程序
↓
机器指令
↓
处理器
↓
寄存器
↓
GPIO
↓
电压 / 电流
↓
物理世界
以前学习编程的时候,“程序运行”有时候还是一个很抽象的概念。
而现在:
我改变一个变量;
变量改变 PWM;
PWM 改变引脚状态;
引脚改变 LED;
最后我真的能看见颜色发生变化。
这让我感觉很多以前彼此割裂的知识,开始慢慢连起来了。
接下来
现在还只是刚刚开始。
接下来准备继续学习:
- GPIO
- 数字输入与输出
- PWM
- ADC
- 串口通信
- 按键
- 各种传感器
- I²C
- SPI
之后逐渐从 Arduino Uno 走向 ESP32。
我目前想象中的路线大概是:
Arduino Uno
↓
ESP32
↓
传感器
↓
毫米波雷达
↓
Wi-Fi / BLE
↓
服务器
↓
边缘计算
↓
AI
再继续向前,我希望把这些东西和自己的专业方向连接起来:
生物医学工程
↓
生理信号
↓
脑机接口
↓
神经反馈
↓
实时闭环系统
最终,我真正想做的不是一盏会闪的 LED。
而是完整的:
感知
↓
计算
↓
理解
↓
决策
↓
反馈
闭环。
Arduino 并不是终点。
它只是我真正开始理解硬件、嵌入式和现实世界计算系统的第一站。
不过,如果以后真的做出了那些复杂得多的系统,再回过头来看——
这一切,大概就是从第一次让一盏 LED 按照自己的代码亮起来开始的。