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

前面五节你都在一根一根地接线。这一节换个方式:一整套设备,直接叠上去。 Sense HAT 是一块官方扩展板——64 颗彩色小灯 + 一个摇杆 + 一堆环境传感器,插上去就能用。它还有个了不起的来历:它的兄弟真的在国际空间站上跑过学生的代码。

📝 本节和前面几节的关系

7-1 讲 40 针时提过 HAT 这个词和"BCM 0/1 两根针留给 HAT 身份识别";本节终于见到一块真 HAT,也明白那两根针为什么不能动。 7-3 到 7-5 里,LED、按钮、传感器全是你自己一根线一根线接的;Sense HAT 把这些都做进了一块板子——所以本节的重点不是"新原理",而是"同一批概念,换一种组织方式"。

一、Sense HAT 是什么:叠上去的一块板

HAT = Hardware Attached on Top,意思是"装在(树莓派)上面的硬件"。它不是随便一块扩展板,而是有规格的:

💡 它为什么会存在:一段空间站的故事

Sense HAT 是为国际空间站(ISS)的 Astro Pi 项目设计的——那是树莓派基金会和欧洲空间局合作的一个教育项目:把两块装了 Sense HAT 的树莓派送上太空,让学生写的 Python 代码在天上真的跑起来。 为什么非得是"树莓派 + Sense HAT"这种组合?因为在空间站上你得用一块能开机、能联网、还不用焊线的板子:LED 矩阵用来给学生显示反馈,传感器用来测舱内环境,而代码就是普通的 Python——学生在教室里写的东西,可以原样送到 400 公里高空去运行。 这是本课最值得知道的一件事:你现在要玩的这块板子,和天上的那两块是同一款产品。

二、上面有些什么

三大部分,正好对应你前三节学的东西:

2.1 8×8 RGB LED 矩阵(= 64 颗灯)

64 颗灯排成 8 行 8 列,每颗都能单独调颜色。(普通 LED 只能亮/灭,这里的每颗灯相当于 7-4 讲的"三色 + 可调亮度"。)

它的坐标:x 从 0(最左)到 7(最右),y 从 0(最上)到 7(最下)——和 6-6 讲的列表下标一样从 0 开始。

2.2 五向摇杆(= 一个更高级的按钮)

上、下、左、右、按下(中)五个方向。它其实是 7-3 那个按钮的升级版:不只"按没按",还能告诉你"往哪个方向推"。

2.3 一堆环境与运动传感器

传感器 它测什么 大白话
加速度计 三个方向的加速度 感觉得到"倾斜、晃动、掉下去"(静止时读数就是重力)
陀螺仪 转动的快慢 感觉得到"在转、转多快"
磁力计 磁场方向 就是电子罗盘(能指北)
气压计 空气压力 能算"大概在几米高",也能看天气趋势
湿度计 空气湿度 就是湿度
温度计 温度 注意:它会被 CPU 烤热(见第五节)

九轴 IMU 是什么:加速度计(3 轴)+ 陀螺仪(3 轴)+ 磁力计(3 轴)= 9 轴。三个合起来,才能算出"板子在空间里的姿态"(手机能自动转屏,靠的就是这套)。

V2 版还多一个色彩传感器(能测环境光的颜色和亮度)。

💡 为什么这堆传感器是"一块板子上的一整组"

因为它省掉了你 7-5 那种"接一颗芯片、配四根线、还要开 SPI"的全过程。 Sense HAT 和树莓派之间主要靠 I2C 通信(还记得 7-1 说的吗——I2C 只用两根线就能管一串设备)。板上有一颗小控制器替它管那 64 颗灯和摇杆,树莓派这边只要发指令过去。 这就是"扩展板"的价值:把复杂的接线,变成一句 apt install。

三、装上它:两步

3.1 硬件:断电、对准、压下去

  1. 先关机、拔掉电源(这一点没有例外);
  2. 把 Sense HAT 八 轻轻对准 40 针排针——注意方向:板子上的孔要和排针一一对齐,别硬按;
  3. 确认四周都平贴、没有歪斜,然后用配的螺丝/铜柱固定(避免它被碰歪)。

如果你的板子上已经有别的东西插在排针上,得先拔掉(或者用"堆叠排针"叠着插,但那是进阶话题)。

3.2 软件:一条命令

上电、连上网之后:

sudo apt update
sudo apt install sense-hat
sudo reboot

三条命令各干什么:

命令 作用
apt update 刷新软件清单(4-5 讲过的规矩:先 update)
apt install sense-hat 装上官方 Python 库,它会顺便把 I2C 也打开
reboot 重启(接口开关生效必须重启)

验证 I2C 打开了:

ls /dev/i2c*

看到什么算成功:出现 /dev/i2c-1。

再看一眼板子在不在总线上:

sudo apt install -y i2c-tools
i2cdetect -y 1

你会看到什么:一张十六进制的格子表,其中某几个格子里印着地址(如 1c、5c、6a)。

读结果:有地址 = 板子被认出来了(每个传感器占一个 I2C 地址)。整张表全是 --(空格)= 它没接好——回去检查排针对齐没有,或者 I2C 没开。

四、用它:四个方向

4.1 点亮矩阵

from sense_hat import SenseHat

sense = SenseHat()

sense.show_message("Hello", text_colour=(0, 255, 0), scroll_speed=0.06)   # 滚动显示
sense.show_letter("A")                                                   # 显示一个字符
sense.set_pixel(0, 0, 255, 0, 0)                                         # 左上角点一颗红灯
sense.clear(0, 0, 255)                                                   # 整块变蓝
sense.clear()                                                            # 全灭

几个常用参数:

参数 作用
text_colour=(r, g, b) 字色(每个分量 0~255)
back_colour=(r, g, b) 背景色
scroll_speed= 滚动速度(越小越快,默认 0.1)

还有更高级的两招:

sense.set_rotation(180)     # 整块显示转 180 度(取决于你的板子朝哪边)
sense.set_pixels([...])     # 一次画 64 颗灯(列表必须正好 64 个元素)
⚠️ `show_message` **不支持中文**

Sense HAT 内部用的是一套很小的点阵英文字体,show_message("你好") 会显示成一堆问号或方块。 想在矩阵上表达中文只有一个办法:自己画——用 set_pixels() 手工拼出 8×8 的点阵图案(一个汉字要 8×8 甚至更大才看得清,实际很难)。 规矩:矩阵上的文字一律用英文/数字。

4.2 读传感器

print(sense.get_temperature())       # 温度(℃)
print(sense.get_humidity())          # 湿度(%)
print(sense.get_pressure())          # 气压(hPa)
print(sense.get_accelerometer_raw()) # 三轴加速度(原地原始值)
print(sense.get_gyroscope_raw())     # 三轴角速度
print(sense.get_compass())           # 罗盘方向(角度)
print(sense.get_orientation())       # 姿态:俯仰 pitch/横滚 roll/偏航 yaw

4.3 用摇杆

from sense_hat import SenseHat, ACTION_PRESSED

sense = SenseHat()

for event in sense.stick.get_events():
    print(event.direction, event.action)     # 例如 up pressed

摇杆的用法和 7-3 的按钮一模一样——只是"事件"多了个方向。这是本节最值得体会的一点:你已经学过的概念,换个设备还在用。

五、⚠️ 两个必须知道的坑

5.1 温度读数偏高:不是坏了

你会读到 40 多度甚至更高,吓一跳。原因:温度传感器就在 CPU 旁边几毫米的地方——它测的是"被 CPU 烤热的板子",不是"室温"。

两个解法:

办法 做法 效果
换一个传感器读 sense.get_temperature_from_pressure() 通常更接近室温(这颗离 CPU 远一点)
把板子挪开 用一根 40 针排线把 Sense HAT 引到远处 最准(物理隔离热源)

对比着看:sense.get_temperature() 和 sense.get_temperature_from_pressure() 两个数差好几度是正常的——这本身就是一个很好的实验:让你亲眼看到"传感器读数是"环境 + 它自己的处境"共同决定的。

5.2 显示方向不对:不是接线错

你装的方向和板子"以为"的方向可能差了 90° 或 180°,于是"上"变成了"下"。

sense.set_rotation(180)     # 依次试 0 / 90 / 180 / 270,哪个正就用哪个
⚠️ 还有一件事:LED 矩阵会被"系统"占用

Sense HAT 的矩阵有时会被系统当作状态显示屏用(比如开机时显示彩虹、或者显示系统状态)。 如果发现自己的显示"被覆盖",或者程序跑完图案就没了——先确定没有别的进程在用矩阵(别同时跑两个会显示的脚本),必要时重启一次,再跑你的程序。 这和 7-2 讲的"一根针只能有一个主人"是同一条原理:同一块设备也只能有一个主人。

六、动手 Demo:三小步

第 1 步 · 打招呼

nano hat_hello.py
from sense_hat import SenseHat

sense = SenseHat()
sense.set_rotation(0)                       # 方向不对就改 0/90/180/270

sense.show_message("PI", text_colour=(0, 255, 0), scroll_speed=0.08)
sense.show_letter("7", text_colour=(255, 255, 0))
sense.clear()
print("看到了吗?")

跑:python3 hat_hello.py

看到什么算成功:矩阵上滚出绿色的 PI,然后显示一个黄色的 7,最后熄灭。如果字是倒着的,把 set_rotation 改成 180 再跑一次。

第 2 步 · 一次把所有传感器读一遍

nano hat_sensors.py
from sense_hat import SenseHat
import time

sense = SenseHat()

for i in range(5):
    t1 = sense.get_temperature()
    t2 = sense.get_temperature_from_pressure()
    h = sense.get_humidity()
    p = sense.get_pressure()
    acc = sense.get_accelerometer_raw()
    o = sense.get_orientation()

    print(f"--- 第 {i + 1} 次 ---")
    print(f"温度(传感器): {t1:.1f} °C    温度(气压推算): {t2:.1f} °C")
    print(f"湿度: {h:.1f} %       气压: {p:.1f} hPa")
    print(f"加速度: x={acc['x']:+.2f} y={acc['y']:+.2f} z={acc['z']:+.2f} g")
    print(f"姿态: 俯仰 {o['pitch']:.1f}°  横滚 {o['roll']:.1f}°  偏航 {o['yaw']:.1f}°")
    time.sleep(2)

跑:python3 hat_sensors.py

你会看到什么算成功(数字会随环境变化,下面是示意):

--- 第 1 次 ---
温度(传感器): 41.3 °C    温度(气压推算): 33.8 °C
湿度: 46.2 %       气压: 1002.7 hPa
加速度: x=+0.01 y=-0.03 z=+1.00 g
姿态: 俯仰 0.4°  横滚 -0.4°  偏航 152.3°

读结果 · 三个数慢慢看:

现象 含义
两个温度差 5~8 度 正常的。上面那个是被 CPU 烤热的(5.1 讲的坑)
z ≈ +1.00 g 这就是重力。静止平放时,某个轴会正好"扛着 1 个 g"——g 是重力加速度单位
倾斜板子时 x/y 变化 那根"扛重力"的轴在转移——加速度计就是这么感知倾斜的
yaw 慢慢漂 正常的。磁力计容易受周围磁场干扰(音箱、电机、甚至桌子里的钢筋)

动手试试:把板子某一边抬起来,再跑一次,看 x / y / z 怎么变。你刚才做的事,就是"用代码读出一个物理动作"。

第 3 步 · 摇杆控制一个小点(小游戏雏形)

nano hat_joystick.py
from sense_hat import SenseHat, ACTION_PRESSED
import time

sense = SenseHat()
sense.clear()

x, y = 3, 3          # 从中间开始

def draw():
    sense.clear()
    sense.set_pixel(x, y, 255, 255, 255)

def move(dx, dy):
    global x, y
    x = min(7, max(0, x + dx))       # 夹在 0~7 之间,出不了边界
    y = min(7, max(0, y + dy))
    draw()

draw()
print("用摇杆移动白点,Ctrl+C 退出。")

try:
    while True:
        for event in sense.stick.get_events():
            if event.action == ACTION_PRESSED:
                if event.direction == "up":
                    move(0, -1)      # 往上=y 变小(y=0 在最上面)
                elif event.direction == "down":
                    move(0, 1)
                elif event.direction == "left":
                    move(-1, 0)
                elif event.direction == "right":
                    move(1, 0)
        time.sleep(0.05)
finally:
    sense.clear()

跑:python3 hat_joystick.py

看到什么算成功:中间有一颗白点,用摇杆推它四处移动,到边就停住。

读结果:min(7, max(0, ...)) 这个套法叫边界夹取——先和 0 取较大者(防小于 0),再和 7 取较小者(防大于 7)。这是所有游戏里"角色不能走出屏幕"的标准写法。

这一小段代码里,已经有一个游戏的全部要素了:一个位置(x, y)、一个渲染函数(draw())、一个输入处理(摇杆事件)。你在这一节串起来的东西,就是 7-3 到 7-5 的全部积累。

七、30 秒实验:用气压算一算"我在几米高"

气压随高度下降,所以从气压能反推海拔:

python3 -c "
p = 1000.0        # 把它改成你刚才读到的气压值
h = 44330 * (1 - (p / 1013.25) ** (1 / 5.255))
print(f'气压 {p:.1f} hPa  →  大约海拔 {h:.0f} 米')
"

你会看到什么(用 p = 1000.0 时):

气压 1000.0 hPa  →  大约海拔 111 米

读结果:1013.25 hPa 是海平面的标准大气压,所以气压低于它就意味着"比海平面高"。把 p 换成你自己读到的值跑一遍,就知道自己大概在海拔多少米。

⚠️ 别指望这个数能当高度计用

气压还随天气变化(同一个地方,晴天和台风天能差几十 hPa,换算成高度就是几百米)。 所以这个公式只能给你"数量级"的答案——想真正测高度,得结合当天的当地气压基准去校准(或者用 GPS)。 这个实验的价值不在"测准",而在让你看到:传感器读出来的一个数字,经过一条公式,就变成了另一个有意义的信息。这就是"物联网"最基础的动作。

八、常见误区

  1. "sense.get_temperature() 读到 41 度,是板子出问题了" —— 不是。温度传感器紧挨着 CPU,测的是"被烤热的板子"。想接近室温就用 get_temperature_from_pressure(),或者用排线把 HAT 引开。
  2. "show_message 能显示中文" —— 不能。它只有一套很小的英文字体,中文会变成问号/方块。矩阵上的文字只能用英文和数字。
  3. "显示的字是倒的,说明接线接反了" —— 不是接线问题。HAT 是整块压上去的,没有接反这回事。用 set_rotation() 调方向(依次试 0/90/180/270)。
  4. "传感器这么多,肯定占掉了一堆 GPIO 针" —— 它主要靠 I2C(两根线)通信,板上自带小控制器替它管灯和摇杆。这也是 HAT 这种形态存在的意义:不占用你一堆针。
  5. "i2cdetect 全是空格,是软件没装好" —— 先查排针有没有插到底、有没有对齐(物理接触问题最常见),再查 I2C 开了没有(ls /dev/i2c*)。
  6. "摇杆和 7-3 的按钮是两种完全不同的东西" —— 本质一样,都是"读一个输入 + 触发一段代码",摇杆只是多了方向信息。学过的概念换个设备照样用,这就是本节最想让你体会的一点。
  7. "set_pixels() 里放多少个元素都行" —— 必须正好 64 个(8×8),少了或多了会报错。顺序是从左上开始、一行一行排下去。
自测三问(点开对答案) 1. Sense HAT 的 LED 矩阵坐标里,`(0, 0)` 是哪一个角?往上走是 `y` 变大还是变小? 2. 你读到传感器温度 41.5°C、气压推算温度 34.1°C。哪个更接近室温?为什么差这么多? 3. Sense HAT 装上以后,"7-3 学的按钮"和"7-6 的摇杆"在用法上像在哪里? **参考答案** 1. **`(0, 0)` 是最左上角**;`x` 向右增大(0→7),**`y` 向下增大(0→7)**。 **所以"往上"= `y` 变小**——这就是 Demo 第 3 步里 `move(0, -1)` 表示"上"的原因。**屏幕上"y 向下长"是几乎所有图形系统的共同约定**(因为它来源于"一行一行往下打印"的排字方式)。 2. **气压推算的那个(34.1°C)更接近室温。**原因是 **`get_temperature()` 那颗传感器就贴在 CPU 旁边,CPU 工作时会发热,把它烤高了**;而气压传感器那颗离热源远一些。 想更准有两条路:**改用 `get_temperature_from_pressure()`**,或者**用 40 针排线把 Sense HAT 引到离板子远一点的地方**(物理隔开热源)。 3. **像在"事件驱动"这层**:两边都不是你去"不停问它按了没",而是**拿到事件/回调去处理**;区别只是**摇杆的事件多带一个"方向"**(上下左右中),而按钮只有"按下/松开"。 **这正说明:硬件换了一茬,你在 7-3 学的写法还能直接用**——因为"设备对象 + 事件回调"这套抽象是 gpiozero 和 Sense HAT 库**共同的设计思路**。
📄 第七章读完了,回头看这条线

7-1 看清这排针 → 7-2 拿到指挥它的工具 → 7-3 第一次通电(灯亮了)→ 7-4 让灯会"半亮" → 7-5 学会"读"外面的世界 → 7-6 用一块板子把这一切打包。 走完这一章,你这台树莓派终于不只是"一台小电脑"了——它有了眼睛(传感器)、有了手(输出针)、有了耳朵(输入针),能感知和改变身边的物理世界。 这也是全课到此为止最大的转折:前六章你都在"把机器本身搭起来、用起来";从这一章起,机器开始替你接触现实。后面要学的摄像头、显示屏、机器人,全都建立在这一章的基础上。

相关笔记