一、W25Q64簡介
1、W25Q64的內(nèi)存空間結(jié)構(gòu): ?一頁256字節(jié),4K(4096 字節(jié))為一個扇區(qū),16個扇區(qū)為1塊,容量為8M字節(jié),共有128個塊,2048 個扇區(qū)。 ?
2、W25Q64每頁大小由256字節(jié)組成,每頁的256字節(jié)用一次頁編程指令即可完成。
3、擦除指令分別支持: 16頁(1個扇區(qū))、128頁、256頁、全片擦除。?
二、電路圖
1、軟件模擬的SPI:線可以任意接
2、硬件模擬的SPI:要按以下方式連接
3、本次軟件模擬和硬件模擬使用同一個電路圖,方便切換
CS(片選):PA4? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?DO(從機輸出):PA6
CLK(時鐘):PA5? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?DI(從機輸入):PA7
三、軟件SPI讀寫W25Q64
1、SPI.c(初始化寄存器,實現(xiàn)讀取一個字節(jié)的功能)
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; //推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //上拉輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
MySPI_W_SS(1); //SS默認高電平(下降沿為開始工作,低電平狀態(tài)為工作中,上升沿為結(jié)束工作)
MySPI_W_SCK(0); //SCK默認為低電平(上升沿移入數(shù)據(jù),下降沿移出數(shù)據(jù))
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//模式0
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
//先SS下降沿,移出數(shù)據(jù),SCK上升沿,移入數(shù)據(jù),再SCK下降沿,移出數(shù)據(jù),下面只管主機,
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //將數(shù)據(jù)移出到MOSI線
MySPI_W_SCK(1); //上升沿移入數(shù)據(jù)
//當(dāng)MISO為1時,置變量指定位為1,當(dāng)MISO為0時,不做處理,指定位為默認的初值0
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //將移入的數(shù)據(jù)讀取出來
MySPI_W_SCK(0); //下降沿移出數(shù)據(jù)
}
return ByteReceive; //讀取出來的數(shù)據(jù)
}
/*
1、for循環(huán)的優(yōu)化
MySPI_W_MOSI(ByteSend & 0x80 ); //將數(shù)據(jù)移出到MOSI線
ByteSend << 1; //將數(shù)據(jù)左移動1位,去掉最高位,最低位置0
MySPI_W_SCK(1); //上升沿移入數(shù)據(jù)
if (MySPI_R_MISO() == 1){ByteSend |= 0x01;} //將移入的數(shù)據(jù)讀取出來,如果是0不管,如果是1,將最低位置1
MySPI_W_SCK(0); //下降沿移出數(shù)據(jù)
2、模式1
MySPI_W_SCK(1); //上升沿移出來數(shù)據(jù)
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //將數(shù)據(jù)移出到MOSI線
MySPI_W_SCK(0); //下降沿移入數(shù)據(jù)
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //將移入的數(shù)據(jù)讀取出來
*/
2、W25Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
/**
* 函 數(shù):W25Q64初始化
* 參 數(shù):無
* 返 回 值:無
*/
void W25Q64_Init(void)
{
MySPI_Init(); //先初始化底層的SPI
}
/**
* 函 數(shù):MPU6050讀取ID號
* 參 數(shù):MID 工廠ID,使用輸出參數(shù)的形式返回
* 參 數(shù):DID 設(shè)備ID,使用輸出參數(shù)的形式返回
* 返 回 值:無
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_JEDEC_ID); //交換發(fā)送讀取ID的指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交換接收MID,通過輸出參數(shù)返回
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交換接收DID高8位
*DID <<= 8; //高8位移到高位
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交換接收DID的低8位,通過輸出參數(shù)返回
MySPI_Stop(); //SPI終止
}
/**
* 函 數(shù):W25Q64寫使能
* 參 數(shù):無
* 返 回 值:無
*/
void W25Q64_WriteEnable(void)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交換發(fā)送寫使能的指令
MySPI_Stop(); //SPI終止
}
/**
* 函 數(shù):W25Q64等待忙
* 參 數(shù):無
* 返 回 值:無
*/
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交換發(fā)送讀狀態(tài)寄存器1的指令
Timeout = 100000; //給定超時計數(shù)時間
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循環(huán)等待忙標(biāo)志位
{
Timeout --; //等待時,計數(shù)值自減
if (Timeout == 0) //自減到0后,等待超時
{
/*超時的錯誤處理代碼,可以添加到此處*/
break; //跳出等待,不等了
}
}
MySPI_Stop(); //SPI終止
}
/**
* 函 數(shù):W25Q64頁編程
* 參 數(shù):Address 頁編程的起始地址,范圍:0x000000~0x7FFFFF
* 參 數(shù):DataArray 用于寫入數(shù)據(jù)的數(shù)組
* 參 數(shù):Count 要寫入數(shù)據(jù)的數(shù)量,范圍:0~256
* 返 回 值:無
* 注意事項:寫入的地址范圍不能跨頁
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable(); //寫使能
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交換發(fā)送頁編程的指令
MySPI_SwapByte(Address >> 16); //交換發(fā)送地址23~16位
MySPI_SwapByte(Address >> 8); //交換發(fā)送地址15~8位
MySPI_SwapByte(Address); //交換發(fā)送地址7~0位
for (i = 0; i < Count; i ++) //循環(huán)Count次
{
MySPI_SwapByte(DataArray[i]); //依次在起始地址后寫入數(shù)據(jù)
}
MySPI_Stop(); //SPI終止
W25Q64_WaitBusy(); //等待忙
}
/**
* 函 數(shù):W25Q64扇區(qū)擦除(4KB)
* 參 數(shù):Address 指定扇區(qū)的地址,范圍:0x000000~0x7FFFFF
* 返 回 值:無
*/
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable(); //寫使能
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交換發(fā)送扇區(qū)擦除的指令
MySPI_SwapByte(Address >> 16); //交換發(fā)送地址23~16位
MySPI_SwapByte(Address >> 8); //交換發(fā)送地址15~8位
MySPI_SwapByte(Address); //交換發(fā)送地址7~0位
MySPI_Stop(); //SPI終止
W25Q64_WaitBusy(); //等待忙
}
/**
* 函 數(shù):W25Q64讀取數(shù)據(jù)
* 參 數(shù):Address 讀取數(shù)據(jù)的起始地址,范圍:0x000000~0x7FFFFF
* 參 數(shù):DataArray 用于接收讀取數(shù)據(jù)的數(shù)組,通過輸出參數(shù)返回
* 參 數(shù):Count 要讀取數(shù)據(jù)的數(shù)量,范圍:0~0x800000
* 返 回 值:無
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_READ_DATA); //交換發(fā)送讀取數(shù)據(jù)的指令
MySPI_SwapByte(Address >> 16); //交換發(fā)送地址23~16位
MySPI_SwapByte(Address >> 8); //交換發(fā)送地址15~8位
MySPI_SwapByte(Address); //交換發(fā)送地址7~0位
for (i = 0; i < Count; i ++) //循環(huán)Count次
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后讀取數(shù)據(jù)
}
MySPI_Stop(); //SPI終止
}
3、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID; //定義用于存放MID號的變量
uint16_t DID; //定義用于存放DID號的變量
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; //定義要寫入數(shù)據(jù)的測試數(shù)組
uint8_t ArrayRead[4]; //定義要讀取數(shù)據(jù)的測試數(shù)組
int main(void)
{
/*模塊初始化*/
OLED_Init(); //OLED初始化
W25Q64_Init(); //W25Q64初始化
/*顯示靜態(tài)字符串*/
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
/*顯示ID號*/
W25Q64_ReadID(&MID, &DID); //獲取W25Q64的ID號
OLED_ShowHexNum(1, 5, MID, 2); //顯示MID
OLED_ShowHexNum(1, 12, DID, 4); //顯示DID
W25Q64_SectorErase(0x000000); //扇區(qū)擦除
/*
驗證扇區(qū)擦除功能(方法:注釋掉這一句)
如果不擦除,一開始寫入AA、BB、CC、DD,后面再次寫入55、66、77、88,則讀出來00、22、44、88
即如果不進行擦除,則讀出的數(shù)據(jù)=原始數(shù)據(jù)&寫入的數(shù)據(jù)
*/
W25Q64_PageProgram(0x000000, ArrayWrite, 4); //將寫入數(shù)據(jù)的測試數(shù)組寫入到W25Q64中
W25Q64_ReadData(0x000000, ArrayRead, 4); //讀取剛寫入的測試數(shù)據(jù)到讀取數(shù)據(jù)的測試數(shù)組中
/*
數(shù)據(jù)是不能跨頁寫入
驗證:
若寫入55 66 77 88
W25Q64_PageProgram(0x0000FF, ArrayWrite, 4); //數(shù)據(jù)不能跨頁寫入,66 77 88返回到頁首寫入
W25Q64_ReadData(0x0000FF, ArrayRead, 4); //讀取數(shù)據(jù)可以跨頁讀出
則讀出的是55 FF FF FF ,F(xiàn)F為第二頁的數(shù)據(jù),第二頁是擦除了的,沒有寫入,默認是FF
W25Q64_PageProgram(0x0000FF, ArrayWrite, 4);
W25Q64_ReadData(0x000000, ArrayRead, 4); //讀出:66 77 88 FF
*/
/*顯示數(shù)據(jù)*/
OLED_ShowHexNum(2, 3, ArrayWrite[0], 2); //顯示寫入數(shù)據(jù)的測試數(shù)組
OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
OLED_ShowHexNum(3, 3, ArrayRead[0], 2); //顯示讀取數(shù)據(jù)的測試數(shù)組
OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
while (1)
{
}
}
?四、硬件讀寫I2C文章來源:http://www.zghlxwxcb.cn/news/detail-824068.html
只需要在軟件的基礎(chǔ)上添加以下的代碼文章來源地址http://www.zghlxwxcb.cn/news/detail-824068.html
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)//SS還是軟件模擬
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
/*
初始化步驟
1、開啟時鐘
2、初始化GPIO口
(1)SCK、MOSI是由硬件外設(shè)控制的輸出信號:復(fù)用推挽輸出
(2)MISO是硬件外設(shè)的輸入信號:上拉輸入(輸入設(shè)備可以有很多個,不存在復(fù)用輸入)
(3)SS為軟件控制的輸出信號,配置為通用推挽輸出
3、配置SPI外設(shè): 用結(jié)構(gòu)體
4、開關(guān)控制:調(diào)用SPI_Cmd,給SPI使能
*/
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽輸出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//復(fù)用推挽輸出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主機
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //雙線全雙工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位數(shù)據(jù)幀
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率預(yù)分頻器,配置SCK時鐘的頻率.SPI1:72MHz/128,SPI2:36MHz/128
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //默認低電平,空閑默認低電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //第一個邊沿開始采樣(移入)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //一般選擇軟件NSS模式(不用了解)
SPI_InitStructure.SPI_CRCPolynomial = 7; //隨便填
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
MySPI_W_SS(1);//默認不選中從機
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
/*
等待TXE為1,發(fā)送寄存器為空,發(fā)送寄存器不為空,先不著急寫
過程:
寫入數(shù)據(jù)到TDR-->轉(zhuǎn)移到移位寄存器——>一旦移位寄存器有數(shù)據(jù),時序波形就會自動產(chǎn)生(則ByteSend就會通過MOSI一位一位地移出去)
——>在MOSI線上,就自動產(chǎn)生發(fā)送的時序波形
由于是非連續(xù)傳輸,時序產(chǎn)生的時間內(nèi),不必提前把下個數(shù)據(jù)放到TDR,直接等待這段時間過去就行
在發(fā)送的同時,MISO會移位進行接收
發(fā)送和接收是同步
接收移位完成時,會收到一個字節(jié)數(shù)據(jù)這時會置標(biāo)志位置RXNE
*/
/*
步驟總結(jié)(完成一個字節(jié)的交換)
1、等待TXE為1
2、寫發(fā)送的數(shù)據(jù)至TDR,一旦TDR寫出數(shù)據(jù)來。時序就會自動生成
3、等待RXNE為1,發(fā)送完成,即接收完成,RXNE置1
4、讀取RDR接收的數(shù)據(jù),就是置換接收的一個字節(jié)
注意:(1)必須是發(fā)送,同時接收,要先寫東西,觸發(fā)時序
(2)根據(jù)手冊,
發(fā)送緩沖器空閑標(biāo)志(TXE):寫入DR時,會順便執(zhí)行清楚TXE的操作,無須手動清除
接收緩沖器非空(RXNE):讀取SPI數(shù)據(jù)寄存器可以清除此標(biāo)志
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);//若沒有數(shù)據(jù),則MOSI線為SET
SPI_I2S_SendData(SPI1, ByteSend);//ByteSend為要寫入到DR,即TDR的數(shù)據(jù)(要發(fā)送的數(shù)據(jù)),之后會轉(zhuǎn)入到移位寄存器
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//接收數(shù)據(jù)完畢的時候,MISO線有標(biāo)志位RXNE
return SPI_I2S_ReceiveData(SPI1);//讀取DR,從RDR中,把交換接收的數(shù)據(jù)讀取出來,返回值為RDR接收的數(shù)據(jù)
}
到了這里,關(guān)于26、江科大stm32視頻學(xué)習(xí)筆記——I2C讀寫W25Q64的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!