1.演示:
驾驶界面图
有图无真相,下面视频展示:
汽车仪表盘展示
整个汽车中控仪表盘界面展示:
极米智驾仪表盘中控
极米智驾仪表盘在arm开发板上运行的效果:
(因为****只能上传到2M多的视频大小,所以视频只能压缩到2M,画质比较糊了,但是效果是和上面软件的效果一样的)
极米智驾仪表盘开发板展示
资源已绑定,自行下载哦
演示结束,接下来开始我们的极米智驾仪表盘的教程吧。
2.LVGL设计及移植
关于LVGL设计我是在SquareLine软件上做的,具体UI设计大家可以看自己喜欢来做,我更多教大家怎么移植这样。(我的UI设计在资源里面可以直接下载)
移植:
在SquareLine里面设计好LVGL后,点击左上角的导出选择UI导出或者项目导出(这里我选择项目导出形式)
导出:
导出后点击导出的文件夹里,找到UI文件夹并复制出来到自己的项目工程文件夹下。
这里要提前自己移植好LVGL的标准库,具体方法网上会有教,或者直接拿我移植好的,这里我们把UI文件夹放到项目工程下。
Makefile改写:
把ui文件夹里的.c都包含到Makefile中。
头文件路径改写:
替换:
接着make clean,再make进行编译,会发现系统报错不认识两个函数,这是因为squareline这个软件生成的lvgl版本和我们自己移植的lvgl版本不一样导致的,版本不同,里面的函数有一些会不同,我们只要进行替换即可。
一个是lv_mem_malloc,我们直接在ui的文件夹下搜索这个函数,全局替换成lv_malloc即可;
一个是lv_mem_free,同样方法进行搜索,全局替换成lv_free即可。
(这里因为我已经替换过了就演示不了了,大家照着做就可以了)
最后再进行make clean和make,发现编译通过就可以使用了。
使用:
直接调用ui.c里面的ui_init();函数即可
3.代码:
lvgl:
lvgl有自己的维护组件的一套方法,我们只需要在主程序中调用lvgl初始化并将lvgl的维护组件的程序放到多线程里面即可。
注意:涉及到多线程的问题,那自然就要印出来线程资源抢夺了,当我们在线程中查询或者更改lvgl的组件时,为了不让线程之间互相抢夺组件资源而造成程序崩溃,在这里我们需要引入一个线程锁来维护线程资源不会互相抢夺。
/// @brief lvgl线程轮询函数
/// @param arg 线程传参
/// @return 无
void *pthreadFun_Lvgl(void *arg)
{
while (1)
{
//线程锁,使lvgl轮询组件时不会和其他线程争抢组件资源
pthread_mutex_lock(&suo);
lv_timer_handler(); // 事务处理
lv_tick_inc(5); // 节拍累计 5ms
//解锁
pthread_mutex_unlock(&suo);
//延时5000ms使CPU可以执行其他函数
usleep(5000);
}
}
/*用户节拍获取*/
uint32_t custom_tick_get(void)
{
static uint64_t start_ms = 0;
if (start_ms == 0)
{
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
}
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
uint64_t now_ms;
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
uint32_t time_ms = now_ms - start_ms;
return time_ms;
}
最后,演示一下天气获取的原理和时间显示的原理:
设置天气函数:
void set_weather()
{
// 天气
char cJSON_buff[65535] = {0}, weather_report_buff[1024] = {0}, weather_temperature_buff[5] = {0};
memcpy(cJSON_buff, get_weather_report(), sizeof(cJSON_buff));
printf("天气预报网址给我回复: %s\n", cJSON_buff);
analyse_CJSON_data(cJSON_buff, weather_report_buff, weather_temperature_buff);
printf("主函数获取到天气:%s,温度:%s\n", weather_report_buff, weather_temperature_buff);
if (strstr(weather_report_buff, "晴") != NULL)
{
lv_img_set_src(ui_morentianqiimg, "S:/nfs/ZARD/SecondDemo/img/qingtian.png");
lv_label_set_text(ui_tianwendulabel, weather_temperature_buff);
printf("%d %s %s\n", __LINE__, __FUNCTION__, __FILE__);
lv_img_set_src(ui_ditumorentianqiimg, "S:/nfs/ZARD/SecondDemo/img/qingtian.png");
lv_label_set_text(ui_ditutainqilabel, weather_temperature_buff);
}
else if (strstr(weather_report_buff, "小雨") != NULL)
{
lv_img_set_src(ui_morentianqiimg, "S:/nfs/ZARD/SecondDemo/img/xiaoyu.png");
lv_label_set_text(ui_tianwendulabel, weather_temperature_buff);
printf("%d %s %s\n", __LINE__, __FUNCTION__, __FILE__);
lv_img_set_src(ui_ditumorentianqiimg, "S:/nfs/ZARD/SecondDemo/img/xiaoyu.png");
lv_label_set_text(ui_ditutainqilabel, weather_temperature_buff);
}
else if (strstr(weather_report_buff, "多云") != NULL)
{
// lv_img_set_src(ui_morentianqiimg, "S:/nfs/ZARD/SecondDemo/img/duoyun.png");
lv_img_set_src(ui_morentianqiimg, "S:/duoyun.png");
lv_label_set_text(ui_tianwendulabel, weather_temperature_buff);
printf("%d %s %s\n", __LINE__, __FUNCTION__, __FILE__);
lv_img_set_src(ui_ditumorentianqiimg, "S:/duoyun.png");
// lv_img_set_src(ui_ditumorentianqiimg, "S:/nfs/ZARD/SecondDemo/img/duoyun.png");
lv_label_set_text(ui_ditutainqilabel, weather_temperature_buff);
}
else if (strstr(weather_report_buff, "大雨") != NULL)
{
lv_img_set_src(ui_morentianqiimg, "S:/dayu.png");
// lv_img_set_src(ui_morentianqiimg, "S:/nfs/ZARD/SecondDemo/img/dayu.png");
lv_label_set_text(ui_tianwendulabel, weather_temperature_buff);
printf("%d %s %s\n", __LINE__, __FUNCTION__, __FILE__);
lv_img_set_src(ui_ditumorentianqiimg, "S:/dayu.png");
lv_img_set_src(ui_ditumorentianqiimg, "S:/nfs/ZARD/SecondDemo/img/dayu.png");
lv_label_set_text(ui_ditutainqilabel, weather_temperature_buff);
}
}
连接阿里云并获取阿里云市场天气API数据:
char * get_weather_report()
{
int tcpsock;
int ret;
char ip[20]={0};
static char rbuf[65535]={0};
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET; //地址协议
bindaddr.sin_addr.s_addr=htonl(INADDR_ANY);
bindaddr.sin_port=htons(5548); //客户端端口号
//获取你要访问的http服务器ip地址
//http没有加密的 https在http的基础上添加了加密层
//ali-weather.showapi.com:万维易源网址地址
struct hostent *p=gethostbyname("ali-weather.showapi.com"); //天气预报的网址
//获取IP地址
struct in_addr *q=(struct in_addr *)(*(p->h_addr_list));
strcpy(ip,inet_ntoa(*q));
//定义ipv4地址结构体变量存放服务器的ip和端口号
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr(ip); //http服务器的ip
serveraddr.sin_port=htons(80); //http协议,端口默认就是80
//创建tcp套接字 --》买手机
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建tcp套接字失败了!\n");
return -1;
}
//取消端口绑定限制
int on=1; //非零
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//绑定ip和端口号 --》绑定手机号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败了!\n");
return -1;
}
//连接服务器 --》拨号
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
if(ret==-1)
{
perror("连接服务器失败了!\n");
return -1;
}
// "areaCode": "440117"
//拼接字符串得到完整的http请求
char *httprequest="GET /hour24?area=%E4%B8%AD%E5%9B%BD%E5%B9%BF%E5%B7%9E%E4%BB%8E%E5%8C%96%E5%8C%BA%E6%A3%8B%E6%9D%86 HTTP/1.1\r\n"
"Host: ali-weather.showapi.com\r\n"
"Authorization: APPCODE b547a1cfb0694bd3a4d6c6a8eff836db\r\n\r\n";
//发送刚才的请求
send(tcpsock,httprequest,strlen(httprequest),0);
//接收http服务器回复的应答信息
recv(tcpsock,rbuf,20000,0);
//关闭套接字
close(tcpsock);
return rbuf;
}
解析CJSON数据:
/// @brief 解析CJSON数据得到天气数据和温度
/// @param json_str CJSON数据
/// @param data 天气数据
/// @param weather_temperature 温度
void analyse_CJSON_data(char *json_str, char *data, char *weather_temperature)
{
char *tmp = json_str;
int i;
//tmp遍历到{,即CSJON数据开头,因为阿里云发过来的数据开头有一些脏数据
for (i = 0;; i++)
{
if (tmp[i] == '{')
{
break;
}
}
//json_str指向CJSON数据开头
json_str = &tmp[i - 1];
/**CJSON数据其实就是数据一层包着一层,我们需要一层一层解析数据来取到我们想要的数据
* 具体步骤:
* 第一步先把数据丢到cJSON_Parse换成链表存储,cJSON_Parse返回数据的入口地址
* 接着根据数据是[ {} ]数组包着对象,
* */
// 第一步:把字符串格式的json数据转换成链表存放
cJSON *obj = cJSON_Parse(json_str);
// 第二步,将里面的元素一步一步解析出来
cJSON *showapi_res_body_obj = cJSON_GetObjectItem(obj, "showapi_res_body");
cJSON *hourlist_obj = cJSON_GetObjectItem(showapi_res_body_obj, "hourlist");
cJSON *arr_now = cJSON_GetArrayItem(hourlist_obj, 0);
cJSON *weather_obj = cJSON_GetObjectItem(arr_now, "weather");
cJSON *weather_temperature_obj = cJSON_GetObjectItem(arr_now, "temperature");
sprintf(weather_temperature, "%s°", weather_temperature_obj->valuestring);
printf("获取到天气:%s,温度:%s\n", weather_obj->valuestring, weather_temperature);
memcpy(data, weather_obj->valuestring, strlen(weather_obj->valuestring));
return;
}
更新时间线程:
void *pthreadFun_time(void *arg)
{
// 时间缓冲区
char timeBuff[60] = {0}, tmp[32] = {0};
// 获取时间变量
time_t now;
struct tm *currentTime;
while (1)
{
pthread_mutex_lock(&suo);
time(&now);
currentTime = localtime(&now);
sprintf(timeBuff, "%02d:%02d:%02d",
currentTime->tm_hour,
currentTime->tm_min,
currentTime->tm_sec);
memcpy(tmp, timeBuff, strlen(timeBuff));
if (currentTime->tm_hour > 12)
{
sprintf(timeBuff, "%s%c%s", tmp, ' ', "pm");
}
else
{
sprintf(timeBuff, "%s%c%s", tmp, ' ', "am");
}
lv_label_set_text(ui_timelabel, timeBuff);
lv_label_set_text(ui_daohangtimelabel, timeBuff);
lv_label_set_text(ui_cartimelabel, tmp);
pthread_mutex_unlock(&suo);
usleep(10000);
}
}