宁波专业网站定制制作服务,服务器运维,关于网站建设的期刊文献,网站 微信小程序怎么做1. W25Q64简介
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器#xff0c;常应用于数据存储、字库存储、固件程序存储等场景存储介质#xff1a;Nor Flash#xff08;闪存#xff09;时钟频率#xff1a;80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)存储容…1. W25Q64简介
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器常应用于数据存储、字库存储、固件程序存储等场景存储介质Nor Flash闪存时钟频率80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)存储容量24位地址 W25Q40 4Mbit / 512KByte W25Q80 8Mbit / 1MByte W25Q16 16Mbit / 2MByte W25Q32 32Mbit / 4MByte W25Q64 64Mbit / 8MByte W25Q128 128Mbit / 16MByte W25Q256 256Mbit / 32MByte 1.1 硬件电路
CS上面画条横线或者左边画/表示低电平有效。 1.2 W25Q64框图
W25Q64的容量是8MB如果不进行划分只按照一整块来使用 则容量太大不利于管理所以需要进行划分。常见划分方式为一整块存储空间先划分为若干的块Block其中每一块再划分为若干的扇区Sector对于每个扇区内部又可以分成很多页Page。 1.3 Flash操作注意事项
写入操作时
写入操作前必须先进行写使能每个数据位只能由1改写为0不能由0改写为1 比如在某一个字节的存储单元内存储了0xAA1010 1010这个数据如果直接再次在这个存储单元写入新的数据0x550101 0101则这个存储单元最终的数据为0x00写入数据前必须先擦除擦除后所有数据位变为1 比如擦除后所有位变成1也就是0xFF1111 1111此时写入0x550101 0101这样根据第二条规则存储单元最终的数据为0x55擦除必须按最小擦除单元进行 在W25Q64中可以选择整个芯片擦除、按块擦除、按扇区擦除所以最小的擦除单元就是一个扇区4KB就是4096个字节所以擦除最少就需要4096个字节一起擦连续写入多字节时最多写入一页的数据超过页尾位置的数据会回到页首覆盖写入 因为页缓存区只有256Byte写入操作结束后芯片进入忙状态不响应新的读写操作
读取操作时
直接调用读取时序无需使能无需额外操作没有页的限制读取操作结束后不会进入忙状态但不能在忙状态时读取
2. 软件SPI读写W25Q64
2.1 接线图
SPI的四根通信线CS、DO、CLK和DI因为这里使用软件模拟SPI所以这4根线可以接到STM32的任意GPIO口。
CS片选接到PA4、DO从机输出接到PA6、CLK时钟接到PA5、DI从机输入接到PA7 2.2 代码
先建立一个MySPI模块这个模块中主要包含通信引脚封装、初始化以及SPI通信的3个拼图起始、终止和交换一个字节。
然后基于SPI层再建立一个W25Q64的模块在此模块中调用底层SPI的拼图来拼接各种指令和功能的完整时序比如写使能、擦除、页编程、读数据等。这一层可以称为W25Q64的硬件驱动层。
最后在主函数中调用驱动层的函数实现想要的功能。
MySPI.c
#include stm32f10x.h // Device header// 写SS/CS引脚
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}// 写SCK/CLK引脚
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}// 写MOSI/DI引脚
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}// 读MISO/DO引脚
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;// CS/SS(PA4),CLK/SCK(PA5),DI/MOSI(PA7)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;// DO/MISO(PA6)GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);MySPI_W_SS(1);// 默认高电平不选中从机MySPI_W_SCK(0);// 使用SPI模式0所以默认是低电平
}// 时序基本单元起始条件
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){MySPI_W_MOSI(ByteSend (0x80 i));// 发送ByteSend最高位、次高位...MySPI_W_SCK(1);// 产生上升沿。上升沿时从机会自动把MOSI的数据读走if(MySPI_R_MISO() 1){ByteReceive | (0x80 i);}// 如果读到MISO的数据位是1则把最高位、次高位...存到ByteReceive中MySPI_W_SCK(0);// 产生下降沿。}return ByteReceive;
}MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_Hvoid MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);#endif
W25Q64.c
#include stm32f10x.h // Device header
#include MySPI.h
#include W25Q64_Ins.hvoid W25Q64_Init(void)
{MySPI_Init();
}// 读取ID。因为函数有2个返回值所以使用指针实现
// MID为厂商IDDID为设备ID
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();// SS引脚置低开始传输MySPI_SwapByte(W25Q64_JEDEC_ID);// 发送0x9F(宏定义W25Q64_JEDEC_ID)返回值不使用。0x9F根据手册代表读ID号指令*MID MySPI_SwapByte(W25Q64_DUMMY_BYTE);// 0xFF无意义(宏定义W25Q64_DUMMY_BYTE)这句的作用是把从机有意义的数据置换过来*DID MySPI_SwapByte(W25Q64_DUMMY_BYTE);// 0xFF无意义置换出设备ID的高8位*DID 8;// 把第一次读到的数据运到DID的高8位*DID | MySPI_SwapByte(W25Q64_DUMMY_BYTE);// 0xFF无意义置换出设备ID的低8位MySPI_Stop();
}// 写使能
void W25Q64_WriteEnable(void)
{MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);MySPI_Stop();
}// 等待忙
void W25Q64_WaitBusy(void)
{uint32_t Timeout;MySPI_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);Timeout 100000;// 给定超时计数时间while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) 0x01) 0x01)// 返回值是状态寄存器1取出最低位{Timeout --;// 等待时计数值自减if (Timeout 0)// 自减到0后等待超时{/*超时的错误处理代码可以添加到此处*/break;// 跳出等待不等了}}MySPI_Stop();
}/*** 函 数W25Q64页编程* 参 数Address 页编程的起始地址范围0x000000~0x7FFFFF* 参 数DataArray 用于写入数据的数组* 参 数Count 要写入数据的数量范围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); //交换发送页编程的指令MySPI_SwapByte(Address 16); //交换发送地址23~16位。例如0x123456这里交换0x12MySPI_SwapByte(Address 8); //交换发送地址15~8位。右移8位为0x1234由于交换字节函数只能接收8位数据所以高位舍弃实际发送0x34MySPI_SwapByte(Address); //交换发送地址7~0位。由于交换字节函数只能接收8位数据所以高位舍弃实际发送0x56for (i 0; i Count; i ) //循环Count次{MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据}MySPI_Stop(); //SPI终止W25Q64_WaitBusy(); //等待忙
}/*** 函 数W25Q64扇区擦除4KB* 参 数Address 指定扇区的地址范围0x000000~0x7FFFFF* 返 回 值无*/
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable(); //写使能MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令MySPI_SwapByte(Address 16); //交换发送地址23~16位。例如0x123456这里交换0x12MySPI_SwapByte(Address 8); //交换发送地址15~8位。右移8位为0x1234由于交换字节函数只能接收8位数据所以高位舍弃实际发送0x34MySPI_SwapByte(Address); //交换发送地址7~0位。由于交换字节函数只能接收8位数据所以高位舍弃实际发送0x56MySPI_Stop(); //SPI终止W25Q64_WaitBusy(); //等待忙
}/*** 函 数W25Q64读取数据* 参 数Address 读取数据的起始地址范围0x000000~0x7FFFFF* 参 数DataArray 用于接收读取数据的数组通过输出参数返回* 参 数Count 要读取数据的数量范围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); //交换发送读取数据的指令MySPI_SwapByte(Address 16); //交换发送地址23~16位MySPI_SwapByte(Address 8); //交换发送地址15~8位MySPI_SwapByte(Address); //交换发送地址7~0位for (i 0; i Count; i ) //循环Count次{DataArray[i] MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据}MySPI_Stop(); //SPI终止
}W25Q64.h
#ifndef __W25Q64_H
#define __W25Q64_Hvoid W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);#endifW25Q64_Ins.h
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3#define W25Q64_DUMMY_BYTE 0xFF#endifmain.c
#include stm32f10x.h // Device
#include OLED.h
#include W25Q64.huint8_t MID; //定义用于存放MID号的变量
uint16_t DID; //定义用于存放DID号的变量
uint8_t ArrayWrite[] {0xAA, 0xBB, 0xCC, 0xDD}; //定义要写入数据的测试数组
uint8_t ArrayRead[4]; //定义要读取数据的测试数组int main(void)
{OLED_Init();W25Q64_Init();/*显示静态字符串*/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); //显示MIDOLED_ShowHexNum(1, 12, DID, 4); //显示DID/*W25Q64功能函数测试*/W25Q64_SectorErase(0x000000); //扇区擦除W25Q64_PageProgram(0x000000, ArrayWrite, 4); //将写入数据的测试数组写入到W25Q64中W25Q64_ReadData(0x000000, ArrayRead, 4); //读取刚写入的测试数据到读取数据的测试数组中/*显示数据*/OLED_ShowHexNum(2, 3, ArrayWrite[0], 2); //显示写入数据的测试数组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); //显示读取数据的测试数组OLED_ShowHexNum(3, 6, ArrayRead[1], 2);OLED_ShowHexNum(3, 9, ArrayRead[2], 2);OLED_ShowHexNum(3, 12, ArrayRead[3], 2);while(1){}
}其他引用的头文件和c代码可在此处查阅OLED.h【江协STM32】4 OLED调试工具