SPI
一、简介
SPI 是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选),就可以完成一个主设备和一个或多个从设备之间的通信,事实上 3 根也可以(单向传输时)。在嵌入式系统中,主要应用于 EEPROM,FLASH,实时时钟,AD 转换器,数字信号处理器和数字信号解码器之间的通信,在全双工模式下,传输速率可达上 110000-100000000 Mbps 的水平。
Air724 模块对外提供的硬件 SPI 接口数量有两路,即 SPI1、SPI2,其他 SPI 通道以复用封装作为 LCD 或 MMC 功能管脚使用。
二、演示功能概述
本教程教你如何使用开发板的 SPI 总线读写 FLASH。
三、准备硬件环境
3.1 开发板准备
使用 EVB_Air724 开发板,如下图所示:
淘宝购买链接:Air724UG-NFM 开发板淘宝购买链接 ;
此开发板的详细使用说明参考:Air724UG 产品手册 中的《EVB_Air724UG_AXX 开发板使用说明》,写这篇文章时最新版本的使用说明为:《EVB_Air724UG_A14 开发板使用说明》;开发板使用过程中遇到任何问题,可以直接参考这份使用说明文档。
api:https://ask.openluat.com/wiki/21?wiki_page_id=2249
3.2 数据通信线
USB 数据线一根(micro USB)。
3.3 PC 电脑
WIN7 以及以上版本的 WINDOWS 系统。
3.4 SPI FLASH 模块
25Q64JVSIQ 注意电平是 3.3V
购买链接:luat.taobao.com
3.5 电平转换模块
双向电平转换模块(3.3V、1.8V)
购买链接:luat.taobao.com
3.6 组装硬件环境
USB 数据线插入 USB 口,另一端与电脑相连,拨码开关全部拨到 ON,串口切换开关选择 UART1,USB 供电的 4V 对应开关拨至 ON 档,如下图所示。
由于 SPI FLASH 模块只支持 3.3v 电平,开发板 SPI 接口只支持 1.8V 电平,所以 SPI FLASH 模块和开发板之间需要用电平转换模块将相互的 IO 电平转换,具体连接图如下图所示。
开发板与电平转换模块连接:
名称 |
开发板 |
电平转换模块 |
---|---|---|
CS |
sSPI1_CS/GPIO_10 |
A5 |
CLK |
SPI1_CLK/GPIO_9 |
A1 |
DI |
SPI1_DIN/GPIO_12 |
A3 |
DO |
SPI1_DOUT/GPIO_11 |
A7 |
GND |
GND |
GND |
VCC |
3.3V |
VB |
SPI FLASH 与电平转换模块连接:
名称 |
电平转换模块 |
SPI FLASH |
---|---|---|
CS |
B5 |
CS |
CLK |
B1 |
CLK |
DI |
B3 |
DI |
DO |
B7 |
DO |
GND |
GND |
GND |
四、准备软件环境
4.1 下载调试工具
使用说明参考:Luatools 下载和详细使用
4.2 源码及固件
4.2.1 底层 core 下载
下载底层固件,并解压
链接:https://docs.openluat.com/air724ug/luatos/firmware/
如下图所示,红框的是我们要使用到的
4.2.2 本教程使用的 demo 见附件:
https://gitee.com/openLuat/LuatOS-Air724UG/tree/master/script_LuaTask/demo/spiFlash
4.3 下载固件和脚本到开发板中
- 打开 Luatools,开发板上电开机,如开机成功 Luatools 会打印如下信息。
- 点击项目管理测试选项。
- 进入管理界面,如下图所示。
- 点击选择文件,选择底层固件,我的文件放在 D:\luatOS\Air724 路径中
- 点击增加脚本或资源文件,选择之前下载的程序源码,如下图所示。
- 点击下载底层和脚本,下载完成如下图所示。
五、代码示例介绍
5.1 API 说明
本文用到的接口函数不做详细介绍,可通过点击右侧链接查看具体介绍:spi API
5.2 test_SPIFlash.lua 代码
test_SPIFlash 程序流程。
--- 验证spi flash驱动接口 目前该驱动兼容w25q32 bh25q32
require"spiFlash"
require "pm"
pm.wake("testSpiFlash")
local flashlist = {
[0xEF15] = 'w25q32',
[0xEF16] = 'w25q64',
[0xEF17] = 'w25q128',
[0x6815] = 'bh25q32',
}
sys.taskInit(function()
local spi_flash = spiFlash.setup(spi.SPI_1)
while true do
local manufactureID, deviceID = spi_flash:readFlashID()
print('spi flash id', manufactureID, deviceID)
local flashName = (manufactureID and deviceID) and flashlist[manufactureID*256 + deviceID]
log.info('testSPIFlash', 'flash name', flashName or 'unknown')
print('spi flash erase 4K', spi_flash:erase4K(0x1000))
print('spi flash write', spi_flash:write(0x1000, '123456'))
print('spi flash read', spi_flash:read(0x1000, 6)) -- '123456'
sys.wait(1000)
end
end)
5.3 main.lua 代码
本代码为主程序脚本,系统启动后首先会对 4G 网络进行配置,然后加载 test_SPIFlash 测试模块。
PROJECT = "TEST_SPI_FLASH"
VERSION = "2.0.0"
--加载日志功能模块,并且设置日志输出等级
--如果关闭调用log模块接口输出的日志,等级设置为log.LOG_SILENT即可
require "log"
LOG_LEVEL = log.LOGLEVEL_TRACE
require "sys"
require "net"
--每1分钟查询一次GSM信号强度
--每1分钟查询一次基站信息
net.startQueryAll(60000, 60000)
--此处关闭RNDIS网卡功能
--否则,模块通过USB连接电脑后,会在电脑的网络适配器中枚举一个RNDIS网卡,电脑默认使用此网卡上网,导致模块使用的sim卡流量流失
--如果项目中需要打开此功能,把ril.request("AT+RNDISCALL=0,1")修改为ril.request("AT+RNDISCALL=1,1")即可
--注意:core固件:V0030以及之后的版本、V3028以及之后的版本,才以稳定地支持此功能
ril.request("AT+RNDISCALL=0,1")
require "test_SPIFlashRandomRW"
--启动系统框架
sys.init(0, 0)
sys.run()
5.4 spiFlash.lua代码
spiFlash.lua 为 W25Q32/BH25Q32 驱动代码。
--- 模块功能:W25Q32/BH25Q32驱动代码
-- @module SPIFlash
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.8.26
module(..., package.seeall)
local READ_ID = 0x90
local WRITE_ENABLE = 0x06
local READ_STATUS = 0x05
local FLASH_READ = 0x03
local ERASE_SECTOR_4K = 0x20
local BLOCK_ERASE_64K = 0xD8
local ERASE_CHIP = 0x60
local PAGE_PROGRAM = 0x02
local gpio_set = pio.pin.setval
local mt = { __index = {} }
local function address24bit(addr)
return pack.pack('>I', addr):sub(2, 4)
end
--- 创建spi flash驱动实例
-- @param id spi id
-- @param timeout 等待flash busy状态解除的最长超时时间,默认60000ms
-- @return 返回spi flash驱动实例
-- @usage w25q32 = spiFlash.setup(spi.SPI_1, pio.P0_10)
function setup(id, timeout)
if not spi.setup(id, 0, 0, 8, 13000000, 1) then
log.error('BH25Q32.setup', 'spi setup failed')
end
local o = {
id = id,
timeout = timeout or 60000, -- ms
pagesize = 256,
}
setmetatable(o, mt)
return o
end
--- 释放spi flash驱动实例
-- @param
-- @return
-- @usage w25q32:close()
function mt.__index:close()
spi.close(self.id)
end
--- 读取flash id
-- @return manufactureID, deviceID 返回两个参数 厂商id,设备id
function mt.__index:readFlashID()
log.info("send",pack.pack('bAA', READ_ID, address24bit(0), '\255\255'):toHex())
local r = spi.send_recv(self.id, pack.pack('bAA', READ_ID, address24bit(0), '\255\255'))
if r then
return string.byte(r, 5, 6)
else
return false
end
end
--- 读取flash状态寄存器 STATUS S0-S7
-- @return 返回flash状态寄存器bit0-bit7的值
function mt.__index:readStatus()
local r = spi.send_recv(self.id, string.char(READ_STATUS, 0xff))
return r and string.byte(r, 2)
end
--- 查询flash是否处于busy状态
-- @return true - 繁忙 false - 空闲
function mt.__index:busy()
return self:readStatus() % 2 == 1
end
--- 等待flash空闲
-- @param timeout 超时时间 单位ms
--
function mt.__index:waitNotBusy(timeout)
local status
local step = 50
local count = 0
timeout = timeout or self.timeout
while true do
if not self:busy() then break end
sys.wait(step)
count = count + 1
if count > timeout / step then
log.error('BH25Q32.waitNotBusy', 'timeout')
return false
end
end
return true
end
--- 向spi flash 传输数据
-- @param send_data 要发送的数据
-- @param read_size 可选参数,需要读取的数据长度,如果是写指令,不填参数或者填0
-- @return nil - 失败 true - 写指令发送成功 string - 返回读取到的数据
function mt.__index:transfer(send_data, read_size)
if not self:waitNotBusy() then return end
if type(send_data) == 'number' then send_data = string.char(send_data) end
if not read_size then read_size = 0 end
if read_size == 0 then
spi.send_recv(self.id, string.char(WRITE_ENABLE))
end
local r = spi.send_recv(self.id, send_data .. string.rep('\255', read_size))
if read_size > 0 then
return string.sub(r, send_data:len() + 1, -1)
end
return true
end
--- 读取spi flash 指定地址的数据
-- @param addr flash地址
-- @param len 长度
-- @return nil - 读取失败 string - 读取到的数据
-- @usage w25q32 = spiFlash.setup(spi.SPI_1, pio.P0_10); w25q32:read(0x1000, 6);
function mt.__index:read(addr, len)
return self:transfer(string.char(FLASH_READ) .. address24bit(addr), len)
end
--- 按擦除sector(4K)的方式擦除指定地址的数据
-- @param addr 擦除的起始地址,会自动做4K对齐
-- @return true - 成功 false - 失败
function mt.__index:erase4K(addr)
return self:transfer(string.char(ERASE_SECTOR_4K) .. address24bit(addr - addr % 0x1000)) and self:waitNotBusy()
end
--- 按擦除block 64K的方式擦除指定地址的数据
-- @param addr 擦除的起始地址,会自动做64K对齐
-- @return true - 成功 false - 失败
function mt.__index:erase64K(addr)
return self:transfer(string.char(BLOCK_ERASE_64K) .. address24bit(addr - addr % 0x10000)) and self:waitNotBusy()
end
--- 擦除整个flash芯片的数据
-- @param 无
-- @return true - 成功 false - 失败
function mt.__index:eraseChip()
return self:transfer(ERASE_CHIP) and self:waitNotBusy()
end
--- 向spi flash指定地址写入数据
-- @param address 写入的地址
-- @param data 数据
-- @return number - 成功写入的数据长度
function mt.__index:write(address, data)
local ending_address = address + data:len()
local wrote_len = 0
local bytes_to_write = 0
while address < ending_address do
bytes_to_write = self.pagesize - (address % self.pagesize)
if wrote_len + bytes_to_write > data:len() then bytes_to_write = data:len() - wrote_len end
if not self:transfer(pack.pack('bAA', PAGE_PROGRAM, address24bit(address), data:sub(wrote_len + 1, wrote_len + bytes_to_write))) then
break
end
address = address + bytes_to_write
wrote_len = wrote_len + bytes_to_write
end
return wrote_len
end
六、开机调试
6.1 开发板开机
连接好硬件,启动 Luatools 软件,并下载固件后,系统运行信息将显示在界面中。红框中为开发板连接到 PC 机后正常打印的信息,如下图所示。
6.2 SPI FLASH 调试
读出 SPI FLASH ID, 并显示 SPI FLASH 的型号,同时进行读写测试。
七、常见问题
7.1 为什么 SPI 通信中会出现一些乱码或数据丢失
答:SPI 通信的接口电平不匹配。可用示波器观测 SPI 接口在通信过程中的电平状态和电平质量,确认用户所使用的通信设备中接口电平是否与 Air724 模块 SPI1 接口电平一致,Air724 开发板上的 Air724 模块的 SPI1 接口电平均为 1.8V,如不匹配,请确保一致。不能过高或过低,过低有可能会导致电平电压不能被识别,过高有可能会损坏 SPI 通信接口,另外,建议使用一些高速器件作为电平转换器件,可提高接口电平的信号质量。
7.2 标准 spi 可以驱动的目标芯片电压是多少?
1.8V,这个电压是写死的,无法设置,如果目标芯片工作电压不在此范围内需要用电平转换芯片。
7.3 SPI外置FLASH有demo吗 最大支持多少?
qspi外挂flash参考:demo/qspi 目前有测试过最大的是 16MByte 的 qspi,超过16M不支持,是硬件限制的(QSPI地址只有24位)。
标准spi外挂flash参考:demo/spiFlash,对flash容量没限制,需要自己实现驱动。