第三章 硬件接口基础:UART、SPI、I2C 与 GPIO 控制
各位同学,大家好。欢迎来到第三章。
这一章,咱们要啃硬骨头了。硬件接口,说白了就是芯片之间怎么“说话”。你想想看,指纹模块采集到图像数据,总得传给主控吧?怎么传?用哪根线?传多快?这些就是接口协议要解决的问题。
我个人习惯,在开始写驱动之前,一定先把接口时序图吃透。时序图看不懂,驱动写出来也是瞎蒙。今天咱们就把门禁指纹模块最常用的四种接口——UART、SPI、I2C 和 GPIO,一个一个捋清楚。
3.1 UART 通信协议详解
UART,全称通用异步收发传输器。这玩意儿历史可久了,但至今仍是嵌入式调试和低速通信的“万金油”。
为什么叫“异步”?因为它没有单独的时钟线。发送方和接收方得事先约定好一个“波特率”,比如 115200。然后双方各自按这个节奏收发数据。
核心要点:UART 通信至少需要两根线——TX(发送)和 RX(接收)。注意,交叉连接:A 的 TX 接 B 的 RX,A 的 RX 接 B 的 TX。地线 GND 也必须共地。
我在项目中遇到过好几次,新手把 TX 和 RX 接反了,然后死活调不通。嗯,这种低级错误我也犯过,后来学乖了,上电前先用万用表量一下电平。
UART 的数据帧格式是这样的:
- 起始位:1 位,逻辑 0
- 数据位:5~8 位,通常用 8 位
- 校验位:可选,奇校验或偶校验
- 停止位:1 位或 2 位,逻辑 1
举个实际例子。咱们门禁模块常用的指令格式:
// 发送采集指纹指令
// 包头: 0xEF 0xAA
// 设备地址: 0xFF 0xFF
// 包长度: 0x00 0x03
// 指令码: 0x01 0x00
// 校验和: 0x00 0x05
uint8_t cmd[] = {0xEF, 0xAA, 0xFF, 0xFF, 0x00, 0x03, 0x01, 0x00, 0x00, 0x05};
避坑指南:我曾经在调试 UART 时,发现数据偶尔会错位。查了半天,原来是波特率误差累积导致的。建议:如果主频不是整数倍,尽量选 115200 或 9600,误差小。另外,记得使能 FIFO,能大大减轻 CPU 负担。
3.2 SPI 通信协议详解
SPI,全称串行外设接口。这玩意儿比 UART 快多了,适合传输大块数据,比如指纹图像。
SPI 是同步通信,有专门的时钟线(SCLK)。主设备负责产生时钟,从设备跟着节奏走。说白了,就是主设备“带着”从设备跳舞。
SPI 需要四根线:
- SCLK:时钟线,由主设备控制
- MOSI:主设备输出,从设备输入
- MISO:主设备输入,从设备输出
- CS:片选线,低电平有效。你要跟哪个设备说话,就把它的 CS 拉低
你想想看,如果总线上挂了多个 SPI 设备,怎么区分?靠 CS 线。每个设备一个独立的 CS 引脚,主设备想跟谁聊,就拉低谁的 CS。
SPI 有四种模式,由时钟极性(CPOL)和时钟相位(CPHA)决定:
| 模式 | CPOL | CPHA | 数据采样边沿 |
|---|---|---|---|
| 模式 0 | 0 | 0 | 上升沿采样 |
| 模式 1 | 0 | 1 | 下降沿采样 |
| 模式 2 | 1 | 0 | 上升沿采样 |
| 模式 3 | 1 | 1 | 下降沿采样 |
我个人习惯,拿到一个 SPI 设备的数据手册,第一件事就是查它支持哪种模式。模式不匹配,数据全是乱的。
注意:SPI 的速率不是越高越好。我遇到过一块指纹模块,标称支持 10MHz,但实际布线长了,信号反射严重,降到 4MHz 才稳定。所以,量产前一定要做信号质量测试。
3.3 I2C 通信协议详解
I2C,全称集成电路总线。它只用两根线——SDA(数据线)和 SCL(时钟线),就能挂载多个设备。厉害吧?
I2C 也是同步通信,但跟 SPI 不同,它是“半双工”的。同一时刻,只能一个设备说话,其他设备听着。
I2C 的通信流程是这样的:
- 主设备发送起始信号(SCL 高电平时,SDA 从高变低)
- 主设备发送 7 位从设备地址 + 1 位读写位
- 从设备应答(ACK):拉低 SDA 表示收到
- 主设备发送或接收数据,每字节后跟一个 ACK
- 主设备发送停止信号(SCL 高电平时,SDA 从低变高)
举个例子,读取一个 I2C 温度传感器的代码片段:
// 伪代码:读取 I2C 设备寄存器
i2c_start(); // 发送起始信号
i2c_send_byte(0x90); // 设备地址 0x48 + 写位 0
i2c_wait_ack(); // 等待应答
i2c_send_byte(0x00); // 寄存器地址
i2c_wait_ack();
i2c_start(); // 重复起始信号
i2c_send_byte(0x91); // 设备地址 0x48 + 读位 1
i2c_wait_ack();
uint8_t data = i2c_read_byte(); // 读取数据
i2c_send_nack(); // 发送非应答
i2c_stop(); // 发送停止信号
避坑指南:我曾经在调试 I2C 时,发现设备有时不应答。查了三天,结果是上拉电阻没焊。I2C 的 SDA 和 SCL 是开漏输出,必须外接上拉电阻,一般 4.7kΩ 或 10kΩ。没有上拉电阻,总线拉不高,通信必失败。
I2C 的速率分几种:标准模式 100kHz,快速模式 400kHz,高速模式 3.4MHz。门禁模块里的配置芯片,通常用 100kHz 或 400kHz。
3.4 GPIO 控制与中断
GPIO,通用输入输出口。这可能是最基础,但也是最灵活的外设了。
你想想看,指纹模块的“手指按下”检测,靠什么?靠一个 GPIO 中断。手指一按,电平跳变,CPU 立刻响应。
GPIO 的配置通常包括:
- 方向:输入还是输出
- 上拉/下拉:内部是否接电阻
- 驱动能力:输出电流大小
- 中断触发方式:上升沿、下降沿、高电平、低电平
举个实际例子,配置一个按键中断:
// 伪代码:配置 GPIO 中断
GPIO_InitTypeDef gpio_init = {0};
gpio_init.Pin = GPIO_PIN_0; // 引脚 0
gpio_init.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
gpio_init.Pull = GPIO_PULLUP; // 内部上拉
HAL_GPIO_Init(GPIOA, &gpio_init);
// 使能中断线
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 中断服务函数
void EXTI0_IRQHandler(void)
{
if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
{
// 清除中断标志
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
// 处理手指按下事件
finger_pressed_handler();
}
}
注意:中断服务函数里,千万别做耗时操作!比如打印日志、延时等待。我见过有人直接在中断里调用 printf,结果系统卡死。正确做法:中断里只设置标志位,主循环里处理业务逻辑。
我个人习惯,GPIO 中断的优先级不要设太高。除非是紧急事件,否则给个中等优先级就行。优先级太高,容易把其他任务饿死。
好了,这一章的内容就到这里。四种接口,各有各的脾气。UART 简单但慢,SPI 快但线多,I2C 省线但协议复杂,GPIO 灵活但需要软件配合。实际项目中,往往是多种接口混用。比如指纹模块用 SPI 传图像,用 UART 传指令,用 GPIO 检测手指状态。
下一章,咱们开始实战,把指纹模块的驱动框架搭起来。
公众号:蓝海资料掘金营,微信deep3321