基础资料
基于Air105开发板:Air105 - LuatOS 文档
探讨重点
官方LuatOS-SOC接口fatfs的demo测试。
扩展阅读:
合宙Air103|SDIO (扩展) |LuatOS-SOC接口|官方demo|学习(15-3):SDIO - 文件读写操作_打酱油的工程师的博客-CSDN博客
合宙Air105|SFUD库|FS|SPI FLASH|W25QXX |LuatOS-SOC接口|官方demo|学习(15-4):SFUD库操作-外置flash_打酱油的工程师的博客-CSDN博客
参考博文:
硬件准备
Air105开发板1块,面包板1块, SPI/SDIO接口SD卡模块(SPI/SDIO) 1个,。
Air105开发板
Micro SD卡读写模块未适配:
需采用:SPI/SDIO双功能版本读写模块
SD卡容量应小玉16G,实测>16G时读写会出现问题。
接线方式:
--[[
接线要求:
SPI 使用常规4线接法
Air105开发板 TF模块
PB3 CS
PB2(SPI2_CLK) CLK
PB4(SPI2_MISO) MOSI
PB5(SPI2_MISO) MISO
3.3V VCC
GND GND
]]
接线示例:
Demo执行后SD卡内生成文件:
Demo代码解析及注释:
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "fatfs"
VERSION = "1.0.0"
-- sys库是标配
_G.sys = require("sys")
sys.taskInit(function()
--sys.wait(1000) -- 启动延时
local spiId = 2
local result = spi.setup(
spiId,--串口id
255, -- 不使用默认CS脚
0,--CPHA
0,--CPOL
8,--数据宽度
400*1000 -- 初始化时使用较低的频率
)
local TF_CS = pin.PB3
--设置管脚功能,数字0/1代表输出模式,设置 pin.PB3为输出模式,
gpio.setup(TF_CS, 1)
--fatfs.debug(1) -- 若挂载失败,可以尝试打开调试信息,查找原因
-- 提醒, 若TF/SD模块带电平转换, 通常不支持10M以上的波特率!!
fatfs.mount("SD", spiId, TF_CS, 24000000)
local data, err = fatfs.getfree("SD")
--getfree所获得的数据 table包含的内容有
-- total_sectors 总扇区数量
-- free_sectors 空闲扇区数量
-- total_kb 总字节数,单位kb
-- free_kb 空闲字节数, 单位kb
-- 注意,当前扇区大小固定在512字节
if data then
log.info("fatfs", "getfree", json.encode(data))
else
log.info("fatfs", "err", err)
end
-- 重新设置spi,使用更高速率
-- spi.close(0)
-- sys.wait(100)
-- spi.setup(spiId, 255, 0, 0, 8, 24*1000*1000)
-- #################################################
-- 文件操作测试
--[[io模块是lua原生模块,LuatOS增加了一些API
-- 请配合os模块一起使用
-- 只读模式, 打开文件
local fd = io.open("/xxx.txt", "rb")
-- 读写默认,打开文件
local fd = io.open("/xxx.txt", "wb")
-- 写入文件,且截断为0字节
local fd = io.open("/xxx.txt", "wb+")
-- 追加模式
local fd = io.open("/xxx.txt", "a")
-- 若文件打开成功, fd不为nil,否则就是失败了
-- 注意, 刷机时所添加的文件, 均在 /luadb 目录下, 只读
if fd then
-- 读取指定字节数,如果数据不足,就只返回实际长度的数据
local data = fd:read(12)
-- 按行读取
local line = fd:read("*l")
-- 全部读取
local line = fd:read("*a")
-- 数据写入, 仅w或a模式可调用
-- 数据需要是字符串, lua的字符串是带长度的,可以包含任何二进制数据
fd:write("xxxx")
-- 以下是写入0x12, 0x13
fd:write(string.char(0x12, 0x13))
-- 移动句柄,绝对坐标
fd:seek(1024, io.SEEK_SET)
-- 移动句柄,相对坐标
fd:seek(1024, io.SEEK_CUR)
-- 移动句柄,反向绝对坐标,从文件结尾往文件头部算
fd:seek(124, io.SEEK_END)
-- 执行完操作后,一定要关掉文件
fd:close()]]
-- #################################################
local f = io.open("/sd/boottime", "rb")
local c = 0
if f then
local data = f:read("*a")
--[[string.toHex(str, separator)
将字符串转成HEX
参数
传入值类型
解释
string
需要转换的字符串
string
分隔符, 默认为””
返回值
返回值类型
解释
string
HEX字符串
number
HEX字符串的长度
返回值例如:I/user.fs data 8 38 2
原始值 HEX字符串 字符串长度
]]
log.info("fs", "data", data, data:toHex())
--C为上一次程序写入文件的已重启次数
c = tonumber(data)
f:close()
end
--输出已重启次数
log.info("fs", "boot count", c)
--本次值+1
c = c + 1
f = io.open("/sd/boottime", "wb")
if f ~= nil then
log.info("fs", "write c to file", c, tostring(c))
--将更新后的重启次数c值转化为string后写入文件
f:write(tostring(c))
f:close()
else
log.warn("sdio", "mount not good?!")
end
if fs then
--打印根分区的信息
log.info("fsstat", fs.fsstat("/"))
log.info("fsstat", fs.fsstat("/sd"))
end
-- 测试一下追加, fix in 2021.12.21
--os.remove (filename)删除指定名字的文件(在 POSIX 系统上可以是一个空目录)
--如果函数失败,返回 nil 加一个错误描述串及出错码。
os.remove("/sd/test_a")
sys.wait(50)
f = io.open("/sd/test_a", "w") --写入模式
if f then
f:write("ABC")
f:close()
end
f = io.open("/sd/test_a", "a+") --追加模式
if f then
f:write("def")
f:close()
end
f = io.open("/sd/test_a", "r") --读取模式
if f then
local data = f:read("*a")
log.info("data", data, data == "ABCdef")
f:close()
end
-- 测试一下按行读取, fix in 2022-01-16
--写入3行数据
f = io.open("/sd/testline", "w")
if f then
f:write("abc\n")
f:write("123\n")
f:write("wendal\n")
f:close()
end
sys.wait(100)
--f:read每次读取1行
--而io.readFile(path) 读取整个文件,请注意内存消耗
f = io.open("/sd/testline", "r")
if f then
log.info("sdio", "line1", f:read("*l"))
log.info("sdio", "line2", f:read("*l"))
log.info("sdio", "line3", f:read("*l"))
f:close()
end
-- #################################################
end)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
LOG文件解析:
[2023-02-08 22:00:08.526] no bl encrypt
[2023-02-08 22:00:08.526] main 672:bootloader build release Jan 1 2023 16:21:12!
[2023-02-08 22:00:08.526] Jump_AppRun 76:jump to 0x01010400 !
[2023-02-08 22:00:08.526] main 188:APP Build release Jan 1 2023 16:20:44!
[2023-02-08 22:00:08.526] I/main LuatOS@AIR105 base 22.12 bsp V0013 32bit
[2023-02-08 22:00:08.526] I/main ROM Build: Jan 1 2023 16:20:52
[2023-02-08 22:00:08.531] D/main loadlibs luavm 204792 15376 15376
[2023-02-08 22:00:08.531] D/main loadlibs sys 397480 51032 51616
初始化提示:SPI_ID为2,片选端口为:GPIO19 SPI2_CSN
初始化使用较低频率:
local result = spi.setup(
spiId,--串口id
255, -- 不使用默认CS脚
0,--CPHA
0,--CPOL
8,--数据宽度
400*1000 -- 初始化时使用较低的频率
)
挂载:
fatfs.mount(mount_point, spiid_or_spidevice, spi_cs, spi_speed)
挂载fatfs
参数
传入值类型 | 解释 |
string | fatfs挂载点, 通常填””或者”SD”, 底层会映射到vfs的 /sd 路径 |
int | 传入spi device指针,或者spi的id |
int | 片选脚的GPIO 号, 若前一个参数传的是spi device,这个参数就不需要传 |
int | SPI最高速度,默认10M, 若前2个参数传的是spi device,这个参数就不需要传 |
返回值
返回值类型 | 解释 |
bool | 成功返回true, 否则返回nil或者false |
string | 失败的原因 |
[2023-02-08 22:00:08.532] D/fatfs init sdcard at spi=2 cs=19
初始化:正常
[2023-02-08 22:00:08.606] SDHC_SpiInitCard 684:sdcard init OK OCR:0xc0ff8000!
[2023-02-08 22:00:08.606] I/user.fatfs getfree
--getfree所获得的数据 table包含的内容有
-- total_sectors 总扇区数量
-- free_sectors 空闲扇区数量
-- total_kb 总字节数,单位kb
-- free_kb 空闲字节数, 单位kb
-- 注意,当前扇区大小固定在512字节
{"free_kb":15458224,"total_sectors":30916576,"free_sectors":30916448,"total_kb":15458288}
[2023-02-08 22:00:08.606] I/user.fs data 8 38 2
[2023-02-08 22:00:08.606] I/user.fs boot count 8
--将更新后的重启次数c值转化为string后写入文件
[2023-02-08 22:00:08.611] I/user.fs write c to file 9 9
-- 打印当前分区的信息("/"),例如lfs代表littlefs
[2023-02-08 22:00:08.637] I/user.fsstat true 128 2 4096 lfs
-- -- 打印根分区的信息("/sd"),例如fatfs
[2023-02-08 22:00:08.637] I/user.fsstat true 1932286 1932278 16 fatfs
--读取数据data的内容,与文件内容相同(True))
[2023-02-08 22:00:08.729] I/user.data ABCdef true
[2023-02-08 22:00:08.837] I/user.sdio line1 abc
[2023-02-08 22:00:08.837] I/user.sdio line2 123
[2023-02-08 22:00:08.837] I/user.sdio line3 wendal
PS: io - io操作(扩展)
已适配 Air101/Air103 Air105 ESP32C3 Air780
备注
本页文档由这个文件自动生成。如有错误,请提交issue或帮忙修改后pr,谢谢!
小技巧
本库有专属demo,点此链接查看io的demo例子
示例
-- io模块是lua原生模块,LuatOS增加了一些API
-- 请配合os模块一起使用
-- 只读模式, 打开文件
local fd = io.open("/xxx.txt", "rb")
-- 读写默认,打开文件
local fd = io.open("/xxx.txt", "wb")
-- 写入文件,且截断为0字节
local fd = io.open("/xxx.txt", "wb+")
-- 追加模式
local fd = io.open("/xxx.txt", "a")
-- 若文件打开成功, fd不为nil,否则就是失败了
-- 注意, 刷机时所添加的文件, 均在 /luadb 目录下, 只读
if fd then
-- 读取指定字节数,如果数据不足,就只返回实际长度的数据
local data = fd:read(12)
-- 按行读取
local line = fd:read("*l")
-- 全部读取
local line = fd:read("*a")
-- 数据写入, 仅w或a模式可调用
-- 数据需要是字符串, lua的字符串是带长度的,可以包含任何二进制数据
fd:write("xxxx")
-- 以下是写入0x12, 0x13
fd:write(string.char(0x12, 0x13))
-- 移动句柄,绝对坐标
fd:seek(1024, io.SEEK_SET)
-- 移动句柄,相对坐标
fd:seek(1024, io.SEEK_CUR)
-- 移动句柄,反向绝对坐标,从文件结尾往文件头部算
fd:seek(124, io.SEEK_END)
-- 执行完操作后,一定要关掉文件
fd:close()
end
io.exists(path)
判断文件是否存在
参数
传入值类型 | 解释 |
string | 文件路径 |
返回值
返回值类型 | 解释 |
bool | 存在返回true,否则返回false |
例子
log.info("io", "file exists", io.exists("/boottime"))
io.fileSize(path)
获取文件大小
参数
传入值类型 | 解释 |
string | 文件路径 |
返回值
返回值类型 | 解释 |
int | 文件数据,若文件不存在会返回nil |
例子
local fsize = io.fileSize("/bootime")
if fsize and fsize > 1024 then
log.info("io", "file size", fsize)
end
io.readFile(path)
读取整个文件,请注意内存消耗
参数
传入值类型 | 解释 |
string | 文件路径 |
返回值
返回值类型 | 解释 |
string | 文件数据,若文件不存在会返回nil |
例子
local data = io.readFile("/bootime")
io.writeFile(path, data)
将数据写入文件
参数
传入值类型 | 解释 |
string | 文件路径 |
string | 数据 |
返回值
返回值类型 | 解释 |
boolean | 成功返回true, 否则返回false |
例子
io.writeFile("/bootime", "1")
io.fill(buff, offset, len)
读取文件并填充到zbuff内,但不移动指针位置
参数
传入值类型 | 解释 |
userdata | zbuff实体 |
int | 写入的位置,默认是0 |
int | 写入的长度,默认是zbuff的len减去offset |
返回值
返回值类型 | 解释 |
boolean | 成功返回true,否则返回false |
int | 返回实际读取到的长度,如果小于0也说明是读取失败了 |
例子
local buff = zbuff.create(1024)
local f = io.open("/sd/test.txt")
if f then
f:fill(buff)
end
io.mkdir(path)
创建文件夹
参数
传入值类型 | 解释 |
string | 需要建立的目录路径 |
返回值
返回值类型 | 解释 |
bool | 成功与否 |
int | 底层返回值 |
例子
local ret, errio = io.mkdir("/data/")
log.info("fs", "mkdir", ret, errio)
io.rmdir(path)
删除文件夹
参数
传入值类型 | 解释 |
string | 需要移除的目录路径 |
返回值
返回值类型 | 解释 |
bool | 成功与否 |
int | 底层返回值 |
例子
local ret, errio = io.rmdir("/data/")
log.info("fs", "rmdir", ret, errio)
io.lsdir(path, len, offset)
列出目录下的文件
参数
传入值类型 | 解释 |
string | 需要枚举的目录路径 |
int | 最大长度, 默认10, 最高50 |
int | 偏移量, 默认0, 当目录文件很多时分页查询用 |
返回值
返回值类型 | 解释 |
bool | 成功与否 |
int | 底层返回值 |
例子
local ret, data = io.lsdir("/data/", 10, 0)
if ret then
log.info("fs", "lsdir", json.encode(data))
else
log.info("fs", "lsdir", "fail", ret, data)
end
io.lsmount()
列出所有挂载点
参数
无
返回值
返回值类型 | 解释 |
table | 挂载点列表 |
例子
local data = io.lsmount()
log.info("fs", "lsmount", json.encode(data))