#include "sub_comm/sub_comm_task.h" #include "app_init/app_init.h" #include "app_init/task_lock.h" #include "mylog/mylog.h" #include "wdt_task/wdt_task.h" #include "publicdata/publicdata.h" #include "collect_ctrl/collect_task.h" #include "collect_ctrl/led_ctrl.h" #include "flowctrl/flow_interface.h" #include "fault_cheak/fault_interface.h" #include "app_rtc/app_rtc.h" #include #include #include "usart.h" #define SUBCOMM_TASK_ID (TASK_ID_SubComm) #define SUBCOMM_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(SUBCOMM_TASK_ID)) #define SUBCOMM_LOCK_INIT() (u8_task_lock_ctrl(SUBCOMM_TASK_ID, TASK_LOCK_OP_INIT)) #define SUBCOMM_LOCK_TAKE() (u8_task_lock_ctrl(SUBCOMM_TASK_ID, TASK_LOCK_OP_TAKE)) #define SUBCOMM_LOCK_GIVE() (u8_task_lock_ctrl(SUBCOMM_TASK_ID, TASK_LOCK_OP_GIVE)) #define SUBCOMM_FEED_DOG() (v_wdt_setFlag(SUBCOMM_TASK_ID)) #define SUBCOMM_LOG(fmt, ...) MYLOG_MSG(SUBCOMM_TASK_ID, (fmt), ##__VA_ARGS__) /* ======================== 接收缓存(处理粘包/残包) ======================== */ /* 说明: * - USART0 中断已把数据写入 FIFO,本模块周期从 FIFO 读出并追加到该缓存; * - 缓存中可能存在: * 1) 粘包:一次收到多帧 -> 解析循环连续处理 * 2) 残包:一次只到半帧 -> 保留到下次继续拼 * 3) 杂散字节:找不到帧头/校验失败 -> 丢弃直到重新同步 */ #define SUBCOMM_RX_BUF_MAX (128U) /* ======================== SubComm 模块上下文(模块私有) ======================== */ typedef struct { /* 副板遥控/遥信缓存 */ U_SUB_YK_DATA sub_yk; U_SUB_YX_DATA sub_yx; /* 接收缓存(处理粘包/残包) */ U8_T rx_buf[SUBCOMM_RX_BUF_MAX]; U16_T rx_len; /* 最近一次收到有效 DI 状态帧(0x01)的时间戳(ms)。用于离线超时判断。 */ uint64_t last_di_ms; } S_SUBCOMM_CTX; static S_SUBCOMM_CTX g_subcomm_ctx; /* SubComm 模块运行时上下文(缓存+状态) */ /* ======================== 副板串口协议(USART0) ======================== */ /* 帧格式: * [0] 0x5A 帧头 * [1] cmd 命令码 * [2] len 数据长度 N(0~64) * [3] data[0..N-1] 数据域(可选) * [3+N] sum 校验:从帧头到数据域的累加和(8-bit 截断) * [4+N] 0xA5 帧尾 */ #define SUBCOMM_UART_ID (E_USART_0) #define SUBCOMM_FRAME_HEAD (0x5AU) #define SUBCOMM_FRAME_TAIL (0xA5U) #define SUBCOMM_DATA_MAX (64U) /* 命令码定义 */ #define SUBCOMM_CMD_DI_STATUS (0x01U) /* 辅板DI状态:辅板 -> 主板 */ #define SUBCOMM_CMD_DO_CTRL (0x02U) /* 控制继电器:主板 -> 辅板 */ #define SUBCOMM_CMD_SYS_INFO (0x03U) /* 系统信息:主板 -> 辅板 */ #define SUBCOMM_CMD_GUN_INFO (0x04U) /* 枪信息:主板 -> 辅板 */ /* DI 超时:超过该时间未收到 0x01,则认为副板离线(清零采集缓存)。 */ #define SUBCOMM_DI_TIMEOUT_MS (5000U) /* 接收缓存已纳入 g_subcomm_ctx 统一管理。 */ #if 1 // 初始化/数据清理 /** * @brief SubComm 模块数据初始化。 * * - 清空 YK/YX 缓存(默认全 0); * - 重置接收状态机到“等待帧头”。 */ static void v_sub_comm_data_init(void) { (void)memset(&g_subcomm_ctx.sub_yk, 0, sizeof(g_subcomm_ctx.sub_yk)); (void)memset(&g_subcomm_ctx.sub_yx, 0, sizeof(g_subcomm_ctx.sub_yx)); (void)memset(g_subcomm_ctx.rx_buf, 0, sizeof(g_subcomm_ctx.rx_buf)); g_subcomm_ctx.rx_len = 0U; /* 置 0 表示“尚未收到任何 DI 帧”。 */ g_subcomm_ctx.last_di_ms = 0U; } #endif #if 1 // bit 操作辅助 /* ======================== bit 操作辅助 ======================== */ /** * @brief 按 bit 写入 16bit 点位缓存。 * * @param bits 目标 bit-map 指针 * @param bit_idx bit 下标(0~15) * @param val01 0 或 1 * * @note 本模块约定:点位枚举值 == bit 下标(与 sub_comm_task.h 的枚举顺序一致)。 */ static void v_sub_comm_bit_set_u16(uint16_t *bits, U8_T bit_idx, U8_T val01) { uint16_t mask; /* 当前点位掩码 */ if ((bits == NULL) || (bit_idx >= 16U)) { return; } mask = (uint16_t)(1U << bit_idx); if (val01 == 0U) { *bits = (uint16_t)(*bits & (uint16_t)(~mask)); } else { *bits = (uint16_t)(*bits | mask); } } /** * @brief 读取 16bit 点位缓存的某一 bit。 * @return 0/1 */ static U8_T u8_sub_comm_bit_get_u16(uint16_t bits, U8_T bit_idx) { if (bit_idx >= 16U) { return 0U; } return (U8_T)((bits >> bit_idx) & 0x01U); } /** * @brief 读取 32bit 点位缓存的某一 bit。 * @return 0/1 */ static U8_T u8_sub_comm_bit_get_u32(uint32_t bits, U8_T bit_idx) { if (bit_idx >= 32U) { return 0U; } return (U8_T)((bits >> bit_idx) & 0x01U); } #endif #if 1 // DI 数据域解析/命令分发 /** * @brief 将副板 DI 状态数据域解码进 g_subcomm_ctx.sub_yx。 * * 数据域编码(本工程默认 bit-map 方案,便于紧凑传输): * - len >= 2 时使用 data[0..1] 的 16 个 bit: * bit0 -> YX13 * bit1 -> YX14 * ... * bit9 -> YX22 * bit10 -> DIP1 * ... * bit15 -> DIP6 * * 说明: * - 若现场协议改为“每点 1 字节”或“更多字节”,只需在此函数内替换映射。 */ static void v_sub_comm_apply_di_payload(const U8_T *data, U8_T len) { if ((data == NULL) || (len < 2U)) { return; } /* 当前 0x01 数据域仅承载低 16bit(YX13~YX22 + DIP1~DIP6),预留 8bit 默认 0。 */ g_subcomm_ctx.sub_yx.u32_bits = (uint32_t)data[0] | ((uint32_t)data[1] << 8); } /** * @brief 解析一帧完整有效的副板协议帧,并更新内部缓存。 * @param cmd 命令码 * @param data 数据域 * @param len 数据长度 */ static void v_sub_comm_handle_frame(U8_T cmd, const U8_T *data, U8_T len) { switch (cmd) { case SUBCOMM_CMD_DI_STATUS: v_sub_comm_apply_di_payload(data, len); g_subcomm_ctx.last_di_ms = u64_get_current_millis(); break; default: /* 其它命令暂不处理 */ break; } } #endif #if 1 // RX 缓存解帧(处理粘包/残包) /** * @brief 从接收缓存中尝试解析一帧完整协议帧。 * * @return U8_T 1=成功解析并消费掉该帧;0=缓存中暂时没有完整有效帧(可能是残包/需继续收数) */ static U8_T u8_sub_comm_try_parse_one_frame(void) { U16_T i; /* 遍历下标 */ U16_T frame_start = 0U; /* 帧头位置 */ U16_T total_len; /* 当前帧总长度 */ U8_T cmd; /* 命令码 */ U8_T len; /* 数据域长度 */ U8_T checksum; /* 计算得到的校验值 */ /* 最小帧长度:head+cmd+len+sum+tail = 5 */ if (g_subcomm_ctx.rx_len < 5U) { return 0U; } /* 1) 查找帧头 */ frame_start = 0xFFFFU; for (i = 0U; i < g_subcomm_ctx.rx_len; i++) { if (g_subcomm_ctx.rx_buf[i] == SUBCOMM_FRAME_HEAD) { frame_start = i; break; } } if (frame_start == 0xFFFFU) { /* 缓存里没有帧头,直接清空 */ g_subcomm_ctx.rx_len = 0U; return 0U; } /* 丢弃帧头之前的杂散数据 */ if (frame_start > 0U) { g_subcomm_ctx.rx_len = (U16_T)(g_subcomm_ctx.rx_len - frame_start); (void)memmove(g_subcomm_ctx.rx_buf, &g_subcomm_ctx.rx_buf[frame_start], g_subcomm_ctx.rx_len); } /* 2) 至少要有 head/cmd/len */ if (g_subcomm_ctx.rx_len < 3U) { return 0U; } cmd = g_subcomm_ctx.rx_buf[1]; len = g_subcomm_ctx.rx_buf[2]; if (len > (U8_T)SUBCOMM_DATA_MAX) { /* 长度非法,丢弃 1 字节重新同步 */ g_subcomm_ctx.rx_len--; (void)memmove(g_subcomm_ctx.rx_buf, &g_subcomm_ctx.rx_buf[1], g_subcomm_ctx.rx_len); return 1U; /* 已消费 1 字节,让外层继续循环 */ } total_len = (U16_T)(5U + (U16_T)len); if (g_subcomm_ctx.rx_len < total_len) { /* 残包:等待后续数据 */ return 0U; } /* 3) 校验帧尾 */ if (g_subcomm_ctx.rx_buf[total_len - 1U] != SUBCOMM_FRAME_TAIL) { /* 帧尾不匹配,丢弃 1 字节重新同步 */ g_subcomm_ctx.rx_len--; (void)memmove(g_subcomm_ctx.rx_buf, &g_subcomm_ctx.rx_buf[1], g_subcomm_ctx.rx_len); return 1U; } /* 4) 校验累加和:从 head 到 data(3+len 字节),对比 sum 字节 */ checksum = (U8_T)u16_crc_checksum(&g_subcomm_ctx.rx_buf[0], (U16_T)(3U + (U16_T)len)); if (checksum != g_subcomm_ctx.rx_buf[3U + (U16_T)len]) { /* 校验失败,丢弃 1 字节重新同步 */ g_subcomm_ctx.rx_len--; (void)memmove(g_subcomm_ctx.rx_buf, &g_subcomm_ctx.rx_buf[1], g_subcomm_ctx.rx_len); return 1U; } /* 5) 处理帧 */ v_sub_comm_handle_frame(cmd, &g_subcomm_ctx.rx_buf[3], len); /* 6) 移除已处理数据(支持粘包:后面可能还有帧) */ g_subcomm_ctx.rx_len = (U16_T)(g_subcomm_ctx.rx_len - total_len); if (g_subcomm_ctx.rx_len > 0U) { (void)memmove(g_subcomm_ctx.rx_buf, &g_subcomm_ctx.rx_buf[total_len], g_subcomm_ctx.rx_len); } return 1U; } #endif #if 1 // 系统接口 /* ======================== 系统接口(03/04数据来源与编码) ======================== */ #define SUBCOMM_SYS_TIME_LEN (12U) #define SUBCOMM_SYS_RSVD_LEN (6U) #define SUBCOMM_SYS_INFO_LEN (22U) #define SUBCOMM_GUN_INFO_LEN (14U) #define SUBCOMM_GUN_RSVD_LEN (8U) /** * @brief 获取系统枪数量(并限制到工程上限)。 * @return U8_T 枪数量,范围 0~GUN_MAX_CNT */ static U8_T u8_sub_comm_get_gun_cnt_if(void) { U8_T gun_cnt = g_t_share_data.t_sys_fix_cfg.u8_gunCnt; /* 配置中的枪数量 */ if (gun_cnt > (U8_T)GUN_MAX_CNT) { gun_cnt = (U8_T)GUN_MAX_CNT; } return gun_cnt; } /** * @brief 获取灯板类型(透传系统固定配置)。 * @return U8_T 灯板类型编码 */ static U8_T u8_sub_comm_get_led_board_type_if(void) { return (U8_T)g_t_share_data.t_sys_fix_cfg.e_ledType; } /** * @brief 获取液冷使能标志(0/1)。 * @return U8_T 0/1(取最低位) */ static U8_T u8_sub_comm_get_liquid_cooling_enable_if(void) { return (U8_T)(g_t_share_data.t_sys_fix_cfg.u_enable.bit_enable.bit_YeLenEn & 0x01U); } /** * @brief 获取液冷类型。 * @return U8_T 当前未对接任何系统接口,固定为 0。 */ static U8_T u8_sub_comm_get_liquid_cooling_type_if(void) { return 0U; } /** * @brief 获取指定枪连接状态。 * @param gun_no 枪号(0-based) * @return U8_T 0=未连接,1=已连接 */ static U8_T u8_sub_comm_get_gun_link_if(U8_T gun_no) { return (U8_T)((u8_get_gun_yx_data(YX_GUN_LINK, gun_no) == 0U) ? 0U : 1U); } /** * @brief 获取指定枪对外充电状态(flow 实时状态)。 * @param gun_no 枪号(0-based) * @return U8_T 充电状态枚举值低8位 */ static U8_T u8_sub_comm_get_charge_state_if(U8_T gun_no) { U16_T flow_state = u16_flow_put_data(gun_no, E_FLOW_PUT_DATA_GUN_REAL_STATE, 0U); /* 流程状态原值 */ return (U8_T)(flow_state & 0xFFU); } /** * @brief 获取指定枪故障/告警标志。 * @param gun_no 枪号(0-based) * @return U8_T 0=无,1=故障,2=告警 */ static U8_T u8_sub_comm_get_fault_alarm_flag_if(U8_T gun_no) { U8_T pile_fault = u8_get_fault_sum(0U, 0U); /* 整桩故障数量 */ U8_T gun_fault = u8_get_fault_sum((U8_T)(gun_no + 1U), 0U); /* 该枪故障数量 */ U8_T pile_alarm = u8_get_fault_sum(0U, 1U); /* 整桩告警数量 */ U8_T gun_alarm = u8_get_fault_sum((U8_T)(gun_no + 1U), 1U); /* 该枪告警数量 */ if ((pile_fault != 0U) || (gun_fault != 0U)) { return 1U; } if ((pile_alarm != 0U) || (gun_alarm != 0U)) { return 2U; } return 0U; } /** * @brief 获取指定枪 SOC(0~100)。 * @param gun_no 枪号(0-based) * @return U8_T SOC 百分比 */ static U8_T u8_sub_comm_get_soc_if(U8_T gun_no) { U16_T soc = u16_flow_get_sys_data(gun_no, E_FLOW_GET_DATA_BMS_SOC, 0U); /* BMS 上报 SOC */ if (soc > 100U) { soc = 100U; } return (U8_T)soc; } /** * @brief 计算指定枪对应的指示灯状态编码。 * @param gun_no 枪号(0-based) * @return U8_T 指示灯状态(与 yk_ctrl 约定一致) */ static U8_T u8_sub_comm_get_led_state_if(U8_T gun_no) { /* 统一由 led_ctrl 模块计算并缓存两把枪状态,sub_comm 直接读取缓存。 */ return u8_led_state_get(gun_no); } /** * @brief 生成 12 字节时间 ASCII(YYMMDDhhmmss)。 * @param out 输出缓存,长度至少 12 字节 */ static void v_sub_comm_fill_time_ascii12(U8_T *out) { Comm_Time now; /* RTC 当前时间结构体 */ char time_buf[13]; /* 临时字符串,包含结尾 '\0' */ if (out == NULL) { return; } GetCurrentTime(&now); (void)snprintf(time_buf, sizeof(time_buf), "%02u%02u%02u%02u%02u%02u", (unsigned int)(now.iYear % 100), (unsigned int)now.ucMonth, (unsigned int)now.ucDay, (unsigned int)now.ucHour, (unsigned int)now.ucMin, (unsigned int)now.ucSec); (void)memcpy(out, time_buf, SUBCOMM_SYS_TIME_LEN); } #endif #if 1 //发送函数 /** * @brief 组帧并通过 USART0 发送一条副板协议命令。 * * @param cmd 命令码 * @param data 数据域指针(允许 NULL) * @param len 数据长度(0~64) * * @return U8_T 1=发送成功(至少发出一帧);0=参数不合法或发送失败 */ static U8_T u8_sub_comm_send_cmd(U8_T cmd, const U8_T *data, U8_T len) { U8_T buf[4U + SUBCOMM_DATA_MAX + 1U]; /* 最大发送帧缓存:head+cmd+len+data+sum+tail */ U8_T sum; /* 8位累加和 */ uint16_t total_len; /* 实际总帧长 */ if (len > (U8_T)SUBCOMM_DATA_MAX) { return 0U; } buf[0] = SUBCOMM_FRAME_HEAD; buf[1] = cmd; buf[2] = len; if ((len > 0U) && (data != NULL)) { (void)memcpy(&buf[3], data, len); } /* 累加和:从帧头到数据域的累加和(取低 8 位) */ sum = (U8_T)u16_crc_checksum(&buf[0], (U16_T)(3U + len)); buf[3U + len] = sum; buf[4U + len] = SUBCOMM_FRAME_TAIL; total_len = (uint16_t)(5U + len); if (u16_usart_send(SUBCOMM_UART_ID, buf, total_len) != total_len) { return 0U; } return 1U; } /** * @brief 将副板继电器控制(DO11~DO16)编码为数据域并发送 0x02。 * * 数据域编码(本工程默认 bit-map 方案): * - 2 字节(低字节在前): * - bit0~bit5:K1~K6(即 DO11~DO16) * - bit6~bit11:预留遥控点位(RSVD1~RSVD6) * - bit12~bit15:保留(对齐) * bit0 -> DO11(K1) * bit1 -> DO12(K2) * ... * bit5 -> DO16(K6) */ static void v_sub_comm_send_do_ctrl(void) { /* 下发 2 字节,包含 DO11~DO16 + 预留位 */ U8_T payload[2]; /* 0x02 数据域 */ payload[0] = g_subcomm_ctx.sub_yk.u8_bytes[0]; payload[1] = g_subcomm_ctx.sub_yk.u8_bytes[1]; (void)u8_sub_comm_send_cmd(SUBCOMM_CMD_DO_CTRL, payload, 2U); } /** * @brief 发送 0x03 系统信息帧。 * * 数据域格式: * - [0..11] 时间字符串 YYMMDDhhmmss * - [12] 枪数量 * - [13] 灯板类型 * - [14] 液冷使能(0/1) * - [15] 液冷类型(默认0) * - [16..21] 预留 */ static void v_sub_comm_send_sys_info(void) { U8_T payload[SUBCOMM_SYS_INFO_LEN] = {0}; /* 0x03 数据域 */ v_sub_comm_fill_time_ascii12(&payload[0]); payload[12] = u8_sub_comm_get_gun_cnt_if(); payload[13] = u8_sub_comm_get_led_board_type_if(); payload[14] = u8_sub_comm_get_liquid_cooling_enable_if(); /* liquid_cooling_enable */ payload[15] = u8_sub_comm_get_liquid_cooling_type_if(); /* liquid_cooling_type */ /* payload[16..21] 为预留,默认0 */ (void)u8_sub_comm_send_cmd(SUBCOMM_CMD_SYS_INFO, payload, (U8_T)sizeof(payload)); } /** * @brief 发送 0x04 充电枪信息帧。 * @param gun_no 枪号(0-based) * * 数据域格式: * - [0] 枪号(1-based) [1]连接状态 [2]充电状态 [3]灯状态 * - [4]故障告警标志 [5]SOC [6..13]预留 */ static void v_sub_comm_send_gun_info(U8_T gun_no) { U8_T payload[SUBCOMM_GUN_INFO_LEN] = {0}; /* 0x04 数据域 */ payload[0] = (U8_T)(gun_no + 1U); /* 枪号:1/2 */ payload[1] = u8_sub_comm_get_gun_link_if(gun_no); /* 枪连接状态 */ payload[2] = u8_sub_comm_get_charge_state_if(gun_no); /* 充电状态 */ payload[3] = u8_sub_comm_get_led_state_if(gun_no); /* 指示灯状态 */ payload[4] = u8_sub_comm_get_fault_alarm_flag_if(gun_no);/* 故障告警标志 */ payload[5] = u8_sub_comm_get_soc_if(gun_no); /* 充电SOC */ /* payload[6..13] 为预留,默认0 */ (void)u8_sub_comm_send_cmd(SUBCOMM_CMD_GUN_INFO, payload, (U8_T)sizeof(payload)); } /** * @brief 发送处理:按调用计数轮询发送 0x02/0x03/0x04。 * @param period_ms 本次调用周期(ms,当前方案未使用) * * 轮询策略: * - 偶数轮:固定发送 0x02(保证与上次 0x02 至少间隔一个执行周期); * - 奇数轮:按状态发送信息帧(0=0x03,1=1枪0x04,2=2枪0x04)。 */ static void v_sub_comm_tx_process(uint16_t period_ms) { static U8_T s_send_turn = 0U; /* 发送轮次计数:偶数发0x02,奇数发信息帧 */ static U8_T s_info_slot = 0U; /* 信息帧状态:0=0x03,1=1枪0x04,2=2枪0x04 */ U8_T gun_cnt; /* 当前枪数量 */ (void)period_ms; if ((s_send_turn & 0x01U) == 0U) { v_sub_comm_send_do_ctrl(); s_send_turn++; return; } gun_cnt = u8_sub_comm_get_gun_cnt_if(); switch (s_info_slot) { case 0U: v_sub_comm_send_sys_info(); s_info_slot = 1U; break; case 1U: if (gun_cnt >= 1U) { v_sub_comm_send_gun_info(0U); /* 发送1枪数据 */ } /* 在状态1判断是否双枪:双枪进状态2,否则回状态0。 */ s_info_slot = (gun_cnt >= 2U) ? 2U : 0U; break; case 2U: if (gun_cnt >= 2U) { v_sub_comm_send_gun_info(1U); /* 发送2枪数据 */ } s_info_slot = 0U; break; default: s_info_slot = 0U; break; } s_send_turn++; } #endif #if 1 // 接收函数(USART FIFO -> RX缓存 -> 解帧) /** * @brief 接收处理:从 USART0 FIFO 读取并解析所有已到达数据。 */ static void v_sub_comm_rx_process(void) { /* 接收:尽可能把 FIFO 中的数据取出并追加到接收缓存 */ uint8_t rx_buf[64]; /* USART 临时读缓存 */ uint16_t left; /* 本轮读到的字节数 */ uint16_t free_space;/* 全局接收缓存剩余空间 */ uint16_t copy_len; /* 本次可复制字节数 */ left = u16_usart_recv(SUBCOMM_UART_ID, rx_buf, (uint16_t)sizeof(rx_buf)); while (left > 0U) { /* 追加到缓存,满了则丢弃最老的数据(保留末尾 1/2,类似 meter 的处理) */ free_space = (g_subcomm_ctx.rx_len < (U16_T)SUBCOMM_RX_BUF_MAX) ? (U16_T)(SUBCOMM_RX_BUF_MAX - g_subcomm_ctx.rx_len) : 0U; copy_len = (left > free_space) ? free_space : left; if (copy_len > 0U) { (void)memcpy(&g_subcomm_ctx.rx_buf[g_subcomm_ctx.rx_len], rx_buf, copy_len); g_subcomm_ctx.rx_len = (U16_T)(g_subcomm_ctx.rx_len + copy_len); } else { /* 缓冲区无空间:保留最后 1/2 数据 */ if (g_subcomm_ctx.rx_len > (U16_T)(SUBCOMM_RX_BUF_MAX / 2U)) { (void)memmove(g_subcomm_ctx.rx_buf, &g_subcomm_ctx.rx_buf[g_subcomm_ctx.rx_len - (U16_T)(SUBCOMM_RX_BUF_MAX / 2U)], (U16_T)(SUBCOMM_RX_BUF_MAX / 2U)); g_subcomm_ctx.rx_len = (U16_T)(SUBCOMM_RX_BUF_MAX / 2U); } } /* 若一次读取的数据超出剩余空间,剩余部分丢弃(避免阻塞/反复搬运)。 */ left = u16_usart_recv(SUBCOMM_UART_ID, rx_buf, (uint16_t)sizeof(rx_buf)); } /* 解析:循环消费缓存中的完整帧(处理粘包) */ while (u8_sub_comm_try_parse_one_frame() != 0U) {} } #endif #if 1 // 超时处理函数 /** * @brief 超时处理:若 DI 状态帧(0x01)长时间未更新,则清空 YX/DIP 缓存。 * * 策略: * - 若从未收到过 DI 帧(last_di_ms==0),不处理; * - 若超过 SUBCOMM_DI_TIMEOUT_MS 未更新,清空 g_subcomm_ctx.sub_yx(全部置 0)。 */ static void v_sub_comm_timeout_process(void) { uint64_t now_ms; /* 当前时间戳(ms),用于调试保留 */ uint64_t delta_ms; /* 距离上次 DI 更新时间 */ if (g_subcomm_ctx.last_di_ms == 0U) { return; } now_ms = u64_get_current_millis(); delta_ms = u64_safe_millis_since(g_subcomm_ctx.last_di_ms); if (delta_ms >= (uint64_t)SUBCOMM_DI_TIMEOUT_MS) { (void)memset(&g_subcomm_ctx.sub_yx, 0, sizeof(g_subcomm_ctx.sub_yx)); /* 避免每个周期都重复清零:重置 last_di_ms,等待下次有效帧重新置位。 */ g_subcomm_ctx.last_di_ms = 0U; } (void)now_ms; } #endif #if 1 // 对外接口函数(缓存读写) /** * @brief 写入副板遥控(YK)命令缓存。 * * @param e_type 点位(DO11~DO16 或预留位) * @param e_cmd CMD_OFF/CMD_ON * * @note 仅更新缓存;串口下发由 v_sub_comm_poll() 周期执行。 */ void v_sub_comm_yk_set(E_SUB_YK_TYPE e_type, E_CMD e_cmd) { if ((U8_T)e_type >= (U8_T)E_SUB_YK_NUM) { return; } v_sub_comm_bit_set_u16(&g_subcomm_ctx.sub_yk.u16_bits, (U8_T)e_type, (U8_T)((e_cmd == CMD_OFF) ? 0U : 1U)); } /** * @brief 读取副板遥控(YK)命令缓存。 * @return CMD_OFF/CMD_ON */ E_CMD e_sub_comm_yk_get(E_SUB_YK_TYPE e_type) { if ((U8_T)e_type >= (U8_T)E_SUB_YK_NUM) { return CMD_OFF; } return (u8_sub_comm_bit_get_u16(g_subcomm_ctx.sub_yk.u16_bits, (U8_T)e_type) == 0U) ? CMD_OFF : CMD_ON; } /** * @brief 读取副板遥信(YX)/拨码(DIP)/预留位采集缓存。 * @return 0/1 */ U8_T u8_sub_comm_yx_get(E_SUB_YX_TYPE e_type) { if ((U8_T)e_type >= (U8_T)E_SUB_YX_NUM) { return 0U; } return u8_sub_comm_bit_get_u32(g_subcomm_ctx.sub_yx.u32_bits, (U8_T)e_type); } #endif #if 1 // SubComm 任务入口 /** * @brief 副板通信任务(USART0)。 * * 职责: * - 解析副板上报帧(0x01),刷新副板 DI/DIP 状态; * - 周期下发副板继电器控制(0x02),数据来源为 g_subcomm_ctx.sub_yk 缓存; * - 任务喂狗与运行状态打印。 */ void v_sub_comm_task(void *arg) { uint16_t sleep_time_ms; /* 任务轮询周期(ms) */ uint32_t last_send_time;/* 上次运行日志打印时间(s) */ uint32_t current_time; /* 当前秒计时 */ (void)arg; (void)SUBCOMM_LOCK_INIT(); sleep_time_ms = SUBCOMM_SLEEP_TIME_MS(); last_send_time = get_current_seconds(); v_sub_comm_data_init(); for (;;) { SUBCOMM_FEED_DOG(); current_time = get_current_seconds(); if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) { if (SUBCOMM_LOCK_TAKE() == 1U) { SUBCOMM_LOG("sub comm task is runing, sleep time is %ums", sleep_time_ms); (void)SUBCOMM_LOCK_GIVE(); } last_send_time = current_time; } /* 副板通信:接收 / 发送 / 超时 三部分独立执行 */ v_sub_comm_rx_process(); v_sub_comm_tx_process(sleep_time_ms); v_sub_comm_timeout_process(); mSleep(sleep_time_ms); } } #endif