sonicfirr 发表于 2022-4-17 15:34

【平头哥Sipeed LicheeRV 86 Panel测评】十二、lvgl和pthread实现TCP Client

本帖最后由 sonicfirr 于 2022-4-17 15:40 编辑

<p style="text-indent:24.0000pt; text-align:justify"><span style="font-size:12pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:12.0000pt"><span style="font-family:宋体"><font face="宋体">上篇利用</font><font face="Times New Roman">fork()</font><font face="宋体">创建子进程的形式实现</font><font face="Times New Roman">TCP</font><font face="宋体">连接后的数据包接收,本篇则采用子线程的形式实现相同功能,利用线程间便捷的通信能力,将接收到信息通过</font><font face="Times New Roman">lvgl</font><font face="宋体">控件显示出来。</font></span></span></span></span></span></p>

<h2 style="text-align:justify"><span style="font-size:14pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-weight:bold"><b><span style="font-size:14.0000pt"><span style="font-family:宋体"><span style="font-weight:bold"><font face="Times New Roman">1</font><font face="宋体">、</font><font face="Times New Roman">lvgl</font><font face="宋体">结合</font><font face="Times New Roman">Linux</font><font face="宋体">线程</font></span></span></span></b></span></span></span></span></h2>

<p style="text-indent:24.0000pt; text-align:justify"><span style="font-size:12pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:12.0000pt"><span style="font-family:宋体"><font face="宋体">来自于</font><font face="Times New Roman">lvgl</font><font face="宋体">官网在线文档(</font><font face="Times New Roman">https://docs.lvgl.io/master/porting/os.html#tasks-and-threads</font><font face="宋体">)的介绍,为了保证</font><font face="Times New Roman">lvgl</font><font face="宋体">内核运行后,对屏幕的及时刷新和输入设备的读取,操作系统模式下,需要通过互斥量来保护</font><font face="Times New Roman">lvgl</font><font face="宋体">的</font><font face="Times New Roman">lv_task_handler()</font><font face="宋体">和其它功能函数。</font></span></span></span></span></span></p>

<p style="text-indent:24.0000pt; text-align:justify">&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p align="center" style="text-align:center"><span style="font-size:12pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:10.5000pt"><span style="font-family:宋体"><font face="宋体">图</font><font face="Times New Roman">12-1 lvgl</font><font face="宋体">关于任务和线程的文档截图</font></span></span></span></span></span></p>

<p align="center" style="text-align:center">&nbsp;</p>

<h2 style="text-align:justify"><span style="font-size:14pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-weight:bold"><b><span style="font-size:14.0000pt"><span style="font-family:宋体"><span style="font-weight:bold"><font face="Times New Roman">2</font><font face="宋体">、</font><font face="Times New Roman">TCP Client</font><font face="宋体">多线程案例</font></span></span></span></b></span></span></span></span></h2>

<p style="text-indent:24.0000pt; text-align:justify"><span style="font-size:12pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:12.0000pt"><span style="font-family:宋体"><font face="宋体">本例业务逻辑与上篇类似,</font><font face="Times New Roman">lvgl</font><font face="宋体">展示主界面,包含一个按钮,点击后功能为:连接</font><font face="Times New Roman">TCP Server</font><font face="宋体">;创建定时器周期发送信息;创建子线程做</font><font face="Times New Roman">TCP</font><font face="宋体">接收。</font></span></span></span></span></span></p>

<p style="text-indent:24.0000pt; text-align:justify"><span style="font-size:12pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:12.0000pt"><span style="font-family:宋体"><font face="Times New Roman">lvgl</font><font face="宋体">的内核运行也封装在一个子线程中,即本例建立两个线程,一个应用开启即创建并用于</font><font face="Times New Roman">UI</font><font face="宋体">刷新,一个点击按钮连接</font><font face="Times New Roman">TCP</font><font face="宋体">后创建用于做接收并将接收到字串做控制台输出和屏幕显示(通过</font><font face="Times New Roman">label</font><font face="宋体">)。</font></span></span></span></span></span></p>

<p>&nbsp;</p>

<pre>
<code>/* Includes ------------------------------------------------------- */
#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;pthread.h&gt;
#include &lt;time.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;


/* Private macro -------------------------------------------------- */
#define AITA_DISP_BUF_SIZE   (128 * 1024)
#define AITA_SCREEN_WIDTH      480
#define AITA_SCREEN_HEIGHT   480
#define AITA_TITLE_STRING      "AITA DEMO for LicheeRV with LVGL -- Thread"
#define N                      128
#define OFF                  0
#define ON                     1
#define SERVER_IP            "192.168.31.129"
#define SERVER_PORT            1234
#define SEND_PERIOD            1000
#define SEND_MSG               "Hello World!\n"
#define errlog(errmsg)         do{ perror(errmsg);\
        printf("----%s----%s----%d----\n", __FILE__, __func__, __LINE__);\
        return;\
        } while(0)


/* Global variables ----------------------------------------------- */
lv_indev_t *aita_indev;   //pointer of indev
lv_obj_t *sys_scr;      //pointer of system screen instance
lv_obj_t *head_label;   //pointer of title label instance
lv_obj_t *main_label;   //pointer of main label instance
lv_obj_t *recv_label;   //pointer of label instance for showing tcp recvdata
lv_obj_t *conn_btn;       //pointer of conn button(tcp client) for instance
lv_obj_t *conn_btn_label; //pointer of label instance for conn button
lv_timer_t *tcp_timer;    //pointer of timer instance for tcp polling
int conn_flag = OFF;      //flag of tcp connect status
int sockfd;               //socket file descriptor
struct sockaddr_in server;//tcp server ip;
socklen_t addrlen = sizeof(server);
int tcptimer_counter = 0; //send msg per (tcptimer_counter * SEND_PERIOD) ms
char recv_buf = "";    //recv buffer
pthread_t lvgl_tid;       //lvgl thread id
pthread_t tcprecv_tid;    //tcp receive thread id
pthread_mutex_t lvgl_mutex;


/* Private function prototypes ------------------------------------ */
void aita_InitLVGL(void);
void aita_CreateMainUI(void);
void aita_InitTimer(void);
void aita_InitSocket(void);
void conn_btn_click_cb(lv_event_t *e);
void tcp_timer_cb(lv_timer_t *timer);
void *thread_lvgl(void *arg);
void *thread_tcprecv(void *arg);


/* Private functions ---------------------------------------------- */
int main(void) {
    void *retval;

//by author. initialize lvgl including displaybuffer, device for disp &amp; input
    aita_InitLVGL();
   
//by author. initialize and register event device
//these code must be in main(), otherwise the touch will fail
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&amp;indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER; //by author. choice touchpad
    indev_drv.read_cb = evdev_read;         //by author. input callback
    aita_indev = lv_indev_drv_register(&amp;indev_drv);

//by author. create the main view when the demo starts up   
    aita_CreateMainUI();

//by author. create a timer
    aita_InitTimer();

//by author. create mutex for lvgl
    if(pthread_mutex_init(&amp;lvgl_mutex, NULL) != 0) {
      errlog("initialize mutex error");
    }

//by author. create lvgl thread
    if(pthread_create(&amp;lvgl_tid, NULL, thread_lvgl, (void *)0) != 0) {
      errlog("create lvgl thread error");
    }

//by author. wait for thread exit, this demo should never be here.
    pthread_join(lvgl_tid, &amp;retval);
    printf("lvgl thread exit, return value: %s\n", (char *)retval);
    pthread_mutex_destroy(&amp;lvgl_mutex);
    return 0;
}

/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
      struct timeval tv_start;
      gettimeofday(&amp;tv_start, NULL);
      start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&amp;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 aita_InitLVGL(void) {
    /*LittlevGL init*/
    lv_init();

    /*Linux frame buffer device init*/
    fbdev_init(); //by author. initialize framebuffer device for display
    evdev_init(); //by author. initialize event device for touchpad

    /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf;
    /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&amp;disp_buf, buf, NULL, AITA_DISP_BUF_SIZE);
    /*Initialize and register a display driver*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&amp;disp_drv);
    disp_drv.draw_buf   = &amp;disp_buf;
    disp_drv.flush_cb   = fbdev_flush;
    disp_drv.hor_res    = 480;
    disp_drv.ver_res    = 480;
    lv_disp_drv_register(&amp;disp_drv);
}

void aita_CreateMainUI(void) {
    //by author. create system screen which is basic graphic level
    sys_scr = lv_obj_create(lv_scr_act());
    lv_obj_set_size(sys_scr, AITA_SCREEN_WIDTH, AITA_SCREEN_HEIGHT);
    //by author. create the main title which is just a label
    head_label = lv_label_create(sys_scr);
    lv_label_set_text(head_label, AITA_TITLE_STRING);
    lv_obj_align(head_label, LV_ALIGN_TOP_MID, 0, 50);

    char main_label_text_buf;
    sprintf(main_label_text_buf, "server ip:%s\nserver port:%d", SERVER_IP, SERVER_PORT);
    main_label = lv_label_create(sys_scr);
    lv_label_set_text(main_label, main_label_text_buf);
    lv_obj_align(main_label, LV_ALIGN_CENTER, 0, -50);
    lv_obj_set_style_text_font(main_label, &amp;lv_font_montserrat_40, 0);

    recv_label = lv_label_create(sys_scr);
    lv_label_set_text(recv_label, "");
    lv_obj_align(recv_label, LV_ALIGN_CENTER, 0, 20);

    //by author. create the conn button for connecting a tcp server
    conn_btn = lv_btn_create(sys_scr);
    lv_obj_set_size(conn_btn, 200, 50);
    lv_obj_align(conn_btn, LV_ALIGN_BOTTOM_MID, 0, -50);
    //by author. register clicked-event callback for the "conn_btn" object
    lv_obj_add_event_cb(conn_btn, conn_btn_click_cb, LV_EVENT_CLICKED, NULL);
    conn_btn_label = lv_label_create(conn_btn);
    lv_label_set_text(conn_btn_label, "Connect");
    lv_obj_center(conn_btn_label);
}

//by author. tcp_timer callback which sends msg to server
void tcp_timer_cb(lv_timer_t *timer) {   
    if(send(sockfd, SEND_MSG, sizeof(SEND_MSG), 0) &lt; 0) errlog("send error");
}
//by author. initialize timer for periodic sending
void aita_InitTimer(void) {
    tcp_timer = lv_timer_create(tcp_timer_cb, 1000, NULL);
    lv_timer_set_repeat_count(tcp_timer, -1);
    lv_timer_pause(tcp_timer);
}

//by author. initialize the socket
void aita_InitSocket(void) {
    if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) &lt; 0) errlog("socket error");
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(SERVER_IP);
    server.sin_port = htons(SERVER_PORT);
    printf("sockfd: %d\n", sockfd);
}

//by author. conn_btn callback which conn/disconn server
void conn_btn_click_cb(lv_event_t *e) {
    printf("Conn Btn Clicked\n");
    if(conn_flag == OFF) {
      aita_InitSocket();
      printf("server ip: %s, port: %d\n", SERVER_IP, SERVER_PORT);
      if(connect(sockfd, (struct sockaddr*)&amp;server, addrlen) &lt; 0) errlog("connect error");
      printf("connected\n");
      lv_timer_resume(tcp_timer);
      conn_flag = ON;
      lv_label_set_text(conn_btn_label, "Disconnect");
      //by author. create a thread for tcp recv
      if(pthread_create(&amp;tcprecv_tid, NULL, thread_tcprecv, (void *)0) != 0) {
            errlog("create tcp receive thread error");
      }
    } else {
      lv_timer_pause(tcp_timer);
      pthread_cancel(tcprecv_tid);
      close(sockfd);
      conn_flag = OFF;
      lv_label_set_text(conn_btn_label, "Connect");
    }
}

//by author. lvgl core thread function
void *thread_lvgl(void *arg) {
    while(1) {
      pthread_mutex_lock(&amp;lvgl_mutex);
      lv_task_handler();
      pthread_mutex_unlock(&amp;lvgl_mutex);
      usleep(5000); /* sleep for 5 ms */
    }
}

//by author. tcp receive thread function
void *thread_tcprecv(void *arg) {
    while(1) {
      if(read(sockfd, recv_buf, N) &lt; 0) errlog("recv error");
      printf("server: %s\n", recv_buf);

      pthread_mutex_lock(&amp;lvgl_mutex);
      lv_label_set_text(recv_label, recv_buf);
      pthread_mutex_unlock(&amp;lvgl_mutex);

      memset(recv_buf, 0, N);
    }
}</code></pre>

<p>&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p style="text-align: center;"><span style="font-size:12pt"><span style="125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:10.5000pt"><span style="font-family:宋体"><font face="宋体">图</font><font face="Times New Roman">12-2 </font><font face="宋体">案例效果展示</font></span></span></span></span></span></p>

<p>&nbsp;</p>

<p style="text-indent:24.0000pt; text-align:justify"><span style="font-size:12pt"><span style="line-height:125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:12.0000pt"><span style="font-family:宋体"><font face="宋体">需要注意的是,子线程</font><font face="Times New Roman">pthread</font><font face="宋体">库不是</font><font face="Times New Roman">Linux</font><font face="宋体">系统默认的库,所以在编译项目时需要连接静态库</font><font face="Times New Roman">libpthread.a</font><font face="宋体">,因而本例还需要修改</font><font face="Times New Roman">Makefile</font><font face="宋体">,具体如下所示。</font></span></span></span></span></span></p>

<p style="text-indent:24.0000pt; text-align:justify">&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p align="center" style="text-align:center"><span style="font-size:12pt"><span style="line-height:125%"><span style="font-family:&quot;Times New Roman&quot;"><span style="font-size:10.5000pt"><span style="font-family:宋体"><font face="宋体">图</font><font face="Times New Roman">12-3 </font><font face="宋体">增加</font><font face="Times New Roman">-lpthread</font><font face="宋体">命令参数</font></span></span></span></span></span></p>

Jacktang 发表于 2022-4-17 20:51

<p>这个子线程pthread库不是Linux系统默认的库提醒很重要</p>

sonicfirr 发表于 2022-4-17 21:12

Jacktang 发表于 2022-4-17 20:51
这个子线程pthread库不是Linux系统默认的库提醒很重要

<p>还行吧,基本上不连接就会报错pthread相关函数未定义,一搜索很容易找到原因的。</p>

<p>谢谢,支持。</p>
页: [1]
查看完整版本: 【平头哥Sipeed LicheeRV 86 Panel测评】十二、lvgl和pthread实现TCP Client