Files
CCU621M/app/sub_comm/sub_comm_task.c
T

778 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <string.h>
#include <stdio.h>
#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 数据域仅承载低 16bitYX13~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 获取指定枪 SOC0~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 字节时间 ASCIIYYMMDDhhmmss)。
* @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~bit5K1~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=0x031=1枪0x042=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=0x031=1枪0x042=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