跳转至

07 AirSPINAND_1000

作者:马亚丹 | 最后修改:2026-04-10

一、AirSPINAND_1000 简介

AirSPINAND_1000 是合宙推出的一款 SPI 接口,1Gb 的 SPI NAND Flash 配件小板,规格:

  1. 驱动 IC:华邦 W25N01GVZEIG,1G bit;
  2. 6 路插针: VCC GND CS DI DO CLK

适用于 Air700ECH/Air780EHN/EHU/EHM/EHV/EGH/EPM/Air8000 全系/Air8101

二、功能演示概述

本文演示合宙 4G 模组 Air8101 使用 LuatOS 开发时, 通过 lf 核心库(即 little_flash,下述都用 lf 表示 little_flash)挂载 AirSPINAND_1000 应用功能.

使用 Air8101 核心板下载 Air8101 的 LuatOS 示例代码中 accessory_board/AirSPINAND_1000 的例程进行验证,例程中演示了 flash 的使用方式:

通过 lf 核心库以及 io 核心库,对 flash 模块以文件系统的方式进行读写数据操作,详细逻辑请看示例代码 AirSPINAND_1000.lua 文件

三、准备硬件环境

3.1、 8101 核心板购买链接:https://item.taobao.com/item.htm?id=931016598855&skuId=5816546236665&spm=a1z10.3-c-s.w4002-24045920836.17.4a756ee570yUhN

3.2、 AirSPINAND_1000 小板购买链接:https://item.taobao.com/item.htm?id=977252671603&skuId=6099734431296&spm=a1z10.5-c-s.w4002-24045920841.15.2ebec854VktYE7

AirSPINAND_1000 小板实物图:

3.3、 TYPE-C USB 数据线一根 ,Air8101 核心板和数据线的硬件接线方式为:

  • Air8101 核心板通过 TYPE-C USB 口供电;(背面 usb 开关拨到 off)
  • TYPE-C USB 数据线直接插到开发板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;

3.4、 杜邦线 6 根

Air8101 核心板与 AirSPINORFLASH_1000 小板按以下方式接线:

Air8101核心板
AirSPINAND_1000配件板
SPI0_CS/p54/GPIO15
CS
SPI0_SCK/p28/GPIO14
CLK
SPI0_MOSI/p57/GPIO16
DI
SPI0_MISO/p55/GPIO17
DO
3.3V
VCC
GND
GND

Air8101 核心板管脚图:

Air8101 核心板与 AirSPINAND_1000 小板接线实物图:

四、准备软件环境

在开始实践本示例之前,先准备一下软件环境:

4.1 、Luatools 工具,如果是第一次使用 Luatools 工具,请仔细阅读此链接教程。

4.2 、本demo开发测试时使用的固件为LuatOS-SoC_V2002_Air8101_101.soc,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

4.3、luatos 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/accessory_board/AirSPINAND_1000

main.lua:主程序入口;

lf_fs.lua:示例功能文件,通过 little_flash 核心库和 io 文件系统,对 flash 模块以文件系统的方式进行读写数据操作,在 main.lua 中加载运行。

4.4、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 核心板,根据链接中 4、 烧录和抓取日志 的步骤将本篇文章中演示使用的项目文件烧录到 Air8101 核心板中。

五、API 接口说明

Air8101 不同版本固件对以上三个核心库 API 的支持说明:https://docs.openluat.com/air8101/luatos/firmware/

查看说明可知,所有固件都支持以上三个核心库

六、示例代码和功能展示

6.1 流程介绍

1、搭建好硬件环境

2、Luatools 烧录内核固件和 demo 脚本代码

3、烧录成功后,自动开机运行,查看打印日志,如果正常运行,会打印 SPI 初始化、文件系统挂载、文件系统信息、写入读取文件等。

6.2 代码和 log

6.2.1 main.lua

代码示例(点击查看完整 demo)

PROJECT = "SPI_NAND"
VERSION = "001.000.000"

-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)

-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
if wdt then
    --配置喂狗超时时间为9秒钟
    wdt.init(9000)
    --启动一个循环定时器,每隔3秒钟喂一次狗
    sys.timerLoopStart(wdt.feed, 3000)
end

-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end

-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)


-- 加载lf_fs功能模块
require"lf_fs"



-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

6.2.2 lf_fs.lua

6.2.2.1 核心代码部分(点击查看完整 demo)
-- SPI配置参数
local SPI_ID = 0        -- SPI总线ID,根据实际情况修改
local CS_PIN = 15       -- CS引脚,根据实际情况修改
local CPHA = 0          -- 时钟相位
local CPOL = 0          -- 时钟极性
local data_Width = 8    -- 数据宽度(位)
local bandrate = 4*1000*1000 -- 波特率(Hz),初始化为2MHz
gpio.setup(13, 1)--air8101模组,gpio13控制ldo输出3.3v


-- 1. 以对象方式设置并启用 SPI,返回设备对象
local function spiDev_init_func()
    log.info("lf_fs", "SPI_ID", SPI_ID, "CS_PIN", CS_PIN)

    --以对象的方式初始化spi,高位在前,主模式,半双工模式
    --spi nand flash只支持半双工模式
    local spi_device = spi.deviceSetup(SPI_ID, CS_PIN, CPHA, CPOL, data_Width, bandrate, spi.MSB, 1, 0)

    log.info("硬件spi", "初始化,波特率:", spi_device, bandrate)
    if not spi_device then
        log.error("SPI初始化", "失败")
        return nil
    end
    log.info("SPI初始化", "成功,波特率",bandrate)
    return spi_device
end


-- 2. 初始化Flash设备,返回设备对象
local function init_flash_device(spi_device)
    log.info("Flash初始化", "开始")
    local flash_device = lf.init(spi_device)
    if not flash_device then
        log.error("Flash初始化", "失败")
        return nil
    end
    log.info("Flash初始化", "成功,设备:", flash_device)
    return flash_device
end

-- 3. 挂载文件系统
local function mount_filesystem(flash_device, mount_point)
    log.info("文件系统", "开始挂载:", mount_point)

    -- 检查是否支持挂载功能
    if not lf.mount then
        log.error("文件系统", "lf模块不支持挂载功能")
        return false
    end

    -- 尝试挂载
    local mount_ok = lf.mount(flash_device, mount_point)
    if not mount_ok then
        log.warn("文件系统lf", "挂载失败,尝试重新挂载...")
        mount_ok = lf.mount(flash_device, mount_point)
        if not mount_ok then
            log.error("文件系统", "仍挂载失败")
            return false
        end
    end

    log.info("文件系统", "挂载成功:", mount_point)
    return true
end

-- 4. 打印文件系统信息
local function print_filesystem_info(mount_point)
    log.info("文件系统信息", "开始查询:", mount_point)

    -- 获取文件系统详细信息,总块数/已用块数等
    local ok, total_blocks, used_blocks, block_size, fs_type = fs.fsstat(mount_point)
    if ok then
        log.info("  总block数:", total_blocks)
        log.info("  已用block数:", used_blocks)
        log.info("  block大小:", block_size, "字节")
        log.info("  文件系统类型:", fs_type)
    else
        log.warn("  无法获取详细信息")
    end
end

-- 5. 执行文件操作测试
local function test_file_operations(mount_point)
    log.info("文件操作测试", "开始")

    -- 测试写入文件
    local test_file = mount_point .. "/test.txt"
    local f, err = io.open(test_file, "w")
    if not f then
        log.error("  写入失败", test_file, "错误:", err)
        return false
    end
    local write_data = "hello,  W25N01GV!"
    f:write(write_data)
    f:close()
    log.info("  写入成功", test_file, "内容:", write_data)

    -- 测试读取文件
    local read_data, read_err = io.readFile(test_file)
    if not read_data then
        log.error("  读取失败", test_file, "错误:", read_err)
        return false
    end
    log.info("  读取成功", test_file, "内容:", read_data)

    -- 验证内容一致性
    if read_data ~= write_data then
        log.warn("  内容不一致", "写入:", write_data, "读取:", read_data)
    end

    -- 测试文件追加
    local append_file = mount_point .. "/append.txt"
    os.remove(append_file) -- 清除旧文件
    io.writeFile(append_file, "LuatOS 测试") -- 初始写入

    local f_append, append_err = io.open(append_file, "a+")
    if not f_append then
        log.error("  追加失败", append_file, "错误:", append_err)
        return false
    end
    local append_data = " 这是追加的内容 " 
    f_append:write(append_data)
    -- 执行完操作后,一定要关掉文件
    f_append:close()

    local final_data = io.readFile(append_file)
    log.info("  追加后内容:", final_data)

    log.info("文件操作测试", "完成")

    return true
end

-- 7. 关闭SPI设备,成功返回0
local function spi_close_func()    
    log.info("关闭spi", spi_device:close())
end

-- 主任务函数:按流程调用各功能函数
local function spinand_test_func()
    --1.判断SPI初始化
    spi_device = spiDev_init_func()
    if not spi_device then
        log.error("主流程", "SPI初始化失败,终止")
        return
    end

    -- 流程2:初始化Flash设备
    local flash_device = init_flash_device(spi_device)
    if not flash_device then
        log.error("主流程", "Flash初始化失败,终止")        
        spi_close_func()
        return
    end

    -- 流程3:挂载文件系统
    local mount_point = "/little_flash"
    if not mount_filesystem(flash_device, mount_point) then
        log.error("主流程", "文件系统挂载失败,终止")
        spi_close_func()
        return
    end

    -- 流程4:打印文件系统信息
    print_filesystem_info(mount_point)

    -- 流程5:执行文件操作测试
    if not test_file_operations(mount_point) then
        log.warn("主流程", "文件操作测试部分失败")
    end

     -- 6.关闭SPI设备
    spi_close_func()
end

sys.taskInit(spinand_test_func)
6.2.2.2 例程 log 打印如下:
[2026-01-26 17:16:01.006] luat:U(731):I/user.main SPI_NAND 001.000.000
[2026-01-26 17:16:01.085] luat:U(751):I/user.lf_fs SPI_ID 0 CS_PIN 15
[2026-01-26 17:16:01.085] luat:D(751):spi:半双工模式 0 0
[2026-01-26 17:16:01.085] luat:D(751):spi:SPI(0) gpio init : wire 3 clk 14 cs 15 mosi 16 miso 17
[2026-01-26 17:16:01.085] luat:U(752):I/user.硬件spi 初始化,波特率: SPI*: 609B7258 4000000
[2026-01-26 17:16:01.085] luat:U(752):I/user.SPI初始化 成功,波特率 4000000
[2026-01-26 17:16:01.085] luat:U(753):I/user.Flash初始化 开始
[2026-01-26 17:16:01.085] luat:I(753):little_flash:SFDP header not found.
[2026-01-26 17:16:01.085] luat:I(754):little_flash:JEDEC ID: manufacturer_id:0xEF device_id:0xAA21 
[2026-01-26 17:16:01.085] luat:I(754):little_flash:little flash fonud flash W25N01GVZEIG
[2026-01-26 17:16:01.085] luat:U(805):I/user.Flash初始化 成功,设备: userdata: 609C1338
[2026-01-26 17:16:01.085] luat:U(805):I/user.文件系统 开始挂载: /little_flash
[2026-01-26 17:16:01.270] luat:D(981):little_flash:lfs_mount 0
[2026-01-26 17:16:01.270] luat:D(981):little_flash:vfs mount /little_flash ret 0
[2026-01-26 17:16:01.270] luat:U(982):I/user.文件系统 挂载成功: /little_flash
[2026-01-26 17:16:01.270] luat:U(982):I/user.文件系统信息 开始查询: /little_flash
[2026-01-26 17:16:01.407] luat:U(1130):I/user.  总block数: 1024
[2026-01-26 17:16:01.407] luat:U(1130):I/user.  已用block数: 2
[2026-01-26 17:16:01.407] luat:U(1130):I/user.  block大小: 131072 字节
[2026-01-26 17:16:01.407] luat:U(1130):I/user.  文件系统类型: lfs
[2026-01-26 17:16:01.407] luat:U(1130):I/user.文件操作测试 开始
[2026-01-26 17:16:02.336] luat:U(2062):I/user.  写入成功 /little_flash/test.txt 内容: hello,  W25N01GV!
[2026-01-26 17:16:02.366] luat:U(2089):I/user.  读取成功 /little_flash/test.txt 内容: hello,  W25N01GV!
[2026-01-26 17:16:02.583] luat:U(2305):I/user.  追加后内容: LuatOS 测试 这是追加的内容 
[2026-01-26 17:16:02.583] luat:U(2305):I/user.文件操作测试 完成
[2026-01-26 17:16:02.583] luat:U(2305):I/user.关闭spi true
6.2.2.3 luatools 运行日志图示

七、总结

本文档主要介绍 AirSPINAND_1000 的使用方法。

结合 demo 例程讲解了 nand flash 的应用流程,介绍了 lf/spi/io 的 API 接口使用,旨在最简单的快速上手 Air8101 的 LuatOS 的 nand flash 应用.

八、常见问题

暂无