本帖最后由 qiao--- 于 2024-6-30 02:14 编辑
前言:在我的第二期测评我们成功的移植了LVGL框架,本期测评就可以利用这个框架开发出一些好看的UI。本人的审美能力欠缺,UI制作的可能并不是很好看(这是UI工程师干的活),帖友们不要介意,重要的是开发过程。本期测评将分为以下三个部分的:
- 用GUI_Guider开发一个简易界面并移植运行
- 写一个C语言程序读出系统信息(ip地址,cpu占用率,cpu温度等)
- 将以上两者综合移植
1.用GUI_Guider开发一个简易界面并移植运行
经常看我的测评的贴友应该知道,GUI_Guider是我的老朋友了,我经常用这个软件开发我的lvgl应用。里面自带模拟器开发起来方便,就是里面的控件有点少,不过也够用了。有对GUI_Guider不熟悉的贴友可以看我之前的文章:
【ACM32G103RCT6】8-基于RTC和LVGL的手机时间界面 https://bbs.eeworld.com.cn/thread-1270419-1-1.html
【Sipeed 博流BL808全能板】5-手表界面开发 https://bbs.eeworld.com.cn/thread-1273077-1-1.html
这两篇文章讲了GUI_Guider工程的创建和移植,但是不是Linux下的移植,本期 测评将移植于Linux系统。
在GUI_Guider创建一个工程后简单添加一些控件,并运行模拟器,我的效果如下图所示:
成功运行,接下来就是把我们这个工程移植到我们之前的lvgl框架中。移植步骤还是移植下面两个文件夹,由于我的custom文件夹下没有内容,我就不移植了,我就只移植generated这一个文件夹。
将这个文件夹复制到我们第二期测评拉下来代码的lvgl目录中,目录结构如下所示:
此时这个项目还不知道我们移植这个文件的.c和.h文件的位置,因此还要修改Makefile文件
修改lvgl目录下的lvgl.mk文件,添加以下内容:
include $(LVGL_DIR)/$(LVGL_DIR_NAME)/generated/generated.mk
在generated目录下的generated.mk文件添加以下内容
PRJ_DIR ?= $(LVGL_DIR)/$(LVGL_DIR_NAME)
修改完成后我们还需要修改我们的main.c中的代码,调用我们UI代码。代码主要是添加一个头文件,然后调用系统生成的UI创建代码。
然后进行编译make -j4 ,就可以编译成功。我们运行这个程序,效果如下所示:
但是这个UI里面的数据不是动态显示的,我们还要添加一些代码。
2.写一个C语言程序读出系统信息(ip地址,cpu占用率,cpu温度等)
由于上面的代码不能动态显示,我们还需要写一个demo,读出系统的信息,最后将他移植到我们项目中。Linux 的一些系统信息都是通过文件的形式来提供接口的,我们只需要通过文件IO来访问这些接口就行了。我的示例代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <time.h>
#define MAX_BUFFER_SIZE 256
#define MAX_IP_LENGTH 16 // 定义IPv4地址的最大长度为15个字符,加上字符串终止符'\0'
// 实现获取本地IP地址的函数
int get_local_ip(char *ip_buffer) {
int fd; // 套接字描述符
struct ifreq ifr; // 网络接口请求结构体
// 创建一个套接字
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket"); // 输出错误信息并返回
return -1;
}
// 指定要查询的网络接口(这里使用eth0,可根据实际情况修改)
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ - 1); // 拷贝接口名到请求结构体
// 获取接口的IP地址
if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
perror("ioctl"); // 输出错误信息并返回
close(fd); // 关闭套接字
return -1;
}
// 将获取到的IP地址转换成字符串形式并存储在ip_buffer中
inet_ntop(AF_INET, &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr, ip_buffer, INET_ADDRSTRLEN);
close(fd); // 关闭套接字
return 0; // 返回成功
}
// 获取 CPU 温度
float get_cpu_temperature() {
FILE *fp;
float temp = 0.0;
fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
if (fp == NULL) {
perror("Error opening temperature file");
return -1.0;
}
fscanf(fp, "%f", &temp);
fclose(fp);
return temp / 1000.0; // 转换为摄氏度
}
// 获取 CPU 占用率
float get_cpu_usage() {
FILE *fp;
char buffer[MAX_BUFFER_SIZE];
long double a[4], b[4];
float usage;
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
perror("Error opening CPU stat file");
return -1.0;
}
fgets(buffer, sizeof(buffer), fp);
sscanf(buffer, "cpu %Lf %Lf %Lf %Lf", &a[0], &a[1], &a[2], &a[3]);
fclose(fp);
sleep(1); // 等待一段时间再次获取数据
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
perror("Error opening CPU stat file");
return -1.0;
}
fgets(buffer, sizeof(buffer), fp);
sscanf(buffer, "cpu %Lf %Lf %Lf %Lf", &b[0], &b[1], &b[2], &b[3]);
fclose(fp);
usage = ((b[0] + b[1] + b[2]) - (a[0] + a[1] + a[2])) / ((b[0] + b[1] + b[2] + b[3]) - (a[0] + a[1] + a[2] + a[3])) * 100.0;
return usage;
}
int main() {
float cpu_temp, cpu_usage;
cpu_temp = get_cpu_temperature();
time_t rawtime;
struct tm *timeinfo;
char timeBuffer[80];
char ip_address[MAX_IP_LENGTH]; // 用于存储IP地址的字符数组
time(&rawtime); // 获取当前时间
timeinfo = localtime(&rawtime); // 将时间转换为本地时间结构体
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", timeinfo); // 格式化时间为字符串
printf("当前时间是: %s\n", timeBuffer);
// 调用获取本地IP地址的函数
if (get_local_ip(ip_address) == 0) {
printf("Local IP address: %s\n", ip_address); // 打印获取到的IP地址
} else {
printf("Failed to get local IP address.\n"); // 获取失败时的错误提示
}
if (cpu_temp != -1.0) {
printf("CPU Temperature: %.2f°C\n", cpu_temp);
} else {
printf("Failed to get CPU Temperature.\n");
}
cpu_usage = get_cpu_usage();
if (cpu_usage != -1.0) {
printf("CPU Usage: %.2f%%\n", cpu_usage);
} else {
printf("Failed to get CPU Usage.\n");
}
return 0;
}
在系统上编译并运行就可以成功的打印出系统信息,运行结果如下图所示:
3.将以上两步综合移植
现在我们就需要将上面两步综合让我们的UI能够动态显示。
我的想法是创建一个子线程来更新UI标签,这样就可以动态显示了。我的线程执行代码如下所示:
// 线程函数,用于更新标签文本
void *update_label_thread(void *arg) {
float cpu_temp, cpu_usage;
char cpu_temp_s[10],cpu_usage_s[10];
char ip_address[MAX_IP_LENGTH]; // 用于存储IP地址的字符数组
char timeBuffer[80];
while (1) {
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d\r\n%H:%M:%S", timeinfo);
cpu_temp = get_cpu_temperature();
if (cpu_temp != -1.0) {
printf("CPU Temperature: %.2f°C\n", cpu_temp);
} else {
printf("Failed to get CPU Temperature.\n");
}
// 调用获取本地IP地址的函数
if (get_local_ip(ip_address) == 0) {
printf("Local IP address: %s\n", ip_address); // 打印获取到的IP地址
} else {
printf("Failed to get local IP address.\n"); // 获取失败时的错误提示
}
cpu_usage = get_cpu_usage();
if (cpu_usage != -1.0) {
printf("CPU Usage: %.2f%%\n", cpu_usage);
} else {
printf("Failed to get CPU Usage.\n");
}
// 更新 lvgl 标签的文本
lv_label_set_text(guider_ui.screen_timeInfo, timeBuffer);
lv_label_set_text(guider_ui.screen_ipInfo, ip_address);
sprintf(cpu_usage_s, "%.2f%%", cpu_usage);
lv_label_set_text(guider_ui.screen_CPULoad, cpu_usage_s);
sprintf(cpu_temp_s, "%.2f *C", cpu_temp);
lv_label_set_text(guider_ui.screen_CPUTemp, cpu_temp_s);
// 等待一段时间,这里简单地使用 usleep 模拟
usleep(10000); // 等待 10毫秒
}
return NULL;
}
然后再在main函数里创建线程调用上面那个线程执行函数。
最后进行编译就OK了。动态显示效果如下的视频所示:
VID_20240630_015922
我在这里解压了一个程序,可以看到CPU占用率立即就上去了,而且时间也一直再变,可见我们的程序没有出错。
我改变一下我的ip地址试试。
VID_20240630_020300
IP地址也变了,移植成功!
总结:通过本期测评我们成功用LVGL框架开发出了一个UI,并且能够显示系统信息到UI上,后面我们将移植MQTT程序将这些数据上传到云平台上(这个板子没有传感器,只能采取读系统信息这种方式)。