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 ------ 寄存器的结构体镜像
cpptypedef 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 还有什么
cpptypedef 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),做与具体芯片相关的底层配置:
cppvoid 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 // 0x0040SPE 位在 CR1 的位6:
0=禁止 SPI 设备,1=开启。
清 SPE=0 让 SPI1 停止工作(时钟仍开着),之后才能写入配置。
1.6 将配置信息设置到 SPI 接口(逐位解析,重点)
禁用后,HAL 用一条
WRITE_REG把用户参数按位拼进 CR1,再写 CR2:
cppWRITE_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)
cpphspi1.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步)
① 定义局部变量
cppuint16_t initial_TxXferCount; uint32_t tmp_mode; HAL_SPI_StateTypeDef tmp_state; uint32_t tickstart; uint32_t txallowed = 1U; // 关键:控制收发交替的标志② 记录 tickstart(超时基准)、备份状态
cpptickstart = 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 循环(收发交替)
cppwhile ((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; } }⑧ 收尾:结束事务、清溢出、恢复状态
cppif (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()做两件等待:
cppstatic 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
只写是读写逻辑的子集,只列不同点:
① 接收相关全部置空:
cpphspi->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:
cppwhile (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 完全由主机掌控:
- 硬件层:Master 每发一个字节,Slave 的移位寄存器就同步把数据"推"向 MISO,同时从 MOSI 收数据;
- 软件静默:即使只调 Receive,Slave 硬件也在自动发送(发 DR 缓冲的旧值或0),这个过程硬件自动闭环,无需软件干预;
- 核心任务:软件只需在 Master 传下一帧前,及时检查 RXNE 并把 DR 数据读走,防止被后续数据覆盖(Overrun)。
cpphspi->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库源码
cppHAL_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; } }
cppHAL_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; } }
cppHAL_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; } }



