
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 的地址计算
#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 —— 寄存器的结构体镜像
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 还有什么
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),做与具体芯片相关的底层配置:
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 上):
#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 等位都不能修改。
因此写配置前必须先禁用设备:
// HAL_SPI_Init 开头
__HAL_SPI_DISABLE(hspi);
#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:
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)
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步)
① 定义局部变量
uint16_t initial_TxXferCount;
uint32_t tmp_mode;
HAL_SPI_StateTypeDef tmp_state;
uint32_t tickstart;
uint32_t txallowed = 1U; // 关键:控制收发交替的标志
② 记录 tickstart(超时基准)、备份状态
tickstart = HAL_GetTick();
tmp_state = hspi->State;
tmp_mode = hspi->Init.Mode;
initial_TxXferCount = Size;
③ 状态与参数检查
// 非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;
④ 加锁、置忙、装载事务信息
__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 循环(收发交替)
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; }
}
⑧ 收尾:结束事务、清溢出、恢复状态
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 时钟时序完成一次完整收发。
数据通路示意:
发送方向:
用户缓冲区 → [拷贝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() 做两件等待:
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

只写是读写逻辑的子集,只列不同点:
① 接收相关全部置空:
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:
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 完全由主机掌控:
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 做了微调,形成变体:
| 片选行为 | 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库源码
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;
}
}
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;
}
}
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;
}
}


