造物笔记/嘉立创EDA 教程(原立创EDA)
基于 嘉立创EDA专业版 V4.1.93
造物笔记 / 嘉立创EDA 教程 / 第 1 章 · Quickstart 实战

B-5 写程序与烧录

板子做好了,该让它“活”起来了。这一页用 Arduino IDE 2 给 ESP32-C3 写三个小程序:彩虹渐变、按 BOOT 键切换模式、手机连 Wi-Fi 用网页调颜色。还没拿到板子也没关系,先在网页版模拟器 Wokwi 里玩。

⏱ 约 60 分钟🖱️ 含彩灯效果模拟💻 完整代码 + 逐行中文注释🔧 含排错表

B5.1 拿到板子,先做两个检查#

  1. 看:模组、彩灯、Type-C 有没有歪、有没有明显连锡;排针焊好了没有(不焊也能先用)。
  2. 插电:用 USB-C 线接到电脑。红色电源指示灯 LED4 应该亮。不亮就先别往下走,看本页最后的排错表。
  3. 摸一下稳压芯片 U2:常温或微温是正常的;烫手马上拔线。

B5.2 安装 Arduino IDE 2 和 ESP32 支持包#

  1. 打开 Arduino 官网 arduino.cc/en/software,下载 Arduino IDE 2.x:选 macOS 版(Apple 芯片的 Mac 选 Apple Silicon,老款 Intel Mac 选 Intel)选 Windows 版(安装包 .exe 或 .msi)。
  2. 打开下载的 .dmg,把 Arduino IDE 拖进「应用程序」文件夹。第一次打开时 macOS 会问“是否确定要打开”,点「打开」。
  3. 界面默认是英文,可以在「Arduino IDE」→「Settings…」(⌘,)里把 Language 改成中文,重启生效。
  4. 点左侧边栏的「开发板管理器」图标(或菜单「工具」→「开发板」→「开发板管理器…」),搜索 esp32。
  5. 找到「esp32 by Espressif Systems」(注意不是“Arduino ESP32 Boards”那一个),点「安装」,选最新的 3.x 版本。文件比较大,要等几分钟。

再装一个控制彩灯的库:

  1. 点左侧边栏的「库管理」图标(或「工具」→「管理库…」),搜索 Adafruit NeoPixel,点「安装」。

B5.3 连接板子,选开发板和端口#

  1. 用能传数据的 USB-C 线把板子接到电脑。
  2. 「工具」→「开发板」→「esp32」→ 选「ESP32C3 Dev Module」。
  3. 「工具」→「USB CDC On Boot」→ 选「Enabled」。
  4. 「工具」→「端口」→ 选名字像 /dev/cu.usbmodem… 的那个。
「工具」菜单里要关注的几项
编号选项含义(大白话)本例什么时候改
1开发板告诉 Arduino 你的板子用的是哪种芯片ESP32C3 Dev Module换别的芯片时
2USB CDC On Boot让程序里的 Serial(串口打印)走芯片自带的 USB。我们没有 USB 转串口芯片,必须开,否则串口监视器看不到任何输出Enabled一直开着
3Flash Size模组里 Flash 的大小。ESP32-C3-MINI-1-N4 的 N4 就是 4 MB4MB(默认)—
4端口板子在电脑上的“门牌号”。ESP32-C3 用自带 USB 时,在 Mac 上一般叫 /dev/cu.usbmodem 加一串数字在 Windows 上叫 COM3、COM5 这样的名字(可以在“设备管理器 → 端口(COM 和 LPT)”里看到)选它插拔后号码可能变,重新选
5其他(CPU Frequency、Upload Speed……)保持默认默认—

B5.4 程序 1:彩虹渐变#

「文件」→「新建项目」,把里面的内容全部删掉,粘贴下面的代码,点工具栏的「上传」(→ 箭头按钮,快捷键 ⌘U)。第一次编译比较慢,耐心等一下。下方输出窗口出现 “Hard resetting…”/“Done” 之类的字样就说明上传成功了。

// 程序 1:3 颗 RGB 灯彩虹渐变
#include <Adafruit_NeoPixel.h>        // 引入 Adafruit NeoPixel 库(控制 WS2812B 灯)

#define LED_PIN    8                  // 彩灯的数据线接在 GPIO8(原理图里的 RGB_DIN)
#define LED_COUNT  3                  // 一共串了 3 颗灯

// 创建一个“灯带”对象:3 颗灯、接 GPIO8、颜色顺序 GRB、800 kHz 通信速度(WS2812B 的标准设置)
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

uint16_t hue = 0;                     // 当前色相:0~65535 转一圈(红→黄→绿→青→蓝→紫→红)

void setup() {                        // setup() 只在上电 / 复位时运行一次
  Serial.begin(115200);               // 打开串口,速度 115200(串口监视器里要选同样的速度)
  strip.begin();                      // 初始化灯带
  strip.setBrightness(40);            // 整体亮度 0~255。40 已经很亮,也更省电、不烫
  strip.show();                       // 把“全部熄灭”的状态发出去
  Serial.println("彩虹程序启动");      // 在串口监视器里打印一句话,确认程序在跑
}

void loop() {                         // loop() 会一遍又一遍地循环运行
  for (int i = 0; i < LED_COUNT; i++) {                 // 依次处理第 0、1、2 颗灯
    uint16_t h = hue + i * 65536L / LED_COUNT;          // 每颗灯的色相错开 1/3 圈
    uint32_t color = strip.ColorHSV(h);                 // 把色相换算成 RGB 颜色(饱和度、亮度默认最大)
    strip.setPixelColor(i, strip.gamma32(color));       // gamma32 让颜色过渡在人眼看来更均匀
  }
  strip.show();                       // 把 3 颗灯的新颜色一次性发出去,灯才会变
  hue += 256;                         // 色相往前走一小步(加到 65535 后自动回到 0)
  delay(20);                          // 等 20 毫秒:每秒约 50 帧,转一圈约 5 秒
}

上传完成后三颗灯会开始彩虹渐变。点右上角的「串口监视器」(放大镜图标),波特率选 115200,能看到“彩虹程序启动”(按一下 RST 会重新打印一次)。

彩灯效果模拟(对应程序 1 和程序 2)
模式 0 就是程序 1 的彩虹效果;程序 2 会按 BOOT 键在 4 个模式之间循环。网页里的颜色只是模拟,实物灯的颜色和亮度会有差别。

B5.5 程序 2:按 BOOT 键切换模式#

BOOT 键接在 GPIO9:松开是 HIGH、按下是 LOW(B2.5 的上拉电阻)。程序启动后再按它,它就是一个普通的用户按键。

// 程序 2:按 BOOT 键切换 4 种模式(彩虹 / 呼吸 / 单色 / 关灯),带按键消抖
#include <Adafruit_NeoPixel.h>

#define LED_PIN    8                  // 彩灯数据线:GPIO8
#define LED_COUNT  3                  // 3 颗灯
#define BTN_PIN    9                  // BOOT 键接 GPIO9:松开 = HIGH,按下 = LOW

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

const int MODE_COUNT = 4;             // 一共 4 种模式
int mode = 0;                         // 当前模式:0 彩虹、1 呼吸、2 单色、3 关灯

// ---- 消抖用的变量 ----
// 机械按键按下的一瞬间,触点会“弹”几下,电平在几毫秒内来回跳。
// 不处理的话按一次可能被当成按了好几次,所以要等电平稳定一段时间再认。
int lastReading = HIGH;               // 上一次读到的原始电平
int stableState = HIGH;               // 确认稳定后的电平
unsigned long lastChange = 0;         // 原始电平最近一次变化的时刻(毫秒)
const unsigned long DEBOUNCE_MS = 30; // 稳定超过 30 毫秒才算数

uint16_t hue = 0;                     // 彩虹模式用的色相

void setup() {
  Serial.begin(115200);               // 打开串口
  pinMode(BTN_PIN, INPUT_PULLUP);     // GPIO9 设为输入,并打开芯片内部的上拉电阻
  strip.begin();                      // 初始化灯带
  strip.setBrightness(40);            // 亮度 40/255
  strip.show();                       // 先全部熄灭
}

// 每次调用检查一下按键:只有“确认按下”的那一刻返回 true,按住不放也只算一次
bool buttonPressed() {
  int reading = digitalRead(BTN_PIN);           // 读引脚当前的电平
  if (reading != lastReading) {                 // 和上次不一样:可能在抖动,重新计时
    lastChange = millis();                      // millis() = 开机到现在过了多少毫秒
    lastReading = reading;
  }
  if (millis() - lastChange > DEBOUNCE_MS && reading != stableState) {
    stableState = reading;                      // 稳定了 30 毫秒,确认电平真的变了
    if (stableState == LOW) return true;        // 变成 LOW = 刚刚按下
  }
  return false;                                 // 其余情况:没有新的按下动作
}

void showRainbow() {                            // 模式 0:彩虹(同程序 1)
  for (int i = 0; i < LED_COUNT; i++) {
    uint16_t h = hue + i * 65536L / LED_COUNT;
    strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(h)));
  }
  hue += 256;
}

void showBreath() {                             // 模式 1:呼吸
  float t = millis() / 1000.0;                  // 当前时间(秒)
  uint8_t v = (sin(t * 2.0) + 1.0) * 0.5 * 255; // 正弦曲线:亮度在 0~255 之间慢慢起伏
  uint32_t c = strip.ColorHSV(43690, 230, v);   // 色相 43690 ≈ 蓝色,饱和度 230,亮度 v
  strip.fill(strip.gamma32(c));                 // 3 颗灯填同一个颜色
}

void showSolid() {                              // 模式 2:单色常亮
  strip.fill(strip.Color(255, 80, 0));          // Color(红, 绿, 蓝),这里是橙色 🎨 换成你喜欢的
}

void showOff() {                                // 模式 3:关灯
  strip.clear();                                // 所有灯设为黑色(熄灭)
}

void loop() {
  if (buttonPressed()) {                        // 检测到一次按下
    mode = (mode + 1) % MODE_COUNT;             // 切到下一个模式,3 之后回到 0
    Serial.printf("切换到模式 %d\n", mode);      // 串口打印当前模式
  }
  switch (mode) {                               // 按当前模式计算灯的颜色
    case 0: showRainbow(); break;
    case 1: showBreath();  break;
    case 2: showSolid();   break;
    default: showOff();    break;
  }
  strip.show();                                 // 发出去
  delay(20);                                    // 20 毫秒一帧(不影响按键:消抖靠 millis 计时)
}

B5.6 🎨 程序 3(选做):手机连 Wi-Fi,用网页调颜色#

这个程序让板子自己开一个 Wi-Fi 热点。用手机连上它,打开一个网页,拖动取色器,彩灯就跟着变色:

// 程序 3(选做):板子开一个 Wi-Fi 热点,手机连上后打开网页调颜色
#include <WiFi.h>                     // Wi-Fi 库(esp32 支持包自带)
#include <WebServer.h>                // 简单网页服务器库(esp32 支持包自带)
#include <Adafruit_NeoPixel.h>

#define LED_PIN    8
#define LED_COUNT  3

const char* AP_SSID = "ESP32C3-LED";  // 热点名字 🎨 可以改,建议只用英文和数字
const char* AP_PASS = "12345678";     // 热点密码,至少 8 位

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
WebServer server(80);                 // 在 80 端口(网页默认端口)开一个服务器

// 手机上看到的网页:一个取色器,拖动时把颜色发给板子
const char PAGE[] = R"rawliteral(
<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>彩灯板调色</title>
<style>body{font-family:sans-serif;text-align:center;padding-top:40px}
input{width:220px;height:220px;border:none;background:none}</style></head>
<body><h2>ESP32-C3 彩灯板</h2>
<input type="color" id="c" value="#ff8000">
<p id="s">点上面的色块选颜色</p>
<script>
const c = document.getElementById('c');
c.addEventListener('input', () => {
  fetch('/set?c=' + c.value.substring(1))
    .then(r => r.text())
    .then(t => document.getElementById('s').textContent = t);
});
</script></body></html>
)rawliteral";

void handleRoot() {                   // 有人打开 http://192.168.4.1/ 时运行
  server.send(200, "text/html; charset=utf-8", PAGE);   // 200 = 成功,把网页发回去
}

void handleSet() {                    // 网页请求 /set?c=ff8000 时运行
  if (!server.hasArg("c")) {          // 没带颜色参数
    server.send(400, "text/plain", "missing c");
    return;
  }
  String hex = server.arg("c");                     // 取出 "ff8000" 这样的 6 位十六进制
  long rgb = strtol(hex.c_str(), NULL, 16);         // 十六进制字符串 → 数字
  uint8_t r = (rgb >> 16) & 0xFF;                   // 拆出红
  uint8_t g = (rgb >> 8) & 0xFF;                    // 拆出绿
  uint8_t b = rgb & 0xFF;                           // 拆出蓝
  strip.fill(strip.Color(r, g, b));                 // 3 颗灯都设成这个颜色
  strip.show();
  server.send(200, "text/plain; charset=utf-8", "已设置 #" + hex);  // 告诉网页“设好了”
}

void setup() {
  Serial.begin(115200);
  strip.begin();
  strip.setBrightness(60);
  strip.show();

  WiFi.mode(WIFI_AP);                 // 让板子当“热点”(AP 模式)
  WiFi.softAP(AP_SSID, AP_PASS);      // 开启热点
  Serial.print("热点已开启:");
  Serial.println(AP_SSID);
  Serial.print("手机连上后,用浏览器打开 http://");
  Serial.println(WiFi.softAPIP());    // 热点模式下板子的地址,一般是 192.168.4.1

  server.on("/", handleRoot);         // 访问首页 → handleRoot
  server.on("/set", handleSet);       // 访问 /set → handleSet
  server.begin();                     // 启动网页服务器
}

void loop() {
  server.handleClient();              // 不停地检查有没有手机发来请求
}
  1. 上传程序后,打开串口监视器(115200),按一下 RST,能看到热点名和地址。
  2. 手机打开 Wi-Fi 设置,连接 ESP32C3-LED,密码 12345678。手机可能提示“此网络无法访问互联网”,选择保持连接。
  3. 手机浏览器打开 http://192.168.4.1(以串口监视器里打印的地址为准),点色块选颜色。

B5.7 下载模式:按住 BOOT 再按一下 RST 是在干什么#

大多数时候,Arduino 会通过 USB 自动让芯片进入下载状态,你什么都不用按。但如果遇到下面这些情况,就需要手动进入下载模式:

  • 端口列表里找不到板子;
  • 上传时报错,比如 “Failed to connect” 、“No serial data received”;
  • 之前烧进去的程序有问题,导致 USB 一直不正常、板子反复重启。
  1. 按住 BOOT 键不放
  2. 按一下 RST 键(按下、松开)
  3. 松开 BOOT 键
  4. 重新在「工具」→「端口」里选一下端口(号码可能变了),再点上传
  5. 上传完成后,按一下 RST 让芯片正常启动、运行新程序

B5.8 💡 没拿到板子?先在 Wokwi 里玩#

Wokwi 是一个免费的网页版电子电路模拟器,支持 ESP32-C3 和 WS2812B 灯,打开浏览器就能跑 Arduino 代码,不用装任何东西:

  1. 打开 wokwi.com,新建一个项目,开发板选 ESP32-C3,语言选 Arduino。
  2. 点「+」添加元件,搜索 NeoPixel(WS2812),加 3 颗(或者一条 3 颗的灯带),把第一颗的 DIN 接到开发板的 GPIO8,电源和 GND 接好,灯与灯之间 DOUT → DIN 串起来。
  3. 在 Library Manager(库管理)里添加 Adafruit NeoPixel。
  4. 把程序 1 粘贴进代码区,点绿色的运行按钮。
  5. 想试程序 2:再加一个按键(pushbutton),一端接 GPIO9,另一端接 GND。

B5.9 排错表#