GPIO(通用输入输出)¶
GPIO 是 Arduino 与外部世界交互的基础,通过数字和模拟引脚实现对传感器的读取和对执行器的控制。掌握 GPIO 是 Arduino 开发的第一步。
一、Arduino UNO 引脚分布¶
+-----[USB]-----+
| |
NC | [ ] SCL [ ] | A5 ← I2C 时钟 / 模拟输入
IOREF | [ ] SDA [ ] | A4 ← I2C 数据 / 模拟输入
RESET | [ ] [ ] | A3 ← 模拟输入
3.3V | [ ] [ ] | A2 ← 模拟输入
5V | [ ] [ ] | A1 ← 模拟输入
GND | [ ] [ ] | A0 ← 模拟输入
GND | [ ] |
VIN | [ ] [ ] | D13 ← SCK / LED_BUILTIN
| [ ] | D12 ← MISO
D0 | [ ] RX [ ] | D11 ← MOSI / PWM~
D1 | [ ] TX [ ] | D10 ← SS / PWM~
D2 | [ ] INT0 [ ] | D9 ← PWM~
D3 | [ ]~INT1 [ ] | D8
D4 | [ ] [ ] | D7
D5 | [ ]~ [ ] | D6 ← PWM~
+---------------+
~ = PWM 引脚
引脚分类
| 类型 | 引脚 | 功能 |
|---|---|---|
| 数字 IO | D0~D13 | 输入/输出,HIGH(5V) 或 LOW(0V) |
| PWM 输出 | D3, D5, D6, D9, D10, D11(带 ~ 标记) | 8 位 PWM(0~255) |
| 模拟输入 | A0~A5 | 10 位 ADC(0~1023) |
| 串口 | D0(RX), D1(TX) | 硬件 UART |
| SPI | D10(SS), D11(MOSI), D12(MISO), D13(SCK) | SPI 总线 |
| I2C | A4(SDA), A5(SCL) | I2C 总线 |
| 外部中断 | D2(INT0), D3(INT1) | 边沿触发中断 |
二、数字输出¶
引脚模式设置¶
使用 pinMode() 配置引脚方向:
LED 控制示例¶
const int LED_PIN = 8; // 外接 LED 到 D8
void setup() {
pinMode(LED_PIN, OUTPUT); // 设置为输出模式
}
void loop() {
digitalWrite(LED_PIN, HIGH); // 输出高电平(5V),LED 亮
delay(500);
digitalWrite(LED_PIN, LOW); // 输出低电平(0V),LED 灭
delay(500);
}
电流限制
- 单个 IO 口最大输出电流:40mA(建议不超过 20mA)
- 全部 IO 口总电流不超过 200mA
- 驱动大功率负载(电机、继电器)需要使用==三极管或 MOS 管==做驱动
接线图(LED)¶
电阻计算
实际选择 220Ω 或 330Ω 的标准电阻即可。
三、数字输入¶
按键读取¶
const int BTN_PIN = 2; // 按键接 D2
const int LED_PIN = 13; // LED 接 D13
void setup() {
pinMode(BTN_PIN, INPUT_PULLUP); // 内部上拉,按下为 LOW
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int state = digitalRead(BTN_PIN); // 读取按键状态
if (state == LOW) { // 按键按下(低电平)
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
}
上拉与下拉¶
graph LR
subgraph "INPUT_PULLUP(推荐)"
VCC1[5V] --> R1[内部上拉<br>20~50KΩ]
R1 --> PIN1[IO 引脚]
PIN1 --> BTN1[按键]
BTN1 --> GND1[GND]
end
subgraph "外部下拉"
VCC2[5V] --> BTN2[按键]
BTN2 --> PIN2[IO 引脚]
PIN2 --> R2[外部下拉<br>10KΩ]
R2 --> GND2[GND]
end
| 模式 | 未按下 | 按下 | 优点 |
|---|---|---|---|
INPUT_PULLUP |
HIGH | LOW | 不需要外部电阻 |
INPUT + 外部下拉 |
LOW | HIGH | 逻辑直观 |
INPUT(无电阻) |
浮空不定 | 不确定 | ❌ 不推荐 |
浮空问题
如果使用 INPUT 模式但引脚悬空(没有连接任何电路),读取值会在 HIGH 和 LOW 之间==随机跳变==,这叫做浮空。
解决方法:使用 INPUT_PULLUP 或外接上拉/下拉电阻。
按键去抖¶
机械按键按下时会产生==抖动==(约 5~20ms 内反复跳变),需要软件去抖:
const int BTN_PIN = 2;
const int LED_PIN = 13;
bool ledState = false;
bool lastBtnState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50; // 去抖时间 50ms
void setup() {
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
bool reading = digitalRead(BTN_PIN);
if (reading != lastBtnState) {
lastDebounceTime = millis(); // 重置去抖计时
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
static bool btnState = HIGH;
if (reading != btnState) {
btnState = reading;
if (btnState == LOW) { // 检测到按下沿
ledState = !ledState; // 切换 LED 状态
digitalWrite(LED_PIN, ledState);
}
}
}
lastBtnState = reading;
}
去抖替代方案
- 硬件去抖:按键并联 100nF 电容
- Bounce2 库:
#include <Bounce2.h>,封装好的去抖库,代码更简洁
四、模拟输入(ADC)¶
Arduino UNO 内置 10 位 ADC,分辨率为 1024 级(0~1023)。
基本原理¶
其中 \(V_{ref}\) 默认为 5V。
| 参数 | UNO R3 | Mega 2560 | Nano 33 BLE |
|---|---|---|---|
| ADC 位数 | 10 bit | 10 bit | 12 bit |
| 通道数 | 6 (A0~A5) | 16 (A0~A15) | 8 |
| 参考电压 | 5V / 1.1V | 5V / 1.1V / 2.56V | 3.3V |
| 采样速率 | ~10kHz | ~10kHz | ~200kHz |
读取电位器¶
const int POT_PIN = A0; // 电位器接 A0
void setup() {
Serial.begin(115200);
}
void loop() {
int raw = analogRead(POT_PIN); // 读取 ADC 原始值(0~1023)
float voltage = raw * (5.0 / 1023.0); // 转换为电压值
Serial.print("ADC: ");
Serial.print(raw);
Serial.print(" 电压: ");
Serial.print(voltage, 2); // 保留 2 位小数
Serial.println(" V");
delay(100);
}
接线图(电位器)¶
参考电压设置¶
// 默认参考电压(5V)
analogReference(DEFAULT);
// 内部 1.1V 参考(更高精度测小电压)
analogReference(INTERNAL);
// 外部参考电压(接 AREF 引脚)
analogReference(EXTERNAL);
切换参考电压
切换 analogReference() 后,前几次 analogRead() 的值可能不准确,建议丢弃前几次读数。
五、PWM 输出(模拟输出)¶
Arduino 的 analogWrite() 实际上输出的是 PWM 信号(脉冲宽度调制),而非真正的模拟电压。
PWM 原理¶
PWM 参数¶
| 引脚 | 定时器 | 默认频率 | 用途 |
|---|---|---|---|
| D5, D6 | Timer0 | 976 Hz | ⚠️ 修改会影响 millis()/delay() |
| D9, D10 | Timer1 | 490 Hz | 通用 PWM |
| D3, D11 | Timer2 | 490 Hz | 通用 PWM |
Timer0 特殊性
Timer0 被 Arduino 核心用于 millis()、micros()、delay() 等计时函数。修改 Timer0 的频率会导致这些函数计时不准确!
LED 呼吸灯¶
const int LED_PIN = 9; // 选择 PWM 引脚
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// 渐亮
for (int i = 0; i <= 255; i++) {
analogWrite(LED_PIN, i);
delay(5);
}
// 渐灭
for (int i = 255; i >= 0; i--) {
analogWrite(LED_PIN, i);
delay(5);
}
}
PWM 控制电机速度¶
const int MOTOR_PIN = 9; // 电机驱动 PWM 输入
const int DIR_PIN = 8; // 电机方向控制
const int POT_PIN = A0; // 电位器控制速度
void setup() {
pinMode(MOTOR_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
digitalWrite(DIR_PIN, HIGH); // 设置旋转方向
}
void loop() {
int potVal = analogRead(POT_PIN); // 0~1023
int speed = map(potVal, 0, 1023, 0, 255); // 映射到 0~255
analogWrite(MOTOR_PIN, speed);
delay(10);
}
map() 函数
map(value, fromLow, fromHigh, toLow, toHigh) 是 Arduino 内置的线性映射函数,非常实用。注意它只做==整数运算==,需要浮点映射时要自己计算。
六、实用技巧¶
多路 ADC 快速读取¶
const int NUM_CHANNELS = 4;
const int channels[] = {A0, A1, A2, A3};
int values[NUM_CHANNELS];
void readAllADC() {
for (int i = 0; i < NUM_CHANNELS; i++) {
values[i] = analogRead(channels[i]);
}
}
引脚状态批量控制(端口操作)¶
// 直接操作端口寄存器,比 digitalWrite 快约 50 倍
// PORTD 控制 D0~D7,PORTB 控制 D8~D13
void setup() {
DDRD = B11111100; // D2~D7 设为输出(D0、D1 保留给串口)
}
void loop() {
PORTD = B10101100; // 同时设置 D2~D7 的输出状态
delay(500);
PORTD = B01010100;
delay(500);
}
端口操作注意
- 端口操作虽然快,但可移植性差(不同板子端口映射不同)
- D0、D1 是串口引脚,操作 PORTD 时注意不要影响串口通信
- 建议只在对速度有极高要求时使用
constrain() 限幅¶
七、常用函数速查¶
| 函数 | 功能 | 返回值 |
|---|---|---|
pinMode(pin, mode) |
设置引脚模式 | void |
digitalWrite(pin, val) |
数字输出 HIGH/LOW | void |
digitalRead(pin) |
数字输入 | HIGH 或 LOW |
analogRead(pin) |
模拟输入 | 0~1023(10bit) |
analogWrite(pin, val) |
PWM 输出 | void |
analogReference(type) |
设置 ADC 参考电压 | void |
map(val, fL, fH, tL, tH) |
线性映射 | long |
constrain(val, min, max) |
限幅 | 原类型 |
pulseIn(pin, val, timeout) |
测量脉冲宽度 | 微秒数 |
八、常见问题¶
analogWrite() 可以用在 A0~A5 上吗?
不可以! analogWrite() 只能用在带 ~ 标记的 PWM 引脚(D3、D5、D6、D9、D10、D11)。analogRead() 才用在 A0~A5 上。名字虽然都带 "analog",但完全是不同的功能。
为什么 digitalRead() 读取的值一直跳变?
引脚处于==浮空==状态。使用 INPUT_PULLUP 模式或外接上拉/下拉电阻来解决。
如何提高 ADC 精度?
- 使用
INTERNAL(1.1V)参考电压测量小信号 - 多次采样取平均值(如采样 16 次平均)
- 在 AREF 引脚添加滤波电容
- ADC 输入端加 RC 低通滤波(如 10KΩ + 100nF)
PWM 频率太低有什么影响?
- 控制 LED:频率低于 100Hz 会看到明显闪烁
- 控制电机:频率太低会导致电机发出啸叫声(人耳可听范围 20Hz~20kHz)
- 需要更高 PWM 频率时,可以通过修改定时器预分频器实现