打印
[其他ST产品]

【正点原子K210连载】第二十章 NOR Flash实验《DNK210使用指南-SDK版》

[复制链接]
598|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
第二十章 NOR Flash实验

本章将介绍如何使用Kendryte K210SPI功能,并实现对外部Flash读写并把结果显示在TFTLCD模块上。通过学习本章内容,读者将掌握利用SDK编程技术实现外部Flash的读写方法。
本章分为如下几个小节:
20.1SPI和NOR Flash芯片简介
20.2 硬件设计
20.3 程序设计
20.4 运行验证


20.1 SPINOR Flash简介
20.1.1 SPI简介
有关Kendryte K210的SPI模块介绍,请见第16.1.1小节《SPI简介》。
20.1.2 NOR Flash简介20.1.2.1 Flash简介
Flash是常见的用于存储数据的半导器件它具有容量、可重复擦写、按“扇区/块”擦除、掉电后数据继续保存的特性。常见的Flash主要有NOR FlashNand Flash两种类型,它们的特性如表20.1.2.1.1所。NORNAND两种数门电路,可以简单地认为Flash内部存储单元使哪种门作存储单元就是哪类型的FlashU盘,SSDeMMC等为NAND型NOR Flash则根据设计需要灵活应用于各类PCBBIOS手机等。
特性
NOR FLASH
NAND FLASH
容量
同容量存储器成本
较贵
较便宜
擦除单元
“扇区/”擦除
“扇区/”擦除
读写单元
可以基于字节读写
必须以“块”为单位读写
读取速度
较高
较低
写入速度
较低
较高
集成度
较低
较高
介质类型
随机存储
连续存储
地址线和数据线
独立分开
共用
坏块
较少
较多
是否支持XIP
支持
不支持
表20.1.2.1.1 NOR FlashNAND Flash特性对比
NORNAND在数据写入前都需要有擦除操作,但实际上NOR Flash的一个bit可以从1变成0,而要从01就要擦除后再写入NAND Flash这两种情况都需要擦除。擦除操作的最小单位“扇区/块”,这意着有时候即使只一字节的数据,这个“扇区/块”上之前的数据都可能会被擦除
NOR的地址线和数据线分开,它可以按“字节”读写数据,符合CPU的指令译码执行要求,所以假如NOR上存储了代码指令,CPUNOR一个地址,NOR就能向CPU返回一个数据让CPU执行,中间不需要额外的处理操作,这体现于表20.1.2.1.1中的支持XIP特性(eXecute In Place)。因此可以用NOR Flash直接作为嵌入式MCU程序存储空间。
NAND的数据和地址线共用,只能按“块”来读写数据,假如NAND上存储了代码指令,CPUNAND地址后,它无法直接返回该地址的数据,所以不符合指令译码要求。
若代码存储在NAND上,可以把它先加载到RAM存储器上,再由CPU执行。所以在功能上可以认为NOR是一种断电后数据不丢失的RAM,但它的擦除单位与RAM有区别,且读写速度比RAM要慢得多。
Flash有对应的缺点,我使用过程中需要尽量去规避这些问题:Flash使用寿命,一个是可能的位反转。
使用寿命体现在读写上是Flash的擦除次数都是有限的(NOR Flash普遍是10万次左右),当它的使用接近寿命的时候,可能会出现写操作失败。由于NAND通常是整块擦写,块内有一位失效整个块就会失效,这被称为坏块。使NAND Flash最好通过算法扫描介质找出坏块并标为不可用,因为坏块上的数据是不准确的。
反转是数据位写入1但经过一定时间的环境变化后可能实际变为0情况亦然。位反转的原因很多,可能是器件特性也可能与环境干扰有关,由于位反转问题可能存在,所以FLASH存储器需要“探测/错误更正(EDC/ECC)”算法来确保数据的正确性。
FLASH芯片有很多种芯片型号,比如有:W25Q128BY25Q128NM25Q128,它们是来自不同的厂商的同种规格的NOR Flash芯片,内存空间都是128M字,即16M字节。它们的很多参数、操作都是一样的。
下面我们以W25Q128为例,认识一下具体NOR Flash的特性。
W25Q128是一款大容量SPI Flash产品,其容量为16M。它将16M字节的容量分为256个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每一个扇区16页,每页256个字节,即每个扇区4K个字节。W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q128开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。
W25Q128的擦写周期多达10W次,具有20年的数据保存期限,支持电压为2.7~3.6VW25Q128支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可以到104Mhz(双输出时相当于208Mhz,四输出时相当于280M)。
下面我们看一下W25Q128芯片的管脚图,如图20.1.2.1.1所示。
20.1.2.1.1 W25Q128芯片引脚图
芯片引脚连接如下:CS即片选信号输入,低电平有效;DO是MISO引脚,CLK管脚的下降沿输出数据WP是写保护管脚,高电平可读可写,低电平仅仅可读DIMOSI引脚,主机发送的数据、地址和命令从SI引脚输入到芯片内部,在CLK管脚的上升沿捕获数据CLK是串行时钟引脚,为输入输出提供时钟脉冲;HOLD是保持管脚,低电平有效
Kendryte K210通过SPI总线连接到W25Q128对应的引脚即可启动数据传输。
20.1.2.2 NOR Flash工作时序
前面对于W25Q128的介绍中也提及其存储的体系,W25Q128有写入、读取还有擦除的功能,下面就对这三种操作的时序进行分析,在后面通过代码的形式驱动它。
下面先让我们看一下读操作时序,如图20.1.2.2.1所示:
图20.1.2.2.1 W25Q128读操作时序图
从上图可知读数据指令是03H,可以读出一个字节或者多个字节。发起读操作时,先把CS片选管脚拉低,然后通过MOSI引脚把03H发送芯片,之后再发送要读取的24位地址,这些数据在CLK上升沿时采样。芯片接收完24位地址之后,就会把相对应地址的数据在CLK引脚下降沿从MISO引脚发送出去。从图中可以看出只要CLK一直在工作,那么通过一条读指令就可以把整个芯片存储区的数据读出来。当主机把CS引脚拉高,数据传输停止。
接着我们看一下写时序,这里我们先看页写时序,如图20.1.2.2.2所示:
图20.1.2.2.2 W25Q128页写时序
在发送页写指令之前,需要先发送“写使能”指令。然后主机拉低CS引脚,然后通过MOSI引脚把02H发送到芯片,接着发送24位地址,最后你就可以发送你需要写的字节数据到芯片。完成数据写入之后,需要拉高CS引脚,停止数据传输。
下面介绍一下扇区擦除时序,如图20.1.2.2.3所示:
图20.1.2.2.3 扇区擦除时序图
扇区擦除指的是将一个扇区擦除,通过前面的介绍也知道,W25Q128的扇区大小是4K字节。擦除扇区后,扇区的位全置1,即扇区字节为FFh。同样的,在执行扇区擦除之前,需要先执行写使能指令。这里需要注意的是当前SPI总线的状态,假如总线状态是BUSY,那么这个扇区擦除是无效的,所以在拉低CS引脚准备发送数据前,需要先要确定SPI总线的状态,这就需要执行读状态寄存器指令,读取状态寄存器的BUSY位,需要等待BUSY位为0,才可以执行擦除工作。
接着按时序图分析,主机先拉低CS引脚,然后通过MOSI引脚发送指令代码20h到芯片,然后接着把24位扇区地址发送到芯片,然后需要拉高CS引脚,通过读取寄存器状态等待扇区擦除操作完成。
此外还有对整个芯片进行擦除的操作,时序比扇区擦除更加简单,不用发送24bit地址,只需要发送指令代码C7h到芯片即可实现芯片的擦除。
在W25Q128手册中还有许多种方式的读//擦除操作,我们这里只分析本实验用到的,其他大家可以参考W25Q128手册,存放路径Aà硬件资料à芯片资料。
20.2 硬件设计
20.2.1 例程功能
1. 通过KEY1按键来控制NOR Flash的写入,通过按键KEY0来控制NOR Flash的读取。并在LCD模块上显示相关信息。
20.2.2 硬件资源
1. 独立按键
        KEY0按键 - IO18
        KEY1按键 - IO19
        KEY2按键 - IO16
2. NOR Flash
        CS - F_CS
        SO - F_D1
        WP - F_D2
SI - F_D0
        CLK - F_CLK
        HOLP - F_D3
3. LCD
        LCD_RD - IO34
        LCD_BL - IO35
        LCD_CS - IO36
        LCD_RST - IO37
        LCD_RS - IO38
        LCD_WR - IO39
        LCD_D0~LCD_D7 - SPI0_D0~SPI0_D7
20.2.3 原理图
本章实验内容,需要使用到板载的W25Q128,正点原子DNK210开发板上的NOR Flash连接原理图,如下图所示:
20.2.3.1 ATK-MC2640 NOR Flash接口原理图
20.3 程序设计
20.3.1 NOR Flash驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。NOR Flash驱动源码包括四个文件:w25qxx.c、w25qxx.h、norflash.cnorflash.h。这四个文件可以做个简单的区分,w25qxx.cw25qxx.h文件来源于官方的SDK裸机编程DEMO,用于NOR Flash的底层驱动,norflash.cnorflash.h文件是由正点原子团队为了符合我们的代码规范而编写,提供函数接口方便外部FLASH的读写和擦除等。我们这里主要介绍norflash.cnorflash.h文件的函数。
下面介绍norflash.c文件几个重要的函数,首先是NOR Flash初始化函数,其定义如下:
/**
* @brief       初始化NOR FLASH
* @param      
* @retval      0:成功  1:失败
*/
void norflash_init(void)
{
    uint8_t manuf_id, device_id;
    uint8_t spi_index = 3, spi_ss = 0;
    w25qxx_init(spi_index, spi_ss);
    w25qxx_enable_quad_mode();   /* flash 四倍模式开启*/
    /* 读取flashID */
    norflash_read_id(&manuf_id, &device_id);
    printf("manuf_id:0x%02x, device_id:0x%02x\r\n", manuf_id, device_id);
    if ((manuf_id != 0xEF && manuf_id != 0xC8) || (device_id != 0x17 && device_id != 0x16))
    {
        /* flash初始化失败 */
        printf("w25qxx_read_id error\n");
        printf("manuf_id:0x%02x, device_id:0x%02x\r\n", manuf_id, device_id);
        return 0;
    }
    else
    {
        return 1;
    }
}
在初始化函数中,首先定义变量manuf_id和device_id,分别用于存放NOR Flash的厂家ID和芯片ID,然后选择SPI总线和设备号初始化W25Q128,使能SPI四线模式,最后调用norflash_read_id函数读取厂家ID和设备ID判断W25Q128是否初始化成功,W25Q128的厂家ID0XEF,设备ID0X17,读取成功函数返回0
下面介绍一下FLASH读取函数,其定义如下:
/**
* @brief       读取SPI FLASH
*   @NOTE      在指定地址开始读取指定长度的数据
* @param       pbuf    : 数据存储区
* @param       addr    : 开始读取的地址
* @param       datalen : 要读取的字节数
* @retval      
*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
{
    /* norflash读取数据 */
    w25qxx_read_data(addr, pbuf, datalen, W25QXX_QUAD_FAST);
}
该函数直接调用w25qxx_read_data函数实现,大家可以访问到w25qxx.c文件的_w25qxx_read_data函数,这里可以根据前面的时序图对照理解。
有读函数,那肯定就有写函数,接下来我们介绍一下NOR FLASH写函数,其定义如下:
/**
* @brief       SPI FLASH
*   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
*              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1Block
*              擦除的最小单位为Sector.
*
* @param       pbuf    : 数据存储区
* @param       addr    : 开始写入的地址
* @param       datalen : 要写入的字节数
* @retval      
*/
void norflash_write(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
{
    w25qxx_write_data(addr, pbuf, datalen);
}
该函数直接调用w25qxx_write_data函数实现,我们在w25qxx.c文件找到w25qxx_write_data函数,其代码如下所示。
w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t *data_buf, uint32_t length)
{
    uint32_t sector_addr = 0;
    uint32_t sector_offset = 0;
    uint32_t sector_remain = 0;
    uint32_t write_len = 0;
    uint32_t index = 0;
    uint8_t *pread = NULL;
    uint8_t *pwrite = NULL;
    uint8_t swap_buf[w25qxx_FLASH_SECTOR_SIZE] = {0};
    while (length)
    {
        sector_addr = addr & (~(w25qxx_FLASH_SECTOR_SIZE - 1));
        sector_offset = addr & (w25qxx_FLASH_SECTOR_SIZE - 1);
        sector_remain = w25qxx_FLASH_SECTOR_SIZE - sector_offset;
        write_len = ((length < sector_remain) ? length : sector_remain);
        w25qxx_read_data(sector_addr, swap_buf, w25qxx_FLASH_SECTOR_SIZE);
        pread = swap_buf + sector_offset;
        pwrite = data_buf;
        for (index = 0; index < write_len; index++)
        {
            if ((*pwrite) != ((*pwrite) & (*pread)))
            {
                w25qxx_sector_erase(sector_addr);
                while (w25qxx_is_busy() == W25QXX_BUSY)
                    ;
                break;
            }
            pwrite++;
            pread++;
        }
        if (write_len == w25qxx_FLASH_SECTOR_SIZE)
        {
            w25qxx_sector_program(sector_addr, data_buf);
        }
        else
        {
            pread = swap_buf + sector_offset;
            pwrite = data_buf;
            for (index = 0; index < write_len; index++)
                *pread++ = *pwrite++;
            w25qxx_sector_program(sector_addr, swap_buf);
        }
        length -= write_len;
        addr += write_len;
        data_buf += write_len;
    }
    return W25QXX_OK;
}
该函数可以在NOR Flash的任意地址开始写入任意长度(必须不超过NOR Flash的容量)的数据。我们这里简单介绍一下思路:先获得首地址(addr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个swap_buf的数组,用于擦除时缓存扇区内的数据。
下面简单介绍一下擦除函数,前面工作时序中也有对此描述,现在就来看一下代码:
/**
* @brief       擦除整个芯片
*   @note      等待时间超长...
* @param      
* @retval      
*/
void norflash_erase_chip(void)
{
    w25qxx_chip_erase();
}
/**
* @brief       擦除一个扇区
*   @note      注意,这里是扇区地址,不是字节地址!!
*
* @param       saddr : 扇区地址 根据实际容量设置
* @retval      
*/
void norflash_erase_sector(uint32_t saddr)
{
    //printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */
    w25qxx_sector_erase(saddr);
}
该代码也是老套路,直接调用对应的函数实现擦除功能,norflash_erase_chip函数是整片擦除,所需时间较长,norflash_erase_sector函数是按扇区擦除。需要注意的是假如调用了norflash_erase_chip函数将会对整个NOR Flash进行擦除,一般情况不建议对整个NOR Flash进行擦除,因为Kendryte K210的程序存储在NOR Flash上,整片擦除会导致程序全部丢失
20.3.2 main.c代码
main.c中的代码如下所示:
/* 要写入到FLASH的字符串数组 */
const uint8_t g_text_buf[] = {"NORFLASH TEST"};
#define LCD_SPI_CLK_RATE 15000000
#define TEXT_SIZE   sizeof(g_text_buf) + 4      /* TEXT字符串长度 */
#define DATA_ADDRESS 0xB00000                   /* 读写地址 */
int main(void)
{
    uint8_t key;
    uint8_t i = 0;
    uint8_t datatemp[TEXT_SIZE];
    sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
    sysctl_pll_set_freq(SYSCTL_PLL1, 400000000);
    sysctl_pll_set_freq(SYSCTL_PLL2, 45158400);
    sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
    sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
    sysctl_set_spi0_dvp_data(1);
    lcd_init();                             /* 初始化LCD */
    lcd_set_direction(DIR_YX_LRUD);
    key_init();                             /* 初始化按键 */
    norflash_init();                        /* 初始化NORFLASH */
    lcd_draw_string(10, 10, "KEY1:Write  KEY0:Read", RED); /* 显示提示信息 */
    lcd_draw_string(10, 30, "SPI FLASH Ready!", BLUE);
    while (1)
    {
        key = key_scan(0);
        if (key == KEY1_PRES)   /* KEY1按下,写入 */
        {
            lcd_draw_fill_rectangle(0, 50, 319, 90, WHITE);         /* 清除显示 */
            lcd_draw_string(10, 50, "Start Write FLASH.", BLUE);
            sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
            norflash_write((uint8_t *)datatemp, DATA_ADDRESS, TEXT_SIZE);
            lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);        /* 清除显示 */
            lcd_draw_string(10, 50, "FLASH Write Finished!", BLUE);
        }
        else if (key == KEY0_PRES)   /* KEY0按下,读取字符串并显示 */
        {
            lcd_draw_fill_rectangle(10, 50, 319, 90, WHITE);     /* 清除显示 */
            lcd_draw_string(10, 50, "Start Read FLASH.", BLUE);
            norflash_read(datatemp, DATA_ADDRESS, TEXT_SIZE);      
            lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);       /* 清除显示 */
            lcd_draw_string(10, 50, "The Data Readed Is:   ", BLUE);
            lcd_draw_string(10, 70, (char *)datatemp, BLUE);   
        }
        i++;
        if (i > 200)
        {
            i = 0;
        }
        
        msleep(10);
    }
}
main函数前面,我们定义了g_text_buf数组,用于存放要写入到FLASH的字符串。main函数代码具体流程大致是:首先完成系统级和按键、LCD、NOR Flash初始化工作,然后提醒交互信息,最后通过KEY0去读取地址DATA_ADDRESS处开始的数据并把数据显示在LCD上;另外还可以通过KEY1在地址DATA_ADDRESS处写入g_text_buf数据并在LCD界面中显示传输中,完成后并显示“FLASH Write Finished!”。
20.4 运行验证
DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,可以看到LCD显示信息,LCD显示的内容如图20.4.1所示:
图20.4.1 SPI实验程序运行效果图
通过先按下KEY1写入数据,然后再按KEY0读取数据,得到如图20.4.2所示:
图20.4.2 操作后的显示效果图

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

91

主题

92

帖子

1

粉丝