首页 / 学习路径 / 第 02 章 · C/C++ 语言基础
02
基础 · 语言

C/C++ 语言基础

用 8 路跑马灯把变量、运算符、条件判断、循环、数组、函数全部跑一遍。这一章决定你能不能读懂后面所有代码,是零基础学习者最需要放慢速度的一章。

阅读时长 约 45 分钟硬件 16 件实战案例 1 个前置要求 第 1 章
1

本章要点

变量是程序里最小的存储单元。ESP32 常用 int 存整数、float 存小数、bool 存真假、String 存文字。每种类型占用的字节数不同,这决定了它能装多大的数——比如 uint8_t 只能装 0~255,用来表示引脚编号刚好。

for 循环把「重复做的事」写成一行。跑马灯要依次点亮 8 个 LED,用 for (i = 0; i < 8; i++) 就够了。while 循环适合「不知道要做几次」的场合,比如等 WiFi 连上。

数组把一组相同类型的数据打包管理。LED_PINS[] 存了 8 个引脚号,下标从 0 开始。sizeof(数组)/sizeof(数组[0]) 能自动算出长度,比硬编码数字更安全——以后增减 LED 不用手动改。

2

硬件清单与原理

名称数量作用与原理
LED8 个发光二极管,正向导通电压约 1.8~2.2V,必须串电阻限流,否则会烧毁。
220Ω 电阻8 个限流保护,把电流限制在约 (3.3-2.0)/220 ≈ 6mA,既够亮又安全。
面包板 + 杜邦线若干无焊接搭建电路,方便反复调整接线。
3

接线说明

接线示意 · 8 路跑马灯(每路结构完全相同) ESP32 DevKit V1 GPIO2 GPIO4 GPIO5 GPIO13 GPIO18 GPIO19 GPIO21 GPIO23 GND 共地 图例 信号线 220Ω 电阻 LED(长脚阳极)
每路 LED 结构完全相同,只是接的 GPIO 不同。共 8 组。

8 路 GPIO 分别是:GPIO2 / 4 / 5 / 13 / 18 / 19 / 21 / 23

每一组都按下面两步接,共重复 8 次:

① GPIOx → [220Ω 电阻] → LED 阳极(长脚)

② LED 阴极(短脚) → GND

提示:GPIO6~11 被内部 SPI Flash 占用,绝对不能外接;GPIO34~39 只能输入不能输出。

4

实战案例 02-1 · 8 路跑马灯

功能用途

练习数组、for 循环、函数封装、条件判断——跑马灯是这些知识点的最佳载体。

预期现象

8 个 LED 依次点亮并循环移动,到达两端时自动反向。

ch02_marquee.ino编译通过
/*
 * ============================================================================
 * 第 2 章 · 实战案例 02-1:8 路跑马灯(数组 + 循环 + 函数)
 * ----------------------------------------------------------------------------
 * 硬件清单:
 *   - ESP32 DevKit V1 开发板 × 1
 *   - 5mm 白色/彩色 LED × 8
 *   - 220Ω 电阻 × 8(限流保护)
 *   - 面包板 + 杜邦线若干
 *
 * 接线方式(每路 LED 都是一样的结构,只是接的 GPIO 不同):
 *   GPIO2  → [220Ω 电阻] → LED 阳极(长脚)
 *   LED 阴极(短脚)→ GND
 *   8 路分别使用 GPIO:2 / 4 / 5 / 13 / 18 / 19 / 21 / 23
 *
 *   ⚠ 引脚避坑说明:
 *     - 6~11 号脚被内部 SPI Flash 占用,绝对不能外接
 *     - 0/2/12/15 号脚是「启动模式引脚」,接负载可能影响开机
 *     - 34~39 号脚只能输入、不能输出,所以本案例只用上面这 8 个安全引脚
 *
 * 功能用途:
 *   本章要练的是 C/C++ 的数组、for 循环、函数封装、条件判断。
 *   跑马灯是这些知识点的「最佳载体」:既有重复动作(循环),
 *   又有一一对应的数据表(数组),还需要把重复逻辑封装成函数(函数)。
 *
 * 预期现象:
 *   8 个 LED 依次点亮并循环移动,到达两端时自动反向(往返跑马灯)。
 * ============================================================================
 */

#include <Arduino.h>   // Arduino 核心库

/* ==========================================================================
 * 一、引脚定义:用数组统一管理 8 个 LED 的引脚
 * 数组是「相同类型的一串数据」的集合,下标从 0 开始。
 * ======================================================================== */
const class="kw">uint8_t LED_PINS[] = {2, 4, 5, 13, 18, 19, 21, 23};  // 8 路 LED 的 GPIO 编号

// 用 sizeof 自动计算元素个数,这样以后增减 LED 不用手动改数字
// sizeof(数组) 得到「整个数组占用的字节数」,sizeof(数组[0]) 得到「单个元素占用的字节数」
// 两者相除即为元素个数
const class="kw">size_t LED_COUNT = sizeof(LED_PINS) / sizeof(LED_PINS[0]);

/* ==========================================================================
 * 二、可调参数
 * ======================================================================== */
class="kw">unsigned int stepDelayMs = 300;   // 每一步停留的毫秒数(调小跑得快,调大跑得慢)
int          direction   = 1;      // 移动方向:1 = 正向,-1 = 反向
int          currentIndex = 0;     // 当前亮着的是第几个 LED(下标从 0 开始)

class="kw">unsigned long lastMoveTime = 0;    // 上一次移动的时刻,用于计时

/* ==========================================================================
 * 三、初始化
 * ======================================================================== */
void setup() {
  Serial.begin(115200);   // 启动串口
  delay(200);            // 等待 USB 串口芯片就绪,避免首行乱码

  // for 循环:把 8 个引脚全部初始化为「输出」并熄灭
  // 这里演示了 for 循环 + 数组下标 + 自增运算符 ++ 的组合用法
  for (class="kw">size_t i = 0; i < LED_COUNT; i++) {
    pinMode(LED_PINS[i], OUTPUT);        // 设置为输出模式
    digitalWrite(LED_PINS[i], LOW);      // 初始全部熄灭(低电平点亮,故点亮时写 LOW)
  }

  Serial.println(class="st">"[MARQUEE] 跑马灯启动");
  Serial.print(class="st">"[MARQUEE] LED 总数:");
  Serial.println(LED_COUNT);             // 打印自动计算出来的数量
  Serial.print(class="st">"[MARQUEE] 每次停留:");
  Serial.print(stepDelayMs);
  Serial.println(class="st">" ms");
}

/* ==========================================================================
 * 四、自定义函数:熄灭所有 LED
 * 把「重复的动作」封装成函数,是让代码变好读的第一步。
 * ======================================================================== */
void allOff() {
  for (class="kw">size_t i = 0; i < LED_COUNT; i++) {
    digitalWrite(LED_PINS[i], LOW);      // 逐个熄灭
  }
}

/* ==========================================================================
 * 五、自定义函数:点亮第 index 个 LED,并返回「下一个」应该点亮谁
 * 传入参数:class="kw">size_t index —— 要点亮的下标
 * 返回值:class="kw">size_t —— 下一个要点亮的下标(这样主循环就不用操心边界问题)
 * ======================================================================== */
class="kw">size_t lightNext(class="kw">size_t index) {
  allOff();                              // 先全部熄灭,保证同时只有一个亮
  digitalWrite(LED_PINS[index], LOW);   // 再点亮目标 LED(低电平点亮)

  // 计算下一个下标,并把「越界」的情况折回来
  if (index == 0 && direction == -1) {
    class="kw">return LED_COUNT - 1;                // 已经在最左端且要往回走 → 跳到最右端
  }
  if (index == LED_COUNT - 1 && direction == 1) {
    class="kw">return 0;                           // 已经在最右端且要往前走 → 跳回最左端
  }
  class="kw">return index + direction;             // 常规情况:按当前方向前进一格
}

/* ==========================================================================
 * 六、主循环
 * ======================================================================== */
void loop() {
  class="kw">unsigned long now = millis();          // 取当前时间

  // 每隔 stepDelayMs 毫秒才移动一次
  if (now - lastMoveTime >= stepDelayMs) {
    lastMoveTime = now;                  // 更新计时基准

    // 调用函数完成「点亮 + 算出下一个下标」这一串动作
    class="kw">size_t next = lightNext(currentIndex);

    // 方向翻转逻辑:走到两端就掉头
    if (currentIndex == 0)                 direction = 1;    // 到达最左端 → 改为正向
    if (currentIndex == LED_COUNT - 1)     direction = -1;   // 到达最右端 → 改为反向

    currentIndex = next;                  // 保存新的当前位置

    // 打印当前位置,方便对照观察
    Serial.print(class="st">"[MARQUEE] 当前点亮 LED #");
    Serial.print(currentIndex);
    Serial.print(class="st">"  引脚 GPIO");
    Serial.println(LED_PINS[currentIndex]);
  }
}
完整源码 129 行 · ch02_marquee/ch02_marquee.inoarduino-cli 编译通过
5

本章小结

① 数组让「一组相同用途的引脚」不必写 8 段重复代码。

② sizeof(数组) / sizeof(数组[0]) 是计算数组长度的标准写法,比硬编码数字更安全。

③ uint8_t 表示 0~255 的无符号整数,用来表示引脚编号最合适。

④ size_t 是无符号的「大小类型」,做数组下标和计数比 int 更安全。

⑤ 函数把「做什么」和「在哪调用」分离,方向翻转靠 direction 变量取正负 1 实现。

6

本章练习

练习 1

把 LED 数量改成 4 个,验证数组长度自动计算。

练习 2

把 direction 初始值改成 -1,观察起始方向变化。

练习 3

新增一个 setAllOn() 函数,实现所有 LED 同时点亮。

练习 4

故意把数组长度写成 sizeof(LED_PINS),观察数组越界并解释原因。

7

下一章

03
NEXT · 第 03 章

GPIO 数字输入与输出

引脚模式、内部上拉、按键消抖、长按检测。搞懂 GPIO 就等于拿到了和真实世界对话的能力。

开始学习