2. 开发环境与工具链搭建
做嵌入式这些年,我接触过的传感器少说也有上百种。选接口这件事,说白了就是选通信方式。每种接口都有自己的脾气,选对了事半功倍,选错了……嗯,我见过不少项目因为接口选型翻车,最后改板子改到崩溃。
这一章咱们就把五种最常见的传感器接口掰开揉碎讲清楚。我会结合自己的项目经验,告诉你每种接口什么时候该用,什么时候千万别用。
接口特性对比:一张表看懂全局
先给个全景图。这张表我整理了好几年,每次选型都会拿出来对照一下。
| 接口 | 通信方式 | 引脚数 | 速度 | 功耗 | 典型传感器 |
|---|---|---|---|---|---|
| I2C | 同步、半双工 | 2 (SCL, SDA) | 100kHz~3.4MHz | 低 | 温湿度、气压计 |
| SPI | 同步、全双工 | 4 (SCK, MOSI, MISO, CS) | 最高 80MHz | 中 | 加速度计、陀螺仪 |
| UART | 异步、全双工 | 2 (TX, RX) | 最高 10Mbps | 中 | GPS、蓝牙模块 |
| GPIO | 并行、电平 | 1~N | 取决于MCU | 极低 | 按键、中断触发 |
| ADC | 模拟量 | 1 | 采样率决定 | 低~中 | 光敏、电位器 |
核心原则:接口选型不是越快越好,而是匹配就好。低功耗场景下,I2C 往往是首选,因为它的总线空闲时几乎不耗电。
I2C 接口详解
I2C 是我用得最多的接口。为什么?两根线就能挂一堆传感器,省引脚啊!
时序要求
I2C 的时序其实不复杂。SCL 时钟线,SDA 数据线。起始条件:SCL 高电平时 SDA 从高变低。停止条件:SCL 高电平时 SDA 从低变高。就这么简单。
但有个坑——上拉电阻。我刚开始做项目时,I2C 死活不通,查了半天发现是上拉电阻没焊。标准值是 4.7kΩ,但如果你总线电容大,得换成 2.2kΩ。
驱动开发要点
// I2C 读取温湿度传感器示例
uint8_t i2c_read_byte(uint8_t dev_addr, uint8_t reg_addr) {
uint8_t data;
i2c_start();
i2c_write(dev_addr << 1 | 0); // 写地址
i2c_write(reg_addr); // 写寄存器
i2c_stop();
i2c_start();
i2c_write(dev_addr << 1 | 1); // 读地址
data = i2c_read_nack();
i2c_stop();
return data;
}
我的经验:I2C 驱动里一定要加超时处理。我曾经遇到过传感器死锁,总线一直拉低,整个系统卡死。加个 100ms 超时,遇到问题就复位总线,稳得很。
SPI 接口详解
SPI 比 I2C 快得多,但引脚也多。如果你需要高速采集加速度计数据,SPI 是首选。
时序要求
SPI 有四种模式,由时钟极性(CPOL)和时钟相位(CPHA)决定。大多数传感器用模式 0 或模式 3。我习惯用模式 0:空闲时 SCK 低电平,第一个边沿采样。
这里有个容易踩的坑——片选信号。CS 必须在传输开始前拉低,结束后拉高。有些传感器对 CS 的时序要求很严格,拉高太快会丢数据。
驱动开发要点
// SPI 读取加速度计示例
uint16_t spi_read_accel(uint8_t reg) {
uint8_t tx_buf[2] = {reg | 0x80, 0x00}; // 读命令
uint8_t rx_buf[2] = {0};
CS_LOW();
spi_transfer(tx_buf, rx_buf, 2);
CS_HIGH();
return (rx_buf[0] << 8) | rx_buf[1];
}
注意:SPI 的从设备不能主动发起通信。如果你需要传感器主动上报数据,得用中断引脚配合。我做过一个项目,加速度计检测到震动后通过 GPIO 中断唤醒 MCU,再用 SPI 读取数据,这样功耗控制得很好。
UART 接口详解
UART 是异步通信,不需要时钟线。但正因为异步,双方得约定好波特率。
时序要求
UART 的时序就是波特率。常见的 9600、115200。但要注意,波特率误差不能超过 2%。MCU 的时钟精度不够时,高速通信容易出错。
我记得有一次用内部 RC 振荡器跑 115200,结果丢包率 30%。换成外部晶振就好了。
驱动开发要点
// UART 接收一帧数据
void uart_rx_handler(uint8_t byte) {
static uint8_t buffer[64];
static uint8_t index = 0;
buffer[index++] = byte;
if (byte == '\n') {
process_frame(buffer, index);
index = 0;
}
}
避坑指南:UART 的接收最好用中断或 DMA,别用轮询。轮询会浪费大量 CPU 时间,在低功耗场景下尤其致命。
GPIO 与 ADC 接口
这两个接口虽然简单,但用好了能解决大问题。
GPIO 的妙用
GPIO 不只是用来点灯的。它可以做中断触发、模拟时序、甚至模拟 I2C。我做过一个项目,MCU 引脚不够用,就用两个 GPIO 模拟 I2C 挂传感器,虽然慢了点,但功能完全正常。
ADC 的注意事项
ADC 采集模拟信号,精度取决于参考电压和分辨率。12 位 ADC 的理论精度是 4096 级,但实际能到 10 位就不错了。
为什么?噪声。我遇到过 ADC 采集值跳来跳去,最后发现是电源纹波太大。加个 100nF 电容在传感器电源脚上,问题解决。
// ADC 采集光敏电阻示例
uint16_t adc_read_light(void) {
ADC_StartConversion();
while(!ADC_IsConversionDone());
return ADC_GetResult();
}
常见传感器的接口选择
讲完接口,咱们看看实际传感器怎么选。
| 传感器类型 | 推荐接口 | 理由 |
|---|---|---|
| 温湿度 (SHT30, DHT22) | I2C | 数据量小,功耗低,两根线搞定 |
| 加速度计 (MPU6050, LIS3DH) | SPI / I2C | 高速采集用 SPI,低功耗用 I2C |
| 气压计 (BMP280, MS5611) | I2C | 数据更新慢,I2C 足够 |
| 光敏电阻 | ADC | 模拟量输出,直接 ADC 采集 |
| GPS 模块 | UART | 数据量大,异步通信方便 |
我的建议:如果传感器同时支持 I2C 和 SPI,优先选 I2C。除非你需要高速采集(比如加速度计 1kHz 以上采样率),否则 I2C 的功耗和引脚优势更明显。
接口选择决策流程
下面这张图是我自己总结的接口选型流程,每次做新项目都会走一遍。
实战中的避坑指南
最后分享几个我踩过的坑,希望能帮你少走弯路。
- I2C 总线电容:线太长或者挂的设备太多,总线电容会变大,波形变差。我建议总线上挂的设备不要超过 8 个,线长控制在 30cm 以内。
- SPI 的 MISO 冲突:多个 SPI 设备共用总线时,一定要确保片选信号正确。我曾经因为 CS 没拉高,两个设备同时抢 MISO,数据全乱了。
- UART 的波特率误差:用内部振荡器时,别跑 115200 以上。我吃过亏,后来一律用外部晶振。
- ADC 的参考电压:参考电压一定要稳定。我用过 MCU 内部参考,结果温度一变,采集值就飘。后来换成外部基准,稳如老狗。
重要提醒:低功耗场景下,传感器在不工作时一定要进入休眠模式。很多传感器都有休眠引脚或者寄存器配置。我见过有人让传感器一直工作,电池两天就耗光了。