🍓 第七章 GPIO 硬件接口
📄 一句话概括

上一节我们把数字"骗"成了模拟(PWM)。这一节要面对反过来的难题:怎么"读"一个连续变化的电压。 而这一次骗不过去——树莓派根本没有测量电压的能力,必须外接一颗芯片。本节就讲这颗芯片(MCP3008)怎么接、怎么读。

📝 本节和前面几节的关系

7-3 讲过"数字输入":读一根针是高还是低。但真实世界里的东西(光有多亮、温度多高、旋钮转了多少)不是高或低,而是一个连续的值。 7-4 结尾留了一个问题:"输出可以骗,输入没法骗"——本节就是来解释这句话的。 这一节也是本章唯一需要同时用上前面所有知识的一节:BCM 编号(7-1)、装库与权限(7-2)、接线与限流(7-3)、PWM 调光(7-4)。

一、难题:数字输入只能看到"两档"

7-3 的按钮,读到的是"高 or 低"。这是数字输入的全部能力。

但如果我接一个电位器(可调电阻),想知道"旋钮转到哪个位置"呢?

旋钮从一头转到另一头,中间要经过无数个位置,每个位置对应一个中间的电压。这不是"两档",是一条连续的线。

旋钮位置:  最左 ────────────────────────→ 最右
电压:     0V ─── 0.8V ── 1.6V ── 2.4V ── 3.3V
数字针看到:低?  低?  低?  低?  高?      ← 几乎全是"低",最后才是"高"
             ↑ 中间这一大段,数字输入完全分不出来

一根数字针套上去,只能告诉你"电压有没有超过门槛"——转了大半圈都读作"低",最后一下才变"高"。它把连续的世界压扁成了两档。

二、硬事实:树莓派没有 ADC

要读连续的电压,需要一个叫 ADC 的部件。

ADC = Analog-to-Digital Converter,模数转换器。它做的事就是:把"几点几伏"这个连续量,变成一个数字。

⚠️ 树莓派**没有**内置 ADC,这是它和 Arduino 最大的分水岭之一

在 Arduino 上,analogRead(A0) 是一句理所当然的话——它芯片里就带着 ADC。 在树莓派上,这句话不存在。你翻遍文档也找不到 analogRead,因为它没有任何一根针能测量电压——40 针全都是数字针。 原因和 7-4 讲的"没有 DAC"是同一个:树莓派的主芯片(BCM 系列)是给跑操作系统的单板电脑设计的"应用处理器",它的本职工作是算数、管内存、跑 Linux;而 ADC/DAC 是"单片机的活"。树莓派从设计开始就没打算干这件事。 所以:在树莓派上做模拟输入的答案永远是——外接一颗 ADC 芯片。

顺带钉一个容易混的点:树莓派 Pico 是有 ADC 的(Pico 是微控制器,不是电脑——1-2 讲过"Pico 不是电脑")。别把"树莓派"和"树莓派 Pico"搞混,它们的定位完全不同。

三、解法:买一颗 ADC 芯片

最常用的两颗,选一颗就行:

芯片 通道数 分辨率 接口 特点 适合
MCP3008 8 路 10 位 SPI(4 根线) 便宜、通道多、资料最多 入门首选(本节用它)
ADS1115 4 路 16 位 I2C(2 根线) 精度高得多、还带可编程放大器 要精确、或接线想更少

"位"是什么意思?它决定能分多少档:

"精度差 64 倍"听起来很夸张,但对你的第一个项目毫无影响——测电位器、测光敏电阻,10 位完全够看。MCP3008 更便宜、通道更多、教程更多,所以入门选它。

四、接线:MCP3008(本章最复杂的一张表)

4.1 先做一件事:打开 SPI

接之前必须先在系统里把 SPI 打开(7-1 第四节说过"复用功能要先打开"):

sudo raspi-config

进 Interface Options → SPI → 选 Yes,然后重启。

怎么验证打开了:

ls /dev/spidev*

看到什么算成功:出现 /dev/spidev0.0 和 /dev/spidev0.1 两行。

没打开的话:这个命令什么都看不到(提示 No such file or directory)。这就是"接好了但读不到数"最常见的原因。

4.2 MCP3008 的 16 个脚

MCP3008 是块 16 脚的小芯片,脚位是固定的:

从左上角开始往下数(有凹口的一端朝上、对着自己):1 = CH0,2 = CH1,3 = CH2,4 = CH3,5 = CH4,6 = CH5,7 = CH6,8 = CH7; 从右下角开始往上数:9 = DGND,10 = CS,11 = DIN,12 = DOUT,13 = CLK,14 = AGND,15 = VREF,16 = VDD。

4.3 接线表

MCP3008 的脚 接到树莓派的哪 说明
16 · VDD 3.3V(物理针 1 或 17) 芯片自己的电源
15 · VREF 3.3V(同上) 参考电压——决定量程上限
14 · AGND GND 模拟地
9 · DGND GND 数字地
13 · CLK BCM 11(物理针 23) SPI 时钟
12 · DOUT BCM 9(物理针 21) 芯片 → 树莓派(数据出)
11 · DIN BCM 10(物理针 19) 树莓派 → 芯片(数据进)
10 · CS BCM 8(物理针 24) 片选("我要跟你说话了")
1 · CH0 电位器的中间脚 第 0 号通道的输入
⚠️ `VDD` 和 `VREF` 都接 3.3V,**绝对不要接 5V**

为什么这么强调?因为 VREF 决定了量程:接 3.3V 时量程是 0~3.3V;如果你为了"量程更大"把它接到 5V,那么芯片的 DOUT(数据输出脚)也会用 5V 电平往树莓派送信号——而 7-1 说过,5V 进 GPIO 是在赌板子的命。 规则:凡是和树莓派信号线相连的芯片,供电跟着树莓派走 3.3V。想要量程更大,得加"电平转换"电路,那是另一门功课。

4.4 电位器怎么接(这一步很容易接错)

电位器有三个脚:两端的两个,加中间那个(叫"滑臂")。

电位器的脚 接到
任意一端 3.3V
另一端 GND
中间脚(滑臂) MCP3008 的 CH0

转动旋钮时,中间脚的电压就在 0V 和 3.3V 之间连续变化——它本质上是一个"分压器"。

⚠️ 只接中间脚是**读不到东西**的

新手常犯的错:把电位器当成一个"可变电阻",只接中间脚和某一端。 那样接,中间脚的电压是浮空的(7-3 讲过浮空:读数会乱跳),你会在程序里看到数字毫无规律地乱蹦。 必须三个脚都接上:两端一个接 3.3V、一个接 GND,中间脚送信号去 CH0。

接线前断电;接完用眼睛检查一遍再上电。

五、用 gpiozero 读它:简单得让人不太敢信

gpiozero 内置了 MCP3008 支持,所以你不用装任何新库(这也是本节选它的理由之一):

from gpiozero import MCP3008

pot = MCP3008(channel=0)     # 第 0 号通道

print(pot.raw_value)         # 0 ~ 1023 的整数(芯片给出的原始值)
print(pot.value)             # 0 ~ 1 的小数(换算好的比例)
print(pot.value * 3.3)       # 换算成"伏"

两个属性,一个公式,就是全部:

属性 范围 含义
raw_value 0 ~ 1023 的整数 芯片直接给出的原始数字(10 位)
value 0.0 ~ 1.0 的小数 比例("满量程的百分之几")
电压 0 ~ 3.3V value × 3.3,或"raw_value ÷ 1023 × 3.3"

对照表(这张表值得记):

raw_value value 电压 电位器位置
0 0.000 0.00V 转到最左
256 0.250 0.83V 约 1/4
512 0.500 1.65V 正中间
768 0.751 2.48V 约 3/4
1023 1.000 3.30V 转到最右

看最后一行:raw_value 最大是 1023,不是 1024——因为从 0 开始数,1024 档就是 0~1023(计数从 0 起,这是编程的通用规矩,6-6 下标从 0 起是同一个道理)。

六、动手 Demo:让旋钮说话(两版)

接线(先断电):按上一节的接线表接好 MCP3008 + 电位器,另外保留 7-3 那颗 LED(BCM 17 → 220Ω → GND)。

第 1 步 · 读出来,还画个小柱子

nano adc_read.py
from gpiozero import MCP3008
import time

pot = MCP3008(channel=0)

print("转动电位器,看读数变化。Ctrl+C 退出。")
try:
    while True:
        raw = pot.raw_value
        ratio = pot.value
        volt = ratio * 3.3
        bar = "#" * int(ratio * 40)          # 用 40 个字符表示位置
        print(f"raw={raw:4d}  value={ratio:.3f}  {volt:5.2f}V  |{bar:<40}|")
        time.sleep(0.2)
except KeyboardInterrupt:
    print("\n已结束")
finally:
    pot.close()

跑:python3 adc_read.py

你会看到什么算成功(转动旋钮时,raw、value、电压和柱子一起变):

raw=   0  value=0.000   0.00V  |                                        |
raw= 256  value=0.250   0.83V  |##########                              |
raw= 512  value=0.500   1.65V  |####################                    |
raw= 768  value=0.751   2.48V  |##############################          |
raw=1023  value=1.000   3.30V  |########################################|

读结果:注意 raw 最大到 1023 就上不去了——这就是 10 位的天花板。

这个"柱子"值得多看一眼:"#" * int(ratio * 40) 的意思是把 0~1 的比例映射到 0~40 个字符。同一套"把连续量变成刻度"的思路,就是所有仪表盘、进度条、音量条的底层逻辑。

第 2 步 · 让旋钮控制灯的亮度(把 7-4 和 7-5 接起来)

nano adc_to_led.py
from gpiozero import MCP3008, PWMLED
import time

pot = MCP3008(channel=0)
led = PWMLED(17)

print("转动电位器 → 灯的亮度跟着变。Ctrl+C 退出。")
try:
    while True:
        led.value = pot.value        # 直接把读到的比例送给灯
        time.sleep(0.01)
finally:
    led.off()
    pot.close()

跑:python3 adc_to_led.py

你会看到什么算成功:转动旋钮,灯的亮度平滑跟着变。

读结果:注意中间那行 led.value = pot.value —— 一边是"读进来的模拟量",一边是"送出去的模拟量",这一行就是"闭环控制"的雏形。

这一行也解释了本课的一个大主题:树莓派真正擅长的不是"处理模拟信号",而是"做决定"——读一个值、算一算、再输出一个值。模拟量的采集和输出可以交给专门的芯片,中间的"判断与计算"归它。

七、30 秒实验:算一算"10 位"和"16 位"差多少

python3 -c "
print('MCP3008  (10 位):%.2f mV 一档' % (3.3/1024*1000))
print('ADS1115  (16 位):%.3f mV 一档' % (3.3/65536*1000))
print('相差倍数:%.0f 倍' % (65536/1024))
"

你会看到什么:

MCP3008  (10 位):3.22 mV 一档
ADS1115  (16 位):0.050 mV 一档
相差倍数:64 倍

读结果:"位数"就是"能分多细"。

一句话:先问"我要测的变化有多大",再决定"需要几位"。——这比"哪个芯片更好"这种问法有用得多。

八、常见误区

  1. "树莓派有 analogRead 可以直接读模拟量" —— 没有。这是 Arduino 的写法。树莓派没有任何内置 ADC,读模拟电压必须外接芯片(MCP3008/ADS1115)。记住"树莓派 Pico 有 ADC、树莓派没有"这个区别。
  2. "把芯片的 VREF 接 5V 量程更大更好" —— 绝对不能。接 5V 会让芯片用 5V 电平往 GPIO 送信号,有烧板子的风险(7-1 的铁律)。和树莓派相连的芯片一律 3.3V。
  3. "电位器接中间脚就够了" —— 不行。两端必须分别接 3.3V 和 GND,否则中间脚浮空、读数乱跳。
  4. "接好了代码也抄对了,但读到的永远是 0" —— 十有八九是 SPI 没打开。先跑 ls /dev/spidev* 确认;没有就回 raspi-config 打开并重启。
  5. "raw_value 最大是 1024" —— 是 1023。因为从 0 开始数,1024 档是 0~1023(和"列表下标从 0 开始"同一个道理)。
  6. "10 位太差了,必须上 16 位" —— 先看你要测什么。电位器、光敏、土壤湿度这类,10 位完全够;只有"变化极其微小"的信号(称重、精密传感器)才需要 16 位。
  7. "MCP3008 有 8 个通道,可以同时读 8 路" —— 它是轮流读的(一次选一个通道),不是同时。对绝大多数项目,轮流读快到看不出来(每秒能读十几万次)。
自测三问(点开对答案) 1. 为什么树莓派读不了模拟电压?这和上一节讲的"没有 DAC"有什么关系? 2. MCP3008 读回 `raw_value = 512`,对应的电压是多少?把你算的式子写出来。 3. 你把 ADC 接好了、代码也抄对了,但 `raw_value` 一直是 0,最该先查什么? **参考答案** 1. 因为**树莓派没有内置 ADC(模数转换器)**——它的 40 针**全部是数字针**,只能判断"高还是低",无法测量中间电压。 这和"没有 DAC"是**同一个根本原因**:**树莓派的主芯片是给"跑操作系统的电脑"设计的应用处理器,ADC/DAC 属于单片机的职能**。所以它俩都得靠外接芯片解决(输出用 DAC,输入用 ADC)。 (**注意别和 Pico 混**:Pico 是微控制器,**它自带 ADC**。) 2. **约 1.65V**。算法:**512 ÷ 1023 × 3.3 ≈ 0.5005 × 3.3 ≈ 1.65V**(用 `value` 表达就是 **`value = 0.500`,再乘 3.3**)。 注意分母是 **1023**(不是 1024)——因为 10 位的 1024 档是从 0 数到 1023。 3. **先查 SPI 有没有打开**:跑 `ls /dev/spidev*`,看不到设备就说明接口没开 → `sudo raspi-config` → Interface Options → SPI → 打开并重启。 **这个"读不到数先查接口开关"的顺序很重要**——它比"怀疑接线"和"怀疑代码"都更可能命中(因为接线和代码通常是你照着抄的,而"接口没开"是教程常常漏掉的一步)。

相关笔记