1. 概述

​ 在前几篇的文章中,我们已经学习了LVGL界面绘制以及paho mqtt的同步客户端和异步客户端的操作,那么本篇就会综合前面的知识,加上Linux系统的多线程以及线程间通信的知识,将LVGL、MQTT、多线程、消息队列这些知识使用起来,形成我们最终的产品。

温湿度监控系统应用开发所有文章

  1. 【嵌入式Linux应用开发】移植LVGL到Linux开发板
  2. 【嵌入式Linux应用开发】初步移植MQTT到Ubuntu和Linux开发板
  3. 【嵌入式Linux应用开发】SquareLine Studio与LVGL模拟器
  4. 【嵌入式Linux应用开发】温湿度监控系统——绘制温湿度折线图
  5. 【嵌入式Linux应用开发】温湿度监控系统——学习paho mqtt的基本操作
  6. 【嵌入式Linux应用开发】温湿度监控系统——多线程与温湿度的获取显示
  7. 【嵌入式Linux应用开发】设计温湿度采集MCU子系统

适用开发板

​ 适用于百问网的STM32MP157开发板和IMX6ULL开发板及其对应的屏幕,需要注意的是编译链要对应更改。

  • 100ASK_STM32MP157
  • 100ASK_IMX6ULL

2. Linux的多线程编程

​ Linux的多线程编程如果要深入使用的话,会涉及到很多的知识,在一个庞大的嵌入式产品中,需要开发者对多线程进行精细化设计,来优化代码提高CPU的执行效率,但是在本次的温湿度监控系统中,我们只需要掌握多线程的创建和退出就好。

​ 我们在Ubuntu的终端输入指令man pthread然后按TAB键自动补齐,可以看到很多关于线程的函数:

比如我们对创建线程的api感兴趣,想知道它的信息,就可以在终端输入指令man pthread_create,然后就看到如下信息:

这里会告诉我们要使用这个函数需要包含什么头文件,函数的每个参数是什么意思,返回值有哪些信息等,我们就可以通过这个说明来学习这个函数的使用,然后再去网上参考别人的使用经验,总结成为自己的学习经验。

2.1 创建线程

​ 前面已经通过man指令查看了pthread_create的用法,我们现在直接写代码来学习。首先在前面创建的工作区间新建一个C源文件pthread_1.c

book@100ask:~/workspace$ cd /home/book/workspace
book@100ask:~/workspace$ touch pthread_1.c

然后编辑这个C源文件:

  • 包含线程头文件
#include <pthread.h>
  • 包含C库文件
#include <stdio.h>
  • 创建两个线程的入口函数

线程入口函数的形式从创建线程的API参数就可以确定下来

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

可以看到是void *(*start_routine)(void*)这样的,所以我们的入口函数这样写:

static void *thread1(void *paramater);
static void *thread2(void *paramater)

在入口函数中我们打印一些信息,如下:

printf("Thread 1 running: %d\n", count++);

为了避免打印太快,我们可以加一个延时函数sleep/usleep,我们同样可以使用man指令来学习这两个函数:

  • ​ 延时函数

    • sleep:延时x秒

如果我们要使用sleep函数的话这里看不到我们需要哪些头文件,这时候我们就可以用man指令指定章节查看:man 1/2/3 sleep,下图是man 3 sleep的结果:

  • usleep:延时x微妙

所以我们需要在C源文件中包含头文件:

#include <unistd.h>

我们可以使两个线程不同时间间隔打印:

static void *thread1(void *paramater)
{int count = 0;while(1){printf("Thread 1 running: %d\n", count++);sleep(1);}
}static void *thread2(void *paramater)
{int count = 0;while(1){printf("Thread 2 running: %d\n", count++);sleep(2);}
}
  • 创建线程

    入口函数写好后,我们就可以去创建线程了,我们在main函数中使用pthread_create创建线程:

    • 定义线程句柄
    pthread_t thread1_t;
    pthread_t thread2_t;
    
    • 不设置优先级等属性也不传参创建线程
    int ret = pthread_create(&thread1_t, NULL, thread1, NULL);
    if(ret != 0)
    {printf("Failed to create thread1.\n");return -1;
    }ret = pthread_create(&thread2_t, NULL, thread1, NULL);
    if(ret != 0)
    {printf("Failed to create thread2.\n");return -1;
    }
    

    所以我们的main函数最终是这样:

    int main(char argc, char* argv)
    {pthread_t thread1_t;pthread_t thread2_t;int ret = pthread_create(&thread1_t, NULL, thread1, NULL);if(ret != 0){printf("Failed to create thread1.\n");return -1;}ret = pthread_create(&thread2_t, NULL, thread1, NULL);if(ret != 0){printf("Failed to create thread2.\n");return -1;}while(1){sleep(1);}
    }
    

    然后使用gcc编译它,需要注意的是编译的时候需要连接线程的库pthread,所以编译的时候要加上-lpthread

    gcc -o pthread_1 pthread_1.c -lpthread
    

    这样就得到了可执行输出文件pthread_1,我们./pthread_1执行后的效果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wY4dpps-1657596796741)(LinuxApp-6-finishproducts/image-20220704172748831.png)]

    可以按CTRL+C退出程序。

2.2 退出线程

​ 我们使用man指令学习下如何使用线程退出函数:

man pthread_exit

从描述那里可以看到这个函数可以终止调用该函数的线程,即如果我在thread1里面调用了pthread_exit,那么thread1就会被终止,而其它线程继续运行:

static void *thread1(void *paramater)
{int retval;int count = 0;while(1){printf("Thread 1 running: %d\n", count++);sleep(1);if(count==5) pthread_exit(&retval);}
}static void *thread2(void *paramater)
{int retval;int count = 0;while(1){printf("Thread 2 running: %d\n", count++);sleep(2);if(count==3) pthread_exit(&retval);}
}// 在main函数的主循环中加个打印
int main(char argc, char** argv)
{int ret = pthread_create(&thread1_t, NULL, thread1, NULL);if(ret != 0){printf("Failed to create thread1.\n");return -1;}ret = pthread_create(&thread2_t, NULL, thread2, NULL);if(ret != 0){printf("Failed to create thread2.\n");return -1;}while(1){printf("Main>>>\r\n");sleep(1);}
}

我们这样修改后重新编译执行看下效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xdul86C8-1657596796743)(LinuxApp-6-finishproducts/image-20220704174520751.png)]

可以看到线程1执行5此后就没再打印了,线程2打印了3次后就没打印了。

3. Linux的消息队列

​ 对于消息队列的学习我们还是使用man来查询学习,先使用man msg+TAB键自动补齐,看一下有哪些函数:

3.1 获得一个消息队列

​ 获取一个新的消息需要传入key关键字还要设置一个新建的标志msgflg,如果msgflag设置IPC_CREAT,那么不管key值有没有被其它的消息队列占用,都能成功的获取到消息队列,返回该消息队列的ID,如果该消息队列是已创建的则是打开一个已存在的消息队列;如果msgflag设置为ICP_CREAT | IPC_EXCL,那么如果key已经被其他的队列占用的话,是无法获取到该关键字对应的新的消息队列的,返回错误码-1,例如:

int msg_id = msgget(1234, IPC_PRIVATE | IPC_CREAT);
int msg_id1 = msgget(2345, IPC_CREAT | IPC_EXCL);

3.2 发送消息

​ 发送消息队列的API是msgsnd

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

可以看到在手册中msgsndmsgrcv是一起解释的,对于发送函数msgsnd,需要传入4个参数:

  • 消息队列的IDmsgid

  • 发送的消息数据指针msgp

  • 发送的消息数据大小msgsz

  • 发送标志msgflg;

    发送数据指针mgsp它指向的是一个形如:

struct msgbuf{long mtype;char mtext[1];
};

的结构体,其中mtype必须是一个大于0的值来表示消息类型,这个类型值在后面接收消息的时候可以用到,比如可以让接收方不接收这个类型的消息(搭配msgflg=MSG_EXCEPT使用),也可以让接收方接收到消息队列中第一个类型为msgtyp=mtype的消息,这种情况下就没有队列的先进先出的特性了。

​ 发送的数据大小是msgp中除了mtype的数据大小,而不是整个消息结构体的大小。

​ 发送标志支持如下几种:

  • 0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

  • IPC_NOWAIT:如果消息队列满了,新的消息将不会被写入队列

  • IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程

返回值:

  • 0:发送消息成功
  • -1:发送消息失败,错误码在erorr中

使用示例:

struct msgbuf{long mtype;char value;
};struct msgbuf qbuf = {1, 12};
int qmsg = msgget(1234, IPC_CREAT | IPC_PRIVATE);
int ret0 = msgsnd(qmsg, &qbuf.mtype, sizeof(qbuf.value), 0);   // 阻塞发送
int ret1 = msgsnd(qmsg, &qbuf.mtype, sizeof(qbuf.value), IPC_NOWAIT);  // 非阻塞发送
int ret2 = msgsnd(qmsg, &qbuf.mtype, sizeof(qbuf.value), IPC_NOERROR); // 如果超出截断发送

3.3 接收消息

​ 接收消息的API:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数解释:

参数名称 参数释义
msqid 消息队列的ID
msgp 存放消息的指针
msgsz 指定接收消息的大小
msgtyp 执行接收消息的类型:
0-读取消息队列中第一个数据;
>0:读取消息队列中类型为msgtyp的第一个数据;如果msgflg=MSG_EXCEPT,那么队列中和第一个类型不为msgtyp的数据将会被读取;
<0:将会读取队列中第一个等于或者小于msgtyp类型的数据
msgflg 接收消息队列的标志位:
IPC_CREAT:如果队列中没有数据或者没有指定类型的数据,则直接返回,不堵塞线程,返回错误码在error中;
MSG_COPY:这个标志需要搭配IPC_CREAT使用,将会从指定位置读取消息,如果指定位置处没有消息将会理解返回-1,错误码在error中;
MSG_EXCEPT:指定不接收某些msgtyp的消息数据;
MSG_NOEORROR:如果队列中的数据个数大于指定接收msgsz,那么就会截断只读取msgsz个数据出来;
返回值 0-成功;-1失败,错误码在error中

使用示例:

struct msgbuf{long mtype;char value;
};struct msgbuf sbuf;
int smsg = msgget(1234, IPC_CREAT | IPC_PRIVATE);
int ret = msgrcv(smsg, &sbuf, sizeof(sbuf.value), 1, IPC_NOWAIT);

3.4 销毁消息队列

​ 消息队列创建后是独立于线程的,所以如果退出线程而没有去撤销消息队列的话,那么我们创建的消息队列就会一直存在在后台中,除非我们重启系统,因而我们为了减小这样的影响,可以在退出线程结束程序前将我们创建的队列都销毁掉,使用的接口是:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数解释:

参数名称 参数解释
msgqid 消息队列的ID
cmd 控制这个消息队列的操作指令:
IPC_STAT:将指定ID的消息队列的内核信息copy到msqid_ds *buf中;
IPC_SET:给指定ID的消息队列写入一些保存在msqid_ds *buf中的信息;
IPC_RMID:理解删除指定ID的消息队列;
IPC_INFO:将指定ID的消息队列在系统中的信息copy到msqid_ds *buf中;
MSG_INFO:返回一些和IPC_INFO类似的信息到buf中;
MSG_STAT:返回一些和IPC_STAT类似的信息到buf中;
buf 保存信息的结构体指针,如果
返回值 IPC_STATIPC_SETIPC_RMID下成功的话返回0,在IPC_INFOMSG_INFO下成功的话返回队列在内核或者系统的索引值;
错误的话返回-1,错误码在error中;

使用示例:

int dmsg = msgget(1234, IPC_CREAT | IPC_PRIVATE);
int ret = msgctl(dmsg, `IPC_RMID, NULL);  // 删除队列

4. 显示温湿度

​ 我们在前面已经学习了MQTT的一些基本操作,以及LVGL中表格chart和滑动条slider的一些基本操作,刚才又学习了线程通信的消息队列,现在就可以综合起来实现我们的目标了。

​ 我们想要完成的是将Linux开发板变成一个MQTT客户端,和阿里云服务器建立连接,然后订阅一个主题获得温湿度数据,开发板在接收到了订阅的主题的消息后,处理数据,然后发送消息队列给ui,去设置chart和slider更新显示:

所以我们的开发板其实只需要完成MQTT的订阅任务然后去处理再发送消息给LVGL即可。

4.1 更新chart和slider

​ 我们可以将更新chart和slider数值和显示的函数放到main.c里面去实现:

book@100ask:~$ cd /home/book/workspace/lvgl_demo
book@100ask:~/workspace/lvgl_demo$ vim main.c

然后找个地方加入如下的代码:

void set_temp_humi_data(uint16_t value)
{uint8_t temp_value = (value>>8)&0xFF;uint8_t humi_value = value&0xFF;lv_slider_set_value(ui_tempSlider, temp_value, LV_ANIM_OFF);lv_slider_set_value(ui_humiSlider, humi_value, LV_ANIM_OFF);lv_chart_set_next_value(ui_chart, temp, temp_value);lv_chart_set_next_value(ui_chart, humi, humi_value);lv_chart_refresh(ui_chart);
}

我们对于服务器发送的温湿度格式是:数据=温度*256+湿度,也就是代码中的:

value = (temp_value<<8) + humi_value

而解析就是:

uint8_t temp_value = (value>>8)&0xFF;
uint8_t humi_value = value&0xFF;

这个格式读者可以自定义,只要子系统发送以及监测系统解析的时候是同一套格式即可。

更新LVGL的chart和slider的值其实很简单,调用LVGL对应的API即可,如果不熟悉可以百度和查看LVGL的官方文档。

4.2 建立mqtt客户端以及订阅主题

​ 我们需要将mqtt的源码移植到工程里面(前提是已经按照前面的文章将mqtt安装到了ubuntu和Linux开发板):

book@100ask:~$ cd /home/book/workspace/lvgl_demo
book@100ask:~/workspace/lvgl_demo$ mkdir mqtt
book@100ask:~/workspace/lvgl_demo$ cd mqtt
book@100ask:~/workspace/lvgl_demo/mqtt$ cp -r /home/book/workspace/mqtt/paho.mqtt.c/src ./
book@100ask:~/workspace/lvgl_demo/mqtt$ touch mqtt_iot.h mqtt_iot.c mqtt.mk

​ 这里直接放源码, 登录服务器的链接地址、用户名这些省略,读者自己去阿里云物联网平台建立设备然后填写信息:

// mqtt_iot.h
#ifndef __MQTT_IOT_H__
#define __MQTT_IOT_H__#include <pthread.h>
#include <semaphore.h>
#include <sys/msg.h>typedef enum{DisconThread,PubThread
}Mqtt_Thread;typedef struct
{long mtype;       /* message type, must be > 0 */unsigned int value;    /* message data */
}msgbuf;int mqtt_disconnect(void);
int mqtt_iot(void);#endif /* __MQTT_IOT_H__ */// mqtt_iot.c
#include "mqtt_iot.h"
#include "src/MQTTClient.h"  //需要在系统中提前安装好MQTT,可以参考#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>#define ADDRESS     "tcp://${your mqtt server url}:1883" //根据 MQTT 实际主机地址调整
#define CLIENTID    "${your client id}"
#define USERNAME    "${your username}"
#define PASSWORD    "${your password}"
#define QOS 0
#define TIMEOUT     10000L
#define SUB_TOPIC   "${your subscribe topic}" extern void set_temp_humi_data(unsigned short value);MQTTClient client;  //定义一个MQTT客户端client
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;//传递给MQTTClient_setCallbacks的回调函数 消息到达后,调用此回调函数
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{printf("Message arrived\n");printf(" topic: %s\n", topicName);printf(" message: %.*s\n", message->payloadlen, (char*)message->payload);unsigned short value = 0;unsigned short len = message->payloadlen;char *buf = (char*)message->payload;for(unsigned short i=0; i<len; i++){if(buf[i] == '\0') break;if(buf[i]<='9' && buf[i]>='0')value = value*10 + buf[i] - '0';}set_temp_humi_data(value); // 调用我们封装的LVGL更新函数MQTTClient_freeMessage(&message); // 释放消息MQTTClient_free(topicName);  // 释放主题名return 1;
}//传递给MQTTClient_setCallbacks的回调函数 连接异常断开后调用此回调函数
void connlost(void *context, char *cause)
{printf("\nConnection lost\n");printf(" cause: %s\n", cause);
}// 封装主动断开连接服务器的函数
int mqtt_disconnect(void)
{int rc = EXIT_SUCCESS;if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)  //断开和服务器的连接 {printf("Failed to disconnect, return code %d\n", rc);rc = EXIT_FAILURE;}else{printf("MQTT disconnect success\n");MQTTClient_destroy(&client);}return rc;
}// mqtt建立客户端、连接服务器、订阅主题的封装入口函数
int mqtt_iot(void)
{int rc = EXIT_SUCCESS;if ((rc = MQTTClient_create(&client, ADDRESS, CLIENTID,MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS){printf("Failed to create client, return code %d\n", rc);goto exit;}//设置回调函数,if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd,NULL)) != MQTTCLIENT_SUCCESS){printf("Failed to set callbacks, return code %d\n", rc);goto destroy_exit;}conn_opts.username = USERNAME;conn_opts.password = PASSWORD;conn_opts.keepAliveInterval = 60;conn_opts.cleansession = 1;//连接服务器if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS){printf("Failed to connect, return code %d\n", rc);goto destroy_exit;}//订阅主题 if ((rc = MQTTClient_subscribe(client, SUB_TOPIC, QOS)) != MQTTCLIENT_SUCCESS){printf("Failed to subscribe, return code %d\n", rc);goto destroy_exit;}printf("MQTT connect success, press 'Q' or 'q' to disconnect mqtt server\n");return 0;destroy_exit:MQTTClient_destroy(&client); //释放客户端的资源 return -1;
exit:return -1;
}

而mqtt.mk文件就和ui.mk是一样的写法:

MQTT_DIR_NAME ?= mqtt
CSRCS += $(wildcard $(LVGL_DIR)/$(MQTT_DIR_NAME)/*.c)

4.3 main函数初始化LVGL和mqtt

​ 我们需要在main函数里面初始化LVGL的参数以及我们绘制的ui,还要初始化mqtt成功建立客户端和服务器的连接后,将主动断开服务器的任务放到一个线程里面去:

#include "ui/ui.h"
#include "mqtt/mqtt_iot.h"
#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>// 断开和mqtt服务器连接的线程入口函数
static void *mqtt_disconnect_t(void* argv)
{while(1){char ch;ch = getchar();if(ch=='Q' || ch=='q'){printf("Try to exit mqtt task\n");if(mqtt_disconnect() == EXIT_SUCCESS)   break;}}isConnected = false;pthread_exit(&discon_t);    // 退出线程return NULL;
}// main函数
int main(void)
{/*LittlevGL init*/lv_init();/*Linux frame buffer device init*/fbdev_init();/*A small buffer for LittlevGL to draw the screen's content*/static lv_color_t buf[DISP_BUF_SIZE];/*Initialize a descriptor for the buffer*/static lv_disp_draw_buf_t disp_buf;lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);/*Initialize and register a display driver*/static lv_disp_drv_t disp_drv;lv_disp_drv_init(&disp_drv);disp_drv.draw_buf   = &disp_buf;disp_drv.flush_cb   = fbdev_flush;disp_drv.hor_res    = 1024;disp_drv.ver_res    = 600;lv_disp_drv_register(&disp_drv);evdev_init();static lv_indev_drv_t indev_drv_1;lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/indev_drv_1.type = LV_INDEV_TYPE_POINTER;/*This function will be called periodically (by the library) to get the mouse position and state*/indev_drv_1.read_cb = evdev_read;lv_indev_t *mouse_indev = lv_indev_drv_register(&indev_drv_1);// 调用我们绘制的ui初始化函数ui_init();// 成功建立客户端和服务器的连接且订阅主题后才创建断开连接的线程if(mqtt_iot() == 0){isConnected = true;pthread_create(&discon_t, 0, mqtt_disconnect_t, NULL);}/*Handle LitlevGL tasks (tickless mode)*/while(1) {lv_timer_handler();usleep(5000);}return 0;
}

4.4 编译运行

​ 因为我们添加了mqtt的代码以及对应的.mk,所以需要将mqtt.mk放到工程目录下的Makefile中:

include $(LVGL_DIR)/mqtt/mqtt.mk

然后执行make编译,并且将编译出来的可执行文件拷贝到挂载目录:

book@100ask:~/workspace/lvgl_demo$ make -j4
book@100ask:~/workspace/lvgl_demo$ cp demo ~/nfs_rootfs/

最后去开发板上将其拷贝出来执行:

[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.50.12:/home/book/nfs_rootfs /mnt
[root@100ask:~]# cp /mnt/demo ./
[root@100ask:~]# ./demo

这样界面运行起来了,且也能看到设置的mqtt打印信息:

4.5 阿里云模拟下发消息验证

​ 开发板已经将程序运行起来了,我们就去阿里云下发模拟消息看一下:

然后看下开发板的屏幕以及终端:

可以看到接收到了消息,屏幕就不拍照了,实际上屏幕的滑动条数值也变了,表格也出来了点。

​ 至此,一个温湿度监控系统就完成了,下一步是去搞一个探测温湿度的子系统,用单片机+RT-Thread+MQTT+DHT11来完成。

5. 总结

efile
book@100ask:~/workspace/lvgl_demo$ make -j4
book@100ask:~/workspace/lvgl_demo$ cp demo ~/nfs_rootfs/


最后去开发板上将其拷贝出来执行:```makefile
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.50.12:/home/book/nfs_rootfs /mnt
[root@100ask:~]# cp /mnt/demo ./
[root@100ask:~]# ./demo

这样界面运行起来了,且也能看到设置的mqtt打印信息:

4.5 阿里云模拟下发消息验证

​ 开发板已经将程序运行起来了,我们就去阿里云下发模拟消息看一下:

然后看下开发板的屏幕以及终端:

可以看到接收到了消息,屏幕就不拍照了,实际上屏幕的滑动条数值也变了,表格也出来了点。

​ 至此,一个温湿度监控系统就完成了,下一步是去搞一个探测温湿度的子系统,用单片机+RT-Thread+MQTT+DHT11来完成。

5. 总结

​ 可以看到我们最终没有用到消息队列,这是因为当前的系统功能还很简单,不需要这个操作,但是读者可以在此基础上进行扩展,使这个系统不仅能订阅子系统的消息,还能发送消息给子系统来完成某个控制,这时候可以看下使用消息队列是否会更科学。

【嵌入式Linux应用开发】温湿度监控系统——多线程与温湿度的获取显示相关推荐

  1. 【嵌入式Linux】嵌入式Linux应用开发基础知识之多线程编程

    文章目录 前言 1.多线程基础编程--创建线程和使用等待函数休眠线程 1.1.程序分析--使用信号量PV操作sem_wait 1.2.程序分析--使用条件变量pthread_cond_wait 2.一 ...

  2. 【嵌入式Linux应用开发】温湿度监控系统——绘制温湿度折线图

    1. 概述 ​ 本篇的主要内容是使用SquareLine Studio绘制一个显示温湿度曲线图的表格,将其移植到100ASK STM32MP157开发板上显示,效果图如图所示: 温湿度监控系统应用开发 ...

  3. 【嵌入式Linux应用开发】设计温湿度采集MCU子系统

    1. 概述 ​ 本篇主要是使用百问网的100ASK_STM32F103_PRO开发板加上ESP8266和DHT11设计一个采集环境温湿度的子系统,将温湿度数据上云,让阿里云服务器转发给订阅了该温湿度数 ...

  4. 【嵌入式Linux应用开发】SquareLine Studio与LVGL模拟器

    1. 概述 ​ 本篇重点是讲LVGL的开发辅助工具,以及利用这些工具将LVGL制作UI之后移植到嵌入式Linux开发板上显示. 温湿度监控系统应用开发所有文章 [嵌入式Linux应用开发]移植LVGL ...

  5. 闸机安装linux系统,基于嵌入式Linux的闸机监控系统设计

    摘要: 闸机监控系统,因其具备安全防范和人员管理功能,而被广泛应用于工厂自动化.安全保卫.物流管理等行业.同时,随着射频身份识别(RFID)技术和嵌入式技术的广泛应用,将三者结合使用,必会极大地提高闸 ...

  6. 《嵌入式Linux应用开发完全手册》——1.2 基于ARM处理器的嵌入式Linux系统

    本节书摘来自异步社区<嵌入式Linux应用开发完全手册>一书中的第1章,第1.2节,作者 韦东山,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.2 基于AR ...

  7. 嵌入式Linux裸机开发(六)——S5PV210时钟系统

    嵌入式Linux裸机开发(六)--S5PV210时钟系统 一.时钟系统简介 外设工作需要一定频率的时钟,这些时钟都由系统时钟提供.系统时钟一般由外部低频24MHZ晶体振荡器通过锁相环电路PLL倍频产生 ...

  8. 【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程

    文章目录 前言 1.输入系统应用编程 1.1.输入系统框架及调试 1.1.1.框架概述 1.1.2.编写APP需要的基础知识 1.2.调试技巧 1.2.1.查看设备信息 1.2.2.使用命令查看节点数 ...

  9. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程

    文章目录 1.输入系统应用编程 1.1 什么是输入系统 1.2 输入系统框架及调试 1.2.1 框架概述 1.2.2 编写APP需要掌握的知识 1.2.3 调试技巧 **1. 确定设备信息** **2 ...

最新文章

  1. 尼康单反相机测试软件,尼康D4S数码单反相机专题测试
  2. android中11种常见传感器的使用方法
  3. 魅族Android10内测招募答案,10款机型升级Android 10!魅族Flyme即日起内测招募
  4. 字符串php手册,php知识点复习之字符串
  5. 李嘉诚无锡演讲:骂到你成功
  6. (78)Vivado设置时钟组约束
  7. 张家辉申请“渣渣辉”商标,真的很有知识产权意识了
  8. zabbix安装与配置
  9. 使用boost库获取应用程序的所在目录
  10. 俄罗斯方块c语言游戏代码大全,C语言实现俄罗斯方块小游戏
  11. 触摸屏 java_价值10W大洋的触摸屏技术揭秘。。。
  12. 股票交易手续费怎么计算
  13. layui 模板引擎-laytpl(局部渲染)
  14. 杭州云栖大会“弹性计算用户实践专场”等你来
  15. 打开栅格数据的正确方式
  16. 帝国cms网站管理系统之安全设置最优化分享
  17. ASP.NET Development Server的Directory Browsing模式HTML垃圾代码
  18. Apollo使用篇 - Apollo客户端的使用
  19. 史上最全Python快速入门教程,让你快速入门python学好python
  20. 小陈java学习笔记IO

热门文章

  1. 主窗口(06):【类】QStatusBar [官翻]
  2. 唤醒手腕Python全栈工程师学习笔记(底层原理篇)
  3. 2d模版卷轴游戏总结
  4. 用python写一个可转债的投资策略
  5. 蒙特梭利素材制作灯笼素材蒙氏素材
  6. 布尔表达式和正则表达式_简化布尔表达式的实例
  7. 《啊哈!算法》第二章 - 第一节 - 解密QQ号(Java实现)
  8. HIT软构Lab1心得
  9. 灵感个性音源合集 Native Instruments Play Series Kontakt 6
  10. pythonidechart_Python调用echarts5实现数据可视化-02-魅惑黑