3、通信协议入门:UART、I2C、SPI基础,在能耗监测中的应用场景

各位同学,咱们今天聊点实在的。做嵌入式能耗监测,说白了就是跟各种传感器、芯片打交道。怎么让它们听话、怎么把数据拿回来?靠的就是通信协议。UART、I2C、SPI,这三个是基本功,就像练武要先扎马步一样。

我个人习惯,不管项目多急,先把这三兄弟搞清楚再动手。不然你想想看,焊好板子发现数据死活读不出来,那才叫一个头大。好,咱们一个一个来。

3.1 UART:最老实的串口通信

UART,全称通用异步收发传输器。名字挺长,其实原理很简单——就是一根线发、一根线收,外加一根地线。我最早接触单片机就是从UART开始的,那时候觉得这东西真神奇,两根线就能传数据。

核心要点:

  • 异步通信:没有时钟线,靠双方约定好波特率(比如9600、115200)来同步。
  • 数据格式:起始位(1位)+ 数据位(通常8位)+ 校验位(可选)+ 停止位(1位或2位)。
  • 全双工:可以同时收发,互不干扰。

能耗监测中的应用场景:

UART最常见的用法就是接那些“老派”但可靠的传感器,比如某些电流互感器模块、电表模块。它们输出就是串口数据,你直接用MCU的UART引脚接上就行。

我在项目中遇到过一个问题:用UART读取一个电表数据,波特率设成9600,结果数据全是乱码。后来发现是电表用的不是标准8位数据,而是7位数据位+偶校验。嗯,这里要注意,有些工业设备喜欢玩这种“非标”配置。

避坑指南:

我曾经在调试一个能耗采集器时,UART通信时好时坏。查了半天,发现是地线没接好。UART虽然简单,但地线必须共地,否则电平参考点不一样,数据肯定出错。记住:TX接RX,RX接TX,GND接GND,别搞反了。

// UART初始化示例(STM32 HAL库)
void UART_Init(void) {
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;      // 波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;  // 8位数据
    huart1.Init.StopBits = UART_STOPBITS_1;       // 1位停止位
    huart1.Init.Parity = UART_PARITY_NONE;        // 无校验
    huart1.Init.Mode = UART_MODE_TX_RX;           // 收发模式
    HAL_UART_Init(&huart1);
}

// 发送数据
HAL_UART_Transmit(&huart1, (uint8_t *)"Hello", 5, 1000);

3.2 I2C:两线制,多设备共享总线

I2C,Inter-Integrated Circuit,飞利浦发明的。它只用两根线:SCL(时钟线)和SDA(数据线)。但厉害的是,一根总线上可以挂几十个设备,每个设备有自己的地址。

核心要点:

  • 同步通信:有时钟线,主设备控制时钟。
  • 半双工:同一时刻只能一个方向传输数据。
  • 设备地址:每个从设备有7位或10位地址,主设备通过地址选择通信对象。
  • 上拉电阻:SCL和SDA必须接上拉电阻(通常4.7kΩ),否则无法工作。

能耗监测中的应用场景:

I2C在能耗监测里简直是“万金油”。温度传感器(如LM75)、湿度传感器、光照传感器、甚至某些专用的功率监测芯片(如INA219),清一色都是I2C接口。你想想看,一个MCU的I2C引脚,接上五六个传感器,省引脚又省布线,多爽。

我记得有一次做智能插座项目,里面用了INA219监测电流和电压。INA219就是I2C接口,地址是0x40。我只需要两根线就能读到功率数据,代码写起来也简单。

警告:

I2C总线长度不能太长,一般不超过1米。如果线太长,信号会衰减,通信就会失败。我曾经在工厂里遇到一个情况,传感器离MCU有2米远,I2C死活不通。后来换成RS485才解决。所以,远距离传输别用I2C。

// I2C读取INA219功率数据示例
uint16_t INA219_ReadPower(void) {
    uint8_t data[2];
    uint16_t power_raw;
    
    // 发送寄存器地址(0x03是功率寄存器)
    uint8_t reg_addr = 0x03;
    HAL_I2C_Master_Transmit(&hi2c1, 0x40 << 1, ®_addr, 1, 100);
    
    // 读取2字节数据
    HAL_I2C_Master_Receive(&hi2c1, 0x40 << 1, data, 2, 100);
    
    power_raw = (data[0] << 8) | data[1];
    return power_raw;
}

3.3 SPI:高速四线制,适合大数据量

SPI,Serial Peripheral Interface,摩托罗拉发明的。它比I2C快得多,但用的线也多:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。每个从设备需要一根独立的CS线。

核心要点:

  • 同步通信:主设备提供时钟。
  • 全双工:可以同时收发数据。
  • 速度高:典型速度几MHz到几十MHz,远快于I2C。
  • 片选信号:每个从设备一根CS线,低电平有效。

能耗监测中的应用场景:

SPI适合那些需要高速、大数据量传输的场景。比如高精度ADC(模数转换器),采样率很高,数据量很大,用I2C就太慢了。还有某些专用的电能计量芯片(如ADE7758),也是SPI接口。你想想看,要实时监测三相电的电压、电流、功率因数,数据量可不小,SPI正好派上用场。

我个人习惯,如果传感器数据量超过几百字节每秒,我就优先考虑SPI。I2C虽然方便,但速度上限摆在那里(标准模式100kHz,快速模式400kHz),扛不住大数据量。

避坑指南:

我曾经用SPI接一个ADC芯片,数据读出来总是跳变。查了半天,发现是时钟极性(CPOL)和时钟相位(CPHA)没配置对。SPI有四种模式,主设备和从设备必须匹配。很多芯片手册里会写“Mode 0”或“Mode 3”,你照着配就行。别想当然,一定要看数据手册。

// SPI读取ADC数据示例
uint16_t SPI_ReadADC(void) {
    uint8_t tx_data = 0x00;  // 发送任意数据,触发从设备发送
    uint8_t rx_data;
    
    // CS拉低,选中设备
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
    
    // 收发数据
    HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 100);
    
    // CS拉高,释放设备
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
    
    return rx_data;
}

3.4 三种协议对比与选型建议

好,咱们把三个协议放在一起对比一下,方便你选型时参考。

特性 UART I2C SPI
线数 2(TX、RX) 2(SCL、SDA) 4(SCLK、MOSI、MISO、CS)
通信方式 异步 同步 同步
传输方向 全双工 半双工 全双工
速度 慢(典型115200bps) 中(典型400kHz) 快(典型10MHz+)
多设备支持 点对点 多设备(地址区分) 多设备(片选区分)
抗干扰能力 一般 较弱 较强
典型能耗监测应用 电表模块、串口传感器 温湿度传感器、功率监测芯片 高精度ADC、电能计量芯片

选型建议:

  • 设备少、速度要求不高:用UART,简单可靠。
  • 需要挂多个传感器、引脚有限:用I2C,两线制搞定一切。
  • 需要高速、大数据量:用SPI,别犹豫。

说白了,没有最好的协议,只有最合适的。我在实际项目中,经常是UART、I2C、SPI混着用。比如一个能耗监测终端,用UART接电表,用I2C接温湿度传感器,用SPI接高精度ADC。各司其职,互不干扰。

3.5 小结

嗯,今天咱们把UART、I2C、SPI这三个通信协议的基础讲了一遍。记住它们的核心区别:UART是异步、两线、点对点;I2C是同步、两线、多设备;SPI是同步、四线、高速。在能耗监测中,选对协议能省不少事。

下一章咱们会深入讲讲这些协议在低功耗设计中的注意事项,比如怎么在休眠模式下保持通信、怎么降低通信功耗。到时候再聊。

课后小练习:

找一个你手头的传感器,看看它的数据手册,判断它用的是UART、I2C还是SPI。然后试着用MCU读取它的数据。相信我,动手做一遍比看十遍书都管用。