跳转至

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() 配置引脚方向:

pinMode(pin, mode);
// mode: INPUT      — 高阻态输入
//        INPUT_PULLUP — 内部上拉输入(20~50KΩ)
//        OUTPUT     — 推挽输出

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)

Arduino D8 ──[220Ω]──|>|── GND
                      LED
                    (长脚为正)

电阻计算

\[R = \frac{V_{IO} - V_{LED}}{I_{LED}} = \frac{5V - 2V}{10mA} = 300\Omega\]

实际选择 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_{读取值} = \frac{ADC_{值}}{1023} \times V_{ref}\]

其中 \(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
            ┌──┴──┐
            │     │
            │ POT │  ← 10KΩ 电位器
            │     │
            ├──┬──┤
            │  │  │
           GND │  中间抽头 → A0

参考电压设置

// 默认参考电压(5V)
analogReference(DEFAULT);

// 内部 1.1V 参考(更高精度测小电压)
analogReference(INTERNAL);

// 外部参考电压(接 AREF 引脚)
analogReference(EXTERNAL);

切换参考电压

切换 analogReference() 后,前几次 analogRead() 的值可能不准确,建议丢弃前几次读数。


五、PWM 输出(模拟输出)

Arduino 的 analogWrite() 实际上输出的是 PWM 信号(脉冲宽度调制),而非真正的模拟电压。

PWM 原理

占空比 25%          占空比 50%          占空比 75%
  ┌─┐               ┌──┐              ┌───┐
  │ │               │  │              │   │
──┘ └──────     ────┘  └────      ────┘   └──
\[V_{等效} = \frac{duty}{255} \times 5V\]

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() 限幅

int sensorVal = analogRead(A0);
int limited = constrain(sensorVal, 100, 900); // 限制在 100~900 范围内

七、常用函数速查

函数 功能 返回值
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 精度?

  1. 使用 INTERNAL(1.1V)参考电压测量小信号
  2. 多次采样取平均值(如采样 16 次平均)
  3. 在 AREF 引脚添加滤波电容
  4. ADC 输入端加 RC 低通滤波(如 10KΩ + 100nF)

PWM 频率太低有什么影响?

  • 控制 LED:频率低于 100Hz 会看到明显闪烁
  • 控制电机:频率太低会导致电机发出啸叫声(人耳可听范围 20Hz~20kHz)
  • 需要更高 PWM 频率时,可以通过修改定时器预分频器实现