详解DSP F28069的SPI模块,及代码讲解(适用于2803x、2805x、2806x 型号)
SPI特性:
The SPI module features include:
• SPISOMI: SPI slave-output/master-input pin
• SPISIMO: SPI slave-input/master-output pin
• SPISTE: SPI slave transmit-enable pin
• SPICLK: SPI serial-clock pin
(注意:如果不使用SPI模块,这四个引脚都可以作为GPIO。)
•两种操作模式:Master和Slave
•波特率:125种不同的可编程速率。可采用的最大波特率受限于SPI引脚上使用的I/O缓冲区的最大速度。
•数据字长:1 ~ 16位数据位
•四种时钟方案(由时钟极性和时钟相位位控制)包括:
– Falling edge without phase delay: SPICLK active-high. SPI transmits data on the falling edge of the SPICLK signal and receives data on the rising edge of the SPICLK signal.
– Falling edge with phase delay: SPICLK active-high. SPI transmits data one half-cycle ahead of the falling edge of the SPICLK signal and receives data on the falling edge of the SPICLK signal.
– Rising edge without phase delay: SPICLK inactive-low. SPI transmits data on the rising edge of the SPICLK signal and receives data on the falling edge of the SPICLK signal.
– Rising edge with phase delay: SPICLK inactive-low. SPI transmits data one half-cycle ahead of the rising edge of the SPICLK signal and receives data on the rising edge of the SPICLK signal.
•同时进行收发操作(可在软件中禁用发送功能)
•发送端和接收端操作通过中断驱动或轮询算法来完成
• 4-level transmit/receive FIFO
•延迟发送控制
• 3-wire SPI mode
• SPISTE inversion for digital audio interface receive mode on devices with two SPI modules
Block Diagram(方框图)
Configuring Device Pins
必须配置GPIO多路寄存器来将这个外设连接到设备引脚。一些IO功能是由独立于此外设的GPIO寄存器设置定义的。对于输入信号,应该通过设置适当GPxQSELn寄存器位到11b将GPIO输入限定设置为异步模式。内部拉出可以在GPyPUD寄存器中配置。有关GPIO mux和设置的更多详细信息,请参阅GPIO章节。
SPI Interrupts
SPI模块包含两条中断线:SPIINT/SPIRXINT和SPITXINT。当SPI在非fifo模式下工作时,所有可用的中断都被路由到一起以生成单个SPIINT中断。当采用FIFO模式时,可以同时生成SPIRXINT和SPITXINT。
-
SPIINT/SPIRXINT
当SPI在非fifo模式下工作时,产生的中断称为SPIINT。如果启用了FIFO增强功能,则中断称为SPIRXINT。这些中断在外围中断扩展(PIE)块中共享相同的中断向量。
在非fifo模式下,有两种情况可以触发中断:传输完成(INT_FLAG)或接收端溢出(OVERRUN_FLAG)。这两种情况共享相同的中断向量:SPIINT。
传输完成标志(INT_FLAG)表示SPI已经完成了最后一位的发送或接收,并且已经准备好服务。在设置此位的同时,接收到的字符被放置在接收缓冲区(SPIRXBUF)中。如果SPIINTENA位已设置,INT_FLAG将在SPIINT向量上生成中断。
接收超限标志(OVERRUN_FLAG)表示在从缓冲区读取前一个字符之前,发送或接收操作已经完成。如果OVERRUNINTENA位被设置并且OVERRUN_FLAG之前被清除,OVERRUN_FLAG将在SPIINT向量上生成一个中断。
在FIFO模式下,当当前接收FIFO状态(RXFFST)与接收FIFO中断级别(RXFFIL)匹配时,SPI可以中断CPU。如果RXFFST大于或等于RXFFIL,接收FIFO中断标志(RXFFINT)将被设置。如果设置了RXFFINT并且启用了接收FIFO中断(RXFFIENA = 1),将在PIE块中触发SPIRXINT。 -
SPITXINT
当SPI工作在非fifo模式时,SPITXINT中断不可用。在FIFO模式下,SPITXINT的行为类似于SPIRXINT。当当前transmit FIFO状态(TXFFST)与transmit FIFO中断级别(TXFFIL)匹配时,会生成SPITXINT。如果TXFFST小于或等于TXFFIL,则将设置传输FIFO中断标志(TXFFINT)。如果TXFFINT被设置并且在SPI模块(TXFFIENA = 1)中启用了传输FIFO中断,SPITXINT将在PIE块中被触发。
Master Mode
在主模式下(MASTER_SLAVE = 1), SPI为整个串行通信网络在spiklk引脚上提供串行时钟。数据在SPISIMO引脚上输出,并从SPISOMI引脚锁存。SPIBRR寄存器决定网络的发送和接收比特传输速率。SPIBRR可以选择125种不同的数据传输速率。
写入SPIDAT或SPITXBUF的数据MSB(最高有效位)首先在SPISIMO引脚上启动数据传输。同时,接收到的数据通过SPISOMI pin被转移到SPIDAT的LSB(最低有效位)。当所选的比特数传输完成后,接收到的数据被传输到SPIRXBUF(缓冲接收器)供CPU读取。数据以右对齐方式存储在SPIRXBUF。
当指定的数据位数通过SPIDAT移位后,会发生以下事件:
- SPIDAT内容被转移到SPIRXBUF。
- INT_FLAG位设置为1。
- 如果传输缓冲区SPITXBUF中有有效数据,会通过传输缓冲区已满标志 (BUFFULL_FLAG)来指示,有效数据被传输到SPIDAT并发送出去,在所有位都移出SPIDAT后,spickk将停止。
- 如果SPIINTENA位设置为1,则触发中断。
在典型的应用程序中,SPISTE引脚作为从SPI设备的芯片使能引脚。该引脚在向从机传输数据之前由主机驱动低电平,在传输完成后置高电平。
Slave Mode
在从模式下(MASTER_SLAVE = 0),数据在SPISOMI引脚上移出,在SPISIMO引脚上移进。SPICLK引脚用作串行移位时钟的输入,该时钟由外部网络主机提供。传输速率是由这个时钟定义的。spiclk输入频率不大于LSPCLK频率除以4。
当从网络主机接收spiclk信号的合适的边沿时,写入SPIDAT或SPITXBUF的数据就会被传输到网络中。写入SPITXBUF寄存器的数据将在要传输的字符的所有位移出后传输到SPIDAT寄存器SPIDAT。当要传输的字符的所有位都移出SPIDAT时,写入SPITXBUF寄存器的数据将被传输到SPIDAT寄存器。如果在写入SPITXBUF时当前没有传输字符,则数据将立即传输到SPIDAT。为了接收数据,SPI等待网络主机发送SPICLK信号,然后将SPISIMO引脚上的数据转移到SPIDAT。如果数据要由从机同时传输,而SPITXBUF之前没有加载,在spickk信号开始之前,则必须将数据写入SPITXBUF或SPIDAT。
当清除TALK位时,数据传输被禁用,输出线(SPISOMI)进入高阻抗状态。如果在传输处于活动状态时发生这种情况,则即使SPISOMI被迫进入高阻抗状态,当前字符也将完全传输。这确保SPI仍然能够正确地接收传入的数据。这个TALK位允许许多从设备在网络上捆绑在一起,但是一次只允许一个从设备驱动SPISOMI线。
SPISTE引脚作为从选择引脚。SPISTE引脚上的active-low信号允许从SPI将数据传输到串行数据线;非激活的高信号导致从SPI串行移位寄存器停止,其串行输出引脚进入高阻抗状态。这允许许多从设备在网络上捆绑在一起,尽管一次只能选择一个从设备。
Data Format
4位SPICHAR寄存器字段指定数据字符中的位数(1到16)。该信息指示状态控制逻辑计算接收或传输的比特数,以确定何时处理了一个完整的字符。
以下说明适用于小于16位的字符:
- 数据写入SPIDAT和SPITXBUF时必须左对齐。
- 从SPIRXBUF读回的数据是右对齐的。
- SPIRXBUF包含最近接收到的右对齐字符,加上之前传输中向左移动的任何位(如例12-1所示)。
Baud Rate Selection
SPI模块支持125种不同的波特率和4种不同的时钟方案。根据SPI时钟是在从模式还是主模式,spiclk引脚可以分别接收外部SPI时钟信号或提供SPI时钟信号。
- 在从模式下,SPI时钟从外部源的spiclk引脚上接收,并且不能大于LSPCLK频率除以4。
- 在主模式下,SPI时钟由SPI生成并输出在spiclk引脚上,它不能大于LSPCLK频率除以4。
- 波特率应配置为不超过GPIO开关的最大额定频率。GPIO开关的最大频率请参见设备数据手册
SPI Clocking Schemes
时钟极性选择位(CLKPOLARITY)和时钟相位选择位(CLK_PHASE)控制spiclk引脚上的四种不同的时钟方案。CLKPOLARITY选择时钟的活动边缘,上升或下降。CLK_PHASE选择时钟的半个周期延迟。四种不同的时钟方案如下:
• Falling Edge Without Delay. The SPI transmits data on the falling edge of the SPICLK and receives data on the rising edge of the SPICLK.
• Falling Edge With Delay. The SPI transmits data one half-cycle ahead of the falling edge of the SPICLK signal and receives data on the falling edge of the SPICLK signal.
• Rising Edge Without Delay. The SPI transmits data on the rising edge of the SPICLK signal and receives data on the falling edge of the SPICLK signal.
• Rising Edge With Delay. The SPI transmits data one half-cycle ahead of the rising edge of the SPICLK signal and receives data on the rising edge of the SPICLK signal.
只有当(SPIBRR+1)的结果为偶数时,才会保留SPICLK对称性。当(SPIBRR + 1)为奇数值且SPIBRR大于3时,SPICLK变得不对称。当CLKPOLARITY为0时,spiclk的低脉冲比高脉冲长一个LSPCLK周期,当CLKPOLARITY位设置为1时,SPICLK的高脉冲比低脉冲长1个LSPCLK周期,如图12-6所示。
SPI FIFO Description
下面的步骤解释了FIFO功能,并帮助编程SPI FIFO:
1、 Reset. 在复位时,SPI以标准SPI模式上电,FIFO功能被禁用。FIFO寄存器SPIFFTX, SPIFFRX和SPIFFCT保持不活动状态。
2、 Standard SPI. 标准的28x SPI模式将使用SPIINT/SPIRXINT作为中断源。
3、 Mode change. FIFO mode是通过在SPIFFTX寄存器中将SPIFFENA位设置为1来启用的。SPIRST可以在其操作的任何阶段重置FIFO模式。
4、 Active registers. 所有SPI寄存器和SPI FIFO寄存器SPIFFTX, SPIFFRX和SPIFFCT将被激活。
5、 Interrupts. FIFO模式有两个中断,一个用于发送FIFO SPITXINT和一个用于接收FIFO SPIRXINT。SPIRXINT是SPI FIFO接收、接收错误和接收的常用中断,还有FIFO溢出条件。标准SPI的发送和接收部分的单个SPIINT将被禁用,该中断将作为SPI接收FIFO中断服务。更多信息请参见12.2.3节
6、 Buffers. 发送和接收缓冲区各补充一个4字FIFO。标准SPI的单字传输缓冲区(SPITXBUF)作为FIFO和移位寄存器传输之间的转换缓冲区。只有在移位寄存器的最后一位移出之后,才会从传输FIFO加载单字传输缓冲区。
7、 Delayed transfer(延迟转移). FIFO中的传输字被传输到传输移位寄存器的速率是可编程的。SPIFFCT寄存器位(7−0)FFTXDLY7−FFTXDLY0定义字传输之间的延迟。延迟以SPI串行时钟周期数定义。8位寄存器可以定义0个spiclk周期的最小延迟和255个spiclk周期的最大延迟。零延迟,在连续模式SPI模块可以传输数据与FIFO字之间来回移动。使用255的时钟延迟,SPI模块可以以最大延迟模式传输数据,FIFO字在每个字之间以255个spiklk周期的延迟移出。可编程延迟有助于无缝连接到各种慢SPI外设,比如EEPROMs,ADC、DAC等等。
8、 FIFO status bits. 发送和接收fifo都有状态位TXFFST或RXFFST,它们定义了在任何时候fifo中可用的字数。当发送FIFO重置位(TXFIFO)和接收重置位(RXFIFO)设置为1时,将FIFO指针重置为零。一旦这些位被清除为零,fifo将从开始处恢复操作。
9、 Programmable interrupt levels. 发送和接收fifo都可以产生CPU中断。当传输FIFO状态位(TXFFST) 匹配(小于或等于)中断触发级别位(TXFFIL)时,就会生成传输中断(SPITXINT)。当接收FIFO状态位(RXFFST)匹配(大于或等于)中断触发级别RXFFIL时,产生接收中断(SPIRXINT)。这为SPI的发送和接收部分提供了可编程中断触发器。这些触发级别位的默认值分别为0x11111用于触发接收FIFO中断和0x00000用于触发发送FIFO中断。
复位时初始化
系统复位会强制SPI外设进入以下默认配置:
- SPI外设单元被配置为从模块(MASTER_SLAVE = 0)
- 传输功能被禁用(TALK = 0)
- 在输入端spiclk的信号下降沿,数据被锁存
- 字符长度设置为1位
- SPI中断被禁用
- SPIDAT中的数据被重置为0000h
配置SPI
为防止在初始化更改期间或由于初始化更改而发生不需要的和不可预见的事件,请清除
SPISWRESET位,然后在初始化完成后设置该位。当SPI保持在reset (SPISWRESET = 0)时,配置可以以任何顺序更改。下面的列表按逻辑顺序显示了SPI配置过程。然而,SPI寄存器可以用单个16位写入写入,因此除了SPISWRESET之外,不需要顺序。
-
步骤1: 清除SPI软件复位位(SPISWRESET)为0,强制SPI进入复位状态。
-
步骤2: 根据需要配置SPI:
- 选择主模式或从模式(MASTER_SLAVE)。
- 选择spiclk极性和相位(CLKPOLARITY和CLK_PHASE)。
- 设置所需的波特率SPIBRR。
- 设置SPI字符长度(SPICHAR)。
- 清除SPI标志(OVERRUN_FLAG, INT_FLAG)。
- 如果需要,启用SPISTE反转(STEINV)。
- 如果需要启用三线模式(TRIWIRE)。
- 如果使用FIFO功能:
- 启用FIFO功能(SPIFFENA)。
- 清除FIFO标志(TXFFINTCLR, RXFFOVFCLR, RXFFINTCLR)。
- Release transmit and receive FIFO resets (TXFIFO and RXFIFORESET).(释放发送和接收FIFO重置(TXFIFO和RXFIFORESET)。)
- 从复位中释放SPI FIFO通道(SPIRST)。
-
步骤3:如果使用中断:
- 在非fifo模式下,启用接收溢出和/或SPI中断(OVERRUNINTENA和SPIINTENA)。
- 在FIFO模式下,设置发送和接收中断级别(TXFFIL和RXFFIL),然后启用中断(TXFFIENA和RXFFIENA)。
-
步骤4:将SPISWRESET设置为1将SPI从复位状态释放。
(请勿在通信进行中更改SPI配置。)
数据传输实例
图12-9所示的定时图说明了两个设备之间使用5位字符长度的SPI数据传输,且spiclk是对称的。
重要的是要注意SPI寄存器只允许16位访问。
SPI模块介绍完毕,接下来使用代码来讲解。
代码讲解(配置为主机模式)
- SPI.h
/*
* SPI.h
*
* Created on: 2023年3月7日
* Author: chends
*/
#ifndef USER_INC_SPI_H_
#define USER_INC_SPI_H_
#include "DSP28x_Project.h"
void spiA_init(void);//spi初始化函数
Uint8 spiATxAndRx_T1R1(Uint8* dat);//spi发送一个byte数据,接着接收一个byte数据
Uint8 spiATxAndRx_T4R4(char* dat);//spi连续发送4个byte数据,然后连续接收4个byte数据。
//为什么是4个,因为FIFO的深度是4。
void spiATxAndRx_TnRn(char* dat, Uint8 count,Uint8 RxEnable);//接收和发送一定长度的数据,方法1
void spiATxAndRx2_TnRn(char* dat, Uint8 count,Uint8 RxEnable);//接收和发送一定长度的数据,方法2
#endif /* USER_INC_SPI_H_ */
- SPI.c
/*
* SPI.c
*
* Created on: 2023年3月7日
* Author: chends
*/
#include "SPI.h"
#include "Usart.h"
/*
*初始化函数
*有开启FIFO功能,没有打开中断(因为设置为主机,所以不需要中断),一次发送和接收的长度为8位
* 选择SPI环回模式-> 此模式注意用于测试
*/
void spiA_init(void)
{
//配置gpio口
/*
* SPISTE引脚作为从SPI设备的芯片使能引脚。该引脚在向从机传输数据之前由主机驱动低电平,在传输完成后置高电平。
*/
EALLOW;
GpioCtrlRegs.GPAPUD.bit.GPIO16 = 0; // Enable pull-up on GPIO16 (SPISIMOA)
GpioCtrlRegs.GPAPUD.bit.GPIO17 = 0; // Enable pull-up on GPIO17 (SPISOMIA)
GpioCtrlRegs.GPAPUD.bit.GPIO18 = 0; // Enable pull-up on GPIO18 (SPICLKA)
GpioCtrlRegs.GPAPUD.bit.GPIO19 = 0; // Enable pull-up on GPIO19 (SPISTEA)
GpioCtrlRegs.GPAQSEL2.bit.GPIO16 = 3; // Asynch input GPIO16 (SPISIMOA)
GpioCtrlRegs.GPAQSEL2.bit.GPIO17 = 3; // Asynch input GPIO17 (SPISOMIA)
GpioCtrlRegs.GPAQSEL2.bit.GPIO18 = 3; // Asynch input GPIO18 (SPICLKA)
GpioCtrlRegs.GPAQSEL2.bit.GPIO19 = 3; // Asynch input GPIO19 (SPISTEA)
GpioCtrlRegs.GPAMUX2.bit.GPIO16 = 1; // Configure GPIO16 as SPISIMOA
GpioCtrlRegs.GPAMUX2.bit.GPIO17 = 1; // Configure GPIO17 as SPISOMIA
GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 1; // Configure GPIO18 as SPICLKA
GpioCtrlRegs.GPAMUX2.bit.GPIO19 = 1; // Configure GPIO19 as SPISTEA
SysCtrlRegs.PCLKCR0.bit.SPIAENCLK=1; //使能spia时钟
EDIS;
SpiaRegs.SPICCR.bit.SPISWRESET = 0; //进入复位状态
//根据需要配置SPI
SpiaRegs.SPICCR.bit.CLKPOLARITY = 1; //时钟极性=0,数据在上升沿输出,在下降沿输入。时钟极性=1,数据在下降沿发送数据,在上升沿接收输入。
SpiaRegs.SPICCR.bit.SPICHAR = 7; //7h = 设置为 8-bit word
SpiaRegs.SPICCR.bit.SPILBK = 1; //SPI环回模式选择. 0-disable; 1-enable
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; //0-正常SPI时钟方案;1-spiclk信号延迟半个周期。
SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; //0-slave; 1-master
SpiaRegs.SPICTL.bit.OVERRUNINTENA = 0;//Overrun Interrupt Enable. 0-disablde, 1-enable
SpiaRegs.SPICTL.bit.SPIINTENA = 0;//SPI Interrupt Enable. 0- disable; 1-enable
SpiaRegs.SPICTL.bit.TALK = 1;//Transmit Enable. 0- disable; 1-enable
/*
SpiaRegs.SPISTS.bit.BUFFULL_FLAG: 当将字符写入SPI时,此只读位被设置为1
发送缓冲区SPITXBUF。当完成移出前一个字符时,该字符被自动加载到SPIDAT中,该字符将被清除。
*/
//清除SPI标志(OVERRUN_FLAG, INT_FLAG)
SpiaRegs.SPISTS.bit.INT_FLAG = 0; //清除INT_FLAG
SpiaRegs.SPISTS.bit.OVERRUN_FLAG = 1;//Writing a '1' will clear this bit.
//设置所需的波特率SPIBRR
SpiaRegs.SPIBRR=21;//此时波特率为22.5MHz/(21+1)。 SPI Baud Rate = LSPCLK/(spibrr + 1);其中(spibrr + 1)>=4
//配置FIFO TX
SpiaRegs.SPIFFTX.bit.SPIRST =1;//1-SPI FIFO可以恢复发送或接收。对SPI寄存器位没有影响。
SpiaRegs.SPIFFTX.bit.SPIFFENA =1;//SPI FIFO功能使能。0-disable;1-enable
SpiaRegs.SPIFFTX.bit.TXFFIENA = 0;//TX FIFO中断使能位。 0-禁用
SpiaRegs.SPIFFTX.bit.TXFFIL =0;//发送FIFO中断层级位。0-TX FIFO中断请求是在TX缓冲区中没有剩余字时产生的。
//SpiaRegs.SPIFFTX.bit.TXFFINT //TX FIFO中断标志,只读
SpiaRegs.SPIFFTX.bit.TXFFINTCLR =1; //写入1清除SPIFFTX[TXFFINT]标志。
/*
SpiaRegs.SPIFFTX.bit.TXFFST - 发送FIFO状态,只读
* 0h (R/W) = Transmit FIFO is empty.
1h (R/W) = Transmit FIFO has 1 word.
2h (R/W) = Transmit FIFO has 2 words.
3h (R/W) = Transmit FIFO has 3 words.
4h (R/W) = Transmit FIFO has 4 words, which is the maximum.
*/
SpiaRegs.SPIFFTX.bit.TXFIFO =1;//TX FIFO Reset, 1-从复位中释放传输FIFO
//配置FIFO RX
/*SpiaRegs.SPIFFRX.bit.RXFFOVF:
接收FIFO溢出标志,接收FIFO已溢出,只读位。
在FIFO中已经接收了超过4个单词,并且第一个接收到的单词丢失。
*/
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 1;//Receive FIFO Overflow Clear,Write 1 to clear SPIFFRX[RXFFOVF].
SpiaRegs.SPIFFRX.bit.RXFIFORESET =1;//接收FIFO复位位,1-重新启用接收FIFO操作;0-写0将FIFO指针重置为零,并保持reset。
/*
SpiaRegs.SPIFFRX.bit.RXFFST-接收FIFO状态暂存器,只读:
0h (R/W) = Receive FIFO is empty.
1h (R/W) = Receive FIFO has 1 word.
2h (R/W) = Receive FIFO has 2 words.
3h (R/W) = Receive FIFO has 3 words.
4h (R/W) = Receive FIFO has 4 words, which is the maximum.
*/
//SpiaRegs.SPIFFRX.bit.RXFFINT - 接收中断标志位,只读
SpiaRegs.SPIFFRX.bit.RXFFINTCLR = 1;//接收FIFO中断清除位,写入1清除SPIFFRX[RXFFINT]标志
SpiaRegs.SPIFFRX.bit.RXFFIENA =0;//RX FIFO中断使能位,0-disable,1-基于RXFFIL匹配(大于或等于)的RX FIFO中断将被启用。
SpiaRegs.SPIFFRX.bit.RXFFIL=4;//接收FIFO中断层级位,1-当RX缓冲区中有1个或更多的单词,产生RX FIFO中断请求。写死就代表接收到4位数据才生产中断触发信号,但是前面没开启中断,所以可以忽略。
SpiaRegs.SPIFFCT.all =0;//FIFO传输延迟位 7-0; 延时为0
SpiaRegs.SPIPRI.bit.FREE = 1; // 1-自由运行,继续SPI操作不管挂起或当挂起发生。
SpiaRegs.SPIPRI.bit.SOFT=0;//这个位只有在FREE位为0时才有效。
SpiaRegs.SPIPRI.bit.STEINV=0;//SPISTEn有效电平位, 0h (R/W) = SPISTEn is active low (normal); 1h (R/W) = SPISTE is active high (inverted)
SpiaRegs.SPIPRI.bit.TRIWIRE=0;//0h (R/W) = Normal 4-wire SPI mode. 1-3-wire SPI mode enabled.
SpiaRegs.SPICCR.bit.SPISWRESET = 1;//退出复位状态
}
/*
* spiATxAndRx_T1R1
* 发送一个byte,接收一个byte
* dat:发送的数据指针
* 返回接收的数据
*/
Uint8 spiATxAndRx_T1R1(Uint8* dat)
{
Uint16 data;
//判断FIFO的发送缓冲是否有4个数据了,有的话需要等待FIFO缓存至少释放一个位置,才把需发送的数据移入发送缓冲区。
while(SpiaRegs.SPIFFTX.bit.TXFFST > 3);
data=*dat;
SpiaRegs.SPITXBUF = data<<8;//发送的数据需要左对齐,因为SPITXBUF为16位的,发送需先发送最高位
//读取最新值来保存
while(SpiaRegs.SPIFFRX.bit.RXFFST==0);//等待接收到数据
do{
data=(char)(SpiaRegs.SPIRXBUF & 0xff); //获取数据
asm(" RPT #2 || NOP");//会执行N+1 次NOP 指令,占用N+1 个指令周期。正常情况下占用N+1 个时钟周期。
}while(SpiaRegs.SPIFFRX.bit.RXFFST);//判断是否还有数据在FIFO接收缓冲区,有的话继续接收,把最新的数据给到data
return (Uint8)data;
}
/*
* 发送4个byte,接收4个byte
* dat:发送数据的指针
*/
char recvbuf[4];
Uint8 spiATxAndRx_T4R4(char* dat)
{
Uint16 n=4;
Uint16 data;
while(n--)//连续发送4位数据
{
while(SpiaRegs.SPIFFTX.bit.TXFFST > 3);//跟上面一样,判断FIFO发送缓冲区是否满了
data=*dat;
SpiaRegs.SPITXBUF = data<<8; //左对齐发送
dat +=1;
}
n=0;
//读取4个值来保存
while(SpiaRegs.SPIFFRX.bit.RXFFST!=4);//等待接收FIFO满
data=SpiaRegs.SPIFFRX.bit.RXFFST;
do{
recvbuf[n]=(char)(SpiaRegs.SPIRXBUF & 0xff);//读取接收到的第一个数据
n++;
data -=1;
//读取一个数据,SpiaRegs.SPIFFRX.bit.RXFFST会自动减1,直到0.
while(data != SpiaRegs.SPIFFRX.bit.RXFFST);//此处等待SpiaRegs.SPIFFRX.bit.RXFFST自减1,然后再接收下一个数据
}while(SpiaRegs.SPIFFRX.bit.RXFFST);//一直读,直到读完才离开
return 1;//操作完成,返回1
}
char recv[64];
/*
方法一
* 发送count个byte,允许接收count个byte。count<=64.
* dat:发送的数据指针
* count:需要发送的数据个数
* RxEnable:0-不接收数据;1-接收并打印数据
* 接收的数据将保存到recv[64]中
*/
void spiATxAndRx_TnRn(char* dat, Uint8 count,Uint8 RxEnable)
{
Uint16 n,j;
n=count/4;//获取需要连续发送4个byte数据的次数
j=0;
while(n--)
{
spiATxAndRx_T4R4(dat);//连续发送接收4个byte数据
if(RxEnable)
{//保存接收到的4个byte数据进recv缓存数组中
recv[j]=recvbuf[0];
recv[j+1]=recvbuf[1];
recv[j+2]=recvbuf[2];
recv[j+3]=recvbuf[3];
}
dat +=4;
j+=4;
}
n=count%4;//计算需要单个发送接收的次数
while(n--)
{
if(RxEnable)
{
recv[j]=spiATxAndRx_T1R1((Uint8*)dat);//单个发送接收
j +=1;
}
else
spiATxAndRx_T1R1((Uint8*)dat);//单个发送接收
dat +=1;
}
if(RxEnable)
{//打印接收到的数据
recv[j] = '\0';
UARTa_SendString("spi recv: ");
UARTa_SendString(recv);
UARTa_SendString("\r\n");
}
}
/*
方法二
跟方法二差不多,区别在:方法二不使用spiATxAndRx_T4R4方法进行收发。只使用spiATxAndRx_T1R1进行收发处理。
*/
void spiATxAndRx2_TnRn(char* dat, Uint8 count,Uint8 RxEnable)
{
Uint16 j=0;
Uint8 strl;
while(count--)
{
strl=spiATxAndRx_T1R1((Uint8*)dat);
if(RxEnable)
{
recv[j]=strl;
j +=1;
}
dat +=1;
}
if(RxEnable)
{
recv[j] = '\0';
UARTa_SendString("spi recv: ");
UARTa_SendString(recv);
UARTa_SendString("\r\n");
}
}
- main.c
int main(void)
{
Uint16 COUNT;
char NUMCHAR_[16];
InitSysCtrl();
InitPieCtrl();
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable();
Usart_Init(9600);
msg="Hello,spi TEST!\r\n\0";
UARTa_SendString(msg);
spiA_init();
COUNT=0;
while(1)
{
delay_ms(3000);//延时3秒。需自己写
if(COUNT>9)COUNT=0;
NUMCHAR_[0]='0'+COUNT;
NUMCHAR_[1]='1'+COUNT;
NUMCHAR_[2]='2'+COUNT;
NUMCHAR_[3]='3'+COUNT;
NUMCHAR_[4]='4'+COUNT;
NUMCHAR_[5]='5'+COUNT;
COUNT++;
spiATxAndRx_TnRn(NUMCHAR_,6,1);
}
实验现象
串口助手打印出来的结果
对于SPI通讯的一些问题:
之前使用硬件SPI来驱动OLED屏幕。由于驱动oled只需发送就行了,不需要从oled中接收数据。所以发送函数就直接写:SpiaRegs.SPITXBUF = data;
。
就没有写while(SpiaRegs.SPIFFRX.bit.RXFFST!=1); data=(SpiaRegs.SPIRXBUF & 0xff);
来接收数据。当时以为接收溢出后,芯片只是会通过溢出位来通知,然后覆盖旧数据。但是实际这样写SPI是驱动不了oled的,SPI好像卡死了。
我认为原因是:由于SPI发送后没有把接收到数据的数据移走,当接收数据超出FIFO的接收深度后,会造成SPI通讯堵塞,SPI是不会发送数据的。所以,需要特别注意的是发送数据和接收数据一定要一起进行。不要只发送不接收,或者只接收不发送。