1、SPI總線
????????SPI分為主從工作模式,通常有一個主設備和一個或多個從設備,本文中MCU為主機,W25Q16為從機。
SPI通信有以下四根線:
MISO:主設備數(shù)據(jù)輸入,從設備數(shù)據(jù)輸出。
MOSI:主設備數(shù)據(jù)輸出,從設備數(shù)據(jù)輸入。
SCLK:時鐘信號,由主設備產(chǎn)生。
CS:從設備片選信號,由主設備控制,低電平為選中。
????????SPI可以同時發(fā)出和接收串行數(shù)據(jù),主機發(fā)送一個數(shù)據(jù)的同時從機也將自己數(shù)據(jù)返回給主機。這樣,雙方的數(shù)據(jù)就被交換了。主機控制外設時,寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節(jié);反之,若主機要讀取從機的一個字節(jié),就必須發(fā)送一個空字節(jié)來引發(fā)從機的傳輸。
SPI的特點:高位先發(fā)送,共有四種工作模式。
CPOL(時鐘極性):規(guī)定了SCK時鐘信號空閑狀態(tài)的電平(0-低電平,1-高電平)
CPHA(時鐘相位):規(guī)定了數(shù)據(jù)是在SCK時鐘的上升沿還是下降沿被采樣(0-第一個時鐘邊沿開始采樣,1-第二個時鐘邊沿開始采樣)
模式0:CPOL=0,CPHA =0 ?SCK空閑為低電平,數(shù)據(jù)在SCK的上升沿被采樣(提取數(shù)據(jù))
模式1:CPOL=0,CPHA =1 ?SCK空閑為低電平,數(shù)據(jù)在SCK的下降沿被采樣(提取數(shù)據(jù))
模式2:CPOL=1,CPHA =0 ?SCK空閑為高電平,數(shù)據(jù)在SCK的下降沿被采樣(提取數(shù)據(jù))
模式3:CPOL=1,CPHA =1 ?SCK空閑為高電平,數(shù)據(jù)在SCK的上升沿被采樣(提取數(shù)據(jù))
2、使用STM32CubeMX配置相關引腳
?
3、實現(xiàn)軟件模擬SPI的四種模式
spi.h
#ifndef __SPI_H
#define __SPI_H
#include "main.h"
#define MOSI_H HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_SET)
#define MOSI_L HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_RESET)
#define SCK_H HAL_GPIO_WritePin(SCLK_GPIO_Port, SCLK_Pin, GPIO_PIN_SET)
#define SCK_L HAL_GPIO_WritePin(SCLK_GPIO_Port, SCLK_Pin, GPIO_PIN_RESET)
#define MISO HAL_GPIO_ReadPin(MISO_GPIO_Port, MISO_Pin)
#define F_CS_H HAL_GPIO_WritePin(F_CS_GPIO_Port, F_CS_Pin, GPIO_PIN_SET)
#define F_CS_L HAL_GPIO_WritePin(F_CS_GPIO_Port, F_CS_Pin, GPIO_PIN_RESET)
uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat);
uint8_t SOFT_SPI_RW_MODE1(uint8_t write_dat);
uint8_t SOFT_SPI_RW_MODE2(uint8_t write_dat);
uint8_t SOFT_SPI_RW_MODE3(uint8_t write_dat);
uint8_t SPI2_ReadWriteByte(uint8_t TxData);
#endif
?spi.c
#include "spi.h"
/*spi延時函數(shù),微秒*/
static void spi_delay(uint16_t time)
{
uint16_t i=0;
while(time--)
{
i=10;
while(i--) ;
}
}
//CPOL:規(guī)定了SCK時鐘信號空閑狀態(tài)的電平(0-低電平,1-高電平)
//CPHA:規(guī)定了數(shù)據(jù)是在SCK時鐘的上升沿還是下降沿被采樣(0-第一個時鐘邊沿開始采樣,1-第二個時鐘邊沿開始采樣)
//模式0:CPOL=0,CPHA =0 SCK空閑為低電平,數(shù)據(jù)在SCK的上升沿被采樣(提取數(shù)據(jù))
//模式1:CPOL=0,CPHA =1 SCK空閑為低電平,數(shù)據(jù)在SCK的下降沿被采樣(提取數(shù)據(jù))
//模式2:CPOL=1,CPHA =0 SCK空閑為高電平,數(shù)據(jù)在SCK的下降沿被采樣(提取數(shù)據(jù))
//模式3:CPOL=1,CPHA =1 SCK空閑為高電平,數(shù)據(jù)在SCK的上升沿被采樣(提取數(shù)據(jù))
/* CPOL = 0, CPHA = 0 */
uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat)
{
uint8_t i,read_dat = 0;
SCK_L;
for(i=0;i<8;i++)
{
if(write_dat&0x80)
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
spi_delay(1);
SCK_H;
read_dat <<= 1;
if(MISO)
read_dat++;
spi_delay(1);
SCK_L;
__nop();
}
return read_dat;
}
/* CPOL=0,CPHA=1 */
uint8_t SOFT_SPI_RW_MODE1(uint8_t write_dat)
{
uint8_t i,read_dat = 0;
SCK_L;
for(i=0;i<8;i++)
{
SCK_H;
if(write_dat&0x80)
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
spi_delay(1);
SCK_L;
read_dat <<= 1;
if(MISO)
read_dat++;
spi_delay(1);
}
return read_dat;
}
/* CPOL=1,CPHA=0 */
uint8_t SOFT_SPI_RW_MODE2(uint8_t write_dat)
{
uint8_t i,read_dat = 0;
SCK_H;
for(i=0;i<8;i++)
{
if(write_dat&0x80)
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
spi_delay(1);
SCK_L;
read_dat <<= 1;
if(MISO)
read_dat++;
spi_delay(1);
SCK_H;
}
return read_dat;
}
/* CPOL = 1, CPHA = 1 */
uint8_t SOFT_SPI_RW_MODE3(uint8_t write_dat)
{
uint8_t i,read_dat = 0;
SCK_H;
for(i=0;i<8;i++)
{
SCK_L;
if(write_dat&0x80)
MOSI_H;
else
MOSI_L;
write_dat <<= 1;
spi_delay(1);
SCK_H;
read_dat <<= 1;
if(MISO)
read_dat++;
spi_delay(1);
__nop();
}
return read_dat;
}
//SPI2 讀寫一個字節(jié)
//TxData:要寫入的字節(jié)
//返回值:讀取到的字節(jié)
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
uint8_t Rxdata;
Rxdata = SOFT_SPI_RW_MODE3(TxData);//使用模式3
return Rxdata;
}
4、實現(xiàn)W25Q16的讀寫函數(shù)
參考正點原子例程
w25q16.h
#ifndef __W25Q16_H
#define __W25Q16_H
#include "main.h"
extern uint8_t W25QXX_BUFFER[4096];
//W25X16讀寫指令表
#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
uint16_t W25QXX_ReadID(void);//讀取FLASH ID
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);//讀取flash
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//寫入flash
void W25QXX_Erase_Chip(void); //整片擦除
void W25QXX_Erase_Sector(uint32_t Dst_Addr);//扇區(qū)擦除
void W25QXX_PowerDown(void);//進入掉電模式
void W25QXX_WAKEUP(void);//喚醒
#endif
w25q16.c
#include "w25q16.h"
#include "spi.h"
//容量為16M bit,2M byte,共有32個塊,512個扇區(qū)
//4Kbytes為一個扇區(qū),16個扇區(qū)為1個塊
//讀取SPI_FLASH的狀態(tài)寄存器
//BIT7 6 5 4 3 2 1 0
//SPR RV TB BP2 BP1 BP0 WEL BUSY
//SPR:默認0,狀態(tài)寄存器保護位,配合WP使用
//TB,BP2,BP1,BP0:FLASH區(qū)域?qū)懕Wo設置
//WEL:寫使能鎖定
//BUSY:忙標記位(1,忙;0,空閑)
//默認:0x00
uint8_t W25QXX_ReadSR(void)
{
uint8_t byte=0;
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_ReadStatusReg); //發(fā)送讀取狀態(tài)寄存器命令
byte=SPI2_ReadWriteByte(0Xff); //讀取一個字節(jié)
F_CS_H;//取消片選
return byte;
}
//寫SPI_FLASH狀態(tài)寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以寫!!!
void W25QXX_Write_SR(uint8_t sr)
{
F_CS_L; //使能器件
SPI2_ReadWriteByte(W25X_WriteStatusReg); //發(fā)送寫取狀態(tài)寄存器命令
SPI2_ReadWriteByte(sr); //寫入一個字節(jié)
F_CS_H;//取消片選
}
//等待空閑
void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR()&0x01)==0x01); //等待BUSY位清空
}
//SPI_FLASH寫使能
//將WEL置位
void W25QXX_Write_Enable(void)
{
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_WriteEnable); //發(fā)送寫使能
F_CS_H;//取消片選
}
//SPI_FLASH寫禁止
//將WEL清零
void W25QXX_Write_Disable(void)
{
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_WriteDisable); //發(fā)送寫禁止指令
F_CS_H;//取消片選
}
//讀取芯片ID W25X16的ID:0XEF14
uint16_t W25QXX_ReadID(void)
{
uint16_t Temp = 0;
F_CS_L; //使能器件
SPI2_ReadWriteByte(0x90);//發(fā)送讀取ID命令
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
Temp|=SPI2_ReadWriteByte(0xFF)<<8;
Temp|=SPI2_ReadWriteByte(0xFF);
F_CS_H;//取消片選
return Temp;
}
//讀取SPI FLASH
//在指定地址開始讀取指定長度的數(shù)據(jù)
//pBuffer:數(shù)據(jù)存儲區(qū)
//ReadAddr:開始讀取的地址(24bit)
//NumByteToRead:要讀取的字節(jié)數(shù)(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
uint16_t i;
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_ReadData);//發(fā)送讀取命令
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16)); //發(fā)送24bit地址
SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));
SPI2_ReadWriteByte((uint8_t)ReadAddr);
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循環(huán)讀數(shù)
}
F_CS_H;//取消片選
}
//SPI在一頁(0~65535)內(nèi)寫入少于256個字節(jié)的數(shù)據(jù)
//在指定地址開始寫入最大256字節(jié)的數(shù)據(jù)
//pBuffer:數(shù)據(jù)存儲區(qū)
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節(jié)數(shù)(最大256),該數(shù)不應該超過該頁的剩余字節(jié)數(shù)!!!
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t i;
W25QXX_Write_Enable(); //SET WEL
F_CS_L; //使能器件
SPI2_ReadWriteByte(W25X_PageProgram); //發(fā)送寫頁命令
SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16));//發(fā)送24bit地址
SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));
SPI2_ReadWriteByte((uint8_t)WriteAddr);
for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循環(huán)寫數(shù)
F_CS_H;//取消片選
W25QXX_Wait_Busy();//等待寫入結束
}
//無檢驗寫SPI FLASH
//必須確保所寫的地址范圍內(nèi)的數(shù)據(jù)全部為0XFF,否則在非0XFF處寫入的數(shù)據(jù)將失敗!
//具有自動換頁功能
//在指定地址開始寫入指定長度的數(shù)據(jù),但是要確保地址不越界!
//pBuffer:數(shù)據(jù)存儲區(qū)
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節(jié)數(shù)(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t pageremain;
pageremain=256-WriteAddr%256; //單頁剩余的字節(jié)數(shù)
if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256個字節(jié)
while(1)
{
W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
if(NumByteToWrite==pageremain)break;//寫入結束了
else //NumByteToWrite>pageremain
{
pBuffer+=pageremain;
WriteAddr+=pageremain;
NumByteToWrite-=pageremain; //減去已經(jīng)寫入了的字節(jié)數(shù)
if(NumByteToWrite>256)pageremain=256; //一次可以寫入256個字節(jié)
else pageremain=NumByteToWrite; //不夠256個字節(jié)了
}
};
}
//寫SPI FLASH
//在指定地址開始寫入指定長度的數(shù)據(jù)
//該函數(shù)帶擦除操作!
//pBuffer:數(shù)據(jù)存儲區(qū)
//WriteAddr:開始寫入的地址(24bit)
//NumByteToWrite:要寫入的字節(jié)數(shù)(最大65535)
uint8_t W25QXX_BUFFER[4096];
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
secpos=WriteAddr/4096;//扇區(qū)地址 0~511 for w25x16
secoff=WriteAddr%4096;//在扇區(qū)內(nèi)的偏移
secremain=4096-secoff;//扇區(qū)剩余空間大小
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096個字節(jié)
while(1)
{
W25QXX_Read(W25QXX_BUFFER,secpos*4096,4096);//讀出整個扇區(qū)的內(nèi)容
for(i=0;i<secremain;i++)//校驗數(shù)據(jù)
{
if(W25QXX_BUFFER[secoff+i]!=0XFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
W25QXX_Erase_Sector(secpos);//擦除這個扇區(qū)
for(i=0;i<secremain;i++)//復制
{
W25QXX_BUFFER[i+secoff]=pBuffer[i];
}
W25QXX_Write_NoCheck(W25QXX_BUFFER,secpos*4096,4096);//寫入整個扇區(qū)
}
else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//寫已經(jīng)擦除了的,直接寫入扇區(qū)剩余區(qū)間.
if(NumByteToWrite==secremain)break;//寫入結束了
else//寫入未結束
{
secpos++;//扇區(qū)地址增1
secoff=0;//偏移位置為0
pBuffer+=secremain; //指針偏移
WriteAddr+=secremain;//寫地址偏移
NumByteToWrite-=secremain;//字節(jié)數(shù)遞減
if(NumByteToWrite>4096)secremain=4096;//下一個扇區(qū)還是寫不完
else secremain=NumByteToWrite;//下一個扇區(qū)可以寫完了
}
};
}
//擦除整個芯片
//整片擦除時間:
//W25X16:25s
//W25X32:40s
//W25X64:40s
//等待時間超長...
void W25QXX_Erase_Chip(void)
{
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_ChipErase); //發(fā)送片擦除命令
F_CS_H;//取消片選
W25QXX_Wait_Busy();//等待芯片擦除結束
}
//擦除一個扇區(qū)
//Dst_Addr:扇區(qū)地址 0~511 for w25x16
//擦除一個扇區(qū)的最少時間:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
Dst_Addr*=4096;
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_SectorErase); //發(fā)送扇區(qū)擦除指令
SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16)); //發(fā)送24bit地址
SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8));
SPI2_ReadWriteByte((uint8_t)Dst_Addr);
F_CS_H;//取消片選
W25QXX_Wait_Busy();//等待擦除完成
}
//進入掉電模式
void W25QXX_PowerDown(void)
{
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_PowerDown); //發(fā)送掉電命令
F_CS_H;//取消片選
Delay_Us(3);//等待TPD
}
//喚醒
void W25QXX_WAKEUP(void)
{
F_CS_L;//使能器件
SPI2_ReadWriteByte(W25X_ReleasePowerDown); //send W25X_PowerDown command 0xAB
F_CS_H;//取消片選
Delay_Us(3); //等待TPD
}
5、驗證
添加必要的變量
const uint8_t TEXT_Buffer[]={"W25Q16 TEST"};//要寫入到W25Q16的字符串數(shù)組
#define SIZE sizeof(TEXT_Buffer)
uint16_t W25QXX_TYPE;//定義我們使用的flash芯片型號
uint32_t FLASH_SIZE=2*1024*1024; //FLASH 大小為2M字節(jié)
uint8_t datatemp[SIZE];
在main函數(shù)里循環(huán)讀寫驗證
W25QXX_TYPE = W25QXX_ReadID();
printf("W25QXX_TYPE == %04x\r\n",W25QXX_TYPE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("\r\nStart Write W25Q16....\r\n");
W25QXX_Write((uint8_t*)TEXT_Buffer,FLASH_SIZE-100,SIZE); //從倒數(shù)第100個地址處開始,寫入SIZE長度的數(shù)據(jù)
printf("W25Q16 Write Finished!\r\n");//提示傳送完成
Delay_Ms(1000);
printf("\r\nStart Read W25Q16....\r\n");
W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE); //從倒數(shù)第100個地址處開始,讀出SIZE個字節(jié)
printf("The Data Readed Is: ");//提示傳送完成
printf("%s\r\n",datatemp);//顯示讀到的字符串
Delay_Ms(1000);
}
/* USER CODE END 3 */
編譯下載后在串口助手中查看
文章來源:http://www.zghlxwxcb.cn/news/detail-436220.html
?可以看到,芯片的ID為0xef14,循環(huán)讀寫也成功了。文章來源地址http://www.zghlxwxcb.cn/news/detail-436220.html
到了這里,關于【STM32CubeMX學習】SPI讀寫W25Q16的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!