嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 超级精简系列之十:超级精简的IO模拟SPI的C实现应用值读写FLASH状态寄存器

超级精简系列之十:超级精简的IO模拟SPI的C实现应用值读写FLASH状态寄存器

一. 应用 SPIFLASH状态寄存器的读写

以上实现了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

4.1 W25Q32FV的状态寄存器

我们先从手册找到对应的指令表

3个状态寄存器分别都有对应的读写指令。

 

在实现时我们可以对应的数组通过索引来匹配命令

#define CMD_RD_SR1 0x05
#define CMD_WR_SR1 0x01
#define CMD_RD_SR2 0x35
#define CMD_WR_SR2 0x31
#define CMD_RD_SR3 0x15
#define CMD_WR_SR3 0x11
#define CMD_WR_EN 0x06
#define CMD_WR_DIS 0x04
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.

DRV1DRV0都是1

 

 

4.2 W25Q32FV的状态寄存器的读写驱动

以上从手册了解状态寄存器的足够多的信息,于是就可以开始写代码了。

我们还是面向对象,考虑可移植性。

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

命令定义和初始化解除初始化实现

#define CMD_RD_SR1 0x05
#define CMD_WR_SR1 0x01
#define CMD_RD_SR2 0x35
#define CMD_WR_SR2 0x31
#define CMD_RD_SR3 0x15
#define CMD_WR_SR3 0x11
#define CMD_WR_EN 0x06
#define CMD_WR_DIS 0x04
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;
}

可以看到实现非常简单,具备高可移植性。

4.3 测试

我们还是基于之前的命令行实现,来添加命令进行测试

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查看添加的命令

 

模式3测试

写使能/禁能测试

先写使能读出寄存器0值为1bit1=1 WEL=1

然后写禁止读出寄存器0变为0.

sh>

wren

rdsr 0

val = 2

wrdis

rdsr 0

val = 0

波形如下

 

注意如果这里要将寄存器0改为0必须要

Wren使能,然后wrsr 0 22而不是写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 22才能写0

rdsr

val = 0

写寄存器0

先要写使能

wren

然后写

wrsr 0 252

最后读

rdsr 0

sh>

wren

wrsr 0 252

rdsr 0

val = 252

可以看到回读值正确。

波形如下

 

读寄存器0

命令rdsr 0

sh>

rdsr 0

val = 0

逻辑分析仪抓到信号如下

 

读寄存器1

命令rdsr 1

sh>

rdsr 1

val = 0

逻辑分析仪抓到信号如下

 

读寄存器2

命令rdsr 2

sh>

rdsr 2

val = 96

逻辑分析仪抓到信号如下

 

模式0测试

io_spi_dev.mode改为0

写使能/禁能测试

先写使能读出寄存器0值为1bit1=1 WEL=1

然后写禁止读出寄存器0变为0.

sh>

wren

rdsr 0

val = 2

wrdis

rdsr 0

val = 0

波形如下

 

写寄存器0

先要写使能

wren

然后写

wrsr 0 252

最后读

rdsr 0

sh>

wren

wrsr 0 252

rdsr 0

val = 252

可以看到回读值正确。

波形如下

 

读寄存器0

命令rdsr 0

sh>

rdsr 0

val = 0

逻辑分析仪抓到信号如下

可见空闲时SCK是低,最后CS拉高后SCK为高是因为deinit了,io引脚被默认拉高了,cs之前空闲是低的。

 

读寄存器1

命令rdsr 1

sh>

rdsr 1

val = 0

逻辑分析仪抓到信号如下

读寄存器2

命令rdsr 2

sh>

rdsr 2

val = 96

逻辑分析仪抓到信号如下