在 ChibiOS 中使用 lwIP SNTP 客户端
嵌入式系统的常见任务是使用 RTC 为事件加时间戳。然而,系统架构师需要找到一种将设备 RTC 时间与外部时间源同步的方法。此外,设计者需要处理 RTC 时钟漂移问题,特别是对于长时间运行的设备。本文讨论使用 ChibiOS RTOS 的 STM32 设备的基于 lwIP+SNTP 的方法。本文的 lwIP 特定部分也适用于其他类型的微控制器。
对于高精度或长时间运行的应用,还必须考虑 RTC 时钟漂移。根据使用的时钟源,时钟频率可能显著偏离标称值。
例如在 STM32F4 上,你可以从以下来源派生 RTC 时钟:HSE/HSI 主振荡器 LSI 振荡器 * LSE 振荡器,即 32.768 kHz 外部晶体。
在大多数应用中,RTC 主要用于即使在主电源不可用期间也保持有效时间。因此,这些系统包括某种类型的备份电池(通常是 CR2032)驱动 RTC。在这种情况下,你显然不能从 HSE/HSI 派生 RTC 时钟,因为当主电源不可用时这些时钟不活动。
LSI 振荡器随设备免费提供,但它有一个大多数用户不知道的主要问题:它有极其大的容差值:根据 STM32F407 数据手册(表 34:LSI 振荡器特性),实际晶体频率范围为 17 到 47 kHz,标称值为 32 kHz。这相当于 -53.1 / +46.8 个百分点的容差,或超过 460000 ppm。我想不出任何可以处理这种容差级别的实际应用。虽然给出的容差是整个 -40 到 +105° 温度范围,但我可以确认在室温下高于 +-25%。
LSE 振荡器是唯一剩下的选择。所以记住在你的设计中包含 32.768 kHz 晶体。对于此特定用途,我认为晶体振荡器不是正确的选择,因为它们的功耗相对较高。
还有一些主要问题:如何最初将当前时间戳获取到设备上(取决于你的部署过程,通常不是问题)以及如何保持设备与外部时钟源同步。
后者在需要保持多个设备同步和/或需要近乎单调的时钟源时尤其相关。即使只有百分之一的时钟漂移,RTC 日期时间在一年内也会漂移超过三天。
对于连接到互联网或任何其他计算机网络的应用,lwIP contrib 仓库提供了完整的 SNTP 客户端。SNTP 是 NTP 协议的较低精度版本,仍可与任何普通 NTP 服务器一起使用。
然而,该库缺乏最基本的文档。因此,我提供了一个如何与 ChibiOS RTCD1 驱动程序一起使用的示例。
此处显示的头文件替换了库附带的 sntp.h 头文件。它包括与 RTCD1 驱动程序一起工作所需的编译时配置。对于此示例,我选择 NTP 服务器 swisstime.ethz.ch,因为它从我的位置有良好的 ping。我建议你从 NTP 池中选择合适的服务器。
#ifndef __SNTP_H__
#define __SNTP_H__
#ifdef __cplusplus
extern "C" {
#endif
//自定义配置
#include <hal.h>
#include <chrtclib.h>
#define SNTP_SERVER_ADDRESS "82.197.164.46" /* swisstime.ethz.ch */
#define SNTP_UPDATE_DELAY 90000 /* SNTP update every 90 seconds */
//ChibiOS RTC 驱动程序
#define SNTP_SET_SYSTEM_TIME(sec) rtcSetTimeUnixSec(&RTCD1, (sec))
#define SNTP_GET_SYSTEM_TIME(sec, us) \
do{uint64_t time = rtcGetTimeUnixUsec(&RTCD1);\
(sec) = time / 1000000;\
(us) = time % 1000000;}while(0)
void sntp_init(void);
void sntp_stop(void);
#ifdef __cplusplus
}
#endif
#endif /* __SNTP_H__ */
/*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
* Author: Simon Goldschmidt (lwIP raw API part)
* Modified by Uli Koehler for ChibiOS (2014)
*/配置非常基本,sntp.c 支持一些此处未配置的相当有用的功能 — 最显著的是支持多个 NTP 服务器。
第二步是正确配置 lwIP。这不是 lwIP 教程,我假设读者已经知道堆栈如何工作(如果不知道,这篇文章可能不是最好的起点)。为了避免与理想单调时钟的显著偏差问题,此示例每 90 秒发送一次 SNTP 请求,覆盖默认的 60 分钟。哪个更适合完全取决于你的应用。
可能会遇到两个常见陷阱,都位于 lwipopts.h 头文件中:
首先,SNTP 客户端永久消耗一个 UDP PCB。确保适当增加 MEMP_NUM_UDP_PCB 值。不用说,你需要启用 UDP 才能使 SNTP 工作。
其次,SNTP 客户端通过使用 lwIP sys_timeout() 功能重复调用发送函数。支持的并发超时数的默认选项相当有限,可能不够。我建议将 MEMP_NUM_SYS_TIMEOUT 值至少增加一。
一旦满足这些要求,只需调用
sntp_init();在初始化 lwIP 线程和事件循环后。如果你的配置正确,当收到 SNTP 响应时 RTC 将被更新。不要重复调用 sntp_init(),它会自己使用 lwIP 事件循环调用发送函数。
注意 STM32F4 设备具有毫秒精度的 RTC。SNTP 很少提供优于 1 毫秒的精度。因此,毫秒 RTC 值可能不显著。
如果应用程序崩溃,很可能某些 lwIP 限制已被超过。在这种情况下,请参见此之前的 TechOverflow 文章详细描述如何向 ChibiOS 和 LWIP 断言添加 ARM 硬件断点。