第17篇:嵌入式通用串行外设:UART_SPI_I2C接口原理与外设扩展应用

引言:UART/SPI/I2C是嵌入式芯片外设扩展的核心接口

嵌入式系统设计中,芯片自身资源难以满足实际需求,需通过扩展外设实现功能升级。UART、SPI、I2C作为嵌入式最通用的三类串行通信接口,是芯片与外设数据传输的核心桥梁。

各类嵌入式芯片均集成这三类接口,应用覆盖全嵌入式领域。掌握其原理、硬件设计与驱动开发,是开发者从"会用芯片"到"会设计系统"的关键。本文将从多维度全面解析,助力开发者掌握外设扩展核心能力。

一、UART串行通信的核心原理、协议规范与硬件设计

1.1 核心原理

UART是异步串行通信接口,无需时钟同步,通过TX(发送)、RX(接收)两根数据线实现全双工双向数据传输,核心是完成芯片并行数据与串行数据的双向转换。

通信双方必须约定一致的通信参数,否则会导致数据传输错误。

1.2 协议规范

UART协议核心是通信参数和数据帧格式,核心参数需双方严格一致:

  • 波特率:数据传输速率(bps),常见9600、115200等,是基础参数;

  • 数据位:每帧有效数据位数,最常用8位,对应芯片并行数据;

  • 停止位:数据帧结束标识,常用1位,告知接收端传输完成;

  • 校验位:校验传输错误,常用无校验(简化)或偶校验(提升可靠性)。

最常用帧格式(8位数据+1位停止+无校验):空闲位(高)→起始位(低)→数据位(低位在前)→停止位(高)→空闲位(高),起始位触发接收端同步。

1.3 硬件设计

核心是TX/RX交叉连接和电平匹配,分两种场景:

  1. 同电平设备(如STM32间):TX与RX交叉连接,两设备共地;严禁TX与TX直接相连,避免引脚损坏。

  2. 不同电平设备(如STM32与PC):需通过MAX232等电平转换芯片,否则会导致传输失败或芯片损坏。

注意事项:缩短信号线长度减少干扰;布线超1米加100Ω匹配电阻;严格共地避免干扰。

二、SPI总线的核心原理、工作模式与多设备挂载设计

2.1 核心原理

SPI是同步串行总线,特点是同步传输、全双工、高速(远高于UART),通过4根核心信号线通信。采用主从模式,主机产生时钟,控制通信时序,从机仅被动收发数据。

2.2 核心信号线与工作模式

2.2.1 核心信号线

标准4线制,功能缺一不可:

  • SCLK:主机产生的时钟信号,决定传输速率;

  • MOSI:主机向从机发送数据的通道;

  • MISO:从机向主机返回数据的通道;

  • SS/CS:主机控制的从机选择线,通常低电平有效(需参考 datasheet)。

注:简单外设可简化为3线制,省略MISO或MOSI,实现单向传输。

2.2.2 工作模式

由CPOL(时钟极性)和CPHA(时钟相位)决定,共4种模式,双方需一致否则通信失败:

  • CPOL:空闲时SCLK电平,0为低,1为高;

  • CPHA:数据采样时机,0为第一个跳变沿,1为第二个跳变沿。

常用Mode 0(CPOL=0,CPHA=0)和Mode 3(CPOL=1,CPHA=1),具体需结合外设配置。

2.3 多设备挂载设计

核心思路:共享SCLK、MOSI、MISO,为每个从机分配独立SS/CS,实现方式如下:

  1. 硬件:所有从机对应信号线与主机相连,SS/CS各接主机一个GPIO;

  2. 逻辑:通信时仅拉低目标从机SS/CS,其余保持高电平(高阻态),避免冲突。

注意事项:SCLK频率取所有从机支持最小值;SS/CS配置为推挽输出;信号线尽量等长减少串扰。

三、I2C总线的核心协议、通信机制与硬件设计

3.1 核心原理与特点

I2C是同步串行总线,特点是双线制、半双工、多主多从,适用于短距离、低速、多设备场景(如传感器、RTC)。无需独立从机选择线,通过从机地址区分,节省GPIO资源。

3.2 核心信号线与协议规范

3.2.1 核心信号线

仅2根信号线,均需4.7KΩ上拉电阻接VCC,确保空闲高电平:

  • SDA:传输数据和从机地址,核心数据通道;

  • SCL:主机产生(多主可协商),同步时序。

3.2.2 核心协议规范

通信时序固定:起始条件→地址传输→应答→数据传输→停止条件,核心细节:

  1. 起始(S):SCL高电平时,SDA高变低,启动通信;

  2. 地址传输:7位从机地址+1位读写位(0写1读);

  3. 应答(ACK/NACK):每传8位数据,接收端反馈低电平(成功)或高电平(失败);

  4. 数据传输:写操作主机发数据,读操作从机发数据,每次8位;

  5. 停止(P):SCL高电平时,SDA低变高,结束通信。

补充:常用7位地址(支持128个从机),10位地址适用于多从机场景;部分芯片可通过引脚配置地址,避免冲突。

3.3 硬件设计

核心是双线制+上拉电阻,注意事项:

  1. 上拉电阻:4.7KΩ~10KΩ,从机超8个选2.2KΩ增强驱动;

  2. 多设备:信号线共接,通过地址区分,地址不可重复;

  3. 布线:不超1米,尽量平行,超1米加匹配电阻。

通信失败排查:优先检查上拉电阻、从机地址、布线干扰。

四、嵌入式芯片中三类串行外设的配置流程与驱动开发基础

以STM32F103、HAL库为例,三类接口驱动开发核心流程一致:外设时钟使能→GPIO配置→接口参数配置→中断/DMA配置(可选)→数据收发,兼顾实用与可移植性。

4.1 通用配置流程(共性)

  1. 时钟使能:开启接口和对应GPIO时钟,时钟是外设工作基础;

  2. GPIO配置:UART(TX复用推挽、RX浮空输入);SPI(SCLK/MOSI/SS复用推挽、MISO浮空输入);I2C(SDA/SCL复用开漏+上拉);

  3. 参数配置:匹配外设要求,配置各接口核心参数;

  4. 中断/DMA(可选):实现高速异步传输,降低CPU占用;

  5. 数据收发:调用HAL库对应函数,简化开发。

4.2 三类接口驱动开发差异(个性)

4.2.1 UART驱动开发

重点:波特率结合晶振配置,误差≤1%;优先用中断接收,减少CPU占用。

关键代码示例(简化,基于STM32 HAL库):

c 复制代码
// 1. UART初始化
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void) {
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  if (HAL_UART_Init(&huart1) != HAL_OK) {
    Error_Handler();
  }
}

// 2. 阻塞式发送
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello UART!", 11, 1000);

// 3. 中断接收及回调
uint8_t uart_rx_buf[10];
HAL_UART_Receive_IT(&huart1, uart_rx_buf, 10);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
  // 业务处理
  HAL_UART_Receive_IT(&huart1, uart_rx_buf, 10);
}
4.2.2 SPI驱动开发

重点:工作模式与从机一致;精准控制SS/CS电平;注重收发同步。

关键代码示例(简化,基于STM32 HAL库):

c 复制代码
// 1. SPI初始化(主机Mode 0)
SPI_HandleTypeDef hspi1;
void MX_SPI1_Init(void) {
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  if (HAL_SPI_Init(&hspi1) != HAL_OK) {
    Error_Handler();
  }
}

// 2. SS/CS控制
#define SPI_CS_GPIO_Port GPIOA
#define SPI_CS_Pin GPIO_PIN_4
void SPI_Select_Slave(void) {HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET);}
void SPI_Deselect_Slave(void) {HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET);}

// 3. 全双工收发
uint8_t spi_tx_buf[5] = {0x01, 0x02, 0x03, 0x04, 0x05};
uint8_t spi_rx_buf[5];
SPI_Select_Slave();
HAL_SPI_TransmitReceive(&hspi1, spi_tx_buf, spi_rx_buf, 5, 1000);
SPI_Deselect_Slave();
4.2.3 I2C驱动开发

重点:从机地址含读写位;重视应答处理;时钟频率匹配外设。

关键代码示例(简化,基于STM32 HAL库,写EEPROM):

c 复制代码
// 1. I2C初始化(100KHz)
I2C_HandleTypeDef hi2c1;
void MX_I2C1_Init(void) {
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
    Error_Handler();
  }
}

// 2. 写数据(EEPROM地址0x00写0x12)
#define EEPROM_ADDR 0xA0
uint8_t i2c_tx_buf[2] = {0x00, 0x12};
HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDR, i2c_tx_buf, 2, 1000);

// 3. 读数据(读EEPROM地址0x00)
uint8_t i2c_rx_buf[1];
HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDR, (uint8_t*)&0x00, 1, 1000);
HAL_I2C_Master_Receive(&hi2c1, 0xA1, i2c_rx_buf, 1, 1000);

4.3 驱动开发注意事项

  • 参数匹配:双方协议参数一致,否则通信失败;

  • 中断优先级:合理配置,避免嵌套导致异常;

  • 延时控制:按外设 datasheet 配置延时,避免操作未完成;

  • 错误处理:添加返回值判断,提升系统稳定性。

五、典型实战案例:传感器对接与外设扩展全流程实现

以STM32F103为控制器,对接三类典型外设,完整实现硬件设计、驱动开发与测试,助力理论落地。

案例1:UART对接串口屏(显示与交互)

5.1.1 需求说明

UART1对接TFT串口屏,实现文字显示与触摸交互,通信参数:115200bps、8N1。

5.1.2 硬件设计
  • STM32 USART1_TX(PA9)→串口屏RX;

  • STM32 USART1_RX(PA10)→串口屏TX;

  • STM32 GND→串口屏GND,串口屏VCC接对应电源。

注:5V串口屏需加SN74LS245电平转换,避免损坏STM32。

5.1.3 驱动开发与功能实现
  1. 初始化UART1,开启接收中断;

  2. 编写串口屏控制指令(清空、显示文字);

  3. 中断回调解析触摸指令,执行对应逻辑;

  4. 主函数循环显示,等待触摸交互。

关键代码(简化,基于STM32 HAL库):

c 复制代码
// 发送串口屏指令
void UART_Send_LCD_Command(char* cmd) {
  HAL_UART_Transmit(&huart1, (uint8_t*)cmd, strlen(cmd), 1000);
}

// 主函数
int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  
  UART_Send_LCD_Command("CLR(0);\r\n");
  HAL_Delay(500);
  UART_Send_LCD_Command("DISP_STR(10,10,\"STM32 UART LCD Test\");\r\n");
  
  while (1) {HAL_Delay(100);}
}

// 中断回调解析触摸指令
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
  if (huart == &huart1) {
    if (strstr((char*)uart_rx_buf, "TOUCH") != NULL) {
      HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
    HAL_UART_Receive_IT(&huart1, uart_rx_buf, 30);
  }
}

案例2:SPI对接SPI Flash(数据存储)

5.2.1 需求说明

SPI1对接W25Q64(8MB),实现数据读写与校验,参数:Mode 0、10MHz、8位数据。

5.2.2 硬件设计
  • STM32 SPI1_SCLK(PA5)→W25Q64 SCK;

  • STM32 SPI1_MOSI(PA7)→W25Q64 DI;

  • STM32 SPI1_MISO(PA6)→W25Q64 DO;

  • STM32 GPIOA4→W25Q64 CS;

  • STM32 3.3V→W25Q64 VCC/VCCQ,共地。

5.2.3 驱动开发与功能实现
  1. 初始化SPI1,匹配W25Q64参数;

  2. 编写读ID、扇区擦除、页写、读数据函数;

  3. 主函数实现:写数据→擦除(可选)→读数据→校验。

关键代码(简化,基于STM32 HAL库):

c 复制代码
// 读W25Q64 ID(验证通信)
uint32_t W25Q64_Read_ID(void) {
  uint32_t id = 0;
  SPI_Select_Slave();
  HAL_SPI_Transmit(&hspi1, (uint8_t*)&0x90, 1, 1000);
  HAL_SPI_Receive(&hspi1, (uint8_t*)&id, 3, 1000);
  SPI_Deselect_Slave();
  return id & 0xFFFF; // 标准ID 0x4017
}

// 扇区擦除
void W25Q64_Erase_Sector(uint32_t addr) {
  W25Q64_Write_Enable();
  SPI_Select_Slave();
  HAL_SPI_Transmit(&hspi1, (uint8_t*)&0x20, 1, 1000);
  HAL_SPI_Transmit(&hspi1, (uint8_t*)&addr, 3, 1000);
  SPI_Deselect_Slave();
  W25Q64_Wait_Busy();
}

// 主函数测试
int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_SPI1_Init();
  
  if (W25Q64_Read_ID() != 0x4017) Error_Handler();
  
  uint8_t write_buf[] = "SPI Flash Test: Hello World!";
  uint8_t read_buf[30] = {0};
  uint32_t addr = 0x000000;
  
  W25Q64_Erase_Sector(addr);
  W25Q64_Write_Page(addr, write_buf, strlen((char*)write_buf));
  W25Q64_Read_Data(addr, read_buf, strlen((char*)write_buf));
  
  if (memcmp(write_buf, read_buf, strlen((char*)write_buf)) != 0) Error_Handler();
  
  while (1) {}
}

案例3:I2C对接温湿度传感器(数据采集)

5.3.1 需求说明

I2C1对接DHT11(I2C版),采集温湿度并通过UART1发送到PC,参数:100KHz、从机地址0x40。

5.3.2 硬件设计
  • STM32 I2C1_SDA(PB7)→DHT11 SDA;

  • STM32 I2C1_SCL(PB6)→DHT11 SCL;

  • STM32 3.3V→DHT11 VCC,共地;

  • SDA、SCL串4.7KΩ上拉电阻。

5.3.3 驱动开发与功能实现
  1. 初始化I2C1,匹配DHT11参数;

  2. 编写测量、读数据、校验函数;

  3. 主函数1秒采集一次,格式化后发送到PC。

关键代码(简化,基于STM32 HAL库):

c 复制代码
// 读取DHT11温湿度,0成功1失败
uint8_t DHT11_Read_Data(uint8_t* temp, uint8_t* humi) {
  uint8_t buf[5] = {0};
  HAL_I2C_Master_Transmit(&hi2c1, 0x40, (uint8_t*)&0xE0, 1, 1000);
  HAL_Delay(10);
  
  if (HAL_I2C_Master_Receive(&hi2c1, 0x41, buf, 5, 1000) != HAL_OK) return 1;
  if ((buf[0]+buf[1]+buf[2]+buf[3]) != buf[4]) return 1;
  
  *humi = buf[0]; *temp = buf[2];
  return 0;
}

// 主函数
int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_I2C1_Init();
  
  uint8_t temp, humi;
  char uart_buf[50];
  
  while (1) {
    if (DHT11_Read_Data(&temp, &humi) == 0) {
      sprintf(uart_buf, "Temperature: %d℃, Humidity: %d%%\r\n", temp, humi);
      HAL_UART_Transmit(&huart1, (uint8_t*)uart_buf, strlen(uart_buf), 1000);
    } else {
      HAL_UART_Transmit(&huart1, (uint8_t*)"Read DHT11 Error!\r\n", 18, 1000);
    }
    HAL_Delay(1000);
  }
}

六、多设备挂载的总线设计与可靠性优化技巧

多设备挂载是嵌入式外设扩展的常见场景,核心是在保证总线稳定的前提下,实现多设备高效协同,以下结合三类接口特性,给出针对性设计方案与可靠性优化技巧。

6.1 三类接口多设备挂载核心设计

  • UART多设备挂载:采用"主机-从机"星形拓扑,主机通过GPIO控制从机使能端,同一时刻仅一个从机响应,避免数据冲突;无需时钟同步,布线简单,适合低速多设备场景(如多个串口传感器)。

  • SPI多设备挂载:共享SCLK、MOSI、MISO三根总线,每个从机分配独立SS/CS引脚,主机通过拉低对应SS/CS实现设备选择;支持高速传输,适合多高速外设场景(如SPI Flash、OLED屏)。

  • I2C多设备挂载:共享SDA、SCL两根总线,通过从机地址区分设备,无需额外GPIO;支持多主模式,布线最简洁,适合低速、多小型外设场景(如多个温湿度传感器、RTC)。

6.2 可靠性优化技巧

  1. 总线负载优化:I2C从机数量不超过12个(7位地址),超量时采用I2C中继器;SPI总线负载不超过4个,避免信号衰减,必要时添加总线缓冲器。

  2. 布线优化:多设备布线时,总线尽量短且等长,远离功率器件;SPI、I2C总线添加100Ω匹配电阻,减少串扰;UART布线超1米时,采用屏蔽线。

  3. 时序优化:SPI多设备挂载时,SCLK频率取所有从机支持的最小值;I2C通信时,添加适当延时,确保从机有足够时间响应;UART多设备切换时,需重新同步通信参数。

  4. 容错设计:驱动中添加应答检测、超时处理,避免单个设备故障导致整个总线瘫痪;关键数据传输时,添加校验位(如CRC),提升数据可靠性。

七、三类接口的性能对比与适用场景划分

三类接口各有优劣,需结合传输速率、布线复杂度、设备数量、成本等因素合理选型,以下是完整的性能对比与场景划分,助力开发者快速匹配需求。

7.1 完整性能对比

对比维度 UART SPI I2C
通信方式 异步,无时钟 同步,有时钟 同步,有时钟
信号线数量 2根(全双工);1根(半双工) 4根(标准);3根(简化),全双工 2根,半双工
传输速率 较低(通常≤115200bps) 较高(可达几十MHz) 中等(标准100KHz,快速400KHz)
多设备支持 支持,需额外GPIO控制使能 支持,需独立SS/CS引脚 支持,通过地址区分,无需额外GPIO
布线复杂度 简单 中等 最简单
成本 低,无需额外器件 中,需额外GPIO 低,仅需上拉电阻

7.2 适用场景划分

  • UART:适用于低速、点对点/简单多设备通信,如串口调试、GPS模块、蓝牙模块、串口屏等,优势是布线简单、成本低,无需时钟同步。

  • SPI:适用于高速、短距离、多外设通信,如SPI Flash、OLED屏、ADC芯片等,优势是传输速率高、全双工,适合对速率要求较高的场景。

  • I2C:适用于低速、短距离、多小型外设通信,如温湿度传感器、RTC、EEPROM等,优势是布线简洁、节省GPIO,适合外设数量多、对速率要求不高的场景。

八、总结:嵌入式芯片中通用串行外设的应用设计规范

结合前文原理、硬件设计、驱动开发与实战案例,总结嵌入式通用串行外设(UART/SPI/I2C)的应用设计规范,助力开发者规避设计误区、提升系统稳定性与可移植性。

8.1 选型规范

  1. 优先根据传输速率选型:高速场景选SPI,中速场景选I2C,低速场景选UART;

  2. 结合外设数量选型:多外设、GPIO紧张场景选I2C,高速多外设场景选SPI,少外设、低成本场景选UART;

  3. 结合布线环境选型:长距离、抗干扰要求高场景选UART(屏蔽线),短距离、高速场景选SPI,板内短距离、多外设场景选I2C。

8.2 硬件设计规范

  1. 电平匹配:不同电平设备间必须添加电平转换芯片(如UART的MAX232、5V与3.3V设备的SN74LS245);

  2. 布线要求:总线尽量短、等长,远离功率器件与干扰源;SPI/I2C添加匹配电阻,UART长距离用屏蔽线;

  3. 多设备挂载:严格遵循各类接口的挂载规则,I2C避免地址冲突,SPI合理分配SS/CS引脚,UART控制从机使能端。

8.3 驱动开发规范

  1. 参数统一:通信双方的波特率、数据位、停止位(UART),工作模式(SPI),时钟频率、从机地址(I2C)必须严格一致;

  2. 容错设计:添加应答检测、超时处理、数据校验,提升驱动可靠性;中断驱动需合理配置优先级,避免嵌套异常;

  3. 可移植性:基于HAL库、LL库开发,封装通用接口函数,便于切换不同芯片平台;驱动代码添加详细注释,明确参数含义与操作逻辑。

8.4 调试规范

  1. 分步调试:先调试硬件(用万用表检测引脚电平、总线连接),再调试驱动(先初始化,再测试简单数据收发),最后调试多设备协同;

  2. 故障排查:通信失败优先检查参数匹配、硬件连接、电平匹配,再排查驱动逻辑与时序问题;

  3. 性能优化:根据实际需求调整传输速率、延时参数,平衡传输效率与稳定性。

综上,UART、SPI、I2C作为嵌入式外设扩展的核心接口,其应用设计需兼顾选型合理性、硬件可靠性、驱动规范性。掌握本文所述规范与技巧,可高效完成嵌入式系统外设扩展设计,提升开发效率与系统稳定性。

相关推荐
LCG元2 小时前
STM32实战:基于FreeRTOS的LVGL嵌入式GUI移植(智能手表界面)
stm32·嵌入式硬件·智能手表
Lugas Luo2 小时前
如何利用AI Agent自动分析Linux BSP(Board Support Package)驱动和内核日志
linux·人工智能·嵌入式硬件
振浩微433射频芯片2 小时前
低功耗无线遥控新选择:深度解析VI520R ASK/OOK接收芯片与433MHz方案优势
网络·单片机·嵌入式硬件·物联网·智能家居
leo__5202 小时前
STM32 DMA程序(标准外设库版本)
stm32·单片机·嵌入式硬件
三佛科技-134163842122 小时前
电动牙刷方案开发-基于FT61E131B-RB单片机
单片机·嵌入式硬件·智能家居·pcb工艺
至为芯2 小时前
PY32F071至为芯支持32位ARM内核的高主频MCU微控制器
单片机·嵌入式硬件·mcu
2201_756206343 小时前
STM32L431 下载调试问题排查与解决
stm32·单片机·嵌入式硬件
WeeJot嵌入式3 小时前
【IIC】IIC中断与DMA&状态机编程
单片机·嵌入式硬件
WeeJot嵌入式3 小时前
【OLED】OLED原理&驱动库&取模
stm32·单片机·嵌入式硬件·嵌入式·oled