器→工具, 电子电路

Arduino GPIO 引脚输入与输出

钱魏Way · · 5 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

数字输入与输出

Arduino 的数字输入与输出是其核心功能之一,掌握它们是进行各种项目开发的基础。

Arduino 基础知识回顾

在深入学习数字输入与输出之前,我们先回顾一下 Arduino 的基础知识:

  • 数字引脚 (Digital Pins): Arduino 板上有多个数字引脚,它们可以被配置为输入或输出模式。
    • 输入模式 (INPUT): 当引脚设置为输入模式时,它会读取连接到该引脚的电压状态(高电平或低电平)。
    • 输出模式 (OUTPUT): 当引脚设置为输出模式时,它可以输出高电平(通常为 5V)或低电平(0V),从而控制外部设备。
  • 高电平 (HIGH) 与 低电平 (LOW):
    • HIGH: 通常代表 5V 或3V,表示逻辑上的“真”或“1”。
    • LOW: 通常代表 0V,表示逻辑上的“假”或“0”。
  • 上拉电阻 (Pull-up Resistor) 与 下拉电阻 (Pull-down Resistor):
    • 上拉电阻: 当输入引脚悬空时,通过电阻将引脚拉高到 VCC,确保引脚处于高电平状态。
    • 下拉电阻: 当输入引脚悬空时,通过电阻将引脚拉低到 GND,确保引脚处于低电平状态。
    • 内部上拉电阻: Arduino 的数字引脚通常内置了可编程的上拉电阻,通过 pinMode(pin, INPUT_PULLUP) 设置。这省去了外部电阻,简化了电路。

上拉电阻和下拉电阻的电学原理

在数字电路中,一个输入引脚通常需要一个明确的逻辑高电平(HIGH)或逻辑低电平(LOW)。然而,当一个输入引脚没有连接到任何地方,或者连接的设备处于高阻态(例如,一个按钮被松开时),这个引脚就会处于一种被称为浮空(floating)的状态。

浮空状态的问题

当引脚浮空时,它就像一根天线,很容易受到周围电磁噪声的干扰。这会导致引脚的电压在高电平、低电平之间随机跳变,从而产生不确定的逻辑状态。对于微控制器(比如 Arduino)来说,它无法准确判断当前的输入是 0 还是 1,可能导致程序运行异常。

举个例子,你按下按钮后,希望 LED 亮起。如果按钮松开时引脚浮空,LED 可能会莫名其妙地闪烁,因为它接收到了不确定的信号。

为了解决这个问题,我们需要在输入引脚浮空时,给它一个确定的电压,让它保持在一个已知的逻辑状态。这就是上拉电阻和下拉电阻的作用。

上拉电阻(Pull-up Resistor)

原理: 上拉电阻是将输入引脚连接到正电源(VCC,通常是 5V 或 3.3V)的电阻。

  • 当输入引脚浮空时: 上拉电阻将引脚的电压“拉”高到 VCC。此时,输入引脚检测到高电平。
  • 当外部设备将引脚拉低时: 例如,按下按钮将引脚连接到地(GND)。此时,电流会通过上拉电阻流向 GND。由于电阻的作用,引脚上的电压会降到接近 0V,输入引脚检测到低电平。

电路示意图:

工作流程(以按钮为例):

  • 按钮未按下: 按钮处于断开状态,Arduino 输入引脚通过上拉电阻连接到 VCC。此时,引脚上的电压接近 VCC,被识别为 HIGH
  • 按钮按下: 按钮导通,将 Arduino 输入引脚直接连接到 GND。此时,电流从 VCC 经过上拉电阻、按钮流向 GND。引脚上的电压被强制拉低到 GND,被识别为 LOW

特点:

  • 默认状态为 HIGH。
  • 按下按钮或其他操作时,输入状态变为 LOW。
  • Arduino 内部提供了可编程的内部上拉电阻,可以通过 pinMode(pin, INPUT_PULLUP); 来启用,省去了外部接线。

何时使用: 当你希望在没有外部信号输入时,引脚默认处于高电平状态时。常见于按钮、开关等与 GND 连接的输入设备。

下拉电阻(Pull-down Resistor)

原理: 下拉电阻是将输入引脚连接到地(GND)的电阻。

  • 当输入引脚浮空时: 下拉电阻将引脚的电压“拉”低到 GND。此时,输入引脚检测到低电平。
  • 当外部设备将引脚拉高时: 例如,按下按钮将引脚连接到 VCC。此时,电流会从 VCC 经过按钮流向引脚和下拉电阻,最终回到 GND。引脚上的电压会升高到接近 VCC,输入引脚检测到高电平。

电路示意图:

工作流程(以按钮为例):

  • 按钮未按下: 按钮处于断开状态,Arduino 输入引脚通过下拉电阻连接到 GND。此时,引脚上的电压接近 GND,被识别为 LOW
  • 按钮按下: 按钮导通,将 Arduino 输入引脚直接连接到 VCC。此时,电流从 VCC 经过按钮流向引脚和下拉电阻,最终回到 GND。引脚上的电压被强制拉高到 VCC,被识别为 HIGH

特点:

  • 默认状态为 LOW。
  • 按下按钮或其他操作时,输入状态变为 HIGH。
  • Arduino 没有提供内部下拉电阻,如果需要使用下拉电阻,通常需要外部连接。

何时使用: 当你希望在没有外部信号输入时,引脚默认处于低电平状态时。常见于按钮、开关等与 VCC 连接的输入设备。

为什么需要电阻?

你可能会问,为什么不直接将浮空引脚连接到 VCC 或 GND 呢?

  • 防止短路: 如果没有电阻,当按钮按下时,你可能会直接将 VCC 和 GND 短路(通过按钮),这会导致大电流流过,损坏电源、按钮甚至 Arduino 引脚。电阻起到了限流的作用。
  • 建立电压分压: 电阻与输入引脚的内部阻抗形成一个分压器,确保在按钮按下时,引脚上的电压能够被拉到正确的逻辑电平。

电阻值的选择

选择上拉或下拉电阻的值需要权衡:

  • 太小: 会导致通过电阻的电流过大,浪费电能,甚至可能烧坏设备。
  • 太大: 会使得电阻与寄生电容形成 RC 电路,影响信号的响应速度,或者在噪声干扰下不足以将引脚电压拉到确定的逻辑电平。

对于 Arduino,通常使用的上拉/下拉电阻值在 1kΩ 到 10kΩ 之间是一个比较安全的范围。Arduino 的内部上拉电阻大约是 20kΩ 到 50kΩ。

数字输入与输出示例

按钮:数字输入与 LED 控制

按钮是最常见的数字输入设备,通过它我们可以向 Arduino 发送指令。

按钮工作原理

按钮本质上是一个瞬时开关。当按下时,它导通电流;当松开时,它断开电流。我们需要通过上拉或下拉电阻来确保按钮未按下时引脚有确定的状态。

  • 使用外部下拉电阻: 按钮一端接 VCC,另一端接输入引脚和下拉电阻(另一端接 GND)。按下按钮时,引脚为高电平;松开时,引脚为低电平。
  • 使用外部上拉电阻: 按钮一端接 GND,另一端接输入引脚和上拉电阻(另一端接 VCC)。按下按钮时,引脚为低电平;松开时,引脚为高电平。
  • 使用内部上拉电阻 (推荐): 按钮一端接 GND,另一端接输入引脚。在代码中将引脚设置为 INPUT_PULLUP。按下按钮时,引脚为低电平;松开时,引脚为高电平。

实践案例:按钮控制 LED 亮灭

目标: 按下按钮时 LED 亮起,松开按钮时 LED 熄灭。

所需元件:

  • Arduino UNO 开发板
  • 面包板
  • 按钮 x 1
  • LED x 1
  • 220Ω 电阻 x 1 (用于 LED 限流)
  • 杜邦线若干

电路连接:

  • LED:
    • LED 短脚 (负极) 连接到 220Ω 电阻一端。
    • 电阻另一端连接到 Arduino 的 GND。
    • LED 长脚 (正极) 连接到 Arduino 的数字引脚 13。
  • 按钮 (使用内部上拉电阻):
    • 按钮的一只脚连接到 Arduino 的数字引脚 2。
    • 按钮的对角脚连接到 Arduino 的 GND。

Arduino 代码:

const int buttonPin = 2;   // 按钮连接的数字引脚
const int ledPin = 13;     // LED 连接的数字引脚

int buttonState = 0;       // 存储按钮状态的变量

void setup() {
  // 设置 LED 引脚为输出模式
  pinMode(ledPin, OUTPUT);
  // 设置按钮引脚为输入模式,并启用内部上拉电阻
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(9600); // 开启串口通信,用于调试
}

void loop() {
  // 读取按钮的状态
  buttonState = digitalRead(buttonPin);

  // 检查按钮是否被按下 (当使用INPUT_PULLUP时,按下为LOW)
  if (buttonState == LOW) {
    // 如果按钮被按下,点亮 LED
    digitalWrite(ledPin, HIGH);
    Serial.println("Button Pressed - LED ON");
  } else {
    // 如果按钮未按下,熄灭 LED
    digitalWrite(ledPin, LOW);
    Serial.println("Button Released - LED OFF");
  }
  delay(10); // 增加少量延迟以稳定读取
}

代码解释:

  • pinMode(buttonPin, INPUT_PULLUP);:将 buttonPin 配置为输入模式,并激活内部上拉电阻。这意味着当按钮未按下时,引脚会保持高电平。当按钮按下时,它将引脚连接到 GND,使得引脚变为低电平。
  • digitalRead(buttonPin);:读取 buttonPin 的数字状态 (HIGH 或 LOW)。
  • digitalWrite(ledPin, HIGH); / digitalWrite(ledPin, LOW);:分别点亮或熄灭 LED。

开关:理解如何读取开关的状态

开关与按钮类似,但它通常有锁定状态(ON/OFF),而不是瞬时操作。常见的有拨动开关、翘板开关等。读取开关状态的方法与按钮类似,同样需要考虑上拉或下拉电阻。

开关工作原理

开关通常有两到三根引脚。以最常见的两脚拨动开关为例:在某个位置时,两脚导通;在另一个位置时,两脚断开。三脚开关(SPDT – 单刀双掷)则可以在一个公共端和两个常开/常闭端之间切换连接。

实践案例:读取开关状态并显示

目标: 读取拨动开关的状态,并通过串口监视器显示当前是 “ON” 还是 “OFF”。

所需元件:

  • Arduino UNO 开发板
  • 面包板
  • 拨动开关 (两脚或三脚皆可,这里以两脚为例) x 1
  • 杜邦线若干

电路连接 (使用内部上拉电阻):

  • 拨动开关的一只脚连接到 Arduino 的数字引脚 4。
  • 拨动开关的另一只脚连接到 Arduino 的 GND。

Arduino 代码:

const int switchPin = 4; // 开关连接的数字引脚

int switchState = 0;     // 存储开关状态的变量

void setup() {
  // 设置开关引脚为输入模式,并启用内部上拉电阻
  pinMode(switchPin, INPUT_PULLUP);
  Serial.begin(9600); // 开启串口通信
  Serial.println("Switch Status Monitor");
}

void loop() {
  // 读取开关的状态
  switchState = digitalRead(switchPin);

  // 判断开关状态
  if (switchState == LOW) {
    Serial.println("Switch is ON"); // 当使用INPUT_PULLUP时,接地为ON
  } else {
    Serial.println("Switch is OFF"); // 未接地为OFF
  }
  delay(500); // 每 500 毫秒读取一次,避免串口输出过快
}

代码解释:

  • 此代码与按钮控制 LED 的逻辑类似,主要区别在于我们通过串口输出来观察开关状态,而不是控制 LED。
  • delay(500); 是为了限制串口输出的频率,让你有时间观察。

七段数码管:控制简单的显示器件

七段数码管由七个 LED 段和一个小数点组成。通过控制不同段的亮灭,可以显示 0-9 的数字以及一些字母。

  • 共阳极 (Common Anode): 所有段的阳极连接在一起,连接到 VCC。要点亮某个段,需要将该段的阴极连接到 LOW。
  • 共阴极 (Common Cathode): 所有段的阴极连接在一起,连接到 GND。要点亮某个段,需要将该段的阳极连接到 HIGH。

通常,为了简化控制,我们会使用 数码管驱动芯片,如 74HC595 移位寄存器TM1637 驱动芯片

实践案例:使用 74HC595 驱动一位共阴极数码管显示数字

目标: 使用 74HC595 移位寄存器驱动一位共阴极七段数码管,循环显示数字 0-9。

所需元件:

  • Arduino UNO 开发板
  • 面包板
  • 74HC595 移位寄存器 x 1
  • 一位共阴极七段数码管 x 1
  • 220Ω 电阻 x 8 (每个段一个限流电阻,如果驱动芯片可以限流则可选)
  • 杜邦线若干

74HC595 引脚说明:

  • Vcc (Pin 16): 供电 (5V)
  • GND (Pin 8): 接地
  • DS (Data Serial Input, Pin 14): 串行数据输入
  • ST_CP (Storage Register Clock, Pin 12): 存储寄存器时钟 (锁存器时钟),上升沿时将移位寄存器中的数据传输到输出寄存器
  • SH_CP (Shift Register Clock, Pin 11): 移位寄存器时钟,上升沿时移入一位数据
  • OE (Output Enable, Pin 13): 输出使能 (低电平有效),接 GND 保持常亮
  • Q7S (Pin 9): 串行数据输出 (用于级联)

数码管段位定义 (通常):

数码管段位与 74HC595 输出引脚连接:

你需要查找你所使用数码管的具体引脚定义。假设 74HC595 的 Q0-Q7 对应数码管的 A-G 和 DP。

电路连接:

  • 74HC595 供电:
    • Vcc (Pin 16) -> Arduino 5V
    • GND (Pin 8) -> Arduino GND
    • OE (Pin 13) -> Arduino GND (使能输出)
    • MR (Master Reset, Pin 10) -> Arduino 5V (非复位状态)
  • 74HC595 控制引脚:
    • DS (Pin 14) -> Arduino 数字引脚 11 (或任意数字引脚,将在代码中定义)
    • SH_CP (Pin 11) -> Arduino 数字引脚 12
    • ST_CP (Pin 12) -> Arduino 数字引脚 10
  • 74HC595 输出到数码管 (共阴极):
    • 74HC595 的 Q0 到 Q7 分别通过 220Ω 限流电阻连接到数码管的 a 到 g 和 dp 段。
    • 数码管的公共阴极连接到 Arduino 的 GND。

Arduino 代码:

// 74HC595 引脚定义
const int latchPin = 10;   // ST_CP (存储寄存器时钟)
const int clockPin = 12;   // SH_CP (移位寄存器时钟)
const int dataPin = 11;    // DS (串行数据输入)

// 数码管共阴极段码表 (0-9)
// 对应 74HC595 的 Q0-Q6 依次为 A-G 段。这里假定Q0=A, Q1=B, ..., Q6=G
// 每个字节的位对应数码管的段:dp g f e d c b a (高位到低位)
// 例如:数字 0 (a,b,c,d,e,f亮,g不亮) -> 01111110b -> 0x7E
byte segmentPatterns[] = {
  0b01111110, // 0
  0b00001100, // 1
  0b10110110, // 2
  0b10011110, // 3
  0b11001100, // 4
  0b11011010, // 5
  0b11111010, // 6
  0b00001110, // 7
  0b11111110, // 8
  0b11011110  // 9
};

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  for (int i = 0; i <= 9; i++) {
    // 将数据发送到 74HC595
    digitalWrite(latchPin, LOW); // 拉低锁存器时钟,准备接收数据
    shiftOut(dataPin, clockPin, MSBFIRST, segmentPatterns[i]); // 移位输出数据
    digitalWrite(latchPin, HIGH); // 拉高锁存器时钟,将数据传输到输出引脚

    Serial.print("Displaying: ");
    Serial.println(i);
    delay(1000); // 显示 1 秒
  }
}

代码解释:

  • shiftOut(dataPin, clockPin, MSBFIRST, segmentPatterns[i]); 是 Arduino 库函数,用于将数据串行输出。
    • dataPin: 数据引脚
    • clockPin: 时钟引脚
    • MSBFIRST: 最重要位 (Most Significant Bit) 先输出。你需要根据你数码管的连接方式来确定是 MSBFIRST 还是 LSBFIRST。
    • segmentPatterns[i]: 要发送的数据字节。
  • digitalWrite(latchPin, LOW); 和 digitalWrite(latchPin, HIGH);:控制 74HC595 的锁存器,当锁存器时钟由低到高跳变时,移位寄存器中的数据会被“锁存”并输出到 Q0-Q7。

点阵屏 (如 8×8 LED Dot Matrix)

点阵屏是由多个 LED 组成的一个矩阵,通过控制行和列的通断来点亮特定的 LED。驱动点阵屏通常也需要专门的驱动芯片,如 MAX7219。MAX7219 是一款串行输入/输出共阴极显示驱动芯片,可以非常方便地驱动 8×8 LED 点阵或多位七段数码管。

实践案例:使用 MAX7219 驱动 8×8 LED 点阵屏显示简单图案

目标: 使用 MAX7219 驱动 8×8 LED 点阵屏,显示一个简单的图案。

所需元件:

  • Arduino UNO 开发板
  • 8×8 LED 点阵屏模块 (通常集成 MAX7219) x 1
  • 杜邦线若干

电路连接:

带有 MAX7219 的 8×8 点阵屏模块通常只有 5 个引脚:VCC, GND, DIN, CS, CLK。

  • VCC -> Arduino 5V
  • GND -> Arduino GND
  • DIN (Data In) -> Arduino 数字引脚 11 (或任意数字引脚,将在代码中定义)
  • CS (Chip Select) -> Arduino 数字引脚 10
  • CLK (Clock) -> Arduino 数字引脚 12

Arduino 代码:

为了简化 MAX7219 的控制,强烈建议使用 LedControl 库

  • 安装 LedControl 库: 在 Arduino IDE 中,依次点击 Sketch -> Include Library -> Manage Libraries…。搜索 “LedControl” 并安装。
#include <LedControl.h>

/*
  LedControl(dataPin, clockPin, csPin, numDevices)
  dataPin: DIN (数据输入引脚)
  clockPin: CLK (时钟引脚)
  csPin: CS (片选引脚)
  numDevices: 连接的 MAX7219 芯片数量 (这里是 1)
*/
LedControl lc = LedControl(11, 12, 10, 1);

// 定义一个简单的笑脸图案 (8x8 字节数组)
// 0表示不亮,1表示亮
byte smileysFace[] = {
  0b00111100,
  0b01000010,
  0b10100101,
  0b10000001,
  0b10100101,
  0b10011001,
  0b01000010,
  0b00111100
};

void setup() {
  lc.shutdown(0, false); // 唤醒设备 (0是设备索引,如果只有一个设备就是0)
  lc.setIntensity(0, 8);   // 设置亮度 (0-15)
  lc.clearDisplay(0);    // 清除显示
}

void loop() {
  // 显示笑脸图案
  for (int row = 0; row < 8; row++) {
    lc.setRow(0, row, smileysFace[row]);
  }
  delay(2000); // 显示 2 秒

  // 清除显示
  lc.clearDisplay(0);
  delay(1000); // 延时 1 秒
}

代码解释:

  • #include <LedControl.h>: 引入 LedControl 库。
  • LedControl lc = LedControl(11, 12, 10, 1);: 创建 LedControl 对象,并指定数据、时钟、片选引脚和连接的 MAX7219 芯片数量。
  • shutdown(0, false);: 唤醒设备。
  • setIntensity(0, 8);: 设置亮度。
  • clearDisplay(0);: 清除显示。
  • setRow(0, row, smileysFace[row]);: 设置点阵屏的某一行。第一个参数是设备索引,第二个是行号 (0-7),第三个是该行的数据字节(每个位代表一个 LED 的亮灭)。

模拟信号输入与输出

Arduino 基础知识回顾

在深入学习之前,我们先快速回顾一些 Arduino 基础:

  • 模拟输入引脚 (Analog In):Arduino 板上带有 “A0” 到 “A5″(在某些板上更多)的引脚,这些引脚用于读取模拟信号。它们可以将 0V 到 5V 的电压转换为 0 到 1023 的数字值。这个过程被称为模数转换 (ADC)
  • 数字输出引脚 (Digital Out):Arduino 的数字引脚(例如 0 到 13)可以被设置为 HIGH (5V) 或 LOW (0V)。
  • 脉冲宽度调制 (PWM):某些数字引脚(在 Arduino Uno 上通常标有 “~” 符号,如 3, 5, 6, 9, 10, 11)支持 PWM。PWM 是一种模拟输出技术,通过快速地开启和关闭数字信号来模拟不同的电压水平,从而控制设备的亮度或速度。对于 Arduino,PWM 值范围是 0 到 255。

模拟信号与数字信号的区别

模拟信号 (Analog Signals)

  • 连续性: 模拟信号是连续变化的。它们可以在一定范围内取到无限多个值。就像声波、光线的强度、温度等自然界的现象,它们的变化是平滑且不间断的。
  • 例子:
    • 声音:你的声音音量可以从非常小到非常大,并且中间有无数个渐变。
    • 光线:一盏灯的亮度可以逐渐变亮或变暗。
    • 温度:温度可以从 20°C 缓慢上升到 1°C,再到 20.2°C,中间有无限小的变化。
  • 特点: 模拟信号能更精确地反映物理世界,但也更容易受到噪声干扰,且在传输过程中可能出现信号衰减。

数字信号 (Digital Signals)

  • 离散性: 数字信号是不连续的,只能取有限个离散的值。最常见的是二进制数字信号,只有两种状态:HIGH (高电平)LOW (低电平),通常对应于电压的有无(例如 5V 或 0V)。
  • 例子:
    • 开关:一个开关只有开或关两种状态。
    • 按钮:一个按钮只有按下或未按下两种状态。
    • 计算机数据:计算机内部处理的所有信息都是以 0 和 1 的形式存储和传输的。
  • 特点: 数字信号抗噪声能力强,传输稳定性高,易于存储和处理。

Arduino 如何将模拟电压转化为数字值?

Arduino(例如 Arduino Uno)本身的核心微控制器,比如 ATmega328P,只能理解和处理数字信号(0和1)。然而,现实世界中的大多数传感器(如电位器、光敏电阻、温度传感器等)输出的都是模拟信号。为了让微控制器能够“理解”这些模拟信号,就需要一个特殊的电路来完成模拟到数字的转换,这个电路就是 模数转换器 (Analog-to-Digital Converter, ADC)

Arduino 板上集成了 ADC,它负责将模拟电压转换为数字值。这个过程可以这样理解:

  • 输入电压范围: Arduino 的模拟输入引脚(例如 A0-A5)能够读取 0V 到其参考电压之间的模拟电压。对于大多数 Arduino Uno 板,这个参考电压默认为 5V
  • ADC 分辨率: Arduino Uno 上的 ADC 是 10 位 (10-bit) 的。这意味着它能够将 0V 到 5V 的电压范围分成 210 个离散的等级。
    • 210=1024
    • 所以,ADC 会将模拟电压映射到 0 到 1023 之间的整数值。
    • 0V 对应数字值 0。
    • 5V 对应数字值 1023。
    • 介于 0V 和 5V 之间的任何电压都会被转换为 0 到 1023 之间的某个整数值。
  • 转换过程: 当你使用 analogRead() 函数读取一个模拟输入引脚时,Arduino 的 ADC 会执行以下步骤:
    • 它会测量指定引脚上的实际电压。
    • 然后,它会使用一种内部算法(通常是逐次逼近型 ADC)将这个电压值与参考电压进行比较。
    • 最终,输出一个 0 到 1023 之间的整数。

计算模拟值对应的电压:

通过 ADC 的分辨率,可直接计算出每个数字值对应的电压。

计算公式:电压值 V = 数字值 × 参考电压 / ADC 分辨率范围

以 10 位 ADC + 5V 参考电压为例:

  • 单位电压分辨率:$\dfrac{\text{参考电压}}{\text{ADC 最大值}} = \dfrac{5\text{V}}{1024} \approx 0.00488\text{V}$(即每个数字步进代表 88mV)
  • 计算 analogRead(A0) = 512 时的电压:$V = 512 \times \dfrac{5\text{V}}{1024} = \dfrac{2560}{1024} \approx 2.5\text{V}$

为什么是 0 到 1023,而不是 0 到 1024?

这是一个常见的小误解。10 位 ADC 意味着有 210=1024 个可能的离散状态。如果从 0 开始计数,那么这 1024 个状态就是 0 到 1023。就像有 10 个人排队,如果第一个人是 0 号,那最后一个人就是 9 号,总共有 10 个人。

analogRead() 函数

在 Arduino 编程中,你只需要调用简单的 analogRead() 函数就能完成这个复杂的转换过程:

int sensorValue = analogRead(A0); // 读取 A0 引脚的模拟值,并将其存储在 sensorValue 变量中

分压器原理详解

分压器是电路中一个非常基础且重要的概念,它允许你从一个较高的电压源获得一个较低的电压,或者根据电阻的变化来测量电压。理解分压器是掌握许多模拟传感器工作原理的关键。

什么是分压器?

简单来说,分压器是由两个或更多个串联电阻组成的电路,它可以将输入电压按照电阻的比例分配到各个电阻上,从而在某个电阻两端获得一个分压后的电压。

分压器的基本结构

最常见的分压器由两个串联电阻 ($R_1$) 和 ($R_2$) 组成,如下图所示:

  • $V_{in}$:输入电压,即整个分压器两端的总电压。
  • $R_1$:第一个电阻。
  • $R_2$:第二个电阻。
  • $V_{out}$:输出电压,通常是从 $R_2$ 两端测量到的电压。

分压器的工作原理

根据欧姆定律和串联电路的特性,我们可以推导出分压器的原理。

  • 串联电路的总电阻:在串联电路中,总电阻是各个电阻之和。$R_{total} = R_1 + R_2$
  • 电路中的总电流:根据欧姆定律 ($I = V/R$),流过串联电路的电流是相同的。$I = \frac{V_{in}}{R_{total}} = \frac{V_{in}}{R_1 + R_2}$
  • 输出电压的计算:输出电压 ($V_{out}$) 是流过 $R_2$ 的电流乘以 $R_2$ 的电阻值。$V_{out} = I \times R_2$
  • 将电流 $I$ 的表达式代入:$V_{out} = (\frac{V_{in}}{R_1 + R_2}) \times R_2$
  • 重新整理,得到分压器公式:$V_{out} = V_{in} \times \frac{R_2}{R_1 + R_2}$

这个公式就是分压器的核心,它表明输出电压与输入电压成正比,且比例系数由两个电阻的相对大小决定。

分压器的应用场景

分压器在电子电路中有着广泛的应用,包括:

  • 电压转换/降压:当需要一个比电源电压更低的固定电压时,可以使用分压器。例如,从 5V 电源获得5V 电压。
  • 传感器接口:许多模拟传感器(如光敏电阻 LDR、热敏电阻、力敏电阻等)的电阻值会随外界物理量的变化而变化。将它们与一个固定电阻组成一个分压器,通过测量分压点的电压变化,就可以间接测量物理量。
  • 光敏电阻 (LDR):在光照下电阻值减小。将其与固定电阻串联,光照变化会导致分压点电压变化。
  • 热敏电阻:电阻值随温度变化。
  • 电位器:电位器本身就是一个可变分压器。通过旋转旋钮,改变滑动触点的位置,从而改变 $R_1$ 和 $R_2$ 的比例,实现连续可调的输出电压。
  • 电平转换:在一些数字电路中,如果两个设备的工作电压不同,可以通过分压器进行简单的电平转换(但要注意分压器不能提供电流增益,也不适用于所有高频或大电流应用)。

实例分析:使用光敏电阻测量光强

我们来回顾一下光敏电阻的例子。

假设你将一个光敏电阻 (LDR) 和一个 10k 欧姆的固定电阻串联起来,连接到 Arduino 的 5V 和 GND。LDR 接在 5V 端,10k 欧姆电阻接在 GND 端,Arduino 的模拟输入引脚 (A0) 连接在两者之间。

当光线很强时:LDR 的电阻值会变得很小 (例如 1k 欧姆)。

  • $R_1 (LDR) = 1kΩ$
  • $R_2 (固定电阻) = 10kΩ$
  • $V_{out} = 5V \times \frac{10kΩ}{1kΩ + 10kΩ} = 5V \times \frac{10}{11} \approx 4.54V$
  • 此时 A0 读数会比较高。

当光线很弱时:LDR 的电阻值会变得很大 (例如 100k 欧姆)。

  • $R_1 (LDR) = 100kΩ$
  • $R_2 (固定电阻) = 10kΩ$
  • $V_{out} = 5V \times \frac{10kΩ}{100kΩ + 10kΩ} = 5V \times \frac{10}{110} \approx 0.45V$
  • 此时 A0 读数会比较低。

通过测量 A0 引脚的电压(即 $V_{out}$),我们就可以判断环境光线的强度。

重要的注意事项

  • 电流消耗:分压器会持续消耗电流,因为电阻始终连接在电源上。如果电阻值太小,电流会很大,造成不必要的功耗。如果电阻值太大,可能会受到噪声干扰,且输出电流能力较弱。选择合适的电阻值很重要。
  • 负载效应:当你在分压器的输出端连接一个负载(例如另一个电路或传感器输入)时,这个负载本身也会有自己的输入电阻,它会与分压器中的 $R_2$ 并联,从而改变等效的 $R_2$ 值,影响输出电压。如果负载的输入电阻远大于 $R_2$,则影响可以忽略不计。但对于高精度或低阻抗负载的应用,需要考虑这种负载效应,甚至可能需要使用缓冲器(如运算放大器)来隔离。
  • 不适合提供大电流:分压器不能提供大的输出电流。如果你需要从一个较高电压获得较低电压,并且需要为负载提供显著电流,那么应该考虑使用线性稳压器或开关稳压器。
  • 输入电压波动:分压器的输出电压是输入电压的一个固定比例。如果输入电压波动,输出电压也会跟着波动。

模拟信号输入与输出示例

电位器:读取模拟值并控制 LED 亮度

电位器是一个可变电阻器,通过旋转旋钮可以改变其电阻值,从而改变通过它的电压。

所需材料:

  • Arduino 开发板
  • 面包板
  • 电位器 (10k 欧姆)
  • LED
  • 220 欧姆电阻 (用于 LED 限流)
  • 若干跳线

接线图:

  • 电位器连接:
    • 电位器的一端连接到 Arduino 的 5V。
    • 电位器的另一端连接到 Arduino 的 GND。
    • 电位器的中间引脚连接到 Arduino 的 A0 模拟输入引脚。
  • LED 连接:
    • LED 的长腿 (阳极) 连接到 220 欧姆电阻。
    • 220 欧姆电阻的另一端连接到 Arduino 的 PWM 引脚 (例如数字引脚 9)。
    • LED 的短腿 (阴极) 连接到 Arduino 的 GND。

示例代码:

const int potPin = A0;    // 电位器连接到 A0 引脚
const int ledPin = 9;     // LED 连接到数字引脚 9 (PWM 引脚)

void setup() {
  pinMode(ledPin, OUTPUT); // 设置 LED 引脚为输出模式
  Serial.begin(9600);      // 启动串口通信,用于调试
}

void loop() {
  // 读取电位器的模拟值 (0-1023)
  int potValue = analogRead(potPin);
  Serial.print("电位器原始值: ");
  Serial.println(potValue);

  // 将电位器的值 (0-1023) 映射到 LED 的 PWM 范围 (0-255)
  int brightness = map(potValue, 0, 1023, 0, 255);
  Serial.print("LED 亮度 (PWM): ");
  Serial.println(brightness);

  // 使用 analogWrite() 函数控制 LED 的亮度
  analogWrite(ledPin, brightness);

  delay(10); // 稍作延迟,以便稳定读取和输出
}

代码讲解:

  • analogRead(potPin):读取连接到 potPin (A0) 的模拟电压值,并返回一个 0 到 1023 之间的整数。
  • map(value, fromLow, fromHigh, toLow, toHigh):这是一个非常有用的函数,可以将一个范围的数值映射到另一个范围。在这里,我们将电位器的 0-1023 范围映射到 LED 的 0-255 PWM 范围。
  • analogWrite(ledPin, brightness):将 brightness 值写入到 ledPin。由于 ledPin 是一个 PWM 引脚,这将控制 LED 的亮度。

光敏电阻 (LDR):根据环境光线强度改变 LED 亮度

光敏电阻的电阻值会随着光线强度的变化而变化:光线越强,电阻越小;光线越弱,电阻越大。我们可以将其与一个固定电阻串联构成一个分压器,通过读取分压点的电压来判断光线强度。

所需材料:

  • Arduino 开发板
  • 面包板
  • 光敏电阻 (LDR)
  • 10k 欧姆电阻
  • LED
  • 220 欧姆电阻 (用于 LED 限流)
  • 若干跳线

接线图:

  • 光敏电阻分压器连接:
    • 光敏电阻的一端连接到 Arduino 的 5V。
    • 光敏电阻的另一端连接到 10k 欧姆电阻的一端。
    • 10k 欧姆电阻的另一端连接到 Arduino 的 GND。
    • 光敏电阻与 10k 欧姆电阻的连接点 (分压点) 连接到 Arduino 的 A0 模拟输入引脚。
  • LED 连接:
    • LED 的长腿 (阳极) 连接到 220 欧姆电阻。
    • 220 欧姆电阻的另一端连接到 Arduino 的 PWM 引脚 (例如数字引脚 9)。
    • LED 的短腿 (阴极) 连接到 Arduino 的 GND。

示例代码:

const int ldrPin = A0;    // 光敏电阻连接到 A0 引脚
const int ledPin = 9;     // LED 连接到数字引脚 9 (PWM 引脚)

void setup() {
  pinMode(ledPin, OUTPUT); // 设置 LED 引脚为输出模式
  Serial.begin(9600);      // 启动串口通信,用于调试
}

void loop() {
  // 读取光敏电阻的模拟值 (0-1023)
  // 光线越亮,读数越低 (或越高,取决于LDR的接线方式)
  int ldrValue = analogRead(ldrPin);
  Serial.print("光敏电阻原始值: ");
  Serial.println(ldrValue);

  // 根据 LDR 的读数调整 LED 亮度。
  // 注意:LDR 的读数可能与光线强度呈反比或正比,需要根据实际效果调整映射范围。
  // 假设这里我们希望光线越强,LED 越亮(如果 LDR 值变小,需要反向映射)
  // 让我们假设在亮光下 LDRValue 较小,在暗光下 LDRValue 较大。
  // 因此,我们将小的 LDRValue 映射到高的亮度,大的 LDRValue 映射到低的亮度。
  int brightness = map(ldrValue, 0, 1023, 255, 0); // 将 0-1023 映射到 255-0

  // 限制亮度值在 0-255 范围内,以防映射超出范围
  brightness = constrain(brightness, 0, 255);

  Serial.print("LED 亮度 (PWM): ");
  Serial.println(brightness);

  // 使用 analogWrite() 函数控制 LED 的亮度
  analogWrite(ledPin, brightness);

  delay(50); // 稍作延迟
}

代码讲解:

  • constrain(value, low, high):此函数用于将一个值限制在指定的最小和最大范围内。这在 map() 函数的结果可能超出预期范围时非常有用。
  • 光敏电阻的读数与光线强度的关系取决于你如何连接它。如果光敏电阻连接在 5V 和 A0 之间,而 10k 欧姆电阻连接在 A0 和 GND 之间,那么光线越亮,光敏电阻的阻值越小,A0 读取到的电压越高,analogRead() 值越大。反之,如果光敏电阻连接在 A0 和 GND 之间,而 10k 欧姆电阻连接在 5V 和 A0 之间,那么光线越亮,光敏电阻的阻值越小,A0 读取到的电压越低,analogRead() 值越小。在 map() 函数中调整 toLow 和 toHigh 的顺序可以实现你想要的亮度变化效果。

温度传感器:读取模拟温度传感器的数据

模拟温度传感器 (如 LM35) 会根据温度变化输出不同的模拟电压。LM35 传感器的输出电压与其摄氏温度成线性关系,通常是 10mV/°C。

所需材料:

  • Arduino 开发板
  • 面包板
  • LM35 温度传感器
  • 若干跳线

接线图:

  • LM35 连接:
    • LM35 通常有三个引脚:Vcc (电源)、Out (输出) 和 GND (接地)。
    • LM35 的 Vcc 引脚连接到 Arduino 的 5V。
    • LM35 的 GND 引脚连接到 Arduino 的 GND。
    • LM35 的 Out 引脚连接到 Arduino 的 A0 模拟输入引脚。

示例代码:

const int tempSensorPin = A0; // LM35 温度传感器连接到 A0 引脚

void setup() {
  Serial.begin(9600); // 启动串口通信,用于调试
}

void loop() {
  // 读取温度传感器的模拟值 (0-1023)
  int sensorValue = analogRead(tempSensorPin);

  // 将模拟值转换为电压 (0-5V)
  // Arduino 的模拟输入引脚会将 0V-5V 映射到 0-1023
  // 所以 1 个单位的模拟值对应 5V / 1024 = 0.00488V (约 4.88mV)
  float voltage = sensorValue * (5.0 / 1024.0);

  // 将电压转换为摄氏温度
  // LM35 每 10mV 代表 1 摄氏度 (0.01V/°C)
  float temperatureC = voltage / 0.01;

  // 如果需要,转换为华氏温度
  float temperatureF = (temperatureC * 9.0 / 5.0) + 32.0;

  Serial.print("原始传感器值: ");
  Serial.print(sensorValue);
  Serial.print(", 电压: ");
  Serial.print(voltage);
  Serial.print("V, 温度: ");
  Serial.print(temperatureC);
  Serial.print("°C, ");
  Serial.print(temperatureF);
  Serial.println("°F");

  delay(1000); // 每秒读取一次
}

代码讲解:

  • sensorValue * (5.0 / 1024.0):将 0-1023 的模拟读数转换为实际的电压值。因为 Arduino 的 analogRead() 读数范围是 0 到 1023,对应 0V 到 5V。
  • voltage / 0.01:将电压转换为摄氏温度。LM35 的输出特性是每升高 1 摄氏度,输出电压增加 10mV (即01V)。

Arduino PWM (脉冲宽度调制)

脉冲宽度调制(PWM)是 Arduino 中一个非常重要的功能,它能让你通过数字输出来模拟模拟量的输出效果。这对于控制 LED 亮度、电机速度、舵机角度等应用非常有用。

概念理解:PWM 的原理及其应用

PWM 原理

PWM 的核心思想是通过调节数字信号在高电平(ON)和低电平(OFF)之间的时间比例来模拟不同的平均电压。虽然 Arduino 的数字引脚只能输出高电平(5V 或 3.3V)或低电平(0V),但通过快速切换高低电平,并改变高电平持续的时间(即脉冲宽度),我们可以有效地控制输出的“平均”电压。

  • 周期 (Period):一个完整的 PWM 波形所需的时间。
  • 频率 (Frequency):每秒钟 PWM 波形重复的次数,即周期的倒数。Arduino 的 PWM 引脚通常有固定的频率(例如,Uno 上的某些引脚是 490 Hz,另一些是 980 Hz)。
  • 占空比 (Duty Cycle):在一个周期内,高电平持续时间与整个周期时间的比值。通常用百分比表示。
    • 0% 占空比:输出始终为低电平(0V)。
    • 50% 占空比:高电平持续时间占周期的一半(平均输出约5V)。
    • 100% 占空比:输出始终为高电平(5V)。

Arduino 的 analogWrite() 函数正是用来实现 PWM 输出的。它接受两个参数:引脚号和 0 到 255 之间的值。

  • analogWrite(pin, value):
    • pin:支持 PWM 输出的引脚(通常在引脚号旁边有波浪线 ~ 符号,例如 Uno 上的 3, 5, 6, 9, 10, 11)。
    • value:一个 0 到 255 的整数。这个值决定了占空比,其中 0 表示 0% 占空比,255 表示 100% 占空比。例如,analogWrite(pin, 127) 将设置约 50% 的占空比。

PWM 在控制模拟量中的应用

LED 亮度控制

通过改变占空比,可以控制 LED 的平均电流,从而改变 LED 的亮度。

  • 占空比越高,LED 越亮。
  • 占空比越低,LED 越暗。
// 示例:呼吸灯效果
int ledPin = 9; // 连接到支持 PWM 的引脚

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

void loop() {
  // 从暗到亮
  for (int brightness = 0; brightness <= 255; brightness += 5) {
    analogWrite(ledPin, brightness);
    delay(30);
  }
  // 从亮到暗
  for (int brightness = 255; brightness >= 0; brightness -= 5) {
    analogWrite(ledPin, brightness);
    delay(30);
  }
}

电机速度控制

对于直流电机,改变其供电电压可以改变转速。PWM 同样可以模拟改变供电电压,从而控制电机的转速。

  • 占空比越高,电机转速越快。
  • 占空比越低,电机转速越慢。
  • 注意:直接用 Arduino 引脚驱动电机可能电流不足,通常需要使用电机驱动模块(如 L298N)来放大电流。PWM 信号将连接到电机驱动模块的使能引脚。

舵机:学习如何使用 PWM 控制舵机的转动角度

舵机(Servo Motor)是一种能够精确控制角度的小型电机。它们内部通常包含一个直流电机、减速齿轮组、一个电位器(用于反馈当前角度)和一个控制电路。舵机通过接收PWM 信号来确定其目标角度。

舵机控制原理

与普通的 PWM 不同,舵机需要特定频率(通常是 50 Hz,即 20 ms 周期)的 PWM 信号,并且通过**脉冲的宽度(高电平持续时间)**来控制角度,而不是占空比。

  • 脉冲宽度与角度对应关系(典型值,具体请参考舵机数据手册):
    • 1000 µs (1 ms) 脉冲:通常对应 0°(或 -90°)
    • 1500 µs (1.5 ms) 脉冲:通常对应 90°(中立位置)
    • 2000 µs (2 ms) 脉冲:通常对应 180°(或 +90°)

Arduino 提供了专门的 Servo.h 库来简化舵机的控制。这个库会处理底层的 PWM 信号生成,你只需要告诉它舵机应该转到哪个角度即可。

使用 Servo.h 库控制舵机

  • 引入库
  • 创建 Servo 对象
  • 连接舵机到 Arduino 引脚:
    • 舵机通常有三根线:
      • 红色:VCC(连接到 Arduino 的 5V 或 VCC)
      • 棕色/黑色:GND(连接到 Arduino 的 GND)
      • 橙色/黄色/白色:信号线(连接到 Arduino 的任何数字引脚,通常是 PWM 引脚,但 Servo 库可以模拟 PWM 信号,所以不一定非要 PWM 引脚,但为了兼容性最好还是用 PWM 引脚)
    • 在 setup() 中初始化舵机:
    • 在 loop() 中控制舵机角度:

完整舵机控制示例

#include <Servo.h> // 引入 Servo 库

Servo myServo;    // 创建一个 Servo 对象,名为 myServo
int servoPin = 9; // 将舵机信号线连接到数字引脚 9

void setup() {
  myServo.attach(servoPin); // 将 myServo 对象连接到指定的引脚
  Serial.begin(9600);       // 初始化串口通信,用于调试
}

void loop() {
  // 从 0 度到 180 度循环
  for (int angle = 0; angle <= 180; angle += 1) {
    myServo.write(angle); // 将舵机转到当前角度
    Serial.print("Current angle: ");
    Serial.println(angle);
    delay(15); // 稍微延迟一下,让舵机有时间到达位置
  }

  delay(1000); // 在方向改变前暂停 1 秒

  // 从 180 度到 0 度循环
  for (int angle = 180; angle >= 0; angle -= 1) {
    myServo.write(angle); // 将舵机转到当前角度
    Serial.print("Current angle: ");
    Serial.println(angle);
    delay(15);
  }

  delay(1000); // 循环结束后暂停 1 秒
}

为什么 Arduino 引脚不能直接驱动电机?

Arduino 的数字引脚是为了输出低电流的数字信号而设计的,而不是用于提供驱动电机所需的大电流。主要原因有以下几点:

  • 电流限制(Current Limit):
    • 单个引脚限制:Arduino Uno(基于 ATmega328P 芯片)的每个数字引脚能够安全地提供或吸收的最大电流通常是 20mA(毫安),虽然数据手册上标明极限是 40mA,但为了芯片的长期稳定运行,一般建议不超过 20mA。
    • 总电流限制:整个 Arduino 微控制器芯片(ATmega328P)所有引脚的总电流输出也有一个限制,通常在 200mA 左右。
    • 电机需求:大多数直流电机,即使是很小的电机(例如玩具中的电机),在启动或运行时需要的电流往往远超 20mA 甚至 200mA。例如,一个小型直流电机可能需要几百毫安的电流,而大型电机可能需要几安培(A)甚至几十安培的电流。
    • 损坏风险:如果尝试直接用 Arduino 引脚驱动电流需求高于其承受能力的电机,轻则导致 Arduino 引脚输出电压下降,电机无法正常工作;重则可能烧毁 Arduino 芯片的引脚,甚至整个芯片。
  • 电压不匹配(Voltage Mismatch):
    • Arduino 数字引脚通常输出 5V 或3V 的电压。虽然很多小型电机可以在 5V 下工作,但更多电机(特别是需要更大功率的)可能需要 9V、12V 甚至更高的电压才能达到理想的转速和扭矩。Arduino 的引脚无法直接提供这些更高的电压。
  • 反向电动势(Back EMF)保护:
    • 电机是一种感性负载。当电机突然停止或改变方向时,它的线圈会产生一个反向电动势(Back Electromotive Force, Back EMF),这是一个瞬间的高压尖峰。如果这个电压尖峰直接反馈到 Arduino 的引脚上,很可能会损坏微控制器。电机驱动模块通常内置了保护电路(如续流二极管)来吸收这些尖峰电压,保护控制电路。
  • 方向控制:
    • 要控制直流电机的转向,需要改变电流流过电机的方向。一个简单的数字引脚只能提供正向电流(输出高电平)或吸收电流(输出低电平),无法实现反向电流。而电机驱动模块通常包含一个 **H 桥(H-bridge)**电路,能够方便地切换电流方向,从而控制电机的正转和反转。

L298N 在这里的作用是什么?

L298N 是一款非常常见的双路 H 桥电机驱动芯片,它被广泛应用于控制直流电机和步进电机。它在这里的作用就像一个“功率放大器”和“接口转换器”:

  • 电流放大与高功率承载:
    • L298N 可以处理更高的电压(通常可达 46V)和更大的电流(单个 H 桥可达 2A,峰值更高)。这意味着你可以给 L298N 提供一个独立的、更高电压、更大电流的电源来驱动电机,而 Arduino 只需要提供低电流的控制信号。L298N 内部的晶体管会根据 Arduino 的信号,控制来自高功率电源的电流流向电机。
  • 方向控制(H 桥功能):
    • L298N 芯片内部集成了两个独立的 H 桥电路。每个 H 桥都可以控制一个直流电机的正转、反转和停止。通过组合控制 L298N 的输入引脚,你可以改变电机两端电压的极性,从而控制电机的旋转方向。
  • PWM 信号与使能引脚:
    • 当你说 “PWM 信号将连接到电机驱动模块的使能引脚” 时,这是 L298N 最典型的应用方式之一,用于控制电机速度。
    • L298N 模块通常有两个使能引脚(例如 ENA 和 ENB),分别对应两个 H 桥。这些使能引脚用来控制对应电机是否通电。
    • 当你将 Arduino 的 PWM 信号连接到 L298N 的 使能引脚 时,Arduino 的 analogWrite() 函数产生的 PWM 波形会通过使能引脚,控制 L298N 输出给电机的平均电压。
      • PWM 占空比高:使能引脚长时间处于高电平,电机长时间通电,平均电压高,电机转速快。
      • PWM 占空比低:使能引脚短时间处于高电平,电机短时间通电,平均电压低,电机转速慢。
    • L298N 还有其他输入引脚(例如 IN1, IN2, IN3, IN4)用于控制电机的方向。通过将这些引脚设置为高电平或低电平,你可以决定电机的正转或反转。

简而言之,L298N 作为一个“中间人”

  • 它接收 Arduino 发出的低功率、低电压的控制信号(如数字高低电平用于方向控制,或 PWM 信号用于速度控制)。
  • 它利用一个独立的、高功率的电源,根据 Arduino 的控制信号,放大电流和电压,并将其输送给电机。
  • 它内置了必要的电路(H 桥、保护电路),能够安全有效地驱动电机,并提供方向和速度控制。

通过使用 L298N 这类电机驱动模块,你既保护了 Arduino 不受高电流和反向电动势的损害,又能够充分利用 Arduino 的数字控制能力来实现复杂的电机控制,如调速、换向等。

其他应用

  • 蜂鸣器音调控制:通过快速切换数字信号,可以产生不同频率的声音。
  • 加热元件温度控制:通过控制加热元件通电时间比例来控制平均功率。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注