#include "tjc_usart_hmi.h" #include #include #include #include #include "bsp_Flash.h" // 添加Flash操作 #define STR_LENGTH 100 // 环形缓冲区结构 typedef struct { uint16_t Head; uint16_t Tail; uint16_t Length; uint8_t Ring_data[RINGBUFF_LEN]; } RingBuff_t; static RingBuff_t ringBuff; // 创建一个环形缓冲区 // 指令缓冲区 static uint8_t command_buffer[MAX_COMMAND_LEN]; static uint16_t cmd_index = 0; // COM_Uart2指针,用于访问串口驱动 static bsp_Uart_t *p_TJC_Uart = NULL; // 设备列表(最多32个设备) static DeviceInfo device_list[32]; static uint8_t device_count = 0; static uint8_t alarm_history_count = 6; /******************************************************** 函数名: TJC_CleanBufferFromInvalidPatterns 功能: 从环形缓冲区中清除所有无效的04 FF FF FF模式 返回值: 清除的字节数 **********************************************************/ uint16_t TJC_CleanBufferFromInvalidPatterns(void) { uint16_t cleaned_bytes = 0; uint16_t buff_len = getRingBuffLength(); // 不断查找并删除04 FF FF FF模式 while (buff_len >= 4) { // 查看缓冲区前4个字节 uint8_t pattern[4]; for (uint8_t i = 0; i < 4; i++) { pattern[i] = read1BFromRingBuff(i); } // 检查是否为 04 FF FF FF 模式 if (pattern[0] == 0x04 && pattern[1] == 0xFF && pattern[2] == 0xFF && pattern[3] == 0xFF) { // 删除这4个字节 deleteRingBuff(4); cleaned_bytes += 4; buff_len = getRingBuffLength(); } else { // 如果不是04 FF FF FF模式,停止清理 break; } } return cleaned_bytes; } /******************************************************** 函数名: TJC_Init 功能: 初始化TJC串口屏驱动 输入参数: pUart: 指向bsp_Uart_t结构体的指针,用于与串口屏通信 **********************************************************/ void TJC_Init(bsp_Uart_t *pUart) { if (pUart == NULL) { return; } p_TJC_Uart = pUart; initRingBuffer(); // 清理可能存在的无效数据 TJC_CleanBufferFromInvalidPatterns(); // 将数据解析函数设置为TJC串口屏解析函数 p_TJC_Uart->Rx_DataAnalysis = TJC_ProcessSerialData; // 初始化设备列表 device_count = 0; memset(device_list, 0, sizeof(device_list)); // 发送初始化完成消息 TJCPrintf("t0.txt=\"TJC初始化完成\""); } /******************************************************** 函数名: TJC_SendData 功能: 向串口屏发送数据 输入参数: data: 要发送的数据指针 len: 数据长度 **********************************************************/ void TJC_SendData(uint8_t *data, uint16_t len) { if (p_TJC_Uart == NULL || data == NULL || len == 0) { return; } // 使用bsp_Uart模块的发送函数 p_TJC_Uart->Send(p_TJC_Uart, data, len); } /******************************************************** 函数名: TJCPrintf 功能: 向串口屏发送格式化字符串(自动添加0xFF结束符) 输入参数: 格式字符串和可变参数 **********************************************************/ void TJCPrintf(const char *str, ...) { if (p_TJC_Uart == NULL) { return; } char buffer[STR_LENGTH + 4]; // 预留3个0xFF的位置 uint8_t end_bytes[3] = {0xFF, 0xFF, 0xFF}; va_list arg_ptr; va_start(arg_ptr, str); int len = vsnprintf(buffer, STR_LENGTH, str, arg_ptr); va_end(arg_ptr); if (len > 0) { // 发送格式化字符串 p_TJC_Uart->Send(p_TJC_Uart, (uint8_t *)buffer, len); // 发送结束符 p_TJC_Uart->Send(p_TJC_Uart, end_bytes, 3); } } /******************************************************** 函数名: TJC_SendResponse 功能: 发送响应给串口屏 输入参数: response: 响应字符串 **********************************************************/ void TJC_SendResponse(const char *response) { TJCPrintf("%s", response); } /******************************************************** 函数名: TJC_CheckEndBytes 功能: 检查指令结束符 输入参数: data: 数据指针 len: 数据长度 end_pos: 结束位置指针 返回值: 1: 找到结束符,0: 未找到 **********************************************************/ uint8_t TJC_CheckEndBytes(uint8_t *data, uint16_t len, uint16_t *end_pos) { if (len < 3) return 0; // 从后向前查找更高效 for (uint16_t i = 0; i <= len - 3; i++) { if (data[i] == 0xFF && data[i+1] == 0xFF && data[i+2] == 0xFF) { *end_pos = i; return 1; } } return 0; } /******************************************************** 函数名: CalculateCRC16 功能: 计算MODBUS CRC16校验码 输入参数: data: 数据指针 length: 数据长度 返回值: CRC16校验码 **********************************************************/ uint16_t CalculateCRC16(uint8_t *data, uint16_t length) { uint16_t crc = 0xFFFF; uint16_t i, j; for (i = 0; i < length; i++) { crc ^= data[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return crc; } /******************************************************** 函数名: TJC_AddDeviceToFlash 功能: 解析并存储设备信息到内存数组(后续会存入Flash) 输入参数: data: 指令数据 len: 数据长度 返回值: 1: 成功,0: 失败 **********************************************************/ uint8_t TJC_AddDeviceToFlash(uint8_t *data, uint16_t len) { if (len < 6) { //TJCPrintf("t0.txt=\"指令太短\""); return 0; } DeviceInfo new_device; uint8_t temp_buffer[50]; uint8_t temp_index = 0; // 跳过指令头 AA 55 uint16_t i = 2; // 解析数据1: 端口号(如COM1) temp_index = 0; while (i < len && data[i] != DATA_SEPARATOR) { if (temp_index < sizeof(temp_buffer) - 1) { temp_buffer[temp_index++] = data[i++]; } else { i++; } } if (i >= len) { //TJCPrintf("t0.txt=\"解析端口号失败\""); return 0; } temp_buffer[temp_index] = '\0'; // 解析端口号,支持COM1或直接数字 if (strncmp((char*)temp_buffer, "COM", 3) == 0) { new_device.port = atoi((char*)temp_buffer + 3); } else { new_device.port = atoi((char*)temp_buffer); } // 验证端口号范围:只有COM1-COM4 if (new_device.port < 1 || new_device.port > 4) { TJCPrintf("t0.txt=\"端口号无效:%d(只支持COM1-COM4)\"", new_device.port); return 0; } i++; // 跳过分隔符AA // 解析数据2: 区域名 temp_index = 0; while (i < len && data[i] != DATA_SEPARATOR) { if (temp_index < sizeof(temp_buffer) - 1) { temp_buffer[temp_index++] = data[i++]; } else { i++; } } if (i >= len) { //TJCPrintf("t0.txt=\"解析区域名失败\""); return 0; } temp_buffer[temp_index] = '\0'; strncpy(new_device.region, (char*)temp_buffer, sizeof(new_device.region) - 1); new_device.region[sizeof(new_device.region) - 1] = '\0'; i++; // 跳过分隔符AA // 解析数据3: 设备ID temp_index = 0; while (i < len && data[i] != DATA_SEPARATOR) { if (temp_index < sizeof(temp_buffer) - 1) { temp_buffer[temp_index++] = data[i++]; } else { i++; } } if (i >= len) { //TJCPrintf("t0.txt=\"解析设备ID失败\""); return 0; } temp_buffer[temp_index] = '\0'; new_device.device_id = atoi((char*)temp_buffer); // 验证设备ID范围 if (new_device.device_id < 1 || new_device.device_id > 254) { //TJCPrintf("t0.txt=\"设备ID无效:%d\"", new_device.device_id); return 0; } i++; // 跳过分隔符AA // 解析数据4: 设备名 temp_index = 0; while (i < len && data[i] != DATA_SEPARATOR) { if (temp_index < sizeof(temp_buffer) - 1) { temp_buffer[temp_index++] = data[i++]; } else { i++; } } if (temp_index == 0) { //TJCPrintf("t0.txt=\"解析设备名失败\""); return 0; } temp_buffer[temp_index] = '\0'; strncpy(new_device.device_name, (char*)temp_buffer, sizeof(new_device.device_name) - 1); new_device.device_name[sizeof(new_device.device_name) - 1] = '\0'; // 初始化设备状态为正常 new_device.leak_status = LEAK_NORMAL; new_device.break_status = BREAK_NORMAL; new_device.comm_status = COMM_STATUS_NORMAL; // 显示解析结果 // TJCPrintf("t0.txt=\"解析成功\""); // TJCPrintf("t1.txt=\"端口:%d 区域:%s\"", new_device.port, new_device.region); // TJCPrintf("t2.txt=\"ID:%d 名称:%s\"", new_device.device_id, new_device.device_name); // 检查是否已存在相同ID的设备 for (uint8_t j = 0; j < device_count; j++) { if (device_list[j].device_id == new_device.device_id) { // 更新现有设备 device_list[j] = new_device; //TJCPrintf("t3.txt=\"设备%d已更新\"", new_device.device_id); return 1; } } // 添加新设备到内存数组 if (device_count < 32) { device_list[device_count] = new_device; device_count++; //TJCPrintf("t3.txt=\"设备%d添加成功\"", new_device.device_id); //TJCPrintf("t4.txt=\"总设备数:%d\"", device_count); return 1; } else { //TJCPrintf("t3.txt=\"设备列表已满\""); return 0; } } /******************************************************** 函数名: TJC_SendDeviceList 功能: 发送设备列表到添加设备界面 说明: 将设备信息显示在t1_1到t8_4文本框中 每个设备占用4个文本框,格式如下: - tX_1: 区域名 - tX_2: 端口号 - tX_3: 设备ID - tX_4: 设备名称 其中X从1到8,分别对应第1到第8个设备 最多显示8个设备 **********************************************************/ void TJC_SendDeviceList(void) { // 清空所有相关文本框(t1_1到t8_4) for (int row = 1; row <= 8; row++) { for (int col = 1; col <= 4; col++) { TJCPrintf("t%d_%d.txt=\"\"", row, col); // 每清空几个文本框稍作延迟 if ((row * col) % 4 == 0) { HAL_Delay(5); } } } HAL_Delay(100); if (device_count == 0) { // 如果没有设备,显示提示信息 TJCPrintf("t1_1.txt=\"无设备数据\""); TJCPrintf("t0.txt=\"设备列表为空\""); HAL_Delay(300); TJCPrintf("t0.txt=\"\""); return; } // 计算要显示的设备数量(最多8个) uint8_t display_count = (device_count > 8) ? 8 : device_count; // 显示设备信息 for (uint8_t i = 0; i < display_count; i++) { DeviceInfo *device = &device_list[i]; // 计算行号(从1开始) int row = i + 1; // tX_1: 区域名 TJCPrintf("t%d_1.txt=\"%s\"", row, device->region); HAL_Delay(10); // tX_2: 端口号(显示为COMx格式) TJCPrintf("t%d_2.txt=\"COM%d\"", row, device->port); HAL_Delay(10); // tX_3: 设备ID TJCPrintf("t%d_3.txt=\"%d\"", row, device->device_id); HAL_Delay(10); // tX_4: 设备名称 TJCPrintf("t%d_4.txt=\"%s\"", row, device->device_name); HAL_Delay(10); // 每显示完一个设备稍作等待 HAL_Delay(20); } } /******************************************************** 函数名: TJC_SendRegionStats 功能: 发送区域统计信息到串口屏 说明: 统计每个区域的设备数量和各种报警数量 t1_1显示区域名,t1_2显示该区域设备数量 t1_3显示漏液数量,t1_4显示断带数量,t1_5显示通讯异常数量 最多显示4个区域(t1_1-t4_5) **********************************************************/ void TJC_SendRegionStats(void) { HAL_Delay(100); if (device_count == 0) { TJCPrintf("t1_1.txt=\"无设备数据\""); return; } // 统计每个区域的信息 RegionStats region_stats[10]; uint8_t region_count = 0; // 初始化区域统计数组 for (uint8_t i = 0; i < device_count; i++) { DeviceInfo *device = &device_list[i]; uint8_t found = 0; // 查找是否已存在该区域的统计信息 for (uint8_t j = 0; j < region_count; j++) { if (strcmp(region_stats[j].region_name, device->region) == 0) { found = 1; // 更新该区域的统计信息 region_stats[j].total_devices++; if (device->leak_status == LEAK_ABNORMAL) { region_stats[j].leak_devices++; } if (device->break_status == BREAK_ABNORMAL) { region_stats[j].break_devices++; } if (device->comm_status == COMM_STATUS_ABNORMAL) { region_stats[j].comm_devices++; } break; } } // 如果区域不存在,添加新的区域统计 if (!found && region_count < 10) { strncpy(region_stats[region_count].region_name, device->region, 19); region_stats[region_count].region_name[19] = '\0'; region_stats[region_count].total_devices = 1; // 初始化报警数量 region_stats[region_count].leak_devices = (device->leak_status == LEAK_ABNORMAL) ? 1 : 0; region_stats[region_count].break_devices = (device->break_status == BREAK_ABNORMAL) ? 1 : 0; region_stats[region_count].comm_devices = (device->comm_status == COMM_STATUS_ABNORMAL) ? 1 : 0; region_count++; } } // 计算要显示的区域数量(最多4个) uint8_t display_count = (region_count > 4) ? 4 : region_count; // 显示区域统计信息 for (uint8_t i = 0; i < display_count; i++) { RegionStats *stats = ®ion_stats[i]; // 计算行号(从1开始) int row = i + 1; // 区域名 TJCPrintf("t%d_1.txt=\"%s\"", row, stats->region_name); HAL_Delay(10); // 总设备数量 TJCPrintf("t%d_2.txt=\"%d\"", row, stats->total_devices); HAL_Delay(10); // 漏液设备数量 TJCPrintf("t%d_3.txt=\"%d\"", row, stats->leak_devices); HAL_Delay(10); // 断带设备数量 TJCPrintf("t%d_4.txt=\"%d\"", row, stats->break_devices); HAL_Delay(10); // 通信异常设备数量 TJCPrintf("t%d_5.txt=\"%d\"", row, stats->comm_devices); HAL_Delay(10); // 每显示完一行稍作等待 HAL_Delay(30); } } /******************************************************** 函数名: TJC_SendRegionDeviceDetails 功能: 发送指定区域的设备详情到串口屏 输入参数: region_index: 区域索引(1-4对应不同的区域) 说明: 显示格式: 每个设备显示15个文本框: - tX_1: 设备ID - tX_2: 设备名称 - tX_3: 通信状态(正常/异常) - tX_4-tX_6: 第一个通道状态(漏液/断带/漏液位置) - tX_7-tX_9: 第二个通道状态(漏液/断带/漏液位置) - tX_10-tX_12: 第三个通道状态(漏液/断带/漏液位置) - tX_13-tX_15: 第四个通道状态(漏液/断带/漏液位置) 一个区域最多显示4个设备,分别显示在: t1_1-t1_15: 第一个设备 t2_1-t2_15: 第二个设备 t3_1-t3_15: 第三个设备 t4_1-t4_15: 第四个设备 **********************************************************/ void TJC_SendRegionDeviceDetails(uint8_t region_index) { // 清空所有相关文本框(t1_1到t4_15) for (int row = 1; row <= 4; row++) { for (int col = 1; col <= 15; col++) { TJCPrintf("t%d_%d.txt=\"\"", row, col); if ((row * col) % 4 == 0) { HAL_Delay(5); } } } HAL_Delay(100); // 检查是否有设备 if (device_count == 0) { TJCPrintf("t1_1.txt=\"无设备数据\""); return; } // 获取所有不同的区域名称 char region_names[10][20]; uint8_t region_count = 0; for (uint8_t i = 0; i < device_count && region_count < 10; i++) { DeviceInfo *device = &device_list[i]; uint8_t found = 0; for (uint8_t j = 0; j < region_count; j++) { if (strcmp(region_names[j], device->region) == 0) { found = 1; break; } } if (!found) { strncpy(region_names[region_count], device->region, 19); region_names[region_count][19] = '\0'; region_count++; } } // 检查区域索引是否有效 if (region_index < 1 || region_index > region_count) { TJCPrintf("t1_1.txt=\"区域无效\""); return; } // 获取指定区域的设备(最多4个) uint8_t region_devices[4]; uint8_t region_device_count = 0; for (uint8_t i = 0; i < device_count && region_device_count < 4; i++) { DeviceInfo *device = &device_list[i]; if (strcmp(device->region, region_names[region_index-1]) == 0) { region_devices[region_device_count] = i; region_device_count++; } } // 显示该区域的设备详情 for (uint8_t i = 0; i < region_device_count; i++) { DeviceInfo *device = &device_list[region_devices[i]]; // 计算行号(从1开始) int row = i + 1; // 设备基本信息 TJCPrintf("t%d_1.txt=\"%d\"", row, device->device_id); HAL_Delay(10); TJCPrintf("t%d_2.txt=\"%s\"", row, device->device_name); HAL_Delay(10); // 通信状态 const char *comm_status_str; switch (device->comm_status) { case COMM_STATUS_NORMAL: comm_status_str = "正常"; break; case COMM_STATUS_ABNORMAL: comm_status_str = "异常"; break; default: comm_status_str = "未知"; break; } TJCPrintf("t%d_3.txt=\"%s\"", row, comm_status_str); HAL_Delay(10); // 4个通道的状态 for (int channel = 0; channel < 4; channel++) { int base_col = 4 + channel * 3; // 漏液状态 const char *leak_status_str; switch (device->channels[channel].leak_status) { case LEAK_NORMAL: leak_status_str = "正常"; break; case LEAK_ABNORMAL: leak_status_str = "漏液"; break; default: leak_status_str = "-"; break; } TJCPrintf("t%d_%d.txt=\"%s\"", row, base_col, leak_status_str); HAL_Delay(5); // 断带状态 const char *break_status_str; switch (device->channels[channel].break_status) { case BREAK_NORMAL: break_status_str = "正常"; break; case BREAK_ABNORMAL: break_status_str = "断带"; break; default: break_status_str = "-"; break; } TJCPrintf("t%d_%d.txt=\"%s\"", row, base_col + 1, break_status_str); HAL_Delay(5); // 漏液位置 char leak_meter_str[10]; if (device->channels[channel].leak_status == LEAK_ABNORMAL) { sprintf(leak_meter_str, "%d米", device->channels[channel].leak_meter); } else { sprintf(leak_meter_str, "0米"); } TJCPrintf("t%d_%d.txt=\"%s\"", row, base_col + 2, leak_meter_str); HAL_Delay(5); } // 每显示完一个设备稍作等待 HAL_Delay(30); } } /******************************************************** 函数名: TJC_ProcessDeleteCommand 功能: 处理删除设备指令 输入参数: cmd: 指令数据 len: 指令长度 说明: 指令格式:AA 55 04 [8个选中标志字节] [CRC低字节] [CRC高字节] 每个选中标志字节:00表示未选中,01表示选中删除 最多可以一次删除8个设备 **********************************************************/ void TJC_ProcessDeleteCommand(uint8_t *cmd, uint16_t len) { if (len != 13) { // 添加调试信息 char debug_msg[50]; sprintf(debug_msg, "t0.txt=\"删除指令长度错误:%d\"", len); TJCPrintf(debug_msg); HAL_Delay(300); TJCPrintf("t0.txt=\"\""); return; } // 检查功能码是否正确 if (cmd[2] != CMD_DELETE_DEVICE) { TJCPrintf("t0.txt=\"删除功能码错误\""); HAL_Delay(300); TJCPrintf("t0.txt=\"\""); return; } // 提取8个选中标志字节(从第3字节开始) uint8_t delete_flags[8]; for (int i = 0; i < 8; i++) { delete_flags[i] = cmd[3 + i]; } // 执行删除操作 TJC_DeleteDevices(delete_flags, 8); } /******************************************************** 函数名: findDeviceCmdLength 功能: 查找添加设备指令的长度 返回值: 指令长度,0表示长度不足 说明: 通过查找AA分隔符确定指令长度 **********************************************************/ uint16_t findDeviceCmdLength(void) { uint16_t buff_len = getRingBuffLength(); if (buff_len < 10) { // 最小长度 return 0; } uint8_t aa_count = 0; uint16_t last_aa_pos = 0; // 从第三个字节开始查找AA分隔符 for (uint16_t i = 2; i < buff_len; i++) { uint8_t byte = read1BFromRingBuff(i); if (byte == DATA_SEPARATOR) { aa_count++; last_aa_pos = i; // 添加设备指令需要4个AA分隔符 if (aa_count >= 4) { // 检查是否有足够的空间存放CRC(2字节) if (last_aa_pos + 3 <= buff_len) { return last_aa_pos + 3; // 包括AA和CRC } } } } return 0; // 没有找到完整的指令 } /******************************************************** 函数名: TJC_DeleteDevices 功能: 根据选中标志删除设备 输入参数: delete_flags: 选中标志数组 flag_count: 标志数量 说明: 删除device_list中的设备,并更新device_count 支持一次删除多个设备 **********************************************************/ void TJC_DeleteDevices(uint8_t *delete_flags, uint8_t flag_count) { if (device_count == 0) { //TJCPrintf("t0.txt=\"无设备可删除\""); return; } // 统计要删除的设备数量 uint8_t delete_count = 0; uint8_t to_delete[32] = {0}; // 标记要删除的设备索引 // 遍历标志数组,标记要删除的设备 // 注意:flag_count通常为8,对应当前显示的8个设备 for (uint8_t i = 0; i < flag_count && i < device_count; i++) { if (delete_flags[i] == 0x01) { to_delete[i] = 1; delete_count++; } } if (delete_count == 0) { //TJCPrintf("t0.txt=\"未选中设备\""); return; } // 从后往前删除,避免索引混乱 uint8_t new_device_count = 0; DeviceInfo temp_list[32]; // 复制未标记删除的设备到临时数组 for (uint8_t i = 0; i < device_count; i++) { // 检查当前设备是否在删除列表中 uint8_t should_delete = 0; // 只检查前flag_count个设备(对应界面上显示的设备) if (i < flag_count && to_delete[i] == 1) { should_delete = 1; } if (!should_delete) { // 保留设备 temp_list[new_device_count] = device_list[i]; new_device_count++; } } // 更新设备列表和数量 device_count = new_device_count; for (uint8_t i = 0; i < device_count; i++) { device_list[i] = temp_list[i]; } // 清空剩余位置 for (uint8_t i = device_count; i < 32; i++) { memset(&device_list[i], 0, sizeof(DeviceInfo)); } // // 显示删除结果 // char msg[50]; // sprintf(msg, "t0.txt=\"删除了%d个设备\"", delete_count); // TJCPrintf(msg); // HAL_Delay(300); } /******************************************************** 函数名: TJC_ProcessCustomCommand 功能: 处理自定义指令 输入参数: cmd: 指令数据 len: 指令长度 **********************************************************/ void TJC_ProcessCustomCommand(uint8_t *cmd, uint16_t len) { if (len < 6) { return; // 指令长度不足 } // 验证指令头 if (cmd[0] != CUSTOM_CMD_HEADER_0 || cmd[1] != CUSTOM_CMD_HEADER_1) { return; // 指令头错误 } // 提取CRC(最后两个字节) uint16_t received_crc = (cmd[len-1] << 8) | cmd[len-2]; // 计算CRC(不包括CRC本身) uint16_t calculated_crc = CalculateCRC16(cmd, len-2); // 校验CRC if (received_crc != calculated_crc) { // CRC错误,忽略指令 return; } // 判断指令类型 uint8_t cmd_type = cmd[2]; // 第三个字节为功能码 // 如果是添加设备指令(第三个字节是'C' = 0x43) if (cmd_type == ADD_DEVICE_CMD_BYTE) { // 这是设备添加指令 if (TJC_AddDeviceToFlash(cmd, len)) { TJCPrintf("b2.txt=\"add suc\""); } return; } // 如果是删除设备指令(功能码为0x04) if (cmd_type == CMD_DELETE_DEVICE) { // 处理删除设备指令 TJC_ProcessDeleteCommand(cmd, len); return; } // 如果是标准指令(6字节长度) if (len == 6) { uint8_t sub_cmd = cmd[3]; // 第四个字节为子命令 // 根据功能码和子命令处理 switch (cmd_type) { case CMD_DISPLAY_DATA: // 0x02 显示数据 switch (sub_cmd) { case SUB_CMD_SHOW_DEVICES: // 0x01 显示已添加的设备 TJC_SendDeviceList(); break; case SUB_CMD_REGION_STATS: // 0x02 主界面区域显示 TJC_SendRegionStats(); break; case SUB_CMD_REGION1_DEVICES: // 0x03 第一个区域设备 TJC_SendRegionDeviceDetails(1); break; case SUB_CMD_REGION2_DEVICES: // 0x04 第二个区域设备 TJC_SendRegionDeviceDetails(2); break; case SUB_CMD_REGION3_DEVICES: // 0x05 第三个区域设备 TJC_SendRegionDeviceDetails(3); break; case SUB_CMD_REGION4_DEVICES: // 0x06 第四个区域设备 TJC_SendRegionDeviceDetails(4); break; default: // 未知子命令,忽略 break; } break; case CMD_ALARM: // 0x03 报警 switch (sub_cmd) { case SUB_CMD_HISTORY_ALARM: // 0x01 历史报警 TJC_SendInitCommands(); break; case SUB_CMD_REALTIME_ALARM: // 0x02 实时报警 TJC_SendRealtimeAlarms(); break; default: // 未知子命令,忽略 break; } break; default: // 未知功能码,忽略指令 break; } } else { // 非6字节的标准指令,忽略 } } /******************************************************** 函数名: TJC_ProcessCommand 功能: 处理串口屏指令 输入参数: cmd: 指令数据 len: 指令长度 **********************************************************/ void TJC_ProcessCommand(uint8_t *cmd, uint16_t len) { // 如果指令为空,直接返回 if (len == 0) return; // 首先检查是否为自定义指令(以AA 55开头) if (len >= 3 && cmd[0] == CUSTOM_CMD_HEADER_0 && cmd[1] == CUSTOM_CMD_HEADER_1) { TJC_ProcessCustomCommand(cmd, len); return; } } /******************************************************** 函数名: TJC_ProcessSerialData 功能: 处理串口接收到的数据 输入参数: data: 接收到的数据 len: 数据长度 p_arg: 参数指针 **********************************************************/ void TJC_ProcessSerialData(u8 *data, u16 len, void *p_arg) { // 将接收到的数据写入环形缓冲区 for (uint16_t i = 0; i < len; i++) { writeRingBuff(data[i]); } // 循环处理缓冲区中的数据 while (1) { uint16_t buff_len = getRingBuffLength(); // 如果数据不足3字节(AA 55 功能码),退出 if (buff_len < 3) { break; } // 检查前两个字节是否为 AA 55 uint8_t first_byte = read1BFromRingBuff(0); uint8_t second_byte = read1BFromRingBuff(1); // 情况1:不是 AA 55,删除第一个字节继续 if (first_byte != CUSTOM_CMD_HEADER_0 || second_byte != CUSTOM_CMD_HEADER_1) { deleteRingBuff(1); // 删除第一个字节 continue; // 继续处理下一个字节 } // 获取功能码(第三个字节) uint8_t cmd_type = read1BFromRingBuff(2); // 检查是否为已知功能码 uint8_t valid_cmd = 0; if (cmd_type == ADD_DEVICE_CMD_BYTE || // 添加设备 cmd_type == CMD_DISPLAY_DATA || // 显示数据 cmd_type == CMD_ALARM || // 报警 cmd_type == CMD_DELETE_DEVICE) { // 删除设备 valid_cmd = 1; } // 情况2:未知功能码,删除 AA 55 两个字节 if (!valid_cmd) { deleteRingBuff(2); // 删除 AA 55 continue; // 继续处理 } // 确定指令长度 uint16_t cmd_length = 0; if (cmd_type == ADD_DEVICE_CMD_BYTE) { // 添加设备指令:可变长度,需要查找结束位置 cmd_length = findDeviceCmdLength(); if (cmd_length == 0) { // 长度不足,等待更多数据 break; } } else if (cmd_type == CMD_DISPLAY_DATA || cmd_type == CMD_ALARM) { cmd_length = 6; // 固定6字节 } else if (cmd_type == CMD_DELETE_DEVICE) { cmd_length = 13; // 固定13字节 } // 检查长度是否足够 if (buff_len < cmd_length) { // 数据不足,等待更多数据 break; } // 读取完整指令 uint8_t command_buffer[MAX_COMMAND_LEN]; for (uint16_t i = 0; i < cmd_length; i++) { command_buffer[i] = read1BFromRingBuff(i); } // 情况3:CRC校验失败,删除整个指令 uint16_t received_crc = (command_buffer[cmd_length-1] << 8) | command_buffer[cmd_length-2]; uint16_t calculated_crc = CalculateCRC16(command_buffer, cmd_length-2); if (received_crc != calculated_crc) { deleteRingBuff(cmd_length); // CRC错误,删除整个指令 continue; // 继续处理 } // 所有检查通过,处理有效指令 TJC_ProcessCommand(command_buffer, cmd_length); // 删除已处理的指令 deleteRingBuff(cmd_length); } } /******************************************************** 函数名: initRingBuffer 功能: 初始化环形缓冲区 **********************************************************/ void initRingBuffer(void) { ringBuff.Head = 0; ringBuff.Tail = 0; ringBuff.Length = 0; memset(ringBuff.Ring_data, 0, RINGBUFF_LEN); cmd_index = 0; } /******************************************************** 函数名: writeRingBuff 功能: 往环形缓冲区写入数据 输入参数: data: 要写入的数据 **********************************************************/ void writeRingBuff(uint8_t data) { if (ringBuff.Length >= RINGBUFF_LEN) { // 缓冲区已满,丢弃最旧的数据 ringBuff.Head = (ringBuff.Head + 1) % RINGBUFF_LEN; ringBuff.Length--; } ringBuff.Ring_data[ringBuff.Tail] = data; ringBuff.Tail = (ringBuff.Tail + 1) % RINGBUFF_LEN; ringBuff.Length++; } /******************************************************** 函数名: deleteRingBuff 功能: 删除环形缓冲区中指定长度的数据 输入参数: size: 要删除的数据长度 **********************************************************/ void deleteRingBuff(uint16_t size) { if (size >= ringBuff.Length) { initRingBuffer(); return; } for (int i = 0; i < size; i++) { if (ringBuff.Length == 0) { break; } ringBuff.Head = (ringBuff.Head + 1) % RINGBUFF_LEN; ringBuff.Length--; } } /******************************************************** 函数名: read1BFromRingBuff 功能: 从环形缓冲区读取指定位置的数据 输入参数: position: 读取位置(相对于Head的偏移) 返回值: 读取到的数据 **********************************************************/ uint8_t read1BFromRingBuff(uint16_t position) { if (position >= ringBuff.Length) { return 0; } uint16_t realPosition = (ringBuff.Head + position) % RINGBUFF_LEN; return ringBuff.Ring_data[realPosition]; } /******************************************************** 函数名: getRingBuffLength 功能: 获取环形缓冲区中的数据长度 返回值: 缓冲区中的数据长度 **********************************************************/ uint16_t getRingBuffLength(void) { return ringBuff.Length; } /******************************************************** 函数名: isRingBuffOverflow 功能: 检查环形缓冲区是否已满 返回值: 1: 缓冲区已满,0: 缓冲区未满 **********************************************************/ uint8_t isRingBuffOverflow(void) { return (ringBuff.Length >= RINGBUFF_LEN); } /*测试发送历史报警数据*/ void TJC_SendInitCommands(void) { HAL_Delay(100); for (int row = 1; row <= 15; row++) { char region[10]; char fault_type[10]; char start_time[20]; char end_time[20]; char device_id[10]; // 根据行号生成不同的数据 if (row <= 5) { strcpy(region, "LH"); } else if (row <= 10) { strcpy(region, "SH"); } else { strcpy(region, "BJ"); } // 故障类型交替 if (row % 2 == 0) { strcpy(fault_type, "漏液"); } else { strcpy(fault_type, "断带"); } // 设备ID(两位数,不足补0) sprintf(device_id, "%02d", row); // 计算日期(从2026-01-19开始) int day = 19 + (row - 1) % 30; // 保持在1-31天内 int month = 1 + (row - 1) / 30; // 月份递增 // 开始时间 int start_hour = 8 + (row - 1) / 6; // 每6行小时加1 int start_minute = (row * 3) % 60; if (start_hour >= 24) start_hour = 23; // 防止小时溢出 sprintf(start_time, "2026-%02d-%02d %02d:%02d", month, day, start_hour, start_minute); // 结束时间(开始时间+2小时) int end_hour = start_hour + 2; int end_minute = start_minute + 15; if (end_minute >= 60) { end_minute -= 60; end_hour++; } if (end_hour >= 24) { end_hour = 23; end_minute = 59; } sprintf(end_time, "2026-%02d-%02d %02d:%02d", month, day, end_hour, end_minute); // 发送第1列:区域 TJCPrintf("t%d_1.txt=\"%s\"", row, region); HAL_Delay(5); // 发送第2列:ID TJCPrintf("t%d_2.txt=\"%s\"", row, device_id); HAL_Delay(5); // 发送第3列:设备名称 TJCPrintf("t%d_3.txt=\"HK\"", row); HAL_Delay(5); // 发送第4列:报警类型 TJCPrintf("t%d_4.txt=\"%s\"", row, fault_type); HAL_Delay(5); // 发送第5列:开始时间 TJCPrintf("t%d_5.txt=\"%s\"", row, start_time); HAL_Delay(5); // 发送第6列:结束时间 TJCPrintf("t%d_6.txt=\"%s\"", row, end_time); HAL_Delay(5); // 每发送完一行,稍微等待一下 HAL_Delay(20); } } /******************************************************** 函数名: TJC_SendRealtimeAlarms 功能: 发送实时报警数据到串口屏 说明: 按照以下格式显示: t1_1: 区域名(英文) t1_2: 设备ID (1-254) t1_3: 设备名称(英文) t1_4: 通信状态(正常/异常) t1_5-t1_16: 每3个为一组,共4组,每组显示: 第1个:漏液状态(正常/漏液) 第2个:断带状态(正常/断带) 第3个:漏液米数(如果漏液则显示具体米数,否则显示0米) **********************************************************/ void TJC_SendRealtimeAlarms(void) { HAL_Delay(50); // 示例设备数据(实际使用时应该从设备列表中获取) DeviceInfo device_info = { .port = 1, .region = "SH", .device_id = 25, .device_name = "HK001" }; CommStatus comm_status = COMM_STATUS_NORMAL; // 示例4个通道状态 ChannelStatus channel_status[4] = { {LEAK_NORMAL, BREAK_ABNORMAL, 0}, // CH1: 正常, 断带, 0米 {LEAK_ABNORMAL, BREAK_NORMAL, 3}, // CH2: 漏液, 正常, 3米 {LEAK_NORMAL, BREAK_NORMAL, 0}, // CH3: 正常, 正常, 0米 {LEAK_ABNORMAL, BREAK_NORMAL, 4} // CH4: 漏液, 正常, 4米 }; // t1_1: 区域名 TJCPrintf("t1_1.txt=\"%s\"", device_info.region); HAL_Delay(10); // t1_2: 设备ID TJCPrintf("t1_2.txt=\"%d\"", device_info.device_id); HAL_Delay(10); // t1_3: 设备名称 TJCPrintf("t1_3.txt=\"%s\"", device_info.device_name); HAL_Delay(10); // t1_4: 通信状态 const char *comm_status_str; switch (comm_status) { case COMM_STATUS_NORMAL: comm_status_str = "正常"; break; case COMM_STATUS_ABNORMAL: comm_status_str = "异常"; break; default: comm_status_str = "未知"; break; } TJCPrintf("t1_4.txt=\"%s\"", comm_status_str); HAL_Delay(10); // 2. 发送4个通道的状态 (t1_5 到 t1_16) // 每个通道占用3个文本框 for (int channel = 0; channel < 4; channel++) { ChannelStatus *ch = &channel_status[channel]; // 计算文本框起始索引 int base_index = 5 + channel * 3; // 第一个文本框:漏液状态 const char *leak_status_str; switch (ch->leak_status) { case LEAK_NORMAL: leak_status_str = "正常"; break; case LEAK_ABNORMAL: leak_status_str = "漏液"; break; default: leak_status_str = "未知"; break; } TJCPrintf("t1_%d.txt=\"%s\"", base_index, leak_status_str); HAL_Delay(5); // 第二个文本框:断带状态 const char *break_status_str; switch (ch->break_status) { case BREAK_NORMAL: break_status_str = "正常"; break; case BREAK_ABNORMAL: break_status_str = "断带"; break; default: break_status_str = "未知"; break; } TJCPrintf("t1_%d.txt=\"%s\"", base_index + 1, break_status_str); HAL_Delay(5); // 第三个文本框:漏液位置 char leak_meter_str[10]; if (ch->leak_status == LEAK_ABNORMAL) { sprintf(leak_meter_str, "%d米", ch->leak_meter); } else { sprintf(leak_meter_str, "0米"); } TJCPrintf("t1_%d.txt=\"%s\"", base_index + 2, leak_meter_str); HAL_Delay(5); } } // 兼容旧代码的宏定义 #define usize getRingBuffLength() #define code_c() initRingBuffer() #define udelete(x) deleteRingBuff(x) #define u(x) read1BFromRingBuff(x)