1 Star 0 Fork 42

zhouyongbing / 820开发板

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 98.87 KB
一键复制 编辑 原始数据 按行查看 历史

前言

为什么会写这个教程

近几年物联网开始从一个概念,慢慢进入我们的生活。技术方案也开始从原来的MCU+AT的方式,转变成OPENCPU的方式。据统计目前市面上OPENCPU的产品已经占到了百分之五十左右,但目前却没有一个比较全面系统的教程去学习如何使用OPENCPU进行开发。本教程致力于让更多人了解,学习物联网,并做出实际的作品,享受到技术创造的魅力。

适合的人群

本教程的目标是让一个没有任何编程基础的人学会物联网开发,只需要懂一些基本的电脑操作即可。如果你具有一定的编程基础,曾经学习或使用过单片机,那么你将很容易学会并爱上这个开发方式。

本教程学习门槛很低,如果你觉得过于简单,很多内容你可以选择跳过。如果你遇到了问题,希望你可以首先阅读一下文档,多数的情况文档已经覆盖,如果遇到文档未覆盖的情况可以在群里寻找帮助,但是如果文档已经覆盖,大家将拒绝回答你的问题。

通过本教程能学到什么

如果你认真的学习了这个教程,你学习到的知识将能够满足现今百分之八十的项目场景。通过学习这个教程你将会学习一门新的编程语言,一种新的开发方式;学会基础的电路知识,设计自己的电路板;了解一个物联网项目需要哪些部分,学会设计自己的项目;学习常用的通信协议,并学会搭建简单的测试服务器。

需要准备哪些东西

一台电脑,一块支持luat的开发板,推荐购买教程配套的820开发板(这里是链接)。教程中的所有示例代码都可以在开发板上运行验证。当然如果你有其他的支持luat的开发板,又不想买新的也没关系,因为luat的兼容性是非常好的,你只需要吧一些硬件相关的配置(例如GPIO)稍作修改就可以使用。

致谢

教程内引用了很多luat社区的资源,在此表示感谢。我们力求将教程内容写好,但由于能力有限,难免有出错的地方,如果大家发现错误请及时指出,在此再次向各位读者表示诚挚的感谢。

感谢名单:

QQ:1090557361对文档错别字进行校对

一、硬件介绍

本章将会从主芯片,开发板,外设等方面对我们的开发平台进行介绍。

1.开发板

开发板核心模块为合宙Air820,采用展锐春藤UIS8910DM作为主控,具有双核500Mhz的A5处理器,64Mb SPI Nor Flash,128Mb PSRAM的丰富片上资源。能够支持TCP/UDP/PPP/FTP/HTTP/NITZ/CMUX/NDIS/NTP/HTTPS/PING/FTPS/FILE/TLS多种网络协议。

image-20210602184846991

开发板资源:

1路usb2.0支持从机模式可用于下载调试和数据传输,连接windows和linux可作为网卡使用;

2路串口,一路可通过跳线配置为485,自带电平转换方便调试;

1路LCD专用SPI接口,最大支持QVGA @ 30fps,并将引脚专门引出,可直接兼容淘宝多款屏幕;

1路Camera接口,最高像素30W像素@15fps,可用于扫码和拍照;

2路adc接口,都已引出并加入分压电路,方便测试;

1路I2C接口,自带电平转换,方便多种外设测试;

1路spi接口,自带电平转换,方便多种外设测试;

二、软件介绍

1、环境搭建

  • 固件开发包 luat的软件包分为底层core和上层的lua脚本 底层软件+上层脚本

  • 下载开发工具 Luat下载调试工具v2

  • USB驱动 Air720UH/Air724UG系列 PC 端 USB 驱动

  • 编辑工具 lua编程软件建议vscode vscode安装教程 注意: vscode为Lua脚本编辑工具,仅仅具有脚本代码编辑功能,没有编译功能; Lua为解释性语言,在Luat开发方式下,开发调试过程如下: 1、使用vscode编写Lua脚本 2、使用Luatools烧录Lua脚本 3、使用Luatools输出脚本运行日志

2、安装USB驱动

下载驱动压缩包,解压后,根据电脑配置安装驱动 img 安装成功后,将Air系列CAT1模块连接到电脑USB口,开机后会在设备管理器中虚拟出3个虚拟串口,如下则USB驱动安装完成。 img

3、烧录底层core固件

由于模块出厂默认自带的AT版本固件,因此需要更换为Luat二次开发固件。如果板子红灯只闪烁,那么需要长按侧面的pwk按键开机,然后再烧录固件。 解压底层包后,里面会有很多后缀名不同的固件。

这里以1.3主线说明

缩写 功能
BL 低功耗蓝牙
TS TTS(文字转语音)
QR 二维码生成
CM 摄像头
LL LVGL(图形库)
FT 浮点运算
VT 高清通话
RT RTMP(实时消息传输 )
SD SD卡

例如:Luat_V3035_RDA8910_RBTTSQRLLSDFT.pac代表1.3主线固件 支持低功耗蓝牙,TTS,二维码生成,LVGL,SD卡,浮点计算。

刷固件

注:正常情况下选好pac结尾的固件之后点击下载即可,如果遇到模块不能正常开机,请按住uboot上电进入uboot模式,然后勾选USB BOOT下载后点击下载。

4、烧录lua脚本

底层烧录好模块还不能实现具体功能,必须将上层的脚本下载进入后才能实现具体功能。强调下,必须下载脚本! 打开脚本后里面的结构:

  • demo:示例的脚本代码,实现完整功能,开发的时候可以在demo的代码中修改

  • lib:库文件,必须要全部下载到模块中 demo

  • 操作如下: 1.在工具上创建一个项目 demo

    demo

    2、设置项目

    demo

    点击开始下载后等待10S后开始下载,下载完成后整个下载过程结束。 这样整个luat的开发环境就搭建完成

三、实战篇

1、工程标准格式介绍

Luat开发约定了一个工程的标准格式

- work_name
	- main.lua
	- app.lua
	- ...
  • Luat会读取文件系统中的main.lua文件,main.lua即程序主入口,这里会执行工程的设置和初始化。

  • 推荐将用户程序单独写一个至多个lua文件,通过require引入到main.lua中执行。

  • main.lua格式可参考demo的写法。

main.lua示例

--必须在这个位置定义PROJECT和VERSION变量
--PROJECT:ascii string类型,可以随便定义,只要不使用,就行
--VERSION:ascii string类型,如果使用Luat物联云平台固件升级的功能,必须按照"X.X.X"定义,X表示1位数字;否则可随便定义
PROJECT = "ADC"
VERSION = "1.0.0"

--加载日志功能模块,并且设置日志输出等级
--如果关闭调用log模块接口输出的日志,等级设置为log.LOG_SILENT即可
require "log"
LOG_LEVEL = log.LOGLEVEL_TRACE
--[[
如果使用UART输出日志,打开这行注释的代码"--log.openTrace(true,1,115200)"即可,根据自己的需求修改此接口的参数
如果要彻底关闭脚本中的输出日志(包括调用log模块接口和Lua标准print接口输出的日志),执行log.openTrace(false,第二个参数跟调用openTrace接口打开日志的第二个参数相同),例如:
1、没有调用过sys.opntrace配置日志输出端口或者最后一次是调用log.openTrace(true,nil,921600)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false)即可
2、最后一次是调用log.openTrace(true,1,115200)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false,1)即可
]]
--log.openTrace(true,1,115200)

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")

--加载控制台调试功能模块(此处代码配置的是uart1,波特率115200)
--此功能模块不是必须的,根据项目需求决定是否加载
--使用时注意:控制台使用的uart不要和其他功能使用的uart冲突
--使用说明参考demo/console下的《console功能使用说明.docx》
--require "console"
--console.setup(1, 115200)

--加载硬件看门狗功能模块
--根据自己的硬件配置决定:1、是否加载此功能模块;2、配置Luat模块复位单片机引脚和互相喂狗引脚
--合宙官方出售的Air201开发板上有硬件看门狗,所以使用官方Air201开发板时,必须加载此功能模块
--[[
require "wdt"
wdt.setup(pio.P0_30, pio.P0_31)
]]

--加载网络指示灯和LTE指示灯功能模块
--根据自己的项目需求和硬件配置决定:1、是否加载此功能模块;2、配置指示灯引脚
--合宙官方出售的Air720U开发板上的网络指示灯引脚为pio.P0_1,LTE指示灯引脚为pio.P0_4
require "netLed"
pmd.ldoset(2,pmd.LDO_VLCD)
netLed.setup(true,pio.P0_1,pio.P0_4)
--网络指示灯功能模块中,默认配置了各种工作状态下指示灯的闪烁规律,参考netLed.lua中ledBlinkTime配置的默认值
--如果默认值满足不了需求,此处调用netLed.updateBlinkTime去配置闪烁时长

--加载错误日志管理功能模块【强烈建议打开此功能】
--如下2行代码,只是简单的演示如何使用errDump功能,详情参考errDump的api
require "errDump"
errDump.request("udp://ota.airm2m.com:9072")

--加载远程升级功能模块【强烈建议打开此功能】
--如下3行代码,只是简单的演示如何使用update功能,详情参考update的api以及demo/update
--PRODUCT_KEY = "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K"
--require "update"
--update.request()

--加载
require "testAdc"

--启动系统框架
sys.init(0, 0)
sys.run()

注:使用usb打印log时,建议在协程开始延时3-5s,防止出现丢日志的情况发生。

2、GPIO操作

(1)简介

gpio是通用型输入输出的简称,一个gpio引脚可以用于输入、输出或其他特殊功能。本文介绍如何用Air820开发板学习GPIO操作。

(2)材料准备

820

GPIO 引脚号 上电状态 I/O 管脚描述 备注
UART1_RTS (GPIO_19) 60 INPUT PULL_DOWN O DTE请求发送数据给模块 V_GLOBAL_1V8 不用则悬空
UART1_CTS (GPIO_18) 59 INPUT PULL_DOWN I 模块清除发送 V_GLOBAL_1V8 不用则悬空
NET_STATUS (GPIO_1) 68 INPUT PULL_DOWN O 网络状态指示 VCC_LCD 注意电压域是VCC_LCD; 不用则悬空

(3)操作

本文以demo/gpio为例做演示。

  1. 认识模块的GPIO管脚:Air820UG模块的gpio与其他MCU类的芯片一样,也可以配置为输入/输出/上拉/下拉/中断等状态,只有在硬件设计手册上注明带GPIO功能的管脚才能作为GPIO使用。

注意: 1、有些特珠GPIO需要打开对应的ldo电压域才能正常工作,如复用为LCD接口对应的GPIO,需要打开VLCD管脚电压,其他复用管脚的GPIO同样原理。

2、GPIO对应输入或者输出的该管脚电压域的电压,管脚输出只能输出在该管脚电压域内的电压,管脚输入只能输入在该管脚电压域内的电压,不能输入高于电压域内的电压,否则会导致元器件模块管脚损坏,可以参考硬件设计中的管脚描述

3、Air820模块或其他Air型号的cat.1模块配置管脚GPIO时,均需要引用pins脚本库下的函数。

2、GPIO命名规范:

GPIO 0到GPIO 31表示为pio.P0_0到pio.P0_31 GPIO 32到GPIO XX表示为pio.P1_0到pio.P1_(XX-32),例如GPIO33 表示为pio.P1_1 GPIO 64到GPIO XX表示为pio.P2_0到pio.P2_(XX-64),例如GPIO65 表示为pio.P2_1

3、代码部分:

GPIO输出演示:

local level = 0
pmd.ldoset(2,pmd.LDO_VLCD) 
-- GPIO1配置为输出,默认输出低电平,可通过setGpio1Fnc(0或者1)设置输出电平
local setGpio1Fnc = pins.setup(pio.P0_1,0) -- 默认输出值为低(0)
sys.timerLoopStart(function()
    level = level==0 and 1 or 0
    setGpio1Fnc(level)
    log.info("GPIO1的状态:",level)
end,1000)

这里使用定时器的方式,一秒循环一次运行,将GPIO1依次写0和1,实现闪烁效果。

GPIO输入演示:

-- GPIO19配置为输入,可通过getGpio19Fnc()获取输入电平
local getGpio19Fnc = pins.setup(pio.P0_19)
sys.timerLoopStart(function()
    log.info("GPIO19的状态",getGpio19Fnc())
end,1000)

这里使用定时器的方式,一秒循环一次运行,每秒读取一次GPIO19的io电平并通过log打印出来。

GPIO中断演示:

-- GPIO18配置为中断,可通过getGpio18Fnc()获取输入电平,产生中断时,自动执行gpio18IntFnc函数
-- 产生中断时自动调用intFnc(msg)函数:上升沿中断时:msg为cpu.INT_GPIO_POSEDGE;下降沿中断时:msg为cpu.INT_GPIO_NEGEDGE
function gpio18IntFnc(msg)
    log.info("GPIO18中断回调",msg,getGpio18Fnc())
    if msg==cpu.INT_GPIO_POSEDGE then
        log.info("GPIO18中断回调", "上升沿中断")
    else
        log.info("GPIO18中断回调", "下降沿中断")
    end
end
getGpio18Fnc = pins.setup(pio.P0_18,gpio18IntFnc)

将GPIO设置成中断模式时,需要在pins.setup()函数的第二个值设置中断回调函数,回调函数会输入一个值,当值为cpu.INT_GPIO_POSEDGE时,则为上升沿中断,当值为cpu.INT_GPIO_NEGEDGE时,则为下降沿中断。

效果展示:

gpio-demo

gpio-demo

3、ADC操作

(1)简介

ADC通常指模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。本文介绍如何用Air820开发板,和PC端搭建一个ADC电压采集的功能演示。

(2)材料准备

820

Air820的ADC在中间六针接口的3、4脚。

3脚对应ADC3,4脚对应ADC2 。

(3)操作

本文以demo/adc为例做演示

1、认识模块的ADC引脚

管脚名 管脚号 I/O 管脚描述 备注
ADC2 20 I 模数转换器, 输入范围0~VBAT ADC分辨率12bits, 不用则悬空
ADC3 21 I 模数转换器, 输入范围0~VBAT ADC分辨率12bits, 不用则悬空

2、代码部分

ADC2读取函数:

--- ADC读取测试
-- @return 无
-- @usage read2()
local function read2()
    --ADC2接口用来读取电压
    local ADC_ID = 2
    -- 读取adc
    -- adcval为number类型,表示adc的原始值,无效值为0xFFFF
    -- voltval为number类型,表示转换后的电压值,单位为毫伏,无效值为0xFFFF
    local adcval,voltval = adc.read(ADC_ID)
    log.info("testAdc2.read",adcval,voltval)
end

ADC3读取函数:

--- ADC读取测试
-- @return 无
-- @usage read3()
local function read3()
    --ADC3接口用来读取电压
    local ADC_ID = 3
    -- 读取adc
    -- adcval为number类型,表示adc的原始值,无效值为0xFFFF
    -- voltval为number类型,表示转换后的电压值,单位为毫伏,无效值为0xFFFF
    local adcval,voltval = adc.read(ADC_ID)
    log.info("testAdc3.read",adcval,voltval)
end

Vbatt电池读取函数:

require"misc"
--- Vbat读取测试
-- @retrun 无
-- @usage vbatt()
local function vbatt()
    log.info("vbatt.read",misc.getVbatt())
end

任务创建:

-- 开启对应的adc通道
adc.open(2)
adc.open(3)

-- 定时每1秒读取adc值
sys.timerLoopStart(read2,1000)
sys.timerLoopStart(read3,1000)
sys.timerLoopStart(vbatt,1000)

总结:Air820模块的ADC使用,只需要通过简单的调用adc.read()函数读取,以及通过adc.open()打开就可以使用。

效果展示:

adc-demo

4、IIC操作

(1)简介

Air820模块提供2个I2C接口,速率支持FAST(400KHz)、SLOW(100KHz)、3500KHz。外设地址支持0x00-0x7f。 iic接口是物联网中最常用的接口,本文介绍如何用Air820开发板搭建一个iic功能演示。

(2)材料准备

820

AIr820板载了两个IIC传感器,分别是U1:KXTJ3-1057(三轴加速度传感器),U3:SHT30(温湿度传感器)

kxtj3

sht30

(3)操作

本文以demo/i2c为例做演示

1、首先了解一下i2c的api接口 完整API文档

iic接口 描述
i2c.setup() 打开iic
i2c.send() 向从设备写数据
i2c.recv() 向从设备读取数据
i2c.write() 往指定的寄存器地址 reg 传输数据
i2c.read() 读取指定寄存器地址 reg 的数据内容

2、代码部分

下面以DS3231时钟芯片作为示例讲解,同时提供了SHT30和KXTJ3-1057的相关驱动。

首先声明出DS3231所需要的变量,包括地址和iic id

local i2cid = 2 --i2cid

local DS3231_ADDRESS            =   0x68 -- address pin low (GND), default for InvenSense evaluation board
local i2cslaveaddr              =   DS3231_ADDRESS --slave address

---器件通讯地址
local DS3231_CHIP_ID_ADDR       =   0xD0
local DS3231_ID                 =   0x58

---DS3231所用地址

local REG_SEC				    =   0x00
local REG_MIN				    =   0x01
local REG_HOUR			        =   0x02
local REG_DAY				    =   0x03
local REG_WEEK			        =   0x04
local REG_MON				    =   0x05
local REG_YEAR			        =   0x06
local REG_ALM1_SEC  		    =   0x07
local REG_ALM1_MIN 	  	        =   0x08
local REG_ALM1_HOUR     	    =   0x09
local REG_ALM1_DAY_DATE 	    =   0x0A
local REG_ALM2_MIN  		    =   0x0B
local REG_ALM2_HOUR     	    =   0x0C
local REG_ALM2_DAY_DATE 	    =   0x0D
local REG_CONTROL               =   0x0E
local REG_STATUS                =   0x0F
local REG_AGING_OFFSET          =   0x10
local REG_TEMP_MSB 		        =   0x11
local REG_TEMP_LSB 		        =   0x12

创建iic的自定义读写函数,方便使用

local function i2c_send(data)
    i2c.send(i2cid, i2cslaveaddr, data)
end

local function i2c_recv(data,num)
    i2c.send(i2cid, i2cslaveaddr, data)
    local revData = i2c.recv(i2cid, i2cslaveaddr, num)
    return revData
end

因为DS3231直接读出来的数据不能直接使用,需要进行转码,所以要声明转码函数

local function bcd_to_hex(data)
    local hex = bit.rshift(data,4)*10+bit.band(data,0x0f)
    return hex;
end

local function hex_to_bcd(data)
    local hex = bit.lshift(data/10,4)+data%10
    return hex;
end

创建DS3231初始化函数

--器件初始化
local function DS3231_init()
    i2c_send({REG_CONTROL, 0x04})--close clock out
    log.info("i2c init_ok")
end

创建DS3231获取温度函数

local function ds3231_get_temperature()
    local temp
    local T = i2c_recv(REG_TEMP_MSB,2)
    if bit.band(T:byte(1),0x80) then
        --negative temperature
        temp = T:byte(1)
        temp = temp - (bit.rshift(T:byte(2),6)*0.25)--0.25C resolution
    else
        --positive temperature
        temp =  T:byte(1)
        temp = temp + (bit.band(bit.rshift(T:byte(2),6),0x03)*0.25)
    end
	return temp;
end

创建DS3231获取时间函数

local function ds3231_read_time()
    -- read time
    local time_data = {}
    local data = i2c_recv(REG_SEC,7)
    time_data.tm_year  = bcd_to_hex(data:byte(7)) + 2000
    time_data.tm_mon   = bcd_to_hex(bit.band(data:byte(6),0x7f)) - 1
    time_data.tm_mday  = bcd_to_hex(data:byte(5))
    time_data.tm_hour  = bcd_to_hex(data:byte(3))
    time_data.tm_min   = bcd_to_hex(data:byte(2))
    time_data.tm_sec   = bcd_to_hex(data:byte(1))
	return time_data
end

创建DS3231设置时间函数

local function ds3231_set_time(time)
    -- set time
    local data7 = hex_to_bcd(time.tm_year + 2000)
    local data6 = hex_to_bcd(time.tm_mon + 1)
    local data5 = hex_to_bcd(time.tm_mday)
    local data4 = hex_to_bcd(time.tm_wday+1)
    local data3 = hex_to_bcd(time.tm_hour)
    local data2 = hex_to_bcd(time.tm_min)
    local data1 = hex_to_bcd(time.tm_sec)
    i2c_send({REG_SEC, data1,data2,data3,data4,data5,data6,data7})
end

至此,DS3231的相关函数已经写好了,接下来就要去调用函数完成运行了。

local function DS3231()
    sys.wait(4000)
    if i2c.setup(i2cid,i2c.SLOW) ~= i2c.SLOW then
        log.error("I2c.init","fail")
        return
    end
    DS3231_init()
    while true do
        log.info("ds3231_get_temperature", ds3231_get_temperature())
        local time = ds3231_read_time()	          log.info("ds3231_read_time",time.tm_year,time.tm_mon,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec)
        local set_time = {tm_year=2021,tm_mon=3,tm_mday=0,tm_wday=0,tm_hour=0,tm_min=0,tm_sec=0}
        ds3231_set_time(set_time) log.info("ds3231_read_time",time.tm_year,time.tm_mon,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec)
        sys.wait(1000)
    end
end

函数首先设置使用的iic端口,然后进行DS3231的初始化,随后进入循环读取DS3231内的时间与温度函数,通过调用ds3231_set_time()还可以设置芯片内存储的时间。

最后将函数放入协程中运行。

sys.taskInit(DS3231)

demo中同时提供了SHT30 和KXTJ3-1057的相关驱动,可以自行操作理解,相关问题可反馈到luat社区

5、SPI操作

(1)简介

SPI接口是嵌入式物联网中最常用的通信接口,本文介绍如何用Air820开发板对SPI接口进行功能演示。

(2)材料准备

820

spi

Air820开发板的SPI引出到排针,用户可使用杜邦线接外围元件,电平已转换至3.3v。

(3)操作

本文以demo/spi为例做演示

1、API简介,完整API文档

SPI接口 描述
spi.setup() spi初始化
spi.send() spi写数据
spi.recv() spi读数据
spi.send_recv() spi读写数据
spi.close() 关闭spi

2、代码

sys.taskInit(function ()
    local result = spi.setup(spi.SPI_1,0,0,8,800000,1)--初始化spi,
    log.info("spi1",spi.SPI_1)
    log.info("testSpiFlash.init",result)
    local s = string.fromHex("9f000000000000")
    sys.wait(5000)
    while true do
        log.info("testSpiFlash.readFlashID",spi.send_recv(spi.SPI_1,s):toHex())--收发读写
        --下面方法和上面的等价
        log.info("testSpiFlash.sendCommand",spi.send(spi.SPI_1,string.char(0x9f)))--发数据
        log.info("testSpiFlash.readFlashID",spi.recv(spi.SPI_1,6):toHex())--收数据
        sys.wait(1000)
    end
    spi.close(spi.SPI_1)
end)

本示例通过读取spiflash的id做演示,首先初始化SPI,然后进入循环读取flash的id,这里同时演示了spi的读写。

6、UART

(1)简介

uart是一种异步收发传输器,也是不同模块器件,上位机与下位机之间进行通信的重要桥梁。本文介绍如何使用Air820开发板,进行串口收发、解析数据的功能演示。

(2)材料准备

820

Air820引出三个串口,UART1可用于log输出,at控制,UART2可作为通用串口。UART3在内部连接GPS模块。

Air820开发板可通过跳线帽将UART1和485连接,实现485通讯。

485

(3)操作

1、API简介 完整API文档

UART接口 描述
uart.on () 注册串口事件的处理函数
uart.setup() uart通讯
uart.write() 向串口写字符串或者整型数据
uart.getchar() 从串口读取单字符
uart.read() 从串口读取字符串
uart.set_rs485_oe() 485转向控制
uart.close() 关闭 uart 接口

2、代码

声明一个读取回调函数

local function taskRead()
    local cacheData = ""
    while true do
        local s = uart.read(UART_ID,"*l")
        if s == "" then            
            if not sys.waitUntil("UART_RECEIVE",100) then
                --uart接收数据,如果100毫秒没有收到数据,则打印出来所有已收到的数据,清空数据缓冲区,等待下次数据接收
                --注意:
                --串口帧没有定义结构,仅靠软件延时,无法保证帧的完整性,如果对帧接收的完整性有严格要求,必须自定义帧结构(参考testUart.lua)
                --因为在整个GSM模块软件系统中,软件定时器的精确性无法保证,例如本demo配置的是100毫秒,在系统繁忙时,实际延时可能远远超过100毫秒,达到200毫秒、300毫秒、400毫秒等
                --设置的延时时间越短,误差越大
                if cacheData:len()>0 then
                    log.info("testUartTask.taskRead","100ms no data, received length",cacheData:len())
                    --数据太多,如果全部打印,可能会引起内存不足的问题,所以此处仅打印前1024字节
                    log.info("testUartTask.taskRead","received data",cacheData:sub(1,1024))
                    --打印收到的hex
                    log.info("testUartTask.taskRead","received data",cacheData:sub(1,1024):toHex())
                    write(cacheData)
                    cacheData = ""
                end
            end
        else
            cacheData = cacheData..s            
        end
    end
end

function write(s)
    log.info("testUartTask.write",s)
    uart.write(UART_ID,s)
end

local function writeOk()
    log.info("testUartTask.writeOk")
end

注册串口

--注册串口的数据发送通知函数
uart.on(UART_ID,"sent",writeOk)
uart.on(UART_ID,"receive",function() sys.publish("UART_RECEIVE") end)
--配置并且打开串口
uart.setup(UART_ID,115200,8,uart.PAR_NONE,uart.STOP_1)
--需要485通讯时打开下面的语句
--uart.set_rs485_oe(UART_ID, pio.P0_19)
--如果需要打开“串口发送数据完成后,通过异步消息通知”的功能,则使用下面的这行setup,注释掉上面的一行setup
--uart.setup(UART_ID,115200,8,uart.PAR_NONE,uart.STOP_1,nil,1)

--启动串口数据接收任务
sys.taskInit(taskRead)

解释:当串口接收到数据时,会触发receive同时发送系统消息"UART_RECEIVE",这时协程中接收到"UART_RECEIVE"系统消息,开始通过uart.read读取串口中的数据,并通过log显示。

uartdemo

7、NVM

(1)简介

nvm:非易失性存储器(英语:non-volatile memory,缩写为NVM)是指当电流关掉后,所存储的数据不会消失的电脑存储器,利用文件系统实现的一种非易失性参数存储管理模块。 典型的应用场景为:小数据量的简单键值对参数。不适合大容量数据的存储管理,如果数据量超过10K,建议直接使用io接口操作文件来管理

(2)材料准备

820

(3)操作

1、API简介 完整API文档

NVM接口 描述
nvm.init() 初始化参数存储模块
nvm.set() 设置某个参数的值
nvm.sett() 设置某个table类型参数的某一个索引的值
nvm.flush() 所有参数立即写入文件系统
nvm.get() 读取某个参数的值
nvm.gett() 读取某个table类型参数的某一个索引的值
nvm.restore() 参数恢复出厂设置
nvm.remove() 请求删除参数文件.

注:nvm.remove()一般用在远程升级时,需要用新的config.lua覆盖原来的参数文件的场景,在此场景下,远程升级包下载成功后,在确定要重启前调用此接口,下次开机执行nvm.init(“config.lua”)时,会用新的config.lua文件自动覆盖参数文件;以后再开机就不会自动覆盖了,也就是说"nvm.remove()->重启->nvm.init(“config.lua”)"是一个仅执行一次的完整操作

2、代码

config.lua:默认配置文件

module(...)

name = "zhangsan"
age = "18"
sex = "male"
hobby = {"reading","swimming"}
something = {a=1,b=2}

testNvm.lua:程序代码

require"config"
require"nvm"

nvm.init("config.lua",false)   -- burnSave 本地烧录是否保留已有参数,true为保留,false或者nil为清除

local function testnvm()
    sys.wait(5000)	-- 延时5s等待USB虚拟串口准备好打印
    -- 读取nvm中的初始化数据
    log.info("nvm.读取默认值", nvm.get("name"))
    log.info("nvm.读取默认值", nvm.get("age"))
    log.info("nvm.读取默认值", nvm.get("sex"))
    local hobby = nvm.get("hobby")
    log.info("nvm.读取默认值", hobby[1],hobby[2])
    local some = nvm.get("something")
    log.info("nvm.读取默认值", some["a"])
    sys.wait(1000)

    log.info("nvm.修改", nvm.sett("hobby",1,"read"))
    log.info("nvm.修改", nvm.sett("something","a",235))
    local hobby2 = nvm.get("hobby")
    local so = nvm.get("something")
    log.info("nvm.更新数据", hobby2[1],so["a"])
    nvm.restore()   -- 恢复出厂
end

sys.taskInit(testnvm)

演示:

nvm-demo

8、JSON

(1)简介

​ 简单展示JSON的编译与解析功能。先是JSON编译,再将编译的结果解析和自己添加JSON字符串解析。

(2)材料准备

820

(3)操作

本文以demo/json为例

1、API简介

JSON接口 描述
json.encode() json编译
json.decode() json解析

2、代码

-----------------------encode测试------------------------
local torigin =
{
    KEY1 = "VALUE1",
    KEY2 = "VALUE2",
    KEY3 = "VALUE3",
    KEY4 = "VALUE4",
    KEY5 = {KEY5_1="VALU5_1",KEY5_2="VALU5_2"},
    KEY6 = {1,2,3},
}
local function jsonEncode()
    local jsondata = json.encode(torigin)
    log.info("testJson.encode",jsondata)
end
sys.timerLoopStart(jsonEncode, 1000)
-----------------------encode测试------------------------
-----------------------decode测试------------------------
--{"KEY3":"VALUE3","KEY4":"VALUE4","KEY2":"VALUE2","KEY1":"VALUE1","KEY5":{"KEY5_2":"VALU5_2","KEY5_1":"VALU5_1"}},"KEY6":[1,2,3]}
local function jsonDecode()
    local origin = "{\"KEY3\":\"VALUE3\",\"KEY4\":\"VALUE4\",\"KEY2\":\"VALUE2\",\"KEY1\":\"VALUE1\",\"KEY5\":{\"KEY5_2\":\"VALU5_2\",\"KEY5_1\":\"VALU5_1\"},\"KEY6\":[1,2,3]}"
    local tjsondata,result,errinfo = json.decode(origin)
    if result and type(tjsondata)=="table" then
        log.info("testJson.decode KEY1",tjsondata["KEY1"])
        log.info("testJson.decode KEY2",tjsondata["KEY2"])
        log.info("testJson.decode KEY3",tjsondata["KEY3"])
        log.info("testJson.decode KEY4",tjsondata["KEY4"])
        log.info("testJson.decode KEY5",tjsondata["KEY5"]["KEY5_1"],tjsondata["KEY5"]["KEY5_2"])
        log.info("testJson.decode KEY6",tjsondata["KEY6"][1],tjsondata["KEY6"][2],tjsondata["KEY6"][3])
    else
        log.info("testJson.decode error",errinfo)
    end
end
sys.timerLoopStart(jsonDecode,1000)

-----------------------decode测试------------------------

效果展示

820

9、USBData(USB数据通信)

(1)简介

本文介绍如何用Air820开发板,PC端搭建一个的基于Air820模块虚拟AT串口演示数据透传的功能。

(2)材料准备

820

(3)操作

Air820开发板链接到电脑时,会虚拟出串口

com

AT口默认可发AT指令,同样可以使用lua编程,令虚拟AT口收发用户数据。

本文以demo/usbdata为例

初始化串口

uart.setup(uart.USB, 0, 0, uart.PAR_NONE, uart.STOP_1)

这里使用uart.USB代指虚拟的AT口

声明回调函数

--[[
函数名:usbreader
功能  :向USB AT 口发送数据
参数  :无
返回值:无
]]
local function usbwrite(s)
    log.info("usb send",s)
    uart.write(uart.USB, s) 
end

--[[
函数名:usbreader
功能  :从USB AT 口接收数据
参数  :无
返回值:无
]]
local function usbreader()
    local s
    
    --循环读取收到的数据
    while true do
        --每次读取一行
        s = uart.read(uart.USB, "*l", 0)
        if string.len(s) ~= 0 then
                log.info("usb rcv",s);
                usbwrite(s)                
        else
            break
        end
    end
end

串口注册

uart.on(uart.USB, "receive", usbreader)

效果展示

usbdata

虚拟串口会把接收到的数据回显。

10、SoftDog

(1)简介

软件看门狗是一种定时器,当设置时间内没有执行喂狗命令时,模块就会重启。作用是当程序跑飞、程序错误等情况时能够使模块自动重启,防止模块出错,本文介绍如何用Air820开发板,进行软件看门狗功能演示。

(2)材料准备

820

(3)操作

1、API简介

RTOS库 完整API文档

2、代码

module(...,package.seeall)

--[[
函数名:eatSoftDog
功能  :喂狗
参数  :无
返回值:无
]]
function eatSoftDog()
    print("eatSoftDog test")
    rtos.eatSoftDog()
end

--[[
函数名:closeSoftDog
功能  :关闭软狗
参数  :无
返回值:无
]]
function closeSoftDog()
    print("closeSoftDog test")
    sys.timerStop(eatSoftDog)
    rtos.closeSoftDog()
end

--打开并设置软狗超时时间单位MS,超过设置时间没去喂狗,重启模块
rtos.openSoftDog(10*1000)

--定时喂狗
sys.timerLoopStart(eatSoftDog,5*1000)


--打印版本号
sys.timerLoopStart(log.info,2000,rtos.get_version(),_G.VERSION)

使用rtos.openSoftDog(10*1000)设置软狗,使用rtos.eatSoftDog()喂狗,使用tos.closeSoftDog()关闭软狗,超时未喂狗自动重启。关闭定时喂狗即可查看超时效果。

效果展示

正常:

softdog1

超时未喂狗:

softdog2

11、Audio

(1)简介

模块支持播放音频文件和TTS(文字转语音)功能,带TTS的LUAT版本支持TTS功能。音频文件目前支持mp3和amr。

(2)材料准备

820

外设:喇叭x1、开发板已板载mic

audio

(3)操作

本文以demo/audio为例做演示。

1、API简介 完整API文档

audio接口 描述
audio.play(priority, type, path, vol, cbFnc, dup, dupInterval) 播放音频
audio.stop(cbFnc) 停止音频播放
audio.setVolume(vol) 设置喇叭音量等级
audio.setCallVolume(vol) 设置通话音量等级
audio.setMicVolume(vol) 设置麦克音量等级
audio.getVolume() 获取喇叭音量等级
audio.getCallVolume() 获取通话音量等级
audio.getMicVolume() 获取麦克音量等级
audio.setStrategy(strategy) 设置优先级相同时的播放策略
audio.setTTSSpeed(speed) 设置TTS朗读速度
audio.setChannel(output, input) 设置音频输出、输入通道、设置后实时生效
audio.setMicGain(mode,level) 设置mic增益等级

2、代码

示例:

    --单次播放来电铃声,默认音量等级
    --audio.play(CALL,"FILE","/lua/call.mp3")
    --单次播放来电铃声,音量等级7
    --audio.play(CALL,"FILE","/lua/call.mp3",audiocore.VOL7)
    --单次播放来电铃声,音量等级7,播放结束或者出错调用testcb回调函数
    --audio.play(CALL,"FILE","/lua/call.mp3",audiocore.VOL7,testCb)
    --循环播放来电铃声,音量等级7,没有循环间隔(一次播放结束后,立即播放下一次)
    audio.play(CALL,"FILE","/lua/call.mp3",audiocore.VOL7,nil,true)
    --循环播放来电铃声,音量等级7,循环间隔为2000毫秒
    --audio.play(CALL,"FILE","/lua/call.mp3",audiocore.VOL7,nil,true,2000)

注:通过luatools上传的文件统一放在/lua下

--播放tts测试接口,每次打开一行代码进行测试
--audio.play接口要求TTS数据为UTF8编码,因为本文件编辑时采用的是UTF8编码,所以可以直接使用ttsStr,不用做编码转换
--如果用户自己编辑脚本时,采用的不是UTF8编码,需要调用common.XXX2utf8接口进行转换
local ttsStr = "上海合宙通信科技有限公司欢迎您"
local function testPlayTts()
    --单次播放,默认音量等级
    --audio.play(TTS,"TTS",ttsStr)
    --单次播放,音量等级7
    --audio.play(TTS,"TTS",ttsStr,7)
    --单次播放,音量等级7,播放结束或者出错调用testcb回调函数
    --audio.play(TTS,"TTS",ttsStr,7,testCb)
    --循环播放,音量等级7,没有循环间隔(一次播放结束后,立即播放下一次)
    --audio.play(TTS,"TTS",ttsStr,7,nil,true)
    --循环播放,音量等级7,循环间隔为2000毫秒
    audio.play(TTS,"TTS",ttsStr,7,nil,true,2000)
end

播放多文件:

sys.taskInit(function()
    while true do        
        sys.wait(2000)  
        --播放多arm文件方式
        local multiFile = {"/lua/alipay.amr","/lua/10.amr","/lua/2.amr","/lua/yuan.amr"}
        audio.play(1,"FILE",multiFile,7,function() sys.publish("armMultiTest") end)
        sys.waitUntil("armMultiTest")
        sys.wait(2000)
        --播放多pcm文件方式
        multiFile = {"/lua/alipay.pcm","/lua/10.pcm","/lua/8.pcm","/lua/yuan.pcm"}
        audio.play(1,"FILE",multiFile,7,function() sys.publish("armMultiTest") end)
        sys.waitUntil("armMultiTest")        
    end
end)
----------------------------------------------------------------------------------------
sys.taskInit(function()
    while true do        
        sys.wait(2000)  
        --播放多mp3文件方式
        local multiFile = {"/lua/0.mp3","/lua/1.mp3","/lua/2.mp3","/lua/3.mp3","/lua/4.mp3"}
        audio.play(1,"FILE",multiFile,2,function() sys.publish("mp3MultiTest") end)
        sys.waitUntil("mp3MultiTest") 
    end
end)

说明:刷入demo后,如果底层固件支持TTS,则会播放TTS语音"上海合宙通信科技有限公司欢迎您",否则将播放来电语音。

12、LCD

(1)简介

LCD全称是Liquid Crystal Display,是指液晶显示屏,属于平面显示器的一种。 合宙Cat.1模块LCD接口主要用来UI设计,包括LCD初始化,图片显示操作,文字显示操作等。

(2)材料准备

820

lcd

LCD官方支持型号有:ST7735,ST7789,ILI9341,GC9106,GC9A01,SSD1306

(3)操作

本文以demo/ui为例

1、API 完整API文档

disp API 描述
disp.init() 初始化LCD驱动配置
disp.clear() 清空显示
dis.update() 刷新数据到LCD
disp.puttext() 显示文字
disp.setfontheight() 缩放文字大小
disp.putimage() 显示图片
disp.drawrect() 显示矩形框
disp.setcolor() 设置前景色
disp.setbkcolor() 设置背景色
disp.putqrcode() 显示二维码
disp.loadfont() 加载字体文件
disp.setfont() 设置当前显示字体
disp.sleep() 控制LCD是否进入休眠
disp.write() 向LCD控制器写入命令
disp.getlcdinfo() 获取屏幕分辨率和位深度
disp.close() 关闭显示模块

2、代码

color_lcd_spi_st7735.lua为st7735驱动文件,用户正常情况下无需更改。

lcd.lua为配置导入

logo.lua为开机显示图片的示例

require"uiWin"
require"prompt"
require"idle"


--清空LCD显示缓冲区
disp.clear()
if lcd.WIDTH==128 and lcd.HEIGHT==128 then
--显示logo图片
disp.putimage("/lua/logo_"..(lcd.BPP==1 and "mono.bmp" or "color.png"),lcd.BPP==1 and 41 or 0,lcd.BPP==1 and 18 or 0)
elseif lcd.WIDTH==240 and lcd.HEIGHT==320 then
disp.puttext(common.utf8ToGb2312("欢迎使用Luat"),lcd.getxpos(common.utf8ToGb2312("欢迎使用Luat")),10)
--显示logo图片
disp.putimage("/lua/logo_color_240X320.png",0,80)
else
--从坐标16,0位置开始显示"欢迎使用Luat"
disp.puttext(common.utf8ToGb2312("欢迎使用Luat"),lcd.getxpos(common.utf8ToGb2312("欢迎使用Luat")),0)
--显示logo图片
disp.putimage("/lua/logo_"..(lcd.BPP==1 and "mono.bmp" or "color.png"),lcd.BPP==1 and 41 or 1,lcd.BPP==1 and 18 or 33)
end
--刷新LCD显示缓冲区到LCD屏幕上
disp.update()

--5秒后,打开提示框窗口,提示"3秒后进入待机界面"
--提示框窗口关闭后,自动进入待机界面
sys.timerStart(prompt.open,5000,common.utf8ToGb2312("3秒后"),common.utf8ToGb2312("进入待机界面"),nil,idle.open)

注:通过luatools上传的文件统一放在/lua下

随后进入到idle.lua执行

require"misc"
require"ntp"
require"common"

--appid:窗口id
local appid

--[[
函数名:refresh
功能  :窗口刷新处理
参数  :无
返回值:无
]]
local function refresh()
    --清空LCD显示缓冲区
    disp.clear()
    local oldColor = lcd.setcolor(0xF100)
    disp.puttext(common.utf8ToGb2312("待机界面"),lcd.getxpos(common.utf8ToGb2312("待机界面")),0)
    local tm = misc.getClock()
    local datestr = string.format("%04d",tm.year).."-"..string.format("%02d",tm.month).."-"..string.format("%02d",tm.day)
    local timestr = string.format("%02d",tm.hour)..":"..string.format("%02d",tm.min)..":"..string.format("%02d",tm.sec)
    --显示日期
    lcd.setcolor(0x07E0)
    disp.puttext(datestr,lcd.getxpos(datestr),24)
    --显示时间
    lcd.setcolor(0x001F)
    disp.puttext(timestr,lcd.getxpos(timestr),44)
    
    --刷新LCD显示缓冲区到LCD屏幕上
    disp.update()
    lcd.setcolor(oldColor)
end

--窗口类型的消息处理函数表
local winapp =
{
    onUpdate = refresh,
}

--[[
函数名:clkind
功能  :时间更新处理
参数  :无
返回值:无
]]
local function clkind()
    if uiWin.isActive(appid) then
        refresh()
    end    
end

--[[
函数名:open
功能  :打开待机界面窗口
参数  :无
返回值:无
]]
function open()
    appid = uiWin.add(winapp)
end

ntp.timeSync()
sys.timerLoopStart(clkind,1000)
sys.subscribe("TIME_UPDATE_IND",clkind)

这里使用了uiwin的库(API文档),显示实时时间。

效果展示

lcd-demo

13、QRCode

(1)简介

生成二维码并展示在显示屏上

(2)材料准备

820

lcd

(3)操作

本文以demo/qrcode为例

1、API API完整文档

qrcode接口 描述
qrencode.encode() 二维码编码

2、代码

--LCD分辨率的宽度和高度(单位是像素)
local WIDTH, HEIGHT = 132,162

--- qrencode.encode(string) 创建二维码信息
-- @param string 二维码字符串
-- @return width 生成的二维码信息宽度
-- @return data 生成的二维码数据
-- @usage local width, data = qrencode.encode("http://www.openluat.com")
local width, data = qrencode.encode('http://www.openluat.com')

--- disp.putqrcode(data, width, display_width, x, y) 显示二维码
-- @param data 从qrencode.encode返回的二维码数据
-- @param width 二维码数据的实际宽度
-- @param display_width 二维码实际显示宽度,显示宽度开根号需要是整数
-- @param x 二维码显示起始坐标x
-- @param y 二维码显示起始坐标y

--- 二维码显示函数
local function appQRCode()
	disp.clear()
    local displayWidth = 100
    disp.putqrcode(data, width, displayWidth, (WIDTH-displayWidth)/2, (HEIGHT-displayWidth)/2)
	disp.update()	
end

appQRCode()

效果展示

qrcode

14、Camera

(1)简介

Air724UG支持一路摄像头接口。可以用于扫码,拍照应用。

特点如下:

  • 只支持SPI接口
  • 最高像素30W像素@15fps
  • 支持数据格式YUV422, Y420, RAW8, RAW10
  • 集成GC0310驱动

本文介绍如何用Air820开发板搭建一个camera功能演示。

(2)材料准备

820

lcd

sxt

(3)操作

1、API

使用了disp库,cameraui

Camera API 描述
disp.cameraopen() 初始化摄像头
disp.camerapreview() 打开摄像头预览
disp.camerapreviewzoom() 预览缩放
disp.camerapreviewrotation() 预览旋转
disp.camerapreviewclose() 关闭预览
disp.cameracapture() 拍照片
disp.camerasavephoto() 保存拍摄的照片到文件
disp.camerawritereg() 设置camera sensor寄存器
disp.cameraclose() 关闭摄像头
disp.cameraopen_ext LUA外部配置camera功能

2、代码

本文以demo/camera为例

这里用到了 gc0310_sdr这个摄像头驱动,演示扫描二维码效果。

扫码结果回调函数

-- 扫码结果回调函数
-- @bool result,true或者false,true表示扫码成功,false表示超时失败
-- @string[opt=nil] codeType,result为true时,表示扫码类型;result为false时,为nil;支持QR-Code和CODE-128两种类型
-- @string[opt=nil] codeStr,result为true时,表示扫码结果的字符串;result为false时,为nil
local function scanCodeCb(result,codeType,codeStr)
    --关闭摄像头预览
    disp.camerapreviewclose()
    --关闭摄像头
    disp.cameraclose()
    --允许系统休眠
    pm.sleep("testScanCode")
    --500毫秒后处理扫描结果
    sys.timerStart(function()
        --如果有LCD,显示扫描结果
        if WIDTH~=0 and HEIGHT~=0 then
            disp.clear()
            if result then
                disp.puttext(common.utf8ToGb2312("扫描成功"),0,5)
                disp.puttext(common.utf8ToGb2312("类型: ")..codeType,0,35)
                log.info("scanCodeCb",codeStr:toHex())
                disp.puttext(common.utf8ToGb2312("结果: ")..codeStr,0,65)
            else
                disp.puttext(common.utf8ToGb2312("扫描失败"),0,5)
            end
            disp.update()
        end
    end,500)

    sys.timerStart(scan,1000)
end

显示结果代码

-- 拍照并显示
function takePhotoAndDisplay()
    --唤醒系统
    pm.wake("testTakePhoto")
    --打开摄像头
    disp.cameraopen(1,0,0,1)
    --disp.cameraopen(1,0,0,0)  --因目前core中还有问题没解决,所以不能关闭隔行隔列
    --打开摄像头预览
    --如果有LCD,使用LCD的宽和高
    --如果无LCD,宽度设置为240像素,高度设置为320像素,240*320是Air268F支持的最大分辨率
    disp.camerapreview(0,0,0,0,WIDTH or DEFAULT_WIDTH,HEIGHT or DEFAULT_HEIGHT)
    --设置照片的宽和高像素并且开始拍照
    --此处设置的宽和高和预览时的保持一致
    --此处的第三个参数表示拍摄质量,默认50,100最好
    disp.cameracapture(WIDTH or DEFAULT_WIDTH,HEIGHT or DEFAULT_HEIGHT)
    --设置照片保存路径
    disp.camerasavephoto("/testCamera.jpg")
    log.info("testCamera.takePhotoAndDisplay fileSize",io.fileSize("/testCamera.jpg"))
    --关闭摄像头预览
    disp.camerapreviewclose()
    --关闭摄像头
    disp.cameraclose()
    --允许系统休眠
    pm.sleep("testTakePhoto")

    --testUartSentFile.sendFile()
    --显示拍照图片
    if WIDTH~=0 and HEIGHT~=0 then
        disp.clear()
        disp.putimage("/testCamera.jpg",0,0)
        disp.puttext(common.utf8ToGb2312("照片尺寸: "..io.fileSize("/testCamera.jpg")),0,5)
        disp.update()
    end

    sys.timerStart(takePhotoAndDisplay,5000)
end

程序入口

function scan()
    --唤醒系统
    pm.wake("testScanCode")
    local ret = 0

    --设置扫码回调函数,默认10秒超时
    scanCode.request(scanCodeCb)

    --打开摄像头
    --ret = disp.cameraopen(1,1) -- 内部配置的gc0310 camera
    --ret = disp.cameraopen_ext(gc6153) -- 外部配置gc6153 camera SDR
    ret = disp.cameraopen_ext(gc0310_sdr) -- 外部配置gc0310 camera SDR
    --ret = disp.cameraopen_ext(gc0310_ddr) -- 外部配置gc0310 camera DDR
    --ret = disp.cameraopen_ext(bf302A_sdr) -- 外部配置bf302A camera SDR

    log.info("testScanCode cameraopen_ext ret ", ret)
    --打开摄像头预览
    --zoom: 放缩设置, 正数放大负数缩小,最大4倍,0不放缩
    --disp.camerapreviewzoom(zoom)
    --缩小2倍
    disp.camerapreviewzoom(-2)
    ret = disp.camerapreview(0,0,0,0,128,160)
    --rotation:反转角度设置 暂时只支持0和90度
    --disp.camerapreviewrotation(90)
end
sys.timerStart(scan,1000)

效果展示

cam

15、KeyPad(矩阵键盘)

(1)简介

keypad是物联网中最常用的功能,本文介绍如何用Air820开发板搭建一个键盘功能演示

(2)材料准备

820

keypad-lay

Air820板载的5个按键已经练到keypad上,可以直接测试使用

(3)操作

local function keyMsg(msg)
    --msg.key_matrix_row:行
    --msg.key_matrix_col:列
    --msg.pressed:true表示按下,false表示弹起
    log.info("keyMsg",msg.key_matrix_row,msg.key_matrix_col,msg.pressed)
end

--注册按键消息处理函数
rtos.on(rtos.MSG_KEYPAD,keyMsg)
--初始化键盘阵列
--第一个参数:固定为rtos.MOD_KEYPAD,表示键盘
--第二个参数:目前无意义,固定为0
--第三个参数:表示键盘阵列keyin标记,例如使用了keyin0、keyin1、keyin2、keyin3,则第三个参数为1<<0|1<<1|1<<2|1<<3 = 0x0F
--第四个参数:表示键盘阵列keyout标记,例如使用了keyout0、keyout1、keyout2、keyout3,则第四个参数为1<<0|1<<1|1<<2|1<<3 = 0x0F
rtos.init_module(rtos.MOD_KEYPAD,0,0x0F,0x0F)

效果展示

keypad

16、PM(休眠)

(1)简介

我们在使用模块时,经常会关注到模块的功耗,这篇示例教大家如何使用Air820开发板来测试模块的休眠管理功能。

(2)材料准备

820

(3)操作

本文以demo/pm为例

Air820自带空闲休眠,用户也可以自定义控制休眠。

关于休眠的介绍文章

用户控制有三个API,API详细文档

休眠接口 描述
pm.wake(tag) 某个Lua应用唤醒系统
pm.sleep(tag) 某个Lua应用休眠系统
pm.isSleep(tag) pm.isSleep([tag]) 读取某个Lua应用或者全局的休眠状态

代码部分:

require"pm"

--[[
关于休眠这一部分的说明:
目前的休眠处理有两种方式,
一种是底层core内部,自动处理,例如tcp发送或者接收数据时,会自动唤醒,发送接收结束后,会自动休眠;这部分不用lua脚本控制
另一种是lua脚本使用pm.sleep和pm.wake自行控制,例如,uart连接外围设备,uart接收数据前,要主动去pm.wake,这样才能保证前面接收的数据不出错,当不需要通信时,调用pm.sleep;如果有lcd的项目,也是同样道理
不休眠时功耗至少30mA左右
休眠后,飞行模式不到1mA,非飞行模式的功耗还没有数据(后续补充)
如果不是故意控制的不休眠,一定要保证pm.wake("A")了,有地方去调用pm.sleep("A")
]]


pm.wake("A") --执行本句后,A唤醒了模块
pm.wake("A") --执行本句后,A重复唤醒模块,实际上没什么变化
pm.sleep("A") --执行本句后,A休眠了模块,lua部分已经没有功能唤醒模块了,模块是否休眠由core决定

pm.wake("B") --执行本句后,B唤醒了模块
pm.wake("C") --执行本句后,C唤醒了模块
pm.sleep("B") --执行本句后,B休眠了模块,但是lua部分还有C已经唤醒了模块,模块并不会休眠
pm.sleep("C") --执行本句后,C休眠了模块,lua部分已经没有功能唤醒模块了,模块是否休眠由core决定

注:在testPm.lua内最后一行再加上pm.wake(tag)即可使模块保持唤醒状态

这里的功耗可通过专业供货测试仪进行曲线测试。

FAQ:

为什么模块无法进入休眠

1. 查看模块是否插入USB,USB连接的状态下模块保持唤醒,无法休眠
2. 使用开发板和自己的板子烧录adc的demo进行对比,看模块是否能够进行休眠
3. 使用pm.isSleep()接口查询脚本休眠状态,看是否是调用了pm.wake(tag)后没有去调用pm.sleep(tag)
4. 屏蔽代码,看是由哪部分代码使模块无法休眠

为什么串口1在休眠状态下也能正常收发数据

uart1在core中做了特殊处理,可以实现休眠状态下接收数据不丢失

17、SDCard

(1)简介

sd卡在物联网设备中,经常用于存储较大文件的容器载体。本文介绍如何用Air820开发板,和PC端搭建一个基于sd卡存储文件的功能演示。

(2)材料准备

820

另需一张小于等于32g的tf卡,格式化为Fat32

可选:喇叭,接法参照Audio章节

(3)操作

本文以demo/sdCard为例

1、API

IO库文档

2、代码

注:挂载SD卡使用 io.mount(io.SDCARD),SD卡映射路径为/sdcard0

这里演示写入sd卡数据

require"audio"

function sdCardTask()
    sys.wait(5000)
    --挂载SD卡,返回值0表示失败,1表示成功
    io.mount(io.SDCARD)
    
    --第一个参数1表示sd卡
    --第二个参数1表示返回的总空间单位为KB
    local sdCardTotalSize = rtos.get_fs_total_size(1,1)
    log.info("sd card total size "..sdCardTotalSize.." KB")
    
    --第一个参数1表示sd卡
    --第二个参数1表示返回的总空间单位为KB
    local sdCardFreeSize = rtos.get_fs_free_size(1,1)
    log.info("sd card free size "..sdCardFreeSize.." KB")
    
    
    --遍历读取sd卡根目录下的最多10个文件或者文件夹
    if io.opendir("/sdcard0") then
        for i=1,10 do
            local fType,fName,fSize = io.readdir()
            if fType==32 then
                log.info("sd card file",fName,fSize)               
            elseif fType == nil then
                break
            end
        end        
        io.closedir("/sdcard0")
    end
    
    --向sd卡根目录下写入一个pwron.mp3
    io.writeFile("/sdcard0/pwron.mp3",io.readFile("/lua/pwron.mp3"))
    --播放sd卡根目录下的pwron.mp3
    audio.play(0,"FILE","/sdcard0/pwron.mp3",audiocore.VOL7,function() sys.publish("AUDIO_PLAY_END") end)
    sys.waitUntil("AUDIO_PLAY_END")    
    
    --卸载SD卡,返回值0表示失败,1表示成功
    io.unmount(io.SDCARD)
end

sys.taskInit(sdCardTask)

效果展示

sd

喇叭会播放mp3的音乐

18、FS(文件系统)

(1)简介

本文只要介绍用Air820开发板,进行fs功能演示流程。

(2)材料准备

820

(3)操作

本文以demo/fs为例讲解

1、API 完整API文档

fs功能的使用使用io库进行操作

2、代码

声明用户方法

--[[
    函数名:readfile(filename)
    功能:打开所输入文件名的文件,并输出储存在里面额内容
    参数:文件名
    返回值:无                     ]]
local function readfile(filename)--打开指定文件并输出内容

    local filehandle=io.open(filename,"r")--第一个参数是文件名,第二个是打开方式,'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then          --判断文件是否存在
        local fileval=filehandle:read("*all")--读出文件内容
      if  fileval  then
           print(fileval)  --如果文件存在,打印文件内容
           filehandle:close()--关闭文件
      else
           print("文件为空")--文件不存在
      end
    else
        print("文件不存在或文件输入格式不正确") --打开失败
    end

end



--[[
    函数名: writevala(filename,value)
    功能:向输入的文件中添加内容,内容附加在原文件内容之后
    参数:第一个文件名,第二个需要添加的内容
    返回值:无                         --]]
local function writevala(filename,value)--在指定文件中添加内容,函数名最后一位就是打开的模式
    local filehandle = io.open(filename,"a+")--第一个参数是文件名,后一个是打开模式'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then
        filehandle:write(value)--写入要写入的内容
        filehandle:close()
    else
        print("文件不存在或文件输入格式不正确") --打开失败
    end
end



--[[
    函数名:writevalw(filename,value)
    功能:向输入文件中添加内容,新添加的内容会覆盖掉原文件中的内容
    参数:同上
    返回值:无                 --]]
local function writevalw(filename,value)--在指定文件中添加内容
    local filehandle = io.open(filename,"w")--第一个参数是文件名,后一个是打开模式'r'读模式,'w'写模式,对数据进行覆盖,'a'附加模式,'b'加在模式后面表示以二进制形式打开
    if filehandle then
        filehandle:write(value)--写入要写入的内容
        filehandle:close()
    else
        print("文件不存在或文件输入格式不正确") --打开失败
    end
end


--[[函数名:deletefile(filename)
    功能:删除指定文件
    参数:文件名
    返回值:无             --]]
local function deletefile(filename)
    os.remove(filename)
end

测试代码:

--打印文件系统的剩余空间
print("get_fs_free_size: "..rtos.get_fs_free_size().." Bytes")
sys.timerLoopStart(function() print("get_fs_free_size: "..rtos.get_fs_free_size().." Bytes") end,5000)
--成功创建一个目录(目录已存在,也返回true表示创建成功)
if rtos.make_dir(USER_DIR_PATH) then
    log.info("testFs.readfile")
    readfile(USER_DIR_PATH.."/3.txt")

    log.info("testFs.writevala")
    writevala(USER_DIR_PATH.."/3.txt","great")

    log.info("testFs.readfile")
    readfile(USER_DIR_PATH.."/3.txt")
    log.info("testFs.writevalw")
    writevalw(USER_DIR_PATH.."/3.txt","great")
    log.info("testFs.readfile")
    readfile(USER_DIR_PATH.."/3.txt")

    log.info("testFs.deletefile")
    deletefile(USER_DIR_PATH.."/3.txt")
    log.info("testFs.readfile")
    readfile(USER_DIR_PATH.."/3.txt")
end

19、基站定位

(1)简介

基站定位是蜂窝网络的基本功能。指南

(2)材料准备

820

(3)操作

1、API简介 完整API文档

定位接口 描述
lbsLoc.request(cbFnc, reqAddr, timeout, productKey, host, port, reqTime, reqWifi) 发送根据基站查询经纬度请求(仅支持中国区域的位置查询)

2、代码

以demo/lbsLoc为例

require"lbsLoc"

--[[
功能  :发送查询位置请求
参数  :无
返回值:无
]]
local function reqLbsLoc()   
    lbsLoc.request(getLocCb)
end

--[[
功能  :获取基站对应的经纬度后的回调函数
参数  :
		result:number类型,0表示成功,1表示网络环境尚未就绪,2表示连接服务器失败,3表示发送数据失败,4表示接收服务器应答超时,5表示服务器返回查询失败;为0时,后面的3个参数才有意义
		lat:string类型,纬度,整数部分3位,小数部分7位,例如031.2425864
		lng:string类型,经度,整数部分3位,小数部分7位,例如121.4736522
返回值:无
]]
function getLocCb(result,lat,lng)
    log.info("testLbsLoc.getLocCb",result,lat,lng)
    --获取经纬度成功
    if result==0 then
    --失败
    else
    end
    sys.timerStart(reqLbsLoc,20000)
end

reqLbsLoc()

效果展示

lbslov

20、WiFi Scan

(1)简介

wifi定位是通过wifi的接口获取附近wifi信息,然后调用基站定位接口进行查询。指南

(2)材料准备

820

开发板已画好wifi天线,无需用户外接。

(3)操作

1、API简介 完整API文档

WIFI接口 描述
wifiScan.request(cbFnc, timeout) wifi扫描热点请求

2、代码

require"wifiScan"

module(...,package.seeall)

sys.taskInit(function()
    while true do
        sys.wait(5000)
        
        wifiScan.request(function(result,cnt,tInfo)
            log.info("testWifi.scanCb",result,cnt)
            sys.publish("WIFI_SCAN_IND",result,cnt,tInfo)
        end)
        
        local _,result,cnt,tInfo = sys.waitUntil("WIFI_SCAN_IND")
        if result then
            for k,v in pairs(tInfo) do
                log.info("testWifi.scanCb",k,v)
            end
        end
    end
end)

这里使用了系统的订阅和发布,调用wifiScan.request,扫描成功后发布WIFI_SCAN_IND的订阅,并把扫描到的数据tInfo传递,使用sys.waitUntil("WIFI_SCAN_IND")获取扫描到的数据,进度for循环打印数据,k为wifi的mac地址,v为信号信息。

注意:此处的PRODUCT_KEY仅供演示使用,不保证一直能用,量产项目中一定要使用自己在iot.openluat.com中创建的项目productKey

效果展示

wifiscan

21、GPS

(1)简介

GPS定位是常用功能之一,Air820集成了GPS,本文介绍如何用Air820开发板进行GPS定位功能演示。

(2)材料准备

820

(3)操作

本文以demo/gps/Air820-中科微为例

1、API

中科微(air530z/air820):gpsZkw

国科(Air530): gps

2、代码

打开gps

--测试代码开关,取值1,2
local testIdx = 1
local function test(idx)
    --第1种测试代码
    if idx==1 then
        --执行完下面三行代码后,GPS就会一直开启,永远不会关闭
        --因为gps.open(gps.DEFAULT,{tag="TEST1",cb=test1Cb}),这个开启,没有调用gps.close关闭
        gps.open(gps.DEFAULT,{tag="TEST1",cb=test1Cb})

        --10秒内,如果gps定位成功,会立即调用test2Cb,然后自动关闭这个“GPS应用”
        --10秒时间到,没有定位成功,会立即调用test2Cb,然后自动关闭这个“GPS应用”
        gps.open(gps.TIMERORSUC,{tag="TEST2",val=10,cb=test2Cb})

        --300秒时间到,会立即调用test3Cb,然后自动关闭这个“GPS应用”
        gps.open(gps.TIMER,{tag="TEST3",val=300,cb=test3Cb})
    --第2种测试代码
    elseif idx==2 then
        --执行完下面三行代码打开GPS后,5分钟之后GPS会关闭
        gps.open(gps.DEFAULT,{tag="TEST1",cb=test1Cb})
        sys.timerStart(gps.close,300000,gps.DEFAULT,{tag="TEST1"})
        gps.open(gps.TIMERORSUC,{tag="TEST2",val=10,cb=test2Cb})
        gps.open(gps.TIMER,{tag="TEST3",val=60,cb=test3Cb}) 
    end
end

声明回调函数

local function test1Cb(tag)
    log.info("testGps.test1Cb",tag)
    printGps()
end

local function test2Cb(tag)
    log.info("testGps.test2Cb",tag)
    printGps()
end

local function test3Cb(tag)
    log.info("testGps.test3Cb",tag)
    printGps()
end

local function nmeaCb(nmeaItem)
    log.info("testGps.nmeaCb",nmeaItem)
end

设置程序入口

local function printGps()
    if gps.isOpen() then
        local tLocation = gps.getLocation()
        local speed = gps.getSpeed()
        log.info("testGps.printGps",
            gps.isOpen(),gps.isFix(),
            tLocation.lngType,tLocation.lng,tLocation.latType,tLocation.lat,
            gps.getAltitude(),
            speed,
            gps.getCourse(),
            gps.getViewedSateCnt(),
            gps.getUsedSateCnt())
    end
end

启动

gps.setNmeaMode(2,nmeaCb)

test(testIdx)
sys.timerLoopStart(printGps,2000)

效果

gps

22、Socket

(1)简介

socket是物联网中较为常用的功能,包括TCP UDP通讯。本文介绍如何用Air820开发板使用socket通讯。

(2)材料准备

820

注:用户需要准备可以正常上网的SIM卡

(3)操作

1、API

Socket完整API文档

Socket API 描述
socket.isReady() SOCKET 是否有可用
socket.tcp(ssl, cert) 创建基于TCP的socket对象
socket.udp() 创建基于UDP的socket对象
mt:connect(address, port, timeout) 连接服务器
mt:asyncSelect(keepAlive, pingreq) 异步收发选择器
mt:asyncSend(data, timeout) 异步发送数据
mt:asyncRecv() 异步接收数据
mt:send(data, timeout) 发送数据
mt:recv(timeout, msg, msgNoResume) 接收数据
mt:close() 销毁一个socket
socket.setTcpResendPara(retryCnt, retryMaxTimeout) 设置TCP层自动重传的参数
socket.setDnsParsePara(retryCnt, retryTimeoutMulti) 设置域名解析参数
socket.printStatus() 打印所有socket的状态

2、本文以 demo/socket/sync/sendWaitRecv/shortConnection为例讲解

--启动socket客户端任务
sys.taskInit(
    function()
        local retryConnectCnt = 0
        while true do
            if not socket.isReady() then
                retryConnectCnt = 0
                --等待网络环境准备就绪,超时时间是5分钟
                sys.waitUntil("IP_READY_IND",300000)
            end
            
            if socket.isReady() then
                --创建一个socket tcp客户端
                local socketClient = socket.tcp()
                --阻塞执行socket connect动作,直至成功
                if socketClient:connect("180.97.80.55",12415) then
                    retryConnectCnt = 0
                    if socketClient:send("heart data\r\n") then
                        result,data = socketClient:recv(5000)
                        if result then
                            --TODO:处理收到的数据data
                            log.info("socketTask.recv",data)
                        end
                    end
                else
                    retryConnectCnt = retryConnectCnt+1
                end
                
                --断开socket连接
                socketClient:close()
                if retryConnectCnt>=5 then link.shut() retryConnectCnt=0 end
                sys.wait(20000)
            else
                --进入飞行模式,20秒之后,退出飞行模式
                net.switchFly(true)
                sys.wait(20000)
                net.switchFly(false)
            end
        end
    end
)

进入协程后,首先等待网络连接成功,当socket准备好后,创建一个tcp客户端,随后连接服务器发送消息(这里推荐一个免费的socket测试服务器netlab.luatos.com),发送之后接受服务器返回数据并打印,如果在连接服务器过程中失败5次,则会等待20s后再次重试。如果网络一直为准备好,则会进入飞行模式,20s后退出重试(相当于网络重启)。

效果展示:

socket

socket2

23、Socket SSL

(1)简介

SSL socket是物联网中较为常用的功能,经过SSL证书进行最彻底的身份认证,确保证书持有者的真实性,从而最大限度上确保链接的安全性,为你的物联网产品提供安全的保障。本文介绍如何用Air820开发板,进行单向和双向SSL认证的功能演示。

(2)材料准备

820

注:用户需要准备可以正常上网的SIM卡

(3)操作

本文以demo/socketSsl/shortConnection为例讲解

1、API

Socket完整API文档

Socket API 描述
socket.isReady() SOCKET 是否有可用
socket.tcp(ssl, cert) 创建基于TCP的socket对象
socket.udp() 创建基于UDP的socket对象
mt:connect(address, port, timeout) 连接服务器
mt:asyncSelect(keepAlive, pingreq) 异步收发选择器
mt:asyncSend(data, timeout) 异步发送数据
mt:asyncRecv() 异步接收数据
mt:send(data, timeout) 发送数据
mt:recv(timeout, msg, msgNoResume) 接收数据
mt:close() 销毁一个socket
socket.setTcpResendPara(retryCnt, retryMaxTimeout) 设置TCP层自动重传的参数
socket.setDnsParsePara(retryCnt, retryTimeoutMulti) 设置域名解析参数
socket.printStatus() 打印所有socket的状态

2、代码

require"socket"
require"ntp"

--同步网络时间,因为证书校验时会用到系统时间
ntp.timeSync()
--启动socket客户端任务
sys.taskInit(
    function()
        --单向认证测试时,此变量设置为false;双向认证测试时,此变量设置为true
        local mutualAuth = false
        local socketClient,connectResult
        local retryConnectCnt = 0
            
        while true do
            if not socket.isReady() then
                retryConnectCnt = 0
                --等待网络环境准备就绪,超时时间是5分钟
                sys.waitUntil("IP_READY_IND",300000)
            end
            
            if socket.isReady() then
                --双向认证测试
                if mutualAuth then                   
                    --创建一个socket ssl tcp客户端
                    socketClient = socket.tcp(true,{caCert="ca.crt",clientCert="client.crt",clientKey="client.key"})
                    --阻塞执行socket connect动作,直至成功
                    connectResult = socketClient:connect("36.7.87.100","4434")
                --单向认证测试
                else
                    --创建一个socket ssl tcp客户端
                    socketClient = socket.tcp(true,{caCert="ca.crt"})
                    --阻塞执行socket connect动作,直至成功
                    connectResult = socketClient:connect("36.7.87.100","4433")
                end
                
                if connectResult then
                    retryConnectCnt = 0
                    if socketClient:send("GET / HTTP/1.1\r\nHost: 36.7.87.100\r\nConnection: keep-alive\r\n\r\n") then
                        result,data = socketClient:recv(5000)
                        if result then
                            --TODO:处理收到的数据data
                            log.info("socketTask.recv",data)
                        end
                    end
                else
                    retryConnectCnt = retryConnectCnt+1
                end
                
                --断开socket连接
                socketClient:close()
                if retryConnectCnt>=5 then link.shut() retryConnectCnt=0 end
                sys.wait(20000)
            else
                --进入飞行模式,20秒之后,退出飞行模式
                net.switchFly(true)
                sys.wait(20000)
                net.switchFly(false)
            end
        end
    end
)

推荐免费socket测试平台 netlab.luatos.com

socket ssl通讯前需先使用ntp对时,确保时间准确才可以进行ssl通讯。在创建socket客户端的时候需引入证书文件,demo已给出示例,通讯流程和socket章节基本一致。

效果展示

socketssl

24、MQTT

(1)简介

mqtt是物联网中最常用的功能,本文介绍如何用Air820开发板,进行mqtt数据订阅,发布,接收的功能演示。

(2)材料准备

820

注:用户需要准备可以正常上网的SIM卡

(3)操作

本文以demo/mqtt/sync/sendWaitRecv这个DEMO为例做演示。

1、API

MQTT完整API文档

MQTT API 描述
mqtt.client(clientId, keepAlive, username, password, cleanSession, will, version) 创建一个mqtt client实例
mqttc:connect(host, port, transport, cert, timeout) 连接mqtt服务器
mqttc:subscribe(topic, qos) 订阅主题
mqttc:unsubscribe(topic) 取消订阅主题
mqttc:publish(topic, payload, qos, retain) 发布一条消息
mqttc:receive(timeout, msg) 接收消息
mqttc:disconnect() 断开与服务器的连接

2、代码分析

  • mqttTask.lua实现的是建立一个mqtt的流程和异常处理机制。要先等待网络就绪了才可以做mqtt相关操作,这里的等待超时时间为5分钟,如果模块等待5分钟还没注册上网络的话,会通过进入飞行模式和退出飞行模式的方法重启一下协议栈。
--启动MQTT客户端任务
sys.taskInit(
    function()
        local retryConnectCnt = 0
        while true do
            if not socket.isReady() then
                retryConnectCnt = 0
                --等待网络环境准备就绪,超时时间是5分钟
                sys.waitUntil("IP_READY_IND",300000)
            end         
            if socket.isReady() then
                local imei = misc.getImei()
                --创建一个MQTT客户端
                local mqttClient = mqtt.client(imei,600,"user","password")
            else
                --进入飞行模式,20秒之后,退出飞行模式
                net.switchFly(true)
                sys.wait(20000)
                net.switchFly(false)
            end
        end
    end
)
  • 创建MQTT客户端,并连接,这里clientId用的时设备的IMEI号,设置的心跳时间为600秒,连接类型这里用的是’tcp’,支持’tcp’和’tcp_ssl’两种连接方式,可以根据实际需求来选择不同的连接方式。
   --创建一个MQTT客户端
   local mqttClient = mqtt.client(imei,600,"user","password")
   --连接MQTT
   mqttClient:connect("lbsmqtt.airm2m.com",1884,"tcp")
  • 主题订阅和数据的发布和接收,这里订阅了topic为"/event0"和"/中文event1"为的2这个主题,这里通过while循环处理接收和发送的数据。
if mqttClient:subscribe({["/event0"]=0, ["/中文event1"]=1}) then
   mqttOutMsg.init()
   --循环处理接收和发送的数据
   while true do
     if not mqttInMsg.proc(mqttClient) then log.error("mqttTask.mqttInMsg.proc error") break end
     if not mqttOutMsg.proc(mqttClient) then log.error("mqttTask.mqttOutMsg proc error") break end
   end
   mqttOutMsg.unInit()
   end
  • 数据接收的处理是在mqttInMsg.lua脚本中实现的,这里通过在while循环里调用mqttc:receive()函数来实现的,这里设置的超时时间为60秒,60秒没收到数据的话会跳出次循环。
--- MQTT客户端数据接收处理
-- @param mqttClient,MQTT客户端对象
-- @return 处理成功返回true,处理出错返回false
-- @usage mqttInMsg.proc(mqttClient)
function proc(mqttClient)
    local result,data
    while true do
        result,data = mqttClient:receive(60000,"APP_SOCKET_SEND_DATA")
        --接收到数据
        if result then
            log.info("mqttInMsg.proc",data.topic,string.toHex(data.payload))
                
            --TODO:根据需求自行处理data.payload
        else
            break
        end
    end
	
    return result or data=="timeout" or data=="APP_SOCKET_SEND_DATA"
end
  • 数据发送的处理是在mqttOutMsg.lua中实现的,发送的消息是通过消息队列维护的,要发送的数据先通调用insertMsg()函数插入要发送的消息到msgQueue,等之前的数据发送完成后会通过调用proc()函数从msgQueue队列中取出第一个消息,通过调用mqttClient:publish()函数,真正的把数据发送出去。这里定时发送是通过在回调函数里启动定时的方式实现的。
--数据发送的消息队列
local msgQueue = {}

--插入发发送的数据到消息队列
local function insertMsg(topic,payload,qos,user)
    table.insert(msgQueue,{t=topic,p=payload,q=qos,user=user})
    sys.publish("APP_SOCKET_SEND_DATA")
end

--主题为"/qos0topic"的消息回调函数
local function pubQos0TestCb(result)
    log.info("mqttOutMsg.pubQos0TestCb",result)
    if result then sys.timerStart(pubQos0Test,10000) end
end

--插入主题为"/qos0topic"的消息到消息队列
function pubQos0Test()
    insertMsg("/qos0topic","qos0data",0,{cb=pubQos0TestCb})
end

--主题为"/中文qos1topic"的消息回调函数
local function pubQos1TestCb(result)
    log.info("mqttOutMsg.pubQos1TestCb",result)
    if result then sys.timerStart(pubQos1Test,20000) end
end

--插入主题为"/中文qos1topic"的消息到消息队列
function pubQos1Test()
    insertMsg("/中文qos1topic","中文qos1data",1,{cb=pubQos1TestCb})
end

--从消息队列中取出消息,发送到服务器 
function proc(mqttClient)
    while #msgQueue>0 do
        local outMsg = table.remove(msgQueue,1)
        local result = mqttClient:publish(outMsg.t,outMsg.p,outMsg.q)
        if outMsg.user and outMsg.user.cb then outMsg.user.cb(result,outMsg.user.para) end
        if not result then return end
    end
    return true
end

效果展示

mqtt

25、Update(远程升级)

(1)简介

Lua版本的远程升级包含Core的升级和Lua脚本的升级,升级方式同样可以选择通过合宙官方iot平台升级,或使用自己的服务器来搭建升级服务。

(2)材料准备

820

(3)操作

1、API

Update API 描述
update.request(cbFnc, url, period, redir) 启动远程升级功能

完整API文档

2、代码

--[[
使用Luat物联云平台的升级服务器时,按照如下步骤操作
1、在main.lua中定义PRODUCT_KEY变量
2、加载update模块 require"update"
3、调用update.request()即可
]]
require"update"
update.request()

update.request(cbFnc, url, period, redir)

传入值类型 释义
function 可选参数,默认为nil,cbFnc,每次执行远程升级功能后的回调函数,回调函数的调用形式为: cbFnc(result),resul为true表示升级包下载成功,其余表示下载失败 如果没有设置此参数,则升级包下载成功后,会自动重启
string 可选参数,默认为nil,url,使用http的get命令下载升级包的url,如果没有设置此参数,默认使用Luat iot平台的url 如果用户设置了url,注意:仅传入完整url的前半部分(如果有参数,即传入?前一部分),http.lua会自动添加?以及后面的参数,例如: 设置的url=“www.userserver.com/api/site/firmware_upgrade”,则http.lua会在此url后面补充下面的参数 “?project_key=”…_G.PRODUCT_KEY …“&imei=”…misc.getimei() …“&device_key=”…misc.getsn() …“&firmware_name=”…G.PROJECT…"“…rtos.get_version()…”&version=“…_G.VERSION 如果redir设置为true,还会补充…”&need_oss_url=1"
number 可选参数,默认为nil,period,单位毫秒,定时启动远程升级功能的间隔,如果没有设置此参数,仅执行一次远程升级功能
bool 可选参数,默认为nil,redir,是否访问重定向到阿里云的升级包,使用Luat提供的升级服务器时,此参数才有意义 为了缓解Luat的升级服务器压力,从2018年7月11日起,在iot.openluat.com新增或者修改升级包的升级配置时,升级文件会备份一份到阿里云服务器 如果此参数设置为true,会从阿里云服务器下载升级包;如果此参数设置为false或者nil,仍然从Luat的升级服务器下载升级包

示例

update.request() 		--使用合宙iot,默认配置
update.request(cbFnc) 	--使用合宙iot,执行远程升级功能后回调
update.request(cbFnc,"www.userserver.com/update")	--使用用户私有平台,执行远程升级功能后回调
update.request(cbFnc,nil,4*3600*1000)				--使用合宙iot,执行远程升级功能后回调,定时启动远程升级
update.request(cbFnc,nil,4*3600*1000,true)			--使用合宙iot,执行远程升级功能后回调,定时启动远程升级,定位到阿里云升级

26、蓝牙

(1)简介

蓝牙是一种短距离的无线通信技术,工作在2.4GHZ频段,使用IEEE802.15协议。见指南

(2)材料准备

820

(3)操作

1、API

BT API 描述
btcore.open(mode) 打开蓝牙
btcore.close() 关闭蓝牙
btcore.state() 查询蓝牙状态
btcore.disconnect(handle) 蓝牙主动断开连接
btcore.connect(addr,addr_type) 蓝牙连接
btcore.setname(name) 设置蓝牙名称
btcore.setadvdata(data) 设置蓝牙广播包数据
btcore.setscanrspdata(data) 设置蓝牙响应包数据
btcore.setbeacondata(uuid,major,minor) 设置蓝牙beacon数据
btcore.advertising(enable) 蓝牙广播开关
btcore.scan(enable) 蓝牙扫描开关
btcore.addservice(uuid_s) 蓝牙添加服务
btcore.addcharacteristic(uuid_c,type,permission) 蓝牙添加特征
btcore.adddescriptor(uuid_d,value) 蓝牙添加描述
btcore.findservice(handle) 蓝牙发现服务
btcore.findcharacteristic(uuid_s,handle) 蓝牙发现服务内包含的特征
btcore.opennotification(uuid_c,handle) 蓝牙关闭通知
btcore.send(data,uuid_c,handle) 写蓝牙
btcore.recv(len) 读蓝牙
btcore.getaddr() 读蓝牙MAC地址
btcore.setadvparam() 设置广播参数
btcore.setscanparam() 设置扫描参数

完整API文档

2、代码

本文以demo/bluetooth为例

初始化:

local function init()
    log.info("bt", "init")
    rtos.on(rtos.MSG_BLUETOOTH, function(msg)
        if msg.event == btcore.MSG_OPEN_CNF then
            sys.publish("BT_OPEN", msg.result) --蓝牙打开成功
        elseif msg.event == btcore.MSG_BLE_CONNECT_CNF then
            sys.publish("BT_CONNECT_IND", {["handle"] = msg.handle, ["result"] = msg.result}) --蓝牙连接成功
        elseif msg.event == btcore.MSG_BLE_DISCONNECT_CNF then
            log.info("bt", "ble disconnect") --蓝牙断开连接
        elseif msg.event == btcore.MSG_BLE_DATA_IND then
            sys.publish("BT_DATA_IND", {["data"] = msg.data, ["uuid"] = msg.uuid, ["len"] = msg.len})  --接收到的数据内容
        elseif msg.event == btcore.MSG_BLE_SCAN_CNF then
            sys.publish("BT_SCAN_CNF", msg.result) --打开扫描成功
        elseif msg.event == btcore.MSG_BLE_SCAN_IND then
            sys.publish("BT_SCAN_IND", {["name"] = msg.name, ["addr_type"] = msg.addr_type, ["addr"] = msg.addr, ["manu_data"] = msg.manu_data, 
            ["raw_data"] = msg.raw_data, ["raw_len"] = msg.raw_len, ["rssi"] = msg.rssi})  --接收到扫描广播包数据
        elseif msg.event == btcore.MSG_BLE_FIND_CHARACTERISTIC_IND then
            sys.publish("BT_FIND_CHARACTERISTIC_IND", msg.result)  --发现服务包含的特征
        elseif msg.event == btcore.MSG_BLE_FIND_SERVICE_IND then
            log.info("bt", "find service uuid",msg.uuid)  --发现蓝牙包含的16bit uuid
            if msg.uuid == 0x1800 then          --根据想要的uuid修改
                sys.publish("BT_FIND_SERVICE_IND", msg.result)
            end
        elseif msg.event == btcore.MSG_BLE_FIND_CHARACTERISTIC_UUID_IND then
            uuid_c = msg.uuid
            log.info("bt", "find characteristic uuid",msg.uuid) --发现到服务内包含的特征uuid
        end
    end)
end

打开蓝牙:

local function poweron()
    log.info("bt", "poweron")
    btcore.open(1) --打开蓝牙主模式
    _, result = sys.waitUntil("BT_OPEN", 5000) --等待蓝牙打开成功
end

蓝牙扫描:

local function scan()
    log.info("bt", "scan")
    --btcore.setscanparam(1,48,6,0,0)--扫描参数设置(扫描类型,扫描间隔,扫描窗口,扫描过滤测量,本地地址类型)
    btcore.scan(1) --开启扫描
    _, result = sys.waitUntil("BT_SCAN_CNF", 50000) --等待扫描打开成功
    if result ~= 0 then
        return false
    end
    sys.timerStart(
        function()
            sys.publish("BT_SCAN_IND", nil)
        end,
        10000)  
    while true do
        _, bt_device = sys.waitUntil("BT_SCAN_IND") --等待扫描回复数据
        if not bt_device then
            -- 超时结束
            btcore.scan(0) --停止扫描
            return false
        else
            log.info("bt", "scan result")
            log.info("bt.scan_name", bt_device.name)  --蓝牙名称
			log.info("bt.rssi", bt_device.rssi)  --蓝牙信号强度
            log.info("bt.addr_type", bt_device.addr_type) --地址种类
            log.info("bt.scan_addr", bt_device.addr) --蓝牙地址
            if bt_device.manu_data ~= nil then
                log.info("bt.manu_data", string.toHex(bt_device.manu_data)) --厂商数据
            end
            log.info("bt.raw_len", bt_device.raw_len)
            if bt_device.raw_data ~= nil then
                log.info("bt.raw_data", string.toHex(bt_device.raw_data)) --广播包原始数据
            end

            --蓝牙连接   根据设备蓝牙广播数据协议解析广播原始数据(bt_device.raw_data)
            if (bt_device.name == "Luat_Air724UG") then   --连接的蓝牙名称根据要连接的蓝牙设备修改
                name = bt_device.name
                addr_type = bt_device.addr_type
                addr = bt_device.addr
                manu_data = bt_device.manu_data
                adv_data = bt_device.raw_data -- 广播包数据 根据蓝牙广播包协议解析
            end
            if addr == bt_device.addr and bt_device.raw_data ~= adv_data then --接收到两包广播数据
                scanrsp_data = bt_device.raw_data --响应包数据 根据蓝牙广播包协议解析
                btcore.scan(0)  --停止扫描
                btcore.connect(bt_device.addr)
                log.info("bt.connect_name", name)
                log.info("bt.connect_addr_type", addr_type)
                log.info("bt.connect_addr", addr)
                if manu_data ~= nil then
                    log.info("bt.connect_manu_data", manu_data)
                end
                if adv_data ~= nil then
                    log.info("bt.connect_adv_data", adv_data)
                end
                if scanrsp_data ~= nil then
                    log.info("bt.connect_scanrsp_data", scanrsp_data)
                end
                return true
            end

        end
    end
    return true
end

蓝牙数据传输:

local function data_trans()

    _, bt_connect = sys.waitUntil("BT_CONNECT_IND") --等待连接成功
    if bt_connect.result ~= 0 then
        return false
    end
    --链接成功
    log.info("bt.connect_handle", bt_connect.handle)--蓝牙连接句柄
    log.info("bt", "find all service uuid")
    btcore.findservice()--发现所有16bit服务uuid
    _, result = sys.waitUntil("BT_FIND_SERVICE_IND") --等待发现uuid
    if not result then
        return false
    end

    btcore.findcharacteristic(0xfee0)--服务uuid
    _, result = sys.waitUntil("BT_FIND_CHARACTERISTIC_IND") --等待发现服务包含的特征成功
    if not result then
        return false
    end
    btcore.opennotification(0xfee2); --打开通知 对应特征uuid  

    --btcore.findcharacteristic("9ecadc240ee5a9e093f3a3b50100406e")--服务uuid
    --_, result = sys.waitUntil("BT_FIND_CHARACTERISTIC_IND") --等待发现服务包含的特征成功
    --if not result then
    --    return false
    --end
    --btcore.opennotification("9ecadc240ee5a9e093f3a3b50200406e"); --打开通知 对应特征uuid  
    
    log.info("bt.send", "Hello I'm Luat BLE")
    sys.wait(1000)
    while true do
        local data = "123456"
        btcore.send(data,0xfee1, bt_connect.handle) --发送数据(数据 对应特征uuid 连接句柄)
        --btcore.send(bt_recv.data,"9ecadc240ee5a9e093f3a3b50300406e",bt_connect.handle)
		_, bt_recv = sys.waitUntil("BT_DATA_IND") --等待接收到数据
        local data = ""
        local len = 0
        local uuid = ""
        while true do
            local recvuuid, recvdata, recvlen = btcore.recv(3)
            if recvlen == 0 then
                break
            end
            uuid = recvuuid
            len = len + recvlen
            data = data .. recvdata
        end
        if len ~= 0 then
        log.info("bt.recv_data", data)
        log.info("bt.recv_data len", len)
        log.info("bt.recv_uuid", uuid)
        end
    end
end

程序入口:

local ble_test = {init, poweron, scan, data_trans}

sys.taskInit(function()
    for _, f in ipairs(ble_test) do
        f()
    end
end)
--按顺序执行init、poweron、scan、data_trans

beacon演示

module(..., package.seeall)

local function init()
    log.info("bt", "init")
    rtos.on(rtos.MSG_BLUETOOTH, function(msg)
        if msg.event == btcore.MSG_OPEN_CNF then
            sys.publish("BT_OPEN", msg.result) --蓝牙打开成功
        end
    end)
end

local function poweron()
    log.info("bt", "poweron")
    btcore.open(0) --打开蓝牙从模式
    _, result = sys.waitUntil("BT_OPEN", 5000) --等待蓝牙打开成功
end

local function advertising()

    log.info("bt", "advertising")
    --btcore.setadvparam(0x80,0xa0,0,0,0x07,0,0,"11:22:33:44:55:66") --广播参数设置 (最小广播间隔,最大广播间隔,广播类型,广播本地地址类型,广播channel map,广播过滤策略,定向地址类型,定向地址)
    btcore.setbeacondata("AB8190D5D11E4941ACC442F30510B408",10107,50179) --beacon设置  (uuid,major,minor)
    btcore.advertising(1)-- 打开广播
end

local ble_test = {init, poweron,advertising}

sys.taskInit(function()
    for _, f in ipairs(ble_test) do
        f()
    end
end)

27、RIL(虚拟AT)

(1)简介

VAT 是LUAT 二次开发过程中常用的功能,本文介绍如何用Air820开发板,和PC端搭建一个VAT的测试示例。

(2)材料准备

820

(3)操作

API 完整文档

ril API 描述
ril.regRsp(head, fnc, typ, formt) 注册某个AT命令应答的处理函数
ril.regUrc(prefix, handler) 注册某个urc的处理函数
ril.deRegUrc(prefix) 解注册某个urc的处理函数
ril.request(cmd, arg, onrsp, delay) 发送AT命令到底层软件

实现代码:

--- 模块功能:MYVAT
-- @author JWL
-- @license MIT
-- @copyright HEZHOU
-- @release 2020.04.02

require"ril"

module(..., package.seeall)

--开机就关掉RNDIS 否则物联网卡流量会被网卡给消耗掉。
--ril.request("AT+RNDISCALL=0,1")

--VAT是否打开 "1" 开 ,"0" 关
local flag_enatc = "1" 
-- 串口ID,串口读缓冲区
local  recvQueue =  {}
-- 串口超时,串口准备好后发布的消息
local uartimeout, recvReady,RECV_MAXCNT = 100, "UART_RECV_ID",1024

--向PC 写入数据,目的是将AT指令的返回数据呈现给电脑一方
local function usb_write(data)
    uart.write(uart.USB, data) 
end

--配置USB 的虚拟串口
uart.setup(uart.USB, 0, 0, uart.PAR_NONE, uart.STOP_1)
uart.on(uart.USB, "receive", function()
    table.insert(recvQueue, uart.read(uart.USB, RECV_MAXCNT))
    sys.timerStart(sys.publish, uartimeout, recvReady)
end)

--处理PC 发过来的AT 指令
function app_procmd(str_recv)
    log.info("str_recv------------",str_recv)

    local flag_handled=true    --用来判断AT指令是否已经被应用层处理,如果已经被处理的就不需要发到底层去了。
    local str_rsp =""          --临时存放应用层已经处理的AT指令返回字符串

    local prefix = string.match(str_recv, "[aA][tT](%+%u+)")
    if prefix ~=nil then
        
        if prefix == "+RIL?" then     --读取VAT标志开关标志
            str_rsp = "+RIL:"..flag_enatc
        elseif prefix == "+RIL" then  --设置VAT 开或者关     
            local temp_enatc = string.match( str_recv, "+RIL=(%d+)")
            if temp_enatc ~= nil then
                flag_enatc = temp_enatc
            end
            if flag_enatc == "0" then  ril.setrilcb(nil) end
            str_rsp = "+RIL:"..flag_enatc
        else
            flag_handled=false
        end
    else
        if  string.upper(str_recv) =="AT\r\n" then
            str_rsp ="OK\r\n"
        else
            flag_handled=false
        end
    end

    if str_rsp ~="" then
        usb_write(str_rsp)
    end

    if (not flag_handled) and (flag_enatc == "1") then
        log.info("send at cmd ==>" ,str_recv)
         ril.setrilcb(usb_write)
         ril.request(str_recv)
    end
end

--将从VAT 串口收到的内容进行拼接
sys.subscribe(recvReady, function()
    local str_recv = table.concat(recvQueue)
    recvQueue = {}
    app_procmd(str_recv)
end)

常见问题:

1,VAT 的指令和标准AT 指令,大部分都相同,由于标准AT 指令里面的CIP 开头的网络通信指令在LUAT 中通过 socket 接口实现,所以LUAT 不支持CIP 的AT 指令。 2,PC 端输入的AT 指令需要大写,比如 AT+CSQ 不要写成 At+CsQ

Lua
1
https://gitee.com/zhouyong_bing/X-MagicBox-820.git
git@gitee.com:zhouyong_bing/X-MagicBox-820.git
zhouyong_bing
X-MagicBox-820
820开发板
master

搜索帮助