ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32学习27--SPI通信之W25Q64

STM32学习27--SPI通信之W25Q64 一、W25Q64详解1、简介W25Qxx是低成本的非易失性存储器常用于数据存储、字库存储、固件程序存储等存储介质Nor Flash(闪存)W25Q64是8M存储3.3V供电标准四线SPI24位地址时钟频率80MHz/160MHz双重SPI/324MHz四重SPI2、硬件电路注CS斜杆/或上横线表示低电平有效PA5→SCKPA6→MISOPA7→MOSIPA4→CS普通GPIO输出软件控制WP、HOLD直接接3.3VVCC接3.3V电源就近加0.1uF滤波电容3、存储结构Nor Flash特性总容量8MB8388608字节24位地址0x000000~0x7FFFFF;1)页Page256字节页编程0x02一次最多写256个字节写超过页边界会覆盖本页开头不能按字节写写之前必须擦除2扇区Sector4KB4096字节最小擦除单元扇区擦除指令0x20擦除后全部变成0xFF不能只擦除几个字节。改1个字节要把整个4KB扇区读出→修改→擦除→回写3块Block64KB块擦除0xD8一次擦除64KB4全片擦除0xC7擦完整8MB耗时约十几秒谨慎使用5NOR Flash铁律擦除1→0写只能把1写成0不能把0写成1想写新数据必须先擦除对应扇区擦完才全是0xFF4、SPI时序模式W25Q64支持SPI Mode0(CPOL0,CPHA0)模式主机模式、双线全双工、8bit、MSB First(高位先行SPI1挂载APB272MHz预分频建议4→18MHz最高不超过20MHz。5、最常用指令指令码指令名称作用0x9FJEDEC_ID读芯片IDW25Q64返回0xEF 0x40 0x17用来判断芯片是否正常0x06Write Enable写使能写/擦除之前必须先发这条指令0x05Read Status Register读状态寄存器bit0 BUSY1正在擦写0空闲必须轮询等待0x03Read Data读数据读flash命令3字节地址之后连续读字节地址自动递增0x02Page Program页编程写数据命令3字节地址最多256字节数据0x20Sector Erase扇区擦除擦除4KB最小擦除单位0xD8Block Erase块擦除擦除64KB0xC7Chip Erase全片擦除擦全部8MB注1状态寄存器STATUS REGISTERBUSY是状态寄存器的只读第0位S0当芯片正在执行页编程Page Program写数据、扇区擦除、块擦除、全片擦除、写状态寄存器的时候不是瞬间完成→BUSY1忙。芯片忙的时候除读状态寄存器和擦除暂停指令外其他所有指令全部忽略。等写、擦除动作全部完成之后硬件自动把BUSY置0→芯片空闲可接受新指令。2Write Enable写使能页编程、扇区擦除、块擦除、全片擦除、写状态寄存器之前必须先写使能写使能时序CS拉低→SPI发送1字节命令0x06→CS拉高只需要发送这一个字节不需要地址不需要数据void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); }每次做擦除/页写都有重新调用一次Write Enable。不管是页编程结束、擦除结束芯片硬件会自动把WEL位清零不是开一次写使能就永久生效干完一次写/擦除WEL就清零下一次操作必须重新发0x06。即Write Enable→扇区擦除→WaitBusy→Write Enable→页写→WaitBusy;读指令不需要写使能发送完0x06后CS必须拉高WEL才会置1如果一直CS拉低WEL不会生效扇区擦除代码void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable();//擦除前必须写使能 MySPI_Start(); MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//扇区擦除命令 MySPI_SwapByte((Address16)0xFF); MySPI_SwapByte((Address8)0xFF); MySPI_SwapByte((Address)0xFF); MySPI_Stop(); W25Q64_WaitBusy(); //等待擦除完成WEL自动清零 }一句话总结只要是改动Flash写、擦、改寄存器必须先发0x06只读不用。每一次写/擦操作都要重新0x06。3页编程 page program命令0x02 写数据向已经擦除过值为0xFF的Flash地址写入1~256字节1页数据时序CS拉低→发命令0x02→24bit地址A23--A0→发送1~256字节数据→CS拉低芯片开始内部烧写。24bit地址理解24bit地址高8位地址字节中8位地址字节低8位地址字节举例要写地址0x123456拆分24位addr 0x123456;addr_h (addr16)0xFF//0x12最高字节A23~A16。addr_m (addr8)0xFF//0x34最高字节A15~A8。addr_l addr0xFF//0x56最高字节A7~A0。SPI发送顺序0x02→addr_h→addr_m→addr_l→后续数据字节但写代码时uint32_t addr 0x00ABCDEF32位无符号整数。32为内存布局bit31~bit24bit23~bit16bit15~bit8bit7~bit00x000xAB0xCD0xEFW25Q64需要24bit地址bit23~bit0对应24bit高字节bit23~bit16→值0xAB24bit中字节bit15~bit8→值0xCD24bit低字节bit7~bit0→值0xEF那为啥32位变量右移16位能得到24bit的高字节呢我们操作的是32位变量里面包含的那24个有效地址位。addr16把整个32位数整体向右移16位0x000000AB再0xFF只保留最低8位取出0xAB即addr_h (addr16)0xFF→0xAB;addr_m (addr8)0xFF→0xCDaddr_l addr0xFF→0xEF向右移16位结果仍然是32位uint32_t类型强制转换为uint8_t或者0xFF才能截取8bit字节才能送给SPI发送函数函数参数是uint8_t。void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Counter) { uint16_t i; if(DataArray NULL ||Counter 0){return ;} //空指针检查 W25Q64_WriteEnable(); MySPI_Start(); MySPI_SwapByte(W25Q64_PAGE_PROGRAM); MySPI_SwapByte((Address16)0xFF); MySPI_SwapByte((Address8)0xFF); MySPI_SwapByte((Address)0xFF); for(i 0;iCounter;i) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); W25Q64_WaitBusy(); }注1页编程最大单次256字节如果大于256连续写入多字节时最多写入一页的数据超过页尾位置的数据会回到页首覆盖写入。地址0x0000FFCounter 10直接调用本函数就发送覆盖问题。2指针判断是否为空如果传入DataArray NULL空指针for循环访问DataArray[i]直接死机。3只有CS拉高之后就是MySPI_Stop()执行后芯片才真正启动那个页编程。4读写记忆口诀写外设把要发的数据填进MySPI_SwapByte函数里读外设MySPI_SwapByte括号内填哑字节返回值存进变量比如数组里4读数据Read Data命令0x03普通读指令特点可以连续读时钟不停地址自动1软件模拟优先用0x03时序CS拉低→DI引脚MOSI主机发送1字节命令0x03→继续发24bit地址A23~A0高字节→中间字节→低字节和页编程地址格式一样。24位地址发完之后DI(MOSI可以随便发哑字节0xFF只用来产生SPI时钟脉冲DOMISO引脚W25Q向外输出存储的数据高位MSB先输出。每读完1字节芯片内部地址硬件自动1只要CLK持续给时钟就可以源源不断连续读下去不用重发命令和地址。读完想要的字节把/CS拉高不要中途拉高不然重发命令0x03结束读指令如果Flash正在忙BUSY1在擦除或写此时指令被忽略读出的数据无效。等到BUSY0时再调用读函数void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint16_t Counter) { uint16_t i; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_DATA); MySPI_SwapByte((Address16)0xFF); MySPI_SwapByte((Address8)0xFF); MySPI_SwapByte((Address)0xFF); for(i0;iCounter;i) { DataArray[i] MySPI_SwapByte(W25Q64_DUMMY_BYTE); } MySPI_Stop(); }二、读取W25Q64数据接文章STM32学习26-SPI通信1软件模拟代码W25Q64.c文件#include stm32f10x.h #include MySPI.h #include W25Q64_Ins.h #include stddef.h void W25Q64_Init() { MySPI_Init(); } void W25Q64_ReadID(uint8_t*MID,uint16_t*DID) { MySPI_Start(); MySPI_SwapByte(W25Q64_JEDEC_ID); *MID MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID 8; *DID|MySPI_SwapByte(W25Q64_DUMMY_BYTE); MySPI_Stop(); } void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); } void W25Q64_WaitBusy(void) { uint32_t Timeout 100000; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) (0x01)) 0x01) { Timeout--; if(Timeout 0) { break; } } MySPI_Stop(); } void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Counter) { uint16_t i; if(DataArray NULL ||Counter 0){return ;} //空指针检查 W25Q64_WriteEnable(); MySPI_Start(); MySPI_SwapByte(W25Q64_PAGE_PROGRAM); MySPI_SwapByte((Address16)0xFF); MySPI_SwapByte((Address8)0xFF); MySPI_SwapByte((Address)0xFF); for(i 0;iCounter;i) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); W25Q64_WaitBusy(); } void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); MySPI_Start(); MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); MySPI_SwapByte((Address16)0xFF); MySPI_SwapByte((Address8)0xFF); MySPI_SwapByte((Address)0xFF); MySPI_Stop(); W25Q64_WaitBusy(); } void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint16_t Counter) { uint16_t i; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_DATA); MySPI_SwapByte((Address16)0xFF); MySPI_SwapByte((Address8)0xFF); MySPI_SwapByte((Address)0xFF); for(i0;iCounter;i) { DataArray[i] MySPI_SwapByte(W25Q64_DUMMY_BYTE); } MySPI_Stop(); }2、W25Q64.h文件#ifndef __W25Q64_H #define __W25Q64_H void W25Q64_Init(); void W25Q64_ReadID(uint8_t*MID,uint16_t*DID); void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Counter); void W25Q64_SectorErase(uint32_t Address); void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint16_t Counter); #endif3、main函数#include stm32f10x.h #include Delay.h #include OLED.h #include W25Q64.h uint8_t MID; uint16_t DID; uint8_t ArrayWrite[4] {0x01,0x02,0x0E,0x04}; uint8_t ArrayRead[4]; int main() { OLED_Init(); W25Q64_Init(); OLED_ShowString(1,1,ID:); W25Q64_ReadID(MID,DID); OLED_ShowHexNum(1,4,MID,2); OLED_ShowHexNum(1,7,DID,4); W25Q64_SectorErase(0x000000);//先擦除再写 W25Q64_PageProgram(0x000000,ArrayWrite,4); OLED_ShowHexNum(2,1,ArrayWrite[0],2); OLED_ShowHexNum(2,4,ArrayWrite[1],2); OLED_ShowHexNum(2,7,ArrayWrite[2],2); OLED_ShowHexNum(2,10,ArrayWrite[3],2); W25Q64_ReadData(0x000000,ArrayRead,4); OLED_ShowHexNum(3,1,ArrayRead[0],2); OLED_ShowHexNum(3,4,ArrayRead[1],2); OLED_ShowHexNum(3,7,ArrayRead[2],2); OLED_ShowHexNum(3,10,ArrayRead[3],2); while(1) { } }注1NULL需要包含头文件#include stddef.h2W25Q64_PageProgram(0x000000,ArrayWrite,4);地址改成0x0000FD测试超过页尾的数据回到页首覆盖问题。本文完
返回列表