
i2s协议
嗜血综合征-pn10
2023年2月20日发(作者:世界十大珍稀动物)STM32---SPI通信的总结(库函数操作)
本文主要由7项内容介绍SPI并会在最后附上测试源码供参考:
的通信协议
通信初始化(以STM32为从机,LPC1114为主机介绍)
的读写函数
的中断配置
的SMA操作
6.测试源码
7.易出现的问题及原因和解决方法
一、SPI的通信协议
SPI(SerialPeripheralInterfac)是一种串行同步通讯协议,由一个主设备
和一个或多个从设备组成,主设备启动一个与从设备的同步通讯,从而完成数据的
交换。SPI接口一般由4根线组成,CS片选信号(有的单片机上也称为NSS),
SCLK时钟信号线,MISO数据线(主机输入从机输出),MOSI数据线(主机输
出从机输入),CS决定了唯一的与主设备通信的从设备,如没有CS信号,则
只能存在一个从设备,主设备通过产生移位时钟信号来发起通讯。通讯时主机的数
据由MISO输入,由
MOSI输出,输入的数据在时钟的上升或下降沿被采样,输出数据在紧接着的下降
或上升沿被发出(具体由SPI的时钟相位和极性的设置而决
定)
二、以STM32为例介绍SPI通信
32f103带有3个SPI模块其特性如下:
2221SPI特征
■3线全双工冋步棲输
•帯或不帯第二根双向数按线的职线鴨工同步性输
•8或16位传输帧格式选提
■主或从操种
•支持多主模式
•8个丄模式波特率预分频系数(最大为fpcix/2)
•从摸戒频最大为fpcLx/2)
•主橈式和从模式的快連通缶:摄大SPI速度达到18MHz
•卜:模式和从模式卜均可以由牧件或硬什进行NSS管理]I/从操作摸式的动态改变
•可编程的吋钟极性和相位
•可编程的数据顺斥・MSB在刑或LSB在前
•可触竝中凝的专用笈送和接收标蛊
•SPIS线忙状态様志
•龙持可靠通陆的硬fICRC
2SPI初始化
初始化SPI主要是对SPI要使用到的引脚以及SPI通信协议中时钟相位
和极性进行设置,其实STM32的工程师已经帮我们做好了这写工作,调
用库函数,根据自己的需要来修改其中的参量来完成自己的配置即可,
主要的配置是如下几项:
引脚的配置
SPI1的SCLK,MISO,MOSI分别是PA5,PA6,PA7弓I脚,这几个弓I脚的模
式都配置成GPIO_Mode_AF_PP复用推挽输出(关于GPIO的8种工作模式如不清
楚请自己百度,在此不解释),如果是单主单从,CS引脚可以不配置,都设置成软
件模式即可。
/*ConfigureSPI1pins:SCK,MISOandMOSI*/
GP10_InitStructure=GPI0_P^n_5IGPI0_Pin_6IGPIO_Pin_7;
GPIo2IrLitstructure-GPIOZMode=GPIO_Mode_AF_PP?
GPIO^InitS七匸口^Speed=GPIO_Speed_50MHz;
GPIo2Init(GPIOA
r
&GPIOTnitStructureT;
/*ConfigureI/OforFlashChipselect
GPIO__Pin=GPIO_Pin_4;
GPIO^InitStr口匚tuie=GP10_Mode_AF_PP;/7片选
GPIO_Init(GPIOA
r
&GPIO_);
通信参数的设置
SPI__Directicn=SPI_Direction_2Lines_FullDuplex;//全双工通f言
SPI__Mode=SPI_Mode_S1ave;//做从机
SPI_Iriitstructure-spi_DataSize=SPI_DataSizE_&b;//Sffl
SM^L=SEXCPOLHigh;77空闲时刻为高电平SPl'^CPHA=
SPrcPHA^2Edge;//数据住芻2个跳边沿被采集//////////SPI^~NSS=SPI_NSS_Soft;“C!
吕引廊为软件横我
SPI_lnitStructure
k
SPI_SaudRatePrescaler=SPI_BaudRatePrescaler8;"8分频
SPI^InitStructure
b
SPl2FirStBit=SPl_FirstBLtJiSB;"先传高字节
SPI__CRCPolyn0mial=7;SPI^Init(SPI1,iSPI_liiitSt工ucture);
_Direction_2Lines_FullDuplex把SPI设置成全双工通信;
2.在SPI_Mode里设置你的模式(主机或者从机),
_DataSize是来设置数据传输的帧格式的SPI_DataSize_8l是指8
位数据帧格式,也可以设置为SPI_DataSize_16即卩16位帧格式
_CPOL和SPI_CPHA是两个很重要的参数,是设置SPI通信时
钟的极性和相位的,一共有四种模式
CPOLCPOH
mode0
00
mode1
01
mode2
10
mode3
11
在库函数中CPOL有两个值SPI_CPOL_High(=1)和SPI_CPOL_Low(=0).
CPHA有两个值SPI_CPHA_1Edge(=0)禾口SPI_CPHA_2Edge(=1)
CPOL表示时钟在空闲状态的极性是高电平还是低电平,而CPHA则表示
数据是在什么时刻被采样的,手册中如下:
&1
CPOL:时钟极性
0:空闲状态时,SCK保持低也、佗
1:它闲状态时.SCK保持高电平・
注意=当通信正在进行的時候,不能修改该也・注意:Ps模式
下不梗用.
位0
CPHA:时钟相位
0:数据采样从第•个时钟边沿开始:
1:散据采样从第二个时种边沿开始匚
注意;当通信正在进行的肘候,不能條改该位.注意:Fs模
式卜•不使用.
我的程序中主、从机的这两位设置的相同都是设置成1,即空闲时时钟是高电平,数据
再第二个时钟沿被采样,实验显示数据收发都正常。
(要特别注意极性和相位的设置否则,数据传输会出现错位的现象)一般主从机的
这两个位要设置的一样,但是网上也有人说不能设置成一样的,在后文中我对主从机
极性和相位的配置的16种情况都做了测试,结果见下文。
下图很好的描述了4种模式下的时序状况
射1
引用网友的一句话:
“SPI主模块和与之通信的外设备时钟相位和极性应该一致。个人理解这句话有2层意思:其一,
主设备SPI时钟和极性的配置应该由外设的从设备来决定;其二,二者的配置应该保持一致,即主设备的SDO同从设备
的SDO配置一致,主设备的SDI同从设备的SDI配置一致。因为主从设备是在SCLK的控制下,同时发送和接收数据,并
通过2个双向移位寄存器来交换数据。”
_BaudRatePrescaler波特率的设置
这在主机模式中,这一位的设置直接决定了通信的传输速率,而从机的设
置不会影响数据传输的速率,手册中有这样一句话:
2SPI从模式
在从配置里.SCK引脚用于接收到从主设备来的串厅吋钟。SPLCR1寄%耕屮酮[2:0]的世押不影响数据传输
速率*
_FirstBit这一位是设置首先传输的高字节还是低字节
SPI_FirstBit_MSB是先传输高字节,SPI_FirstBit_LSB是先传输低字节
注意在初始化函数里还有两项重要的内容就是在初始化之前先使能SPI的
时钟和在初始化配置完成后使能SPI。
/*EnableSPI1andGPIOclocks*/
RCC_APB2Periph匚:Lockand(Rrxr—TkPBZPmriph—EPi::!.|RCC_APB2Pariph_GpIOA
7
ENABLE);
(........••初始化配置........)
/*EnableSPI1*/SPI_Cmd(SPI1/ENABLE);|
三、SPI的读写函数
SPI有一个16位的数据寄存器SPI_DR,它对应两个缓冲区,1个发送缓冲区,1个接
收缓冲区,当在控制寄存器里SPI_CR1里对DFF位设置数据帧格式为8位时,发送
和接收只用到SPI_DR[7:0这8位,15-8位被强制为0,帧格式设置成16位时全用。
读写过程在手册中是这样描述的:
数据发送过程
数拥字被并行地肓入发送缓冲器。
当从设备收到时钟信号.井且在MOSI引脚匕出现第一个数据位时,发送过程开始*第个位被发送
出上.余下的位(对于8位数据帧格式,还冇了位;对于16位数据帧格式,还冇15位)被装进移
位寄存器。勺发迖缓肝器川的数掘传输到移位寄存器时.SPISP奇存黯里的TXE标志被设置。如
果设置了API-CR2寄存器上的TXEIE位,将会产牛中断。
数
对于接收方.肖数据接收完成时;
•移位寄存器中的数据传送到接收缓冲器、SPI_SR寄存器屮的RXNE标盅被设蜀。
•如果设置了SPI_CR2寄存器中的RXEIE位,则产生中断。
在最后一个采样时钟边沿后.RXNEfW被置T・榕位寄存器中接收到的数据字节被传送到接收缓
冲器*当读SPI_DR寄存器吋,SPI设备返回这个值。
读寄存器吋,RX忖Efd波清除。
简而言之,
发送时,可以通过检测SPI_SR中的TXE位,当数据寄存器里有数据时,
TXE位是0,当数据全部从数据寄存器的发送缓冲区传输到移位寄存器时
TXE位被置1,这时候可以再往数据寄存器里写入数据。可以通过
while(SPI_l2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET来检测。
SPI_l2S_GetFlagStatus(SPI1SPI_I2S_FLAG_TXE是库函数可以检测SPI的一些状态位。
接收时
可以通过检测SPI_SR中的RXNE位,当数据寄存器里有数据时,RXNE位是0,当数
据全部从数据寄存器的接收缓冲区传输到移位寄存器时RXNE位被置1,这时候可以从
数据寄存器里读出数据。可以通过
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET来;检
测。
源程序如
下,
SPI读写一个字节,读写一体
//SPI1的读写
a3SPl_ccnniuniCflition_sendByte(uSbyt&}
{-—
/*LoopwhileDRregisterinnotemplty*/
while(SPI_l2S_GetFlagStatus(SPI1,SPII2SFLAGTXE)==RESET);/*Sendbyte■through,theSPI1peripheral*/
SPI_l2S_SendData(SPI1
7
byte);
/*Waittoreceiveabyte*/
Whil&(SPI_l2S_G&tFlagEtatlls(SPIlj,SPI_I2S_FLAG_RXNE)==FLESET);Returnthebyt?rsadfromtheSPIbus*/
returnSPII2SReceiveData(SPI1);
当能成功发送和接收一个字节时,发送数组数据就变的简单了,只需要一个for循环,
和指针变量的递增即可。以下仅为参考:
(有一点特别注意,从机数据传输时要依赖主机的时钟,所以主机在接收从机发送的数
据时要往从机发送哑巴字节,这个字节可以自己定义0xff,0xfe等什么字节都可以)
读写分开的函数:
/*Description:spil通信发送数据*/
voidSPI_Ecah_Buffer_Send(u8*pBuffer,u16NumByteToRead)
{
for(inti=0;i SPI_Conmunication_SendByte(*pBuffer); pBuffer++; } } /*Description:spil通信接收收据*/ voidSPI_Buffer_Receive(u8*pBuffer,u16NumByteToRead) { while(NumByteToRead--)/*whilethereisdatatoberead*/ { /*ReadabytefromtheFLASH*/ *pBuffer=SPI_Conmunication_SendByte(Dummy_Byte); /*Pointtothenextlocationwherethebytereadwillbesaved*/ pBuffer++; } } 读写一体的函数 /*Description:spi1通信发送接收读写数据*/ voidSPI_Ecah_Buffer_Send(u8*str,u8*pBuffer,u16NumByteToRead) { for(inti=0;i { *str=SPI_Conmunication_SendByte(*pBuffer); pBuffer++; str++; } } 四、SPI的中断配置 在SPI的SPI_CR2中可以配置,STM32的SPI的通信一共有8个中断其中最常用的 是如下4个。 TXEIE:发送缓冲区空中断使能在发送过程中,数据全部从数据寄存器的发送缓冲区 传输到移位寄存器时 TXE位被置1这时如果使能了TXEIE就会触发发送完成的中断请求。在中断服务函 数里可以做你想做的事情,也可以用一个标志位,在外面完成相应的操作。 (使用中断时要特别注意,及时的清除中断标志,为下一次能够触发中断做准备。) RXNEIE:接收缓冲区非空中断使能 接收同发送。 TXDMAEN:发送缓冲区DMA使能 RXDMAEN:接收缓冲区DMA使能 位7TXEIE:发送缓冲区空屮断使能 0:集止TXE中断; 1:允许TXE中斯,当TXE标总耗位时产生中断请求. 注意:不要同时设^TXEIE和TXDMAEN, 手册中有这样一句话,“不能同时设置TXEIE和TXDMAEN”这一点要特别注意。 也就是说如果你在SPI的通信中不用DMA则使能TXEIE的中断,不使能TXDMAEN 的中断,如果在SPI中使用DMA传输,则禁能TXEIE的中断,只使能TXDMAEN的 中断。 五、SPI的DMA操作 DMA(DirectMemoryAccess)直接内存存取,直接存储器存取用来提供在外设 和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU任何干 预,通过DMA数据可以快速地移动。使用DMA最大的特点就是数据传输 不经过CPU这就节省了CPU的资源,让CPU能有更多的时间来做其他的事情。 SPI的DMA操作,就是在SPI->TXE为1时,会向对应的DMA通道发出请求, DMA通道会发出应答信号,SPI收到应答信号后撤销请求信号,DMA撤销应答信号, 并把内存值装入SPI_DR的发送缓区,SPI的传送开始。 DMA的初始化 voidDMATXInit(void) { RCC_AHBPeriphclockQnd(RCC_AHEPeriph__DMAl r ENABLE)fDMA_InitTypeD®fDMA__InitStruetuz:总; Ju32)SPll_DR_Addr; Me^toryBas-eAddr=(u32}DMATx; DIFi=DIR_?eriphera1DST;//(DMATx—>SPIi_DR_Addr Buffersize=EpiTXEise;"发送数据的个数 Peripheryline=DMA_PeripheralInc_Disable;"外说地址奇存器不变Memjorylnc =DMA_Main0ryIncEnable;和内存地址奇存器递增PeripheralDataSize= DKA_PeripheralDataSize_Byte;"夕卜设数捋竞度为日位MemoryDataSise= DMA_?etlph£ralDataSizeEytm; Mode=DHA_Mocie_yonnal;"工作衽正世缓存樓式下 Priarity=DMA_Pri□rity_High; M2M=EMAM2MDisable;禁止口皿矗内存到内存的传输 DMA_ITConfig(DMAl_Channel3 T DWA_IT_TC R EMABLE);/7幵启DMA传締左成申断 CUfED31aCOUJiter—DMAGetCur?iter(DMA1Channel3); DMA_Cmd!(DMAl_Chann©137ENABLE); DMA_D«Init(DMAl_Channel3); DMAinitstructiire,DMA^PeriphsralBasaAddf DMA" DMA DMA DMA DMA DMA DMA] DMA_InitStructure・DMADMA_InitStruet口r吕・DMA DMA__. DMAInit(DMAlChannels r ADMAInitStructure); InitStructure・DMA. InitStruct□匕吕「DMA Initstrueture・DMA InitStructure・DMA InitStr口匕匕口工 色.DMAInitStructure ・DMAInitStmetLire, DMA DMA_PeripheralBaseAdd是值外设数据的地址,用SPI1故DMA外设地址 对应的是SPI1_DR_Addr, DMA_MemoryBaseAddr是内存地址,它的值可以使,你要发送的数据所存放的数组 的名,因为数组名代表的是数组数据存放的首地址,在SPI-DMA 的发送中可以理解为把DMATX[]数组里的数据传送到SPI1_DR_Addr DMA_DIR是指数据传输的方向,其值发送时其值为 DMA_DIR_PeripheralDST即外设是目的地,方向是DMATX—>SPI1_DR_Addr, 在接受收时其值为DMA_DIR_PeripheralSRC,即外设是数据的来源,传输方向是 SPI1_DR_Addr—>用户指定的数据存储数组。 DMA_BufferSize用来设置传输数据的个数,在STM32的DMA中其值的范围是0— 65536. DMA_Mode指DMA的传输模式DMA_Mode_Normal为正常工作模式 DMA_Mode_Circular是循环工作模式,这里对循环模式的解释我认为有位网友解释的 很不错如下: “循环的意思是指DMA的传输数量计数器会重置初值,由于DMA每传一个数据, 传输数量计数器减一,只有在传输数量计数器的值不为零时,才会响应请求。在循环 模式下,当传输计数器的值减为0后,会重新装载; 而内存(缓存)地址则不管循环非循环模式,都会在每次传输完成后重置为基地址。所 以,如果我们把DMA设置会正常模式,那么在下次传输前,只需对DMA的传输数量 计数器重新写入就行。循环模式一般用于数据更新,比如ADC采用需要不停更新数 据。” 在初始化完成之后要开启DMA的中断,在我的程序中开启的是DMA传输完成中 断。 DMA_ITConfig(DMAl_Channel3,DMA_ZT_TC z ENABLE);〃开启DMA传输完成中断= DMA^GetCurrDataCounter(DMA1ChanneL3); DMA_Cmd.(DMAl_Channe137ENABLE);//使自玄DMA1通薩13 DMA传输有3个中断标志位,常用的是传输完成的中断。如下: 9.3.6中断 可以在DIVIA传输过半、传输完成和传输错误时产生屮断。为应用f器的不同位来打开这 些中断◎ 表39DMA中断请求 中斷事件爭件标盘位便能控制位 传输过半 HTIFHTIE 传输立成 TCIF TCIE 传输错逞 TEIFTEIE 这样在传输完设定的数据个数之后就会触发传输完成的中断,用户可以再中断服务函 数中,进行相应的操作,有一点特别注意,就是要及时清除中断标志位,为下次能够 正常触发中断做准备。 voidDMAl_Channel2_IRQHandler(void)//当传输芫SpiRXSiEe个字节{一一_ SpiCommOn=1;//SP工通信标志位 RXCurrDataCounter=DMA_GetCurrDataCounter(DMA1Channel2};DMA ClearlTPendingBit(Dr4Ai_IT_TC2);//Y青除中断标志位 在我的中断服务函数中有一个标志位SpiCommon被置1后再中断之外进行其他的处 理,同时调用DMA_ClearlTPendingBit(DMA1」T_TC2)来及时清除中断标志。 在进行DMA的数据传输时要先禁能DMA的通道,重置传输数据个数的值,数据的存 储位置等,再使能DMA的通道,等待DMA的传输完成。 我的操作时这样的,先往DMATX[]里写入相应的数据,然后如下 SpiTXSize=53; DMA^Cmd(DMAl_Channel3 1 DISABLE}; DMATXInit(} DMZ^Cmd(DMAl_Channel3,ENABLE); 这样可能有一点不好的地方,因为只改变了SpiTXSize的值,却又重新执行了 DMATXInit()函数,可能此处能够再改善一下。 六测试中出现的问题及原因和解决方法 示波器观察主机能够产生正确的时钟,主机输出引脚也能产生正确的数据,但 是从机不能接受数据。 可能原因: 1.从机的接收中断配置不正确,或者没有打开相应的中断。 2.在从机中TXEIE的中断和TXDMAEN的中断都被使能,手册中说,这两个中断只 能使能1个. 从机能接收数据,但是接收的数据乱码 可能原因: 1.主从机的时钟相位和极性的配置导致的,关于这一点想做一下说明,网上有人说, 主从机时钟的相位和极性要配置的一样,也有人说不能配置的一样,而我对于主从 机的相位和极性的16种组合情况全做了试验,结果如下:(主机LPC1114的 SPI1从机STM32的SPI1) ("表示能正常通信) 主 从通信 CPOLCPHACPOLCPHA 0000 V 0001乱码 (左移1位) 0010 乱码 0011 乱码 0100 乱码 0101乱码 0110 V 0111乱码 1000V 1001V 1010 乱码 1011 乱码 1100 乱码 1101 乱码 1110 V 1111 V (当然可能上述的结果也跟测试环境有关,当对其有所怀疑时,读者不妨 自己实验看一下。) 2.乱码的第二个原因可能是两个设备没有共地而造成的,在出现问题时一定要先检查 一下硬件的连接是否正确,是否有虚焊接触不好的地方而导致通讯不正常。 从机能接收数据,但接收的数据不全,又丢字节的现象发生。 可能原因: 1.如果是通过串口打印来观察接收数据,那要看一下数据中是否有0,结 合自己的串口函数分析一下,因为打印数组或者字符串时遇0会截止 2.看一下接收的数组中,其指针是否是递增的。 3.如果使用了CS片选信号,看一下主机发出的数据是否都在 围内。 CS拉低的范