跳转至

02 外挂Air530W/Air510W定位

一、GNSS 概述

GPS 最先来自美国,1978 年,美国发射了第一颗 GPS 卫星,发明和实践了卫星定位技术。卫星定位技术原理是,覆盖全球的多颗定位卫星连续发射一定频率的无线电信号,移动终端上集成便携式卫星信号接收机,接收机接收卫星信号并测量卫星到终端接收机之间的距离,最终由移动终端利用多颗卫星位置和与这些卫星的距离计算出移动终端的具体位置。后来出现了欧洲的 Galileo、俄罗斯的 GLONASS、中国的北斗等,所以如今的 GPS 实质上是作为一个卫星定位技术体系 GNSS 的代名词,而不是单只美国的 GPS 系统。

GNSS 提供的服务包括定位、授时和导航。定位服务就是 GNSS 终端获得其位置的服务,授时服务就是 GNSS 终端获得正确时间的服务,导航服务是计算 GNSS 终端速度和运动方向的服务。GNSS 不限制终端数,在 GNSS 卫星信号不被阻挡的情况下,在地球上任何地点、任何时间,任何 GNSS 终端都可以得到正确的位置和时间。定位只需要一个条件,那就是能够接收到足够多的卫星信号。因此在室内通常无法定位。

Air510W/Air530W 作为一款单频多系统米级定位精度的卫星导航定位模块,支持 BDS、GPS、Galileo、GLONASS、QZSS,可多系统同时联合定位。能够提供稳定、准确的定位服务。

二、演示功能概述

Air780EHM 和 780EHV 都可以搭配使用 Air530W 和 Air510W 开发板,这几种搭配使用完全一样,本文主要以 Air780EHV 搭配 Air530W 进行示例介绍。

使用 Air780EHV 核心板,外挂 Air530W 开发板进行定位,

single 示例主要是展示 exgnss 库的三种应用模式,

exgnss.DEFAULT 模式

exgnss.TIMERORSUC 模式

exgnss.TIMER 模式

主要操作为:

1、打开三种应用模式

2、等待 40 秒关闭操作 TIMER 模式

3、然后查询三种应用模式是否处于激活模式

4、等待 10 秒关闭全部应用模式,再次查询三种模式是否处于激活模式

5、然后获取最后一次的定位经纬度数据打印

6、定位成功之后使用 exgnss 库获取 gnss 的 rmc 数据

combination 示例主要展示几种不同的应用场景:

第一种场景是:正常模式,第一步先是通过 tcp_client_main 文件连接服务器,然后第二步模块会配置 GNSS 参数,开启 GNSS 应用,第三步会开启一个 60s 的定时器,定时器每 60s 会打开一个 60sTIMERRORSUC 应用,第四步定位成功之后关闭 GNSS,然后获取 rmc 获取经纬度数据,发送经纬度数据到服务器上。

第二种场景是:低功耗模式,第一步先是通过 tcp_client_main 文件连接服务器,然后第二步模块会配置 GNSS 参数,开启 GNSS 应用,第三步会开启一个 60s 的定时器,定时器每 60s 会进入正常模式,打开一个 60sTIMERRORSUC 应用,第四步定位成功之后关闭 GNSS,然后获取 rmc 获取经纬度数据,发送经纬度数据到服务器上,进入低功耗模式。

第三种场景是:PSM+ 模式,唤醒之后第一步是配置 GNSS 参数,开启 GNSS 应用,第二步定位成功之后关闭 GNSS,然后获取 rmc 获取经纬度数据,拼接唤醒信息和经纬度信息,连接服务器,然后把数据发送数据到服务器上,配置休眠唤醒定时器 ,进入飞行模式,然后进入 PSM+ 模式。

三、准备硬件环境

3.1 Air530W 开发板

淘宝购买链接:Air530W 开发板淘宝购买链接

开发板使用教程参考:Air530W 开发板使用说明

3.2 Air510W 开发板

淘宝购买链接:Air530W 开发板淘宝购买链接

开发板使用教程参考:Air530W 开发板使用说明

3.3 Air780EHV 核心板

Air780EHV 核心板购买链接:点击购买 air780EHV 核心板

开发板使用教程:Air780EHV 核心板使用说明

3.5 Air780EHM 核心板

Air780EHM 核心板购买链接:点击购买 air780EHM 核心板

开发板使用教程:Air780EHM 核 心板使用说明

3.5 GNSS 天线

上面购买链接里面包含一个 GNSS 天线。

参考:硬件环境清单,准备以及组装好硬件环境。

3.6 接线和供电

为了测试方便,本次测试给 Air530W 开发板供电用的是 Air780EHV 核心板的 3.3V 给 Air530W 供电,Air530W 的 TXD 和 RXD 接的是 Air780EHV 的串口 2,默认波特率 115200,Air780EHV 控制 Air530W 的 ON_OFF 脚的 gpio 是 gpio21。

Air780EHV核心板
Air530W开发板
3.3V
VDD
29/U2TXD
RXD
28/U2RXD
TXD
GND
GND
107/GPIO21
ON_OFF

由于 Air530W 开发板的设计问题,当前 Air530W 开发板通过飞线把 ON_OFF 脚引了出来,该脚的功能是:默认高电平,内部上拉至 V_BAT;

控制 530W/510W 开机,ON_OFF 脚保持高电平或悬空;

控制 530W/510W 关机,需拉低 ON_OFF 脚实现

后续设计可以通过控制此脚来达到 GNSS 的开和关。

四、准备软件环境

1. 烧录工具 Luatools

2. 本demo开发测试时使用的固件为LuatOS-SoC_V2018_Air780EPM 1号固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件Air780EPM固件Air780EHM固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

3. LuatOS 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/gnss

4. lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件,如果没有下载最新的 lib,可以在 luatos 库里面下载最新的 lib 使用:https://gitee.com/openLuat/LuatOS/tree/master/script/libs

准备好软件环境之后,接下来查看如何烧录项目文件到 Air780EHV核心板中,将本篇文章中演示使用的项目文件烧录到 Air780EHV 核心板中。

五、GNSS 软硬件参考

5.1 API 接口介绍

本教程使用 api 接口为:

https://docs.openluat.com/osapi/ext/exgnss/

5.2 GNSS 硬件参考设计

https://docs.openluat.com/air510w/product/shouce/

硬件方面需要注意的是:

530W/510W 的模块使能引脚是 ON_OFF 脚,该脚的功能是:默认高电平,内部上拉至 V_BAT;

控制 530W/510W 开机,ON_OFF 脚保持高电平或悬空;

控制 530W/510W 关机,需拉低 ON_OFF 脚实现;

注意!

由于 ON_OFF 脚上拉电平是模组供电脚 V_BAT,其电压范围是 3.3V-4.3V(最高可以到 5V),因此,当与之搭配的主控,比如 Air780EHV,通过 GPIO 控制 ON_OFF 脚时,需要通过三极管做隔离,以免 Air780EHV 的 GPIO 被 V_BAT 的高电压造成损坏,详见 530W/510W 的硬件参考设计;

六、代码示例介绍

本文主要提供 2 个 demo 目录,single 和 combination,其中 single 只是对 exgnss 库的三种 GNSS 应用的展示,combination 主要是提供了三种应用场景

noraml:正常功耗模式定位

lowpower:低功耗模式

psm:PSM+ 模式

6.1 exgnss 的三种应用的用法

6.1.1 exgnss 的应用说明

关于 exgnss 库的三种应用说明:

--关于exgnss的三种应用场景:
exgnss.DEFAULT:
--- exgnss应用模式1.
-- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
-- 使用此应用模式调用exgnss.open打开的“gnss应用”,必须主动调用exgnss.close
-- 或者exgnss.close_all才能关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是一直打开,除非自己手动关闭掉

exgnss.TIMERORSUC:
--- exgnss应用模式2.
-- 打开gnss后,如果在gnss开启最大时长到达时,没有定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,如果在gnss开启最大时长内,定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者
-- exgnss.close_all主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,如果规定时间内定位成功就会自动关闭此应用,
-- 如果没有定位成功,时间到了也会自动关闭此应用

exgnss.TIMER:
--- exgnss应用模式3.
-- 打开gnss后,在gnss开启最大时长时间到达时,无论是否定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者exgnss.close_all
-- 主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,无论是否定位成功,到了时间都会自动关闭此应用,
-- 和第二种的区别在于定位成功之后不会自动关闭,到时间之后才会自动关闭

关于应用需要注意的是:使用 exgnss.close()关闭的只是对应的应用,所有应用关闭,gnss 也会关闭,这样做的好处是可以处理不同场景下的不同逻辑,比如服务器下发一条定位指令,定位成功之后关掉,这个时候又下发了一个指令需要长开启定位,这样两种指令就会冲突,需要代码去处理到底是不是要关掉 gnss,这个时候应用的好处就体现出来了,开启 exgnss.TIMERORSUC 第一个应用,定位成功之后关掉对应的应用,同时开启 exgnss.DEFAULT 第二个应用,gnss 就会常开,在应用 1 结束之后,只会对应用进行自动关闭,不会影响到第二个应用,gnss 还会处于常开状态。

关于 agps 辅助定位的逻辑:agps 俗称辅助定位,其逻辑是把星历文件发送给 gnss 芯片,同时发送时间和用基站定位获取的大概位置,然后芯片解析处理,达到快速定位的效果。

exgnss 扩展库的辅助定位功能,我们处理方式是每个小时都会去更新星历,原因是北斗是 1h 一更新,gps 是 4h 一更新,所以取最小值,我们选择了一小时一更新

对于辅助定位需要注意的是,因为辅助定位属于强行把星历文件灌给 gnss 芯片,虽然可以达到快速定位的效果,但是 gnss 芯片实际解析星历需要时间,exgnss 库默认开启了一个 20s 的 gnss 应用,做这个操作是因为,假设开启辅助定位,定位到之后立马关掉 gnss 电源,会导致 gnss 星历解析不完全,这样会影响下次定位为冷启动,冷启动时间大概需要 35s。如果不需要这个逻辑,可以接受冷启动,或者每次都使用辅助定位,可以通过 exgnss.setup()的 auto_open=false 配置添加到配置表里面。

6.1.2 代码讲解

main.lua

主要是为了加载了 exgnss 扩展库,加载 gnss 代码

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
demo演示的功能为
使用Air780EHV核心板外挂Air530W开发板,通过exgnss.lua扩展库,开启GNSS定位,展示模块的三种应用状态
]]

--[[
必须定义PROJECT和VERSION变量Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            XYZ各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "gnsstest"
VERSION = "001.000.000"

--添加硬狗防止程序卡死
if wdt then
    wdt.init(9000)--初始化watchdog设置为9s
    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
end

-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end

-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)

exgnss=require("exgnss")    

require"gnss"

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

gnss.lua

主要操作为:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数,通过 exgnss.open 开启三种不同的应用

2.等待 40 秒使用 exgnss.close 关闭 TIMER 模式,使用 exgnss.close 需要两个参数,第一个是 exgnss 应用模式,第二个是 tag 标签

3.然后利用 exgnss.is_active 查询三种应用模式是否处于激活模式,查询应用是否处于激活状态同样需要两个参数,第一个是 exgnss 应用模式,第二个是 tag 标签

4.等待 10 秒使用 exgnss.close_all 关闭全部应用模式,再次查询三种模式是否处于激活模式

5.然后使用 exgnss.last_loc 获取最后一次的定位经纬度数据打印

6.定位成功之后使用 subscribe 订阅"GNSS_STATE"消息,根据获取到的值判断是否定位成功,定位成功用 exgnss.rmc 获取 rmc 数据

--[[
@module  gnss
@summary gnss应用测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air780EHV核心板外挂Air530W开发板,该文件演示的功能会用到exgnss.lua扩展库

--关于exgnss的三种应用场景:
exgnss.DEFAULT:
--- exgnss应用模式1.
-- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
-- 使用此应用模式调用exgnss.open打开的“gnss应用”,必须主动调用exgnss.close
-- 或者exgnss.close_all才能关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是一直打开,除非自己手动关闭掉

exgnss.TIMERORSUC:
--- exgnss应用模式2.
-- 打开gnss后,如果在gnss开启最大时长到达时,没有定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,如果在gnss开启最大时长内,定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者
-- exgnss.close_all主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,如果规定时间内定位成功就会自动关闭此应用,
-- 如果没有定位成功,时间到了也会自动关闭此应用

exgnss.TIMER:
--- exgnss应用模式3.
-- 打开gnss后,在gnss开启最大时长时间到达时,无论是否定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者exgnss.close_all
-- 主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,无论是否定位成功,到了时间都会自动关闭此应用,
-- 和第二种的区别在于定位成功之后不会自动关闭,到时间之后才会自动关闭

本示例主要是展示exgnss库的三种应用模式,然后关闭操作和查询应用是否有效操作,还有关闭全部应用操作,
以及定位成功之后如何使用exgnss库获取gnss的rmc数据
]]

local function mode1_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
end

local function mode2_cb(tag)
    log.info("TAGmode2_cb+++++++++",tag)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
end

local function mode3_cb(tag)
    log.info("TAGmode3_cb+++++++++",tag)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
end

local function gnss_fnc()
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,780EGH和8000默认串口2
        -- uartbaud=115200,    --串口波特率,780EGH和8000默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
         ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false 
        gnss_volgpio=21 --设置GNSS模块的供电脚,外挂GNSS模块需要设置,4G定位二合一的模块不需要设置
    }
    --设置gnss参数
    exgnss.setup(gnssotps)
    --开启gnss应用
    exgnss.open(exgnss.TIMER,{tag="modeTimer",val=60,cb=mode1_cb})  --使用TIMER模式,开启60s后关闭
    exgnss.open(exgnss.DEFAULT,{tag="modeDefault",cb=mode2_cb}) --使用DEFAULT模式,开启后一直运行  
    exgnss.open(exgnss.TIMERORSUC,{tag="modeTimerorsuc",val=60,cb=mode3_cb})    --使用TIMERORSUC模式,开启60s,如果定位成功,则直接关闭
    sys.wait(40000)
    log.info("关闭一个gnss应用,然后查看下所有应用的状态")
    --关闭一个gnss应用
    exgnss.close(exgnss.TIMER,{tag="modeTimer"})--关闭tag为modeTimer应用
    --查询3个gnss应用状态
    log.info("gnss应用状态1",exgnss.is_active(exgnss.TIMER,{tag="modeTimer"}))
    log.info("gnss应用状态2",exgnss.is_active(exgnss.DEFAULT,{tag="modeDefault"}))
    log.info("gnss应用状态3",exgnss.is_active(exgnss.TIMERORSUC,{tag="modeTimerorsuc"}))
    sys.wait(10000)
    --关闭所有gnss应用
    exgnss.close_all()
    --查询3个gnss应用状态
    log.info("gnss应用状态1",exgnss.is_active(exgnss.TIMER,{tag="modeTimer"}))
    log.info("gnss应用状态2",exgnss.is_active(exgnss.DEFAULT,{tag="modeDefault"}))
    log.info("gnss应用状态3",exgnss.is_active(exgnss.TIMERORSUC,{tag="modeTimerorsuc"}))
    --查询最后一次定位结果
    local loc= exgnss.last_loc()
    if loc then
        log.info("lastloc", loc.lat,loc.lng)
    end
end

sys.taskInit(gnss_fnc)

--GNSS定位状态的消息处理函数:
local function gnss_state(event, ticks)
    -- event取值有
    -- "FIXED":string类型 定位成功
    -- "LOSE": string类型 定位丢失
    -- "CLOSE": string类型 GNSS关闭,仅配合使用gnss.lua有效

    -- ticks number类型 是事件发生的时间,一般可以忽略
    log.info("exgnss", "state", event)
    if event=="FIXED" then
        --获取rmc数据
        --json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
        log.info("nmea", "rmc0", json.encode(exgnss.rmc(0),"5f"))
    end
end
sys.subscribe("GNSS_STATE",gnss_state)

6.1.3 效果展示

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.2 正常功耗模式下的 GNSS 应用

main.lua

主要是支持 4 个场景的切换,每次只能打开一个用于测试,选择对应的功能进行测试

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
demo演示的功能为
使用Air780EHV核心板外挂Air530W开发板,通过exgnss扩展库,开启GNSS定位,展示模块的三种功耗模式:正常模式,低功耗模式,PSM+模式 
]]

--[[
必须定义PROJECT和VERSION变量Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            XYZ各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "gnsstest"
VERSION = "001.000.000"

--添加硬狗防止程序卡死
if wdt then
    wdt.init(9000)--初始化watchdog设置为9s
    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
end

-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end

-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)

exgnss=require("exgnss")    

--以下功能每次只能打开一个用于测试,选择对应的功能进行测试

require"normal"  --正常模式,搭配GNSS定时开启发送经纬度数据到服务器,不进入任何低功耗模式
-- require"lowpower"    -- 低功耗模式,搭配GNSS定时开启发送经纬度数据到服务器,定位成功之后关闭GNSS进入低功耗模式
-- require"psm"     -- PSM+模式,唤醒开启GNSS定位,定位成功之后发送经纬度数据到服务器,然后关闭GNSS进入PSM+模式
-- require"vibration"  -- 振动检测模式,搭配GNSS触发震动检测之后,持续发送经纬度数据到服务器

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

6.2.1 代码讲解

normal.lua

normal 主要操作:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数,然后 require tcp_client_main 文件,连接服务器

2.通过 exgnss.open 开启 exgnss.TIMERORSUC 应用模式,设置的 tag 为 normal,定位时间为 60s,回调函数为 normal_cb,这个模式定位成功之后会关掉 GNSS,然后触发回调函数,回调函数的操作为,通过 exgnss.rmc(0)接口获取 rmc 数据,然后把经纬度拼接成 json 字符串的形式,通过 sys.publish 函数,把数据传给 tcp_client_sender.lua 文件里面处理。

3.用 sys.timerLoopStart()起一个 60s 触发一次的打开 GNSS 定位的定时器,回调函数为 normal_open,回调函数内容和上面开启 exgnss.TIMERORSUC 应用模式一致

--[[
@module  normal
@summary gnss正常测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air780EHV核心板,外挂Air530W开发板,外接GPS天线,定位然后发送经纬度数据给服务器,
起一个60s定位一次的定时器,模块60s一定位,然后定位成功获取到经纬度发送到服务器上面
]]

tcp_client_main=require("tcp_client_main")

local function normal_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    local  rmc=exgnss.rmc(0)    --获取rmc数据
    log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
    local data=string.format('{"lat":%5f,"lng":%5f}', rmc.lat, rmc.lng)
    sys.publish("SEND_DATA_REQ", "gnssnormal", data) --发送数据到服务器
end
local function normal_open()
    exgnss.open(exgnss.TIMERORSUC,{tag="normal",val=60,cb=normal_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        -- debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,780EGH和8000默认串口2
        -- uartbaud=115200,    --串口波特率,780EGH和8000默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
        ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false 
        gnss_volgpio=21 --设置GNSS模块的供电脚,外挂GNSS模块需要设置,4G定位二合一的模块不需要设置
    }
    exgnss.setup(gnssotps)  --配置GNSS参数
    exgnss.open(exgnss.TIMERORSUC,{tag="normal",val=60,cb=normal_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
    sys.timerLoopStart(normal_open,60000)       --每60s开启一次GNSS

end

sys.taskInit(gnss_fnc)

6.2.2 效果展示

测试服务器收到消息:

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.3 低功耗模式下的 GNSS 应用

6.3.1 代码讲解

lowpower.lua

normal 主要操作:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数,然后 require tcp_client_main 文件,连接服务器。

2.通过 exgnss.open 开启 exgnss.TIMERORSUC 应用模式,设置的 tag 为 lowpower,定位时间为 60s,回调函数为 lowpower_cb,这个模式定位成功之后会关掉 GNSS,然后触发回调函数,回调函数的操作为,通过 exgnss.rmc(0)接口获取 rmc 数据,然后把经纬度拼接成 json 字符串的形式,通过 sys.publish 函数,把数据传给 tcp_client_sender.lua 文件里面处理,然后调用 pm.power(pm.WORK_MODE, 1)--进入低功耗模式。这个函数主要是为了让模块进入低功耗模式。

3.用 sys.timerLoopStart()起一个 60s 触发一次的打开 GNSS 定位的定时器,回调函数为 lower_open,回调函数内容主要是调用 pm.power(pm.WORK_MODE, 0)进入正常模式,然后和上面开启 exgnss.TIMERORSUC 应用模式一致,

然后关闭 USB,节省功耗

--[[
@module  lowpower
@summary gnss低功耗测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air780EHV核心板外挂Air530W开发板,起一个60s定位一次的定时器,唤醒模块60s一定位
然后定位成功获取到经纬度发送到服务器上面,然后进入休眠
需要注意的是:低功耗模式模式如果把780EHV核心板上面的拨扭拨到off的话,会导致3.3V没有电,需要外部给530W开发板供电
]]

tcp_client_main=require("tcp_client_main")

local function lowpower_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    local  rmc=exgnss.rmc(0)    --获取rmc数据
    log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
    local data=string.format('{"lat":%5f,"lng":%5f}', rmc.lat, rmc.lng)
    sys.publish("SEND_DATA_REQ", "gnsslowpower", data) --发送数据到服务器
    pm.power(pm.WORK_MODE, 1)--进入低功耗模式
end

local function lower_open()
    pm.power(pm.WORK_MODE, 0)   --进入正常模式
    exgnss.open(exgnss.TIMERORSUC,{tag="lowpower",val=60,cb=lowpower_cb})
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        -- debug=true,    --是否输出调试信息
        uart=2,    --使用的串口,780EHV使用串口2和530W进行连接
        uartbaud=115200,    --串口波特率,Air530W默认波特率为115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
        ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false 
        gnss_volgpio=21 --设置GNSS模块的供电脚,外挂GNSS模块需要设置,4G定位二合一的模块不需要设置
    }
    exgnss.setup(gnssotps)  --配置GNSS参数
    exgnss.open(exgnss.TIMERORSUC,{tag="lowpower",val=60,cb=lowpower_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
    sys.timerLoopStart(lower_open,60000)       --每60s开启一次GNSS
    -- gpio.close(24)--此脚为gnss备电脚和三轴加速度传感器的供电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高0.5-1MA左右
    -- --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
    pm.power(pm.USB, false)
end

sys.taskInit(gnss_fnc)

6.3.2 效果展示

下面是单纯的 Air530W 开发板的功耗:

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.4 PSM+ 模式下的 GNSS 应用

6.4.1 代码讲解

psm.lua

psm 主要操作:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数。

2.通过 exgnss.open 开启 exgnss.TIMERORSUC 应用模式,设置的 tag 为 psm,定位时间为 60s,回调函数为 psm_cb,这个模式定位成功之后会关掉 GNSS,然后触发回调函数,回调函数的操作为,通过 exgnss.rmc(0)接口获取 rmc 数据,获取经纬度并赋值变量,然后使用 sysplus.taskInitEx 开启 TCP 短连接协程,连接服务器发送经纬度消息和唤醒消息内容。

3.发送完毕之后设置定时器,然后进入飞行模式,进入 psm+ 模式

--[[
@module  psm
@summary gnss使用psm测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air780EHV核心板外挂Air530W开发板,开启定位,获取到定位发送到服务器上面,然后启动一个60s的定时器唤醒PSM+模式
模块开启定位,然后定位成功获取到经纬度发送到服务器上面,然后进入PSM+模式,等待唤醒
需要注意的是:低功耗模式模式如果把780EHV核心板上面的拨扭拨到off的话,会导致3.3V没有电,需要外部给530W开发板供电
]]
pm.power(pm.WORK_MODE, 0) 

local lat,lng

-- 电脑访问:https://iot.luatos.com/#/page6/netlab/
-- 详细使用说明参考:[合宙 TCP/UDP web 测试工具使用说明](https://docs.openluat.com/common/TCPUDP_Test/) 。

-- 点击 打开TCP 按钮,会创建一个TCP server
-- 将server的地址和端口赋值给下面这两个变量
local server_ip = "112.125.89.8" 
local server_port = 43706 -- 换成自己的

local period = 3 * 60 * 60 * 1000 -- 定时器唤醒时间,3小时唤醒一次

local reason, slp_state = pm.lastReson() -- 获取唤醒原因
log.info("wakeup state", pm.lastReson())
local libnet = require "libnet"

local d1Name = "D1_TASK"
local function netCB(msg)
    log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
end

local function testTask(ip, port)
    local txData
    --拼接处理定位数据和唤醒原因
    if reason == 0 then
        txData = "normal wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    elseif reason == 1 then
        txData = "timer wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    elseif reason == 2 then
        txData = "pad wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    elseif reason == 3 then
        txData = "uart1 wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    end
    if slp_state > 0 then
        mobile.flymode(0, false) -- 退出飞行模式,进入psm+前进入飞行模式,唤醒后需要主动退出
    end

    local netc, needBreak
    local result, param, is_err
    -- 创建socket client对象
    netc = socket.create(nil, d1Name)
    --关闭debug信息
    socket.debug(netc, false)
    -- 配置socket client对象为netc
    socket.config(netc) 
    local retry = 0
    --这边会尝试连接服务器并且发送,最多处理3次,如果3次都不行就退出,发送成功就直接退出
    while retry < 3 do
        while not socket.adapter(socket.dft()) do
            log.warn("tcp_client_main_task_func", "wait IP_READY")
            -- 在此处阻塞等待联网成功成功的消息"IP_READY"
            -- 或者等待30秒超时退出阻塞等待状态
            sys.waitUntil("IP_READY", 30000)
        end

        log.info(rtos.meminfo("sys"))
        -- 连接server
        result = libnet.connect(d1Name, 5000, netc, ip, port)

        if result then
            log.info("服务器连上了")
            --连接成功发送数据
            result, param = libnet.tx(d1Name, 15000, netc, txData)
            --如果发送失败就直接退出
            if not result then
                log.info("服务器断开了", result, param)
                break
            else
                needBreak = true
            end
        else
            log.info("服务器连接失败")
        end
        --关闭socket
        libnet.close(d1Name, 5000, netc)
        retry = retry + 1
        --如果发送成功结束循环
        if needBreak then
            break
        end
    end
    socket.release(netc)

    uart.setup(1, 9600) -- 配置uart1,外部唤醒用

    -- 关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
    pm.power(pm.USB, false)

    pm.dtimerStart(3, period) -- 启动深度休眠定时器

    mobile.flymode(0, true) -- 启动飞行模式,规避可能会出现的网络问题
    pm.power(pm.WORK_MODE, 3) -- 进入极致功耗模式

    sys.wait(15000) -- demo演示唤醒时间是三十分钟,如果15s后模块重启,则说明进入极致功耗模式失败,
    log.info("进入极致功耗模式失败,尝试重启")
    rtos.reboot()
end

local function psm_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    local  rmc=exgnss.rmc(0)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
    lat,lng=rmc.lat,rmc.lng
    sysplus.taskInitEx(testTask, d1Name, netCB, server_ip, server_port)
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        -- debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,780EGH和8000默认串口2
        -- uartbaud=115200,    --串口波特率,780EGH和8000默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
         ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把agps_autoopen设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        auto_open=false,
        gnss_volgpio=21 --设置GNSS模块的供电脚,外挂GNSS模块需要设置,4G定位二合一的模块不需要设置
    }
    exgnss.setup(gnssotps)
    exgnss.open(exgnss.TIMERORSUC,{tag="psm",val=60,cb=psm_cb})
end

sys.taskInit(gnss_fnc)

6.4.2 效果展示

780EHV 的 PSM+ 模式的待机功耗为 3ua,Air530W 开发板的待机功耗大概为 300ua 左右。

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.6 TCP 部分代码

TCP 部分代码为通用格式内容,里面有对注释的描述,在 GNSS 篇章就不过多对 TCP 的代码做讲解了,如果有需要可以看 TCP 章节

tcp_client_main.lua

--[[
@module  tcp_client_main
@summary tcp client socket主应用功能模块 
@version 1.0
@date    2025.07.01
@author  朱天华
@usage
本文件为tcp client socket主应用功能模块,核心业务逻辑为:
1、创建一个tcp client socket,连接server
2、处理连接异常,出现异常后执行重连动作;
3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;

本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
]]

local libnet = require "libnet"

-- 加载tcp client socket数据接收功能模块
local tcp_client_receiver = require "tcp_client_receiver"
-- 加载tcp client socket数据发送功能模块
local tcp_client_sender = require "tcp_client_sender"

-- 电脑访问:https://iot.luatos.com/#/page6/netlab/
-- 详细使用说明参考:[合宙 TCP/UDP web 测试工具使用说明](https://docs.openluat.com/common/TCPUDP_Test/) 。

-- 点击 打开TCP 按钮,会创建一个TCP server
-- 将server的地址和端口赋值给下面这两个变量
local SERVER_ADDR = "112.125.89.8"
local SERVER_PORT = 44333

-- tcp_client_main的任务名
local TASK_NAME = tcp_client_sender.TASK_NAME

-- 处理未识别的消息
local function tcp_client_main_cbfunc(msg)
    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
end

-- tcp client socket的任务处理函数
local function tcp_client_main_task_func() 

    local socket_client
    local result, para1, para2

    while true do
        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
        while not socket.adapter(socket.dft()) do
            log.warn("mqtt_client_main_task_func", "wait IP_READY", socket.dft())
            -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
            -- 或者等待1秒超时退出阻塞等待状态;
            -- 注意:此处的1000毫秒超时不要修改的更长;
            -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
            -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
            sys.waitUntil("IP_READY", 1000)
        end

        -- 检测到了IP_READY消息
        log.info("tcp_client_main_task_func", "recv IP_READY")

        -- 创建socket client对象
        socket_client = socket.create(nil, TASK_NAME)
        -- 如果创建socket client对象失败
        if not socket_client then
            log.error("tcp_client_main_task_func", "socket.create error")
            goto EXCEPTION_PROC
        end

        -- 配置socket client对象为tcp client
        result = socket.config(socket_client)
        -- 如果配置失败
        if not result then
            log.error("tcp_client_main_task_func", "socket.config error")
            goto EXCEPTION_PROC
        end

        -- 连接server
        result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
        -- 如果连接server失败
        if not result then
            log.error("tcp_client_main_task_func", "libnet.connect error")
            goto EXCEPTION_PROC
        end

        log.info("tcp_client_main_task_func", "libnet.connect success")

        -- 数据收发以及网络连接异常事件总处理逻辑
        while true do
            -- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
            -- 如果处理失败,则退出循环
            if not tcp_client_receiver.proc(socket_client) then
                log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
                break
            end

            -- 数据发送处理
            -- 如果处理失败,则退出循环
            if not tcp_client_sender.proc(TASK_NAME, socket_client) then
                log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
                break
            end

            -- 阻塞等待socket.EVENT事件或者15秒钟超时
            -- 以下三种业务逻辑会发布事件:
            -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
            -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
            -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
            result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
            log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)

            -- 如果连接异常,则退出循环
            if not result then
                log.warn("tcp_client_main_task_func", "connection exception")
                break
            end
        end

        -- 出现异常    
        ::EXCEPTION_PROC::

        -- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
        tcp_client_sender.exception_proc()

        -- 如果存在socket client对象
        if socket_client then
            -- 关闭socket client连接
            libnet.close(TASK_NAME, 5000, socket_client)

            -- 释放socket client对象
            socket.release(socket_client)
            socket_client = nil
        end

        -- 5秒后跳转到循环体开始位置,自动发起重连
        sys.wait(5000)
    end
end

--创建并且启动一个task
--运行这个task的主函数tcp_client_main_task_func
sysplus.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)

tcp_client_reveiver.lua

--[[
@module  tcp_client_receiver
@summary tcp client socket数据接收应用功能模块 
@version 1.0
@date    2025.07.01
@author  朱天华
@usage
本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;

本文件的对外接口有2个:
1tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用
2sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
   将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
   需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息
]]

local tcp_client_receiver = {}

-- socket数据接收缓冲区
local recv_buff = nil

-- 数据接收应用入口函数
function tcp_client_receiver.proc(socket_client)
    -- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
    if recv_buff==nil then
        recv_buff = zbuff.create(1024)
        -- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
        -- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
        -- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
    end

    -- 循环从内核的缓冲区读取接收到的数据
    -- 如果读取失败,返回false,退出
    -- 如果读取成功,处理数据,并且继续循环读取
    -- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
    while true do
        -- 从内核的缓冲区中读取数据到recv_buff中
        -- 如果recv_buff的存储空间不足,会自动扩容
        local result = socket.rx(socket_client, recv_buff)
        -- 读取数据失败
        -- 有两种情况:
        -- 1、recv_buff扩容失败
        -- 2、socket client和server之间的连接断开
        if not result then
            log.error("tcp_client_receiver.proc", "socket.rx error")
            return false
        end

        -- 如果读取到了数据, used()就必然大于0, 进行处理
        if recv_buff:used() > 0 then
            log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
            -- 读取socket数据接收缓冲区中的数据,赋值给data
            local data = recv_buff:query()
            -- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
            sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
            table.insert(tab,data)
            -- 清空socket数据接收缓冲区中的数据
            recv_buff:del()
            -- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
        else
            break
        end
    end

    return true
end

return tcp_client_receiver

tcp_client_sender.lua

--[[
@module  tcp_client_sender
@summary tcp client socket数据发送应用功能模块 
@version 1.0
@date    2025.07.01
@author  朱天华
@usage
本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
1sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中
2tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列send_queue,逐条发送数据到server
3tcp client socket和server之间的连接如果出现异常tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列send_queue中未发送的数据
4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;

本文件的对外接口有3个:
1sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
   其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去
   demo项目中uart_app.lua和timer_app.lua中publish了这个消息
2tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用
3tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用
]]

local tcp_client_sender = {}

local libnet = require "libnet"

--[[
数据发送队列,数据结构为:
{
    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
}
data的内容为真正要发送的数据,必须存在;
func的内容为数据发送结果的用户回调函数,可以不存在
para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
]]
local send_queue = {}

-- tcp_client_main的任务名
tcp_client_sender.TASK_NAME = "tcp_client_main"

-- "SEND_DATA_REQ"消息的处理函数
local function send_data_req_proc_func(tag, data, cb)
    log.info("DATA",tag,data)
    -- 将原始数据增加前缀,然后插入到发送队列send_queue中
    table.insert(send_queue, {data=data, cb=cb})
    -- 通知tcp_client_main主任务有数据需要发送
    -- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
    sysplus.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
end

-- 数据发送应用逻辑处理入口
function tcp_client_sender.proc(task_name, socket_client)
    local send_item
    local result, buff_full

    -- 遍历数据发送队列send_queue
    while #send_queue>0 do
        -- 取出来第一条数据赋值给send_item
        -- 同时从队列send_queue中删除这一条数据
        send_item = table.remove(send_queue,1)

        -- 发送这条数据,超时时间15秒钟
        result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)

        -- 发送失败
        if not result then
            log.error("tcp_client_sender.proc", "libnet.tx error")

            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
            if send_item.cb and send_item.cb.func then
                send_item.cb.func(false, send_item.cb.para)
            end

            return false
        end

        -- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
        if buff_full then
            log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
            table.insert(send_queue, 1, send_item)
            return true
        end

        log.info("tcp_client_sender.proc", "send success")
        -- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
        if send_item.cb and send_item.cb.func then
            send_item.cb.func(true, send_item.cb.para)
        end
    end

    return true
end

-- 数据发送应用逻辑异常处理入口
function tcp_client_sender.exception_proc()
    -- 遍历数据发送队列send_queue
    while #send_queue>0 do
        local send_item = table.remove(send_queue,1)
        -- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
        if send_item.cb and send_item.cb.func then
            send_item.cb.func(false, send_item.cb.para)
        end
    end
end

-- 订阅"SEND_DATA_REQ"消息;
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)

return tcp_client_sender

将定位成功后的经纬度复制到此页面。可以查看位置信息和坐标系转换后的经纬度。

七、总结

本教程详细介绍了如何使用 Air780EHV 外挂 Air530W 开发板搭配 exgnss 扩展库使用 GNSS 功能,包括硬件连接、软件配置、代码编写等步骤。通过本教程的学习,读者应该能够掌握 Air530W 的使用

八、注意事项与常见问题

8.1、GNSS 定位经纬度不准确

1、坐标没有纠偏,参考:https://docs.openluat.com/file/GPS-Offset.html 进行纠偏处理

2、周围有比较高的障碍物,会导致定位误差

3、在开阔地带,正常情况下定位精度只能做到 5 米

4、不能在室内测试,必须到室外测试;如果只能在室内测试,可以淘宝搜索“GNSS 信号转发器”

8.2、Air530W 获取到的经纬度数据是基于什么坐标系

使用国际标准 WGS-84 坐标系,需要自己进行坐标系转换各 GNSS 坐标系说明以及转换方法

8.3、GPS 天线如何设计

参考:https://docs.openluat.com/air510w/product/shouce/

8.4、可视卫星、可用卫星有什么区别

可视卫星是当前区域,接收条件良好情况下,应该可以收到卫星信号的卫星。

可用卫星是当前已经收到信号并正在使用参与定位的卫星。

8.5、 GGA 和 RMC 应该用哪个

视具体情况而定,建议用 gga,信息相对更全面。

8.6、如何解读 NMEA 报文每个字段的含义

参考:NMEA-0183 协议简介

8.7、车载使用时需要天线引出到车顶上吗

1、挡风玻璃如果没有贴膜或者贴了不含金属材料的膜,可以放在挡风玻璃下,但是 GNSS 信号会有一定衰减,在万不得已的情况下,可以放在挡风玻璃下,最好再实际测试确认一下。 2、挡风玻璃如果贴了含有金属材料的膜,则不能放在挡风玻璃下,必须将天线到车顶。

8.8、如何输出原始 NMEA 数据

可以通过 exgnss.setup()接口的 bind 参数进行绑定。

local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        -- debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,780EGH和8000默认串口2
        -- uartbaud=115200,    --串口波特率,780EGH和8000默认115200
        bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
         ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把agps_autoopen设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- agps_autoopen=false 
        gnss_volgpio=21 --设置GNSS模块的供电脚,外挂GNSS模块需要设置,4G定位二合一的模块不需要设置
    }
    exgnss.setup(gnssotps)

8.9 GNSS 常见名词解释

[1]GNSS:混合定位,不同于 GPS 定位,狭义上讲的 GPS 系统,单指美国的 24 颗 GPS 卫星以及地面上 1 个主控站、3 个数据注入站和 5 个监测站及作为用户端的 GPS 接收机组成的一整套系统。GNSS 是指通过观测 GNSS 卫星获得坐标系内绝对定位坐标的测量技术。 GNSS 是所有导航定位卫星的总称,凡是可以通过捕获跟踪其卫星信号实现定位的系统,均可纳入 GNSS 系统的范围。国内用户接触最多的应该是美国的 24 颗 GPS 卫星,以及中国的北斗卫星(截至到 2023 年 5 月 17 日 10 时 49 分,中国已有五十六颗北斗导航卫星),其余还有俄罗斯 GLONASS、欧盟 GALILEO、 日本的准天顶卫星系统、印度的 IRNSS(独立的区域导航系统,覆盖印度领土及周边 1500 km 范围内,提供定位精度优于 20 米的服务)等其余定位系统。

[2]冷启动:指在一个陌生的环境下启动 GPS,直到 GPS 芯片和可用卫星联系并且计算出坐标的过程。以下几种情况开机均属冷启动:

  1. 初次开机使用时;
  2. 电池耗尽导致 GPS 芯片内星历信息丢失时;
  3. 关机状态下将接收机移动 1000 公里以上距离。

也就是说,冷启动是通过硬件方式的强制性启动,因为物理距离较远,或者时间间隔很久,GPS 芯片已经把内部的星历信息清除掉,或者内部的星历信息完全失效。GPS 接收机失去卫星参数,或者已经存在的参数和实际接收到卫星参数相差太多,导致 GPS 芯片无法靠星历快速搜星,所以必须从新获得卫星提供的坐标数据。

这也是很多定位器(譬如车载定位器)启动后,搜星时间长、定位耗时久的原因

[3]热启动:指在上次关机的地方没有过多移动过,且距离上次定位时间小于 1 个小时。再次定位时,GPS 芯片通过软件的方式,可以继续使用之前的星历快速搜星,实现秒定位。PS:普通的 GNSS 芯片,星历最长有效期为 12 小时,故此星历过期后,GPS 芯片无法使用星历实现快速定位。

[4]温启动:指距离上次定位时间超过 1 个小时的启动,搜星定位时间介于冷启动和热启动之间的情况。

譬如某时间使用过 GPS 定位实现 3D FIX,GPS 芯片内部生成星历(或者外部灌入 AGPS 数据),那么在 1 小时内启动 GPS 芯片进行定位的行为就属于温启动。启动后,GPS 芯片首先会输出上次的位置信息。因为上次关机前的经纬度和高度已知,但由于关机时间过长,卫星状态发生了变化,之前 3D FIX 时的卫星接受不到了,所以星历中参数中的若干颗卫星已经和 GPS 接收机失去了联系,GPS 芯片需要继续搜星补充位置信息,所以搜星的时间要长于热启动,短于冷启动。

[5]星历:是用于描述太空飞行体位置和速度的表达式———两行式轨道数据系统。卫星、航天器或飞行体一旦进入太空,即被列入 NORAD 卫星星历编号目录。列入 NORAD 卫星星历编号目录的太空飞行体将被终生跟踪。卫星、火箭残骸等飞行体成为太空垃圾时,仍被列入 NORAD 卫星编号目录,直到目标消失。卫星星历以开普勒定律的 6 个轨道参数之间的数学关系确定飞行体的时间、坐标、方位、速度等各项参数,具有极高的精度。卫星星历能精确计算、预测、描绘、跟踪卫星、飞行体的时间、位置、速度等运行状态;能表达天体、卫星、航天器、导弹、太空垃圾等飞行体的精确参数;能将飞行体置于三维的空间;用时间立体描绘天体的过去、现在和将来。卫星星历的时间按世界标准时间(UTC)计算。卫星星历定时更新。

[6]AGPS:辅助全球卫星定位系统(英语:Assisted Global Positioning System,简称:AGPS)指的是一种 GPS 的运行方式。它可以利用地面基地站的资讯,配合传统 GPS 卫星,让定位的速度更快

[7]有源天线:通常对于设备或车载机而言,由于设备与 GPS 接收模块之间往往有距离,考虑到安装的便利性可能会有超过 1 米的距离,在这种情况下我们只能选择有源 GPS 天线,由于天线长度的信号衰减需要进行补偿,一般有两级低噪声放大器(LNA)进行天线前端信号放大,放大后的信号经电缆输出,电缆同步提供 LNA 所需要的直流电压

由于天线收到的信号在有源天线接受头内完成信号接受与天线放大,并且远离 GPS 设备或其他电器设备,干扰源最小,而且安装位置由于天线距离延长安装位置可以选择非常理想的环境,所以实际使用时往往感觉信号较强

[8]无源天线:使用无源 GPS 天线时,由于只有一个陶瓷片接收天空的卫星信号,直接连接到模块的 RF-IN 脚,这种联接方式结构简单,而且标准的 25_25_4 的陶瓷片成本低廉,技术成熟,占空体积小,适合于强调紧凑型空间 GPS 导航产品,蓝牙 GPS,手机 GPS 及其他小型 GPS 消费类产品。

这种天线的布局是从天线的引脚直达模块的 RF-IN 脚,这根导线需要进行 50 欧阻抗匹配,而且在天线附近不能有电磁干扰,对 PCB 的设计及整机的 EMI 设计要求较高,但如果设计得优良的无源天线 GPS 产品同样有非常好的表现效果,而且功耗比较低,无需考虑天线自身的功耗。

[9]半边天以及开拓地带:GPS 卫星运行在距地 36000KM 的轨道上,信号强度相当弱(GPS 卫星的功率有多大?)。GPS 的民用 C/A 码从卫星发出来的时候信号只有 27W 左右,达到地球的时候在-158.5dBW 以上。用对数形式表示可能不直观,换算成十进制等于将近 0.0000000000000001W,相当小。所以,只有室外开阔的、无遮挡、晴好的地方,才能搜到更多的卫星,SNR 值更高(阴天都会有影响哦),GPS 芯片才能更快、更好的实现定位。

半边天一般指楼宇内窗边,打开窗户,只能搜到一半天空的卫星。

[10]定位纠偏:OpenLuat 的所有 GNSS 模块均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米甚至上百米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。

国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位,坐标转换可在合宙提供的坐标转换网站上直观的展示处理

[11]重捕:是指接收终端在丢失所接收信号状态下,从重新接收到信号开始,至终端设备输出符合定位精度要求的定位结果所需的时间。失锁重捕时间反映了在接收机信号失锁,定位中断后重新恢复定位的速度。失锁重捕时间短的接收机在易中断环境中(如隧道等)的定位性能好,因此失锁重捕时间可以有效评估车载终端的性能

[12]低噪声放大器:主要用于接收信号的前端,放大天线从空中接收到的微弱信号,降低噪声干扰,以供系统解调出所需的信息数据

8.10、静态漂移是什么,如抑制静态漂移?

1、静态漂移是什么?为什么会出现静态漂移

简单来说就是GNSS在静止时的定位会东飘西飘,无法固定在一个点,无法像运动状态时可以比较准确;从地图上来看的话,会看到无数个漂移的点在实际位置飘来飘去,我们通常把这种现象叫做GNSS静态漂移;

GNSS静态漂移无法根除,原因是:

卫星信号相关因素:

GPS卫星在发射信号后传播过程中会遇到多种干扰因素,包括大气层电离层变化、云层遮挡等天气影响;

卫星信号受到干扰或遮挡会导致定位不准确,特别是在高林立的城市环境或山区、森林地带;

环境干扰因素:

周边高大建筑物的多径反射造成信号误差;

网络传输不稳定可能导致定位数据丢失,造成设备在地图上显示"飞跃"现象;

2、如何抑制静态漂移

静态漂移目前主要靠两个办法抑制:

1.GNSS速度与位移一致性检查​: 通过exgnss.vtg()获取GNSS速度值,搭配经纬度信息一起发送到服务器端,服务器端做算法优化过滤,计算当前与上次定位点之间的平面位移距离。若位移量显著大于基于速度推算的理论最大合理位移(例如:位移 > 速度 × 时间间隔 × 容错系数),则认为本次定位数据存在跳变,不可信,系统将自动丢弃当前数据,继续沿用上一次的定位结果。

2.基于Gsensor的运动状态一致性检查: 外挂Gsensor的使用运动检测模式(参考Demo实现),持续检测设备动态。若在连续10秒内有效运动次数低于5次,则判定设备处于静止状态。在静止状态下,维持使用上一次的可信定位结果。