周立功教授新书《面向AMetal框架与接口的编程(上)》,对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向接口编程的开发思想,聚焦自己的“核心域”,改变自己的编程思维,实现企业和个人的共同进步。经周立功教授授权,即日起,致远电子公众号将对该书内容进行连载,愿共勉之。
第六章为重用外设驱动代码,本文内容为6.2 SPI NOR Flash 存储器。
6.2 SPI NOR Flash 存储器
SPI NOR Flash 是一种SPI 接口的非易失闪存芯片,本节以***旺宏电子的MX25L1606为例详细介绍在AMetal 中如何使用类似的Flash 存储器。
>>> 6.2.1 基本功能
MX25L1606 总容量为16M(16×1024×1024)bits,即2M字节。每个字节对应一个存储地址,因此其存储数据的地址范围为0x000000 ~ 0x1FFFFF。
在MX25L1606 中,存储器有块(block)、扇区(sector)和页(page)的概念。页大小为256 字节,每个扇区包含16页,扇区大小为4K(4096)字节,每个块包含16 个扇区,块的大小为64K(65536)字节,其组织结构示意图详见表6.5。
表6.5 MX25L1606 存储器组织结构
MX25L1606 的通信接口为标准4 线SPI 接口(支持模式0 和模式3),即CS、MOSI、MISO、CLK,详见图6.3。其中,CS(#1)、SO(#2)、SI(#5)、SCLK(#6)分别为SPI 的CS、MISO、MOSI 和CLK 信号引脚。特别地,WP(#3)用于写保护,HOLD(#7)用于暂停数据传输。一般来说,这两个引脚不会使用,可通过上拉电阻上拉至高电平。MicroPort-NorFlash 模块通过MicroPort 接口与AM824-Core 相连。
图6.3 SPI Flash 电路原理图
>>> 6.2.2 初始化
AMetal 提供了支持常见的MX25L8006、MX25L1606……等系列SPI Flash 器件的驱动函数,使用其它各功能函数前必须先完成初始化,其函数原型(am_mx25xx.h)为:
该函数意在获取器件的实例句柄mx25xx_handle。其中,p_dev 为指向am_mx25xx_dev_t类型实例的指针,p_devinfo 为指向am_mx25xx_devinfo_t 类型实例信息的指针。
(1)实例
定义am_mx25xx_dev_t 类型(am_mx25xx.h)实例如下:
其中,g_mx25xx_dev 为用户自定义的实例,其地址作为p_dev 的实参传递。
(2)实例信息
实例信息主要描述了具体器件的固有信息,即使用的SPI 片选引脚、SPI 模式、SPI 速率和器件具体型号等,其类型am_mx25xx_devinfo_t 的定义(am_mx25xx.h)如下:
其中,spi_mode 为SPI 模式,MX25L1606 支持模式0(AM_SPI_MODE_0)和模式3(AM_SPI_MODE_3)。spi_cs_pin 为与实际电路相关的片选引脚,MicroPort-NorFlash 模块通过MicroPort 接口与AM824-Core 相连时,默认片选引脚为PIO0_1。spi_speed 为时钟信号的频率,针对MX25L1606,其支持的最高频率为86MHz,因此可以将该值直接设置为86000000。但由于LPC824 芯片的主频为30MHz,所以SPI 最大速率仅30MHz。type 为具体器件的型号,其包含了具体型号相关的信息,比如,页大小信息等,当前已经支持的器件型号详见am_mx25xx.h 中对应的宏,MX25L1606 对应的宏为:AM_MX25XX_MX25L1606。
基于以上信息,实例信息定义如下:
其中,g_mx25xx_devinfo 为用户自定义的实例信息,其地址作为p_devinfo 的实参传递。
(3)SPI 句柄spi_handle
若使用LPC824 的SPI0 与MX25L1606 通信,则通过LPC82x 的SPI0 实例初始化函数am_lpc82x_spi0_inst_init()获得SPI 句柄。即:
SPI 句柄即可直接作为spi_handle 的实参传递。
(4)实例句柄
MX25L1606 初始化函数am_mx25xx_init ()的返回值MX25L1606 实例的句柄,作为其它功能接口(擦除、读、写)的第一个参数(handle)的实参。
其类型am_mx25xx_handle_t(am_mx25xx.h)定义如下:
若返回值为NULL,说明初始化失败;若返回值不为NULL,说明返回了有效的handle。
基于模块化编程思想,将初始化相关的实例、实例信息等的定义存放到对应的配置文件中,通过头文件引出实例初始化函数接口,源文件和头文件的程序范例分别详见程序清单6.14 和程序清单6.15。
程序清单6.14 实例初始化函数范例程序(am_hwconf_mx25xx.c)
程序清单6.15 实例初始化函数接口(am_hwconf_mx25xx.h)
后续只需要使用无参数的实例初始化函数,即可获取到MX25xx 的实例句柄。即:
注意,spi_handle 用于区分SPI0、SPI1,mx25xx_handle 用于区分同一系统中的多个MX25xx 器件。
>>> 6.2.3 接口函数
SPI Flash 比较特殊,在写入数据前必须确保相应的地址单元已经被擦除,因此除读写函数外,还有一个擦除函数,其接口函数详见表6.6。
表6.6 MX25xx 接口函数
各API 的返回值含义都是相同的:AM_OK 表示成功,负值表示失败,失败原因可根据具体的值查看am_errno.h 文件中相对应的宏定义。正值的含义由各API 自行定义,无特殊说明时,表明不会返回正值。
1. 擦除
擦除就是将数据全部重置为0xFF,即所有存储单元的位设置为1。擦除操作并不能直接擦除某个单一地址单元,擦除的最小单元为扇区,即每次只能擦除单个或多个扇区。擦除一段地址空间的函数原型为:
其中,handle 为MX25L1606 的实例句柄,addr 为待擦除区域的首地址,由于擦除的最小单元为扇区,因此该地址必须为某扇区的起始地址0x000000(0)、0x001000(4096)、0x002000(2×4096)……同时,擦除长度必须为扇区大小的整数倍。
如果返回AM_OK,说明擦除成功,反之失败。假定需要从0x001000 地址开始,连续擦除2 个扇区,范例程序详见程序清单6.16。
程序清单6.16 擦除范例程序
0x001000 ~ 0x3FFF 空间被擦除了,即可向该段地址空间内写入数据。
2. 写入数据
在写入数据前,需确保写入地址已被擦除。即将需要变为0 的位清0,但写入操作无法将0 变为1。比如,写入数据0x55 就是将bit1、bit3、bit5、bit7 清0,其余位的值保持不变。若存储的数据已经是0x55,再写入0xAA(写入0xAA 实际上就是将bit0、bit2、bit4、bit6清0,其余位不变),则最终存储的数据将变为0x00,而不是后面再写入的0xAA。因此为了保证正常写入数据,写入数据前必须确保相应的地址段已经被擦除了。
从指定的起始地址开始写入一段数据的函数原型为:
如果返回AM_OK,说明写入数据成功,反之失败。假定从0x001000 地址开始,连续写入128 字节数据,范例程序详见程序清单6.17。
程序清单6.17 写入数据范例程序
虽然只写入了128 字节数据,但由于擦除的最小单元为扇区,因此擦除了4096 字节(一个扇区)。已经擦除的区域后续可以直接写入数据,而不必再次擦除,比如,紧接着写入128字节数据后的地址,再写入128 字节数据,详见程序清单6.18。
程序清单6.18 写入数据范例程序
若需要再次从0x001000 地址连续写入128 字节数据,由于之前已经写入过数据,因此必须重新擦除后方可再次写入。
3. 读取数据
从指定的起始地址开始读取一段数据的函数原型为:
如果返回值为AM_OK,则说明读取成功,反之失败。假定从0x001000 地址开始,连续读取128 字节数据,详见程序清单6.19。
程序清单6.19 读取数据范例程序
范例程序的实现和接口详见程序清单6.20 和程序清单6.21。
程序清单6.20 MX25XX 测试程序实现(app_test_mx25xx.c)
由于读写数据需要的缓存空间较大(128 字节),因此在缓存的定义前增加了static 修饰符,使其内存空间从全局数据区域中分配。如果直接从函数的运行栈中分配128 字节空间,则完全有可能导致栈溢出,进而系统崩溃。
程序清单6.21 MX25XX 测试程序接口声明(app_test_mx25xx.h)
相应的范例程序详见程序清单6.22。
程序清单6.22 MX25L1602 读写范例程序
由于app_test_mx25xx()的参数为MX25XX 的实例handle,与MX25xx 器件具有依赖关系,因此无法实现跨平台调用。
>>> 6.2.4 MTD 通用接口函数
由于MX25L1606 是典型的FLASH 存储器件,因此将其抽象为一个读写MX25L1606的MTD(Memory Technology Device),使之与具体器件无关,实现跨平台调用,其函数原型详见表6.7。
表6.7 MTD 通用接口函数
1 MTD 初始化函数
MTD 初始化函数意在获取MTD 实例句柄,其函数原型为:
其中,MX25L1606 实例句柄(mx25xx_handle)作为实参传递给handle,p_mtd 为指向am_mtd_serv_t 类型实例的指针,reserved_nblks 作为实例信息,表明保留的块数。
-
实例(MTD 存储设备)
定义am_mtd_serv_t 类型(am_mtd.h)实例如下:
其中,g_mx25xx_mtd 为用户自定义的实例,其地址作为p_mtd 的实参传递。
-
实例信息
reserved_nblks 表示实例相关的信息,用于MX25L1606 保留的块数,这些保留的块不会被MTD 标准接口使用。保留的块从器件的起始块开始计算,若该值为5,则MX25XX 器件的块0~块4 将不会被MTD 使用,MTD 读写数据将从块5 开始。如果没有特殊需求,则该值设置为0。
将MTD 初始化函数的调用存放到配置文件中,引出对应的实例初始化接口,详见程序清单6.23 和程序清单6.24。
程序清单6.23 新增MTD 实例初始化函数(am_hwconf_mx25xx.c)
程序清单6.24 am_hwconf_mx25xx.h 文件内容更新(1)
am_mx25xx_mtd_inst_init()函数无任何参数,与其相关实例和实例信息的定义均在文件内部完成,因此直接调用该函数即可获得MTD 句柄。即:
这样一来,在后续使用其它MTD 通用接口函数时,均可使用该函数的返回值mtd_handle作为第一个参数(handle)的实参传递。
显然,若使用MX25XX 接口,则调用am_mx25xx_inst_init()获取MX25XX 实例句柄;若使用MTD 通用接口,则调用am_mx25xx_mtd_inst_init()获取MTD 实例句柄。
2. 擦除
写入数据前需要确保相应地址已经被擦除,其函数原型为:
擦除单元的大小可以使用宏AM_MTD_ERASE_UNIT_SIZE_GET()获得。比如:
其中的addr 表示擦除区域的首地址,必须为擦除单元大小的整数倍。同样地,len 也必须为擦除单元大小的整数倍。由于MX25L1606 擦除单元的大小与扇区大小(4096)一样,因此addr 必须为某扇区的起始地址0x000000(0)、0x001000(4096)、0x002000(2×4096)……
如果返回AM_OK,说明擦除成功,反之失败。假定从0x001000 地址开始,连续擦除2个扇区,范例程序详见程序清单6.25。
程序清单6.25 擦除范例程序
使用该段程序后,地址空间0x001000 ~ 0x3FFF 即被擦除了,后续即可向该段地址空间内写入数据。
3. 写入数据
写入数据前需要确保写入地址已被擦除,其函数原型为:
如果返回AM_OK,说明写入数据成功,反之失败。假定从0x001000 地址开始,连续写入128 字节数据的范例程序详见程序清单6.26。
程序清单6.26 写入数据范例程序
4. 读取数据
从指定的起始地址开始读取一段数据的函数原型为:
如果返回值为AM_OK,则说明读取成功,反之失败。假定从0x001000 地址开始,连续读取128 字节数据的范例程序详见程序清单6.27。
程序清单6.27 读取数据范例程序
MTD 通用接口测试程序和接口分别详见程序清单6.28 和程序清单6.29。
程序清单6.28 MTD 测试程序实现(app_test_mtd.c)
程序清单6.29 接口声明(app_test_mtd.h)
由于该程序只需要MTD 句柄,因此与具体器件无关,可以实现跨平台复用。若读写数据的结果完全相等,则返回AM_OK,反之返回AM_ERROR,范例程序详见程序清单6.30。
程序清单6.30 MTD 读写范例程序
>>> 6.2.5 FTL 通用接口函数
由于此前的接口需要在每次写入数据前,确保相应的存储空间已经被擦除,则势必会给编程带来很大的麻烦。与此同时,由于MX25L1606 的某一地址段擦除次数超过10 万次的上限,则在相应段地址空间存储数据将不再可靠。
假设将用户数据存放到0x001000~0x001FFF 连续的4K 地址中,则每次更新这些数据都要重新擦除该地址段。而其它存储空间完全没有使用过,MX25L1606 的使用寿命大打折扣。AMetal 提供了FTL(Flash Translation Layer)通用接口供用户使用,其函数原型详见表6.8。
表6.8 FTL 通用接口函数(am_ftl.h)
1. FTL 初始化函数
FTL 初始化函数意在获取FTL 实例句柄,其函数原型为:
其中,p_ftl 为指向am_ftl_serv_t 类型实例的指针,p_buf 和len 作为实例信息,为FTL驱动程序提供必要的RAM 空间,MTD 初始化函数获得mtd_handle 为MTD 实例句柄。
(1)实例
定义am_ftl_serv_t 类型(am_mtd.h)实例如下:
其中,g_ftl 为用户自定义的实例,其地址作为p_ftl 的实参传递。
(2)实例信息
FTL 驱动程序需要使用一定的RAM 空间,这也是使用FTL 通用接口所要付出的代价。由于该空间的大小与具体器件的容量大小、擦除单元大小相关,因此该内存空间由用户根据实际情况提供。需要的内存大小(字节数)由下面的公式得到:
其中,sizeerase 为擦除单元的大小,对于MX25L1606,其为扇区大小,即4096。sizemtd_chip为MTD 实例的总容量。MX25L1606 对应的MTD 实例,其大小为除去保留块的总容量,若保留块为0,就是MX25L1606 的容量大小,即2M。需要的内存容量大小为:
对于MX25L1606,若使用FTL,则需要大约2.5KB 的RAM。显然对于一些小型嵌入式系统来说,RAM 的耗费实在“太大”了,所以要根据实际情况选择是否使用FTL。若RAM充足,而又比较在意Flash 的使用寿命,可以选择使用FTL。容量大小使用am_ftl.h 中的宏:
该宏根据器件总容量和擦除单元大小,自动计算实际需要的RAM 大小。
若使用FTL 通用接口操作MX25L1606,则需要定义如下内存空间供FTL 使用。即:
其中,g_ftl_buf 为内存空间的首地址,其作为p_buf 的实参传递,内存空间的大小(即数组元素的个数)作为len 的实参传递。
(3)MTD 句柄mtd_handle
该MTD 句柄可以通过MTD 实例初始化函数获得。即:
获得的MTD 句柄即可直接作为mtd_handle 的实参传递。
(4)实例句柄
FTL 初始化函数am_ftl_init ()的返回值为FTL 实例句柄,该句柄将作为读写接口第一个参数(handle)的实参。其类型am_ftl_handle_t(am_ftl.h)定义如下:
若返回值为NULL,说明初始化失败;若返回值不为NULL,说明返回了有效的handle。
将FTL 初始化函数的调用存放到配置文件中,引出对应的实例初始化接口,详见程序清单6.31 和程序清单6.32。
程序清单6.31 新增FTL 实例初始化函数(am_hwconf_mx25xx.c)
程序清单6.32 am_hwconf_mx25xx.h 文件内容更新(2)
am_mx25xx_ftl_inst_init()无任何参数,与其相关实例和实例信息的定义均在文件内部完成,因此直接调用该函数即可获得FTL 句柄。即:
这样一来,在后续使用其它FTL 通用接口函数时,均可使用该函数的返回值ftl_handle作为第一个参数(handle)的实参传递。
2. 写入数据
当调用FTL 通用接口时,读写数据都是以块为单位,每块数据的字节数固定为512 字节。其函数原型为:
为了延长Flash 的使用寿命,在实际写入时,会数据写入到擦除次数最少的区域。因此lbn 只是一个逻辑块序号,与实际的存储地址没有关系。逻辑块只是一个抽象的概念,每个逻辑块的大小固定为512 字节,与MX25L1606 的物理存储块没有任何关系。
由于MX25L1606 每个逻辑块固定为512 字节,因此理论上逻辑块的个数为4096(2×1024×1024÷512),lbn 的有效值为0 ~ 4095。但实际上擦除每个单元都要耗费一个逻辑块,MX25L1606 擦除单元的大小为4096,即512 个擦除单元,因此FTL 消耗了512 个逻辑块,则可用的逻辑块为3584(4096~512)个,lbn 的有效值为0~3583。
由此可见,FTL 不仅要占用2.5K RAM,还要占用256K 的MX25L1606 存储空间(512个逻辑块,每个逻辑块大小为512 字节),这也是使用FTL 要付出的“代价”。如果返回AM_OK,说明写入数据成功,反之失败。假定写入一块数据(512 字节)至逻辑块2 中,其范例程序详见程序清单6.33。
程序清单6.33 写入数据范例程序
3. 读取数据
读取一块数据的函数原型为:
如果返回值为AM_OK,则说明读取成功,反之失败。假定从逻辑块2 中读取一块(512字节)数据,其范例程序详见程序清单6.34。
程序清单6.34 读取数据范例程序
FTL 通用接口测试程序和接口分别详见程序清单6.35 和程序清单6.36。
程序清单6.35 FTL 测试程序实现(app_test_ftl.c)
程序清单6.36 FTL 测试接口声明(app_test_ftl.h)
由于写入前无需再执行擦除操作,则编写应用程序更加便捷。同样,由于应用程序仅仅只需要FTL 句柄,则所有接口也全部为FTL 通用接口,因此应用程序是可以跨平台复用的,范例程序详见程序清单6.37。
程序清单6.37 FTL 读写范例程序
>>> 6.2.6 微型数据库
由于哈希表所使用的链表头数组空间、关键字和记录值等都存储在malloc 分配的动态空间中,这些信息在程序结束或系统掉电后都会丢失。在实际的应用中,往往希望将信息存储在非易失存储器中。典型的应用是将信息存储在文件中,从本质上来看,只要掌握了哈希表的原理,无论信息存储在什么地方,操作的方式都是一样的。
在AMetal 中,基于非易失存储器实现了一套可以直接使用的哈希表接口,由于数据不会因为掉电或程序终止而丢失,因此可以将其视为一个微型数据库,相关接口详见表6.9。
表6.9 数据库接口(hash_kv.h)
显然,除命名空间由 hash_db_*修改为了hash_kv_*(为了与之前的程序进行区分)外,仅仅是初始化函数中,多了一个文件名参数,即内部不再使用malloc 分配空间存储记录信息,而是使用该文件名指定的文件存储相关信息。如此一来记录存储在文件中,信息不会因掉电或程序终止而丢失。其中,hash_kv_t 为数据库结构体类型,使用数据库前,应使用该类型定义一个数据库实例,比如,“hash_kv_t hash;”。
由于各个函数的功能与《程序设计与数据结构》一书中介绍的哈希表的各个函数的功能完全一致,因此可以使用如程序清单6.38 所示的代码进行测试验证。
程序清单6.38 数据库综合范例程序
-
ametal
+关注
关注
2文章
24浏览量
11392 -
非易失闪存芯片
+关注
关注
0文章
1浏览量
1638 -
spi片选引脚
+关注
关注
0文章
1浏览量
2648 -
spi模式、spi速率
+关注
关注
0文章
2浏览量
2286
原文标题:周立功:重用外设驱动代码——SPI NOR Flash 存储器
文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论