前置知识:了解 CrossPoint Reader 的硬件组成(第 2 章),已搭建好开发环境(第 3 章)。 本章目标:理解嵌入式开发中三种最核心的硬件通信方式,理解 HAL 层设计思想,看懂设备自动识别的实现。
在嵌入式世界里,CPU 和外部设备(墨水屏、SD 卡、传感器……)之间需要”对话”。这种对话不是通过网线或蓝牙,而是通过 PCB 上的铜线——也就是引脚之间的物理连接。 本章介绍三种最常见的”对话方式”:GPIO(直接控制引脚电平)、SPI(高速串行总线)、I2C(低速多设备总线)。CrossPoint Reader 同时使用了这三种方式。
4.1 GPIO 基础
什么是 GPIO
GPIO 的全称是 General-Purpose Input/Output,翻译过来就是”通用输入输出引脚”。 ESP32-C3 有大约 22 个 GPIO 引脚。每个引脚本质上就是芯片上的一根”触手”,可以:- 输出:CPU 命令它输出高电平(3.3V)或低电平(0V)
- 输入:CPU 读取它当前是高电平还是低电平
类比:GPIO 就像一个个”开关”。设为输出时,CPU 可以拨动开关;设为输入时,CPU 可以查看开关被外部拨到了哪一侧。
数字输出:控制 LED 和 MOSFET
最简单的 GPIO 用法是数字输出——让引脚输出高或低电平。类比:GPIO 引脚就像一根手指,它能拨动一个继电器开关(MOSFET),而继电器开关控制着大功率电路。你的手指力气小,但可以拨动一个开关来控制整间房的灯。
数字输入:读按键
INPUT_PULLUP 值得解释一下。如果一个输入引脚什么都不接,它的电平是不确定的(可能随机跳变,术语叫”浮空”)。上拉电阻的作用是:在没有按键按下时,把引脚”拉”到高电平,给它一个确定的默认状态。按键按下后,引脚被接到地(GND),电平变低,CPU 就知道”按键按下了”。
模拟输入:ADC 读电池电压
GPIO 不仅可以读”高/低”两种状态,有些引脚还可以读取模拟电压值——这就是 ADC(Analog-to-Digital Converter,模数转换器)。中断:让硬件主动通知 CPU
前面的digitalRead() 是一种轮询方式——CPU 不断地去查看按键状态。这就像你每隔几秒看一次手机有没有新消息,很浪费精力。
更高效的方式是中断:告诉硬件”当按键按下时,主动通知我”。
类比:轮询就像每隔五分钟去信箱看看有没有信;中断就像装了一个门铃——有信来了,邮递员按门铃通知你。
IRAM_ATTR 是一个特殊标记,告诉编译器”把这个函数放到 IRAM(内部 RAM)里”。因为中断发生时 Flash 可能正在被使用(比如正在读取数据),如果中断函数也存在 Flash 里就会冲突。放到 RAM 里就没这个问题了。
CrossPoint Reader 的按键检测就使用了中断方式,这样设备在深度睡眠时可以被按键唤醒——CPU 休眠时没法轮询,但中断可以把它”叫醒”。
4.2 SPI 总线
什么是 SPI
SPI 的全称是 Serial Peripheral Interface(串行外设接口)。它是一种高速的通信方式,适合传输大量数据——比如把一整屏画面数据发送给墨水屏,或者从 SD 卡读取一本电子书。 SPI 使用 4 根线:| 线名 | 全称 | 方向 | 作用 |
|---|---|---|---|
| SCLK | Serial Clock | 主机 → 从机 | 时钟信号,“节拍器” |
| MOSI | Master Out, Slave In | 主机 → 从机 | 主机发送数据给从机 |
| MISO | Master In, Slave Out | 从机 → 主机 | 从机发送数据给主机 |
| CS | Chip Select | 主机 → 从机 | 片选,选择跟哪个从机通信 |
类比:SPI 就像一套对讲机系统。SCLK 是统一的节拍(“一、二、三,发送!”),MOSI 是主机的麦克风,MISO 是主机的耳机,CS 是频道选择——你要先选好频道(拉低某个设备的 CS),对方才会听你说话。
为什么墨水屏和 SD 卡可以共享 SPI
CrossPoint Reader 上,墨水屏和 SD 卡共用同一组 SPI 总线(共享 SCLK、MOSI、MISO 三根线),只是各有各的 CS 引脚。- 要和墨水屏通信:拉低
CS_EPD(选中墨水屏),保持CS_SD为高(SD 卡”睡觉”),然后通过 MOSI/MISO 交换数据。 - 要和 SD 卡通信:拉低
CS_SD(选中 SD 卡),保持CS_EPD为高(墨水屏”睡觉”),然后通过 MOSI/MISO 交换数据。 - 同一时刻只能和一个设备通信——CS 保证了”对话”的排他性。
类比:一条电话线上接了两部电话(墨水屏和 SD 卡)。你拨某个设备的”分机号”(拉低它的 CS),只有被叫的那部电话会响,另一部不理你。
SPI 的速度优势
SPI 通常可以跑到几十 MHz 的时钟频率。CrossPoint Reader 的墨水屏 SPI 时钟设为 20MHz 左右——这意味着每秒可以传输约 20Mbit 的数据。对于把一整帧画面(约 200KB)推送到墨水屏来说,绰绰有余。4.3 I2C 总线
什么是 I2C
I2C 的全称是 Inter-Integrated Circuit(集成电路间通信),读作”I-squared-C”或”I-two-C”。它是一种低速但灵活的通信方式,只需要 2 根线:| 线名 | 全称 | 作用 |
|---|---|---|
| SDA | Serial Data | 数据线(双向) |
| SCL | Serial Clock | 时钟线 |
类比:如果 SPI 是”对讲机”(快但需要多根线),I2C 就像挂号信系统——只有一条邮路(SDA),但每封信上都写着收件人的地址。虽然慢一些,但一条线就能寄给很多人。
地址寻址:每个设备有唯一地址
I2C 总线上可以挂多个设备,每个设备有一个 7 位地址(0x00 ~ 0x7F)。主机(ESP32-C3)发送数据时,先发送目标地址,只有地址匹配的设备才会响应。 CrossPoint Reader(X3 版本)的 I2C 总线上挂了 3 个芯片:- BQ27220(地址 0x55):电量计芯片,精确追踪电池的充电/放电状态
- DS3231(地址 0x68):实时时钟(RTC),即使设备关机也能保持走时
- QMI8658(地址 0x6B):六轴运动传感器(陀螺仪 + 加速度计),可以检测设备的姿态
SPI vs I2C 速查对比
| 特性 | SPI | I2C |
|---|---|---|
| 线数 | 4 根(SCLK+MOSI+MISO+CS) | 2 根(SDA+SCL) |
| 速度 | 快(通常 1~80 MHz) | 慢(通常 100~400 KHz) |
| 设备数量 | 每加一个设备多一根 CS 线 | 靠地址区分,理论上 127 个 |
| 典型用途 | 屏幕、SD 卡、Flash | 传感器、时钟、电量计 |
| 选谁 | 需要传大量数据时 | 数据量小但设备多时 |
总结:数据量大、速度要求高的设备(墨水屏、SD 卡)走 SPI;数据量小但设备多的(传感器、时钟)走 I2C。CrossPoint Reader 两种都用了,各司其职。
4.4 HAL 层设计思想
为什么要封装硬件细节
假设你要在屏幕上显示一行文字。最底层的操作是:- 拉低墨水屏的 CS 引脚
- 通过 SPI 发送”设置显示窗口”命令
- 计算每个像素的位置
- 把像素数据一个字节一个字节地通过 SPI 发出去
- 发送”刷新屏幕”命令
- 拉高 CS 引脚
类比:你开车时只需要踩油门、刹车、转方向盘。你不需要知道发动机的活塞怎么运动、刹车片怎么摩擦、转向柱怎么传动——这些细节被”封装”在了汽车的操控接口里。HAL 就是嵌入式开发中的”汽车操控接口”。
CrossPoint Reader 的 HAL 模块
CrossPoint Reader 将硬件操作封装为四个 HAL 模块:- HalDisplay:封装墨水屏的 SPI 通信。上层只需调用
clearScreen()、drawPixel()等函数,不需要知道底层的 SPI 命令序列。 - HalGPIO:管理所有 GPIO 引脚的配置和中断注册。上层只需调用
isButtonPressed()即可,不需要关心上拉电阻和中断配置。 - HalPowerManager:封装电池电量检测(通过 I2C 读取 BQ27220 或通过 ADC 直接读取)和电源控制(MOSFET 开关)。
- HalStorage:封装 SD 卡的 SPI 通信和文件系统操作。上层只需调用
readFile()/writeFile()。
4.5 设备自动识别(X3 vs X4)
CrossPoint Reader 有两个硬件版本:- X3:I2C 总线上挂了 BQ27220(电量计)、DS3231(时钟)、QMI8658(陀螺仪)
- X4:没有这些 I2C 芯片,用 ADC 读电池电压,用 NTP 网络对时
核心思路:I2C 探测
原理很简单——向 I2C 总线上的已知地址发送信号,看有没有设备应答。有应答说明芯片存在(X3),没有应答说明芯片不在(X4)。 这就像在一栋公寓楼里挨个敲门:如果 0x55 号门有人应答、0x68 号门有人应答、0x6B 号门有人应答——那这栋楼就是 X3 版本。如果敲了半天没人应,那就是 X4。完整代码解析
probeXXXSignature() 函数不只是简单地检查”有没有设备应答”,还会读取芯片的特征寄存器来验证身份——就像不只是看有没有人开门,还要核对身份证。
为什么要探测两轮
- 加快启动速度——探测 I2C 设备需要时间
- 避免极端情况下的误判——只在首次开机(或 NVS 被清除时)才做探测
score() 的含义
X3ProbeResult 有一个 score() 方法,返回”成功探测到的芯片数量”。阈值设为 2 表示:三个芯片中至少检测到两个就认定为 X3。之所以不要求全部三个都在,是为了容错——万一某个芯片偶尔接触不良,不至于把 X3 误判为 X4。
本章要点
- GPIO 是最基本的硬件通信方式。数字输出控制 LED/MOSFET,数字输入读取按键,模拟输入(ADC)读取电压,中断实现硬件事件的即时通知。
- SPI 是高速串行总线(4 根线),适合大数据量传输。墨水屏和 SD 卡共享 SPI 总线,通过各自的 CS 片选线区分”跟谁说话”。
- I2C 是低速双线总线,通过地址寻址,适合挂载多个低速传感器。CrossPoint Reader X3 的 I2C 总线上挂了电量计(0x55)、时钟(0x68)、陀螺仪(0x6B)。
- HAL 层将硬件操作细节封装为简洁的接口,让上层代码不依赖具体硬件。这使得同一套应用代码可以在不同硬件版本(X3/X4)上运行。
- 设备自动识别通过 I2C 探测实现:向已知地址发信号,有设备应答就是 X3,否则是 X4。两轮探测保证可靠性,结果缓存到 NVS 加快后续启动。
- 嵌入式开发的一条重要原则:不要依赖单次测量,多测几次取共识——硬件世界充满噪声和不确定性。