news 2026/4/25 16:05:56

基于STM32F103C8T6标准库和寄存器的SPI外设全速连续收发解决方案(附赠硬件SPI不同场景的通信函数)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于STM32F103C8T6标准库和寄存器的SPI外设全速连续收发解决方案(附赠硬件SPI不同场景的通信函数)

纵观全网,我几乎没找到任何关于SPI硬件全速连续收发的时序操作,但是个人学完SPI外设后又觉得这个全速收发还是要搞一搞的,于是就有了这篇文章,本人水平有限,如果有不对的地方还请指正呀,谢谢。

首先明确:STM32的SPI外设应用难度和UART差不多,这是因为它和UART一样。基础SPI协议本身是负责数据收发,没有像IIC一样过于复杂的机制,所以不需要担心阻塞等待标志位时出现硬件错误从而死循环的情况。基础SPI外设使用只需要注意三个重要标志位就可以:TXE发送寄存器空,这个时候意味着可以写入写一个要发送的字节;RXNE接受寄存器非空,意味着可以读取接受寄存器的数据了,注意需要及时读取,否则数据会被下次通信覆盖;BSY忙标志位,该标志位表示硬件是否彻底活动完毕,推荐BSY置0后再拉片选以保证通信完整性。

接下来进入正文:我对全速SPI连续双向收发的时序理解,以下是STM32手册的SPI全速时序示意图,搞明白这个就能写一个简单的连续收发操作了。

这里最麻烦的就是TXE和RXNE的把控,所以主要看TXE和RXNE的出现时机,以及发送寄存器,接收寄存器和负责实际通信的数据移位寄存器操作就可以了。

由于最开始的时候移位寄存器是空的,所以首次写DR寄存器时DR寄存器的数据会瞬间进入移位寄存器,TXE标志瞬间触发,此时为了连续全速就要赶紧将下一个字节写入DR寄存器了,然后等待移位寄存器处理完毕后上一个字节的RXNE就来了,此时读取的是上一个字节换出来的数据,所以这里会有一个错位,这也是连续收发难受的根源所在。

手册并没有给出发送完毕后的时序,所以这里我将结合自己的理解去描述这个过程:在发送最后一个字节时,最后一个字节位于DR寄存器中,而此时移位寄存器内部还有数据没有操作完毕,这个没操作完的数据正是倒数第二个要发送的字节,等数据操作完毕之后将会出现RXNE,由于数据对应倒数第二个发送字节,所以这个RXNE也是倒数第二个你要接受的字节,然后最后一个字节进入移位寄存器,触发TXE。但是这个TXE没啥用了,因为数据发完了,而且过程是你发最后一个字节的时候,接受的是上一次的字节,所以最后发完最后一个字节要收两次数据才算完整通信。这也是收发错位带来的必然结果吧,毕竟开始的时候连发两次,通信最后接收的时候连收两次,相当之合理了哈哈哈。

总结一下就是:刚开始由于移位寄存器是空的,所以要一口气送两个数据进去,产生收发错位,这个收发错位也导致发第n个数据时要接受的是第n-1个数据,而发完第n个数据后要连续收两次才能把第n个数据也拿到手,所以这就是整个SPI连续字节收发的总逻辑,接下来我会这样实现操作:

void ST_SPI1_PwrTransAndRece(uint8_t *tarray, uint8_t *rarray, uint16_t size){ uint16_t i; SPI1->DR = tarray[0];//开始发送 while(!(SPI1->SR & SPI_SR_TXE));//等待发送寄存器空 for(i = 1;i < size; i++){ SPI1->DR = tarray[i];//开始发送 while(!(SPI1->SR & SPI_SR_RXNE));//等待上一个字节的接受寄存器非空 rarray[i-1] = SPI1->DR;//接受字节 while(!(SPI1->SR & SPI_SR_TXE));//等待本次字节发送寄存器空 } while(!(SPI1->SR & SPI_SR_RXNE));//等上一个RXNE rarray[i-1] = SPI1->DR;//接受字节 while(SPI1->SR & SPI_SR_BSY);//等待BSY标志完成 }

它是先发送数据,然后等待TXE后赶紧发下一个数据,发完下一个数据之后就可以等待上一个字节的接收了,所以要等RXNE,收完之后本次TXE也就被触发了,然后下一个数据写,收本次数据,等,这个可以看一下手册的时序图,然后你会发现上述“发本次收上次”的操作是可以循环的,唯一注意的是开始时连发两次,结束前连收两次,所以循环体要做好衔接,于是就有了我的代码,它是“写本次数据,读上一次数据”循环,然后开头多发一次,结尾多收一次,实现整体字节连续收发,最后等待硬件彻底不忙了之后函数结束,自行处理片选引脚。

但是只写这一个全速连续收发显然太少了,有时候还用不上这个,所以我也同样实现了只发送函数,单个字节通信函数,只接收函数以及本次的双向通信接收函数还有硬件初始化操作,以形成完整的基础硬件SPI收发体系。

首先是硬件初始化函数:

void ST_SPI1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;//SPI1的MOSI和SCLK GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//SPI1的MISO GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA,&GPIO_InitStructure); SPI_StructInit(&SPI_InitStructure); SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_Init(SPI1,&SPI_InitStructure); SPI_Cmd(SPI1,ENABLE); }

然后是单个字节通信函数:

uint8_t ST_SPI1_Change1Byte(uint8_t byte){ SPI1->DR = byte;//发送字节 while(SPI1->SR & SPI_SR_BSY);//等待BSY标志完成 return SPI1->DR;//返回交换的字节 }

这个同时也是大多数人写的不连续字节通信,即等待一个字节完全操作完毕后才进行下一次操作。

只发送函数:

void ST_SPI1_OnlyTransmit(uint8_t *array, uint16_t size){ uint16_t i; for(i = 0;i < size;i++){ SPI1->DR = array[i];//发字节 while(!(SPI1->SR & SPI_SR_TXE));//等待发送寄存器空 } while(SPI1->SR & SPI_SR_BSY);//等待BSY标志完成 i = SPI1->DR;//清除发送之后残留的RXNE,防止其他地方读到垃圾数据 }

这里最后一句话是我实测的时候发现全双工模式下只发送某些命令而不读取接收寄存器的话STM32还是会有RXNE标志,会污染读取操作,所以加了一句读取操作清理垃圾数据

只接收函数:

void ST_SPI1_OnlyReceive(uint8_t *array, uint16_t size){ uint8_t i; SPI1->DR = 0xFF;//抛砖引玉 while(!(SPI1->SR & SPI_SR_TXE));//等待发送寄存器空 for(i = 0;i < size - 1; i++){ SPI1->DR = 0xFF;//抛砖引玉 while(!(SPI1->SR & SPI_SR_RXNE));//等上一个RXNE array[i] = SPI1->DR;//接受字节 while(!(SPI1->SR & SPI_SR_TXE));//等待本次字节发送寄存器空 } while(!(SPI1->SR & SPI_SR_RXNE));//等上一个RXNE array[i] = SPI1->DR;//接受字节 while(SPI1->SR & SPI_SR_BSY);//等待BSY标志完成 }

这个只接收函数因为是无效字节换取数据,所以采用的也是全速连续字节流通信,但是这里发送全是0xFF,毕竟复用双向全速通信函数的话,你实际使用起来也不愿意额外开一个全是0xFF的数组来恶心自己吧哈哈哈。

再就是全速双向收发函数了:

void ST_SPI1_PwrTransAndRece(uint8_t *tarray, uint8_t *rarray, uint16_t size){ uint16_t i; SPI1->DR = tarray[0];//开始发送 while(!(SPI1->SR & SPI_SR_TXE));//等待发送寄存器空 for(i = 1;i < size; i++){ SPI1->DR = tarray[i];//开始发送 while(!(SPI1->SR & SPI_SR_RXNE));//等待上一个字节的接受寄存器非空 rarray[i-1] = SPI1->DR;//接受字节 while(!(SPI1->SR & SPI_SR_TXE));//等待本次字节发送寄存器空 } while(!(SPI1->SR & SPI_SR_RXNE));//等上一个RXNE rarray[i-1] = SPI1->DR;//接受字节 while(SPI1->SR & SPI_SR_BSY);//等待BSY标志完成 }

好了,这就是我这篇文章的全部内容了,重点是全速双向连续收发操作,附赠了常规基础SPI硬件操作通信,如果喜欢的话还请点点赞,点点关注呀,谢谢大家的阅读和支持,一个人写文章真的很不容易~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 16:05:18

终极指南:如何用airPLS算法轻松实现智能基线校正

终极指南&#xff1a;如何用airPLS算法轻松实现智能基线校正 【免费下载链接】airPLS baseline correction using adaptive iteratively reweighted Penalized Least Squares 项目地址: https://gitcode.com/gh_mirrors/ai/airPLS 你是不是经常在光谱分析、色谱检测或生…

作者头像 李华
网站建设 2026/4/25 16:04:18

NCRE二级Java考试指定IDE:NetBeans 2007教育版从下载到Hello World保姆级教程

NCRE二级Java考试指定IDE&#xff1a;NetBeans 2007教育版从下载到Hello World保姆级教程 对于准备参加NCRE二级Java考试的考生来说&#xff0c;熟悉考试指定的开发环境是成功的第一步。本文将带你从零开始&#xff0c;一步步完成NetBeans 2007教育版的下载、安装配置到编写第一…

作者头像 李华
网站建设 2026/4/25 16:02:36

HashCheck Windows右键哈希校验终极指南:三步守护文件安全

HashCheck Windows右键哈希校验终极指南&#xff1a;三步守护文件安全 【免费下载链接】HashCheck HashCheck Shell Extension for Windows with added SHA2, SHA3, and multithreading; originally from code.kliu.org 项目地址: https://gitcode.com/gh_mirrors/ha/HashChe…

作者头像 李华
网站建设 2026/4/25 16:02:18

AI大模型学习指南:小白也能掌握的AI核心技能,收藏这份干货!

本文深入浅出地介绍了AI的概念、核心目标及四大研究领域&#xff0c;包括基础设施建设、算法研发、主要技术方向和行业解决方案。文章详细阐述了各领域代表公司及优质岗位&#xff0c;并特别针对算法岗位的学习路径进行了指导&#xff0c;帮助读者了解AI技术全貌&#xff0c;为…

作者头像 李华
网站建设 2026/4/25 16:00:02

AMD Hummingbird-XT: 面向消费端的高性能视频生成算法

AMD Hummingbird-XT: 面向消费端的高性能视频生成算法 原文作者&#xff1a;Takashi Isobe, He Cui, Mengmeng Ge, Dong Zhou, Dong Li, KuanTing Lin, Chandra Yang, Wickey Wang, Emad Barsoum. 引言 随着近些年扩散模型的出现与快速发展[1]&#xff0c;视频生成算法在分辨…

作者头像 李华