Chapter 04 · Arduino 入门

模拟 IO 与 PWM

用 ADC 读取连续电压,用 PWM 模拟"半亮",把电位器旋钮变成调光器,做一个会呼吸的 LED。

课程进度33%

关键概念

模拟信号 vs 数字信号
数字信号只有 0/1 两态(如开关);模拟信号在一定范围内连续变化(如温度、光照、旋钮角度)。现实世界大多是模拟量,单片机要处理它们就需要 ADC 把电压转成数字。
ADC(模数转换器)
Analog-to-Digital Converter。把 0~5V 连续电压量化成整数。Uno 的 ADC 是 10 位,即把电压均分成 2^10=1024 级,输出 0~1023。分辨率 = 5V/1024 ≈ 4.9mV,即每 4.9mV 电压变化会让读数加 1。
analogRead(pin)
读取模拟输入引脚(A0~A5)的电压,返回 0~1023 的整数。0 对应 0V,1023 对应参考电压(默认 5V)。仅模拟引脚支持。一次转换约需 100 微秒。
PWM(脉冲宽度调制)
Pulse Width Modulation。数字引脚无法真正输出 2.5V,但可以极快地在 0V/5V 之间反复切换。若一个周期里有一半时间是高电平,平均效果就相当于 2.5V。通过调节"高电平占比"(占空比),就能模拟出连续的"电压",用于调光、调速。
占空比 (Duty Cycle)
一个 PWM 周期内高电平时间所占的百分比。0% = 一直低(全灭),100% = 一直高(全亮),50% = 半亮。analogWrite 用 0~255 表示占空比,128 约等于 50%。
analogWrite(pin, value)
在支持 PWM 的引脚(Uno 上带 ~ 号:D3/D5/D6/D9/D10/D11)输出指定占空比的方波,value 范围 0~255(8 位)。注意它不是真正的模拟输出(除非芯片带 DAC),而是 PWM。名字里有 analog 只是沿用直觉。
map() 函数
数值区间线性映射。map(value, fromLow, fromHigh, toLow, toHigh)。例如把 analogRead 的 0~1023 映射到 analogWrite 的 0~255:map(v, 0, 1023, 0, 255)。它做整数运算、会截断小数。是连接"读传感器"和"控输出"的桥梁。
参考电压 (AREF)
ADC 判断"满量程 1023"对应多少伏。默认用 5V 供电电压(DEFAULT)。也可切成内部 1.1V(INTERNAL)以提高小信号分辨率,或外接 AREF 引脚电压。用 analogReference() 设置。测微弱信号时改小参考电压能提升精度。

电位器接线与读取

电位器(可变电阻)是最直观的模拟输入。三个脚:两端接 5V 和 GND,中间(滑片)接 A0。旋钮转动改变分压,A0 读到 0~5V。

电位器接线(分压器原理) 5V │ ┃ 电位器左端 ┌───┸───┐ │ │◄── 滑片(中间脚) ──○ Arduino A0 │ 10kΩ │ (随旋钮在 0~5V 间变化) └───┰───┘ ┃ 电位器右端 │ GND 旋到最左:A0 ≈ 0V → analogRead ≈ 0 旋到中间:A0 ≈ 2.5V → analogRead ≈ 512 旋到最右:A0 ≈ 5V → analogRead ≈ 1023
// 读电位器,串口打印原始值和对应电压
const int potPin = A0;

void setup() {
  Serial.begin(9600);
  // 模拟输入引脚不必调用 pinMode,默认即为输入
}

void loop() {
  int raw = analogRead(potPin);        // 0~1023
  float volts = raw * (5.0 / 1023.0); // 换算成电压

  Serial.print("raw=");
  Serial.print(raw);
  Serial.print("  volts=");
  Serial.println(volts, 2);       // 保留 2 位小数

  delay(200);
}

PWM 是怎么"骗"出亮度的

同一引脚,不同占空比的平均电压 占空比 25% (value≈64): 5V ─┐ ┌─┐ ┌─┐ ┌ └────┘ └────┘ └────┘ 平均 ≈ 1.25V → 微亮 占空比 50% (value=128): 5V ──┐ ┌──┐ ┌──┐ ┌── └──┘ └──┘ └──┘ 平均 ≈ 2.5V → 半亮 占空比 75% (value≈191): 5V ────┐┌────┐┌────┐┌── └┘ └┘ └┘ 平均 ≈ 3.75V → 较亮 Uno 的 PWM 频率约 490Hz(部分引脚 980Hz),切换太快, 人眼看到的是稳定的平均亮度,感觉不到闪烁。

电位器调光:读 ADC → 控 PWM

把电位器接 A0,LED(串 330Ω)接 D9(带 ~,支持 PWM)。旋钮控制亮度:

// 旋电位器调节 LED 亮度
const int potPin = A0;
const int ledPin = 9;   // 必须是 ~ 引脚

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  int raw = analogRead(potPin);           // 0~1023
  // 把 ADC 的 0~1023 映射到 PWM 的 0~255
  int bright = map(raw, 0, 1023, 0, 255);
  analogWrite(ledPin, bright);         // 输出对应亮度
}
为什么不直接除以 4?

1023 / 255 ≈ 4.01,用 raw / 4 也大致可行,但 map() 更清晰、能自动处理边界(保证满旋对应 255),且换其他区间时不用重算除数。养成用 map 表达"区间对应关系"的习惯,代码可读性更好。

呼吸灯:让 LED 缓慢明灭

用 for 循环把亮度从 0 加到 255 再减回来,看起来就像呼吸。这是 PWM 的经典 demo:

// 呼吸灯 —— 亮度渐入渐出
const int ledPin = 9;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // 渐亮:0 → 255
  for (int b = 0; b <= 255; b++) {
    analogWrite(ledPin, b);
    delay(8);   // 每级停 8ms,整个渐亮约 2 秒
  }
  // 渐灭:255 → 0
  for (int b = 255; b >= 0; b--) {
    analogWrite(ledPin, b);
    delay(8);
  }
}
亮度感知是非线性的

人眼对亮度的感受接近对数关系:从 0 到 50 的变化看起来很明显,从 200 到 255 却几乎看不出。所以线性的 analogWrite(0→255) 呼吸灯,会觉得"暗处变化快、亮处变化慢"。追求平滑可用 gamma 校正表(把线性值映射为指数曲线),属于进阶技巧。

digitalWrite vs analogWrite 对比

特性digitalWriteanalogWrite (PWM)
输出值HIGH / LOW(两态)0 ~ 255(占空比)
本质恒定高/低电平快速方波,调占空比
适用引脚任意数字引脚仅带 ~ 的 PWM 引脚
典型用途开关灯、继电器调光、调速、蜂鸣器音调
是真模拟吗否,是平均效果模拟
PWM 引脚别乱选

没有 ~ 号的引脚调用 analogWrite,Arduino 会退化成:value < 128 输出 LOW,≥128 输出 HIGH,只有全灭/全亮两态,无法调光。若发现"呼吸灯只会突然亮灭",八成是把 LED 接到了非 PWM 引脚。Uno 支持 PWM 的是 D3、D5、D6、D9、D10、D11。

本章小结

现实世界多是连续的模拟量,单片机靠 ADC 把电压量化成数字:Uno 的 ADC 是 10 位,analogRead(A0~A5) 把 0~5V 转成 0~1023(分辨率约 4.9mV),默认参考电压 5V,可用 analogReference() 改成内部 1.1V 提升小信号精度。电位器是最典型的模拟输入(分压原理)。反过来,数字引脚不能真输出中间电压,但可用 PWM——极快地在 0V/5V 间切换,靠调节高电平占比(占空比)模拟出平均电压。analogWrite(pin, 0~255) 在带 ~ 的 PWM 引脚(D3/D5/D6/D9/D10/D11)输出方波,0=全灭、255=全亮、128≈半亮;对非 PWM 引脚调用只会退化成开/关两态。map(v, 0,1023, 0,255) 把 ADC 读数线性映射到 PWM 区间,是"读传感器控输出"的桥梁。呼吸灯用循环让占空比 0↔255 往复即可;注意人眼亮度感知非线性,追求平滑需 gamma 校正。

⚒️ 配套开发者工具箱 · 17 款免费在线工具