【平头哥Sipeed LicheeRV 86 Panel测评】十一、lvgl和fork()实现TCP Client

sonicfirr   2022-4-12 15:02 楼主

本篇记录在Tina系统上实现TCP Client的过程,结合lvgl实现点击屏幕按钮开启TCP连接。在连接TCP Server后,开启lvgl定时器,周期性向Server发送字串,并创建一个子进程接收Server发来的字串。

 

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


/* 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 -- TCP Client"
#define N                      128
#define OFF                    0
#define ON                     1
#define SERVER_IP              "192.168.31.8"
#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 *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[N] = "";    //recv buffer
pid_t pid;                //recv process id


/* 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);


/* Private functions ---------------------------------------------- */
int main(void) {
//by author. initialize lvgl including displaybuffer, device for disp & 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(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER; //by author. input device type, choice touchpad here
    indev_drv.read_cb = evdev_read;         //by author. callback to read input device data
    aita_indev = lv_indev_drv_register(&indev_drv);

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

//by author. create a timer
    aita_InitTimer();
    
    /*Handle LitlevGL tasks (tickless mode)*/
    while(1) {
        lv_task_handler();
        usleep(5000);
    }

    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(&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 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[AITA_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, AITA_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    = 480;
    disp_drv.ver_res    = 480;
    lv_disp_drv_register(&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[64];
    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, 0);
    lv_obj_set_style_text_font(main_label, &lv_font_montserrat_40, 0);

    //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) < 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)) < 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*)&server, addrlen) < 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 process for tcp recv
        pid = fork();
        if(pid < 0) {
            errlog("fork error");
        } else if(pid == 0) {
            while(1) {
                if(read(sockfd, recv_buf, N) < 0) errlog("recv error");
                printf("server: %s\n", recv_buf);
                memset(recv_buf, 0, N);
            }
        }
    } else {
        lv_timer_pause(tcp_timer);
        close(sockfd);
        conn_flag = OFF;
        lv_label_set_text(conn_btn_label, "Connect");
    }
}

 

有关代码设计的说明:

1lvgl是线程不安全的,所以这里通过创建进程来读取socket缓冲。初始想利用一个10ms定时器来进行,结果发现read()或另一个函数recv()都是阻塞式,一旦调用就会等待收到数据,因而会影响到UI刷新。

2)个人分析采用fork()创建进程的方式也并不安全,因为子进程是复制的父进程,如果程序可以跳出接收的死循环块,也可能会转到lvgl组件相关的代码,进而出现外设资源等竞争的问题(以上观点仅是分析而得,没有验证)。当然,应该还有另一种多进程方法,即单独创建TCP通信的应用,然后使用execl()exec组函数创建独立代码段的进程(这个思路后面找机会再验证)。

3)另外,子进程接收的信息仅做控制台输出,如果要显示在屏幕上,就需要进程通信传递给父进程(也就是案例主要的程序段)。这里也展现了lvgl的不足之处,作为主要使用对象为MCUGUI库,其在多线程上缺乏支持。

        4)后续的工作方向上,本人准备先以Linux API学习为主,并尝试利用Linux自身的多线程框架,同时加载lvglsocket通信达到更好的契合效果。

 

1649747994159.gif

 

1649748065192.gif

 

 

 

 

本帖最后由 sonicfirr 于 2022-4-12 15:35 编辑

回复评论 (2)

屏幕是直接用LVGL在Linux下面画的?

默认摸鱼,再摸鱼。2022、9、28
点赞  2022-4-12 16:22
freebsder 发表于 2022-4-12 16:22 屏幕是直接用LVGL在Linux下面画的?

是的,论坛有博主manhuami2007共享的移植案例,在下载区搜lvgl就可以找到的。

也可以看看帖子:https://bbs.eeworld.com.cn/thread-1197092-1-1.html

本帖最后由 sonicfirr 于 2022-4-12 19:14 编辑
点赞  2022-4-12 19:11
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复