STM32F103C8T6_SPI完整教程

STM32F103C8T6 SPI协议完整教程:从入门到精通

适用芯片: STM32F103C8T6
难度等级: 入门到进阶


📋 目录

  1. SPI协议基础理论
  2. STM32F103 SPI硬件资源
  3. SPI寄存器详解
  4. SPI主从模式配置
  5. 硬件电路连接
  6. 标准库开发实战
  7. HAL库开发实战
  8. 高级应用与优化
  9. 常见问题与调试技巧

1. SPI协议基础理论

1.1 什么是SPI?

**SPI(Serial Peripheral Interface,串行外设接口)**是一种高速、全双工、同步的通信总线,由Motorola公司提出,广泛应用于嵌入式系统中与外设(如Flash存储器、传感器、显示屏驱动器等)进行通信。

1.2 SPI协议特点

  • 全双工通信: 数据可以同时双向传输
  • 高速传输: 传输速度可达几十MHz
  • 简单硬件: 只需要4根线(最少3根)
  • 主从模式: 采用主从架构
  • 同步通信: 使用时钟信号同步数据传输

1.3 SPI信号线定义

SPI协议通常使用以下4根信号线:

信号线名称全称方向说明
MOSIMaster Out Slave In主机→从机主机数据输出线
MISOMaster In Slave Out主机←从机从机数据输出线
SCKSerial Clock主机→从机同步时钟信号
CS/SSChip Select/Slave Select主机→从机片选信号(低电平有效)

1.4 SPI工作原理

        主机 (Master)                 从机 (Slave)
    ┌──────────────┐              ┌──────────────┐
    │              │              │              │
    │   MOSI  ─────┼─────────────>│  MOSI        │
    │              │              │              │
    │   MISO       │<─────────────┼────  MISO    │
    │              │              │              │
    │   SCK   ─────┼─────────────>│  SCK         │
    │              │              │              │
    │   CS    ─────┼─────────────>│  CS          │
    │              │              │              │
    └──────────────┘              └──────────────┘

工作流程

  1. 主机通过拉低CS信号选通从机
  2. 主机产生时钟信号SCK
  3. 在SCK的边沿,主机通过MOSI发送数据给从机
  4. 同时,从机通过MISO发送数据给主机
  5. 传输完成后,主机拉高CS信号结束通信

1.5 SPI时序模式

SPI有4种时钟模式(Mode),由CPOL和CPHA决定:

模式CPOLCPHA时钟极性数据采样
Mode 000空闲为低电平上升沿采样
Mode 101空闲为低电平下降沿采样
Mode 210空闲为高电平下降沿采样
Mode 311空闲为高电平上升沿采样

注意: 通信双方必须使用相同的时序模式!


2. STM32F103 SPI硬件资源

2.1 STM32F103C8T6 SPI外设概述

STM32F103C8T6共有3个SPI外设:

  • SPI1: 高速SPI(最高18MHz)
  • SPI2: 标准SPI(最高18MHz)
  • SPI3: 标准SPI(不支持直接映射,需重映射)

2.2 SPI引脚资源表

SPI1标准配置:

引脚功能重映射功能
PA4NSS保留为NSS
PA5SCKSPI1_SCK
PA6MISOSPI1_MISO
PA7MOSISPI1_MOSI

SPI1重映射配置:

引脚功能说明
PA15NSS重映射后的片选
PB3SCK重映射后的时钟
PB4MISO重映射后的输入
PB5MOSI重映射后的输出

SPI2标准配置:

引脚功能
PB12NSS
PB13SCK
PB14MISO
PB15MOSI

2.3 SPI时钟源配置

STM32F103的APB总线时钟配置关系:

系统时钟 → APB2 → SPI1 (最大18MHz)
系统时钟 → APB1 → SPI2/SPI3 (最大18MHz)

波特率计算公式:

SPI速度 = 系统时钟 / 分频系数

3. SPI寄存器详解

3.1 控制寄存器1 (SPIx_CR1)

SPI控制寄存器1关键位说明:

  • CPHA: 时钟相位
  • CPOL: 时钟极性
  • MSTR: 主/从模式选择
  • BR: 波特率选择
  • SPE: SPI使能
  • LSBFIRST: 帧格式(LSB/MSB)
  • SSI: 内部从机选择
  • SSM: 软件从机管理
  • RXONLY: 只接收模式
  • DFF: 数据帧格式
  • CRCEN: CRC使能
  • BIDIMODE: 双向数据模式使能

3.2 状态寄存器 (SPIx_SR)

SPI状态寄存器位说明:

  • RXNE: 接收缓冲区非空
  • TXE: 发送缓冲区空
  • CHSIDE: 通道侧
  • UDR: 下溢标志
  • CRCERR: CRC错误标志
  • MODF: 模式错误
  • OVR: 溢出错误
  • BSY: 忙标志

3.3 数据寄存器 (SPIx_DR)

双向数据寄存器,根据DFF位配置,可以是8位或16位数据。


4. SPI主从模式配置

4.1 主机模式配置要点

主机模式特点:

  • 产生时钟信号
  • 控制CS片选信号
  • 主动发起通信

配置步骤:

  1. 设置MSTR位为1
  2. 配置时钟频率(BR位)
  3. 配置时序模式(CPOL/CPHA)
  4. 使能SPI(SPE位)
  5. 控制NSS信号(硬件或软件)

4.2 从机模式配置要点

从机模式特点:

  • 接收主机时钟
  • 响应CS信号
  • 被动接收/发送数据

配置步骤:

  1. 设置MSTR位为0
  2. 配置时序模式(与主机一致)
  3. 使能SPI(SPE位)
  4. 配置NSS(通常使用SSM软件模式)

5. 硬件电路连接

5.1 典型连接示例

STM32F103C8T6  (主机)      连接     SPI Flash (从机)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    PA4 (NSS)  ───────────> CS
    PA5 (SCK)  ───────────> CLK
    PA7 (MOSI) ───────────> DI
    PA6 (MISO) <─────────── DO
    GND        ───────────> GND
    3.3V       ───────────> VCC

5.2 注意事项

⚠️ 重要提示:

  1. 电源匹配: 确保主机和从机使用相同的电源电压(通常3.3V)
  2. 电平匹配: STM32F103工作电压为3.3V,注意电平兼容性
  3. 上拉电阻: 根据器件需求添加适当的上拉/下拉电阻
  4. PCB布线: 高速信号线尽量短,避免干扰

5.3 多从机连接

方式一:独立片选(推荐)

主机 (STM32)           多个从机
PA4 (NSS1) ─────────> FLASH1_CS
PA5 (SCK)  ─────────> FLASH1_CLK
PA7 (MOSI) ─────────> FLASH1_DI
PA6 (MISO) <───────── FLASH1_DO

PA8 (NSS2) ─────────> FLASH2_CS
PA5 (SCK)  ─────────> FLASH2_CLK
PA7 (MOSI) ─────────> FLASH2_DI
PA6 (MISO) <───────── FLASH2_DO

6. 标准库开发实战

6.1 SPI引脚配置

/**
 * SPI GPIO初始化配置
 * 使用SPI1,标准引脚配置:PA4(NSS), PA5(SCK), PA6(MISO), PA7(MOSI)
 */
void SPI1_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 1. 使能GPIOA和SPI1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
    
    // 2. 配置PA5(SCK), PA7(MOSI) 为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 高速输出
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 3. 配置PA6(MISO) 为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 4. 配置PA4(NSS) 为推挽输出,用于片选控制
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 5. 初始状态:NSS拉高(未选中)
    GPIO_SetBits(GPIOA, GPIO_Pin_4);
}

6.2 SPI参数配置

/**
 * SPI1初始化配置
 * 配置为主机模式,8位数据,Mode0模式
 */
void SPI1_Config(void)
{
    SPI_InitTypeDef SPI_InitStructure;
    
    // 初始化SPI1结构体参数
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                      // 主机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  // 8位数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                         // CPOL=0
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                       // CPHA=0 (Mode0)
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                          // 软件NSS
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // 72MHz/64≈1.125MHz
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                 // MSB先发
    SPI_InitStructure.SPI_CRCPolynomial = 7;                           // CRC多项式
    
    // 初始化SPI1
    SPI_Init(SPI1, &SPI_InitStructure);
    
    // 使能SPI1
    SPI_Cmd(SPI1, ENABLE);
}

6.3 片选控制函数

/**
 * SPI1片选信号控制
 * @param state: 1=选中从机(拉低), 0=释放从机(拉高)
 */
void SPI1_CS_Control(uint8_t state)
{
    if(state == 1)
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 拉低,选中从机
    }
    else
    {
        GPIO_SetBits(GPIOA, GPIO_Pin_4);   // 拉高,释放从机
    }
}

6.4 数据收发函数

/**
 * SPI1发送一个字节
 * @param byte: 要发送的数据
 * @return: 接收到的数据
 */
uint8_t SPI1_SendByte(uint8_t byte)
{
    // 等待发送缓冲区空
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    
    // 发送数据
    SPI_I2S_SendData(SPI1, byte);
    
    // 等待接收完成
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    
    // 返回接收到的数据
    return SPI_I2S_ReceiveData(SPI1);
}

/**
 * SPI1接收一个字节
 * @return: 接收到的数据
 */
uint8_t SPI1_ReceiveByte(void)
{
    // SPI主机模式下,发送时钟信号,同时接收数据
    return SPI1_SendByte(0xFF); // 发送0xFF(无效数据)以产生时钟
}

6.5 主函数测试代码

#include "stm32f10x.h"

// 函数声明
void SPI1_GPIO_Config(void);
void SPI1_Config(void);
void SPI1_CS_Control(uint8_t state);
uint8_t SPI1_SendByte(uint8_t byte);
uint8_t SPI1_ReceiveByte(void);

/**
 * 主函数:SPI通信测试
 */
int main(void)
{
    uint8_t send_data = 0x55; // 发送的数据
    uint8_t recv_data;        // 接收的数据
    
    // 1. 系统时钟配置(使用外部8MHz晶振,PLL到72MHz)
    SystemInit();
    
    // 2. SPI1初始化
    SPI1_GPIO_Config();  // GPIO配置
    SPI1_Config();       // SPI配置
    
    while(1)
    {
        // 选中从机
        SPI1_CS_Control(1);
        
        // 发送数据并接收
        recv_data = SPI1_SendByte(send_data);
        
        // 释放从机
        SPI1_CS_Control(0);
        
        // 延时,便于观察
        for(volatile int i = 0; i < 1000000; i++);
    }
}

7. HAL库开发实战

7.1 HAL库SPI初始化

#include "main.h"
#include "stm32f1xx_hal.h"

SPI_HandleTypeDef hspi1;

/**
 * SPI1初始化函数(HAL库版本)
 */
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;        // 8位数据
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;      // CPOL=0
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;          // CPHA=0 (Mode0)
    hspi1.Init.NSS = SPI_NSS_SOFT;                  // 软件NSS
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; // 分频系数
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;         // MSB先发
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;         // 禁用TI模式
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用CRC
    
    // 初始化SPI1
    if(HAL_SPI_Init(&hspi1) != HAL_OK)
    {
        Error_Handler(); // 错误处理
    }
}

/**
 * SPI1底层驱动函数(由HAL库调用)
 */
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    if(hspi->Instance == SPI1)
    {
        // 使能SPI1和GPIOA时钟
        __HAL_RCC_SPI1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        // 配置PA5(SCK), PA7(MOSI)
        GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 配置PA6(MISO)
        GPIO_InitStruct.Pin = GPIO_PIN_6;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 配置PA4(NSS)为软件控制
        GPIO_InitStruct.Pin = GPIO_PIN_4;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 初始状态:NSS拉高
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    }
}

7.2 HAL库数据收发

/**
 * HAL库:SPI发送数据
 */
HAL_StatusTypeDef SPI1_Transmit(uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
    // 选中从机
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    
    // 发送数据
    HAL_StatusTypeDef status = HAL_SPI_Transmit(&hspi1, pData, Size, Timeout);
    
    // 释放从机
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    
    return status;
}

/**
 * HAL库:SPI接收数据
 */
HAL_StatusTypeDef SPI1_Receive(uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
    // 选中从机
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    
    // 接收数据
    HAL_StatusTypeDef status = HAL_SPI_Receive(&hspi1, pData, Size, Timeout);
    
    // 释放从机
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    
    return status;
}

/**
 * HAL库:SPI发送并接收数据(全双工)
 */
HAL_StatusTypeDef SPI1_TransmitReceive(uint8_t *pTxData, uint8_t *pRxData, 
                                       uint16_t Size, uint32_t Timeout)
{
    // 选中从机
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    
    // 发送并接收数据
    HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi1, pTxData, pRxData, Size, Timeout);
    
    // 释放从机
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    
    return status;
}

8. 高级应用与优化

8.1 16位数据传输

/**
 * SPI1配置为16位数据模式
 */
void SPI1_Config_16bit(void)
{
    SPI_InitTypeDef SPI_InitStructure;
    
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; // 16位数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);
}

8.2 软件模拟SPI

// 定义模拟SPI引脚
#define SIM_SPI_MOSI_PIN     GPIO_Pin_7
#define SIM_SPI_MISO_PIN     GPIO_Pin_6
#define SIM_SPI_SCK_PIN      GPIO_Pin_5
#define SIM_SPI_CS_PIN       GPIO_Pin_4
#define SIM_SPI_PORT         GPIOA

#define SIM_SPI_MOSI_HIGH()  GPIO_SetBits(SIM_SPI_PORT, SIM_SPI_MOSI_PIN)
#define SIM_SPI_MOSI_LOW()   GPIO_ResetBits(SIM_SPI_PORT, SIM_SPI_MOSI_PIN)
#define SIM_SPI_MISO_READ()  GPIO_ReadInputDataBit(SIM_SPI_PORT, SIM_SPI_MISO_PIN)
#define SIM_SPI_SCK_HIGH()   GPIO_SetBits(SIM_SPI_PORT, SIM_SPI_SCK_PIN)
#define SIM_SPI_SCK_LOW()    GPIO_ResetBits(SIM_SPI_PORT, SIM_SPI_SCK_PIN)

/**
 * 软件SPI发送一个字节(Mode0模式)
 */
uint8_t SoftwareSPI_Transfer(uint8_t data)
{
    uint8_t i;
    uint8_t recv_data = 0;
    
    for(i = 0; i < 8; i++)
    {
        // 设置MOSI数据位
        if(data & 0x80)
            SIM_SPI_MOSI_HIGH();
        else
            SIM_SPI_MOSI_LOW();
        
        data <<= 1;
        
        // 产生时钟上升沿
        SIM_SPI_SCK_HIGH();
        
        // 读取MISO数据位
        recv_data <<= 1;
        if(SIM_SPI_MISO_READ())
            recv_data |= 0x01;
        
        // 产生时钟下降沿
        SIM_SPI_SCK_LOW();
    }
    
    return recv_data;
}

9. 常见问题与调试技巧

9.1 常见问题排查

问题1: 通信无响应

可能原因:

  1. 时序模式不匹配
  2. 引脚配置错误
  3. 片选信号未正确控制

解决方法:

  • 检查时序模式必须一致(CPOL、CPHA)
  • 确保引脚配置为复用功能(GPIO_Mode_AF_PP)
  • 检查片选信号是否正确控制
问题2: 数据传输错误

可能原因:

  1. MSB/LSB顺序不匹配
  2. 数据帧格式不一致
  3. 波特率设置过高

解决方法:

  • 确保MSB/LSB顺序一致
  • 确保数据宽度一致(8位或16位)
  • 降低波特率进行测试

9.2 调试工具与方法

使用逻辑分析仪

推荐工具:

  • Saleae Logic Analyzer
  • DSView
  • 示波器

检查要点:

  1. 时钟信号是否正确产生
  2. MOSI/MISO数据是否符合预期
  3. 时序模式是否正确
  4. 片选信号是否正常
使用串口输出调试信息
void SPI_Debug_Print(uint8_t *tx_data, uint8_t *rx_data, uint16_t length)
{
    uint16_t i;
    printf("SPI Debug Info:\r\n");
    printf("TX: ");
    for(i = 0; i < length; i++)
    {
        printf("0x%02X ", tx_data[i]);
    }
    printf("\r\nRX: ");
    for(i = 0; i < length; i++)
    {
        printf("0x%02X ", rx_data[i]);
    }
    printf("\r\n");
}

9.3 实际应用案例

案例1: 与SPI Flash通信
/**
 * W25Q64 Flash ID读取命令
 */
uint32_t W25Q64_ReadID(void)
{
    uint8_t id_data[3];
    
    // 发送读取ID命令(0x9F)
    SPI1_CS_Control(1);
    SPI1_SendByte(0x9F);       // 命令
    id_data[0] = SPI1_SendByte(0xFF); // 制造商ID
    id_data[1] = SPI1_SendByte(0xFF); // 设备ID高字节
    id_data[2] = SPI1_SendByte(0xFF); // 设备ID低字节
    SPI1_CS_Control(0);
    
    return (id_data[0] << 16) | (id_data[1] << 8) | id_data[2];
}

📚 总结

本文档详细介绍了STM32F103C8T6的SPI协议通信,包括:

  1. 理论基础:SPI协议原理、时序模式、信号线定义
  2. 硬件资源:STM32F103的SPI外设特点和引脚资源
  3. 寄存器配置:详细解释了关键寄存器的用法
  4. 实战编程:提供了标准库和HAL库的完整代码示例
  5. 高级应用:16位数据、软件SPI等高级技巧
  6. 调试技巧:常见问题排查和性能优化方法

希望本文档能帮助大家快速掌握STM32F103的SPI通信!


写在最后

如果本文档对您有帮助,欢迎点赞、收藏、转发!

如有问题或建议,欢迎在评论区留言讨论!

祝您学习愉快,开发顺利! 🎉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值