【STM32】 SPI阻塞式通信与HAL源码 (源码分析)


SPI阻塞式通信与HAL源码

源码解析

源码解析分两大块:

初始化(MX_SPI1_Init / MspInit) 和 收发(TransmitReceive / Transmit / Receive)****。

核心是搞清楚"CubeMX 的每一项配置,最终落到了哪个寄存器的哪一位"。

1. 初始化相关

1.1 MX_SPI1_Init

MX_SPI1_Init() 里通过句柄 hspi1 逐项填入配置(主机、双线全双工、8位、Mode0、软件NSS、64分频、MSB、禁TI、禁CRC),最后调用 HAL_SPI_Init(&hspi1)

这些代码就是 CubeMX 图形配置的 C 语言翻译。


1.2 SPI_HandleTypeDef 和 SPI_TypeDef:句柄如何映射到硬件

关键一句:hspi1.Instance = SPI1;

(1)存储器映像(STM32 地址划分,图9)

Cortex-M3 的 4GB 地址空间被切成 8 个 512MB 的块:

地址范围 用途
block 0 0x00000000~0x1FFFFFFF Code(Flash/SRAM)
block 1 0x20000000~0x3FFFFFFF SRAM
block 2 0x40000000~0x5FFFFFFF 片上外设 Peripherals
block 3~6 0x60000000~0x9FFFFFFF FSMC(外扩存储)
block 7 0xA0000000~0xFFFFFFFF Cortex-M3 内部私有外设

外设块 block 2 又分:

APB1(0x40000000 起)、APB2(0x40010000 起)、

AHB(0x40018000 起)、Cortex-M3 内部(0xE0000000 起)。


(2)SPI1 的地址计算

cpp 复制代码
#define PERIPH_BASE        0x40000000UL
#define APB2PERIPH_BASE    (PERIPH_BASE + 0x00010000UL)  // = 0x40010000
#define SPI1_BASE          (APB2PERIPH_BASE + 0x00003000UL) // = 0x40013000
#define SPI1               ((SPI_TypeDef *)SPI1_BASE)

即 SPI1 地址范围:

0x40013000 ~ 0x400133FF

(SPI2 在 APB1 的 0x40003800,SPI3 在 0x40003C00。)


(3)SPI_TypeDef ------ 寄存器的结构体镜像

cpp 复制代码
typedef struct {
  __IO uint32_t CR1;      // 偏移 0x00  控制寄存器1
  __IO uint32_t CR2;      // 偏移 0x04  控制寄存器2
  __IO uint32_t SR;       // 偏移 0x08  状态寄存器
  __IO uint32_t DR;       // 偏移 0x0C  数据寄存器
  __IO uint32_t CRCPR;    // 偏移 0x10  CRC多项式
  __IO uint32_t RXCRCR;   // 偏移 0x14  接收CRC
  __IO uint32_t TXCRCR;   // 偏移 0x18  发送CRC
  __IO uint32_t I2SCFGR;  // 偏移 0x1C  I2S配置
  __IO uint32_t I2SPR;    // 偏移 0x20  I2S预分频
} SPI_TypeDef;

核心机制(C 语言结构体映射硬件):

结构体成员顺序与 9 个寄存器的偏移顺序完全一致,且都是 32 位(4 字节),自然形成 0x00、0x04...0x20 的偏移量。

于是 hspi1.Instance 指向 0x40013000 后,Cortex-M3 就能像操作内存一样用指针操作 SPI1 的所有寄存器。SPI2、SPI3 同理。


(4)SPI_HandleTypeDef 句柄里除了 Instance 还有什么

cpp 复制代码
typedef struct __SPI_HandleTypeDef {
  SPI_TypeDef            *Instance;      // 寄存器基址
  SPI_InitTypeDef         Init;          // 初始化参数(模式/极性/相位等)
  const uint8_t          *pTxBuffPtr;    // 发送缓冲区指针
  uint16_t                TxXferSize;    // 待发总数
  __IO uint16_t           TxXferCount;   // 发送计数(还剩多少)
  uint8_t                *pRxBuffPtr;    // 接收缓冲区指针
  uint16_t                RxXferSize;    // 待收总数
  __IO uint16_t           RxXferCount;   // 接收计数
  void (*pTxISR)(struct __SPI_HandleTypeDef *hspi);  // 发送中断回调
  void (*pRxISR)(struct __SPI_HandleTypeDef *hspi);  // 接收中断回调
  DMA_HandleTypeDef      *hdmatx, *hdmarx;          // DMA句柄
  HAL_LockTypeDef         Lock;          // 资源锁(防重入)
  __IO HAL_SPI_StateTypeDef State;       // 空闲/忙/错
  __IO uint32_t           ErrorCode;     // 错误码
  /* 回调函数指针(USE_HAL_SPI_REGISTER_CALLBACKS 时) */
} SPI_HandleTypeDef;

1.3 SPI 复用 GPIO:HAL_SPI_MspInit

HAL_SPI_Init() 内部会回调 HAL_SPI_MspInit()(位于 stm32f1xx_hal_msp.c),做与具体芯片相关的底层配置:

cpp 复制代码
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hspi->Instance == SPI1)
  {
    __HAL_RCC_SPI1_CLK_ENABLE();    // 开 SPI1 时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();   // 开 GPIOA 时钟

    // PA5 -> SPI1_SCK, PA7 -> SPI1_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 -> SPI1_MISO:输入(浮空)
    GPIO_InitStruct.Pin  = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

MSP = MCU Support Package(微控制器支持包)。

同样是 I2C/SPI 协议,在 F1 和 F4 上逻辑一样,但引脚(GPIO)、时钟(RCC)、中断号(IRQ)可能完全不同,这部分差异就放在 MspInit 里处理。


1.4 开启 SPI1 时钟

__HAL_RCC_SPI1_CLK_ENABLE() 展开为操作 RCC 的 APB2ENR 寄存器(SPI1 挂在 APB2 上):

cpp 复制代码
#define __HAL_RCC_SPI1_CLK_ENABLE()  do { \
  __IO uint32_t tmpreg; \
  SET_BIT(RCC->APB2ENR, RCC_APB2ENR_SPI1EN); \
  tmpreg = RCC->APB2ENR; UNUSED(tmpreg);      // 读后延迟,等待时钟稳定
} while(0)

RCC_APB2ENR(APB2 外设时钟使能寄存器,偏移 0x18,复位 0):

  • 位12 SPI1EN:软件置1开启 SPI1 时钟,清0关闭;
  • 同寄存器还有 USART1EN(位14)、ADC1EN(位9)、GPIOAEN(位2) 等。

重要规则:外设时钟未使能时,软件读它的寄存器只会得到 0x0。


1.5 SPI 使能与禁用:时钟使能 ≠ 设备使能

两个概念必须分清:

  • 时钟使能:外设接入时钟树,能被配置;
  • 设备使能(SPE 位):外设真正开始工作。

SPI 不允许在通信过程中动态改配置------参考手册明确:

当通信正在进行时,MSTR、BR、CPOL、CPHA、DFF 等位都不能修改。

因此写配置前必须先禁用设备:

cpp 复制代码
// HAL_SPI_Init 开头
__HAL_SPI_DISABLE(hspi);
cpp 复制代码
#define __HAL_SPI_DISABLE(__HANDLE__)  CLEAR_BIT((__HANDLE__)->Instance->CR1, SPI_CR1_SPE)
#define SPI_CR1_SPE_Pos   (6U)
#define SPI_CR1_SPE       SPI_CR1_SPE_Msk   // 0x0040

SPE 位在 CR1 的位6:

0=禁止 SPI 设备,1=开启。

清 SPE=0 让 SPI1 停止工作(时钟仍开着),之后才能写入配置。


1.6 将配置信息设置到 SPI 接口(逐位解析,重点)

禁用后,HAL 用一条 WRITE_REG 把用户参数按位拼进 CR1,再写 CR2:

cpp 复制代码
WRITE_REG(hspi->Instance->CR1,
   (hspi->Init.Mode        & (SPI_CR1_MSTR | SPI_CR1_SSI)) |
   (hspi->Init.Direction   & (SPI_CR1_RXONLY | SPI_CR1_BIDIMODE)) |
   (hspi->Init.DataSize    & SPI_CR1_DFF) |
   (hspi->Init.CLKPolarity & SPI_CR1_CPOL) |
   (hspi->Init.CLKPhase    & SPI_CR1_CPHA) |
   (hspi->Init.NSS         & SPI_CR1_SSM) |
   (hspi->Init.BaudRatePrescaler & SPI_CR1_BR_Msk) |
   (hspi->Init.FirstBit    & SPI_CR1_LSBFIRST) |
   (hspi->Init.CRCCalculation & SPI_CR1_CRCEN));

WRITE_REG(hspi->Instance->CR2, (hspi->Init.NSS >> 16) & SPI_CR2_SSOE);

下面逐项对照本实验配置(Mode0、主机、全双工、8位、MSB、软件NSS、64分频):

1.6.1 配置主从(MSTR 位2 + SSI 位8)
复制代码
hspi1.Init.Mode = SPI_MODE_MASTER;
#define SPI_MODE_MASTER  (SPI_CR1_MSTR | SPI_CR1_SSI)
  • MSTR(位2):1=主设备,0=从设备;
  • SSI(位8,内部从机选择):只在 SSM=1(软件从机管理)时有意义,它决定 NSS 的内部电平。主机模式下 SSI 必须置1,否则会因 NSS 被拉低而发生模式故障(MODF),退化成从机。
  • 结论:主机模式 = MSTR=1 + SSI=1(配合软件片选,让外部 NSS 引脚无效)。
1.6.2 配置双线全双工(RXONLY 位10 + BIDIMODE 位15)
复制代码
hspi1.Init.Direction = SPI_DIRECTION_2LINES;   // = 0x00000000
  • BIDIMODE(位15):0=双线(双向),1=单线(双向);
  • RXONLY(位10):BIDIMODE=0 时,0=全双工(收发),1=只接收。
  • 本实验这两位都为 0 → 标准双线全双工。
1.6.3 配置 8 位数据帧(DFF 位11)
复制代码
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;  // = 0
  • DFF(位11,Data Frame Format):0=8位帧,1=16位帧;只能在 SPE=0 时改。
  • 本实验 DFF=0 → 8 位。
1.6.4 配置极性和相位(CPOL 位1 + CPHA 位0)
cpp 复制代码
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;   // 0
hspi1.Init.CLKPhase    = SPI_PHASE_1EDGE;    // 0
  • CPOL(位1):空闲电平,0=低;
  • CPHA(位0):0=第1个边沿采样,1=第2个边沿采样。
  • 本实验两者皆0 → Mode0。
1.6.5 配置软件片选(SSM 位9)
复制代码
hspi1.Init.NSS = SPI_NSS_SOFT;   // = SPI_CR1_SSM
  • SSM(位9,Software Slave Management):1=软件从机管理(NSS 引脚电平由 SSI 决定,外部 NSS 引脚空闲可作它用),0=硬件管理。
  • 本实验 SSM=1 → 启用软件片选,不用默认 NSS(PA4),改用 GPIO(PE6)片选。
1.6.6 配置 MSB(LSBFIRST 位7)
复制代码
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;  // = 0
  • LSBFIRST(位7):0=先传 MSB,1=先传 LSB。
  • 本实验为0 → MSB 优先。

初始化收尾:

复制代码
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->State     = HAL_SPI_STATE_READY;
return HAL_OK;

总结论:配置 SPI,本质就是用用户设置去写 SPI_CR1(和 CR2)的各个标志位。


2. 全双工读写:HAL_SPI_TransmitReceive

函数虽长,但只看关键骨架即可。

2.1 执行流程(分7步)

① 定义局部变量

cpp 复制代码
uint16_t initial_TxXferCount;
uint32_t tmp_mode;
HAL_SPI_StateTypeDef tmp_state;
uint32_t tickstart;
uint32_t txallowed = 1U;     // 关键:控制收发交替的标志

② 记录 tickstart(超时基准)、备份状态

cpp 复制代码
tickstart = HAL_GetTick();
tmp_state = hspi->State;
tmp_mode  = hspi->Init.Mode;
initial_TxXferCount = Size;

③ 状态与参数检查

cpp 复制代码
// 非READY,或主机双线模式下处于BUSY → 直接返回 BUSY(防重入)
if (!((tmp_state == HAL_SPI_STATE_READY) ||
   ((tmp_mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES)
    && (tmp_state == HAL_SPI_STATE_BUSY))))
  return HAL_BUSY;

if ((pTxData == NULL) || (pRxDData == NULL) || (Size == 0U))
  return HAL_ERROR;

④ 加锁、置忙、装载事务信息

cpp 复制代码
__HAL_LOCK(hspi);
if (hspi->State != HAL_SPI_STATE_BUSY_RX)
  hspi->State = HAL_SPI_STATE_BUSY_TX_RX;

hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->pRxBuffPtr = (uint8_t *)pRxDData; 
hspi->RxXferSize = Size; hspi->RxXferCount = Size;
hspi->pTxBuffPtr = (const uint8_t *)pTxData; 
hspi->TxXferSize = Size; hspi->TxXferCount = Size;
hspi->RxISR = NULL; hspi->TxISR = NULL;

⑤ 使能 SPI 设备(初始化时 SPE=0,这里才真正开启)

复制代码
if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  __HAL_SPI_ENABLE(hspi);   // SPE=1,时钟+设备都开启,SPI 正常工作

⑥ 从机或单字节:先预发一个字节

复制代码
if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
{
  *((__IO uint8_t *)&hspi->Instance->DR) = *((const uint8_t *)hspi->pTxBuffPtr);
  hspi->pTxBuffPtr += sizeof(uint8_t);
  hspi->TxXferCount--;
}

从机自己不能产生 SCK,必须在主机时钟到来前就把数据放进 DR 备好;

若只有 1 字节要发,直接发即可,不必进 while。

⑦ 核心 while 循环(收发交替)

cpp 复制代码
while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U))
{
  // 发送:TXE=1(发送缓冲空)& 还有数据 & 当前允许发 → 写DR
  if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE) && (hspi->TxXferCount > 0U) && (txallowed == 1U))
  {
    *((__IO uint8_t *)&hspi->Instance->DR) = *((const uint8_t *)hspi->pTxBuffPtr);
    hspi->pTxBuffPtr++;
    hspi->TxXferCount--;
    txallowed = 0U;          // 发了一次,下次不允许再发,该收了
  }

  // 接收:RXNE=1(接收缓冲非空)& 还要收 → 读DR
  if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE) && (hspi->RxXferCount > 0U))
  {
    *(uint8_t *)hspi->pRxBuffPtr = hspi->Instance->DR;
    hspi->pRxBuffPtr++;
    hspi->RxXferCount--;
    txallowed = 1U;          // 收了一次,下次该发了
  }

  // 超时检测
  if (((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY))
  { hspi->State = HAL_SPI_STATE_READY; __HAL_UNLOCK(hspi); return HAL_TIMEOUT; }
}

⑧ 收尾:结束事务、清溢出、恢复状态

cpp 复制代码
if (SPI_EndRxTxTransaction(hspi, Timeout, tickstart) != HAL_OK) {
  hspi->ErrorCode = HAL_SPI_ERROR_FLAG; __HAL_UNLOCK(hspi); return HAL_ERROR;
}
if (hspi->Init.Direction == SPI_DIRECTION_2LINES)
  __HAL_SPI_CLEAR_OVRFLAG(hspi);     // 清溢出标志(双线模式收到但没读会置OVR)

hspi->State = HAL_SPI_STATE_READY;
__HAL_UNLOCK(hspi);
return (hspi->ErrorCode != HAL_SPI_ERROR_NONE) ? HAL_ERROR : HAL_OK;
软件 vs 硬件两个角度的结论(重点理解)
  • **软件角度:阻塞全双工的流程是 发→收→发→收...,由 while 循环在 CPU 时钟节奏下执行,每次都检测标志位(TXE/RXNE)决定能否动作,不满足就检测超时。**发送本质 = 把用户缓冲区数据拷贝到 DR;接收 = 从 DR 拷回用户缓冲区。
  • 硬件角度:SPI 自己会把 DR 数据移到移位寄存器并逐位发出,同时从 MISO 采样进移位寄存器再拷回 DR,收发同时进行(SPI 内部有发送/接收两个缓冲区),一个 8 时钟时序完成一次完整收发。

数据通路示意:

cpp 复制代码
发送方向:
用户缓冲区 → [拷贝1] → SPI_DR(发送缓冲区) → 硬件自动搬 → 移位寄存器 → MOSI引脚

接收方向:
MISO引脚 → 移位寄存器 → 硬件自动搬 → SPI_DR(接收缓冲区) → [拷贝2] → 用户缓冲区

TXE 就是 "拷贝 1" 的闸门RXNE 就是 "拷贝 2" 的闸门


2.1.1 细节1:SPI_DR 数据寄存器(偏移0x0C)

  • DR 对应两个缓冲区:写操作 → 发送缓冲;读操作 → 接收缓冲;
  • 8位帧(DFF=0)时只用 DR[7:0],接收时 DR[15:8] 强制为0;16位帧用整个 DR[15:0]

2.1.2 细节2:SPI_FLAG_TXE(SR 位1)

  • TXE(位1,Transmit buffer Empty):1=发送缓冲空,0=非空;
  • 工作过程:CPU 写数据到发送缓冲 → TXE 自动清0(非空)→ SPI 把数据从缓冲拷到移位寄存器(准备/正在发送)→ 缓冲空 → TXE 自动置1,用户可继续写下一字节;
  • 用户层只需不断检测 TXE 判断能否再发。
  • __HAL_SPI_GET_FLAG 本质就是 (SR & FLAG) == FLAG 地反复查标志。

注意:硬件外设与 CPU 速度不一定匹配,所以才需要这种"查询-等待"机制。


2.1.3 细节3:SPI_FLAG_RXNE(SR 位0)

  • RXNE(位0,Receive buffer not Empty):1=接收缓冲非空(有数据可读),0=空;
  • 过程:SPI1 从 MISO 采样进移位寄存器,凑满 8 位后自动拷到接收缓冲 → RXNE=1;用户读 DR 后 RXNE 自动清0;
  • 用户检测 RXNE=1 就读数据。

关键认知:DR 缓冲与移位寄存器之间的互相移动是 SPI 硬件自动完成的,与 Cortex-M3 无关。


2.1.4 细节4:SPI_FLAG_BSY 与 SPI_EndRxTxTransaction(SR 位7)

BSY(位7,Busy Flag):

1=SPI 正忙于通信或发送缓冲非空,0=不忙,由硬件置位/复位。

SPI_EndRxTxTransaction() 做两件等待:

cpp 复制代码
static HAL_StatusTypeDef SPI_EndRxTxTransaction(SPI_HandleTypeDef *hspi, uint32_t Timeout, uint32_t Tickstart)
{
  // 1. 等 TXE=1:最后一字节已从"缓冲"搬到"移位寄存器"
  if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_TXE, SET, Timeout, Tickstart) != HAL_OK) {
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG); return HAL_TIMEOUT;
  }
  // 2. 等 BSY=0:硬件不再忙碌,最后一字节已在引脚上逐位发完
  if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_BSY, RESET, Timeout, Tickstart) != HAL_OK) {
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG); return HAL_TIMEOUT;
  }
  return HAL_OK;
}
  • 为什么这里不检测 RXNE?
    • while 循环退出条件是 RxXferCount==0,说明最后一字节已被 CPU 从 DR 取走,RXNE 硬件上会自动清零,无需再查。
  • 为什么必须等 TXE?
    • while 循环结束只代表"用户数据都写进 DR 了",但最后一字节可能还没从缓冲搬到移位寄存器(循环结束条件不保证这一点)。
  • 数据进了移位寄存器就算发完吗?
    • 不是,硬件还要花时间逐位发送。所以必须再等 BSY=0,才证明引脚上电平跳变彻底完成,此时拉高 CS 才安全(BSY 长时间不释放通常意味着总线被挂死或时钟配置错误)。

3. 全双工只写:HAL_SPI_Transmit

只写是读写逻辑的子集,只列不同点:

① 接收相关全部置空:

cpp 复制代码
hspi->State      = HAL_SPI_STATE_BUSY_TX;
hspi->pTxBuffPtr = (const uint8_t *)pData;
hspi->TxXferSize = Size; hspi->TxXferCount = Size;
hspi->pRxBuffPtr = (uint8_t *)NULL;   // 不准备接收
hspi->RxXferSize = 0U;  hspi->RxXferCount = 0U;

② while 循环只看 TXE 发送,从不读 DR:

cpp 复制代码
while (hspi->TxXferCount > 0U)
{
  if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
  {
    *((__IO uint8_t *)&hspi->Instance->DR) = *((const uint8_t *)hspi->pTxBuffPtr);
    hspi->pTxBuffPtr++;
    hspi->TxXferCount--;
  }
  else { /* 超时管理 */ }
}

核心:

SPI 物理上是全双工配置,写入多少对方就会同步返回多少,但 HAL 只写 DR、不读 DR,于是对方返回的数据在接收缓冲区被不断覆盖(或退出时随清标志被忽略)。

这就是"只写"的实现方式。

结束同样调 SPI_EndRxTxTransaction 等 TXE/BSY,并清 OVR。


4. 全双工只读:HAL_SPI_Receive

① Master 只读 → 直接复用 TransmitReceive(零拷贝技巧):

复制代码
if ((hspi->Init.Mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES))
{
  hspi->State = HAL_SPI_STATE_BUSY_RX;
  /* 发送Dummy产生SCK,把pData同时当发送缓冲和接收缓冲 */
  return HAL_SPI_TransmitReceive(hspi, pData, pData, Size, Timeout);
}

天才设计:

主机标准 SPI 想读,必须发 Size 个 Dummy 产生时钟。

HAL 干脆直接调用 TransmitReceive,把同一个 pData 同时作为发送缓冲和接收缓冲------发出去的是 Dummy(DR 残留值),收回来的数据覆盖同一地址。

这样零拷贝,避免了在 HAL 内部额外分配 Dummy 缓冲区。


② Slave 模式下的只读(后续逻辑):

从机模式下 SCK 完全由主机掌控:

  1. 硬件层:Master 每发一个字节,Slave 的移位寄存器就同步把数据"推"向 MISO,同时从 MOSI 收数据;
  2. 软件静默:即使只调 Receive,Slave 硬件也在自动发送(发 DR 缓冲的旧值或0),这个过程硬件自动闭环,无需软件干预;
  3. 核心任务:软件只需在 Master 传下一帧前,及时检查 RXNE 并把 DR 数据读走,防止被后续数据覆盖(Overrun)。
cpp 复制代码
hspi->State      = HAL_SPI_STATE_BUSY_RX;
hspi->pRxBuffPtr = (uint8_t *)pData;
hspi->RxXferSize = Size; hspi->RxXferCount = Size;
hspi->pTxBuffPtr = (uint8_t *)NULL;
hspi->TxXferSize = 0U; hspi->TxXferCount = 0U;
__HAL_SPI_ENABLE(hspi);

while (hspi->RxXferCount > 0U)        // 8位模式
{
  if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE))
  {
    *(uint8_t *)hspi->pRxBuffPtr = *((__IO uint8_t *)&hspi->Instance->DR);
    hspi->pRxBuffPtr++;
    hspi->RxXferCount--;
  }
  else { /* 超时管理 */ }
}
// 结束调 SPI_EndRxTransaction(只等 BSY,无发送需求)

最终总结论(务必记住):

  • 只要是标准 SPI(MISO/MOSI 双向全双工),无论调用哪个收发函数,硬件层面始终是全双工的;
  • HAL 库在内部丢弃不关心的数据,从而在软件逻辑上表现出"只读"或"只写";
  • 这是一种 "逻辑上软件半双工,物理上硬件全双工" 的做法。

5. TI 是什么公司?为什么 SPI 有 TI 模式?

TI(Texas Instruments,德州仪器):

全球领先的半导体跨国公司,总部位于美国得克萨斯州达拉斯。主营模拟芯片和嵌入式处理器,在 MSP430、MSP432、DSP 系列单片机及电源管理芯片领域市占率极高。

为什么会有 TI 模式?

SPI 并不是有严格国际标准的协议,而是 Motorola(摩托罗拉)在 20 世纪 80 年代发明的"事实标准(de facto standard)"。由于没有强制标准,不同厂商按自身需求对 SPI 做了微调,形成变体:

对比 Motorola 模式 TI 模式(Synchronous Serial Mode)
片选行为 NSS/CS 在整个数据帧期间保持低电平 帧开始前发一个高电平脉冲(通常称 FSS)来同步,而非全程拉低
时钟配置 CPOL+CPHA 组合出 Mode0~3 不用 CPOL/CPHA 组合,采样通常固定在时钟下降沿,靠帧同步脉冲标识起始
定位 最常用标准 SPI TI 自定义的同步串行标准

兼容性原因:

STM32 作为通用 MCU 平台,为了能直接驱动 TI 生产的特定芯片(如某些高性能 ADC、DAC、编码器),在硬件上内置了对 TI 同步串行协议的支持。

使用规则:

驱动普通 Flash(Winbond、PMC 等)时,必须 TIMode = DISABLE,用默认 Motorola 模式;只有连接 TI 生产且明确要求"同步串行模式"的设备时,才开启 TI 模式。


HAL库源码

cpp 复制代码
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint32_t tickstart;
  uint16_t initial_TxXferCount;

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));

  /* Init tickstart for timeout management*/
  tickstart = HAL_GetTick();
  initial_TxXferCount = Size;

  if (hspi->State != HAL_SPI_STATE_READY)
  {
    return HAL_BUSY;
  }

  if ((pData == NULL) || (Size == 0U))
  {
    return HAL_ERROR;
  }

  /* Process Locked */
  __HAL_LOCK(hspi);

  /* Set the transaction information */
  hspi->State       = HAL_SPI_STATE_BUSY_TX;
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pTxBuffPtr  = (const uint8_t *)pData;
  hspi->TxXferSize  = Size;
  hspi->TxXferCount = Size;

  /*Init field not used in handle to zero */
  hspi->pRxBuffPtr  = (uint8_t *)NULL;
  hspi->RxXferSize  = 0U;
  hspi->RxXferCount = 0U;
  hspi->TxISR       = NULL;
  hspi->RxISR       = NULL;

  /* Configure communication direction : 1Line */
  if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
  {
    /* Disable SPI Peripheral before set 1Line direction (BIDIOE bit) */
    __HAL_SPI_DISABLE(hspi);
    SPI_1LINE_TX(hspi);
  }

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);
  }
#endif /* USE_SPI_CRC */

  /* Check if the SPI is already enabled */
  if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  }

  /* Transmit data in 16 Bit mode */
  if (hspi->Init.DataSize == SPI_DATASIZE_16BIT)
  {
    if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
    {
      hspi->Instance->DR = *((const uint16_t *)hspi->pTxBuffPtr);
      hspi->pTxBuffPtr += sizeof(uint16_t);
      hspi->TxXferCount--;
    }
    /* Transmit data in 16 Bit mode */
    while (hspi->TxXferCount > 0U)
    {
      /* Wait until TXE flag is set to send data */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
      {
        hspi->Instance->DR = *((const uint16_t *)hspi->pTxBuffPtr);
        hspi->pTxBuffPtr += sizeof(uint16_t);
        hspi->TxXferCount--;
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          hspi->State = HAL_SPI_STATE_READY;
          __HAL_UNLOCK(hspi);
          return HAL_TIMEOUT;
        }
      }
    }
  }
  /* Transmit data in 8 Bit mode */
  else
  {
    if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
    {
      *((__IO uint8_t *)&hspi->Instance->DR) = *((const uint8_t *)hspi->pTxBuffPtr);
      hspi->pTxBuffPtr += sizeof(uint8_t);
      hspi->TxXferCount--;
    }
    while (hspi->TxXferCount > 0U)
    {
      /* Wait until TXE flag is set to send data */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
      {
        *((__IO uint8_t *)&hspi->Instance->DR) = *((const uint8_t *)hspi->pTxBuffPtr);
        hspi->pTxBuffPtr += sizeof(uint8_t);
        hspi->TxXferCount--;
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          hspi->State = HAL_SPI_STATE_READY;
          __HAL_UNLOCK(hspi);
          return HAL_TIMEOUT;
        }
      }
    }
  }
#if (USE_SPI_CRC != 0U)
  /* Enable CRC Transmission */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
  }
#endif /* USE_SPI_CRC */

  /* Check the end of the transaction */
  if (SPI_EndRxTxTransaction(hspi, Timeout, tickstart) != HAL_OK)
  {
    hspi->ErrorCode = HAL_SPI_ERROR_FLAG;
  }

  /* Clear overrun flag in 2 Lines communication mode because received is not read */
  if (hspi->Init.Direction == SPI_DIRECTION_2LINES)
  {
    __HAL_SPI_CLEAR_OVRFLAG(hspi);
  }

  hspi->State = HAL_SPI_STATE_READY;
  /* Process Unlocked */
  __HAL_UNLOCK(hspi);

  if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
  {
    return HAL_ERROR;
  }
  else
  {
    return HAL_OK;
  }
}

cpp 复制代码
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
#if (USE_SPI_CRC != 0U)
  __IO uint32_t tmpreg = 0U;
#endif /* USE_SPI_CRC */
  uint32_t tickstart;

  if (hspi->State != HAL_SPI_STATE_READY)
  {
    return HAL_BUSY;
  }

  if ((hspi->Init.Mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES))
  {
    hspi->State = HAL_SPI_STATE_BUSY_RX;
    /* Call transmit-receive function to send Dummy data on Tx line and generate clock on CLK line */
    return HAL_SPI_TransmitReceive(hspi, pData, pData, Size, Timeout);
  }

  /* Init tickstart for timeout management*/
  tickstart = HAL_GetTick();

  if ((pData == NULL) || (Size == 0U))
  {
    return HAL_ERROR;
  }

  /* Process Locked */
  __HAL_LOCK(hspi);

  /* Set the transaction information */
  hspi->State       = HAL_SPI_STATE_BUSY_RX;
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pRxBuffPtr  = (uint8_t *)pData;
  hspi->RxXferSize  = Size;
  hspi->RxXferCount = Size;

  /*Init field not used in handle to zero */
  hspi->pTxBuffPtr  = (uint8_t *)NULL;
  hspi->TxXferSize  = 0U;
  hspi->TxXferCount = 0U;
  hspi->RxISR       = NULL;
  hspi->TxISR       = NULL;

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);
    /* this is done to handle the CRCNEXT before the latest data */
    hspi->RxXferCount--;
  }
#endif /* USE_SPI_CRC */

  /* Configure communication direction: 1Line */
  if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
  {
    /* Disable SPI Peripheral before set 1Line direction (BIDIOE bit) */
    __HAL_SPI_DISABLE(hspi);
    SPI_1LINE_RX(hspi);
  }

  /* Check if the SPI is already enabled */
  if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  }

  /* Receive data in 8 Bit mode */
  if (hspi->Init.DataSize == SPI_DATASIZE_8BIT)
  {
    /* Transfer loop */
    while (hspi->RxXferCount > 0U)
    {
      /* Check the RXNE flag */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE))
      {
        /* read the received data */
        (* (uint8_t *)hspi->pRxBuffPtr) = *(__IO uint8_t *)&hspi->Instance->DR;
        hspi->pRxBuffPtr += sizeof(uint8_t);
        hspi->RxXferCount--;
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          hspi->State = HAL_SPI_STATE_READY;
          __HAL_UNLOCK(hspi);
          return HAL_TIMEOUT;
        }
      }
    }
  }
  else
  {
    /* Transfer loop */
    while (hspi->RxXferCount > 0U)
    {
      /* Check the RXNE flag */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE))
      {
        *((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;
        hspi->pRxBuffPtr += sizeof(uint16_t);
        hspi->RxXferCount--;
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          hspi->State = HAL_SPI_STATE_READY;
          __HAL_UNLOCK(hspi);
          return HAL_TIMEOUT;
        }
      }
    }
  }

#if (USE_SPI_CRC != 0U)
  /* Handle the CRC Transmission */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    /* freeze the CRC before the latest data */
    SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);

    /* Check if CRCNEXT is well reset by hardware */
    if (READ_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT))
    {
      /* Workaround to force CRCNEXT bit to zero in case of CRCNEXT is not reset automatically by hardware */
      CLEAR_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
    }
    /* Read the latest data */
    if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK)
    {
      /* the latest data has not been received */
      __HAL_UNLOCK(hspi);
      return HAL_TIMEOUT;
    }

    /* Receive last data in 16 Bit mode */
    if (hspi->Init.DataSize == SPI_DATASIZE_16BIT)
    {
      *((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;
    }
    /* Receive last data in 8 Bit mode */
    else
    {
      (*(uint8_t *)hspi->pRxBuffPtr) = *(__IO uint8_t *)&hspi->Instance->DR;
    }

    /* Wait the CRC data */
    if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK)
    {
      SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);
      hspi->State = HAL_SPI_STATE_READY;
      __HAL_UNLOCK(hspi);
      return HAL_TIMEOUT;
    }

    /* Read CRC to Flush DR and RXNE flag */
    tmpreg = READ_REG(hspi->Instance->DR);
    /* To avoid GCC warning */
    UNUSED(tmpreg);
  }
#endif /* USE_SPI_CRC */

  /* Check the end of the transaction */
  if (SPI_EndRxTransaction(hspi, Timeout, tickstart) != HAL_OK)
  {
    hspi->ErrorCode = HAL_SPI_ERROR_FLAG;
  }

#if (USE_SPI_CRC != 0U)
  /* Check if CRC error occurred */
  if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_CRCERR) != RESET)
  {
    /* Check if CRC error is valid or not (workaround to be applied or not) */
    if (SPI_ISCRCErrorValid(hspi) == SPI_VALID_CRC_ERROR)
    {
      SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);

      /* Reset CRC Calculation */
      SPI_RESET_CRC(hspi);
    }
    else
    {
      __HAL_SPI_CLEAR_CRCERRFLAG(hspi);
    }
  }
#endif /* USE_SPI_CRC */

  hspi->State = HAL_SPI_STATE_READY;
  /* Unlock the process */
  __HAL_UNLOCK(hspi);
  if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
  {
    return HAL_ERROR;
  }
  else
  {
    return HAL_OK;
  }
}

cpp 复制代码
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData,
                                          uint16_t Size, uint32_t Timeout)
{
  uint16_t             initial_TxXferCount;
  uint32_t             tmp_mode;
  HAL_SPI_StateTypeDef tmp_state;
  uint32_t             tickstart;
#if (USE_SPI_CRC != 0U)
  __IO uint32_t tmpreg = 0U;
#endif /* USE_SPI_CRC */

  /* Variable used to alternate Rx and Tx during transfer */
  uint32_t             txallowed = 1U;

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES(hspi->Init.Direction));

  /* Init tickstart for timeout management*/
  tickstart = HAL_GetTick();

  /* Init temporary variables */
  tmp_state           = hspi->State;
  tmp_mode            = hspi->Init.Mode;
  initial_TxXferCount = Size;

  if (!((tmp_state == HAL_SPI_STATE_READY) || \
        ((tmp_mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES) &&
         (tmp_state == HAL_SPI_STATE_BUSY_RX))))
  {
    return HAL_BUSY;
  }

  if ((pTxData == NULL) || (pRxData == NULL) || (Size == 0U))
  {
    return HAL_ERROR;
  }

  /* Process Locked */
  __HAL_LOCK(hspi);

  /* Don't overwrite in case of HAL_SPI_STATE_BUSY_RX */
  if (hspi->State != HAL_SPI_STATE_BUSY_RX)
  {
    hspi->State = HAL_SPI_STATE_BUSY_TX_RX;
  }

  /* Set the transaction information */
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pRxBuffPtr  = (uint8_t *)pRxData;
  hspi->RxXferCount = Size;
  hspi->RxXferSize  = Size;
  hspi->pTxBuffPtr  = (const uint8_t *)pTxData;
  hspi->TxXferCount = Size;
  hspi->TxXferSize  = Size;

  /*Init field not used in handle to zero */
  hspi->RxISR       = NULL;
  hspi->TxISR       = NULL;

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);
  }
#endif /* USE_SPI_CRC */

  /* Check if the SPI is already enabled */
  if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  }

  /* Transmit and Receive data in 16 Bit mode */
  if (hspi->Init.DataSize == SPI_DATASIZE_16BIT)
  {
    if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
    {
      hspi->Instance->DR = *((const uint16_t *)hspi->pTxBuffPtr);
      hspi->pTxBuffPtr += sizeof(uint16_t);
      hspi->TxXferCount--;

#if (USE_SPI_CRC != 0U)
      /* Enable CRC Transmission */
      if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE))
      {
        SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
      }
#endif /* USE_SPI_CRC */

    }
    while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U))
    {
      /* Check TXE flag */
      if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U))
      {
        hspi->Instance->DR = *((const uint16_t *)hspi->pTxBuffPtr);
        hspi->pTxBuffPtr += sizeof(uint16_t);
        hspi->TxXferCount--;
        /* Next Data is a reception (Rx). Tx not allowed */
        txallowed = 0U;

#if (USE_SPI_CRC != 0U)
        /* Enable CRC Transmission */
        if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE))
        {
          SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
        }
#endif /* USE_SPI_CRC */
      }

      /* Check RXNE flag */
      if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U))
      {
        *((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;
        hspi->pRxBuffPtr += sizeof(uint16_t);
        hspi->RxXferCount--;
        /* Next Data is a Transmission (Tx). Tx is allowed */
        txallowed = 1U;
      }
      if (((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY))
      {
        hspi->State = HAL_SPI_STATE_READY;
        __HAL_UNLOCK(hspi);
        return HAL_TIMEOUT;
      }
    }
  }
  /* Transmit and Receive data in 8 Bit mode */
  else
  {
    if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
    {
      *((__IO uint8_t *)&hspi->Instance->DR) = *((const uint8_t *)hspi->pTxBuffPtr);
      hspi->pTxBuffPtr += sizeof(uint8_t);
      hspi->TxXferCount--;

#if (USE_SPI_CRC != 0U)
      /* Enable CRC Transmission */
      if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE))
      {
        SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
      }
#endif /* USE_SPI_CRC */
    }
    while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U))
    {
      /* Check TXE flag */
      if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U))
      {
        *(__IO uint8_t *)&hspi->Instance->DR = *((const uint8_t *)hspi->pTxBuffPtr);
        hspi->pTxBuffPtr++;
        hspi->TxXferCount--;
        /* Next Data is a reception (Rx). Tx not allowed */
        txallowed = 0U;

#if (USE_SPI_CRC != 0U)
        /* Enable CRC Transmission */
        if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE))
        {
          SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
        }
#endif /* USE_SPI_CRC */
      }

      /* Wait until RXNE flag is reset */
      if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U))
      {
        (*(uint8_t *)hspi->pRxBuffPtr) = hspi->Instance->DR;
        hspi->pRxBuffPtr++;
        hspi->RxXferCount--;
        /* Next Data is a Transmission (Tx). Tx is allowed */
        txallowed = 1U;
      }
      if ((((HAL_GetTick() - tickstart) >=  Timeout) && ((Timeout != HAL_MAX_DELAY))) || (Timeout == 0U))
      {
        hspi->State = HAL_SPI_STATE_READY;
        __HAL_UNLOCK(hspi);
        return HAL_TIMEOUT;
      }
    }
  }

#if (USE_SPI_CRC != 0U)
  /* Read CRC from DR to close CRC calculation process */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    /* Wait until TXE flag */
    if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK)
    {
      /* Error on the CRC reception */
      SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);
      hspi->State = HAL_SPI_STATE_READY;
      __HAL_UNLOCK(hspi);
      return HAL_TIMEOUT;
    }
    /* Read CRC */
    tmpreg = READ_REG(hspi->Instance->DR);
    /* To avoid GCC warning */
    UNUSED(tmpreg);
  }

  /* Check if CRC error occurred */
  if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_CRCERR) != RESET)
  {
    /* Check if CRC error is valid or not (workaround to be applied or not) */
    if (SPI_ISCRCErrorValid(hspi) == SPI_VALID_CRC_ERROR)
    {
      SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);

      /* Reset CRC Calculation */
      SPI_RESET_CRC(hspi);
      __HAL_UNLOCK(hspi);
      return HAL_ERROR;
    }
    else
    {
      __HAL_SPI_CLEAR_CRCERRFLAG(hspi);
    }
  }
#endif /* USE_SPI_CRC */

  /* Check the end of the transaction */
  if (SPI_EndRxTxTransaction(hspi, Timeout, tickstart) != HAL_OK)
  {
    hspi->ErrorCode = HAL_SPI_ERROR_FLAG;
    __HAL_UNLOCK(hspi);
    return HAL_ERROR;
  }

  /* Clear overrun flag in 2 Lines communication mode because received is not read */
  if (hspi->Init.Direction == SPI_DIRECTION_2LINES)
  {
    __HAL_SPI_CLEAR_OVRFLAG(hspi);
  }


  hspi->State = HAL_SPI_STATE_READY;
  /* Unlock the process */
  __HAL_UNLOCK(hspi);

  if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
  {
    return HAL_ERROR;
  }
  else
  {
    return HAL_OK;
  }
}

相关推荐
Doraemomo1 小时前
IMX6ULL裸机开发——PWM
arm开发·嵌入式硬件·imx6ull
老当益壮梁奶奶2 小时前
ARM汇编学习笔记(五):从按键轮询到 GIC 中断控制器(ARM Cortex-A7 )
arm开发·笔记·单片机·嵌入式硬件
WWJA王文举2 小时前
Bootloader、App 和升级分区之间是什么关系?
stm32·freertos·嵌入式开发·在线升级
西城微科方案开发3 小时前
基于SIC8F0535芯片的红外额温枪开发方案
单片机·嵌入式硬件
LCG元3 小时前
STM32F103 NTC 热敏电阻测温实战:分压网络设计、Steinhart-Hart 标定与 ADC 采样优化实测
stm32·单片机·嵌入式硬件
M78佐菲3 小时前
ARM学习笔记(五)
linux·arm开发·笔记·嵌入式硬件·学习
恒锐丰科技林技术员4 小时前
EG2184 半桥栅极驱动芯片详解:高压半桥电路的性价比之选
经验分享·嵌入式硬件·硬件工程
_Ningye4 小时前
通用异步收发器 UART
单片机
威嵌神州4 小时前
复旦微 FMQL100TAI 开发 8 问:仿真器、系统、接口高频问题解答
人工智能·嵌入式硬件·fpga开发