模拟 I/O
模拟输入输出让 Arduino 能够处理连续变化的信号,如读取传感器数值或控制 LED 亮度。本章将详细介绍模拟 I/O 的原理和应用。
模拟 vs 数字
数字信号
- 只有两种状态:高电平(5V)或低电平(0V)
- 用于开关控制、数字通信
- 抗干扰能力强
模拟信号
- 可以在 0V 到 5V 之间连续变化
- 用于传感器读取、精细控制
- 更接近自然界的物理量
数字信号: 模拟信号:
│ ┌──┐ │ ╭─╮
5V ─┤ │ │ 5V ─┤ ╱ ╲
│ │ │ │ ╱ ╲
0V ─┴────┘ └── 0V ─┴─╱ ╲──
0 1 0 1 连续变化
模拟输入
Arduino Uno 有 6 个模拟输入引脚(A0-A5),使用 10 位 ADC(模数转换器),可以将 0-5V 电压转换为 0-1023 的数值。
analogRead() 函数
读取模拟引脚的电压值:
int value = analogRead(pin);
pin:模拟引脚编号(A0-A5,也可以直接用数字 0-5)- 返回值:0-1023(对应 0V-5V)
电压换算公式:
电压 = (返回值 / 1023) × 5V
读取电位器
电位器(可变电阻)是最基础的模拟输入器件:
硬件连接:
电位器引脚 1 ── 5V
电位器引脚 2 ── A0(中间引脚,信号输出)
电位器引脚 3 ── GND
代码:
const int POT_PIN = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int rawValue = analogRead(POT_PIN);
float voltage = rawValue * (5.0 / 1023.0);
Serial.print("原始值: ");
Serial.print(rawValue);
Serial.print(" | 电压: ");
Serial.print(voltage);
Serial.println(" V");
delay(100);
}
光敏电阻读取光线
光敏电阻(LDR)的阻值随光线强度变化:
硬件连接:
5V ── LDR ── A0 ── 10kΩ 电阻 ── GND
代码:
const int LDR_PIN = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int lightValue = analogRead(LDR_PIN);
// 将读数映射为亮度等级
int brightnessLevel = map(lightValue, 0, 1023, 0, 100);
Serial.print("光线强度: ");
Serial.print(brightnessLevel);
Serial.println("%");
delay(500);
}
读取温度传感器(LM35)
LM35 温度传感器输出电压与温度成正比(10mV/°C):
硬件连接:
LM35 VCC ── 5V
LM35 OUT ── A0
LM35 GND ── GND
代码:
const int TEMP_PIN = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int reading = analogRead(TEMP_PIN);
// 转换为电压
float voltage = reading * 5.0 / 1023.0;
// LM35: 10mV = 1°C
float temperature = voltage * 100;
Serial.print("温度: ");
Serial.print(temperature);
Serial.println(" °C");
delay(1000);
}
模拟输出(PWM)
Arduino 使用 PWM(脉冲宽度调制)模拟模拟输出。通过快速切换高低电平,改变平均电压。
PWM 原理
占空比 25%: 占空比 50%: 占空比 75%:
┌┐ ┌──┐ ┌───┐
││ │ │ │ │
│└──────── │ └──── │ └──
│ │ │
└─────────── └─────────── └────────
低亮度 中亮度 高亮度
analogWrite() 函数
输出 PWM 信号:
analogWrite(pin, value);
pin:PWM 引脚(D3、D5、D6、D9、D10、D11)value:0-255(0 表示完全关闭,255 表示完全开启)
LED 亮度控制
硬件连接:
Arduino D9 (PWM) ── LED 正极
└── 220Ω 电阻 ── GND
代码:
const int LED_PIN = 9;
void setup() {
// PWM 引脚不需要 pinMode 设置
}
void loop() {
// 渐亮
for (int i = 0; i <= 255; i++) {
analogWrite(LED_PIN, i);
delay(10);
}
// 渐暗
for (int i = 255; i >= 0; i--) {
analogWrite(LED_PIN, i);
delay(10);
}
}
电位器控制 LED 亮度
结合模拟输入和 PWM 输出:
const int POT_PIN = A0;
const int LED_PIN = 9;
void setup() {
Serial.begin(9600);
}
void loop() {
// 读取电位器值(0-1023)
int potValue = analogRead(POT_PIN);
// 映射到 PWM 范围(0-255)
int brightness = map(potValue, 0, 1023, 0, 255);
// 设置 LED 亮度
analogWrite(LED_PIN, brightness);
Serial.print("电位器: ");
Serial.print(potValue);
Serial.print(" | 亮度: ");
Serial.println(brightness);
delay(50);
}
控制舵机
舵机使用 PWM 信号控制角度(通常 0-180°):
#include <Servo.h>
Servo myServo;
const int SERVO_PIN = 9;
const int POT_PIN = A0;
void setup() {
myServo.attach(SERVO_PIN);
Serial.begin(9600);
}
void loop() {
int potValue = analogRead(POT_PIN);
// 将 0-1023 映射到 0-180 度
int angle = map(potValue, 0, 1023, 0, 180);
myServo.write(angle);
Serial.print("角度: ");
Serial.println(angle);
delay(15); // 给舵机足够时间转动
}
综合示例:自动夜灯
根据环境光线自动调节 LED 亮度:
const int LDR_PIN = A0;
const int LED_PIN = 9;
// 校准值(根据实际环境调整)
const int DARK_THRESHOLD = 300; // 低于此值认为是黑暗
const int BRIGHT_THRESHOLD = 800; // 高于此值认为是明亮
void setup() {
Serial.begin(9600);
}
void loop() {
int lightLevel = analogRead(LDR_PIN);
// 根据光线强度计算 LED 亮度
// 环境越暗,LED 越亮
int ledBrightness;
if (lightLevel <= DARK_THRESHOLD) {
ledBrightness = 255; // 全亮
} else if (lightLevel >= BRIGHT_THRESHOLD) {
ledBrightness = 0; // 关闭
} else {
// 线性映射
ledBrightness = map(lightLevel, DARK_THRESHOLD, BRIGHT_THRESHOLD, 255, 0);
}
analogWrite(LED_PIN, ledBrightness);
Serial.print("光线: ");
Serial.print(lightLevel);
Serial.print(" | LED亮度: ");
Serial.println(ledBrightness);
delay(100);
}
综合示例:简易温度计
使用 LM35 和多个 LED 显示温度范围:
const int TEMP_PIN = A0;
const int LED_COLD = 8; // 蓝色 LED - 低温
const int LED_NORM = 9; // 绿色 LED - 正常
const int LED_HOT = 10; // 红色 LED - 高温
void setup() {
Serial.begin(9600);
pinMode(LED_COLD, OUTPUT);
pinMode(LED_NORM, OUTPUT);
pinMode(LED_HOT, OUTPUT);
}
void loop() {
float temperature = readTemperature();
// 根据温度点亮不同 LED
if (temperature < 20) {
digitalWrite(LED_COLD, HIGH);
digitalWrite(LED_NORM, LOW);
digitalWrite(LED_HOT, LOW);
} else if (temperature < 30) {
digitalWrite(LED_COLD, LOW);
digitalWrite(LED_NORM, HIGH);
digitalWrite(LED_HOT, LOW);
} else {
digitalWrite(LED_COLD, LOW);
digitalWrite(LED_NORM, LOW);
digitalWrite(LED_HOT, HIGH);
}
Serial.print("温度: ");
Serial.print(temperature);
Serial.println(" °C");
delay(1000);
}
float readTemperature() {
int reading = analogRead(TEMP_PIN);
float voltage = reading * 5.0 / 1023.0;
return voltage * 100;
}
高级技巧
平滑传感器读数
使用移动平均减少传感器噪声:
const int SENSOR_PIN = A0;
const int NUM_SAMPLES = 10;
void setup() {
Serial.begin(9600);
}
void loop() {
int average = getSmoothedReading();
Serial.print("平滑值: ");
Serial.println(average);
delay(100);
}
int getSmoothedReading() {
long sum = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
sum += analogRead(SENSOR_PIN);
delayMicroseconds(100); // 短暂延时让 ADC 稳定
}
return sum / NUM_SAMPLES;
}
改变 PWM 频率
Arduino 默认 PWM 频率约为 490Hz(D5、D6 为 980Hz),某些应用需要调整:
// 注意:这会改变定时器设置,可能影响其他功能
// 设置 D9、D10 的 PWM 频率为 62.5kHz
void setup() {
// 将 Timer1 设置为模式 5(快速 PWM,8位)
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
TCCR1B = _BV(CS10) | _BV(WGM12); // 无预分频
}
void loop() {
analogWrite(9, 128); // D9 输出 50% 占空比
analogWrite(10, 64); // D10 输出 25% 占空比
}
常见问题
1. 模拟读数不稳定
原因:
- 电源噪声
- 传感器信号弱
- ADC 参考电压不稳定
解决方法:
- 添加去耦电容(0.1μF)
- 使用移动平均算法
- 使用外部参考电压
2. PWM 输出有噪音
原因:
- PWM 频率过低,能听到蜂鸣声
- 负载电流过大
解决方法:
- 提高 PWM 频率
- 添加滤波电路
- 使用驱动电路
3. 模拟输入精度不够
Arduino 的 10 位 ADC 提供约 4.9mV 的分辨率。如需更高精度:
- 使用外部 ADC 芯片(如 ADS1115,16 位)
- 使用运算放大器放大信号
- 降低参考电压提高分辨率
下一步
掌握了模拟 I/O 后,我们将学习串口通信,这是 Arduino 与电脑、其他设备交互的重要方式。