基础资料
基于Air103开发板:Air103 - LuatOS 文档
探讨重点
对官方SPI FLASH demo中功能的复现,进行相关内容的学习及探讨。
实现功能
功能:lua快速驱动-W25QXX(XX代表内存容量,本例采用128MB) ;
硬件准备
Air103开发板1块,面包板1块,W25Q128,导线若干。
软件版本
AIR103:LuatOS@AIR103 base 22.10 bsp V0013
软件使用
接口文档可参考:SPI库
硬件准备
-
air103开发板
-
SPI FLASH 这里选择W25Q128
-
数据手册可参考同系列W25Q128数据手册
接线示意
SPI_CS/GPIO20 ----- CS
SPI_MISO/GPIO19 ----- DO
SPI_MOSI/GPIO21 ----- DI air103
SPI_CLK/GPIO18 ----- CLK
SPI_FLASH 3.3V ----- VCC
GND ----- GND
操作方式
SPI的操作有两种方式,一种是直接对SPI通道进行读写,一种是抽象为一个SPI设备再进行读写
1、直接对SPI通道进行读写
初始化CS引脚手动控制
使用air103时,需要我们手动控制CS拉低来使能设备
将spi.transfer封装成一个新的函数起来前后用CS操作环绕
function transfer(CS, spiID, data, sendLen, recvLen)
CS(0)
local res = spi.transfer(spiID, data, sendLen, recvLen)
CS(1)
return res
end
初始化SPI
初始化SPI通道0,24MHz CLK,半双工模式
local spiID, CS_GPIO = 0, 20
-- CS 参数传255代表手动控制CS引脚,最后一个mode参数传0代表半双工模式
local setupRes = spi.setup(spiID, 255, 0, 0, 8, 24 * 1000 * 1000, spi.MSB, 1, 0)
if setupRes ~= 0 then
log.error(PROJECT .. ".setup", "ERROR")
return
end
读取SPI FLASH的制造商和设备ID
查阅W25Q128数据手册可知 查询制造商和设备ID的指令为0x90
指令描述如下图
Read Manufacturer / Device ID (90h)
The Read Manufacturer/Device ID instruction is an alternative to the Release from Power-down / Device ID instruction that provides both the JEDEC assigned manufacturer ID and the specific device ID.
The Read Manufacturer/Device ID instruction is very similar to the Release from Power-down / Device ID instruction. The instruction is initiated by driving the /CS pin low and shifting the instruction code “90h” followed by a 24-bit address (A23-A0) of 000000h. After which, the Manufacturer ID for Winbond (EFh) and the Device ID are shifted out on the falling edge of CLK with most significant bit (MSB) first as shown in figure 26. The Device ID values for the W25Q80, W25Q16, and W25Q32 are listed in Manufacturer and Device Identification table. If the 24-bit address is initially set to 000001h the Device ID will be read first and then followed by the Manufacturer ID. The Manufacturer and Device IDs can be read continuously, alternating from one to the other. The instruction is completed by driving /CS high.
log.info(PROJECT .. ".chipID", string.toHex(transfer(CS, spiID, string.char(0x90, 0, 0, 0), 4, 2)))
日志如下
I/user.spi.chipID EF17 4
返回值0xEF17中EF为制造商Winbond Serial Flash,17为型号W25Q128JV
擦除指定地址扇区
查阅W25Q128数据手册可知 擦除指定地址扇区的指令为0x20(Sector Erase (4KB))
在执行擦除指令之前需要执行写使能指令0x06
擦除地址0x01开始的4K-bytes大小的扇区
1.1.1 Instruction Set Table 1 (1)
INSTRUCTION NAME | BYTE 1 (CODE) | BYTE 2 | BYTE 3 | BYTE 4 | BYTE 5 | BYTE 6 |
Write Enable | 06h | |||||
Write Disable | 04h | |||||
Read Status | 05h | (S7–S0) (2) | ||||
Read Status | 35h | (S15-S8) (2) | ||||
Write Status | 01h | (S7–S0) | (S15-S8) | |||
Page Program | 02h | A23–A16 | A15–A8 | A7–A0 | (D7–D0) | |
Quad Page | 32h | A23–A16 | A15–A8 | A7–A0 | (3) | |
Block Erase | D8h | A23–A16 | A15–A8 | A7–A0 | ||
Block Erase | 52h | A23–A16 | A15–A8 | A7–A0 | ||
Sector Erase | 20h | A23–A16 | A15–A8 | A7–A0 | ||
Chip Erase | C7h/60h | |||||
Erase Suspend | 75h | |||||
Erase Resume | 7Ah | |||||
Power-down | B9h | |||||
High Performance Mode | A3h | dummy | dummy | dummy | ||
Mode Bit Reset (4) | FFh | FFh | ||||
Release Power down | ABh | dummy | dummy | dummy | (ID7-ID0) (5) | |
Manufacturer/ Device | 90h | dummy | dummy | 00h | (M7-M0) | (ID7-ID0) |
Read Unique | 4Bh | dummy | dummy | dummy | Dummy | (ID63-ID0) |
JEDEC ID | 9Fh | (M7-M0) | (ID15-ID8) | (ID7-ID0) |
代码如下
spiFlash:send(string.char(0x06))
sys.wait(100)
spiFlash:send(string.char(0x20, 0x00, 0x00, 0x01))
-- 擦除需要消耗一定的时间
sys.wait(1000)
20h | A23–A16 | A15–A8 | A7–A0 |
-- 20h开头,每个字节描述,发送4byte,接收0byte transfer(CS, spiID, string.char(0x20, 0x00, 0x00, 0x01), 4, 0) -- 擦除需要消耗一定的时间 sys.wait(1000)
读写SPI FLASH
Page Program | 02h | A23–A16 | A15–A8 | A7–A0 | (D7–D0) |
查阅W25Q128数据手册可知 页写的指令为0x02,一次最多写入256字节的数据,读取数据的指令为0x03
1.1.2 Instruction Set Table 2 (Read Instructions)
INSTRUCTION NAME | BYTE 1 (CODE) | BYTE 2 | BYTE 3 | BYTE 4 | BYTE 5 | BYTE 6 |
Read Data | 03h | A23–A16 | A15–A8 | A7–A0 | (D7–D0) | |
Fast Read | 0Bh | A23–A16 | A15–A8 | A7–A0 | dummy | (D7–D0) |
Fast Read Dual Output | 3Bh | A23–A16 | A15–A8 | A7–A0 | dummy | (1) |
Fast Read Dual I/O | BBh | (2) | (2) | (1) | ||
Fast Read | 6Bh | A23–A16 | A15–A8 | A7–A0 | dummy | (3) |
Fast Read | EBh | (4) | (5) | (3) |
在执行擦除指令之前需要执行写使能指令0x06
代码如下
spiFlash:send(string.char(0x06))
sys.wait(100)
spiFlash:send(string.char(0x02, 0x00, 0x00, 0x01) .. PROJECT)
sys.wait(100)
local readRes = spiFlash:transfer(string.char(0x03, 0x00, 0x00, 0x01), 4, string.len(PROJECT))
log.info(PROJECT .. ".readRes", readRes)
日志如下
I/user.spi.readRes spi
观察日志,读出的数据与我们写入的数据一致
禁用写使能并关闭对应的SPI通道和CS GPIO
查阅W25Q128数据手册可知 禁用写使能的指令为0x04
1.1.1 rite Disable (04h)
The Write Disable instruction (Figure 5) resets the Write Enable Latch (WEL) bit in the Status Register to a 0. The Write Disable instruction is entered by driving /CS low, shifting the instruction code “04h” into the DI pin and then driving /CS high. Note that the WEL bit is automatically reset after Power-up and upon completion of the Write Status Register, Page Program, Sector Erase, Block Erase and Chip Erase instructions.
代码如下
spiFlash:send(string.char(0x04))
log.info(PROJECT .. ".device_close", spiFlash:close())
2、抽象为一个SPI设备再进行读写
抽象SPi外设
抽象SPI通道0的SPI FLASH,24MHz CLK,半双工模式
代码如下
local spiID, CS_GPIO = 0, 20 --20(GPIO)=pin.PB04
-- CS 参数传255代表手动控制CS引脚,最后一个mode参数传0代表半双工模式
--spi.setup(id, cs(255,手动控制), CPHA(极性), CPOL(相位), dataw(数据宽度,默认8bit), bandrate(默认2M=2000000), bitdict(大小端, 默认spi.MSB(大端首先存储 MSB 字节,即高字节存储在低地址;)
--, 可选spi.LSB(小端,则首先存储LSB字节,即低字节存储在低地址。)),ms(主从设置, 默认主1, 可选从机0. 通常只支持主机模式), mode(工作模式, 全双工1, 半双工0, 默认全双工))
local spiFlash = spi.deviceSetup(spiID, CS_GPIO, 0, 0, 8, 24 * 1000 * 1000, spi.MSB, 1, 0)
读取SPI FLASH的制造商和设备ID
查阅W25Q128数据手册可知 查询制造商和设备ID的指令为0x90
代码如下
log.info(PROJECT .. ".chipID", string.toHex(spiFlash:transfer(string.char(0x90, 0, 0, 0), 4, 2)))
日志如下
I/user.spi.chipID EF16 4
返回值0xEF17中EF为制造商Winbond Serial Flash,16为型号W25Q64
擦除指定地址扇区
查阅W25Q128数据手册可知 擦除指定地址扇区的指令为0x20
在执行擦除指令之前需要执行写使能指令0x06
擦除地址0x01开始的4K-bytes大小的扇区
代码如下
spiFlash:send(string.char(0x06))
sys.wait(100)
spiFlash:send(string.char(0x20, 0x00, 0x00, 0x01))
-- 擦除需要消耗一定的时间
sys.wait(1000)
读写SPI FLASH
查阅W25Q128数据手册可知 页写的指令为0x02,一次最多写入256字节的数据,读取数据的指令为0x03
在执行擦除指令之前需要执行写使能指令0x06
spiFlash:send(string.char(0x06))
sys.wait(100)
spiFlash:send(string.char(0x02, 0x00, 0x00, 0x01) .. PROJECT)
sys.wait(100)
local readRes = spiFlash:transfer(string.char(0x03, 0x00, 0x00, 0x01), 4, string.len(PROJECT))
log.info(PROJECT .. ".readRes", readRes)
日志如下
I/user.spi.readRes spi
观察日志,读出的数据与我们写入的数据一致
禁用写使能并关闭对应的SPI通道
查阅W25Q128数据手册可知 禁用写使能的指令为0x04
spiFlash:send(string.char(0x04)) log.info(PROJECT .. ".device_close", spiFlash:close())
代码注释
function transfer(CS, spiID, data, sendLen, recvLen)
CS(0)
local res = spi.transfer(spiID, data, sendLen, recvLen)
CS(1)
return res
end
local function test()
local spiID, CS_GPIO = 0, 20
local setupRes = spi.setup(spiID, 255, 0, 0, 8, 10 * 1000 * 1000, spi.MSB, 1, 0)
if setupRes ~= 0 then
log.error(PROJECT .. ".setup", "ERROR")
return
end
local CS = gpio.setup(CS_GPIO, 0)
log.info(PROJECT .. ".chipID", string.toHex(transfer(CS, spiID, string.char(0x90, 0, 0, 0), 4, 2)))
transfer(CS, spiID, string.char(0x06), 1, 0)
sys.wait(100)
transfer(CS, spiID, string.char(0x20, 0x00, 0x00, 0x01), 4, 0)
sys.wait(1000)
transfer(CS, spiID, string.char(0x06), 1, 0)
sys.wait(100)
transfer(CS, spiID, string.char(0x02, 0x00, 0x00, 0x01) .. PROJECT, 4 + string.len(PROJECT), 0)
sys.wait(100)
local readRes = transfer(CS, spiID, string.char(0x03, 0x00, 0x00, 0x01), 4, string.len(PROJECT))
log.info(PROJECT .. ".readRes", readRes)
transfer(CS, spiID, string.char(0x04), 1, 0)
gpio.close(CS_GPIO)
spi.close(spiID)
local spiFlash = spi.deviceSetup(spiID, CS_GPIO, 0, 0, 8, 10 * 1000 * 1000, spi.MSB, 1, 0)
log.info(PROJECT .. ".chipID", string.toHex(spiFlash:transfer(string.char(0x90, 0, 0, 0), 4, 2)))
spiFlash:send(string.char(0x06))
sys.wait(100)
spiFlash:send(string.char(0x20, 0x00, 0x00, 0x01))
sys.wait(1000)
spiFlash:send(string.char(0x06))
sys.wait(100)
spiFlash:send(string.char(0x02, 0x00, 0x00, 0x01) .. PROJECT)
sys.wait(100)
local readRes = spiFlash:transfer(string.char(0x03, 0x00, 0x00, 0x01), 4, string.len(PROJECT))
log.info(PROJECT .. ".readRes", readRes)
spiFlash:send(string.char(0x04))
log.info(PROJECT .. ".device_close", spiFlash:close())
end