代码见:https://github.com/qinyunti/py32f403-nes.git
一.前言
前面我们实现了SPI接口,并读到了ID,现在就开始实现FLASH的读写接口,为后面文件系统移植做准备。
二.驱动
flash.h中我们定义数据结构
typedef uint32_t (*flash_spi_io)(uint8_t* tx_buffer, uint8_t* rx_buffer, uint32_t len, int flag);
/**
* \struct flash_dev_st
* FLASH结构.
*/
typedef struct
{
flash_spi_io spi_trans; /**< spi传输接口 */
void* buffer; /**< sector缓存 */
uint32_t sector_size; /**< sector大小 */
uint32_t sector_bits; /**< sector大小 位数 */
uint32_t page_size; /**< page大小 */
uint32_t page_bits; /**< page大小 位数 */
} flash_dev_st;
有用户实现spi传输接口,提供buffer,指定其他参数,这样flash.c和flash.h完全可移植,无需任何修改。
Flash.c的实现,完全参照FLASH的芯片手册来进行。
主要实现以下命令
#define FLASH_CMD_WEL 0x06
#define FLASH_CMD_PAGEPROGRAM 0x02
#define FLASH_CMD_READ 0x03
#define FLASH_CMD_ERASESECTOR 0x20
#define FLASH_CMD_READSR1 0x05
#define FLASH_CMD_READSR2 0x35
#define FLASH_CMD_READSR3 0x15
#define FLASH_CMD_WRITESR1 0x01
#define FLASH_CMD_WRITESR2 0x31
#define FLASH_CMD_WRITESR3 0x11
2.1写使能
发一个字节0x06即可
int flash_write_enable(flash_dev_st* dev)
{
uint8_t cmd = FLASH_CMD_WEL;
dev->spi_trans(&cmd, 0, 1, 1);
return 0;
}
2.2读状态寄存器
发一个字节命令,然后读一字节状态
int flash_read_sr1(flash_dev_st* dev, uint8_t* sr)
{
uint8_t cmd[2] = {FLASH_CMD_READSR1,0xFF};
uint8_t rx[2];
dev->spi_trans(cmd, rx, 2, 1);
*sr = rx[1];
return 0;
}
int flash_read_sr2(flash_dev_st* dev, uint8_t* sr)
{
uint8_t cmd[2] = {FLASH_CMD_READSR2,0xFF};
uint8_t rx[2];
dev->spi_trans(cmd, rx, 2, 1);
*sr = rx[1];
return 0;
}
int flash_read_sr3(flash_dev_st* dev, uint8_t* sr)
{
uint8_t cmd[2] = {FLASH_CMD_READSR3,0xFF};
uint8_t rx[2];
dev->spi_trans(cmd, rx, 2, 1);
*sr = rx[1];
return 0;
}
2.3写状态寄存器
发送一字节命令和一字节数据
int flash_write_sr1(flash_dev_st* dev, uint8_t val)
{
uint8_t cmd[2] = {FLASH_CMD_WRITESR1,0};
cmd[1] = val;
dev->spi_trans(cmd, 0, 2, 1);
return 0;
}
int flash_write_sr2(flash_dev_st* dev, uint8_t val)
{
uint8_t cmd[2] = {FLASH_CMD_WRITESR2,0};
cmd[1] = val;
dev->spi_trans(cmd, 0, 2, 1);
return 0;
}
int flash_write_sr3(flash_dev_st* dev, uint8_t val)
{
uint8_t cmd[2] = {FLASH_CMD_WRITESR3,0};
cmd[1] = val;
dev->spi_trans(cmd, 0, 2, 1);
return 0;
}
2.4等待完成
#define FLASH_SR1_BUSY (1 << 0)
int flash_wait_busy(flash_dev_st* dev)
{
uint8_t sr = 0;
do{
flash_read_sr1(dev, &sr);
} while(FLASH_SR1_BUSY & sr);
return 0;
}
读状态寄存器1,等待其bit0变为0
2.5Sector擦除
发送1字节命令+3字节地址
int flash_erase_sector(flash_dev_st* dev, uint32_t addr)
{
uint8_t cmd[4];
flash_write_enable(dev);
cmd[0] = FLASH_CMD_ERASESECTOR;
cmd[1] = (uint8_t)(addr >> 16 & 0xFF);
cmd[2] = (uint8_t)(addr >> 8 & 0xFF);
cmd[3] = (uint8_t)(addr >> 0 & 0xFF);
dev->spi_trans(cmd, 0, 4, 1);
flash_wait_busy(dev);
return 0;
}
2.6Page编程
先要写使能
然后发1字节命令+3字节地址,此时不拉高CS
然后发数据
最后等待完成
int flash_pageprogram(flash_dev_st* dev, uint8_t* buffer, uint32_t addr, uint32_t len)
{
uint8_t cmd[4];
cmd[0] = FLASH_CMD_PAGEPROGRAM;
cmd[1] = (uint8_t)(addr >> 16 & 0xFF);
cmd[2] = (uint8_t)(addr >> 8 & 0xFF);
cmd[3] = (uint8_t)(addr >> 0 & 0xFF);
flash_write_enable(dev);
dev->spi_trans(cmd, 0, 4, 0);
dev->spi_trans(buffer, 0, len, 1);
flash_wait_busy(dev);
return 0;
}
2.7读数据
先发送1字节命令+3字节地址,此时不拉高CS
然后读数据
uint32_t flash_read(flash_dev_st* dev, uint8_t* buffer, uint32_t addr, uint32_t len)
{
uint8_t cmd[4];
cmd[0] = FLASH_CMD_READ;
cmd[1] = (uint8_t)(addr >> 16 & 0xFF);
cmd[2] = (uint8_t)(addr >> 8 & 0xFF);
cmd[3] = (uint8_t)(addr >> 0 & 0xFF);
dev->spi_trans(cmd, 0, 4, 0);
dev->spi_trans(0, buffer, len, 1);
return 0;
}
2.8写数据
实现任意地址的任意长度写。
按照sector为单位,如果待写入数据不是sector地址和大小对齐,则先读出来然后修改,最后写入。写入前自动擦除。
uint32_t flash_write(flash_dev_st* dev, uint8_t* buffer, uint32_t addr, uint32_t len)
{
/**
* | | | | |
* | |
* < len >
* addr
* <sec_head > < sec_tail >
* start_addr
*/
uint32_t sec_head;
uint32_t sec_tail = 0;
uint32_t sec_mid_num;
uint32_t start_addr;
uint32_t end_addr;
uint32_t fill;
start_addr = addr & (~(dev->sector_size-1));
end_addr = (addr+len) & (~(dev->sector_size-1));
sec_head = addr & (dev->sector_size-1);
if((end_addr != start_addr) || (sec_head == 0)){
sec_tail = (addr + len) & (dev->sector_size-1);
}
sec_mid_num = (end_addr - start_addr) >> dev->sector_bits;
if((sec_head != 0) || (sec_tail != 0)){
if(sec_mid_num > 1){
sec_mid_num--;
}else{
sec_mid_num = 0;
}
}
/* head */
if(sec_head > 0)
{
flash_read(dev,dev->buffer, start_addr, dev->sector_size);
fill =( len > (dev->sector_size-sec_head)) ? (dev->sector_size-sec_head) : len;
memcpy(dev->buffer+sec_head,buffer,len);
flash_erase_sector(dev,start_addr);
for (uint32_t j=0; j<16; j++) {
flash_pageprogram(dev,dev->buffer + (j<<dev->page_bits),start_addr + (j<<dev->page_bits), dev->page_size);
}
buffer += fill;
start_addr += dev->sector_size;
}
/* mid */
for(uint32_t i=0; i<sec_mid_num; i++)
{
flash_erase_sector(dev,start_addr);
for (uint32_t j=0; j<16; j++) {
flash_pageprogram(dev, buffer + (j<<dev->page_bits), start_addr + (j<<dev->page_bits), dev->page_size);
}
buffer += dev->sector_size;
start_addr += dev->sector_size;
}
/* tail */
if(sec_tail > 0)
{
flash_read(dev,dev->buffer+sec_tail, start_addr+sec_tail, dev->sector_size - sec_tail);
memcpy(dev->buffer,buffer,sec_tail);
flash_erase_sector(dev,start_addr);
for (uint32_t j=0; j<16; j++) {
flash_pageprogram(dev, dev->buffer + (j<<dev->page_bits),start_addr + (j<<dev->page_bits), dev->page_size);
}
}
return len;
}
三.测试
封装一个flash_itf.c/h
实现FLASH读写,屏蔽实例相关信息。
flash_itf.h
#ifndef FLASH_ITF_H
#define FLASH_ITF_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* \fn flash_itf_init
* 初始化
*/
int flash_itf_init(void);
/**
* \fn flash_itf_deinit
* 反初始化
*/
int flash_itf_deinit(void);
/**
* \fn flash_itf_read
* 读数据
* \param[in] addr 读开始地址
* \param[out] buffer 存储读出的数据
* \param[in] len 待读出的长度
* \retval 返回实际读出的数据长度
*/
uint32_t flash_itf_read(uint8_t* buffer, uint32_t addr, uint32_t len);
/**
* \fn flash_itf_write
* 写数据
* \param[in] addr 写开始地址
* \param[out] buffer 存储待写的数据
* \param[in] len 待写入的长度
* \retval 返回实际写入的数据长度
*/
uint32_t flash_itf_write(uint8_t* buffer, uint32_t addr, uint32_t len);
#ifdef __cplusplus
}
#endif
#endif
flash_itf.c
#include "flash_itf.h"
#include "flash.h"
#include "spi.h"
#include "FreeRTOS.h"
static flash_dev_st s_flash;
static uint32_t spi_io(uint8_t* tx_buffer, uint8_t* rx_buffer, uint32_t len, int flag)
{
return spi_trans(0, 0, tx_buffer, rx_buffer, len, flag);
}
int flash_itf_init(void)
{
s_flash.spi_trans = spi_io;
s_flash.page_bits = 8;
s_flash.page_size = 256;
s_flash.sector_bits = 12;
s_flash.sector_size = 4096;
s_flash.buffer = pvPortMalloc(4096);
return 0;
}
int flash_itf_deinit(void)
{
vPortFree(s_flash.buffer);
return 0;
}
uint32_t flash_itf_read(uint8_t* buffer, uint32_t addr, uint32_t len)
{
return flash_read(&s_flash, buffer, addr, len);
}
uint32_t flash_itf_write(uint8_t* buffer, uint32_t addr, uint32_t len)
{
return flash_write(&s_flash, buffer, addr, len);
}
Shell_func.c中
#include "flash_itf.h"
申明两个函数
static void printflashfunc(uint8_t* param);
static void writeflashfunc(uint8_t* param);
g_shell_cmd_list_ast中添加两行,添加两个命令
{ (uint8_t*)"printflash", printflashfunc, (uint8_t*)"printflash addr[hex] len"},
{ (uint8_t*)"writeflash", writeflashfunc, (uint8_t*)"writeflash addr[hex] hexstr"},
实现如下
static void printflashfunc(uint8_t* param)
{
uint8_t buffer[16];
uint32_t addr;
uint32_t len;
if(2 == sscanf((const char*)param, "%*s %x %d", &addr, &len))
{
uint32_t toread;
uint32_t read = 0;
while(read < len)
{
toread = ((len-read) > sizeof(buffer)) ? sizeof(buffer) : (len-read);
flash_itf_read(buffer, addr+read, toread);
read += toread;
for(uint32_t i=0; i<toread ;i++)
{
xprintf("%02x ",buffer);
}
xprintf("\r\n");
}
}
}
static void writeflashfunc(uint8_t* param)
{
uint8_t buffer[32+1];
uint8_t hex[16];
uint32_t addr;
uint32_t len;
if(2 == sscanf((const char*)param, "%*s %x %s", &addr, buffer))
{
len = str2hex((const char*)buffer, hex, strlen((char*)buffer));
if(len>0)
{
flash_itf_write(hex,addr,len);
}
}
}
Main.c中初始化
flash_itf_init();
运行看到新添加的命令
测试读写,写入0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 0x99
回读看到正确
四.总结
现在我们基于SPI驱动,根据FLASH的手册,实现了FLASH的读写接口。 FLASH驱动完全可移植可复用。 并增加了shell指令进行读写测试。后面就可以移植文件系统了。