【正点原子探索者STM32F407开发板例程连载+教学】第30章 SPI通信实验
第三十章?SPI?實驗? [mw_shl_code=c,true]1.硬件平臺:正點原子探索者STM32F407開發板?2.軟件平臺:MDK5.1 3.固件庫版本:V1.4.0 [/mw_shl_code] ?? 本章我們將向大家介紹STM32F4的SPI功能。在本章中,我們將使用STM32F4自帶的SPI來實現對外部FLASH(W25Q128)的讀寫,并將結果顯示在TFTLCD模塊上。本章分為如下幾個部分: 30.1 SPI?簡介 30.2?硬件設計 30.3?軟件設計 30.4?下載驗證 30.1 SPI?簡介SPI?是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在?EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便,正是出于這種簡單易用的特性,現在越來越多的芯片集成了這種通信協議,STM32F4也有SPI接口。下面我們看看SPI的內部簡明圖(圖30.1.1): ? ? 圖30.1.1 SPI內部結構簡明圖 ? SPI接口一般使用4條線通信: MISO?主設備數據輸入,從設備數據輸出。 MOSI?主設備數據輸出,從設備數據輸入。 SCLK時鐘信號,由主設備產生。 CS從設備片選信號,由主設備控制。 從圖中可以看出,主機和從機都有一個串行移位寄存器,主機通過向它的SPI串行寄存器寫入一個字節來發起一次傳輸。寄存器通過MOSI信號線將字節傳送給從機,從機也將自己的移位寄存器中的內容通過MISO信號線返回給主機。這樣,兩個移位寄存器中的內容就被交換。外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節;反之,若主機要讀取從機的一個字節,就必須發送一個空字節來引發從機的傳輸。? SPI主要特點有:可以同時發出和接收串行數據;可以當作主機或從機工作;提供頻率可編程時鐘;發送結束中斷標志;寫沖突保護;總線競爭保護等。 SPI總線四種工作方式?SPI?模塊為了和外設進行數據交換,根據外設工作要求,其輸出串行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協議沒有重大的影響。如果?CPOL=0,串行同步時鐘的空閑狀態為低電平;如果CPOL=1,串行同步時鐘的空閑狀態為高電平。時鐘相位(CPHA)能夠配置用于選擇兩種不同的傳輸協議之一進行數據傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)數據被采樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣。SPI主模塊和與之通信的外設備時鐘相位和極性應該一致。 不同時鐘相位下的總線數據傳輸時序如圖30.1.2所示: ? 圖30.1.2?不同時鐘相位下的總線傳輸時序(CPHA=0/1) STM32F4的SPI功能很強大,SPI時鐘最高可以到37.5Mhz,支持DMA,可以配置為SPI協議或者I2S協議(支持全雙工I2S)。 本章,我們將使用STM32F4的SPI來讀取外部SPI FLASH芯片(W25Q128),實現類似上節的功能。這里對SPI我們只簡單介紹一下SPI的使用,STM32F4的SPI詳細介紹請參考《STM32F4xx中文參考手冊》第721頁,27節。然后我們再介紹下SPI ?FLASH芯片。 這節,我們使用STM32F4的SPI1的主模式,下面就來看看SPI1部分的設置步驟吧。SPI相關的庫函數和定義分布在文件stm32f4xx_spi.c以及頭文件stm32f4xx_spi.h中。STM32的主模式配置步驟如下: 1)配置相關引腳的復用功能,使能SPI1時鐘。 我們要用SPI1,第一步就要使能SPI1的時鐘,SPI1的時鐘通過APB2ENR的第12位來設置。其次要設置SPI1的相關引腳為復用(AF5)輸出,這樣才會連接到SPI1上。這里我們使用的是PB3、4、5這3個(SCK.、MISO、MOSI,CS使用軟件管理方式),所以設置這三個為復用IO,復用功能為AF5。 使能SPI1時鐘的方法為: RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1時鐘 復用PB3,PB4,PB5為SPI1引腳的方法為: GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3復用為?SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4復用為?SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5復用為?SPI1 同時我們要設置相應的引腳模式為復用功能模式: GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能 2)初始化SPI1,設置SPI1工作模式等。 這一步全部是通過SPI1_CR1來設置,我們設置SPI1為主機模式,設置數據格式為8位,然后通過CPOL和CPHA位來設置SCK時鐘極性及采樣方式。并設置SPI1的時鐘頻率(最大37.5Mhz),以及數據的格式(MSB在前還是LSB在前)。在庫函數中初始化SPI的函數為: void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct); 跟其他外設初始化一樣,第一個參數是SPI標號,這里我們是使用的SPI1。下面我們來看看第二個參數結構體類型SPI_InitTypeDef的定義: typedef struct { ? uint16_t SPI_Direction; ? uint16_t SPI_Mode; ? uint16_t SPI_DataSize; ? uint16_t SPI_CPOL; ? uint16_t SPI_CPHA; ? uint16_t SPI_NSS;?? ? uint16_t SPI_BaudRatePrescaler;? ? uint16_t SPI_FirstBit;??? ? uint16_t SPI_CRCPolynomial; }SPI_InitTypeDef; 結構體成員變量比較多,接下來我們簡單講解一下: 第一個參數SPI_Direction是用來設置SPI的通信方式,可以選擇為半雙工,全雙工,以及串行發和串行收方式,這里我們選擇全雙工模式SPI_Direction_2Lines_FullDuplex。 第二個參數SPI_Mode用來設置SPI的主從模式,這里我們設置為主機模式SPI_Mode_Master,當然有需要你也可以選擇為從機模式SPI_Mode_Slave。 第三個參數SPI_DataSiz為8位還是16位幀格式選擇項,這里我們是8位傳輸,選擇SPI_DataSize_8b。 第四個參數SPI_CPOL用來設置時鐘極性,我們設置串行同步時鐘的空閑狀態為高電平所以我們選擇SPI_CPOL_High。 第五個參數SPI_CPHA用來設置時鐘相位,也就是選擇在串行同步時鐘的第幾個跳變沿(上升或下降)數據被采樣,可以為第一個或者第二個條邊沿采集,這里我們選擇第二個跳變沿,所以選擇SPI_CPHA_2Edge 第六個參數SPI_NSS設置NSS信號由硬件(NSS管腳)還是軟件控制,這里我們通過軟件控制NSS關鍵,而不是硬件自動控制,所以選擇SPI_NSS_Soft。 第七個參數SPI_BaudRatePrescaler很關鍵,就是設置SPI波特率預分頻值也就是決定SPI的時鐘的參數,從2分頻到256分頻8個可選值,初始化的時候我們選擇256分頻值SPI_BaudRatePrescaler_256,?傳輸速度為84M/256=328.125KHz。 第八個參數SPI_FirstBit設置數據傳輸順序是MSB位在前還是LSB位在前,,這里我們選擇SPI_FirstBit_MSB高位在前。 第九個參數SPI_CRCPolynomial是用來設置CRC校驗多項式,提高通信可靠性,大于1即可。 設置好上面9個參數,我們就可以初始化SPI外設了。初始化的范例格式為: SPI_InitTypeDef? SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;? //雙線雙向全雙工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;??????????? //主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;? // SPI發送接收8位幀結構 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步時鐘的空閑狀態為高電平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二個跳變沿數據被采樣 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;??? //NSS信號由軟件控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //預分頻256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;? //數據傳輸從MSB位開始 SPI_InitStructure.SPI_CRCPolynomial = 7;?????? //CRC值計算的多項式 SPI_Init(SPI2, &SPI_InitStructure);? //根據指定的參數初始化外設SPIx寄存器 3)使能SPI1。 這一步通過SPI1_CR1的bit6來設置,以啟動SPI1,在啟動之后,我們就可以開始SPI通訊了。庫函數使能SPI1的方法為: SPI_Cmd(SPI1, ENABLE); //使能SPI1外設 4)SPI傳輸數據 通信接口當然需要有發送數據和接受數據的函數,固件庫提供的發送數據函數原型為: void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); 這個函數很好理解,往SPIx數據寄存器寫入數據?Data,從而實現發送。 固件庫提供的接受數據函數原型為: uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)?; 這個函數也不難理解,從SPIx數據寄存器讀出接受到的數據。 5)查看SPI傳輸狀態 在SPI傳輸過程中,我們經常要判斷數據是否傳輸完成,發送區是否為空等等狀態,這是通過函數SPI_I2S_GetFlagStatus實現的,這個函數很簡單就不詳細講解,判斷發送是否完成的方法是: SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE); SPI1的使用就介紹到這里,接下來介紹一下W25Q128。W25Q128是華邦公司推出的大容量SPI FLASH產品,W25Q128的容量為128Mb,該系列還有W25Q80/16/32/64等。ALIENTEK所選擇的W25Q128容量為128Mb,也就是16M字節。 W25Q128將16M的容量分為256個塊(Block),每個塊大小為64K字節,每個塊又分為16個扇區(Sector),每個扇區4K個字節。W25Q128的最小擦除單位為一個扇區,也就是每次必須擦除4K個字節。這樣我們需要給W25Q128開辟一個至少4K的緩存區,這樣對SRAM要求比較高,要求芯片必須有4K以上SRAM才能很好的操作。 W25Q128的擦寫周期多達10W次,具有20年的數據保存期限,支持電壓為2.7~3.6V,W25Q128支持標準的SPI,還支持雙輸出/四輸出的SPI,最大SPI時鐘可以到80Mhz(雙輸出時相當于160Mhz,四輸出時相當于320M),更多的W25Q128的介紹,請參考W25Q128的DATASHEET。 30.2?硬件設計本章實驗功能簡介:開機的時候先檢測W25Q128是否存在,然后在主循環里面檢測兩個按鍵,其中1個按鍵(KEY1)用來執行寫入W25Q128的操作,另外一個按鍵(KEY0)用來執行讀出操作,在TFTLCD模塊上顯示相關信息。同時用DS0提示程序正在運行。 所要用到的硬件資源如下: 1)??指示燈DS0 2)??KEY_UP和KEY1按鍵 3)?TFTLCD模塊 4)?SPI 5)?W25Q128 這里只介紹W25Q128與STM32F4的連接,板上的W25Q128是直接連在STM32F4的SPI1上的,連接關系如圖30.2.1所示: ? 圖30.2.1?STM32F4與W25Q128連接電路圖 這里,我們的F_CS是連接在PB14上面的,另外要特別注意:W25Q128和NRF24L01共用SPI1,所以這兩個器件在使用的時候,必須分時復用(通過片選控制)才行。 30.3?軟件設計打開我們光盤的SPI實驗工程,可以看到我們加入了spi.c,flash.c文件以及頭文件spi.h和flash.h,同時引入了庫函數文件stm32f4xx_spi.c文件以及頭文件stm32f4xx_spi.h。 打開spi.c文件,看到如下代碼: //以下是SPI模塊的初始化代碼,配置成主機模式?????????????????????????????????????????? ?? //SPI口初始化 //這里針是對SPI1的初始化 void SPI1_Init(void) {???? ? GPIO_InitTypeDef? GPIO_InitStructure; ? SPI_InitTypeDef? SPI_InitStructure; ?????? ? RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA時鐘 ? RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//?使能SPI1時鐘 ? ? //GPIOFB3,4,5初始化設置:?復用功能輸出?? ? GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5 ? GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能 ? GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出 ? GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz ? GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 ? GPIO_Init(GPIOB, &GPIO_InitStructure);//?初始化 ? ? //配置引腳復用映射 GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3復用為?SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4復用為?SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5復用為?SPI1 ? //這里只針對SPI口初始化 RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//復位SPI1 RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止復位SPI1 ? SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;? //設置SPI全雙工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;??????? //設置SPI工作模式:主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;???? //設置SPI的數據大小: 8位幀結構 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步時鐘的空閑狀態為高電平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //數據捕獲于第二個時鐘沿 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; ? //NSS信號由硬件管理 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;??? //預分頻256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //數據傳輸從MSB位開始 SPI_InitStructure.SPI_CRCPolynomial = 7;??? //CRC值計算的多項式 SPI_Init(SPI1, &SPI_InitStructure); //根據指定的參數初始化外設SPIx寄存器 ? SPI_Cmd(SPI1, ENABLE); //使能SPI1 SPI1_ReadWriteByte(0xff);//啟動傳輸?????????? }?? //SPI1速度設置函數 //SPI速度=fAPB2/分頻系數 //入口參數范圍:@ref SPI_BaudRate_Prescaler //SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256 //fAPB2時鐘一般為84Mhz: void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler) { ???assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性 ?????? SPI1->CR1&=0XFFC7;//位3-5清零,用來設置波特率 ?????? SPI1->CR1|=SPI_BaudRatePrescaler;?? //設置SPI1速度 ?????? SPI_Cmd(SPI1,ENABLE); //使能SPI1 } //SPI1?讀寫一個字節 //TxData:要寫入的字節 //返回值:讀取到的字節 u8 SPI1_ReadWriteByte(u8 TxData) {??????????? ?????????????????? ? ? ? while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待發送區空? SPI_I2S_SendData(SPI1, TxData); //通過外設SPIx發送一個byte??數據 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完 return SPI_I2S_ReceiveData(SPI1); //返回通過SPIx最近接收的數據?????? ???????????? ??? } 此部分代碼主要初始化SPI,這里我們選擇的是SPI1,所以在SPI1_Init函數里面,其相關的操作都是針對SPI1的,其初始化步驟和我們上面介紹的一樣。在初始化之后,我們就可以開始使用SPI1了,這里特別注意,SPI初始化函數的最后有一個啟動傳輸,這句話最大的作用就是維持MOSI為高電平,而且這句話也不是必須的,可以去掉。 在SPI1_Init函數里面,把SPI1的頻率設置成了最低(84Mhz,256分頻)。在外部函數里面,我們通過SPI1_SetSpeed來設置SPI1的速度,而我們的數據發送和接收則是通過SPI1_ReadWriteByte函數來實現的。 接下來我們來看看w25qxx.c文件內容。由于篇幅所限,詳細代碼,這里就不貼出了。我們僅介紹幾個重要的函數,首先是W25QXX_Read函數,該函數用于從W25Q128的指定地址讀出指定長度的數據。其代碼如下:? ??? //讀取SPI FLASH? //在指定地址開始讀取指定長度的數據 //pBuffer:數據存儲區 //ReadAddr:開始讀取的地址(24bit) //NumByteToRead:要讀取的字節數(最大65535) void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)?? { ????? u16 i;?? ??????????????????????????????????????????????????????????????? ???? ?????? W25QXX_CS=0;???????????????????????? //使能器件?? ??? SPI1_ReadWriteByte(W25X_ReadData);????? //發送讀取命令?? ??? SPI1_ReadWriteByte((u8)((ReadAddr)>>16));? //發送24bit地址??? ??? SPI1_ReadWriteByte((u8)((ReadAddr)>>8));?? ??? SPI1_ReadWriteByte((u8)ReadAddr);?? ??? for(i=0;i<NumByteToRead;i++) ?????? { ??????? pBuffer=SPI1_ReadWriteByte(0XFF);?? //循環讀數? ??? } ?????? W25QXX_CS=1;? ??????????????????????????? ?????????? ?????? } 由于W25Q128支持以任意地址(但是不能超過W25Q128的地址范圍)開始讀取數據,所以,這個代碼相對來說就比較簡單了,在發送24位地址之后,程序就可以開始循環讀數據了,其地址會自動增加的,不過要注意,不能讀的數據超過了W25Q128的地址范圍哦!否則讀出來的數據,就不是你想要的數據了。 有讀的函數,當然就有寫的函數了,接下來,我們介紹W25QXX_Write這個函數,該函數的作用與W25QXX_Flash_Read的作用類似,不過是用來寫數據到W25Q128里面的,代碼如下: //寫SPI FLASH? //在指定地址開始寫入指定長度的數據 //該函數帶擦除操作! //pBuffer:數據存儲區? WriteAddr:開始寫入的地址(24bit)???????????????????????????????????????? //NumByteToWrite:要寫入的字節數(最大65535)?? u8 W25QXX_BUFFER[4096];??????????? void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)?? { ?????? u32 secpos; ?????? u16 secoff; u16 secremain; u16 i; ?????? u8 * W25QXX_BUF;??? ? ?? ? W25QXX_BUF=W25QXX_BUFFER; ???? ????? secpos=WriteAddr/4096;//扇區地址? ?????? secoff=WriteAddr%4096;//在扇區內的偏移 ?????? secremain=4096-secoff;//扇區剩余空間大小?? ????? //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//測試用 ????? if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096個字節 ?????? while(1) ?????? {???? ????????????? W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//讀出整個扇區的內容 ????????????? for(i=0;i<secremain;i++)//校驗數據 ????????????? { ???????????????????? if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除? ?? ?? ????????????? } ????????????? if(i<secremain)//需要擦除 ????????????? { ???????????????????? W25QXX_Erase_Sector(secpos);//擦除這個扇區 ???????????????????? for(i=0;i<secremain;i++)?????? ?? //復制 ???????????????????? { ??????????????????????????? W25QXX_BUF[i+secoff]=pBuffer;? ? ???????????????????? } ???????????????????? W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//寫入整個扇區 ????????????? }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//已擦除的,直接寫?????? ?? ????????????? if(NumByteToWrite==secremain)break;//寫入結束了 ????????????? else//寫入未結束 ????????????? { ???????????????????? secpos++;?????????????????????????????????? //扇區地址增1 ???????????????????? secoff=0;??????????????????????????????????? //偏移位置為0 ??? ? ????????????? ?? ? pBuffer+=secremain;? ??????????????? //指針偏移 ???????????????????? WriteAddr+=secremain;?????????????? //寫地址偏移? ?? ????????????? ?? ? NumByteToWrite-=secremain;????? //字節數遞減 ???????????????????? if(NumByteToWrite>4096)secremain=4096; //下一個扇區還是寫不完 ???????????????????? else secremain=NumByteToWrite;?????????????? //下一個扇區可以寫完了 ????????????? }???? ?????? }; } 該函數可以在W25Q128的任意地址開始寫入任意長度(必須不超過W25Q128的容量)的數據。我們這里簡單介紹一下思路:先獲得首地址(WriteAddr)所在的扇區,并計算在扇區內的偏移,然后判斷要寫入的數據長度是否超過本扇區所剩下的長度,如果不超過,再先看看是否要擦除,如果不要,則直接寫入數據即可,如果要則讀出整個扇區,在偏移處開始寫入指定長度的數據,然后擦除這個扇區,再一次性寫入。當所需要寫入的數據長度超過一個扇區的長度的時候,我們先按照前面的步驟把扇區剩余部分寫完,再在新扇區內執行同樣的操作,如此循環,直到寫入結束。這里我們還定義了一個W25QXX_BUFFER的全局變量,用于擦除時緩存扇區內的數據。 其他的代碼就比較簡單了,我們這里不介紹了。對于頭文件w25qxx.h,這里面就定義了一些與W25Q128操作相關的命令和函數(部分省略了),這些命令在W25Q128的數據手冊上都有詳細的介紹,感興趣的讀者可以參考該數據手冊。 最后,我們看看main函數,代碼如下: //要寫入到W25Q128的字符串數組 const u8 TEXT_Buffer[]={"Explorer STM32F4 SPI TEST"}; #define SIZE sizeof(TEXT_Buffer)????? int main(void) {??????????? u8 key, datatemp[SIZE]; ?????? u16 i=0; ?????? u32 FLASH_SIZE; ?????? NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設置系統中斷優先級分組2 ?????? delay_init(168);? //初始化延時函數 ?????? uart_init(115200);???????? //初始化串口波特率為115200???? ?????? LED_Init();???????????????????????? //初始化LED ????? LCD_Init();???????????????????????? //LCD初始化 ?????? KEY_Init(); ?????????????????????? //按鍵初始化? ?????? W25QXX_Init();????????????????? //W25QXX初始化 ????? POINT_COLOR=RED; ?????? LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");?????? ?????? LCD_ShowString(30,70,200,16,16,"SPI TEST");?????? ?????? LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); ?????? LCD_ShowString(30,110,200,16,16,"2014/5/7");?????? ?????? LCD_ShowString(30,130,200,16,16,"KEY1:Write? KEY0:Read");? //顯示提示信息??????????? ?????? while(W25QXX_ReadID()!=W25Q128)????? //檢測不到W25Q128 ?????? { ????????????? LCD_ShowString(30,150,200,16,16,"W25Q128 Check Failed!"); ????????????? delay_ms(500); ????????????? LCD_ShowString(30,150,200,16,16,"Please Check!? ??????"); ????????????? delay_ms(500); ????????????? LED0=!LED0;????????????? //DS0閃爍 ?????? } ?????? LCD_ShowString(30,150,200,16,16,"W25Q128 Ready!"); ?????? FLASH_SIZE=128*1024*1024;?? //FLASH?大小為2M字節 ? ??? POINT_COLOR=BLUE;???????????????????? //設置字體為藍色? ? ?????? while(1) ?????? { ????????????? key=KEY_Scan(0); ????????????? if(key==KEY1_PRES)//KEY1按下,寫入W25Q128 ????????????? { ???????????????????? LCD_Fill(0,170,239,319,WHITE);//清除半屏??? ??????????????????? LCD_ShowString(30,170,200,16,16,"Start Write W25Q128...."); ???????????????????? W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);????????????? //從倒數第100個地址處開始,寫入SIZE長度的數據 ???????????????????? LCD_ShowString(30,170,200,16,16,"W25Q128 Write Finished!");//提示完成 ????????????? } ????????????? if(key==KEY0_PRES)//KEY0按下,讀取字符串并顯示 ????????????? { ??????????????????? LCD_ShowString(30,170,200,16,16,"Start Read W25Q128.... "); ???????????????????? W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);? //從倒數第100個地址處開始,讀出SIZE個字節 ???????????????????? LCD_ShowString(30,170,200,16,16,"The Data Readed Is:?? ");?????? //提示傳送完成 ???????????????????? LCD_ShowString(30,190,200,16,16,datatemp);?? //顯示讀到的字符串 ????????????? } ????????????? i++; ????????????? delay_ms(10); ????????????? if(i==20) ????????????? { ???????????????????? LED0=!LED0;//提示系統正在運行???? ???????????????????? i=0; ????????????? }??????????? ?? ?????? }?????? } 這部分代碼和IIC實驗那部分代碼大同小異,我們就不多說了,實現的功能就和IIC差不多,不過此次寫入和讀出的是SPI FLASH,而不是EEPROM。 30.4?下載驗證在代碼編譯成功之后,我們通過下載代碼到ALIENTEK探索者STM32F4開發板上,通過先按KEY1按鍵寫入數據,然后按KEY0讀取數據,得到如圖30.4.1所示: ? 圖30.4.1 SPI實驗程序運行效果圖 伴隨DS0的不停閃爍,提示程序在運行。程序在開機的時候會檢測W25Q128是否存在,如果不存在則會在TFTLCD模塊上顯示錯誤信息,同時DS0慢閃。大家可以通過跳線帽把PB4和PB5短接就可以看到報錯了。 ? ?實驗詳細手冊和源碼下載地址:http://www.openedv.com/posts/list/41586.htm? ? 正點原子探索者STM32F407開發板購買地址:http://item.taobao.com/item.htm?id=41855882779 ?? ? ? |
探索者實驗連載
實驗25 SPI實驗.zip
?
第三十章 SPI 實驗-STM32F4開發指南-正點原子探索者STM32開發板.pdf
?
http://openedv.com/thread-43360-1-1.html
總結
以上是生活随笔為你收集整理的【正点原子探索者STM32F407开发板例程连载+教学】第30章 SPI通信实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于java的电子政务网的设计及开发
- 下一篇: cpuz测试分数天梯图_cpubench