[Sipeed LicheeRV 86 Panel Review] 11. Implementing TCP Client with lvgl and fork()
[Copy link]
This post was last edited by sonicfirr on 2022-4-12 15:35
This article records the process of implementing TCP Client on the Tina system, and combines lvgl to realize opening a TCP connection by clicking a screen button. After connecting to the TCP Server , start the lvgl timer, periodically send strings to the Server , and create a child process to receive the strings sent by the 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");
}
}
Notes on code design:
1 ) lvgl is not thread-safe, so a process is created here to read the socket buffer. Initially, I wanted to use a 10ms timer, but I found that read() or another function recv() are blocking. Once called, they will wait for data to be received, which will affect the UI refresh.
2 ) Personally, I think that using fork() to create a process is not safe either, because the child process is a copy of the parent process. If the program can jump out of the receiving dead loop block, it may also jump to the code related to the lvgl component, and then there will be competition for peripheral resources (the above view is only obtained through analysis and has not been verified). Of course, there should be another multi-process method, that is, to create a TCP communication application separately, and then use execl() and other exec group functions to create a process with an independent code segment (this idea will be verified later when there is an opportunity).
3 ) In addition, the information received by the child process is only output to the console. If it is to be displayed on the screen, it needs to be passed to the parent process (the main program segment of the case) through process communication. This also shows the shortcomings of lvgl . As a GUI library mainly used for MCU , it lacks support for multi-threading.
4 ) In terms of subsequent work direction, I plan to focus on learning the Linux API first , and try to use Linux 's own multi-threaded framework, while loading lvgl and socket communication to achieve a better fit.
|