Arduino 从入门到进阶完整指南

版本:V1.2.0  |  更新日期:2026/11/08 12:00

本文档整合了最全的微课与进阶教材大纲,围绕 Arduino UNO 主板、传感器扩展板与面包板,提供从软件安装、基础语法到复杂电机控制的无缝完整教程。

1. 软件平台与基础架构

1.1 官方与第三方下载源

为了确保软件的安全与纯净,请务必从官方或国内权威镜像站下载开发环境:

1.2 IDE 1.x 与 2.x 操作指南

Arduino IDE 是编写、编译并上传代码的专用软件。目前有两大主流版本:

1.3 库管理器与生态拓展

对于复杂的外设(如屏幕、传感器),需要引入第三方库。在 IDE 中点击左侧的“库管理器”图标,搜索名称并安装。在代码头部使用 #include 调用。官方社区提供了大量开源的驱动支持。

1.4 代码基础结构与运行原理

在开始接线和编写逻辑之前,必须了解 Arduino 程序的“空壳”结构。每一个 Arduino 程序都由 setup() 和 loop() 两个必须存在的核心函数构成。

C++ 空壳结构
void setup() {
    // 上电或复位后,只执行一次。用于初始化配置
}

void loop() {
    // setup执行完毕后,无限循环执行。用于编写主逻辑
}

运行原理详解:

1.5 常用代码块的作用与搭配语法

为了在后续项目中灵活拼装代码,初学者必须先掌握以下几个最核心的“代码块组合”及其语法搭配:

1. 引脚模式配置块(准备阶段)

语法: pinMode(引脚号, 模式);

作用: 告诉单片机某个引脚是负责“输出电”还是“读取电”。

搭配: 必须放在 setup() 函数中。例如 pinMode(9, OUTPUT); 是把 9 号引脚设为输出。如果要读取按键,就写 INPUT_PULLUP。

2. 数字输出控制块(动作阶段)

语法: digitalWrite(引脚号, 状态);

作用: 控制引脚输出 5V 高电平(HIGH)或 0V 低电平(LOW)。

搭配: 通常放在 loop() 中,且该引脚必须事先在 setup 里设为 OUTPUT。常与 delay() 搭配使用,比如:输出 HIGH → 等 1 秒 → 输出 LOW。

3. 数字输入读取块(感知阶段)

语法: digitalRead(引脚号);

作用: 读取引脚当前的电平状态(HIGH 或 LOW),常用于判断按键是否被按下。

搭配: 必须先设为输入模式。读取后通常与 if 语句搭配,例如:if (digitalRead(2) == LOW) { ... }。

4. 模拟输入读取块(连续感知阶段)

语法: analogRead(模拟引脚);

作用: 读取 0-5V 之间的连续电压,并将其转换为 0-1023 之间的整数。

搭配: 只能用在 A0~A5 引脚上。读取后的数值通常配合 map() 函数,转换成 0-255 后交给 PWM 引脚使用。

5. 模拟输出(PWM)控制块

语法: analogWrite(引脚号, 数值);

作用: 在支持 PWM 的引脚上输出 0-255 之间的模拟值,用于调光或调速。

搭配: 引脚必须带 ~ 符号(如 9 号)。数值通常来自 analogRead 经过 map 处理后的结果。

6. 条件判断块(逻辑大脑)

语法: if (条件) { 动作A } else { 动作B }

作用: 根据条件是否成立,决定执行哪部分代码。

搭配: 可以嵌套,也可以配合逻辑运算符 &&(与)、||(或)使用。

1.6 编程符号与语法规则解析

在学习后续代码前,必须掌握以下基础符号的含义与搭配规则,这些是构成代码的“砖块”:

1. 大括号 { } —— 代码的“容器”

大括号用来定义一个函数或一段代码的边界。就像盒子一样,把相关的一组指令装在一起。例如 void setup() { ... },大括号里的所有内容都属于 setup 函数。大括号必须成对出现,少一个就会编译报错。

2. 分号 ; —— 语句的“句号”

在 C++ 中,每条执行指令(如 pinMode(9, OUTPUT);)结尾必须加英文分号。它代表“这一句话结束,请执行”。如果没有分号,编译器会把下一行也当成这一句的一部分,导致语法错误。

3. 双斜杠 // —— 代码的“隐身衣”(注释)

写在 // 后面的所有文字都不会被单片机执行,它们只是写给人看的说明。用来解释代码的作用,方便以后复习或让别人看懂。

4. 圆括号 ( ) —— 函数的“传参口”

圆括号专门跟在函数名后面,用来向函数传递参数。比如 pinMode(9, OUTPUT),意思就是告诉 pinMode 函数:“我要配置的是 9号引脚,模式是 输出”。参数之间用英文逗号隔开。

5. 双引号 "" —— 字符串的“衣服”

当你想打印一段文字(如 "Hello Arduino")时,必须用双引号把文字包裹起来,告诉单片机“这是一串字符,不是变量名”。

2. 硬件基础与电路连接

2.1 UNO 核心板与传感器扩展板

本指南实验均基于 Arduino UNO R3 与 Sensor Shield 传感器扩展板 叠层使用。扩展板将 UNO 所有引脚引出为标准的 S (信号)、V (电源)、G (地) 三针接口,用公对母杜邦线即可将任意引脚引出到面包板,极大简化了接线过程。

2.2 面包板的内部玄机

面包板两侧的红蓝电源轨竖向连通,中间孔位横向连通(5 孔一组),中间凹槽隔断上下半区。接线时,将扩展板或 UNO 的 5V 与 GND 分别接入面包板两侧的红蓝电源轨,后续所有需要供电的元件都从这两条轨取电。

2.3 电阻与电子元件原理

驱动 LED 时,必须串联 220Ω 限流电阻,以防电流过大烧毁 LED 或微控制器引脚。光敏电阻等阻值变化的传感器,需配合 10kΩ 固定电阻 组成分压电路,才能将电阻变化转化为可读取的电压。

3. 基础数字 I/O 与输出进阶

3.1 案例1:点亮第一颗 LED

作用: 验证数字输出,让 LED 以固定频率闪烁。

代码拆解与搭配逻辑:

1. pinMode(9, OUTPUT);:配置 9 号引脚为输出模式,相当于把 9 号门设为“只能出不能进”。

2. digitalWrite(9, HIGH);:让 9 号引脚输出高电平(5V),电流流出,点亮 LED。

3. delay(1000);:让 CPU 原地等待 1000 毫秒,保持当前状态。

4. 搭配逻辑:在 setup 中初始化引脚,在 loop 中交替执行“亮-等-灭-等”,从而形成闪烁效果。

C++
void setup() {
    pinMode(9, OUTPUT); // 将9号引脚设置为输出模式
}

void loop() {
    digitalWrite(9, HIGH); // 9号引脚输出高电平(5V),点亮LED
    delay(1000);           // 程序暂停1000毫秒(1秒)
    digitalWrite(9, LOW);  // 9号引脚输出低电平(0V),熄灭LED
    delay(1000);           // 程序暂停1000毫秒(1秒)
}

接线步骤

所需设备:UNO + 扩展板(已插好)、面包板、LED 灯 1 个、220Ω 电阻 1 个、公对母杜邦线 2 根。

步骤:

1. 取一根杜邦线,母头插在扩展板 D9 的 S 针(信号),公头插在面包板任意一组(如第 1 行的 a 孔)。

2. 将 220Ω 电阻的一端插在第 1 行 b 孔,另一端插在第 1 行 d 孔(跨接在同组导通的孔内)。

3. 将 LED 长脚(正极)插在第 1 行 e 孔,短脚(负极)插在第 2 行 a 孔。

4. 再取一根杜邦线,母头插在扩展板 GND 的 G 针,公头插在面包板第 2 行 b 孔。

3.2 案例2:炫彩流水灯与 SOS 求救

作用: 通过数组控制多个 LED 依次点亮,并模拟摩斯密码 SOS 求救信号。

代码拆解与搭配逻辑:

模块 1:数组与引脚定义
int ledPins[] = {2, 3, 4, 5, 6}; 定义了一个存放 5 个引脚号的数组。它把多个引脚打包在一起,方便后续统一管理。

模块 2:初始化 for 循环
for (int i = 0; i < 5; i++) { pinMode(ledPins[i], OUTPUT); } 这段代码遍历数组中的每一个引脚,并将其全部初始化为输出模式。相比写 5 遍 pinMode 更简洁。

模块 3:流水灯正向与逆向逻辑
使用两个 for 循环。第一个从 0 到 4 递增,依次点亮;第二个从 4 到 0 递减,实现反向流水效果。每次点亮后配合 delay(100) 产生视觉暂留。

模块 4:SOS 频闪逻辑
依靠三个独立的 for 循环组合。第一个循环重复 3 次短闪(200ms),第二个循环重复 3 次长闪(600ms),第三个再次重复 3 次短闪。最终组合成“SOS”的求救信号节奏。

C++ 流水灯
int ledPins[] = {2, 3, 4, 5, 6}; // 定义一个数组,存放5个LED的引脚号

void setup() {
    for (int i = 0; i < 5; i++) {  // 循环5次
        pinMode(ledPins[i], OUTPUT); // 将数组中的5个引脚都设置为输出模式
    }
}

void loop() {
    for (int i = 0; i < 5; i++) {  // 从左到右依次点亮
        digitalWrite(ledPins[i], HIGH); // 点亮当前引脚
        delay(100);                   // 延时100毫秒
        digitalWrite(ledPins[i], LOW);  // 熄灭当前引脚
    }
    for (int i = 4; i >= 0; i--) {  // 从右到左依次点亮
        digitalWrite(ledPins[i], HIGH); // 点亮当前引脚
        delay(100);                   // 延时100毫秒
        digitalWrite(ledPins[i], LOW);  // 熄灭当前引脚
    }
}
C++ SOS 求救
void setup() {
    pinMode(9, OUTPUT); // 设置9号引脚为输出模式
}

void loop() {
    // 字母 S:3次短闪
    for (int i = 0; i < 3; i++) {
        digitalWrite(9, HIGH); delay(200); digitalWrite(9, LOW); delay(200);
    }
    // 字母 O:3次长闪
    for (int i = 0; i < 3; i++) {
        digitalWrite(9, HIGH); delay(600); digitalWrite(9, LOW); delay(200);
    }
    // 字母 S:3次短闪
    for (int i = 0; i < 3; i++) {
        digitalWrite(9, HIGH); delay(200); digitalWrite(9, LOW); delay(200);
    }
    delay(2000); // 发完一轮SOS,等待2秒后重新开始
}

接线步骤

流水灯所需设备:LED 灯 5 个、220Ω 电阻 5 个、公对母杜邦线 6 根(5 根信号,1 根地线)。

流水灯接线:

1. 将 D2、D3、D4、D5、D6 的 S 针分别用杜邦线引到面包板第 1、3、5、7、9 行的 a 孔。

2. 每一行之间跨接 220Ω 电阻(如 1b 到 1d)。

3. 5 个 LED 的负极全部汇总到面包板的蓝色电源轨。

4. 从扩展板 GND 引一根线接到面包板蓝色电源轨,完成共地。

SOS 所需设备:LED 1 个、220Ω 电阻 1 个、杜邦线 2 根。

SOS 接线:同案例 1,接在 D9 即可。

3.3 案例3:按键控制与软件消抖

作用: 读取按键状态并控制 LED,解决机械按键在按下瞬间产生的抖动问题。

代码拆解与搭配逻辑:

模块 1:状态记录变量
lastReading 用于记录按键上一次的状态;lastDebounce 用于记录状态改变的时间点。这些变量必须定义在函数外部,才能跨循环保留数值。

模块 2:变化检测与计时
在 loop 中,如果当前读取的状态(reading)与上次不同,说明按键被动了,立刻用 millis() 记录下当前的时间戳。

模块 3:消抖时间过滤
通过 (millis() - lastDebounce) > 50 判断,如果距离上次变化已经超过了 50 毫秒,说明电平已经稳定,不是机械抖动,此时才真正读取并控制 LED。

C++
int buttonPin = 2;          // 按键连接在2号引脚
int ledPin = 13;            // LED连接在13号引脚
int lastReading = HIGH;     // 记录上一次的读取状态
unsigned long lastDebounce = 0; // 记录上一次状态改变的时间

void setup() {
    pinMode(ledPin, OUTPUT);        // 设置LED为输出模式
    pinMode(buttonPin, INPUT_PULLUP); // 设置按键为输入模式,并启用内部上拉电阻
}

void loop() {
    int reading = digitalRead(buttonPin); // 读取当前按键状态
    if (reading != lastReading) {         // 如果状态发生了变化(按下或松开)
        lastDebounce = millis();          // 记录当前时间
    }
    if ((millis() - lastDebounce) > 50) { // 如果距离上次变化已经过了50毫秒(消抖)
        if (reading == LOW) {             // 如果当前状态是低电平(按下)
            digitalWrite(ledPin, HIGH);   // 点亮LED
        } else {
            digitalWrite(ledPin, LOW);    // 熄灭LED
        }
    }
    lastReading = reading;                // 更新上一次的读取状态
}

接线步骤

所需设备:四脚轻触按键 1 个、LED 灯 1 个、220Ω 电阻 1 个、公对母杜邦线 3 根。

步骤:

1. 将按键跨接在面包板中间凹槽上方(如第 5 行的 e 孔和 f 孔),因为按键同侧两脚内部是不连通的。

2. 杜邦线母头接扩展板 D2 的 S 针,公头接按键左上角引脚(第 5 行 e 孔)。

3. 杜邦线母头接扩展板 GND,公头接按键左下角引脚(第 5 行 a 孔)。

4. LED 接在 D13 或面包板上均可(参考案例 1)。代码中使用了 INPUT_PULLUP,因此无需外部电阻即可读取按键状态。

3.4 案例4:蜂鸣器发声与电子琴

作用: 使无源蜂鸣器输出指定频率的声音,并模拟警报或制作电子琴。

代码拆解与搭配逻辑:

模块 1:tone() 发声原理
tone(引脚, 频率) 会驱动单片机在引脚上生成高频方波,频率越高,蜂鸣器振动越快,音调越尖。停止发声用 noTone(引脚)。

模块 2:宏定义音符
使用 #define NOTE_C4 262 给频率数值起个有意义的名字,不占内存且代码易读。

模块 3:多按键电子琴逻辑
通过连续的 if...else if 语句,依次检测 2、3、4 号引脚的状态。一旦检测到低电平(按键按下),立刻发出对应的音调;若都没按下,则停止发声。

C++ 警报器
void setup() {
    pinMode(8, OUTPUT); // 设置8号引脚为输出模式
}

void loop() {
    // 频率从200Hz逐渐升高到800Hz
    for (int freq = 200; freq <= 800; freq += 10) {
        tone(8, freq);    // 在8号引脚输出对应频率的声音
        delay(5);         // 延时5毫秒
    }
    // 频率从800Hz逐渐降低到200Hz
    for (int freq = 800; freq >= 200; freq -= 10) {
        tone(8, freq);    // 在8号引脚输出对应频率的声音
        delay(5);         // 延时5毫秒
    }
}
C++ 电子琴
// 定义音符频率的常量
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330

void setup() {
    pinMode(2, INPUT_PULLUP); // 设置按键引脚为输入,启用内部上拉
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(8, OUTPUT);       // 设置蜂鸣器引脚为输出
}

void loop() {
    if (digitalRead(2) == LOW) tone(8, NOTE_C4);      // 如果按键1按下,发出C4音
    else if (digitalRead(3) == LOW) tone(8, NOTE_D4); // 如果按键2按下,发出D4音
    else if (digitalRead(4) == LOW) tone(8, NOTE_E4); // 如果按键3按下,发出E4音
    else noTone(8);                                   // 如果都没按,停止发声
}

接线步骤

警报器所需设备:无源蜂鸣器 1 个、公对母杜邦线 2 根。

警报器接线:蜂鸣器正极(长脚)接扩展板 D8 的 S 针,负极接 GND。

电子琴所需设备:无源蜂鸣器 1 个、按键 3 个、杜邦线若干。

电子琴接线:

1. 蜂鸣器正极接 D8(S 针),负极接 GND。

2. 按键 1 两端分别接 D2 的 S 针和 GND。

3. 按键 2 两端分别接 D3 的 S 针和 GND。

4. 按键 3 两端分别接 D4 的 S 针和 GND。

4. 模拟信号与传感器生态

4.1 案例5:电位器 ADC 采集与调光

作用: 旋转电位器,手动控制 LED 亮度。

代码拆解与搭配逻辑:

模块 1:analogRead 读取模拟量
analogRead(A0) 读取电位器中心滑片上的电压,单片机将其转换为 0-1023 的数字。

模块 2:map 映射函数
因为 PWM 引脚只能接受 0-255 的数值,map(val, 0, 1023, 0, 255) 将 0-1023 等比例压缩为 0-255。

模块 3:analogWrite 输出
analogWrite(9, brightness) 输出 PWM 方波控制 LED 亮度。整个流程实现了“读取-转换-输出”的闭环。

C++
void setup() {
    pinMode(9, OUTPUT); // 设置9号引脚为输出模式
}

void loop() {
    int val = analogRead(A0); // 读取A0引脚的模拟值(0-1023)
    int brightness = map(val, 0, 1023, 0, 255); // 将0-1023映射到0-255的PWM范围
    analogWrite(9, brightness); // 输出PWM信号控制LED亮度
}

接线步骤

所需设备:旋转电位器(3 脚)1 个、LED 1 个、220Ω 电阻 1 个、杜邦线若干。

步骤:

1. 将电位器插在面包板上,引脚分别占据 a、b、c 三行。

2. 电位器左侧引脚连 5V(扩展板 V 针),右侧引脚连 GND。

3. 中间引脚(滑片)用杜邦线连到扩展板 A0 的 S 针。

4. LED 正极经 220Ω 电阻接 D9 的 S 针,负极接 GND。

4.2 案例6:光敏电阻智能夜灯

作用: 根据环境亮度自动调节 LED 亮度。

代码拆解与搭配逻辑:

模块 1:光线与电压的反比逻辑
光线越暗,光敏电阻阻值越大,分压电路在 A0 处产生的电压就越高,analogRead 读取到的数值就越大。

模块 2:constrain 限幅保护
经过 map 映射后的数值有可能会超出 0-255 的范围。constrain() 函数强制将数值约束在合法范围内,防止程序崩溃。

模块 3:delay 控制刷新率
在循环末尾加一个极短的 delay(50),避免 ADC 读取过快导致灯光闪烁不稳定。

C++
void setup() {
    pinMode(9, OUTPUT); // 设置9号引脚为输出模式
}

void loop() {
    int lightVal = analogRead(A0); // 读取A0引脚的模拟值(光线越暗,值越大)
    int pwmVal = map(lightVal, 300, 1023, 0, 255); // 将环境光线映射到PWM输出范围
    analogWrite(9, constrain(pwmVal, 0, 255)); // 限制PWM值在0-255之间,并输出控制LED
    delay(50); // 短暂延时
}

接线步骤

所需设备:光敏电阻 1 个、10kΩ 固定电阻 1 个、LED 1 个、220Ω 电阻 1 个、杜邦线若干。

步骤:

1. 在面包板上搭建分压电路:将光敏电阻一端接 5V,另一端接 A0(S 针)。

2. 在 A0 所在的同一行,跨接 10kΩ 电阻到 GND。这样 A0 读取的电压会随光线变化。

3. LED 正极经 220Ω 电阻接 D9 的 S 针,负极接 GND。

4.3 案例7:热释电红外 (PIR) 报警

作用: 检测人体运动,触发声光报警。

代码拆解与搭配逻辑:

模块 1:传感器数字输出读取
与读取按键类似,使用 digitalRead(3) 读取传感器。当有人移动时,PIR 内部输出高电平(HIGH)。

模块 2:条件触发联动
通过 if (pirState == HIGH) 判断。如果高电平,点亮 LED 并同时调用 tone(8, 1000) 让蜂鸣器长鸣;否则关闭它们。

C++
void setup() {
    pinMode(3, INPUT);     // 设置3号引脚为输入模式(连接PIR传感器)
    pinMode(13, OUTPUT);   // 设置13号引脚为输出模式(连接LED)
    pinMode(8, OUTPUT);    // 设置8号引脚为输出模式(连接蜂鸣器)
    Serial.begin(9600);    // 初始化串口通信
}

void loop() {
    int pirState = digitalRead(3); // 读取PIR传感器的状态
    if (pirState == HIGH) {        // 如果检测到人体运动(高电平)
        digitalWrite(13, HIGH);    // 点亮LED
        tone(8, 1000);             // 蜂鸣器发声
    } else {                       // 如果没有检测到运动
        digitalWrite(13, LOW);     // 熄灭LED
        noTone(8);                 // 关闭蜂鸣器
    }
    delay(200); // 延时200毫秒
}

接线步骤

所需设备:HC-SR501 PIR 模块 1 个、LED 1 个、无源蜂鸣器 1 个、杜邦线若干。

步骤:

1. PIR 模块有 3 根针(VCC、OUT、GND)。直接用母对母杜邦线或公对母线将 VCC 接扩展板 5V,GND 接 GND,OUT 接 D3 的 S 针。

2. LED 正极经 220Ω 电阻接 D13 的 S 针,负极接 GND。

3. 蜂鸣器正极接 D8 的 S 针,负极接 GND。

4.4 案例8:热敏电阻温度计

作用: 读取热敏电阻的阻值变化,通过串口输出当前温度值。

代码拆解与搭配逻辑:

模块 1:模拟值转电压
raw * 5.0 / 1023.0 将读取到的 0-1023 数值还原成真实的电压值(0-5V)。

模块 2:分压公式算阻值
利用串联分压原理,已知上拉电阻(10kΩ)和总电压(5V),倒推出热敏电阻当前的阻值。

模块 3:物理公式转温度
通过 Steinhart-Hart 方程(包含对数 log 运算),把电阻值转换成开尔文温度,最后减去 273.15 转为摄氏度。

C++
void setup() {
    Serial.begin(9600); // 初始化串口通信,波特率9600
}

void loop() {
    int raw = analogRead(A1); // 读取A1引脚的模拟值
    float voltage = raw * 5.0 / 1023.0; // 将模拟值转换为电压值(0-5V)
    // 根据分压公式计算热敏电阻阻值
    float resistance = (10000.0 * voltage) / (5.0 - voltage); 
    // 使用Steinhart-Hart方程(B值法)计算温度
    float temperature = 1.0 / (1.0 / 298.15 + log(resistance / 10000.0) / 3950.0) - 273.15;
    Serial.println(temperature); // 将温度值打印到串口
    delay(1000); // 延时1秒
}

接线步骤

所需设备:NTC 热敏电阻 1 个、10kΩ 固定电阻 1 个、杜邦线 3 根。

步骤:

1. 在面包板上搭建分压电路:热敏电阻一端接 5V,另一端连接 A1(S 针)。

2. 在 A1 的同组孔位,跨接 10kΩ 固定电阻到 GND。

5. 通信协议与显示系统

5.1 案例9:I2C OLED 屏幕显示

作用: 在 OLED 屏幕上打印文字和传感器数据。

代码拆解与搭配逻辑:

模块 1:引入库与对象创建
#include 引入屏幕驱动库,随后实例化 display 对象,并告诉它屏幕宽 128、高 64。

模块 2:初始化与坐标设置
begin() 负责握手,clearDisplay() 清屏,setCursor(x, y) 设置文字起笔的坐标。

模块 3:缓冲与刷新机制
println() 只把文字写进内存缓存里,必须调用 display.display() 才会将画面推送到屏幕上,这能防止屏幕闪烁。

C++
#include                // 引入I2C通信库
#include          // 引入图形库
#include      // 引入OLED驱动库

// 创建一个OLED对象,宽度128,高度64
Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
    // 初始化OLED屏幕,地址为0x3C
    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
    display.clearDisplay();       // 清空屏幕
    display.setTextSize(1);       // 设置文字大小
    display.setTextColor(WHITE);  // 设置文字颜色为白色
    display.setCursor(0, 10);     // 设置文字起始坐标
    display.println("Hello Arduino"); // 打印文字
    display.display();            // 将缓冲区内容刷新到屏幕
}

void loop() {
    // 屏幕上显示静态内容,无需循环
}

接线步骤

所需设备:I2C 接口 0.96 寸 OLED 屏 1 个、母对母或公对母杜邦线 4 根。

步骤:

1. 扩展板 A4 是 SDA,A5 是 SCL。

2. 将 OLED 的 GND 接扩展板 GND,VCC 接 5V。

3. 将 OLED 的 SDA 接扩展板 A4 的 S 针。

4. 将 OLED 的 SCL 接扩展板 A5 的 S 针。

5.2 案例10:超声波测距仪

作用: 测量前方障碍物距离并打印到串口。

代码拆解与搭配逻辑:

模块 1:微秒级触发信号
先拉低 Trig 保证信号纯净,然后拉高并延时 10 微秒,再拉低。这 10 微秒的高电平就是启动超声波发射的“发令枪”。

模块 2:pulseIn 测量回响时间
Echo 引脚收到回波后变为高电平,持续的时间就是声波往返的时间。pulseIn(echoPin, HIGH) 就是用来抓取这个微秒级的时长。

模块 3:声速公式转换距离
声速约 340m/s(即 0.034cm/μs)。因为声波是一去一回,所以计算出的总距离必须除以 2。

C++
const int trigPin = 9;  // 触发引脚连接9号
const int echoPin = 10; // 回响引脚连接10号

void setup() {
    Serial.begin(9600);         // 初始化串口
    pinMode(trigPin, OUTPUT);   // 设置触发引脚为输出
    pinMode(echoPin, INPUT);    // 设置回响引脚为输入
}

void loop() {
    digitalWrite(trigPin, LOW); 
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); 
    delayMicroseconds(10);       // 发送10微秒的高电平脉冲触发超声波
    digitalWrite(trigPin, LOW);
    
    long duration = pulseIn(echoPin, HIGH); // 测量回响引脚高电平的持续时间
    int distance = duration * 0.034 / 2;    // 根据声速计算距离(单位:厘米)
    
    Serial.print(distance);      // 打印距离到串口
    Serial.println(" cm");
    delay(500);                  // 延时500毫秒
}

接线步骤

所需设备:HC-SR04 超声波模块 1 个、母对母或公对母杜邦线 4 根。

步骤:

1. VCC 接扩展板 5V,GND 接 GND。

2. Trig(触发)接扩展板 D9 的 S 针。

3. Echo(回响)接扩展板 D10 的 S 针。

5.3 案例11:DHT11 温湿度气象站

作用: 读取环境温度和湿度数据,需要安装 DHT 库。

代码拆解与搭配逻辑:

模块 1:DHT 对象定义
DHT dht(2, DHT11) 声明传感器型号为 DHT11,数据引脚为 2 号,程序会自动处理单总线的复杂时序。

模块 2:读取与间隔
调用 readTemperature() 和 readHumidity() 获取数据。DHT11 转换速度慢,必须配合 delay(2000) 保证每次读取都有效。

C++
#include "DHT.h"            // 引入DHT传感器库

DHT dht(2, DHT11);          // 创建DHT对象,数据引脚接2号,型号为DHT11

void setup() {
    Serial.begin(9600);     // 初始化串口
    dht.begin();            // 启动DHT传感器
}

void loop() {
    float t = dht.readTemperature(); // 读取温度
    float h = dht.readHumidity();    // 读取湿度
    Serial.print(t); Serial.print("C "); // 打印温度
    Serial.print(h); Serial.println("%"); // 打印湿度
    delay(2000);                     // DHT11读取间隔至少2秒
}

接线步骤

所需设备:DHT11 模块(3 针或 4 针)1 个、杜邦线 3 根。

步骤:

1. 模块 VCC 接扩展板 5V,GND 接 GND。

2. 模块 DATA(数据)引脚接扩展板 D2 的 S 针。

5.4 案例12:红外遥控与串口通信

作用: 接收红外遥控器发出的键值,需要安装 IRremote 库。

代码拆解与搭配逻辑:

模块 1:中断解码
红外信号是一串极其短暂的脉冲。irrecv.decode(&results) 会在底层利用中断捕获脉冲,并存入 results 对象中。

模块 2:准备下一次接收
一旦读取成功,必须调用 irrecv.resume() 清空状态,否则无法接收下一个按键的信号。

C++
#include        // 引入红外遥控库

const int RECV_PIN = 3;     // 红外接收头连接3号引脚
IRrecv irrecv(RECV_PIN);    // 创建红外接收对象
decode_results results;     // 用于存储解码结果

void setup() {
    Serial.begin(9600);     // 初始化串口
    irrecv.enableIRIn();    // 启动红外接收
}

void loop() {
    if (irrecv.decode(&results)) {            // 如果接收到红外信号并解码成功
        Serial.println(results.value, HEX);   // 将按键值以十六进制打印到串口
        irrecv.resume();                      // 准备接收下一个信号
    }
}

接线步骤

所需设备:HX1838 红外接收头(通常 3 针)1 个、杜邦线 3 根。

步骤:

1. 接收头 VCC 接 5V,GND 接 GND。

2. 接收头 OUT(数据)接扩展板 D3 的 S 针。

6. 执行器与电机控制系统

6.1 案例13:舵机扫频与角度控制

作用: 通过代码控制舵机旋转到指定角度(0-180度),并来回扫动。

代码拆解与搭配逻辑:

模块 1:对象绑定
myservo.attach(9) 是告诉单片机,我接下来要控制的舵机,信号线接在 9 号引脚上。

模块 2:角度写入与扫频
利用两个 for 循环,让角度 pos 从 0 递增到 180,再递减回 0,并不断用 write() 写进去,实现来回扫动。

C++
#include           // 引入舵机控制库

Servo myservo;              // 创建舵机对象

void setup() {
    myservo.attach(9);      // 将舵机信号线连接到9号引脚
}

void loop() {
    // 从0度旋转到180度
    for (int pos = 0; pos <= 180; pos += 5) {
        myservo.write(pos); // 设置舵机角度
        delay(15);          // 等待舵机转动到位
    }
    // 从180度旋转回0度
    for (int pos = 180; pos >= 0; pos -= 5) {
        myservo.write(pos);
        delay(15);
    }
}

接线步骤

所需设备:SG90 舵机 1 个(带 3 根线:棕、红、橙/黄)。

步骤:

1. 舵机的棕色线(GND)接扩展板 GND。

2. 红色线(VCC)接扩展板 5V。

3. 橙色或黄色线(信号)接扩展板 D9 的 S 针。

注意:如果舵机抖动严重,说明 UNO 供电不足,需使用外部 5V 电源供电,并将外部电源负极与 UNO 的 GND 相连(共地)。

6.2 案例14:步进电机精准定位

作用: 驱动步进电机按固定步数旋转,实现精确定位。

代码拆解与搭配逻辑:

模块 1:步进参数初始化
Stepper myStepper(一圈步数, 引脚1, 引脚2, 引脚3, 引脚4) 声明了一圈需要走多少步,以及控制各线圈的引脚编号。

模块 2:步数与速度控制
setSpeed(60) 规定了转速。step(步数) 控制转动的角度。传入正数正转,负数反转。必须注意,step 是阻塞执行的,转完之前程序会停在这一行。

C++
#include         // 引入步进电机库

const int stepsPerRev = 200; // 步进电机转一圈的步数
// 创建一个步进电机对象,控制引脚为8, 9, 10, 11
Stepper myStepper(stepsPerRev, 8, 9, 10, 11);

void setup() {
    myStepper.setSpeed(60);  // 设置电机转速为60 RPM
}

void loop() {
    myStepper.step(stepsPerRev);  // 正转一圈
    delay(500);                   // 停顿500毫秒
    myStepper.step(-stepsPerRev); // 反转一圈
    delay(500);
}

接线步骤

所需设备:28BYJ-48 步进电机及 ULN2003 驱动板 1 套、母对母杜邦线 6 根。

步骤:

1. 步进电机的 5 针插头直接插在驱动板的白色插座上。

2. 驱动板上的 IN1、IN2、IN3、IN4 用杜邦线分别连接到扩展板的 D8、D9、D10、D11 的 S 针。

3. 驱动板的 VCC 接扩展板 5V(若电机不转,可尝试接外部电源),GND 接 GND。

6.3 案例15:直流电机调速与正反转

作用: 控制直流电机的正反转与转速。

代码拆解与搭配逻辑:

模块 1:方向控制逻辑
通过给 IN1 和 IN2 输入相反的高低电平(一个 HIGH 一个 LOW)来改变电流方向,从而控制电机正转或反转。

模块 2:速度控制逻辑
ENA 引脚必须连接带 PWM 功能的引脚(如 9 号)。使用 analogWrite() 输出 0-255 的占空比,即可实现从静止到全速的无级调速。

C++
const int ENA = 9;          // 使能引脚(控制速度),接9号PWM引脚
const int IN1 = 8;          // 方向控制引脚1
const int IN2 = 7;          // 方向控制引脚2

void setup() {
    pinMode(ENA, OUTPUT);
    pinMode(IN1, OUTPUT);
    pinMode(IN2, OUTPUT);
}

void loop() {
    // 正转,速度中等 (150/255)
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, 150);
    delay(2000); // 转动2秒
    
    // 反转,速度全开 (255/255)
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
    analogWrite(ENA, 255);
    delay(2000); // 转动2秒
}

接线步骤

所需设备:L298N 电机驱动模块 1 个、直流电机 1 个、外部电池盒(供电)、杜邦线若干。

步骤:

1. 将外部电源(如 9V 电池)正极接 L298N 的 12V 输入,负极接 GND。

2. 极其重要:将 L298N 的 GND 与扩展板的 GND 连在一起(共地)。

3. L298N 的 ENA 接扩展板 D9 的 S 针(必须是 PWM 引脚)。

4. IN1 接扩展板 D8,IN2 接扩展板 D7。

5. 将直流电机的两根线分别接入 L298N 的 OUT1 和 OUT2 端子。

6.4 案例16:综合智能小车避障逻辑

作用: 结合超声波传感器与直流电机,实现小车遇到障碍物自动后退转弯。

代码拆解与搭配逻辑:

模块 1:自定义函数封装
int getDistance() 把复杂的超声波时序代码全部藏起来,对外只提供一个“返回距离”的功能,让主程序看起来干净利落。

模块 2:条件决策与电机联动
在 loop 中,如果 dist < 20,立即执行刹车、停顿、单侧反转的指令,实现避障;否则,保持两侧前进。

C++
const int trigPin = 12;     // 超声波触发引脚
const int echoPin = 13;     // 超声波回响引脚
const int motorLeft = 5;    // 左电机速度控制(PWM)
const int motorRight = 6;   // 右电机速度控制(PWM)

void setup() {
    pinMode(trigPin, OUTPUT);
    pinMode(echoPin, INPUT);
    pinMode(motorLeft, OUTPUT);
    pinMode(motorRight, OUTPUT);
}

// 封装一个函数用于测量距离
int getDistance() {
    digitalWrite(trigPin, LOW); delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW);
    long duration = pulseIn(echoPin, HIGH);
    return duration * 0.034 / 2;
}

void loop() {
    int dist = getDistance(); // 获取距离
    if (dist < 20) {          // 如果距离小于20厘米
        // 停止
        digitalWrite(motorLeft, LOW);
        digitalWrite(motorRight, LOW);
        delay(500);
        // 后退转向
        digitalWrite(motorLeft, HIGH);
        digitalWrite(motorRight, LOW);
        delay(500);
    } else {
        // 安全,全速前进
        digitalWrite(motorLeft, HIGH);
        digitalWrite(motorRight, HIGH);
    }
}

接线步骤

所需设备:HC-SR04 超声波 1 个、L298N 驱动板 1 个、两个直流电机、小车底盘、外部电源、杜邦线若干。

步骤:

1. 超声波 Trig 接扩展板 D12,Echo 接 D13。

2. L298N 的 ENA 接 D5(控制左轮速度),IN1 接 D8,IN2 接 D7(控制左轮方向)。

3. L298N 的 ENB 接 D6(控制右轮速度),IN3 接 D4,IN4 接 D3(控制右轮方向)。

4. 电机接入 OUT1/OUT2(左)、OUT3/OUT4(右)。

5. 必须确保 L298N 使用外部电池供电,并将 L298N 的 GND 与 UNO 扩展板的 GND 相连。

7. 底层进阶与入坟避坑

7.1 millis() 非阻塞调度

作用: 在多任务系统中替代 delay(),避免程序假死卡顿。

代码拆解与搭配逻辑:

模块 1:记录时间戳
previousMillis 就像一个秒表,记录上一次执行动作的时间点。

模块 2:时间差比较
每次循环都用当前时间 currentMillis 减去上次记录的时间。如果差值大于设定间隔,说明该干活了。

模块 3:更新时钟
干完活后,必须把 previousMillis = currentMillis,重置秒表,为下一次计时做准备。

C++
unsigned long previousMillis = 0; // 记录上一次触发的时间
const long interval = 1000;       // 设定时间间隔(1000毫秒)

void setup() {
    pinMode(13, OUTPUT);          // 设置13号引脚为输出
}

void loop() {
    unsigned long currentMillis = millis(); // 获取当前系统运行时间
    // 如果当前时间减去上次时间,大于设定的间隔
    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;     // 更新上次触发时间
        digitalWrite(13, !digitalRead(13)); // 翻转LED的状态
    }
    // 这里可以继续写其他任务,绝对不会被卡住
}

接线步骤

所需设备:无需外部元件,使用 UNO 板载 13 号引脚 LED 即可。若需外接,参考案例 1 接在 D13。

7.2 内存溢出与 F() 宏

作用: 防止 UNO 仅有的 2KB SRAM 被大量字符串耗尽导致不断重启。

代码拆解与搭配逻辑:

模块 1:内存存储机制
未加 F() 时,双引号里的字符串会被复制到 RAM 中,占空间。F() 宏强制它留在 Flash 闪存里,释放 RAM。

模块 2:全局应用
通常将程序中所有不需要修改的固定文本(如 Serial.println)都用 F() 包起来。

C++
void setup() {
    Serial.begin(9600); // 初始化串口
}

void loop() {
    // 使用 F() 宏,将字符串强制存储在 Flash 闪存中,避免占用 SRAM
    Serial.println(F("String stored in Flash")); 
    delay(1000);
}

接线步骤

所需设备:无需额外接线,只需将 UNO 用 USB 连接至电脑,打开 IDE 的串口监视器(波特率 9600)即可看到输出。

7.3 供电危机与共地原则

致命警告: UNO 的 5V 引脚输出电流极其有限。驱动电机、继电器时,必须使用外部独立电源供电,并确保外部电源的负极与 UNO 的 GND 相连(共地),否则必烧板子。