数字电路通信——时序与I2C,I2S,SPI,UART协议

一、时序:数字通信的节拍器

时序是数字系统协同工作的基础,它定义了数据何时有效、何时被采样。理解时序,是掌握任何通信协议的前提。

1. 时钟信号:系统的心跳
时钟信号为所有同步数字电路提供统一的节拍。在通信中,发送方和接收方必须遵循相同的时钟规则(或通过协议协商)才能正确传输数据。

2. 关键时序参数

  • 建立时间 (Tsu):数据在时钟沿(如上升沿)到来之前必须保持稳定的最短时间。好比在老师点名(时钟沿)前,你必须已经举手(数据稳定)并保持一会儿。
  • 保持时间 (Th):数据在时钟沿到来之后必须继续保持稳定的最短时间。老师点完名后,你的手还需要举着一小会儿,确保被记录。
  • 时钟周期 (Tclk) 与频率 (Fclk)Fclk = 1 / Tclk。周期越短,频率越高,通信速度越快,但对时序的要求也越苛刻。

ESP32 基础定时器示例:

#include"driver/timer.h"voidbasic_timer_init(){timer_config_tconfig={.divider=80,// 80 MHz APB_CLK / 80 = 1 MHz.counter_dir=TIMER_COUNT_UP,.counter_en=TIMER_PAUSE,.alarm_en=TIMER_ALARM_EN,.auto_reload=TIMER_AUTORELOAD_EN,};timer_init(TIMER_GROUP_0,TIMER_0,&config);timer_set_counter_value(TIMER_GROUP_0,TIMER_0,0);timer_set_alarm_value(TIMER_GROUP_0,TIMER_0,1000000);// 1秒后触发警报timer_enable_intr(TIMER_GROUP_0,TIMER_0);timer_start(TIMER_GROUP_0,TIMER_0);}

二、I2C:优雅的双线总线

I2C (Inter-Integrated Circuit) 以其简洁(仅需两根线:SDA-数据、SCL-时钟)和多主从架构,成为板级设备通信的经典选择。

1. 核心工作模式

  • 开漏输出:I2C 总线物理层采用开漏输出,这意味着设备只能将总线拉低(输出0),释放总线时靠上拉电阻回到高电平(1)。这天然实现了“线与”功能,避免了总线冲突。
  • 地址寻址:每个从设备都有一个7位或10位的唯一地址。主设备通过发送地址来发起通信。
  • 应答机制:每传输完一个字节(8位数据),接收方必须发送一个应答位(ACK),确保数据被成功接收。

2. 通信流程(以读取为例)

  1. 主设备发送起始条件 (S):在 SCL 高电平时,SDA 产生一个下降沿。
  2. 主设备发送从设备地址 (7位) + 读写位 (1位)
  3. 从设备回复应答位 (ACK)
  4. 主设备发送寄存器地址(可选,取决于设备)。
  5. 从设备再次回复ACK
  6. 主设备发送重复起始条件 (Sr)
  7. 主设备再次发送从设备地址 + 读位
  8. 从设备回复ACK并开始发送数据。
  9. 主设备接收数据,并在最后一个字节后回复非应答位 (NACK)
  10. 主设备发送停止条件 §:在 SCL 高电平时,SDA 产生一个上升沿。

ESP32 读取 I2C 传感器 (如 BMP280) 示例:

#include"driver/i2c.h"#defineI2C_MASTER_SCL_IO22#defineI2C_MASTER_SDA_IO21#defineI2C_MASTER_FREQ_HZ100000#defineBMP280_ADDR0x76#defineBMP280_REG_ID0xD0voidi2c_master_init(){i2c_config_tconf={.mode=I2C_MODE_MASTER,.sda_io_num=I2C_MASTER_SDA_IO,.scl_io_num=I2C_MASTER_SCL_IO,.sda_pullup_en=GPIO_PULLUP_ENABLE,// 必须上拉!.scl_pullup_en=GPIO_PULLUP_ENABLE,.master.clk_speed=I2C_MASTER_FREQ_HZ,};i2c_param_config(I2C_NUM_0,&conf);i2c_driver_install(I2C_NUM_0,conf.mode,0,0,0);}uint8_tbmp280_read_id(){uint8_treg_addr=BMP280_REG_ID;uint8_tdata_rd[1]={0};// 写寄存器地址i2c_cmd_handle_tcmd=i2c_cmd_link_create();i2c_master_start(cmd);i2c_master_write_byte(cmd,(BMP280_ADDR<<1)|I2C_MASTER_WRITE,true);i2c_master_write_byte(cmd,reg_addr,true);i2c_master_stop(cmd);i2c_master_cmd_begin(I2C_NUM_0,cmd,1000/portTICK_PERIOD_MS);i2c_cmd_link_delete(cmd);// 读数据cmd=i2c_cmd_link_create();i2c_master_start(cmd);i2c_master_write_byte(cmd,(BMP280_ADDR<<1)|I2C_MASTER_READ,true);i2c_master_read_byte(cmd,data_rd,I2C_MASTER_NACK);i2c_master_stop(cmd);i2c_master_cmd_begin(I2C_NUM_0,cmd,1000/portTICK_PERIOD_MS);i2c_cmd_link_delete(cmd);returndata_rd[0];}

三、SPI:高速全双工的王者

SPI (Serial Peripheral Interface) 采用主从模式,使用四根线实现高速全双工通信,是连接 Flash、屏幕、ADC 等高速外设的首选。

1. 四线定义

  • SCLK (Serial Clock):主设备输出的时钟信号。
  • MOSI (Master Out Slave In):主设备输出,从设备输入。
  • MISO (Master In Slave Out):从设备输出,主设备输入。
  • CS/SS (Chip Select / Slave Select):片选信号,低电平有效。主设备通过拉低对应从机的 CS 来选择与之通信。

2. 时钟极性 (CPOL) 与相位 (CPHA)
这是 SPI 配置中最容易混淆的部分,它们共同决定了数据在时钟的哪个边沿被采样和输出。

  • CPOL=0:时钟空闲时为低电平。
  • CPOL=1:时钟空闲时为高电平。
  • CPHA=0:数据在时钟的第一个边沿(对于 CPOL=0 是上升沿)被采样,在下一个边沿切换。
  • CPHA=1:数据在时钟的第二个边沿被采样,在第一个边沿切换。

3. 通信特点

  • 全双工:可以同时发送和接收数据。
  • 无寻址:通过硬件片选线选择从机,理论上无限扩展(需要足够多的 GPIO)。
  • 高速:速率通常远高于 I2C 和 UART。

ESP32 读写 SPI Flash (W25Q128) 示例:

#include"driver/spi_master.h"#definePIN_NUM_MISO19#definePIN_NUM_MOSI23#definePIN_NUM_CLK18#definePIN_NUM_CS5voidspi_flash_init(){spi_bus_config_tbuscfg={.miso_io_num=PIN_NUM_MISO,.mosi_io_num=PIN_NUM_MOSI,.sclk_io_num=PIN_NUM_CLK,.quadwp_io_num=-1,.quadhd_io_num=-1,.max_transfer_sz=4096,};spi_bus_initialize(SPI2_HOST,&buscfg,SPI_DMA_CH_AUTO);spi_device_interface_config_tdevcfg={.clock_speed_hz=10*1000*1000,// 10 MHz.mode=0,// CPOL=0, CPHA=0.spics_io_num=PIN_NUM_CS,.queue_size=7,};spi_device_handle_tspi;spi_bus_add_device(SPI2_HOST,&devcfg,&spi);}uint32_tread_flash_id(spi_device_handle_tspi){uint8_tcmd=0x9F;// 读ID命令uint8_trx_data[3]={0};spi_transaction_tt={.length=8,// 命令长度,单位:位.tx_buffer=&cmd,.rxlength=24,// 接收数据长度,单位:位.rx_buffer=rx_data,};spi_device_transmit(spi,&t);return(rx_data[0]<<16)|(rx_data[1]<<8)|rx_data[2];}

四、UART:异步串行的元老

UART (Universal Asynchronous Receiver/Transmitter) 是一种异步、全双工的串行通信协议,不需要时钟线,仅依靠双方约定的波特率进行通信。

1. 数据帧结构
一个完整的 UART 帧包含:

  1. 起始位:1 位低电平,标志传输开始。
  2. 数据位:5-9 位有效数据,通常为 8 位。
  3. 校验位(可选):奇校验或偶校验,用于简单的错误检测。
  4. 停止位:1、1.5 或 2 位高电平,标志传输结束。

2. 关键概念

  • 波特率:每秒传输的符号数。常见的波特率有 9600, 115200 等。发送和接收方的波特率必须严格一致,否则会产生错位。
  • 异步:通信双方没有共享的时钟信号,依靠起始位同步,并依靠内部时钟对每位进行采样。
  • 电平标准:UART 是逻辑电平协议。实际应用中常通过电平转换芯片(如 MAX3232)转换为 RS-232 或 RS-485 标准,以进行长距离通信。

ESP32 双机 UART 回环测试示例:

#include"driver/uart.h"#defineECHO_TEST_TXD(GPIO_NUM_17)#defineECHO_TEST_RXD(GPIO_NUM_16)#defineBUF_SIZE(1024)voiduart_loopback_init(){uart_config_tuart_config={.baud_rate=115200,.data_bits=UART_DATA_8_BITS,.parity=UART_PARITY_DISABLE,.stop_bits=UART_STOP_BITS_1,.flow_ctrl=UART_HW_FLOWCTRL_DISABLE,};uart_param_config(UART_NUM_2,&uart_config);uart_set_pin(UART_NUM_2,ECHO_TEST_TXD,ECHO_TEST_RXD,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE);uart_driver_install(UART_NUM_2,BUF_SIZE*2,0,0,NULL,0);}voiduart_send_receive(){char*test_str="Hello UART!\n";uart_write_bytes(UART_NUM_2,test_str,strlen(test_str));uint8_tdata[BUF_SIZE];intlen=uart_read_bytes(UART_NUM_2,data,BUF_SIZE,20/portTICK_PERIOD_MS);if(len>0){// 处理接收到的数据}}

五、I2S:专注音频的数字流

I2S (Inter-IC Sound) 是专门为数字音频数据传输设计的协议,它分离了数据和时钟,能保证高保真音频的同步。

1. 三线(或更多)定义

  • BCLK (Bit Clock):位时钟,每个脉冲对应一位数据。
  • WS (Word Select):字选择(或左右声道时钟),用于指示当前传输的是左声道(低电平)还是右声道(高电平)数据。
  • SD (Serial Data):串行数据线。
  • MCLK (Master Clock, 可选):主时钟,为编解码器提供系统时钟参考。

2. 通信特点

  • 时序严格:对时钟的稳定性要求高,抖动会影响音质。
  • 数据格式:支持标准 I2S、左对齐、右对齐等多种数据格式。
  • 应用固定:主要用于连接音频 ADC/DAC、数字麦克风、音频处理器等。

ESP32 播放音频示例 (使用内部 DAC):

#include"driver/i2s.h"#defineI2S_DOUT25// 数据输出(接DAC)#defineI2S_BCLK26// 位时钟#defineI2S_LRC27// 左右声道时钟voidi2s_audio_init(){i2s_config_ti2s_config={.mode=I2S_MODE_MASTER|I2S_MODE_TX,// 主模式,发送.sample_rate=44100,.bits_per_sample=I2S_BITS_PER_SAMPLE_16BIT,.channel_format=I2S_CHANNEL_FMT_RIGHT_LEFT,// 双声道.communication_format=I2S_COMM_FORMAT_STAND_I2S,.dma_buf_count=8,.dma_buf_len=64,.use_apll=false,.intr_alloc_flags=ESP_INTR_FLAG_LEVEL1,};i2s_pin_config_tpin_config={.bck_io_num=I2S_BCLK,.ws_io_num=I2S_LRC,.data_out_num=I2S_DOUT,.data_in_num=I2S_PIN_NO_CHANGE,};i2s_driver_install(I2S_NUM_0,&i2s_config,0,NULL);i2s_set_pin(I2S_NUM_0,&pin_config);}// 假设 audio_buffer 中已有 PCM 音频数据voidplay_audio(constint16_t*audio_buffer,size_tlen){size_tbytes_written;i2s_write(I2S_NUM_0,audio_buffer,len*sizeof(int16_t),&bytes_written,portMAX_DELAY);}

六、协议对比与选型指南

特性I2CSPIUARTI2S
通信方式同步、半双工同步、全双工异步、全双工同步、全双工(通常)
线数2 (SDA, SCL)3+ (SCLK, MOSI, MISO, CS…)2 (TX, RX)3+ (BCLK, WS, SD, MCLK)
速度标准(100kbps)、快速(400kbps)、高速(3.4Mbps)可达数十甚至上百 Mbps通常 < 10 Mbps取决于采样率与位深(如 44.1kHz * 16bit * 2 = 1.41Mbps)
寻址方式软件(7/10位地址)硬件(片选线)无(点对点)无(通常点对点)
主要优势引脚少、多主从、有应答速度极快、简单直接简单、无需时钟线、距离远(加转换)音频专用、时钟与数据分离、保真度高
典型应用传感器、EEPROM、RTCFlash、屏幕、ADC调试日志、GPS模块、蓝牙模块音频DAC/ADC、数字麦克风

选型建议:

  • 追求引脚最少、连接多个低速设备:选I2C
  • 追求极限速度、连接高速外设:选SPI
  • 简单双机通信、调试打印、连接传统模块:选UART
  • 一切音频相关应用:选I2S

七、总结:从理论到实践,我的学习心得

这些协议不仅是技术,更是解决问题的思维。理解了它们的设计哲学(比如 I2C 的“线与”如何避免冲突,UART 如何靠起始位同步),再遇到新的通信模块也不会发怵。希望这篇梳理能帮你建立起清晰的通信协议知识框架,在未来的项目里游刃有余。