完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
|
|
首先要说明的一个就是硬件的接线。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) //等待接收完一个byte { 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是两个全局变量。在线调试的时候直接观察自己收发的数据。 |
|
|
|
|
|
嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-使用AHT20进行环境监测之AHT20传感器介绍
510 浏览 0 评论
792 浏览 0 评论
833 浏览 1 评论
基于瑞萨FPB-RA4E2智能床头灯项目——1编译环境搭建与点亮驱动ws2812全彩LED
797 浏览 0 评论
嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-LCD显示图片编程示例之介绍mmap
1244 浏览 0 评论
【youyeetoo X1 windows 开发板体验】少儿AI智能STEAM积木平台
11812 浏览 31 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-28 09:45 , Processed in 0.851329 second(s), Total 94, Slave 75 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号