Arduino 程序只有两个函数:setup() 通电后跑一次、loop() 之后一遍一遍永远跑下去。本节把 写作 → 验证 → 上传 → 观察 这条流水线走通——从这一节起,你写的东西真的会进入芯片。
8-1 讲了"Arduino 没有操作系统",本节就把它变成实实在在的两行代码形状:没有 main()、没有系统调用,只有 setup() 和 loop()。
8-2 装好了 IDE 与 CLI、也把串口权限打通了——本节是第一次真的用它们。
本节末尾会让板子"说话"(串口打印),这正是 8-4 与 8-5 的起点:8-5 就是让树莓派上的 Python 接管那个"串口监视器"的角色。
一、Arduino 程序长什么样
每一个 Arduino 程序(叫 sketch,直译"草图")都是这个样子:
void setup() {
// 通电后,这里只跑一次
}
void loop() {
// 这里跑完一遍,立刻从头再来,永远不停
}
就这两个函数。 没有第三个,也没有 main()。
你写 C++ 程序时,入口永远是 main()(C++ 课讲过)。Arduino 也有,只是被藏起来了,它大概是这样的:
int main() {
init(); // 初始化硬件
setup(); // 你写的
while (1) { // 死循环
loop(); // 你写的
}
}
看懂这个"隐藏的 main",你就理解了 8-1 那句话:Arduino 上,你的 loop() 就是这台机器唯一的秩序。
三个"新手第一次就困惑"的点
① 一个程序 = 一个文件夹,不是单个文件
树莓派上写 Python 是 hello.py 一个文件。Arduino 不一样:
Hello/
└── Hello.ino ← 代码写在这里
⚠️ 文件夹名必须和 .ino 文件同名(这里是 Hello/Hello.ino)。不同名的话,IDE 打不开它,会报"找不到主文件"。这不是你的错,是 Arduino 的规定。
② 不用写 #include <stdio.h> 这类头文件
Arduino 的核心函数(pinMode、digitalWrite、delay、Serial)自动可用,不用 include。
③ 语言是"简化版 C++"
语法是 C++,但:
- 常用操作被封装成了好念的函数(digitalWrite 比直接操作寄存器友好太多)
- 同样的代码换个板子可能就不一样(AVR/ESP32/RP2040 的底层不同)
二、IDE 2.x 界面导览(顺带对照老教程)
打开 IDE,你需要的其实只有四个地方:
| 位置 | 是什么 | 什么时候用 |
|---|---|---|
| 左侧竖排的第一个按钮(✓) | 验证(Verify):只编译,不上传 | 想快速看有没有语法错 |
| 左侧第二个按钮(→) | 上传(Upload):编译 + 写进芯片 | 正式把程序送进去 |
| 顶部第二个下拉框 | 选板子(Select Board) | 每次都要确认 |
| 顶部第三个下拉框 | 选端口(Select Port) | 每次都要确认 |
| 右上角放大镜图标 | 串口监视器(Serial Monitor) | 让板子"说话"的时候 |
网上大量中文教程用的是 IDE 1.8.x(2017 年的界面)。在那个版本里,这两件事藏在菜单栏:
| 老教程(1.8.x) | 新版(2.x) |
|---|---|
菜单 工具 → 开发板 → Arduino Uno |
顶部下拉 Select Board |
菜单 工具 → 端口 → /dev/ttyACM0 |
顶部下拉 Select Port |
菜单 工具 → 串口监视器 |
右侧放大镜图标 |
事情是一样的,只是位置变了。 找按钮的时候心里有这张对照表就不会慌。
顺带一句:IDE 2.x 可以在设置里把界面切成简体中文,不习惯英文的话可以去开。
三、四步流水线:写作 → 验证 → 上传 → 观察
这是本节最值得记住的一条纪律。 每次改代码都按这四步走:
第 1 步 · 选板子
顶部 Select Board 下拉 → 选 Arduino Uno。
⚠️ 选错板子的后果:编译能过,但上传之后行为莫名其妙(因为引脚定义、时钟频率都不一样)。
第 2 步 · 选端口
顶部 Select Port 下拉 → 选 /dev/ttyACM0(CH340 兼容板可能是 /dev/ttyUSB0)。
下拉里是空的怎么办 → 回到 8-2 的验收 3:板子没插好、或者 USB 线是充电线。
第 3 步 · 写代码
第 4 步 · 验证 与 上传(这一步的区分很重要)
| 按钮 | 做什么 | 什么时候用 |
|---|---|---|
| 验证 ✓ | 只编译,检查语法,不碰板子 | 只想确认"我写得对不对" |
| 上传 → | 先编译,再把结果写进芯片 | 想让板子真的跑起来 |
在 4-5 学 apt 时,我强调过:apt update 只刷新清单、apt upgrade 才真的装东西。
这里是同一个道理:验证只"检查",上传才"生效"。
所以:改完代码必须重新上传。 芯片里跑的永远是你最后一次上传的那个版本——改完不传就试,是白试(新手最常犯的困惑:"我明明改了啊")。
四、第一个程序:Blink(让板上的灯眨起来)
这个例子 Arduino 自带,在 文件 → 示例 → 01.Basics → Blink。代码是:
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // 把板载 LED 那个脚设成"输出"
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // 通电(高电平)→ 灯亮
delay(1000); // 等 1000 毫秒 = 1 秒
digitalWrite(LED_BUILTIN, LOW); // 断电(低电平)→ 灯灭
delay(1000); // 再等 1 秒
}
逐句读一遍:
| 代码 | 意思 |
|---|---|
pinMode(引脚, OUTPUT) |
声明这个脚的身份:是输出(我能决定它是高还是低)。必须放在 setup() 里先声明一次 |
digitalWrite(引脚, HIGH) |
让它变高(5V)→ 灯亮 |
digitalWrite(引脚, LOW) |
让它变低(0V)→ 灯灭 |
delay(1000) |
等 1000 毫秒(⚠️ 单位是毫秒,不是秒) |
LED_BUILTIN |
一个常量,在 Uno 上等于 13 |
因为出厂程序本来就是 Blink(8-1 你看到的那颗一秒一闪的灯就是它)。
你会以为"是不是没上传成功"——不是。
验证的办法:把两个 delay(1000) 都改成 delay(200)。上传后灯明显变快,就证明你写的代码真的进去了。
Demo 第一步:让灯快起来
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(200); // ← 只改这两个数
digitalWrite(LED_BUILTIN, LOW);
delay(200);
}
看到什么算成功:L 灯由"一秒一闪"变成"一秒闪两次到三次"。
读结果:delay(200) + delay(200) = 一轮 400 毫秒,一秒钟正好跑 2.5 轮(亮 2 次或 3 次,取决于你什么时候开始看)。这个"数秒钟闪几下"的习惯,以后调任何 Arduino 程序都用得上——它是最省事的"进度指示"。
五、让板子说话:串口监视器
灯只能告诉你"两种状态"。想让板子报数字,就得用串口。
int count = 0; // 计数器
void setup() {
Serial.begin(9600); // 打开串口,波特率 9600
}
void loop() {
count = count + 1;
Serial.print("count = "); // 打印不带换行
Serial.println(count); // 打印并换行
delay(1000);
}
两个要点:
Serial.begin(9600)必须在setup()里,只写一次Serial.print不换行、Serial.println换行(多了ln两个字母)——想让每次输出独占一行就用后者
然后点右上角的放大镜图标打开串口监视器(老教程是 工具 → 串口监视器)。
① 右下角的波特率下拉,必须和 Serial.begin() 里的数字一致。
程序里写 9600,监视器里就得选 9600。不一致的后果是一屏乱码(`、ÿÿÿ`)——这不是板子坏了,是两边语速没对上(就像两个人一个说快一个说慢)。
② 串口监视器开着的时候,是不能上传程序的。 因为它占住了串口,上传要用同一个口,会报"端口忙/被占用"之类的错。 上传之前先把监视器窗口关掉——这个坑几乎人人踩一次。
看到什么算成功:串口监视器里每秒滚出一行:
count = 1
count = 2
count = 3
...
Arduino 的串口传的是原始字节,中文需要三字节,监视器的编码设置一不注意就是乱码。 调试信息一律用英文和数字,最省事(和 C++ 课里"别用中文做字符串切分"是同一类务实建议)。 要显示中文的话,通常交给树莓派上的 Python 或网页去做——这也正是本章后面两节要干的事。
六、动手 Demo:三步递进
三步走完,你就把 Arduino 的"输入—处理—输出"全摸了一遍。
第一步 · 改快慢(上面已给代码)
把 delay(1000) 改成 delay(200),看灯速变化。
第二步 · 让板子报数(上面已给代码)
每秒报一次计数,在串口监视器里看着数字往上走。
第三步 · 让板子听命令(本节最有价值的一步)
这一步是 8-5 的预演:板子不再是"自己演自己看",而是等外面发指令。
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
Serial.println("ready"); // 开机先报一声
}
void loop() {
if (Serial.available() > 0) { // 有没有收到字符?
char c = Serial.read(); // 读一个字符
if (c == '1') {
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LED ON");
} else if (c == '0') {
digitalWrite(LED_BUILTIN, LOW);
Serial.println("LED OFF");
}
}
}
怎么操作:上传完成后,打开串口监视器,在输入框里敲一个 1 回车,再敲一个 0 回车。
看到什么算成功:
ready
LED ON ← 敲了 1
LED OFF ← 敲了 0
并且板载 L 灯随你的输入亮、灭。
读结果:
| 代码 | 在干什么 |
|---|---|
Serial.available() |
问"缓冲区里有几个字符到了"(0 表示还没有) |
Serial.read() |
取走一个字符(取走就没了) |
if (c == '1') |
注意是字符 '1'(带单引号),不是数字 1 |
这三行就是"单片机接受外部命令"的最小完整形态。8-5 里,敲键盘的那个人会变成树莓派上的 Python 脚本——一模一样的三行代码,一个字母都不用改。
七、30 秒实验:故意写错,学会读编译错误
读不懂报错,是新手最大的时间黑洞。 主动制造一次:
把 pinMode 故意改成 pinmode(小写 m),点验证。
你会看到:
error: 'pinmode' was not declared in this scope; did you mean 'pinMode'?
pinmode(LED_BUILTIN, OUTPUT);
^~~~~~~
读结果(这是本节最实用的一条):
error:后面那句话就是根因——"pinmode没在这里声明过",翻译成人话就是"没有这个函数"did you mean 'pinMode'?—— 编译器直接告诉你正确拼写了^<del>~</del>~指着出错的准确位置- ⚠️ 关键:Arduino 报的错往往是"一串",第一条才是根因。 后面那些"库文件里第 xxx 行出错"多半是被第一条连累的。先解决第一条,再重新编译——很多人从最后一条开始看,越看越乱。
C++ 课讲模板时提过:模板库的报错会引出一长串,因为错误在实例化之后才暴露。Arduino 用的是同一套编译器(avr-gcc),表现一模一样。 处理原则通用:只看第一条,从自己的代码里找。
八、常见误区
-
"文件夹名随便起" —— 必须和
.ino文件同名(Hello/Hello.ino)。不同名 IDE 打不开。 -
"上传前不用选端口" —— 必须选。不选就是一句"没有指定端口",一步都走不下去。
-
"验证和上传是一回事" —— 验证只编译;上传才写进芯片。改完代码要重新上传。
-
"串口监视器开着也能上传" —— ❌ 会失败,因为串口被它占着。上传前先关掉监视器窗口。
-
"串口监视器里乱码=板子坏了" —— 九成是波特率不匹配(程序里 9600、监视器里选了 115200)。让两边数字一致即可。
-
"代码改完就已经生效了" —— ❌ 芯片里跑的是最后一次上传的版本。改完不传,试的还是旧程序。
-
"
delay(1000)是 1 秒,所以它就在等 1 秒" —— 时间是对的,但它同时"瞎"了 1 秒:delay期间程序什么都干不了,串口来的命令也读不到。 这是新手进阶时必踩的一课:想让灯闪的同时还能响应按钮,就不能用delay,得改用millis()记时间戳。这个坑先记着——将来做"灯在闪、按钮随时能停"这类需求时,就是它。 -
"Arduino 用的就是普通 C++,我 C++ 课学过了" —— 语法是,但生态不同:大量硬件操作被封装成函数(
digitalWrite而不是操作寄存器),而且不同板子的同名函数行为可能不同。C++ 的底子是优势,但别以为能原样照搬。
自测三问(点开对答案)
1. Arduino 程序的文件夹名和 `.ino` 文件名有什么要求?为什么? **参考答案**:**必须同名**(`Hello/Hello.ino`)。这是 Arduino 的规定——IDE 靠"文件夹里的主文件与文件夹同名"来定位入口。不同名,**IDE 直接打不开这个程序**。 2. 「验证」和「上传」有什么区别?为什么"改完代码再看没变化"? **参考答案**:**验证只编译**(检查语法,完全不碰板子);**上传是先编译、再把结果写进芯片**。芯片里跑的永远是**最后一次上传**的版本——所以**改完代码必须重新上传**,否则你看到的还是旧程序(改完不传就试,是白试)。 3. 串口监视器里显示一屏乱码,最可能是什么问题?还有一条关于它的纪律是什么? **参考答案**:**波特率不匹配**——程序里 `Serial.begin()` 的数字要和监视器右下角下拉里选的数字一致(比如都是 9600)。**另一条纪律**:串口监视器**占着串口**,开着它上传程序会失败,**上传前必须先关掉监视器窗口**。- ☐ 下一节(8-4 使用 Python 开发 Arduino 程序)
相关笔记
- 在树莓派建立 Arduino 开发环境 —— 上一节:环境与串口权限都在那里装好
- 认识 Arduino Uno 开发板 —— "没有操作系统"这条判据,在这里变成了
setup()+loop()的形状 - 在树莓派开发 Python 程序 —— 对照读:Python 的"写→跑"与 Arduino 的"写→编译→上传"差在哪一步
- 使用命令行安装和卸载应用程序 —— 「只刷新清单」vs「真的生效」那条区分,与本节「验证」vs「上传」是同一类
- C++的特点 —— Arduino 的语言就是 C++ 的简化版,这里能看到底子
- 🍓 树莓派 · 索引 —— 全课路线图