raw_spi.lua 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. --[[
  2. @module raw_spi
  3. @summary raw_spi测试功能模块
  4. @version 1.0
  5. @date 2025.11.03
  6. @author 马亚丹
  7. @usage
  8. 本demo演示的功能为:使用Air8000核心板通过SPI库原始SPI接口实现对 NAND Flash(W25N01GV)的操作,演示读数据写数据等操作。
  9. 以 Air8000核心板为例, 接线如下:
  10. Air8000 AirSPINAND_1000配件版
  11. GND(任意) GND
  12. VDD_EXT VCC
  13. GPIO12/SPI1_CS CS,片选
  14. SPI1_SLK CLK,时钟
  15. SPI1_MOSI DI,主机输出,从机输入
  16. SPI1_MISO DO,主机输入,从机输出
  17. --使用SPI1,硬件SPI CS接在gpio12上
  18. 核心逻辑:
  19. 1.初始化并启用spi,如果初始化失败,退出程序
  20. 2.spi启用后读取并验证nand flash芯片ID,如果验证失败,退出程序
  21. 3.验证nand flash芯片后读取寄存器状态,确认芯片就绪
  22. 4.验证是否是坏块,非坏块进行擦除块区,为写入数据做准备
  23. 5.擦除块区后,写数据到块区,并读取块区数据与写入数据进行验证
  24. 6.关闭写使能并关闭SPI。
  25. ]]
  26. -- SPI配置参数
  27. local SPI_ID = 1 -- SPI总线ID,根据实际情况修改
  28. local CS_PIN = 12 -- CS引脚,根据实际情况修改
  29. local CPHA = 0 -- 时钟相位
  30. local CPOL = 0 -- 时钟极性
  31. local data_Width = 8 -- 数据宽度(位)
  32. local bandrate = 2*1000*1000 -- 波特率(Hz),初始化为2MHz
  33. local timeout = 1000 -- 操作超时时间(ms)
  34. local cspin = gpio.setup(CS_PIN, 1) --CS脚置于高电平
  35. spi_device = nil
  36. -- 1. 定义功能函数:发送和接收数据
  37. local function spi_transfer_func(sendData, recvLen)
  38. -- 选中设备
  39. cspin(0)
  40. if sendData then
  41. local sendLen = #sendData
  42. -- 发送数据
  43. spi_device:send(sendData, sendLen)
  44. end
  45. local recvData = ""
  46. if recvLen and recvLen > 0 then
  47. -- 接收数据
  48. recvData = spi_device:recv(recvLen)
  49. end
  50. -- 取消选中
  51. cspin(1)
  52. return recvData
  53. end
  54. --2. 定义功能函数: 读状态寄存器(指令0x0F)
  55. local function spi_readStatus_func()
  56. -- 发送0x0F指令(1字节),接收1字节状态
  57. local status = spi_transfer_func(string.char(0x0F), 1)
  58. return status and status:byte(1) or 0
  59. end
  60. -- 3. 定义功能函数:初始化SPI并复位芯片
  61. local function spiDev_init_func()
  62. log.info("W25N01GV", "初始化SPI1...")
  63. spi_device = spi.deviceSetup(SPI_ID, nil, CPHA, CPOL, data_Width, bandrate,
  64. spi.MSB, --高低位顺序
  65. spi.master, --主模式
  66. spi.half --半双工,spi flash只支持半双工
  67. )
  68. if not spi_device then
  69. log.error("SPI初始化失败")
  70. return false
  71. end
  72. return spi_device
  73. end
  74. --4. 定义功能函数:读取并验证芯片的JEDEC ID
  75. local function spi_readChipId_func()
  76. -- 读ID指令0x9F,发送2字节指令,接收3字节ID
  77. -- 发送长度=2(0x9F,0x00),接收长度=3
  78. local id = spi_transfer_func(string.char(0x9F, 0x00), 3)
  79. if #id ~= 3 then
  80. log.error("读ID失败,返回长度:", #id)
  81. return false
  82. end
  83. local b1, b2, b3 = id:byte(1, 3)
  84. log.info("芯片ID:", string.format("0x%02X 0x%02X 0x%02X", b1, b2, b3))
  85. -- W25N01GV的标准ID:0xEF(厂商)、0xAA(设备)、0x21(容量)
  86. if b1 == 0xEF and b2 == 0xAA and b3 == 0x21 then
  87. return true
  88. else
  89. log.error("非W25N01GV芯片")
  90. return false
  91. end
  92. end
  93. --5. 定义功能函数:等待写入完成
  94. local function spi_waitForComplete_func()
  95. while timeout > 0 do
  96. local status = spi_readStatus_func()
  97. -- WIP位为0表示写入完成
  98. if bit.band(status, 0x01) == 0 then
  99. return true
  100. end
  101. sys.wait(10)
  102. timeout = timeout - 10
  103. end
  104. log.error("spi", "等待写入超时")
  105. return false
  106. end
  107. --6. 定义功能函数:读取数据
  108. local function spi_read_page(page_addr, length, column_addr)
  109. local is_column_provided = column_addr ~= nil
  110. column_addr = column_addr or 0x00
  111. if length <= 0 or length > 2048 then
  112. log.error("读取长度无效")
  113. return ""
  114. end
  115. -- 0x03指令+4字节地址(3字节页地址+1字节列地址偏移)
  116. local cmd = string.char(0x03)
  117. .. string.char(bit.rshift(page_addr, 16) & 0xFF) -- 页地址高8位
  118. .. string.char(bit.rshift(page_addr, 8) & 0xFF) -- 页地址中8位
  119. .. string.char(page_addr & 0xFF) -- 页地址低8位
  120. .. string.char(column_addr & 0xFF) -- 列地址偏移(页内起始字节)
  121. local data = spi_transfer_func(cmd, length)
  122. if is_column_provided then
  123. log.info("读取主阵列(偏移0x" .. string.format("%02X", column_addr) .. ")", "读到的数据", string.toHex(data), "长度", #data)
  124. else
  125. log.info("读到的数据", string.toHex(data), "长度", #data)
  126. end
  127. return data
  128. end
  129. -- 7. 定义功能函数::读取页的数据区+OOB区(指令0x51),用于判断是否是坏块
  130. local function spi_read_page_with_oob(page_addr, data_len, oob_len)
  131. if data_len < 0 or data_len > 2048 or oob_len < 0 or oob_len > 64 then
  132. log.error("读取长度无效")
  133. return "", ""
  134. end
  135. -- 0x51指令:读取数据+OOB
  136. local cmd = string.char(0x51)
  137. .. string.char(bit.rshift(page_addr, 16) & 0xFF)
  138. .. string.char(bit.rshift(page_addr, 8) & 0xFF)
  139. .. string.char(page_addr & 0xFF)
  140. -- 总读取长度:数据区+OOB区
  141. local total_data = spi_transfer_func(cmd, data_len + oob_len)
  142. local data = total_data:sub(1, data_len)
  143. local oob = total_data:sub(data_len + 1, data_len + oob_len)
  144. return data, oob
  145. end
  146. -- 8. 定义功能函数:判断是否是坏块
  147. local function is_bad_block(block_addr)
  148. -- 手册规定:块0~7出厂保证为有效块,可直接跳过检测
  149. if block_addr >= 0 and block_addr <= 7 then
  150. log.debug("块", block_addr, "是出厂保证有效块,直接判定为好块")
  151. return false
  152. end
  153. -- 块的第0页
  154. local page_addr = block_addr * 64
  155. -- 读取主阵列第1字节(偏移0x01)
  156. local main_data = spi_read_page(page_addr, 1, 0x01)
  157. -- 读取OOB区前2字节
  158. local _, oob = spi_read_page_with_oob(page_addr, 0, 2)
  159. -- 检测主阵列第1字节是否为0xFF
  160. local main_bad = false
  161. if #main_data >= 1 and main_data:byte(1) ~= 0xFF then
  162. main_bad = true
  163. end
  164. -- 检测OOB区前2字节是否为0xFF
  165. local oob_bad = false
  166. if #oob >= 2 and (oob:byte(1) ~= 0xFF or oob:byte(2) ~= 0xFF) then
  167. oob_bad = true
  168. end
  169. -- 主阵列第1字节和OOB区前2字节,任一非0xFF则判定为坏块
  170. local is_bad = main_bad or oob_bad
  171. log.debug("块", block_addr,
  172. "主阵列第1字节:0x" .. string.format("%02X", main_data:byte(1) or 0x00),
  173. "OOB前2字节:0x" .. string.format("%02X, 0x%02X", oob:byte(1) or 0x00, oob:byte(2) or 0x00),
  174. "判定为" .. (is_bad and "坏块" or "好块"))
  175. return is_bad
  176. end
  177. -- 9. 定义功能函数: 块擦除(指令0xD8)
  178. local function spi_erase_block(block_addr)
  179. if is_bad_block(block_addr) then
  180. log.error("块", block_addr, "是坏块,跳过擦除")
  181. return false
  182. end
  183. -- 写使能
  184. spi_transfer_func(string.char(0x06))
  185. local cmd = string.char(0xD8)
  186. .. string.char(bit.rshift(block_addr, 16) & 0xFF)
  187. .. string.char(bit.rshift(block_addr, 8) & 0xFF)
  188. .. string.char(block_addr & 0xFF)
  189. spi_transfer_func(cmd)
  190. -- 确保等待擦除完成
  191. if not spi_waitForComplete_func() then
  192. log.error("擦除未完成")
  193. return false
  194. end
  195. return true
  196. end
  197. -- 10. 定义功能函数: 页写入
  198. local function spi_write_page(page_addr, data)
  199. if #data > 2048 then
  200. log.error("数据超过页大小2048字节")
  201. return false
  202. end
  203. -- 写使能
  204. spi_transfer_func(string.char(0x06))
  205. -- 加载缓冲区(0x02指令)
  206. local cmd_load = string.char(0x02)
  207. .. string.char(bit.rshift(page_addr, 16) & 0xFF)
  208. .. string.char(bit.rshift(page_addr, 8) & 0xFF)
  209. .. string.char(page_addr & 0xFF)
  210. spi_transfer_func(cmd_load .. data)
  211. -- 执行编程(0x10指令)
  212. local cmd_exec = string.char(0x10)
  213. .. string.char(bit.rshift(page_addr, 16) & 0xFF)
  214. .. string.char(bit.rshift(page_addr, 8) & 0xFF)
  215. .. string.char(page_addr & 0xFF)
  216. spi_transfer_func(cmd_exec)
  217. return spi_waitForComplete_func()
  218. end
  219. -- 11. 定义功能函数:核心测试函数
  220. local function spi_test_func()
  221. --spi初始化
  222. spi_device = spiDev_init_func()
  223. if not spi_device then
  224. return
  225. end
  226. --读取芯片id
  227. if not spi_readChipId_func() then
  228. spi.close(spi_device)
  229. return
  230. end
  231. --坏块检查并进行块擦除,test_block是定义的操作的块编码
  232. --手册规定:块0~7出厂保证为有效块,可直接跳过检测。块取值0~1023
  233. local test_block = 7
  234. log.info("擦除块", test_block, "...")
  235. if not spi_erase_block(test_block) then
  236. spi.close(spi_device)
  237. return
  238. end
  239. local test_page = test_block * 64
  240. local test_data = "Hello, W25N01GV!"
  241. log.info("写入数据", test_data, "到页", test_page, "块", test_block)
  242. --写数据到指定块
  243. if not spi_write_page(test_page, test_data) then
  244. log.info("写入失败")
  245. spi.close(spi_device)
  246. return
  247. end
  248. --读取当前块写入的数据,并验证与写入是否一致
  249. local read_data = spi_read_page(test_page, #test_data)
  250. if read_data == test_data then
  251. log.info("数据验证成功:", read_data)
  252. else
  253. log.error("数据验证失败")
  254. log.info("预期:", test_data)
  255. log.info("实际:", string.toHex(read_data))
  256. end
  257. --操作完成关闭SPI
  258. spi.close(spi_device)
  259. end
  260. sys.taskInit(spi_test_func)