以上实现了IO模拟SPI接口,整个过程很自然,参考时序图操作IO即可。是骡子是马拉出来遛遛。那么我们就继续基于此来进行SPIFALSH的操作,以W25Q32FV为例,先实现状态寄存器的读写,然后再实现FLASH的读写实现FLASH编辑器的工具。
W25Q32FV手册参考《https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/?__locale=en&partNo=W25Q32FV》
我们先从手册找到对应的指令表
有3个状态寄存器分别都有对应的读写指令。
在实现时我们可以对应的数组通过索引来匹配命令
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};
然后查看具体命令的时序图
我们看到读寄存器很简单,拉低CS发送指令字节,下一个字节就是返回的寄存器值。持续给SCK和保持CS低则重复输出寄存器值。
写寄存器,也很简单,拉低CS先发命令字节,然后发数据字节,拉高CS即可。
然后我们再来看状态寄存器值的含义
状态寄存器0,高6位对应保护位,WEL是写使能状态,BUSY是是否正在操作忙状态,这两位比较重要。一般操作完要检查BUSY是否为0才能继续,写之前要判断WEL是否为1可写。
该寄存器值默认值为0.
状态寄存器2
该寄存器默认值也是0,具体含义参考手册。
状态寄存器3,该寄存器默认值0x60.
即DRV1和DRV0都是1
以上从手册了解状态寄存器的足够多的信息,于是就可以开始写代码了。
我们还是面向对象,考虑可移植性。
W25Q32FV.h种
先抽象对SPI接口的依赖,设计设备类结构体
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; /**< 缓存地址 */
} 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);
最后就是实现接口,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;
}
可以看到实现非常简单,具备高可移植性。
我们还是基于之前的命令行实现,来添加命令进行测试
shell_func.c中
申明实现函数,g_shell_cmd_list_ast中添加命令
static void wrsrfunc(uint8_t* param);
static void rdsrfunc(uint8_t* param);
static void wrenfunc(uint8_t* param);
static void wrdisfunc(uint8_t* param);
{ (uint8_t*)"wrsr", wrsrfunc, (uint8_t*)"wrsr sr val"},
{ (uint8_t*)"rdsr", rdsrfunc, (uint8_t*)"rdsr sr"},
{ (uint8_t*)"wren", wrenfunc, (uint8_t*)"wren"},
{ (uint8_t*)"wrdis", wrdisfunc, (uint8_t*)"wrdis"},
实现IO操作接口和设备实例
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 void io_spi_port_delay(uint32_t delay)
{
}
static io_spi_dev_st io_spi_dev=
{
.cs_write = io_spi_port_cs_write,
.deinit = io_spi_port_deinit,
.delay_pf = io_spi_port_delay,
.delayns = 100,
.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,
};
实现flash依赖的接口和设备实例
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);
}
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,
};
实现写命令
void wrsrfunc(uint8_t* param)
{
uint32_t sr;
uint32_t val;
uint8_t* p = param;
while(1)
{
if((*p > 'z') || (*p < 'a'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
sr = atoi((const char*)p);
while(1)
{
if((*p > '9') || (*p < '0'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
val = atoi((const char*)p);
w25qxx_init(&w25qxx_dev);
w25qxx_wr_sr(&w25qxx_dev, sr, val);
w25qxx_deinit(&w25qxx_dev);
}
实现读命令
void rdsrfunc(uint8_t* param)
{
uint32_t sr;
uint8_t val=0;
uint8_t* p = param;
while(1)
{
if((*p > 'z') || (*p < 'a'))
{
break;
}
else
{
p++;
}
}
while(1)
{
if(*p != ' ')
{
break;
}
else
{
p++;
}
}
sr = atoi((const char*)p);
w25qxx_init(&w25qxx_dev);
w25qxx_rd_sr(&w25qxx_dev, sr, &val);
w25qxx_deinit(&w25qxx_dev);
printf("val = %d\r\n",val);
}
实现使能禁止命令
void wrenfunc(uint8_t* param)
{
(void)param;
w25qxx_init(&w25qxx_dev);
w25qxx_wr_enable(&w25qxx_dev);
w25qxx_deinit(&w25qxx_dev);
}
void wrdisfunc(uint8_t* param)
{
(void)param;
w25qxx_init(&w25qxx_dev);
w25qxx_wr_disable(&w25qxx_dev);
w25qxx_deinit(&w25qxx_dev);
}
进入命令行help查看添加的命令
先写使能读出寄存器0值为1即bit1=1 WEL=1
然后写禁止读出寄存器0变为0.
sh>
wren
rdsr 0
val = 2
wrdis
rdsr 0
val = 0
波形如下
注意如果这里要将寄存器0改为0必须要
Wren使能,然后wrsr 0 2写2而不是写0。
sh>
rdsr 0
val = 252
wrsr 0 2
rdsr 0
val = 252
wren
rdsr 0
val = 254 此时已经写使能但是wrsr 0 0不能写0
wrsr 0 0
rdsr 0
val = 254
wrsr 0 2 wrsr 0 2写2才能写0
rdsr
val = 0
先要写使能
wren
然后写
wrsr 0 252
最后读
rdsr 0
sh>
wren
wrsr 0 252
rdsr 0
val = 252
可以看到回读值正确。
波形如下
命令rdsr 0
sh>
rdsr 0
val = 0
逻辑分析仪抓到信号如下
命令rdsr 1
sh>
rdsr 1
val = 0
逻辑分析仪抓到信号如下
命令rdsr 2
sh>
rdsr 2
val = 96
逻辑分析仪抓到信号如下
io_spi_dev的.mode改为0
先写使能读出寄存器0值为1即bit1=1 WEL=1
然后写禁止读出寄存器0变为0.
sh>
wren
rdsr 0
val = 2
wrdis
rdsr 0
val = 0
波形如下
先要写使能
wren
然后写
wrsr 0 252
最后读
rdsr 0
sh>
wren
wrsr 0 252
rdsr 0
val = 252
可以看到回读值正确。
波形如下
命令rdsr 0
sh>
rdsr 0
val = 0
逻辑分析仪抓到信号如下
可见空闲时SCK是低,最后CS拉高后SCK为高是因为deinit了,io引脚被默认拉高了,cs之前空闲是低的。
命令rdsr 1
sh>
rdsr 1
val = 0
逻辑分析仪抓到信号如下
命令rdsr 2
sh>
rdsr 2
val = 96
逻辑分析仪抓到信号如下