|
|
@@ -0,0 +1,289 @@
|
|
|
+--[[
|
|
|
+@module raw_spi
|
|
|
+@summary raw_spi测试功能模块
|
|
|
+@version 1.0
|
|
|
+@date 2025.11.03
|
|
|
+@author 马亚丹
|
|
|
+@usage
|
|
|
+本demo演示的功能为:使用Air8000核心板通过SPI库原始SPI接口实现对 NAND Flash(W25N01GV)的操作,演示读数据写数据等操作。
|
|
|
+以 Air8000核心板为例, 接线如下:
|
|
|
+Air8000 AirSPINAND_1000配件版
|
|
|
+GND(任意) GND
|
|
|
+VDD_EXT VCC
|
|
|
+GPIO12/SPI1_CS CS,片选
|
|
|
+SPI1_SLK CLK,时钟
|
|
|
+SPI1_MOSI DI,主机输出,从机输入
|
|
|
+SPI1_MISO DO,主机输入,从机输出
|
|
|
+
|
|
|
+--使用SPI1,硬件SPI CS接在gpio12上
|
|
|
+
|
|
|
+核心逻辑:
|
|
|
+1.初始化并启用spi,如果初始化失败,退出程序
|
|
|
+2.spi启用后读取并验证nand flash芯片ID,如果验证失败,退出程序
|
|
|
+3.验证nand flash芯片后读取寄存器状态,确认芯片就绪
|
|
|
+4.验证是否是坏块,非坏块进行擦除块区,为写入数据做准备
|
|
|
+5.擦除块区后,写数据到块区,并读取块区数据与写入数据进行验证
|
|
|
+6.关闭写使能并关闭SPI。
|
|
|
+
|
|
|
+]]
|
|
|
+-- SPI配置参数
|
|
|
+local SPI_ID = 1 -- SPI总线ID,根据实际情况修改
|
|
|
+local CS_PIN = 12 -- CS引脚,根据实际情况修改
|
|
|
+local CPHA = 0 -- 时钟相位
|
|
|
+local CPOL = 0 -- 时钟极性
|
|
|
+local data_Width = 8 -- 数据宽度(位)
|
|
|
+local bandrate = 2000000 -- 波特率(Hz),初始化为2MHz
|
|
|
+local timeout = 1000 -- 操作超时时间(ms)
|
|
|
+local cspin = gpio.setup(CS_PIN, 1) --CS脚置于高电平
|
|
|
+spi_device = nil
|
|
|
+
|
|
|
+
|
|
|
+-- 1. 定义功能函数:发送和接收数据
|
|
|
+local function spi_transfer_func(sendData, recvLen)
|
|
|
+ -- 选中设备
|
|
|
+ cspin(0)
|
|
|
+
|
|
|
+ if sendData then
|
|
|
+ local sendLen = #sendData
|
|
|
+ -- 发送数据
|
|
|
+ spi_device:send(sendData, sendLen)
|
|
|
+ end
|
|
|
+
|
|
|
+ local recvData = ""
|
|
|
+ if recvLen and recvLen > 0 then
|
|
|
+ -- 接收数据
|
|
|
+ recvData = spi_device:recv(recvLen)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 取消选中
|
|
|
+ cspin(1)
|
|
|
+ return recvData
|
|
|
+end
|
|
|
+--2. 定义功能函数: 读状态寄存器(指令0x0F)
|
|
|
+local function spi_readStatus_func()
|
|
|
+ -- 发送0x0F指令(1字节),接收1字节状态
|
|
|
+ local status = spi_transfer_func(string.char(0x0F), 1)
|
|
|
+ return status and status:byte(1) or 0
|
|
|
+end
|
|
|
+
|
|
|
+-- 3. 定义功能函数:初始化SPI并复位芯片
|
|
|
+local function spiDev_init_func()
|
|
|
+ log.info("W25N01GV", "初始化SPI1...")
|
|
|
+ spi_device = spi.deviceSetup(SPI_ID, nil, CPHA, CPOL, data_Width, bandrate,
|
|
|
+ spi.MSB, --高低位顺序
|
|
|
+ spi.master, --主模式
|
|
|
+ spi.half --半双工
|
|
|
+ )
|
|
|
+ if not spi_device then
|
|
|
+ log.error("SPI初始化失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ return spi_device
|
|
|
+end
|
|
|
+
|
|
|
+--4. 定义功能函数:读取并验证芯片的JEDEC ID
|
|
|
+local function spi_readChipId_func()
|
|
|
+ -- 读ID指令0x9F,发送2字节指令,接收3字节ID
|
|
|
+ -- 发送长度=2(0x9F,0x00),接收长度=3
|
|
|
+ local id = spi_transfer_func(string.char(0x9F, 0x00), 3)
|
|
|
+ if #id ~= 3 then
|
|
|
+ log.error("读ID失败,返回长度:", #id)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ local b1, b2, b3 = id:byte(1, 3)
|
|
|
+ log.info("芯片ID:", string.format("0x%02X 0x%02X 0x%02X", b1, b2, b3))
|
|
|
+ -- W25N01GV的标准ID:0xEF(厂商)、0xAA(设备)、0x21(容量)
|
|
|
+ if b1 == 0xEF and b2 == 0xAA and b3 == 0x21 then
|
|
|
+ return true
|
|
|
+ else
|
|
|
+ log.error("非W25N01GV芯片")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+--5. 定义功能函数:等待写入完成
|
|
|
+local function spi_waitForComplete_func()
|
|
|
+ while timeout > 0 do
|
|
|
+ local status = spi_readStatus_func()
|
|
|
+ -- WIP位为0表示写入完成
|
|
|
+ if bit.band(status, 0x01) == 0 then
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ sys.wait(10)
|
|
|
+ timeout = timeout - 10
|
|
|
+ end
|
|
|
+
|
|
|
+ log.error("spi", "等待写入超时")
|
|
|
+ return false
|
|
|
+end
|
|
|
+
|
|
|
+--6. 定义功能函数:读取数据
|
|
|
+local function spi_read_page(page_addr, length, column_addr)
|
|
|
+ local is_column_provided = column_addr ~= nil
|
|
|
+ column_addr = column_addr or 0x00
|
|
|
+ if length <= 0 or length > 2048 then
|
|
|
+ log.error("读取长度无效")
|
|
|
+ return ""
|
|
|
+ end
|
|
|
+ -- 0x03指令+4字节地址(3字节页地址+1字节列地址偏移)
|
|
|
+ local cmd = string.char(0x03)
|
|
|
+ .. string.char(bit.rshift(page_addr, 16) & 0xFF) -- 页地址高8位
|
|
|
+ .. string.char(bit.rshift(page_addr, 8) & 0xFF) -- 页地址中8位
|
|
|
+ .. string.char(page_addr & 0xFF) -- 页地址低8位
|
|
|
+ .. string.char(column_addr & 0xFF) -- 列地址偏移(页内起始字节)
|
|
|
+ local data = spi_transfer_func(cmd, length)
|
|
|
+ if is_column_provided then
|
|
|
+ log.info("读取主阵列(偏移0x" .. string.format("%02X", column_addr) .. ")", "读到的数据", string.toHex(data), "长度", #data)
|
|
|
+ else
|
|
|
+ log.info("读到的数据", string.toHex(data), "长度", #data)
|
|
|
+ end
|
|
|
+ return data
|
|
|
+end
|
|
|
+
|
|
|
+-- 7. 定义功能函数::读取页的数据区+OOB区(指令0x51),用于判断是否是坏块
|
|
|
+local function spi_read_page_with_oob(page_addr, data_len, oob_len)
|
|
|
+ if data_len < 0 or data_len > 2048 or oob_len < 0 or oob_len > 64 then
|
|
|
+ log.error("读取长度无效")
|
|
|
+ return "", ""
|
|
|
+ end
|
|
|
+ -- 0x51指令:读取数据+OOB
|
|
|
+ local cmd = string.char(0x51)
|
|
|
+ .. string.char(bit.rshift(page_addr, 16) & 0xFF)
|
|
|
+ .. string.char(bit.rshift(page_addr, 8) & 0xFF)
|
|
|
+ .. string.char(page_addr & 0xFF)
|
|
|
+ -- 总读取长度:数据区+OOB区
|
|
|
+ local total_data = spi_transfer_func(cmd, data_len + oob_len)
|
|
|
+ local data = total_data:sub(1, data_len)
|
|
|
+ local oob = total_data:sub(data_len + 1, data_len + oob_len)
|
|
|
+ return data, oob
|
|
|
+end
|
|
|
+
|
|
|
+-- 8. 定义功能函数:判断是否是坏块
|
|
|
+local function is_bad_block(block_addr)
|
|
|
+ -- 手册规定:块0~7出厂保证为有效块,可直接跳过检测
|
|
|
+ if block_addr >= 0 and block_addr <= 7 then
|
|
|
+ log.debug("块", block_addr, "是出厂保证有效块,直接判定为好块")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 块的第0页
|
|
|
+ local page_addr = block_addr * 64
|
|
|
+ -- 读取主阵列第1字节(偏移0x01)
|
|
|
+ local main_data = spi_read_page(page_addr, 1, 0x01)
|
|
|
+ -- 读取OOB区前2字节
|
|
|
+ local _, oob = spi_read_page_with_oob(page_addr, 0, 2)
|
|
|
+
|
|
|
+ -- 检测主阵列第1字节是否为0xFF
|
|
|
+ local main_bad = false
|
|
|
+ if #main_data >= 1 and main_data:byte(1) ~= 0xFF then
|
|
|
+ main_bad = true
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检测OOB区前2字节是否为0xFF
|
|
|
+ local oob_bad = false
|
|
|
+ if #oob >= 2 and (oob:byte(1) ~= 0xFF or oob:byte(2) ~= 0xFF) then
|
|
|
+ oob_bad = true
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 主阵列第1字节和OOB区前2字节,任一非0xFF则判定为坏块
|
|
|
+ local is_bad = main_bad or oob_bad
|
|
|
+ log.debug("块", block_addr,
|
|
|
+ "主阵列第1字节:0x" .. string.format("%02X", main_data:byte(1) or 0x00),
|
|
|
+ "OOB前2字节:0x" .. string.format("%02X, 0x%02X", oob:byte(1) or 0x00, oob:byte(2) or 0x00),
|
|
|
+ "判定为" .. (is_bad and "坏块" or "好块"))
|
|
|
+ return is_bad
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+-- 9. 定义功能函数: 块擦除(指令0xD8)
|
|
|
+local function spi_erase_block(block_addr)
|
|
|
+ if is_bad_block(block_addr) then
|
|
|
+ log.error("块", block_addr, "是坏块,跳过擦除")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ -- 写使能
|
|
|
+ spi_transfer_func(string.char(0x06))
|
|
|
+ local cmd = string.char(0xD8)
|
|
|
+ .. string.char(bit.rshift(block_addr, 16) & 0xFF)
|
|
|
+ .. string.char(bit.rshift(block_addr, 8) & 0xFF)
|
|
|
+ .. string.char(block_addr & 0xFF)
|
|
|
+ spi_transfer_func(cmd)
|
|
|
+ -- 确保等待擦除完成
|
|
|
+ if not spi_waitForComplete_func() then
|
|
|
+ log.error("擦除未完成")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 10. 定义功能函数: 页写入
|
|
|
+local function spi_write_page(page_addr, data)
|
|
|
+ if #data > 2048 then
|
|
|
+ log.error("数据超过页大小2048字节")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ -- 写使能
|
|
|
+ spi_transfer_func(string.char(0x06))
|
|
|
+
|
|
|
+ -- 加载缓冲区(0x02指令)
|
|
|
+ local cmd_load = string.char(0x02)
|
|
|
+ .. string.char(bit.rshift(page_addr, 16) & 0xFF)
|
|
|
+ .. string.char(bit.rshift(page_addr, 8) & 0xFF)
|
|
|
+ .. string.char(page_addr & 0xFF)
|
|
|
+ spi_transfer_func(cmd_load .. data)
|
|
|
+
|
|
|
+ -- 执行编程(0x10指令)
|
|
|
+ local cmd_exec = string.char(0x10)
|
|
|
+ .. string.char(bit.rshift(page_addr, 16) & 0xFF)
|
|
|
+ .. string.char(bit.rshift(page_addr, 8) & 0xFF)
|
|
|
+ .. string.char(page_addr & 0xFF)
|
|
|
+ spi_transfer_func(cmd_exec)
|
|
|
+ return spi_waitForComplete_func()
|
|
|
+end
|
|
|
+
|
|
|
+-- 11. 定义功能函数:核心测试函数
|
|
|
+local function spi_test_func()
|
|
|
+ --spi初始化
|
|
|
+ spi_device = spiDev_init_func()
|
|
|
+ if not spi_device then
|
|
|
+ return
|
|
|
+ end
|
|
|
+ --读取芯片id
|
|
|
+ if not spi_readChipId_func() then
|
|
|
+ spi.close(spi_device)
|
|
|
+ return
|
|
|
+ end
|
|
|
+ --坏块检查并进行块擦除,test_block是定义的操作的块编码
|
|
|
+ --手册规定:块0~7出厂保证为有效块,可直接跳过检测。块取值0~1023
|
|
|
+ local test_block = 7
|
|
|
+ log.info("擦除块", test_block, "...")
|
|
|
+ if not spi_erase_block(test_block) then
|
|
|
+ spi.close(spi_device)
|
|
|
+ return
|
|
|
+ end
|
|
|
+ local test_page = test_block * 64
|
|
|
+ local test_data = "Hello, W25N01GV!"
|
|
|
+ log.info("写入数据", test_data, "到页", test_page, "块", test_block)
|
|
|
+ --写数据到指定块
|
|
|
+ if not spi_write_page(test_page, test_data) then
|
|
|
+ log.info("写入失败")
|
|
|
+ spi.close(spi_device)
|
|
|
+ return
|
|
|
+ end
|
|
|
+ --读取当前块写入的数据,并验证与写入是否一致
|
|
|
+ local read_data = spi_read_page(test_page, #test_data)
|
|
|
+ if read_data == test_data then
|
|
|
+ log.info("数据验证成功:", read_data)
|
|
|
+ else
|
|
|
+ log.error("数据验证失败")
|
|
|
+ log.info("预期:", test_data)
|
|
|
+ log.info("实际:", string.toHex(read_data))
|
|
|
+ end
|
|
|
+ --操作完成关闭SPI
|
|
|
+ spi.close(spi_device)
|
|
|
+end
|
|
|
+
|
|
|
+sys.taskInit(spi_test_func)
|