eefocus_3914144 发表于 2026-7-31 22:47:12

【瑞萨RA-eco-RA6M4评测】Zephyr 为了较时,我用上了NTP

# DA16200 NTP (SNTP) 时间同步

> 本文档说明如何利用 DA16200 模块内置的 SNTP 客户端,在 WiFi 连网后自动同步网络时间,并把时间写入 Zephyr 系统时钟,让 OLED 滚动时钟上电即显示真实时间(替代原来手动 `date set` 的方式)。
>
> DA16200 整体移植见 [【瑞萨RA-eco-RA6M4评测】Zephyr驱动DA16200 wifi模块 - #板卡评测 - 与非网](https://www.eefocus.com/forum/thread-236025-1-1.html)

---




## 目录

1. [原理](#1-原理)
2. [相关 AT 命令](#2-相关-at-命令)
3. [代码实现](#3-代码实现)
4. [配置项](#4-配置项)
5. [运行效果](#5-运行效果)
6. [注意事项与排错](#6-注意事项与排错)

---

## 1. 原理

时间同步分两段,DA16200 模块和 MCU 各管一半:

Internet NTP 服务器
│SNTP 协议 (UDP 123)

DA16200 模块 ── 内置 SNTP 客户端,自动对时并维护本地时钟
│AT+TIME=? 查询(UART)

RA6M4 (MCU) ── 解析 +TIME: 响应,sys_clock_settime() 写入系统时钟
│time(NULL),main.c 每秒读取

OLED 滚动时钟显示真实时间
!(https://www.eefocus.com/forum/data/attachment/forum/202607/31/224702xfy0tj0epyjaetwe.png)


要点:

- **模块侧**:`AT+NWSNTP=1` 启动 SNTP 客户端后,模块自动与服务器对时,且配置写入 NVRAM——模块重启后自动重新对时,无需 MCU 干预。
- **MCU 侧**:只在(重)连网成功后从模块读一次时间写入 Zephyr 系统时钟(与 shell `date set` 相同的内核 API `sys_clock_settime`)。`main.c` 每秒读 `time(NULL)`,时钟跳变后滚动时钟自动跟上,**主程序无需任何改动**。

---

## 2. 相关 AT 命令

出自 UM-WI-003《DA16200 AT Command User Manual》:

| 命令 | 格式 | 说明 |
|------|------|------|
| 启动 SNTP | `AT+NWSNTP=1,<服务器>,<周期秒>` | 启动 SNTP 客户端;周期可选(60~131072 秒,默认 86400)。启动即对时,并写 NVRAM(重启自动启动) |
| 停止 SNTP | `AT+NWSNTP=0` | 停止并清除 NVRAM 自动启动标志 |
| 查询 SNTP 状态 | `AT+NWSNTP` | 返回 `+NWSNTP:<状态>` |
| 指定服务器 | `AT+NWSNS=<服务器>` / `AT+NWSNS1/2` | 最多可配 3 个服务器,失败时轮询 |
| 设对时周期 | `AT+NWSNUP=<秒>` | 单独设置重同步周期 |
| 时区 | `AT+TZONE=<秒>` | GMT 偏移(-43200 ~ 43200),中国 UTC+8 = **28800** |
| 查询时间 | `AT+TIME=?` | 返回 `+TIME:,` |
| 手动设时 | `AT+TIME=<日期>,<时间>` | 无网时也可手动写模块时钟 |

实测响应示例:

```
AT+TIME=?
+TIME:2026-07-31,22:23:05
OK
```


---

## 3. 代码实现

实现对用户不可见,封装在 `src/drivers/da16200.c` 的 `sync_time_from_module()` 中,由连接管理线程在**每次连网成功后**自动调用:
```

da16200_connect_ap() 成功


AT+TZONE=28800                      时区 UTC+8(尽力而为)


AT+NWSNTP=1,ntp.aliyun.com,3600   启动 SNTP(失败直接放弃对时,不影响联网)


轮询 AT+TIME=?(每秒一次,最多 15 次)
│解析 +TIME:yyyy-mm-dd,hh:mm:ss
│年份 < 2024 视为未同步完成,继续等

timeutil_timegm() 转 time_t


sys_clock_settime(SYS_CLOCK_REALTIME, &ts)   写入 Zephyr 系统时钟

```
关键设计:

- **不引入新 Kconfig**:`sys_clock_settime()` 是 Zephyr 原生系统调用(`zephyr/sys/clock.h`),与 shell `date set` 同源,无需开启 POSIX 选项。
- **有效性判断**:模块未完成首次对时前 `AT+TIME=?` 返回的是出厂默认日期(远早于 2024),驱动以 `年份 >= 2024` 作为同步完成判据,最多等 15 秒。
- **对时失败不致命**:SNTP 启动失败或超时只打印日志,WiFi 连接状态和其余功能不受影响。
- **重连自动补对时**:断线重连成功后线程会再次调用 `sync_time_from_module()`,长时间运行的时钟漂移随之修正(模块侧 SNTP 也按 3600 秒周期自行重同步)。

```

/* Sync the Zephyr system clock from the module after the AP join: set its

* timezone, start the SNTP client, then poll AT+TIME=? until the module
* reports a sane date and copy it into SYS_CLOCK_REALTIME (same kernel API
* the "date set" shell command uses). main.c reads time(NULL) every second,
* so the rolling clock picks up the real time on its own. */
static void sync_time_from_module(void)
{
char cmd;

snprintf(cmd, sizeof(cmd), "AT+TZONE=%d\r\n", DA16200_TIMEZONE_SEC);
(void)send_cmd(cmd, "OK", NULL, 500, 2, 200);

snprintf(cmd, sizeof(cmd), "AT+NWSNTP=1,%s,3600\r\n", DA16200_SNTP_SERVER);
if (send_cmd(cmd, "OK", NULL, 500, 3, 200) < 0) {
printf("da16200: SNTP start failed\n");
return;
}

for (int i = 0; i < 15; i++) {
k_msleep(1000);


if (send_cmd("AT+TIME=?\r\n", "+TIME:", "OK", 500, 1, 0) < 0) {
continue;
}

const char *p = strstr(resp, "+TIME:");
int yr, mo, dy, hh, mm, ss;

if (p != NULL &&
sscanf(p + 6, "%d-%d-%d,%d:%d:%d",
&yr, &mo, &dy, &hh, &mm, &ss) == 6 &&
yr >= 2024) {
struct tm tm = {
.tm_year = yr - 1900,
.tm_mon= mo - 1,
.tm_mday = dy,
.tm_hour = hh,
.tm_min= mm,
.tm_sec= ss,
};
struct timespec ts = { .tv_sec = timeutil_timegm(&tm) };

sys_clock_settime(SYS_CLOCK_REALTIME, &ts);
printf("da16200: time synced %04d-%02d-%02d %02d:%02d:%02d\n",
yr, mo, dy, hh, mm, ss);
return;
}


}
printf("da16200: time sync timeout\n");
}
`

```
---

## 4. 配置项

都在 `src/drivers/da16200.h` 顶部:

```c
#define DA16200_SNTP_SERVER    "ntp.aliyun.com"   /* SNTP 服务器 */
#define DA16200_TIMEZONE_SEC   28800            /* 时区偏移秒,UTC+8 */
```

- 国内推荐服务器:`ntp.aliyun.com`、`cn.pool.ntp.org`、`ntp.tencent.com`;海外可用 `pool.ntp.org`。
- 时区按秒计:UTC+8 = 8×3600 = 28800;UTC+9 = 32400;UTC-5 = -18000。
- SNTP 重同步周期(代码中固定为 3600 秒)如需调整,改 `da16200.c` 中 `AT+NWSNTP=1,%s,3600` 一处即可。

---

## 5. 运行效果

烧录后串口日志:

```
da16200: connected, IP 192.168.3.45
da16200: time synced 2026-07-31 22:23:05
```

OLED 滚动时钟在连网成功后数秒内从 `00:00:xx` 跳转为当前时间。

---

## 6. 注意事项与排错

| 现象 | 原因 | 处理 |
|------|------|------|
| `da16200: SNTP start failed` | `AT+NWSNTP` 未回 OK | 多为瞬时问题,下次重连会重试;持续出现检查服务器域名拼写 |
| `da16200: time sync timeout` | 15 秒内模块没同步到时间 | 路由器无外网 / UDP 123 被拦 / DNS 解析失败。换服务器(如直接填 IP `203.107.6.88`),或检查路由器出口 |
| 时间对了但差 8 小时 | `AT+TZONE` 被拒或时区宏不对 | 确认 `DA16200_TIMEZONE_SEC=28800`;模块时区只影响 `AT+TIME` 输出,不影响 SNTP 本身 |
| 断电重上电后时间先跳回 00:00 | 正常:MCU 系统时钟不保存 | 连网成功后自动重新对时(几秒到十几秒) |
| 想彻底关闭自动对时 | — | 把 `conn_mgr_thread` 中 `sync_time_from_module();` 一行注释掉即可 |

补充说明:

- 模块侧 SNTP 配置存于 NVRAM,但本驱动每次连网都会执行 `AT+DEFAP`(清空 NVRAM)后重新下发配置,因此配置始终以代码为准,不存在"旧配置残留"问题。
- MCU 系统时钟易失(无电池 RTC),每次上电都依赖这次对时;如需断电守时,另行启用工程里预留的硬件 RTC(见 PORTING.md §4.6)。

---


页: [1]
查看完整版本: 【瑞萨RA-eco-RA6M4评测】Zephyr 为了较时,我用上了NTP