分享

还在苦恼单片机通讯吗?SPI双机通讯带你飞!

 xpxys99 2017-10-13

由于项目的需要,需要使用两个STM32的单片机进行双机通讯。以前使用SPI都是使用spi外挂类似FLASH或者显示屏之类的片子。不需要从机进行反数据。本来以为会很简单。没想到在实际调试中却发现了一些问题,耽搁了两天才调试完成。现在谈谈我的一些经过。也为有类似问题的同学提供一些参考。

还在苦恼单片机通讯吗?SPI双机通讯带你飞!

我使用的是stm32f103单片机的SPI1。PA5,PA6,PA7三个引脚的硬件spi。下面我把程序拆分粘贴上来。谈谈自己调试中遇到的问题,跟解决方案。

首先要说明的一个就是硬件的接线。SPI接线不能跟串口一样交叉。这点一定要注意。

MISO---------MISO

MOSI---------MOSI

SCK------------SCK

主机片选IO可自己选----------从机NSS引脚PA4

首先在IO引脚这里的配置一定要注意。有些人直接都配置成AF_PP,这样在主机通讯的时候是没有问题的,但是在从机也这样配置就会出现错误。因为从机是不能发送时钟信号的需要接受主机产生的时钟,所以需要配置成浮空输入。具体程序如下

主机*******************************************************************************

//**********************配置GPIO管脚*********************************

void GPIO_Configuration(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;//sck mosi

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//miso

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//cs

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//cs

GPIO_Init(GPIOB,&GPIO_InitStructure);

}

从机**********************************************************************************

//**********************配置GPIO管脚*********************************

void GPIO_Configuration(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;//sck mosi

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//miso

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//cs

GPIO_Init(GPIOA,&GPIO_InitStructure);

}

配置完IO接下来就要配置spi的初始化函数了。这里我在网上看到有人说CPOL,CPHA的配置不能完全一样。说会产生数据位的错误。但是官方的数据手册说是应该配置成一样的。通过我自己试验发现配置成一样的并没有发生数据位错误的现象。也许是单片机的原因吧。反正我是配置成一样的 。

主机***************************************************************************************

//**********************SPI初始化函数*********************************

void SPI_Configuration(void)

{

SPI_InitTypeDef SPI_InitStructure;

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//SPI_FirstBit_MSB;

SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);

}

从机**********************************************************************************************

//**********************SPI初始化函数*********************************

void SPI_Configuration(void)

{

SPI_InitTypeDef SPI_InitStructure;

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//SPI_FirstBit_MSB;

SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);

}

因为从机的片选需要用主机进行控制。(实际上我是6片单片机进行的SPI通讯。)所以 SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; 这里从机一定要配置成hard硬件片选模式。只有这样才能用主机的IO才可以控制从机

接下来其实就是最关键的地方了也是我出现了很多问题的地方。我先说我最开始直接用官方的库函数出现的问题。主机从机的代码这里是一样的。我先发一个有问题的。

u8 SPI_Send_Byte(u8 byte)

{

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判断是否忙,不忙的时候才发送数据

SPI_I2S_SendData(SPI1, byte); //发送数据

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判断接收标志

return SPI_I2S_ReceiveData(SPI1); //返回接收数据

}

这里使用这个函数用主机给从机发送数据的时候是完全没有问题的。数据可以很稳定的发送和接收。但是当从机给主机发送的时候就会发生数据错乱的问题。所以从机反数据的时候我浪费了很长的时间去测试。包括使用示波器观察。因为spi是全双工通信的。所以无论你怎样工作数据收发都是同步进行的。而芯片内部寄存器实际上只有一个来存储这些数据。如果没有及时的清空就会对数据产生影响。所以用这个函数就会发生紊乱。

那么要解决这个函数的问题,实际上就要把数据分开发送,就是一个字节一个字节的发送。发一个的同时接受一个。一发一收永远同步进行。因此我这里套用了一个别人的代码。不过当时这个代码还是有问题的。一会再说。先发一个最原始的代码。

网上的原始代码*************************************************************************************************

u8 SPI_Send_Byte(u8 byte)

{

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判断是否忙,不忙的时候才发送数据

SPI_I2S_SendData(SPI1, byte); //发送数据

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判断接收标志

return SPI_I2S_ReceiveData(SPI1); //返回接收数据

}

u8 SPI1_ReadWriteByte(u8 TxData) //这个代码是抄的原子大哥的,我就不BB了

{

u16 retry=0;

u8 data;

while((SPI1->SR&1<1)==0)>

{

retry++;

if(retry>0XFFFE)return 0;

}

SPI1->DR=TxData; //发送一个byte

retry=0;

while((SPI1->SR&1<0)==0)>

{

retry++;

if(retry>0XFFFE)return 0;

}

data=SPI1->DR;

return SPI1->DR; //返回收到的数据

}

这段代码表面看起来天衣无缝。并且使用主机发送的时候也是没有问题的。当我用从机返回数据的时候我还是出现了之前数据错误的问题。这下我纠结了。理论上是不应该有问题。因此还是只能从程序上找问题。其实这个问题就是最后一句话

return SPI1->DR; //返回收到的数据

因为从机需要及时的把数据发送出去,如果还是返回SPI1->DR; 这个寄存器的数据其实已经不是刚刚的数据了,因为只有一个寄存器,而且全双工一直在工作。这时的寄存器已经是一个不稳定状态了,数据当然会发生紊乱。所以这里需要直接返回的是这个变量的值。return SPI1->DR; 改成return data; 这样就可以了,因为变量的值是不会被刷新的。最后实际测试也是完全正确的。从机也可以很稳定的发送数据。

下面我发送一下我的主函数。测试程序。

主机***************************************************************************************

#include 'pbdata.h'

u8 a;

u8 b;

int main(void)

{

RCC_Configuration();

GPIO_Configuration();

SPI_Configuration();

USART_Configration();

NVIC_Configuration(); //中断优先级配置

// GPIO_ResetBits(GPIOB,GPIO_Pin_5);//从机开始工作

// GPIO_SetBits(GPIOB,GPIO_Pin_5);//从机停止工作

while(1)

{

if(a==1)

{

GPIO_ResetBits(GPIOB,GPIO_Pin_5);//从机开始工作

if(b==1)

{

// while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判断是否忙,不忙的时候才发送数据

// // SPI1->DR=spisend1;//发送

// SPI_I2S_SendData(SPI1, spisend1); //发送数据

// while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判断接收标志

// // spivalue1=SPI1->DR;//接收

// spivalue1=SPI_I2S_ReceiveData(SPI1);

u8 a=8;

while(a--)

{

spivalue1=SPI1_ReadWriteByte( spisend1);

}

}

}

if(a==0)

{

GPIO_SetBits(GPIOB,GPIO_Pin_5);//从机停止工作

}

delay_ms(2);

}

}

这里的a,b是为了在线调试的时候对片选和收发开关进行控制。

从机***********************************************************************************************

#include 'pbdata.h'

u8 a;

int main(void)

{

RCC_Configuration();

GPIO_Configuration();

SPI_Configuration();

USART_Configration();

NVIC_Configuration(); //中断优先级配置

while(1)

{ u8 a=8;

if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)

{

while(a--)

{

spivalue2= SPI1_ReadWriteByte(spisend2); //这个代码是抄的原子大哥的,我就不BB了

}

}

}

}

while(a--)这个是因为我发送的数据是8帧的。所以分8次发送。

spisend和spivalue是两个全局变量。在线调试的时候直接观察自己收发的数据。

spi一般用作片间通讯较多。很少使用板间通讯。因此我也在担心不稳定的问题。所以我特地亲自只做了3根长度2米5左右的线进行了测试。没有添加上拉电阻等原件。实际测试发现数据还是很稳定的。因此可以判定在这个距离以内是没有必要去担心的。另外提醒大家在做这个实验的时候一定要记得两个电路板要共地不要忽略了这个。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多