/* * 模块名称:Remote Display * 模块功能:实现 RemoDispBus 协议,提供远程 LCD 显示与按键转发功能 * 适用平台:通用嵌入式平台 * 作者:王建锋 * 创建日期:2026-05-11 * 修改记录: * 2026-05-11 王建锋 创建初始版本 */ #include #include #include "Drv/lcd/lcd.h" #include "TCP/tcp.h" #include "Drv/key.h" #include "remoteDisplay.h" #include "remoteDisplayProtocol.h" /* 回复帧最大长度:头4 + 最大 payload(4+LCD_DISPLAYMEMORYSIZE) + crc1 */ #define REMO_REPLY_BUF_SIZE (4 + 4 + LCD_DISPLAYMEMORYSIZE + 1) /* * 函数功能:计算数据的XOR校验和 * 入口参数:p_data - 数据缓冲区指针 const uint8_t* 非NULL * len - 数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN * 返回值:校验和值 uint8_t 0 - 255 * 限定条件:p_data指针必须指向有效的内存区域,长度至少为len字节 * 函数说明:1. 采用XOR算法计算校验和 * 2. 将所有数据字节进行异或运算 * 3. 当len为0时,返回0 */ static uint8_t calc_crc(const uint8_t *p_data, unsigned int len) { uint8_t crc = 0; unsigned int i; if (p_data == NULL) { return 0; } for (i = 0; i < len; i++) { crc ^= p_data[i]; } return crc; } /* * 函数功能:构造设备回复帧并发送 * 入口参数:client - 客户端 socket int 有效的 socket 描述符 * cmd - 命令字 uint8_t 参考 RemoDispBus 命令定义 * data - 回复数据指针 const uint8_t* 可为 NULL * data_len - 回复数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN * 出口参数:无 * 返回值:0 - 发送成功 * -1 - 发送失败或参数非法 * 限定条件:client 已连接且有效 * 函数说明:构造 [TAG_DEVICE][cmd][len_hi][len_lo][data][crc] 帧并一次性发送 */ static int send_reply(int client, uint8_t cmd, const uint8_t* data, unsigned int data_len) { uint8_t buf[REMO_REPLY_BUF_SIZE]; unsigned int total = 4 + data_len + 1; if (total > REMO_REPLY_BUF_SIZE) return -1; buf[0] = TAG_DEVICE; buf[1] = cmd & 0xFF; buf[2] = (data_len >> 8) & 0xFF; buf[3] = data_len & 0xFF; if (data_len) memcpy(buf + 4, data, data_len); buf[4 + data_len] = calc_crc(data, data_len); return TcpClient_Send(client, (const char*)buf, total) == (int)total ? 0 : -1; } /* * 函数功能:处理 CMD_LCDMEM 请求并回复显存数据 * 入口参数:client - 客户端 socket int 有效的 socket 描述符 * req_data - 请求数据指针 const uint8_t* 期望至少 4 个字节 * req_len - 请求数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN * 出口参数:无 * 返回值:无 * 限定条件:req_data 可为 NULL,但 req_len 必须有效 * 函数说明:解析大端起始地址,读取显存并通过 send_reply 发送结果 */ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int req_len) { const uint8_t *framebuffer = Lcd_GetFrameBuffer(); unsigned int start_addr = 0; if (req_len >= 4) start_addr = ((unsigned int)req_data[0] << 24) | ((unsigned int)req_data[1] << 16) | ((unsigned int)req_data[2] << 8) | req_data[3]; /* 回复 payload = [4B 地址大端][显存],Python 取 payload[4:] 为位图 */ uint8_t payload[4 + LCD_DISPLAYMEMORYSIZE]; payload[0] = (start_addr >> 24) & 0xFF; payload[1] = (start_addr >> 16) & 0xFF; payload[2] = (start_addr >> 8) & 0xFF; payload[3] = start_addr & 0xFF; unsigned int copy_len = LCD_DISPLAYMEMORYSIZE; if (start_addr < LCD_DISPLAYMEMORYSIZE) { unsigned int offset = start_addr; copy_len = LCD_DISPLAYMEMORYSIZE - offset; memcpy(payload + 4, framebuffer + offset, copy_len); } else { copy_len = 0; } send_reply(client, CMD_LCDMEM, payload, 4 + copy_len); } /* * 函数功能:处理 CMD_INIT 请求并回复 LCD 参数 * 入口参数:client - 客户端 socket int 有效的 socket 描述符 * 出口参数:无 * 返回值:无 * 限定条件:client 已连接且有效 * 函数说明:回复 LCD 宽度、高度和显存大小的 8 字节信息 */ static void handle_cmd_init(int client) { uint8_t data[8]; data[0] = (LCD_SIZE_X >> 8) & 0xFF; data[1] = LCD_SIZE_X & 0xFF; data[2] = (LCD_SIZE_Y >> 8) & 0xFF; data[3] = LCD_SIZE_Y & 0xFF; data[4] = (LCD_DISPLAYMEMORYSIZE >> 24) & 0xFF; data[5] = (LCD_DISPLAYMEMORYSIZE >> 16) & 0xFF; data[6] = (LCD_DISPLAYMEMORYSIZE >> 8) & 0xFF; data[7] = LCD_DISPLAYMEMORYSIZE & 0xFF; send_reply(client, CMD_INIT, data, 8); } /* * 函数功能:处理 CMD_KEY 请求并更新远程按键信号 * 入口参数:client - 客户端 socket int 有效的 socket 描述符 * data - 键值数据指针 const uint8_t* 期望至少 1 字节 * len - 数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN * 出口参数:无 * 返回值:无 * 限定条件:data 必须包含至少 1 个字节键值 * 函数说明:将按键值写入全局远程按键结构,供主循环处理 */ static void handle_cmd_key(int client, const uint8_t* data, unsigned int len) { #ifdef DEBUG printf("handle_cmd_key: 0x%02X\n", data[0]); #endif g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW; g_tRemoteKey.byKeyValue = data[0]; } /* * 函数功能:处理 CMD_KEEPLIVE 请求并回复空帧 * 入口参数:client - 客户端 socket int 有效的 socket 描述符 * 出口参数:无 * 返回值:无 * 限定条件:client 已连接且有效 * 函数说明:发送保活响应帧,无数据部分 */ static void handle_cmd_keeplive(int client) { send_reply(client, CMD_KEEPLIVE, (const uint8_t*)NULL, 0); } /* * 函数功能:TCP 服务器线程入口函数 * 入口参数:arg - 线程参数指针 void* 实际类型为 server_thread_arg_t* * 出口参数:无 * 返回值:无 * 限定条件:arg 必须指向有效的 server_thread_arg_t 实例 * 函数说明:创建 TCP 监听 socket,接受客户端连接并按 RemoDispBus 协议处理请求 */ void tcp_server_thread_fn(void* arg) { server_thread_arg_t* ctx = (server_thread_arg_t*)arg; int server_sock = TcpServer_Listen((uint16_t)ctx->port); if (server_sock == TCP_INVALID_SOCKET) { fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port); return; } *ctx->p_server_sock = server_sock; printf("[TCP Server] RemoDispBus 监听端口 %d,等待客户端连接...\n", ctx->port); /* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */ while (!*ctx->p_quit) { /* 阻塞等待一个客户端连接;主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */ int client = TcpServer_Accept(server_sock); if (client == TCP_INVALID_SOCKET) { if (*ctx->p_quit) { break; } continue; } printf("[TCP Server] 客户端连接成功\n"); /* 为当前客户端初始化独立的协议解析实例 */ protocol_instance_t proto_ins; protocol_init(&proto_ins); /* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */ while (!*ctx->p_quit) { uint8_t recv_byte; /* 逐字节接收 */ int n = TcpClient_Recv(client, (uint8_t*)&recv_byte, 1); if (n > 0) { /* 成功收到1字节 */ /* 喂给状态机逐字节处理 */ protocol_byte_handler(&proto_ins, recv_byte); /* 检查是否有完整帧解析完成 */ if (proto_ins.frame_done_flag == 1) { /* 根据解析到的CMD分发处理 */ switch (proto_ins.recv_cmd) { case CMD_KEEPLIVE: handle_cmd_keeplive(client); break; case CMD_INIT: handle_cmd_init(client); break; case CMD_KEY: handle_cmd_key(client, proto_ins.recv_buf, proto_ins.expect_data_len); break; case CMD_LCDMEM: handle_cmd_lcdmem(client, proto_ins.recv_buf, proto_ins.expect_data_len); break; default: send_reply(client, proto_ins.recv_cmd, (const uint8_t*)NULL, 0); break; } /* 处理完成后,复位协议实例,准备接收下一帧 */ protocol_init(&proto_ins); } } else { /* n==0(客户端关闭)或 n<0(读取错误)*/ printf("[TCP Server] 客户端关闭连接\n"); break; } } /* 当前客户端处理结束,关闭该连接;外层循环继续 Accept 下一个客户端 */ TcpClient_Close(client); } TcpServer_Close(server_sock); *ctx->p_server_sock = TCP_INVALID_SOCKET; printf("[TCP Server] 已退出\n"); } /* * 函数功能:启动 TCP 服务并创建服务器线程 * 入口参数:out_server_th - 输出线程句柄 thread_handle_t* 非空 * io_server_arg - 输入输出服务器线程参数 server_thread_arg_t* 非空 * 出口参数:out_server_th - 输出创建的线程句柄 * 返回值:0 - 成功 * 1 - 失败(已调用 Tcp_Cleanup) * 限定条件:io_server_arg 必须初始化并提供有效端口、socket 和退出标志指针 * 函数说明:初始化 TCP 子系统并创建服务器线程,若失败则清理资源 */ int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg) { if (Tcp_Init() != 0) { fprintf(stderr, "Tcp_Init failed\n"); return 1; } if (Thread_Create(tcp_server_thread_fn, io_server_arg, out_server_th) != 0) { fprintf(stderr, "Thread_Create(server) failed\n"); Tcp_Cleanup(); return 1; } Sleep(200); return 0; }