一、ping通外网
在开发板上使用NTP服务要确保 开发板能够ping外网!
bash
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主机开启下面这个内容:
bash
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
或网络配置脚本里加:
bash
echo -e "nameserver 223.5.5.5\nnameserver 8.8.8.8" > /etc/resolv.conf
我设置了开机自启动配置脚本,脚本如下(我还挂载了NFS,如果不需要可以关掉):
bash
#!/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; then
echo -e "nameserver 223.5.5.5\nnameserver 8.8.8.8" >> /etc/resolv.conf
fi
exit 0
把这个脚本放到RK3568的/etc/init.d下,做一个软连接到里面的文件,以S开头。
按照上面步骤即可ping通外网并且开机自启动。
过一段时间发现ping不通执行手动清除:
bash
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 开始的秒数。
cpp
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 packet
unsigned 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}; // 2s
int 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+8
setenv("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请求包-->接收请求包-->解析请求包-->根据时区解析出正常的时间。
三、更新时间
cpp
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;
}
// 使用 snprintf
snprintf(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 内部的数据修改是原子化的,不会被并发打断。
cpp
void set_time(lv_obj_t *cell){
// 创建并保存全局 date_label/time_label
date_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_label
timer->user_data = time_label; // v8.2 里直接赋值
update_clock(timer);
}
这样就实现了在lvgl中的时间同步!
四、计算周几
使用系统自带函数mktime来计算
cpp
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;
}