air103#

本章介绍LuatOS的spi库使用方式

简介#

spi库提供LuatOS与使用spi协议外设的通讯

SPI 是英语 (Serial Peripheral interface) 的缩写,顾名思义就是串行外围设备接口。是 Motorola 首先在其 MC68HCXX 系列处理器上定义的

SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI 是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便

参考Air103_MCU设计手册V1.2.pdf可知,air103有1路SPI,但支持映射到不同的管脚组合,通道可以为0,1 我们选择通道0

硬件准备#

W25Q128是一款支持SPI通信协议的128M-Bit的FLASH

接线示意

        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通道进行读写,一种是抽象为一个SPI设备再进行读写

直接对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)

代码如下

log.info(PROJECT .. ".chipID", string.toHex(transfer(CS, spiID, string.char(0x90, 0, 0, 0), 4, 2)))

日志如下

I/user.spi.chipID EF17 4

返回值0xEF17EF为制造商Winbond Serial Flash,17为型号W25Q128JV

擦除指定地址扇区#

查阅W25Q128数据手册可知 擦除指定地址扇区的指令为0x20

在执行擦除指令之前需要执行写使能指令0x06

擦除地址0x01开始的4K-bytes大小的扇区

代码如下

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)

读写SPI FLASH#

查阅W25Q128数据手册可知 页写的指令为0x02,一次最多写入256字节的数据,读取数据的指令为0x03

在执行擦除指令之前需要执行写使能指令0x06

代码如下

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)

日志如下

I/user.spi.readRes spi

观察日志,读出的数据与我们写入的数据一致

禁用写使能并关闭对应的SPI通道和CS GPIO#

查阅W25Q128数据手册可知 禁用写使能的指令为0x04

代码如下

transfer(CS, spiID, string.char(0x04), 1, 0)
gpio.close(CS_GPIO)
spi.close(spiID)

抽象为一个SPI设备再进行读写#

抽象SPi外设#

抽象SPI通道5的SPI FLASH,24MHz CLK,半双工模式

代码如下

local spiID, CS_GPIO = 0, 20
-- 最后一个mode参数传0代表半双工模式
local spiFlash = spi.deviceSetup(spiID, CS_GPIO, 0, 0, 8, 24 * 1000 * 1000, spi.MSB, 1, 0)

读取SPI FLASH的制造商和设备ID#

查阅W25Q128数据手册可知 查询制造商和设备ID的指令为0x90

指令描述如下图

Read Manufacturer / Device ID (90h)

代码如下

log.info(PROJECT .. ".chipID", string.toHex(spiFlash:transfer(string.char(0x90, 0, 0, 0), 4, 2)))

日志如下

I/user.spi.chipID EF17 4

返回值0xEF17EF为制造商Winbond Serial Flash,17为型号W25Q128JV

擦除指定地址扇区#

查阅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())

日志如下

I/user.spi.device_close true

完整代码#

PROJECT = "spi"
VERSION = "1.0.0"
sys = require("sys")

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

sys.taskInit(test)

sys.run()