🚗 W806#

W806是什么?#

W806是一款QFN56 封装,6mm x 6mm 大小的mcu. 本芯片与合宙Air103可互换.

LuatOS为它提供哪些功能#

  • 基于Lua 5.3.6,提供95%的原生库支持

  • 适配LuaTask,提供极为友好的sys.lua

  • 文件系统大小112KB,格式littlefs 2.1.

  • 支持外挂psram,最大8MB

  • gpio GPIO管脚控制功能(映射表后面有提供)

  • uart 串口输入输出功能,支持uart0(芯片日志/调试/刷机)/uart1~4(用户可用)

  • i2c iic总线master功能,并自带多种温湿度传感器驱动

  • disp 基于i2c的显示屏支持,例如SSD1306

  • eink 支持多款墨水屏

  • lcd 支持多款彩色SPI屏

  • lvgl 支持全部LVGL原生组件和动画效果,并内嵌中文字体

  • zbuff 像C语言一样操作内存字节数组,高效可靠

  • json lua对象与json字符串的双向转换

  • log 简洁的日志功能

  • wdt 硬件看门狗,安全保护

  • pwm 多个PWM输出管脚,存在复用关系

  • adc 2个adc通道外部电平检测,一个内部温度检测

  • sensor 单总线驱动,默认支持DS18B20

  • pm 功耗管理,可进入低功耗模式并定时唤醒

  • hwtimer 硬件定时器(开发中)

  • rtc 实时时钟(开发中)

  • sdio 通过SDIO硬件接口读写TF卡

  • mcu 主频调节,最低可到2M(需调低uart波特率)

LuatOS大QQ群: 1061642968

特别说明#

由于W806的开发板复位引脚没有连接串口芯片的rts,因此需要手动复位下载,开发的时候比较麻烦。可以如下图所示,将串口芯片的rts脚飞线接到芯片的复位引脚,下载软件就可以自动复位芯片了。

image-20211103172223853

编译固件#

如果只想编译自定义功能的固件可以直接使用的在线云编译生成自定义固件。无需任何环境安装,可以自定义选择各种功能。

想自己编译教程参考源码编译教程按照Air103的方式进行编译

烧录固件#

完成编译固件以后,我相信大家已经获得了.soc后缀的固件文件,下面介绍如何把固件烧录进设备。

工具配置#

首先下载最新版本的Luatools:点我下载

建议新建一个Luatools文件夹,将exe文件放入其中后,再打开exe文件

选择选项及工具工具配置

img

uart选项卡,设置波特率为921600,然后重启软件。

烧录#

将设备通过usb线连接到电脑,可以看到出现一个新的COM口,勾选通用串口打印,并在工具中选中这个COM口

image-20211028153429404

点击下载固件按钮,选中刚刚下载的soc文件,直接下载即可,因为w806开发板的串口芯片rts脚没有连接到芯片复位引脚,所以点击下载后需要手动按板子上的rst按键重启。如果按特别说明涨价改过,就无需手动复位。

image-20211028153732352

等待进度条走完固件就下载成功了

image-20211028154052448

烧录脚本#

点击项目管理测试按钮,点击左下角的创建项目新建一个新项目

选好芯片使用的固件,选好脚本即可下载

demo类的脚本,可以前往LuatOS官方仓库,在/demo文件夹可以找到

Luatools工具也自动下载了正式版本的一些实例脚本可以在resource\某种型号\某个版本\demo文件夹看到,可以直接选择脚本进行烧录测试

image-20211028154249078

如果芯片当前已经烧录的固件版本,和这里选择的固件相同,那么可以点击下载脚本只下载脚本;反之,建议点击下载底层和脚本,进行全刷。

查看日志#

如果没有打开串口,点击打开串口即可查看日志

如果还没反应,可以试试点击重启串口

image-20211028154337785

GPIO输出-点灯#

本章将使用GPIO接口控制开发板的LED灯进行闪烁,做个灯神

硬件准备#

开发板一块,usb线一根

软件使用#

根据原理图可以找到开发板所带的三个灯分别为PB0,1,2控制,低电平亮起

image-20211103173519650

我们查找Air103/W806的文档LuatOS 文档可知PB0的引脚标号为16

根据GPIO操作 的接口,简单写几行代码,完整代码见demo目录的gpio文件夹

local LED1 = gpio.setup(16, 0) -- PB0输出模式
sys.taskInit(function()
    while 1 do
        log.info("LED开启")
        LED1(0)
        sys.wait(1000)
        log.info("LED关闭")
        LED1(1)
        sys.wait(1000)
    end
end)

烧录看一下效果

Video_20211103_054207_512

一闪一闪亮晶晶,GPIO输出搞定

GPIO输入-按键#

输出控制上一节学会了,趁热打铁学一下输入

硬件准备#

开发板一块,usb线一根

软件使用#

一样的套路,先看原理图,RESET按键别想了一按就复位,那就选BOOT了,也就是PA0

image-20211103175057318

老规矩查找Air103/W806的文档LuatOS 文档可知PA0的引脚标号为0

根据gpio - GPIO操作 的接口,简单写几行代码,完整代码见demo目录的gpio文件夹

gpio.setup(0, function(val)
    log.info("PA0", val)
end, gpio.PULLUP)--按键按下接地,因此需要上拉

烧录,测试一下。按下boot键再松开,日志可以看的正常触发中断

image-20211103184039818

GPIO的输入输出我们就学习完成了。

PWM#

本章将使用PWM驱动板载的led实现呼吸灯效果

简介#

PWM(Pulse Width Modulation , 脉冲宽度调制) 是一种对模拟信号电平进行数字编码的方法,通过不同频率的脉冲使用方波的占空比用来对一个具体模拟信号的电平进行编码,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替所需要波形的设备。

硬件准备#

开发板一块,usb线一根

软件使用#

根据前面的GPIO学习我们知道板载的灯是接在PB0,1,2上的,恰好这几个引脚都支持PWM,所以就试试做个呼吸灯吧

查一下API发现PWM的使用很简单,就直接open就行,然后调节占空比就可以调节亮度,上代码。完整代码在demo的pwm文件夹

sys.taskInit(function()
    while 1 do
        -- 仿呼吸灯效果
        log.info("pwm", ">>>>>")
        for i = 100,1,-1 do 
            pwm.open(0, 1000, i) -- 频率1000hz, 占空比0-100
            sys.wait(20)
        end
        sys.wait(1000)
        for i = 100,1,-1 do 
            pwm.open(0, 1000, 100 - i)
            sys.wait(20)
        end
        gpio.setup(16,1)
        sys.wait(1000)
    end
end)

烧录看效果

Video_20211104_083035_358

呼吸灯实现了

UART#

本章讲学习如何配置串口,并通过串口发送收到的数据进行回环

简介#

UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。

UART 串口的特点是将数据一位一位地顺序传送,只要 2 根传输线就可以实现双向通信,一根线发送数据的同时用另一根线接收数据。UART 串口通信有几个重要的参数,分别是波特率、起始位、数据位、停止位和奇偶检验位,对于两个使用 UART 串口通信的端口,这些参数必须匹配,否则通信将无法正常完成。

硬件准备#

W806开发板一块,usb转串口线一根

软件使用#

查看文档LuatOS 文档可以看到,W806有5个uart,其中uart0做下载调试用,所以我们就选uart1吧。也就是PB6->TX然后PB7->RX把TX接串口线RX,RX接串口线TX,然后别忘了共地。

接好线以后开始写代码,根据UART 的API接口说明,首先我们初始化串口

local result = uart.setup(
    UART_ID,--串口id
    115200,--波特率
    8,--数据位
    1--停止位
)

要做到串口数据回环首先要学习如何接收串口数据,接口里面的uart.on(id, event, func)注册串口事件回调,就是中断回调方式接收,收到以后再通过uart.write接口发出来就好了,理论分析完成,上代码

-- 串口ID,串口读缓冲区
local UART_ID, sendQueue = 1, {}
-- 串口超时,串口准备好后发布的消息
--例子是100ms,按需求改
local uartimeout, recvReady = 100, "UART_RECV_ID"
--初始化
local result = uart.setup(
    UART_ID,--串口id
    115200,--波特率
    8,--数据位
    1--停止位
)
uart.on(UART_ID, "receive", function(uid, length)
    local s
    while true do--保证读完不能丢包
        s = uart.read(uid, length)
        if #s == 0 then break end
        table.insert(sendQueue, s)
    end
    sys.timerStart(sys.publish, uartimeout, recvReady)
end)
-- 向串口发送收到的字符串
sys.subscribe(recvReady, function()
    --拼接所有收到的数据
    local str = table.concat(sendQueue)
    -- 串口的数据读完后清空缓冲区
    sendQueue = {}
    --注意打印会影响运行速度,调试完注释掉
    --log.info("uartTask.read length", #str, str:sub(1,100))
    uart.write(UART_ID,str) --回复
    --在这里处理接收到的数据,这是例子
end)

对于不定长的数据接收一般采用超时断帧机制,也就是在收到前一个字节开启定时器,在一定时间内没有收到下一个字节就认为这是一包数据。

下载查看效果,收到不定长数据后回环输出。

image-20211104140609894

ADC#

本章将会向大家介绍LuatOS的ADC功能。将会实现使用Air101开发板读取内部温度并在日志中打印。

简介#

模拟数字转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。W806芯片具有两路16位ADC,最高采样率1KHz。

硬件准备#

W806开发板一块,可调电源一个

软件使用#

这次我们先找文档LuatOS 文档可以看到除了可以读取外接的IO电压之外,还以读取内部的温度和供电电压。我们这次选取ADC0,也就是PA1引脚。

我们给PA1脚接可调电源(一定注意电压不要超过2.4V)

根据ADC库库的接口,简单写几行代码,完整代码见demo目录的adc文件夹

sys.taskInit(function()
    while 1 do
        adc.open(0) -- 模块上的ADC0脚-PA1, 0~2.4v,不要超过范围使用!!!
        adc.open(10) -- CPU温度
        adc.open(11) -- VBAT电压,最新代码才支持
        sys.wait(500)
        log.debug("adc", "adc0", adc.read(0))
        log.debug("adc", "adc_temp", adc.read(10))
        log.debug("adc", "vbat", adc.read(11))
        sys.wait(500)
    end
end)

烧录,观察日志

image-20211103185746448

外接表显PA1电压2.092V,实测ADC有一定误差,建议之作阈值检测之类的用。

I2C#

本章将会向大家介绍LuatOS的I2C功能。将会实现使用W806开发板I2C读取SHT30传感器的温湿度

简介#

I2C(Inter Integrated Circuit)总线是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。I2C 总线传输数据时只需两根信号线,一根是双向数据线 SDA(serial data),另一根是双向时钟线 SCL(serial clock)

硬件准备#

W806开发板一块,SHT30传感器一个

软件使用#

首先还是查文档LuatOS 文档查阅文档可知PA1为SCL引脚,PA4为SDA引脚。我们将传感器按照这个定义连到板子上。

开始写代码,先找接口说明i2c - I2C操作 - LuatOS 文档,先初始化,需要地址,查阅SHT30手册,地址是由ADDR这个引脚电平决定,我这个模块悬空,就是0x44

image-20211103192701546

知道地址了,接下来看一下怎么测量温度,手册说连续测量需要发命令0x2C06,参照LuatOS的接口,直接i2c.send()就完事

image-20211103193151985

然后接收数据,根据手册的公式计算实际温湿度。

image-20211103193445226

代码如下,完整代码见demo目录的i2c文件夹

sys.taskInit(function()
    -- 初始化i2c,使用id为0
    if i2c.setup(0, i2c.FAST, 0x44) == 1 then
        log.info("存在 i2c0")
    else
        i2c.close(0) -- 关掉
    end
    while 1 do
        local w = i2c.send(0, 0x44, string.char(0x2c, 0x06)) -- 发送单次采集命令
        sys.wait(10) -- 等待采集
        local r = i2c.recv(0, 0x44, 6) -- 读取数据采集结果
        log.info("recv", r:toHex())
        local a, b, c, d, e, f, g = string.unpack("BBBBBB", r)
        log.info("a", a, b, c, d, e, f, g)
        t = ((4375 * (a * 256 + b)) / 16384) - 4500 -- 根据SHT30传感器手册给的公式计算温度和湿度
        h = ((2500 * (d * 256 + e)) / 16384)
        log.warn("这里是温度", t / 100) -- 打印温度
        log.warn("这里是湿度", h / 100) -- 打印湿度
        sys.wait(1000)
    end
end)

烧录查看结果,都11月份了,W806让我的板子在这个冬季不再寒冷

image-20211103194212585

SPI#

本章将使用W806的硬件SPI读取flah的ID并打印

简介#

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,设备分为主机和从机,目前W806的SPI仅能作为主机使用。

硬件准备#

W806开发板一块,SPI的flash一个

软件使用#

先找引脚图研究怎么接线,查文档LuatOS 文档查阅文档可知W806芯片有两组硬件SPI,我们选择使用SPI0,也就是PB2接CLK,PB5接MOSI,PB3接MISO,PB4接CS。接下来研究如何驱动。

我用的flash是W25Q128,根据手册可以查到有一个读flash的型号的通用命令0x9F,正常的话W25Q128应该会返回0xEF4018

image-20211104124049436

我们参照API手册先初始化SPI,创建一个SPI对象spi_device = spi.deviceSetup(0,20,0,0,8,2000000,spi.MSB,1,1)

发0x9F指令,读取返回的3个字节recv = spi_device:transfer(string.char(0x9f),nil,3)

整体代码如下,完整代码见demo目录的SPI文件夹

sys.taskInit(
    function()
        local spi_device = spi.deviceSetup(0,20,0,0,8,2000000,spi.MSB,1,1)
        while 1 do
            local recv = spi_device:transfer(string.char(0x9f),nil,3)
            log.info("spi data",recv:toHex())
            sys.wait(1000)
        end
    end
)

下载到设备,可以看到日志中打印的值是EF4018符合手册说明,SPI通信正常

image-20211104133500593

SFUD-外置flash#

本章将使用W806的硬件SPI挂载flash到文件系统,直接读写

简介#

上一章我们学会了用SPI读取外置flash的ID,实际通常外挂的spi flash的读写指令都是兼容的,我们在日常使用的过程中如果直接使用spi通过指令对flash进行读写会很麻烦,于是乎LuatOS就设计了一套接口,将外置的spi flash通过这套接口实现抽象读写,并对接Lua的io实现简单读写。

硬件准备#

W806开发板一块,SPI的flash一个

软件使用#

接线图看SPI那一章即可不在赘述

参考sfud 文档写个简单的demo,大致思路就是先给sfud创建一个spi的硬件设备对象,然后通过sfud的抽象接口操作spi实现对flash的操作

local spi_flash = spi.deviceSetup(0,20,0,0,8,2000000,spi.MSB,1,1)--PB6
log.info("sfud.init",sfud.init(spi_flash))
log.info("sfud.getDeviceNum",sfud.getDeviceNum())
local sfud_device = sfud.getDeviceTable()
log.info("sfud.write",sfud.write(sfud_device,1024,"sfud"))
log.info("sfud.read",sfud.read(sfud_device,1024,4))

先测试一下能不能正常连接设备,烧录。查看日志可以看到sfud的库在初始化的时候自动帮我们获取了flash的厂商、大小等信息,我们通过sfud.write()和read()接口读写也都正常。

image-20211104165343872

可是这样我们就满足了吗?不!我们要把flash变成文件系统的一部分直接通过lua原生接口读写。根据API的说明我们可以找到sfud.mount()这个函数,通过他就可以直接挂载到文件系统了,mount很像linux的操作,有没有。

接下来实践一下,在前面的代码基础上增加如下代码:

log.info("sfud.mount",sfud.mount(sfud_device,"/sfud"))
log.info("fsstat", fs.fsstat("/sfud"))
f = io.open ("/sfud/a.txt", "wb")
f:write(string.rep("1234567890", 100))
f:close()
log.info("fsize", fs.fsize("/sfud/a.txt"))
f = io.open ("/sfud/a.txt", "rb")
local data = f:read("*a")
log.info("fs", "data", data)

我们挂载到文件系统后,新建一个文件写入1000字节数据,然后读一下文件大小,再把文件内容读出来。

完整代码在demo的sfud文件夹

烧录看一下效果

image-20211104170149895

完全符合预期,可以直接通过文件系统操作flash,是不是很方便。

SDIO#

本章将会学习使用SDIO接口驱动TF卡,并把TF卡挂载到文件系统进行读写

简介#

上一章我们学会了用sfud这个接口直接挂载flash,可是spi就俩数据线有点慢,而且flash容量有限。这时候就想到更高速率的SDIO接口了,SDIO接口设计之初就是为了读写SD卡,后来被拓展很多高速通信中也借用这个接口。

硬件准备#

W806开发板一块,支持SDIO的tf卡或SD卡模块。一定要类似下图这种支持SDIO的4数据线的。

image-20211104171407771

不要这种只支持SPI的

image-20211104171659482

软件使用#

先看查文档LuatOS 文档研究一下怎么接线

PB_06

GPIO_22 / UART1_TX / SDIO_CLK

PB_07

GPIO_23 / UART1_RX / SDIO_CMD

PB_08

GPIO_24 / SDIO_D0

PB_09

GPIO_25 / SDIO_D1

PB_10

GPIO_26 / SDIO_D2

PB_11

GPIO_27 / SDIO_D3

按照这个方式接好模块,别忘了放卡。

接下找API 文档,和sfud一个套路,初始化硬件,读写测试(最好别这么测,直接读写sd卡插windows上都不识别),我就不测了

直接上挂载的部分,除了前两句,剩下的全是原生接口,和sfud一样,挂载完直接使用,完整代码在demo的sdio文件夹

sdio.init(0)
sdio.sd_mount(0, "/sd")
f = io.open ("/sd/a.txt", "wb")
f:write(string.rep("1234567890", 100))
f:close()
log.info("fsize", fs.fsize("/sd/a.txt"))
f = io.open ("/sd/a.txt", "rb")
local data = f:read("*a")
log.info("fs", "data", data)

烧录看看结果

image-20211104191441354

SD卡使用第一次这么简单,两行代码就搞定。

LCD#

这一章我们将学习如何驱动LCD屏幕,并在屏幕上显示汉字和图形

简介#

通常驱动屏幕是一件很复杂的事情,因为LCD屏幕有很多指令,显示数据还要制作字库。LuatOS将LCD进行了封装,内置了多种常用的屏幕驱动,不在列表的也可以通过Lua脚本配置指令进行驱动。使用虚拟显存的思想,将硬件驱动和图像绘制抽离,使用更简单方便。

硬件准备#

W806开发板一块,SPI的屏幕一个,我用的ST7735

软件使用#

老规矩先看查文档LuatOS 文档研究一下怎么接线,只需前两个引脚必须用这个表的解法,后面的可以随便接,我推荐按我的接

SCL

PB2

18

SDA

PB5

21

RES

PB3

19

DC

PB1

17

CS

PB4

20

BL

PB0

16

接下来老规矩看API思路都一样,先初始化硬件,在搞其他。按照接口的说明,我们要先初始话一个spi对象,然后根据硬件配置RES和DC等引脚,引脚列表前面我写了,所以初始化代码如下

local spi_lcd = spi.deviceSetup(0,20,0,0,8,2000000,spi.MSB,1,1)
log.info("lcd.init",lcd.init("st7735",{port = "device",pin_dc = 17, pin_pwr = 16,pin_rst = 19,direction = 0,w = 128,h = 160,xoffset = 2,yoffset = 1},spi_lcd))

接下来研究怎么显示一点东西,就简单画个框画个圆显示一下汉字吧,API里写的比较清楚了,这里就不多解释了。其中设置字体那个地方的字体是可以在前面讲过的本地编译或者云编译的时候选择,选的不一样这里设置也不一样,需要注意。完整代码在demo的lcd文件。

log.info("lcd.drawLine", lcd.drawLine(0,20,128,20,0x001F))
log.info("lcd.drawRectangle", lcd.drawRectangle(20,40,120,70,0xF800))
log.info("lcd.drawCircle", lcd.drawCircle(64,100,20,0x0CE0))
lcd.setFont(lcd.font_opposansm16_chinese)
lcd.drawStr(40,66,"测试")

烧录查看效果

image-20211104195148800

换个屏试试,只需要改一下初始化参数,ili9341也就驱动起来了

log.info("lcd.init",lcd.init("ili9341",{port = "device",pin_dc = 17, pin_pwr = 16,pin_rst = 19,direction = 0,w = 240,h = 320,xoffset = 0,yoffset = 0},spi_lcd))

看看效果

image-20211104200855780

LuatOS对显示设备的抽象很值得学习

LVGL#

本章将简单介绍如何使用LVGL显示更美观的画面

简介#

上一章学了LCD的库,可以顺利的画点画线显示汉字了,可是想把界面做好看,这些简单的接口还是不能满足需求。于是乎就出现了很多图形库,LuatOS集成了LVGL图形库,可以利用大量控件写出美观的界面。

硬件准备#

W806开发板一块,SPI的屏幕一个,我用的ST7735

软件使用#

接线看LCD那一章不重复,LVGL作为图形库首先还是LCD那套初始化屏幕,然后就是使用LVGL渲染屏幕内容。直接上代码了

log.info("lvgl", lvgl.init())
lvgl.disp_set_bg_color(nil, 0xFFFFFF)
local scr = lvgl.obj_create(nil, nil)
local btn = lvgl.btn_create(scr)
lvgl.obj_align(btn, lvgl.scr_act(), lvgl.ALIGN_CENTER, 0, 0)
local label = lvgl.label_create(btn)
lvgl.label_set_text(label, "LuatOS!")
lvgl.scr_load(scr)

看一下效果,这按键就比用画线做出来的好看多了

image-20211104202107236

更多的LVGL使用教程可以参考API和lvgl官方文档

RTC#

本章将会向大家介绍LuatOS的RTC功能。将会实现使用W806开发板读取RTC的值并在日志中打印。

简介#

RTC (Real-Time Clock)实时时钟可以提供精确的实时时间,它可以用于产生年、月、日、时、分、秒等信息。目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。有些时钟芯片为了在主电源掉电时还可以工作,会外加电池供电,使时间信息一直保持有效

硬件准备#

W806开发板一块

软件使用#

代码展示

log.info("os.date()", os.date())--打印时间
local t = rtc.get()--获取RTC时间
log.info("rtc", json.encode(t))--打印RTC时间
sys.wait(2000)--延迟
rtc.set({year=2021,mon=10,day=31,hour=17,min=8,sec=43})--rtc时间设置
log.info("os.date()", os.date())--打印时间