由于項(xiàng)目的需要,,需要使用兩個(gè)STM32的單片機(jī)進(jìn)行雙機(jī)通訊。以前使用SPI都是使用spi外掛類(lèi)似FLASH或者顯示屏之類(lèi)的片子,。不需要從機(jī)進(jìn)行反數(shù)據(jù),。本來(lái)以為會(huì)很簡(jiǎn)單。沒(méi)想到在實(shí)際調(diào)試中卻發(fā)現(xiàn)了一些問(wèn)題,,耽擱了兩天才調(diào)試完成?,F(xiàn)在談?wù)勎业囊恍┙?jīng)過(guò)。也為有類(lèi)似問(wèn)題的同學(xué)提供一些參考,。 我使用的是stm32f103單片機(jī)的SPI1。PA5,,PA6,,PA7三個(gè)引腳的硬件spi。下面我把程序拆分粘貼上來(lái),。談?wù)勛约赫{(diào)試中遇到的問(wèn)題,,跟解決方案。 首先要說(shuō)明的一個(gè)就是硬件的接線(xiàn),。SPI接線(xiàn)不能跟串口一樣交叉,。這點(diǎn)一定要注意。 MISO---------MISO MOSI---------MOSI SCK------------SCK 主機(jī)片選IO可自己選----------從機(jī)NSS引腳PA4 首先在IO引腳這里的配置一定要注意,。有些人直接都配置成AF_PP,這樣在主機(jī)通訊的時(shí)候是沒(méi)有問(wèn)題的,,但是在從機(jī)也這樣配置就會(huì)出現(xiàn)錯(cuò)誤。因?yàn)閺臋C(jī)是不能發(fā)送時(shí)鐘信號(hào)的需要接受主機(jī)產(chǎn)生的時(shí)鐘,,所以需要配置成浮空輸入,。具體程序如下 主機(jī)******************************************************************************* //**********************配置GPIO管腳********************************* void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽復(fù)用 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); } 從機(jī)********************************************************************************** //**********************配置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接下來(lái)就要配置spi的初始化函數(shù)了。這里我在網(wǎng)上看到有人說(shuō)CPOL,CPHA的配置不能完全一樣,。說(shuō)會(huì)產(chǎn)生數(shù)據(jù)位的錯(cuò)誤,。但是官方的數(shù)據(jù)手冊(cè)說(shuō)是應(yīng)該配置成一樣的。通過(guò)我自己試驗(yàn)發(fā)現(xiàn)配置成一樣的并沒(méi)有發(fā)生數(shù)據(jù)位錯(cuò)誤的現(xiàn)象,。也許是單片機(jī)的原因吧,。反正我是配置成一樣的 。 主機(jī)*************************************************************************************** //**********************SPI初始化函數(shù)********************************* 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); } 從機(jī)********************************************************************************************** //**********************SPI初始化函數(shù)********************************* 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); } 因?yàn)閺臋C(jī)的片選需要用主機(jī)進(jìn)行控制,。(實(shí)際上我是6片單片機(jī)進(jìn)行的SPI通訊,。)所以 SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; 這里從機(jī)一定要配置成hard硬件片選模式。只有這樣才能用主機(jī)的IO才可以控制從機(jī) 接下來(lái)其實(shí)就是最關(guān)鍵的地方了也是我出現(xiàn)了很多問(wèn)題的地方,。我先說(shuō)我最開(kāi)始直接用官方的庫(kù)函數(shù)出現(xiàn)的問(wèn)題,。主機(jī)從機(jī)的代碼這里是一樣的。我先發(fā)一個(gè)有問(wèn)題的,。 u8 SPI_Send_Byte(u8 byte) { while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判斷是否忙,,不忙的時(shí)候才發(fā)送數(shù)據(jù) SPI_I2S_SendData(SPI1, byte); //發(fā)送數(shù)據(jù) while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判斷接收標(biāo)志 return SPI_I2S_ReceiveData(SPI1); //返回接收數(shù)據(jù) } 這里使用這個(gè)函數(shù)用主機(jī)給從機(jī)發(fā)送數(shù)據(jù)的時(shí)候是完全沒(méi)有問(wèn)題的。數(shù)據(jù)可以很穩(wěn)定的發(fā)送和接收,。但是當(dāng)從機(jī)給主機(jī)發(fā)送的時(shí)候就會(huì)發(fā)生數(shù)據(jù)錯(cuò)亂的問(wèn)題,。所以從機(jī)反數(shù)據(jù)的時(shí)候我浪費(fèi)了很長(zhǎng)的時(shí)間去測(cè)試。包括使用示波器觀(guān)察,。因?yàn)閟pi是全雙工通信的。所以無(wú)論你怎樣工作數(shù)據(jù)收發(fā)都是同步進(jìn)行的,。而芯片內(nèi)部寄存器實(shí)際上只有一個(gè)來(lái)存儲(chǔ)這些數(shù)據(jù),。如果沒(méi)有及時(shí)的清空就會(huì)對(duì)數(shù)據(jù)產(chǎn)生影響。所以用這個(gè)函數(shù)就會(huì)發(fā)生紊亂。 那么要解決這個(gè)函數(shù)的問(wèn)題,,實(shí)際上就要把數(shù)據(jù)分開(kāi)發(fā)送,,就是一個(gè)字節(jié)一個(gè)字節(jié)的發(fā)送。發(fā)一個(gè)的同時(shí)接受一個(gè),。一發(fā)一收永遠(yuǎn)同步進(jìn)行,。因此我這里套用了一個(gè)別人的代碼。不過(guò)當(dāng)時(shí)這個(gè)代碼還是有問(wèn)題的,。一會(huì)再說(shuō),。先發(fā)一個(gè)最原始的代碼。 網(wǎng)上的原始代碼************************************************************************************************* u8 SPI_Send_Byte(u8 byte) { while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判斷是否忙,,不忙的時(shí)候才發(fā)送數(shù)據(jù) SPI_I2S_SendData(SPI1, byte); //發(fā)送數(shù)據(jù) while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判斷接收標(biāo)志 return SPI_I2S_ReceiveData(SPI1); //返回接收數(shù)據(jù) } u8 SPI1_ReadWriteByte(u8 TxData) //這個(gè)代碼是抄的原子大哥的,,我就不BB了 { u16 retry=0; u8 data; while((SPI1->SR&1<1)==0)>1)==0)> { retry++; if(retry>0XFFFE)return 0; } SPI1->DR=TxData; //發(fā)送一個(gè)byte retry=0; while((SPI1->SR&1<0)==0)>0)==0)> { retry++; if(retry>0XFFFE)return 0; } data=SPI1->DR; return SPI1->DR; //返回收到的數(shù)據(jù) } 這段代碼表面看起來(lái)天衣無(wú)縫。并且使用主機(jī)發(fā)送的時(shí)候也是沒(méi)有問(wèn)題的,。當(dāng)我用從機(jī)返回?cái)?shù)據(jù)的時(shí)候我還是出現(xiàn)了之前數(shù)據(jù)錯(cuò)誤的問(wèn)題,。這下我糾結(jié)了。理論上是不應(yīng)該有問(wèn)題,。因此還是只能從程序上找問(wèn)題,。其實(shí)這個(gè)問(wèn)題就是最后一句話(huà) return SPI1->DR; //返回收到的數(shù)據(jù) 因?yàn)閺臋C(jī)需要及時(shí)的把數(shù)據(jù)發(fā)送出去,如果還是返回SPI1->DR; 這個(gè)寄存器的數(shù)據(jù)其實(shí)已經(jīng)不是剛剛的數(shù)據(jù)了,,因?yàn)橹挥幸粋€(gè)寄存器,,而且全雙工一直在工作。這時(shí)的寄存器已經(jīng)是一個(gè)不穩(wěn)定狀態(tài)了,,數(shù)據(jù)當(dāng)然會(huì)發(fā)生紊亂,。所以這里需要直接返回的是這個(gè)變量的值。return SPI1->DR; 改成return data; 這樣就可以了,,因?yàn)樽兞康闹凳遣粫?huì)被刷新的,。最后實(shí)際測(cè)試也是完全正確的。從機(jī)也可以很穩(wěn)定的發(fā)送數(shù)據(jù),。 下面我發(fā)送一下我的主函數(shù),。測(cè)試程序。 主機(jī)*************************************************************************************** #include 'pbdata.h' u8 a; u8 b; int main(void) { RCC_Configuration(); GPIO_Configuration(); SPI_Configuration(); USART_Configration(); NVIC_Configuration(); //中斷優(yōu)先級(jí)配置 // GPIO_ResetBits(GPIOB,GPIO_Pin_5);//從機(jī)開(kāi)始工作 // GPIO_SetBits(GPIOB,GPIO_Pin_5);//從機(jī)停止工作 while(1) { if(a==1) { GPIO_ResetBits(GPIOB,GPIO_Pin_5);//從機(jī)開(kāi)始工作 if(b==1) { // while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判斷是否忙,,不忙的時(shí)候才發(fā)送數(shù)據(jù) // // SPI1->DR=spisend1;//發(fā)送 // SPI_I2S_SendData(SPI1, spisend1); //發(fā)送數(shù)據(jù) // while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判斷接收標(biāo)志 // // 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);//從機(jī)停止工作 } delay_ms(2); } } 這里的a,,b是為了在線(xiàn)調(diào)試的時(shí)候?qū)ζx和收發(fā)開(kāi)關(guān)進(jìn)行控制。 從機(jī)*********************************************************************************************** #include 'pbdata.h' u8 a; int main(void) { RCC_Configuration(); GPIO_Configuration(); SPI_Configuration(); USART_Configration(); NVIC_Configuration(); //中斷優(yōu)先級(jí)配置 while(1) { u8 a=8; if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0) { while(a--) { spivalue2= SPI1_ReadWriteByte(spisend2); //這個(gè)代碼是抄的原子大哥的,,我就不BB了 } } } } while(a--)這個(gè)是因?yàn)槲野l(fā)送的數(shù)據(jù)是8幀的,。所以分8次發(fā)送。 spisend和spivalue是兩個(gè)全局變量,。在線(xiàn)調(diào)試的時(shí)候直接觀(guān)察自己收發(fā)的數(shù)據(jù),。 spi一般用作片間通訊較多,。很少使用板間通訊。因此我也在擔(dān)心不穩(wěn)定的問(wèn)題,。所以我特地親自只做了3根長(zhǎng)度2米5左右的線(xiàn)進(jìn)行了測(cè)試,。沒(méi)有添加上拉電阻等原件。實(shí)際測(cè)試發(fā)現(xiàn)數(shù)據(jù)還是很穩(wěn)定的,。因此可以判定在這個(gè)距離以?xún)?nèi)是沒(méi)有必要去擔(dān)心的,。另外提醒大家在做這個(gè)實(shí)驗(yàn)的時(shí)候一定要記得兩個(gè)電路板要共地不要忽略了這個(gè)。 |
|