一、什么是 NTP
1.1 、NTP
网络时间协议(英语:Network Time Protocol,简称 NTP)是在数据网络潜伏时间可变的计算机系统之间通过分组交换进行时钟同步的一个网络协议。自 1985 年以来,NTP 是仍在使用的最古老的互联网协议之一。NTP 由特拉华大学的 David L. Mills 设计。
NTP 意图将所有参与计算机的协调世界时(UTC)时间同步到几毫秒的误差内。它使用 Marzullo 算法的修改版来选择准确的时间服务器,其设计旨在减轻可变网络延迟造成的影响。NTP 通常可以在公共互联网保持几十毫秒的误差,并且在理想的局域网环境中可以实现超过 1 毫秒的精度。不对称路由和拥塞控制可能导致 100 毫秒(或更高)的错误。
该协议通常描述为一种主从式架构,但它也可以用在点对点网上中,对等体双方可将另一端认定为潜在的时间源。发送和接收时间戳采用用户数据报协议(UDP)的通信端口 123 实现。这也可以使用广播或多播,其中的客户端在最初的往返校准交换后被动地监听时间更新。NTP 提供一个即将到来闰秒调整的警告,但不会传输有关本地时区或夏时制的信息。
1.2 、SNTP
简单网络时间协议(Simple Network Time Protocol),由 NTP 改编而来,主要用来同步因特网中的计算机时钟。在 RFC2030 中定义。
SNTP 是简化版的 NTP,NTP(Network Time Protocol,网络时间协议)是用于同步系统时间的协议。它通过网络连接多个设备,确保这些设备的系统时钟保持一致。SNTP 相比较 NTP 主要区别在于精确度和复杂性。SNTP 通常用于不需要高精度同步的设备。而 NTP 则用于要求较高时间精度的场景。
虽然现在的程序名字使用的是 SNTP,但现在底层的实现已经改成 NTP 了,只是名字还没改过来。对模组来说, 没有区别。
模组通信中的 NTP,全称是网络时间协议(Network Time Protocol),核心作用是通过网络让模组(如物联网模组、工业通信模组)与标准时间服务器同步,获取精确的系统时间,解决模组本地时钟漂移、时间不准确的问题。
1.3 、SNTP 在模组通信中的角色
工作原理:
- 模组(NTP 客户端)通过蜂窝网络(4G)、Wi-Fi 等连接到 NTP 服务器(公共服务器,或私有服务器);
- 模组向服务器发送时间请求,携带自身本地时间戳;
- 服务器接收请求后,返回自身精确时间(含服务器时间戳、延迟补偿信息);
- 模组根据往返延迟计算误差,校准本地时钟,完成时间同步。
作用:
- 同步模组本地时间:模组内置时钟(RTC)长期运行会出现 "漂移"(时间偏差),NTP 可通过网络校准,使时间误差控制在毫秒级甚至微秒级;
- 保障数据时序有效性:模组上报的传感器数据、日志、事件等需精确时间戳,NTP 同步后的时间能确保数据时序可追溯、可分析;
- 支持定时任务执行:依赖精准时间的场景(如定时上报数据、定时唤醒设备),需 NTP 提供时间基准。
模组通信中的典型应用场景:
- 物联网数据采集:传感器数据上报时,用 NTP 同步的时间戳标记采集时间,便于后台分析数据时序关系;
- 日志审计:模组运行日志、故障日志需精确时间,方便定位问题发生时间点;
- 远程控制与调度:后台向模组下发定时任务(如凌晨 2 点执行设备自检),需模组时间与后台一致;
- 合规要求:部分行业(如金融、能源)对数据时间精度有合规要求,NTP 是满足要求的核心方案。
模组使用 NTP 的关键注意点:
- 网络依赖:需具备网络连接能力(蜂窝、Wi-Fi 、以太网等),无网络时无法同步;
- 功耗优化:低功耗模组会减少 NTP 同步频率(如每天 1 次),避免频繁联网耗电;
二、功能演示概述
本文演示合宙 4G 模组使用 LuatOS 开发时, 4G 通信中 NTP 网络协议的应用功能.
使用 Air8000 开发板下载 Air8000 的 LuatOS 示例代码中 NTP 的例程进行验证,例程中实现的功能核心业务逻辑为:
- 判断是否联网
- 网络就绪后开始时间同步
- 时间同步成功,获取本地时间和 UTC 时间,按默认间隔时间循环打印获取的时间信息
- 时间同步失败,打印提醒
三、准备硬件环境
3.1 参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。
3.2 Air8000 开发板一块 + 可上网的 sim 卡一张 +4g 天线一根 +wifi 天线一根 + 网线一根:
- sim 卡插入开发板的 sim 卡槽
- 天线装到开发板上
- 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
3.3 TYPE-C USB 数据线一根 ,Air8000 开发板和数据线的硬件接线方式为:
- Air8000 开发板通过 TYPE-C USB 口供电;(外部供电/USB 供电 拨动开关 拨到 USB 供电一端)
- TYPE-C USB 数据线直接插到开发板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
四、准备软件环境
在开始实践本示例之前,先准备一下软件环境:
-
Luatools 工具,如果是第一次使用 Luatools 工具,请仔细阅读此链接教程。
-
本demo开发测试时使用的固件为LuatOS-SoC_V2016_Air8000_1.soc,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;
-
luatos 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/ntp
1)、main.lua:主程序入口;
2)、netdrv_device.lua:网卡驱动设备,可以配置使用 netdrv 文件夹内的五种网卡(单 4g 网卡,单 wifi 网卡,单 spi 以太网卡,多网卡,pc 模拟器上的网卡)中的任何一种网卡;
3)、netdrv 文件夹:五种网卡,单 4g 网卡、单 wifi 网卡、单 spi 以太网卡、多网卡、pc 模拟器上的网卡,供 netdrv_device.lua 加载配置;
4)、ntp_test.lua: 功能演示核心脚本,联网、时间同步、获取时间等,在 main.lua 中加载运行。
- lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到 Air8000 开发板,按照链接中 第 15 页的固件升级 操作说明将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。
五、API 接口说明
六、示例代码和功能展示
6.1 流程介绍
1、搭建好硬件环境
2、demo 脚本代码 netdrv_device.lua 中,按照自己的网卡需求启用对应的 Lua 文件
- 如果需要单 4G 网卡,打开 require "netdrv_4g",其余注释掉
- 如果需要单 WIFI STA 网卡,打开 require "netdrv_wifi",其余注释掉;同时 netdrv_wifi.lua 中的 wlan.connect("茶室-降功耗,找合宙!", "Air123456", 1),前两个参数,修改为自己测试时 wifi 热点的名称和密码;注意:仅支持 2.4G 的 wifi,不支持 5G 的 wifi
- 如果需要以太网卡,打开 require "netdrv_eth_spi",其余注释掉
- 如果需要多网卡,打开 require "netdrv_multiple",其余注释掉;同时 netdrv_multiple.lua 中的 ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时 wifi 热点的名称和密码;注意:仅支持 2.4G 的 wifi,不支持 5G 的 wifi
3、如果使用自定义 NTP 服务器 地址,脚本文件 ntp_test.lua 中,在 ntp_servers 表中修改为自己的服务器地址,并在 sntp_sync_loop()函数中,注释 sntp 时间同步方式 2,打开方式 1
4、Luatools 烧录内核固件和修改后的 demo 脚本代码
5、烧录成功后,代码会自动运行,查看打印日志,如果正常运行,会打印网络就绪、时间同步成功、本地时间以及 URC 时间等信息,如下 log 显示:
6.2 代码和 log
- main.lua 代码示例(点击查看 ntp 的完整 demo)
lua
PROJECT = "ntp_demo"
VERSION = "001.000.000"
-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)
-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
if wdt then
--配置喂狗超时时间为9秒钟
wdt.init(9000)
--启动一个循环定时器,每隔3秒钟喂一次狗
sys.timerLoopStart(wdt.feed, 3000)
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)
-- 加载网络驱动设备功能模块,在该文件中修改自己使用的联网方式
require"netdrv_device"
--加载ntp_test功能模块
require "ntp_test"
-- 启动系统调度(必须放在最后)
sys.run()
- 核心代码部分(点击查看 ntp 的完整 demo)
lua
-- 自定义NTP服务器列表,可选配置,默认使用内置的ntp服务器地址ntp.aliyun.com
local ntp_servers = {
"ntp.aliyun.com",
"ntp.air32.cn",
"time1.cloud.tencent.com"
}
--本地时间是指:当前时区的时间,默认是东八区北京时间,可以通过rtc.timezone接口查询或者设置时区
--UTC时间是指:0时区的时间
--东八区的时间是在UTC时间的基础上增加8个小时
-- 打印时间信息的工具函数
local function print_time_details()
--设置为东3区时间
--rtc.timezone(12)
--获取东3区的时间字符串
--东3区时间字符串 Fri Oct 24 12:01:20 2025
--log.info("sntp", "东3区时间字符串", os.date())
-- 获取本地时间字符串
-- 本地时间字符串 Fri Oct 24 17:01:15 2025
log.info("sntp", "本地时间字符串", os.date())
-- 获取UTC时间字符串
-- UTC时间字符串 Fri Oct 24 09:01:15 2025
log.info("sntp", "UTC时间字符串", os.date("!%c"))
-- 格式化本地时间字符串
-- 本地时间字符串 2025-10-24 17:01:15
log.info("sntp", "格式化本地时间字符串", os.date("%Y-%m-%d %H:%M:%S"))
-- 格式化UTC时间字符串
-- UTC时间字符串 2025-10-24 09:01:15
log.info("sntp", "格式化UTC时间字符串", os.date("!%Y-%m-%d %H:%M:%S"))
-- RTC时钟原始数据(UTC时间)
local rtc_time = rtc.get()
-- RTC时钟(UTC) {"year":2025,"min":1,"hour":9,"mon":10,"sec":15,"day":24}
log.info("sntp", "RTC时钟(UTC)", json.encode(rtc_time))
-- 本地时间戳(秒级)
--本地时间戳 1761296475
log.info("sntp", "本地时间戳", os.time())
-- 获取本地时间的table
-- 本地时间结构 {"wday":6,"min":1,"yday":297,"hour":17,"isdst":false,"year":2025,"month":10,"sec":15,"day":24}
local local_struct_time = os.date("*t")
log.info("sntp", "本地时间结构", json.encode(local_struct_time))
--结构时间转时间戳 1761325275
log.info("sntp", "结构时间转时间戳", os.time(local_struct_time))
end
-- 打印高精度时间戳
local function print_high_precision_time()
local ntp_time = socket.ntptm()
if ntp_time and ntp_time.tsec then
--tm数据 {"sms":89,"tms":320,"vaild":true,"tsec":1761296475,"lms":231,"ndeley":28,"lsec":18,"ssec":1761296457}
log.info("tm数据", json.encode(ntp_time))
-- 格式化:秒.毫秒
--高精度时间戳 1761296475.320
log.info("sntp", "高精度时间戳", string.format("%u.%03d", ntp_time.tsec, ntp_time.tms))
else
log.warn("sntp", "高精度时间戳获取失败")
end
end
-- SNTP同步主逻辑
local function sntp_sync_loop()
--查看网卡适配器的联网状态是否IP_READY,true表示已经准备好可以联网了,false暂时不可以联网
while not socket.adapter(socket.dft()) do
log.warn("sntp", "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("sntp", "recv IP_READY", socket.dft())
while true do
log.info("sntp", "开始同步:")
-- 方式1:使用自定义服务器列表
-- log.info("sntp", "开始同步,服务器列表:", json.encode(ntp_servers))
-- 选择自己要用的的服务器
--socket.sntp(ntp_servers[2])
-- 方式2: 使用默认的ntp服务器地址:ntp.aliyun.com
socket.sntp()
-- 等待同步结果(成功:NTP_UPDATE,失败:超时)
local sync_success = sys.waitUntil("NTP_UPDATE", 5000)
if sync_success then
--ntp同步成功之后,已经自动将系统时间设置为本地时间
--在此处的脚本中,不需要再调用任何时间设置接口去手动设置时间
--本地时间:默认为东八区北京时间,如果使用rtc.timezone(zone)设置过本地时间,则本地时间为自己设置过的时区对应的时间
-- 同步成功:打印时间信息
log.info("sntp", "时间同步成功")
print_time_details()
print_high_precision_time()
--时间同步成功
--再等待1小时再次发起下次时间同步,这里的等待时长可以按自己的需求修改。
sys.wait(3600*1000)
else
log.warn("sntp", "时间同步失败")
--时间同步失败
--再等待10秒重新发起时间同步,这里的等待时长可以按自己的需求修改。
sys.wait(10*1000)
end
end
end
-- 订阅NTP错误消息
sys.subscribe("NTP_ERROR", function(err_info)
log.error("sntp", "同步过程发生错误", err_info or "未知错误")
end)
-- 启动SNTP同步任务
sys.taskInit(sntp_sync_loop)
- 例程 log 打印如下:
lua
[2025-11-20 15:13:07.472][000000000.677] I/user.main ntp_demo 001.000.000
[2025-11-20 15:13:07.520][000000000.733] W/user.sntp wait IP_READY 1 3
[2025-11-20 15:13:08.380][000000001.734] W/user.sntp wait IP_READY 1 3
[2025-11-20 15:13:09.377][000000002.735] W/user.sntp wait IP_READY 1 3
[2025-11-20 15:13:09.872][000000003.184] D/mobile cid1, state0
[2025-11-20 15:13:09.874][000000003.185] D/mobile bearer act 0, result 0
[2025-11-20 15:13:09.880][000000003.185] D/mobile NETIF_LINK_ON -> IP_READY
[2025-11-20 15:13:09.883][000000003.186] I/user.netdrv_4g.ip_ready_func IP_READY 10.151.30.113 255.255.255.255 0.0.0.0 nil
[2025-11-20 15:13:09.889][000000003.187] I/user.sntp recv IP_READY 1 3
[2025-11-20 15:13:09.898][000000003.187] I/user.sntp 开始同步:
[2025-11-20 15:13:09.906][000000003.188] D/sntp query ntp.aliyun.com
[2025-11-20 15:13:09.910][000000003.188] dns_run 676:ntp.aliyun.com state 0 id 1 ipv6 0 use dns server0, try 0
[2025-11-20 15:13:09.914][000000003.237] dns_run 693:dns all done ,now stop
[2025-11-20 15:13:09.922][000000003.249] D/mobile TIME_SYNC 0
[2025-11-20 15:13:09.926][000000003.250] I/user.sntp 时间同步成功
[2025-11-20 15:13:09.929][000000003.251] I/user.sntp 本地时间字符串 Thu Nov 20 15:13:10 2025
[2025-11-20 15:13:09.935][000000003.251] I/user.sntp UTC时间字符串 Thu Nov 20 07:13:10 2025
[2025-11-20 15:13:09.940][000000003.252] I/user.sntp 格式化本地时间字符串 2025-11-20 15:13:10
[2025-11-20 15:13:09.943][000000003.253] I/user.sntp 格式化UTC时间字符串 2025-11-20 07:13:10
[2025-11-20 15:13:09.947][000000003.254] I/user.sntp RTC时钟(UTC) {"year":2025,"min":13,"hour":7,"mon":11,"sec":10,"day":20}
[2025-11-20 15:13:09.952][000000003.254] I/user.sntp 本地时间戳 1763622790
[2025-11-20 15:13:09.957][000000003.255] I/user.sntp 本地时间结构 {"wday":5,"min":13,"yday":324,"hour":15,"isdst":false,"year":2025,"month":11,"sec":10,"day":20}
[2025-11-20 15:13:09.960][000000003.255] I/user.sntp 结构时间转时间戳 1763651590
[2025-11-20 15:13:09.963][000000003.256] I/user.tm数据 {"sms":0,"tms":256,"tsec":3,"lms":256,"ndeley":0,"lsec":3,"ssec":0}
[2025-11-20 15:13:09.973][000000003.257] I/user.sntp 高精度时间戳 3.256
[2025-11-20 15:13:09.977][000000003.319] D/sntp Unix timestamp: 1763622790
- luatools 页面显示如下:

七、总结
本文档主要介绍 4G 通信中 ntp 通信的应用。
结合 demo 例程讲解了 ntp 基本原理,介绍了 ntp 主要 API,旨在最简单的快速上手 Air8000 的 LuatOS 的 ntp 开发.
八、常见问题
1、NTP 同步时间后,这个 clock 精度高吗,需要多久同步一次
并不能保证任何时间任何地点都能百分百同步到正确的时间。 所以,如果用户项目中的业务逻辑严格依赖于时间同步功能 则不要使用本功能模块,建议使用自己的应用服务器来同步时间。
2、多长时间 NTP 同步一次
正常使用, 一小时一次, 已经足够了, 甚至 1 天一次也可以。
3、这个函数 socket.sntp()后每次在程序中调用 os.time 也是实时时间了吗?
是的,只要时间同步成功了,就是实时时间了。