Linux:NTP服务
一、ping通外网
在开发板上使用NTP服务要确保 开发板能够ping外网!
ip addr show eth0 # 查看 eth0 是否有 IP 且 UP
ip route show # 查看默认路由是否存在
ping 192.168.100.1 # 测试能否到达网关
ip route replace default via 192.168.100.1 dev eth0
replace 会自动替换已经存在的默认路由,非常方便。确保ip route show 有下面这个节点
同时确保Linux主机开启下面这个内容:
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o enp2s0 -j MASQUERADE
之后板子就可以 ping 8.8.8.8
或 ping www.baidu.com
了。
如果域名不通,再配置DNS /etc/resolv.conf
加一行:nameserver 223.5.5.5 nameserver 8.8.8.8
如果重启后 /etc/resolv.conf
被重置,需要在 启动脚本里重新写入 DNS,例如在 /etc/rc.local
或网络配置脚本里加:
echo -e "nameserver 223.5.5.5\nnameserver 8.8.8.8" > /etc/resolv.conf
我设置了开机自启动配置脚本,脚本如下(我还挂载了NFS,如果不需要可以关掉):
#!/bin/sh# 配置板子 IP
ip addr add 192.168.100.10/24 dev eth0 2>/dev/null# 挂载 NFS
mountpoint -q /mnt/driver_project || mount -t nfs -o vers=3,nolock 192.168.100.1:/home/dd/nfs_share /mnt/driver_project/# 设置默认路由
ip route replace default via 192.168.100.1 dev eth0# 更新 DNS
if ! grep -q "223.5.5.5" /etc/resolv.conf; thenecho -e "nameserver 223.5.5.5\nnameserver 8.8.8.8" >> /etc/resolv.conf
fiexit 0
把这个脚本放到RK3568的/etc/init.d下,做一个软连接到里面的文件,以S开头。
按照上面步骤即可ping通外网并且开机自启动。
过一段时间发现ping不通执行手动清除:
ip addr flush dev eth0
ip addr add 192.168.100.10/24 dev eth0
ip route replace default via 192.168.100.1 dev eth0 metric 100
二、lvgl下NTP服务代码
这里是有关NTP的socket网络编程,需要注意下面几个点:
1、ntp_server
:NTP 服务器地址(如 ntp1.aliyun.com
)
2、NTP 使用 UDP 协议。SOCK_DGRAM
+ IPPROTO_UDP
3、
NTP 默认端口是 123。
4、
NTP 请求包固定 48 字节。
5、0x1b表示:00 011 011 00表示无警告 011表示NTPv3 011表示客户端模式。这是一个标准的 NTP 客户端请求包的起始字节。
6、NTP返回的时间戳在包的第 40~43 字节。
7、NTP 时间是从 1900-01-01 00:00:00 开始的秒数。
int sys_get_time_from_ntp(const char* ntp_server, int *year, int *month , int *day, int* hour, int *minute, int *second)
{struct addrinfo hints, *res = NULL;int sockfd = -1;memset(&hints , 0 , sizeof(hints));hints.ai_family = AF_UNSPEC;hints.ai_socktype = SOCK_DGRAM;hints.ai_protocol = IPPROTO_UDP; // 这里使用的是UDP/* 解析域名 */int gai_ret = getaddrinfo(ntp_server, "123", &hints, &res);if (gai_ret != 0) {fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_ret));return -1;}/* 建立UDP socket */sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);if (sockfd == -1) {perror("socket creation failed");freeaddrinfo(res);return -1;}// NTP packetunsigned char buf[48] = {0};buf[0] = 0x1b;ssize_t sent = sendto(sockfd, buf, sizeof(buf), 0, res->ai_addr, res->ai_addrlen);if (sent != (ssize_t)sizeof(buf)) {perror("sendto");close(sockfd);freeaddrinfo(res);return -1;}// 使用 select 做监听,看看sockfd是否有返回数据fd_set readfds;FD_ZERO(&readfds);FD_SET(sockfd, &readfds);struct timeval timeout = {2, 0}; // 2sint ret = select(sockfd + 1, &readfds, NULL, NULL, &timeout);if (ret == 0) {fprintf(stderr, "receive from NTP server timeout\n");close(sockfd);freeaddrinfo(res);return -1;} else if (ret < 0) {perror("select error");close(sockfd);freeaddrinfo(res);return -1;}ssize_t rec = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);if (rec < 0) {perror("recvfrom");close(sockfd);freeaddrinfo(res);return -1;} else if (rec < 48) {fprintf(stderr, "short ntp packet received\n");close(sockfd);freeaddrinfo(res);return -1;}close(sockfd);freeaddrinfo(res);uint32_t secsSince1900;memcpy(&secsSince1900, &buf[40], sizeof(secsSince1900));secsSince1900 = ntohl(secsSince1900); // 网络字节序转换为主机字节序。time_t unixTime = (time_t)(secsSince1900 - NTP_TIMESTAMP_DELTA);// set timezone to UTC+8setenv("TZ", "CST-8", 1);tzset();struct tm *timeinfo = localtime(&unixTime);if (!timeinfo) {perror("localtime failed");return -1;}if (year) *year = timeinfo->tm_year + 1900;if (month) *month = timeinfo->tm_mon + 1;if (day) *day = timeinfo->tm_mday;if (hour) *hour = timeinfo->tm_hour;if (minute) *minute = timeinfo->tm_min;if (second) *second = timeinfo->tm_sec;return 0;
}
主要的流程就是:建立UDP连接-->发送NTP请求包-->接收请求包-->解析请求包-->根据时区解析出正常的时间。
三、更新时间
void update_clock(lv_timer_t * timer)
{(void)timer;if (!time_label || !date_label) return;time_t current_time;struct tm *timeinfo;char time_str[64];char data_str[64];if (ntp_synced) {current_time = time(NULL);timeinfo = localtime(¤t_time); // 这里的current_time时同步之后的时间} else {static struct tm default_time = {.tm_year = 100, // 2000.tm_mon = 0,.tm_mday = 1,.tm_hour = 0,.tm_min = 0,.tm_sec = 0};timeinfo = &default_time;}// 使用 snprintfsnprintf(time_str, sizeof(time_str), "%02d:%02d:%02d",timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);snprintf(data_str, sizeof(data_str), "%04d-%02d-%02d",timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday);pthread_mutex_lock(&lvgl_mutex);lv_label_set_text(time_label, time_str);lv_label_set_text(date_label, data_str);pthread_mutex_unlock(&lvgl_mutex);
}
LVGL 本身不是线程安全的,要求所有 UI 的更新必须在同一个线程里执行,否则会出现:
内存冲突(不同线程同时修改 LVGL 内部的数据结构)
崩溃 / UI 显示异常
所以在在最后的时候修改值的文本信息加了一把锁。这样保证了:LVGL 内部的数据修改是原子化的,不会被并发打断。
void set_time(lv_obj_t *cell){// 创建并保存全局 date_label/time_labeldate_label = lv_label_create(cell);lv_label_set_text(date_label, "----/--/--");lv_obj_align(date_label, LV_ALIGN_CENTER, 0, -14);lv_obj_set_style_text_color(date_label, lv_color_hex(0xFFFFFF), 0);lv_obj_set_style_text_font(date_label, &lv_font_montserrat_20, 0);time_label = lv_label_create(cell);lv_label_set_text(time_label, "--:--");lv_obj_align(time_label, LV_ALIGN_CENTER, 0, 14);lv_obj_set_style_text_color(time_label, lv_color_hex(0xFFFFFF), 0);lv_obj_set_style_text_font(time_label, &lv_font_montserrat_28, 0);int year, month, day, hour, minute, second;if (sys_get_time_from_ntp("ntp.aliyun.com", &year, &month, &day, &hour, &minute, &second) == 0){ntp_synced = true;printf("NTP 时间同步成功:%04d-%02d-%02d %02d:%02d:%02d\n",year, month, day, hour, minute, second);struct tm timeinfo = {.tm_year = year - 1900,.tm_mon = month - 1,.tm_mday = day,.tm_hour = hour,.tm_min = minute,.tm_sec = second};time_t new_time = mktime(&timeinfo);// 如果你想设置系统时间,可以在这里调用 settimeofday(需 root 权限)// struct timeval tv = { .tv_sec = new_time, .tv_usec = 0 };// settimeofday(&tv, NULL);} else {ntp_synced = false;printf("NTP 时间同步失败, 使用默认时间\n");}// 创建 LVGL 定时器,并立即更新一次lv_timer_t *timer = lv_timer_create(update_clock, 1000, NULL);// 将 user_data 设为 NULL,因为 update_clock 使用全局 date_label/time_labeltimer->user_data = time_label; // v8.2 里直接赋值update_clock(timer);
}
这样就实现了在lvgl中的时间同步!
四、计算周几
使用系统自带函数mktime来计算
int sys_get_day_of_week(int year , int month , int day)
{ struct tm timeinfo = {0};timeinfo.tm_year = year - 1900;timeinfo.tm_mon = month - 1;timeinfo.tm_mday = day;timeinfo.tm_hour = 12;/* 自动计算星期几 */mktime(&timeinfo);return timeinfo.tm_wday;
}