硬件接口基础:GPIO、I2C、SPI、UART
各位同学,咱们今天聊点实在的。做嵌入式驱动开发,说白了就是跟芯片的脚打交道。你得知道它什么时候拉高、什么时候拉低,数据怎么从这根线传到那根线。我刚开始接触这行的时候,总觉得协议栈很神秘,后来发现——嗯,底层无非就是这四种接口:GPIO、I2C、SPI、UART。把这四个吃透了,市面上九成以上的外设你都能搞定。
GPIO:最基础的“开关”
GPIO 的全称是 General-Purpose Input/Output,通用输入输出口。你可以把它想象成一个智能开关——既能读外面的电平(输入),也能控制外面的电平(输出)。
我个人习惯把 GPIO 的工作模式分成三类:
- 推挽输出:内部有俩管子,一个推高、一个拉低。输出高电平就是直接怼到 VCC,低电平就是拉到 GND。驱动能力强,适合控制 LED、继电器这类负载。
- 开漏输出:只有拉低的能力,没有推高的能力。想输出高电平?得靠外部上拉电阻。我最早在 I2C 总线上看到这个模式时还纳闷——为什么不能直接推高?后来才明白,开漏才能实现“线与”功能,多个设备同时拉低总线不会打架。
- 浮空输入:引脚既不拉高也不拉低,电平完全由外部决定。这时候如果外部没接信号,引脚电平就是不确定的——我踩过这个坑,按键没按下时读到的值乱跳,后来加了个上拉电阻才稳定。
I2C 协议:两根线的“对话”
I2C 只用两根线——SCL(时钟)和 SDA(数据)。但别小看这两根线,它支持一主多从、多主多从,每个设备都有自己的地址。我当年第一次调 I2C 传感器时,折腾了一整天没反应,最后发现是从机地址写错了——嗯,这种低级错误谁没犯过呢?
I2C 的时序其实不复杂,核心就几个动作:
| 动作 | SDA 行为 | SCL 行为 |
|---|---|---|
| 起始条件 | 高→低 | 高 |
| 停止条件 | 低→高 | 高 |
| 数据位传输 | 数据有效期间不变 | 高电平采样 |
| 应答位 | 从机拉低表示 ACK | 第 9 个时钟 |
写驱动的时候,我建议你把起始、停止、发送字节、接收字节这几个基本函数先写好。剩下的就是拼积木——比如读一个寄存器,就是:起始→发设备地址+写位→发寄存器地址→重复起始→发设备地址+读位→读数据→停止。
SPI 协议:全双工的“高速公路”
SPI 比 I2C 快得多,因为它有四根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。而且它是全双工的——发数据的同时也能收数据。我调过 SPI 接口的 LCD 屏,刷新率比 I2C 的屏快了一个数量级。
SPI 的时序参数主要看四个:
- CPOL(时钟极性):空闲时时钟是高还是低
- CPHA(时钟相位):数据在时钟的上升沿还是下降沿采样
- 数据位宽:通常是 8 位,但也有 16 位、32 位的
- 传输速率:从几百 KHz 到几十 MHz 都有
CPOL 和 CPHA 组合起来就是四种模式。我见过最坑的情况是:芯片手册写的是 Mode 0,但实际要配成 Mode 3 才能正常工作——说白了,手册也可能有错,最终得靠逻辑分析仪确认。
// SPI 发送一个字节的伪代码
uint8_t spi_transfer(uint8_t data_out) {
uint8_t data_in = 0;
for (int i = 7; i >= 0; i--) {
// 在时钟边沿发送数据位
if (data_out & (1 << i))
MOSI_HIGH();
else
MOSI_LOW();
// 产生时钟脉冲
SCLK_HIGH();
delay_half_period();
// 在另一个边沿采样数据
if (MISO_READ())
data_in |= (1 << i);
SCLK_LOW();
delay_half_period();
}
return data_in;
}
关键点:SPI 没有应答机制,主设备只管发,从设备只管收。如果从设备没准备好,数据就丢了。所以片选信号的时序很重要——我习惯在 CS 拉低后等几个微秒再开始发时钟。
UART:最古老的“串口”
UART 就是咱们常说的串口。它只需要两根线——TX(发送)和 RX(接收),而且不需要时钟线。为什么?因为收发双方约定好相同的波特率,各自用自己的时钟采样。我最早学单片机就是从串口打印 "Hello World" 开始的,那时候觉得这东西真神奇。
UART 的一帧数据长这样:
- 起始位:1 位,拉低表示开始传输
- 数据位:5~9 位,通常 8 位
- 校验位:可选,奇校验或偶校验
- 停止位:1 位或 2 位,拉高表示结束
波特率是 UART 最关键的参数。9600 波特率意味着每秒传 9600 个位,算上起始位和停止位,实际传一个字节大概需要 1ms 左右。我调过 115200 的串口,也调过 2400 的——速度越慢越稳定,但传输大文件时能急死人。
四种接口的对比
| 特性 | GPIO | I2C | SPI | UART |
|---|---|---|---|---|
| 线数 | 1 根 | 2 根 | 4 根 | 2 根 |
| 传输方式 | 并行/串行 | 半双工 | 全双工 | 全双工 |
| 速度 | 取决于软件 | 100KHz~3.4MHz | 最高几十 MHz | 最高几 MHz |
| 从机数量 | 无限制 | 127 个(7 位地址) | 取决于片选引脚 | 点对点 |
| 典型应用 | 按键、LED | 传感器、EEPROM | LCD、ADC、SD 卡 | 调试口、GPS 模块 |
知识体系总览
下面这张图把四种接口的核心要素串起来了。你写驱动的时候,脑子里要有这张图——先看接口类型,再看时序参数,最后写代码。
好了,这一章的内容就这些。GPIO 是基础中的基础,I2C 和 SPI 是嵌入式系统里最常用的总线,UART 则是调试利器。你把这些时序和原理搞清楚了,后面写驱动就是水到渠成的事。