前面实现了IO模拟SPI接口,并且以W25Q32FV为例进行了状态寄存器的读写,使用逻辑分析仪测试了不同MODE的不同操作的波形。
现在继续在此基础上类似之前的IO模拟IIC,实现EEPROM读写,实现EEPROM编辑器工具。
现在使用IO模拟SPI,实现SPI FLASH的读写,实现SPI FLASH编辑器的工具。这样我们的瑞士军刀工具集又添加一项新的功能。
写需要先使能,之前已经实现且测试过了。
SPI FLASH的命令非常多可以参考手册,但是实现读写实际只需要写使能,读状态寄存器,之外3条基本命令,sector擦除,page编程和读数据。
先来了解下这些命令的时序,按照着时序实现驱动。
可以看到只需要拉低CS,然后发送page编程命令0x02,然后是3字节 24位的地址,然后是最多256字节的数据(假设从page 偏移0开始)。超过page最大偏移256字节则会绕回。
于是实现代码就很简单了
int w25qxx_page_program(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size, uint8_t wait)
{
int res;
uint8_t s0 = 0xFF;
uint8_t tx[4];
if(dev == 0)
{
return -1;
}
w25qxx_wr_enable(dev); /* 先写使能 */
/* 命令和地址 */
tx[0] = CMD_PAGE_PROGRAM;
tx[1] = (addr >> 16) & 0xFF; /* A23~A16*/
tx[2] = (addr >> 8) & 0xFF; /* A15~A8 */
tx[3] = (addr >> 0) & 0xFF; /* A7~A0 */
dev->enable();
res = dev->trans(tx,0,4);
/* 数据 */
res = dev->trans(buffer,0,size);
dev->disbale();
/* 等待完成 */
if (wait != 0)
{
do
{
w25qxx_rd_sr(dev, 0, &s0);
}while((s0 & 0x01) != 0);
}
return res;
}
为了方便后面sector操作还可以基于此实现sector的编程
/*
* 完整的sector写入
*/
static int w25qxx_sector_program(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer)
{
int res = 0;
if((dev == 0) || (buffer == 0))
{
return -1;
}
if((addr % dev->sectorsize) != 0)
{
return -1; /* 必须sector大小对齐 */
}
for(uint32_t i= 0; i
sectorsize/dev->pagesize; i++) {
/* 循环一个sector的page数 */
res = w25qxx_page_program(dev, addr+i*dev->pagesize, buffer+i*dev->pagesize, dev->pagesize, 1);
}
return res;
}
也很简单,拉低CS发送命令0x20,发送3字节地址,拉高CS即可。
实现代码如下
int w25qxx_sector_erase(w25qxx_dev_st* dev, uint32_t addr, uint8_t wait)
{
int res;
uint8_t s0 = 0xFF;
uint8_t tx[4];
if(dev == 0)
{
return -1;
}
w25qxx_wr_enable(dev); /* 先写使能 */
/* 擦除 */
tx[0] = CMD_SECTOR_ERASE;
tx[1] = (addr >> 16) & 0xFF; /* A23~A16*/
tx[2] = (addr >> 8) & 0xFF; /* A15~A8 */
tx[3] = (addr >> 0) & 0xFF; /* A7~A0 */
dev->enable();
res = dev->trans(tx,0,4);
dev->disbale();
/* 等待完成 */
if (wait != 0)
{
do
{
w25qxx_rd_sr(dev, 0, &s0);
}while((s0 & 0x01) != 0);
}
return res;
支持随机地址任意长度读。
只需要先拉低CS,发送命令0x03,然后24位地址,然后连续读即可。
int w25qxx_read_data(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size)
{
int res;
uint8_t tx[4];
if((dev == 0) || (buffer == 0))
{
return -1;
}
/* 命令和地址 */
tx[0] = CMD_READ_DATA;
tx[1] = (addr >> 16) & 0xFF; /* A23~A16*/
tx[2] = (addr >> 8) & 0xFF; /* A15~A8 */
tx[3] = (addr >> 0) & 0xFF; /* A7~A0 */
dev->enable();
res = dev->trans(tx,0,4);
/* 数据 */
res = dev->trans(0,buffer,size);
dev->disbale();
return res;
}
前面的page编程,sector 擦除,读数据都是原生支持的操作。
但是对于写则需要一定的处理,因为flash写之前需要erase擦除,而擦除最小单位是sector,
所以任意写也必须要要以sector为单位, 需要:读- 擦除 -修改 -写入这几个步骤,
且是以sector为单位。如果写很少的数据也需要先读出来修改后再写入。
即如下所示,写任意长度数据,可能是如下情况
Head部分
中间完整的sectors部分
最后tail部分
这三部分可能任何部分都没有,可能是任何组合。代码只需要按照这个模式,考虑3部分即可。
int w25qxx_write_data(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size)
{
uint32_t head; /* 最开始不足sector的大小 */
uint32_t addralign;
uint32_t tail; /* 最后不足sector的大小 */
uint32_t sectors; /* 中间完整的sector数 */
int res = 0;
if((dev == 0) || (buffer == 0))
{
return -1;
}
addralign = addr % dev->sectorsize;
if(addralign == 0)
{
head = 0; /* 刚好对齐 */
}
else
{
head = dev->sectorsize - addralign; /* 不对齐,head大小 */
addr = addr-addralign; /* 将地址对齐到前面的sector边缘 */
}
sectors = (size - head)/dev->sectorsize;
tail = (size - head) % dev->sectorsize;
/* head部分 */
if(head != 0)
{
/* 前面不是sector对齐,addralign部分需要读出来不能改变 */
res = w25qxx_read_data(dev, addr, dev->buffer, addralign);
/* 擦除该sector */
res = w25qxx_sector_erase(dev, addr, 1);
/* 修改后面head部分 */
memcpy(dev->buffer+addralign,buffer,head);
buffer += head;
/* 更新整个sector */
res = w25qxx_sector_program(dev,addr,dev->buffer);
addr += dev->sectorsize;
}
/* 中间整数个sector */
while(sectors > 0)
{
/* 擦除该sector */
res = w25qxx_sector_erase(dev, addr, 1);
/* 更新整个sector */
res = w25qxx_sector_program(dev,addr,buffer);
addr += dev->sectorsize;
buffer += dev->sectorsize;
sectors--;
}
/* tail部分 */
if(tail > 0)
{
/* 后面不是sector对齐,sectorsize-tail部分需要读出来不能改变 */
res = w25qxx_read_data(dev, addr+tail, dev->buffer+tail, dev->sectorsize-tail);
/* 擦除该sector */
res = w25qxx_sector_erase(dev, addr, 1);
/* 修改tail部分 */
memcpy(dev->buffer,buffer,tail);
/* 更新整个sector */
res = w25qxx_sector_program(dev,addr,dev->buffer);
}
return res;
}
shell_func.c中添加命令
实现函数申明
static void printexflashfunc(uint8_t* param);
static void writeexflashfunc(uint8_t* param);
g_shell_cmd_list_ast中添加命令行
{ (uint8_t*)"printexflash", printexflashfunc, (uint8_t*)"printexflash addr len"},
{ (uint8_t*)"writeexflash", writeexflashfunc, (uint8_t*)"writeexflash addr hexstr"},
接口移植
static void io_spi_port_init(void)
{
}
static void io_spi_port_deinit(void)
{
}
static void io_spi_port_cs_write(uint8_t val)
{
;
}
static void io_spi_port_sck_write(uint8_t val)
{
}
static void io_spi_port_mosi_write(uint8_t val)
{
}
static uint8_t io_spi_port_miso_read(void)
{
}
static io_spi_dev_st io_spi_dev=
{
.cs_write = io_spi_port_cs_write,
.deinit = io_spi_port_deinit,
.delay_pf = 0,
.delayns = 0,
.init = io_spi_port_init,
.miso_read = io_spi_port_miso_read,
.mode = 3,
.mosi_write = io_spi_port_mosi_write,
.msb = 1,
.sck_write = io_spi_port_sck_write,
};
static void w25qxx_port_enable(void)
{
io_spi_enable(&io_spi_dev);
}
static void w25qxx_port_disable(void)
{
io_spi_disable(&io_spi_dev);
}
static int w25qxx_port_trans(uint8_t* tx, uint8_t* rx, uint32_t size)
{
return io_spi_trans(&io_spi_dev, tx, rx, size);
}
static void w25qxx_port_init(void)
{
io_spi_init(&io_spi_dev);
}
static void w25qxx_port_deinit(void)
{
io_spi_deinit(&io_spi_dev);
}
uint8_t flash_buffer[4096];
static w25qxx_dev_st w25qxx_dev=
{
.deinit = w25qxx_port_deinit,
.disbale = w25qxx_port_disable,
.enable = w25qxx_port_enable,
.init = w25qxx_port_init,
.trans = w25qxx_port_trans,
.buffer = flash_buffer,
.sectorsize = 4096,
.pagesize = 256,
};
实现读函数
void printexflashfunc(uint8_t* param)
{
uint8_t buffer[16];
uint32_t addr;
uint32_t len;
uint8_t* p = param;
int res;
while(1)
{
if((*p > 'z') || (*p < 'a'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
addr = atoi((const char*)p);
while(1)
{
if((*p > '9') || (*p < '0'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
len = atoi((const char*)p);
uint32_t toread;
uint32_t read = 0;
w25qxx_init(&w25qxx_dev);
while(read < len)
{
toread = ((len-read) > sizeof(buffer)) ? sizeof(buffer) : (len-read);
if(0 != (res = w25qxx_read_data(&w25qxx_dev, addr+read, buffer, toread)))
{
printf("read err %d\r\n",res);
w25qxx_deinit(&w25qxx_dev);
return;
}
read += toread;
for(uint32_t i=0; i
{
printf("%02x ",buffer[i]);
}
printf("\r\n");
}
w25qxx_deinit(&w25qxx_dev);
}
实现写函数
void writeexflashfunc(uint8_t* param)
{
uint8_t buffer[16];
uint32_t addr;
uint32_t len=0;
uint8_t* p = param;
uint8_t flag;
uint8_t tmp;
int res;
while(1)
{
if((*p > 'z') || (*p < 'a'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
addr = atoi((const char*)p);
while(1)
{
if((*p > '9') || (*p < '0'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
flag = 0;
while(*p)
{
if(flag == 0)
{
tmp = char2hex(*p) << 4;
flag = 1;
}
else if(flag == 1)
{
tmp |= char2hex(*p);
flag = 0;
buffer[len++] = tmp;
if(len>=16)
{
break;
}
}
p++;
}
w25qxx_init(&w25qxx_dev);
if(0 != (res = w25qxx_write_data(&w25qxx_dev, addr, buffer, len)))
{
printf("write err %d\r\n",res);
w25qxx_deinit(&w25qxx_dev);
return;
}
printf("\r\n");
w25qxx_deinit(&w25qxx_dev);
}
可以看到命令行添加了如下命令
如下测试sector 4096边界处,前面sector最后1字节,后面sector开头5个字节.
回读正确,且没有破坏其他地址的数据。
写过程如下
读过程如下
可以明显的看到了读支持随机读,简单很多。
而随机写需要读-擦除-修改-写入可以看到明显复杂很多。
以上实现了自由修改SPIFLASH的工具,我们的Demo也又添加了一项功能,朝着瑞士军刀型工具集完善。可以看出以上IO模拟SPI的代码非常简单容易移植,非常好用。包括spiflash的读写实现也可以作为库代码使用,也是按照开头的思想实现的。
很多设备的参数都是存储在spiflash里的,有了这个工具我们就可以进行自由的改写,甚至hark,clone某些设备。我们再结合之前的文章《https://mp.weixin.qq.com/s/cRgopFr1uwuxxAIxXBrj0Q超级精简系列之五:超级精简的磨损均衡掉电保护多份备份存储机制》即可实现数据存储器,还可以移植文件系统继续扩展功能。我们延续一贯的精简线路,设计积累自已的轮子,用时才能”真香”。
io_spi.h
extern "C"{
typedef void (*io_spi_cs_write_pf)(uint8_t val); /**< CS写接口 */
typedef void (*io_spi_sck_write_pf)(uint8_t val); /**< SCK写接口 */
typedef void (*io_spi_mosi_write_pf)(uint8_t val); /**< MOSI写接口 */
typedef uint8_t (*io_spi_miso_read_pf)(void); /**< MISO读接口 */
typedef void (*io_spi_delay_ns_pf)(uint32_t delay); /**< 延时接口 */
typedef void (*io_spi_init_pf)(void); /**< 初始化接口 */
typedef void (*io_spi_deinit_pf)(void); /**< 解除初始化接口 */
/**
* \struct io_spi_dev_st
* 接口结构体
*/
typedef struct
{
io_spi_cs_write_pf cs_write; /**< cs写接口 */
io_spi_sck_write_pf sck_write; /**< sck写接口 */
io_spi_mosi_write_pf mosi_write; /**< mosi写接口 */
io_spi_miso_read_pf miso_read; /**< miso读接口 */
io_spi_delay_ns_pf delay_pf; /**< 延时接口 */
io_spi_init_pf init; /**< 初始化接口 */
io_spi_deinit_pf deinit; /**< 解除初始化接口 */
uint32_t delayns; /**< 延迟时间 */
uint8_t mode; /**< 模式0~3 bit0 CPHA bit1 CPOL */
uint8_t msb; /**< 1高位在前 否则低位在前 */
} io_spi_dev_st;
/**
* \fn io_spi_enable
* 发送CS使能信号,拉低CS
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_enable(io_spi_dev_st* dev);
/**
* \fn io_spi_disable
* 拉高CS,取消片选
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_disable(io_spi_dev_st* dev);
/**
* \fn io_spi_trans
* 传输,发送的同时读
* \param[in] dev \ref io_spi_dev_st
* \param[in] tx 待发送的数据 如果tx为空则默认发送FF
* \param[out] rx 存储接收的数据 如果rx为空则不读
* \param[in] size 传输的字节数
* \retval 0 读成功
* \retval -1 参数错误
*/
int io_spi_trans(io_spi_dev_st* dev, uint8_t* tx, uint8_t* rx, uint32_t size);
/**
* \fn io_spi_init
* 初始化
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_init(io_spi_dev_st* dev);
/**
* \fn io_spi_deinit
* 解除初始化
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_deinit(io_spi_dev_st* dev);
}
io_spi.c
#include "io_spi.h"
void io_spi_enable(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->cs_write != 0) && (dev->sck_write != 0))
{
/* 准备空闲时的SCK状态,在CS拉低之前准备好 */
dev->sck_write((dev->mode & 0x02) >> 1);
if(dev->delay_pf != 0)
{
dev->delay_pf(dev->delayns);
}
/* 拉低CS */
dev->cs_write(0);
/* (5) SCK电平保持 */
//if(dev->delay_pf != 0)
//{
// dev->delay_pf(dev->delayns);
//}
}
}
void io_spi_disable(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->cs_write != 0))
{
dev->cs_write(1);
}
}
/**
* _____ _____
* CS |_____________________________________________________________|
* _____________ _________
* SCK(CPOL=0) xx__________| |___ xxx __________| |__________
* __________ ____xxx __________ __________
* SCK(CPOL=1) xx |_____________| |_________|
* (0)
* (1)
* (2)
* (3)(4)
* (5)
* (6)(7)
* MISO ^ ^
* MOSI ^
* (1) (2) (4) (6)
* (3) (5)
* 其中()表示行为,^表示MOSI/MISO的输出或者采样位置.
* (0) io_spi_enable 准备SCK空闲状态,拉低CS.
* (1) 准备SCK初始状态,和(0)时SCK初始状态一样,代码中执行这个操作的目的仅仅是初始化局部变量cpol而已.
* (2) 输出MOSI数据.
* (3) 反转SCK产生第1个边沿.
* (4) 如果CPHA=0 则第1个边沿采样,MISO在此采样.
* (5) SCK高/低电平保持时间.
* (6) 反转SCK产生第2个边沿.
* (7) 如果CPHA=1 则第2个边沿采样,MISO在此采样.
*/
int io_spi_trans(io_spi_dev_st* dev, uint8_t* tx, uint8_t* rx, uint32_t size)
{
uint32_t i = 0; /* 字节数循环 */
uint8_t j = 0; /* 位数循环 */
uint8_t msb = 0; /* MSB标志 */
uint8_t cpha = 0; /* 相位标志bit0 */
uint8_t cpol = 0; /* 极性标志bit1 */
uint8_t rx_val = 0; /* 发送字节缓存 */
uint8_t tx_val = 0; /* 接收字节缓存 */
if(dev == 0)
{
return -1;
}
if((dev->miso_read == 0) || (dev->mosi_write == 0) || (dev->sck_write == 0))
{
/* dev->delay_pf 可以不实现 */
return -1;
}
cpha = dev->mode & 0x01;
msb = dev->msb;
/* (1) 准备空闲时的SCK状态 */
cpol = (dev->mode & 0x02) >> 1;
/* 这一句其实可以不用,和io_spi_enable效果一样,这里仅需要初始化cpol局部变量即可
* 加上这一句可以在此确保SCK引脚状态初始化,可靠性角度来说加上提高冗余.
*/
dev->sck_write(cpol);
for(i=0; i
{
/* 取待发送的值, 用户没有提供则发送0xFF */
if(tx != 0)
{
tx_val = *tx++;
}
else
{
tx_val = 0xFF;
}
/* 接收到的值初始化 */
rx_val = 0;
for(j=0 ;j<8; j++)
{
/* (2)对于发送,不管对方哪个边沿采样,都是都在第一个边沿之前准备好MOSI就行
* 如果对于对方第一个边沿采样,这里修改MOSI之后最好有个数据建立时间
*/
if(msb)
{
dev->mosi_write(tx_val & 0x80); /* 高位在前,先发送高位,未发送数据再往高位移动 */
tx_val <<= 0x1; /* 注意写的时候是先写后移位 */
}
else
{
dev->mosi_write(tx_val & 0x01); /* 低位在前,先发送低位,未发送数据再往高位移动 */
tx_val >>= 0x1;
}
/* (3)反转产生第1个CLK边沿 */
cpol ^= 0x01;
dev->sck_write(cpol);
if(rx != 0)
{
if(cpha == 0)
{
/* (4)第一个边沿采样 */
if(msb)
{
rx_val <<= 0x1; /* 注意读的时候是先移位后读 */
rx_val |= dev->miso_read(); /* 高位在前,先读到低位,已接收数据再往高位移动 */
}
else
{
rx_val >>= 0x1;
rx_val |= dev->miso_read() <<7; /* 低位在前,先读到高位,已接收数据再往低位移动 */
}
}
}
/* (5) SCK电平保持 */
if(dev->delay_pf != 0)
{
dev->delay_pf(dev->delayns);
}
/* (6)反转产生第2个CLK边沿 */
cpol ^= 0x01;
dev->sck_write(cpol);
if(rx != 0)
{
if(cpha == 1)
{
/* (7) 第2个边沿采样 */
if(msb)
{
rx_val <<= 0x1;
rx_val |= dev->miso_read(); /* 高位在前,先读到低位再往高位移动 */
}
else
{
rx_val >>= 0x1;
rx_val |= dev->miso_read()<<7; /* 低位在前,先读到高位再往低位移动 */
}
}
}
/* (5) SCK电平保持 */
if(dev->delay_pf != 0)
{
dev->delay_pf(dev->delayns);
}
}
/* 存储读到的值 */
if(rx != 0)
{
*rx++ = rx_val;
}
}
return 0;
}
void io_spi_init(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->init != 0))
{
dev->init();
}
}
void io_spi_deinit(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->deinit != 0))
{
dev->deinit();
}
}
W25Q32FV.h
extern "C"{
typedef void (*w25qxx_spi_enable_pf)(void); /**< SPI接口使能 */
typedef void (*w25qxx_spi_disable_pf)(void); /**< SPI接口禁能 */
typedef int (*w25qxx_spi_trans_pf)(uint8_t* tx, uint8_t* rx, uint32_t size); /**< SPI读写接口 */
typedef void (*w25qxx_spi_init_pf)(void); /**< 初始化接口 */
typedef void (*w25qxx_spi_deinit_pf)(void); /**< 解除初始化接口 */
/**
* \struct w25qxx_dev_st
* 接口结构体
*/
typedef struct
{
w25qxx_spi_enable_pf enable; /**< SPI接口使能 */
w25qxx_spi_disable_pf disbale; /**< SPI接口禁能 */
w25qxx_spi_trans_pf trans; /**< SPI读写接口 */
w25qxx_spi_init_pf init; /**< 初始化接口 */
w25qxx_spi_deinit_pf deinit; /**< 解除初始化接口 */
uint8_t* buffer; /**< 缓存地址 */
uint32_t pagesize; /**< page大小 */
uint32_t sectorsize; /**< sector大小 */
} w25qxx_dev_st;
/**
* \fn w25qxx_init
* 初始化
* \param[in] dev \ref w25qxx_dev_st
*/
void w25qxx_init(w25qxx_dev_st* dev);
/**
* \fn w25qxx_deinit
* 解除初始化
* \param[in] dev \ref w25qxx_dev_st
*/
void w25qxx_deinit(w25qxx_dev_st* dev);
/**
* \fn w25qxx_rd_sr
* 读状态寄存器
* \param[in] dev \ref w25qxx_dev_st
* \param[in] sr 状态寄存器序号0~2
* \param[out] val 存储读到的值
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_rd_sr(w25qxx_dev_st* dev, uint8_t sr, uint8_t* val);
/**
* \fn w25qxx_wr_sr
* 写状态寄存器
* \param[in] dev \ref w25qxx_dev_st
* \param[in] sr 状态寄存器序号0~2
* \param[in] val 待写入的值
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_wr_sr(w25qxx_dev_st* dev, uint8_t sr, uint8_t val);
/**
* \fn w25qxx_wr_enable
* 写使能
* \param[in] dev \ref w25qxx_dev_st
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_wr_enable(w25qxx_dev_st* dev);
/**
* \fn w25qxx_wr_disable
* 写禁止
* \param[in] dev \ref w25qxx_dev_st
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_wr_disable(w25qxx_dev_st* dev);
/**
* \fn w25qxx_sector_erase
* sector擦除
* \param[in] dev \ref w25qxx_dev_st
* \param[in] addr 24位地址
* \param[in] wait 设置为1则等待完成 设置为0不等待
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_sector_erase(w25qxx_dev_st* dev, uint32_t addr, uint8_t wait);
/**
* \fn w25qxx_page_program
* page编程
* \param[in] dev \ref w25qxx_dev_st
* \param[in] addr 24位地址
* \param[in] buffer 待写入数据
* \param[in] size 待写入数据大小
* \param[in] wait 设置为1则等待完成 设置为0不等待
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_page_program(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size, uint8_t wait);
/**
* \fn w25qxx_read_data
* 读数据
* \param[in] dev \ref w25qxx_dev_st
* \param[in] addr 24位地址
* \param[in] buffer 存读出的数据
* \param[in] size 读出数据大小
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_read_data(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size);
/**
* \fn w25qxx_write_data
* 写数据, 读出-擦除-修改-写入,支持任意大小的写入
* \param[in] dev \ref w25qxx_dev_st
* \param[in] addr 24位地址
* \param[in] buffer 存读出的数据
* \param[in] size 读出数据大小
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_write_data(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size);
}
W25Q32FV.c
const uint8_t s_cmd_rd_sr[3]={CMD_RD_SR1,CMD_RD_SR2,CMD_RD_SR3};
const uint8_t s_cmd_wr_sr[3]={CMD_WR_SR1,CMD_WR_SR2,CMD_WR_SR3};
void w25qxx_init(w25qxx_dev_st* dev)
{
dev->init();
}
/**
* \fn w25qxx_deinit
* 解除初始化
* \param[in] dev \ref w25qxx_dev_st
*/
void w25qxx_deinit(w25qxx_dev_st* dev)
{
dev->deinit();
}
/**
* \fn w25qxx_rd_sr
* 读状态寄存器
* \param[in] dev \ref w25qxx_dev_st
* \param[in] sr 状态寄存器序号0~2
* \param[out] val 存储读到的值
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_rd_sr(w25qxx_dev_st* dev, uint8_t sr, uint8_t* val)
{
int res;
uint8_t tx[2];
uint8_t rx[2];
if((dev == 0) || (val == 0))
{
return -1;
}
if(sr >= sizeof(s_cmd_rd_sr)/sizeof(s_cmd_rd_sr[0]))
{
return -1;
}
tx[0]=s_cmd_rd_sr[sr];
tx[1]=0xFF;
dev->enable();
res = dev->trans(tx,rx,2);
dev->disbale();
*val = rx[1];
return res;
}
/**
* \fn w25qxx_wr_sr
* 写状态寄存器
* \param[in] dev \ref w25qxx_dev_st
* \param[in] sr 状态寄存器序号0~2
* \param[in] val 待写入的值
* \return 参考w25qxx_spi_trans_pf的返回值
*/
int w25qxx_wr_sr(w25qxx_dev_st* dev, uint8_t sr, uint8_t val)
{
int res;
uint8_t tx[2];
if((dev == 0) || (val == 0))
{
return -1;
}
if(sr >= sizeof(s_cmd_wr_sr)/sizeof(s_cmd_wr_sr[0]))
{
return -1;
}
tx[0]=s_cmd_wr_sr[sr];
tx[1]=val;
dev->enable();
res = dev->trans(tx,0,2);
dev->disbale();
return res;
}
int w25qxx_wr_enable(w25qxx_dev_st* dev)
{
int res;
uint8_t tx[1];
if(dev == 0)
{
return -1;
}
tx[0]=CMD_WR_EN;
dev->enable();
res = dev->trans(tx,0,1);
dev->disbale();
return res;
}
int w25qxx_wr_disable(w25qxx_dev_st* dev)
{
int res;
uint8_t tx[1];
if(dev == 0)
{
return -1;
}
tx[0]=CMD_WR_DIS;
dev->enable();
res = dev->trans(tx,0,1);
dev->disbale();
return res;
}
int w25qxx_sector_erase(w25qxx_dev_st* dev, uint32_t addr, uint8_t wait)
{
int res;
uint8_t s0 = 0xFF;
uint8_t tx[4];
if(dev == 0)
{
return -1;
}
w25qxx_wr_enable(dev); /* 先写使能 */
/* 擦除 */
tx[0] = CMD_SECTOR_ERASE;
tx[1] = (addr >> 16) & 0xFF; /* A23~A16*/
tx[2] = (addr >> 8) & 0xFF; /* A15~A8 */
tx[3] = (addr >> 0) & 0xFF; /* A7~A0 */
dev->enable();
res = dev->trans(tx,0,4);
dev->disbale();
/* 等待完成 */
if (wait != 0)
{
do
{
w25qxx_rd_sr(dev, 0, &s0);
}while((s0 & 0x01) != 0);
}
return res;
}
int w25qxx_page_program(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size, uint8_t wait)
{
int res;
uint8_t s0 = 0xFF;
uint8_t tx[4];
if(dev == 0)
{
return -1;
}
w25qxx_wr_enable(dev); /* 先写使能 */
/* 命令和地址 */
tx[0] = CMD_PAGE_PROGRAM;
tx[1] = (addr >> 16) & 0xFF; /* A23~A16*/
tx[2] = (addr >> 8) & 0xFF; /* A15~A8 */
tx[3] = (addr >> 0) & 0xFF; /* A7~A0 */
dev->enable();
res = dev->trans(tx,0,4);
/* 数据 */
res = dev->trans(buffer,0,size);
dev->disbale();
/* 等待完成 */
if (wait != 0)
{
do
{
w25qxx_rd_sr(dev, 0, &s0);
}while((s0 & 0x01) != 0);
}
return res;
}
/*
* 完整的sector写入
*/
static int w25qxx_sector_program(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer)
{
int res = 0;
if((dev == 0) || (buffer == 0))
{
return -1;
}
if((addr % dev->sectorsize) != 0)
{
return -1; /* 必须sector大小对齐 */
}
for(uint32_t i= 0; i
sectorsize/dev->pagesize; i++) {
/* 循环一个sector的page数 */
res = w25qxx_page_program(dev, addr+i*dev->pagesize, buffer+i*dev->pagesize, dev->pagesize, 1);
}
return res;
}
int w25qxx_read_data(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size)
{
int res;
uint8_t tx[4];
if((dev == 0) || (buffer == 0))
{
return -1;
}
/* 命令和地址 */
tx[0] = CMD_READ_DATA;
tx[1] = (addr >> 16) & 0xFF; /* A23~A16*/
tx[2] = (addr >> 8) & 0xFF; /* A15~A8 */
tx[3] = (addr >> 0) & 0xFF; /* A7~A0 */
dev->enable();
res = dev->trans(tx,0,4);
/* 数据 */
res = dev->trans(0,buffer,size);
dev->disbale();
return res;
}
/**
* 按照sector进行 读-擦除-修改-写入 更新sector
* sector写入由page program循环 完成。
* 需要考虑head不对齐部分
* 中间完整的sectors部分
* tail不对齐部分
* sectorsize
* | | | | |
* ^-------------------------------------------------------------^
* addralign|---head----|-------------------sectors--------- ----| tail|
*/
int w25qxx_write_data(w25qxx_dev_st* dev, uint32_t addr, uint8_t* buffer, uint32_t size)
{
uint32_t head; /* 最开始不足sector的大小 */
uint32_t addralign;
uint32_t tail; /* 最后不足sector的大小 */
uint32_t sectors; /* 中间完整的sector数 */
int res = 0;
if((dev == 0) || (buffer == 0))
{
return -1;
}
addralign = addr % dev->sectorsize;
if(addralign == 0)
{
head = 0; /* 刚好对齐 */
}
else
{
head = dev->sectorsize - addralign; /* 不对齐,head大小 */
addr = addr-addralign; /* 将地址对齐到前面的sector边缘 */
}
sectors = (size - head)/dev->sectorsize;
tail = (size - head) % dev->sectorsize;
/* head部分 */
if(head != 0)
{
/* 前面不是sector对齐,addralign部分需要读出来不能改变 */
res = w25qxx_read_data(dev, addr, dev->buffer, addralign);
/* 擦除该sector */
res = w25qxx_sector_erase(dev, addr, 1);
/* 修改后面head部分 */
memcpy(dev->buffer+addralign,buffer,head);
buffer += head;
/* 更新整个sector */
res = w25qxx_sector_program(dev,addr,dev->buffer);
addr += dev->sectorsize;
}
/* 中间整数个sector */
while(sectors > 0)
{
/* 擦除该sector */
res = w25qxx_sector_erase(dev, addr, 1);
/* 更新整个sector */
res = w25qxx_sector_program(dev,addr,buffer);
addr += dev->sectorsize;
buffer += dev->sectorsize;
sectors--;
}
/* tail部分 */
if(tail > 0)
{
/* 后面不是sector对齐,sectorsize-tail部分需要读出来不能改变 */
res = w25qxx_read_data(dev, addr+tail, dev->buffer+tail, dev->sectorsize-tail);
/* 擦除该sector */
res = w25qxx_sector_erase(dev, addr, 1);
/* 修改tail部分 */
memcpy(dev->buffer,buffer,tail);
/* 更新整个sector */
res = w25qxx_sector_program(dev,addr,dev->buffer);
}
return res;
}