打印
[应用相关]

STM32中I2C与SPI接口开发全攻略:从设计到调试

[复制链接]
48|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Puchou|  楼主 | 2025-5-13 22:04 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在嵌入式系统开发中,I2C和SPI是两种最常用的串行通信协议,广泛应用于传感器、存储器、显示屏等外设的连接。本文将全面介绍在STM32平台上设计I2C和SPI接口代码时的注意事项、开发流程以及调试技巧,帮助开发者避免常见陷阱,提高开发效率。

I2C与SPI协议基础
I2C协议核心要点
I2C(Inter-Integrated Circuit)是一种由Philips公司开发的两线制串行通信协议,具有以下特点:

两线制:仅需SCL(串行时钟线)和SDA(串行数据线)两条线
多主多从:支持多个主设备和从设备共享同一总线
地址寻址:每个从设备有唯一地址(通常7位或10位)
半双工:同一时间只能单向传输数据
速度分级:标准模式100kbps,快速模式400kbps,高速模式3.4Mbps
I2C协议通过起始条件(S)、从机地址(7位)、读写位(R/W)、应答位(A)、数据(DATA)和停止条件(P)构成完整通信帧。在STM32中,I2C外设支持标准模式和快速模式。

SPI协议核心要点
SPI(Serial Peripheral Interface)是由Motorola开发的全双工同步串行接口,主要特点包括:

四线制:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、SS(片选)
全双工:可同时发送和接收数据
主从架构:一主多从,每个从设备需要独立片选线
无寻址机制:通过硬件片选选择从设备
高速传输:速度可达几十MHz(如W25Q64 Flash支持80MHz)
SPI有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)组合决定:

模式0:CPOL=0,CPHA=0
模式1:CPOL=0,CPHA=1
模式2:CPOL=1,CPHA=0
模式3:CPOL=1,CPHA=1
STM32硬件资源与引脚配置
I2C引脚配置要点
在STM32F103系列中,I2C接口的默认引脚分配为:

I2C1:SCL-PB6,SDA-PB7
I2C2:SCL-PB10,SDA-PB11
配置时需注意:

必须将GPIO设置为复用开漏输出模式(I2C协议要求)
需要外接上拉电阻(通常3.3kΩ-10kΩ)
避免与其他复用功能引脚冲突
长距离传输时考虑电平转换和抗干扰设计
示例配置代码(使用HAL库):

// 使能I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

// 配置PB6为复用开漏模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

c
运行

SPI引脚配置要点
STM32F103C8T6提供两个SPI接口:

SPI1:SCK-PA5, MISO-PA6, MOSI-PA7, NSS-PA4
SPI2:SCK-PB13, MISO-PB14, MOSI-PB15, NSS-PB12
配置注意事项:

GPIO应设置为复用推挽输出(MOSI/SCK)和浮空输入(MISO)
NSS(片选)可使用硬件管理或软件控制
多从机系统需为每个从机分配独立片选线
高速传输时注意PCB布局,缩短走线长度
使用STM32CubeMX配置SPI的示例:

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_256;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
HAL_SPI_Init(&hspi1);

c
运行


I2C开发实践与注意事项
I2C初始化配置
完整的I2C初始化应包括以下参数设置:

I2C_HandleTypeDef hi2c1;

void MX_I2C1_Init(void)
{
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000; // 100kHz
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0; // 主模式通常设为0
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  HAL_I2C_Init(&hi2c1);
}

c
运行


常见I2C操作API
主设备发送数据:
HAL_I2C_Master_Transmit(&hi2c1, DEV_ADDR, pData, Size, Timeout);

c
运行

主设备接收数据:
HAL_I2C_Master_Receive(&hi2c1, DEV_ADDR, pData, Size, Timeout);

c
运行

存储器写入(带内部地址):
HAL_I2C_Mem_Write(&hi2c1, DEV_ADDR, MEM_ADDR, I2C_MEMADD_SIZE_8BIT, pData, Size, Timeout);

c
运行

存储器读取:
HAL_I2C_Mem_Read(&hi2c1, DEV_ADDR, MEM_ADDR, I2C_MEMADD_SIZE_8BIT, pData, Size, Timeout);

c
运行

I2C开发关键注意事项
总线死锁处理:
STM32的I2C外设容易因异常中断导致总线死锁
解决方案:检测到错误时执行硬件复位
__HAL_RCC_I2C1_FORCE_RESET();
__HAL_RCC_I2C1_RELEASE_RESET();
MX_I2C1_Init();

c
运行

地址对齐:
7位地址需左移1位,最低位表示读写方向(0-写,1-读)
例如设备地址0x68,写地址为0xD0,读地址为0xD1
时序问题:
严格按照设备手册的时序要求
关键信号(起始、停止、重复起始)的时序要准确
错误处理:
if(__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_AF) != RESET) {
    __HAL_I2C_CLEAR_FLAG(&hi2c1, I2C_FLAG_AF);
}

c
运行

上拉电阻选择:
根据总线电容和速度选择合适阻值(通常3.3kΩ-10kΩ)
高速模式下可能需要更小的阻值
SPI开发实践与注意事项
SPI初始化配置
典型SPI主模式配置示例:

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; // CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;     // CPHA=0
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
HAL_SPI_Init(&hspi1);

c
运行


常见SPI操作API
阻塞式传输:
HAL_SPI_Transmit(&hspi1, pData, Size, Timeout);
HAL_SPI_Receive(&hspi1, pData, Size, Timeout);
HAL_SPI_TransmitReceive(&hspi1, pTxData, pRxData, Size, Timeout);

c
运行

中断方式:
HAL_SPI_Transmit_IT(&hspi1, pData, Size);
HAL_SPI_Receive_IT(&hspi1, pData, Size);

c
运行

DMA方式:
HAL_SPI_Transmit_DMA(&hspi1, pData, Size);
HAL_SPI_Receive_DMA(&hspi1, pData, Size);

c
运行

SPI开发关键注意事项
模式匹配:
必须与从设备的工作模式(CPOL/CPHA)完全一致
可通过逻辑分析仪验证时序
片选信号管理:
软件控制NSS时,需手动拉低/拉高GPIO
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 选中
HAL_SPI_Transmit(&hspi1, &data, 1, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);   // 释放

c
运行

从机模式问题:
从机模式下易出现OVERRUN错误和BSY标志置位
解决方案:错误时重置SPI外设
__HAL_RCC_SPI1_FORCE_RESET();
__HAL_RCC_SPI1_RELEASE_RESET();
MX_SPI1_Init();

c
运行

数据对齐:
确保数据帧大小(8位/16位)与设备匹配
MSB/LSB先行设置要正确
高速传输优化:
使用DMA减少CPU开销
适当提高时钟分频系数
优化PCB布局,减少寄生电容
调试技巧与工具
逻辑分析仪的使用
逻辑分析仪是调试I2C/SPI的利器,可以:

捕获波形:直观显示SCL/SDA或SCK/MOSI/MISO信号
协议解码:自动解析I2C/SPI数据帧
时序测量:验证建立时间、保持时间等参数
典型SPI波形分析(模式0):

SCK空闲低电平,第一个边沿(上升沿)采样数据
MOSI在SCK上升沿前稳定,下降沿可变化
常见问题排查
I2C无应答:
检查设备地址是否正确
验证上拉电阻是否合适
确认从设备是否就绪
SPI数据错误:
确认CPOL/CPHA设置
检查片选信号时序
验证时钟频率是否超出从设备限制
DMA传输异常:
检查缓冲区地址对齐
验证DMA通道配置
确保传输完成前不访问缓冲区
调试代码示例
I2C扫描工具(检测总线上的设备):

void I2C_Scan(I2C_HandleTypeDef *hi2c)
{
    uint8_t error, addr;
    for(addr = 1; addr < 128; addr++)
    {
        error = HAL_I2C_IsDeviceReady(hi2c, addr << 1, 2, 2);
        if(error == HAL_OK) {
            printf("Device found at 0x%02X\n", addr);
        }
    }
}

c
运行


性能优化与高级应用
DMA优化通信性能
DMA(直接内存访问)可显著提升通信效率:

配置步骤:
在CubeMX中启用SPI/I2C的DMA通道
设置DMA传输方向(外设↔内存)
配置优先级和数据宽度
示例代码:
// SPI DMA发送
HAL_SPI_Transmit_DMA(&hspi1, txBuffer, length);

// SPI DMA接收
HAL_SPI_Receive_DMA(&hspi1, rxBuffer, length);

// 传输完成回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
    // 处理完成事件
}

c
运行


多从机系统设计
I2C多从机:
利用不同设备地址区分
注意总线电容和上拉电阻计算
考虑使用I2C多路复用器(如PCA9548)
SPI多从机:
为每个从机分配独立片选线
注意总线负载和信号完整性
高速系统考虑使用缓冲器
低功耗设计
I2C省电技巧:
空闲时进入睡眠模式
降低总线速度
关闭不用的从设备电源
SPI省电技巧:
通信后禁用SPI外设
使用硬件NSS自动管理
选择支持低功耗的从设备
实战案例:EEPROM与Flash读写
I2C连接AT24Cxx EEPROM
设备地址:
基础地址0xA0(写)/0xA1(读)
A0/A1/A2引脚决定低三位
页写入:
#define EEPROM_ADDR 0xA0

void EEPROM_WritePage(uint16_t memAddr, uint8_t *data, uint8_t len)
{
    HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, memAddr,
                     I2C_MEMADD_SIZE_8BIT, data, len, 100);
    HAL_Delay(5); // 等待写入完成
}

c
运行

随机读取:
uint8_t EEPROM_ReadByte(uint16_t memAddr)
{
    uint8_t val;
    HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, memAddr,
                    I2C_MEMADD_SIZE_8BIT, &val, 1, 100);
    return val;
}

c
运行

SPI连接W25Qxx Flash
初始化:
void W25Q_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 配置CS引脚
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    W25Q_CS_HIGH();
}

c
运行


读取ID:
uint16_t W25Q_ReadID(void)
{
    uint8_t cmd[4] = {0x90, 0x00, 0x00, 0x00};
    uint8_t id[2];
    W25Q_CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Receive(&hspi1, id, 2, 100);
    W25Q_CS_HIGH();
    return (id[0] << 8) | id[1];
}

c
运行


页编程:
void W25Q_PageProgram(uint32_t addr, uint8_t *data, uint16_t len)
{
    uint8_t cmd[4];
    cmd[0] = 0x02; // 页编程指令
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    W25Q_WriteEnable();
    W25Q_CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Transmit(&hspi1, data, len, 100);
    W25Q_CS_HIGH();
    W25Q_WaitForWriteEnd();
}

c
运行


总结与最佳实践
I2C开发黄金法则
始终检查应答信号:每个字节传输后验证ACK/NACK
处理总线忙状态:启动前检查BUSY标志
合理设置超时:避免程序死锁
使用中断/DMA:提高系统效率
添加重试机制:关键操作应有重试逻辑
SPI开发黄金法则
严格匹配模式:CPOL/CPHA必须正确
管理好片选信号:确保时序符合要求
高速系统优化:使用DMA,缩短走线
注意字节序:MSB/LSB设置要一致
错误恢复机制:包括SPI外设重置
通用建议
充分利用STM32CubeMX:可视化配置外设
参考官方示例:ST提供标准外设库和HAL库示例
分层设计:分离硬件驱动与业务逻辑
完善日志:记录通信错误和状态
保持更新:关注ST官网的勘误和更新
通过掌握这些开发要点和注意事项,开发者能够在STM32平台上高效可靠地实现I2C和SPI通信,构建稳定高效的嵌入式系统。记住,实践是最好的老师,多动手实验,多使用调试工具,才能真正掌握这些通信接口的精髓。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/niuTyler/article/details/147588889

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

39

主题

113

帖子

0

粉丝