第20章 SPI介绍及应用
第二十章 SPI介绍及应用
1. SPI简介
1.1 基本特点
| 特性 | 说明 |
|---|---|
| 通信方式 | 同步串行通信(由时钟同步) |
| 数据方向 | 全双工(可同时发送与接收) |
| 通信速度 | 高速(可达几 MHz 到几十 MHz) |
| 连接方式 | 主从架构,支持多从机 |
| 信号线 | 至少 4 根线(典型四线制) |
| 无地址机制 | 使用片选(CS)选择从设备 |
| 无固定协议层 | 灵活性高,由开发者自定义数据格式 |
1.2 SPI 四大信号线(4线制标准)
SPI 通常使用以下 4 条信号线:
| 信号 | 名称 | 方向 | 说明 |
|---|---|---|---|
| SCLK | Serial Clock (也称 SCK) |
主 → 从 | 由主设备产生,同步数据传输 |
| MOSI | Master Out, Slave In | 主 → 从 | 主设备发送,从设备接收的数据线 |
| MISO | Master In, Slave Out | 从 → 主 | 从设备发送,主设备接收的数据线 |
| CS / NSS | Chip Select / Slave Select (也称 SS 或 nCS) |
主 → 从 | 低电平有效,用于选择目标从设备 |
✅ 注意:
- 所有信号均为推挽输出,无需上拉电阻。
- CS 可以是多个(每个从设备一根),也可以通过译码器管理。
1.3 SPI 的四种工作模式(Clock Polarity & Phase)
SPI 有 4 种工作模式,由两个参数决定:
| 参数 | 缩写 | 含义 |
|---|---|---|
| CPOL | Clock Polarity | 时钟空闲状态(0: 低电平,1: 高电平) |
| CPHA | Clock Phase | 数据采样边沿(0: 第一个边沿,1: 第二个边沿) |
| 模式 | CPOL | CPHA | 采样边沿 | 更新边沿 | 常见设备 |
|---|---|---|---|---|---|
| Mode 0 | 0 | 0 | SCLK 上升沿 | 下降沿 | 多数设备(如nRF24L01) |
| Mode 1 | 0 | 1 | SCLK 下降沿 | 上升沿 | 少数传感器 |
| Mode 2 | 1 | 0 | SCLK 下降沿 | 上升沿 | 一些Flash |
| Mode 3 | 1 | 1 | SCLK 上升沿 | 下降沿 | MAX6675、部分EEPROM |
2. SPI应用示例
2.1 SPI初始化
#include "spi.h"
#include "usart.h"
#include <stdio.h>
SPI_HandleTypeDef spi_handle;
void spi_init(void)
{
__HAL_RCC_SPI1_CLK_ENABLE();
spi_handle.Instance = SPI1;
spi_handle.Init.Mode = SPI_MODE_MASTER; // 设置SPI工作模式为主模式
spi_handle.Init.Direction = SPI_DIRECTION_2LINES; // 设置SPI数据传输方向,2线模式
spi_handle.Init.DataSize = SPI_DATASIZE_8BIT; // 设置SPI的数据大小为8位
spi_handle.Init.CLKPolarity = SPI_POLARITY_HIGH; // 时钟极性为高
spi_handle.Init.CLKPhase = SPI_PHASE_2EDGE; // 第二个时钟沿采样
spi_handle.Init.NSS = SPI_NSS_SOFT; // 设置NSS信号由硬件管理
spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 设置波特率预分频值为256
spi_handle.Init.TIMode = SPI_TIMODE_DISABLED; // 禁用TI模式
spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED; // 禁用CRC计算
spi_handle.Init.CRCPolynomial = 7; // 设置CRC计算多项式为7
__HAL_SPI_ENABLE(&spi_handle);
spi1_read_write_byte(0xFF);
printf("SPI init success!\r\n");
}
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hspi->Instance==SPI1)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
// SPI1_SCK PB3 SPI1_MISO PB4 SPI1_MOSI PB5
GPIO_InitStruct.Pin = GPIO_PIN_3; // SCK复用输出
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_4; // MISO复用输输出
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_5; // MOSI复用输出
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}
/**
* @brief SPI1速度设置函数
* @note SPI1时钟选择来自APB1, 即PCLK1, 为 42MHz
* SPI速度 = PCLK1 / 2^(speed + 1)
* @param speed : SPI1时钟分频系数
取值为SPI_BAUDRATEPRESCALER_2~SPI_BAUDRATEPRESCALER_2 256
* @retval 无
*/
void spi1_set_speed(uint8_t speed)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(speed));// 判断有效性
__HAL_SPI_DISABLE(&spi_handle); // 关闭SPI
spi_handle.Instance->CR1 &= 0XFFC7; //位3-5清零,用来设置波特率
spi_handle.Instance->CR1 |= speed << 3; //设置SPI速度
__HAL_SPI_ENABLE(&spi_handle); // 使能SPI
}
uint8_t spi1_read_write_byte(uint8_t txdata)
{
uint8_t rxdata;
HAL_SPI_TransmitReceive(&spi_handle, &txdata, &rxdata, 1, 1000);
return rxdata;
}
2.2 flash相关参数宏定义
#ifndef __FLASH_H
#define __FLASH_H
#include "sys.h"
/* FLASH芯片列表 */
#define W25Q80 0XEF13 /* W25Q80 芯片ID */
#define W25Q16 0XEF14 /* W25Q16 芯片ID */
#define W25Q32 0XEF15 /* W25Q32 芯片ID */
#define W25Q64 0XEF16 /* W25Q64 芯片ID */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
#define W25Q256 0XEF18 /* W25Q256 芯片ID */
#define BY25Q64 0X6816 /* BY25Q64 芯片ID */
#define BY25Q128 0X6817 /* BY25Q128 芯片ID */
#define NM25Q64 0X5216 /* NM25Q64 芯片ID */
#define NM25Q128 0X5217 /* NM25Q128 芯片ID */
/* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_FastReadQuad 0xEB
#define FLASH_PageProgram 0x02
#define FLASH_PageProgramQuad 0x32
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
#define FLASH_SetReadParam 0xC0
#define FLASH_EnterQPIMode 0x38
#define FLASH_ExitQPIMode 0xFF
extern uint16_t myflash_type; /* 定义FLASH芯片型号 */
/* CS引脚控制 */
#define FLASH_CS(x) do{ x ? \
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET); \
}while(0)
/* 函数声明 */
static void flash_wait_busy(void);
void flash_write_enable(void);
static void flash_send_address(uint32_t address);
uint8_t flash_read_sr(uint8_t regno);
void flash_write_sr(uint8_t regno, uint8_t sr);
uint16_t flash_read_id(void);
void flash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
static void flash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
static void flash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
void flash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
void flash_erase_chip(void);
void flash_erase_sector(uint32_t saddr);
void spi_flash_init(void);
#endif /* __FLASH_H */
2.3 flash初始化
// SPI FLASH GPIO Configuration
void spi_flash_init(void)
{
uint8_t temp;
__HAL_RCC_GPIOB_CLK_ENABLE(); // CS-PB14
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = GPIO_PIN_14;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
FLASH_CS(0); // 取消片选
spi_init(); // 初始化SPI
spi1_set_speed(SPI_SPEED_4); // 设置SPI速度
myflash_type = flash_read_id();
if(myflash_type == W25Q256)
{
temp = flash_read_sr(3); // 读取状态寄存器3,判断地址模式
if((temp & 0x01) == 0) // 如果不受4字节地址模式,进入4字节地址模式
{
flash_write_enable();
temp |= 1<<1;
flash_write_sr(3, temp);
FLASH_CS(0);
spi1_read_write_byte(FLASH_Enable4ByteAddr); // 发送4字节地址使能命令
FLASH_CS(1);
}
}
printf("SPI_FLASH Init Success\r\n");
}
2.4 flash相关功能函数
#include "flash.h"
#include "delay.h"
#include "usart.h"
#include "spi.h"
uint16_t myflash_type = W25Q128;
static void flash_wait_busy(void)
{
while((flash_read_sr(1) & 0x01) == 0x01); // 等待BUSY位清空
}
// 25QXX写使能-将S1寄存器的WEL置位
void flash_write_enable(void)
{
FLASH_CS(0);
spi1_read_write_byte(FLASH_WriteEnable); // 发送写使能
FLASH_CS(1);
}
/**
* @brief 25QXX发送地址
* @note 根据芯片型号的不同, 发送24ibt / 32bit地址
* @param address : 要发送的地址
* @retval 无
*/
static void flash_send_address(uint32_t address)
{
if(myflash_type == W25Q256) // 只有W25Q256支持4字节地址模式
{
spi1_read_write_byte((uint8_t)((address)>>24)); // 发送 bit31 ~ bit24 地址
}
spi1_read_write_byte((uint8_t)((address)>>16)); // 发送 bit23 ~ bit16 地址
spi1_read_write_byte((uint8_t)((address)>>8)); // 发送 bit15 ~ bit8 地址
spi1_read_write_byte((uint8_t)address); // 发送 bit7 ~ bit0 地址
}
/**
* @brief 读取25QXX的状态寄存器,25QXX一共有3个状态寄存器
* @note 状态寄存器1:
* BIT7 6 5 4 3 2 1 0
* SPR RV TB BP2 BP1 BP0 WEL BUSY
* SPR:默认0,状态寄存器保护位,配合WP使用
* TB,BP2,BP1,BP0:FLASH区域写保护设置
* WEL:写使能锁定
* BUSY:忙标记位(1,忙;0,空闲)
* 默认:0x00
*
* 状态寄存器2:
* BIT7 6 5 4 3 2 1 0
* SUS CMP LB3 LB2 LB1 (R) QE SRP1
*
* 状态寄存器3:
* BIT7 6 5 4 3 2 1 0
* HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
*
* @param regno: 状态寄存器号,范:1~3
* @retval 状态寄存器值
*/
uint8_t flash_read_sr(uint8_t regno)
{
uint8_t byte = 0, command = 0;
switch (regno)
{
case 1:
command = FLASH_ReadStatusReg1; // 读状态寄存器1指令
break;
case 2:
command = FLASH_ReadStatusReg2; // 读状态寄存器2指令
break;
case 3:
command = FLASH_ReadStatusReg3; // 读状态寄存器3指令
break;
default:
command = FLASH_ReadStatusReg1;
break;
}
FLASH_CS(0);
spi1_read_write_byte(command); // 发送读寄存器命令
byte = spi1_read_write_byte(0Xff);// 读取一个字节
FLASH_CS(1);
return byte;
}
/**
* @brief 写25QXX状态寄存器
* @note 寄存器说明见norflash_read_sr函数说明
* @param regno: 状态寄存器号,范:1~3
* @param sr : 要写入状态寄存器的值
* @retval 无
*/
void flash_write_sr(uint8_t regno, uint8_t sr)
{
uint8_t command = 0;
switch (regno)
{
case 1:
command = FLASH_WriteStatusReg1; // 写状态寄存器1指令
break;
case 2:
command = FLASH_WriteStatusReg2; // 写状态寄存器2指令
break;
case 3:
command = FLASH_WriteStatusReg3; // 写状态寄存器3指令
break;
default:
command = FLASH_WriteStatusReg1;
break;
}
FLASH_CS(0);
spi1_read_write_byte(command);
spi1_read_write_byte(sr); // 写入一个字节
FLASH_CS(1);
}
/**
* @brief 读取芯片ID
* @param 无
* @retval FLASH芯片ID
* @note 芯片ID列表见: flash.h, 芯片列表部分
*/
// 修改后完整函数
uint16_t flash_read_id(void)
{
uint16_t deviceid;
FLASH_CS(0);
spi1_read_write_byte(FLASH_JedecDeviceID); // 0x9F
deviceid = spi1_read_write_byte(0xFF) << 8; // 制造商ID
deviceid |= spi1_read_write_byte(0xFF); // 设备ID高字节
spi1_read_write_byte(0xFF); // 消耗容量字节
FLASH_CS(1);
return deviceid;
}
/**
* @brief 读取SPI FLASH
* @note 在指定地址开始读取指定长度的数据
* @param pbuf : 数据存储区
* @param addr : 开始读取的地址(最大32bit)
* @param datalen : 要读取的字节数(最大65535)
* @retval 无
*/
void flash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
FLASH_CS(0);
spi1_read_write_byte(FLASH_ReadData); // 发送读取命令
flash_send_address(addr); // 发送地址
for (i = 0; i < datalen; i++)
{
pbuf[i] = spi1_read_write_byte(0XFF);
}
FLASH_CS(1);
}
/**
* @brief SPI在一页(0~65535)内写入少于256个字节的数据
* @note 在指定地址开始写入最大256字节的数据
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
* @retval 无
*/
static void flash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
flash_write_enable();
FLASH_CS(0);
spi1_read_write_byte(FLASH_PageProgram); // 发送写页命令
flash_send_address(addr);
for (i = 0; i < datalen; i++)
{
spi1_read_write_byte(pbuf[i]);
}
FLASH_CS(1);
flash_wait_busy(); // 等待写入结束
}
/**
* @brief 无检验写SPI FLASH
* @note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
* 具有自动换页功能
* 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
*
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 无
*/
static void flash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t pageremain;
pageremain = 256 - addr % 256; // 单页剩余的字节数
if (datalen <= pageremain) // 不大于256个字节
{
pageremain = datalen;
}
while (1)
{
/* 当写入字节比页内剩余地址还少的时候, 一次性写完
* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
*/
flash_write_page(pbuf, addr, pageremain);
if (datalen == pageremain) // 写入结束了
{
break;
}
else // 当前页写不完, 继续写下一页
{
pbuf += pageremain; // pbuf指针地址偏移,前面已经写了pageremain字节
addr += pageremain; // 写地址偏移,前面已经写了pageremain字节
datalen -= pageremain; // 写入总长度减去已经写入了的字节数
if (datalen > 256) // 剩余数据还大于一页,可以一次写一页
{
pageremain = 256; // 一次可以写入256个字节
}
else // 剩余数据小于一页,可以一次写完
{
pageremain = datalen; // 不够256个字节了
}
}
}
}
/**
* @brief 写SPI FLASH
* @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
* SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
* 擦除的最小单位为Sector.
*
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 无
*/
uint8_t g_norflash_buf[4096]; /* 扇区缓存 */
void flash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *norflash_buf;
norflash_buf = g_norflash_buf;
secpos = addr / 4096; // 扇区地址
secoff = addr % 4096; // 在扇区内的偏移
secremain = 4096 - secoff; // 扇区剩余空间大小
//printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
if (datalen <= secremain)
{
secremain = datalen; // 不大于4096个字节
}
while (1)
{
flash_read(norflash_buf, secpos * 4096, 4096); // 读出整个扇区的内容
for (i = 0; i < secremain; i++) // 校验数据
{
if (norflash_buf[secoff + i] != 0XFF)
{
break; // 需要擦除, 直接退出for循环
}
}
if (i < secremain) // 需要擦除
{
flash_erase_sector(secpos); // 擦除这个扇区
for (i = 0; i < secremain; i++)
{
norflash_buf[i + secoff] = pbuf[i];
}
flash_write_nocheck(norflash_buf, secpos * 4096, 4096); // 写入整个扇区
}
else // 写已经擦除了的,直接写入扇区剩余区间.
{
flash_write_nocheck(pbuf, addr, secremain);
}
if (datalen == secremain)
{
break; // 写入结束了
}
else // 写入未结束
{
secpos++; // 扇区地址增1
secoff = 0; // 偏移位置为0
pbuf += secremain; // 指针偏移
addr += secremain; // 写地址偏移
datalen -= secremain; // 字节数递减
if (datalen > 4096)
{
secremain = 4096; // 下一个扇区还是写不完
}
else
{
secremain = datalen; // 下一个扇区可以写完了
}
}
}
}
// 擦除整个芯片
void flash_erase_chip(void)
{
flash_write_enable();
flash_wait_busy();
FLASH_CS(0);
spi1_read_write_byte(FLASH_ChipErase);
FLASH_CS(1);
flash_wait_busy();
}
/**
* @brief 擦除一个扇区
* @note 注意,这里是扇区地址,不是字节地址!!
* 擦除一个扇区的最少时间:150ms
*
* @param saddr : 扇区地址 根据实际容量设置
* @retval 无
*/
void flash_erase_sector(uint32_t saddr)
{
//printf("fe:%x\r\n", saddr); /* 监视falsh擦除情况,测试用 */
saddr *= 4096;
flash_write_enable();
flash_wait_busy();
FLASH_CS(0);
spi1_read_write_byte(FLASH_SectorErase);
flash_send_address(saddr);
FLASH_CS(1);
flash_wait_busy();
}
2.5 主函数测试
#include "bsp_init.h"
#include "flash.h"
#include "spi.h"
const uint8_t text_buf[] = "STM32 SPI TEST";
#define TEXT_SIZE sizeof(text_buf)
int main(void)
{
uint8_t key_value;
uint16_t i = 0;
uint8_t data_buf[TEXT_SIZE];
uint32_t flash_size;
bsp_init();
spi_flash_init();
LCD_ShowString(30,110,200,16,16,"KEY1:Write KEY0:Read");
LCD_ShowString(30,130,200,16,16,"SPI FLASH Ready!");
while(1)
{
key_value = key_scan(0);
if(key_value == KEY1_Press)
{
LCD_ShowString(30,150,200,16,16,"Start Write FLASH....");
sprintf((char*)data_buf, "%s%d", (char*)text_buf, i);
flash_write((uint8_t*)data_buf, flash_size-100, TEXT_SIZE);
LCD_ShowString(30,150,200,16,16,"FLASH Write Finished!");
}
if(key_value == KEY0_Press)
{
LCD_ShowString(30,150,200,16,16,"Start Read FLASH... .");
flash_read(data_buf, flash_size-100, TEXT_SIZE);
LCD_ShowString(30,150,200,16,16,"FLASH Read Finished! ");
LCD_ShowString(30,170,200,16,16,(char*)data_buf);
}
i++;
if(i == 20)
{
LED_TOGGLE(LED1_GPIO_Pin);
i = 0;
}
delay_ms(10);
}
}
3. SPI常见函数(HAL库)
3.1 SPI 初始化与配置
3.1.1 HAL_SPI_Init()
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
配置结构体:
typedef struct {
SPI_TypeDef *Instance; // SPI实例 (SPI1, SPI2, SPI3)
uint32_t Mode; // 模式: SPI_MODE_MASTER/SLAVE
uint32_t Direction; // 方向:
// SPI_DIRECTION_2LINES (全双工)
// SPI_DIRECTION_2LINES_RXONLY
// SPI_DIRECTION_1LINE (半双工)
uint32_t DataSize; // 数据大小: SPI_DATASIZE_8BIT/16BIT
uint32_t CLKPolarity; // 时钟极性: SPI_POLARITY_LOW/HIGH
uint32_t CLKPhase; // 时钟相位: SPI_PHASE_1EDGE/2EDGE
uint32_t NSS; // 片选模式:
// SPI_NSS_SOFT (软件控制)
// SPI_NSS_HARD_INPUT/OUTPUT
uint32_t BaudRatePrescaler; // 波特率分频:
// SPI_BAUDRATEPRESCALER_2/4/...
uint32_t FirstBit; // 位序: SPI_FIRSTBIT_MSB/LSB
uint32_t TIMode; // TI模式: SPI_TIMODE_DISABLE
uint32_t CRCCalculation;// CRC计算: SPI_CRCCALCULATION_DISABLE
uint32_t CRCPolynomial; // CRC多项式
} SPI_InitTypeDef;
主模式初始化示例:
SPI_HandleTypeDef hspi1;
void SPI_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;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
Error_Handler();
}
}
3.2 SPI 数据传输函数
3.2.1 阻塞模式
// 发送数据
HAL_StatusTypeDef HAL_SPI_Transmit(
SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
// 接收数据
HAL_StatusTypeDef HAL_SPI_Receive(
SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
// 同时发送和接收(最常用)
HAL_StatusTypeDef HAL_SPI_TransmitReceive(
SPI_HandleTypeDef *hspi,
uint8_t *pTxData,
uint8_t *pRxData,
uint16_t Size,
uint32_t Timeout);
示例:
// 发送命令并接收响应
uint8_t txData[2] = {0x90, 0x00}; // 读取ID命令
uint8_t rxData[2];
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低片选
HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 2, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 释放片选
3.2.2 中断模式
// 中断发送
HAL_StatusTypeDef HAL_SPI_Transmit_IT(...);
// 中断接收
HAL_StatusTypeDef HAL_SPI_Receive_IT(...);
// 中断发送接收
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(...);
3.2.3 DMA模式
// DMA发送
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(...);
// DMA接收
HAL_StatusTypeDef HAL_SPI_Receive_DMA(...);
// DMA发送接收(高效方式)
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(...);
3.3 SPI 回调函数
// 发送完成回调
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
// 接收完成回调
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);
// 发送接收完成回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);
// 错误回调
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi);
示例实现:
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
if(hspi->Instance == SPI1) {
// 处理SPI1传输完成
data_ready_flag = 1;
}
}
3.4 SPI 中断处理
// SPI中断服务函数
void SPIx_IRQHandler(void) {
HAL_SPI_IRQHandler(&hspix);
}
示例:
// stm32f4xx_it.c中
void SPI1_IRQHandler(void) {
HAL_SPI_IRQHandler(&hspi1);
}
3.5 SPI 状态管理
3.5.1 状态获取
// 获取SPI状态
HAL_SPI_StateTypeDef HAL_SPI_GetState(SPI_HandleTypeDef *hspi);
// 获取错误代码
uint32_t HAL_SPI_GetError(SPI_HandleTypeDef *hspi);
3.5.2 错误代码常量
HAL_SPI_ERROR_NONE // 无错误
HAL_SPI_ERROR_MODF // 模式错误
HAL_SPI_ERROR_CRC // CRC错误
HAL_SPI_ERROR_OVR // 溢出错误
HAL_SPI_ERROR_FRE // 帧格式错误
HAL_SPI_ERROR_DMA // DMA传输错误
HAL_SPI_ERROR_FLAG // 标志错误
3.6 SPI 高级控制
// 中止传输
HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_Abort_IT(SPI_HandleTypeDef *hspi);
// 控制CRC计算
HAL_StatusTypeDef HAL_SPI_EnableCRC(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DisableCRC(SPI_HandleTypeDef *hspi);

浙公网安备 33010602011771号