PIC微控制器是微芯片为嵌入式项目提供的强大平台。它的通用性使其能够找到进入许多应用程序的途径,并且仍在不断发展。如果您一直在关注我们的PIC教程,那么您会发现我们已经从基础知识开始涵盖了有关PIC微控制器的大量教程。在同一流程中,我们将继续学习PIC可用的通信协议以及如何使用它们。我们已经用PIC微控制器介绍了I2C。
在庞大的嵌入式应用系统中,没有微控制器可以独自执行所有活动。在某些时候它必须与其他设备进行通信以共享信息,有许多 不同类型的通信协议 可以共享这些信息,但是最常用的是 USART,IIC,SPI和CAN。每个通信协议都有其自身的优点和缺点。现在让我们集中讨论 SPI协议 ,因为这是本教程要学习的内容。
什么是SPI通信协议?
术语SPI代表“串行外围设备接口”。它是一种常见的通信协议,用于在两个微控制器之间发送数据或从传感器向微控制器读取/写入数据。它还用于与SD卡,移位寄存器,显示控制器等进行通信。
SPI协议如何工作?
SPI通信是同步通信,这意味着它在时钟信号的帮助下工作,该时钟信号在交换数据的两个设备之间共享。它也是全双工通信,因为它可以使用单独的总线发送和接收数据。所述SPI通信要求5线来操作。主站和从站之间的简单SPI通信电路如下所示
通信所需的五根电线是SCK(串行时钟),MOSI(主输出从动输入),MISO(主输入从动输出)和SS(从选择)。 SPI通信始终仅在主机和从机之间进行。一个主站可以连接多个从站。主机负责产生时钟脉冲,所有从机均共享该时钟脉冲。同样,所有通信只能由主机启动。
SCK引脚(又名SCL串行时钟)与从机共享主机产生的时钟信号。MOSI引脚(又名SDA –串行数据输出)用于将数据从主机发送到从机。MISO引脚(又名SDI –串行数据输入)用于将数据从从站获取到主机。您也可以按照上图中的箭头标记了解数据/信号的移动。最后,当有多个从模块连接到主模块时,将使用SS引脚(也称为CS –芯片选择)。此输入可用于选择所需的从站。下图显示了一个示例电路,其中多个从器件与主器件连接以进行SPI通信。
I2C和SPI通信之间的区别
我们已经学习了与PIC的I2C通信,因此我们必须熟悉I2C的工作原理以及在哪里可以使用它们,例如I2C可以用于连接RTC模块。但是现在,当我们已经有了I2C时,为什么需要SPI协议。原因是I2C和SPI通信以其自身的方式都是优势,因此是特定于应用程序的。
在某种程度上,可以认为I2C通信比SPI通信具有一些优势,因为I2C使用的引脚数较少,并且当有大量从设备连接到总线时,它非常方便。但是I2C的缺点是它具有用于发送和接收数据的相同总线,因此速度相对较慢。因此,它完全基于应用程序来确定项目的SPI和I2C协议。
使用XC8编译器的PIC16F877A的SPI:
足够的基础知识,现在让我们学习如何使用MPLABX IDE和XC8编译器在PIC16F877A微控制器上使用SPI通信。在我们开始明确本教程仅讨论 使用XC8编译器的PIC16F877a中的SPI之前,该过程对于其他单片机将是相同的,但可能需要稍作更改。还要记住,对于像PIC18F系列这样的高级微控制器,编译器本身可能内置了一些库以使用SPI功能,但是对于PIC16F877A则不存在这样的库,因此让我们自己构建一个。此处说明的库将作为标题文件提供给下载,该文件可在底部下载,可用于PIC16F877A与其他SPI器件进行通信。
在本教程中,我们将编写一个使用SPI通信从SPI总线写入和读取数据的小程序。然后,我们将使用Proteus模拟进行验证。与SPI寄存器相关的所有代码都将在名为PIC16f877a_SPI.h的头文件中进行。这样,我们可以在所有所有需要进行SPI通信的项目中使用此头文件。在主程序中,我们将仅使用头文件中的函数。完整的代码以及头文件可以从此处下载。
SPI头文件说明:
在头文件中,我们必须初始化PIC16F877a的SPI通信。一如既往的最佳起点是PIC16F877A数据手册。控制PIC16F8777a的SPI通信的寄存器是SSPSTAT和SSPCON寄存器。您可以在数据表的第74和75页上找到有关它们的信息。
初始化SPI通信时,必须选择许多参数选项。最常用的选项是将时钟频率设置为Fosc / 4,并在中间进行设置,并且在理想状态下将时钟设置为低。因此,我们还在头文件中使用相同的配置,您可以通过更改相应的位来轻松更改它们。
SPI_Initialize_Master()
SPI初始化主机功能用于作为主机启动SPI通信。在此功能内,我们将各自的引脚RC5和RC3设置为输出引脚。然后我们配置SSPTAT和SSPCON寄存器以打开SPI通信
无效SPI_Initialize_Master() { TRISC5 = 0; // SSPSTAT = 0b00000000; // pg 74/234 SSPCON = 0b00100000; //第75/234页 TRISC3 = 0; //设置为从模式输出 }
SPI_Initialize_Slave()
此功能用于将微控制器设置为在从模式下工作以进行SPI通信。在从模式下,引脚RC5应设置为输出,而引脚RC3应设置为输入。从机和主机的SSPSTAT和SSPCON设置方式相同。
无效SPI_Initialize_Slave() { TRISC5 = 0; // SDO引脚应声明为输出 SSPSTAT = 0b00000000; // pg 74/234 SSPCON = 0b00100000; //第75/234页 TRISC3 = 1; //设置为进入主模式 }
SPI_Write(字符传入)
SPI写入功能用于将数据写入SPI总线。它通过传入的变量从用户获取信息,然后使用它传递给Buffer寄存器。SSPBUF将在连续的时钟脉冲中清零,并且数据将逐位发送到总线。
无效SPI_Write(字符传入) { SSPBUF =传入; //将用户给定的数据写入缓冲区 }
SPI_Ready2Read()
SPI就绪读取功能用于检查SPI总线中的数据是否已完全接收以及是否可以读取。SSPSTAT寄存器具有一个称为BF的位,一旦完全接收到数据,该位将置1,因此我们检查是否未设置该位,然后必须等待其被设置为从SPI总线读取任何内容。
无符号SPI_Ready2Read() { 如果(SSPSTAT&0b00000001) 返回1; 否则 返回0; }
SPI_Read()
SPI读取用于将数据从SPI总线读取到微控制器中。SPI总线中存在的数据将存储在SSPBUF中,我们必须等待直到完整的数据存储在Buffer中,然后才能将其读入变量。在读取缓冲区之前,我们先检查SSPSTAT寄存器的BF位,以确保数据接收完成。
char SPI_Read()//读取接收到的数据 { while(!SSPSTATbits.BF); //保持BF位置1,以确保读取完整的数据 。return(SSPBUF); //返回读取的数据 }
主程序说明:
上一节中说明的功能将在头文件中,并且可以将其调用到主c文件中。因此,让我们编写一个小程序来检查SPI通信是否正常工作。我们将很少的数据写入SPI总线,并使用变形模拟检查SPI调试器中是否接收到相同的数据。
与往常一样,通过设置配置位开始程序,然后将刚才说明的头文件添加到程序中非常重要,如下所示
#包括
如果您已经从上面下载的zip文件中打开了程序,则默认情况下,头文件将出现在项目文件的头文件目录中。否则,您必须在项目内部手动添加头文件,添加后的项目文件如下所示
在主文件中,我们必须将PIC初始化为SPI通信的主设备,然后在无限while循环中,我们将随机的三个16进制值写入SPI总线,以检查在仿真过程中是否收到相同的值。
void main() { SPI_Initialize_Master(); while(1){ SPI_Write(0X0A); __delay_ms(100); SPI_Write(0X0F); __delay_ms(100); SPI_Write(0X15); __delay_ms(100); } }
注意,程序中使用的随机值是0A,0F和15,它们是十六进制值,因此在仿真过程中我们应该看到相同的值。就是说代码已全部完成,这只是一个示例,但是我们可以使用相同的方法与其他MCU或与其他运行SPI协议的传感器模块进行通信。
使用SPI调试器模拟PIC:
现在我们的程序已经准备好了,我们可以对其进行编译,然后进行仿真。Proteus具有一个很好的便捷功能,称为 SPI调试器 ,可用于监视SPI总线上的数据。因此,我们使用相同的电路并构建如下所示的电路。
由于仿真中只有一个SPI器件,因此我们不使用SS引脚,不使用时应将其接地,如上所示。只需将十六进制文件加载到PIC16F877A单片机中,然后单击播放按钮即可模拟我们的程序。模拟开始后,您将获得一个弹出窗口,其中显示SPI总线中的数据,如下所示
让我们仔细看一下传入的数据,并检查它是否与我们在程序中编写的数据相同。
数据的接收顺序与我们在程序中写入的顺序相同,并且突出显示了相同的内容。您也可以尝试模拟一个程序以使用SPI协议与两个PIC微控制器通信。您必须将一个PIC编程为主机,将另一个PIC作为从机。在头文件中已经给出了所有为此目的所需的头文件。
这只是SPI可以做什么的一瞥,它还可以读取数据并将其写入多个设备。我们将通过与SPI协议兼容的各种模块之间的接口,在即将到来的教程中介绍有关SPI的更多信息。
希望您理解该项目并从中学到一些有用的东西。如果您有任何疑问,请在下面的评论部分中发布它们,或使用论坛获取技术帮助。
完整的主代码已在下面给出;您可以从此处下载包含所有代码的头文件