之前, 一直觉得SPI和w25q128都是很复杂的操作.
看过野火的示例代码, .....哗, c代码+注释几百行, h文件也过百, 涉及函数记不清有多少, 反正很高大上.
原子哥的, 翻查参考数次, 寄存器版本的很精简, 但新手想理解其中的分扇区和分页逻辑还有点吃力.
之前在LTDC的屏显上, 用来存取字库, 蒙查查地东拼西凑, 反正能正常工作, 没出问题, 嘻~
这两天反复参查, 更深入学习后, 精简了代码, 主要是write分页部分, 使其脉络更清晰.
函数也只留三个全局函数, 使用时, 修改头文件就可以使用.
三个主函数: Init, Read, Write
五个基本功能函数: sendByte, writeSector, writeEnable, earseSector, waitReady
/*==================================================================================================================
* 文件名称 W25Qxx.c
* 功能描述
* 创建信息 L 2019.5.11
*
=================================================================================================================*/
#include "w25qx.h"
#include "led.h"
u16 W25QxxType = 0 ;
void vW25qx_ReadID(void);
// 5个基本功能函数
u8 cW25qx_SendByte(u8 d); // 5_1
void vW25qx_writeEnable(void) ; // 5_2
void vW25qx_waitReady(void) ; // 5_3
void vW25qx_eraseSector(u32 addr); // 5_4
void vW25qx_writeSector(char* p,u32 addr,u16 num); // 5_5
/*****************************************************************************
* @Fun W25Qxx_Init
* @brief 字模存储设备
* @arg
*
* @return
*
*/
void vW25qx_Init()
{
// NSS W25Qx设备片选线
vSys_SetGPIO (W25Qx_NSS_GPIOx , W25Qx_NSS_PIN , 1,0,2,1,0);
W25QX_NSS_HIGH ; // 片选拉高
// SPI_GPIO
vSys_SetGPIO (W25Qx_CLK_GPIOx , W25Qx_CLK_PIN ,2,0,2,1,W25Qx_SPIx_AFx ); // SPI_CLK
vSys_SetGPIO (W25Qx_MISO_GPIOx ,W25Qx_MISO_PIN ,2,0,2,1,W25Qx_SPIx_AFx ); // SPI_MISO
vSys_SetGPIO (W25Qx_MOSI_GPIOx ,W25Qx_MOSI_PIN, 2,0,2,1,W25Qx_SPIx_AFx ); // SPI_MOSI
// SPI_通信配置
W25Qx_RCC_EN ; // 使能SPI
RCC->APB2RSTR |= 0x1<<20; // 复位SPI5
RCC->APB2RSTR &=~(0x1<<20); // 停止复位SPI5
W25Qx_SPIx -> CR1 = 0x1<<0; // CPHA:时钟相位,0x1=在第2个时钟边沿进行数据采样
W25Qx_SPIx -> CR1 |= 0x1<<1; // CPOL:时钟极性,0x1=空闲状态时,SCK保持高电平
W25Qx_SPIx -> CR1 |= 0x1<<2; // 主从模式: 1 = 主配置
W25Qx_SPIx -> CR1 |= 0x0<<3; // 波特率控制[5:3]: 0 = fPCLK /2
W25Qx_SPIx -> CR1 |= 0x0<<7; // 帧格式: 0 = 先发送MSB
W25Qx_SPIx -> CR1 |= 0x1<<9; // 软件从器件管理 : 1 = 使能软件从器件管理(软件NSS)
W25Qx_SPIx -> CR1 |= 0x1<<8; // 内部从器件选择,根据9位设置(失能内部NSS)
W25Qx_SPIx -> CR1 |= 0x0<<11; // 数据帧格式, 0 = 8位
W25Qx_SPIx -> CR1 |= 0x1<<6; // SPI使能 1 = 使能外设
print("Flash存储 配置完成");
vW25qx_ReadID(); // 读取芯片型号,以判断通讯是否正常
}
/*****************************************************************************
* @Fun W25Qxx_ReadID
* @brief 读取芯片型号,用于判断通讯状况
* @arg
*
* @return 芯片型号值
*
*/
void vW25qx_ReadID()
{
// 1: 读取芯片型号, 判断联接状况
W25QX_NSS_LOW;
cW25qx_SendByte(0x90); // 发送读取ID命令,命令分两分,第一字节是命令,第四字节是0
cW25qx_SendByte(0x00);
cW25qx_SendByte(0x00);
cW25qx_SendByte(0x00); // 第四字节必节须是 0h
W25QxxType = cW25qx_SendByte(0x00)<<8; // u16 W25QxxType 在本文件定义,全局
W25QxxType |= cW25qx_SendByte(0x00);
W25QX_NSS_HIGH;
switch (W25QxxType)
{
case W25Q16:
sprintf(Data.w25qx_Type ,"%s","W25Q16");
break;
case W25Q32:
sprintf(Data.w25qx_Type ,"%s","W25Q32");
break;
case W25Q64:
sprintf(Data.w25qx_Type ,"%s","W25Q64");
break;
case W25Q128:
sprintf(Data.w25qx_Type ,"%s","W25Q128");
break;
case W25Q256:
sprintf(Data.w25qx_Type ,"%s","W25Q256"); // 注意:W25Q256的地址是4字节
break;
default:
sprintf(Data.w25qx_Type ,"%s","Flash设备失败 !!!");
Flag.w25qx_Fail =1; // 设备失败
break;
}
// 2:读取存储数据, 增加启动次数记录
if(Flag.w25qx_Fail !=1 )
{
u32 numAddr=0x00000000; // 数据地址, 0x0000:标志0xEE, 0x0001:标志0X00, 0x0002:数据高位, 0x0003:数据低位
char d[4]; //
u16 f =0; // 标志
u16 num =0; // 启动次数
vW25qx_Read( d, numAddr, 4); // 读取4个字节数据
f = (d[0]<<8) | d[1]; // 标志
num = (d[2]<<8) | d[3]; // 启动次数
//printf("f= 0X%X, num= %d\n", f, num);
if(f!=0xEE00) {
num=1;
d[2]=0;
d[3]=1;
}
else{
num++;
d[2]=(u8)(num>>8);
d[3]=(u8)num;
}
d[0]=0xEE;
d[1]=0x00;
vW25qx_Write(d, numAddr ,4);
Data.w25qx_Num =num;
}
if( Flag.w25qx_Fail ==1) vLed_RedOn ();
char c[45]=" ";
sprintf (c," 存储设备:%s ,第%d次使用!", Data.w25qx_Type, Data.w25qx_Num);
print(c);
}
/******************************************************************************
* @Function W25Qxx_Read 全局 4_3
* @Description 读取数据
*
* @Input u8 *p 读出的数值存放位置
* u32 addr 读取地址
* u16 num 连续读取的字节数
*
* @Return
*
**/
void vW25qx_Read(char *p,u32 addr, u16 num)
{
W25QX_NSS_LOW ;
cW25qx_SendByte ( 0x03); // 发送读取命令 03h
cW25qx_SendByte ((u8)(addr>>16));
cW25qx_SendByte ((u8)(addr>>8));
cW25qx_SendByte ((u8)addr);
for(int i=0;i4096)?4096:num; // 计算新扇区写入字节数
}
}
}
// 内部功能函数
// *******************************************************************************************************************************
// 5_1 发送1字节,返回1字节
// SPI通信,只一个动作:向DR写入从设命令值,同步读出数据!写读组合,按从设时序图来. 作为主设,因为收发同步,连接收发送中断也不用开,未验证其它中断对其工作的影响.
u8 cW25qx_SendByte(u8 d)
{
while((W25Qx_SPIx ->SR&(0x1<<1)) == 0); // 等待发送区为空
W25Qx_SPIx ->DR =d;
while((W25Qx_SPIx->SR&(0x1<<0)) == 0); // 等待接收完数据
return W25Qx_SPIx->DR ;
}
// 5_2 写使能
void vW25qx_writeEnable()
{
W25QX_NSS_LOW ;
cW25qx_SendByte (0x6); // 命令: Write Enable : 06h
W25QX_NSS_HIGH ;
}
// 5_3 等待空闲
void vW25qx_waitReady()
{
u8 t=1;
W25QX_NSS_LOW ;
cW25qx_SendByte (0x5); // 命令: Read Status Register : 05h
while((t&0x1)==1)
t=cW25qx_SendByte(0x00); // 只要发送读状态寄存器指令,芯片就会持续向主机发送最新的状态寄存器内容 ,直到收到通信的停止信号。
W25QX_NSS_HIGH ;
}
// 5_4 擦除一个扇区, 每扇区>150ms
void vW25qx_eraseSector(u32 addr)
{
addr=addr*4096; // 从第几扇区开始
vW25qx_writeEnable();
vW25qx_waitReady();
// 命令
W25QX_NSS_LOW ;
cW25qx_SendByte (0x20); // 命令: Sector Erase(4K) : 20h
cW25qx_SendByte ((u8)(addr>>16));
cW25qx_SendByte ((u8)(addr>>8));
cW25qx_SendByte ((u8)addr);
W25QX_NSS_HIGH ;
vW25qx_waitReady();
}
// 5_5 写扇区. 要分页写入
void vW25qx_writeSector(char *p,u32 addr,u16 num)
{
u16 pageRemain = 256; // W25Qxx每个页命令最大写入字节数:256字节;
// 扇区:4096bytes, 缓存页:256bytes, 写扇区要分16次页命令写入
for(char i=0;i<16;i++)
{
vW25qx_writeEnable (); // 写使能
vW25qx_waitReady (); // 等待空闲
W25QX_NSS_LOW ; // 低电平,开始
cW25qx_SendByte(0x02); // 命令: page program : 02h , 每个写页命令最大缓存256字节
cW25qx_SendByte((u8)(addr>>16)); // 地址
cW25qx_SendByte((u8)(addr>> 8));
cW25qx_SendByte ((u8)addr);
for(u16 i=0;i