【STM32】SPI程序示例
00. 目錄
文章目錄
- 00. 目錄
- 01. SPI簡介
- 02. 功能描述
- 03. 硬件模塊
- 04. 軟件設計
- 05. 結果驗證
- 06. 附錄
- 07. 聲明
01. SPI簡介
SPI 是英語 Serial Peripheral interface 的縮寫,顧名思義就是串行外圍設備接口。是 Motorola首先在其 MC68HCXX 系列處理器上定義的。SPI 接口主要應用在 EEPROM,FLASH,實時時鐘,AD 轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線,節約了芯片的管腳,同時為 PCB 的布局上節省空間,提供方便,正是出于這種簡單易用的特性,現在越來越多的芯片集成了這種通信協議,STM32F4 也有 SPI 接口。
02. 功能描述
使用 STM32F4 自帶的 SPI來實現對外部 FLASH(W25Q128)的讀寫,并將結果顯示在 TFTLCD 模塊上。
03. 硬件模塊
開機的時候先檢測 W25Q128 是否存在,然后在主循環里面檢測兩個按鍵,其中 1 個按鍵(KEY1)用來執行寫入 W25Q128 的操作,另外一個按鍵(KEY0)用來執行讀出操作,在 TFTLCD 模塊上顯示相關信息。同時用 DS0 提示程序正在運行。
所要用到的硬件資源如下:
1) 指示燈 DS0
2) KEY_UP 和 KEY1 按鍵
3) TFTLCD 模塊
4) SPI
5) W25Q128
04. 軟件設計
spi.h
#ifndef __SPI_H #define __SPI_H #include "sys.h"void SPI1_Init(void); //初始化SPI1口 void SPI1_SetSpeed(u8 SpeedSet); //設置SPI1速度 u8 SPI1_ReadWriteByte(u8 TxData);//SPI1總線讀寫一個字節#endifspi.c
#include "spi.h" //以下是SPI模塊的初始化代碼,配置成主機模式 //SPI口初始化 //這里針是對SPI1的初始化 void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef SPI_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB時鐘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;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3復用為 SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4復用為 SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5復用為 SPI1//這里只針對SPI口初始化RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//復位SPI1RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止復位SPI1SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //設置SPI工作模式:設置為主SPISPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //設置SPI的數據大小:SPI發送接收8位幀結構SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步時鐘的空閑狀態為高電平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定義波特率預分頻的值:波特率預分頻值為256SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值計算的多項式SPI_Init(SPI1, &SPI_InitStructure); //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器SPI_Cmd(SPI1, ENABLE); //使能SPI外設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){} //等待接收完一個byte return SPI_I2S_ReceiveData(SPI1); //返回通過SPIx最近接收的數據 }w25qxx.h
#ifndef __W25QXX_H #define __W25QXX_H #include "sys.h" //W25X系列/Q系列芯片列表 //W25Q80 ID 0XEF13 //W25Q16 ID 0XEF14 //W25Q32 ID 0XEF15 //W25Q64 ID 0XEF16 //W25Q128 ID 0XEF17 #define W25Q80 0XEF13 #define W25Q16 0XEF14 #define W25Q32 0XEF15 #define W25Q64 0XEF16 #define W25Q128 0XEF17extern u16 W25QXX_TYPE; //定義W25QXX芯片型號 #define W25QXX_CS PBout(14) //W25QXX的片選信號// //指令表 #define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_FastReadDual 0x3B #define W25X_PageProgram 0x02 #define W25X_BlockErase 0xD8 #define W25X_SectorErase 0x20 #define W25X_ChipErase 0xC7 #define W25X_PowerDown 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_ManufactDeviceID 0x90 #define W25X_JedecDeviceID 0x9F void W25QXX_Init(void); u16 W25QXX_ReadID(void); //讀取FLASH ID u8 W25QXX_ReadSR(void); //讀取狀態寄存器 void W25QXX_Write_SR(u8 sr); //寫狀態寄存器 void W25QXX_Write_Enable(void); //寫使能 void W25QXX_Write_Disable(void); //寫保護 void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite); void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //讀取flash void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//寫入flash void W25QXX_Erase_Chip(void); //整片擦除 void W25QXX_Erase_Sector(u32 Dst_Addr); //扇區擦除 void W25QXX_Wait_Busy(void); //等待空閑 void W25QXX_PowerDown(void); //進入掉電模式 void W25QXX_WAKEUP(void); //喚醒 #endifw25qxx.c
#include "w25qxx.h" #include "spi.h" #include "delay.h" #include "usart.h" u16 W25QXX_TYPE=W25Q128; //默認是W25Q128//4Kbytes為一個Sector //16個扇區為1個Block //W25Q128 //容量為16M字節,共有128個Block,4096個Sector //初始化SPI FLASH的IO口 void W25QXX_Init(void) { GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//使能GPIOG時鐘//GPIOB14GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//PB14GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//輸出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//PG7GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化GPIO_SetBits(GPIOG,GPIO_Pin_7);//PG7輸出1,防止NRF干擾SPI FLASH的通信 W25QXX_CS=1; //SPI FLASH不選中SPI1_Init(); //初始化SPISPI1_SetSpeed(SPI_BaudRatePrescaler_4); //設置為21M時鐘,高速模式 W25QXX_TYPE=W25QXX_ReadID(); //讀取FLASH ID. } //讀取W25QXX的狀態寄存器 //BIT7 6 5 4 3 2 1 0 //SPR RV TB BP2 BP1 BP0 WEL BUSY //SPR:默認0,狀態寄存器保護位,配合WP使用 //TB,BP2,BP1,BP0:FLASH區域寫保護設置 //WEL:寫使能鎖定 //BUSY:忙標記位(1,忙;0,空閑) //默認:0x00 u8 W25QXX_ReadSR(void) { u8 byte=0; W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_ReadStatusReg); //發送讀取狀態寄存器命令 byte=SPI1_ReadWriteByte(0Xff); //讀取一個字節 W25QXX_CS=1; //取消片選 return byte; } //寫W25QXX狀態寄存器 //只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以寫!!! void W25QXX_Write_SR(u8 sr) { W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_WriteStatusReg); //發送寫取狀態寄存器命令 SPI1_ReadWriteByte(sr); //寫入一個字節 W25QXX_CS=1; //取消片選 } //W25QXX寫使能 //將WEL置位 void W25QXX_Write_Enable(void) {W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_WriteEnable); //發送寫使能 W25QXX_CS=1; //取消片選 } //W25QXX寫禁止 //將WEL清零 void W25QXX_Write_Disable(void) { W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_WriteDisable); //發送寫禁止指令 W25QXX_CS=1; //取消片選 } //讀取芯片ID //返回值如下: //0XEF13,表示芯片型號為W25Q80 //0XEF14,表示芯片型號為W25Q16 //0XEF15,表示芯片型號為W25Q32 //0XEF16,表示芯片型號為W25Q64 //0XEF17,表示芯片型號為W25Q128 u16 W25QXX_ReadID(void) {u16 Temp = 0; W25QXX_CS=0; SPI1_ReadWriteByte(0x90);//發送讀取ID命令 SPI1_ReadWriteByte(0x00); SPI1_ReadWriteByte(0x00); SPI1_ReadWriteByte(0x00); Temp|=SPI1_ReadWriteByte(0xFF)<<8; Temp|=SPI1_ReadWriteByte(0xFF); W25QXX_CS=1; return Temp; } //讀取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[i]=SPI1_ReadWriteByte(0XFF); //循環讀數 }W25QXX_CS=1; } //SPI在一頁(0~65535)內寫入少于256個字節的數據 //在指定地址開始寫入最大256字節的數據 //pBuffer:數據存儲區 //WriteAddr:開始寫入的地址(24bit) //NumByteToWrite:要寫入的字節數(最大256),該數不應該超過該頁的剩余字節數!!! void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) {u16 i; W25QXX_Write_Enable(); //SET WEL W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_PageProgram); //發送寫頁命令 SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //發送24bit地址 SPI1_ReadWriteByte((u8)((WriteAddr)>>8)); SPI1_ReadWriteByte((u8)WriteAddr); for(i=0;i<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);//循環寫數 W25QXX_CS=1; //取消片選 W25QXX_Wait_Busy(); //等待寫入結束 } //無檢驗寫SPI FLASH //必須確保所寫的地址范圍內的數據全部為0XFF,否則在非0XFF處寫入的數據將失敗! //具有自動換頁功能 //在指定地址開始寫入指定長度的數據,但是要確保地址不越界! //pBuffer:數據存儲區 //WriteAddr:開始寫入的地址(24bit) //NumByteToWrite:要寫入的字節數(最大65535) //CHECK OK void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16 pageremain; pageremain=256-WriteAddr%256; //單頁剩余的字節數 if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256個字節while(1){ W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//寫入結束了else //NumByteToWrite>pageremain{pBuffer+=pageremain;WriteAddr+=pageremain; NumByteToWrite-=pageremain; //減去已經寫入了的字節數if(NumByteToWrite>256)pageremain=256; //一次可以寫入256個字節else pageremain=NumByteToWrite; //不夠256個字節了}}; } //寫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[i]; }W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//寫入整個扇區 }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//寫已經擦除了的,直接寫入扇區剩余區間. if(NumByteToWrite==secremain)break;//寫入結束了else//寫入未結束{secpos++;//扇區地址增1secoff=0;//偏移位置為0 pBuffer+=secremain; //指針偏移WriteAddr+=secremain;//寫地址偏移 NumByteToWrite-=secremain; //字節數遞減if(NumByteToWrite>4096)secremain=4096; //下一個扇區還是寫不完else secremain=NumByteToWrite; //下一個扇區可以寫完了} }; } //擦除整個芯片 //等待時間超長... void W25QXX_Erase_Chip(void) { W25QXX_Write_Enable(); //SET WEL W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_ChipErase); //發送片擦除命令 W25QXX_CS=1; //取消片選 W25QXX_Wait_Busy(); //等待芯片擦除結束 } //擦除一個扇區 //Dst_Addr:扇區地址 根據實際容量設置 //擦除一個山區的最少時間:150ms void W25QXX_Erase_Sector(u32 Dst_Addr) { //監視falsh擦除情況,測試用 printf("fe:%x\r\n",Dst_Addr); Dst_Addr*=4096;W25QXX_Write_Enable(); //SET WEL W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_SectorErase); //發送扇區擦除指令 SPI1_ReadWriteByte((u8)((Dst_Addr)>>16)); //發送24bit地址 SPI1_ReadWriteByte((u8)((Dst_Addr)>>8)); SPI1_ReadWriteByte((u8)Dst_Addr); W25QXX_CS=1; //取消片選 W25QXX_Wait_Busy(); //等待擦除完成 } //等待空閑 void W25QXX_Wait_Busy(void) { while((W25QXX_ReadSR()&0x01)==0x01); // 等待BUSY位清空 } //進入掉電模式 void W25QXX_PowerDown(void) { W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_PowerDown); //發送掉電命令 W25QXX_CS=1; //取消片選 delay_us(3); //等待TPD } //喚醒 void W25QXX_WAKEUP(void) { W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB W25QXX_CS=1; //取消片選 delay_us(3); //等待TRES1 }main.c
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "lcd.h" #include "spi.h" #include "w25qxx.h" #include "key.h" //要寫入到W25Q16的字符串數組 const u8 TEXT_Buffer[]={"Explorer STM32F4 SPI TEST"}; #define SIZE sizeof(TEXT_Buffer) int main(void) { u8 key;u16 i=0;u8 datatemp[SIZE];u32 FLASH_SIZE;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設置系統中斷優先級分組2delay_init(168); //初始化延時函數uart_init(115200); //初始化串口波特率為115200LED_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/6"); 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);LED1=!LED1; //DS0閃爍}LCD_ShowString(30,150,200,16,16,"W25Q128 Ready!"); FLASH_SIZE=16*1024*1024; //FLASH 大小為16字節POINT_COLOR=BLUE; //設置字體為藍色 while(1){key=Key_Scan();if(key==KEY1_PRESS)//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_PRESS)//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){LED1=!LED1;//提示系統正在運行 i=0;} } }05. 結果驗證
按 KEY1 按鍵寫入數據,然后按 KEY0 讀取數據。DS0 的不停閃爍,提示程序在運行。
06. 附錄
6.1 【STM32】STM32系列教程匯總
網址:【STM32】STM32系列教程匯總
07. 聲明
該教程參考了正點原子的《STM32 F4 開發指南》
總結
以上是生活随笔為你收集整理的【STM32】SPI程序示例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【STM32】SPI相关函数和类型
- 下一篇: 【STM32】ESP8266模块简介