搜索
bottom↓
回复: 0

《DNK210使用指南 -SDK版 V1.0》第二十章 NOR Flash实验

[复制链接]

出0入234汤圆

发表于 昨天 09:45 | 显示全部楼层 |阅读模式
2.jpg
1)实验平台:正点原子DNK210开发板
2)购买链接:https://detail.tmall.com/item.htm?id=782801398750
3)全套实验源码+手册+视频下载地址:http://openedv.com/thread-348335-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子手把手教你学DNK210快速入门视频教程:https://www.bilibili.com/video/BV1kD421G7fu
6)正点原子FPGA交流群:132780729
1.png
3.png

第二十章 NOR Flash实验


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

       20.1 SPI和NOR 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 Flash和Nand Flash两种类型,它们的特性如表20.1.2.1.1所示。NOR和NAND是两种数字门电路,可以简单地认为Flash内部存储单元使用哪种门作存储单元就是哪类型的Flash。U盘,SSD,eMMC等为NAND型,而NOR Flash则根据设计需要灵活应用于各类PCB上,如BIOS,手机等。

1.png
表20.1.2.1.1 NOR Flash和NAND Flash特性对比

       NOR与NAND在数据写入前都需要有擦除操作,但实际上NOR Flash的一个bit可以从1变成0,而要从0变1就要擦除后再写入,NAND Flash这两种情况都需要擦除。擦除操作的最小单位为“扇区/块”,这意味着有时候即使只写一字节的数据,则这个“扇区/块”上之前的数据都可能会被擦除。
       NOR的地址线和数据线分开,它可以按“字节”读写数据,符合CPU的指令译码执行要求,所以假如NOR上存储了代码指令,CPU给NOR一个地址,NOR就能向CPU返回一个数据让CPU执行,中间不需要额外的处理操作,这体现于表20.1.2.1.1中的支持XIP特性(eXecute In Place)。因此可以用NOR Flash直接作为嵌入式MCU的程序存储空间。
       NAND的数据和地址线共用,只能按“块”来读写数据,假如NAND上存储了代码指令,CPU给NAND地址后,它无法直接返回该地址的数据,所以不符合指令译码要求。
       若代码存储在NAND上,可以把它先加载到RAM存储器上,再由CPU执行。所以在功能上可以认为NOR是一种断电后数据不丢失的RAM,但它的擦除单位与RAM有区别,且读写速度比RAM要慢得多。
       Flash也有对应的缺点,我们在使用过程中需要尽量去规避这些问题:一是Flash的使用寿命,另一个是可能的位反转。
       使用寿命体现在:读写上是Flash的擦除次数都是有限的(NOR Flash普遍是10万次左右),当它的使用接近寿命的时候,可能会出现写操作失败。由于NAND通常是整块擦写,块内有一位失效整个块就会失效,这被称为坏块。使用NAND Flash最好通过算法扫描介质找出坏块并标记为不可用,因为坏块上的数据是不准确的。
       位反转是数据位写入时为1,但经过一定时间的环境变化后可能实际变为0的情况,反之亦然。位反转的原因很多,可能是器件特性也可能与环境、干扰有关,由于位反转的问题可能存在,所以FLASH存储器需要“探测/错误更正(EDC/ECC)”算法来确保数据的正确性。
       FLASH芯片有很多种芯片型号,比如有:W25Q128、BY25Q128、NM25Q128,它们是来自不同的厂商的同种规格的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.6V,W25Q128支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可以到104Mhz(双输出时相当于208Mhz,四输出时相当于280M)。
       下面我们看一下W25Q128芯片的管脚图,如图20.1.2.1.1所示。

第二十章 NOR Flash实验2129.png
图 20.1.2.1.1 W25Q128芯片引脚图

       芯片引脚连接如下:CS即片选信号输入,低电平有效;DO是MISO引脚,在CLK管脚的下降沿输出数据;WP是写保护管脚,高电平可读可写,低电平仅仅可读;DI是MOSI引脚,主机发送的数据、地址和命令从SI引脚输入到芯片内部,在CLK管脚的上升沿捕获数据;CLK是串行时钟引脚,为输入输出提供时钟脉冲;HOLD是保持管脚,低电平有效。
       Kendryte K210通过SPI总线连接到W25Q128对应的引脚即可启动数据传输。

       20.1.2.2 NOR Flash工作时序
       前面对于W25Q128的介绍中也提及其存储的体系,W25Q128有写入、读取还有擦除的功能,下面就对这三种操作的时序进行分析,在后面通过代码的形式驱动它。
       下面先让我们看一下读操作时序,如图20.1.2.2.1所示:

第二十章 NOR Flash实验2500.png
图20.1.2.2.1 W25Q128读操作时序图

       从上图可知读数据指令是03H,可以读出一个字节或者多个字节。发起读操作时,先把CS片选管脚拉低,然后通过MOSI引脚把03H发送芯片,之后再发送要读取的24位地址,这些数据在CLK上升沿时采样。芯片接收完24位地址之后,就会把相对应地址的数据在CLK引脚下降沿从MISO引脚发送出去。从图中可以看出只要CLK一直在工作,那么通过一条读指令就可以把整个芯片存储区的数据读出来。当主机把CS引脚拉高,数据传输停止。
       接着我们看一下写时序,这里我们先看页写时序,如图20.1.2.2.2所示:

第二十章 NOR Flash实验2772.png
图20.1.2.2.2 W25Q128页写时序

       在发送页写指令之前,需要先发送“写使能”指令。然后主机拉低CS引脚,然后通过MOSI引脚把02H发送到芯片,接着发送24位地址,最后你就可以发送你需要写的字节数据到芯片。完成数据写入之后,需要拉高CS引脚,停止数据传输。
       下面介绍一下扇区擦除时序,如图20.1.2.2.3所示:

第二十章 NOR Flash实验2938.png
图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连接原理图,如下图所示:

第二十章 NOR Flash实验3856.png
图20.2.3.1 ATK-MC2640 NOR Flash接口原理图


       20.3 程序设计

       20.3.1 NOR Flash驱动代码
       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。NOR Flash驱动源码包括四个文件:w25qxx.c、w25qxx.h、norflash.c和norflash.h。这四个文件可以做个简单的区分,w25qxx.c和w25qxx.h文件来源于官方的SDK裸机编程DEMO,用于NOR Flash的底层驱动,norflash.c和norflash.h文件是由正点原子团队为了符合我们的代码规范而编写,提供函数接口方便外部FLASH的读写和擦除等。我们这里主要介绍norflash.c和norflash.h文件的函数。
       下面介绍norflash.c文件几个重要的函数,首先是NOR Flash初始化函数,其定义如下:
  1. /**
  2. * @brief       初始化NOR FLASH
  3. * @param       无
  4. * @retval      0:成功  1:失败
  5. */
  6. void norflash_init(void)
  7. {
  8.     uint8_t manuf_id, device_id;
  9.     uint8_t spi_index = 3, spi_ss = 0;
  10.     w25qxx_init(spi_index, spi_ss);
  11.     w25qxx_enable_quad_mode();   /* flash 四倍模式开启*/
  12.     /* 读取flash的ID */
  13.     norflash_read_id(&manuf_id, &device_id);
  14.     printf("manuf_id:0x%02x, device_id:0x%02x\r\n", manuf_id, device_id);

  15.     if ((manuf_id != 0xEF && manuf_id != 0xC8) || (device_id != 0x17 && device_id != 0x16))
  16.     {
  17.         /* flash初始化失败 */
  18.         printf("w25qxx_read_id error\n");
  19.         printf("manuf_id:0x%02x, device_id:0x%02x\r\n", manuf_id, device_id);
  20.         return 0;
  21.     }
  22.     else
  23.     {
  24.         return 1;
  25.     }
  26. }
复制代码
       在初始化函数中,首先定义变量manuf_id和device_id,分别用于存放NOR Flash的厂家ID和芯片ID,然后选择SPI总线和设备号初始化W25Q128,使能SPI四线模式,最后调用norflash_read_id函数读取厂家ID和设备ID判断W25Q128是否初始化成功,W25Q128的厂家ID是0XEF,设备ID是0X17,读取成功函数返回0。
       下面介绍一下FLASH读取函数,其定义如下:
  1. /**
  2. * @brief       读取SPI FLASH
  3. *   @note      在指定地址开始读取指定长度的数据
  4. * @param       pbuf    : 数据存储区
  5. * @param       addr    : 开始读取的地址
  6. * @param       datalen : 要读取的字节数
  7. * @retval      无
  8. */
  9. void norflash_read(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
  10. {
  11.     /* norflash读取数据 */
  12.     w25qxx_read_data(addr, pbuf, datalen, W25QXX_QUAD_FAST);
  13. }
复制代码
       该函数直接调用w25qxx_read_data函数实现,大家可以访问到w25qxx.c文件的_w25qxx_read_data函数,这里可以根据前面的时序图对照理解。
       有读函数,那肯定就有写函数,接下来我们介绍一下NOR FLASH写函数,其定义如下:
  1. /**
  2. * @brief       写SPI FLASH
  3. *   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
  4. *              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
  5. *              擦除的最小单位为Sector.
  6. *
  7. * @param       pbuf    : 数据存储区
  8. * @param       addr    : 开始写入的地址
  9. * @param       datalen : 要写入的字节数
  10. * @retval      无
  11. */
  12. void norflash_write(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
  13. {
  14.     w25qxx_write_data(addr, pbuf, datalen);
  15. }
复制代码
       该函数直接调用w25qxx_write_data函数实现,我们在w25qxx.c文件找到w25qxx_write_data函数,其代码如下所示。
  1. w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t *data_buf, uint32_t length)
  2. {
  3.     uint32_t sector_addr = 0;
  4.     uint32_t sector_offset = 0;
  5.     uint32_t sector_remain = 0;
  6.     uint32_t write_len = 0;
  7.     uint32_t index = 0;
  8.     uint8_t *pread = NULL;
  9.     uint8_t *pwrite = NULL;
  10.     uint8_t swap_buf[w25qxx_FLASH_SECTOR_SIZE] = {0};

  11.     while (length)
  12.     {
  13.         sector_addr = addr & (~(w25qxx_FLASH_SECTOR_SIZE - 1));
  14.         sector_offset = addr & (w25qxx_FLASH_SECTOR_SIZE - 1);
  15.         sector_remain = w25qxx_FLASH_SECTOR_SIZE - sector_offset;
  16.         write_len = ((length < sector_remain) ? length : sector_remain);
  17.         w25qxx_read_data(sector_addr, swap_buf, w25qxx_FLASH_SECTOR_SIZE);
  18.         pread = swap_buf + sector_offset;
  19.         pwrite = data_buf;
  20.         for (index = 0; index < write_len; index++)
  21.         {
  22.             if ((*pwrite) != ((*pwrite) & (*pread)))
  23.             {
  24.                 w25qxx_sector_erase(sector_addr);
  25.                 while (w25qxx_is_busy() == W25QXX_BUSY)
  26.                     ;
  27.                 break;
  28.             }
  29.             pwrite++;
  30.             pread++;
  31.         }
  32.         if (write_len == w25qxx_FLASH_SECTOR_SIZE)
  33.         {
  34.             w25qxx_sector_program(sector_addr, data_buf);
  35.         }
  36.         else
  37.         {
  38.             pread = swap_buf + sector_offset;
  39.             pwrite = data_buf;
  40.             for (index = 0; index < write_len; index++)
  41.                 *pread++ = *pwrite++;
  42.             w25qxx_sector_program(sector_addr, swap_buf);
  43.         }
  44.         length -= write_len;
  45.         addr += write_len;
  46.         data_buf += write_len;
  47.     }
  48.     return W25QXX_OK;
  49. }
复制代码
       该函数可以在NOR Flash的任意地址开始写入任意长度(必须不超过NOR Flash的容量)的数据。我们这里简单介绍一下思路:先获得首地址(addr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个swap_buf的数组,用于擦除时缓存扇区内的数据。
       下面简单介绍一下擦除函数,前面工作时序中也有对此描述,现在就来看一下代码:
  1. /**
  2. * @brief       擦除整个芯片
  3. *   @note      等待时间超长...
  4. * @param       无
  5. * @retval      无
  6. */
  7. void norflash_erase_chip(void)
  8. {
  9.     w25qxx_chip_erase();
  10. }

  11. /**
  12. * @brief       擦除一个扇区
  13. *   @note      注意,这里是扇区地址,不是字节地址!!
  14. *
  15. * @param       saddr : 扇区地址 根据实际容量设置
  16. * @retval      无
  17. */
  18. void norflash_erase_sector(uint32_t saddr)
  19. {
  20.     //printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */
  21.     w25qxx_sector_erase(saddr);
  22. }
复制代码
       该代码也是老套路,直接调用对应的函数实现擦除功能,norflash_erase_chip函数是整片擦除,所需时间较长,norflash_erase_sector函数是按扇区擦除。需要注意的是假如调用了norflash_erase_chip函数将会对整个NOR Flash进行擦除,一般情况不建议对整个NOR Flash进行擦除,因为Kendryte K210的程序存储在NOR Flash上,整片擦除会导致程序全部丢失。

       20.3.2 main.c代码
       main.c中的代码如下所示:
  1. /* 要写入到FLASH的字符串数组 */
  2. const uint8_t g_text_buf[] = {"NORFLASH TEST"};

  3. #define LCD_SPI_CLK_RATE 15000000
  4. #define TEXT_SIZE   sizeof(g_text_buf) + 4      /* TEXT字符串长度 */
  5. #define DATA_ADDRESS 0xB00000                   /* 读写地址 */

  6. int main(void)
  7. {
  8.     uint8_t key;
  9.     uint8_t i = 0;
  10.     uint8_t datatemp[TEXT_SIZE];

  11.     sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
  12.     sysctl_pll_set_freq(SYSCTL_PLL1, 400000000);
  13.     sysctl_pll_set_freq(SYSCTL_PLL2, 45158400);
  14.     sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
  15.     sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
  16.     sysctl_set_spi0_dvp_data(1);

  17.     lcd_init();                             /* 初始化LCD */
  18.     lcd_set_direction(DIR_YX_LRUD);
  19.     key_init();                             /* 初始化按键 */
  20.     norflash_init();                        /* 初始化NORFLASH */

  21.     lcd_draw_string(10, 10, "KEY1:Write  KEY0:Read", RED); /* 显示提示信息 */
  22.     lcd_draw_string(10, 30, "SPI FLASH Ready!", BLUE);

  23.     while (1)
  24.     {
  25.         key = key_scan(0);

  26.         if (key == KEY1_PRES)   /* KEY1按下,写入 */
  27.         {
  28.             lcd_draw_fill_rectangle(0, 50, 319, 90, WHITE);         /* 清除显示 */
  29.             lcd_draw_string(10, 50, "Start Write FLASH.", BLUE);

  30.             sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
  31.             norflash_write((uint8_t *)datatemp, DATA_ADDRESS, TEXT_SIZE);
  32.             lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);        /* 清除显示 */
  33.             lcd_draw_string(10, 50, "FLASH Write Finished!", BLUE);
  34.         }

  35.         else if (key == KEY0_PRES)   /* KEY0按下,读取字符串并显示 */
  36.         {
  37.             lcd_draw_fill_rectangle(10, 50, 319, 90, WHITE);     /* 清除显示 */
  38.             lcd_draw_string(10, 50, "Start Read FLASH.", BLUE);
  39.             norflash_read(datatemp, DATA_ADDRESS, TEXT_SIZE);      
  40.             lcd_draw_fill_rectangle(10, 50, 319, 70, WHITE);       /* 清除显示 */
  41.             lcd_draw_string(10, 50, "The Data Readed Is:   ", BLUE);
  42.             lcd_draw_string(10, 70, (char *)datatemp, BLUE);   
  43.         }

  44.         i++;
  45.         if (i > 200)
  46.         {
  47.             i = 0;
  48.         }
  49.         
  50.         msleep(10);
  51.     }
  52. }
复制代码
       在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所示:

第二十章 NOR Flash实验11177.png
图20.4.1 SPI实验程序运行效果图

       通过先按下KEY1写入数据,然后再按KEY0读取数据,得到如图20.4.2所示:

第二十章 NOR Flash实验11241.png
图20.4.2 操作后的显示效果图

阿莫论坛20周年了!感谢大家的支持与爱护!!

你熬了10碗粥,别人一桶水倒进去,淘走90碗,剩下10碗给你,你看似没亏,其实你那10碗已经没有之前的裹腹了,人家的一桶水换90碗,继续卖。说白了,通货膨胀就是,你的钱是挣来的,他的钱是印来的,掺和在一起,你的钱就贬值了。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-10-16 16:05

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表