diff --git a/apps/boot/application/main.c b/apps/boot/application/main.c index 8c11db6..7c48401 100644 --- a/apps/boot/application/main.c +++ b/apps/boot/application/main.c @@ -166,6 +166,56 @@ void MX_USART_DeInit (void) HAL_UART_DeInit(&huart2); } +static void boot_JumpToAPP (void) +{ + static uint32_t _stack_addr; /* APP ջ¶¥µØַ */ + static uint32_t _reset_handler; /* APP reset handler µØַ */ + typedef void(*APP_MAIN_FUNC)(void); + APP_MAIN_FUNC APP_Main; + + _stack_addr = *(volatile uint32_t *)BASE_APPLICATION; + _reset_handler = *(volatile uint32_t *)(BASE_APPLICATION + 4); + + /* ÉèÖÃËùÓÐʱÖӵ½ĬÈÏ״̬£¬ʹÓÃHSIʱÖÓ */ + HAL_RCC_DeInit(); + + /* ¹رÕȫ¾ÖÖжÏ */ + __disable_irq(); + + /* ¹رյδð¶¨ʱÆ÷£¬¸´λµ½ĬÈÏֵ */ + SysTick->CTRL = 0; + SysTick->LOAD = 0; + SysTick->VAL = 0; + + /* ¹رÕËùÓÐÖжϣ¬Çå³ýËùÓÐÖжϹÒÆð±ê־ */ + for (uint8_t i = 0; i < 8; i++) { + NVIC->ICER[i] = 0xFFFFFFFF; + NVIC->ICPR[i] = 0xFFFFFFFF; + } + + /* ÍâÉè deinit */ + HAL_DeInit(); + MX_USART_DeInit(); + MX_DMA_DeInit(); + + + /* ÉèÖÃÖ÷¶ÑջָÕë */ + __set_MSP(_stack_addr); + + /* ÔÚ RTOS ¹¤³̣¬ÕâÌõÓï¾äºÜÖØҪ£¬ÉèÖÃΪÌØȨ¼¶ģʽ£¬ʹÓà MSP ָÕë */ + __set_CONTROL(0); + + /* ÉèÖÃÖжÏÏòÁ¿±í */ + SCB->VTOR = BASE_APPLICATION; + + /* Ìøתµ½ APP £¬Ê׵ØַÊÇ MSP £¬µØַ +4 ÊǸ´λÖжϷþÎñ³ÌÐòµØַ */ + APP_Main = (APP_MAIN_FUNC)_reset_handler; + APP_Main(); + + /* ¸´λ»òÌøת³ɹ¦µĻ°£¬²»»áִÐе½ÕâÀï */ + while (1) { + } +} int main (void) { @@ -190,9 +240,10 @@ int main (void) VERSION_MAIN, VERSION_SUB, VERSION_FIX, BUILD_TIMESTAMP); #endif APP_Init(); - + boot_JumpToAPP(); while (1) { - APP_Running(); + + //APP_Running(); } return -1; diff --git a/apps/chrg/applications/bsp/chrg_gpio.h b/apps/chrg/applications/bsp/chrg_gpio.h index 624ca38..96bfce7 100644 --- a/apps/chrg/applications/bsp/chrg_gpio.h +++ b/apps/chrg/applications/bsp/chrg_gpio.h @@ -32,18 +32,18 @@ #define GPO_PIN_CH1_P2 GET_PIN(C, 1) // P2 connect K5 for GND/PGND // 北向继电器2组 -#define GPO_PIN_CH2_P0 GET_PIN(A, 5) // P0 -#define GPO_PIN_CH2_P1 GET_PIN(A, 4) // P1 +#define GPO_PIN_CH2_P0 GET_PIN(A, 5) // P0 //SINK D+D- CC +#define GPO_PIN_CH2_P1 GET_PIN(A, 4) // P1 //SOURCE D+D- CC #define GPO_PIN_CH2_P2 GET_PIN(A, 1) // P2 // 北向继电器3组 -#define GPO_PIN_CH3_P0 GET_PIN(C, 5) // P0 -#define GPO_PIN_CH3_P1 GET_PIN(C, 4) // P1 +#define GPO_PIN_CH3_P0 GET_PIN(C, 5) // P0 //SINK D+D- CC +#define GPO_PIN_CH3_P1 GET_PIN(C, 4) // P1 //SOURCE D+D- CC #define GPO_PIN_CH3_P2 GET_PIN(A, 7) // P2 // 北向继电器4组 -#define GPO_PIN_CH4_P0 GET_PIN(B, 15) // P0 -#define GPO_PIN_CH4_P1 GET_PIN(B, 14) // P1 +#define GPO_PIN_CH4_P0 GET_PIN(B, 15) // P0 //SINK D+D- CC +#define GPO_PIN_CH4_P1 GET_PIN(B, 14) // P1 //SOURCE D+D- CC #define GPO_PIN_CH4_P2 GET_PIN(B, 13) // P2 // 北向通信口选通 U14 diff --git a/apps/chrg/applications/thread/chrg_comm.c b/apps/chrg/applications/thread/chrg_comm.c index 402a739..b7bd820 100644 --- a/apps/chrg/applications/thread/chrg_comm.c +++ b/apps/chrg/applications/thread/chrg_comm.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_comm.c 完成日期 : -当前版本号 : V1.0 +当前版本号 : V1.1.9 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -26,6 +26,9 @@ #include "stm32f4xx_hal.h" +#define HOLD_REG_FIRMWARE_VERSION 0x0300U /* 协议板固件版本,只读 */ +#define CHRG_FIRMWARE_VERSION 0x0119U /* 当前协议板版本:V1.1.9 */ + rt_uint8_t ucDiscInBuf[(TOTAL_DISC_REGS+BITS_UCHAR-1)/BITS_UCHAR]; rt_uint8_t ucCoilBuf[(TOTAL_COIL_REGS+BITS_UCHAR-1)/BITS_UCHAR]; rt_uint16_t usRegInBuf[TOTAL_INPUT_REGS]; @@ -36,10 +39,64 @@ rt_uint8_t g_proto_pData[80] = {0};//上位机协议指令,控制四个通道 rt_uint16_t g_proto_reqLen = 0; rt_uint8_t mobude_ymodem_buf[MB_YMODEM_DATA_SIZE] = {0};//南向功率板升级缓冲区 rt_uint8_t mobude_ymodem_buf_2[MB_YMODEM_DATA_SIZE] = {0};//南向功率板升级缓冲区 + +#define YMODEM_C_PERIOD_MS 1000 +#define YMODEM_META_ACK_TIMEOUT_MS 3000 +#define YMODEM_DATA_ACK_TIMEOUT_MS 1000 +#define YMODEM_MAX_RETRIES 3 + +/* COM侧升级会话状态:接收缓存负责拼帧,备份缓存负责超时/NAK重发。 */ +static rt_uint16_t g_ymodem_rx_len = 0; +static rt_uint16_t g_ymodem_expected_len = 0; +static rt_uint16_t g_ymodem_last_len = 0; +static rt_uint8_t g_ymodem_waiting_ack = 0; +static rt_uint8_t g_ymodem_retry_count = 0; +static rt_uint8_t g_ymodem_eot_done = 0; +static rt_tick_t g_ymodem_send_tick = 0; +static rt_tick_t g_ymodem_c_tick = 0; /* Modbus 影子寄存器: 0x06/0x10 写操作暂存到此, PARAM_EFFECT 生效后复制到实际 pNOR->sw */ static struct chrg_switch_t g_shadow_sw[TOTAL_NOR_CHS]; static rt_uint8_t g_shadow_dirty[TOTAL_NOR_CHS] = {0}; /* 0=已生效 1=待生效,防止重复覆盖 */ +/* Source协议类别缓存:0xFF表示本通道尚未成功下发过协议。 */ +#define SOURCE_PD_STATE_UNKNOWN 0xFFU +#define SOURCE_PD_STATE_NON_PD 0x00U +#define SOURCE_PD_STATE_PD 0x01U +static rt_uint8_t last_pd_pro[TOTAL_NOR_CHS] = { + SOURCE_PD_STATE_UNKNOWN, SOURCE_PD_STATE_UNKNOWN, + SOURCE_PD_STATE_UNKNOWN, SOURCE_PD_STATE_UNKNOWN +}; + +static rt_uint8_t chrg_comm_source_pd_state(rt_uint8_t protocol) +{ + return ((protocol == PRO_PD0PSS) || (protocol == PRO_PD31AVS)) ? + SOURCE_PD_STATE_PD : SOURCE_PD_STATE_NON_PD; +} + +void chrg_comm_source_protocol_ack(rt_uint8_t ch, rt_uint8_t protocol) +{ + if (ch < TOTAL_NOR_CHS) { + last_pd_pro[ch] = chrg_comm_source_pd_state(protocol); + } +} + +/* + * Source和Sink共用的参数差异结果。前五项是两个角色都具备的控制属性, + * pd_changed用于Source完整协议参数比较,后三项用于Sink快充协议配置。 + */ +typedef struct +{ + rt_bool_t protocol_changed; + rt_bool_t cc_changed; + rt_bool_t cv_changed; + rt_bool_t voltage_changed; + rt_bool_t current_changed; + rt_bool_t pd_changed; + rt_bool_t group_changed; + rt_bool_t pro_voltage_changed; + rt_bool_t pro_current_changed; +} chrg_param_diff_t; + /* 首次写入影子寄存器前,从真实结构体同步,避免覆盖运行中的关键标志位 */ static void chrg_shadow_sync(rt_uint8_t ch) { @@ -65,7 +122,12 @@ struct chrg_comm_t chrgcomm = { static void chrg_mb_write_sink(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t sou_value, struct chrg_switch_t *pSW); static void chrg_mb_write_src(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t sou_value, struct chrg_switch_t *pSW); -static void chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t sou_value, struct chrg_switch_t *pSW, struct chrg_north_t *pNOR, struct chrg_south_t *pSOU); +static eMBException chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t sou_value, struct chrg_switch_t *pSW, struct chrg_north_t *pNOR, struct chrg_south_t *pSOU); +static int chrg_sink_diff_send_reg(eIDX_SOU_CH ch, + const chrg_param_diff_t *pDiff, struct chrg_switch_t *pReal); +static rt_bool_t chrg_comm_handle_sink_load_current( + const chrg_param_diff_t *pDiff, rt_uint8_t old_work_mode, + struct chrg_switch_t *pReal); /*! \brief Function to set bits in a byte buffer. * @@ -185,16 +247,6 @@ rt_uint8_t mb_get_bits (rt_uint8_t * ucByteBuf, rt_uint16_t usBitOffset, rt_uint return (rt_uint8_t)usWordBuf; } -static void set_ymodem_update_channel(struct chrg_north_t *pNOR,struct chrg_south_t *pSOU,eIDX_SOU_CH ch) -{ - for (int i = 0; i < 4; i++) { - pNOR->enable[i] = 0; - pSOU->enable[i] = 0; - } - pNOR->enable[ch] = 1; - pSOU->enable[ch] = 1; -} - static eMBException mb_error (eMBErrorCode eErrorCode) { eMBException eStatus; @@ -221,6 +273,16 @@ static eMBException mb_error (eMBErrorCode eErrorCode) } //extern eMBErrorCode Modbus_Protocal_Set(rt_uint16_t Role_value,rt_uint16_t src,rt_uint16_t pps,rt_uint16_t pro_value,rt_uint16_t volt_value,rt_uint16_t curr_value); +/***************************************************************** +函数名称: mb_reg_input_cb +函数描述: Modbus输入寄存器读取回调函数 +输入参数: pucRegBuffer:输入寄存器返回数据缓冲区 + usAddress:输入寄存器起始地址 + usNRegs:需要读取的输入寄存器数量 +输出参数: pucRegBuffer:按Modbus高字节在前格式填充寄存器数据 +返回说明: MB_ENOERR:读取成功 MB_ENOREG:地址范围错误 +其它说明: 只读usRegInBuf,不处理写操作;每个输入寄存器转换为两个字节 +*****************************************************************/ eMBErrorCode mb_reg_input_cb (rt_uint8_t *pucRegBuffer, rt_uint16_t usAddress, rt_uint16_t usNRegs) { eMBErrorCode eStatus = MB_ENOERR; @@ -351,20 +413,12 @@ eMBErrorCode mb_reg_test_cb (rt_uint8_t *req, rt_uint8_t *pucRegBuffer, rt_uint1 *****************************************************************/ eMBErrorCode mb_ymodem_updata_cb (rt_uint8_t *state, rt_uint8_t *pucRegBuffer) { - eMBErrorCode eStatus = MB_ENOERR; - - switch (*state) { - /* read current register values from the protocol stack. */ - case 0: - /* 返回值反映实际ymodem升级状态: ACK_C=空闲, ACK=升级中 */ - pucRegBuffer[0] = (*state == Ymodem_No) ? ACK_C : ACK; - break; - case 1: - pucRegBuffer[0] = (*state == Ymodem_No) ? ACK_C : ACK; - break; - /* write current register values with new values from the protocol stack. */ - } - return eStatus; + if ((state == RT_NULL) || (pucRegBuffer == RT_NULL)) { + return MB_EINVAL; + } + /* Ymodem_No的枚举值为0xFF,不能用只覆盖0和1的switch生成响应。 */ + pucRegBuffer[0] = (*state == Ymodem_No) ? ACK_C : ACK; + return MB_ENOERR; } eMBErrorCode mb_reg_coils_cb (rt_uint8_t *req, rt_uint8_t *pucRegBuffer, rt_uint16_t usAddress, @@ -537,8 +591,6 @@ eMBException funcReadHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t *resFrame, rt_uint16_t *resLen) { rt_uint16_t regAddress = 0, cntReg = 0; - struct chrg_north_t *pNOR = &chrgnorth; - struct chrg_switch_t *pSW = &pNOR->sw[0]; eMBException eStatus = MB_EX_NONE; eMBErrorCode eRegStatus; @@ -553,6 +605,23 @@ eMBException funcReadHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, return MB_EX_ILLEGAL_DATA_VALUE; } + /* + * 协议板版本号使用独立的只读地址0x0300,不占用现有通道保持寄存器空间。 + * 当前版本V1.1.9编码为0x0119,Modbus响应按高字节在前返回01 19。 + * 该特殊地址只支持一次读取一个寄存器,避免与普通连续寄存器混用。 + */ + if (regAddress == HOLD_REG_FIRMWARE_VERSION) { + if (cntReg != 1U) { + return MB_EX_ILLEGAL_DATA_VALUE; + } + resFrame[1] = MB_READ_HOLDING_REGISTER; + resFrame[2] = 2U; + resFrame[3] = (rt_uint8_t)(CHRG_FIRMWARE_VERSION >> 8); + resFrame[4] = (rt_uint8_t)(CHRG_FIRMWARE_VERSION & 0xFFU); + *resLen = 5U; + return MB_EX_NONE; + } + resFrame[1] = MB_READ_HOLDING_REGISTER; resFrame[2] = cntReg * 2; @@ -562,17 +631,18 @@ eMBException funcReadHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, } else { *resLen = 3+cntReg * 2; } - rt_kprintf("------COM VOLT =%d -------\n------COM CURR =%d-------", pSW->Now_voltage,pSW->Now_current); +// rt_kprintf("------COM READ addr=%d count=%d-------\n", regAddress, cntReg); return eStatus; } /***************************************************************** 函数名称: funcReadInputRegister -函数描述: 读输入寄存器 +函数描述: 处理Modbus功能码0x04,读取输入寄存器 输入参数: reqFrame: 请求数据 reqLen: 请求长度 resFrame:响应数据 resLen:响应长度 -输出参数: - -返回说明: - -其它说明: - +输出参数: resFrame:输入寄存器响应帧 resLen:响应帧长度 +返回说明: MB_EX_NONE:读取成功 其他:Modbus异常码 +其它说明: 请求地址和数量校验方式与功能码0x03保持一致;输入寄存器 + 数据从usRegInBuf读取,响应数据按高字节在前返回 *****************************************************************/ eMBException funcReadInputRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t *resFrame, rt_uint16_t *resLen) @@ -596,13 +666,16 @@ eMBException funcReadInputRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, resFrame[1] = MB_READ_INPUT_REGISTER; resFrame[2] = cntReg * 2; - eRegStatus = mb_reg_discrete_cb(&resFrame[3], regAddress, cntReg); + eRegStatus = mb_reg_input_cb(&resFrame[3], regAddress, cntReg); if (eRegStatus != MB_ENOERR) { eStatus = mb_error(eRegStatus); } else { *resLen = 3+cntReg*2; } +#if COMM_DEBUG + rt_kprintf("COM read input: addr=%d count=%d\n", regAddress, cntReg); +#endif return eStatus; } @@ -803,6 +876,7 @@ typedef struct { } SrcProtoMap_t; static const SrcProtoMap_t src_proto_map[] = { + {0x20, PRO_NONE}, // 标准协议(无快充) {0x2A, PRO_PD0PSS}, // PD {0x22, PRO_FCP}, // FCP {0x23, PRO_SCPB}, // SCPB @@ -846,22 +920,24 @@ static rt_uint16_t src_proto_lookup(rt_uint16_t reg_val) *****************************************************************/ static rt_uint16_t src_type_lookup(rt_uint16_t reg_val,struct chrg_switch_t *pSW) { + struct vc_pd_t *pPD = &pSW->source.pd_set; + switch(reg_val){ case 0: - pSW->source.src = 0; - pSW->source.pps = 0; + pPD->src_type = TYPE_SRC_PDO; + pPD->pps_type = TYPE_PPS_SPRPPS; break; case 1: - pSW->source.src = 0; - pSW->source.pps = 1; + pPD->src_type = TYPE_SRC_PDO; + pPD->pps_type = TYPE_PPS_EPR; break; case 2: - pSW->source.src = 3; - pSW->source.pps = 0; + pPD->src_type = TYPE_SRC_APDO; + pPD->pps_type = TYPE_PPS_SPRPPS; break; case 3: - pSW->source.src = 3; - pSW->source.pps = 1; + pPD->src_type = TYPE_SRC_APDO; + pPD->pps_type = TYPE_PPS_EPR; break; default: return 1; @@ -960,7 +1036,10 @@ eMBException funcWriteHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t reqLen, } else if (regAddress_H == HOLD_REG_SYS_BASE) { - chrg_mb_write_sys(ch, reg_offset, sou_value, pSW, pNOR, pSOU); + eStatus = chrg_mb_write_sys(ch, reg_offset, sou_value, pSW, pNOR, pSOU); + if (eStatus != MB_EX_NONE) { + return eStatus; + } } build_response: @@ -1021,378 +1100,6 @@ eMBException funcWriteMultipleCoils (rt_uint8_t *reqFrame, rt_uint16_t reqLen, return eStatus; } -static void parse_load_no_group(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t reqLen, struct chrg_switch_t *pSW) -{ - rt_uint16_t volt_mv = 0; // 协议请求电压(mV) - rt_uint16_t curr_ma = 0; // 负载电流(mA) - - // 数据区索引:0=状态+通道,1=CC线,2,选择恒压恒流模式3-4=电压,5-6=电流,6-7=保留 - volt_mv = u8v_to_u16(&pData[3]); // 电压(mV,大端) - curr_ma = u8v_to_u16(&pData[5]); // 电流(mA,大端) - - // 按协议命令码赋值 - switch (protoCmd) - { - case 0x11: // QC2.0 - pSW->sink.protocol = LOAD_PRO_QC20; - break; - case 0x12: // QC3.0 - pSW->sink.protocol = LOAD_PRO_QC30; - break; - case 0x13: // FCP - pSW->sink.protocol = LOAD_PRO_FCP; - break; - case 0x14: // AFC - pSW->sink.protocol = LOAD_PRO_AFC; - break; - case 0x15: // SCP - pSW->sink.protocol = LOAD_PRO_SCP; - break; - case 0x16: // VIVO - pSW->sink.protocol = LOAD_PRO_VIVO; - break; - case 0x17: // TFC - pSW->sink.protocol = LOAD_PRO_TFC_RFC; - break; - default: - rt_kprintf("无组号负载协议不支持:0x%02X\n", protoCmd); - return; - } - - // 赋值通用参数 - pSW->sink.loadv = volt_mv; - pSW->sink.loadc = curr_ma; - pSW->sink.Pro_Group = 0; // 无组号 - rt_kprintf("pro=%d,volt_mv=%d,curr=%d",pSW->sink.protocol,volt_mv,curr_ma); -} - -static void parse_load_with_group(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t reqLen, struct chrg_switch_t *pSW) -{ - rt_uint8_t group_num = 0; // 组号 - rt_uint16_t volt_mv = 0; // 协议请求电压(mV) - rt_uint16_t curr_ma = 0; // 负载电流(mA) - - // 数据区索引:0=状态+通道,1=CC线,2=恒压恒流模式,3=组号 - group_num = pData[3]; - pSW->sink.Pro_Group = group_num; - - // 按协议命令码分支 - switch (protoCmd) - { - case CMD_LOAD_PD0_MAN: // PD0(手动) - pSW->sink.protocol = LOAD_PRO_PDO_MANUAL; - // PD0特殊:组号后为3个保留2字节,无电压电流 - volt_mv = 0; - curr_ma = 0; - break; - case CMD_LOAD_PD0_AUTO: // PDO自动 - pSW->sink.protocol = LOAD_PRO_PDO_AUTO; - volt_mv = u8v_to_u16(&pData[4]); // 4-5=电压 - curr_ma = u8v_to_u16(&pData[6]); // 6-7=电流 - break; - case CMD_LOAD_PPS_MAN: // PPS(手动) - pSW->sink.protocol = LOAD_PRO_PPS_MANUAL; - volt_mv = u8v_to_u16(&pData[4]); - curr_ma = u8v_to_u16(&pData[6]); - break; - case CMD_LOAD_PPS_AUTO: // PPS(自动) - pSW->sink.protocol = LOAD_PRO_PPS_AUTO; - volt_mv = u8v_to_u16(&pData[4]); - curr_ma = u8v_to_u16(&pData[6]); - break; - case CMD_LOAD_PD31: // PD3.1 - pSW->sink.protocol = LOAD_PRO_PD31AVS; - volt_mv = u8v_to_u16(&pData[4]); - curr_ma = u8v_to_u16(&pData[6]); - break; - case CMD_LOAD_UFCS: // UFCS - pSW->sink.protocol = LOAD_PRO_UFCS; - volt_mv = u8v_to_u16(&pData[4]); - curr_ma = u8v_to_u16(&pData[6]); - break; - case CMD_LOAD_VOOC: // VOOC - pSW->sink.protocol = LOAD_PRO_VOOC; - volt_mv = 0; - curr_ma = 0; - break; - case CMD_LOAD_AVS: // AVS - pSW->sink.protocol = LOAD_PRO_AVS; - volt_mv = u8v_to_u16(&pData[4]); - curr_ma = u8v_to_u16(&pData[6]); - break; - default: - rt_kprintf("有组号负载协议不支持:0x%02X\n", protoCmd); - return; - } - rt_kprintf("volt_mv=%d,curr_ma=%d\n",volt_mv,curr_ma); - // 赋值通用参数 - pSW->sink.loadv = volt_mv; - pSW->sink.loadc = curr_ma; - -} - -// //sink开机函数 -// rt_uint8_t chrg_com_sink_on(rt_uint8_t ch, struct chrg_switch_t *pSW, rt_uint8_t *enable){ - -// *enable = 1; -// pSW->id = ID_SINK; -// pSW->sub = IDX_SET_SINK_CURR; -// pSW->change_flag = 0x01; -// pSW->sink.work_on_step = 0; -// return 0; -// } - -//source开机函数 -rt_uint8_t chrg_com_source_on(rt_uint8_t ch, rt_uint8_t protoCmd, struct chrg_switch_t *pSW, rt_uint8_t *enable){ - *enable = 1; - pSW->id = ID_SOURCE; - pSW->source.LCD_Set_Pro_Flag=3; - pSW->sub = IDX_SET_SOURCE_ID; - pSW->change_flag = 0x01; - pSW->continue_flag = 1; - return 0; -} - -/***************************************************************** -函数名称: funcWriteProtoCal -函数描述: 写入协议 -输入参数: reqFrame: 请求数据 reqLen: 请求长度 resFrame:响应数据 resLen:响应长度 -输出参数: - -返回说明: - -其它说明: - -*****************************************************************/ -eMBException funcWriteProtoCal (rt_uint8_t *reqFrame, rt_uint16_t reqLen, - rt_uint8_t *resFrame, rt_uint16_t *resLen) -{ - /************ 局部变量定义 ************/ - struct chrg_north_t *pNOR = &chrgnorth; - struct chrg_south_t *pSOU = &chrgsouth; - struct chrg_switch_t *pSW = RT_NULL; - - rt_uint8_t slaveAddr = 0; // 从机地址(0x01~0xFF) - rt_uint8_t protoCmd = 0; // 协议命令码(0x11~0x1F/0x22~0x2C/0x2F) - rt_uint16_t regAddress = 0; // 寄存器起始地址 - rt_uint8_t *pData = RT_NULL; // 协议数据区起始指针(状态+通道字段开始) - rt_uint8_t statusChByte = 0;// 状态+通道字节(reqFrame[5]) - eIDX_SOU_CH ch = IDX_SOU_CH1;// 通道号(1~4) - rt_uint8_t chNum = 0; // 通道号数值(1~4) - rt_uint8_t devStatus = 0; // 设备状态(0=不变,1=开机,2=关机) - rt_uint8_t ccSel = 0; // CC线选择(0=CC1+CC2,1=CC1,2=CC2,3=断开) -// *g_proto_pData = RT_NULL; - g_proto_reqLen = 0; - // Modbus异常码 - eMBException eStatus = MB_EX_NONE; - eMBErrorCode eRegStatus; - rt_uint16_t reg_offset = 0; - rt_uint16_t sout_reg = 0; - rt_uint16_t sou_value = 0; - - /************ 校验 ************/ - // 最小帧长度:地址(1)+命令码(1)+寄存器(2)+数据区(至少1)+校验(2) = 7字节 - if (reqLen < 7) { - rt_kprintf("The long error:%d must more 7\n", reqLen); - return MB_EX_ILLEGAL_DATA_VALUE; - } - - /************ 解析帧 ************/ - slaveAddr = reqFrame[0]; // 从机地址(0x01~0xFF) - protoCmd = reqFrame[3]; // 协议命令码(负载0x11~0x1F,电源0x22~0x2C/0x2F) - rt_kprintf("pro=%04x\n",protoCmd); - pData = &reqFrame[4]; // 数据区起始(跳过地址+命令码+寄存器) - statusChByte = pData[0]; // 状态+通道字节(数据区第1字节) - - /************ 地址校验 ************/ - if (protoCmd < PRO_REG_START || protoCmd >= TOTAL_PROTOCAL_CNT) { - rt_kprintf("Address error:0x%04X\n", protoCmd); - return MB_EX_ILLEGAL_DATA_ADDRESS; - } - - /************ 提取通道号和状态 ************/ - // bit0-3:状态(0=不变,1=开机,2=关机) - devStatus = statusChByte & 0x0F; - rt_kprintf("status=%d",devStatus); - // bit4-7:通道号(1=CH1,2=CH2,3=CH3,4=CH4) - chNum = (statusChByte >> 4) & 0x0F; - ccSel = pData[1]; - rt_uint8_t cv_mode = pData[2]; - // 通道号合法性校验 - if (chNum < 1 || chNum > 4) { - rt_kprintf("通道号非法:%d(仅支持1~4)\n", chNum); - return MB_EX_ILLEGAL_DATA_ADDRESS; - } - ch = (eIDX_SOU_CH)(chNum - 1); // 转换为枚举类型(IDX_SOU_CH1=0) - /************ 硬件结构体绑定 ************/ - if (pNOR == RT_NULL) { - rt_kprintf("ERROR: pNOR is NULL!\n"); - return MB_EX_SLAVE_DEVICE_FAILURE; - } - pSW = &pNOR->sw[ch]; // 绑定当前通道的协议配置结构体 - pSW->protoCmd = protoCmd; //com端协议命令码 - pSW->CV_mode = cv_mode; //恒压恒流模式选择 - - rt_memcpy(g_proto_pData, pData, reqLen - 4); - g_proto_reqLen = reqLen; - rt_kprintf("com_recitive\n"); - /************ 按「负载/电源」分支解析 ************/ - // ========== 负载协议解析 (0x11~0x1F) ========== - if (protoCmd >= 0x11 && protoCmd <= 0x1F) { - pSW->sink.cc_set = ccSel; //负载CC线选择 - if(pSW->source.On_Flag){ //负载切电源 - pSW->source.change_sink_Flag = 1; - chrg_sou_stop(ch,pSW,pNOR); - }else{ - if(devStatus==2){ //负载关机 - chrg_sou_stop(ch,pSW,pNOR); - } - else if(devStatus==1){ //负载开机 - //chrg_com_sink_on(ch,pSW,&pNOR->enable[ch]); - } - } - } - // ==========电源协议解析 (0x22~0x2C/0x2F) ========== - else if ((protoCmd >= 0x22 && protoCmd <= 0x2C) || protoCmd == 0x2F) { - pSW->source.cc_set = ccSel; //电源CC线选择 - if(pSW->sink.On_work){ //电源切负载 - pSW->sink.change_source_flag = 1; - chrg_sou_stop(ch,pSW,pNOR); - }else{ - if(devStatus==2){ //电源关机 - chrg_sou_stop(ch,pSW,pNOR); - }else if(devStatus==1){ //电源开机 - chrg_com_source_on(ch,pSW->protoCmd,pSW,&pNOR->enable[ch]); - } - } - } - // ========== 非法命令码 ========== - else { - rt_kprintf("非法协议命令码:0x%02X\n", protoCmd); - return MB_EX_ILLEGAL_FUNCTION; - } - - // /************ 配置参数写入保持寄存器 ************/ - eRegStatus = mb_reg_holding_cb(&reqFrame[4], &resFrame[3], regAddress, 1, MB_REG_WRITE); - if (eRegStatus != MB_ENOERR) { - eStatus = mb_error(eRegStatus); - rt_kprintf("寄存器写入失败:%d\n", eRegStatus); - } - - /************ 构建响应帧 ************/ - rt_memcpy(resFrame, reqFrame, reqLen); - *resLen = reqLen - 2; // 去掉CRC校验位 - - /************ 调试输出 ************/ -// rt_kprintf("Pro Set OK:Ch=%d,Pro0x%02X,Status%d,CC Set%d\n", -// chNum, protoCmd, devStatus, ccSel); - return eStatus; -} - -/***************************************************************** -函数名称: funcSinkTest -函数描述: 负载测试 -输入参数: reqFrame: 请求数据 reqLen: 请求长度 resFrame:响应数据 resLen:响应长度 -输出参数: - -返回说明: - -其它说明: - -*****************************************************************/ -eMBException funcSinkTest (rt_uint8_t *reqFrame, rt_uint16_t reqLen, - rt_uint8_t *resFrame, rt_uint16_t *resLen) -{ - /************ 局部变量定义 ************/ - struct chrg_north_t *pNOR = &chrgnorth; - struct chrg_south_t *pSOU = &chrgsouth; - struct chrg_switch_t *pSW = RT_NULL; - - rt_uint8_t slaveAddr = 0; // 从机地址 - rt_uint8_t test_mode = 0; // 测试模式 0x01/0x02/0x03 - rt_uint16_t regAddress = 0; // 寄存器地址 - rt_uint8_t *pData = RT_NULL; // 数据区指针 - rt_uint8_t statusChByte = 0; // 状态+通道字节 - eIDX_SOU_CH ch = IDX_SOU_CH1; // 通道枚举 - rt_uint8_t chNum = 0; // 通道号 1~4 - rt_uint8_t devStatus = 0; // 设备状态 0=启动 1=停止,2=继续,3=结束 - - // Modbus异常 - eMBException eStatus = MB_EX_NONE; - eMBErrorCode eRegStatus; - -// /************ 长度校验 ************/ -// if (reqLen < 11) { -// rt_kprintf("Len error:%d <11\n", reqLen); -// return MB_EX_ILLEGAL_DATA_VALUE; -// } - - /************ 解析帧 ************/ - slaveAddr = reqFrame[0]; - test_mode = reqFrame[3]; // 测试命令:0x01短路 0x02OCP 0x03动态 - pData = &reqFrame[4]; // 数据起始 - statusChByte= pData[0]; // 状态通道字节 - - /************ 地址校验 ************/ - if (test_mode < 0x01 || test_mode > 0x03) { - rt_kprintf("Test mode err:0x%02X\n", test_mode); - return MB_EX_ILLEGAL_DATA_ADDRESS; - } - - /************ 解析状态 & 通道 ************/ - devStatus = statusChByte & 0x0F; // 低4位:状态 - chNum = (statusChByte >> 4) & 0x0F; // 高4位:通道 - - /************ 通道校验 ************/ - if (chNum <1 || chNum>4) { - rt_kprintf("Ch err:%d\n", chNum); - return MB_EX_ILLEGAL_DATA_ADDRESS; - } - ch = (eIDX_SOU_CH)(chNum -1); - - /************ 绑定结构体 ************/ - pSW = &pNOR->sw[ch]; - pSW->sink.test.test_mode = test_mode; //测试模式 - pSW->sink.test.run_flag = devStatus; //设备状态 - pSW->change_flag = 0x01; - struct test *pTset = &pSW->sink.test; - #if SINK_TEST_DEBUG - rt_kprintf("test_mode=%d\n",test_mode); - rt_kprintf("test_flag=%d\n",pTset->test_mode); - #endif - /*********************************************************/ - switch(test_mode) - { - case 0x01: - // 短路测试(OCPC) - sink_test_short_set(pSW, pData, devStatus); - break; - case 0x02: - // OCP 测试 - sink_test_ocp_set(pSW, pData, devStatus); - break; - case 0x03: - // 动态测试 - sink_test_dynamic_set(pSW, pData, devStatus); - break; - default: - return MB_EX_ILLEGAL_FUNCTION; - } - /************ 启动 / 停止控制 ************/ - if(devStatus == 0) //启动 - { - pNOR->enable[ch] = 1; - pSW->id = ID_SINK; - pSW->sink.test.work_step = 0; - pSOU->online[ch] = 1; - } - else if(devStatus == 1) //停止 - { - pSW->id = ID_SINK; - pSW->sink.test.stop_step = 0; - rt_kprintf("Sink Test Stop CH%d\n", chNum); - } - - /************ 响应帧 ************/ - rt_memcpy(resFrame, reqFrame, reqLen-2); - *resLen = reqLen -2; - - return eStatus; -} /***************************************************************** 函数名称: funcWriteMultipleHoldingRegister 函数描述: 写多个保持寄存器(0x10) @@ -1470,13 +1177,15 @@ eMBException funcWriteMultipleHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t switch (sout_reg) { case REG_VOLTAGE_OUT + 1: - pSW->source.max_vol = sou_value; + pSW->source.set_volt = sou_value; pSW->sink.loadv = sou_value; chrg_set_sou_reg(ch, sout_reg, sou_value); +#if COMM_DEBUG rt_kprintf("voltage=%d", sou_value); +#endif break; case REG_CURRENT_OUT + 1: - pSW->source.protective_curr = sou_value; + pSW->source.set_curr = sou_value; pSW->sink.loadc = sou_value; chrg_set_sou_reg(ch, sout_reg, sou_value); break; @@ -1484,7 +1193,9 @@ eMBException funcWriteMultipleHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t rt_kprintf("THE SOUT_REG=%d", sout_reg); break; } +#if COMM_DEBUG rt_kprintf("ch=:%d,reg=%02x,value=%02x", ch, sout_reg, sou_value); +#endif } #else // 正常产品模式:分区地址逻辑 @@ -1515,7 +1226,10 @@ eMBException funcWriteMultipleHoldingRegister (rt_uint8_t *reqFrame, rt_uint16_t } else if (regAddress_H == HOLD_REG_SYS_BASE) { - chrg_mb_write_sys(ch, ch_reg_offset, sou_value, pSW, pNOR, pSOU); + eStatus = chrg_mb_write_sys(ch, ch_reg_offset, sou_value, pSW, pNOR, pSOU); + if (eStatus != MB_EX_NONE) { + return eStatus; + } } else { @@ -1554,13 +1268,10 @@ eMBException funcTrimELoad (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t *resFrame, rt_uint16_t *resLen) { struct chrg_north_t *pNOR = &chrgnorth; - struct chrg_south_t *pSOU = &chrgsouth; - struct chrg_switch_t *pSW = RT_NULL; - rt_uint16_t regAddress = 0, cntReg = 0; + rt_uint16_t regAddress = 0; rt_uint8_t RegCount; eMBException eStatus = MB_EX_NONE; - eMBErrorCode eRegStatus; if (4+MB_PDU_FUNC_WRITE_MUL_SIZE_MIN > reqLen) { rt_kprintf("LEN ERROR"); @@ -1571,8 +1282,11 @@ eMBException funcTrimELoad (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t enable_flag = (reqFrame[2]>>4)&0x0F; regAddress = reqFrame[3]; RegCount = u8v_to_u16(&reqFrame[4]); - rt_kprintf("ch=%d,enable=%d,reg=0x%02x,count=%d\n",ch+1,enable_flag,regAddress,RegCount); - if(ch<0||ch>3){ +#if COMM_TRIM_DEBUG + rt_kprintf("trim ch=%d enable=%d reg=0x%02x count=%d\n", + ch + 1, enable_flag, regAddress, RegCount); +#endif + if(ch >= TOTAL_SOU_CHS){ rt_kprintf("CH ERROR"); return MB_EX_ILLEGAL_DATA_VALUE; } @@ -1581,13 +1295,11 @@ eMBException funcTrimELoad (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_kprintf("Enable Flag ERROR"); return MB_EX_ILLEGAL_DATA_VALUE; } - set_ymodem_update_channel(pNOR, pSOU, ch); //关掉除选定通道外的其他通道使能 //提取首指针 struct chrg_trim *pTrim = &pNOR->trim[ch]; rt_uint8_t *pWriteData = &reqFrame[7]; - pNOR->north_mode = 1; pTrim->trim_enable_flag = enable_flag; - pTrim->trim_step = 0; + pTrim->trim_mode = 0; //多寄存器控制操作 for(int i =0;itrim_mode == 0) { + return MB_EX_ILLEGAL_DATA_ADDRESS; + } + + /* + * COM只负责解析校准数据。完整校准序列由南向模块组装并加入RUN_COM队列, + * 目标通道由队列项确定,因此无需关闭任何南向通道的正常轮询使能。 + */ + if (chrg_sou_trim_submit(ch, pTrim) != 0) { + return MB_EX_SLAVE_BUSY; } rt_memcpy(resFrame, reqFrame, reqLen); *resLen = 6; - - //eRegStatus = mb_reg_holding_cb(&reqFrame[7], &resFrame[3], regAddress, cntReg, MB_REG_WRITE); - if (eRegStatus != MB_ENOERR) { - eStatus = mb_error(eRegStatus); - } return eStatus; } @@ -1657,54 +1378,135 @@ eMBException funcTrimELoad (rt_uint8_t *reqFrame, rt_uint16_t reqLen, Ymodem_state ystate = Ymodem_No; rt_uint8_t s_first_soh_rcvd = 0; + +/***************************************************************** +函数名称: chrg_ymodem_protocol_reset +函数描述: 清空COM侧Ymodem拼帧、重发和握手状态 +输入参数: - +输出参数: - +返回说明: - +其它说明: 不改变南向轮询模式和目标通道 +*****************************************************************/ +static void chrg_ymodem_protocol_reset(void) +{ + rt_memset(mobude_ymodem_buf, 0, sizeof(mobude_ymodem_buf)); + rt_memset(mobude_ymodem_buf_2, 0, sizeof(mobude_ymodem_buf_2)); + g_ymodem_rx_len = 0; + g_ymodem_expected_len = 0; + g_ymodem_last_len = 0; + g_ymodem_waiting_ack = 0; + g_ymodem_retry_count = 0; + g_ymodem_eot_done = 0; + g_ymodem_send_tick = 0; + g_ymodem_c_tick = rt_tick_get(); + s_first_soh_rcvd = 0; +} + +/***************************************************************** +函数名称: chrg_ymodem_session_start +函数描述: 停止普通南向请求并进入指定通道的升级透传模式 +输入参数: ch:需要升级的南向功率板通道 +输出参数: - +返回说明: 0:成功 <0:线程互斥锁未就绪或加锁失败 +其它说明: 不修改chrgnorth.enable和chrgsouth.enable,升级结束后恢复自动轮询 +*****************************************************************/ +static int chrg_ymodem_session_start(eIDX_SOU_CH ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + struct chrg_rollsou_t *pROLL = &chrgrollsou; + struct chrg_south_t *pSOU = &chrgsouth; + + if ((ch >= TOTAL_SOU_CHS) || (pTHR->mutex == RT_NULL)) { + return -1; + } + if (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(100)) != RT_EOK) { + return -2; + } + /* 先阻止南向线程继续发普通Modbus请求,等待已发请求退出邮箱等待。 */ + pROLL->run = RUN_UPDATA; + pROLL->ch = (rt_uint8_t)ch; + pROLL->send_flag = 0; + pROLL->leng = 0; + pROLL->updata_result = 0; + rt_mutex_release(pTHR->mutex); + rt_thread_mdelay(25); + + chrg_eload_clear_ymodem_resp(); + if (pSOU->rb != RT_NULL) { + rt_ringbuffer_reset(pSOU->rb); + } + chrg_ymodem_protocol_reset(); + + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + pROLL->mode = 1; + pROLL->run = RUN_UPDATA; + pROLL->ch = (rt_uint8_t)ch; + rt_mutex_release(pTHR->mutex); + ystate = Ymodem_Wait; + return 0; +} + +/***************************************************************** +函数名称: chrg_ymodem_session_stop +函数描述: 退出南向升级透传模式并恢复普通自动轮询 +输入参数: - +输出参数: - +返回说明: - +其它说明: 清除残留升级应答,避免其被普通Modbus解析器误识别 +*****************************************************************/ +static void chrg_ymodem_session_stop(void) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + struct chrg_rollsou_t *pROLL = &chrgrollsou; + struct chrg_south_t *pSOU = &chrgsouth; + + if (pTHR->mutex != RT_NULL) { + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + pROLL->mode = 0; + pROLL->send_flag = 0; + pROLL->leng = 0; + pROLL->updata_result = 0; + pROLL->run = RUN_AUTO; + rt_mutex_release(pTHR->mutex); + } + chrg_eload_clear_ymodem_resp(); + if (pSOU->rb != RT_NULL) { + rt_ringbuffer_reset(pSOU->rb); + } + chrg_ymodem_protocol_reset(); + ystate = Ymodem_No; +} + eMBException funYmodemUpadata (rt_uint8_t *reqFrame, rt_uint16_t reqLen, rt_uint8_t *resFrame, rt_uint16_t *resLen) { - rt_uint8_t test_mode = 0; // 测试模式 0x01/0x02/0x03 - eIDX_SOU_CH ch = IDX_SOU_CH1; // 通道枚举 - rt_uint8_t chNum = 0; // 通道号 1~4 - chNum = reqFrame[4]; - ch = (eIDX_SOU_CH)(chNum); - struct chrg_north_t *pNOR = &chrgnorth; - struct chrg_south_t *pSOU = &chrgsouth; - struct chrg_rollsou_t *pROLL = &chrgrollsou; - // Modbus异常 - eMBException eStatus = MB_EX_NONE; - eMBErrorCode eRegStatus; - if(chNum<0||chNum>3){ - return 0; + rt_uint8_t ch_num; + eIDX_SOU_CH ch; + + if ((reqFrame == RT_NULL) || (resFrame == RT_NULL) || (resLen == RT_NULL) || + (reqLen != 8)) { + return MB_EX_ILLEGAL_DATA_VALUE; } - rt_kprintf("ch = %d",ch); - //解析上位机的帧头判断状态 - if(reqLen==8){ - if(reqFrame[5] == 0xff){ - pROLL->mode = 1; - set_ymodem_update_channel(pNOR, pSOU, ch); - ystate = Ymodem_Wait; - pROLL->ch = ch; - s_first_soh_rcvd = 0; - chrg_eload_clear_ymodem_resp(); /* 进入升级时清除残留应答 */ - } - if(reqFrame[5] == 0x00){ - pROLL->mode = 0; - ystate = Ymodem_No; - set_ymodem_update_channel(pNOR, pSOU, ch); - s_first_soh_rcvd = 0; - chrg_eload_clear_ymodem_resp(); /* 退出升级时清除残留应答 */ - } + ch_num = reqFrame[4]; + if (ch_num >= TOTAL_SOU_CHS) { + return MB_EX_ILLEGAL_DATA_ADDRESS; + } + ch = (eIDX_SOU_CH)ch_num; + + if (reqFrame[5] == 0xFF) { + if (chrg_ymodem_session_start(ch) != 0) { + return MB_EX_SLAVE_BUSY; } + } else if (reqFrame[5] == 0x00) { + chrg_ymodem_session_stop(); + } else { + return MB_EX_ILLEGAL_DATA_VALUE; + } - #if SINK_TEST_DEBUG - rt_kprintf("test_mode=%d\n",test_mode); - //rt_kprintf("test_flag=%d\n",pTset->test_mode); - #endif - - eRegStatus = mb_ymodem_updata_cb(&ystate,resFrame); - /************ 响应帧 ************/ - // rt_memcpy(resFrame, reqFrame, reqLen-2); - *resLen = 1;//reqLen -2; - - return eStatus; + /* 保留原0x20响应内容,外层统一补设备地址和Modbus CRC。 */ + (void)mb_ymodem_updata_cb(&ystate, resFrame); + *resLen = 1; + return MB_EX_NONE; } static struct code_func_t funcHandlers[MB_HANDLERS_MAX] = { @@ -1783,12 +1585,288 @@ static int chrg_comm_parse_msg (struct chrg_comm_t *pCOMM) u16_to_u8v_com(crc, &pCOMM->tx_buf[pCOMM->tx_len]); pCOMM->tx_len += 2; +#if COMM_FRAME_DEBUG + LOG_HEX("sendC", 32, pCOMM->tx_buf, pCOMM->tx_len); +#endif ret = chrg_tty_send(pCOMM->tty, pCOMM->tx_buf, pCOMM->tx_len); } return ret; } +/* + * 旧版Ymodem处理代码保留在下方注释中作为历史参考。当前实现使用完整拼帧、 + * CRC校验和停止等待重发机制,不再依赖一次串口接收刚好得到一整帧。 + */ + +/***************************************************************** +函数名称: chrg_ymodem_frame_size +函数描述: 根据Ymodem控制字符取得一帧应接收的总长度 +输入参数: header:Ymodem帧头或控制字符 +输出参数: - +返回说明: 133:SOH帧 1029:STX帧 1:EOT/CAN 0:无效字符 +其它说明: - +*****************************************************************/ +static rt_uint16_t chrg_ymodem_frame_size(rt_uint8_t header) +{ + if (header == SOH) { + return YMODEM_SOH_SIZE; + } + if (header == STX) { + return YMODEM_STX_SIZE; + } + if ((header == EOT) || (header == YM_CAN)) { + return 1; + } + return 0; +} + +/***************************************************************** +函数名称: chrg_ymodem_crc16 +函数描述: 计算Ymodem数据区使用的CRC16-XMODEM校验值 +输入参数: data:数据区地址 length:数据区长度 +输出参数: - +返回说明: CRC16-XMODEM校验值 +其它说明: 初值0,多项式0x1021,结果按高字节在前写入Ymodem帧 +*****************************************************************/ +static rt_uint16_t chrg_ymodem_crc16(const rt_uint8_t *data, rt_uint16_t length) +{ + rt_uint16_t crc = 0; + rt_uint16_t i; + rt_uint8_t bit; + + for (i = 0; i < length; i++) { + crc ^= (rt_uint16_t)data[i] << 8; + for (bit = 0; bit < 8; bit++) { + crc = (crc & 0x8000) ? (rt_uint16_t)((crc << 1) ^ 0x1021) : + (rt_uint16_t)(crc << 1); + } + } + return crc; +} + +/***************************************************************** +函数名称: chrg_ymodem_frame_valid +函数描述: 校验Ymodem数据帧长度、包号反码和数据区CRC +输入参数: frame:完整Ymodem帧 length:帧总长度 +输出参数: - +返回说明: RT_TRUE:有效 RT_FALSE:无效 +其它说明: EOT和CAN为单字节控制帧,不包含包号与CRC +*****************************************************************/ +static rt_bool_t chrg_ymodem_frame_valid(const rt_uint8_t *frame, rt_uint16_t length) +{ + rt_uint16_t data_len; + rt_uint16_t frame_crc; + + if ((frame == RT_NULL) || (length != chrg_ymodem_frame_size(frame[0]))) { + return RT_FALSE; + } + if ((frame[0] == EOT) || (frame[0] == YM_CAN)) { + return RT_TRUE; + } + if ((rt_uint8_t)(frame[1] + frame[2]) != 0xFF) { + return RT_FALSE; + } + data_len = (frame[0] == SOH) ? 128 : 1024; + frame_crc = ((rt_uint16_t)frame[length - 2] << 8) | frame[length - 1]; + return chrg_ymodem_crc16(&frame[YMODEM_DATA_OFFSET], data_len) == frame_crc; +} + +/***************************************************************** +函数名称: chrg_ymodem_resend_last +函数描述: 将最近一次完整Ymodem帧重新提交给南向发送线程 +输入参数: - +输出参数: - +返回说明: 0:提交成功 -1:无可重发帧 其它:南向提交错误 +其它说明: 仅在NAK或等待应答超时时调用,始终使用原帧实际长度 +*****************************************************************/ +static int chrg_ymodem_resend_last(void) +{ + int ret; + + if (g_ymodem_last_len == 0) { + return -1; + } + ret = chrg_send_sou_upadata_data(mobude_ymodem_buf_2, g_ymodem_last_len); + if (ret == 0) { + g_ymodem_retry_count++; + g_ymodem_send_tick = rt_tick_get(); + g_ymodem_waiting_ack = 1; + } + return ret; +} + +/***************************************************************** +函数名称: chrg_ymodem_process +函数描述: 累计COM串口分段数据,形成完整Ymodem帧后提交到南向线程 +输入参数: data:本次串口数据 length:本次数据长度 +输出参数: - +返回说明: 0:处理完成 -2:仍在等待上一帧应答 -3:帧校验失败 -4:提交失败 +其它说明: 一帧可由多次串口接收组成,只有完整且校验通过后才会发送到功率板 +*****************************************************************/ +static int chrg_ymodem_process(const rt_uint8_t *data, rt_uint16_t length) +{ + rt_uint16_t offset = 0; + rt_uint16_t copy_len; + int ret; + + if ((data == RT_NULL) || (length == 0)) { + return 0; + } + while (offset < length) { + if (g_ymodem_waiting_ack != 0) { + return -2; + } + if (g_ymodem_expected_len == 0) { + g_ymodem_expected_len = chrg_ymodem_frame_size(data[offset]); + if (g_ymodem_expected_len == 0) { + offset++; + continue; + } + g_ymodem_rx_len = 0; + } + + copy_len = g_ymodem_expected_len - g_ymodem_rx_len; + if (copy_len > (length - offset)) { + copy_len = length - offset; + } + rt_memcpy(&mobude_ymodem_buf[g_ymodem_rx_len], &data[offset], copy_len); + g_ymodem_rx_len += copy_len; + offset += copy_len; + if (g_ymodem_rx_len < g_ymodem_expected_len) { + continue; + } + + if (!chrg_ymodem_frame_valid(mobude_ymodem_buf, g_ymodem_rx_len)) { + g_ymodem_rx_len = 0; + g_ymodem_expected_len = 0; + return -3; + } + + g_ymodem_last_len = g_ymodem_rx_len; + rt_memcpy(mobude_ymodem_buf_2, mobude_ymodem_buf, g_ymodem_last_len); + if (mobude_ymodem_buf[0] == SOH) { + if (g_ymodem_eot_done != 0) { + /* 第二次EOT之后的0号空SOH是Ymodem收尾帧,只回复ACK。 */ + ystate = Ymodem_Rec_Finish; + } else if ((mobude_ymodem_buf[1] == 0) && (s_first_soh_rcvd == 0)) { + /* 只有传输开始时的0号SOH是文件名/大小帧,需要回复ACK+C。 */ + ystate = Ymodem_Rec_Start; + s_first_soh_rcvd = 1; + } else { + /* 包号非0的SOH是普通128字节固件数据帧,只回复ACK。 */ + ystate = Ymodem_Rec_1024; + } + } else if (mobude_ymodem_buf[0] == STX) { + ystate = Ymodem_Rec_1024; + } else if (mobude_ymodem_buf[0] == EOT) { + ystate = Ymodem_Rec_End; + } else { + ystate = Ymodem_Rec_Finish; + } + + ret = chrg_send_sou_upadata_data(mobude_ymodem_buf_2, g_ymodem_last_len); + g_ymodem_rx_len = 0; + g_ymodem_expected_len = 0; + if (ret != 0) { + return -4; + } + g_ymodem_retry_count = 0; + g_ymodem_waiting_ack = 1; + g_ymodem_send_tick = rt_tick_get(); + } + return 0; +} + +/***************************************************************** +函数名称: ymodem_send +函数描述: 处理南向Ymodem应答、生成上位机应答并执行超时重发 +输入参数: pCOMM:COM通信对象 +输出参数: pCOMM->tx_buf和pCOMM->tx_len +返回说明: 1:需要向上位机发送数据 0:本轮无数据发送 +其它说明: 首包前每秒发送一次C;SOH首包和第二次EOT后按Ymodem规则发送ACK+C +*****************************************************************/ +static rt_uint8_t ymodem_send(struct chrg_comm_t *pCOMM) +{ + rt_uint8_t south_resp; + rt_uint32_t timeout_ms; + rt_tick_t now; + + pCOMM->tx_len = 0; + now = rt_tick_get(); + if ((s_first_soh_rcvd == 0) && (g_ymodem_waiting_ack == 0) && + ((now - g_ymodem_c_tick) >= rt_tick_from_millisecond(YMODEM_C_PERIOD_MS))) { + pCOMM->tx_buf[0] = ACK_C; + pCOMM->tx_len = 1; + g_ymodem_c_tick = now; + return 1; + } + + south_resp = chrg_eload_get_ymodem_resp(); + if (south_resp == ACK) { + g_ymodem_waiting_ack = 0; + g_ymodem_retry_count = 0; + if (ystate == Ymodem_Rec_Start) { + pCOMM->tx_buf[0] = ACK; + pCOMM->tx_buf[1] = ACK_C; + pCOMM->tx_len = 2; + } else if (ystate == Ymodem_Rec_End) { + pCOMM->tx_buf[0] = ACK; + pCOMM->tx_buf[1] = ACK_C; + pCOMM->tx_len = 2; + g_ymodem_eot_done = 1; + ystate = Ymodem_Rec_Finish; + } else { + pCOMM->tx_buf[0] = ACK; + pCOMM->tx_len = 1; + } + return 1; + } + if (south_resp == NAK) { + if (g_ymodem_retry_count >= YMODEM_MAX_RETRIES) { + pCOMM->tx_buf[0] = YM_CAN; + pCOMM->tx_buf[1] = YM_CAN; + pCOMM->tx_len = 2; + chrg_ymodem_session_stop(); + return 1; + } + (void)chrg_ymodem_resend_last(); + return 0; + } + if (south_resp == ACK_C) { + /* 等待当前帧ACK时忽略功率板周期C,避免打乱上位机停止等待握手。 */ + if (g_ymodem_waiting_ack != 0) { + return 0; + } + pCOMM->tx_buf[0] = ACK_C; + pCOMM->tx_len = 1; + return 1; + } + if (south_resp == YM_CAN) { + pCOMM->tx_buf[0] = YM_CAN; + pCOMM->tx_buf[1] = YM_CAN; + pCOMM->tx_len = 2; + chrg_ymodem_session_stop(); + return 1; + } + + if (g_ymodem_waiting_ack != 0) { + timeout_ms = (ystate == Ymodem_Rec_Start) ? + YMODEM_META_ACK_TIMEOUT_MS : YMODEM_DATA_ACK_TIMEOUT_MS; + if ((now - g_ymodem_send_tick) >= rt_tick_from_millisecond(timeout_ms)) { + if (g_ymodem_retry_count >= YMODEM_MAX_RETRIES) { + pCOMM->tx_buf[0] = YM_CAN; + pCOMM->tx_buf[1] = YM_CAN; + pCOMM->tx_len = 2; + chrg_ymodem_session_stop(); + return 1; + } + (void)chrg_ymodem_resend_last(); + } + } + return 0; +} + ///***************************************************************** //函数名称: ymodem_send //函数描述: 每隔一秒发一次0x43告诉上位机准备数据 @@ -1926,10 +2004,36 @@ static int chrg_comm_parse_msg (struct chrg_comm_t *pCOMM) *****************************************************************/ static int chrg_comm_updata_north (struct chrg_comm_t *pCOMM) { -// if (RT_NULL == pCOMM) { -// return -1; -// } int ret = 0; + int process_ret; + rt_uint8_t need_tx; + + if (pCOMM == RT_NULL) { + return -1; + } + + /* 升级模式中仍允许接收0x20停止命令,必须先校验地址和Modbus CRC。 */ + if ((pCOMM->rx_len == 8) && (pCOMM->rx_buf[1] == MB_YMODEM_UPDATAE) && + ((pCOMM->rx_buf[0] == pCOMM->addr) || + (pCOMM->rx_buf[0] == MB_ADDRESS_BROADCAST)) && + (mb_crc16(pCOMM->rx_buf, pCOMM->rx_len) == 0)) { + return chrg_comm_parse_msg(pCOMM); + } + + if (pCOMM->rx_len > 0) { + process_ret = chrg_ymodem_process(pCOMM->rx_buf, pCOMM->rx_len); + if ((process_ret == -3) || (process_ret == -4)) { + /* 本地校验或南向提交失败时要求上位机重发当前帧。 */ + pCOMM->tx_buf[0] = NAK; + pCOMM->tx_len = 1; + return chrg_tty_send(pCOMM->tty, pCOMM->tx_buf, pCOMM->tx_len); + } + } + + need_tx = ymodem_send(pCOMM); + if ((need_tx != 0) && (pCOMM->tx_len > 0)) { + ret = chrg_tty_send(pCOMM->tty, pCOMM->tx_buf, pCOMM->tx_len); + } // uint8_t need_tx = 0; // pCOMM->tx_len = 0; //进来先清空发送缓冲区 // eMBException eStatus = MB_EX_NONE; @@ -1997,9 +2101,12 @@ void chrg_comm_thread_entry (void *data) len = chrg_tty_recv(pTTY, pCOMM->rx_buf, sizeof(pCOMM->rx_buf)); // LOG_HEX("Num=", len, pCOMM->rx_buf, len); if(pROLL->mode==0x01){ //升级模式走单独协议 - pCOMM->rx_len = len; + pCOMM->rx_len = (len > 0) ? (rt_uint16_t)len : 0; pCOMM->tx_len = 0; chrg_comm_updata_north(pCOMM); + if (len <= 0) { + rt_thread_mdelay(1); + } }else{ if (len <= 0) { rt_thread_mdelay(1); @@ -2008,6 +2115,9 @@ void chrg_comm_thread_entry (void *data) pCOMM->rx_len = len; pCOMM->tx_len = 0; +#if COMM_FRAME_DEBUG + LOG_HEX("recvC", 32, pCOMM->rx_buf, pCOMM->rx_len); +#endif chrg_comm_parse_msg(pCOMM); } } @@ -2166,7 +2276,6 @@ static void chrg_mb_write_src(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t { case SRC_REG_PROTOCOL: // 0x00: 协议 pSW->source.protocol = src_proto_lookup(sou_value); - pSW->source.Set_Pro_Flag = 1; pSW->protoCmd = (rt_uint8_t)sou_value; // rt_kprintf("[SRC CH%d] Protocol=0x%02X -> 0x%02X\n", ch + 1, sou_value, pSW->source.protocol); break; @@ -2180,22 +2289,21 @@ static void chrg_mb_write_src(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t // rt_kprintf("[SRC CH%d] Mode=%d\n", ch + 1, sou_value); break; case SRC_REG_MIN_VOLT: // 0x03: 最小电压 (mV) - pSW->source.min_vol = sou_value; + pSW->source.pd_set.mm.min_vol = sou_value; //rt_kprintf("[SRC CH%d] MinVolt=%d mV\n", ch + 1, sou_value); break; case SRC_REG_MAX_VOLT: // 0x04: 最大电压 (mV) - pSW->source.max_vol = sou_value; + pSW->source.pd_set.mm.max_vol = sou_value; // rt_kprintf("[SRC CH%d] MaxVolt=%d mV\n", ch + 1, sou_value); break; case SRC_REG_MAX_CURR: // 0x05: 最大电流 (mA) - pSW->source.protective_curr = sou_value; - pSW->source.vc.set_current = sou_value; + pSW->source.pd_set.mm.max_cur = sou_value; //rt_kprintf("[SRC CH%d] MaxCurr=%d mA\n", ch + 1, sou_value); break; case SRC_REG_CC_SEL: // 0x06: CC选择 - pSW->source.cc_set = (rt_uint8_t)sou_value; + pSW->source.CC_Set = (rt_uint8_t)sou_value; //rt_kprintf("[SRC CH%d] CC_SEL=%d\n", ch + 1, sou_value); break; @@ -2210,148 +2318,499 @@ static void chrg_mb_write_src(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t //rt_kprintf("ret_offset = %d, value = %d",reg_offset,sou_value); } -static void chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, rt_uint16_t sou_value, - struct chrg_switch_t *pSW, struct chrg_north_t *pNOR, struct chrg_south_t *pSOU) +/***************************************************************** +函数名称: chrg_comm_get_param_diff +函数描述: 按当前角色统一比较运行参数与待生效影子参数 +输入参数: role:当前角色 pReal:正在运行的参数 pShadow:待生效参数 +输出参数: pDiff:各公共属性及Sink专用协议属性的变化标志 +返回说明: - +其它说明: 本函数只计算差异,不修改状态、不发送命令;Source直接比较 + 完整pd_set,并从pd_set.mm提取标准协议电压电流差异 +*****************************************************************/ +static void chrg_comm_get_param_diff(eIDX_ID role, + const struct chrg_switch_t *pReal, + const struct chrg_switch_t *pShadow, + chrg_param_diff_t *pDiff) { - struct chrg_comm_t *pCOMM = &chrgcomm; - switch (reg_offset) - { - case SYS_REG_ROLE_SWITCH: // 0x00: 源/载状态切换 - if (sou_value == 0x01) - { - pSW->id = ID_SINK; - } - else if (sou_value == 0x02) - { - pSW->id = ID_SOURCE; - pSW->source.Set_Pro_Flag = 1; - } - break; + if ((pReal == RT_NULL) || (pShadow == RT_NULL) || (pDiff == RT_NULL)) { + return; + } + rt_memset(pDiff, 0, sizeof(*pDiff)); + pDiff->cv_changed = (pReal->CV_mode != pShadow->CV_mode); - case SYS_REG_PARAM_EFFECT: // 0x01: 参数生效标志 - if (sou_value == 0x01) - { - g_shadow_dirty[ch] = 0; // 标记已生效 + if (role == ID_SOURCE) { + pDiff->protocol_changed = + (pReal->source.protocol != pShadow->source.protocol); + pDiff->cc_changed = + (pReal->source.CC_Set != pShadow->source.CC_Set); + /* 两个数组均来自静态零初始化结构,填充前未定义字节保持为0。 */ + pDiff->pd_changed = (rt_memcmp(&pReal->source.pd_set, + &pShadow->source.pd_set, sizeof(pReal->source.pd_set)) != 0); + pDiff->voltage_changed = + (pReal->source.pd_set.mm.max_vol != + pShadow->source.pd_set.mm.max_vol); + pDiff->current_changed = + (pReal->source.pd_set.mm.max_cur != + pShadow->source.pd_set.mm.max_cur); + } else if (role == ID_SINK) { + pDiff->protocol_changed = + (pReal->sink.protocol != pShadow->sink.protocol); + pDiff->cc_changed = + (pReal->sink.cc_set != pShadow->sink.cc_set); + pDiff->voltage_changed = + (pReal->sink.loadv != pShadow->sink.loadv); + pDiff->current_changed = + (pReal->sink.loadc != pShadow->sink.loadc); + pDiff->group_changed = + (pReal->sink.Pro_Group != pShadow->sink.Pro_Group); + pDiff->pro_voltage_changed = + (pReal->sink.pro_loadv != pShadow->sink.pro_loadv); + pDiff->pro_current_changed = + (pReal->sink.pro_loadc != pShadow->sink.pro_loadc); + } +} - struct chrg_switch_t *pReal = &pNOR->sw[ch]; - rt_uint8_t pro_change = 0; - sink_proto_diff_t diff_info; - chrg_sink_get_proto_diff(pReal, pSW, &diff_info, &pro_change); - eIDX_ID old_id = pReal->id; - eIDX_ID new_id = pReal->id; - rt_uint16_t sink_work = pSW->sink.Now_State; - rt_uint16_t source_work = pSW->source.Now_State; - //rt_uint8_t old_sink_test = pReal->sink.test.test_mode; - struct chrg_thread_t *pTHR_NOR = &chrgthr[IDX_THR_ROLL_NOR]; - rt_mutex_take(pTHR_NOR->mutex, RT_WAITING_FOREVER); - rt_memcpy(pReal, &g_shadow_sw[ch], sizeof(struct chrg_switch_t)); - if (new_id == ID_SINK) - { - if (sink_work == 0x01) // 负载启动 - { - rt_uint8_t curr_test = pReal->sink.test.test_mode; - rt_uint8_t curr_test_old = pReal->sink.test.test_mode_old; - // 测试模式状态发生切换:0<->非0 - if ((curr_test_old == 0 && curr_test != 0) || (curr_test == 0 && curr_test_old != 0)) - { - if (curr_test_old == 0) - { - // 从正常模式进入测试模式 - pReal->sink.work_mode = (curr_test == 3) ? SINK_WORK_ON_TEST_CHANGE : SINK_WORK_ON_TEST_SHOUT; - pReal->sink.test.test_mode_old = curr_test; - } - else - { - // 从测试模式切回正常模式 - pReal->sub = IDX_SET_SINK_CCLINE; - pReal->sink.work_mode = SINK_WORK_INIT; - } - } - else - { - // 测试模式无变化,处理负载初始化/参数更新 - if (pReal->sink.On_work == 0) - { - // 负载首次启动硬件初始化 - pReal->sink.On_work = 1; - pReal->sub = IDX_SET_SINK_CCLINE; - pReal->sink.work_mode = SINK_WORK_INIT; - } - else - { - if (pro_change) - { - // 协议参数变更,重新初始化链路 - pReal->sub = IDX_SET_SINK_CCLINE; - pReal->sink.work_mode = SINK_WORK_WAIT; - } - else - { - // 仅下发差异寄存器 - chrg_sink_diff_send_reg((eIDX_SOU_CH)ch, &diff_info, pReal); - } - } - } +/***************************************************************** +函数名称: chrg_comm_apply_sink_effect +函数描述: 处理COM参数生效后的同角色负载启停、测试和差异更新 +输入参数: ch:通道 pReal:运行参数 pShadow:待生效影子参数 +输出参数: 更新pReal并按需提交南向命令 +返回说明: MB_EX_NONE:成功 MB_EX_SLAVE_BUSY:命令提交失败 +其它说明: 角色切换不在本函数处理,由chrg_mb_write_sys统一调度 +*****************************************************************/ +static eMBException chrg_comm_apply_sink_effect(rt_uint8_t ch, + struct chrg_switch_t *pReal, const struct chrg_switch_t *pShadow) +{ + rt_bool_t full_handshake_change; + rt_bool_t protocol_voltage_only; + rt_bool_t protocol_current_change; + rt_bool_t protocol_data_changed; + rt_uint8_t curr_test; + rt_uint8_t curr_test_old; + rt_uint8_t old_cv_mode = pReal->CV_mode; + rt_uint16_t old_loadv = pReal->sink.loadv; + rt_uint16_t old_loadc = pReal->sink.loadc; + rt_uint8_t old_on_work = pReal->sink.On_work; + rt_uint8_t old_work_mode = pReal->sink.work_mode; + rt_uint16_t sink_work = pShadow->sink.Now_State; + chrg_param_diff_t diff_info; + + chrg_comm_get_param_diff(ID_SINK, pReal, pShadow, &diff_info); + /* + * 协议类型、CC和协议组号变化需要完整握手;协议电压和协议电流 + * 变化只需要软卸载后重发协议,不关闭南向继电器。 + */ + full_handshake_change = diff_info.protocol_changed || diff_info.cc_changed || + diff_info.group_changed; + protocol_data_changed = diff_info.protocol_changed || + diff_info.group_changed || diff_info.pro_voltage_changed || + diff_info.pro_current_changed; + protocol_current_change = diff_info.pro_current_changed && + !full_handshake_change; + protocol_voltage_only = diff_info.pro_voltage_changed && + !full_handshake_change && !protocol_current_change; + rt_memcpy(pReal, pShadow, sizeof(struct chrg_switch_t)); + + if (sink_work == 0x01) { + curr_test = pReal->sink.test.test_mode; + curr_test_old = pReal->sink.test.test_mode_old; + if (((curr_test_old == 0) && (curr_test != 0)) || + ((curr_test == 0) && (curr_test_old != 0))) { + if (curr_test_old == 0) { + if (curr_test == SINK_TEST_DYNA) { + pReal->sink.work_mode = SINK_WORK_ON_TEST_DYNA; + } else if (curr_test == SINK_TEST_OCP) { + pReal->sink.work_mode = SINK_WORK_ON_TEST_OCP; + } else { + pReal->sink.work_mode = SINK_WORK_ON_TEST_SHORT; } - else // sink_work == 0x00 负载停止 - { - pReal->sink.On_work = 0; - pReal->sink.work_mode = SINK_WORK_STOP; - } - chrg_sink_work(ch, pReal); + pReal->sink.test.test_mode_old = curr_test; + } else { + /* Protocol-before-CC test flow is disabled in this version. */ + pReal->sub = IDX_SET_SINK_CC_BEFORE_PD; + pReal->sink.work_mode = SINK_WORK_INIT; } - else if (new_id == ID_SOURCE && source_work == 0x01) - { - chrg_com_source_on(ch, pReal->protoCmd, pReal, &pNOR->enable[ch]); - } - else if (new_id == ID_SOURCE && source_work == 0x00) - { - chrg_sou_stop(ch, pReal, pNOR); - } - rt_mutex_release(pTHR_NOR->mutex); - } - break; - default: - rt_kprintf("[SYS CH%d] Unknown reg offset=0x%02X\n", ch + 1, reg_offset); - break; - } + } else if (pReal->sink.On_work == 0) { + pReal->sink.On_work = 1; + /* Protocol-before-CC test flow is disabled in this version. */ + pReal->sub = IDX_SET_SINK_CC_BEFORE_PD; + pReal->sink.work_mode = SINK_WORK_INIT; + } else if (full_handshake_change) { + /* + * 只有CC实际变化时才发送目标CC命令并等待;协议类型或组号 + * 单独变化时直接重发PD帧,不重复设置未变化的CC。 + * 协议与CC同时变化时先设置目标CC,CC稳定后再发送协议; + * 仅协议变化时不重复设置未变化的CC。 + */ + if (diff_info.cc_changed && protocol_data_changed) { + /* Protocol-before-CC test flow is disabled in this version. */ + pReal->sub = IDX_SET_SINK_CC_BEFORE_PD; + } else if (diff_info.cc_changed) { + pReal->sub = IDX_SET_SINK_CCLINE; + } else { + pReal->sub = IDX_SET_SINK_PD_ONLY; + } + pReal->sink.work_mode = SINK_WORK_HANDSHAKE_PREPARE; + } else if (protocol_voltage_only || protocol_current_change) { + /* + * 协议电压、协议电流变化都只执行软卸载,不关闭南向继电器。 + * 低电流待机本身已经WORK_STOP,无需重复提交卸载命令;运行中 + * 则等待实际电流降到安全范围后再重发协议。 + */ + pReal->sub = IDX_SET_SINK_PD_ONLY; + if (old_work_mode == SINK_WORK_LOAD_STANDBY) { + pReal->sink.work_mode = SINK_WORK_WAIT; + } else { + /* 运行状态修改协议参数时保持继电器,只停止电子负载。 */ + pReal->sink.work_mode = SINK_WORK_PROTOCOL_UNLOAD; + } + } else if (chrg_comm_handle_sink_load_current(&diff_info, + old_work_mode, pReal)) { + /* 电流跨门限的停止或恢复已转换为独立Sink状态,由统一入口下发。 */ + } else if (chrg_sink_diff_send_reg((eIDX_SOU_CH)ch, + &diff_info, pReal) != 0) { + pReal->CV_mode = old_cv_mode; + pReal->sink.loadv = old_loadv; + pReal->sink.loadc = old_loadc; + return MB_EX_SLAVE_BUSY; + } + } else { + pReal->sink.On_work = 0; + /* 组合控制或上位机重发可能在关机尚未完成时再次提交OFF。 + * 此时必须保留当前停止阶段,不能重新进入STOP并清零低电流确认次数, + * 否则实际电流即使一直为0,也永远累计不到连续2帧。 */ + if ((old_work_mode == SINK_WORK_STOP_WAIT) || + (old_work_mode == SINK_WORK_STOP_RELAY) || + (old_work_mode == SINK_WORK_STOP_RELAY_WAIT)) { + pReal->sink.work_mode = old_work_mode; + } else if ((old_on_work == 0U) && + (old_work_mode == SINK_WORK_WAIT)) { + /* 已完成停止时重复OFF保持WAIT,不重复操作继电器。 */ + pReal->sink.work_mode = SINK_WORK_WAIT; + } else { + pReal->sink.work_mode = SINK_WORK_STOP; + } + } + + return (chrg_sink_work((eIDX_SOU_CH)ch, pReal) == 0) ? + MB_EX_NONE : MB_EX_SLAVE_BUSY; } -void chrg_sink_get_proto_diff(struct chrg_switch_t *pReal, - struct chrg_switch_t *pShadow, - sink_proto_diff_t *pDiff, - rt_uint8_t *pChange) + +/***************************************************************** +函数名称: chrg_comm_handle_sink_load_current +函数描述: 处理普通负载电流跨越最低带载门限时的状态转换 +输入参数: pDiff:本次参数差异 old_work_mode:生效前工作状态 + pReal:已复制新参数的运行结构体 +输出参数: 必要时更新sink.work_mode +返回说明: RT_TRUE:本次电流变化已处理,禁止再直接写电流寄存器 + RT_FALSE:未跨门限,继续执行普通差异下发 +其它说明: 北向协议、CC和协议电流变化在调用本函数前处理;本函数只管理 + 南向电子负载的WORK_STOP/CURRENT=0及无握手快速恢复 +*****************************************************************/ +static rt_bool_t chrg_comm_handle_sink_load_current( + const chrg_param_diff_t *pDiff, rt_uint8_t old_work_mode, + struct chrg_switch_t *pReal) { - pDiff->proto_changed = (pReal->sink.protocol != pShadow->sink.protocol) ? 1 : 0; - pDiff->group_changed = (pReal->sink.Pro_Group != pShadow->sink.Pro_Group) ? 1 : 0; - pDiff->ccset_changed = (pReal->sink.cc_set != pShadow->sink.cc_set) ? 1 : 0; - pDiff->pro_volt_changed = (pReal->sink.pro_loadv != pShadow->sink.pro_loadv) ? 1 : 0; - pDiff->pro_curr_changed = (pReal->sink.pro_loadc != pShadow->sink.pro_loadc) ? 1 : 0; - pDiff->volt_changed = (pReal->sink.loadv != pShadow->sink.loadv) ? 1 : 0; - pDiff->curr_changed = (pReal->sink.loadc != pShadow->sink.loadc) ? 1 : 0; - pDiff->cvmode_changed = (pReal->CV_mode != pShadow->CV_mode) ? 1 : 0; - // 任意一项变更则 pro_change = 0,全部不变 = 1 - *pChange = (!pDiff->proto_changed && !pDiff->group_changed && - !pDiff->pro_volt_changed && !pDiff->pro_curr_changed && - !pDiff->ccset_changed && !pDiff->cvmode_changed) ? 0 : 1; - + rt_bool_t should_start; + + if ((pDiff == RT_NULL) || (pReal == RT_NULL) || + !pDiff->current_changed || + (pReal->CV_mode != MODE_CONSTANT_CURRENT)) { + return RT_FALSE; + } + should_start = chrg_sink_load_should_start(pReal); + + if (old_work_mode == SINK_WORK_RUNING) { + if (!should_start) { + pReal->sink.work_mode = SINK_WORK_LOAD_STOP; + return RT_TRUE; + } + return RT_FALSE; + } + + if (old_work_mode == SINK_WORK_LOAD_STANDBY) { + if (should_start) { + /* + * 低电流待机恢复带载时,不允许由COM直接打开电子负载。 + * 先强制回到北向电压查询入口,确认实际电压稳定后再进入RUNING开载。 + */ + pReal->sub = IDX_GET_SINK_VC; + pReal->sink.work_mode = SINK_WORK_WAIT; + } + return RT_TRUE; + } + + /* 协议握手期间禁止把低于门限的小电流提前写入南向功率板。 */ + return should_start ? RT_FALSE : RT_TRUE; } -void chrg_sink_diff_send_reg(eIDX_SOU_CH ch, sink_proto_diff_t *pDiff, struct chrg_switch_t *pReal) -{ - chrg_sou_com_sou_set(); - if(pReal->CV_mode == 0x01){ - if (pDiff->volt_changed) - { - chrg_sou_com_batch_add_reg(ch, REG_VOLTAGE_OUT+1, pReal->sink.loadv); - } - }else{ - if (pDiff->curr_changed) - { - chrg_sou_com_batch_add_reg(ch, REG_CURRENT_OUT+1, pReal->sink.loadc); - } - } - if(pDiff->cvmode_changed){ - chrg_sou_com_batch_add_reg(ch, REG_MODE, pReal->CV_mode); - } + +/***************************************************************** +函数名称: chrg_comm_apply_source_effect +函数描述: 处理COM参数生效后的同角色电源启停和参数差异更新 +输入参数: ch:通道 pReal:运行参数 pShadow:待生效影子参数 +输出参数: 更新pReal、Source状态和必要的南向寄存器 +返回说明: MB_EX_NONE:成功 MB_EX_SLAVE_BUSY:命令提交失败 +其它说明: 只复制Source配置字段,避免影子结构覆盖北向实时set_volt/set_curr; + 快充参数变化进入WAIT,标准协议电压统一交给RUNING状态下发 +*****************************************************************/ +static eMBException chrg_comm_apply_source_effect(rt_uint8_t ch, + struct chrg_switch_t *pReal, const struct chrg_switch_t *pShadow) +{ + chrg_param_diff_t diff_info; + rt_bool_t protocol_config_changed; + rt_bool_t cc_changed; + rt_bool_t north_config_changed; + rt_bool_t protocol_switch_requested; + rt_bool_t need_source_reset; + rt_uint8_t new_pd_state; + rt_uint8_t old_pd_state; + struct vc_pd_t old_pd_set; + eSRC_PRO old_protocol = pReal->source.protocol; + rt_uint8_t old_cc_set = pReal->source.CC_Set; + rt_uint8_t old_now_state = pReal->source.Now_State; + rt_uint8_t old_work_mode = pReal->source.work_mode; + rt_uint8_t old_cv_mode = pReal->CV_mode; + rt_uint8_t old_proto_cmd = pReal->protoCmd; + rt_uint8_t old_sub = pReal->sub; + rt_uint16_t old_set_volt = pReal->source.set_volt; + rt_uint16_t old_set_curr = pReal->source.set_curr; + rt_uint16_t source_work = pShadow->source.Now_State; + rt_uint16_t regs[2]; + rt_uint16_t vals[2]; + rt_uint16_t target_curr; + rt_uint8_t count = 0; + + chrg_comm_get_param_diff(ID_SOURCE, pReal, pShadow, &diff_info); + rt_memcpy(&old_pd_set, &pReal->source.pd_set, sizeof(old_pd_set)); + /* 标准协议的电压电流直接交给功率板,不因pd_set数值变化重发0xD0。 */ + protocol_config_changed = diff_info.protocol_changed || + ((pShadow->source.protocol != PRO_NONE) && diff_info.pd_changed); + cc_changed = diff_info.cc_changed; + north_config_changed = protocol_config_changed || cc_changed; + protocol_switch_requested = north_config_changed; + new_pd_state = chrg_comm_source_pd_state( + (rt_uint8_t)pShadow->source.protocol); + /* PD与非PD之间发生跨类别切换时执行完整硬件复位。 */ + old_pd_state = chrg_comm_source_pd_state((rt_uint8_t)old_protocol); + need_source_reset = (old_pd_state != new_pd_state); + target_curr = pShadow->source.pd_set.mm.max_cur; + + /* 影子区只拥有COM配置;北向解析产生的运行目标和回读数据继续由pReal持有。 */ + pReal->CV_mode = pShadow->CV_mode; + pReal->protoCmd = pShadow->protoCmd; + pReal->source.protocol = pShadow->source.protocol; + pReal->source.CC_Set = pShadow->source.CC_Set; + pReal->source.Now_State = pShadow->source.Now_State; + rt_memcpy(&pReal->source.pd_set, &pShadow->source.pd_set, + sizeof(pReal->source.pd_set)); + + if (source_work == 0x01) { + if (pReal->source.On_Flag == 0) { + /* 每次重新启动的第一次协议下发不发送复位。 */ + last_pd_pro[ch] = SOURCE_PD_STATE_UNKNOWN; + /* + * 停止状态启动不需要先断开CC,直接从设置协议开始; + * 收到0xD0应答并等待保护时间后,再连接目标CC。 + */ + chrg_roll_nor_source_switch_reset(ch); + pReal->sub = IDX_SET_SOURCE_PD; + pReal->source.On_Flag = 1; + pReal->source.work_mode = SOURCE_WORK_INIT; + if (chrg_source_work_state(ch, pReal) != 0) { + goto source_effect_submit_failed; + } + return MB_EX_NONE; + } + + /* + * PD配置或CC发生变化时进入切换流程;跨PD类别切换时先执行完整复位。 + * 同类别协议配置变化和仅CC切换直接从0xD0开始。 + */ + if (north_config_changed) { + chrg_roll_nor_source_switch_reset(ch); + pReal->sub = need_source_reset ? + IDX_SET_SOURCE_RESET : IDX_SET_SOURCE_PD; + } + + pReal->source.work_mode = SOURCE_WORK_WAIT; + if (protocol_switch_requested) { + /* + * COM只进入协议切换状态;南向输出清零统一由Source状态函数执行。 + * sub已在上方确定,北向轮询线程执行本状态并清零成功后,继续 + * CC_CLOSE或协议设置,不需要COM直接发送任何清零命令。 + */ + pReal->source.work_mode = need_source_reset ? + SOURCE_WORK_FULL_RESET : SOURCE_WORK_PROTOCOL_SWITCH; + } + + if (pReal->source.protocol != PRO_NONE) { + /* 快充只更新协议配置并等待协商回码,不直接改功率板输出。 */ + return MB_EX_NONE; + } + + /* 标准协议的模式和电流可直接提交,电压只能由RUNING状态写入。 */ + if (!protocol_switch_requested && diff_info.cv_changed) { + regs[count] = REG_MODE; + vals[count++] = pReal->CV_mode; + } + if (!protocol_switch_requested && diff_info.current_changed) { + regs[count] = REG_CURRENT_OUT + 1; + vals[count++] = target_curr; + } + + if ((count > 0) && (chrg_sou_com_batch_submit((eIDX_SOU_CH)ch, + regs, vals, count) != 0)) { + goto source_effect_submit_failed; + } + + /* COM只更新标准协议配置并保持WAIT;下一帧北向解析负责比较 + * pd_set目标与set_volt,再决定是否进入RUNING写电压。 */ + pReal->source.set_curr = target_curr; + if (!protocol_switch_requested) { + pReal->source.work_mode = SOURCE_WORK_WAIT; + } + return MB_EX_NONE; + } + + if (pReal->source.On_Flag == 0) { + chrg_roll_nor_source_switch_reset(ch); + pReal->source.work_mode = SOURCE_WORK_WAIT; + return MB_EX_NONE; + } + chrg_roll_nor_source_switch_reset(ch); + pReal->source.work_mode = SOURCE_WORK_STOP; + return (chrg_source_work_state(ch, pReal) == 0) ? + MB_EX_NONE : MB_EX_SLAVE_BUSY; + +source_effect_submit_failed: + /* 南向命令提交失败必须恢复差异基准,保证下一次PARAM_EFFECT仍会重发。 */ + pReal->CV_mode = old_cv_mode; + pReal->protoCmd = old_proto_cmd; + pReal->sub = old_sub; + pReal->source.protocol = old_protocol; + pReal->source.CC_Set = old_cc_set; + pReal->source.Now_State = old_now_state; + pReal->source.work_mode = old_work_mode; + pReal->source.set_volt = old_set_volt; + pReal->source.set_curr = old_set_curr; + rt_memcpy(&pReal->source.pd_set, &old_pd_set, + sizeof(pReal->source.pd_set)); + return MB_EX_SLAVE_BUSY; +} + +/***************************************************************** +函数名称: chrg_mb_write_sys +函数描述: 处理系统区角色切换和影子参数生效 +输入参数: ch:通道 reg_offset:系统寄存器偏移 sou_value:写入值 + pSW:影子参数 pNOR:北向总控 pSOU:南向总控 +输出参数: 更新角色或将参数分发给对应角色处理函数 +返回说明: MB_EX_NONE:成功 MB_EX_SLAVE_BUSY:命令提交失败 +其它说明: 同角色Source/Sink逻辑分别封装,角色切换仍统一走SWITCH状态 +*****************************************************************/ +static eMBException chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, + rt_uint16_t sou_value, struct chrg_switch_t *pSW, + struct chrg_north_t *pNOR, struct chrg_south_t *pSOU) +{ + eMBException status; + struct chrg_switch_t *pReal; + struct chrg_thread_t *pTHR_NOR; + eIDX_ID old_id; + eIDX_ID new_id; + int switch_ret; + + (void)pSOU; + switch (reg_offset) { + case SYS_REG_ROLE_SWITCH: + if (sou_value == 0x01) { + pSW->id = ID_SINK; + } else if (sou_value == 0x02) { + pSW->id = ID_SOURCE; + } + break; + + case SYS_REG_PARAM_EFFECT: + if (sou_value != 0x01) { + break; + } + pReal = &pNOR->sw[ch]; + pTHR_NOR = &chrgthr[IDX_THR_ROLL_NOR]; + rt_mutex_take(pTHR_NOR->mutex, RT_WAITING_FOREVER); + old_id = pReal->id; + new_id = pSW->id; + g_shadow_dirty[ch] = 0; + /* + * 使每次COM参数生效拥有独立版本。北向线程等待回码期间如果版本变化, + * 必须放弃旧请求,防止旧回码把新配置的sub错误推进到下一步。 + */ + pNOR->config_version[ch]++; + + if (old_id != new_id) { + /* 角色切换时清除旧Source协议切换的等待上下文。 */ + chrg_roll_nor_source_switch_reset(ch); + rt_memcpy(pReal, pSW, sizeof(struct chrg_switch_t)); + if (new_id == ID_SOURCE) { + /* 角色重新切到Source后,首个协议命令不需要复位。 */ + last_pd_pro[ch] = SOURCE_PD_STATE_UNKNOWN; + pReal->source.work_mode = SOURCE_WORK_SWITCH; + switch_ret = chrg_source_work_state(ch, pReal); + } else { + pReal->sink.work_mode = SINK_WORK_SWITCH; + switch_ret = chrg_sink_work((eIDX_SOU_CH)ch, pReal); + } + status = (switch_ret == 0) ? MB_EX_NONE : MB_EX_SLAVE_BUSY; + } else if (new_id == ID_SINK) { + status = chrg_comm_apply_sink_effect(ch, pReal, pSW); + } else { + status = chrg_comm_apply_source_effect(ch, pReal, pSW); + } + + if (status != MB_EX_NONE) { + g_shadow_dirty[ch] = 1; + } + rt_mutex_release(pTHR_NOR->mutex); + return status; + + default: + rt_kprintf("[SYS CH%d] Unknown reg offset=0x%02X\n", ch + 1, reg_offset); + break; + } + return MB_EX_NONE; +} +/***************************************************************** +函数名称: chrg_sink_diff_send_reg +函数描述: 根据统一差异结果下发普通负载的CV、电压或电流变化 +输入参数: ch:南向通道 pDiff:统一参数差异 pReal:已生效运行参数 +输出参数: - +返回说明: 0:无差异或提交成功 <0:南向批量队列提交失败 +其它说明: 协议、组号和CC变化由Sink生效函数重置北向sub,本函数只处理 + 不需要重配协议的功率参数 +*****************************************************************/ +static int chrg_sink_diff_send_reg(eIDX_SOU_CH ch, + const chrg_param_diff_t *pDiff, struct chrg_switch_t *pReal) +{ + rt_uint16_t regs[2]; + rt_uint16_t vals[2]; + rt_uint8_t count = 0; + + if (pReal->CV_mode == 0x01) { + if (pDiff->voltage_changed) { + regs[count] = REG_VOLTAGE_OUT + 1; + vals[count++] = pReal->sink.loadv; + } + } else if (pDiff->current_changed) { + regs[count] = REG_CURRENT_OUT + 1; + vals[count++] = pReal->sink.loadc; + } + if (pDiff->cv_changed) { + regs[count] = REG_MODE; + vals[count++] = pReal->CV_mode; + } + + if ((count > 0) && (chrg_sou_com_batch_submit(ch, regs, vals, count) != 0)) { + LOG_E("CH%d sink diff batch submit failed", ch + 1); + return -1; + } + return 0; } diff --git a/apps/chrg/applications/thread/chrg_comm.h b/apps/chrg/applications/thread/chrg_comm.h index 0754c9a..9f294a6 100644 --- a/apps/chrg/applications/thread/chrg_comm.h +++ b/apps/chrg/applications/thread/chrg_comm.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_comm.h 完成日期 : -当前版本号 : V1.0 +当前版本号 : V1.1.9 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -19,23 +19,8 @@ #define MB_HANDLERS_MAX (16) -typedef struct -{ - //北向协议设置 - rt_uint8_t proto_changed; - rt_uint8_t group_changed; - rt_uint16_t pro_volt_changed; - rt_uint16_t pro_curr_changed; - //南向功率设置 - rt_uint8_t volt_changed; - rt_uint8_t curr_changed; - rt_uint8_t ccset_changed; - rt_uint8_t cvmode_changed; -} sink_proto_diff_t; -static void chrg_sink_get_proto_diff(struct chrg_switch_t *pReal, - struct chrg_switch_t *pShadow, - sink_proto_diff_t *pDiff, - rt_uint8_t *pNoChange); +/* 北向Source协议设置成功后,通知COM保存本通道的PD/非PD类别。 */ +extern void chrg_comm_source_protocol_ack(rt_uint8_t ch, rt_uint8_t protocol); typedef enum { MB_REG_READ, /*!< Read register values and pass to protocol stack. */ @@ -228,8 +213,6 @@ extern void chrg_comm_thread_entry (void *data); extern rt_uint8_t chrg_com_sink_on(rt_uint8_t ch, struct chrg_switch_t *pSW, rt_uint8_t *enable); extern rt_uint8_t chrg_com_source_on(rt_uint8_t ch, rt_uint8_t protoCmd, struct chrg_switch_t *pSW, rt_uint8_t *enable); -extern void chrg_sink_diff_send_reg(eIDX_SOU_CH ch, sink_proto_diff_t *pDiff, struct chrg_switch_t *pReal); - #endif diff --git a/apps/chrg/applications/thread/chrg_lcd.c b/apps/chrg/applications/thread/chrg_lcd.c index ba22ca7..9439c26 100644 --- a/apps/chrg/applications/thread/chrg_lcd.c +++ b/apps/chrg/applications/thread/chrg_lcd.c @@ -37,26 +37,6 @@ struct chrg_lcd_t chrglcd = { // 55 41 00 0D 0A 停止 // 55 41 01 0D 0A 运行 -void chrg_sou_work(eIDX_SOU_CH ch,struct chrg_switch_t *pSW) -{ - struct chrg_south_t *pSOU = &chrgsouth; - if(pSW->id == ID_SOURCE){ - chrg_set_sou_reg(ch, REG_WORK, WORK_START); -// rt_thread_mdelay(50); - pSW->source.Set_CV_Flag = 1; - pSW->source.Set_Pro_Flag = 1; - pSW->source.On_Flag = 1; - pSW->sub = IDX_SET_SOURCE_PD; - } - else if(pSW->id == ID_SINK) { - pSW->sink.On_work = 1; - pSW->sub=IDX_SET_SINK_PD; - } - pSOU->online[ch] = 1; - chrg_nor_sw_rel((eIDX_NOR_CH)ch, pSW->id); - rt_thread_mdelay(50); -} - void chrg_sou_stop(eIDX_SOU_CH ch,struct chrg_switch_t *pSW,struct chrg_north_t *pNOR) { struct chrg_south_t *pSOU = &chrgsouth; @@ -64,7 +44,6 @@ void chrg_sou_stop(eIDX_SOU_CH ch,struct chrg_switch_t *pSW,struct chrg_north_t return; } if(pSW->id == ID_SOURCE){ - pSW->source.Stop_step_flag = 0; pSW->source.On_Flag = 0; pSW->sub = IDX_GET_SOURCE_VC; } @@ -82,7 +61,6 @@ int chrg_pro_set(struct chrg_switch_t *pSW, rt_uint8_t protocol){ if (pSW->id == ID_SOURCE) { // Source端 pSW->source.protocol = protocol; - pSW->source.LCD_Set_Pro_Flag = 1; } else if (pSW->id == ID_SINK) { // Sink端 pSW->sink.protocol = protocol; @@ -211,7 +189,6 @@ static int chrg_lcd_parse (rt_uint8_t *recv, rt_uint16_t len) } pNOR->enable[ch] = 1; rt_kprintf("ch=%d\n",ch); - chrg_sou_work((eIDX_SOU_CH)ch,pSW); pSW->continue_flag = 0; } //关闭 @@ -235,14 +212,16 @@ static int chrg_lcd_parse (rt_uint8_t *recv, rt_uint16_t len) Recv_Data = recv[LCD_IDX_VAL+1]<<8 | recv[LCD_IDX_VAL]; // 拼接16位电压值 pSW = &pNOR->sw[ch]; // 按角色存入source/sink - pSW->source.vc.set_voltage = Recv_Data; // 电源侧电压(mV) + pSW->source.pd_set.mm.max_vol = Recv_Data; // 电源协议目标电压(mV) + pSW->source.set_volt = Recv_Data; // 当前准备写入功率板的电压目标 pSW->sink.loadv = Recv_Data; // 负载侧电压(mV,) - pSW->source.Set_Pro_Flag = 1; - // 写入电压寄存器 - if(pSW->id==ID_SINK) { - chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_VOLTAGE_OUT+1, Recv_Data); - } - else chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_VOLTAGE_OUT+1, 5000); + /* Sink保留原直接设置方式;Source电压必须统一经过RUNING状态补偿后下发。 */ + if (pSW->id == ID_SINK) { + chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_VOLTAGE_OUT + 1, Recv_Data); + } else if (pSW->source.On_Flag == 1) { + pSW->source.work_mode = SOURCE_WORK_RUNING; + (void)chrg_source_work_state(ch, pSW); + } break; } @@ -255,9 +234,9 @@ static int chrg_lcd_parse (rt_uint8_t *recv, rt_uint16_t len) Recv_Data = recv[LCD_IDX_VAL+1]<<8 | recv[LCD_IDX_VAL]; // 拼接16位电流值 pSW = &pNOR->sw[ch]; //按角色存入source/sink - pSW->source.vc.set_current = Recv_Data; // 电源侧电流(mA) + pSW->source.pd_set.mm.max_cur = Recv_Data; // 电源协议最大电流(mA) + pSW->source.set_curr = Recv_Data; // 当前准备写入功率板的电流目标 pSW->sink.loadc = Recv_Data; // 负载侧电流(mA) - pSW->source.Set_Pro_Flag = 1; // rt_kprintf("cur=%d",Recv_Data); // 写入电流寄存器 for(int i = 0;i<10;i++){ @@ -390,7 +369,9 @@ void chrg_lcd_thread_entry (void *data) return ; } +#if LCD_RX_ENABLE int len = 0; +#endif struct chrg_tty_t *pTTY = RT_NULL; struct chrg_lcd_t *pLCD = &chrglcd; pTTY = chrg_tty_create(pLCD->devname, 921600, 0, -1, 1); @@ -398,8 +379,10 @@ void chrg_lcd_thread_entry (void *data) LOG_E("tty can't create."); return ; } - +#if LCD_RX_ENABLE + /* LCD接收功能开启时,最多等待100ms屏幕回传数据。 */ chrg_tty_set_recv_tmo(pTTY, 100); +#endif if (chrg_tty_connect(pTTY) != RT_EOK) { chrg_tty_destory(pTTY); return; @@ -407,6 +390,12 @@ void chrg_lcd_thread_entry (void *data) pLCD->tty = pTTY; +#if LCD_RX_ENABLE + /* + * 保留原LCD接收及控制解析流程,需要恢复屏幕控制时只需将 + * LCD_RX_ENABLE改为1。接收关闭时本段不参与编译,避免chrg_tty_recv + * 在等待回传期间持有TTY锁并阻塞chrg_lcd_send。 + */ while (1) { rt_memset(pLCD->rx_buf, 0, SIZE_BUF_TTY); len = chrg_tty_recv(pTTY, pLCD->rx_buf, sizeof(pLCD->rx_buf)); @@ -420,6 +409,15 @@ void chrg_lcd_thread_entry (void *data) chrg_lcd_parse(pLCD->rx_buf, len); } } +#else + /* + * 仅发送模式仍保留LCD线程和串口实例。线程低频休眠且不访问TTY, + * 量测解析中的LCD发送可以直接取得TTY锁,不再受100ms接收超时影响。 + */ + while (1) { + rt_thread_mdelay(1000); + } +#endif } diff --git a/apps/chrg/applications/thread/chrg_lcd.h b/apps/chrg/applications/thread/chrg_lcd.h index b229c12..5a76824 100644 --- a/apps/chrg/applications/thread/chrg_lcd.h +++ b/apps/chrg/applications/thread/chrg_lcd.h @@ -18,6 +18,9 @@ #define THR_NAME_LCD "thr.lcd" #define DEV_NAME_LCD "uart2" +/* LCD发送保持启用;当前产品不使用屏幕回传控制,关闭接收可避免接收线程持有TTY锁阻塞量测上报。 */ +#define LCD_RX_ENABLE 0 + #define LCD_FRAME_HEAD 0x55 @@ -45,7 +48,6 @@ extern void chrg_lcd_thread_entry (void *data); extern rt_uint8_t On_Flag; extern rt_uint8_t Flag_start; -extern void chrg_sou_work(eIDX_SOU_CH ch,struct chrg_switch_t *pSW); extern void chrg_sou_stop(eIDX_SOU_CH ch,struct chrg_switch_t *pSW,struct chrg_north_t *pNOR); extern void chrg_set_eload_sink(eIDX_SOU_CH ch,struct chrg_switch_t *pSW); extern void chrg_set_eload_source(eIDX_SOU_CH ch,struct chrg_switch_t *pSW); diff --git a/apps/chrg/applications/thread/chrg_north.c b/apps/chrg/applications/thread/chrg_north.c index 39bdd49..d520023 100644 --- a/apps/chrg/applications/thread/chrg_north.c +++ b/apps/chrg/applications/thread/chrg_north.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_north.c 完成日期 : -当前版本号 : V1.0 +当前版本号 : V1.1.9 主要功能 : 实现北向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -23,249 +23,320 @@ //#define LOG_TAG "chrg.nor" #define DBG_LEVEL DBG_LOG #include -rt_uint16_t volt; -uint16_t chrg_compare(rt_int16_t a, rt_int16_t b) + +#define SOURCE_VOLT_ERROR_MV 200U // Source目标电压允许误差,单位mV +#define SOURCE_VOLT_RETRY_FRAMES 3U // Source电压持续超差后的重发确认帧数 +#define SOURCE_QC_MIN_VOLT_MV 5000U // QC2.0/QC3.0允许使用的最低目标电压 +#define SOURCE_QC_SWITCH_ERROR 0x04U // Source协议或CC切换失败错误位 +#define SINK_CURRENT_START_GRACE_MS 200U // Sink启动后暂不判断无电流的保护时间 +#define SINK_VOLT_STABLE_ERROR_MV 2000U // Sink实际电压与协议目标电压的最大允许误差 +#define SINK_VOLT_5V_TARGET_MV 5000U // VSafe5V目标电压,需延长稳定确认时间 +#define SINK_VOLT_CONFIRM_NORMAL_COUNT 5U // 非5V目标连续有效电压确认帧数 +#define SINK_VOLT_CONFIRM_5V_COUNT 15U // 5V目标连续有效电压确认帧数,避免把握手阶段VSafe5V误判为协议稳定 + +/***************************************************************** +函数名称: chrg_source_apply_protocol_voltage_rule +函数描述: 根据电源协议类型修正北向解析得到的目标电压 +输入参数: pSW:当前通道控制结构体 volt_mv:北向解析电压(mV) +输出参数: - +返回说明: 应写入Source状态机的目标电压(mV) +其它说明: QC2.0/QC3.0在完成升压协商前必须保持默认5V,因此其北向 + 回码低于5000mV时统一按5000mV处理;其它协议保持原值 +*****************************************************************/ +static rt_uint16_t chrg_source_apply_protocol_voltage_rule( + const struct chrg_switch_t *pSW, rt_uint16_t volt_mv) { - if(a>b) return a - b; - else return b - a; + if (pSW == RT_NULL) { + return volt_mv; + } + + if (((pSW->source.protocol == PRO_QC20) || + (pSW->source.protocol == PRO_QC30)) && + (volt_mv < SOURCE_QC_MIN_VOLT_MV)) { + return SOURCE_QC_MIN_VOLT_MV; + } + + return volt_mv; } /***************************************************************** -函数名称: chrg_source_stop -功能描述: 南向功率源停止流程控制函数(分步执行停止操作) -函数描述: 通过状态机分步执行南向功率停止流程:停止功率输出 -> 关闭北向继电器 -> 清空控制标志并禁用使能 -输入参数: - frame_data :原始数据帧指针,用于提取当前电压值 - pSW :北向通道控制结构体指针 - idx :通道编号(功率通道/北向通道索引) - enable :北向通道使能,停止后关掉使能 -返回说明: 无返回值 +函数名称: chrg_sink_voltage_is_stable +函数描述: 判断北向返回的Sink实际电压是否已进入协议目标电压允许范围 +输入参数: actual_volt_mv:北向返回的实际电压(mV) + target_volt_mv:当前协议申请的目标电压(mV) +输出参数: - +返回说明: RT_TRUE:实际电压与目标电压差值不大于允许误差 + RT_FALSE:实际电压尚未到达目标范围或仍保留旧协议电压 +其它说明: 使用差值的绝对值比较,避免只判断下限时将20V残压误判为5V已稳定 *****************************************************************/ -void chrg_source_stop(rt_uint8_t *frame_data,struct chrg_switch_t *pSW,rt_uint8_t idx,rt_uint8_t *enable){ - switch(pSW->source.Stop_step_flag){ - case 0: // 停止工作 - chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_WORK, WORK_STOP); - rt_thread_mdelay(50); - rt_uint16_t volt_mv_Now = (frame_data[4] << 8) | frame_data[5]; - volt = volt_mv_Now; - if(volt_mv_Now < 1000){ - pSW->source.Stop_step_flag = 1; - } - break; - case 1: // 关闭北向继电器 - chrg_nor_sw_rel((eIDX_NOR_CH)idx,0); - rt_thread_mdelay(50); - pSW->source.Stop_step_flag = 2; - break; - case 2: // 状态2:已执行完停止操作,清空标志位 - pSW->source.Set_CV_Flag = 0; - *enable = 0; - break; - default: - break; +static rt_bool_t chrg_sink_voltage_is_stable(rt_uint16_t actual_volt_mv, + rt_uint16_t target_volt_mv) +{ + rt_uint16_t voltage_error_mv; + + if (actual_volt_mv >= target_volt_mv) { + voltage_error_mv = actual_volt_mv - target_volt_mv; + } else { + voltage_error_mv = target_volt_mv - actual_volt_mv; } + + return (voltage_error_mv <= SINK_VOLT_STABLE_ERROR_MV) ? + RT_TRUE : RT_FALSE; } /***************************************************************** -函数名称: chrg_source_standard_output -功能描述: 南向功率源标准输出控制函数 -函数描述: 根据CV标志位,分别设置电源输出电压或输出电流,实现标准模式下的恒压/恒流输出 -输入参数: - idx :功率通道编号 - pSW :北向通道控制结构体指针,包含电压/电流设定值、CV控制标志等参数 -输出参数: - pSW :更新CV控制标志位 -返回说明: 无返回值 +函数名称: chrg_sink_voltage_confirm_count +函数描述: 根据Sink协议目标电压选择进入RUNING前所需的连续稳定帧数 +输入参数: target_volt_mv:当前协议申请的目标电压,单位mV +输出参数: - +返回说明: 5V目标返回延长确认帧数,其它目标返回普通确认帧数 +其它说明: 快充协议切换时总线会先经过VSafe5V;当目标本身也是5V时, + 单凭电压值无法区分过渡阶段和协议稳定阶段,因此增加确认时间, + 防止电子负载提前启动后导致协议板复位 *****************************************************************/ -void chrg_source_standard_output(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +static rt_uint8_t chrg_sink_voltage_confirm_count(rt_uint16_t target_volt_mv) { - if(pSW->source.Set_CV_Flag) { - chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_VOLTAGE_OUT+1, pSW->source.max_vol); -#if DEBUG_NORTH - rt_kprintf("Volt_Set%d\r\n",pSW->source.vc.set_voltage); -#endif - pSW->source.Set_CV_Flag = 1; - pSW->change_flag = 0; - } -// if(pSW->source.Set_CV_Flag==0){ -// chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_CURRENT_OUT+1, pSW->source.max_vol); -//#if DEBUG_NORTH -// rt_kprintf("Curr_set%d\r\n",Curr_mA); -//#endif -// pSW->source.Set_CV_Flag = 0; -// } + return (target_volt_mv == SINK_VOLT_5V_TARGET_MV) ? + SINK_VOLT_CONFIRM_5V_COUNT : SINK_VOLT_CONFIRM_NORMAL_COUNT; } -//电源快充 -void chrg_source_fast_output(rt_uint8_t *frame_data, eIDX_SOU_CH idx, - struct chrg_switch_t *pSW, struct chrg_north_t *pNOR) +/***************************************************************** +函数名称: chrg_source_voltage_control +函数描述: 根据目标电压与实际电压统一控制Source电压下发和重试 +输入参数: idx:当前通道 pSW:通道控制结构体 volt_mv:目标电压(mV) + curr_ma:目标电流(mA) +输出参数: 更新source.set_volt/set_curr、work_mode和电压重试计数 +返回说明: 0:状态处理成功 <0:参数错误或命令提交失败 +其它说明: set_volt同时作为上次目标,停止时会清零;目标变化时立即下发, + 目标不变但实际误差连续3帧超过200mV时重发 +*****************************************************************/ +static int chrg_source_voltage_control(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW, rt_uint16_t volt_mv, rt_uint16_t curr_ma) { - struct chrg_rollsou_t *pROLL = &chrgrollsou; - rt_uint16_t volt_mv = (frame_data[8] << 8) | frame_data[9]; - rt_uint16_t Curr_mA = (frame_data[10] << 8) | frame_data[11]; - rt_uint16_t volt_mv_Now = (frame_data[4] << 8) | frame_data[5]; - rt_uint16_t Curr_mA_Now = (frame_data[6] << 8) | frame_data[7]; - rt_uint16_t volt_compare = chrg_compare(volt_mv, volt_mv_Now); - rt_uint16_t Curr_compare = chrg_compare(Curr_mA, Curr_mA_Now); - static rt_uint8_t number = 0; -#if LCD_OPEN - // rt_kprintf("volt_mv=%d,volt_mv_Now=%d,compare=%d\r\n",volt_mv,volt_mv_Now,volt_compare); - if(volt_compare<5000){ //比较设定值与实际值差距,小于2V认为达成设定,允许屏幕开始变换 - number++; - number = number%10; - if(number == 9) - { - pSW->change_flag = 0; - } - }else { //没有达成设定,屏幕不允许变换 - pSW->change_flag = 0x01; + static rt_uint8_t volt_retry_count[TOTAL_SOU_CHS] = {0}; + rt_uint16_t volt_error; + rt_uint16_t old_volt_mv; + rt_uint16_t old_curr_ma; + rt_bool_t need_set = RT_FALSE; + int ret; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; } -#endif -// if(volt_mv_Now==0){ -// chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_WORK, WORK_START); -// } -// else{ - if (pSW->source.Set_CV_Flag == 0){ - { - chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_VOLTAGE_OUT+1, volt_mv); - } - pSW->source.Set_CV_Flag = 1; -#if DEBUG_NORTH - rt_kprintf("Volt_Set%d\r\n",volt_mv); -#endif - } - else if(pSW->source.Set_CV_Flag==1){ - chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_WORK, WORK_START); - pSW->source.Set_CV_Flag = 0; - }else{ - pSW->source.Set_CV_Flag=0; + + old_volt_mv = pSW->source.set_volt; + old_curr_ma = pSW->source.set_curr; + pSW->source.set_volt = volt_mv; + pSW->source.set_curr = curr_ma; + if (!pSW->source.On_Flag) { + return 0; + } + volt_error = (pSW->Now_voltage >= volt_mv) ? + (pSW->Now_voltage - volt_mv) : + (volt_mv - pSW->Now_voltage); + + if (volt_mv != old_volt_mv) { + /* 新目标与set_volt旧值不同,需要立即写入功率板。 */ + need_set = RT_TRUE; + } else if (volt_error <= SOURCE_VOLT_ERROR_MV) { + volt_retry_count[idx] = 0; + } else { + if (volt_retry_count[idx] < SOURCE_VOLT_RETRY_FRAMES) { + volt_retry_count[idx]++; } + if (volt_retry_count[idx] >= SOURCE_VOLT_RETRY_FRAMES) { + need_set = RT_TRUE; + } + } + + pSW->source.work_mode = need_set ? + SOURCE_WORK_RUNING : SOURCE_WORK_WAIT; + ret = chrg_source_work_state(idx, pSW); + if ((ret == 0) && need_set) { + volt_retry_count[idx] = 0; + } else if ((ret != 0) && need_set) { + /* 入队失败时恢复旧目标,使下一帧仍能立即识别为目标变化。 */ + pSW->source.set_volt = old_volt_mv; + pSW->source.set_curr = old_curr_ma; + } + return ret; } -//电源设置 +/***************************************************************** +函数名称: chrg_source_parse +函数描述: 按电源协议类型处理目标电压并驱动Source状态机 +输入参数: frame_data:完整北向回码 idx:当前通道 pSW:通道控制结构体 + pNOR:北向总控结构体 +输出参数: 更新电压、电流、source.work_mode及电压重试计数 +返回说明: 0:回码正常 <0:参数错误、切换错误或功率板命令提交失败 +其它说明: 标准协议使用COM配置目标参与实际电压校验;快充协议读取 + frame_data[8~11]中的协商目标值;frame_data[14]的bit2表示 + Source板协议/CC切换失败,错误回码不得驱动功率板输出 +*****************************************************************/ +int chrg_source_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx, + struct chrg_switch_t *pSW, struct chrg_north_t *pNOR) +{ + rt_uint16_t volt_mv; + rt_uint16_t curr_ma; + + (void)pNOR; + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL) || + (frame_data == RT_NULL)) { + return -1; + } + pSW->source.qc_err = frame_data[14]; + if ((pSW->source.qc_err & SOURCE_QC_SWITCH_ERROR) != 0U) { + return -2; + } + + /* 标准协议不使用北向帧内目标值,但仍进入统一误差判断,确保INIT或 + * COM下发的目标电压没有生效时能够自动重试。 */ + if (pSW->source.protocol == PRO_NONE) { + return chrg_source_voltage_control(idx, pSW, + pSW->source.pd_set.mm.max_vol, + pSW->source.pd_set.mm.max_cur); + } + + if (!pSW->source.On_Flag) { + return 0; + } + volt_mv = (frame_data[8] << 8) | frame_data[9]; + curr_ma = (frame_data[10] << 8) | frame_data[11]; +// /* 特殊协议规则只修正运行目标,不覆盖北向原始回码和COM协议配置。 */ +// volt_mv = chrg_source_apply_protocol_voltage_rule(pSW, volt_mv); + return chrg_source_voltage_control(idx, pSW, volt_mv, curr_ma); +} + +/***************************************************************** +函数名称: chrg_source_setting +函数描述: 根据电源协议类型和启停标志选择标准输出或快充解析流程 +输入参数: frame_data:北向回码 idx:当前通道 pSW:通道控制结构体 + pNOR:北向总控结构体 +输出参数: 可能更新功率板设定值或北向子状态sub +返回说明: - +其它说明: 标准协议直接设置输出,快充协议使用协商结果驱动状态机 +*****************************************************************/ void chrg_source_setting(rt_uint8_t *frame_data, eIDX_SOU_CH idx, struct chrg_switch_t *pSW, struct chrg_north_t *pNOR) { //执行输出调整 - if (pSW->source.On_Flag == 1) { - if(pSW->source.protocol==0x00){//标准下输出设定值 - chrg_source_standard_output(idx, pSW); - } - else{ //快充部分按协商结果输出 - chrg_source_fast_output(frame_data, idx, pSW, pNOR); -// rt_kprintf("set_end\r\n"); - } + if (pSW->source.On_Flag == 1) { + /* 标准协议与快充协议都由解析入口选择各自目标值并统一校验。 */ + (void)chrg_source_parse(frame_data, idx, pSW, pNOR); }else if(pSW->source.On_Flag == 0){ //chrg_source_stop(frame_data,pSW,idx,&pNOR->enable[idx]); pSW->sub = IDX_SET_SOURCE_STOP_STATE; } } -////解析负载返回数据 -//void chrg_sink_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx, -// struct chrg_switch_t *pSW, struct chrg_north_t *pNOR) -//{ -// rt_uint16_t volt_mv = ((frame_data[4] << 8) | frame_data[5])*10; -// static uint8_t volt_low[TOTAL_SOU_CHS] = {0}; -// static uint8_t no_curr[TOTAL_SOU_CHS] = {0}; -// pSW->VZ = ((frame_data[14] << 8) | frame_data[15]); -// pSW->VF = ((frame_data[16] << 8) | frame_data[17]); -//#if DEBUG_NORTH -// LOG_D("Sink[%d] real volt: %d mV", idx, volt_mv); -//#endif -// -// if(pSW->sink.On_work == 1) { -// if(pSW->sink.test.test_mode == 0){ -// if (volt_mv <= WORK_MIN_VOLT) { // 电压过低,禁止进入工作状态机 -// volt_low[idx]++; -// //if(volt_low[idx]>=5) pSW->sink.work_mode = SINK_WORK_LOW; // 复位工作状态机 -// } -// else{ // 电压稳定时执行工作状态机(继电器/工作模式切换) -// volt_low[idx] = 0; -// pSW->change_flag = 0; -// if((pSW->sink.pro_loadv - volt_mv) <= 2000){ -// pSW->sink.work_mode = SINK_WORK_RUNING; -// if(pSW->Now_current<=200){ -// if(no_curr[idx]++>=5){ -// pSW->sink.work_mode = SINK_WORK_WAIT; -// no_curr[idx] = 0; -// } -// } -// else no_curr[idx] = 0; -// } -// } -// } -// } -// else{ -// volt_low[idx] = 0; -//// pSW->sink.work_mode = SINK_WORK_STOP; -// } -// chrg_sink_work(idx,pSW); -//} - -//解析负载返回数据 +/***************************************************************** +函数名称: chrg_sink_parse +函数描述: 解析北向负载板回码并根据电压、电流反馈驱动Sink状态机 +输入参数: frame_data:完整北向回码 idx:当前通道 pSW:通道控制结构体 + pNOR:北向总控结构体 +输出参数: 更新VZ、VF、低压计数、稳定电压计数、无电流计数及sink.work_mode +返回说明: - +其它说明: 电压连续低于门槛进入LOW;掉电处理恢复副继电器后,电压重新 + 出现时先重设协议和CC;一帧有效电压达到目标附近即进入RUNING +*****************************************************************/ void chrg_sink_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx, struct chrg_switch_t *pSW, struct chrg_north_t *pNOR) { - rt_uint16_t volt_mv = ((frame_data[4] << 8) | frame_data[5])*10; - static uint8_t volt_low[TOTAL_SOU_CHS] = {0}; - static uint8_t no_curr[TOTAL_SOU_CHS] = {0}; - pSW->VZ = ((frame_data[14] << 8) | frame_data[15]); - pSW->VF = ((frame_data[16] << 8) | frame_data[17]); + rt_uint16_t volt_mv = ((frame_data[4] << 8) | frame_data[5]) * 10; + rt_uint8_t stable_confirm_count; + static rt_uint8_t volt_low[TOTAL_SOU_CHS] = {0}; + static rt_uint8_t no_curr[TOTAL_SOU_CHS] = {0}; + static rt_uint8_t volt_set_ok[TOTAL_SOU_CHS] = {0}; + static rt_uint8_t volt_stable_count[TOTAL_SOU_CHS] = {0}; + static rt_tick_t running_start_tick[TOTAL_SOU_CHS] = {0}; -#if DEBUG_NORTH - LOG_D("Sink[%d] real volt: %d mV", idx, volt_mv); + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return; + } + stable_confirm_count = chrg_sink_voltage_confirm_count( + pSW->sink.pro_loadv); + + pSW->VZ = ((frame_data[14] << 8) | frame_data[15]); + pSW->VF = ((frame_data[16] << 8) | frame_data[17]); + +#if NORTH_SINK_DEBUG + LOG_D("Sink[%d] real volt: %d mV", idx, volt_mv); #endif if(pSW->sink.On_work == 1 && pSW->sink.test.test_mode == 0) { if (volt_mv <= WORK_MIN_VOLT) { // 电压过低,禁止进入工作状态机 -// volt_low[idx]++; -// if(volt_low[idx]>=5) pSW->sink.work_on_step = SINK_WORK_LOW; // 复位工作状态机 + volt_stable_count[idx] = 0; + if (volt_set_ok[idx] == 1) { + if (++volt_low[idx] >= 50) { + volt_set_ok[idx] = 0; + no_curr[idx] = 0; + //pSW->sink.work_mode = SINK_WORK_LOW; + } + } } else{ // 电压稳定时执行工作状态机(继电器/工作模式切换) volt_low[idx] = 0; + volt_set_ok[idx] = 1; pSW->change_flag = 0; - if((pSW->sink.pro_loadv - volt_mv) <= 2000){ - pSW->sink.work_mode = SINK_WORK_RUNING; - if(pSW->Now_current<=200){ - if(no_curr[idx]++>=5){ - pSW->sink.work_mode = SINK_WORK_WAIT; + if (pSW->sink.work_mode == SINK_WORK_LOW_MONITOR) { + /* 副继电器恢复后首次重新检测到输入电压,只启动协议重握手。 + * 当前回码不计入稳定电压次数,防止跳过协议设置直接带载。 */ + volt_stable_count[idx] = 0; + no_curr[idx] = 0; + //pSW->sub = IDX_SET_SINK_CC_BEFORE_PD; + pSW->sink.work_mode = SINK_WORK_WAIT; + } else if ((pSW->sink.work_mode == SINK_WORK_LOW) || + (pSW->sink.work_mode == SINK_WORK_LOW_AUX_WAIT)) { + /* 掉电安全批次完成前,即使收到残留回码也不得进入RUNING。 */ + volt_stable_count[idx] = 0; + no_curr[idx] = 0; + } + else if (chrg_sink_voltage_is_stable(volt_mv, + pSW->sink.pro_loadv)) { + if ((pSW->sink.work_mode != SINK_WORK_RUNING) && + (pSW->sink.work_mode != SINK_WORK_LOAD_STANDBY)) { + if (volt_stable_count[idx] < stable_confirm_count) { + volt_stable_count[idx]++; + } + if (volt_stable_count[idx] >= stable_confirm_count) { + /* 回码已经过CRC、ID、命令和配置版本校验,并连续达到当前 + * 目标对应的确认帧数;5V采用更长窗口过滤VSafe5V过渡阶段。 */ + volt_stable_count[idx] = 0; + pSW->sink.work_mode = SINK_WORK_RUNING; + running_start_tick[idx] = rt_tick_get(); no_curr[idx] = 0; - } + } + } else if (pSW->sink.work_mode == SINK_WORK_LOAD_STANDBY) { + /* 协议保持在线但电子负载主动停止,不执行无电流重试。 */ + volt_stable_count[idx] = 0; + no_curr[idx] = 0; + } else if ((pSW->Now_current > 200) || + (pSW->sink.loadc < 200)) { + volt_stable_count[idx] = 0; + no_curr[idx] = 0; + } else if ((rt_tick_t)(rt_tick_get() - running_start_tick[idx]) >= + rt_tick_from_millisecond(SINK_CURRENT_START_GRACE_MS)) { + volt_stable_count[idx] = 0; + if (++no_curr[idx] >= 2) { + pSW->sink.work_mode = SINK_WORK_WAIT; + no_curr[idx] = 0; + } } - else no_curr[idx] = 0; + } else { + volt_stable_count[idx] = 0; + no_curr[idx] = 0; } } } else{ volt_low[idx] = 0; -// pSW->sink.work_on_step = SINK_WORK_STOP; // 复位工作状态机 + no_curr[idx] = 0; + volt_set_ok[idx] = 0; + volt_stable_count[idx] = 0; } chrg_sink_work(idx,pSW); } -/***************************************************************** -函数名称: chrg_north_handle_Seting_Volt -功能描述: 处理设置电压/电流命令,解析命令数据并执行相应操作 -****************************************************/ -rt_uint8_t change_number = 0; -static void chrg_north_handle_Seting_Volt(rt_uint8_t *frame_data, rt_uint16_t frame_len) -{ - // 提取电压/电流值 - struct chrg_north_t *pNOR = &chrgnorth; - rt_uint16_t idx = pNOR->idx; - struct chrg_switch_t *pSW = &chrgnorth.sw[idx]; - - static uint8_t number = 0; - //电源部分 - if(pSW->id==ID_SOURCE){ - chrg_source_setting(frame_data,(eIDX_SOU_CH)idx, pSW, pNOR); - } - else{ - chrg_sink_parse(frame_data,(eIDX_SOU_CH)idx, pSW, pNOR); - } -} - struct chrg_north_t chrgnorth = { .devname = DEV_NAME_NORTH, // 设备名 .tty = RT_NULL, // 串口句柄初始为空 @@ -276,11 +347,11 @@ struct chrg_north_t chrgnorth = { .CV_mode = 2, .change_flag = 0, .show_step = 0, + .sub = IDX_GET_SINK_VC, .id = ID_SINK, // 初始为负载模式 .source = { // 电源侧参数初始化 .On_Flag = 0, // 启动标志初始关闭 - .Set_CV_Flag = 1, // 启停控制初始置1 - .Set_Pro_Flag = 1 // 电压/电流切换初始置1 + .work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT }, // 关键:补充逗号分隔source和sink .sink = { .protocol = 0xFE, @@ -297,8 +368,7 @@ struct chrg_north_t chrgnorth = { .change_flag = 0, .source = { .On_Flag = 0, - .Set_CV_Flag = 1, - .Set_Pro_Flag = 1 + .work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT }, .sink = { .protocol = 0xFE, @@ -314,8 +384,7 @@ struct chrg_north_t chrgnorth = { .change_flag = 0, .source = { .On_Flag = 0, - .Set_CV_Flag = 1, - .Set_Pro_Flag = 1 + .work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT }, .sink = { .protocol = 0xFE, @@ -331,8 +400,7 @@ struct chrg_north_t chrgnorth = { .change_flag = 0, .source = { .On_Flag = 0, - .Set_CV_Flag = 1, - .Set_Pro_Flag = 1 + .work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT }, .sink = { .protocol = 0xFE, @@ -373,8 +441,8 @@ int chrg_north_send (rt_uint8_t *data, rt_size_t length) struct chrg_tty_t *pTTY = chrgnorth.tty; if (RT_NULL != pTTY) { -#if DEBUG_NORTH //是否打印 -// LOG_HEX("sendN", 32, data, length); +#if NORTH_FRAME_DEBUG + LOG_HEX("sendN", 32, data, length); #endif ret = chrg_tty_send(pTTY, data, length); } @@ -383,98 +451,164 @@ int chrg_north_send (rt_uint8_t *data, rt_size_t length) } /***************************************************************** -函数名称: chrg_north_frame_head -函数描述: 北向通道数据帧头提取 -输入参数: *rb: 缓冲区 *head: 帧头 id:编号 +函数名称: chrg_north_frame_shape_valid +函数描述: 检查北向回码命令和长度组合是否符合协议定义 +输入参数: id:角色 length:帧长度 command:回码命令 输出参数: - -返回说明: <0: 错误 >0:帧长度 -其它说明: 提取3字节帧头 +返回说明: RT_TRUE:组合合法 RT_FALSE:组合非法 +其它说明: 0x20量测回码固定21字节,设置命令回码固定5字节 *****************************************************************/ -static int chrg_north_frame_head (struct rt_ringbuffer *rb, rt_uint8_t *head, eIDX_ID id) +static rt_bool_t chrg_north_frame_shape_valid(eIDX_ID id, + rt_uint8_t length, rt_uint8_t command) { - int i = 0; - rt_uint8_t data = 0, stage = 0; - rt_size_t ret = 0; - rt_size_t len_rb = 0; // ring数据长度 - rt_uint8_t len_frame = 0; // 帧长度 + if (command == SRC_CMD_GET_VOLTAGE) { + return (length == 21U) ? RT_TRUE : RT_FALSE; + } + if (command == SRC_CMD_GET_PROTOCOL) { + return (length >= MIN_FRAME_LENGTH) ? RT_TRUE : RT_FALSE; + } - if (RT_NULL == rb) { - rt_kprintf("len_rb=NULL%d\r\n",len_rb); + if (id == ID_SOURCE) { + if ((command == SRC_CMD_SET_CC_LEVEL) || + (command == SRC_CMD_SET_CC_CHOOSE) || + (command == SRC_CMD_RESET) || + (command == SRC_CMD_SET_PROTOCOL)) { + return (length == MIN_FRAME_LENGTH) ? RT_TRUE : RT_FALSE; + } + return RT_FALSE; + } + + if (id == ID_SINK) { + switch (command) { + case SINK_CMD_SET_CC: + case SINK_CMD_SET_LEVEL: + case SINK_CMD_SET_QC20: + case SINK_CMD_SET_QC30: + case SINK_CMD_SET_FCP: + case SINK_CMD_SET_AFC: + case SINK_CMD_SET_SCP: + case SINK_CMD_SET_VIVO: + case SINK_CMD_SET_TFC: + case SINK_CMD_SET_PD0: + case SINK_CMD_SET_PPS: + case SINK_CMD_SET_PD3P1: + case SINK_CMD_SET_UFCS: + case SINK_CMD_SET_VOOC: + case SINK_CMD_SET_AVS: + case LOAD_PRO_NONE: + return (length == MIN_FRAME_LENGTH) ? RT_TRUE : RT_FALSE; + default: + return RT_FALSE; + } + } + + return RT_FALSE; +} + +/***************************************************************** +函数名称: chrg_north_ringbuffer_byte +函数描述: 不移动读指针地读取北向环形缓冲区指定偏移字节 +输入参数: rb:环形缓冲区 offset:相对当前读指针偏移 data:输出字节 +输出参数: data返回读取到的字节 +返回说明: 0:成功 <0:参数或偏移错误 +其它说明: 仅由北向接收线程调用,不与其它线程并发访问该环形缓冲区 +*****************************************************************/ +static int chrg_north_ringbuffer_byte(const struct rt_ringbuffer *rb, + rt_size_t offset, rt_uint8_t *data) +{ + rt_size_t index; + + if ((rb == RT_NULL) || (data == RT_NULL) || + (offset >= rt_ringbuffer_data_len((struct rt_ringbuffer *)rb))) { + return -1; + } + + index = rb->read_index + offset; + if (index >= rb->buffer_size) { + index -= rb->buffer_size; + } + *data = rb->buffer_ptr[index]; + return 0; +} + +/***************************************************************** +函数名称: chrg_north_ringbuffer_discard +函数描述: 从北向环形缓冲区丢弃指定数量字节 +输入参数: rb:环形缓冲区 length:需要丢弃的字节数 +输出参数: - +返回说明: - +其它说明: 用于移除帧头前噪声,同时保留末尾可能未接收完整的帧头 +*****************************************************************/ +static void chrg_north_ringbuffer_discard(struct rt_ringbuffer *rb, + rt_size_t length) +{ + rt_uint8_t data; + + while ((length > 0U) && (rt_ringbuffer_getchar(rb, &data) != 0U)) { + length--; + } +} + +/***************************************************************** +函数名称: chrg_north_frame_head +函数描述: 在北向字节流中查找并提取合法四字节帧头 +输入参数: rb:环形缓冲区 head:帧头输出区 id:当前通道角色 +输出参数: head返回长度、固定字节、角色和命令 +返回说明: >0:完整帧长度 0:暂未找到完整帧头 <0:参数或读取错误 +其它说明: 未找到时保留末尾3字节,支持帧头跨多次UART接收 +*****************************************************************/ +static int chrg_north_frame_head(struct rt_ringbuffer *rb, + rt_uint8_t *head, eIDX_ID id) +{ + rt_size_t i; + rt_size_t j; + rt_size_t len_rb; + rt_uint8_t length; + rt_uint8_t reserved; + rt_uint8_t frame_id; + rt_uint8_t command; + + if ((rb == RT_NULL) || (head == RT_NULL)) { return -1; } len_rb = rt_ringbuffer_data_len(rb); - - - if (len_rb < LEN_FRAME_HEAD + 2) { // 最短5字节 - // ============================= - // 简单版:打印错误帧 + 清空缓冲区 - // ============================= - rt_kprintf("[ERROR] 帧长度不足,len=%d, data: ", len_rb); - - // 把所有数据读出来、打印、同时清空 - rt_uint8_t dummy; - while (rt_ringbuffer_getchar(rb, &dummy) == RT_EOK) - { - rt_kprintf("%02x ", dummy); // 打印每一个字节 - } - - rt_kprintf("\r\n"); - - return -1; + if (len_rb < LEN_FRAME_HEAD) { + return 0; } - for (i = 0; i < len_rb - 2; i++) { // 找头三个字节 [帧长, 0x00, id] - ret = rt_ringbuffer_getchar(rb, &data); - if (0 == ret) { + + /* 使用非破坏读取逐字节滑动,查找完整的4字节候选帧头。 */ + for (i = 0; (i + LEN_FRAME_HEAD) <= len_rb; i++) { + if ((chrg_north_ringbuffer_byte(rb, i, &length) != 0) || + (chrg_north_ringbuffer_byte(rb, i + 1U, &reserved) != 0) || + (chrg_north_ringbuffer_byte(rb, i + 2U, &frame_id) != 0) || + (chrg_north_ringbuffer_byte(rb, i + 3U, &command) != 0)) { + break; + } + + /* 长度、固定字节、角色或命令不匹配时继续搜索下一个候选位置。 */ + if ((reserved != 0x00U) || (frame_id != (rt_uint8_t)id) || + (length < MIN_FRAME_LENGTH) || + !chrg_north_frame_shape_valid(id, length, command)) { continue; } - switch (stage) { - case 0: - head[0] = data; - len_frame = data; - if (data < MIN_FRAME_LENGTH) { // 长度不符 - - stage = 0; - continue; + /* 找到合法帧头后,仅丢弃它前面的噪声,再正式取出4字节帧头。 */ + chrg_north_ringbuffer_discard(rb, i); + for (j = 0; j < LEN_FRAME_HEAD; j++) { + if (rt_ringbuffer_getchar(rb, &head[j]) == 0U) { + return -1; } - - stage = 1; - break; - - case 1: - head[1] = data; - if (0x00 != data) { - stage = 0; - } else { - stage = 2; - } - break; - - case 2: - head[2] = data; - if ((rt_uint8_t)id != data) { - stage = 0; - } else { -// if (id == ID_SINK) { -// rt_uint8_t ext1, ext2; -// // 主动读取并丢弃2字节扩展字段 -// rt_ringbuffer_getchar(rb, &ext1); -// rt_ringbuffer_getchar(rb, &ext2); -// rt_kprintf("Sink:%02X %02X\r\n", ext1, ext2); -// } - return len_frame; - } - break; - - default: - - break; } - + return length; } - return -1; + /* 保留末尾可能尚未接收完整的4字节帧头,等待下一批UART数据续接。 */ + if (len_rb > (LEN_FRAME_HEAD - 1U)) { + chrg_north_ringbuffer_discard(rb, + len_rb - (LEN_FRAME_HEAD - 1U)); + } + return 0; } /***************************************************************** @@ -486,10 +620,123 @@ static int chrg_north_frame_head (struct rt_ringbuffer *rb, rt_uint8_t *head, eI 其它说明: - *****************************************************************/ int a_number; + +#define NORTH_FRAME_TIMEOUT_MS 100 + +/***************************************************************** +函数名称: chrg_north_frame_reset +函数描述: 复位北向帧接收状态并按需释放未完成帧内存 +输入参数: pFrame:帧接收上下文 release:是否释放pMB及payload +输出参数: 清零接收长度、帧头标志、超时计时和消息指针 +返回说明: - +其它说明: 完整帧已移交邮箱时release应为RT_FALSE,异常中止时为RT_TRUE +*****************************************************************/ +static void chrg_north_frame_reset(struct pickup_info_t *pFrame, rt_bool_t release) +{ + if (RT_NULL == pFrame) { + return; + } + + if (release && (RT_NULL != pFrame->pMB)) { + if (RT_NULL != pFrame->pMB->payload) { + rt_free(pFrame->pMB->payload); + } + rt_free(pFrame->pMB); + } + + pFrame->last_tick = 0; + pFrame->length = 0; + pFrame->findhead = 0; + pFrame->len_msg = 0; + pFrame->pMB = RT_NULL; +} + +/***************************************************************** +函数名称: chrg_north_frame_append +函数描述: 从北向环形缓冲区追加数据到当前未完成帧 +输入参数: rb:北向接收环形缓冲区 pFrame:帧接收上下文 +输出参数: 更新已接收长度和最后接收时间 +返回说明: 1:完整帧接收完成 0:仍需等待数据 <0:参数或状态异常 +其它说明: 单次仅复制当前帧剩余长度,避免读取下一帧数据 +*****************************************************************/ +static int chrg_north_frame_append(struct rt_ringbuffer *rb, + struct pickup_info_t *pFrame) +{ + rt_size_t remaining; + rt_size_t copied; + + if ((RT_NULL == rb)||(RT_NULL == pFrame)||(RT_NULL == pFrame->pMB)|| + (RT_NULL == pFrame->pMB->payload)||(pFrame->len_msg <= 0)|| + (pFrame->length > (rt_size_t)pFrame->len_msg)) { + return -1; + } + + remaining = (rt_size_t)pFrame->len_msg - pFrame->length; + if (0 == remaining) { + return 1; + } + + copied = rt_ringbuffer_data_len(rb); + if (copied > remaining) { + copied = remaining; + } + if (copied > 0) { + copied = rt_ringbuffer_get(rb, + &pFrame->pMB->payload[pFrame->length], copied); + pFrame->length += copied; + pFrame->last_tick = rt_tick_get(); + } + + return (pFrame->length == (rt_size_t)pFrame->len_msg) ? 1 : 0; +} + +/***************************************************************** +函数名称: chrg_north_frame_dispatch +函数描述: 校验并分发已经组装完成的北向数据帧 +输入参数: pFrame:包含完整pMB消息的帧接收上下文 +输出参数: 合法完整帧发送到北向轮询邮箱 +返回说明: - +其它说明: 本函数只负责帧结构和CRC校验,不执行Source/Sink业务解析; + 校验失败时释放消息,校验成功后将消息所有权移交北向轮询线程 +*****************************************************************/ +static void chrg_north_frame_dispatch(struct pickup_info_t *pFrame) +{ + struct mb_msg_t *pMB; + rt_uint8_t crc_calc; + rt_uint8_t crc_recv; + + if (pFrame == RT_NULL) { + return; + } + pMB = pFrame->pMB; + if ((pMB == RT_NULL) || (pMB->payload == RT_NULL) || + (pMB->length < MIN_FRAME_LENGTH) || + (pMB->length > 0xFFU) || + (pMB->payload[0] != (rt_uint8_t)pMB->length) || + (pMB->payload[1] != 0x00U)) { + LOG_E("invalid north frame: len=%d", pMB ? pMB->length : 0); + chrg_north_frame_reset(pFrame, RT_TRUE); + return; + } + + crc_calc = calc_crc8(pMB->payload, (rt_uint8_t)(pMB->length - 1U)); + crc_recv = pMB->payload[pMB->length - 1U]; + if (crc_calc != crc_recv) { + LOG_E("north crc failed: len=%d calc=%02x recv=%02x", + pMB->length, crc_calc, crc_recv); + chrg_north_frame_reset(pFrame, RT_TRUE); + return; + } + + chrg_north_frame_reset(pFrame, RT_FALSE); + (void)chrg_thread_mb_send(IDX_THR_NORTH, pMB); +} + static int chrg_north_pickup_frame (struct rt_ringbuffer *rb, struct pickup_info_t *pFrame, eIDX_NOR_CH idx) { int cnt = 0; + int status = 0; rt_size_t len_rb = 0; if ((RT_NULL == rb)||(RT_NULL == pFrame)) { return -1; @@ -503,63 +750,52 @@ static int chrg_north_pickup_frame (struct rt_ringbuffer *rb, pFrame->len_msg = chrg_north_frame_head(rb, pFrame->head, pSW->id); // rt_kprintf("msg=:%d",pFrame->len_msg); if (pFrame->len_msg > 0) { + if ((pFrame->len_msg < LEN_FRAME_HEAD)|| + (pFrame->len_msg >= SIZE_BUF_TTY)) { + LOG_E("invalid msg length: %d", pFrame->len_msg); + chrg_north_frame_reset(pFrame, RT_TRUE); + return -3; + } + pFrame->pMB = (struct mb_msg_t *)rt_malloc(sizeof(struct mb_msg_t)); if (RT_NULL == pFrame->pMB) { LOG_E("malloc mb failed."); + chrg_north_frame_reset(pFrame, RT_FALSE); return -2; } pFrame->pMB->length = pFrame->len_msg; - if (pFrame->pMB->length >= SIZE_BUF_TTY) { - rt_free(pFrame->pMB); - pFrame->pMB = RT_NULL; - LOG_E("too long msg."); - return -3; - } + pFrame->pMB->payload = RT_NULL; pFrame->pMB->payload = (rt_uint8_t *)rt_malloc(pFrame->len_msg); if (RT_NULL == pFrame->pMB->payload) { LOG_E("malloc buf failed."); + chrg_north_frame_reset(pFrame, RT_TRUE); return -4; } rt_memcpy(pFrame->pMB->payload, pFrame->head, LEN_FRAME_HEAD); + pFrame->length = LEN_FRAME_HEAD; + pFrame->findhead = 1; + pFrame->last_tick = rt_tick_get(); - len_rb = rt_ringbuffer_data_len(rb); -// rt_kprintf("len_rb=%d\r\n",len_rb); -// rt_kprintf("len=%d",pFrame->len_msg - LEN_FRAME_HEAD); - if (len_rb >= pFrame->len_msg - LEN_FRAME_HEAD) { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[LEN_FRAME_HEAD], - pFrame->len_msg - LEN_FRAME_HEAD); - if(pFrame->pMB->length==21){ - chrg_north_handle_Seting_Volt(pFrame->pMB->payload, pFrame->pMB->length); - } - chrg_thread_mb_send(IDX_THR_NORTH, pFrame->pMB); - pFrame->length = 0; - pFrame->len_msg = 0; + status = chrg_north_frame_append(rb, pFrame); + if (status > 0) { + chrg_north_frame_dispatch(pFrame); cnt++; + } else if (status < 0) { + chrg_north_frame_reset(pFrame, RT_TRUE); + return -5; } else { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[LEN_FRAME_HEAD], len_rb); - pFrame->length = LEN_FRAME_HEAD + len_rb; - pFrame->findhead = 1; - pFrame->last_tick = rt_tick_get(); break; } } } else { - len_rb = rt_ringbuffer_data_len(rb); - int len_need = pFrame->len_msg - LEN_FRAME_HEAD; - if (len_rb >= len_need) { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[pFrame->length], len_need); - - chrg_thread_mb_send(IDX_THR_NORTH, pFrame->pMB); - //chrg_north_pkg_decode(pFrame->pMB->payload, pFrame->len_msg, pSW); - - pFrame->length = 0; - pFrame->findhead = 0; - pFrame->len_msg = 0; + status = chrg_north_frame_append(rb, pFrame); + if (status > 0) { + chrg_north_frame_dispatch(pFrame); cnt++; + } else if (status < 0) { + chrg_north_frame_reset(pFrame, RT_TRUE); + return -5; } else { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[pFrame->length], len_rb); - pFrame->length += len_rb; - pFrame->last_tick = rt_tick_get(); break; } } @@ -587,6 +823,8 @@ void chrg_north_thread_entry (void *data) } int len = 0; + rt_size_t put_len = 0; + eIDX_NOR_CH frame_idx = IDX_NOR_CH1; // 当前未完成帧所属的物理通道 struct chrg_tty_t *pTTY = RT_NULL; struct chrg_north_t *pNOR = &chrgnorth; //北向协议板线程体通道初始化为CH1, @@ -613,6 +851,13 @@ void chrg_north_thread_entry (void *data) } while (1) { + if (pNOR->frame.findhead && + ((rt_tick_t)(rt_tick_get() - pNOR->frame.last_tick) >= + rt_tick_from_millisecond(NORTH_FRAME_TIMEOUT_MS))) { + chrg_north_frame_reset(&pNOR->frame, RT_TRUE); + LOG_W("partial frame timeout"); + } + rt_memset(pNOR->rx_buf, 0, sizeof(pNOR->rx_buf)); len = chrg_tty_recv(pTTY, pNOR->rx_buf, sizeof(pNOR->rx_buf)); if (len <= 0) { @@ -620,17 +865,27 @@ void chrg_north_thread_entry (void *data) continue; } - rt_ringbuffer_put(pNOR->rb, pNOR->rx_buf, len); -#if DEBUG_NORTH - LOG_HEX("recvN", 32, pNOR->rx_buf, len); -#endif - if (1 == pNOR->frame.findhead) { - if (rt_tick_get() >= pNOR->frame.last_tick + 100) { // 100ms - pNOR->frame.findhead = 0; // reset - //LOG_E("timeo\r\n"); - } + /* + * 四个物理通道共用一个UART组帧器。切换通道后必须终止旧通道的未完成帧, + * 防止旧帧继续吞入新通道回码。本复位只在接收线程执行,避免发送线程与 + * 接收线程同时操作环形缓冲区和组帧上下文。 + */ + if (frame_idx != pNOR->idx) { + rt_ringbuffer_reset(pNOR->rb); + chrg_north_frame_reset(&pNOR->frame, RT_TRUE); + frame_idx = pNOR->idx; } + put_len = rt_ringbuffer_put(pNOR->rb, pNOR->rx_buf, len); + if (put_len != (rt_size_t)len) { + LOG_E("north ringbuffer overflow: recv=%d put=%d", len, put_len); + rt_ringbuffer_reset(pNOR->rb); + chrg_north_frame_reset(&pNOR->frame, RT_TRUE); + continue; + } +#if NORTH_FRAME_DEBUG + LOG_HEX("recvN", 32, pNOR->rx_buf, len); +#endif chrg_north_pickup_frame(pNOR->rb, &pNOR->frame, pNOR->idx); } } @@ -654,5 +909,25 @@ static int north_ch(rt_uint8_t argc, char **argv) MSH_CMD_EXPORT(north_ch, north channel select); +static int north_diag(int argc, char **argv) +{ + rt_uint8_t i; + struct chrg_north_t *pNOR = &chrgnorth; + struct chrg_rollnor_t *pROLL = &chrgrollnor; + + rt_kprintf("roll: run=%d idx=%d ch=%d type=%d sub=%d\r\n", + pROLL->run, pROLL->idx, pROLL->ch, pROLL->type, pROLL->sub); + rt_kprintf("rx: rb_len=%d findhead=%d frame_len=%d received=%d\r\n", + pNOR->rb ? rt_ringbuffer_data_len(pNOR->rb) : 0, + pNOR->frame.findhead, pNOR->frame.len_msg, pNOR->frame.length); + for (i = 0; i < TOTAL_NOR_CHS; i++) { + rt_kprintf("ch%d: enable=%d id=%d sub=%d\r\n", i, + pNOR->enable[i], pNOR->sw[i].id, pNOR->sw[i].sub); + } + return 0; +} + +MSH_CMD_EXPORT(north_diag, show north polling and frame state); + #endif diff --git a/apps/chrg/applications/thread/chrg_north.h b/apps/chrg/applications/thread/chrg_north.h index 9004694..8ad285d 100644 --- a/apps/chrg/applications/thread/chrg_north.h +++ b/apps/chrg/applications/thread/chrg_north.h @@ -19,15 +19,20 @@ #define THR_NAME_NORTH "thr.nor" #define DEV_NAME_NORTH "uart3" -#define LEN_FRAME_HEAD (3) // [帧长, 0x00, id] +#define LEN_FRAME_HEAD (4) // [帧长, 0x00, id, 命令] #define MIN_FRAME_LENGTH (5) // 帧最短5字节 #define WORK_MIN_VOLT 3000 // 工作电压下限,单位mV -#define DEBUG_NORTH 0x00 // 0 = 关闭,1 = 开启 +#define NORTH_FRAME_DEBUG 0x00 // 北向UART原始收发帧 +#define NORTH_SOURCE_DEBUG 0x01 // Source协议解析及组帧 +#define NORTH_SINK_DEBUG 0x00 // Sink协议解析及组帧 +#define SOUTH_FRAME_DEBUG 0x00 // 南向UART原始收发帧 +#define SOUTH_COMMAND_DEBUG 0x01 // 南向寄存器写命令 +#define COMM_FRAME_DEBUG 0x00 // COM UART原始收发帧 +#define COMM_DEBUG 0x00 // COM寄存器读写过程 +#define COMM_TRIM_DEBUG 0x00 // COM校准参数解析 #define LCD_OPEN 0x01 // 0为关闭屏幕显示 1为打开 #define TRIM_NORTH 0x00 // 0 = 关闭, 1 = 开启 #define SINK_TEST_DEBUG 0x00 // 0 = 关闭,1 = 开启 -#define PRO_TEST_DEBUG 0x00 // -#define DEBUG_SOUTH 0x00 // 0 = 关闭,1 = 开启 // 北向4通路索引 U14 -> typedef enum { IDX_NOR_CH1 = IDX_CH1, // J3 @@ -95,7 +100,6 @@ struct chrg_trim { rt_int16_t trim_source_curr_b; // 校准电源电流值b mA rt_int16_t trim_zero; // 校准飘零值 mV/mA rt_uint8_t trim_mode; // 校准模式, 0-默认值,1-校准电压,2-校准电流,3-校准电源电流 - rt_uint8_t trim_step; rt_uint8_t trim_enable_flag; //0就是默认写用户区,1则写出场数据区 }; @@ -105,6 +109,7 @@ struct chrg_north_t { struct chrg_tty_t *tty; struct rt_ringbuffer *rb; struct pickup_info_t frame; + rt_uint32_t config_version[TOTAL_NOR_CHS]; // COM配置版本,用于识别等待期间到达的旧回码 struct chrg_trim trim[TOTAL_NOR_CHS]; rt_uint8_t rx_buf[SIZE_BUF_TTY]; rt_uint8_t free_flag; @@ -112,11 +117,34 @@ struct chrg_north_t { rt_uint8_t manual; // 手动插入命令 eIDX_NOR_CH idx; // 当前通路 struct chrg_switch_t sw[TOTAL_NOR_CHS]; - rt_uint8_t north_mode; // 0-正常模式, 1-校准模式 }; extern struct chrg_north_t chrgnorth; +/***************************************************************** +函数名称: chrg_source_parse +函数描述: 解析Source的0x20电压电流回码并驱动电源状态机 +输入参数: frame_data:已通过CRC校验的完整回码 idx:对应通道 + pSW:通道控制结构体 pNOR:北向控制结构体 +输出参数: 更新Source目标电压、电流和工作状态 +返回说明: 0:回码正常 <0:参数错误、Source切换错误或功率命令提交失败 +其它说明: 仅由北向轮询线程在确认回码角色、命令和通道匹配后调用 +*****************************************************************/ +extern int chrg_source_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx, + struct chrg_switch_t *pSW, struct chrg_north_t *pNOR); + +/***************************************************************** +函数名称: chrg_sink_parse +函数描述: 解析Sink的0x20电压电流回码并驱动负载状态机 +输入参数: frame_data:已通过CRC校验的完整回码 idx:对应通道 + pSW:通道控制结构体 pNOR:北向控制结构体 +输出参数: 更新Sink协议量测值和工作状态 +返回说明: - +其它说明: 仅由北向轮询线程在确认回码角色、命令和通道匹配后调用 +*****************************************************************/ +extern void chrg_sink_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx, + struct chrg_switch_t *pSW, struct chrg_north_t *pNOR); + /***************************************************************** 函数名称: chrg_north_switch 函数描述: 北向通道选择 diff --git a/apps/chrg/applications/thread/chrg_roll_nor.c b/apps/chrg/applications/thread/chrg_roll_nor.c index 52e1f01..c70d52b 100644 --- a/apps/chrg/applications/thread/chrg_roll_nor.c +++ b/apps/chrg/applications/thread/chrg_roll_nor.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_roll_nor.c 完成日期 : -当前版本号 : V1.0 +当前版本号 : V1.1.9 主要功能 : 实现北向通信轮巡请求命令。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -17,6 +17,8 @@ #include "chrg_sink.h" #include "chrg_source.h" #include "chrg_roll_nor.h" +#include "chrg_roll_sou.h" +#include "chrg_comm.h" #include "chrg_north.h" #include "chrg_switch.h" #include "chrg_utils.h" @@ -24,6 +26,17 @@ #include "chrg_wdt.h" #define LOG_TAG "chrg.rollnor" #include + +#define NORTH_RESPONSE_TIMEOUT_MS 50U // 单次北向请求等待匹配回码的总时间 +#define NORTH_REQUEST_MAX_RETRIES 2U // 首次发送失败后最多重发2次,总发送次数为3次 +#define NORTH_MEASURE_MAX_ATTEMPTS 1U // 0x20量测查询失败后直接留给下一轮轮询重发 +#define NORTH_RETRY_INTERVAL_MS 5U // 相同请求两次发送之间的保护间隔 +#define NORTH_POLL_INTERVAL_MS 10U // 一个通道事务结束后到下一通道的轮询间隔 +#define SOURCE_PROTOCOL_WAIT_MS 100U // 每通道0xD0应答后的协议生效保护时间 +#define SINK_CC_WAIT_MS 50U // Sink设置目标CC应答后的非阻塞保护时间 +#define SINK_HANDSHAKE_FIRST_QUERY_DELAY_MS 50U // 协议/CC回码后首条0x20的等待时间 +#define SINK_HANDSHAKE_QUERY_INTERVAL_MS 50U // 握手未完成时后续0x20查询间隔 +#define SOURCE_CHANNEL_BIT(ch) ((rt_uint8_t)(1U << (ch))) const rt_base_t sw_uart[TOTAL_NOR_CHS] = { IDX_GPO_UART_SW1, IDX_GPO_UART_SW2, @@ -37,6 +50,273 @@ struct chrg_rollnor_t chrgrollnor = { // .Set_Role = ID_SINK, // .group = 0, }; + +/* 所有参与CC_CLOSE的通道共享300ms屏障;协议等待仍按通道独立计时。 */ +static rt_uint8_t g_source_protocol_wait_mask = 0U; +static rt_tick_t g_source_protocol_wait_start[TOTAL_NOR_CHS] = {0}; +static rt_uint8_t g_sink_cc_wait_mask = 0U; +static rt_tick_t g_sink_cc_wait_start[TOTAL_NOR_CHS] = {0}; +/* Sink协议/CC握手期间的0x20查询节流记录,按通道独立计时。 */ +static rt_bool_t g_sink_query_throttle[TOTAL_NOR_CHS] = {RT_FALSE}; +static rt_bool_t g_sink_query_first_pending[TOTAL_NOR_CHS] = {RT_FALSE}; +static rt_tick_t g_sink_query_tick[TOTAL_NOR_CHS] = {0}; + +/***************************************************************** +函数名称: chrg_roll_nor_sink_query_is_slow +函数描述: 判断当前Sink状态是否仍处于协议握手量测阶段 +输入参数: pSW:当前负载通道控制结构体 +输出参数: - +返回说明: RT_TRUE:0x20查询按握手节奏发送 RT_FALSE:使用正常轮询周期 +其它说明: 只有协议/CC设置后等待协商电压的阶段限速;RUNING和低电流 + 待机不受影响,避免影响正常量测刷新速度 +*****************************************************************/ +static rt_bool_t chrg_roll_nor_sink_query_is_slow( + const struct chrg_switch_t *pSW) +{ + if (pSW == RT_NULL) { + return RT_FALSE; + } + return (pSW->sink.work_mode == SINK_WORK_WAIT) || + (pSW->sink.work_mode == SINK_WORK_INIT_AUX_WAIT) || + (pSW->sink.work_mode == SINK_WORK_LOW_MONITOR); +} + +/***************************************************************** +函数名称: chrg_roll_nor_sink_query_wait_start +函数描述: 启动Sink握手阶段的0x20查询节流计时 +输入参数: ch:北向通道编号 +输出参数: 记录当前tick并开启节流 +返回说明: - +其它说明: 协议或CC回码成功后调用,避免下一轮立即查询协议板 +*****************************************************************/ +static void chrg_roll_nor_sink_query_wait_start(rt_uint8_t ch) +{ + if (ch >= TOTAL_NOR_CHS) { + return; + } + g_sink_query_throttle[ch] = RT_TRUE; + g_sink_query_first_pending[ch] = RT_TRUE; + g_sink_query_tick[ch] = rt_tick_get(); +} + +/***************************************************************** +函数名称: chrg_roll_nor_source_switch_reset +函数描述: 清理指定Source通道的CC_CLOSE及协议等待记录 +输入参数: ch:北向通道编号 +输出参数: 清除对应通道的屏障位和协议等待计时 +返回说明: - +其它说明: 调用者必须持有北向轮询线程互斥锁 +*****************************************************************/ +void chrg_roll_nor_source_switch_reset(rt_uint8_t ch) +{ + if (ch >= TOTAL_NOR_CHS) { + return; + } + g_source_protocol_wait_mask &= + (rt_uint8_t)~SOURCE_CHANNEL_BIT(ch); + g_source_protocol_wait_start[ch] = 0U; +} + +/***************************************************************** +函数名称: chrg_roll_nor_source_cc_close_register +函数描述: 将Source通道登记到本轮共享CC_CLOSE等待屏障 +输入参数: ch:北向通道编号 +输出参数: 设置pending位并清除该通道旧ACK和等待状态 +返回说明: - +其它说明: 新通道加入已经开始的屏障时重新等待,保证所有登记通道共享完整保护时间 +*****************************************************************/ +static void chrg_roll_nor_source_protocol_wait_start(rt_uint8_t ch) +{ + if (ch >= TOTAL_NOR_CHS) { + return; + } + g_source_protocol_wait_mask |= SOURCE_CHANNEL_BIT(ch); + g_source_protocol_wait_start[ch] = rt_tick_get(); +} + +/***************************************************************** +函数名称: chrg_roll_nor_source_wait_process +函数描述: 处理Source共享CC_CLOSE等待和每通道协议生效等待 +输入参数: ch:北向通道编号 pSW:通道状态 +输出参数: CC_CLOSE等待到期进入0xD0,协议等待到期进入目标CC设置 +返回说明: RT_TRUE:本轮仍处于等待,不发送命令 RT_FALSE:可继续发送 +其它说明: CC_CLOSE使用共享300ms屏障,协议ACK后每通道独立等待100ms +*****************************************************************/ +static rt_bool_t chrg_roll_nor_source_wait_process(rt_uint8_t ch, + struct chrg_switch_t *pSW) +{ + rt_uint8_t bit; + rt_tick_t elapsed; + + if ((ch >= TOTAL_NOR_CHS) || (pSW == RT_NULL)) { + return RT_FALSE; + } + bit = SOURCE_CHANNEL_BIT(ch); + + if ((pSW->id != ID_SOURCE) || !pSW->source.On_Flag) { + chrg_roll_nor_source_switch_reset(ch); + return RT_FALSE; + } + if (pSW->source.work_mode == SOURCE_WORK_INIT_WAIT) { + /* 南向初始化批次未完成时,禁止发送北向协议命令。 */ + return RT_TRUE; + } + + if (pSW->sub == IDX_WAIT_SOURCE_PROTOCOL) { + if ((g_source_protocol_wait_mask & bit) == 0U) { + pSW->sub = IDX_SET_SOURCE_CC_SET; + return RT_FALSE; + } + elapsed = (rt_tick_t)(rt_tick_get() - + g_source_protocol_wait_start[ch]); + if (elapsed < rt_tick_from_millisecond(SOURCE_PROTOCOL_WAIT_MS)) { + return RT_TRUE; + } + g_source_protocol_wait_mask &= (rt_uint8_t)~bit; + g_source_protocol_wait_start[ch] = 0U; + pSW->sub = IDX_SET_SOURCE_CC_SET; + return RT_FALSE; + } + return RT_FALSE; +} + +/***************************************************************** +函数名称: chrg_roll_nor_sink_cc_wait_start +函数描述: Sink目标CC设置收到有效应答后启动该通道的稳定计时 +输入参数: ch:北向通道编号 +输出参数: 记录通道等待标志和起始tick +返回说明: - +其它说明: 每个通道独立计时,多个通道切换CC时等待时间不会串行累加 +*****************************************************************/ +static void chrg_roll_nor_sink_cc_wait_start(rt_uint8_t ch) +{ + if (ch >= TOTAL_NOR_CHS) { + return; + } + g_sink_cc_wait_mask |= SOURCE_CHANNEL_BIT(ch); + g_sink_cc_wait_start[ch] = rt_tick_get(); +} + +/***************************************************************** +函数名称: chrg_roll_nor_sink_wait_process +函数描述: 管理Sink安全卸载、南北并行握手和CC设置后的稳定等待 +输入参数: ch:北向通道编号 pSW:通道实时控制结构体 +输出参数: 南向批次完成后允许协议/CC;低电流和CC均满足后允许查询 +返回说明: RT_TRUE:本轮仍需等待,不发送北向命令 RT_FALSE:可继续发送 +其它说明: 完整重握手中,南向批次完成后协议/CC与低电流确认并行; + 进入GET_SINK_VC后,必须等低电流确认完成才允许发送0x20 +*****************************************************************/ +static rt_bool_t chrg_roll_nor_sink_wait_process(rt_uint8_t ch, + struct chrg_switch_t *pSW) +{ + rt_uint8_t bit; + rt_tick_t elapsed; + + if ((ch >= TOTAL_NOR_CHS) || (pSW == RT_NULL)) { + return RT_FALSE; + } + bit = SOURCE_CHANNEL_BIT(ch); + + /* 关机必须先完成南向低电流确认和继电器批次,期间禁止北向发送。 */ + if ((pSW->sink.work_mode == SINK_WORK_STOP_WAIT) || + (pSW->sink.work_mode == SINK_WORK_STOP_RELAY) || + (pSW->sink.work_mode == SINK_WORK_STOP_RELAY_WAIT)) { + return RT_TRUE; + } + + if ((pSW->id != ID_SINK) || !pSW->sink.On_work) { + g_sink_cc_wait_mask &= (rt_uint8_t)~bit; + g_sink_cc_wait_start[ch] = 0U; + g_sink_query_throttle[ch] = RT_FALSE; + g_sink_query_first_pending[ch] = RT_FALSE; + g_sink_query_tick[ch] = 0U; + return RT_FALSE; + } + /* 普通软卸载和低电流恢复仍禁止北向;INIT_AUX_WAIT允许南北向并行。 */ + if ((pSW->sink.work_mode == SINK_WORK_UNLOAD_WAIT) || + (pSW->sink.work_mode == SINK_WORK_LOW_AUX_WAIT)) { + return RT_TRUE; + } + + /* 协议或CC切换必须先完成低电流确认;该过程保持南向继电器状态不变。 */ + if (pSW->sink.work_mode == SINK_WORK_HANDSHAKE_WAIT) { + return RT_TRUE; + } + + /* D0回码确认后先经过一个非发送状态,禁止把协议回码当成量测回码。 */ + if (pSW->sub == IDX_WAIT_SINK_PD_ACK) { + pSW->sub = IDX_GET_SINK_VC; + chrg_roll_nor_sink_query_wait_start(ch); + return RT_TRUE; + } + + if ((pSW->sub != IDX_WAIT_SINK_CC) && + (pSW->sub != IDX_WAIT_SINK_CC_BEFORE_PD)) { + /* COM在等待期间提交新配置时,清理旧事务留下的CC计时记录。 */ + g_sink_cc_wait_mask &= (rt_uint8_t)~bit; + g_sink_cc_wait_start[ch] = 0U; + if (!chrg_roll_nor_sink_query_is_slow(pSW)) { + g_sink_query_throttle[ch] = RT_FALSE; + g_sink_query_first_pending[ch] = RT_FALSE; + g_sink_query_tick[ch] = 0U; + } + } else { + if ((g_sink_cc_wait_mask & bit) == 0U) { + if (pSW->sub == IDX_WAIT_SINK_CC_BEFORE_PD) { + pSW->sub = IDX_SET_SINK_PD; + } else { + pSW->sub = IDX_GET_SINK_VC; + chrg_roll_nor_sink_query_wait_start(ch); + } + return RT_TRUE; + } + elapsed = (rt_tick_t)(rt_tick_get() - g_sink_cc_wait_start[ch]); + if (elapsed < rt_tick_from_millisecond(SINK_CC_WAIT_MS)) { + return RT_TRUE; + } + + if (pSW->sub == IDX_WAIT_SINK_CC_BEFORE_PD) { + /* CC先设置完成后进入协议设置,不立即查询电压。 */ + g_sink_query_throttle[ch] = RT_FALSE; + g_sink_query_first_pending[ch] = RT_FALSE; + g_sink_query_tick[ch] = 0U; + g_sink_cc_wait_mask &= (rt_uint8_t)~bit; + g_sink_cc_wait_start[ch] = 0U; + pSW->sub = IDX_SET_SINK_PD; + return RT_TRUE; + } + + /* 协议已先设置;CC应答并完成保护等待后进入电压查询。 */ + g_sink_query_throttle[ch] = RT_TRUE; + g_sink_query_first_pending[ch] = RT_TRUE; + g_sink_query_tick[ch] = g_sink_cc_wait_start[ch]; + g_sink_cc_wait_mask &= (rt_uint8_t)~bit; + g_sink_cc_wait_start[ch] = 0U; + pSW->sub = IDX_GET_SINK_VC; + return RT_TRUE; + } + + /* 首条0x20等待200ms;握手未稳定时,后续查询间隔缩短为100ms。 */ + if (chrg_roll_nor_sink_query_is_slow(pSW) && + (pSW->sub == IDX_GET_SINK_VC)) { + rt_uint32_t query_interval_ms; + + if (!g_sink_query_throttle[ch]) { + chrg_roll_nor_sink_query_wait_start(ch); + return RT_TRUE; + } + query_interval_ms = g_sink_query_first_pending[ch] ? + SINK_HANDSHAKE_FIRST_QUERY_DELAY_MS : + SINK_HANDSHAKE_QUERY_INTERVAL_MS; + elapsed = (rt_tick_t)(rt_tick_get() - g_sink_query_tick[ch]); + if (elapsed < rt_tick_from_millisecond(query_interval_ms)) { + return RT_TRUE; + } + g_sink_query_first_pending[ch] = RT_FALSE; + g_sink_query_tick[ch] = rt_tick_get(); + } + return RT_FALSE; +} /***************************************************************** 函数名称: sink_fill_pd_buf 函数描述: 根据快充协议类型填充PD指令的5字节数据缓冲区 @@ -67,8 +347,8 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt case 0xF6: // QC3.0协议 case 0xF2: // FCP协议 buf[0] = pd_volt / 100; // 第5字节=电压(0.1V) - buf[1] = pd_curr / 100; // 第6字节=电流(0.1A) - #if PRO_TEST_DEBUG + buf[1] = 0x00;//pd_curr / 100; // 第6字节=电流(0.1A) + #if NORTH_SINK_DEBUG rt_kprintf("type: %02X, Voltage(0.1V)=%d\n", pd_val, pd_volt/100); rt_kprintf("type: %02x, Curr(0.1A)=%d\n",pd_curr,pd_curr/100); #endif @@ -82,7 +362,7 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt buf[2] = pd_volt & 0xFF; // 第7字节=电压低8位(mV) buf[3] = (pd_curr >> 8) & 0xFF; // 第8字节=电流高8位(mA) buf[4] = pd_curr & 0xFF; // 第9字节=电流低8位(mA) - #if PRO_TEST_DEBUG + #if NORTH_SINK_DEBUG rt_kprintf("type: %02X, Group=0, Voltage=%d mV, Current=%d mA\n", pd_val, pd_volt, pd_curr); #endif break; @@ -95,7 +375,7 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt buf[2] = pd_volt & 0xFF; // 第7字节=电压低8位(mV) buf[3] = (pd_curr >> 8) & 0xFF; // 第8字节=电流高8位(mA) buf[4] = pd_curr & 0xFF; // 第9字节=电流低8位(mA) - #if PRO_TEST_DEBUG + #if NORTH_SINK_DEBUG rt_kprintf("type: %02X, Group=0, Voltage=%d mV, Current=%d mA\n", pd_val, pd_volt, pd_curr); #endif break; @@ -108,15 +388,15 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt buf[1] = pd_volt & 0xFF; // 第7字节=电压低8位(mV) buf[2] = (pd_curr >> 8) & 0xFF; // 第8字节=电流高8位(mA) buf[3] = pd_curr & 0xFF; // 第9字节=电流低8位(mA) - buf[4] = 0; - #if PRO_TEST_DEBUG + buf[4] = 0x00; + #if NORTH_SINK_DEBUG rt_kprintf("type: %02X, Voltage=%d mV, Current=%d mA\n", pd_val, pd_volt, pd_curr); #endif break; // VOOC(无参数) case 0xE2: // VOOC协议 - #if PRO_TEST_DEBUG + #if NORTH_SINK_DEBUG rt_kprintf("type: VOOC(0xE2), No parameters\n"); #endif break; @@ -129,7 +409,7 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt rt_uint8_t volt_v = pd_volt / 1000; // 转成V(5000mV→5V) volt_hex = volt_v - 5; // 5V对应0,20V对应15(0xF) } else { - #if PRO_TEST_DEBUG + #if NORTH_SINK_DEBUG rt_kprintf("AFC VOLT ERROR VOLT=:%d mV Set 5000~20000\n", pd_volt); #endif return -1; @@ -140,9 +420,11 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt if (pd_curr >= 750 && pd_curr <= 3000) { // 电流范围:0.75A(750mA)~3.0A(3000mA) rt_uint16_t curr_ma_step = (pd_curr - 750) / 150; // 步长150mA(0.75→1.5→...→3.0) curr_hex = curr_ma_step; // 0.75A对应0,3.0A对应15(0xF) +#if NORTH_SINK_DEBUG rt_kprintf("curr_ma_step = %d",curr_ma_step); +#endif } else { - #if PRO_TEST_DEBUG + #if NORTH_SINK_DEBUG rt_kprintf("AFC CURR ERROR%d mA SET 750~3000 \n", pd_curr); #endif return -1; @@ -150,8 +432,10 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt // 3. 合并电压Hex(高4位)和电流Hex(低4位)→ 填充第五字节 buf[0] = (volt_hex << 4) | curr_hex; +#if NORTH_SINK_DEBUG rt_kprintf("type: AFC(0xF9), Voltage=%d V, Current=%.2f A, volaue=0x%X\n", pd_volt/1000, pd_curr/1000.0, buf[0]); +#endif } break; // 未知类型 @@ -162,38 +446,6 @@ int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt return 0; } -rt_uint16_t v1=0,v2=0; - - -//电源关闭 -void chrg_nor_source_stop(struct chrg_switch_t *pSW,rt_uint8_t idx,rt_uint8_t *enable){ - switch(pSW->source.Stop_step_flag){ - case 0: - chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_WORK, WORK_STOP); - pSW->source.Stop_step_flag = 1; - break; - case 1: // 停止工作 - chrg_set_sou_reg((eIDX_SOU_CH)idx, REG_WORK, WORK_STOP); - pSW->source.Stop_step_flag = 2; - break; - case 2: // 关闭北向继电器 - chrg_nor_sw_rel((eIDX_NOR_CH)idx,0); - pSW->source.Stop_step_flag = 3; - break; - case 3: // 状态2:已执行完停止操作,清空标志位 - pSW->source.Set_CV_Flag = 0; - if(pSW->source.change_sink_Flag == 1){ - pSW->source.change_sink_Flag = 0; -// chrg_com_sink_on(idx,pSW,enable); - }else{ - *enable = 0; - } - break; - default: - pSW->source.Stop_step_flag = 0; - break; - } -} /***************************************************************** 函数名称: chrg_roll_nor_sw_uart @@ -217,106 +469,237 @@ static void chrg_roll_nor_sw_uart (eIDX_NOR_CH ch, eIDX_ID id) } -//设置负载协议 -void chrg_roll_nor_sink_pro_set(struct chrg_switch_t *pSW) +/***************************************************************** +函数名称: chrg_roll_nor_sink_pro_set +函数描述: 根据当前负载协议参数组帧并下发负载协议设置命令 +输入参数: pSW:当前通道控制结构体 +输出参数: - +返回说明: >0:发送长度 <0:参数组帧或串口发送失败 +其它说明: 本函数只负责发送,不修改pSW->sub,状态由有效回码确认后推进 +*****************************************************************/ +static int chrg_roll_nor_sink_pro_set(struct chrg_switch_t *pSW) { rt_uint8_t buf[6] = {0}; - sink_fill_pd_buf(pSW->sink.Pro_Group,pSW->sink.protocol,pSW->sink.pro_loadv,pSW->sink.pro_loadc,buf); - sink_set_pd(pSW->sink.protocol,buf); -#if DEBUG_NORTH + if (sink_fill_pd_buf(pSW->sink.Pro_Group, pSW->sink.protocol, + pSW->sink.pro_loadv, pSW->sink.pro_loadc, buf) != 0) { + return -1; + } +#if NORTH_SINK_DEBUG rt_kprintf("set_pro\r\n"); -#endif +#endif + return sink_set_pd(pSW->sink.protocol, buf); } -//设置电源协议 -void chrg_roll_nor_source_pro_set(struct chrg_switch_t *pSW){ - if(pSW->source.Set_Pro_Flag == 1){ - pSW->source.Set_Pro_Flag = 0; - if(pSW->source.LCD_Set_Pro_Flag==1){ - source_set_pd(pSW->source.protocol,0,0,pSW->source.vc.set_voltage,0,pSW->source.vc.set_current); - } - else if(pSW->source.LCD_Set_Pro_Flag==2){ - source_set_pd_com(pSW->source.protocol,pSW->source.pro_gear_idx); - } - else if(pSW->source.LCD_Set_Pro_Flag==3){ - source_set_pd_new_com(pSW->source.protocol,pSW); - } - }else { - pSW->sub = IDX_GET_SOURCE_VC; - } -} -static int chrg_roll_nor_parse (struct chrg_rollnor_t *pROLL, rt_uint8_t *data) +/***************************************************************** +函数名称: chrg_roll_nor_source_pro_set +函数描述: 根据当前电源协议参数组帧并下发电源协议设置命令 +输入参数: pSW:当前通道控制结构体 +输出参数: - +返回说明: >0:发送长度 <0:参数组帧或串口发送失败 +其它说明: 本函数只负责发送,不修改pSW->sub,状态由有效回码确认后推进 +*****************************************************************/ +static int chrg_roll_nor_source_pro_set(struct chrg_switch_t *pSW) { - if ((RT_NULL == pROLL)||(RT_NULL == data)) { - return -1; + return source_set_pro(pSW->source.protocol, pSW); +} + +/***************************************************************** +函数名称: chrg_roll_nor_expected_cmd +函数描述: 根据当前角色和sub状态取得本次北向请求期望的回码命令 +输入参数: pSW:当前通道控制结构体 +输出参数: - +返回说明: 非0:期望命令码 0:当前状态没有可识别的北向请求 +其它说明: 必须在chrg_roll_nor_set发送命令前调用,确保保存的期望命令 + 与实际发送命令一致 +*****************************************************************/ +static rt_uint8_t chrg_roll_nor_expected_cmd(const struct chrg_switch_t *pSW) +{ + if (pSW == RT_NULL) { + return 0; } - chrg_north_switch(IDX_NOR_CH1); - // 读取北向全局结构体中当前通道的Source/Sink参数 - struct chrg_switch_t *pSW = &chrgnorth.sw[pROLL->ch]; - if (1 == pROLL->type) { - switch (pROLL->sub) { - case IDX_GET_SOURCE_VC: { - - } - break; - - case IDX_GET_SOURCE_PD: { - - } - break; - - case IDX_SET_SOURCE_PD: { - - } - break; - - case IDX_SET_SOURCE_CCLVL: { - - } - break; + if (pSW->id == ID_SOURCE) { + switch (pSW->sub) { + case IDX_GET_SOURCE_VC: + return SRC_CMD_GET_VOLTAGE; + case IDX_GET_SOURCE_PD: + return SRC_CMD_GET_PROTOCOL; + case IDX_SET_SOURCE_PD: + return SRC_CMD_SET_PROTOCOL; + case IDX_SET_SOURCE_RESET: + return SRC_CMD_RESET; + case IDX_SET_SOURCE_CC_SET: + return SRC_CMD_SET_CC_CHOOSE; + case IDX_SET_SOURCE_CCLVL: + return SRC_CMD_SET_CC_LEVEL; default: - return -1; - break; + return 0; } + } - } else if (0 == pROLL->type) { - chrg_roll_nor_sw_uart(IDX_NOR_CH1,ID_SINK); - switch (pROLL->sub) { - case IDX_GET_SINK_VC: { - sink_get_vc(pSW->sink.pro_loadv,pSW->sink.pro_loadc); - } - break; - - case IDX_GET_SINK_PD: { - - } - break; - - case IDX_SET_SINK_PD: { - - } - break; - - case IDX_SET_SINK_CCLINE: { - - } - break; - - case IDX_SET_SINK_CCLVL: { - - } - break; - + if (pSW->id == ID_SINK) { + switch (pSW->sub) { + case IDX_GET_SINK_VC: + return SINK_CMD_GET_VOLTAGE; + case IDX_GET_SINK_PD: + return SINK_CMD_GET_PD; + case IDX_SET_SINK_PD: + case IDX_SET_SINK_PD_ONLY: +#if 0 + case IDX_SET_SINK_PD_BEFORE_CC: +#endif + return pSW->sink.protocol; + case IDX_SET_SINK_CCLINE: + case IDX_SET_SINK_CC_BEFORE_PD: + return SINK_CMD_SET_CC; + case IDX_SET_SINK_CCLVL: + return SINK_CMD_SET_LEVEL; default: - return -1; + return 0; } - } else { - return -1; } return 0; } + +/***************************************************************** +函数名称: chrg_roll_nor_response_matches +函数描述: 校验北向回码与当前请求的通道角色、命令和固定长度是否匹配 +输入参数: pMB:已通过north.c结构及CRC校验的回码 expected_idx:请求通道 + expected_id:请求角色 expected_cmd:请求命令 +输出参数: - +返回说明: 0:回码匹配 <0:参数错误或回码与请求不匹配 +其它说明: 本函数只判断请求归属,业务解析在配置版本复核通过后执行 +*****************************************************************/ +static int chrg_roll_nor_response_matches(const struct mb_msg_t *pMB, + rt_uint8_t expected_idx, eIDX_ID expected_id, rt_uint8_t expected_cmd) +{ + if ((pMB == RT_NULL) || (pMB->payload == RT_NULL) || + (pMB->length < MIN_FRAME_LENGTH) || + (expected_idx >= TOTAL_NOR_CHS) || (expected_cmd == 0U)) { + return -1; + } + + if ((pMB->payload[2] != (rt_uint8_t)expected_id) || + (pMB->payload[3] != expected_cmd)) { + LOG_W("north response mismatch: ch=%d id=%02x/%02x cmd=%02x/%02x", + expected_idx + 1U, pMB->payload[2], expected_id, + pMB->payload[3], expected_cmd); + return -2; + } + + if ((expected_cmd == SRC_CMD_GET_VOLTAGE) && (pMB->length != 21U)) { + return -3; + } + if ((expected_cmd != SRC_CMD_GET_VOLTAGE) && + (expected_cmd != SRC_CMD_GET_PROTOCOL) && + (pMB->length != MIN_FRAME_LENGTH)) { + return -4; + } + return 0; +} + +/***************************************************************** +函数名称: chrg_roll_nor_free_response +函数描述: 释放北向邮箱回码及其payload +输入参数: pMB:待释放邮箱消息 +输出参数: - +返回说明: - +其它说明: 统一匹配成功、失配和手动模式下的消息释放路径 +*****************************************************************/ +static void chrg_roll_nor_free_response(struct mb_msg_t *pMB) +{ + if (pMB == RT_NULL) { + return; + } + if (pMB->payload != RT_NULL) { + rt_free(pMB->payload); + } + rt_free(pMB); +} + +/***************************************************************** +函数名称: chrg_roll_nor_drain_mailbox +函数描述: 非阻塞清理上一事务遗留在北向邮箱中的回码 +输入参数: pTN:北向接收线程信息 +输出参数: - +返回说明: - +其它说明: 必须在发送新请求前调用,避免旧回码被误认为本次请求回码 +*****************************************************************/ +static void chrg_roll_nor_drain_mailbox(struct chrg_thread_t *pTN) +{ + struct mb_msg_t *pMB = RT_NULL; + + if ((pTN == RT_NULL) || (pTN->mb == RT_NULL)) { + return; + } + while (rt_mb_recv(pTN->mb, (rt_uint32_t *)&pMB, 0) == RT_EOK) { + chrg_roll_nor_free_response(pMB); + pMB = RT_NULL; + } +} + +/***************************************************************** +函数名称: chrg_roll_nor_wait_response +函数描述: 在统一截止时间内等待与当前角色和命令匹配的北向回码 +输入参数: pTN:北向接收线程 expected_idx:请求通道 expected_id:请求角色 + expected_cmd:请求命令 matched_response:匹配回码输出指针 +输出参数: matched_response返回匹配消息,所有权交给调用者 +返回说明: 0:收到匹配回码 <0:参数错误或等待超时 +其它说明: 不匹配但CRC正确的旧回码会释放,并继续等待至本次总超时 +*****************************************************************/ +static int chrg_roll_nor_wait_response(struct chrg_thread_t *pTN, + rt_uint8_t expected_idx, eIDX_ID expected_id, rt_uint8_t expected_cmd, + struct mb_msg_t **matched_response) +{ + rt_tick_t start_tick; + rt_tick_t timeout_tick; + rt_tick_t elapsed; + rt_tick_t remaining; + rt_err_t ret; + struct mb_msg_t *pMB = RT_NULL; + + if ((pTN == RT_NULL) || (pTN->mb == RT_NULL) || + (matched_response == RT_NULL)) { + return -1; + } + *matched_response = RT_NULL; + start_tick = rt_tick_get(); + timeout_tick = rt_tick_from_millisecond(NORTH_RESPONSE_TIMEOUT_MS); + + while (1) { + elapsed = (rt_tick_t)(rt_tick_get() - start_tick); + if (elapsed >= timeout_tick) { + return -1; + } + remaining = timeout_tick - elapsed; + ret = rt_mb_recv(pTN->mb, (rt_uint32_t *)&pMB, remaining); + if (ret != RT_EOK) { + return -1; + } + if (pMB == RT_NULL) { + continue; + } + + if (chrg_roll_nor_response_matches(pMB, expected_idx, + expected_id, expected_cmd) == 0) { + *matched_response = pMB; + return 0; + } + + /* CRC正确但不属于本次请求的旧回码不能结束当前等待。 */ + chrg_roll_nor_free_response(pMB); + pMB = RT_NULL; + } +} +/***************************************************************** +函数名称: chrg_roll_nor_cmd +函数描述: 执行北向手动插入命令的入口检查 +输入参数: pROLL:北向轮询控制结构体 +输出参数: - +返回说明: 0:参数有效 <0:参数为空 +其它说明: 当前仅保留入口,具体手动命令由后续功能扩展 +*****************************************************************/ static int chrg_roll_nor_cmd (struct chrg_rollnor_t *pROLL) { if (RT_NULL == pROLL) { @@ -324,71 +707,213 @@ static int chrg_roll_nor_cmd (struct chrg_rollnor_t *pROLL) } return 0; } -void chrg_roll_nor_set(rt_uint8_t ch, struct chrg_switch_t *pSW,rt_uint8_t *enable) +/***************************************************************** +函数名称: chrg_roll_nor_set +函数描述: 按当前角色和sub状态发送一条北向协议命令 +输入参数: ch:北向通道编号 pSW:通道控制结构体 next_sub:候选下一状态输出指针 +输出参数: next_sub保存正确回码后应进入的状态,不直接修改pSW->sub +返回说明: >0:发送长度 <0:发送失败或状态不支持 + 其它说明: Source按差异执行协议、CC或组合命令;涉及PD的运行中切换先发送CC_CLOSE; + 停止状态启动和主动停止均不额外发送CC_CLOSE; + Sink完整握手依次执行协议设置、目标CC线选择、稳定等待和VC查询 +*****************************************************************/ +static int chrg_roll_nor_set(rt_uint8_t ch, struct chrg_switch_t *pSW, + rt_uint8_t *next_sub) { + if ((pSW == RT_NULL) || (next_sub == RT_NULL)) { + return -1; + } + *next_sub = pSW->sub; + if(pSW->id==ID_SOURCE){ //电源 chrg_roll_nor_sw_uart((eIDX_NOR_CH)ch, ID_SOURCE); - switch(pSW->sub){ - case IDX_SET_SOURCE_ID: //角色 - chrg_set_eload_source(ch,pSW); - if(pSW->continue_flag) - pSW->sub = IDX_SET_SOURCE_CC_SET; -// rt_kprintf("set_start\r\n"); - break; - case IDX_SET_SOURCE_CC_SET: - source_set_cc_choose(pSW->source.cc_set); - pSW->sub = IDX_SET_SOURCE_CV; - break; - case IDX_SET_SOURCE_CV: //恒压恒流 - pSW->sub = IDX_SER_SOURCE_VOLT; - break; - case IDX_SER_SOURCE_VOLT://电压 - chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_VOLTAGE_OUT+1, 0); - pSW->sub = IDX_SER_SOURCE_CURR; - break; - case IDX_SER_SOURCE_CURR://电流 - chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_CURRENT_OUT+1, pSW->source.protective_curr); - pSW->sub = IDX_SET_SOURCE_WORK; - break; - case IDX_SET_SOURCE_WORK: - chrg_sou_work(ch,pSW); - break; + switch(pSW->sub){ + case IDX_SET_SOURCE_RESET: + *next_sub = IDX_SET_SOURCE_PD; + return source_reset(); case IDX_SET_SOURCE_PD: - chrg_roll_nor_source_pro_set(pSW); - pSW->source.On_Flag = 1; - break; + *next_sub = IDX_WAIT_SOURCE_PROTOCOL; + return chrg_roll_nor_source_pro_set(pSW); + case IDX_SET_SOURCE_CC_SET: + *next_sub = IDX_GET_SOURCE_VC; + return source_set_cc_choose(pSW->source.CC_Set); case IDX_GET_SOURCE_VC: - source_get_vc(pSW->source.vc.set_voltage, pSW->source.vc.set_current); - break; - case IDX_SET_SOURCE_CCLVL: - source_set_cc_lvl(pSW->source.vol_lvl); - break; - case IDX_SET_SOURCE_STOP_STATE: - chrg_nor_source_stop(pSW,ch,enable); - break; + return source_get_vc(pSW->source.set_volt, + pSW->source.set_curr); + case IDX_GET_SOURCE_PD: + *next_sub = IDX_GET_SOURCE_VC; + return source_get_pd(); default: - - break; + return -1; } - }else{ + } + else if (pSW->id == ID_SINK) { chrg_roll_nor_sw_uart((eIDX_NOR_CH)ch, ID_SINK); switch(pSW->sub){ case IDX_GET_SINK_VC: //查询 - sink_get_vc(pSW->sink.pro_loadv,pSW->sink.pro_loadc); - break; + return sink_get_vc(pSW->sink.pro_loadv, + pSW->sink.pro_loadc); + case IDX_GET_SINK_PD: + *next_sub = IDX_GET_SINK_VC; + return sink_get_pd(); case IDX_SET_SINK_CCLINE: //CC线选择 - sink_set_cc_line(pSW->sink.cc_set); - pSW->sub = IDX_SET_SINK_PD; - break; + *next_sub = IDX_WAIT_SINK_CC; + return sink_set_cc_line(pSW->sink.cc_set); + case IDX_SET_SINK_CC_BEFORE_PD: + *next_sub = IDX_WAIT_SINK_CC_BEFORE_PD; + return sink_set_cc_line(pSW->sink.cc_set); case IDX_SET_SINK_CCLVL: //CC电平 - sink_set_cc_lvl(pSW->sink.cc_lvl); - break; + return sink_set_cc_lvl(pSW->sink.cc_lvl); case IDX_SET_SINK_PD: - chrg_roll_nor_sink_pro_set(pSW); - pSW->sub = IDX_GET_SINK_VC; - break; + /* 只有协议回码确认后,下一轮才允许进入电压查询。 */ + *next_sub = IDX_WAIT_SINK_PD_ACK; + return chrg_roll_nor_sink_pro_set(pSW); + case IDX_SET_SINK_PD_ONLY: + *next_sub = IDX_WAIT_SINK_PD_ACK; + return chrg_roll_nor_sink_pro_set(pSW); +#if 0 + case IDX_SET_SINK_PD_BEFORE_CC: + /* 协议回码确认后再设置CC,不提前查询电压。 */ + *next_sub = IDX_SET_SINK_CCLINE; + return chrg_roll_nor_sink_pro_set(pSW); +#endif + default: + return -1; + } + } + + return -1; +} + +/***************************************************************** +函数名称: chrg_roll_nor_auto_request +函数描述: 执行一次自动北向请求事务,并在超时后有限次数重发 +输入参数: idx:通道 pSW:通道状态 pTHR:轮询线程 pTN:北向接收线程 + pNOR:北向共享状态 +输出参数: - +返回说明: 0:请求成功 1:等待期间COM配置变化,旧事务已取消 <0:重试后失败 +其它说明: 匹配回码有效且配置版本未变化时解析量测值并提交next_sub; + 发送时不修改实时sub,CRC错误帧由north.c丢弃并表现为本次等待超时 +*****************************************************************/ +static int chrg_roll_nor_auto_request(rt_uint8_t idx, + struct chrg_switch_t *pSW, struct chrg_thread_t *pTHR, + struct chrg_thread_t *pTN, struct chrg_north_t *pNOR) +{ + rt_bool_t advance_sub = RT_TRUE; + rt_uint8_t attempt; // 当前发送序号:0为首次,1~2为重发 + rt_uint8_t request_sub = 0; // 本次请求实际使用的sub快照 + rt_uint8_t next_sub = 0; // 正确回码后准备提交的候选sub + rt_uint8_t expected_cmd = 0; // 本次请求期望收到的命令码 + rt_uint8_t max_attempts; // 本次请求允许的总发送次数 + eIDX_ID expected_id = ID_SINK; // 本次请求发送时的角色快照 + rt_uint32_t request_version = 0; // 本次请求发送时的COM配置版本 + int send_ret; // 当前命令的串口发送结果 + int wait_ret; // 当前命令的回码等待结果 + struct mb_msg_t *pMB = RT_NULL; // 已匹配回码,处理后统一释放 + + if ((idx >= TOTAL_NOR_CHS) || (pSW == RT_NULL) || + (pTHR == RT_NULL) || (pTN == RT_NULL) || (pNOR == RT_NULL)) { + return -1; + } + + /* 默认保留设置命令的3次发送;首轮取得实际命令码后,0x20会改为 + * 单次发送并留给下一轮轮询重试,避免量测无回码时长期占用通道。 */ + max_attempts = NORTH_REQUEST_MAX_RETRIES + 1U; + for (attempt = 0; attempt < max_attempts; attempt++) { + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + if (attempt == 0U) { + /* 首次发送固定请求状态,后续重发必须继续使用同一份请求上下文。 */ + request_sub = pSW->sub; + expected_id = pSW->id; + expected_cmd = chrg_roll_nor_expected_cmd(pSW); + if (expected_cmd == SRC_CMD_GET_VOLTAGE) { + max_attempts = NORTH_MEASURE_MAX_ATTEMPTS; + } + request_version = pNOR->config_version[idx]; + /* 新事务发送前清理已结束或已超时事务遗留的邮箱回码。 */ + chrg_roll_nor_drain_mailbox(pTN); + } else if ((pNOR->config_version[idx] != request_version) || + (pSW->id != expected_id) || (pSW->sub != request_sub)) { + /* COM或其它控制入口已修改配置,禁止继续重发旧命令。 */ + rt_mutex_release(pTHR->mutex); + return 1; + } + + if (expected_cmd == 0U) { + rt_mutex_release(pTHR->mutex); + return -1; + } + send_ret = chrg_roll_nor_set(idx, pSW, &next_sub); + rt_mutex_release(pTHR->mutex); + + if (send_ret > 0) { + wait_ret = chrg_roll_nor_wait_response(pTN, idx, + expected_id, expected_cmd, &pMB); + if ((wait_ret == 0) && (pMB != RT_NULL)) { + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + if ((pNOR->config_version[idx] != request_version) || + (pSW->id != expected_id) || (pSW->sub != request_sub)) { + /* 回码到达前配置已变化,该回码属于旧事务,只释放不解析。 */ + rt_mutex_release(pTHR->mutex); + chrg_roll_nor_free_response(pMB); + return 1; + } + + /* 只有固定21字节的0x20量测回码需要进入Source/Sink业务解析。 */ + if ((expected_cmd == SRC_CMD_GET_VOLTAGE) && + (pMB->length == 21U)) { + if (expected_id == ID_SOURCE) { + (void)chrg_source_parse(pMB->payload, + (eIDX_SOU_CH)idx, pSW, pNOR); + } else { + chrg_sink_parse(pMB->payload, + (eIDX_SOU_CH)idx, pSW, pNOR); + } + } + /* ID、命令、CRC和配置版本均确认有效后,才正式推进实时sub。 */ + if ((expected_id == ID_SOURCE) && + (request_sub == IDX_SET_SOURCE_RESET)) { + /* 复位回码后重新执行Source INIT,初始化完成后再推进到PD。 */ + pSW->source.work_mode = SOURCE_WORK_INIT; + advance_sub = RT_FALSE; + /* INIT完成前保持当前sub,禁止通用逻辑提前推进到PD。 */ + } else if ((expected_id == ID_SOURCE) && + (request_sub == IDX_SET_SOURCE_PD)) { + /* 0xD0应答后每通道独立等待100ms,再连接目标CC。 */ + chrg_roll_nor_source_protocol_wait_start(idx); + chrg_comm_source_protocol_ack(idx, + (rt_uint8_t)pSW->source.protocol); + } else if ((expected_id == ID_SINK) && + ((request_sub == IDX_SET_SINK_CCLINE) || + (request_sub == IDX_SET_SINK_CC_BEFORE_PD))) { + /* 北向Sink继电器保持接通,仅等待目标CC配置稳定。 */ + chrg_roll_nor_sink_cc_wait_start(idx); + } else if ((expected_id == ID_SINK) && + (request_sub == IDX_SET_SINK_PD_ONLY)) { + /* 仅重发协议时没有CC回码作为节流入口,直接启动200ms计时。 */ + chrg_roll_nor_sink_query_wait_start(idx); + } + /* 业务解析可能主动切换sub,例如掉电恢复后要求重新设置协议。 + * 只有sub仍属于本次请求时,才应用请求函数计算出的next_sub。 */ + if (advance_sub && (pSW->sub == request_sub)) { + pSW->sub = next_sub; + } + rt_mutex_release(pTHR->mutex); + chrg_roll_nor_free_response(pMB); + return 0; } } + + if ((attempt + 1U) < max_attempts) { + /* 本次发送失败或等待超时,保持request_sub并准备重发同一命令。 */ + rt_thread_mdelay(NORTH_RETRY_INTERVAL_MS); + } + } + + LOG_W("north request failed: ch=%d id=%02x cmd=%02x sub=%d attempts=%d", + idx + 1U, expected_id, expected_cmd, request_sub, + max_attempts); + return -1; } /***************************************************************** @@ -408,7 +933,10 @@ void chrg_roll_nor_thread_entry (void *data) } rt_uint8_t index = 0, idx = 0, i = 0; - rt_uint8_t run = 0, crc = 0; + rt_uint8_t run = 0; + rt_bool_t source_waiting = RT_FALSE; + rt_bool_t sink_waiting = RT_FALSE; + int state_work_ret = 0; rt_err_t ret = RT_EOK; struct chrg_rollnor_t *pROLL = &chrgrollnor; struct chrg_thread_t *pTN = &chrgthr[IDX_THR_NORTH]; @@ -419,15 +947,67 @@ void chrg_roll_nor_thread_entry (void *data) chrg_roll_nor_sw_uart((eIDX_NOR_CH)i, ID_SINK); } while (1) { - chrg_wdt_feed(); + chrg_wdt_feed(); rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); run = pROLL->run; rt_mutex_release(pTHR->mutex); switch (run) { case RUN_AUTO: { idx = index%TOTAL_NOR_CHS; + pSW = &pNOR->sw[idx]; + source_waiting = RT_FALSE; + sink_waiting = RT_FALSE; + state_work_ret = 0; + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + /* + * INIT/STOP必须在enable过滤前执行:STOP会把enable清零,而COM + * 启动时只改变work_mode。若先判断enable,停机通道会直接continue, + * INIT永远没有机会把enable重新置1,也就无法恢复北向轮询。 + * Source->Sink切换后的SINK_WORK_INIT同样从这里首次执行。 + */ + if ((pSW->id == ID_SOURCE) && + ((pSW->source.work_mode == SOURCE_WORK_INIT) || + (pSW->source.work_mode == SOURCE_WORK_STOP) || + (pSW->source.work_mode == SOURCE_WORK_PROTOCOL_SWITCH) || + (pSW->source.work_mode == SOURCE_WORK_FULL_RESET) || + (pSW->source.work_mode == SOURCE_WORK_FULL_RESET_WAIT) || + (pSW->source.work_mode == SOURCE_WORK_FULL_RESET_RELAY) || + (pSW->source.work_mode == SOURCE_WORK_FULL_RESET_RELAY_WAIT) || + (pSW->source.work_mode == SOURCE_WORK_INIT_WAIT))) { + state_work_ret = chrg_source_work_state(idx, pSW); + } else if ((pSW->id == ID_SINK) && + ((pSW->sink.work_mode == SINK_WORK_INIT) || + (pSW->sink.work_mode == SINK_WORK_INIT_AUX_WAIT) || + (pSW->sink.work_mode == SINK_WORK_UNLOAD_WAIT) || + (pSW->sink.work_mode == SINK_WORK_LOW) || + (pSW->sink.work_mode == SINK_WORK_LOW_AUX_WAIT) || + (pSW->sink.work_mode == SINK_WORK_HANDSHAKE_PREPARE) || + (pSW->sink.work_mode == SINK_WORK_HANDSHAKE_WAIT) || + (pSW->sink.work_mode == SINK_WORK_STOP_WAIT) || + (pSW->sink.work_mode == SINK_WORK_STOP_RELAY) || + (pSW->sink.work_mode == SINK_WORK_STOP_RELAY_WAIT))) { + state_work_ret = chrg_sink_work((eIDX_SOU_CH)idx, pSW); + } + /* + * 协议切换等待只推进时间状态,不发送串口命令。该检查位于enable + * 过滤前,使停止或角色切换后的通道也能及时清理残留事务记录。 + */ + source_waiting = chrg_roll_nor_source_wait_process(idx, pSW); + sink_waiting = chrg_roll_nor_sink_wait_process(idx, pSW); + rt_mutex_release(pTHR->mutex); + if (state_work_ret != 0) { + /* 南向状态动作未成功入队时保留原状态,下轮重试且禁止北向先行。 */ + CHRG_CHANNEL_NEXT(index); + rt_thread_mdelay(2); + continue; + } + if (source_waiting || sink_waiting) { + CHRG_CHANNEL_NEXT(index); + rt_thread_mdelay(2); + continue; + } if (0 == pNOR->enable[idx]) { - index++; + CHRG_CHANNEL_NEXT(index); rt_thread_mdelay(2); continue; } else { @@ -435,17 +1015,12 @@ void chrg_roll_nor_thread_entry (void *data) chrg_north_switch((eIDX_NOR_CH)idx); chrgnorth.idx = idx; pROLL->idx = idx; - pSW = &pNOR->sw[idx]; - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); - if(pNOR->north_mode == 0){ - chrg_roll_nor_set(pROLL->idx,pSW,&pNOR->enable[idx]); - } - if(pNOR->north_mode == 1){ - chrg_roll_trim_set(pROLL->idx,pNOR); - } - rt_mutex_release(pTHR->mutex); + (void)chrg_roll_nor_auto_request(idx, pSW, + pTHR, pTN, pNOR); } - index++; + CHRG_CHANNEL_NEXT(index); + rt_thread_mdelay(NORTH_POLL_INTERVAL_MS); + continue; } break; @@ -469,32 +1044,19 @@ void chrg_roll_nor_thread_entry (void *data) break; } - ret = rt_mb_recv(pTN->mb, (rt_uint32_t *)&pMB_R, 200); // 超时 200ms + ret = rt_mb_recv(pTN->mb, (rt_uint32_t *)&pMB_R, 50); // 1kHz tick下超时约50ms if ((RT_EOK == ret)&&(RT_NULL != pMB_R)) { // 存在返回数据 - crc = calc_crc8(pMB_R->payload, pMB_R->length-1); - if (crc == pMB_R->payload[pMB_R->length-1]) { // 数据正确 - if (RUN_AUTO != run) { // 恢复自动轮巡 - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); - pROLL->run = RUN_AUTO; - rt_mutex_release(pTHR->mutex); - } else { - chrg_roll_nor_parse(pROLL, pMB_R->payload); - } - } else { - LOG_E("crc failed."); + /* north.c只会向邮箱提交已经通过结构和CRC校验的完整帧。 */ + if (RUN_AUTO != run) { // 恢复自动轮巡 + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + pROLL->run = RUN_AUTO; + rt_mutex_release(pTHR->mutex); } - if (RT_NULL != pMB_R->payload) { - rt_free(pMB_R->payload); - pMB_R->payload = RT_NULL; - } + chrg_roll_nor_free_response(pMB_R); + pMB_R = RT_NULL; - if (RT_NULL != pMB_R) { - rt_free(pMB_R); - pMB_R = RT_NULL; - } - - rt_thread_mdelay(20); // 间隔 200ms + rt_thread_mdelay(10); // 相邻北向请求间隔50ms } else { if (RUN_AUTO != run) { // 非自动轮巡 rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); @@ -503,9 +1065,7 @@ void chrg_roll_nor_thread_entry (void *data) rt_mutex_release(pTHR->mutex); } } - } - } #if 1 @@ -704,171 +1264,3 @@ MSH_CMD_EXPORT(source, north source test); #endif -//校准CRC计算 -rt_uint16_t chrg_trim_crc(rt_uint8_t ch, struct chrg_north_t *pNOR) -{ - struct chrg_trim *pTrim = &pNOR->trim[ch]; - rt_uint8_t buf[22]; - int idx = 0; - - buf[idx++] = (pTrim->trim_volt_k_H >> 8) & 0xFF; - buf[idx++] = pTrim->trim_volt_k_H & 0xFF; - - buf[idx++] = (pTrim->trim_volt_k_L >> 8) & 0xFF; - buf[idx++] = pTrim->trim_volt_k_L & 0xFF; - - buf[idx++] = (pTrim->trim_volt_b >> 8) & 0xFF; - buf[idx++] = pTrim->trim_volt_b & 0xFF; - - buf[idx++] = (pTrim->trim_curr_k_H >> 8) & 0xFF; - buf[idx++] = pTrim->trim_curr_k_H & 0xFF; - - buf[idx++] = (pTrim->trim_curr_k_L >> 8) & 0xFF; - buf[idx++] = pTrim->trim_curr_k_L & 0xFF; - - buf[idx++] = (pTrim->trim_curr_b >> 8) & 0xFF; - buf[idx++] = pTrim->trim_curr_b & 0xFF; - - buf[idx++] = (pTrim->trim_source_curr_k_H >> 8) & 0xFF; - buf[idx++] = pTrim->trim_source_curr_k_H & 0xFF; - - buf[idx++] = (pTrim->trim_source_curr_k_L >> 8) & 0xFF; - buf[idx++] = pTrim->trim_source_curr_k_L & 0xFF; - - buf[idx++] = (pTrim->trim_source_curr_b >> 8) & 0xFF; - buf[idx++] = pTrim->trim_source_curr_b & 0xFF; - - buf[idx++] = (pTrim->trim_zero >> 8) & 0xFF; - buf[idx++] = pTrim->trim_zero & 0xFF; - - return mb_crc16(buf, 20); -} -//电源校准设置 -void chrg_trim_set_volt(rt_uint8_t ch,struct chrg_north_t *pNOR){ - struct chrg_trim *pTRIM = &pNOR->trim[ch]; - switch(pTRIM->trim_step){ - case 0: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Volt_CAL_K_H, pTRIM->trim_volt_k_H); - break; - case 1: - break; - case 2: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Volt_CAL_K_L, pTRIM->trim_volt_k_L); - break; - case 3: - break; - case 4: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Volt_CAL_B, pTRIM->trim_volt_b); - break; - pNOR->north_mode = 0; - break; - } - pTRIM->trim_step++; -} -//电流校准设置 -void chrg_trim_set_curr(rt_uint8_t ch,struct chrg_north_t *pNOR){ - struct chrg_trim *pTRIM = &pNOR->trim[ch]; - switch(pTRIM->trim_step){ - case 0: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Current_CAL_K_H, pTRIM->trim_curr_k_H); - break; - case 1: - break; - case 2: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Current_CAL_K_L, pTRIM->trim_curr_k_L); - break; - case 3: - break; - case 4: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Current_CAL_B, pTRIM->trim_curr_b); - pNOR->north_mode = 0; - break; - } - pTRIM->trim_step++; -} -//电源校准设置 -void chrg_trim_set_source_curr(rt_uint8_t ch,struct chrg_north_t *pNOR){ - struct chrg_trim *pTRIM = &pNOR->trim[ch]; - rt_uint16_t crc = 0; - switch(pTRIM->trim_step){ - case 0: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_SourceCurrent_CAL_K_H, pTRIM->trim_source_curr_k_H); - break; - case 1: - break; - case 2: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_SourceCurrent_CAL_K_L, pTRIM->trim_source_curr_k_L); - break; - case 3: - break; - case 4: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_SourceCurrent_CAL_B, pTRIM->trim_source_curr_b); - break; - case 5: - break; - case 6: - crc = chrg_trim_crc(ch, pNOR); - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Trim_CRC_CAL, crc); - rt_kprintf("crc=%04X\n",crc); - break; - case 7: - break; - case 8: - if(pTRIM->trim_enable_flag == 0){ - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_CAL_WriteEnable_ADD,ModbusRTU_CAL_WriteValue); - }else{ - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_CAL_WriteRecovery,ModbusRTU_CAL_WriteRecovery_Value); - } - pNOR->north_mode = 0; - break; - } - pTRIM->trim_step++; -} -//校准飘零设置 -void chrg_trim_set_zero(rt_uint8_t ch,struct chrg_north_t *pNOR){ - struct chrg_trim *pTRIM = &pNOR->trim[ch]; - rt_uint16_t crc = 0; - switch(pTRIM->trim_step){ - case 0: - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Trim_Zero_CAL, pTRIM->trim_zero); - break; - case 1: - break; - case 2: - crc = chrg_trim_crc(ch, pNOR); - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_Trim_CRC_CAL, crc); - break; - case 3: - break; - case 4: - if(pTRIM->trim_enable_flag == 0){ - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_CAL_WriteEnable_ADD,ModbusRTU_CAL_WriteValue); - }else{ - chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_CAL_WriteRecovery,ModbusRTU_CAL_WriteRecovery_Value); - } - pNOR->north_mode = 0; - break; - } - pTRIM->trim_step++; -} -//校准设置 -void chrg_roll_trim_set(rt_uint8_t ch, struct chrg_north_t *pNOR){ - struct chrg_trim *pTRIM = &pNOR->trim[ch]; - switch(pTRIM->trim_mode){ - case MODE_VOLT_CAL: - chrg_trim_set_volt(ch, pNOR); - break; - case MODE_CURR_CAL: - chrg_trim_set_curr(ch, pNOR); - break; - case MODE_SOURCE_CURR_CAL: - chrg_trim_set_source_curr(ch, pNOR); - break; - case MODE_ZERO_CAL: - chrg_trim_set_zero(ch, pNOR); - break; - } -} - - - diff --git a/apps/chrg/applications/thread/chrg_roll_nor.h b/apps/chrg/applications/thread/chrg_roll_nor.h index 8fbc669..5e638a3 100644 --- a/apps/chrg/applications/thread/chrg_roll_nor.h +++ b/apps/chrg/applications/thread/chrg_roll_nor.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_roll_nor.h 完成日期 : -当前版本号 : V1.0 +当前版本号 : V1.1.9 主要功能 : 实现北向通信轮巡请求命令。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -41,6 +41,20 @@ enum { IDX_SET_SINK_WORK_STATE, IDX_SET_SINK_STOP_TEST,//停止负载测试 IDX_SET_SINK_STORP_WAIT, + + /* Source北向事务状态,追加在末尾以保持原有sub数值不变。 */ + IDX_SET_SOURCE_RESET, // PD与非PD协议跨类切换前发送Source复位 + IDX_WAIT_SOURCE_PROTOCOL, // 0xD0应答后的协议生效等待 + + /* Sink北向事务状态追加在末尾,避免改变已有sub数值。 */ + IDX_SET_SINK_PD_ONLY, // 仅重发协议,协议应答后直接查询协商电压 + IDX_WAIT_SINK_CC, // CC设置应答后的非阻塞稳定等待 + IDX_SET_SINK_CC_BEFORE_PD, // 先设置CC,稳定后再设置协议 + IDX_WAIT_SINK_CC_BEFORE_PD, // 等待CC稳定后进入协议设置 + IDX_WAIT_SINK_PD_ACK, // 协议回码确认后的非发送过渡状态 +#if 0 + IDX_SET_SINK_PD_BEFORE_CC, // 先设置协议,协议回码后再设置CC +#endif }; struct chrg_rollnor_t { @@ -80,8 +94,26 @@ extern struct chrg_rollnor_t chrgrollnor; extern void chrg_roll_nor_thread_entry (void *data); extern int sink_fill_pd_buf(rt_uint8_t group,rt_uint8_t pd_val, rt_uint16_t pd_volt, rt_uint16_t pd_curr, rt_uint8_t *buf); -extern void chrg_roll_trim_set(rt_uint8_t ch, struct chrg_north_t *pNOR); extern void roll_nor_set(void); + +/***************************************************************** +函数名称: chrg_roll_nor_source_cc_close_register +函数描述: 将Source通道登记到本轮共享CC_CLOSE等待屏障 +输入参数: ch:北向通道编号 +输出参数: - +返回说明: - +其它说明: 仅需要在协议切换入口为IDX_SET_SOURCE_CC_CLOSE时调用 +*****************************************************************/ +/***************************************************************** +函数名称: chrg_roll_nor_source_switch_reset +函数描述: 清理指定Source通道的CC_CLOSE及协议等待记录 +输入参数: ch:北向通道编号 +输出参数: - +返回说明: - +其它说明: 停止、角色切换或新配置覆盖旧切换状态时调用 +*****************************************************************/ +extern void chrg_roll_nor_source_switch_reset(rt_uint8_t ch); + #define sink_time 3 #endif diff --git a/apps/chrg/applications/thread/chrg_roll_sou.c b/apps/chrg/applications/thread/chrg_roll_sou.c index 94be810..4c91f93 100644 --- a/apps/chrg/applications/thread/chrg_roll_sou.c +++ b/apps/chrg/applications/thread/chrg_roll_sou.c @@ -1,373 +1,3 @@ -// /**************************************************************************** -// 文件名称 : chrg_roll_sou.c -// 完成日期 : -// 当前版本号 : V1.0 -// 主要功能 : 实现南向通信轮巡请求命令。以独立线程处理。 -// 版本历史 : 创建原始版本 -// 说明 : -// ******************************************************************************/ - -// #include -// #include -// #include - -// #include "chrg_def.h" -// #include "chrg_led.h" -// #include "chrg_thread.h" -// #include "chrg_roll_sou.h" -// #include "chrg_eload.h" -// #include "chrg_utils.h" - -// #define LOG_TAG "chrg.rollsou" -// #include - -// struct chrg_rollsou_t chrgrollsou = { -// .run = RUN_AUTO, -// .ch = IDX_SOU_CH1, -// .part = MB_R_PART1, -// .mode = 0, -// }; - -// #define SOU_CMD_QUEUE_SIZE 4 - -// struct chrg_sou_cmd_t { -// eIDX_SOU_CH ch; -// rt_uint16_t reg; -// rt_uint16_t value; -// }; - -// static struct chrg_sou_cmd_t sou_cmd_queue[SOU_CMD_QUEUE_SIZE]; -// static rt_uint8_t sou_cmd_head = 0; -// static rt_uint8_t sou_cmd_tail = 0; -// static rt_uint8_t sou_cmd_count = 0; - -// static void sou_cmd_enqueue(eIDX_SOU_CH ch, rt_uint16_t reg, rt_uint16_t value) -// { -// if (sou_cmd_count < SOU_CMD_QUEUE_SIZE) { -// sou_cmd_queue[sou_cmd_tail].ch = ch; -// sou_cmd_queue[sou_cmd_tail].reg = reg; -// sou_cmd_queue[sou_cmd_tail].value = value; -// sou_cmd_tail = (sou_cmd_tail + 1) % SOU_CMD_QUEUE_SIZE; -// sou_cmd_count++; -// } else { -// LOG_W("sou command queue full, drop command\n"); -// } -// } - -// static int sou_cmd_dequeue(struct chrg_sou_cmd_t *cmd) -// { -// if (sou_cmd_count == 0) { -// return -1; -// } - -// cmd->ch = sou_cmd_queue[sou_cmd_head].ch; -// cmd->reg = sou_cmd_queue[sou_cmd_head].reg; -// cmd->value = sou_cmd_queue[sou_cmd_head].value; -// sou_cmd_head = (sou_cmd_head + 1) % SOU_CMD_QUEUE_SIZE; -// sou_cmd_count--; -// return 0; -// } - -// static int sou_cmd_exists(void) -// { -// return sou_cmd_count > 0; -// } - -// /***************************************************************** -// 函数名称: chrg_set_sou_reg -// 函数描述: 南向通道设置寄存器请求命令 -// 输入参数: idx:通道 reg:寄存器地址 value:寄存器值 -// 输出参数: - -// 返回说明: - -// 其它说明: - -// *****************************************************************/ -// void chrg_set_sou_reg (eIDX_SOU_CH idx, rt_uint16_t reg, rt_uint16_t value) -// { -// struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; -// struct chrg_rollsou_t *pROLL = &chrgrollsou; - -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// sou_cmd_enqueue(idx, reg, value); -// pROLL->run = RUN_LCD; -// rt_mutex_release(pTHR->mutex); -// rt_kprintf("ch=:%d,reg=%02x,value=%02x\n",idx,reg,value); -// } - -// /***************************************************************** -// 函数名称: chrg_send_sou_upadata_data -// 函数描述: 南向通道升级命令 -// 输入参数: idx:通道 reg:寄存器地址 value:寄存器值 -// 输出参数: - -// 返回说明: - -// 其它说明: - -// *****************************************************************/ -// void chrg_send_sou_upadata_data (rt_uint8_t *buf,rt_uint16_t leng) -// { -// struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; -// struct chrg_rollsou_t *pROLL = &chrgrollsou; -// pROLL->updata_data = buf; -// pROLL->leng = leng; -// // rt_kprintf("idx=%d,reg=%02x,value=%02x\n",RUN_UPDATAidx,reg,value); -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// pROLL->run = RUN_UPDATA; -// rt_mutex_release(pTHR->mutex); -// } - -// ///***************************************************************** -// //函数名称: chrg_set_sou_more_reg -// //函数描述: 南向通道设置寄存器请求命令 -// //输入参数: idx:通道 reg:寄存器地址 value:寄存器值 value2:第二个寄存器值 -// //输出参数: - -// //返回说明: - -// //其它说明: - -// //*****************************************************************/ -// //void chrg_set_sou_more_reg (eIDX_SOU_CH idx, rt_uint16_t reg, rt_uint16_t value,rt_uint16_t value2) -// //{ -// // struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; -// // struct chrg_rollsou_t *pROLL = &chrgrollsou; -// // pROLL->more_reg_flag = 1; // 使用多寄存器标志位 -// // pROLL->ch = idx; -// // pROLL->reg = reg; -// // pROLL->value = value; -// // pROLL->value2 = value2; -// // rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// // pROLL->run = RUN_LCD; -// // rt_mutex_release(pTHR->mutex); -// //} -// /***************************************************************** -// 函数名称: chrg_roll_sou_thread_entry -// 函数描述: 南向通道轮巡请求数据线程体 -// 输入参数: *data: 本线程信息 -// 输出参数: - -// 返回说明: - -// 其它说明: - -// *****************************************************************/ -// rt_uint8_t aaaaaa = 0; -// void chrg_roll_sou_thread_entry (void *data) -// { -// struct chrg_thread_t *pTHR = (struct chrg_thread_t *)data; - -// if (RT_NULL == pTHR) { -// return ; -// } - -// rt_uint8_t run = 0; -// rt_uint8_t index = 0, idx = 0; -// rt_err_t ret = RT_EOK; -// struct chrg_rollsou_t *pROLL = &chrgrollsou; -// struct chrg_thread_t *pTS = &chrgthr[IDX_THR_SOUTH]; -// struct mb_msg_t *pMB_R = RT_NULL; -// rt_uint8_t number = 0; -// while (1) { -// /* 每轮先非阻塞排空积压消息,防止邮箱满导致 south 线程永久阻塞 */ -// { -// rt_uint8_t drain_cnt = 0; -// struct mb_msg_t *pDrain = RT_NULL; -// while (drain_cnt < 8) { -// ret = rt_mb_recv(pTS->mb, (rt_uint32_t *)&pDrain, 0); -// if (ret != RT_EOK) break; -// if (pDrain != RT_NULL) { -// if (pDrain->payload != RT_NULL) rt_free(pDrain->payload); -// rt_free(pDrain); -// } -// drain_cnt++; -// } -// } - -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// run = pROLL->run; -// rt_mutex_release(pTHR->mutex); -// switch (run) { -// // rt_kprintf("-------- ROLL SOU TIME START \n --------"); -// case RUN_AUTO: { -// if(pROLL->mode!=0){ - -// }else{ -// idx = index%(TOTAL_SOU_CHS); // [0, 3] -// // rt_kprintf("start_idx = %d",idx); -// if (0 == chrgsouth.enable[idx]) { // 通道未启用 -// index++; -// rt_thread_mdelay(5); -// continue; -// } else { // 通道启用 -// chrg_led_flashing(IDX_LED3, TIMES_ONE, PULSE_TIME*2, PULSE_TIME*2); -// if (idx != (rt_uint8_t)pROLL->idx) { -// pROLL->idx = (eIDX_SOU_CH)idx; -// chrg_south_switch((eIDX_SOU_CH)idx); -// pROLL->part = MB_R_PART1; -// } -// chrg_eload_refresh(ID_ELOAD, pROLL->part); -// if (pROLL->part < TOTAL_MB_R) { -// //pROLL->part++; -// //if (pROLL->part > MB_R_PART1) { // 仅1条,若需轮巡全部,注释此条件 -// index++; -// //} -// } else { -// index++; -// } -// } -// } -// } -// break; - -// case RUN_LCD: { -// struct chrg_sou_cmd_t cmd; -// rt_uint8_t do_cmd = 0; -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// if (sou_cmd_dequeue(&cmd) == 0) { -// do_cmd = 1; -// pROLL->ch = cmd.ch; -// } -// rt_mutex_release(pTHR->mutex); -// if (do_cmd) { -// chrg_south_switch(cmd.ch); -// chrg_eload_write_reg(ID_ELOAD, cmd.reg, cmd.value); -// } -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// if (!sou_cmd_exists()) { -// pROLL->run = RUN_AUTO; -// } -// rt_mutex_release(pTHR->mutex); -// } -// break; - -// case RUN_CONSOLE: { -// chrg_south_switch((eIDX_SOU_CH)pROLL->ch); -// chrg_eload_refresh(ID_ELOAD, pROLL->part); -// } -// break; -// case RUN_UPDATA:{ -// chrg_south_switch((eIDX_SOU_CH)pROLL->ch); -// if(pROLL->send_flag==1){ -// chrg_eload_send_ymodem_data(pROLL->updata_data,pROLL->leng); -// pROLL->send_flag = 0; -// } -// /* 升级模式不下发Modbus邮件,等待south线程捕获ymodem应答后继续 */ -// rt_thread_mdelay(50); -// continue; -// } -// break; -// case RUN_COM: { -// chrg_south_switch((eIDX_SOU_CH)pROLL->ch); -// chrg_eload_write_reg(ID_ELOAD, pROLL->reg, pROLL->value); -// } -// break; -// case RUN_NONE: -// default: -// rt_thread_mdelay(5); -// continue; -// break; -// } - -// ret = rt_mb_recv(pTS->mb, (rt_uint32_t *)&pMB_R, 100 ); // 超时 200ms -// if ((RT_EOK == ret)&&(RT_NULL != pMB_R)) { // 存在返回数据 -// if (0 == mb_crc16(pMB_R->payload, pMB_R->length)) { -// if (RUN_AUTO != run) { // 恢复自动轮巡或继续执行待发命令 -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// pROLL->run = sou_cmd_exists() ? RUN_LCD : RUN_AUTO; -// rt_mutex_release(pTHR->mutex); -// } -// else { // 自动轮巡态,分析接收的数据 -// #if DEBUG_SOUTH -// rt_kprintf("length = %d",pMB_R->length); -// #endif -// if(pMB_R->length!=43){//31修改成43,一共19个寄存器*2+4,多读了4位 -// ret = rt_mb_recv(pTS->mb, (rt_uint32_t *)&pMB_R, 30); // 超时 200ms -// #if DEBUG_SOUTH -// rt_kprintf("ret = %d\n",ret); -// #endif -// if ((RT_EOK == ret)&&(RT_NULL != pMB_R)) { // 存在返回数据 -// chrg_eload_parse(idx, pROLL->part, pMB_R->payload, pMB_R->length); -// } -// } -// else chrg_eload_parse(idx, pROLL->part, pMB_R->payload, pMB_R->length); -// } -// } -// else { -// LOG_E("crc failed."); -// } - -// if (RT_NULL != pMB_R->payload) { -// rt_free(pMB_R->payload); -// pMB_R->payload = RT_NULL; -// } - -// if (RT_NULL != pMB_R) { -// rt_free(pMB_R); -// pMB_R = RT_NULL; -// } -// // rt_thread_mdelay(200); // 间隔 20ms -// rt_thread_mdelay(10); // 间隔 200ms -// } else { // 设备应答超时 -// if (RUN_AUTO != run) { // 非自动轮巡 -// rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); -// pROLL->run = sou_cmd_exists() ? RUN_LCD : RUN_AUTO; // 切换会自动轮巡或继续执行待发命令 -// rt_mutex_release(pTHR->mutex); -// } -// } -// // rt_kprintf("-------- ROLL SOU TIME END\n --------"); -// } -// } - -// #if 1 -// static int south_send(rt_uint8_t argc, char **argv) -// { -// int ch = 0, pt = 0; - -// if (3 != argc) { -// rt_kprintf("help : %s [0-3] [0-1]\r\n", argv[0]); -// return -1; -// } - -// ch = (rt_uint8_t)atoi(argv[1]); -// if ((ch < 0)||(ch >= TOTAL_SOU_CHS)) { -// rt_kprintf("help : %s [0-3] [0-1]\r\n", argv[0]); -// return -1; -// } - -// pt = (rt_uint8_t)atoi(argv[2]); -// if ((pt < 0)||(pt >= TOTAL_MB_R)) { -// rt_kprintf("help : %s [0-3] [0-1]\r\n", argv[0]); -// return -1; -// } - -// struct chrg_rollsou_t *pROLL = &chrgrollsou; -// pROLL->run = RUN_CONSOLE; -// pROLL->ch = (rt_uint8_t)ch; -// pROLL->part = (rt_uint8_t)pt; - - -// return 0; -// } - -// MSH_CMD_EXPORT(south_send, south send test); - -// static int south_run (int argc, char **argv) -// { -// if (2 != argc) { -// rt_kprintf("help : %s start|stop\r\n", argv[0]); -// return -1; -// } - -// struct chrg_rollsou_t *pROLL = &chrgrollsou; - -// if (rt_strcmp(argv[1], "start") == 0) { -// pROLL->run = RUN_AUTO; -// } else if (rt_strcmp(argv[1], "stop") == 0) { -// pROLL->run = RUN_NONE; -// } else { -// rt_kprintf("help : %s start|stop\r\n", argv[0]); -// return -1; -// } - -// return 0; -// } - -// MSH_CMD_EXPORT(south_run, south auto run); - -// #endif - - /**************************************************************************** 文件名称 : chrg_roll_sou.c 完成日期 : @@ -385,13 +15,35 @@ #include "chrg_led.h" #include "chrg_thread.h" #include "chrg_roll_sou.h" +#include "chrg_north.h" #include "chrg_eload.h" #include "chrg_utils.h" #define LOG_TAG "chrg.rollsou" #include +#define SOU_MUTEX_TIMEOUT_MS 100 +#define SOU_RESPONSE_TIMEOUT_MS 20 +#define SOU_COM_MAX_ATTEMPTS 3 +#define SOU_REL_SWITCH_DELAY_MS 100U +#define SOU_CHANNEL_BIT(ch) ((rt_uint8_t)(1U << (ch))) + chrg_rollsou_com_t g_sou_com_batch = {0}; +/* 一次性P1量测分别记录待发送请求和正在等待回码的请求。 + * 当前帧尚未结束时再次申请的下一帧保存在pending中,不能被当前帧 + * 的完成处理清除,否则多通道运行时低电流确认会间歇性停止推进。 */ +static rt_uint8_t g_sou_measure_once_pending_mask = 0U; +static rt_uint8_t g_sou_measure_once_active_mask = 0U; + +/* A channel enters normal measurement polling only after its version probe + * receives a valid response. A failed probe remains pending for the next + * round, which also covers the south board's slower power-up time. */ +static rt_uint8_t g_sou_version_ready[TOTAL_SOU_CHS] = {0U}; + +/* COM批量命令按通道轮询发送。一次写命令收到匹配回码后才移动游标; + * 回码错误或超时时游标保持不变,使同一条命令优先完成重发,避免破坏 + * 本通道批次内的寄存器执行顺序。 */ +static rt_uint8_t g_sou_com_batch_rr_next_ch = 0U; struct chrg_rollsou_t chrgrollsou = { .run = RUN_AUTO, @@ -399,31 +51,434 @@ struct chrg_rollsou_t chrgrollsou = { .part = MB_R_PART1, }; -//向通道批量缓存添加一条寄存器 -int chrg_sou_com_batch_add_reg(eIDX_SOU_CH ch, rt_uint16_t reg, rt_uint16_t val) -{ - if(ch >= TOTAL_SOU_CHS) return -1; - sou_com_batch_ch_t *pBatch = &g_sou_com_batch.ch[ch]; - if(pBatch->reg_cnt >= SOU_COM_BATCH_REG_MAX) - { - LOG_E("sou com batch reg full"); - return -2; - } - pBatch->reg_list[pBatch->reg_cnt] = reg; - pBatch->val_list[pBatch->reg_cnt] = val; - pBatch->reg_cnt++; - pBatch->pending = RT_TRUE; - return 0; -} -//启动批量下发(填充完影子结构体后调用) -void chrg_sou_com_sou_set(void) + +/***************************************************************** +函数名称: chrg_send_sou_upadata_data +函数描述: 缓存一帧升级数据并切换南向线程到升级发送状态 +输入参数: buf:升级帧数据 leng:升级帧长度 +输出参数: - +返回说明: 0:提交成功 -1:参数错误 -2:上一帧尚未发送 -3:互斥锁超时 +其它说明: 使用结构体内部数组保存数据,防止COM接收缓存变化导致升级帧损坏 +*****************************************************************/ +int chrg_send_sou_upadata_data(const rt_uint8_t *buf, rt_uint16_t leng) { struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; struct chrg_rollsou_t *pROLL = &chrgrollsou; - pROLL->run = RUN_COM; - + + if ((buf == RT_NULL) || (leng == 0) || (leng > sizeof(pROLL->updata_buf)) || + (pTHR->mutex == RT_NULL)) { + return -1; + } + if (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK) { + return -3; + } + if (pROLL->send_flag != 0) { + rt_mutex_release(pTHR->mutex); + return -2; + } + + memcpy(pROLL->updata_buf, buf, leng); + pROLL->leng = leng; + pROLL->updata_result = 0; + pROLL->send_flag = 1; + pROLL->run = RUN_UPDATA; + rt_mutex_release(pTHR->mutex); + return 0; } +//向通道批量缓存添加一条寄存器 +int chrg_sou_com_batch_submit(eIDX_SOU_CH ch, const rt_uint16_t *regs, + const rt_uint16_t *vals, rt_uint8_t count) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + struct chrg_rollsou_t *pROLL = &chrgrollsou; + sou_com_batch_ch_t *pBatch; + rt_uint16_t remaining; + rt_uint8_t i; + + if ((ch >= TOTAL_SOU_CHS) || (RT_NULL == regs) || (RT_NULL == vals) || + (count == 0) || (count > SOU_COM_BATCH_REG_MAX) || (RT_NULL == pTHR->mutex)) { + return -1; + } + if (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK) { + LOG_E("sou com batch lock timeout"); + return -3; + } + + pBatch = &g_sou_com_batch.ch[ch]; + if (pBatch->cur_idx > pBatch->reg_cnt) { + LOG_E("sou com batch index invalid: ch=%d cur=%d cnt=%d", + ch, pBatch->cur_idx, pBatch->reg_cnt); + pBatch->pending = RT_FALSE; + pBatch->cur_idx = 0; + pBatch->reg_cnt = 0; + pBatch->next_send_tick = 0U; + } + + /* + * cur_idx 之前的命令均已收到有效响应,追加新事务前先回收这些空间。 + * 否则快速连续写入时,即使大部分命令已经执行完成,也可能因为 + * reg_cnt 未清零而误报批队列已满。 + */ + if (pBatch->cur_idx > 0) { + remaining = pBatch->reg_cnt - pBatch->cur_idx; + if (remaining > 0) { + memmove(pBatch->reg_list, &pBatch->reg_list[pBatch->cur_idx], + remaining * sizeof(pBatch->reg_list[0])); + memmove(pBatch->val_list, &pBatch->val_list[pBatch->cur_idx], + remaining * sizeof(pBatch->val_list[0])); + } + pBatch->reg_cnt = remaining; + pBatch->cur_idx = 0; + } + + /* + * 电流调节采用最新值有效策略。若同一通道仍有尚未发送的电流命令, + * 直接覆盖旧值,避免四通道快速测试时队列积累过时的电流设置。 + * 当前命令已经发送时不覆盖,保证响应仍与原命令匹配。 + */ + if ((count == 1) && (regs[0] == (REG_CURRENT_OUT + 1))) { + for (i = pBatch->cur_idx; i < pBatch->reg_cnt; i++) { + if (pBatch->reg_list[i] == regs[0]) { + pBatch->val_list[i] = vals[0]; + pBatch->pending = RT_TRUE; + pROLL->run = RUN_COM; + rt_mutex_release(pTHR->mutex); + return 0; + } + } + } + + if ((pBatch->reg_cnt > SOU_COM_BATCH_REG_MAX) || + (count > (SOU_COM_BATCH_REG_MAX - pBatch->reg_cnt))) { + LOG_E("sou com batch reg full: ch=%d queued=%d append=%d max=%d", + ch, pBatch->reg_cnt, count, SOU_COM_BATCH_REG_MAX); + rt_mutex_release(pTHR->mutex); + return -2; + } + if (pBatch->reg_cnt == 0U) { + pBatch->next_send_tick = 0U; + } + + for (i = 0; i < count; i++) { + pBatch->reg_list[pBatch->reg_cnt] = regs[i]; + pBatch->val_list[pBatch->reg_cnt] = vals[i]; + pBatch->reg_cnt++; + } + pBatch->result = 0; + pBatch->pending = RT_TRUE; + pROLL->run = RUN_COM; + rt_mutex_release(pTHR->mutex); + return 0; +} + +/***************************************************************** +函数名称: chrg_sou_com_batch_is_pending +函数描述: 查询指定通道是否仍有COM南向批量命令等待执行或等待有效应答 +输入参数: ch:南向功率板通道 +输出参数: - +返回说明: RT_TRUE:批量事务未完成或查询互斥锁失败 RT_FALSE:批量事务已完成 +其它说明: 查询失败时按未完成处理,防止北向协议配置越过南向卸载命令提前执行 +*****************************************************************/ +rt_bool_t chrg_sou_com_batch_is_pending(eIDX_SOU_CH ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + rt_bool_t pending; + + if ((ch >= TOTAL_SOU_CHS) || (pTHR->mutex == RT_NULL) || + (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return RT_TRUE; + } + + pending = g_sou_com_batch.ch[ch].pending; + rt_mutex_release(pTHR->mutex); + return pending; +} + +/***************************************************************** +函数名称: chrg_sou_com_batch_get_result +函数描述: 获取指定通道最近一次南向COM批次的执行结果 +输入参数: ch:南向功率板通道 +输出参数: - +返回说明: 1:全部命令收到匹配回码 0:执行中/无结果 -1:失败取消 -2:参数或锁错误 +其它说明: pending清零既可能是成功也可能是失败,卸载确认必须通过本结果区分 +*****************************************************************/ +int chrg_sou_com_batch_get_result(eIDX_SOU_CH ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + int result; + + if ((ch >= TOTAL_SOU_CHS) || (pTHR->mutex == RT_NULL) || + (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -2; + } + result = g_sou_com_batch.ch[ch].result; + rt_mutex_release(pTHR->mutex); + return result; +} + +/***************************************************************** +函数名称: chrg_sou_measure_once_submit +函数描述: 登记指定通道的一次性P1量测优先请求 +输入参数: ch:南向功率板通道 +输出参数: 设置通道请求位 +返回说明: 0:登记成功或请求已存在 <0:参数或互斥锁错误 +其它说明: RUN_COM批次完成并切回RUN_AUTO后,南向线程会优先执行该查询 +*****************************************************************/ +int chrg_sou_measure_once_submit(eIDX_SOU_CH ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + + if ((ch >= TOTAL_SOU_CHS) || (pTHR->mutex == RT_NULL) || + (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } + g_sou_measure_once_pending_mask |= SOU_CHANNEL_BIT(ch); + rt_mutex_release(pTHR->mutex); + return 0; +} + +/***************************************************************** +函数名称: sou_measure_once_next +函数描述: 获取当前待执行的一次性P1量测通道 +输入参数: ch:输出通道指针 +输出参数: ch:最低编号的待查询通道 +返回说明: 1:存在请求 0:无请求 <0:参数或互斥锁错误 +其它说明: 请求位在本次查询完成或超时后清除,查询期间保持置位以防重复排队 +*****************************************************************/ +static int sou_measure_once_next(rt_uint8_t *ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + rt_uint8_t idx; + + if ((ch == RT_NULL) || (pTHR->mutex == RT_NULL) || + (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } + for (idx = 0; idx < TOTAL_SOU_CHS; idx++) { + rt_uint8_t bit = SOU_CHANNEL_BIT(idx); + + if ((g_sou_measure_once_pending_mask & bit) != 0U) { + /* 发送前从pending转移到active。查询等待期间的新申请会重新 + * 置pending位,并在当前查询结束后继续执行。 */ + g_sou_measure_once_pending_mask &= (rt_uint8_t)~bit; + g_sou_measure_once_active_mask |= bit; + *ch = idx; + rt_mutex_release(pTHR->mutex); + return 1; + } + } + rt_mutex_release(pTHR->mutex); + return 0; +} + +/***************************************************************** +函数名称: sou_measure_once_finish +函数描述: 结束指定通道的一次性P1量测请求 +输入参数: ch:南向功率板通道 +输出参数: 清除对应请求位 +返回说明: - +其它说明: 有效回码和超时都会结束本次请求;负载状态机按量测序号决定是否重查 +*****************************************************************/ +static void sou_measure_once_finish(rt_uint8_t ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + + if ((ch >= TOTAL_SOU_CHS) || (pTHR->mutex == RT_NULL) || + (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return; + } + /* 只结束当前正在等待的查询;等待期间登记的下一帧pending请求必须保留。 */ + g_sou_measure_once_active_mask &= (rt_uint8_t)~SOU_CHANNEL_BIT(ch); + rt_mutex_release(pTHR->mutex); +} + +int chrg_sou_com_batch_add_reg(eIDX_SOU_CH ch, rt_uint16_t reg, rt_uint16_t val) +{ + return chrg_sou_com_batch_submit(ch, ®, &val, 1); +} + +/***************************************************************** +函数名称: chrg_sou_trim_crc +函数描述: 按功率板协议顺序计算全部校准参数的Modbus CRC16 +输入参数: pTrim:校准参数结构体 +输出参数: - +返回说明: 校准参数CRC16 +其它说明: CRC覆盖电压、负载电流、电源电流及零点共20字节 +*****************************************************************/ +static rt_uint16_t chrg_sou_trim_crc(const struct chrg_trim *pTrim) +{ + rt_uint8_t buf[20]; + rt_uint8_t idx = 0; + rt_uint16_t value; + +#define TRIM_CRC_APPEND(_value) \ + do { \ + value = (rt_uint16_t)(_value); \ + buf[idx++] = (rt_uint8_t)(value >> 8); \ + buf[idx++] = (rt_uint8_t)(value & 0xFF); \ + } while (0) + + TRIM_CRC_APPEND(pTrim->trim_volt_k_H); + TRIM_CRC_APPEND(pTrim->trim_volt_k_L); + TRIM_CRC_APPEND(pTrim->trim_volt_b); + TRIM_CRC_APPEND(pTrim->trim_curr_k_H); + TRIM_CRC_APPEND(pTrim->trim_curr_k_L); + TRIM_CRC_APPEND(pTrim->trim_curr_b); + TRIM_CRC_APPEND(pTrim->trim_source_curr_k_H); + TRIM_CRC_APPEND(pTrim->trim_source_curr_k_L); + TRIM_CRC_APPEND(pTrim->trim_source_curr_b); + TRIM_CRC_APPEND(pTrim->trim_zero); + +#undef TRIM_CRC_APPEND + + return mb_crc16(buf, sizeof(buf)); +} + +/***************************************************************** +函数名称: chrg_sou_trim_set_commit +函数描述: 根据校准区标志填写最后一条写使能命令 +输入参数: pTrim:校准参数 reg:寄存器地址指针 value:寄存器值指针 +输出参数: reg:写使能地址 value:对应魔数 +返回说明: - +其它说明: 0写用户校准区,1写出厂校准区 +*****************************************************************/ +static void chrg_sou_trim_set_commit(const struct chrg_trim *pTrim, + rt_uint16_t *reg, rt_uint16_t *value) +{ + if (pTrim->trim_enable_flag == 0) { + *reg = ModbusRTU_CAL_WriteEnable_ADD; + *value = ModbusRTU_CAL_WriteValue; + } else { + *reg = ModbusRTU_CAL_WriteRecovery; + *value = ModbusRTU_CAL_WriteRecovery_Value; + } +} + +/***************************************************************** +函数名称: chrg_sou_trim_set_volt +函数描述: 组装电源电压校准、CRC和写使能指令 +输入参数: ch:南向通道 pTrim:校准参数 +输出参数: - +返回说明: 0:提交成功 <0:队列提交失败 +其它说明: 指令顺序为K_H、K_L、B、CRC、写使能 +*****************************************************************/ +static int chrg_sou_trim_set_volt(eIDX_SOU_CH ch, const struct chrg_trim *pTrim) +{ + rt_uint16_t regs[5] = { + ModbusRTU_Volt_CAL_K_H, ModbusRTU_Volt_CAL_K_L, + ModbusRTU_Volt_CAL_B, ModbusRTU_Trim_CRC_CAL, 0 + }; + rt_uint16_t vals[5] = { + pTrim->trim_volt_k_H, pTrim->trim_volt_k_L, + (rt_uint16_t)pTrim->trim_volt_b, chrg_sou_trim_crc(pTrim), 0 + }; + + chrg_sou_trim_set_commit(pTrim, ®s[4], &vals[4]); + return chrg_sou_com_batch_submit(ch, regs, vals, 5); +} + +/***************************************************************** +函数名称: chrg_sou_trim_set_curr +函数描述: 组装负载电流校准、CRC和写使能指令 +输入参数: ch:南向通道 pTrim:校准参数 +输出参数: - +返回说明: 0:提交成功 <0:队列提交失败 +其它说明: 指令顺序为K_H、K_L、B、CRC、写使能 +*****************************************************************/ +static int chrg_sou_trim_set_curr(eIDX_SOU_CH ch, const struct chrg_trim *pTrim) +{ + rt_uint16_t regs[5] = { + ModbusRTU_Current_CAL_K_H, ModbusRTU_Current_CAL_K_L, + ModbusRTU_Current_CAL_B, ModbusRTU_Trim_CRC_CAL, 0 + }; + rt_uint16_t vals[5] = { + pTrim->trim_curr_k_H, pTrim->trim_curr_k_L, + (rt_uint16_t)pTrim->trim_curr_b, chrg_sou_trim_crc(pTrim), 0 + }; + + chrg_sou_trim_set_commit(pTrim, ®s[4], &vals[4]); + return chrg_sou_com_batch_submit(ch, regs, vals, 5); +} + +/***************************************************************** +函数名称: chrg_sou_trim_set_source_curr +函数描述: 组装电源电流校准、CRC和写使能指令 +输入参数: ch:南向通道 pTrim:校准参数 +输出参数: - +返回说明: 0:提交成功 <0:队列提交失败 +其它说明: 指令顺序为K_H、K_L、B、CRC、写使能 +*****************************************************************/ +static int chrg_sou_trim_set_source_curr(eIDX_SOU_CH ch, + const struct chrg_trim *pTrim) +{ + rt_uint16_t regs[5] = { + ModbusRTU_SourceCurrent_CAL_K_H, ModbusRTU_SourceCurrent_CAL_K_L, + ModbusRTU_SourceCurrent_CAL_B, ModbusRTU_Trim_CRC_CAL, 0 + }; + rt_uint16_t vals[5] = { + pTrim->trim_source_curr_k_H, pTrim->trim_source_curr_k_L, + (rt_uint16_t)pTrim->trim_source_curr_b, chrg_sou_trim_crc(pTrim), 0 + }; + + chrg_sou_trim_set_commit(pTrim, ®s[4], &vals[4]); + return chrg_sou_com_batch_submit(ch, regs, vals, 5); +} + +/***************************************************************** +函数名称: chrg_sou_trim_set_zero +函数描述: 组装零点校准、CRC和写使能指令 +输入参数: ch:南向通道 pTrim:校准参数 +输出参数: - +返回说明: 0:提交成功 <0:队列提交失败 +其它说明: 指令顺序为ZERO、CRC、写使能 +*****************************************************************/ +static int chrg_sou_trim_set_zero(eIDX_SOU_CH ch, const struct chrg_trim *pTrim) +{ + rt_uint16_t regs[3] = {ModbusRTU_Trim_Zero_CAL, ModbusRTU_Trim_CRC_CAL, 0}; + rt_uint16_t vals[3] = { + (rt_uint16_t)pTrim->trim_zero, chrg_sou_trim_crc(pTrim), 0 + }; + + chrg_sou_trim_set_commit(pTrim, ®s[2], &vals[2]); + return chrg_sou_com_batch_submit(ch, regs, vals, 3); +} + +/***************************************************************** +函数名称: chrg_sou_trim_submit +函数描述: 根据校准类型选择组包函数并提交到南向RUN_COM队列 +输入参数: ch:南向功率板通道 pTrim:已解析的校准参数 +输出参数: - +返回说明: 0:提交成功 <0:参数、校准模式或队列提交失败 +其它说明: 本函数不修改南向enable,RUN_COM按ch主动切换目标通道 +*****************************************************************/ +int chrg_sou_trim_submit(eIDX_SOU_CH ch, const struct chrg_trim *pTrim) +{ + if ((ch >= TOTAL_SOU_CHS) || (pTrim == RT_NULL)) { + return -1; + } + + switch (pTrim->trim_mode) { + case MODE_VOLT_CAL: + return chrg_sou_trim_set_volt(ch, pTrim); + case MODE_CURR_CAL: + return chrg_sou_trim_set_curr(ch, pTrim); + case MODE_SOURCE_CURR_CAL: + return chrg_sou_trim_set_source_curr(ch, pTrim); + case MODE_ZERO_CAL: + return chrg_sou_trim_set_zero(ch, pTrim); + default: + return -1; + } +} +//启动批量下发(填充完影子结构体后调用) + /***************************************************************** 函数名称: chrg_set_sou_reg 函数描述: 南向通道设置寄存器请求命令 @@ -437,11 +492,16 @@ void chrg_set_sou_reg (eIDX_SOU_CH idx, rt_uint16_t reg, rt_uint16_t value) struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; struct chrg_rollsou_t *pROLL = &chrgrollsou; + if (RT_NULL == pTHR->mutex) { + return; + } + if (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(100)) != RT_EOK) { + LOG_E("sou command lock timeout"); + return; + } pROLL->ch = idx; pROLL->reg = reg; pROLL->value = value; - - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); pROLL->run = RUN_LCD; rt_mutex_release(pTHR->mutex); } @@ -455,18 +515,233 @@ void chrg_set_sou_reg (eIDX_SOU_CH idx, rt_uint16_t reg, rt_uint16_t value) 其它说明: - *****************************************************************/ void chrg_set_sou_reg_com (eIDX_SOU_CH idx, rt_uint16_t reg, rt_uint16_t value) +{ + chrg_sou_com_batch_add_reg(idx, reg, value); +} + +static int sou_com_batch_next(rt_uint8_t *ch, rt_uint16_t *reg, rt_uint16_t *value) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + struct chrg_rollsou_t *pROLL = &chrgrollsou; + rt_bool_t pending_wait = RT_FALSE; + rt_tick_t now; + rt_uint8_t idx; + rt_uint8_t offset; + + if ((RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } + + now = rt_tick_get(); + /* 从上一次成功命令的下一个通道开始查找,使多通道批次按 + * "CH1一条 -> CH2一条 -> ..." 交替执行,而不是低编号通道独占总线。 */ + for (offset = 0; offset < TOTAL_SOU_CHS; offset++) { + idx = (rt_uint8_t)((g_sou_com_batch_rr_next_ch + offset) % + TOTAL_SOU_CHS); + sou_com_batch_ch_t *pBatch = &g_sou_com_batch.ch[idx]; + if (pBatch->pending != RT_TRUE) { + continue; + } + if ((pBatch->reg_cnt == 0) || (pBatch->cur_idx >= pBatch->reg_cnt)) { + pBatch->pending = RT_FALSE; + pBatch->result = -1; + pBatch->cur_idx = 0; + pBatch->reg_cnt = 0; + pBatch->next_send_tick = 0U; + continue; + } + pending_wait = RT_TRUE; + if ((pBatch->next_send_tick != 0U) && + ((rt_int32_t)(now - pBatch->next_send_tick) < 0)) { + /* 当前通道处于继电器保护时间时,继续查找其它可发送通道。 */ + continue; + } + pBatch->next_send_tick = 0U; + + *ch = idx; + *reg = pBatch->reg_list[pBatch->cur_idx]; + *value = pBatch->val_list[pBatch->cur_idx]; + rt_mutex_release(pTHR->mutex); + return 1; + } + + if (!pending_wait) { + pROLL->run = RUN_AUTO; + } + rt_mutex_release(pTHR->mutex); + return 0; +} + +static int sou_com_batch_complete(rt_uint8_t ch, rt_uint16_t reg, rt_uint16_t value) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + sou_com_batch_ch_t *pBatch; + + if ((ch >= TOTAL_SOU_CHS) || (RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } + + pBatch = &g_sou_com_batch.ch[ch]; + if ((pBatch->pending != RT_TRUE) || (pBatch->cur_idx >= pBatch->reg_cnt) || + (pBatch->reg_list[pBatch->cur_idx] != reg) || + (pBatch->val_list[pBatch->cur_idx] != value)) { + rt_mutex_release(pTHR->mutex); + return -1; + } + + pBatch->cur_idx++; + /* 当前寄存器已收到匹配写回码。下一次调度从下一个通道开始; + * 当前通道的下一条命令会在其它就绪通道各执行一条后继续发送。 */ + g_sou_com_batch_rr_next_ch = (rt_uint8_t)((ch + 1U) % TOTAL_SOU_CHS); + /* 暂时关闭副继电器与主继电器之间的固定延时;需要恢复时重新启用本段。 */ + if ((reg == REG_REL_ON) && (value == ALL_REL_OFF) && + (pBatch->cur_idx < pBatch->reg_cnt) && + (pBatch->reg_list[pBatch->cur_idx] == REG_REL_ON) && + (pBatch->val_list[pBatch->cur_idx] == FU_REL_ON)) { + pBatch->next_send_tick = rt_tick_get() + + rt_tick_from_millisecond(SOU_REL_SWITCH_DELAY_MS); + } else if ((reg == REG_REL_ON) && (value == FU_REL_ON) && + (pBatch->cur_idx < pBatch->reg_cnt) && + (pBatch->reg_list[pBatch->cur_idx] == REG_REL_ON) && + (pBatch->val_list[pBatch->cur_idx] == ALL_REL_ON)) { + pBatch->next_send_tick = rt_tick_get() + + rt_tick_from_millisecond(SOU_REL_SWITCH_DELAY_MS); + } + if (pBatch->cur_idx >= pBatch->reg_cnt) { + pBatch->pending = RT_FALSE; + pBatch->result = 1; + pBatch->cur_idx = 0; + pBatch->reg_cnt = 0; + pBatch->next_send_tick = 0U; + } + rt_mutex_release(pTHR->mutex); + return 0; +} + +int chrg_sou_com_batch_cancel(eIDX_SOU_CH ch) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + sou_com_batch_ch_t *pBatch; + + if ((ch >= TOTAL_SOU_CHS) || (RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } + pBatch = &g_sou_com_batch.ch[ch]; + pBatch->pending = RT_FALSE; + pBatch->result = -1; + pBatch->cur_idx = 0; + pBatch->reg_cnt = 0; + pBatch->next_send_tick = 0U; + /* 已取消事务对应的优先P1量测同样失效,后续状态会重新申请新量测。 */ + g_sou_measure_once_pending_mask &= (rt_uint8_t)~SOU_CHANNEL_BIT(ch); + g_sou_measure_once_active_mask &= (rt_uint8_t)~SOU_CHANNEL_BIT(ch); + rt_mutex_release(pTHR->mutex); + return 0; +} + +static rt_bool_t sou_com_batch_pending_locked(void) +{ + rt_uint8_t idx; + + for (idx = 0; idx < TOTAL_SOU_CHS; idx++) { + if (g_sou_com_batch.ch[idx].pending == RT_TRUE) { + return RT_TRUE; + } + } + return RT_FALSE; +} + +static void sou_single_command_finish(rt_uint8_t expected_run) { struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; struct chrg_rollsou_t *pROLL = &chrgrollsou; - pROLL->ch = idx; - pROLL->reg = reg; - pROLL->value = value; - - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); - pROLL->run = RUN_COM; + if ((RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return; + } + if (pROLL->run == expected_run) { + pROLL->run = sou_com_batch_pending_locked() ? RUN_COM : RUN_AUTO; + } rt_mutex_release(pTHR->mutex); - rt_kprintf("ch=:%d,reg=%02x,value=%02x\n",idx,reg,value); +} + +static rt_bool_t sou_com_response_matches(struct mb_msg_t *pMB, rt_uint16_t reg, + rt_uint16_t value) +{ + return (pMB != RT_NULL) && (pMB->payload != RT_NULL) && (pMB->length == 8) && + (pMB->payload[0] == ID_ELOAD) && (pMB->payload[1] == MB_REGISTER_WR) && + (u8v_to_u16(&pMB->payload[2]) == reg) && + (u8v_to_u16(&pMB->payload[4]) == value) && + (mb_crc16(pMB->payload, pMB->length) == 0); +} + +static void sou_msg_free(struct mb_msg_t **ppMB) +{ + if ((ppMB == RT_NULL) || (*ppMB == RT_NULL)) { + return; + } + if ((*ppMB)->payload != RT_NULL) { + rt_free((*ppMB)->payload); + (*ppMB)->payload = RT_NULL; + } + rt_free(*ppMB); + *ppMB = RT_NULL; +} + +/***************************************************************** +函数名称: sou_com_batch_current_matches +函数描述: 判断当前南向批次正在等待的命令是否仍与指定命令一致 +输入参数: ch:南向通道 reg:寄存器地址 value:寄存器值 +输出参数: - +返回说明: RT_TRUE:指定命令仍是该通道当前批次的等待项 + RT_FALSE:批次已取消、已替换或当前等待项不同 +其它说明: 用于忽略取消旧批次后迟到的回码,防止旧回码触发新安全批次重试或取消 +*****************************************************************/ +static rt_bool_t sou_com_batch_current_matches(rt_uint8_t ch, + rt_uint16_t reg, rt_uint16_t value) +{ + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + sou_com_batch_ch_t *pBatch; + rt_bool_t matched; + + if ((ch >= TOTAL_SOU_CHS) || (RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, + rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return RT_FALSE; + } + + pBatch = &g_sou_com_batch.ch[ch]; + matched = (pBatch->pending == RT_TRUE) && + (pBatch->cur_idx < pBatch->reg_cnt) && + (pBatch->reg_list[pBatch->cur_idx] == reg) && + (pBatch->val_list[pBatch->cur_idx] == value); + rt_mutex_release(pTHR->mutex); + return matched; +} + +static void sou_com_command_failed(rt_uint8_t ch, rt_uint16_t reg, rt_uint16_t value, + const char *reason, rt_uint8_t *retry_count) +{ + /* 该命令所属批次已被安全状态替换时,迟到回码或超时不得影响新批次。 */ + if (!sou_com_batch_current_matches(ch, reg, value)) { + *retry_count = 0; + return; + } + + (*retry_count)++; + if (*retry_count < SOU_COM_MAX_ATTEMPTS) { + return; + } + + LOG_E("com batch failed after %d attempts: ch=%d reg=0x%04x value=0x%04x (%s)", + SOU_COM_MAX_ATTEMPTS, ch, reg, value, reason); + if (chrg_sou_com_batch_cancel((eIDX_SOU_CH)ch) == 0) { + *retry_count = 0; + } } @@ -489,22 +764,63 @@ void chrg_roll_sou_thread_entry (void *data) rt_uint8_t run = 0; rt_uint8_t index = 0, idx = 0; + rt_uint8_t batch_ch = 0; + rt_uint8_t retry_count = 0; + rt_uint8_t command_ch = 0; + rt_uint8_t single_ch = 0; + rt_uint8_t single_part = 0; + rt_uint16_t command_reg = 0; + rt_uint16_t command_value = 0; + rt_uint16_t single_reg = 0; + rt_uint16_t single_value = 0; + rt_bool_t command_valid = RT_FALSE; + rt_bool_t measure_once = RT_FALSE; + rt_bool_t version_probe = RT_FALSE; + rt_uint8_t response_part = MB_R_PART1; rt_err_t ret = RT_EOK; struct chrg_rollsou_t *pROLL = &chrgrollsou; struct chrg_thread_t *pTS = &chrgthr[IDX_THR_SOUTH]; struct mb_msg_t *pMB_R = RT_NULL; - sou_com_batch_ch_t *pCurBatch = RT_NULL; while (1) { rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); run = pROLL->run; + single_ch = pROLL->ch; + single_part = pROLL->part; + single_reg = pROLL->reg; + single_value = pROLL->value; rt_mutex_release(pTHR->mutex); - rt_uint8_t batch_ch = 0; // 缓存当前正在下发的批量通道 + command_valid = RT_FALSE; + measure_once = RT_FALSE; + version_probe = RT_FALSE; + response_part = MB_R_PART1; + if ((run != RUN_COM) && (run != RUN_UPDATA)) { + retry_count = 0; + } // rt_kprintf("------ROLL SOU START \n--------"); switch (run) { case RUN_AUTO: { + int measure_ret = sou_measure_once_next(&idx); + if (measure_ret > 0) { + /* 卸载确认优先读取当前通道P1,不等待四通道普通轮询走完。 */ + measure_once = RT_TRUE; + response_part = MB_R_PART1; + chrg_led_flashing(IDX_LED3, TIMES_ONE, PULSE_TIME*2, PULSE_TIME*2); + if (idx != (rt_uint8_t)pROLL->idx) { + pROLL->idx = (eIDX_SOU_CH)idx; + chrg_south_switch((eIDX_SOU_CH)idx); + } + if (g_sou_version_ready[idx] == 0U) { + version_probe = RT_TRUE; + chrg_eload_read_version(ID_ELOAD); + } else { + chrg_eload_refresh(ID_ELOAD, MB_R_PART1); + } + break; + } idx = index%(TOTAL_SOU_CHS); // [0, 3] + response_part = pROLL->part; if (0 == chrgsouth.enable[idx]) { // 通道未启用 - index++; + CHRG_CHANNEL_NEXT(index); rt_thread_mdelay(10); continue; } else { // 通道启用 @@ -514,53 +830,66 @@ void chrg_roll_sou_thread_entry (void *data) chrg_south_switch((eIDX_SOU_CH)idx); pROLL->part = MB_R_PART1; } - chrg_eload_refresh(ID_ELOAD, pROLL->part); + if (g_sou_version_ready[idx] == 0U) { + version_probe = RT_TRUE; + chrg_eload_read_version(ID_ELOAD); + } else { + chrg_eload_refresh(ID_ELOAD, pROLL->part); + } if (pROLL->part < TOTAL_MB_R) { - index++; + CHRG_CHANNEL_NEXT(index); } } } break; case RUN_LCD: { - chrg_south_switch((eIDX_SOU_CH)pROLL->ch); - chrg_eload_write_reg(ID_ELOAD, pROLL->reg, pROLL->value); + chrg_south_switch((eIDX_SOU_CH)single_ch); + chrg_eload_write_reg(ID_ELOAD, single_reg, single_value); } break; case RUN_COM: { - pCurBatch = RT_NULL; - // 遍历查找有待下发批量任务的通道 - for(idx = 0; idx < TOTAL_SOU_CHS; idx++) - { - if(g_sou_com_batch.ch[idx].pending == RT_TRUE) - { - pCurBatch = &g_sou_com_batch.ch[idx]; - break; - } + int next_ret = sou_com_batch_next(&command_ch, &command_reg, &command_value); + if (next_ret <= 0) { + retry_count = 0; + rt_thread_mdelay(1); + continue; } - // 无任何批量任务,切回自动轮巡 - if(pCurBatch == RT_NULL) - { - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); - pROLL->run = RUN_AUTO; - rt_mutex_release(pTHR->mutex); - break; - } - // 切换当前通道串口 - chrg_south_switch((eIDX_SOU_CH)idx); - batch_ch = idx; - // 发送当前下标寄存器 - rt_uint16_t reg = pCurBatch->reg_list[pCurBatch->cur_idx]; - rt_uint16_t val = pCurBatch->val_list[pCurBatch->cur_idx]; - chrg_eload_write_reg(ID_ELOAD, reg, val); + batch_ch = command_ch; + command_valid = RT_TRUE; + chrg_south_switch((eIDX_SOU_CH)command_ch); + rt_kprintf("ch=%d",command_ch); + chrg_eload_write_reg(ID_ELOAD, command_reg, command_value); } break; + case RUN_UPDATA: + { + /* + * 升级响应是单字节ACK/NAK/C,不是Modbus消息,因此本分支发送后 + * 直接返回循环,由COM升级状态机读取响应,不能进入下方邮箱等待。 + */ + if ((pROLL->mode != 1) || (pROLL->send_flag == 0) || + (pROLL->leng == 0) || (pROLL->leng > sizeof(pROLL->updata_buf))) { + rt_thread_mdelay(1); + continue; + } + chrg_south_switch((eIDX_SOU_CH)single_ch); + ret = chrg_eload_send_ymodem_data(pROLL->updata_buf, pROLL->leng); + + rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); + pROLL->updata_result = (rt_int16_t)ret; + pROLL->send_flag = 0; + rt_mutex_release(pTHR->mutex); + rt_thread_mdelay(1); + continue; + } + case RUN_CONSOLE: { - chrg_south_switch((eIDX_SOU_CH)pROLL->ch); - chrg_eload_refresh(ID_ELOAD, pROLL->part); + chrg_south_switch((eIDX_SOU_CH)single_ch); + chrg_eload_refresh(ID_ELOAD, single_part); } break; @@ -571,76 +900,63 @@ void chrg_roll_sou_thread_entry (void *data) break; } - ret = rt_mb_recv(pTS->mb, (rt_uint32_t *)&pMB_R, 50); - if ((RT_EOK == ret)&&(RT_NULL != pMB_R)) - { - if (0 == mb_crc16(pMB_R->payload, pMB_R->length)) - { - if (RUN_AUTO != run) - { - if(run == RUN_COM) - { - // CRC校验成功,本条发送完成,下标+1,下发下一条 - sou_com_batch_ch_t *pBatch = &g_sou_com_batch.ch[batch_ch]; - pBatch->cur_idx++; - // 当前通道全部指令发完,清除pending标记 - if(pBatch->cur_idx >= pBatch->reg_cnt) - { - pBatch->pending = RT_FALSE; - pBatch->cur_idx = 0; - pBatch->reg_cnt = 0; - } - } - else - { - // LCD/CONSOLE单条模式,应答成功直接切回自动轮巡 - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); - pROLL->run = RUN_AUTO; - rt_mutex_release(pTHR->mutex); - } - } - else - { - // AUTO自动采集,解析返回数据 - chrg_eload_parse(idx, pROLL->part, pMB_R->payload, pMB_R->length); - } - } - else - { - LOG_E("crc failed, resend current reg"); - // CRC错误,不递增cur_idx,下一轮重发本条 + pMB_R = RT_NULL; + ret = rt_mb_recv(pTS->mb, (rt_uint32_t *)&pMB_R, + rt_tick_from_millisecond(SOU_RESPONSE_TIMEOUT_MS)); + if ((RT_EOK == ret) && (RT_NULL != pMB_R)) { + rt_bool_t response_valid; + + if (run == RUN_COM) { + response_valid = command_valid && + sou_com_response_matches(pMB_R, command_reg, command_value); + } else { + response_valid = (pMB_R->payload != RT_NULL) && + (mb_crc16(pMB_R->payload, pMB_R->length) == 0); } - // 释放报文内存 - if (RT_NULL != pMB_R->payload) - { - rt_free(pMB_R->payload); - pMB_R->payload = RT_NULL; - } - if (RT_NULL != pMB_R) - { - rt_free(pMB_R); - pMB_R = RT_NULL; - } - rt_thread_mdelay(20); - } - else - { - // 应答超时处理 - if (RUN_AUTO != run) - { - if(run == RUN_COM) - { - // 批量下发超时:不递增cur_idx,下一轮循环重发当前寄存器 - LOG_E("com batch timeout, resend current reg"); - } - else - { - // LCD/CONSOLE单条超时,切回自动轮巡 - rt_mutex_take(pTHR->mutex, RT_WAITING_FOREVER); - pROLL->run = RUN_AUTO; - rt_mutex_release(pTHR->mutex); + if (response_valid) { + if (run == RUN_COM) { + if (sou_com_batch_complete(batch_ch, command_reg, command_value) == 0) { + retry_count = 0; + } else { + sou_com_command_failed(batch_ch, command_reg, command_value, + "batch changed", &retry_count); + } + } else if (run != RUN_AUTO) { + sou_single_command_finish(run); + } else { + if (version_probe) { + if (chrg_eload_parse_version(idx, pMB_R->payload, + pMB_R->length) == 0) { + g_sou_version_ready[idx] = 1U; + pROLL->part = MB_R_PART1; + } + } else { + chrg_eload_parse(idx, response_part, pMB_R->payload, + pMB_R->length); + } } + } else if (run == RUN_COM) { + sou_com_command_failed(batch_ch, command_reg, command_value, + "invalid response", &retry_count); + } else { + LOG_E("crc failed"); + } + if ((run == RUN_AUTO) && measure_once) { + sou_measure_once_finish(idx); + } + + sou_msg_free(&pMB_R); + } else { + sou_msg_free(&pMB_R); + if ((run == RUN_AUTO) && measure_once) { + sou_measure_once_finish(idx); + } + if ((run == RUN_COM) && command_valid) { + sou_com_command_failed(batch_ch, command_reg, command_value, + "timeout", &retry_count); + } else if (run != RUN_AUTO) { + sou_single_command_finish(run); } } // rt_kprintf("------ROLL SOU END \n--------"); @@ -670,10 +986,15 @@ static int south_send(rt_uint8_t argc, char **argv) } struct chrg_rollsou_t *pROLL = &chrgrollsou; + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + if ((RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } pROLL->run = RUN_CONSOLE; pROLL->ch = (rt_uint8_t)ch; pROLL->part = (rt_uint8_t)pt; - + rt_mutex_release(pTHR->mutex); return 0; } @@ -688,15 +1009,23 @@ static int south_run (int argc, char **argv) } struct chrg_rollsou_t *pROLL = &chrgrollsou; + struct chrg_thread_t *pTHR = &chrgthr[IDX_THR_ROLL_SOU]; + rt_uint8_t new_run; if (rt_strcmp(argv[1], "start") == 0) { - pROLL->run = RUN_AUTO; + new_run = RUN_AUTO; } else if (rt_strcmp(argv[1], "stop") == 0) { - pROLL->run = RUN_NONE; + new_run = RUN_NONE; } else { rt_kprintf("help : %s start|stop\r\n", argv[0]); return -1; } + if ((RT_NULL == pTHR->mutex) || + (rt_mutex_take(pTHR->mutex, rt_tick_from_millisecond(SOU_MUTEX_TIMEOUT_MS)) != RT_EOK)) { + return -1; + } + pROLL->run = new_run; + rt_mutex_release(pTHR->mutex); return 0; } diff --git a/apps/chrg/applications/thread/chrg_roll_sou.h b/apps/chrg/applications/thread/chrg_roll_sou.h index b707c96..f1427b8 100644 --- a/apps/chrg/applications/thread/chrg_roll_sou.h +++ b/apps/chrg/applications/thread/chrg_roll_sou.h @@ -12,11 +12,13 @@ #include +struct chrg_trim; #define THR_NAME_ROLL_SOU "thr.rollsou" // 单通道批量下发缓存:保存当前需要同步的寄存器列表 -#define SOU_COM_BATCH_REG_MAX 16 +/* 为连续提交初始化、普通启动和动态启动命令预留足够空间。 */ +#define SOU_COM_BATCH_REG_MAX 32 typedef struct { rt_uint16_t reg_cnt; // 当前待下发寄存器数量 @@ -24,6 +26,8 @@ typedef struct rt_uint16_t val_list[SOU_COM_BATCH_REG_MAX]; // 对应寄存器值数组 rt_uint8_t cur_idx; // 当前下发到第几条 rt_bool_t pending; // 本通道有待下发批量指令 + rt_int8_t result; // 0:执行中/无结果 1:成功 -1:失败取消 + rt_tick_t next_send_tick; // 非0时下一条命令不得早于该tick发送 } sou_com_batch_ch_t; // COM批量下发总控结构体 @@ -46,9 +50,10 @@ struct chrg_rollsou_t { rt_uint16_t reg; // LCD 触发时,寄存器地址 rt_uint16_t value; // LCD 触发时,寄存器值 - rt_uint8_t *updata_data;//南向升级的数据 - rt_uint16_t leng; - rt_uint8_t send_flag; //发送标志位 + rt_uint8_t updata_buf[YMODEM_STX_SIZE];/* 南向升级帧缓存,避免上层缓冲区被提前覆盖 */ + rt_uint16_t leng; /* 当前待发送升级帧的实际长度 */ + rt_int16_t updata_result;/* 最近一次升级帧发送结果 */ + rt_uint8_t send_flag; /* 1:升级帧待发送或正在发送,0:允许提交下一帧 */ rt_uint8_t mode;// 0 为正常轮询,1为升级,2为校准 }; @@ -82,9 +87,56 @@ extern void chrg_set_sou_reg_com (eIDX_SOU_CH idx, rt_uint16_t reg, rt_uint16_t *****************************************************************/ extern void chrg_roll_sou_thread_entry (void *data); -extern void chrg_send_sou_upadata_data (rt_uint8_t *buf,rt_uint16_t leng); -extern void chrg_sou_com_sou_set(void); +/***************************************************************** +函数名称: chrg_send_sou_upadata_data +函数描述: 将一帧升级数据复制到南向升级缓存并唤醒升级发送分支 +输入参数: buf:升级帧数据 leng:升级帧长度 +输出参数: - +返回说明: 0:提交成功 -1:参数错误 -2:上一帧尚未发送 -3:互斥锁超时 +其它说明: 数据在函数内完成复制,调用者返回后可以立即复用原缓冲区 +*****************************************************************/ +extern int chrg_send_sou_upadata_data(const rt_uint8_t *buf, rt_uint16_t leng); extern int chrg_sou_com_batch_add_reg(eIDX_SOU_CH ch, rt_uint16_t reg, rt_uint16_t val); +extern int chrg_sou_com_batch_submit(eIDX_SOU_CH ch, const rt_uint16_t *regs, + const rt_uint16_t *vals, rt_uint8_t count); +/***************************************************************** +函数名称: chrg_sou_com_batch_cancel +函数描述: 取消指定通道尚未完成的南向COM批次 +输入参数: ch:南向功率板通道 +输出参数: - +返回说明: 0:批次已取消 <0:参数或互斥锁错误 +其它说明: 已发送到功率板的当前命令无法撤回,但其后续未发送命令将被清除; + 当前命令的迟到回码会按批次已变更处理,不再推进旧批次 +*****************************************************************/ +extern int chrg_sou_com_batch_cancel(eIDX_SOU_CH ch); +extern rt_bool_t chrg_sou_com_batch_is_pending(eIDX_SOU_CH ch); +/***************************************************************** +函数名称: chrg_sou_com_batch_get_result +函数描述: 获取指定通道最近一次南向COM批次的执行结果 +输入参数: ch:南向功率板通道 +输出参数: - +返回说明: 1:全部命令收到匹配回码 0:执行中/无结果 -1:失败取消 -2:参数或锁错误 +其它说明: 用于区分pending清零是正常完成还是重试耗尽后的失败取消 +*****************************************************************/ +extern int chrg_sou_com_batch_get_result(eIDX_SOU_CH ch); +/***************************************************************** +函数名称: chrg_sou_measure_once_submit +函数描述: 请求南向线程优先查询指定通道的一帧P1量测数据 +输入参数: ch:南向功率板通道 +输出参数: - +返回说明: 0:请求成功或已存在同通道请求 <0:参数或互斥锁错误 +其它说明: 只登记一次性请求,实际切通道、发送0x03和解析仍由南向线程完成 +*****************************************************************/ +extern int chrg_sou_measure_once_submit(eIDX_SOU_CH ch); +/***************************************************************** +函数名称: chrg_sou_trim_submit +函数描述: 根据校准类型组装功率板校准指令并提交到南向COM队列 +输入参数: ch:南向功率板通道 pTrim:已解析的校准参数 +输出参数: - +返回说明: 0:提交成功 <0:参数、校准模式或队列提交失败 +其它说明: 实际发送、应答校验和重试由RUN_COM分支统一管理 +*****************************************************************/ +extern int chrg_sou_trim_submit(eIDX_SOU_CH ch, const struct chrg_trim *pTrim); #endif diff --git a/apps/chrg/applications/thread/chrg_south.c b/apps/chrg/applications/thread/chrg_south.c index 2fb6463..3c8fcd5 100644 --- a/apps/chrg/applications/thread/chrg_south.c +++ b/apps/chrg/applications/thread/chrg_south.c @@ -23,6 +23,8 @@ #define DBG_LEVEL DBG_LOG #include +#define SOUTH_FRAME_TIMEOUT_MS 100 + #define MB_ACK_HEAD_SIZE 3 // modbus 响应头3字节 struct chrg_south_t chrgsouth = { @@ -157,6 +159,9 @@ int chrg_south_send (rt_uint8_t *data, rt_size_t length) struct chrg_tty_t *pTTY = chrgsouth.tty; if (RT_NULL != pTTY) { +#if SOUTH_FRAME_DEBUG + LOG_HEX("sendS", 32, data, length); +#endif ret = chrg_tty_send(pTTY, data, length); } return ret; @@ -183,7 +188,9 @@ static int chrg_south_frame_head (struct rt_ringbuffer *rb, rt_uint8_t *head, eI len_rb = rt_ringbuffer_data_len(rb); if (len_rb < MB_ACK_HEAD_SIZE) { // 帧头3字节 +#if SOUTH_FRAME_DEBUG rt_kprintf("len_rb=:%d",len_rb); +#endif return -1; } @@ -249,10 +256,70 @@ static int chrg_south_frame_head (struct rt_ringbuffer *rb, rt_uint8_t *head, eI 返回说明: <0: 错误 >0:帧长度 其它说明: - *****************************************************************/ +static void chrg_south_frame_reset(struct pickup_info_t *pFrame, rt_bool_t release) +{ + if (RT_NULL == pFrame) { + return; + } + + if (release && (RT_NULL != pFrame->pMB)) { + if (RT_NULL != pFrame->pMB->payload) { + rt_free(pFrame->pMB->payload); + } + rt_free(pFrame->pMB); + } + + pFrame->last_tick = 0; + pFrame->length = 0; + pFrame->findhead = 0; + pFrame->len_msg = 0; + pFrame->pMB = RT_NULL; +} + +static int chrg_south_frame_append(struct rt_ringbuffer *rb, + struct pickup_info_t *pFrame) +{ + rt_size_t remaining; + rt_size_t copied; + + if ((RT_NULL == rb)||(RT_NULL == pFrame)||(RT_NULL == pFrame->pMB)|| + (RT_NULL == pFrame->pMB->payload)||(pFrame->len_msg <= 0)|| + (pFrame->length > (rt_size_t)pFrame->len_msg)) { + return -1; + } + + remaining = (rt_size_t)pFrame->len_msg - pFrame->length; + if (0 == remaining) { + return 1; + } + + copied = rt_ringbuffer_data_len(rb); + if (copied > remaining) { + copied = remaining; + } + if (copied > 0) { + copied = rt_ringbuffer_get(rb, + &pFrame->pMB->payload[pFrame->length], copied); + pFrame->length += copied; + pFrame->last_tick = rt_tick_get(); + } + + return (pFrame->length == (rt_size_t)pFrame->len_msg) ? 1 : 0; +} + +static void chrg_south_frame_dispatch(struct pickup_info_t *pFrame) +{ + struct mb_msg_t *pMB = pFrame->pMB; + + chrg_south_frame_reset(pFrame, RT_FALSE); + chrg_thread_mb_send(IDX_THR_SOUTH, pMB); +} + static int chrg_south_pickup_frame (struct rt_ringbuffer *rb, struct pickup_info_t *pFrame) { int cnt = 0; + int status = 0; rt_size_t len_rb = 0; if ((RT_NULL == rb)||(RT_NULL == pFrame)) { @@ -263,63 +330,55 @@ static int chrg_south_pickup_frame (struct rt_ringbuffer *rb, if (0 == pFrame->findhead) { pFrame->len_msg = chrg_south_frame_head(rb, pFrame->head, ID_ELOAD); if (pFrame->len_msg > 0) { + if ((pFrame->len_msg < MB_ACK_HEAD_SIZE)|| + (pFrame->len_msg >= SIZE_BUF_TTY)) { + LOG_E("invalid msg length: %d", pFrame->len_msg); + chrg_south_frame_reset(pFrame, RT_TRUE); + return -3; + } + pFrame->pMB = (struct mb_msg_t *)rt_malloc(sizeof(struct mb_msg_t)); if (RT_NULL == pFrame->pMB) { LOG_E("malloc mb failed."); + chrg_south_frame_reset(pFrame, RT_FALSE); return -2; } pFrame->pMB->length = pFrame->len_msg; - if (pFrame->pMB->length >= SIZE_BUF_TTY) { - rt_free(pFrame->pMB); - pFrame->pMB = RT_NULL; - LOG_E("too long msg."); - return -3; - } - + pFrame->pMB->payload = RT_NULL; pFrame->pMB->payload = (rt_uint8_t *)rt_malloc(pFrame->len_msg); if (RT_NULL == pFrame->pMB->payload) { LOG_E("malloc buf failed."); + chrg_south_frame_reset(pFrame, RT_TRUE); return -4; } rt_memcpy(pFrame->pMB->payload, pFrame->head, MB_ACK_HEAD_SIZE); + pFrame->length = MB_ACK_HEAD_SIZE; + pFrame->findhead = 1; + pFrame->last_tick = rt_tick_get(); - len_rb = rt_ringbuffer_data_len(rb); - if (len_rb >= pFrame->len_msg - MB_ACK_HEAD_SIZE) { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[MB_ACK_HEAD_SIZE], - pFrame->len_msg - MB_ACK_HEAD_SIZE); - - chrg_thread_mb_send(IDX_THR_SOUTH, pFrame->pMB); - - pFrame->length = 0; - pFrame->len_msg = 0; + status = chrg_south_frame_append(rb, pFrame); + if (status > 0) { + chrg_south_frame_dispatch(pFrame); cnt++; + } else if (status < 0) { + chrg_south_frame_reset(pFrame, RT_TRUE); + return -5; } else { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[MB_ACK_HEAD_SIZE], len_rb); - pFrame->length = MB_ACK_HEAD_SIZE + len_rb; - pFrame->findhead = 1; - pFrame->last_tick = rt_tick_get(); break; } } } else { - len_rb = rt_ringbuffer_data_len(rb); - int len_need = pFrame->len_msg - MB_ACK_HEAD_SIZE; - if (len_rb >= len_need) { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[pFrame->length], len_need); - - chrg_thread_mb_send(IDX_THR_SOUTH, pFrame->pMB); - - pFrame->length = 0; - pFrame->findhead = 0; - pFrame->len_msg = 0; + status = chrg_south_frame_append(rb, pFrame); + if (status > 0) { + chrg_south_frame_dispatch(pFrame); cnt++; + } else if (status < 0) { + chrg_south_frame_reset(pFrame, RT_TRUE); + return -5; } else { - rt_ringbuffer_get(rb, &pFrame->pMB->payload[pFrame->length], len_rb); - pFrame->length += len_rb; - pFrame->last_tick = rt_tick_get(); break; } } @@ -373,6 +432,13 @@ void chrg_south_thread_entry (void *data) } while (1) { + if (pSOU->frame.findhead && + ((rt_tick_t)(rt_tick_get() - pSOU->frame.last_tick) >= + rt_tick_from_millisecond(SOUTH_FRAME_TIMEOUT_MS))) { + chrg_south_frame_reset(&pSOU->frame, RT_TRUE); + LOG_W("partial frame timeout"); + } + rt_memset(pSOU->rx_buf, 0, sizeof(pSOU->rx_buf)); len = chrg_tty_recv(pTTY, pSOU->rx_buf, sizeof(pSOU->rx_buf)); if (len <= 0) { @@ -387,21 +453,17 @@ void chrg_south_thread_entry (void *data) rt_uint8_t resp_byte; while (rt_ringbuffer_data_len(pSOU->rb) > 0) { rt_ringbuffer_getchar(pSOU->rb, &resp_byte); - if (resp_byte == ACK || resp_byte == NAK || resp_byte == ACK_C) { + if (resp_byte == ACK || resp_byte == NAK || + resp_byte == ACK_C || resp_byte == YM_CAN) { chrg_eload_set_ymodem_resp(resp_byte); } } continue; } - //LOG_HEX("recvS", 32, pSOU->rx_buf, len); - - if (1 == pSOU->frame.findhead) { - if (rt_tick_get() >= pSOU->frame.last_tick + 100) { // 100ms - pSOU->frame.findhead = 0; // reset - //LOG_E("timeo\r\n"); - } - } +#if SOUTH_FRAME_DEBUG + LOG_HEX("recvS", 32, pSOU->rx_buf, len); +#endif chrg_south_pickup_frame(pSOU->rb, &pSOU->frame); } diff --git a/apps/chrg/applications/thread/chrg_thread.c b/apps/chrg/applications/thread/chrg_thread.c index d15de89..cdfb7fe 100644 --- a/apps/chrg/applications/thread/chrg_thread.c +++ b/apps/chrg/applications/thread/chrg_thread.c @@ -146,32 +146,40 @@ rt_err_t chrg_thread_evt_send (eIDX_THRS idx, rt_uint32_t data) 返回说明: 0: OK , !=0 : FAILED 其它说明: - *****************************************************************/ +static void chrg_thread_mb_free(void *data) +{ + struct mb_msg_t *pMB = (struct mb_msg_t *)data; + + if (RT_NULL == pMB) { + return; + } + if (RT_NULL != pMB->payload) { + rt_free(pMB->payload); + } + rt_free(pMB); +} + rt_err_t chrg_thread_mb_send (eIDX_THRS idx, void *data) { - rt_err_t ret = RT_EOK; + rt_err_t ret; - if (idx >= TOTAL_THRS) { + if ((idx >= TOTAL_THRS)||(RT_NULL == data)) { + chrg_thread_mb_free(data); return -RT_ERROR; } struct chrg_thread_t *pTHR = &chrgthr[idx]; - if (RT_NULL != pTHR->mb) { - ret = rt_mb_send(pTHR->mb, (rt_uint32_t)data); - if (RT_EOK != ret) { - if (ret == -RT_EFULL) { - LOG_E("mb full, drop message. [%d]", ret); - if (RT_NULL != data) { - struct mb_msg_t *pMB = (struct mb_msg_t *)data; - if (RT_NULL != pMB->payload) { - rt_free(pMB->payload); - } - rt_free(pMB); - } - } else { - LOG_E("mb send failed. [%d]", ret); - } - } + if (RT_NULL == pTHR->mb) { + LOG_E("mb '%s' is not ready.", pTHR->name); + chrg_thread_mb_free(data); + return -RT_ERROR; + } + + ret = rt_mb_send(pTHR->mb, (rt_uint32_t)data); + if (RT_EOK != ret) { + LOG_E("mb send failed, drop message. [%d]", ret); + chrg_thread_mb_free(data); } return ret; diff --git a/apps/chrg/applications/utils/chrg_def.h b/apps/chrg/applications/utils/chrg_def.h index 3ed8388..181f43c 100644 --- a/apps/chrg/applications/utils/chrg_def.h +++ b/apps/chrg/applications/utils/chrg_def.h @@ -12,6 +12,19 @@ #include +/* + * Channel polling mode: + * 1 - multi-channel hardware, advance the polling channel index; + * 0 - single-channel hardware, keep polling channel 0. + */ +#define CHRG_MULTI_CHANNEL 1 + +#if CHRG_MULTI_CHANNEL +#define CHRG_CHANNEL_NEXT(index) do { (index)++; } while (0) +#else +#define CHRG_CHANNEL_NEXT(index) do { } while (0) +#endif + // 4路选通 typedef enum { IDX_CH1 = 0, // 通路1 @@ -88,7 +101,7 @@ struct vc_pd_t { rt_uint8_t group_num; // 挡位数量 rt_uint8_t src_type; // 源类型 rt_uint8_t pps_type; // PPS类型 - struct vc_mm_t mm[2]; // 电压电流极值 + struct vc_mm_t mm; // 电压电流极值 }; /* 邮件内容 */ diff --git a/apps/chrg/applications/utils/chrg_eload.c b/apps/chrg/applications/utils/chrg_eload.c index d5986ef..2826834 100644 --- a/apps/chrg/applications/utils/chrg_eload.c +++ b/apps/chrg/applications/utils/chrg_eload.c @@ -18,7 +18,29 @@ #include "chrg_regs.h" #include "chrg_utils.h" #include "chrg_south.h" + +#define ELOAD_VALID_VOLTAGE_MIN_MV 2000U + extern void LCD_SHOW(rt_uint8_t idx, struct eload_p1_t *pP1, struct chrg_eload_t *pLOAD, struct chrg_switch_t *pSW, rt_uint8_t online); + +/* 每通道P1量测更新序号;32位读写在STM32F405上为原子访问。 */ +static volatile rt_uint32_t g_eload_sample_seq[TOTAL_SOU_CHS] = {0}; + +/***************************************************************** +函数名称: chrg_eload_get_sample_seq +函数描述: 获取指定南向通道P1量测数据的更新序号 +输入参数: idx:南向功率板通道 +输出参数: - +返回说明: 当前通道量测序号,通道非法时返回0 +其它说明: 卸载确认记录命令下发前序号,只接受序号变化后的新量测 +*****************************************************************/ +rt_uint32_t chrg_eload_get_sample_seq(rt_uint8_t idx) +{ + if (idx >= TOTAL_SOU_CHS) { + return 0; + } + return g_eload_sample_seq[idx]; +} static rt_uint16_t ripple_base[TOTAL_SOU_CHS] = {0}; static rt_uint8_t ripple_cal_flag[TOTAL_SOU_CHS] = {0}; rt_uint16_t real_ripple[TOTAL_SOU_CHS] = {0}; @@ -32,10 +54,31 @@ static rt_uint8_t g_eload_ymodem_ready = 0; // 应答就绪标志(1=有新应 函数描述: 存储南向板返回的ymodem应答字节(由chrg_roll_sou在RUN_UPDATA调用) 输入参数: resp: 南向ymodem应答 (ACK=0x06 / NAK=0x15 / 'C'=0x43) *****************************************************************/ +#define ELOAD_YMODEM_RESP_DEPTH 8 +static rt_uint8_t g_ymodem_resp_queue[ELOAD_YMODEM_RESP_DEPTH]; +static rt_uint8_t g_ymodem_resp_read = 0; +static rt_uint8_t g_ymodem_resp_write = 0; +static rt_uint8_t g_ymodem_resp_count = 0; + void chrg_eload_set_ymodem_resp(rt_uint8_t resp) { - g_eload_ymodem_resp = resp; - g_eload_ymodem_ready = 1; + rt_base_t level = rt_hw_interrupt_disable(); + + /* 队列已满时丢弃周期性的C,保留更关键的ACK/NAK/CAN。 */ + if (g_ymodem_resp_count >= ELOAD_YMODEM_RESP_DEPTH) { + if (resp == ACK_C) { + rt_hw_interrupt_enable(level); + return; + } + g_ymodem_resp_read = (g_ymodem_resp_read + 1) % ELOAD_YMODEM_RESP_DEPTH; + g_ymodem_resp_count--; + } + g_ymodem_resp_queue[g_ymodem_resp_write] = resp; + g_ymodem_resp_write = (g_ymodem_resp_write + 1) % ELOAD_YMODEM_RESP_DEPTH; + g_ymodem_resp_count++; + g_eload_ymodem_resp = resp; + g_eload_ymodem_ready = 1; + rt_hw_interrupt_enable(level); } /***************************************************************** @@ -45,13 +88,19 @@ void chrg_eload_set_ymodem_resp(rt_uint8_t resp) *****************************************************************/ rt_uint8_t chrg_eload_get_ymodem_resp(void) { - rt_uint8_t resp = 0; - if (g_eload_ymodem_ready) { - resp = g_eload_ymodem_resp; - g_eload_ymodem_ready = 0; - g_eload_ymodem_resp = 0; - } - return resp; + rt_uint8_t resp = 0; + rt_base_t level = rt_hw_interrupt_disable(); + + if (g_ymodem_resp_count > 0) { + resp = g_ymodem_resp_queue[g_ymodem_resp_read]; + g_ymodem_resp_read = (g_ymodem_resp_read + 1) % ELOAD_YMODEM_RESP_DEPTH; + g_ymodem_resp_count--; + } + g_eload_ymodem_ready = (g_ymodem_resp_count > 0); + g_eload_ymodem_resp = g_eload_ymodem_ready ? + g_ymodem_resp_queue[g_ymodem_resp_read] : 0; + rt_hw_interrupt_enable(level); + return resp; } /***************************************************************** @@ -60,8 +109,15 @@ rt_uint8_t chrg_eload_get_ymodem_resp(void) *****************************************************************/ void chrg_eload_clear_ymodem_resp(void) { - g_eload_ymodem_resp = 0; - g_eload_ymodem_ready = 0; + rt_base_t level = rt_hw_interrupt_disable(); + + g_ymodem_resp_read = 0; + g_ymodem_resp_write = 0; + g_ymodem_resp_count = 0; + g_eload_ymodem_resp = 0; + g_eload_ymodem_ready = 0; + rt_memset(g_ymodem_resp_queue, 0, sizeof(g_ymodem_resp_queue)); + rt_hw_interrupt_enable(level); } static int memcmp_8bytes(const rt_uint8_t *buf1, const rt_uint8_t *buf2) @@ -116,9 +172,20 @@ int chrg_eload_read_regs (rt_uint8_t addr, rt_uint16_t reg, rt_uint16_t len) u16_to_u8v(value, &buf[4]); crc = mb_crc16(buf, 6); u16_to_u8v(swap_u16(crc), &buf[6]); - rt_kprintf("addr = %d,reg = %02x,value = %02x\n",addr,reg,value); - return chrg_south_send(buf, 8); - } +#if SOUTH_COMMAND_DEBUG + rt_kprintf("south write: addr=%d reg=0x%04x value=0x%04x\n", + addr, reg, value); +#endif + return chrg_south_send(buf, 8); +} + +/* Read the version first so a newly powered south board is not queried with + * the long measurement frame before its Modbus interface is ready. */ +int chrg_eload_read_version (rt_uint8_t addr) +{ + return chrg_eload_read_regs(addr, MODBUSRTU_SW_VERSION_REG, + MODBUSRTU_SW_VERSION_LEN); +} /***************************************************************** 函数名称: chrg_eload_send_ymodem_data @@ -233,6 +300,24 @@ int chrg_eload_refresh (rt_uint8_t addr, rt_uint8_t part) return ret; } +int chrg_eload_parse_version (rt_uint8_t idx, rt_uint8_t *data, rt_uint16_t len) +{ + rt_uint16_t version; + + /* A one-register Modbus 0x03 response is 7 bytes including CRC16. */ + if ((idx >= TOTAL_SOU_CHS) || (data == RT_NULL) || + (len != (MODBUSRTU_SW_VERSION_LEN * 2U + 5U)) || + (data[0] != ID_ELOAD) || (data[1] != MB_REGISTER_RD) || + (data[2] != 2U) || (mb_crc16(data, len) != 0U)) { + return -1; + } + + version = u8v_to_u16(&data[3]); + /* HOLD_REG_CH0_VERSION is the per-channel south board version register. */ + usRegHoldBuf[HOLD_REG_CH0_VERSION + idx * TOTAL_HOLD_CH_REGS] = version; + return 0; +} + /***************************************************************** 函数名称: chrg_eload_parse 函数描述: 解析源载返回数据 @@ -276,9 +361,25 @@ int chrg_eload_parse (rt_uint8_t idx, rt_uint8_t pt, rt_uint8_t *data, rt_uint16 pLOAD->vpkn = swap_u16(pP1->Vpkn); pLOAD->vpp = swap_u16(pP1->Vpp); pLOAD->Ripple = swap_u16(pP1->Ripple); - pSW->Now_voltage = pLOAD->voltage; + /* 保留功率板返回的真实电流,供卸载保护判断使用。 */ pSW->Now_current = pLOAD->current; - if(idx == 0) + /* + * 低于2V时直接清零量测结果。只清理量测字段,保留工作状态、 + * 故障、模式及测试结果,避免影响通道状态判断。 + */ + if (pLOAD->voltage < ELOAD_VALID_VOLTAGE_MIN_MV) { + pLOAD->voltage = 0; + pLOAD->current = 0; + pLOAD->vpkp = 0; + pLOAD->vpkn = 0; + pLOAD->vpp = 0; + pLOAD->Ripple = 0; + pSW->VZ = 0; + pSW->VF = 0; + } + pSW->Now_voltage = pLOAD->voltage; + /* 电压、电流均更新完成后再发布序号,避免状态机读取到半更新数据。 */ + g_eload_sample_seq[idx]++; #if LCD_OPEN // for(int i = 0;i<4;i++){ // if(pNOR->sw[i].change_flag == 0x01){ @@ -338,17 +439,12 @@ void Show_Curr(rt_uint8_t idx, struct chrg_eload_t *pLOAD, char *tmp, rt_uint8_t } void Show_Ripple(rt_uint8_t idx, struct chrg_eload_t *pLOAD, char *tmp, rt_uint8_t online,struct chrg_switch_t *pSW) { - rt_int32_t ripple_rand = rand() % 11; // 生成0到10之间的随机数 - if(online == 0){ + if ((online == 0) || + (pSW->Now_voltage < ELOAD_VALID_VOLTAGE_MIN_MV)) { snprintf(tmp, 36, "status.CH%d_Ripple.val=0",idx+1); return; - }else{ - if(pSW->Now_voltage<200){ - snprintf(tmp, 36, "status.CH%d_Ripple.val=%d",idx+1, ripple_rand); - }else{ - snprintf(tmp, 36, "status.CH%d_Ripple.val=%d",idx+1, pLOAD->Ripple); - } } + snprintf(tmp, 36, "status.CH%d_Ripple.val=%d",idx+1, pLOAD->Ripple); } void Show_DZF(rt_uint8_t idx, struct chrg_eload_t *pLOAD, char *tmp, rt_uint8_t online,rt_uint32_t vzf) { @@ -391,4 +487,4 @@ void LCD_SHOW(rt_uint8_t idx, struct eload_p1_t *pP1, struct chrg_eload_t *pLOAD case SHOW_DZF: Show_DZF(idx, pLOAD, tmp, online,vzf); pSW->show_step = SHOW_STATUS; break; } chrg_lcd_send(tmp); -} \ No newline at end of file +} diff --git a/apps/chrg/applications/utils/chrg_eload.h b/apps/chrg/applications/utils/chrg_eload.h index ceaa267..0fbd63a 100644 --- a/apps/chrg/applications/utils/chrg_eload.h +++ b/apps/chrg/applications/utils/chrg_eload.h @@ -164,6 +164,10 @@ typedef enum { TOTAL_MB_R } eIDX_MB_PART; +/* South board firmware version register: one 16-bit register at 0x0301. */ +#define MODBUSRTU_SW_VERSION_REG 0x0301U +#define MODBUSRTU_SW_VERSION_LEN 1U + #define SIZE_P1_RD 19 // 读19个寄存器 #define SIZE_P2_RD 28 // 读28个寄存器 @@ -239,6 +243,15 @@ struct chrg_eload_t { *****************************************************************/ extern int chrg_eload_read_regs (rt_uint8_t addr, rt_uint16_t reg, rt_uint16_t len); +/***************************************************************** +Function: chrg_eload_read_version +Description: Request the south board firmware version register. +Input: addr: south board address +Output: - +Return: <0: error, >0: sent length +*****************************************************************/ +extern int chrg_eload_read_version (rt_uint8_t addr); + /***************************************************************** 函数名称: chrg_eload_write_reg 函数描述: 向南向发送写单个寄存器 @@ -268,6 +281,24 @@ extern int chrg_eload_refresh (rt_uint8_t addr, rt_uint8_t part); 其它说明: - *****************************************************************/ extern int chrg_eload_parse (rt_uint8_t idx, rt_uint8_t pt, rt_uint8_t *data, rt_uint16_t len); + +/***************************************************************** +Function: chrg_eload_parse_version +Description: Parse and store a valid south board firmware version. +Input: idx: channel, data: Modbus response, len: response length +Output: firmware version is stored at the channel version register +Return: 0: valid, <0: invalid response +*****************************************************************/ +extern int chrg_eload_parse_version (rt_uint8_t idx, rt_uint8_t *data, rt_uint16_t len); +/***************************************************************** +函数名称: chrg_eload_get_sample_seq +函数描述: 获取指定南向通道P1量测数据的更新序号 +输入参数: idx:南向功率板通道 +输出参数: - +返回说明: 当前通道量测序号,通道非法时返回0 +其它说明: 每成功解析一帧P1数据后递增,用于区分控制命令前后的新旧量测 +*****************************************************************/ +extern rt_uint32_t chrg_eload_get_sample_seq(rt_uint8_t idx); // 定义2条继电器指令,控制主继电器和滤波继电器(保留原有有效指令,移除未使用的停止指令) static const rt_uint8_t EXTRA_CMD_SOURCE[8] = {0x03, 0x06, 0x00, 0x01, 0x00, 0x01, 0x18, 0x28}; extern int chrg_eload_send_relay(rt_uint8_t cmd_type); diff --git a/apps/chrg/applications/utils/chrg_north_pkg.c b/apps/chrg/applications/utils/chrg_north_pkg.c index b442e14..bc6e685 100644 --- a/apps/chrg/applications/utils/chrg_north_pkg.c +++ b/apps/chrg/applications/utils/chrg_north_pkg.c @@ -41,14 +41,9 @@ int chrg_north_pkg_encode (eIDX_ID id, rt_uint8_t cmd, rt_uint8_t *data_in, if (len_in > 0) { rt_memcpy(&data_out[4], data_in, len_in); } - data_out[len_in+4] = 0x2a;//calc_crc8(data_out, len_in+4); -// rt_kprintf("pro_data= \n"); + data_out[len_in+4] = calc_crc8(data_out, len_in+4);//0x2a; *len_out = 5+len_in; -// for(int i = 0; i < *len_out;i++) -// { -// rt_kprintf("%02x ",data_out[i]); -// } -// rt_kprintf("\n"); + return 0; } @@ -81,7 +76,7 @@ int chrg_north_pkg_decode (rt_uint8_t *data, rt_uint16_t length, struct chrg_swi struct get_vol_ack_t *pAck = (struct get_vol_ack_t *)data; pS->vc.voltage = pAck->voltage; pS->vc.version = pAck->version; - // ? + // } break; @@ -89,9 +84,9 @@ int chrg_north_pkg_decode (rt_uint8_t *data, rt_uint16_t length, struct chrg_swi struct get_pd_pro_ack_t *pAck = (struct get_pd_pro_ack_t *)data; pS->pd_get[0].src_type = pAck->type; pS->pd_get[0].pps_type = pAck->pps; - pS->pd_get[0].mm[0].max_vol = pAck->vmax; - pS->pd_get[0].mm[0].min_vol = pAck->vmin; - pS->pd_get[0].mm[0].max_cur = pAck->curpwr; + pS->pd_get[0].mm.max_vol = pAck->vmax; + pS->pd_get[0].mm.min_vol = pAck->vmin; + pS->pd_get[0].mm.max_cur = pAck->curpwr; } break; diff --git a/apps/chrg/applications/utils/chrg_regs.h b/apps/chrg/applications/utils/chrg_regs.h index 635a128..c18391e 100644 --- a/apps/chrg/applications/utils/chrg_regs.h +++ b/apps/chrg/applications/utils/chrg_regs.h @@ -93,60 +93,17 @@ enum { TOTAL_COIL_REGS // ¼̵çÆ÷×ÜÊý£¨24¸ö£© }; -#define TOTAL_INPUT_CH_REGS (10) // µ¥ͨµÀ ÊäÈë¼ĴæÆ÷ - +#define TOTAL_INPUT_CH_REGS (50) // µ¥ͨµÀÊäÈë¼ĴæÆ÷£¬ͨµÀµØַ²½³¤Ϊ50 +#define TOTAL_INPUT_REGS TOTAL_INPUT_CH_REGS * 4 // ÊäÈë¼ĴæÆ÷Áбí +// ÿ¸öͨµÀռÓÃ50¸öµØַ£¬µ±ǰʵ¼ÊʹÓÃǰ4¸ö¼ĴæÆ÷£¬ÆäÓàµØַԤÁô¡£ enum { INPUT_REG_CH0_STA1 = INPUT_REG_START, // ͨµÀ0 ¹¤×÷״̬1 ¶¨ÒåͬԴÔØ INPUT_REG_CH0_STA2, // ͨµÀ0 ¹¤×÷״̬2 ¶¨ÒåͬԴÔØ INPUT_REG_CH0_FAULT1, // ͨµÀ0 ¹ÊÕÏ״̬1 ¶¨ÒåͬԴÔØ - INPUT_REG_CH0_FAULT2, // ͨµÀ0 ¹ÊÕÏ״̬2 ¶¨ÒåͬԴÔØ - INPUT_REG_04, // ÊäÈë¼ĴæÆ÷04 - INPUT_REG_05, // ÊäÈë¼ĴæÆ÷05 - INPUT_REG_06, // ÊäÈë¼ĴæÆ÷06 - INPUT_REG_07, // ÊäÈë¼ĴæÆ÷07 - INPUT_REG_08, // ÊäÈë¼ĴæÆ÷08 - INPUT_REG_09, // ÊäÈë¼ĴæÆ÷09 - // add more TOTAL_INPUT_CH_REGS - - INPUT_REG_CH1_STA1, // ͨµÀ1 ¹¤×÷״̬1 - INPUT_REG_CH1_STA2, // ͨµÀ1 ¹¤×÷״̬2 - INPUT_REG_CH1_FAULT1, // ͨµÀ1 ¹ÊÕÏ״̬1 - INPUT_REG_CH1_FAULT2, // ͨµÀ1 ¹ÊÕÏ״̬2 - INPUT_REG_14, // ÊäÈë¼ĴæÆ÷14 - INPUT_REG_15, // ÊäÈë¼ĴæÆ÷15 - INPUT_REG_16, // ÊäÈë¼ĴæÆ÷16 - INPUT_REG_17, // ÊäÈë¼ĴæÆ÷17 - INPUT_REG_18, // ÊäÈë¼ĴæÆ÷18 - INPUT_REG_19, // ÊäÈë¼ĴæÆ÷19 - - INPUT_REG_CH2_STA1, // ͨµÀ2 ¹¤×÷״̬1 - INPUT_REG_CH2_STA2, // ͨµÀ2 ¹¤×÷״̬2 - INPUT_REG_CH2_FAULT1, // ͨµÀ2 ¹ÊÕÏ״̬1 - INPUT_REG_CH2_FAULT2, // ͨµÀ2 ¹ÊÕÏ״̬2 - INPUT_REG_24, // ÊäÈë¼ĴæÆ÷24 - INPUT_REG_25, // ÊäÈë¼ĴæÆ÷25 - INPUT_REG_26, // ÊäÈë¼ĴæÆ÷26 - INPUT_REG_27, // ÊäÈë¼ĴæÆ÷27 - INPUT_REG_28, // ÊäÈë¼ĴæÆ÷28 - INPUT_REG_29, // ÊäÈë¼ĴæÆ÷29 - - INPUT_REG_CH3_STA1, // ͨµÀ3 ¹¤×÷״̬1 - INPUT_REG_CH3_STA2, // ͨµÀ3 ¹¤×÷״̬2 - INPUT_REG_CH3_FAULT1, // ͨµÀ3 ¹ÊÕÏ״̬1 - INPUT_REG_CH3_FAULT2, // ͨµÀ3 ¹ÊÕÏ״̬2 - INPUT_REG_34, // ÊäÈë¼ĴæÆ÷34 - INPUT_REG_35, // ÊäÈë¼ĴæÆ÷35 - INPUT_REG_36, // ÊäÈë¼ĴæÆ÷36 - INPUT_REG_37, // ÊäÈë¼ĴæÆ÷37 - INPUT_REG_38, // ÊäÈë¼ĴæÆ÷38 - INPUT_REG_39, // ÊäÈë¼ĴæÆ÷39 - INPUT_REG_40, // ÊäÈë¼ĴæÆ÷40 - - - TOTAL_INPUT_REGS // ÊäÈë¼ĴæÆ÷×ÜÊý + INPUT_REG_CH0_FAULT2 // ͨµÀ0 ¹ÊÕÏ״̬2 ¶¨ÒåͬԴÔØ + }; -//AIÐ޸쬲»һ¶¨׼ȷ // ======================== мĴæÆ÷ӳÉä (»ùÓÚ0x06/0x10±ê׼Modbus¹¦ÄÜÂë) ======================== // µØַ¿ռä¹滮: // ¸ºÔØ(Sink): 0x0000 + ͨµÀºšÁ29 + ²ÎÊýƫÒÆ (4ͨµÀ¡Á29=116¸ö, 0x0000~0x0073) diff --git a/apps/chrg/applications/utils/chrg_sink.c b/apps/chrg/applications/utils/chrg_sink.c index 64b705d..97361d3 100644 --- a/apps/chrg/applications/utils/chrg_sink.c +++ b/apps/chrg/applications/utils/chrg_sink.c @@ -18,6 +18,110 @@ #include "chrg_def.h" #include "chrg_rel.h" #include "chrg_comm.h" +#include "chrg_eload.h" + +#define SINK_UNLOAD_QUERY_INTERVAL_MS 20U // 卸载期间两次优先量测请求的最小间隔 +#define SINK_UNLOAD_CURRENT_MAX_MA 200U // 判定负载已安全卸载的最大实际电流 +#define SINK_UNLOAD_CONFIRM_COUNT 2U // 连续满足低电流条件的量测帧数 + +/* 主继电器闭合跟踪:1=已提交ALL_REL_ON(主继电器应已闭合) 0=未闭合/已断开。 + * 用于0V硬保护区分"首次开载/握手恢复"(主继电器未闭合,Now_voltage=0属正常) + * 与"运行或待机中主继电器已闭合但输入电压为0"(协议板断电窗口,禁止开载)。 */ +static rt_uint8_t g_sink_rel_main_closed[TOTAL_SOU_CHS] = {0}; + +/* 主继电器电压到位确认:1=提交闭合后实测电压已到达工作下限,继电器确认闭合。 + * 持续0V监控只对已到位通道生效,避免继电器动作期间(电压尚未建立)误触发掉电保护。 */ +static rt_uint8_t g_sink_rel_main_ready[TOTAL_SOU_CHS] = {0}; + +/* 卸载确认只接受停止批次成功后产生的新P1量测。 */ +static rt_uint32_t g_sink_unload_sample_seq[TOTAL_SOU_CHS] = {0}; +static rt_bool_t g_sink_unload_batch_confirmed[TOTAL_SOU_CHS] = {RT_FALSE}; +static rt_tick_t g_sink_unload_query_tick[TOTAL_SOU_CHS] = {0}; +static rt_uint8_t g_sink_unload_confirm_count[TOTAL_SOU_CHS] = {0}; + +enum { + SINK_UNLOAD_NEXT_RUNNING = 0, + SINK_UNLOAD_NEXT_HANDSHAKE, + SINK_UNLOAD_NEXT_STOP_RELAY +}; + +/***************************************************************** +函数名称: chrg_sink_load_should_start +函数描述: 判断普通负载的设定值是否允许启动南向电子负载 +输入参数: pSW:当前通道控制结构体 +输出参数: - +返回说明: RT_TRUE:允许WORK_START RT_FALSE:应保持WORK_STOP +其它说明: 门限只作用于恒流模式的负载电流loadc;协议电流pro_loadc无论 + 大小都继续参与北向握手,恒压模式不受该恒流门限限制 +*****************************************************************/ +rt_bool_t chrg_sink_load_should_start(const struct chrg_switch_t *pSW) +{ + if (pSW == RT_NULL) { + return RT_FALSE; + } + if (pSW->CV_mode != MODE_CONSTANT_CURRENT) { + return RT_TRUE; + } + return (pSW->sink.loadc >= SINK_LOAD_ENABLE_MIN_MA) ? + RT_TRUE : RT_FALSE; +} + +/***************************************************************** +函数名称: chrg_sink_unload_confirm_reset +函数描述: 清除指定通道的卸载批次和低电流确认上下文 +输入参数: idx:南向功率板通道 +输出参数: 清零量测序号、批次确认标志、查询tick和连续确认次数 +返回说明: - +其它说明: 每次开始软卸载或完整重握手前调用,禁止沿用旧量测结果 +*****************************************************************/ +static void chrg_sink_unload_confirm_reset(eIDX_SOU_CH idx) +{ + if (idx >= TOTAL_SOU_CHS) { + return; + } + g_sink_unload_sample_seq[idx] = 0U; + g_sink_unload_batch_confirmed[idx] = RT_FALSE; + g_sink_unload_query_tick[idx] = 0U; + g_sink_unload_confirm_count[idx] = 0U; +} + +/***************************************************************** +函数名称: chrg_sink_handshake_north_ready +函数描述: 判断完整握手的南向安全批次是否已经执行完成 +输入参数: idx:南向功率板通道 +输出参数: - +返回说明: RT_TRUE:辅助继电器命令已完成并已建立低电流量测基线 + RT_FALSE:南向安全批次仍在执行 +其它说明: 基线建立后返回成功,实际低电流确认仍由当前状态函数继续执行; + 用于允许北向协议/CC与南向低电流确认并行推进 +*****************************************************************/ +/***************************************************************** +函数名称: chrg_sink_unload_query_submit +函数描述: 按最小间隔提交当前通道的一次性P1量测请求 +输入参数: idx:南向功率板通道 force:RT_TRUE立即提交 RT_FALSE按间隔提交 +输出参数: 更新本通道最近一次查询tick +返回说明: 0:请求成功、已存在或尚未到查询时间 <0:请求提交失败 +其它说明: 电流长期不归零时保留普通轮询窗口,避免持续优先查询阻塞其它通道 +*****************************************************************/ +static int chrg_sink_unload_query_submit(eIDX_SOU_CH idx, rt_bool_t force) +{ + rt_tick_t now; + + if (idx >= TOTAL_SOU_CHS) { + return -1; + } + now = rt_tick_get(); + if (!force && + ((rt_tick_t)(now - g_sink_unload_query_tick[idx]) < + rt_tick_from_millisecond(SINK_UNLOAD_QUERY_INTERVAL_MS))) { + return 0; + } + if (chrg_sou_measure_once_submit(idx) != 0) { + return -1; + } + g_sink_unload_query_tick[idx] = now; + return 0; +} /***************************************************************** 函数名称: sink_get_vc 函数描述: sink电压电流查询请求 @@ -128,454 +232,738 @@ int sink_set_pd (rt_uint8_t pd, rt_uint8_t *buf) return chrg_north_send(frame, len); } -//// 短路测试参数解析 指令码:0x01 -//void sink_test_short_set(struct chrg_switch_t *pSW, rt_uint8_t *pData, rt_uint8_t run_flag) -//{ -// struct test_short *pTest = &pSW->sink.test.short_test; - -// // 解析报文:高字节pData[1]、低字节pData[2] → 测试保持时间 -// pTest->hold_time = (pData[1] << 8) | pData[2]; -// pTest->set_mode = pData[3]; // 模式选择:0=测试后一直保持;1=保持指定时长后恢复原有电流 -// rt_kprintf("[SINK] Short Test: time=%d, mode=%d\n", -// pTest->hold_time, pTest->set_mode); -//} - -////============================================================================= -//// OCP过流保护测试参数解析 指令码:0x02 -////============================================================================= -//void sink_test_ocp_set(struct chrg_switch_t *pSW, rt_uint8_t *pData, rt_uint8_t run_flag) -//{ -// struct test_OCP *pTest = &pSW->sink.test.OCP_test; - -// pTest->start_curr = (pData[1] << 8) | pData[2]; // 解析:OCP测试起始电流 -// pTest->end_curr = (pData[3] << 8) | pData[4]; // 解析:OCP测试终止电流 -// pTest->change_curr = (pData[5] << 8) | pData[6]; // 解析:电流步进值 -// pTest->change_keep_time = (pData[7] << 8) | pData[8]; // 解析:单步电流保持时间 -// pTest->hold_time = (pData[9] << 8) | pData[10]; // 解析:OCP触发后保持时间 -// pTest->stop_volt = (pData[11]<< 8) | pData[12]; // 解析:停止判定电压 -// pTest->set_mode = pData[13]; // 测试结束模式:0=保持设定时间后恢复;1=一直保持 -// pTest->hold_curr = pData[14]; // OCP触发后是否带载,0=带载,1=不带载 -// // pTest->change_mode = pData[11]; // 电流变化模式(预留):0=置0;1=达标立即置0;2=保持当前电流 -// // pTest->interval_time = pData[12]; // 指令保护间隔时间(预留) - -// pSW->sink.loadc = pTest->start_curr; // 负载电流初始化为OCP起始电流 -//#if SINK_TEST_DEBUG -// rt_kprintf("[SINK] OCP Test: start=%d, end=%d\n", -// pTest->start_curr, pTest->end_curr); -//#endif -//} - -////============================================================================= -//// 动态负载测试参数解析 指令码:0x03 -////============================================================================= -//void sink_test_dynamic_set(struct chrg_switch_t *pSW, rt_uint8_t *pData, rt_uint8_t run_flag) -//{ -// struct test_change *pTest = &pSW->sink.test.change_test; -//// rt_uint16_t change_curr = 0; -// pTest->curr1_set = (pData[1] << 8) | pData[2]; // 解析:第一阶段设定电流 -// pTest->time1_set = (pData[3] << 8) | pData[4]; // 解析:第一阶段保持时长 -// pTest->curr2_set = (pData[5] << 8) | pData[6]; // 解析:第二阶段设定电流 -// pTest->time2_set = (pData[7]<< 8) | pData[8]; // 解析:第二阶段保持时长 -// pTest->k_Rise = (pData[9] << 8) | pData[10]; // 电流上升斜率 -// pTest->k_Fall = (pData[11]<< 8) | pData[12]; // 电流下降斜率 -// // pTest->start_time = (pData[14]<< 8) | pData[13]; // 产品稳定时长(预留) - -// // pSW->sink.loadc = pTest->curr1_set; -//#if SINK_TEST_DEBUG -// rt_kprintf("[SINK] Dynamic Test: curr1=%d, curr2=%d\n", -// pTest->curr1_set, pTest->curr2_set); -//#endif -//} - -////============================================================================= -//// 短路测试状态机执行函数 -//// 依据 work_step 分步配置寄存器,启动短路测试 -////============================================================================= -//void chrg_nor_sink_short_test(rt_uint8_t ch, struct chrg_switch_t *pSW) -//{ -// struct test *pTest = &pSW->sink.test; -// -// switch (pTest->work_step) -// { -// case 0: -// // 第一步:切换为电子负载模式,吸合负载开关 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_ELOAD, ELOAD_LOAD); -//// rt_kprintf("first\r\n"); -// chrg_nor_sw_rel((eIDX_NOR_CH)ch,ID_SINK); -// pTest->work_step = 1; -// break; -// case 1: -// // 根据模式分支配置参数 -// if(pTest->short_test.set_mode == 0x00) { //直接释放 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_ShortTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN_NO_CURR); //直接为0 -// pTest->work_step = 2; -// } -// else{ //按设定的时间后释放 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_ShortTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN_ALWAYS_KEPP); //保持 -// pTest->work_step = 4; -// } -// -// rt_kprintf("set_mode = %d",pTest->short_test.set_mode); -// break; -// case 2: -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_ShortTest_Hold_Time , pTest->short_test.hold_time); -// pTest->work_step = 4; -// break; -// case 4: -// // 启动整机工作,清零状态标志 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_WORK, WORK_START); -// pTest->work_step = 5; -// pSW->change_flag = 0; -// break; -// case 5: -// break; -// default: -// pTest->work_step = 0; -// break; -// } -//} - -////============================================================================= -//// OCP过流保护测试状态机执行函数 -//// 分步配置电流、时长、模式寄存器,最终启动OCP测试 -////============================================================================= -//// OCP测试执行 -//void chrg_nor_sink_OCP_test(rt_uint8_t ch, struct chrg_switch_t *pSW) -//{ -// struct test *pTest = &pSW->sink.test; - -// switch (pTest->work_step) -// { -// case 0: // 初始化:切负载模式、闭合负载开关 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_ELOAD, ELOAD_LOAD); -//// rt_kprintf("first\r\n"); -// chrg_nor_sw_rel((eIDX_NOR_CH)ch,ID_SINK); -// pTest->work_step = SET_OCP_STARTCURR; -// break; -// case SET_OCP_STARTCURR: // 设置OCP起始电流 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_StartCurrent, pTest->OCP_test.start_curr); -// pTest->work_step = SET_OCP_ENDCURR; -// break; -// case SET_OCP_ENDCURR: // 设置OCP终止电流 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_StopCurrent , pTest->OCP_test.end_curr); -// pTest->work_step = SET_OCP_STEP; -// break; -// case SET_OCP_STEP: // 设置电流步进值 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_Step_CURR , pTest->OCP_test.change_curr); -// pTest->work_step = SET_OCP_STEP_HOLD_TIME; -// break; -// case SET_OCP_STEP_HOLD_TIME: // 设置单步保持时间 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_Step_Hold_Time , pTest->OCP_test.change_keep_time); -// pTest->work_step = SINK_OCP_STOP_VOLT; -// break; -// case SINK_OCP_STOP_VOLT: //设置截止电压 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_Vtrig_Stop , pTest->OCP_test.stop_volt); -// pTest->work_step = SET_OCP_CMD_MODE_RUN; -// break; -// case SET_OCP_CMD_MODE_RUN: // 标记测试运行模式 -// if(pTest->OCP_test.set_mode == 0x00) { //释放 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN_NO_CURR); //直接为0 -// pTest->work_step = SET_OCP_HOLD_TIME; -// } -// else{ //保持 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN_ALWAYS_KEPP); //保持 -// pTest->work_step = SET_OCP_RUNNING; -// } -// break; -// case SET_OCP_HOLD_TIME: // 设置OCP触发后保持时间 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_OCPTest_Hold_Time , pTest->OCP_test.hold_time); -// pTest->work_step = SET_OCP_RUNNING; -// break; -// case SET_OCP_RUNNING: // 启动OCP测试 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_WORK, WORK_START); -// pSW->change_flag = 0; -// pTest->work_step = 0x10; -// break; -// case 0x10: -// break; -// default: -// break; -// } -//} -////============================================================================= -//// 动态负载测试状态机执行函数 -//// 依次配置两段电流、时长、升降斜率,启动动态负载测试 -////============================================================================= -//void chrg_nor_sink_change_test(rt_uint8_t ch, struct chrg_switch_t *pSW) -//{ -// struct test *pTest = &pSW->sink.test; - -// switch (pTest->work_step) -// { -// case 0: // 初始步骤:切换电子负载模式、闭合负载开关 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_ELOAD, ELOAD_LOAD); -// chrg_nor_sw_rel((eIDX_NOR_CH)ch,ID_SINK); -// pTest->work_step = SET_DYNA_CURR1; -// break; -// case SET_DYNA_CURR1: // 设置第一路输出电流 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_L1, pTest->change_test.curr1_set); -// pTest->work_step = SET_DYNA_TIME1; -// #if SINK_TEST_DEBUG -// rt_kprintf("Curr1: %d\n", pTest->change_test.curr1_set); -// #endif -// break; -// case SET_DYNA_TIME1: // 设置第一阶段保持时间 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_T1 , pTest->change_test.time1_set); -// pTest->work_step = SET_DYNA_CURR2; -// #if SINK_TEST_DEBUG -// rt_kprintf("Time1: %d\n", pTest->change_test.time1_set); -// #endif -// break; -// case SET_DYNA_CURR2: // 设置第二路输出电流 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_L2 , pTest->change_test.curr2_set); -// pTest->work_step = SET_DYNA_TIME2; -// #if SINK_TEST_DEBUG -// rt_kprintf("Curr2: %d\n", pTest->change_test.curr2_set); -// #endif -// break; -// case SET_DYNA_TIME2: // 设置第二阶段保持时间 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_T2 , pTest->change_test.time2_set); -// pTest->work_step = SET_DYNA_RISE_SLEW_RATE; -// #if SINK_TEST_DEBUG -// rt_kprintf("Time2: %d\n", pTest->change_test.time2_set); -// #endif -// break; -// case SET_DYNA_RISE_SLEW_RATE: // 设置电流上升斜率 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_Rise_Slew_Rate , pTest->change_test.k_Rise); -// pTest->work_step = SET_DYNA_FALL_SLEW_RATE; -// #if SINK_TEST_DEBUG -// rt_kprintf("Rise Slew Rate: %d\n", pTest->change_test.k_Rise); -// #endif -// break; -// case SET_DYNA_FALL_SLEW_RATE: // 设置电流下降斜率 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_Fall_Slew_Rate , pTest->change_test.k_Fall); -// pTest->work_step = SET_DYNA_CND_MODE; -// #if SINK_TEST_DEBUG -// rt_kprintf("Fall Slew Rate: %d\n", pTest->change_test.k_Fall); -// #endif -// break; -// case SET_DYNA_CND_MODE: // 标记动态测试为运行状态 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN); -// pTest->work_step = SET_DYNA_RUNNING; -// pTest->work_step = SET_DYNA_CND_MODE; -// #if SINK_TEST_DEBUG -// rt_kprintf("Fall Slew Rate: %d\n", pTest->change_test.k_Fall); -// #endif -// break; -// case SET_DYNA_RUNNING: // 启动整机工作,锁定当前运行步骤、清空标志位 -// chrg_set_sou_reg((eIDX_SOU_CH)ch, REG_WORK, WORK_START); -// pTest->work_step = SET_DYNA_RUNNING; -// pTest->work_step = 0x11; -// pSW->change_flag = 0; -// break; -// case 0x11: -// break; -// default: -// #if SINK_TEST_DEBUG -// rt_kprintf("Invalid work step: %d\n", pTest->work_step); -// #endif -// break; -// } -//} - -////============================================================================= -//// 短路测试停止处理函数 -//// 依据 stop_step 分步执行停止流程,清除测试标记并跳转状态 -////============================================================================= -//void chrg_nor_sink_short_test_stop(rt_uint8_t idx, struct chrg_switch_t *pSW,rt_uint8_t *enable) -//{ -// struct test *pTest = &pSW->sink.test; -// switch (pTest->stop_step) -// { -// case TEST_STOP_CMD_MODE: // 下发停止指令,关闭短路测试运行模式 -// chrg_set_sou_reg(idx, ModbusRTU_ShortTest_CMD_MODE_FLAG, ModbusRTU_DYNA_CMD_MODE_STOP); -// // pTest->stop_step = TEST_STOP_RUN; -// // break; -// // case TEST_STOP_RUN: // 清除测试标志,切换至负载状态管理流程 -// //chrg_set_sou_reg(idx, REG_WORK, WORK_STOP); -// //pTest->stop_step = TEST_STOP_REL; -// //chrg_nor_sw_rel((eIDX_NOR_CH)idx,0); -// //*enable = 0; -// pSW->change_flag = 0; -// pSW->sink.test.test_mode = 0; // 清空短路测试标记 -// pSW->sub = 21; // IDX_SET_SINK_WORK_STATE; 跳转至负载工作状态处理 -// break; -// default: -// break; -// } -//} - -////============================================================================= -//// OCP过流保护测试停止处理函数 -////============================================================================= -//void chrg_nor_sink_OCP_test_stop(rt_uint8_t idx, struct chrg_switch_t *pSW,rt_uint8_t *enable) -//{ -// struct test *pTest = &pSW->sink.test; -// switch (pTest->stop_step) -// { -// case TEST_STOP_CMD_MODE: // 下发停止指令,关闭OCP测试运行模式 -// chrg_set_sou_reg((eIDX_SOU_CH)idx, ModbusRTU_OCPTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_STOP); -// pSW->change_flag = 0; -// pSW->sink.test.test_mode = 0; // 清空OCP测试标记 -// pSW->sub = 21; // IDX_SET_SINK_WORK_STATE; -// break; -// default: -// break; -// } -//} - -////============================================================================= -//// 动态负载测试停止处理函数 -////============================================================================= -//void chrg_nor_sink_change_test_stop(rt_uint8_t idx, struct chrg_switch_t *pSW,rt_uint8_t *enable) -//{ -// struct test *pTest = &pSW->sink.test; -// switch (pTest->stop_step) -// { -// case TEST_STOP_CMD_MODE: // 下发停止指令,关闭动态测试运行模式 -// chrg_set_sou_reg((eIDX_SOU_CH)idx, ModbusRTU_DYNA_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_STOP); -// pSW->change_flag = 0; -// pSW->sink.test.test_mode = 0; // 清空动态测试标记 -// pSW->sub = 21; // IDX_SET_SINK_WORK_STATE; -// break; -// default: -// break; -// } -//} - -//sink开机初始化 -void chrg_sink_work_init(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) -{ - struct chrg_south_t *pSOU = &chrgsouth; +/***************************************************************** +函数名称: chrg_sink_work_init +函数描述: 执行负载INIT入口,将副继电器和初始化命令按顺序提交南向 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 立即打开北向Sink继电器和enable,并进入INIT_AUX_WAIT后台确认 +返回说明: 0:初始化批次提交成功 <0:参数或命令提交失败 + 其它说明: 南向顺序为FU_REL_ON、CURRENT=0、MODE、VON和可选测试停止; + 北向按“CC线->协议->电压查询”顺序执行,南北向从批次入队后并行执行 +*****************************************************************/ +static int chrg_sink_work_init(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ struct chrg_north_t *pNOR = &chrgnorth; - - pSW->sink.On_work = 1; - pSOU->enable[idx] = 1; - pSOU->online[idx] = 1; - pNOR->enable[idx] = 1; - - chrg_nor_sw_rel((eIDX_NOR_CH)idx, ID_SINK); //打开北向继电器 - // 1. 电流输出置0 - chrg_sou_com_batch_add_reg(idx, REG_CURRENT_OUT+1, 0); - // 2. 设置硬件恒压/恒流模式 - chrg_sou_com_batch_add_reg(idx,REG_MODE,pSW->CV_mode); - switch(pSW->sink.test.test_mode_old){ - case 0: - - break; - case 1://短路测试退出 - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)idx, ModbusRTU_ShortTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_STOP); - break; - case 2: - - break; - case 3: - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)idx, ModbusRTU_DYNA_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_STOP); - break; + rt_uint16_t regs[5] = {REG_REL_ON, REG_CURRENT_OUT + 1, + REG_MODE, Eload_Start_V_ON, 0}; + rt_uint16_t vals[5] = {FU_REL_ON, 0, 0, 0, 0}; + rt_bool_t first_start; + rt_uint8_t count = 4U; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; } + /* COM进入INIT前已经设置On_work,使用原工作状态区分首次提交和后台重试。 */ + first_start = (pSW->sink.work_mode != SINK_WORK_INIT_AUX_WAIT) ? + RT_TRUE : RT_FALSE; + vals[2] = pSW->CV_mode; + vals[3] = pSW->sink.volt_on <= 500 ? 500 : pSW->sink.volt_on; + if (pSW->sink.test.test_mode_old == SINK_TEST_SHORT) { + regs[count] = ModbusRTU_ShortTest_CMD_MODE_FLAG; + vals[count++] = ModbusRTU_DYNA_CMD_MODE_STOP; + } else if (pSW->sink.test.test_mode_old == SINK_TEST_OCP) { + regs[count] = ModbusRTU_OCPTest_CMD_MODE_FLAG; + vals[count++] = ModbusRTU_DYNA_CMD_MODE_STOP; + } else if (pSW->sink.test.test_mode_old == SINK_TEST_DYNA) { + regs[count] = ModbusRTU_DYNA_CMD_MODE_FLAG; + vals[count++] = ModbusRTU_DYNA_CMD_MODE_STOP; + } + if (chrg_sou_com_batch_submit(idx, regs, vals, count) != 0) { + return -1; + } + + pSW->sink.On_work = 1; + if (first_start) { + /* 南向批次已入队,北向按“CC->协议”顺序立即开始完整握手。 */ + chrg_nor_sw_rel((eIDX_NOR_CH)idx, ID_SINK); + pNOR->enable[idx] = 1; + /* Protocol-before-CC test flow is disabled in this version. */ + pSW->sub = IDX_SET_SINK_CC_BEFORE_PD; + } + pSW->sink.work_mode = SINK_WORK_INIT_AUX_WAIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_work_init_aux_wait +函数描述: 后台等待整组南向初始化命令完成,不阻塞北向协议握手 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 初始化完成后进入SINK_WORK_WAIT,失败时重提整个初始化批次 +返回说明: 0:继续等待或初始化完成 <0:参数错误 +其它说明: 重试时保留当前北向sub,禁止把正在执行的协议/CC事务拉回起点 +*****************************************************************/ +static int chrg_sink_work_init_aux_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + struct chrg_south_t *pSOU = &chrgsouth; + int batch_result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 0; + } + batch_result = chrg_sou_com_batch_get_result(idx); + if (batch_result <= 0) { + /* 北向保持当前事务,南向独立重试完整初始化批次。 */ + return chrg_sink_work_init(idx, pSW); + } + + /* 北向可能已推进到CC或电压查询,此处只更新南向完成状态。 */ + pSOU->online[idx] = 1; pSW->sink.test.test_mode_old = 0; pSW->sink.work_mode = SINK_WORK_WAIT; + return 0; } -//sink进入运行状态 -void chrg_sink_work_volt_runing(eIDX_SOU_CH idx, struct chrg_switch_t *pSW){ - chrg_sou_com_batch_add_reg(idx, 0x01, 0x02); - //打开主继电器 - chrg_sou_com_batch_add_reg(idx, 0x01, 0x03); - if(pSW->CV_mode == MODE_CONSTANT_VOLTAGE) - { - chrg_sou_com_batch_add_reg(idx, REG_VOLTAGE_OUT+1, pSW->sink.loadv); - } - else - { - chrg_sou_com_batch_add_reg(idx, REG_CURRENT_OUT+1, pSW->sink.loadc); - } - chrg_sou_com_batch_add_reg(idx, REG_WORK, WORK_START); -} - -//sink进入低电压状态 -void chrg_sink_nor_work_low(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +/***************************************************************** +函数名称: chrg_sink_volt_low_protect +函数描述: 主继电器闭合后持续监控南向实测输入电压,掉电立即转掉电保护 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 电压到位时确认ready;掉电时强制进入SINK_WORK_LOW并执行掉电批次 +返回说明: 0:正常或保护批次已提交 <0:参数错误或保护批次提交失败 +其它说明: 由北向轮询线程每轮调用,不依赖北向0x20回码。协议板断电时北向 + 回码会中断(volt_low计数不再累积),而南向P1量测由功率板独立 + 供电持续刷新,是本保护唯一可靠的电量来源。只有"提交闭合且实测 + 电压已到位"的通道才启用掉电判定,继电器动作期间不误触发 +*****************************************************************/ +int chrg_sink_volt_low_protect(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) { - chrg_sou_com_batch_add_reg(idx, 0x01, 0x00); - chrg_sou_com_batch_add_reg(idx, REG_WORK, WORK_STOP); + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (g_sink_rel_main_closed[idx] == 0) { + return 0; + } + if (pSW->Now_voltage >= WORK_MIN_VOLT) { + /* 实测电压到达工作下限,确认主继电器已闭合。 */ + g_sink_rel_main_ready[idx] = 1; + return 0; + } + if (g_sink_rel_main_ready[idx] != 0) { + /* 曾经到位后再次掉电:协议板处于断电窗口,禁止负载带载, + * 强制进入掉电安全处理等待协议板重新上电。 */ + pSW->sink.work_mode = SINK_WORK_LOW; + return chrg_sink_work(idx, pSW); + } + /* 已提交闭合但电压尚未建立(继电器动作中),继续等待不保护。 */ + return 0; } -//com端关机流程 -void chrg_sink_nor_work_off(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) -{ +/***************************************************************** +函数名称: chrg_sink_work_volt_runing +函数描述: 执行负载RUNING状态,闭合全部继电器并启动负载 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 非0:命令提交失败 +其它说明: 辅助继电器已在INIT、重握手或掉电恢复阶段打开;RUNING只发送 + ALL_REL_ON、目标负载值和WORK_START,避免FU_REL_ON重复关闭主继电器 +*****************************************************************/ +static int chrg_sink_work_volt_runing(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + rt_uint16_t regs[3] = {REG_REL_ON, 0, REG_WORK}; + rt_uint16_t vals[3] = {ALL_REL_ON, 0, WORK_START}; + int ret; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (!chrg_sink_load_should_start(pSW)) { + /* + * 北向协议已完成,低电流设定仍需要接通主继电器,使负载通路 + * 处于完整连接状态;但禁止电子负载带载,仅发送WORK_STOP, + * 不改写电流设定,避免空载待机影响下一次有效电流设置。 + */ + rt_uint16_t stop_regs[2] = {REG_REL_ON, REG_WORK}; + rt_uint16_t stop_vals[2] = {ALL_REL_ON, WORK_STOP}; + + ret = chrg_sou_com_batch_submit(idx, stop_regs, stop_vals, 2U); + if (ret == 0) { + pSW->sink.work_mode = SINK_WORK_LOAD_STANDBY; + g_sink_rel_main_closed[idx] = 1; + } + return ret; + } + + /* 只有南向实测电压曾经到位后再次掉到低压,才判定为掉电。 + * 初次RUNING时继电器批次刚入队,Now_voltage可能仍是上一帧的0, + * 此时不能取消尚未发送到WORK_START的启动批次。 */ + if ((g_sink_rel_main_ready[idx] != 0) && + (pSW->Now_voltage < WORK_MIN_VOLT)) { + pSW->sink.work_mode = SINK_WORK_LOW; + return 0; + } + + if (pSW->CV_mode == MODE_CONSTANT_VOLTAGE) { + regs[1] = REG_VOLTAGE_OUT + 1; + vals[1] = pSW->sink.loadv; + } else { + regs[1] = REG_CURRENT_OUT + 1; + vals[1] = pSW->sink.loadc; + } + /* RUNING批次必须完整发送:ALL_REL_ON、目标负载值、WORK_START。 */ + ret = chrg_sou_com_batch_submit(idx, regs, vals, 3U); + if (ret == 0) { + g_sink_rel_main_closed[idx] = 1; + } + return ret; +} + +/***************************************************************** +函数名称: chrg_sink_work_load_stop +函数描述: 普通负载运行中设定电流降到最低门限以下时安全停止带载 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 命令入队成功后进入SINK_WORK_LOAD_STANDBY +返回说明: 0:命令提交成功 <0:参数或命令提交失败 +其它说明: 命令只发送WORK_STOP;不改变北向协议、CC、电流设定和 + 主副继电器,保证后续恢复有效电流时无需重新协议握手 +*****************************************************************/ +static int chrg_sink_work_load_stop(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t val = WORK_STOP; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_submit(idx, ®, &val, 1U) != 0) { + return -1; + } + pSW->sink.work_mode = SINK_WORK_LOAD_STANDBY; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_work_load_start +函数描述: 从主继电器未打开的低电流待机状态等待北向电压稳定后再启动负载 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 命令入队成功后进入SINK_WORK_RUNING +返回说明: 0:命令提交成功 <0:参数或命令提交失败 +其它说明: 本状态只作为待启动标记,不直接下发继电器或WORK_START;必须由北向 + 解析确认实际电压稳定后转入SINK_WORK_RUNING,再执行标准启动顺序 +*****************************************************************/ +static int chrg_sink_work_load_start(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (!chrg_sink_load_should_start(pSW)) { + return -1; + } + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_nor_work_low +函数描述: 执行负载掉电处理,卸载后关闭主副继电器并重新打开副继电器 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 非0:命令提交失败 +其它说明: 命令顺序为WORK_STOP、CURRENT=1000mA、ALL_REL_OFF、FU_REL_ON; + ALL_REL_OFF后立即恢复副继电器,为协议板重新得电保留通路 +*****************************************************************/ +static int chrg_sink_nor_work_low(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t value = WORK_STOP; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + /* 低压保护优先级最高,先丢弃该通道残留的启动或设定命令。 */ + if (chrg_sou_com_batch_cancel(idx) != 0) { + return -1; + } + /* Low voltage stops the electronic load only. Do not alter South relays: + * an intentional protocol transition must not cause a relay-induced drop. */ + if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + return -1; + } + /* 主继电器已随掉电批次断开,闭合跟踪与电压到位确认一并清除。 */ + g_sink_rel_main_closed[idx] = 0; + g_sink_rel_main_ready[idx] = 0; + pSW->sink.work_mode = SINK_WORK_LOW_AUX_WAIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_work_low_aux_wait +函数描述: 等待掉电安全批次和副继电器恢复命令全部收到有效回码 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 成功后进入SINK_WORK_LOW_MONITOR并恢复北向电压查询入口 +返回说明: 0:继续等待或状态转换成功 <0:参数错误 +其它说明: 批次失败时重新执行完整掉电处理,不允许跳过副继电器确认 +*****************************************************************/ +static int chrg_sink_work_low_aux_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + int batch_result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 0; + } + batch_result = chrg_sou_com_batch_get_result(idx); + if (batch_result <= 0) { + return chrg_sink_nor_work_low(idx, pSW); + } + + pSW->sub = IDX_GET_SINK_VC; + pSW->sink.work_mode = SINK_WORK_LOW_MONITOR; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_nor_work_off +函数描述: 执行负载主动关机流程 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 清除运行、使能和在线标志,关闭北向继电器 +返回说明: 0:关机成功 <0:停止命令提交失败 +其它说明: 先断开功率继电器并发送WORK_STOP,再关闭软件通道 +*****************************************************************/ +static int chrg_sink_work_stop_prepare(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t value = WORK_STOP; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_cancel(idx) != 0) { + return -1; + } + if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + return -1; + } + + /* 主动关机先停止电子负载。WORK_STOP收到匹配回码后,必须通过 + * 停止命令之后的新P1量测连续确认低电流,才允许断开功率继电器。 */ + chrg_sink_unload_confirm_reset(idx); + pSW->sink.work_mode = SINK_WORK_STOP_WAIT; + return 0; +} + +static int chrg_sink_work_stop_relay(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_REL_ON; + rt_uint16_t value = ALL_REL_OFF; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + return -1; + } + + /* 继电器关闭命令已提交,等待南向回码后再清理软件通道。 */ + pSW->sink.work_mode = SINK_WORK_STOP_RELAY_WAIT; + return 0; +} + +static int chrg_sink_work_stop_relay_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ struct chrg_south_t *pSOU = &chrgsouth; struct chrg_north_t *pNOR = &chrgnorth; + int result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 0; + } + result = chrg_sou_com_batch_get_result(idx); + if (result <= 0) { + return chrg_sink_work_stop_relay(idx, pSW); + } pSW->sink.On_work = 0; - pSOU->enable[idx] = 0; pSOU->online[idx] = 0; pNOR->enable[idx] = 0; + g_sink_rel_main_closed[idx] = 0; + g_sink_rel_main_ready[idx] = 0; - chrg_nor_sw_rel((eIDX_NOR_CH)idx, 0); //关闭北向继电器 - chrg_sou_com_batch_add_reg(idx, 0x01, 0x00); - chrg_sou_com_batch_add_reg(idx, REG_WORK, WORK_STOP); + chrg_nor_sw_rel((eIDX_NOR_CH)idx, 0); + /* 主动停止已经完成,退出异步等待状态,禁止下一轮重复执行。 */ + pSW->sink.work_mode = SINK_WORK_WAIT; + return 0; } -//动态测试启动 -void chrg_sink_change_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) +/***************************************************************** +函数名称: chrg_sink_work_switch +函数描述: 处理“电源切换到负载”的过渡状态 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 清除旧电源运行标志,设置负载北向入口和INIT状态 +返回说明: 0:停止命令成功加入队列 非0:命令入队失败 +其它说明: pSW->id已由COM更新为ID_SINK;先提交旧电源WORK_STOP, + 后续负载INIT使用同一通道队列,保证停止命令先执行 +*****************************************************************/ +static int chrg_sink_work_switch(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t value = WORK_STOP; + rt_uint16_t eload_reg = REG_ELOAD; + rt_uint16_t eload_value = ELOAD_LOAD; + int ret; + + ret = chrg_sou_com_batch_submit(idx, ®, &value, 1); + if (ret != 0) { + return ret; + } + /* 等待源模式停止命令入队后,再单独切回负载角色,避免同一批次 + * 发生源载模式切换后后续负载初始化命令被功率板忽略。 */ + ret = chrg_sou_com_batch_submit(idx, &eload_reg, &eload_value, 1); + if (ret != 0) { + return ret; + } + + /* 停止成功入队后,清除旧Source运行标志。 */ + pSW->source.On_Flag = 0; + /* 首次进入Sink时先设置目标CC线,CC稳定后再设置协议。 */ + /* Protocol-before-CC test flow is disabled in this version. */ + pSW->sub = IDX_SET_SINK_CC_BEFORE_PD; + /* SWITCH只负责旧角色退出,后续启动统一交给Sink INIT状态。 */ + pSW->sink.work_mode = SINK_WORK_INIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_work_protocol_unload +函数描述: 协议电压或电流变化前的软卸载:停止电子负载 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 <0:命令提交失败 +其它说明: 不操作北向和南向功率继电器;WORK_STOP完成后进入UNLOAD_WAIT, + 连续确认两帧实际电流不大于200mA,才允许北向重配协议并使用软恢复流程 +*****************************************************************/ +static int chrg_sink_work_protocol_unload(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t value = WORK_STOP; + + if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + return -1; + } + + /* WORK_STOP回码只确认命令接收;还需连续读取两帧合格的实际电流。 */ + chrg_sink_unload_confirm_reset(idx); + pSW->sink.work_mode = SINK_WORK_UNLOAD_WAIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_work_handshake_prepare +函数描述: 执行协议或CC完整重握手前的南向安全断开和副继电器恢复 +输入参数: idx:南向功率板通道 pSW:当前通道控制结构体 +输出参数: 命令提交成功后进入SINK_WORK_HANDSHAKE_WAIT +返回说明: 0:四条命令提交成功 <0:参数或命令提交失败 +其它说明: 命令顺序固定为WORK_STOP、CURRENT=1000mA、ALL_REL_OFF、FU_REL_ON; + WORK_STOP已关闭电子负载,1000mA仅为功率板停止阶段保存的电流设定值; + 主副继电器全部断开并确认回码后再单独打开副继电器; + 北向Sink继电器保持接通,低电流确认完成后才允许重新握手 +*****************************************************************/ +static int chrg_sink_work_handshake_prepare(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t value = WORK_STOP; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + /* 新CC/协议事务必须抢占本通道尚未发送的旧启动和设定命令。 */ + if (chrg_sou_com_batch_cancel(idx) != 0) { + return -1; + } + if (chrg_sou_com_batch_submit(idx, ®, &value, 1U) != 0) { + return -1; + } + + /* 协议或CC切换先停止电子负载,连续2次确认低电流后再操作继电器。 */ + chrg_sink_unload_confirm_reset(idx); + pSW->sink.work_mode = SINK_WORK_HANDSHAKE_WAIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_sink_work_current_wait +函数描述: 等待负载安全断开批次完成,并连续确认两帧实际电流不大于200mA +输入参数: idx:南向功率板通道 pSW:当前通道控制结构体 + full_handshake:RT_TRUE为完整重握手 RT_FALSE为协议参数软卸载 +输出参数: 确认成功后将sink.work_mode切换为SINK_WORK_WAIT +返回说明: 0:继续等待或确认成功 <0:状态查询或优先量测请求失败 +其它说明: 软卸载只要求WORK_STOP收到匹配回码后建立量测基线;完整重握手 + 还要求CURRENT=1000mA、ALL_REL_OFF和FU_REL_ON回码成功,但保持北向Sink继电器接通 +*****************************************************************/ +static int chrg_sink_work_current_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW, rt_uint8_t next_action) +{ + rt_uint32_t sample_seq; + int batch_result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 0; + } + + batch_result = chrg_sou_com_batch_get_result(idx); + if (batch_result < 0) { + /* 写命令失败时重新执行停止命令,禁止使用量测值提前放行。 */ + if (next_action == SINK_UNLOAD_NEXT_HANDSHAKE) { + return chrg_sink_work_handshake_prepare(idx, pSW); + } + if (next_action == SINK_UNLOAD_NEXT_STOP_RELAY) { + return chrg_sink_work_stop_prepare(idx, pSW); + } + return chrg_sink_work_protocol_unload(idx, pSW); + } + if (batch_result == 0) { + /* 无有效批次结果时保持阻塞,并重新提交停止动作。 */ + if (next_action == SINK_UNLOAD_NEXT_HANDSHAKE) { + return chrg_sink_work_handshake_prepare(idx, pSW); + } + if (next_action == SINK_UNLOAD_NEXT_STOP_RELAY) { + return chrg_sink_work_stop_prepare(idx, pSW); + } + return chrg_sink_work_protocol_unload(idx, pSW); + } + + if (!g_sink_unload_batch_confirmed[idx]) { + /* 从最后一条写回码之后建立基线,排除RUN_COM之前残留的旧量测。 */ + g_sink_unload_sample_seq[idx] = chrg_eload_get_sample_seq(idx); + g_sink_unload_batch_confirmed[idx] = RT_TRUE; + return chrg_sink_unload_query_submit(idx, RT_TRUE); + } + + sample_seq = chrg_eload_get_sample_seq(idx); + if (sample_seq != g_sink_unload_sample_seq[idx]) { + g_sink_unload_sample_seq[idx] = sample_seq; + if (pSW->Now_current <= SINK_UNLOAD_CURRENT_MAX_MA) { + g_sink_unload_confirm_count[idx]++; + if (g_sink_unload_confirm_count[idx] >= SINK_UNLOAD_CONFIRM_COUNT) { + g_sink_unload_batch_confirmed[idx] = RT_FALSE; + g_sink_unload_confirm_count[idx] = 0U; + if (next_action == SINK_UNLOAD_NEXT_HANDSHAKE) { + /* 协议或CC切换保持南向继电器状态,低电流确认后直接开始北向配置。 */ + pSW->sink.work_mode = SINK_WORK_WAIT; + } else if (next_action == SINK_UNLOAD_NEXT_STOP_RELAY) { + pSW->sink.work_mode = SINK_WORK_STOP_RELAY; + } else { + /* Keep relays closed and resume through the common + * voltage-confirmed RUNING path. */ + pSW->sink.work_mode = SINK_WORK_WAIT; + } + return 0; + } + } else { + /* 必须是连续两帧合格量测;中间出现带载电流时重新计数。 */ + g_sink_unload_confirm_count[idx] = 0U; + } + } + + /* 当前帧仍有电流或查询未返回时,请求下一帧;接口按位登记,不会重复排队。 */ + return chrg_sink_unload_query_submit(idx, RT_FALSE); +} + +/***************************************************************** +函数名称: chrg_sink_TEST_DYNA_ON +函数描述: 配置并启动动态负载测试 +输入参数: ch:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 非0:命令提交失败 +其它说明: 一次提交两组负载、保持时间、斜率、测试命令和WORK_START +*****************************************************************/ +static int chrg_sink_TEST_DYNA_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) { struct test *pTest = &pSW->sink.test; - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_L1, pTest->change_test.curr1_set); - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_T1 , pTest->change_test.time1_set); - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_L2 , pTest->change_test.curr2_set); - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_T2 , pTest->change_test.time2_set); - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, ModbusRTU_DYNA_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN); - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, REG_WORK, WORK_START); + rt_uint16_t regs[8] = {REG_REL_ON, REG_REL_ON, + ModbusRTU_DYNA_L1, ModbusRTU_DYNA_T1, ModbusRTU_DYNA_L2, + ModbusRTU_DYNA_T2, ModbusRTU_DYNA_CMD_MODE_FLAG, REG_WORK}; + rt_uint16_t vals[8] = {0x02, 0x03, + pTest->change_test.curr1_set, pTest->change_test.time1_set, + pTest->change_test.curr2_set, pTest->change_test.time2_set, + ModbusRTU_DYNA_CMD_MODE_RUN, WORK_START}; + + /* + * 动态测试可能在普通 RUNING 状态打开功率板继电器之前进入。 + * 因此动态启动流程必须自行打开两路继电器,避免快速从 PD 设置 + * 切换到动态测试时,负载已经启动但功率板继电器仍处于关闭状态。 + */ + return chrg_sou_com_batch_submit((eIDX_SOU_CH)ch, regs, vals, 8); } -//短路测试启动 -void chrg_sink_SHOUT_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) +/***************************************************************** +函数名称: chrg_sink_TEST_SHORT_ON +函数描述: 配置并启动负载短路测试 +输入参数: ch:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 非0:命令提交失败 +其它说明: 根据set_mode选择定时无载或持续保持模式 +*****************************************************************/ +static int chrg_sink_TEST_SHORT_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) { - struct test *pTest = &pSW->sink.test; - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, ModbusRTU_ShortTest_CMD_MODE_FLAG , ModbusRTU_DYNA_CMD_MODE_RUN_ALWAYS_KEPP); //保持 - chrg_sou_com_batch_add_reg((eIDX_SOU_CH)ch, REG_WORK, WORK_START); + rt_uint16_t regs[3] = {ModbusRTU_ShortTest_Hold_Time,ModbusRTU_ShortTest_CMD_MODE_FLAG, REG_WORK}; + rt_uint16_t vals[3] = {0,0,0}; + if(pSW->sink.test.short_test.set_mode == 0) + { + vals[0] = pSW->sink.test.short_test.hold_time; + vals[1] = ModbusRTU_DYNA_CMD_MODE_RUN_NO_CURR; + } + else{ + vals[1] = ModbusRTU_DYNA_CMD_MODE_RUN_ALWAYS_KEPP; + } + vals[2] = WORK_START; + return chrg_sou_com_batch_submit((eIDX_SOU_CH)ch, regs, vals, 3); +} + +/***************************************************************** +函数名称: chrg_sink_TEST_OCP_ON +函数描述: 配置并启动负载OCP过流保护测试 +输入参数: ch:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 非0:命令提交失败 +其它说明: 一次提交功率继电器、OCP参数、测试命令和WORK_START; + set_mode为0时保持指定时间后恢复,为1时一直保持 +*****************************************************************/ +static int chrg_sink_TEST_OCP_ON(rt_uint8_t ch, struct chrg_switch_t *pSW) +{ + struct test_OCP *pOCP = &pSW->sink.test.OCP_test; + rt_uint16_t regs[11] = {REG_REL_ON, REG_REL_ON, + ModbusRTU_OCPTest_StartCurrent, ModbusRTU_OCPTest_StopCurrent, + ModbusRTU_OCPTest_Step_CURR, ModbusRTU_OCPTest_Step_Hold_Time, + ModbusRTU_OCPTest_Vtrig_Stop, ModbusRTU_OCPTest_Hold_Time, + ModbusRTU_OCPTest_HOLD_CURR, ModbusRTU_OCPTest_CMD_MODE_FLAG, + REG_WORK}; + rt_uint16_t vals[11] = {0x02, 0x03, + pOCP->start_curr, pOCP->end_curr, pOCP->change_curr, + pOCP->change_keep_time, pOCP->stop_volt, pOCP->hold_time, + pOCP->hold_curr, ModbusRTU_DYNA_CMD_MODE_RUN_NO_CURR, + WORK_START}; + + if (pOCP->set_mode != 0) { + vals[9] = ModbusRTU_DYNA_CMD_MODE_RUN_ALWAYS_KEPP; + } + return chrg_sou_com_batch_submit((eIDX_SOU_CH)ch, regs, vals, 11); } -//负载工作 -void chrg_sink_work(eIDX_SOU_CH idx, struct chrg_switch_t *pSW){ +/***************************************************************** +函数名称: chrg_sink_work +函数描述: 负载工作状态机统一入口 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 根据sink.work_mode执行对应状态动作 +返回说明: 0:状态处理成功 <0:参数、状态或命令提交失败 +其它说明: 每通道记录上次成功状态,相同状态不重复下发寄存器 +*****************************************************************/ +int workmode = 0; +int chrg_sink_work(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ static uint8_t last_state[TOTAL_SOU_CHS] ={100,100,100,100}; - rt_uint8_t work_mode = pSW->sink.work_mode; + rt_uint8_t work_mode; + int ret = -1; - // 本次状态和上次一致,直接跳过,不重复下发寄存器 - if (work_mode == last_state[idx]) - { - return; - } - chrg_sou_com_sou_set(); + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + work_mode = pSW->sink.work_mode; + workmode = work_mode; + /* 异步等待状态需要持续检查批次或新量测,其余相同状态保持去重。 */ + if ((work_mode == last_state[idx]) && + (work_mode != SINK_WORK_INIT_AUX_WAIT) && + (work_mode != SINK_WORK_STOP_WAIT) && + (work_mode != SINK_WORK_STOP_RELAY_WAIT) && + (work_mode != SINK_WORK_UNLOAD_WAIT) && + (work_mode != SINK_WORK_LOW_AUX_WAIT) && + (work_mode != SINK_WORK_HANDSHAKE_WAIT)) { + return 0; + } switch(work_mode){ case SINK_WORK_INIT: - chrg_sink_work_init(idx,pSW); + ret = chrg_sink_work_init(idx,pSW); + break; + case SINK_WORK_INIT_AUX_WAIT: + ret = chrg_sink_work_init_aux_wait(idx, pSW); break; case SINK_WORK_WAIT: - + ret = 0; break; case SINK_WORK_RUNING: - chrg_sink_work_volt_runing(idx,pSW); + ret = chrg_sink_work_volt_runing(idx,pSW); break; - case SINK_WORK_RUNING_2: - chrg_sink_work_volt_runing(idx,pSW); - break; - case SINK_WORK_ON_TEST_CHANGE: - chrg_sink_change_ON(idx,pSW); + case SINK_WORK_ON_TEST_DYNA: + ret = chrg_sink_TEST_DYNA_ON(idx,pSW); break; - case SINK_WORK_ON_TEST_SHOUT: - chrg_sink_SHOUT_ON(idx,pSW); + case SINK_WORK_ON_TEST_SHORT: + ret = chrg_sink_TEST_SHORT_ON(idx,pSW); + break; + case SINK_WORK_ON_TEST_OCP: + ret = chrg_sink_TEST_OCP_ON(idx,pSW); break; case SINK_WORK_LOW: - chrg_sink_nor_work_low(idx,pSW); + ret = chrg_sink_nor_work_low(idx,pSW); + break; + case SINK_WORK_LOW_AUX_WAIT: + ret = chrg_sink_work_low_aux_wait(idx, pSW); + break; + case SINK_WORK_LOW_MONITOR: + ret = 0; break; case SINK_WORK_STOP: - chrg_sink_nor_work_off(idx,pSW); - break; + ret = chrg_sink_work_stop_prepare(idx, pSW); + break; + case SINK_WORK_STOP_WAIT: + ret = chrg_sink_work_current_wait(idx, pSW, + SINK_UNLOAD_NEXT_STOP_RELAY); + break; + case SINK_WORK_STOP_RELAY: + ret = chrg_sink_work_stop_relay(idx, pSW); + break; + case SINK_WORK_STOP_RELAY_WAIT: + ret = chrg_sink_work_stop_relay_wait(idx, pSW); + break; + case SINK_WORK_SWITCH: + ret = chrg_sink_work_switch(idx,pSW); + break; + case SINK_WORK_PROTOCOL_UNLOAD: + ret = chrg_sink_work_protocol_unload(idx, pSW); + break; + case SINK_WORK_UNLOAD_WAIT: + ret = chrg_sink_work_current_wait(idx, pSW, + SINK_UNLOAD_NEXT_RUNNING); + break; + case SINK_WORK_HANDSHAKE_PREPARE: + ret = chrg_sink_work_handshake_prepare(idx, pSW); + break; + case SINK_WORK_HANDSHAKE_WAIT: + ret = chrg_sink_work_current_wait(idx, pSW, + SINK_UNLOAD_NEXT_HANDSHAKE); + break; + case SINK_WORK_LOAD_STANDBY: + ret = 0; + break; + case SINK_WORK_LOAD_STOP: + ret = chrg_sink_work_load_stop(idx, pSW); + break; + case SINK_WORK_LOAD_START: + ret = chrg_sink_work_load_start(idx, pSW); + break; + default: + break; } -// 更新缓存,保存本次最新状态 - last_state[idx] = work_mode; + if (ret == 0) { + last_state[idx] = work_mode; + } + return ret; } diff --git a/apps/chrg/applications/utils/chrg_sink.h b/apps/chrg/applications/utils/chrg_sink.h index 6eba83d..ecce436 100644 --- a/apps/chrg/applications/utils/chrg_sink.h +++ b/apps/chrg/applications/utils/chrg_sink.h @@ -201,16 +201,40 @@ enum { #define SINK_CMD_SET_AVS 0xE3 // AVS +/* + * 普通恒流负载允许执行WORK_START的最低设定电流,单位mA。 + * 小于该值时北向仍完成协议握手,但南向保持WORK_STOP且不改写电流设定, + * 避免功率板在零电流或几十mA的小电流设定下启动电子负载产生冲击。 + */ +#define SINK_LOAD_ENABLE_MIN_MA 100U + +/* 主继电器闭合后的0V持续监控:由北向轮询线程每轮调用,掉电立即转掉电保护。 */ +extern int chrg_sink_volt_low_protect(eIDX_SOU_CH idx, struct chrg_switch_t *pSW); + enum { - SINK_WORK_INIT =0, - SINK_WORK_WAIT, - SINK_WORK_VOLT_OK, - SINK_WORK_RUNING, - SINK_WORK_RUNING_2, - SINK_WORK_LOW, - SINK_WORK_STOP, - SINK_WORK_ON_TEST_CHANGE, - SINK_WORK_ON_TEST_SHOUT, + SINK_WORK_INIT = 0, // 启动入口:第一条南向命令只打开副继电器 + SINK_WORK_INIT_AUX_WAIT, // 等待启动副继电器收到有效南向回码 + SINK_WORK_WAIT, // 北向协议设置或电压查询等待状态 + SINK_WORK_VOLT_OK, // 预留的电压达标状态 + SINK_WORK_RUNING, // 电压稳定后提交继电器、电流和启动命令 + SINK_WORK_LOW, // 运行电压过低,执行掉电安全处理 + SINK_WORK_LOW_AUX_WAIT, // 等待掉电处理及副继电器恢复批次完成 + SINK_WORK_LOW_MONITOR, // 副继电器已恢复,等待输入电压重新出现 + SINK_WORK_STOP, // 用户主动停止:先停止负载并确认低电流 + SINK_WORK_SWITCH, // 停止旧电源后进入负载INIT + SINK_WORK_PROTOCOL_UNLOAD, // 协议电压或电流变化:掉载但不切换继电器 + SINK_WORK_UNLOAD_WAIT, // 等待软卸载完成并连续两次确认电流不大于200mA + SINK_WORK_ON_TEST_DYNA, // 动态负载测试启动状态 + SINK_WORK_ON_TEST_SHORT, // 短路测试启动状态 + SINK_WORK_ON_TEST_OCP, // OCP过流保护测试启动状态 + SINK_WORK_HANDSHAKE_PREPARE, // 完整重握手:停止、清流、全关继电器后打开副继电器 + SINK_WORK_HANDSHAKE_WAIT, // 南向低电流确认与北向协议/CC并行 + SINK_WORK_LOAD_STANDBY, // 协议已就绪但主继电器未打开,低设定电流下保持停止 + SINK_WORK_LOAD_STOP, // 运行中跨到低电流:只发送WORK_STOP关闭电子负载 + SINK_WORK_LOAD_START, // 主继电器未打开的低电流待机恢复:等待北向电压稳定后进入RUNING + SINK_WORK_STOP_WAIT, // 关机:等待连续2次实际电流达到安全范围 + SINK_WORK_STOP_RELAY, // 关机:低电流确认后提交关闭南向继电器 + SINK_WORK_STOP_RELAY_WAIT // 关机:等待关闭南向继电器的回码 }; // sink 参数结构 @@ -234,11 +258,9 @@ struct chrg_sink_t { rt_uint8_t work_mode; // 0=停止 1=工作 2=低电压 }; - //纹波寄存器 #define ModbusRTU_Ripple_Value 0x35 //读取纹波 - /******************** 负载测试 ************************* */ #define ModbusRTU_CAL_WriteEnable 0x34 //使能写入;0x4361代表使能写入;C+a ASCII码 #define ModbusRTU_Test_CMD 0x4361 // @@ -384,7 +406,9 @@ extern void chrg_nor_sink_change_test(rt_uint8_t ch, struct chrg_switch_t *pSW); extern void chrg_nor_sink_test_stop(rt_uint8_t ch,struct chrg_switch_t *pSW,rt_uint8_t *enable); extern void chrg_nor_sink_test_run(rt_uint8_t ch, struct chrg_switch_t *pSW); -extern void chrg_sink_work(eIDX_SOU_CH idx, struct chrg_switch_t *pSW); +/* 判断普通负载的当前设定是否允许启动电子负载。 */ +extern rt_bool_t chrg_sink_load_should_start(const struct chrg_switch_t *pSW); +extern int chrg_sink_work(eIDX_SOU_CH idx, struct chrg_switch_t *pSW); #endif diff --git a/apps/chrg/applications/utils/chrg_source.c b/apps/chrg/applications/utils/chrg_source.c index 76ebabf..01626a3 100644 --- a/apps/chrg/applications/utils/chrg_source.c +++ b/apps/chrg/applications/utils/chrg_source.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_source.c 完成日期 : -当前版本号 : V1.0 +当前版本号 : V1.1.9 主要功能 : 北向数据source请求数据填充及发送。 版本历史 : 创建原始版本 说明 : @@ -13,6 +13,395 @@ #include "chrg_utils.h" #include "chrg_roll_nor.h" #include "chrg_source.h" +#include "chrg_eload.h" +#include "chrg_roll_sou.h" +#include "chrg_rel.h" + +#define SOURCE_VOLT_COMPENSATION_MV 200U + +static int chrg_source_work_running(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW); +static int chrg_source_work_full_reset(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW); +static int chrg_source_work_full_reset_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW); +static int chrg_source_work_full_reset_relay(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW); +static int chrg_source_work_full_reset_relay_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW); +static int chrg_source_work_init_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW); + +/***************************************************************** +函数名称: chrg_source_init_submit +函数描述: 将电源模式初始化参数批量提交到南向功率板命令队列 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:提交成功 <0:参数错误或队列提交失败 +其它说明: INIT不写电压;电压统一由RUNING状态写入,避免多个入口重复控制 +*****************************************************************/ +int chrg_source_init_submit(rt_uint8_t idx, struct chrg_switch_t *pSW) +{ + /* Source启动参数一次入队,最后发送WORK_START使功率板开始输出。 */ + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + rt_uint16_t regs[5] = { + REG_ELOAD, + REG_MODE, + REG_REL_ON, + REG_CURRENT_OUT + 1, + REG_WORK + }; + rt_uint16_t vals[5] = { + ELOAD_SOURCE, + pSW->CV_mode, + 0x03, + pSW->source.set_curr, + WORK_START + }; + + return chrg_sou_com_batch_submit(idx, regs, vals, 5); +} + +/***************************************************************** +函数名称: chrg_source_work_init +函数描述: 执行电源INIT状态,初始化功率板并打开对应北向通道 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 更新电源运行标志、通道使能、在线状态及work_mode +返回说明: 0:初始化命令提交成功 非0:初始化失败 +其它说明: 标准协议从pd_set准备目标并转入RUNING;快充协议清空旧电压后 + 进入WAIT,等待北向协商结果,INIT本身不写功率板电压 +*****************************************************************/ +static int chrg_source_work_init(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + struct chrg_south_t *pSOU = &chrgsouth; + struct chrg_north_t *pNOR = &chrgnorth; + int ret; + + /* 最大电流是功率板启动保护值,快充回码到达后可再更新运行目标。 */ + pSW->source.set_curr = pSW->source.pd_set.mm.max_cur; + if (pSW->source.protocol == PRO_NONE) { + pSW->source.set_volt = pSW->source.pd_set.mm.max_vol; + } else { + /* 禁止快充重新启动时沿用上一次协商电压。 */ + pSW->source.set_volt = 0; + } + + ret = chrg_source_init_submit(idx, pSW); + if (ret != 0) { + return ret; + } + + pSW->source.On_Flag = 1; + pSW->source.qc_err = 0; + pNOR->enable[idx] = 1; + pSOU->online[idx] = 1; + chrg_nor_sw_rel((eIDX_NOR_CH)idx, ID_SOURCE); + /* 初始化批次必须全部收到南向回码后,才能继续协议设置或输出控制。 */ + pSW->source.work_mode = SOURCE_WORK_INIT_WAIT; + return 0; +} + +static int chrg_source_work_init_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + int result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 1; + } + result = chrg_sou_com_batch_get_result(idx); + if (result <= 0) { + /* 南向初始化失败时,保持北向协议不变并重发完整初始化批次。 */ + pSW->source.work_mode = SOURCE_WORK_INIT; + return 1; + } + + if (pSW->sub == IDX_SET_SOURCE_RESET) { + /* 复位后的初始化完成,才允许进入PD协议设置。 */ + pSW->sub = IDX_SET_SOURCE_PD; + pSW->source.work_mode = SOURCE_WORK_WAIT; + return 0; + } + + if (pSW->source.protocol == PRO_NONE) { + /* 标准协议不等待协商,初始化完成后直接写入目标电压。 */ + pSW->source.work_mode = SOURCE_WORK_RUNING; + return chrg_source_work_running(idx, pSW); + } + pSW->source.work_mode = SOURCE_WORK_WAIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_source_work_wait +函数描述: 执行电源WAIT状态 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:状态处理完成 +其它说明: WAIT状态不下发命令,由北向解析函数判断目标电压是否变化 +*****************************************************************/ +static int chrg_source_work_wait(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + /* WAIT状态不产生南向命令,等待下一帧北向电压数据。 */ + return 0; +} + +/***************************************************************** +函数名称: chrg_source_work_running +函数描述: 执行电源RUNING状态,下发补偿后的最新目标电压 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: - +返回说明: 0:命令提交成功 非0:命令提交失败 +其它说明: 功率板设定值固定比目标高200mV,防止限压导致输出进入稳流模式; + 目标最大值为48000mV,无需做uint16_t饱和处理 +*****************************************************************/ +static int chrg_source_work_running(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + /* 只对本次下发值补偿,不修改set_volt,防止多次执行时累计增加。 */ + rt_uint16_t reg = REG_VOLTAGE_OUT + 1; + rt_uint16_t value = pSW->source.set_volt; + + return chrg_sou_com_batch_submit(idx, ®, &value, 1); +} + +/***************************************************************** +函数名称: chrg_source_work_protocol_switch +函数描述: 执行电源协议或CC切换前的功率板输出清零 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 清零source.set_volt,并在命令入队成功后进入WAIT状态 +返回说明: 0:清零命令提交成功 非0:状态或命令提交失败 +其它说明: COM在进入本状态前必须将sub设置为协议、CC或组合事务入口; + 本状态只管理南向清零,不修改北向sub, + 标准协议同时按0V、模式、电流顺序入队,保证南向命令顺序固定 +*****************************************************************/ +static int chrg_source_work_protocol_switch(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t regs[3]; + rt_uint16_t vals[3]; + rt_uint8_t count = 0; + int ret; + + if ((pSW->sub != IDX_SET_SOURCE_PD) && + (pSW->sub != IDX_SET_SOURCE_RESET)) { + return -1; + } + + regs[count] = REG_VOLTAGE_OUT + 1; + vals[count++] = 0; + if (pSW->source.protocol == PRO_NONE) { + /* 标准协议切换时,模式和限流值必须排在0V命令之后生效。 */ + regs[count] = REG_MODE; + vals[count++] = pSW->CV_mode; + regs[count] = REG_CURRENT_OUT + 1; + vals[count++] = pSW->source.pd_set.mm.max_cur; + } + + /* 只有整批命令成功加入南向队列后,才允许北向继续协议切换。 */ + ret = chrg_sou_com_batch_submit(idx, regs, vals, count); + if (ret != 0) { + return ret; + } + + pSW->source.set_volt = 0; + pSW->source.work_mode = SOURCE_WORK_WAIT; + return 0; +} + +static int chrg_source_work_full_reset(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_WORK; + rt_uint16_t value = WORK_STOP; + + /* Cancel stale south commands so the reset sequence starts from a known state. */ + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_cancel(idx) != 0) { + return -1; + } + if (chrg_sou_com_batch_submit(idx, ®, &value, 1) != 0) { + return -1; + } + pSW->source.set_volt = 0; + pSW->source.work_mode = SOURCE_WORK_FULL_RESET_WAIT; + return 1; +} + +static int chrg_source_work_full_reset_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + int result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 1; + } + result = chrg_sou_com_batch_get_result(idx); + if (result <= 0) { + pSW->source.work_mode = SOURCE_WORK_FULL_RESET; + return 1; + } + pSW->source.work_mode = SOURCE_WORK_FULL_RESET_RELAY; + return 1; +} + +static int chrg_source_work_full_reset_relay(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + rt_uint16_t reg = REG_REL_ON; + rt_uint16_t value = ALL_REL_OFF; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_submit(idx, ®, &value, 1) != 0) { + return -1; + } + pSW->source.work_mode = SOURCE_WORK_FULL_RESET_RELAY_WAIT; + return 1; +} + +static int chrg_source_work_full_reset_relay_wait(eIDX_SOU_CH idx, + struct chrg_switch_t *pSW) +{ + int result; + + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + if (chrg_sou_com_batch_is_pending(idx)) { + return 1; + } + result = chrg_sou_com_batch_get_result(idx); + if (result <= 0) { + pSW->source.work_mode = SOURCE_WORK_FULL_RESET_RELAY; + return 1; + } + + /* South relays are off; now disconnect D+/D- and CC before SOURCE_RESET. */ + chrg_nor_sw_rel((eIDX_NOR_CH)idx, 0); + pSW->sub = IDX_SET_SOURCE_RESET; + pSW->source.work_mode = SOURCE_WORK_WAIT; + return 1; +} + +/***************************************************************** +函数名称: chrg_source_work_stop +函数描述: 执行电源主动关机流程 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 清除目标电压、运行标志、通道使能和在线状态 +返回说明: 0:停止成功 非0:停止命令提交失败 +其它说明: 依次关闭功率板两路继电器、清零电压并发送WORK_STOP; + 停止命令成功入队后立即关闭北向通道,不额外发送CC_CLOSE +*****************************************************************/ +static int chrg_source_work_stop(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + struct chrg_south_t *pSOU = &chrgsouth; + struct chrg_north_t *pNOR = &chrgnorth; + int ret; + + rt_uint16_t regs[3] = {REG_REL_ON, REG_VOLTAGE_OUT + 1, REG_WORK}; + rt_uint16_t vals[3] = {ALL_REL_OFF, 0, WORK_STOP}; + + /* 先断开两路功率继电器,再清零输出并停止,避免关机瞬间继续向外供电。 */ + ret = chrg_sou_com_batch_submit(idx, regs, vals, 3); + if (ret != 0) { + return ret; + } + + pSW->source.set_volt = 0; + pSW->source.On_Flag = 0; + pSW->source.qc_err = 0; + pNOR->enable[idx] = 0; + pSOU->online[idx] = 0; + chrg_nor_sw_rel((eIDX_NOR_CH)idx, 0); + pSW->sub = IDX_SET_SOURCE_STOP_STATE; + pSW->source.work_mode = SOURCE_WORK_WAIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_source_work_switch +函数描述: 处理“负载切换到电源”的过渡状态 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 清除旧负载运行标志,设置电源北向入口和INIT状态 +返回说明: 0:停止命令成功加入队列 非0:命令入队失败 +其它说明: pSW->id已由COM更新为ID_SOURCE;先关闭旧负载继电器并提交 + WORK_STOP,后续电源INIT使用同一通道队列,保证停止命令先执行 +*****************************************************************/ +static int chrg_source_work_switch(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) +{ + rt_uint16_t regs[3] = {REG_REL_ON, REG_VOLTAGE_OUT + 1, REG_WORK}; + rt_uint16_t vals[3] = {ALL_REL_OFF, 0, WORK_STOP}; + int ret; + + ret = chrg_sou_com_batch_submit(idx, regs, vals, 3); + if (ret != 0) { + return ret; + } + + /* 停止成功入队后,清除旧Sink运行标志。 */ + pSW->sink.On_work = 0; + pSW->source.set_volt = 0; + /* 首次进入Source按“设置协议->连接CC”启动,不能沿用Sink的sub状态。 */ + pSW->sub = IDX_SET_SOURCE_PD; + /* SWITCH只负责旧角色退出,后续启动统一交给Source INIT状态。 */ + pSW->source.work_mode = SOURCE_WORK_INIT; + return 0; +} + +/***************************************************************** +函数名称: chrg_source_work_state +函数描述: 电源工作状态机统一入口 +输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 +输出参数: 根据source.work_mode执行对应状态动作 +返回说明: 0:状态处理成功 <0:参数、状态或命令提交失败 +其它说明: 支持INIT、WAIT、RUNING、STOP、SWITCH和PROTOCOL_SWITCH状态 +*****************************************************************/ +int chrg_source_work_state(rt_uint8_t idx, struct chrg_switch_t *pSW) +{ + if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { + return -1; + } + + /* 所有Source功率板控制统一从本状态机出口执行。 */ + switch (pSW->source.work_mode) { + case SOURCE_WORK_INIT: + return chrg_source_work_init(idx, pSW); + case SOURCE_WORK_WAIT: + return chrg_source_work_wait(idx, pSW); + case SOURCE_WORK_RUNING: + return chrg_source_work_running(idx, pSW); + case SOURCE_WORK_STOP: + return chrg_source_work_stop(idx, pSW); + case SOURCE_WORK_SWITCH: + return chrg_source_work_switch(idx, pSW); + case SOURCE_WORK_PROTOCOL_SWITCH: + return chrg_source_work_protocol_switch(idx, pSW); + case SOURCE_WORK_FULL_RESET: + return chrg_source_work_full_reset(idx, pSW); + case SOURCE_WORK_FULL_RESET_WAIT: + return chrg_source_work_full_reset_wait(idx, pSW); + case SOURCE_WORK_FULL_RESET_RELAY: + return chrg_source_work_full_reset_relay(idx, pSW); + case SOURCE_WORK_FULL_RESET_RELAY_WAIT: + return chrg_source_work_full_reset_relay_wait(idx, pSW); + case SOURCE_WORK_INIT_WAIT: + return chrg_source_work_init_wait(idx, pSW); + default: + return -1; + } +} + rt_uint8_t Pro_Group_VCMM[11][8] = { {0} // 11个挡位,8个参数(src, pps, vmin_H,vmin_L, vmax_H,vmax_L,cur_set_H,cur_set_L) }; @@ -58,6 +447,18 @@ int source_get_pd (void) return chrg_north_send(frame, len); } +int source_reset (void) +{ + rt_uint16_t len = 0; + rt_uint8_t data[8] = {0}; + rt_uint8_t frame[16] = {0}; + + data[0] = 0x00; + chrg_north_pkg_encode(ID_SOURCE, SRC_CMD_RESET, data, 1, frame, &len); + + return chrg_north_send(frame, len); +} + /***************************************************************** 函数名称: source_set_cc_lvl 函数描述: source 设置 cc 等级 @@ -96,11 +497,13 @@ int source_set_cc_choose (rt_uint8_t cc_set) data[0] = cc_set; chrg_north_pkg_encode(ID_SOURCE, SRC_CMD_SET_CC_CHOOSE, data, 1, frame, &len); - rt_kprintf("\n"); - for(int i=0;i> 8) & 0xFF; // vmax_H Pro_Group_VCMM[0][5] = max_volt_mv & 0xFF; // vmax_L Pro_Group_VCMM[0][6] = (max_curr_ma >> 8) & 0xFF; // cur_set_H +#if NORTH_SOURCE_DEBUG rt_kprintf("H_C=%02x",Pro_Group_VCMM[0][6]); +#endif Pro_Group_VCMM[0][7] = max_curr_ma & 0xFF; // cur_set_L +#if NORTH_SOURCE_DEBUG rt_kprintf("L_C=%02x\n",Pro_Group_VCMM[0][7]); +#endif return 0; } @@ -152,12 +559,12 @@ void parse_power_single_gear(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t // rt_kprintf("%0x",pData[i]); // } // rt_kprintf("\n"); - #if PRO_TEST_DEBUG +#if NORTH_SOURCE_DEBUG rt_kprintf("one_gear"); rt_kprintf("min_volt_mv = %d\n",min_volt_mv); rt_kprintf("max_volt_mv = %d\n",max_volt_mv); rt_kprintf("prtect_curr= %04x",*protect_curr); - #endif +#endif *pro_gear_idx = 1; // 单档协议固定1 // 按协议命令码赋值 switch (protoCmd) @@ -264,7 +671,7 @@ void parse_pd_11group_pdo(rt_uint8_t *pdoData, rt_uint8_t pdo_group_num) { // 电流/功率拆分 Pro_Group_VCMM[i][6] = (cur_power >> 8) & 0xFF; // cur_set_H Pro_Group_VCMM[i][7] = cur_power & 0xFF; // cur_set_L - #if PRO_TEST_DEBUG + #if NORTH_SOURCE_DEBUG // 调试打印 rt_kprintf("max_H=%d",Pro_Group_VCMM[i][4]); rt_kprintf("max_L=%d",Pro_Group_VCMM[i][5]); @@ -305,9 +712,13 @@ void parse_power_pdo_gear(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t re rt_int16_t comp_volt_mv = 0; // 补偿电压(mV) // 数据区索引:0=状态+通道,1=CC线,2=恒压恒流模式,3=PDO组数(bit12-15) +#if NORTH_SOURCE_DEBUG rt_kprintf("pdata=%04x",pData[3]); +#endif pdo_group_num = (pData[3] >> 4) & 0x0F; // Bit12-15提取组数 +#if NORTH_SOURCE_DEBUG rt_kprintf("num=%d\n",pdo_group_num); +#endif *pro = PRO_PD0PSS; *pro_gear_idx = pdo_group_num; // 解析11组PDO(小端序,44字节) @@ -316,13 +727,11 @@ void parse_power_pdo_gear(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t re // 解析保留字段(20字节) + 保护电流 + 补偿电压 *protect_ma = u8v_to_u16(&pData[4 + 44 + 20]); // 67-68字节 comp_volt_mv = (rt_int16_t)u8v_to_u16(&pData[4 + 44 + 22]); // 69-70字节 -// #if PRO_TEST_DEBUG +#if NORTH_SOURCE_DEBUG rt_kprintf("project_curr=%d mA, add_volt=%d mV\n", *protect_ma, comp_volt_mv); -// #endif. +#endif } - - /***************************************************************** 函数名称: parse_mulit_gear_more 函数描述: 解析三-四挡位原始数据,填充8参数高低字节缓冲区(同步修改) @@ -408,7 +817,7 @@ rt_uint8_t parse_mulit_gear_more(rt_uint8_t *pData, rt_uint8_t gear_num) { Pro_Group_VCMM[i][5] = vmax_mv & 0xFF; // vmax_L Pro_Group_VCMM[i][6] = (cur_power >> 8) & 0xFF; // cur_set_H Pro_Group_VCMM[i][7] = cur_power & 0xFF; // cur_set_L - #if PRO_TEST_DEBUG + #if NORTH_SOURCE_DEBUG // 调试打印 rt_kprintf("parse_mulit_gear_pd0 PDO[%d]:rc=0x%02X, pps=0x%02X,V_Min=%dmV,V_MAX=%dmV,C/P=%d\n", i, Pro_Group_VCMM[i][0], Pro_Group_VCMM[i][1], vmin_mv, vmax_mv, cur_power); @@ -443,7 +852,9 @@ void parse_multi_gear_more(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t r return; } *pro_gear_idx = gear_num; +#if NORTH_SOURCE_DEBUG rt_kprintf("pro_gear_idx = %d\n",gear_num); +#endif // 解析保护电流和补偿电压(不同协议偏移不同) switch (protoCmd) { @@ -462,7 +873,7 @@ void parse_multi_gear_more(rt_uint8_t protoCmd, rt_uint8_t *pData, rt_uint16_t r } *protect_ma = u8v_to_u16(&pData[4 + 16]); // 19-20字节 comp_volt_mv = (rt_int16_t)u8v_to_u16(&pData[4 + 18]); - #if PRO_TEST_DEBUG + #if NORTH_SOURCE_DEBUG for(int i = 0;i<30;i++){ rt_kprintf("data[%d]=%02x",i,pData[i]); } @@ -524,7 +935,7 @@ rt_uint8_t parse_ufcs_gear_more(rt_uint8_t *pData, rt_uint8_t gear_num) { Pro_Group_VCMM[i][5] = vmax_mv & 0xFF; Pro_Group_VCMM[i][6] = (cur_ma >> 8) & 0xFF; Pro_Group_VCMM[i][7] = cur_ma & 0xFF; - #if PRO_TEST_DEBUG + #if NORTH_SOURCE_DEBUG // ================= 打印格式 ================= rt_kprintf("parse_ufcs_gear_more PDO[%d]:PPS/AVS,V_Min=%dmV,V_MAX=%dmV,Max_Current=%dmA\n", i, vmin_mv, vmax_mv, cur_ma); @@ -568,10 +979,10 @@ void parse_power_ufcs(rt_uint8_t *pData, rt_uint16_t reqLen, uint8_t *pro_gear_i // rt_kprintf("data[%d]=%02x",i,pData[i]); // } comp_volt_mv = 0;//(rt_int16_t)u8v_to_u16(&pData[3 + 14]); -//#if PRO_TEST_DEBUG +#if NORTH_SOURCE_DEBUG rt_kprintf("parse_power_ufcs:gear=%d,protect=%dmA,comp=%dmV\n", gear_num,*protect_ma, comp_volt_mv); -//#endif +#endif // 调用下面的循环解析 parse_ufcs_gear_more(&pData[4], gear_num); @@ -705,6 +1116,8 @@ rt_uint8_t pd_fill_single_group_need_src_com(rt_uint8_t *data, rt_uint8_t start_ rt_uint8_t idx = start_idx; struct chrg_src_t *source = &psw->source; + const struct vc_pd_t *pd = &source->pd_set; + const struct vc_mm_t *mm = &pd->mm; // 1. src + pps(2字节) data[idx++] = 0x00; data[idx++] = 0x00; @@ -715,23 +1128,23 @@ rt_uint8_t pd_fill_single_group_need_src_com(rt_uint8_t *data, rt_uint8_t start_ data[idx++] = 0x13; data[idx++] = 0x88; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; - data[idx++] = source->src; - data[idx++] = source->pps; - rt_kprintf("src = %d pps = %d\n",source->src,source->pps); + data[idx++] = pd->src_type; + data[idx++] = pd->pps_type; + //rt_kprintf("src = %d pps = %d\n", pd->src_type, pd->pps_type); // 2. 最小电压(高+低,直接取已拆分好的值) - data[idx++] = source->min_vol >> 8; - data[idx++] = source->min_vol & 0xFF; + data[idx++] = mm->min_vol >> 8; + data[idx++] = mm->min_vol & 0xFF; // 3. 最大电压(高+低,直接取已拆分好的值) - data[idx++] = source->max_vol >> 8; - data[idx++] = source->max_vol & 0xFF; + data[idx++] = mm->max_vol >> 8; + data[idx++] = mm->max_vol & 0xFF; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; @@ -742,28 +1155,30 @@ rt_uint8_t pd_fill_single_group_no_src_com(rt_uint8_t *data, rt_uint8_t start_id rt_uint8_t idx = start_idx; struct chrg_src_t *source = &psw->source; + const struct vc_mm_t *mm = &source->pd_set.mm; // 1. src + pps(2字节) data[idx++] = 0x00; data[idx++] = 0x00; // 2. 最小电压(高+低,直接取已拆分好的值) - data[idx++] = source->min_vol >> 8; - data[idx++] = source->min_vol & 0xFF; + data[idx++] = 0x13;//mm->min_vol >> 8; + data[idx++] = 0x18;//mm->min_vol & 0xFF; // 3. 最大电压(高+低,直接取已拆分好的值) - data[idx++] = source->max_vol >> 8; - data[idx++] = source->max_vol & 0xFF; + data[idx++] = mm->max_vol >> 8; + data[idx++] = mm->max_vol & 0xFF; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; return idx - start_idx; // 返回填充的总字节数 } -rt_uint8_t pd_fill_single_group_FCP_AFC(rt_uint8_t *data, rt_uint8_t start_idx,struct chrg_switch_t *psw){ +rt_uint8_t pd_fill_single_group_AFC(rt_uint8_t *data, rt_uint8_t start_idx,struct chrg_switch_t *psw){ rt_uint8_t idx = start_idx; struct chrg_src_t *source = &psw->source; + const struct vc_mm_t *mm = &source->pd_set.mm; // 1. src + pps(2字节) data[idx++] = 0x00; data[idx++] = 0x00; @@ -774,8 +1189,8 @@ rt_uint8_t pd_fill_single_group_FCP_AFC(rt_uint8_t *data, rt_uint8_t start_idx,s data[idx++] = 0x13; data[idx++] = 0x88; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; @@ -783,14 +1198,14 @@ rt_uint8_t pd_fill_single_group_FCP_AFC(rt_uint8_t *data, rt_uint8_t start_idx,s data[idx++] = 0x00; data[idx++] = 0x00; // 2. 最小电压(高+低,直接取已拆分好的值) - data[idx++] = source->max_vol >> 8; - data[idx++] = source->max_vol & 0xFF; + data[idx++] = mm->max_vol >> 8; + data[idx++] = mm->max_vol & 0xFF; // 3. 最大电压(高+低,直接取已拆分好的值) - data[idx++] = source->max_vol >> 8; - data[idx++] = source->max_vol & 0xFF; + data[idx++] = mm->max_vol >> 8; + data[idx++] = mm->max_vol & 0xFF; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; @@ -801,6 +1216,7 @@ rt_uint8_t pd_fill_single_group_UFCS(rt_uint8_t *data, rt_uint8_t start_idx,stru rt_uint8_t idx = start_idx; struct chrg_src_t *source = &psw->source; + const struct vc_mm_t *mm = &source->pd_set.mm; // 1. src + pps(2字节) data[idx++] = 0x00; data[idx++] = 0x00; @@ -811,8 +1227,8 @@ rt_uint8_t pd_fill_single_group_UFCS(rt_uint8_t *data, rt_uint8_t start_idx,stru data[idx++] = 0x13; data[idx++] = 0x88; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; @@ -820,48 +1236,47 @@ rt_uint8_t pd_fill_single_group_UFCS(rt_uint8_t *data, rt_uint8_t start_idx,stru data[idx++] = 0x00; data[idx++] = 0x00; // 2. 最小电压(高+低,直接取已拆分好的值) - data[idx++] = source->min_vol >> 8; - data[idx++] = source->min_vol & 0xFF; + data[idx++] = mm->min_vol >> 8; + data[idx++] = mm->min_vol & 0xFF; // 3. 最大电压(高+低,直接取已拆分好的值) - data[idx++] = source->max_vol >> 8; - data[idx++] = source->max_vol & 0xFF; + data[idx++] = mm->max_vol >> 8; + data[idx++] = mm->max_vol & 0xFF; // 4. 电流/功率(高+低,直接取已拆分好的值) - data[idx++] = source->protective_curr >> 8; - data[idx++] = source->protective_curr & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; // 5. 保留位6字节(直接填0) memset(&data[idx], 0x00, 6); idx+=6; return idx - start_idx; // 返回填充的总字节数 } -int source_set_pd_com(rt_uint8_t pro,rt_uint8_t pro_gear_idx) +static rt_uint8_t chrg_source_fill_AFC_three_gears(rt_uint8_t *data, + rt_uint8_t start_idx, struct chrg_switch_t *psw) { - rt_uint16_t len = 0; - rt_uint8_t data[150] = {0}; - rt_uint8_t frame[150] = {0}; - rt_uint8_t send_len = 0; //数据长度 - rt_uint8_t max_group_idx = 0; //挡位索引 - data[0] = pro; //协议 - rt_uint8_t idx = 1; - for(rt_uint8_t i = 0;isource.pd_set.mm; + rt_uint8_t idx = start_idx; + rt_uint8_t gear; - chrg_north_pkg_encode(ID_SOURCE, SRC_CMD_SET_PROTOCOL, data, send_len, frame, &len); -// for(rt_uint8_t i=0;i> 8; + data[idx++] = fixed_voltages[gear] & 0xFF; + data[idx++] = fixed_voltages[gear] >> 8; + data[idx++] = fixed_voltages[gear] & 0xFF; + data[idx++] = mm->max_cur >> 8; + data[idx++] = mm->max_cur & 0xFF; + memset(&data[idx], 0x00, 6); + idx += 6; + } + + return idx - start_idx; } -int source_set_pd_new_com(rt_uint8_t pro, struct chrg_switch_t *pSW) +int source_set_pro(rt_uint8_t pro, struct chrg_switch_t *pSW) { rt_uint16_t len = 0; rt_uint8_t data[150] = {0}; @@ -872,19 +1287,25 @@ int source_set_pd_new_com(rt_uint8_t pro, struct chrg_switch_t *pSW) rt_uint8_t idx = 1; //从data[1]开始填充 rt_uint8_t fill_len = 0; rt_uint8_t protocal = pSW->source.protocol; - if(protocal == 0x0A){ + if(protocal == PRO_PD0PSS){ fill_len = pd_fill_single_group_need_src_com(data, idx, pSW); - }else if(protocal == 0x03||protocal == 0x07||protocal == 0x08 ){ + }else if(protocal == PRO_SCPB||protocal == PRO_QC30||protocal == PRO_QC20 ||protocal == PRO_FCP ){ fill_len = pd_fill_single_group_no_src_com(data, idx, pSW); - }else if(protocal == 0x0C){ + }else if(protocal == PRO_UFCS){ fill_len = pd_fill_single_group_UFCS(data, idx, pSW); }else{ - fill_len = pd_fill_single_group_FCP_AFC(data, idx, pSW); + fill_len = chrg_source_fill_AFC_three_gears(data, idx, pSW); } idx += fill_len; send_len = idx; // 总填充字节数 chrg_north_pkg_encode(ID_SOURCE, SRC_CMD_SET_PROTOCOL, data, send_len, frame, &len); - rt_kprintf("idx = %d ",idx); +#if NORTH_SOURCE_DEBUG + rt_kprintf("source protocol frame:"); + for (rt_uint16_t i = 0; i < len; i++) { + rt_kprintf(" %02x", frame[i]); + } + rt_kprintf("\n"); +#endif // for(rt_uint8_t i=0;i1 0 0 - 0 + 1 1 4096 @@ -275,7 +275,7 @@ 1 - 0x8040000 + 0x8000000 0x40000 diff --git a/apps/chrg/codex_build.log b/apps/chrg/codex_build.log new file mode 100644 index 0000000..5b5cbf5 --- /dev/null +++ b/apps/chrg/codex_build.log @@ -0,0 +1,9 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_roll_nor.c... +linking... +Program Size: Code=146668 RO-data=15332 RW-data=9328 ZI-data=15968 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:02 diff --git a/apps/chrg/codex_low_voltage_relay_verify.log b/apps/chrg/codex_low_voltage_relay_verify.log new file mode 100644 index 0000000..7d4c47d --- /dev/null +++ b/apps/chrg/codex_low_voltage_relay_verify.log @@ -0,0 +1,9 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_sink.c... +linking... +Program Size: Code=147156 RO-data=15312 RW-data=9328 ZI-data=15984 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:02 diff --git a/apps/chrg/codex_protocol_ack_verify.log b/apps/chrg/codex_protocol_ack_verify.log new file mode 100644 index 0000000..0b3c0f9 --- /dev/null +++ b/apps/chrg/codex_protocol_ack_verify.log @@ -0,0 +1,18 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_north_pkg.c... +compiling chrg_lcd.c... +compiling chrg_thread.c... +compiling chrg_south.c... +compiling chrg_sink.c... +compiling chrg_north.c... +compiling chrg_source.c... +compiling chrg_roll_nor.c... +compiling chrg_roll_sou.c... +compiling chrg_comm.c... +linking... +Program Size: Code=146988 RO-data=15312 RW-data=9328 ZI-data=15984 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:03 diff --git a/apps/chrg/codex_soft_resume_remove.log b/apps/chrg/codex_soft_resume_remove.log new file mode 100644 index 0000000..5f103a7 --- /dev/null +++ b/apps/chrg/codex_soft_resume_remove.log @@ -0,0 +1,21 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_rel.c... +compiling chrg_vcom.c... +compiling chrg_north_pkg.c... +compiling chrg_lcd.c... +compiling chrg_thread.c... +compiling chrg_south.c... +compiling chrg_eload.c... +compiling chrg_sink.c... +compiling chrg_north.c... +compiling chrg_source.c... +compiling chrg_roll_nor.c... +compiling chrg_roll_sou.c... +compiling chrg_comm.c... +linking... +Program Size: Code=146964 RO-data=15312 RW-data=9328 ZI-data=15984 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:02 diff --git a/apps/chrg/codex_stop_current_confirm_verify.log b/apps/chrg/codex_stop_current_confirm_verify.log new file mode 100644 index 0000000..df56d49 --- /dev/null +++ b/apps/chrg/codex_stop_current_confirm_verify.log @@ -0,0 +1,21 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_vcom.c... +compiling chrg_thread.c... +compiling chrg_north_pkg.c... +compiling chrg_lcd.c... +compiling chrg_rel.c... +compiling chrg_eload.c... +compiling chrg_sink.c... +compiling chrg_south.c... +compiling chrg_source.c... +compiling chrg_north.c... +compiling chrg_roll_nor.c... +compiling chrg_roll_sou.c... +compiling chrg_comm.c... +linking... +Program Size: Code=147032 RO-data=15312 RW-data=9328 ZI-data=15984 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:03 diff --git a/apps/chrg/codex_stop_direct_verify.log b/apps/chrg/codex_stop_direct_verify.log new file mode 100644 index 0000000..53e6e21 --- /dev/null +++ b/apps/chrg/codex_stop_direct_verify.log @@ -0,0 +1,21 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_rel.c... +compiling chrg_north_pkg.c... +compiling chrg_thread.c... +compiling chrg_vcom.c... +compiling chrg_lcd.c... +compiling chrg_eload.c... +compiling chrg_sink.c... +compiling chrg_source.c... +compiling chrg_south.c... +compiling chrg_north.c... +compiling chrg_roll_nor.c... +compiling chrg_roll_sou.c... +compiling chrg_comm.c... +linking... +Program Size: Code=146976 RO-data=15312 RW-data=9328 ZI-data=15984 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:03 diff --git a/apps/chrg/codex_stop_repeat_guard_verify.log b/apps/chrg/codex_stop_repeat_guard_verify.log new file mode 100644 index 0000000..ccbd541 --- /dev/null +++ b/apps/chrg/codex_stop_repeat_guard_verify.log @@ -0,0 +1,9 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_comm.c... +linking... +Program Size: Code=147096 RO-data=15312 RW-data=9328 ZI-data=15984 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:02 diff --git a/apps/chrg/codex_verify.log b/apps/chrg/codex_verify.log new file mode 100644 index 0000000..72d757a --- /dev/null +++ b/apps/chrg/codex_verify.log @@ -0,0 +1,9 @@ +*** Using Compiler 'V6.16', folder: 'D:\keil mdk 2\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_sink.c... +linking... +Program Size: Code=147104 RO-data=15312 RW-data=9328 ZI-data=16000 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:01 diff --git a/apps/chrg/debug_hot.ini b/apps/chrg/debug_hot.ini index bccb64e..d5b340e 100644 --- a/apps/chrg/debug_hot.ini +++ b/apps/chrg/debug_hot.ini @@ -1 +1,4 @@ -LOAD %L INCREMENTAL \ No newline at end of file +LOAD %L INCREMENTAL +BS HardFault_Handler +BS rt_assert_handler +G diff --git a/apps/chrg/keil_flash.log b/apps/chrg/keil_flash.log new file mode 100644 index 0000000..69d25eb --- /dev/null +++ b/apps/chrg/keil_flash.log @@ -0,0 +1,2 @@ +Load "D:\\HUACE_Project\\3_Code\\HCDF_MID_405_Charger\\chrg_V.3.0\\apps\\chrg\\build\\keil\\Obj\\chrg.axf" +Erase Done.Programming Done.Verify OK.Flash Load finished at 18:22:08 diff --git a/apps/chrg/keil_pd_dynamic_build.log b/apps/chrg/keil_pd_dynamic_build.log new file mode 100644 index 0000000..13afe79 --- /dev/null +++ b/apps/chrg/keil_pd_dynamic_build.log @@ -0,0 +1,18 @@ +*** Using Compiler 'V6.16', folder: 'D:\Program Files\Keil_v5\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_roll_nor.c... +compiling chrg_north.c... +compiling chrg_south.c... +compiling chrg_comm.c... +compiling chrg_roll_sou.c... +compiling chrg_lcd.c... +compiling chrg_thread.c... +compiling chrg_sink.c... +compiling chrg_north_pkg.c... +compiling chrg_eload.c... +linking... +Program Size: Code=141040 RO-data=15212 RW-data=8544 ZI-data=13968 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:06 diff --git a/apps/chrg/keil_rebuild.log b/apps/chrg/keil_rebuild.log new file mode 100644 index 0000000..53f749a --- /dev/null +++ b/apps/chrg/keil_rebuild.log @@ -0,0 +1,130 @@ +*** Using Compiler 'V6.16', folder: 'D:\Program Files\Keil_v5\ARM\ARMCLANG\Bin' +Rebuild target 'chrg' +compiling main.c... +compiling chrg_switch.c... +compiling chrg_wdt.c... +compiling chrg_gpio.c... +compiling chrg_tmr.c... +compiling chrg_fal.c... +compiling chrg_tty.c... +compiling chrg_led.c... +compiling chrg_vcom.c... +compiling chrg_rel.c... +compiling chrg_roll_nor.c... +compiling chrg_north.c... +compiling chrg_south.c... +compiling chrg_comm.c... +compiling chrg_roll_sou.c... +compiling chrg_lcd.c... +compiling chrg_thread.c... +compiling chrg_source.c... +compiling chrg_sink.c... +compiling chrg_north_pkg.c... +compiling chrg_eload.c... +compiling chrg_utils.c... +compiling syscall_mem.c... +compiling syscalls.c... +compiling cctype.c... +compiling cstdlib.c... +compiling cstring.c... +compiling ctime.c... +compiling cunistd.c... +compiling cwchar.c... +compiling device.c... +compiling hwtimer.c... +compiling completion_comm.c... +compiling completion_up.c... +compiling condvar.c... +compiling dataqueue.c... +compiling pipe.c... +compiling ringblk_buf.c... +compiling ringbuffer.c... +compiling waitqueue.c... +compiling workqueue.c... +compiling dev_pin.c... +compiling dev_serial.c... +compiling dev_watchdog.c... +compiling stm32f4xx_hal_msp.c... +compiling board.c... +compiling drv_flash_f4.c... +compiling drv_gpio.c... +compiling drv_tim.c... +compiling drv_usart.c... +compiling drv_usbd.c... +compiling drv_wdt.c... +compiling drv_common.c... +compiling fal.c... +compiling fal_flash.c... +compiling fal_partition.c... +compiling fal_rtt.c... +compiling shell.c... +compiling msh_parse.c... +compiling cmd.c... +compiling msh.c... +compiling clock.c... +compiling components.c... +compiling cpu_up.c... +compiling defunct.c... +compiling idle.c... +compiling ipc.c... +compiling irq.c... +compiling kservice.c... +compiling memheap.c... +compiling mempool.c... +compiling object.c... +compiling scheduler_comm.c... +compiling scheduler_up.c... +compiling thread.c... +compiling timer.c... +compiling rt_vsnprintf_tiny.c... +compiling kerrno.c... +compiling kstring.c... +compiling rt_vsscanf.c... +compiling kstdio.c... +compiling atomic_arm.c... +compiling div0.c... +compiling showmem.c... +assembling context_rvds.S... +compiling cpuport.c... +compiling usbdevice.c... +compiling cdc_vcom.c... +compiling usbdevice_core.c... +assembling startup_stm32f405xx.s... +compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_gpio.c... +compiling stm32f4xx_hal_flash.c... +compiling stm32f4xx_hal_usart.c... +compiling stm32f4xx_hal_rng.c... +compiling stm32f4xx_hal_uart.c... +compiling stm32f4xx_hal_wwdg.c... +compiling stm32f4xx_hal_tim.c... +compiling stm32f4xx_hal_cryp_ex.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_rcc.c... +compiling stm32f4xx_hal_hcd.c... +compiling stm32f4xx_ll_usb.c... +compiling stm32f4xx_hal_pccard.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_flash_ramfunc.c... +compiling stm32f4xx_hal_tim_ex.c... +compiling stm32f4xx_hal_lptim.c... +compiling stm32f4xx_hal_cec.c... +compiling stm32f4xx_hal_cryp.c... +compiling stm32f4xx_hal.c... +compiling stm32f4xx_hal_iwdg.c... +compiling stm32f4xx_hal_pcd_ex.c... +compiling stm32f4xx_hal_pcd.c... +compiling stm32f4xx_hal_crc.c... +compiling stm32f4xx_hal_dma_ex.c... +compiling stm32f4xx_hal_cortex.c... +compiling stm32f4xx_hal_dma.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling stm32f4xx_hal_pwr_ex.c... +compiling ulog.c... +compiling console_be.c... +linking... +Program Size: Code=139712 RO-data=15040 RW-data=8544 ZI-data=13704 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:29 diff --git a/apps/chrg/keil_sink_retry_rebuild.log b/apps/chrg/keil_sink_retry_rebuild.log new file mode 100644 index 0000000..c6f7b18 --- /dev/null +++ b/apps/chrg/keil_sink_retry_rebuild.log @@ -0,0 +1,130 @@ +*** Using Compiler 'V6.16', folder: 'D:\Program Files\Keil_v5\ARM\ARMCLANG\Bin' +Rebuild target 'chrg' +compiling main.c... +compiling chrg_switch.c... +compiling chrg_wdt.c... +compiling chrg_gpio.c... +compiling chrg_tmr.c... +compiling chrg_fal.c... +compiling chrg_tty.c... +compiling chrg_led.c... +compiling chrg_vcom.c... +compiling chrg_rel.c... +compiling chrg_roll_nor.c... +compiling chrg_north.c... +compiling chrg_south.c... +compiling chrg_comm.c... +compiling chrg_roll_sou.c... +compiling chrg_lcd.c... +compiling chrg_thread.c... +compiling chrg_source.c... +compiling chrg_sink.c... +compiling chrg_north_pkg.c... +compiling chrg_eload.c... +compiling chrg_utils.c... +compiling syscall_mem.c... +compiling syscalls.c... +compiling cctype.c... +compiling cstdlib.c... +compiling cstring.c... +compiling ctime.c... +compiling cunistd.c... +compiling cwchar.c... +compiling device.c... +compiling hwtimer.c... +compiling completion_comm.c... +compiling completion_up.c... +compiling condvar.c... +compiling dataqueue.c... +compiling pipe.c... +compiling ringblk_buf.c... +compiling ringbuffer.c... +compiling waitqueue.c... +compiling workqueue.c... +compiling dev_pin.c... +compiling dev_serial.c... +compiling dev_watchdog.c... +compiling stm32f4xx_hal_msp.c... +compiling board.c... +compiling drv_flash_f4.c... +compiling drv_gpio.c... +compiling drv_tim.c... +compiling drv_usart.c... +compiling drv_usbd.c... +compiling drv_wdt.c... +compiling drv_common.c... +compiling fal.c... +compiling fal_flash.c... +compiling fal_partition.c... +compiling fal_rtt.c... +compiling shell.c... +compiling msh_parse.c... +compiling cmd.c... +compiling msh.c... +compiling clock.c... +compiling components.c... +compiling cpu_up.c... +compiling defunct.c... +compiling idle.c... +compiling ipc.c... +compiling irq.c... +compiling kservice.c... +compiling memheap.c... +compiling mempool.c... +compiling object.c... +compiling scheduler_comm.c... +compiling scheduler_up.c... +compiling thread.c... +compiling timer.c... +compiling rt_vsnprintf_tiny.c... +compiling kerrno.c... +compiling kstring.c... +compiling rt_vsscanf.c... +compiling kstdio.c... +compiling atomic_arm.c... +compiling div0.c... +compiling showmem.c... +assembling context_rvds.S... +compiling cpuport.c... +compiling usbdevice.c... +compiling cdc_vcom.c... +compiling usbdevice_core.c... +assembling startup_stm32f405xx.s... +compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_gpio.c... +compiling stm32f4xx_hal_flash.c... +compiling stm32f4xx_hal_usart.c... +compiling stm32f4xx_hal_rng.c... +compiling stm32f4xx_hal_uart.c... +compiling stm32f4xx_hal_wwdg.c... +compiling stm32f4xx_hal_tim.c... +compiling stm32f4xx_hal_cryp_ex.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_rcc.c... +compiling stm32f4xx_hal_hcd.c... +compiling stm32f4xx_ll_usb.c... +compiling stm32f4xx_hal_pccard.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_flash_ramfunc.c... +compiling stm32f4xx_hal_tim_ex.c... +compiling stm32f4xx_hal_lptim.c... +compiling stm32f4xx_hal_cec.c... +compiling stm32f4xx_hal_cryp.c... +compiling stm32f4xx_hal.c... +compiling stm32f4xx_hal_iwdg.c... +compiling stm32f4xx_hal_pcd_ex.c... +compiling stm32f4xx_hal_pcd.c... +compiling stm32f4xx_hal_crc.c... +compiling stm32f4xx_hal_dma_ex.c... +compiling stm32f4xx_hal_cortex.c... +compiling stm32f4xx_hal_dma.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling stm32f4xx_hal_pwr_ex.c... +compiling ulog.c... +compiling console_be.c... +linking... +Program Size: Code=140360 RO-data=15152 RW-data=8544 ZI-data=13712 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:29 diff --git a/apps/chrg/keil_trace_build.log b/apps/chrg/keil_trace_build.log new file mode 100644 index 0000000..d96d67b --- /dev/null +++ b/apps/chrg/keil_trace_build.log @@ -0,0 +1,130 @@ +*** Using Compiler 'V6.16', folder: 'D:\Program Files\Keil_v5\ARM\ARMCLANG\Bin' +Rebuild target 'chrg' +compiling main.c... +compiling chrg_switch.c... +compiling chrg_wdt.c... +compiling chrg_gpio.c... +compiling chrg_tmr.c... +compiling chrg_fal.c... +compiling chrg_tty.c... +compiling chrg_led.c... +compiling chrg_vcom.c... +compiling chrg_rel.c... +compiling chrg_roll_nor.c... +compiling chrg_north.c... +compiling chrg_south.c... +compiling chrg_comm.c... +compiling chrg_roll_sou.c... +compiling chrg_lcd.c... +compiling chrg_thread.c... +compiling chrg_source.c... +compiling chrg_sink.c... +compiling chrg_north_pkg.c... +compiling chrg_eload.c... +compiling chrg_utils.c... +compiling syscall_mem.c... +compiling syscalls.c... +compiling cctype.c... +compiling cstdlib.c... +compiling cstring.c... +compiling ctime.c... +compiling cunistd.c... +compiling cwchar.c... +compiling device.c... +compiling hwtimer.c... +compiling completion_comm.c... +compiling completion_up.c... +compiling condvar.c... +compiling dataqueue.c... +compiling pipe.c... +compiling ringblk_buf.c... +compiling ringbuffer.c... +compiling waitqueue.c... +compiling workqueue.c... +compiling dev_pin.c... +compiling dev_serial.c... +compiling dev_watchdog.c... +compiling stm32f4xx_hal_msp.c... +compiling board.c... +compiling drv_flash_f4.c... +compiling drv_gpio.c... +compiling drv_tim.c... +compiling drv_usart.c... +compiling drv_usbd.c... +compiling drv_wdt.c... +compiling drv_common.c... +compiling fal.c... +compiling fal_flash.c... +compiling fal_partition.c... +compiling fal_rtt.c... +compiling shell.c... +compiling msh_parse.c... +compiling cmd.c... +compiling msh.c... +compiling clock.c... +compiling components.c... +compiling cpu_up.c... +compiling defunct.c... +compiling idle.c... +compiling ipc.c... +compiling irq.c... +compiling kservice.c... +compiling memheap.c... +compiling mempool.c... +compiling object.c... +compiling scheduler_comm.c... +compiling scheduler_up.c... +compiling thread.c... +compiling timer.c... +compiling rt_vsnprintf_tiny.c... +compiling kerrno.c... +compiling kstring.c... +compiling rt_vsscanf.c... +compiling kstdio.c... +compiling atomic_arm.c... +compiling div0.c... +compiling showmem.c... +assembling context_rvds.S... +compiling cpuport.c... +compiling usbdevice.c... +compiling cdc_vcom.c... +compiling usbdevice_core.c... +assembling startup_stm32f405xx.s... +compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_gpio.c... +compiling stm32f4xx_hal_flash.c... +compiling stm32f4xx_hal_usart.c... +compiling stm32f4xx_hal_rng.c... +compiling stm32f4xx_hal_uart.c... +compiling stm32f4xx_hal_wwdg.c... +compiling stm32f4xx_hal_tim.c... +compiling stm32f4xx_hal_cryp_ex.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_rcc.c... +compiling stm32f4xx_hal_hcd.c... +compiling stm32f4xx_ll_usb.c... +compiling stm32f4xx_hal_pccard.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_flash_ramfunc.c... +compiling stm32f4xx_hal_tim_ex.c... +compiling stm32f4xx_hal_lptim.c... +compiling stm32f4xx_hal_cec.c... +compiling stm32f4xx_hal_cryp.c... +compiling stm32f4xx_hal.c... +compiling stm32f4xx_hal_iwdg.c... +compiling stm32f4xx_hal_pcd_ex.c... +compiling stm32f4xx_hal_pcd.c... +compiling stm32f4xx_hal_crc.c... +compiling stm32f4xx_hal_dma_ex.c... +compiling stm32f4xx_hal_cortex.c... +compiling stm32f4xx_hal_dma.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling stm32f4xx_hal_pwr_ex.c... +compiling ulog.c... +compiling console_be.c... +linking... +Program Size: Code=139960 RO-data=15064 RW-data=8544 ZI-data=13704 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:27 diff --git a/apps/chrg/keil_trace_build2.log b/apps/chrg/keil_trace_build2.log new file mode 100644 index 0000000..37fa659 --- /dev/null +++ b/apps/chrg/keil_trace_build2.log @@ -0,0 +1,9 @@ +*** Using Compiler 'V6.16', folder: 'D:\Program Files\Keil_v5\ARM\ARMCLANG\Bin' +Build target 'chrg' +compiling chrg_north.c... +linking... +Program Size: Code=140256 RO-data=15160 RW-data=8544 ZI-data=13704 +FromELF: creating hex file... +After Build - User command #1: fromelf --bin .\build\keil\Obj\chrg.axf -o build/chrg.bin +".\build\keil\Obj\chrg.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:02 diff --git a/docs/2026-07-21-source-state-machine-remediation-plan.md b/docs/2026-07-21-source-state-machine-remediation-plan.md new file mode 100644 index 0000000..43f98d0 --- /dev/null +++ b/docs/2026-07-21-source-state-machine-remediation-plan.md @@ -0,0 +1,121 @@ +# Source 状态机改造与角色切换维护计划 + +## 1. 改造目标 + +将电源(Source)控制整理为与负载(Sink)类似的独立状态机,但保留两者业务差异: + +- 北向协议板只负责角色/CC 线选择、协议设置及周期读取协议板电压电流。 +- 功率板角色、模式、电压、电流、继电器和启停全部由 `chrg_source.c` 管理。 +- 所有 Source 南向寄存器命令统一使用 `chrg_sou_com_batch_submit()`,不再散落调用 `chrg_set_sou_reg()`。 +- Source 不判断带载电流。协议板返回电压有效则进入 `RUNNING`;启动后持续无电压则回到 `WAIT`。 +- 协议、协议电压或协议电流发生变化时,重新进入 `INIT`。 +- Source/Sink 不停机切换时,必须先停止旧角色,确认完成后再初始化新角色。 + +## 2. 当前已完成的修改 + +当前工作区已完成第一阶段,尚未形成完整状态机: + +1. `apps/chrg/applications/utils/chrg_source.c` + - 新增 `chrg_source_init_submit(rt_uint8_t idx, struct chrg_switch_t *pSW)`。 + - 通过一个批次下发 `REG_ELOAD`、`REG_MODE`、`REG_VOLTAGE_OUT + 1`、`REG_CURRENT_OUT + 1` 和 `REG_WORK = WORK_STOP`。 +2. `apps/chrg/applications/utils/chrg_source.h` + - 增加上述函数声明。 +3. `apps/chrg/applications/thread/chrg_roll_nor.c` + - Source 协议设置流程进入 `IDX_GET_SOURCE_VC` 后提交南向初始化批次。 + - 仅当批次提交成功时设置 `source.On_Flag = 1`。 + +注意:当前仍有旧 Source 路径在 `chrg_north.c`、`chrg_roll_nor.c` 和 `chrg_lcd.c` 中直接调用 `chrg_set_sou_reg()`。在完成后续步骤前,新旧逻辑会同时存在,不能作为最终版本发布。 + +## 3. 目标状态机 + +在 `chrg_source.h` 中增加明确的 Source 状态: + +```c +typedef enum { + SOURCE_WORK_STOP = 0, + SOURCE_WORK_WAIT, + SOURCE_WORK_INIT, + SOURCE_WORK_START, + SOURCE_WORK_RUNNING, + SOURCE_WORK_STOPPING, + SOURCE_WORK_FAULT +} source_work_mode_t; +``` + +在 `chrg_src_t` 中增加每通道状态及参数快照:`work_mode`、`last_protocol`、`last_voltage`、`last_current`、`zero_volt_count`、`retry_count`。参数快照只能在对应命令确认完成后更新。 + +状态动作: + +| 状态 | 动作 | 后续状态 | +|---|---|---| +| `INIT` | 批量设置 Source 角色、模式、电压、电流及继电器,保持停止 | `START` | +| `START` | 批量下发 `REG_WORK = WORK_START` | `WAIT` | +| `WAIT` | 北向周期读取协议板 VC | 有电压进入 `RUNNING` | +| `RUNNING` | 更新 VC;检测协议/电压/电流变化 | 变化进入 `INIT` | +| `STOPPING` | 批量停止输出、关闭功率板继电器 | `STOP` | +| `STOP` | 清理使能并断开北向继电器 | 保持 | + +建议电压大于 `WORK_MIN_VOLT` 即视为有效。电压为零连续 3 次再由 `RUNNING` 回到 `WAIT`,避免单帧异常造成状态抖动。 + +## 4. COM 端改造 + +修改 `chrg_mb_write_sys()`: + +- 当目标角色为 Source 且当前未工作时,只设置 `pSW->sub = IDX_SET_SOURCE_CC_SET`,由北向依次完成 CC 和协议设置;协议应答完成后才进入 `SOURCE_WORK_INIT`。 +- 同角色下,比较协议、协议电压、协议电流。任一变化则进入 `SOURCE_WORK_INIT`;相同参数不得重复初始化。 +- 不要在收到 `SYS_REG_ROLE_SWITCH` 时立即覆盖实际角色。应保存 `target_role`,在参数生效时启动切换流程。 +- 切换期间返回 `MB_EX_SLAVE_BUSY`,或者只更新影子参数并等待本次切换完成。 + +角色切换顺序: + +```text +Source -> Sink: Source STOPPING -> STOP -> 切换角色/继电器 -> Sink INIT +Sink -> Source: Sink STOP -> 切换角色/继电器 -> 北向 CC/协议 -> Source INIT +``` + +禁止 Source 和 Sink 的功率板继电器、工作命令在同一通道同时有效。 + +## 5. 北向流程改造 + +精简 `chrg_roll_nor_set()` 的 Source 分支,只保留: + +1. `IDX_SET_SOURCE_CC_SET`:设置 CC 线,成功后进入 `IDX_SET_SOURCE_PD`。 +2. `IDX_SET_SOURCE_PD`:设置协议,应答成功后设置 `SOURCE_WORK_INIT`。 +3. `IDX_GET_SOURCE_VC`:读取协议板电压电流。 + +删除 Source 分支中的 `IDX_SET_SOURCE_ID`、`IDX_SET_SOURCE_CV`、`IDX_SER_SOURCE_VOLT`、`IDX_SER_SOURCE_CURR`、`IDX_SET_SOURCE_WORK` 南向设置动作。校准、控制台和 Sink 分支不在本次范围内。 + +`chrg_north.c` 只解析 Source VC:更新 `source.vc`、`Now_voltage`、`Now_current`,并根据电压推进 `WAIT/RUNNING`。移除 `chrg_source_standard_output()`、`chrg_source_fast_output()` 和旧的分步停机控制,避免解析函数直接下发南向命令。 + +## 6. 南向完成确认 + +`chrg_sou_com_batch_submit()` 返回成功仅表示入队成功,不表示功率板已经执行。需要为每通道增加批次完成序号或完成事件: + +- `INIT` 批次全部应答匹配后才能进入 `START`。 +- `START` 应答成功后才能进入 `WAIT`。 +- `STOPPING` 批次完成后才能断开北向继电器和修改实际角色。 +- CRC 错误、寄存器不匹配或超时不得推进状态;达到重试上限后进入 `FAULT`。 + +## 7. 推荐实施顺序 + +1. 增加 Source 状态枚举、参数快照及 `chrg_source_work()`。 +2. 将 INIT、START、STOPPING 的南向动作全部迁移到 `chrg_source.c`。 +3. 增加南向批次完成通知,再改为应答驱动状态转换。 +4. 精简北向 Source 子状态,只保留 CC、协议和 VC。 +5. 修改 COM 同角色参数差异及跨角色切换。 +6. 清理 `chrg_lcd.c` 中重复的 Source 启停和角色寄存器写入。 +7. 编译并在硬件验证后删除旧字段和旧函数。 + +## 8. 验证清单 + +- Keil 编译 `apps/chrg/chrg.uvprojx`,确认无未定义符号和头文件循环依赖。 +- 单通道验证 Source 首次启动、停止、相同参数重复生效。 +- 分别改变协议、协议电压和协议电流,确认只执行一次重新 INIT。 +- 模拟协议板电压持续为零,确认进入 WAIT 且不会反复提交相同批次。 +- 验证 Source -> Sink、Sink -> Source,示波器确认切换期间无输出重叠。 +- 四通道同时运行并交叉切换,检查南向队列饥饿、响应错配、超时和缓存串通道。 +- 回归 Sink 正常负载、动态测试、无电流重启及低压处理。 + +## 9. 接手注意事项 + +开始修改前先执行 `git status --short` 和 `git diff`。当前 `chrg_north.c` 还包含此前 Sink 连续确认次数改为 3 的修改及注释清理,不能在 Source 重构时回退。不要提交 Keil `build/`、`Obj/`、AXF、BIN 或 MAP 等生成文件。 diff --git a/docs/2026-07-30-source-cc-close-protocol-switch.md b/docs/2026-07-30-source-cc-close-protocol-switch.md new file mode 100644 index 0000000..924f571 --- /dev/null +++ b/docs/2026-07-30-source-cc-close-protocol-switch.md @@ -0,0 +1,101 @@ +# Source CC_CLOSE 协议切换修改说明 + +## 修改目标 + +修正 Source 北向协议板的命令顺序,避免在 CC 保持 Attached 时直接重配 +`0xD0`。多通道同时切换时采用共享等待屏障,使保护时间并行发生,不按 +通道串行累计。 + +## 本轮实施步骤 + +### 第一步:统一状态命名 + +断开 CC 的命令状态命名为 `IDX_SET_SOURCE_CC_CLOSE`,等待状态命名为 +`IDX_WAIT_SOURCE_CC_CLOSE`。`CC_CLOSE` 固定发送 `0x17=0x03`,不覆盖 +COM 保存的目标 `CC_Set`。 + +### 第二步:调整停止态启动顺序 + +Source 从停止状态启动以及 Sink 切换为 Source 时,不发送 `CC_CLOSE`, +直接按以下顺序执行: + +```text +IDX_SET_SOURCE_PD +-> IDX_WAIT_SOURCE_PROTOCOL +-> IDX_SET_SOURCE_CC_SET +-> IDX_GET_SOURCE_VC +``` + +即先设置协议,收到 `0xD0` ACK 并完成保护等待后,再连接目标 CC。 + +### 第三步:识别必须断开 CC 的切换 + +COM 参数生效时,满足以下任一条件进入 `CC_CLOSE`: + +- 新协议为 PD/PPS 或 PD3.1/AVS,并且协议或 CC 线与上次不同。 +- 上次协议为 PD/PPS 或 PD3.1/AVS,本次切换为非 PD 协议。 + +其它协议配置变化从 `IDX_SET_SOURCE_PD` 开始。标准协议仅改变电压、电流 +时直接更新功率板,不重复下发 `0xD0`。 + +### 第四步:执行 CC_CLOSE 命令链 + +COM先确定北向`sub`,协议或CC发生变化时进入 +`SOURCE_WORK_PROTOCOL_SWITCH`,不在COM中调用发送函数。北向轮询线程 +执行该Source工作状态,负责向南向功率板写入0V;标准协议还会在同一批 +命令中依次写模式和电流。提交成功后清零`set_volt`并进入 +`SOURCE_WORK_WAIT`,随后按已经选定的`sub`继续执行。 + +需要断开 CC 的通道按以下顺序执行: + +```text +IDX_SET_SOURCE_CC_CLOSE 发送 0x17=0x03 +-> IDX_WAIT_SOURCE_CC_CLOSE 等待共享保护时间 +-> IDX_SET_SOURCE_PD 发送 0xD0 +-> IDX_WAIT_SOURCE_PROTOCOL 等待协议 ACK 后保护时间 +-> IDX_SET_SOURCE_CC_SET 发送 0x17=目标 CC +-> IDX_GET_SOURCE_VC 恢复 0x20 查询 +``` + +每条设置命令仍需收到角色、命令、长度和 CRC 均正确的回码后才能推进 +`sub`;错误回码继续沿用现有丢弃和有限重发逻辑。 + +### 第五步:多通道共享 CC_CLOSE 等待 + +使用 `pending_mask` 和 `acked_mask` 管理本轮参与切换的 Source 通道。 +所有已登记通道都收到 `0x17` ACK 后,只启动一次 300 ms 保护等待。 +一个通道和四个通道的保护等待本身均为 300 ms;四通道只额外产生必要的 +命令收发时间,不会累计为四次 300 ms。 + +### 第六步:握手稳定确认(本轮不修改) + +本轮不增加“连续两次 `0x20` 电压稳定后才进入 RUNNING”的新逻辑,继续 +使用现有 `chrg_source_parse()` 电压误差和重试规则。 + +### 第七步:配置覆盖保护 + +继续使用 `config_version` 防止 COM 新配置被旧回码推进。停止、角色切换 +或不再需要 `CC_CLOSE` 时,会把通道移出共享等待屏障,避免残留状态影响 +下一次启动。 + +### 第八步:完整失败恢复(本轮不修改) + +本轮不增加握手总超时、再次 detach、整套流程最多重试两轮以及故障上报。 +单条北向命令仍使用现有的三次发送机制。完整恢复流程待单独验证后实施。 + +## 时间参数 + +| 参数 | 当前值 | 说明 | +|---|---:|---| +| `SOURCE_CC_CLOSE_WAIT_MS` | 300 ms | 从本轮最后一个有效 `CC_CLOSE` ACK 开始 | +| `SOURCE_PROTOCOL_WAIT_MS` | 100 ms | 每通道收到 `0xD0` ACK 后并行等待 | +| `NORTH_RESPONSE_TIMEOUT_MS` | 50 ms | 单次北向回码等待 | +| `NORTH_REQUEST_MAX_RETRIES` | 2 | 首次发送外最多重发两次 | + +## 建议验证顺序 + +1. 单通道停止态启动 PD,确认发送顺序为 `D0 -> 17(目标CC) -> 20`。 +2. 单通道 PD 改协议或 CC,确认顺序为 `17(03) -> 等待 -> D0 -> 17(目标CC) -> 20`。 +3. 单通道 PD 切换 QC/FCP/标准,确认切换前先发送 `17(03)`。 +4. 四通道连续生效,确认四路 `17(03)` 均回码后共享一次约 300 ms 等待。 +5. 连续执行 20 至 30 轮协议切换,检查是否仍出现 Source 芯片 PD 状态异常。 diff --git a/docs/Allegro.jrl b/docs/Allegro.jrl new file mode 100644 index 0000000..b56c5ce --- /dev/null +++ b/docs/Allegro.jrl @@ -0,0 +1,2519 @@ +\t (00:00:05) Allegro 17.2 S079 Windows SPB 64-bit Edition +\t (00:00:05) Journal start - Tue Aug 25 19:43:26 2026 +\t (00:00:05) Host=DESKTOP-5RA9F3K User=Adminuser Pid=39932 CPUs=20 +\t (00:00:05) CmdLine= D:\Cadence\SPB_17.2\tools\bin\Allegro.exe D:\HC-PMP-SW\Power_ControlPannel\MP_Control_sw_405\MP_SW_Chrg\chrg\docs\HC_DFCD_Protocol_V3_0.brd +\t (00:00:05) + (00:00:05) Loading axlcore.cxt +\t (00:00:06) Opening existing design... +\i (00:00:06) QtSignal SPBFoldDockArea FoldAreaTabWidget currentChanged HC_DFCD_Protocol_V3_0 +\d (00:00:06) Design opened: D:/HC-PMP-SW/Power_ControlPannel/MP_Control_sw_405/MP_SW_Chrg/chrg/docs/HC_DFCD_Protocol_V3_0.brd +\i (00:00:06) trapsize 8975 +\i (00:00:06) trapsize 9251 +\i (00:00:06) trapsize 9375 +\i (00:00:07) trapsize 8018 +\i (00:00:07) trapsize 8018 +\i (00:00:07) generaledit +\i (00:00:10) zoom out 1 +\i (00:00:10) setwindow pcb +\i (00:00:10) zoom out 138.1616 64.8823 +\i (00:00:10) trapsize 16037 +\i (00:00:10) zoom out 1 +\i (00:00:10) setwindow pcb +\i (00:00:10) zoom out 201.4873 63.2786 +\i (00:00:10) trapsize 32073 +\i (00:00:10) zoom out 1 +\i (00:00:10) setwindow pcb +\i (00:00:10) zoom out 502.3331 132.2094 +\i (00:00:10) trapsize 64146 +\i (00:00:11) zoom in 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom in -32.0050 32.1412 +\i (00:00:11) trapsize 32073 +\i (00:00:11) zoom in 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom in -32.0050 32.1412 +\i (00:00:11) trapsize 16037 +\i (00:00:11) zoom in 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom in -32.0049 32.1412 +\i (00:00:11) trapsize 8018 +\i (00:00:11) zoom in 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom in -32.8068 31.0187 +\i (00:00:11) trapsize 4009 +\i (00:00:11) zoom out 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom out 8.5676 58.6818 +\i (00:00:11) trapsize 8018 +\i (00:00:11) zoom out 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom out 8.4072 58.2007 +\i (00:00:11) trapsize 16037 +\i (00:00:11) zoom in 1 +\i (00:00:11) setwindow pcb +\i (00:00:11) zoom in 80.5716 56.2764 +\i (00:00:11) trapsize 8018 +\i (00:00:12) zoom out 1 +\i (00:00:12) setwindow pcb +\i (00:00:12) zoom out 26.5284 75.0392 +\i (00:00:12) trapsize 16037 +\i (00:00:13) zoom in 1 +\i (00:00:13) setwindow pcb +\i (00:00:13) zoom in 7.2846 90.8350 +\i (00:00:13) trapsize 8018 +\i (00:00:13) zoom in 1 +\i (00:00:13) setwindow pcb +\i (00:00:13) zoom in 7.2846 90.8350 +\i (00:00:13) trapsize 4009 +\i (00:00:14) zoom out 1 +\i (00:00:14) setwindow pcb +\i (00:00:14) zoom out 38.3956 49.0598 +\i (00:00:14) trapsize 8018 +\i (00:00:14) zoom in 1 +\i (00:00:14) setwindow pcb +\i (00:00:14) zoom in 38.3955 49.2202 +\i (00:00:14) trapsize 4009 +\i (00:00:14) zoom out 1 +\i (00:00:14) setwindow pcb +\i (00:00:14) zoom out 38.2352 49.3004 +\i (00:00:14) trapsize 8018 +\i (00:00:15) zoom in 1 +\i (00:00:15) setwindow pcb +\i (00:00:15) zoom in 44.3291 43.6876 +\i (00:00:15) trapsize 4009 +\i (00:00:15) zoom in 1 +\i (00:00:15) setwindow pcb +\i (00:00:15) zoom in 44.3291 43.6876 +\i (00:00:15) trapsize 2005 +\i (00:00:15) zoom out 1 +\i (00:00:15) setwindow pcb +\i (00:00:15) zoom out 41.4425 44.9305 +\i (00:00:15) trapsize 4009 +\i (00:00:16) zoom out 1 +\i (00:00:16) setwindow pcb +\i (00:00:16) zoom out 35.8297 45.3314 +\i (00:00:16) trapsize 8018 +\i (00:00:16) zoom out 1 +\i (00:00:16) setwindow pcb +\i (00:00:16) zoom out 33.7449 44.0484 +\i (00:00:16) trapsize 16037 +\i (00:00:16) zoom out 1 +\i (00:00:16) setwindow pcb +\i (00:00:16) zoom out 37.2732 25.7668 +\i (00:00:16) trapsize 32073 +\i (00:00:16) zoom out 1 +\i (00:00:16) setwindow pcb +\i (00:00:16) zoom out 173.2635 25.1253 +\i (00:00:16) trapsize 64146 +\i (00:00:16) zoom out 1 +\i (00:00:16) setwindow pcb +\i (00:00:16) zoom out 413.1708 40.5204 +\i (00:00:16) trapsize 128293 +\i (00:00:16) zoom out 1 +\i (00:00:16) setwindow pcb +\i (00:00:16) zoom out 859.6295 35.3887 +\i (00:00:16) trapsize 256585 +\i (00:00:17) zoom out 1 +\i (00:00:17) setwindow pcb +\i (00:00:17) zoom out 402.9074 158.5498 +\i (00:00:17) trapsize 513171 +\i (00:00:17) zoom out 1 +\i (00:00:17) setwindow pcb +\i (00:00:17) zoom out 895.5514 382.3806 +\i (00:00:17) trapsize 723589 +\i (00:00:17) zoom out 1 +\i (00:00:17) setwindow pcb +\i (00:00:17) zoom out 1303.7627 580.1737 +\i (00:00:17) trapsize 723589 +\i (00:00:18) zoom in 1 +\i (00:00:18) setwindow pcb +\i (00:00:18) zoom in 30.2460 -56.5847 +\i (00:00:18) trapsize 361795 +\i (00:00:18) zoom in 1 +\i (00:00:18) setwindow pcb +\i (00:00:18) zoom in 30.2460 -56.5846 +\i (00:00:18) trapsize 180897 +\i (00:00:18) zoom in 1 +\i (00:00:18) setwindow pcb +\i (00:00:18) zoom in 30.2460 -56.5846 +\i (00:00:18) trapsize 90449 +\i (00:00:18) zoom in 1 +\i (00:00:18) setwindow pcb +\i (00:00:18) zoom in 28.4371 -60.2025 +\i (00:00:18) trapsize 45224 +\i (00:00:18) zoom out 1 +\i (00:00:18) setwindow pcb +\i (00:00:18) zoom out 24.8191 -9.5513 +\i (00:00:18) trapsize 90449 +\i (00:00:18) zoom out 1 +\i (00:00:18) setwindow pcb +\i (00:00:18) zoom out 32.0551 1.3025 +\i (00:00:18) trapsize 180897 +\i (00:00:19) zoom out 1 +\i (00:00:19) setwindow pcb +\i (00:00:19) zoom out 17.5833 59.1896 +\i (00:00:19) trapsize 361795 +\i (00:00:19) zoom out 1 +\i (00:00:19) setwindow pcb +\i (00:00:19) zoom out 10.3474 211.1433 +\i (00:00:19) trapsize 723589 +\i (00:00:19) zoom in 1 +\i (00:00:19) setwindow pcb +\i (00:00:19) zoom in -13.1693 15.7742 +\i (00:00:19) trapsize 361795 +\i (00:00:20) zoom in 1 +\i (00:00:20) setwindow pcb +\i (00:00:20) zoom in -13.1693 15.7742 +\i (00:00:20) trapsize 180897 +\i (00:00:20) zoom in 1 +\i (00:00:20) setwindow pcb +\i (00:00:20) zoom in -13.1692 15.7743 +\i (00:00:20) trapsize 90449 +\i (00:00:21) zoom out 1 +\i (00:00:21) setwindow pcb +\i (00:00:21) zoom out 3.1116 1.3025 +\i (00:00:21) trapsize 180897 +\i (00:00:21) zoom out 1 +\i (00:00:21) setwindow pcb +\i (00:00:21) zoom out 3.1116 15.7743 +\i (00:00:21) trapsize 361795 +\i (00:00:21) zoom out 1 +\i (00:00:21) setwindow pcb +\i (00:00:21) zoom out 15.7742 15.7743 +\i (00:00:21) trapsize 723589 +\i (00:00:22) zoom in 1 +\i (00:00:22) setwindow pcb +\i (00:00:22) zoom in 1.3025 -42.1129 +\i (00:00:22) trapsize 361795 +\i (00:00:22) zoom in 1 +\i (00:00:22) setwindow pcb +\i (00:00:22) zoom in 1.3025 -42.1128 +\i (00:00:22) trapsize 180897 +\i (00:00:23) zoom in 1 +\i (00:00:23) setwindow pcb +\i (00:00:23) zoom in 1.3025 -24.0231 +\i (00:00:23) trapsize 90449 +\i (00:00:23) zoom in 1 +\i (00:00:23) setwindow pcb +\i (00:00:23) zoom in 1.3025 -24.0230 +\i (00:00:23) trapsize 45224 +\i (00:00:24) zoom in 1 +\i (00:00:24) setwindow pcb +\i (00:00:24) zoom in 4.0160 1.3026 +\i (00:00:24) trapsize 22612 +\i (00:00:24) zoom out 1 +\i (00:00:24) setwindow pcb +\i (00:00:24) zoom out 3.5638 28.4372 +\i (00:00:24) trapsize 45224 +\i (00:00:24) zoom out 1 +\i (00:00:24) setwindow pcb +\i (00:00:24) zoom out -1.8631 25.7237 +\i (00:00:24) trapsize 90449 +\i (00:00:25) zoom in 1 +\i (00:00:25) setwindow pcb +\i (00:00:25) zoom in -7.2901 53.7627 +\i (00:00:25) trapsize 45224 +\i (00:00:25) zoom in 1 +\i (00:00:25) setwindow pcb +\i (00:00:25) zoom in -2.7677 50.1448 +\i (00:00:25) trapsize 22612 +\i (00:00:25) zoom in 1 +\i (00:00:25) setwindow pcb +\i (00:00:25) zoom in -2.7677 50.1448 +\i (00:00:25) trapsize 11306 +\i (00:00:25) zoom in 1 +\i (00:00:25) setwindow pcb +\i (00:00:25) zoom in -2.7677 50.1448 +\i (00:00:25) trapsize 5653 +\i (00:00:26) zoom out 1 +\i (00:00:26) setwindow pcb +\i (00:00:26) zoom out 25.1583 52.9713 +\i (00:00:26) trapsize 11306 +\i (00:00:27) zoom out 1 +\i (00:00:27) setwindow pcb +\i (00:00:27) zoom out 13.8523 41.6653 +\i (00:00:27) trapsize 22612 +\i (00:00:27) zoom out 1 +\i (00:00:27) setwindow pcb +\i (00:00:27) zoom out 12.9477 40.7608 +\i (00:00:27) trapsize 45224 +\i (00:00:27) zoom in 1 +\i (00:00:27) setwindow pcb +\i (00:00:27) zoom in 19.3922 20.8622 +\i (00:00:27) trapsize 22612 +\i (00:00:28) zoom in 1 +\i (00:00:28) setwindow pcb +\i (00:00:28) zoom in 22.5579 11.3651 +\i (00:00:28) trapsize 11306 +\i (00:00:28) zoom in 1 +\i (00:00:28) setwindow pcb +\i (00:00:28) zoom in 22.5579 11.3651 +\i (00:00:28) trapsize 5653 +\i (00:00:28) zoom in 1 +\i (00:00:28) setwindow pcb +\i (00:00:28) zoom in 22.5579 11.3652 +\i (00:00:28) trapsize 2827 +\i (00:00:29) zoom out 1 +\i (00:00:29) setwindow pcb +\i (00:00:29) zoom out 29.3416 34.6558 +\i (00:00:29) trapsize 5653 +\i (00:00:30) zoom in 1 +\i (00:00:30) setwindow pcb +\i (00:00:30) zoom in 27.5326 45.7357 +\i (00:00:30) trapsize 2827 +\i (00:00:30) zoom in 1 +\i (00:00:30) setwindow pcb +\i (00:00:30) zoom in 28.4936 46.3576 +\i (00:00:30) trapsize 1413 +\i (00:00:31) zoom in 1 +\i (00:00:31) setwindow pcb +\i (00:00:31) zoom in 28.4937 45.3400 +\i (00:00:31) trapsize 707 +\i (00:00:31) zoom in 1 +\i (00:00:31) setwindow pcb +\i (00:00:31) zoom in 28.4654 45.2977 +\i (00:00:31) trapsize 353 +\i (00:00:31) zoom out 1 +\i (00:00:31) setwindow pcb +\i (00:00:31) zoom out 29.2992 45.4531 +\i (00:00:31) trapsize 707 +\i (00:00:32) zoom out 1 +\i (00:00:32) setwindow pcb +\i (00:00:32) zoom out 29.2993 45.4108 +\i (00:00:32) trapsize 1413 +\i (00:00:32) zoom in 1 +\i (00:00:32) setwindow pcb +\i (00:00:32) zoom in 30.0341 47.7003 +\i (00:00:32) trapsize 707 +\i (00:00:32) zoom in 1 +\i (00:00:32) setwindow pcb +\i (00:00:32) zoom in 30.0341 47.7003 +\i (00:00:32) trapsize 353 +\i (00:00:33) zoom out 1 +\i (00:00:33) setwindow pcb +\i (00:00:33) zoom out 29.7868 46.8523 +\i (00:00:33) trapsize 707 +\i (00:00:33) zoom out 1 +\i (00:00:33) setwindow pcb +\i (00:00:33) zoom out 29.7869 46.8524 +\i (00:00:33) trapsize 1413 +\i (00:00:33) zoom in 1 +\i (00:00:33) setwindow pcb +\i (00:00:33) zoom in 29.4194 39.9840 +\i (00:00:33) trapsize 707 +\i (00:00:33) zoom in 1 +\i (00:00:33) setwindow pcb +\i (00:00:33) zoom in 29.4336 40.3232 +\i (00:00:33) trapsize 353 +\i (00:00:34) zoom out 1 +\i (00:00:34) setwindow pcb +\i (00:00:34) zoom out 29.5396 40.5988 +\i (00:00:34) trapsize 707 +\i (00:00:34) zoom out 1 +\i (00:00:34) setwindow pcb +\i (00:00:34) zoom out 29.8506 42.9449 +\i (00:00:34) trapsize 1413 +\i (00:00:34) zoom out 1 +\i (00:00:34) setwindow pcb +\i (00:00:34) zoom out 29.8506 42.9448 +\i (00:00:34) trapsize 2827 +\i (00:00:35) zoom in 1 +\i (00:00:35) setwindow pcb +\i (00:00:35) zoom in 30.6985 48.5414 +\i (00:00:35) trapsize 1413 +\i (00:00:35) zoom in 1 +\i (00:00:35) setwindow pcb +\i (00:00:35) zoom in 30.6985 48.5414 +\i (00:00:35) trapsize 707 +\i (00:00:35) zoom in 1 +\i (00:00:35) setwindow pcb +\i (00:00:35) zoom in 30.1191 48.5697 +\i (00:00:35) trapsize 353 +\i (00:00:36) zoom out 1 +\i (00:00:36) setwindow pcb +\i (00:00:36) zoom out 29.5962 48.0963 +\i (00:00:36) trapsize 707 +\i (00:00:36) zoom in 1 +\i (00:00:36) setwindow pcb +\i (00:00:36) zoom in 29.6811 44.6762 +\i (00:00:36) trapsize 353 +\i (00:00:37) zoom in 1 +\i (00:00:37) setwindow pcb +\i (00:00:37) zoom in 29.8154 44.8741 +\i (00:00:37) trapsize 177 +\i (00:00:37) zoom out 1 +\i (00:00:37) setwindow pcb +\i (00:00:37) zoom out 29.8154 44.8918 +\i (00:00:37) trapsize 353 +\i (00:00:37) zoom out 1 +\i (00:00:37) setwindow pcb +\i (00:00:37) zoom out 29.8013 44.8918 +\i (00:00:37) trapsize 707 +\i (00:00:37) zoom out 1 +\i (00:00:37) setwindow pcb +\i (00:00:37) zoom out 29.6741 45.0756 +\i (00:00:37) trapsize 1413 +\i (00:00:37) zoom out 1 +\i (00:00:37) setwindow pcb +\i (00:00:37) zoom out 29.6740 45.0756 +\i (00:00:37) trapsize 2827 +\i (00:00:38) zoom out 1 +\i (00:00:38) setwindow pcb +\i (00:00:38) zoom out 29.6741 45.5278 +\i (00:00:38) trapsize 5653 +\i (00:00:38) zoom in 1 +\i (00:00:38) setwindow pcb +\i (00:00:38) zoom in 29.6741 45.5277 +\i (00:00:38) trapsize 2827 +\i (00:00:38) zoom in 1 +\i (00:00:38) setwindow pcb +\i (00:00:38) zoom in 29.6741 45.3016 +\i (00:00:38) trapsize 1413 +\i (00:00:38) zoom in 1 +\i (00:00:38) setwindow pcb +\i (00:00:38) zoom in 29.7024 45.6973 +\i (00:00:38) trapsize 707 +\i (00:00:39) zoom in 1 +\i (00:00:39) setwindow pcb +\i (00:00:39) zoom in 29.5187 45.9376 +\i (00:00:39) trapsize 353 +\i (00:00:39) zoom in 1 +\i (00:00:39) setwindow pcb +\i (00:00:39) zoom in 29.5328 45.9023 +\i (00:00:39) trapsize 177 +\i (00:00:39) zoom out 1 +\i (00:00:39) setwindow pcb +\i (00:00:39) zoom out 29.6671 45.7186 +\i (00:00:39) trapsize 353 +\i (00:00:40) zoom out 1 +\i (00:00:40) setwindow pcb +\i (00:00:40) zoom out 29.9426 44.7011 +\i (00:00:40) trapsize 707 +\i (00:00:41) zoom out 1 +\i (00:00:41) setwindow pcb +\i (00:00:41) zoom out 29.6317 43.7825 +\i (00:00:41) trapsize 1413 +\i (00:00:41) zoom out 1 +\i (00:00:41) setwindow pcb +\i (00:00:41) zoom out 30.3384 42.8214 +\i (00:00:41) trapsize 2827 +\i (00:00:41) zoom out 1 +\i (00:00:41) setwindow pcb +\i (00:00:41) zoom out 30.1688 42.7083 +\i (00:00:41) trapsize 5653 +\i (00:00:41) zoom out 1 +\i (00:00:41) setwindow pcb +\i (00:00:41) zoom out 30.1689 42.7083 +\i (00:00:41) trapsize 11306 +\i (00:00:42) zoom out 1 +\i (00:00:42) setwindow pcb +\i (00:00:42) zoom out 26.0987 42.4822 +\i (00:00:42) trapsize 22612 +\i (00:00:42) zoom out 1 +\i (00:00:42) setwindow pcb +\i (00:00:42) zoom out 54.2149 41.5778 +\i (00:00:42) trapsize 45224 +\i (00:00:42) zoom out 1 +\i (00:00:42) setwindow pcb +\i (00:00:42) zoom out 208.4298 45.1957 +\i (00:00:42) trapsize 90449 +\i (00:00:42) zoom in 1 +\i (00:00:42) setwindow pcb +\i (00:00:42) zoom in 10.3473 3.5893 +\i (00:00:42) trapsize 45224 +\i (00:00:42) zoom in 1 +\i (00:00:42) setwindow pcb +\i (00:00:42) zoom in 10.3474 3.5893 +\i (00:00:42) trapsize 22612 +\i (00:00:42) zoom in 1 +\i (00:00:42) setwindow pcb +\i (00:00:42) zoom in 10.3474 3.5893 +\i (00:00:42) trapsize 11306 +\i (00:00:43) zoom in 1 +\i (00:00:43) setwindow pcb +\i (00:00:43) zoom in 10.3474 3.5894 +\i (00:00:43) trapsize 5653 +\i (00:00:43) zoom in 1 +\i (00:00:43) setwindow pcb +\i (00:00:43) zoom in 10.3474 3.5894 +\i (00:00:43) trapsize 2827 +\i (00:00:44) zoom out 1 +\i (00:00:44) setwindow pcb +\i (00:00:44) zoom out 12.0434 18.1178 +\i (00:00:44) trapsize 5653 +\i (00:00:44) zoom out 1 +\i (00:00:44) setwindow pcb +\i (00:00:44) zoom out 12.0433 18.0046 +\i (00:00:44) trapsize 11306 +\i (00:00:44) zoom in 1 +\i (00:00:44) setwindow pcb +\i (00:00:44) zoom in 19.0531 53.5058 +\i (00:00:44) trapsize 5653 +\i (00:00:45) zoom in 1 +\i (00:00:45) setwindow pcb +\i (00:00:45) zoom in 27.1935 45.5915 +\i (00:00:45) trapsize 2827 +\i (00:00:45) zoom in 1 +\i (00:00:45) setwindow pcb +\i (00:00:45) zoom in 27.1935 45.5916 +\i (00:00:45) trapsize 1413 +\i (00:00:46) zoom in 1 +\i (00:00:46) setwindow pcb +\i (00:00:46) zoom in 28.7481 45.3089 +\i (00:00:46) trapsize 707 +\i (00:03:01) zoom out 1 +\i (00:03:01) setwindow pcb +\i (00:03:01) zoom out 30.9669 43.6271 +\i (00:03:01) trapsize 1413 +\i (00:03:01) zoom out 1 +\i (00:03:01) setwindow pcb +\i (00:03:01) zoom out 30.6561 43.0619 +\i (00:03:01) trapsize 2827 +\i (00:03:01) zoom out 1 +\i (00:03:01) setwindow pcb +\i (00:03:01) zoom out 30.0342 41.6486 +\i (00:03:01) trapsize 5653 +\i (00:03:02) zoom in 1 +\i (00:03:02) setwindow pcb +\i (00:03:02) zoom in 14.8840 27.2898 +\i (00:03:02) trapsize 2827 +\i (00:03:02) zoom in 1 +\i (00:03:02) setwindow pcb +\i (00:03:02) zoom in 14.8840 27.2898 +\i (00:03:02) trapsize 1413 +\i (00:03:03) zoom out 1 +\i (00:03:03) setwindow pcb +\i (00:03:03) zoom out 15.1667 26.6680 +\i (00:03:03) trapsize 2827 +\i (00:03:03) zoom out 1 +\i (00:03:03) setwindow pcb +\i (00:03:03) zoom out 17.1452 27.7987 +\i (00:03:03) trapsize 5653 +\i (00:03:03) zoom out 1 +\i (00:03:03) setwindow pcb +\i (00:03:03) zoom out 17.1451 27.7986 +\i (00:03:03) trapsize 11306 +\i (00:03:06) zoom in 1 +\i (00:03:06) setwindow pcb +\i (00:03:06) zoom in 15.3362 29.1554 +\i (00:03:06) trapsize 5653 +\i (00:03:06) zoom in 1 +\i (00:03:06) setwindow pcb +\i (00:03:06) zoom in 15.5624 28.4770 +\i (00:03:06) trapsize 2827 +\i (00:03:06) zoom in 1 +\i (00:03:06) setwindow pcb +\i (00:03:06) zoom in 18.1062 28.3075 +\i (00:03:06) trapsize 1413 +\i (00:03:06) zoom in 1 +\i (00:03:06) setwindow pcb +\i (00:03:06) zoom in 18.2476 28.4489 +\i (00:03:06) trapsize 707 +\i (00:03:07) zoom out 1 +\i (00:03:07) setwindow pcb +\i (00:03:07) zoom out 18.9684 29.2686 +\i (00:03:07) trapsize 1413 +\i (00:03:07) zoom out 1 +\i (00:03:07) setwindow pcb +\i (00:03:07) zoom out 18.9684 29.2686 +\i (00:03:07) trapsize 2827 +\i (00:03:07) zoom out 1 +\i (00:03:07) setwindow pcb +\i (00:03:07) zoom out 21.1731 31.2472 +\i (00:03:07) trapsize 5653 +\i (00:03:08) zoom in 1 +\i (00:03:08) setwindow pcb +\i (00:03:08) zoom in 22.6430 41.1966 +\i (00:03:08) trapsize 2827 +\i (00:03:08) zoom in 1 +\i (00:03:08) setwindow pcb +\i (00:03:08) zoom in 24.7347 41.9315 +\i (00:03:08) trapsize 1413 +\i (00:03:09) zoom in 1 +\i (00:03:09) setwindow pcb +\i (00:03:09) zoom in 26.5154 42.4403 +\i (00:03:09) trapsize 707 +\i (00:03:09) zoom in 1 +\i (00:03:09) setwindow pcb +\i (00:03:09) zoom in 28.7202 42.9632 +\i (00:03:09) trapsize 353 +\i (00:03:10) zoom in 1 +\i (00:03:10) setwindow pcb +\i (00:03:10) zoom in 29.7448 42.5816 +\i (00:03:10) trapsize 177 +\i (00:03:10) zoom out 1 +\i (00:03:10) setwindow pcb +\i (00:03:10) zoom out 29.7130 42.5569 +\i (00:03:10) trapsize 353 +\i (00:03:17) zoom in 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom in 29.8615 43.1151 +\i (00:03:17) trapsize 177 +\i (00:03:17) zoom in 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom in 29.8615 43.1151 +\i (00:03:17) trapsize 88 +\i (00:03:17) zoom in 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom in 29.8615 43.1151 +\i (00:03:17) trapsize 44 +\i (00:03:17) zoom in 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom in 29.8615 43.1151 +\i (00:03:17) trapsize 22 +\i (00:03:17) zoom out 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom out 29.8606 43.1151 +\i (00:03:17) trapsize 44 +\i (00:03:17) zoom out 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom out 29.8526 43.1151 +\i (00:03:17) trapsize 88 +\i (00:03:17) zoom out 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom out 29.8525 43.1151 +\i (00:03:17) trapsize 177 +\i (00:03:17) zoom in 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom in 29.8525 43.1151 +\i (00:03:17) trapsize 88 +\i (00:03:17) zoom out 1 +\i (00:03:17) setwindow pcb +\i (00:03:17) zoom out 29.8525 43.1151 +\i (00:03:17) trapsize 177 +\i (00:03:18) zoom out 1 +\i (00:03:18) setwindow pcb +\i (00:03:18) zoom out 29.5840 43.1469 +\i (00:03:18) trapsize 353 +\i (00:03:18) zoom out 1 +\i (00:03:18) setwindow pcb +\i (00:03:18) zoom out 29.5841 43.1468 +\i (00:03:18) trapsize 707 +\i (00:03:19) zoom out 1 +\i (00:03:19) setwindow pcb +\i (00:03:19) zoom out 29.5841 43.1468 +\i (00:03:19) trapsize 1413 +\i (00:03:19) zoom in 1 +\i (00:03:19) setwindow pcb +\i (00:03:19) zoom in 29.0188 48.5735 +\i (00:03:19) trapsize 707 +\i (00:03:20) zoom in 1 +\i (00:03:20) setwindow pcb +\i (00:03:20) zoom in 29.1036 48.5736 +\i (00:03:20) trapsize 353 +\i (00:03:20) zoom out 1 +\i (00:03:20) setwindow pcb +\i (00:03:20) zoom out 29.8244 48.1284 +\i (00:03:20) trapsize 707 +\i (00:03:20) zoom out 1 +\i (00:03:20) setwindow pcb +\i (00:03:20) zoom out 29.8102 48.1002 +\i (00:03:20) trapsize 1413 +\i (00:03:20) zoom out 1 +\i (00:03:20) setwindow pcb +\i (00:03:20) zoom out 29.7820 47.9871 +\i (00:03:20) trapsize 2826 +\i (00:03:20) zoom out 1 +\i (00:03:20) setwindow pcb +\i (00:03:20) zoom out 25.5425 39.1688 +\i (00:03:20) trapsize 5653 +\i (00:03:20) zoom out 1 +\i (00:03:20) setwindow pcb +\i (00:03:20) zoom out 25.5424 38.2643 +\i (00:03:20) trapsize 11306 +\i (00:03:21) zoom in 1 +\i (00:03:21) setwindow pcb +\i (00:03:21) zoom in 14.8648 24.0193 +\i (00:03:21) trapsize 5653 +\i (00:03:21) zoom in 1 +\i (00:03:21) setwindow pcb +\i (00:03:21) zoom in 14.8648 24.0193 +\i (00:03:21) trapsize 2826 +\i (00:03:22) zoom in 1 +\i (00:03:22) setwindow pcb +\i (00:03:22) zoom in 13.3385 30.3504 +\i (00:03:22) trapsize 1413 +\i (00:03:22) zoom in 1 +\i (00:03:22) setwindow pcb +\i (00:03:22) zoom in 13.5929 30.4918 +\i (00:03:22) trapsize 707 +\i (00:03:22) zoom out 1 +\i (00:03:22) setwindow pcb +\i (00:03:22) zoom out 14.0169 31.5235 +\i (00:03:22) trapsize 1413 +\i (00:03:22) zoom out 1 +\i (00:03:22) setwindow pcb +\i (00:03:22) zoom out 14.0170 31.4952 +\i (00:03:22) trapsize 2826 +\i (00:03:22) zoom out 1 +\i (00:03:22) setwindow pcb +\i (00:03:22) zoom out 14.1865 30.8734 +\i (00:03:22) trapsize 5653 +\i (00:03:22) zoom out 1 +\i (00:03:22) setwindow pcb +\i (00:03:22) zoom out 14.4126 30.5342 +\i (00:03:22) trapsize 11306 +\i (00:03:23) zoom in 1 +\i (00:03:23) setwindow pcb +\i (00:03:23) zoom in 19.3871 42.0659 +\i (00:03:23) trapsize 5653 +\i (00:03:23) zoom in 1 +\i (00:03:23) setwindow pcb +\i (00:03:23) zoom in 19.1610 41.3876 +\i (00:03:23) trapsize 2826 +\i (00:03:24) zoom in 1 +\i (00:03:24) setwindow pcb +\i (00:03:24) zoom in 19.7263 38.7308 +\i (00:03:24) trapsize 1413 +\i (00:03:29) zoom out 1 +\i (00:03:29) setwindow pcb +\i (00:03:29) zoom out 30.6079 43.8749 +\i (00:03:29) trapsize 2826 +\i (00:03:29) zoom out 1 +\i (00:03:29) setwindow pcb +\i (00:03:29) zoom out 30.6079 43.8749 +\i (00:03:29) trapsize 5653 +\i (00:03:30) zoom in 1 +\i (00:03:30) setwindow pcb +\i (00:03:30) zoom in 34.5649 51.5628 +\i (00:03:30) trapsize 2826 +\i (00:03:30) zoom in 1 +\i (00:03:30) setwindow pcb +\i (00:03:30) zoom in 34.5649 51.5628 +\i (00:03:30) trapsize 1413 +\i (00:03:31) zoom in 1 +\i (00:03:31) setwindow pcb +\i (00:03:31) zoom in 30.4949 47.8885 +\i (00:03:31) trapsize 707 +\i (00:03:31) zoom in 1 +\i (00:03:31) setwindow pcb +\i (00:03:31) zoom in 30.1699 47.6342 +\i (00:03:31) trapsize 353 +\i (00:03:31) zoom in 1 +\i (00:03:31) setwindow pcb +\i (00:03:31) zoom in 30.1699 47.6342 +\i (00:03:31) trapsize 177 +\i (00:03:32) zoom out 1 +\i (00:03:32) setwindow pcb +\i (00:03:32) zoom out 30.1593 47.5812 +\i (00:03:32) trapsize 353 +\i (00:03:32) zoom out 1 +\i (00:03:32) setwindow pcb +\i (00:03:32) zoom out 30.1523 47.5600 +\i (00:03:32) trapsize 707 +\i (00:04:44) zoom out 1 +\i (00:04:44) setwindow pcb +\i (00:04:44) zoom out 24.4713 44.8467 +\i (00:04:44) trapsize 1413 +\i (00:04:45) zoom out 1 +\i (00:04:45) setwindow pcb +\i (00:04:45) zoom out 24.1886 45.2141 +\i (00:04:45) trapsize 2826 +\i (00:04:45) zoom out 1 +\i (00:04:45) setwindow pcb +\i (00:04:45) zoom out 24.1320 45.2141 +\i (00:04:45) trapsize 5653 +\i (00:04:45) zoom in 1 +\i (00:04:45) setwindow pcb +\i (00:04:45) zoom in 19.3836 42.7268 +\i (00:04:45) trapsize 2826 +\i (00:04:46) zoom in 1 +\i (00:04:46) setwindow pcb +\i (00:04:46) zoom in 19.2706 41.4267 +\i (00:04:46) trapsize 1413 +\i (00:04:46) zoom in 1 +\i (00:04:46) setwindow pcb +\i (00:04:46) zoom in 33.0634 45.1858 +\i (00:04:46) trapsize 707 +\i (00:04:47) zoom in 1 +\i (00:04:47) setwindow pcb +\i (00:04:47) zoom in 33.0634 45.1858 +\i (00:04:47) trapsize 353 +\i (00:04:47) zoom out 1 +\i (00:04:47) setwindow pcb +\i (00:04:47) zoom out 31.0920 45.7581 +\i (00:04:47) trapsize 707 +\i (00:04:47) zoom out 1 +\i (00:04:47) setwindow pcb +\i (00:04:47) zoom out 31.0921 45.7582 +\i (00:04:47) trapsize 1413 +\i (00:04:48) zoom in 1 +\i (00:04:48) setwindow pcb +\i (00:04:48) zoom in 28.0678 48.5563 +\i (00:04:48) trapsize 707 +\i (00:04:48) zoom in 1 +\i (00:04:48) setwindow pcb +\i (00:04:48) zoom in 27.3047 48.6694 +\i (00:04:48) trapsize 353 +\i (00:06:16) zoom out 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom out 29.7001 47.4399 +\i (00:06:16) trapsize 707 +\i (00:06:16) zoom in 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom in 29.7001 47.4400 +\i (00:06:16) trapsize 353 +\i (00:06:16) zoom out 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom out 29.7001 47.4400 +\i (00:06:16) trapsize 707 +\i (00:06:16) zoom out 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom out 29.7001 47.4401 +\i (00:06:16) trapsize 1413 +\i (00:06:16) zoom in 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom in 27.1845 45.6029 +\i (00:06:16) trapsize 707 +\i (00:06:16) zoom in 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom in 27.4248 45.7018 +\i (00:06:16) trapsize 353 +\i (00:06:16) zoom in 1 +\i (00:06:16) setwindow pcb +\i (00:06:16) zoom in 27.5803 45.9774 +\i (00:06:16) trapsize 177 +\i (00:06:17) zoom out 1 +\i (00:06:17) setwindow pcb +\i (00:06:17) zoom out 28.9829 45.9774 +\i (00:06:17) trapsize 353 +\i (00:06:17) zoom out 1 +\i (00:06:17) setwindow pcb +\i (00:06:17) zoom out 28.9971 45.9775 +\i (00:06:17) trapsize 707 +\i (00:06:17) zoom in 1 +\i (00:06:17) setwindow pcb +\i (00:06:17) zoom in 30.6506 47.9277 +\i (00:06:17) trapsize 353 +\i (00:06:17) zoom in 1 +\i (00:06:17) setwindow pcb +\i (00:06:17) zoom in 30.6506 47.9347 +\i (00:06:17) trapsize 177 +\i (00:06:18) zoom out 1 +\i (00:06:18) setwindow pcb +\i (00:06:18) zoom out 30.1596 48.0372 +\i (00:06:18) trapsize 353 +\i (00:07:57) zoom out 1 +\i (00:07:57) setwindow pcb +\i (00:07:57) zoom out 26.9728 45.2108 +\i (00:07:57) trapsize 707 +\i (00:07:57) zoom out 1 +\i (00:07:57) setwindow pcb +\i (00:07:57) zoom out 26.9728 45.2107 +\i (00:07:57) trapsize 1413 +\i (00:11:49) zoom out 1 +\i (00:11:49) setwindow pcb +\i (00:11:49) zoom out 18.6067 51.6549 +\i (00:11:49) trapsize 2826 +\i (00:11:49) zoom in 1 +\i (00:11:49) setwindow pcb +\i (00:11:49) zoom in 18.1544 50.8071 +\i (00:11:49) trapsize 1413 +\i (00:11:49) zoom out 1 +\i (00:11:49) setwindow pcb +\i (00:11:49) zoom out 18.1545 50.8071 +\i (00:11:49) trapsize 2826 +\i (00:11:49) zoom out 1 +\i (00:11:49) setwindow pcb +\i (00:11:49) zoom out 18.0979 50.6375 +\i (00:11:49) trapsize 5653 +\i (00:11:49) zoom out 1 +\i (00:11:49) setwindow pcb +\i (00:11:49) zoom out 17.5326 52.4464 +\i (00:11:49) trapsize 11306 +\i (00:11:50) zoom in 1 +\i (00:11:50) setwindow pcb +\i (00:11:50) zoom in -8.6964 92.6943 +\i (00:11:50) trapsize 5653 +\i (00:11:50) zoom in 1 +\i (00:11:50) setwindow pcb +\i (00:11:50) zoom in -9.4878 93.4857 +\i (00:11:50) trapsize 2826 +\i (00:11:50) zoom in 1 +\i (00:11:50) setwindow pcb +\i (00:11:50) zoom in -9.6008 93.5988 +\i (00:11:50) trapsize 1413 +\i (00:11:50) zoom out 1 +\i (00:11:50) setwindow pcb +\i (00:11:50) zoom out 5.9161 87.2394 +\i (00:11:50) trapsize 2826 +\i (00:11:50) zoom out 1 +\i (00:11:50) setwindow pcb +\i (00:11:50) zoom out 5.9726 86.9002 +\i (00:11:50) trapsize 5653 +\i (00:11:51) zoom in 1 +\i (00:11:51) setwindow pcb +\i (00:11:51) zoom in 13.9996 86.7872 +\i (00:11:51) trapsize 2826 +\i (00:11:51) zoom in 1 +\i (00:11:51) setwindow pcb +\i (00:11:51) zoom in 14.3388 87.6916 +\i (00:11:51) trapsize 1413 +\i (00:11:51) zoom in 1 +\i (00:11:51) setwindow pcb +\i (00:11:51) zoom in 13.7170 87.9178 +\i (00:11:51) trapsize 707 +\i (00:11:51) zoom out 1 +\i (00:11:51) setwindow pcb +\i (00:11:51) zoom out 8.3328 90.2778 +\i (00:11:51) trapsize 1413 +\i (00:11:52) zoom out 1 +\i (00:11:52) setwindow pcb +\i (00:11:52) zoom out 8.3610 90.2496 +\i (00:11:52) trapsize 2826 +\i (00:11:52) zoom in 1 +\i (00:11:52) setwindow pcb +\i (00:11:52) zoom in 8.1914 95.0545 +\i (00:11:52) trapsize 1413 +\i (00:11:52) zoom in 1 +\i (00:11:52) setwindow pcb +\i (00:11:52) zoom in 7.9653 94.7719 +\i (00:11:52) trapsize 707 +\i (00:11:52) zoom in 1 +\i (00:11:52) setwindow pcb +\i (00:11:52) zoom in 8.3893 94.1360 +\i (00:11:52) trapsize 353 +\i (00:11:53) zoom in 1 +\i (00:11:53) setwindow pcb +\i (00:11:53) zoom in 9.9862 92.0304 +\i (00:11:53) trapsize 177 +\i (00:11:53) zoom out 1 +\i (00:11:53) setwindow pcb +\i (00:11:53) zoom out 9.5906 92.0374 +\i (00:11:53) trapsize 353 +\i (00:11:53) zoom out 1 +\i (00:11:53) setwindow pcb +\i (00:11:53) zoom out 9.5905 91.9951 +\i (00:11:53) trapsize 707 +\i (00:11:54) zoom in 1 +\i (00:11:54) setwindow pcb +\i (00:11:54) zoom in 9.8167 89.5220 +\i (00:11:54) trapsize 353 +\i (00:11:54) zoom in 1 +\i (00:11:54) setwindow pcb +\i (00:11:54) zoom in 9.8167 89.4161 +\i (00:11:54) trapsize 177 +\i (00:11:55) zoom out 1 +\i (00:11:55) setwindow pcb +\i (00:11:55) zoom out 9.6436 89.6210 +\i (00:11:55) trapsize 353 +\i (00:12:04) zoom out 1 +\i (00:12:04) setwindow pcb +\i (00:12:04) zoom out 9.8979 91.1402 +\i (00:12:04) trapsize 707 +\i (00:12:04) zoom out 1 +\i (00:12:04) setwindow pcb +\i (00:12:04) zoom out 9.8838 91.1402 +\i (00:12:04) trapsize 1413 +\i (00:12:04) zoom out 1 +\i (00:12:04) setwindow pcb +\i (00:12:04) zoom out 9.8838 91.1401 +\i (00:12:04) trapsize 2826 +\i (00:12:05) zoom out 1 +\i (00:12:05) setwindow pcb +\i (00:12:05) zoom out 10.8448 79.7215 +\i (00:12:05) trapsize 5653 +\i (00:12:05) zoom out 1 +\i (00:12:05) setwindow pcb +\i (00:12:05) zoom out 11.2970 79.4953 +\i (00:12:05) trapsize 11306 +\i (00:12:05) zoom in 1 +\i (00:12:05) setwindow pcb +\i (00:12:05) zoom in 5.8204 58.9192 +\i (00:12:05) trapsize 5653 +\i (00:12:06) zoom in 1 +\i (00:12:06) setwindow pcb +\i (00:12:06) zoom in 3.8984 65.3634 +\i (00:12:06) trapsize 2826 +\i (00:12:06) zoom in 1 +\i (00:12:06) setwindow pcb +\i (00:12:06) zoom in 8.1380 67.3419 +\i (00:12:06) trapsize 1413 +\i (00:12:07) zoom in 1 +\i (00:12:07) setwindow pcb +\i (00:12:07) zoom in 12.1798 69.3769 +\i (00:12:07) trapsize 707 +\i (00:12:07) zoom in 1 +\i (00:12:07) setwindow pcb +\i (00:12:07) zoom in 12.0667 71.0587 +\i (00:12:07) trapsize 353 +\i (00:12:57) zoom out 1 +\i (00:12:57) setwindow pcb +\i (00:12:57) zoom out 10.7948 71.5745 +\i (00:12:57) trapsize 707 +\i (00:12:57) zoom out 1 +\i (00:12:57) setwindow pcb +\i (00:12:57) zoom out 10.3851 71.3060 +\i (00:12:57) trapsize 1413 +\i (00:12:57) zoom out 1 +\i (00:12:57) setwindow pcb +\i (00:12:57) zoom out 9.9611 71.1081 +\i (00:12:57) trapsize 2826 +\i (00:12:58) zoom in 1 +\i (00:12:58) setwindow pcb +\i (00:12:58) zoom in 1.8210 67.8296 +\i (00:12:58) trapsize 1413 +\i (00:12:58) zoom in 1 +\i (00:12:58) setwindow pcb +\i (00:12:58) zoom in 1.7363 67.6600 +\i (00:12:58) trapsize 707 +\i (00:12:58) zoom in 1 +\i (00:12:58) setwindow pcb +\i (00:12:58) zoom in 1.8352 68.8188 +\i (00:12:58) trapsize 353 +\i (00:12:58) zoom in 1 +\i (00:12:58) setwindow pcb +\i (00:12:58) zoom in 1.8352 68.8189 +\i (00:12:58) trapsize 177 +\i (00:12:59) zoom out 1 +\i (00:12:59) setwindow pcb +\i (00:12:59) zoom out 2.6442 69.4195 +\i (00:12:59) trapsize 353 +\i (00:12:59) zoom out 1 +\i (00:12:59) setwindow pcb +\i (00:12:59) zoom out 2.6372 69.4054 +\i (00:12:59) trapsize 707 +\i (00:12:59) zoom out 1 +\i (00:12:59) setwindow pcb +\i (00:12:59) zoom out 3.3015 69.1794 +\i (00:12:59) trapsize 1413 +\i (00:12:59) zoom out 1 +\i (00:12:59) setwindow pcb +\i (00:12:59) zoom out 3.6407 68.3315 +\i (00:12:59) trapsize 2826 +\i (00:12:59) zoom out 1 +\i (00:12:59) setwindow pcb +\i (00:12:59) zoom out 3.6406 68.3315 +\i (00:12:59) trapsize 5653 +\i (00:13:00) zoom out 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom out 16.5290 88.2293 +\i (00:13:00) trapsize 11306 +\i (00:13:00) zoom in 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom in 57.1478 77.3761 +\i (00:13:00) trapsize 5653 +\i (00:13:00) zoom in 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom in 56.5825 75.4541 +\i (00:13:00) trapsize 2826 +\i (00:13:00) zoom in 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom in 56.2998 75.3410 +\i (00:13:00) trapsize 1413 +\i (00:13:00) zoom out 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom out 44.2876 74.6627 +\i (00:13:00) trapsize 2826 +\i (00:13:00) zoom out 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom out 44.2877 74.6628 +\i (00:13:00) trapsize 5653 +\i (00:13:00) zoom out 1 +\i (00:13:00) setwindow pcb +\i (00:13:00) zoom out 44.5137 73.5322 +\i (00:13:00) trapsize 11306 +\i (00:13:01) zoom in 1 +\i (00:13:01) setwindow pcb +\i (00:13:01) zoom in 3.3614 84.6117 +\i (00:13:01) trapsize 5653 +\i (00:13:01) zoom in 1 +\i (00:13:01) setwindow pcb +\i (00:13:01) zoom in 3.2483 84.6117 +\i (00:13:01) trapsize 2826 +\i (00:13:01) zoom in 1 +\i (00:13:01) setwindow pcb +\i (00:13:01) zoom in 3.1918 84.6118 +\i (00:13:01) trapsize 1413 +\i (00:13:02) zoom in 1 +\i (00:13:02) setwindow pcb +\i (00:13:02) zoom in 10.3143 72.6279 +\i (00:13:02) trapsize 707 +\i (00:13:02) zoom in 1 +\i (00:13:02) setwindow pcb +\i (00:13:02) zoom in 10.2720 72.2464 +\i (00:13:02) trapsize 353 +\i (00:13:03) zoom out 1 +\i (00:13:03) setwindow pcb +\i (00:13:03) zoom out 12.8722 72.1263 +\i (00:13:03) trapsize 707 +\i (00:13:06) zoom out 1 +\i (00:13:06) setwindow pcb +\i (00:13:06) zoom out 11.2613 74.0766 +\i (00:13:06) trapsize 1413 +\i (00:13:06) zoom out 1 +\i (00:13:06) setwindow pcb +\i (00:13:06) zoom out 9.4806 73.7374 +\i (00:13:06) trapsize 2826 +\i (00:13:06) zoom out 1 +\i (00:13:06) setwindow pcb +\i (00:13:06) zoom out 8.8589 73.5677 +\i (00:13:06) trapsize 5653 +\i (00:13:06) zoom out 1 +\i (00:13:06) setwindow pcb +\i (00:13:06) zoom out 6.5977 72.7764 +\i (00:13:06) trapsize 11306 +\i (00:13:07) zoom in 1 +\i (00:13:07) setwindow pcb +\i (00:13:07) zoom in -9.7814 89.2825 +\i (00:13:08) trapsize 5653 +\i (00:13:08) zoom in 1 +\i (00:13:08) setwindow pcb +\i (00:13:08) zoom in 9.6642 89.9609 +\i (00:13:08) trapsize 2826 +\i (00:13:08) zoom in 1 +\i (00:13:08) setwindow pcb +\i (00:13:08) zoom in 9.3251 89.2826 +\i (00:13:08) trapsize 1413 +\i (00:13:09) zoom in 1 +\i (00:13:09) setwindow pcb +\i (00:13:09) zoom in 11.5863 92.5895 +\i (00:13:09) trapsize 707 +\i (00:13:11) zoom in 1 +\i (00:13:11) setwindow pcb +\i (00:13:11) zoom in 10.4416 91.7558 +\i (00:13:11) trapsize 353 +\i (00:13:12) zoom out 1 +\i (00:13:12) setwindow pcb +\i (00:13:12) zoom out 9.7703 91.2188 +\i (00:13:12) trapsize 707 +\i (00:13:12) zoom out 1 +\i (00:13:12) setwindow pcb +\i (00:13:12) zoom out 9.7704 91.2188 +\i (00:13:12) trapsize 1413 +\i (00:13:16) zoom out 1 +\i (00:13:16) setwindow pcb +\i (00:13:16) zoom out 20.3411 87.6859 +\i (00:13:16) trapsize 2826 +\i (00:13:16) zoom out 1 +\i (00:13:16) setwindow pcb +\i (00:13:16) zoom out 20.5673 87.4597 +\i (00:13:16) trapsize 5653 +\i (00:13:16) zoom out 1 +\i (00:13:16) setwindow pcb +\i (00:13:16) zoom out 28.8203 77.8499 +\i (00:13:16) trapsize 11306 +\i (00:13:16) zoom out 1 +\i (00:13:16) setwindow pcb +\i (00:13:16) zoom out 108.0229 77.8500 +\i (00:13:16) trapsize 22611 +\i (00:13:17) zoom in 1 +\i (00:13:17) setwindow pcb +\i (00:13:17) zoom in 38.3805 37.1498 +\i (00:13:17) trapsize 11306 +\i (00:13:17) zoom in 1 +\i (00:13:17) setwindow pcb +\i (00:13:17) zoom in 38.1544 37.1498 +\i (00:13:17) trapsize 5653 +\i (00:13:19) zoom out 1 +\i (00:13:19) setwindow pcb +\i (00:13:19) zoom out 40.1895 53.5429 +\i (00:13:19) trapsize 11306 +\i (00:13:20) zoom in 1 +\i (00:13:20) setwindow pcb +\i (00:13:20) zoom in 25.2660 68.4663 +\i (00:13:20) trapsize 5653 +\i (00:13:20) zoom out 1 +\i (00:13:20) setwindow pcb +\i (00:13:20) zoom out 11.0209 73.6669 +\i (00:13:20) trapsize 11306 +\i (00:13:22) zoom in 1 +\i (00:13:22) setwindow pcb +\i (00:13:22) zoom in -14.7559 94.9214 +\i (00:13:22) trapsize 5653 +\i (00:13:22) zoom in 1 +\i (00:13:22) setwindow pcb +\i (00:13:22) zoom in -14.7558 94.8084 +\i (00:13:22) trapsize 2826 +\i (00:13:22) zoom in 1 +\i (00:13:22) setwindow pcb +\i (00:13:22) zoom in -14.7558 94.8084 +\i (00:13:22) trapsize 1413 +\i (00:13:23) zoom in 1 +\i (00:13:23) setwindow pcb +\i (00:13:23) zoom in 14.6670 89.9753 +\i (00:13:23) trapsize 707 +\i (00:13:23) zoom out 1 +\i (00:13:23) setwindow pcb +\i (00:13:23) zoom out 14.5823 89.9753 +\i (00:13:23) trapsize 1413 +\i (00:13:23) zoom out 1 +\i (00:13:23) setwindow pcb +\i (00:13:23) zoom out 14.5822 89.9754 +\i (00:13:23) trapsize 2826 +\i (00:13:24) zoom in 1 +\i (00:13:24) setwindow pcb +\i (00:13:24) zoom in 17.6912 90.6536 +\i (00:13:24) trapsize 1413 +\i (00:13:24) zoom in 1 +\i (00:13:24) setwindow pcb +\i (00:13:24) zoom in 11.4166 91.1058 +\i (00:13:24) trapsize 707 +\i (00:13:25) zoom in 1 +\i (00:13:25) setwindow pcb +\i (00:13:25) zoom in 10.4274 91.1200 +\i (00:13:25) trapsize 353 +\i (00:13:37) zoom out 1 +\i (00:13:37) setwindow pcb +\i (00:13:37) zoom out 7.3042 91.9679 +\i (00:13:37) trapsize 707 +\i (00:13:37) zoom out 1 +\i (00:13:37) setwindow pcb +\i (00:13:37) zoom out 7.3749 91.7842 +\i (00:13:37) trapsize 1413 +\i (00:13:38) zoom in 1 +\i (00:13:38) setwindow pcb +\i (00:13:38) zoom in 15.7128 90.5123 +\i (00:13:38) trapsize 707 +\i (00:13:38) zoom in 1 +\i (00:13:38) setwindow pcb +\i (00:13:38) zoom in 15.4585 90.2862 +\i (00:13:38) trapsize 353 +\i (00:13:39) zoom out 1 +\i (00:13:39) setwindow pcb +\i (00:13:39) zoom out 13.0278 90.6466 +\i (00:13:39) trapsize 707 +\i (00:13:59) zoom in 1 +\i (00:13:59) setwindow pcb +\i (00:13:59) zoom in 18.7796 87.8202 +\i (00:13:59) trapsize 353 +\i (00:13:59) zoom out 1 +\i (00:13:59) setwindow pcb +\i (00:13:59) zoom out 18.7796 87.8202 +\i (00:13:59) trapsize 707 +\i (00:14:18) zoom out 1 +\i (00:14:18) setwindow pcb +\i (00:14:18) zoom out 20.5178 89.3464 +\i (00:14:18) trapsize 1413 +\i (00:14:18) zoom out 1 +\i (00:14:18) setwindow pcb +\i (00:14:18) zoom out 20.5178 89.3464 +\i (00:14:18) trapsize 2826 +\i (00:14:18) zoom out 1 +\i (00:14:18) setwindow pcb +\i (00:14:18) zoom out 20.5178 89.3465 +\i (00:14:18) trapsize 5653 +\i (00:14:18) zoom out 1 +\i (00:14:18) setwindow pcb +\i (00:14:18) zoom out 20.5178 89.3464 +\i (00:14:18) trapsize 11306 +\i (00:14:19) zoom out 1 +\i (00:14:19) setwindow pcb +\i (00:14:19) zoom out 37.0238 62.2130 +\i (00:14:19) trapsize 22611 +\i (00:14:20) zoom in 1 +\i (00:14:20) setwindow pcb +\i (00:14:20) zoom in 17.1260 21.0606 +\i (00:14:20) trapsize 11306 +\i (00:14:20) zoom in 1 +\i (00:14:20) setwindow pcb +\i (00:14:20) zoom in 17.1260 20.8345 +\i (00:14:20) trapsize 5653 +\i (00:14:20) zoom in 1 +\i (00:14:20) setwindow pcb +\i (00:14:20) zoom in 17.1260 20.8346 +\i (00:14:20) trapsize 2826 +\i (00:14:20) zoom in 1 +\i (00:14:20) setwindow pcb +\i (00:14:20) zoom in 19.5002 23.7741 +\i (00:14:20) trapsize 1413 +\i (00:14:21) zoom out 1 +\i (00:14:21) setwindow pcb +\i (00:14:21) zoom out 15.0911 25.2438 +\i (00:14:21) trapsize 2826 +\i (00:14:21) zoom out 1 +\i (00:14:21) setwindow pcb +\i (00:14:21) zoom out 15.0911 25.2438 +\i (00:14:21) trapsize 5653 +\i (00:14:21) zoom out 1 +\i (00:14:21) setwindow pcb +\i (00:14:21) zoom out 15.0910 25.2439 +\i (00:14:21) trapsize 11306 +\i (00:14:22) zoom out 1 +\i (00:14:22) setwindow pcb +\i (00:14:22) zoom out 15.0909 25.2438 +\i (00:14:22) trapsize 22611 +\i (00:14:23) zoom in 1 +\i (00:14:23) setwindow pcb +\i (00:14:23) zoom in -31.0359 19.8172 +\i (00:14:23) trapsize 11306 +\i (00:14:23) zoom in 1 +\i (00:14:23) setwindow pcb +\i (00:14:23) zoom in -31.0359 19.8172 +\i (00:14:23) trapsize 5653 +\i (00:14:26) zoom in 1 +\i (00:14:26) setwindow pcb +\i (00:14:26) zoom in 43.8071 20.6086 +\i (00:14:26) trapsize 2826 +\i (00:14:26) zoom in 1 +\i (00:14:26) setwindow pcb +\i (00:14:26) zoom in 43.8071 20.6086 +\i (00:14:26) trapsize 1413 +\i (00:14:26) zoom in 1 +\i (00:14:26) setwindow pcb +\i (00:14:26) zoom in 37.9847 22.4740 +\i (00:14:26) trapsize 707 +\i (00:14:26) zoom in 1 +\i (00:14:26) setwindow pcb +\i (00:14:26) zoom in 34.7627 23.2371 +\i (00:14:26) trapsize 353 +\i (00:14:27) zoom in 1 +\i (00:14:27) setwindow pcb +\i (00:14:27) zoom in 34.7627 23.2442 +\i (00:14:27) trapsize 177 +\i (00:14:28) zoom out 1 +\i (00:14:28) setwindow pcb +\i (00:14:28) zoom out 35.8756 23.5798 +\i (00:14:28) trapsize 353 +\i (00:14:29) zoom in 1 +\i (00:14:29) setwindow pcb +\i (00:14:29) zoom in 35.9534 24.8518 +\i (00:14:29) trapsize 177 +\i (00:14:34) zoom out 1 +\i (00:14:34) setwindow pcb +\i (00:14:34) zoom out 35.6743 24.3077 +\i (00:14:34) trapsize 353 +\i (00:14:34) zoom out 1 +\i (00:14:34) setwindow pcb +\i (00:14:34) zoom out 35.6955 24.3006 +\i (00:14:34) trapsize 707 +\i (00:14:34) zoom out 1 +\i (00:14:34) setwindow pcb +\i (00:14:34) zoom out 37.2500 23.9333 +\i (00:14:34) trapsize 1413 +\i (00:14:34) zoom out 1 +\i (00:14:34) setwindow pcb +\i (00:14:34) zoom out 37.3630 23.8485 +\i (00:14:34) trapsize 2826 +\i (00:14:34) zoom out 1 +\i (00:14:34) setwindow pcb +\i (00:14:34) zoom out 39.9633 24.0180 +\i (00:14:34) trapsize 5653 +\i (00:14:38) zoom out 1 +\i (00:14:38) setwindow pcb +\i (00:14:38) zoom out 52.9648 39.7328 +\i (00:14:38) trapsize 11306 +\i (00:14:39) zoom in 1 +\i (00:14:39) setwindow pcb +\i (00:14:39) zoom in 60.9917 57.1434 +\i (00:14:39) trapsize 5653 +\i (00:14:40) zoom in 1 +\i (00:14:40) setwindow pcb +\i (00:14:40) zoom in 55.6781 61.3265 +\i (00:14:40) trapsize 2826 +\i (00:14:40) zoom in 1 +\i (00:14:40) setwindow pcb +\i (00:14:40) zoom in 56.1868 61.4395 +\i (00:14:40) trapsize 1413 +\i (00:14:40) zoom in 1 +\i (00:14:40) setwindow pcb +\i (00:14:40) zoom in 58.3914 55.7868 +\i (00:14:40) trapsize 707 +\i (00:14:40) zoom in 1 +\i (00:14:40) setwindow pcb +\i (00:14:40) zoom in 58.4762 56.3662 +\i (00:14:40) trapsize 353 +\i (00:14:41) zoom out 1 +\i (00:14:41) setwindow pcb +\i (00:14:41) zoom out 58.1794 57.2636 +\i (00:14:41) trapsize 707 +\i (00:14:42) zoom out 1 +\i (00:14:42) setwindow pcb +\i (00:14:42) zoom out 57.2327 58.0268 +\i (00:14:42) trapsize 1413 +\i (00:14:42) zoom in 1 +\i (00:14:42) setwindow pcb +\i (00:14:42) zoom in 57.1761 59.5531 +\i (00:14:42) trapsize 707 +\i (00:14:42) zoom in 1 +\i (00:14:42) setwindow pcb +\i (00:14:42) zoom in 57.1761 59.5673 +\i (00:14:42) trapsize 353 +\i (00:14:43) zoom out 1 +\i (00:14:43) setwindow pcb +\i (00:14:43) zoom out 56.7521 58.7901 +\i (00:14:43) trapsize 707 +\i (00:14:43) zoom out 1 +\i (00:14:43) setwindow pcb +\i (00:14:43) zoom out 57.1761 59.3130 +\i (00:14:43) trapsize 1413 +\i (00:14:44) zoom in 1 +\i (00:14:44) setwindow pcb +\i (00:14:44) zoom in 58.1937 59.8217 +\i (00:14:44) trapsize 707 +\i (00:14:45) zoom in 1 +\i (00:14:45) setwindow pcb +\i (00:14:45) zoom in 54.6607 58.3520 +\i (00:14:45) trapsize 353 +\i (00:14:46) zoom out 1 +\i (00:14:46) setwindow pcb +\i (00:14:46) zoom out 56.5756 57.3557 +\i (00:14:46) trapsize 707 +\i (00:14:46) zoom out 1 +\i (00:14:46) setwindow pcb +\i (00:14:46) zoom out 56.5756 57.3558 +\i (00:14:46) trapsize 1413 +\i (00:14:50) zoom in 1 +\i (00:14:50) setwindow pcb +\i (00:14:50) zoom in 57.5365 58.5994 +\i (00:14:50) trapsize 707 +\i (00:14:51) zoom in 1 +\i (00:14:51) setwindow pcb +\i (00:14:51) zoom in 58.4269 57.3275 +\i (00:14:51) trapsize 353 +\i (00:14:52) zoom in 1 +\i (00:14:52) setwindow pcb +\i (00:14:52) zoom in 59.6563 57.2851 +\i (00:14:52) trapsize 177 +\i (00:14:52) zoom out 1 +\i (00:14:52) setwindow pcb +\i (00:14:52) zoom out 59.6387 57.2675 +\i (00:14:52) trapsize 353 +\i (00:15:56) zoom in 1 +\i (00:15:56) setwindow pcb +\i (00:15:56) zoom in 55.7666 57.3381 +\i (00:15:56) trapsize 177 +\i (00:15:57) zoom in 1 +\i (00:15:57) setwindow pcb +\i (00:15:57) zoom in 55.9327 57.4618 +\i (00:15:57) trapsize 88 +\i (00:15:57) zoom out 1 +\i (00:15:57) setwindow pcb +\i (00:15:57) zoom out 55.9945 57.4230 +\i (00:15:57) trapsize 177 +\i (00:15:58) zoom out 1 +\i (00:15:58) setwindow pcb +\i (00:15:58) zoom out 55.9945 57.4229 +\i (00:15:58) trapsize 353 +\i (00:15:58) zoom out 1 +\i (00:15:58) setwindow pcb +\i (00:15:58) zoom out 55.9946 57.4230 +\i (00:15:58) trapsize 707 +\i (00:15:59) zoom in 1 +\i (00:15:59) setwindow pcb +\i (00:15:59) zoom in 55.9946 57.4513 +\i (00:15:59) trapsize 353 +\i (00:15:59) zoom in 1 +\i (00:15:59) setwindow pcb +\i (00:15:59) zoom in 56.0228 57.6068 +\i (00:15:59) trapsize 177 +\i (00:16:00) zoom in 1 +\i (00:16:00) setwindow pcb +\i (00:16:00) zoom in 56.0229 57.6068 +\i (00:16:00) trapsize 88 +\i (00:16:02) zoom out 1 +\i (00:16:02) setwindow pcb +\i (00:16:02) zoom out 56.0229 57.6069 +\i (00:16:02) trapsize 177 +\i (00:16:02) zoom out 1 +\i (00:16:02) setwindow pcb +\i (00:16:02) zoom out 56.0193 57.5962 +\i (00:16:02) trapsize 353 +\i (00:16:03) zoom out 1 +\i (00:16:03) setwindow pcb +\i (00:16:03) zoom out 55.9628 57.5186 +\i (00:16:03) trapsize 707 +\i (00:16:03) zoom out 1 +\i (00:16:03) setwindow pcb +\i (00:16:03) zoom out 55.9064 57.4620 +\i (00:16:03) trapsize 1413 +\i (00:16:03) zoom out 1 +\i (00:16:03) setwindow pcb +\i (00:16:03) zoom out 55.1150 54.0421 +\i (00:16:03) trapsize 2826 +\i (00:16:04) zoom out 1 +\i (00:16:04) setwindow pcb +\i (00:16:04) zoom out 50.5927 52.9680 +\i (00:16:04) trapsize 5653 +\i (00:16:04) zoom out 1 +\i (00:16:04) setwindow pcb +\i (00:16:04) zoom out 50.1404 52.5158 +\i (00:16:04) trapsize 11306 +\i (00:16:04) zoom out 1 +\i (00:16:04) setwindow pcb +\i (00:16:04) zoom out 28.2075 28.7740 +\i (00:16:04) trapsize 22611 +\i (00:16:04) zoom out 1 +\i (00:16:04) setwindow pcb +\i (00:16:04) zoom out 22.7809 22.8951 +\i (00:16:04) trapsize 45222 +\i (00:16:04) zoom out 1 +\i (00:16:04) setwindow pcb +\i (00:16:04) zoom out 108.9274 10.2329 +\i (00:16:04) trapsize 90445 +\i (00:16:05) zoom in 1 +\i (00:16:05) setwindow pcb +\i (00:16:05) zoom in -18.5997 28.4315 +\i (00:16:05) trapsize 45222 +\i (00:16:05) zoom in 1 +\i (00:16:05) setwindow pcb +\i (00:16:05) zoom in -18.5997 28.4316 +\i (00:16:05) trapsize 22611 +\i (00:16:05) zoom in 1 +\i (00:16:05) setwindow pcb +\i (00:16:05) zoom in -18.5996 28.4316 +\i (00:16:05) trapsize 11306 +\i (00:16:05) zoom in 1 +\i (00:16:05) setwindow pcb +\i (00:16:05) zoom in -18.5996 28.4316 +\i (00:16:05) trapsize 5653 +\i (00:16:05) zoom in 1 +\i (00:16:05) setwindow pcb +\i (00:16:05) zoom in 7.9686 29.4492 +\i (00:16:05) trapsize 2826 +\i (00:16:06) zoom out 1 +\i (00:16:06) setwindow pcb +\i (00:16:06) zoom out 13.8475 31.0320 +\i (00:16:06) trapsize 5653 +\i (00:16:07) drag_start grid 36.0065 29.7884 +\i (00:16:07) drag_stop 32.3887 39.7373 +\i (00:16:08) drag_start grid -18.0342 33.1801 +\i (00:16:08) drag_stop -15.9992 47.0860 +\i (00:16:09) zoom in 1 +\i (00:16:09) setwindow pcb +\i (00:16:09) zoom in 35.1021 20.4048 +\i (00:16:09) trapsize 2826 +\i (00:16:09) zoom in 1 +\i (00:16:09) setwindow pcb +\i (00:16:09) zoom in 35.1021 20.4048 +\i (00:16:09) trapsize 1413 +\i (00:16:09) zoom in 1 +\i (00:16:09) setwindow pcb +\i (00:16:09) zoom in 35.1304 20.8005 +\i (00:16:09) trapsize 707 +\i (00:16:09) zoom in 1 +\i (00:16:09) setwindow pcb +\i (00:16:09) zoom in 35.2717 21.7756 +\i (00:16:09) trapsize 353 +\i (00:16:09) zoom in 1 +\i (00:16:09) setwindow pcb +\i (00:16:09) zoom in 35.2718 21.7756 +\i (00:16:09) trapsize 177 +\i (00:16:10) zoom out 1 +\i (00:16:10) setwindow pcb +\i (00:16:10) zoom out 35.3107 22.9026 +\i (00:16:10) trapsize 353 +\i (00:16:10) zoom out 1 +\i (00:16:10) setwindow pcb +\i (00:16:10) zoom out 35.2895 22.8956 +\i (00:16:10) trapsize 707 +\i (00:16:10) zoom in 1 +\i (00:16:10) setwindow pcb +\i (00:16:10) zoom in 37.1408 24.2805 +\i (00:16:10) trapsize 353 +\i (00:16:10) zoom in 1 +\i (00:16:10) setwindow pcb +\i (00:16:10) zoom in 37.1408 24.2805 +\i (00:16:10) trapsize 177 +\i (00:16:11) zoom in 1 +\i (00:16:11) setwindow pcb +\i (00:16:11) zoom in 36.2081 23.8848 +\i (00:16:11) trapsize 88 +\i (00:16:13) zoom out 1 +\i (00:16:13) setwindow pcb +\i (00:16:13) zoom out 36.3495 23.8407 +\i (00:16:13) trapsize 177 +\i (00:16:15) zoom out 1 +\i (00:16:15) setwindow pcb +\i (00:16:15) zoom out 36.3353 23.7559 +\i (00:16:15) trapsize 353 +\i (00:16:15) zoom out 1 +\i (00:16:15) setwindow pcb +\i (00:16:15) zoom out 36.3353 23.7559 +\i (00:16:15) trapsize 707 +\i (00:16:15) zoom out 1 +\i (00:16:15) setwindow pcb +\i (00:16:15) zoom out 35.9679 24.7876 +\i (00:16:15) trapsize 1413 +\i (00:16:16) zoom out 1 +\i (00:16:16) setwindow pcb +\i (00:16:16) zoom out 33.8199 27.5009 +\i (00:16:16) trapsize 2826 +\i (00:16:17) zoom in 1 +\i (00:16:17) setwindow pcb +\i (00:16:17) zoom in 34.9505 36.9411 +\i (00:16:17) trapsize 1413 +\i (00:16:17) zoom in 1 +\i (00:16:17) setwindow pcb +\i (00:16:17) zoom in 35.1766 36.7715 +\i (00:16:17) trapsize 707 +\i (00:16:17) zoom out 1 +\i (00:16:17) setwindow pcb +\i (00:16:17) zoom out 35.3038 32.6026 +\i (00:16:17) trapsize 1413 +\i (00:16:17) zoom out 1 +\i (00:16:17) setwindow pcb +\i (00:16:17) zoom out 35.3038 32.5178 +\i (00:16:17) trapsize 2826 +\i (00:16:18) zoom in 1 +\i (00:16:18) setwindow pcb +\i (00:16:18) zoom in 34.0037 22.1731 +\i (00:16:18) trapsize 1413 +\i (00:16:18) zoom in 1 +\i (00:16:18) setwindow pcb +\i (00:16:18) zoom in 34.2864 23.6994 +\i (00:16:18) trapsize 707 +\i (00:16:19) zoom in 1 +\i (00:16:19) setwindow pcb +\i (00:16:19) zoom in 34.3429 23.9256 +\i (00:16:19) trapsize 353 +\i (00:16:19) zoom out 1 +\i (00:16:19) setwindow pcb +\i (00:16:19) zoom out 31.8557 23.7631 +\i (00:16:19) trapsize 707 +\i (00:16:20) zoom out 1 +\i (00:16:20) setwindow pcb +\i (00:16:20) zoom out 31.9829 25.1621 +\i (00:16:20) trapsize 1413 +\i (00:16:20) zoom out 1 +\i (00:16:20) setwindow pcb +\i (00:16:20) zoom out 32.3787 25.3034 +\i (00:16:20) trapsize 2826 +\i (00:16:27) zoom out 1 +\i (00:16:27) setwindow pcb +\i (00:16:27) zoom out 40.4056 12.6977 +\i (00:16:27) trapsize 5653 +\i (00:16:27) zoom out 1 +\i (00:16:27) setwindow pcb +\i (00:16:27) zoom out 51.1460 15.9763 +\i (00:16:27) trapsize 11306 +\i (00:16:28) zoom in 1 +\i (00:16:28) setwindow pcb +\i (00:16:28) zoom in 19.9424 21.8553 +\i (00:16:28) trapsize 5653 +\i (00:16:28) zoom in 1 +\i (00:16:28) setwindow pcb +\i (00:16:28) zoom in 24.6908 21.8553 +\i (00:16:28) trapsize 2826 +\i (00:16:29) zoom in 1 +\i (00:16:29) setwindow pcb +\i (00:16:29) zoom in 29.9479 17.0505 +\i (00:16:29) trapsize 1413 +\i (00:16:30) zoom in 1 +\i (00:16:30) setwindow pcb +\i (00:16:30) zoom in 25.9910 17.3614 +\i (00:16:30) trapsize 707 +\i (00:16:30) zoom in 1 +\i (00:16:30) setwindow pcb +\i (00:16:30) zoom in 26.4997 17.6441 +\i (00:16:30) trapsize 353 +\i (00:16:31) zoom out 1 +\i (00:16:31) setwindow pcb +\i (00:16:31) zoom out 25.3692 18.3012 +\i (00:16:31) trapsize 707 +\i (00:16:32) zoom out 1 +\i (00:16:32) setwindow pcb +\i (00:16:32) zoom out 27.5737 18.0045 +\i (00:16:32) trapsize 1413 +\i (00:16:33) zoom out 1 +\i (00:16:33) setwindow pcb +\i (00:16:33) zoom out 34.6680 17.9196 +\i (00:16:33) trapsize 2826 +\i (00:16:33) zoom out 1 +\i (00:16:33) setwindow pcb +\i (00:16:33) zoom out 34.6680 17.9197 +\i (00:16:33) trapsize 5653 +\i (00:16:34) zoom in 1 +\i (00:16:34) setwindow pcb +\i (00:16:34) zoom in 19.2924 23.1202 +\i (00:16:34) trapsize 2826 +\i (00:16:35) zoom in 1 +\i (00:16:35) setwindow pcb +\i (00:16:35) zoom in 19.2924 23.1768 +\i (00:16:35) trapsize 1413 +\i (00:16:35) zoom out 1 +\i (00:16:35) setwindow pcb +\i (00:16:35) zoom out 20.4230 23.3747 +\i (00:16:35) trapsize 2826 +\i (00:16:36) zoom in 1 +\i (00:16:36) setwindow pcb +\i (00:16:36) zoom in 36.9291 24.2791 +\i (00:16:36) trapsize 1413 +\i (00:16:36) zoom in 1 +\i (00:16:36) setwindow pcb +\i (00:16:36) zoom in 36.9291 24.2508 +\i (00:16:36) trapsize 707 +\i (00:16:47) zoom out 1 +\i (00:16:47) setwindow pcb +\i (00:16:47) zoom out 37.7205 24.2650 +\i (00:16:47) trapsize 1413 +\i (00:16:48) zoom out 1 +\i (00:16:48) setwindow pcb +\i (00:16:48) zoom out 37.4096 25.5369 +\i (00:16:48) trapsize 2826 +\i (00:16:53) zoom in 1 +\i (00:16:53) setwindow pcb +\i (00:16:53) zoom in 34.6963 23.6715 +\i (00:16:53) trapsize 1413 +\i (00:16:53) zoom in 1 +\i (00:16:53) setwindow pcb +\i (00:16:53) zoom in 34.6963 23.6715 +\i (00:16:53) trapsize 707 +\i (00:16:54) zoom in 1 +\i (00:16:54) setwindow pcb +\i (00:16:54) zoom in 34.8659 23.6856 +\i (00:16:54) trapsize 353 +\i (00:18:46) zoom out 1 +\i (00:18:46) setwindow pcb +\i (00:18:46) zoom out 33.3609 23.1274 +\i (00:18:46) trapsize 707 +\i (00:18:46) zoom out 1 +\i (00:18:46) setwindow pcb +\i (00:18:46) zoom out 33.2337 23.1133 +\i (00:18:46) trapsize 1413 +\i (00:18:46) zoom out 1 +\i (00:18:46) setwindow pcb +\i (00:18:46) zoom out 33.0359 23.0002 +\i (00:18:46) trapsize 2826 +\i (00:18:47) zoom out 1 +\i (00:18:47) setwindow pcb +\i (00:18:47) zoom out 32.8662 24.0176 +\i (00:18:47) trapsize 5653 +\i (00:18:47) zoom out 1 +\i (00:18:47) setwindow pcb +\i (00:18:47) zoom out 32.8663 24.0177 +\i (00:18:47) trapsize 11306 +\i (00:18:47) zoom out 1 +\i (00:18:47) setwindow pcb +\i (00:18:47) zoom out 32.8662 24.0177 +\i (00:18:47) trapsize 22611 +\i (00:18:47) zoom in 1 +\i (00:18:47) setwindow pcb +\i (00:18:47) zoom in -37.5931 84.6156 +\i (00:18:47) trapsize 11306 +\i (00:18:47) zoom in 1 +\i (00:18:47) setwindow pcb +\i (00:18:47) zoom in -14.0774 83.2590 +\i (00:18:47) trapsize 5653 +\i (00:18:47) zoom in 1 +\i (00:18:47) setwindow pcb +\i (00:18:47) zoom in -5.5982 83.4851 +\i (00:18:47) trapsize 2826 +\i (00:18:48) zoom in 1 +\i (00:18:48) setwindow pcb +\i (00:18:48) zoom in 32.5017 85.5202 +\i (00:18:48) trapsize 1413 +\i (00:18:48) zoom in 1 +\i (00:18:48) setwindow pcb +\i (00:18:48) zoom in 26.2836 85.1811 +\i (00:18:48) trapsize 707 +\i (00:18:48) zoom out 1 +\i (00:18:48) setwindow pcb +\i (00:18:48) zoom out 22.3408 86.5661 +\i (00:18:48) trapsize 1413 +\i (00:18:49) zoom out 1 +\i (00:18:49) setwindow pcb +\i (00:18:49) zoom out 20.1928 87.4988 +\i (00:18:49) trapsize 2826 +\i (00:18:49) zoom out 1 +\i (00:18:49) setwindow pcb +\i (00:18:49) zoom out 20.1928 87.4987 +\i (00:18:49) trapsize 5653 +\i (00:18:50) zoom in 1 +\i (00:18:50) setwindow pcb +\i (00:18:50) zoom in 16.8012 94.9604 +\i (00:18:50) trapsize 2826 +\i (00:18:50) zoom in 1 +\i (00:18:50) setwindow pcb +\i (00:18:50) zoom in 16.8012 94.9604 +\i (00:18:50) trapsize 1413 +\i (00:18:51) zoom in 1 +\i (00:18:51) setwindow pcb +\i (00:18:51) zoom in 18.1296 92.1906 +\i (00:18:51) trapsize 707 +\i (00:18:51) zoom in 1 +\i (00:18:51) setwindow pcb +\i (00:18:51) zoom in 18.1014 92.1623 +\i (00:18:51) trapsize 353 +\i (00:18:52) zoom out 1 +\i (00:18:52) setwindow pcb +\i (00:18:52) zoom out 19.1260 91.2367 +\i (00:18:52) trapsize 707 +\i (00:28:15) zoom out 1 +\i (00:28:15) setwindow pcb +\i (00:28:15) zoom out 20.6240 92.1411 +\i (00:28:15) trapsize 1413 +\i (00:28:15) zoom out 1 +\i (00:28:15) setwindow pcb +\i (00:28:15) zoom out 20.1718 91.5193 +\i (00:28:15) trapsize 2826 +\i (00:28:15) zoom out 1 +\i (00:28:15) setwindow pcb +\i (00:28:15) zoom out 23.4504 89.6538 +\i (00:28:15) trapsize 5653 +\i (00:28:15) zoom out 1 +\i (00:28:15) setwindow pcb +\i (00:28:15) zoom out 23.5635 88.9756 +\i (00:28:15) trapsize 11306 +\i (00:28:16) zoom in 1 +\i (00:28:16) setwindow pcb +\i (00:28:16) zoom in 60.3133 82.1922 +\i (00:28:16) trapsize 5653 +\i (00:28:17) zoom in 1 +\i (00:28:17) setwindow pcb +\i (00:28:17) zoom in 8.9860 87.2797 +\i (00:28:17) trapsize 2826 +\i (00:28:17) zoom in 1 +\i (00:28:17) setwindow pcb +\i (00:28:17) zoom in 7.3467 86.9405 +\i (00:28:17) trapsize 1413 +\i (00:28:17) zoom in 1 +\i (00:28:17) setwindow pcb +\i (00:28:17) zoom in 7.3468 87.0254 +\i (00:28:17) trapsize 707 +\i (00:28:19) zoom out 1 +\i (00:28:19) setwindow pcb +\i (00:28:19) zoom out 7.5164 87.1526 +\i (00:28:19) trapsize 1413 +\i (00:28:20) zoom in 1 +\i (00:28:20) setwindow pcb +\i (00:28:20) zoom in 13.3105 90.0638 +\i (00:28:20) trapsize 707 +\i (00:28:21) zoom in 1 +\i (00:28:21) setwindow pcb +\i (00:28:21) zoom in 15.6989 89.1028 +\i (00:28:21) trapsize 353 +\i (00:28:23) zoom out 1 +\i (00:28:23) setwindow pcb +\i (00:28:23) zoom out 15.5505 88.9545 +\i (00:28:23) trapsize 707 +\i (00:28:23) zoom in 1 +\i (00:28:23) setwindow pcb +\i (00:28:23) zoom in 15.5505 88.9545 +\i (00:28:23) trapsize 353 +\i (00:28:24) zoom in 1 +\i (00:28:24) setwindow pcb +\i (00:28:24) zoom in 15.5505 88.9545 +\i (00:28:24) trapsize 177 +\i (00:28:25) zoom out 1 +\i (00:28:25) setwindow pcb +\i (00:28:25) zoom out 15.5505 88.9545 +\i (00:28:25) trapsize 353 +\i (00:28:26) zoom in 1 +\i (00:28:26) setwindow pcb +\i (00:28:26) zoom in 13.1763 89.3573 +\i (00:28:26) trapsize 177 +\i (00:28:26) zoom out 1 +\i (00:28:26) setwindow pcb +\i (00:28:26) zoom out 12.7170 89.4456 +\i (00:28:26) trapsize 353 +\i (00:28:26) zoom in 1 +\i (00:28:26) setwindow pcb +\i (00:28:26) zoom in 12.7170 89.4457 +\i (00:28:26) trapsize 177 +\i (00:28:26) zoom in 1 +\i (00:28:26) setwindow pcb +\i (00:28:26) zoom in 12.7170 89.4457 +\i (00:28:26) trapsize 88 +\i (00:28:27) zoom out 1 +\i (00:28:27) setwindow pcb +\i (00:28:27) zoom out 12.7170 89.4457 +\i (00:28:27) trapsize 177 +\i (00:28:28) zoom in 1 +\i (00:28:28) setwindow pcb +\i (00:28:28) zoom in 12.7170 89.4457 +\i (00:28:28) trapsize 88 +\i (00:28:28) zoom out 1 +\i (00:28:28) setwindow pcb +\i (00:28:28) zoom out 12.7170 89.4457 +\i (00:28:28) trapsize 177 +\i (00:28:28) zoom out 1 +\i (00:28:28) setwindow pcb +\i (00:28:28) zoom out 12.7170 89.4422 +\i (00:28:28) trapsize 353 +\i (00:28:29) zoom out 1 +\i (00:28:29) setwindow pcb +\i (00:28:29) zoom out 12.7170 89.4069 +\i (00:28:29) trapsize 707 +\i (00:28:29) zoom out 1 +\i (00:28:29) setwindow pcb +\i (00:28:29) zoom out 12.6747 89.0960 +\i (00:28:29) trapsize 1413 +\i (00:28:29) zoom out 1 +\i (00:28:29) setwindow pcb +\i (00:28:29) zoom out 12.5334 88.5307 +\i (00:28:29) trapsize 2826 +\i (00:28:29) zoom out 1 +\i (00:28:29) setwindow pcb +\i (00:28:29) zoom out 12.5334 87.2871 +\i (00:28:29) trapsize 5653 +\i (00:28:29) zoom out 1 +\i (00:28:29) setwindow pcb +\i (00:28:29) zoom out 18.6384 82.4257 +\i (00:28:29) trapsize 11306 +\i (00:28:29) zoom out 1 +\i (00:28:29) setwindow pcb +\i (00:28:29) zoom out 19.5427 74.9640 +\i (00:28:29) trapsize 22611 +\i (00:28:30) zoom in 1 +\i (00:28:30) setwindow pcb +\i (00:28:30) zoom in -8.9474 10.7482 +\i (00:28:30) trapsize 11306 +\i (00:28:30) zoom in 1 +\i (00:28:30) setwindow pcb +\i (00:28:30) zoom in -8.9474 10.7482 +\i (00:28:30) trapsize 5653 +\i (00:36:11) zoom in 1 +\i (00:36:11) setwindow pcb +\i (00:36:11) zoom in 5.1846 56.7619 +\i (00:36:11) trapsize 2826 +\i (00:36:11) zoom in 1 +\i (00:36:11) setwindow pcb +\i (00:36:11) zoom in 5.1846 56.7620 +\i (00:36:11) trapsize 1413 +\i (00:36:11) zoom in 1 +\i (00:36:11) setwindow pcb +\i (00:36:11) zoom in 5.1847 56.7620 +\i (00:36:11) trapsize 707 +\i (00:36:11) zoom in 1 +\i (00:36:11) setwindow pcb +\i (00:36:11) zoom in 10.1733 56.5641 +\i (00:36:11) trapsize 353 +\i (00:38:18) zoom out 1 +\i (00:38:18) setwindow pcb +\i (00:38:18) zoom out 9.2759 56.6489 +\i (00:38:18) trapsize 707 +\i (00:38:18) zoom out 1 +\i (00:38:18) setwindow pcb +\i (00:38:18) zoom out 9.2619 56.5359 +\i (00:38:18) trapsize 1413 +\i (00:38:18) zoom out 1 +\i (00:38:18) setwindow pcb +\i (00:38:18) zoom out 9.1488 56.3945 +\i (00:38:18) trapsize 2826 +\i (00:38:18) zoom out 1 +\i (00:38:18) setwindow pcb +\i (00:38:18) zoom out 9.1488 56.2815 +\i (00:38:18) trapsize 5653 +\i (00:38:18) zoom out 1 +\i (00:38:18) setwindow pcb +\i (00:38:18) zoom out 9.1488 56.2815 +\i (00:38:18) trapsize 11306 +\i (00:38:19) zoom out 1 +\i (00:38:19) setwindow pcb +\i (00:38:19) zoom out 14.5754 75.0488 +\i (00:38:19) trapsize 22611 +\i (00:38:19) zoom out 1 +\i (00:38:19) setwindow pcb +\i (00:38:19) zoom out 116.1630 183.0921 +\i (00:38:19) trapsize 45222 +\i (00:38:19) zoom out 1 +\i (00:38:19) setwindow pcb +\i (00:38:19) zoom out 336.8482 449.9041 +\i (00:38:19) trapsize 90445 +\i (00:38:20) zoom in 1 +\i (00:38:20) setwindow pcb +\i (00:38:20) zoom in -31.2620 98.9785 +\i (00:38:20) trapsize 45222 +\i (00:38:21) zoom in 1 +\i (00:38:21) setwindow pcb +\i (00:38:21) zoom in -33.0709 67.3229 +\i (00:38:21) trapsize 22611 +\i (00:38:21) zoom in 1 +\i (00:38:21) setwindow pcb +\i (00:38:21) zoom in -33.0708 67.3229 +\i (00:38:21) trapsize 11306 +\i (00:38:22) zoom in 1 +\i (00:38:22) setwindow pcb +\i (00:38:22) zoom in 27.7533 89.2557 +\i (00:38:22) trapsize 5653 +\i (00:38:22) zoom in 1 +\i (00:38:22) setwindow pcb +\i (00:38:22) zoom in 25.9445 88.6904 +\i (00:38:22) trapsize 2826 +\i (00:38:22) zoom out 1 +\i (00:38:22) setwindow pcb +\i (00:38:22) zoom out 25.4923 87.6730 +\i (00:38:22) trapsize 5653 +\i (00:38:23) zoom in 1 +\i (00:38:23) setwindow pcb +\i (00:38:23) zoom in 47.6512 90.7254 +\i (00:38:23) trapsize 2826 +\i (00:38:23) zoom in 1 +\i (00:38:23) setwindow pcb +\i (00:38:23) zoom in 51.5517 91.8560 +\i (00:38:23) trapsize 1413 +\i (00:38:23) zoom in 1 +\i (00:38:23) setwindow pcb +\i (00:38:23) zoom in 51.5517 91.8843 +\i (00:38:23) trapsize 707 +\i (00:38:24) zoom out 1 +\i (00:38:24) setwindow pcb +\i (00:38:24) zoom out 50.3082 91.2908 +\i (00:38:24) trapsize 1413 +\i (00:38:24) zoom out 1 +\i (00:38:24) setwindow pcb +\i (00:38:24) zoom out 50.2233 91.1777 +\i (00:38:24) trapsize 2826 +\i (00:38:25) zoom out 1 +\i (00:38:25) setwindow pcb +\i (00:38:25) zoom out 31.5126 88.5773 +\i (00:38:25) trapsize 5653 +\i (00:38:25) zoom out 1 +\i (00:38:25) setwindow pcb +\i (00:38:25) zoom out 20.4331 90.7254 +\i (00:38:25) trapsize 11306 +\i (00:38:26) zoom out 1 +\i (00:38:26) setwindow pcb +\i (00:38:26) zoom out 20.4329 88.0121 +\i (00:38:26) trapsize 22611 +\i (00:38:26) zoom out 1 +\i (00:38:26) setwindow pcb +\i (00:38:26) zoom out 20.4329 87.5599 +\i (00:38:26) trapsize 45222 +\i (00:38:26) zoom out 1 +\i (00:38:26) setwindow pcb +\i (00:38:26) zoom out 20.4329 87.5600 +\i (00:38:26) trapsize 90445 +\i (00:40:54) zoom in 1 +\i (00:40:54) setwindow pcb +\i (00:40:54) zoom in -15.7449 26.6227 +\i (00:40:54) trapsize 45222 +\i (00:40:54) zoom in 1 +\i (00:40:54) setwindow pcb +\i (00:40:54) zoom in -14.8405 26.6227 +\i (00:40:54) trapsize 22611 +\i (00:40:54) zoom in 1 +\i (00:40:54) setwindow pcb +\i (00:40:54) zoom in -14.3882 26.6228 +\i (00:40:54) trapsize 11306 +\i (00:40:54) zoom in 1 +\i (00:40:54) setwindow pcb +\i (00:40:54) zoom in -1.0476 49.0079 +\i (00:40:54) trapsize 5653 +\i (00:40:54) zoom in 1 +\i (00:40:54) setwindow pcb +\i (00:40:54) zoom in -1.0476 49.0079 +\i (00:40:54) trapsize 2826 +\i (00:40:55) zoom in 1 +\i (00:40:55) setwindow pcb +\i (00:40:55) zoom in 3.2486 55.5086 +\i (00:40:55) trapsize 1413 +\i (00:40:55) zoom out 1 +\i (00:40:55) setwindow pcb +\i (00:40:55) zoom out 5.3119 60.3983 +\i (00:40:55) trapsize 2826 +\i (00:40:55) zoom out 1 +\i (00:40:55) setwindow pcb +\i (00:40:55) zoom out 5.3119 60.3984 +\i (00:40:55) trapsize 5653 +\i (00:41:09) zoom in 1 +\i (00:41:09) setwindow pcb +\i (00:41:09) zoom in 11.1909 65.1467 +\i (00:41:09) trapsize 2826 +\i (00:41:10) zoom in 1 +\i (00:41:10) setwindow pcb +\i (00:41:10) zoom in 11.1909 65.1467 +\i (00:41:10) trapsize 1413 +\i (00:41:10) zoom in 1 +\i (00:41:10) setwindow pcb +\i (00:41:10) zoom in 9.4668 65.0619 +\i (00:41:10) trapsize 707 +\i (00:41:11) zoom in 1 +\i (00:41:11) setwindow pcb +\i (00:41:11) zoom in 9.2407 65.7686 +\i (00:41:11) trapsize 353 +\i (00:41:11) zoom in 1 +\i (00:41:11) setwindow pcb +\i (00:41:11) zoom in 11.2050 65.3093 +\i (00:41:11) trapsize 177 +\i (00:41:12) zoom out 1 +\i (00:41:12) setwindow pcb +\i (00:41:12) zoom out 11.3923 65.2528 +\i (00:41:12) trapsize 353 +\i (00:41:14) drag_start grid 10.7352 65.2246 +\i (00:41:14) drag_stop 10.9754 65.2246 +\i (00:41:14) zoom out 1 +\i (00:41:14) setwindow pcb +\i (00:41:14) zoom out 11.1521 65.1398 +\i (00:41:14) trapsize 707 +\i (00:41:15) zoom in 1 +\i (00:41:15) setwindow pcb +\i (00:41:15) zoom in 5.8809 65.5638 +\i (00:41:15) trapsize 353 +\i (00:41:16) zoom in 1 +\i (00:41:16) setwindow pcb +\i (00:41:16) zoom in 9.8166 65.4719 +\i (00:41:16) trapsize 177 +\i (00:43:11) zoom out 1 +\i (00:43:11) setwindow pcb +\i (00:43:11) zoom out 9.3291 65.8005 +\i (00:43:11) trapsize 353 +\i (00:52:31) zoom out 1 +\i (00:52:31) setwindow pcb +\i (00:52:31) zoom out 9.2655 65.2070 +\i (00:52:31) trapsize 707 +\i (00:52:31) zoom out 1 +\i (00:52:31) setwindow pcb +\i (00:52:31) zoom out 9.1525 65.0940 +\i (00:52:31) trapsize 1413 +\i (00:52:31) zoom in 1 +\i (00:52:31) setwindow pcb +\i (00:52:31) zoom in 8.4177 64.5571 +\i (00:52:31) trapsize 707 +\i (00:52:31) zoom out 1 +\i (00:52:31) setwindow pcb +\i (00:52:31) zoom out 8.3612 64.5147 +\i (00:52:31) trapsize 1413 +\i (00:52:32) zoom out 1 +\i (00:52:32) setwindow pcb +\i (00:52:32) zoom out 8.1068 62.5646 +\i (00:52:32) trapsize 2826 +\i (00:52:32) zoom out 1 +\i (00:52:32) setwindow pcb +\i (00:52:32) zoom out 9.4069 62.4515 +\i (00:52:32) trapsize 5653 +\i (00:52:32) zoom out 1 +\i (00:52:32) setwindow pcb +\i (00:52:32) zoom out 9.8591 63.0167 +\i (00:52:32) trapsize 11306 +\i (00:52:32) zoom out 1 +\i (00:52:32) setwindow pcb +\i (00:52:32) zoom out 25.7182 70.0262 +\i (00:52:32) trapsize 22611 +\i (00:52:32) zoom in 1 +\i (00:52:32) setwindow pcb +\i (00:52:32) zoom in 154.1498 87.2206 +\i (00:52:32) trapsize 11306 +\i (00:52:33) zoom out 1 +\i (00:52:33) setwindow pcb +\i (00:52:33) zoom out 38.3805 78.8545 +\i (00:52:33) trapsize 22611 +\i (00:52:33) zoom out 1 +\i (00:52:33) setwindow pcb +\i (00:52:33) zoom out 38.3805 77.4978 +\i (00:52:33) trapsize 45222 +\i (00:52:33) zoom out 1 +\i (00:52:33) setwindow pcb +\i (00:52:33) zoom out 38.3805 238.2634 +\i (00:52:33) trapsize 90445 +\i (00:52:34) zoom in 1 +\i (00:52:34) setwindow pcb +\i (00:52:34) zoom in -24.9309 60.9917 +\i (00:52:34) trapsize 45222 +\i (00:52:34) zoom in 1 +\i (00:52:34) setwindow pcb +\i (00:52:34) zoom in -14.9820 107.1186 +\i (00:52:34) trapsize 22611 +\i (00:52:34) zoom in 1 +\i (00:52:34) setwindow pcb +\i (00:52:34) zoom in -15.8864 102.1442 +\i (00:52:34) trapsize 11306 +\i (00:52:34) zoom in 1 +\i (00:52:34) setwindow pcb +\i (00:52:34) zoom in -15.8864 100.3353 +\i (00:52:34) trapsize 5653 +\i (00:52:35) zoom out 1 +\i (00:52:35) setwindow pcb +\i (00:52:35) zoom out 18.7087 95.4740 +\i (00:52:35) trapsize 11306 +\i (00:52:35) zoom out 1 +\i (00:52:35) setwindow pcb +\i (00:52:35) zoom out 18.0304 95.0217 +\i (00:52:35) trapsize 22611 +\i (00:52:35) zoom in 1 +\i (00:52:35) setwindow pcb +\i (00:52:35) zoom in 6.7248 77.8372 +\i (00:52:35) trapsize 11306 +\i (00:52:35) zoom in 1 +\i (00:52:35) setwindow pcb +\i (00:52:35) zoom in 6.7248 78.0634 +\i (00:52:35) trapsize 5653 +\i (00:52:36) zoom in 1 +\i (00:52:36) setwindow pcb +\i (00:52:36) zoom in 12.4906 83.9423 +\i (00:52:36) trapsize 2826 +\i (00:52:36) zoom in 1 +\i (00:52:36) setwindow pcb +\i (00:52:36) zoom in 12.4906 83.9989 +\i (00:52:36) trapsize 1413 +\i (00:52:36) zoom in 1 +\i (00:52:36) setwindow pcb +\i (00:52:36) zoom in 17.6630 88.6342 +\i (00:52:36) trapsize 707 +\i (00:52:59) zoom out 1 +\i (00:52:59) setwindow pcb +\i (00:52:59) zoom out 27.9229 89.8354 +\i (00:52:59) trapsize 1413 +\i (00:52:59) zoom out 1 +\i (00:52:59) setwindow pcb +\i (00:52:59) zoom out 26.4814 89.3549 +\i (00:52:59) trapsize 2826 +\i (00:52:59) zoom out 1 +\i (00:52:59) setwindow pcb +\i (00:52:59) zoom out 20.1502 91.7291 +\i (00:52:59) trapsize 5653 +\i (00:52:59) zoom out 1 +\i (00:52:59) setwindow pcb +\i (00:52:59) zoom out 20.6024 80.4236 +\i (00:52:59) trapsize 11306 +\i (00:53:03) zoom in 1 +\i (00:53:03) setwindow pcb +\i (00:53:03) zoom in -5.7113 94.6686 +\i (00:53:03) trapsize 5653 +\i (00:53:03) zoom in 1 +\i (00:53:03) setwindow pcb +\i (00:53:03) zoom in -5.5982 93.8772 +\i (00:53:03) trapsize 2826 +\i (00:53:04) zoom in 1 +\i (00:53:04) setwindow pcb +\i (00:53:04) zoom in 21.0830 90.2594 +\i (00:53:04) trapsize 1413 +\i (00:53:04) zoom in 1 +\i (00:53:04) setwindow pcb +\i (00:53:04) zoom in 21.3092 90.4573 +\i (00:53:04) trapsize 707 +\i (00:53:05) zoom in 1 +\i (00:53:05) setwindow pcb +\i (00:53:05) zoom in 24.7857 90.1464 +\i (00:53:05) trapsize 353 +\i (00:53:05) zoom out 1 +\i (00:53:05) setwindow pcb +\i (00:53:05) zoom out 24.7291 90.1252 +\i (00:53:05) trapsize 707 +\i (00:53:05) zoom out 1 +\i (00:53:05) setwindow pcb +\i (00:53:05) zoom out 24.7292 90.1253 +\i (00:53:05) trapsize 1413 +\i (00:53:06) zoom in 1 +\i (00:53:06) setwindow pcb +\i (00:53:06) zoom in 27.0469 90.1535 +\i (00:53:06) trapsize 707 +\i (00:53:07) zoom out 1 +\i (00:53:07) setwindow pcb +\i (00:53:07) zoom out 27.3437 90.3090 +\i (00:53:07) trapsize 1413 +\i (00:53:07) zoom out 1 +\i (00:53:07) setwindow pcb +\i (00:53:07) zoom out 27.6829 89.4610 +\i (00:53:07) trapsize 2826 +\i (00:53:08) zoom in 1 +\i (00:53:08) setwindow pcb +\i (00:53:08) zoom in 27.6830 89.4610 +\i (00:53:08) trapsize 1413 +\i (00:53:08) zoom in 1 +\i (00:53:08) setwindow pcb +\i (00:53:08) zoom in 27.7113 89.4610 +\i (00:53:08) trapsize 707 +\i (00:53:09) zoom out 1 +\i (00:53:09) setwindow pcb +\i (00:53:09) zoom out 27.0189 89.6730 +\i (00:53:09) trapsize 1413 +\i (00:53:09) zoom out 1 +\i (00:53:09) setwindow pcb +\i (00:53:09) zoom out 27.0189 89.6730 +\i (00:53:09) trapsize 2826 +\i (00:53:10) zoom out 1 +\i (00:53:10) setwindow pcb +\i (00:53:10) zoom out 29.3365 87.6379 +\i (00:53:10) trapsize 5653 +\i (00:53:11) zoom in 1 +\i (00:53:11) setwindow pcb +\i (00:53:11) zoom in 40.0763 88.3163 +\i (00:53:11) trapsize 2826 +\i (00:53:11) zoom in 1 +\i (00:53:11) setwindow pcb +\i (00:53:11) zoom in 37.3065 88.6555 +\i (00:53:11) trapsize 1413 +\i (00:53:11) zoom in 1 +\i (00:53:11) setwindow pcb +\i (00:53:11) zoom in 33.8583 89.6165 +\i (00:53:11) trapsize 707 +\i (00:53:13) zoom in 1 +\i (00:53:13) setwindow pcb +\i (00:53:13) zoom in 32.2756 90.1111 +\i (00:53:13) trapsize 353 +\i (00:53:13) zoom out 1 +\i (00:53:13) setwindow pcb +\i (00:53:13) zoom out 32.7702 89.9556 +\i (00:53:13) trapsize 707 +\i (00:53:14) zoom out 1 +\i (00:53:14) setwindow pcb +\i (00:53:14) zoom out 34.6922 90.2666 +\i (00:53:14) trapsize 1413 +\i (00:53:15) zoom out 1 +\i (00:53:15) setwindow pcb +\i (00:53:15) zoom out 36.8402 89.6731 +\i (00:53:15) trapsize 2826 +\i (00:53:16) zoom in 1 +\i (00:53:16) setwindow pcb +\i (00:53:16) zoom in 40.4015 89.1078 +\i (00:53:16) trapsize 1413 +\i (00:53:16) zoom in 1 +\i (00:53:16) setwindow pcb +\i (00:53:16) zoom in 40.4015 89.1079 +\i (00:53:16) trapsize 707 +\i (00:53:16) zoom in 1 +\i (00:53:16) setwindow pcb +\i (00:53:16) zoom in 40.4015 89.1079 +\i (00:53:16) trapsize 353 +\i (00:53:17) zoom out 1 +\i (00:53:17) setwindow pcb +\i (00:53:17) zoom out 40.0765 89.1927 +\i (00:53:17) trapsize 707 +\i (01:06:24) zoom out 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom out 36.0631 89.7722 +\i (01:06:24) trapsize 1413 +\i (01:06:24) zoom in 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom in 36.0348 89.7440 +\i (01:06:24) trapsize 707 +\i (01:06:24) zoom out 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom out 35.9924 89.6875 +\i (01:06:24) trapsize 1413 +\i (01:06:24) zoom out 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom out 35.5120 89.0374 +\i (01:06:24) trapsize 2826 +\i (01:06:24) zoom out 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom out 35.5120 89.0373 +\i (01:06:24) trapsize 5653 +\i (01:06:24) zoom out 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom out 23.9802 95.5946 +\i (01:06:24) trapsize 11306 +\i (01:06:24) zoom out 1 +\i (01:06:24) setwindow pcb +\i (01:06:24) zoom out 96.4912 87.0023 +\i (01:06:24) trapsize 22611 +\i (01:06:25) zoom out 1 +\i (01:06:25) setwindow pcb +\i (01:06:25) zoom out 300.6703 77.0534 +\i (01:06:25) trapsize 45222 +\i (01:06:25) zoom in 1 +\i (01:06:25) setwindow pcb +\i (01:06:25) zoom in -45.7331 36.5716 +\i (01:06:25) trapsize 22611 +\i (01:06:25) zoom in 1 +\i (01:06:25) setwindow pcb +\i (01:06:25) zoom in -44.3764 36.5716 +\i (01:06:25) trapsize 11306 +\i (01:06:26) zoom in 1 +\i (01:06:26) setwindow pcb +\i (01:06:26) zoom in -2.7718 36.3455 +\i (01:06:26) trapsize 5653 +\i (01:06:26) zoom in 1 +\i (01:06:26) setwindow pcb +\i (01:06:26) zoom in -5.3721 36.0063 +\i (01:06:26) trapsize 2826 +\i (01:06:26) zoom out 1 +\i (01:06:26) setwindow pcb +\i (01:06:26) zoom out 41.3766 42.5636 +\i (01:06:26) trapsize 5653 +\i (01:06:27) zoom in 1 +\i (01:06:27) setwindow pcb +\i (01:06:27) zoom in 58.3349 52.2863 +\i (01:06:27) trapsize 2826 +\i (01:06:28) zoom in 1 +\i (01:06:28) setwindow pcb +\i (01:06:28) zoom in 58.6176 55.5084 +\i (01:06:28) trapsize 1413 +\i (01:06:28) zoom in 1 +\i (01:06:28) setwindow pcb +\i (01:06:28) zoom in 58.8438 57.0065 +\i (01:06:28) trapsize 707 +\i (01:06:30) zoom in 1 +\i (01:06:30) setwindow pcb +\i (01:06:30) zoom in 54.3923 57.5576 +\i (01:06:30) trapsize 353 +\i (01:06:31) zoom in 1 +\i (01:06:31) setwindow pcb +\i (01:06:31) zoom in 54.1591 57.7131 +\i (01:06:31) trapsize 177 +\i (01:06:31) zoom out 1 +\i (01:06:31) setwindow pcb +\i (01:06:31) zoom out 54.1910 57.7167 +\i (01:06:31) trapsize 353 +\i (01:06:31) zoom out 1 +\i (01:06:31) setwindow pcb +\i (01:06:31) zoom out 54.1838 57.7025 +\i (01:06:31) trapsize 707 +\i (01:06:31) zoom out 1 +\i (01:06:31) setwindow pcb +\i (01:06:31) zoom out 54.1698 57.5895 +\i (01:06:31) trapsize 1413 +\i (01:06:32) zoom in 1 +\i (01:06:32) setwindow pcb +\i (01:06:32) zoom in 60.2466 57.7592 +\i (01:06:32) trapsize 707 +\i (01:06:32) zoom in 1 +\i (01:06:32) setwindow pcb +\i (01:06:32) zoom in 60.9956 58.0560 +\i (01:06:32) trapsize 353 +\i (01:06:32) zoom out 1 +\i (01:06:32) setwindow pcb +\i (01:06:32) zoom out 60.6281 57.6603 +\i (01:06:32) trapsize 707 +\i (01:06:32) zoom out 1 +\i (01:06:32) setwindow pcb +\i (01:06:32) zoom out 60.5858 57.4907 +\i (01:06:32) trapsize 1413 +\i (01:06:33) zoom out 1 +\i (01:06:33) setwindow pcb +\i (01:06:33) zoom out 59.0596 56.9255 +\i (01:06:33) trapsize 2826 +\i (01:06:33) zoom out 1 +\i (01:06:33) setwindow pcb +\i (01:06:33) zoom out 58.7770 56.3602 +\i (01:06:33) trapsize 5653 +\i (01:06:37) zoom out 1 +\i (01:06:37) setwindow pcb +\i (01:06:37) zoom out 72.2307 30.9226 +\i (01:06:37) trapsize 11306 +\i (01:06:37) zoom out 1 +\i (01:06:37) setwindow pcb +\i (01:06:37) zoom out 159.1242 30.9227 +\i (01:06:37) trapsize 22611 +\i (01:06:38) zoom in 1 +\i (01:06:38) setwindow pcb +\i (01:06:38) zoom in 98.0740 24.1393 +\i (01:06:38) trapsize 11306 +\i (01:06:39) zoom in 1 +\i (01:06:39) setwindow pcb +\i (01:06:39) zoom in 53.7561 22.1043 +\i (01:06:39) trapsize 5653 +\i (01:06:39) zoom in 1 +\i (01:06:39) setwindow pcb +\i (01:06:39) zoom in 53.7561 22.1043 +\i (01:06:39) trapsize 2826 +\i (01:06:40) zoom in 1 +\i (01:06:40) setwindow pcb +\i (01:06:40) zoom in 62.1788 33.0708 +\i (01:06:40) trapsize 1413 +\i (01:06:41) zoom out 1 +\i (01:06:41) setwindow pcb +\i (01:06:41) zoom out 65.7966 34.5123 +\i (01:06:41) trapsize 2826 +\i (01:06:41) zoom in 1 +\i (01:06:41) setwindow pcb +\i (01:06:41) zoom in 64.7226 42.2001 +\i (01:06:41) trapsize 1413 +\i (01:06:41) zoom in 1 +\i (01:06:41) setwindow pcb +\i (01:06:41) zoom in 66.4750 42.9915 +\i (01:06:41) trapsize 707 +\i (01:10:08) zoom out 1 +\i (01:10:08) setwindow pcb +\i (01:10:08) zoom out 63.0692 39.9814 +\i (01:10:08) trapsize 1413 +\i (01:10:08) zoom out 1 +\i (01:10:08) setwindow pcb +\i (01:10:08) zoom out 62.8713 39.9248 +\i (01:10:08) trapsize 2826 +\i (01:10:09) zoom out 1 +\i (01:10:09) setwindow pcb +\i (01:10:09) zoom out 49.8698 43.3165 +\i (01:10:09) trapsize 5653 +\i (01:10:09) zoom out 1 +\i (01:10:09) setwindow pcb +\i (01:10:09) zoom out 49.7568 43.3166 +\i (01:10:09) trapsize 11306 +\i (01:10:09) zoom out 1 +\i (01:10:09) setwindow pcb +\i (01:10:09) zoom out 49.7568 43.3166 +\i (01:10:09) trapsize 22611 +\i (01:10:09) zoom out 1 +\i (01:10:09) setwindow pcb +\i (01:10:09) zoom out 49.3045 101.2396 +\i (01:10:09) trapsize 45222 +\i (01:10:10) zoom in 1 +\i (01:10:10) setwindow pcb +\i (01:10:10) zoom in -13.1023 85.4117 +\i (01:10:10) trapsize 22611 +\i (01:10:10) zoom in 1 +\i (01:10:10) setwindow pcb +\i (01:10:10) zoom in -13.1023 85.4118 +\i (01:10:10) trapsize 11306 +\i (01:10:11) zoom in 1 +\i (01:10:11) setwindow pcb +\i (01:10:11) zoom in -1.5705 90.8385 +\i (01:10:11) trapsize 5653 +\i (01:10:11) zoom in 1 +\i (01:10:11) setwindow pcb +\i (01:10:11) zoom in -2.0227 90.6124 +\i (01:10:11) trapsize 2826 +\i (01:10:11) zoom in 1 +\i (01:10:11) setwindow pcb +\i (01:10:11) zoom in 19.1753 88.5209 +\i (01:10:11) trapsize 1413 +\i (01:10:12) zoom in 1 +\i (01:10:12) setwindow pcb +\i (01:10:12) zoom in 13.2116 89.4819 +\i (01:10:12) trapsize 707 +\i (01:10:12) zoom in 1 +\i (01:10:12) setwindow pcb +\i (01:10:12) zoom in 14.3988 89.9483 +\i (01:10:12) trapsize 353 +\i (01:10:13) zoom out 1 +\i (01:10:13) setwindow pcb +\i (01:10:13) zoom out 14.7874 90.0049 +\i (01:10:13) trapsize 707 +\i (13:16:02) zoom out 1 +\i (13:16:02) setwindow pcb +\i (13:16:02) zoom out 20.4120 90.6549 +\i (13:16:02) trapsize 1413 +\i (13:16:02) zoom out 1 +\i (13:16:02) setwindow pcb +\i (13:16:02) zoom out 20.4403 90.5702 +\i (13:16:02) trapsize 2826 +\i (13:16:02) zoom out 1 +\i (13:16:02) setwindow pcb +\i (13:16:02) zoom out 20.4404 90.5702 +\i (13:16:02) trapsize 5653 +\i (13:16:02) zoom out 1 +\i (13:16:02) setwindow pcb +\i (13:16:02) zoom out 20.4404 90.5702 +\i (13:16:02) trapsize 11306 +\i (13:16:03) zoom out 1 +\i (13:16:03) setwindow pcb +\i (13:16:03) zoom out 61.4439 29.9723 +\i (13:16:03) trapsize 22611 +\i (13:16:04) zoom out 1 +\i (13:16:04) setwindow pcb +\i (13:16:04) zoom out 4.4637 29.0678 +\i (13:16:04) trapsize 45222 +\i (13:16:04) zoom out 1 +\i (13:16:04) setwindow pcb +\i (13:16:04) zoom out 57.3739 40.8256 +\i (13:16:04) trapsize 90445 +\i (13:16:04) zoom out 1 +\i (13:16:04) setwindow pcb +\i (13:16:04) zoom out 77.2717 55.2968 +\i (13:16:04) trapsize 180890 +\i (13:16:05) zoom in 1 +\i (13:16:05) setwindow pcb +\i (13:16:05) zoom in 23.0049 -24.0264 +\i (13:16:05) trapsize 90445 +\i (13:16:05) zoom in 1 +\i (13:16:05) setwindow pcb +\i (13:16:05) zoom in 21.1960 -24.0264 +\i (13:16:05) trapsize 45222 +\i (13:16:07) zoom out 1 +\i (13:16:07) setwindow pcb +\i (13:16:07) zoom out -5.0329 13.9604 +\i (13:16:07) trapsize 90445 +\i (13:16:08) zoom in 1 +\i (13:16:08) setwindow pcb +\i (13:16:08) zoom in -8.6507 50.1383 +\i (13:16:08) trapsize 45222 +\i (13:16:08) zoom in 1 +\i (13:16:08) setwindow pcb +\i (13:16:08) zoom in -8.6507 50.1383 +\i (13:16:08) trapsize 22611 +\i (13:16:09) zoom in 1 +\i (13:16:09) setwindow pcb +\i (13:16:09) zoom in -8.6507 49.2339 +\i (13:16:09) trapsize 11306 +\i (13:16:09) zoom in 1 +\i (13:16:09) setwindow pcb +\i (13:16:09) zoom in -0.2845 50.1384 +\i (13:16:09) trapsize 5653 +\i (13:16:09) zoom in 1 +\i (13:16:09) setwindow pcb +\i (13:16:09) zoom in -0.2845 50.1385 +\i (13:16:09) trapsize 2826 +\i (13:16:32) exit +\t (13:16:32) Journal end - Wed Aug 26 08:59:54 2026 diff --git a/docs/HC_DFCD_Protocol_V3_0.brd b/docs/HC_DFCD_Protocol_V3_0.brd new file mode 100644 index 0000000..8cd4f63 Binary files /dev/null and b/docs/HC_DFCD_Protocol_V3_0.brd differ diff --git a/docs/QMP4810300-4多协议可编程双向电源通信协议V_1.1.xlsx b/docs/QMP4810300-4多协议可编程双向电源通信协议V_1.1.xlsx new file mode 100644 index 0000000..7c05d2f Binary files /dev/null and b/docs/QMP4810300-4多协议可编程双向电源通信协议V_1.1.xlsx differ diff --git a/docs/QMP4810300-4多协议可编程双向电源通信协议V_1.2.xlsx b/docs/QMP4810300-4多协议可编程双向电源通信协议V_1.2.xlsx new file mode 100644 index 0000000..c263bf5 Binary files /dev/null and b/docs/QMP4810300-4多协议可编程双向电源通信协议V_1.2.xlsx differ diff --git a/docs/generate_five_port_test_plans.ps1 b/docs/generate_five_port_test_plans.ps1 new file mode 100644 index 0000000..94547ad --- /dev/null +++ b/docs/generate_five_port_test_plans.ps1 @@ -0,0 +1,310 @@ +param( + [string]$OutputDirectory = 'D:\HC-PMP-SW\Power_ControlPannel\MP_Control_sw_405\拖拽式平台\docs' +) + +$ErrorActionPreference = 'Stop' + +function New-Criteria { + param([int]$VoltageToleranceMv, [int]$CurrentToleranceMa) + [ordered]@{ + VoltageToleranceMv = $VoltageToleranceMv + CurrentToleranceMa = $CurrentToleranceMa + ShortVoltageMinMv = 0 + ShortVoltageMaxMv = 1000 + ShortCurrentMinMa = 0 + ShortCurrentMaxMa = 1000 + OcpVoltageMinMv = 0 + OcpVoltageMaxMv = 5000 + OcpCurrentMinMa = 4500 + OcpCurrentMaxMa = 5500 + DynamicVpkPlusMinMv = 0 + DynamicVpkPlusMaxMv = 65535 + DynamicVpkMinusMinMv = 0 + DynamicVpkMinusMaxMv = 65535 + } +} + +function New-DmpItem { + param( + [string]$Name, + [int]$Channel, + [string]$Protocol, + [int]$VoltageMv, + [int]$ProtocolCurrentMa, + [int]$LoadCurrentMa, + [int]$VoltageToleranceMv, + [int]$CurrentToleranceMa, + [int]$QueryDelayMs, + [string]$OutputAction = 'Start', + [int]$Gear = 0, + [int]$CcSelection = -1, + [string]$SinkFunction = 'StandardLoad' + ) + + [ordered]@{ + Id = [guid]::NewGuid().ToString() + Name = $Name + Channel = $Channel + Direction = 'Sink' + OutputAction = $OutputAction + Protocol = $Protocol + Mode = 'ConstantCurrent' + PdoType = 'Fpdo' + SinkFunction = $SinkFunction + Gear = $Gear + CcSelection = if ($CcSelection -ge 0) { $CcSelection } elseif ($Channel -eq 2) { 1 } else { 0 } + MinVoltageMv = 3300 + VoltageMv = $VoltageMv + CurrentMa = $ProtocolCurrentMa + LoadVoltageMv = $VoltageMv + LoadCurrentMa = $LoadCurrentMa + VonVoltageMv = 100 + ShortHoldTimeMs = 1000 + HoldAfterTest = $false + OcpStartCurrentMa = 1000 + OcpEndCurrentMa = 5000 + OcpStepCurrentMa = 100 + OcpStepTimeMs = 10 + OcpRecoveryTimeMs = 1000 + OcpStopVoltageMv = 4000 + OcpRecoveryWithLoad = $true + DynamicCurrent1Ma = 1000 + DynamicTime1 = 100 + DynamicCurrent2Ma = 5000 + DynamicTime2 = 100 + DynamicRiseSlope = 1 + DynamicFallSlope = 1 + ApplyDelayMs = 500 + QueryDelayMs = $QueryDelayMs + DurationMs = 1000 + StopOnFault = $true + Criteria = New-Criteria $VoltageToleranceMv $CurrentToleranceMa + Status = 'Pending' + Result = '' + Verdict = '' + MeasuredVoltageMv = $null + MeasuredCurrentMa = $null + QueryTime = $null + Measurements = @() + } +} + +function New-PortConfig { + param( + [string]$Port, + [int]$Channel, + [string]$Protocol, + [int]$VoltageMv, + [int]$ProtocolCurrentMa, + [int]$LoadCurrentMa, + [int]$VoltageToleranceMv, + [int]$CurrentToleranceMa, + [int]$Gear = 0 + ) + [pscustomobject]@{ + Port = $Port + Channel = $Channel + Protocol = $Protocol + VoltageMv = $VoltageMv + ProtocolCurrentMa = $ProtocolCurrentMa + LoadCurrentMa = $LoadCurrentMa + VoltageToleranceMv = $VoltageToleranceMv + CurrentToleranceMa = $CurrentToleranceMa + Gear = $Gear + } +} + +function Add-SingleTest { + param( + [System.Collections.Generic.List[object]]$Items, + [string]$Prefix, + [pscustomobject]$Config, + [int]$CcSelection = -1 + ) + $voltage = $Config.VoltageMv / 1000 + $current = $Config.LoadCurrentMa / 1000 + $Items.Add((New-DmpItem "$Prefix-$($Config.Port)-$voltage`V-$current`A" $Config.Channel $Config.Protocol ` + $Config.VoltageMv $Config.ProtocolCurrentMa $Config.LoadCurrentMa ` + $Config.VoltageToleranceMv $Config.CurrentToleranceMa 10000 'Start' $Config.Gear $CcSelection)) + $Items.Add((New-DmpItem "$Prefix-$($Config.Port)-停止" $Config.Channel $Config.Protocol ` + $Config.VoltageMv $Config.ProtocolCurrentMa $Config.LoadCurrentMa 0 0 1000 'Stop' $Config.Gear $CcSelection)) +} + +function Add-SequencedPortTest { + param( + [System.Collections.Generic.List[object]]$Items, + [string]$Face, + [int]$CcSelection, + [pscustomobject]$Case, + [string]$Port, + [int]$Channel + ) + + $sinkFunction = if ($Case.SinkFunction) { $Case.SinkFunction } else { 'StandardLoad' } + $namePrefix = "$Face-$($Case.Label)-$Port" + $start = New-DmpItem "$namePrefix-启动" $Channel $Case.Protocol $Case.Voltage ` + $Case.ProtocolCurrent $Case.LoadCurrent $Case.VTol $Case.ITol 5000 'Start' 0 $CcSelection $sinkFunction + if ($sinkFunction -eq 'ShortCircuit') { + $start.Criteria.ShortVoltageMinMv = 0 + $start.Criteria.ShortVoltageMaxMv = 3000 + $start.Criteria.ShortCurrentMinMa = 0 + $start.Criteria.ShortCurrentMaxMa = 65535 + } + $Items.Add($start) + $Items.Add((New-DmpItem "$namePrefix-停止" $Channel $Case.Protocol $Case.Voltage ` + $Case.ProtocolCurrent $Case.LoadCurrent 0 0 5000 'Stop' 0 $CcSelection $sinkFunction)) +} + +function Add-CombinationTest { + param( + [System.Collections.Generic.List[object]]$Items, + [string]$Prefix, + [object[]]$Configs + ) + + foreach ($config in $Configs) { + $Items.Add((New-DmpItem "$Prefix-$($config.Port)-配置" $config.Channel $config.Protocol ` + $config.VoltageMv $config.ProtocolCurrentMa $config.LoadCurrentMa 0 0 100 'Start' $config.Gear)) + } + for ($index = 0; $index -lt $Configs.Count; $index++) { + $config = $Configs[$index] + $delay = if ($index -eq 0) { 10000 } else { 1000 } + $Items.Add((New-DmpItem "$Prefix-$($config.Port)-验证" $config.Channel $config.Protocol ` + $config.VoltageMv $config.ProtocolCurrentMa $config.LoadCurrentMa ` + $config.VoltageToleranceMv $config.CurrentToleranceMa $delay 'Start' $config.Gear)) + } + foreach ($config in $Configs) { + $Items.Add((New-DmpItem "$Prefix-$($config.Port)-停止" $config.Channel $config.Protocol ` + $config.VoltageMv $config.ProtocolCurrentMa $config.LoadCurrentMa 0 0 1000 'Stop' $config.Gear)) + } +} + +function Write-Plan { + param([string]$FileName, [string]$Name, [System.Collections.Generic.List[object]]$Items) + $plan = [ordered]@{ + ProductId = 'DMP' + Name = $Name + Communication = [ordered]@{ + PortName = 'COM6' + BaudRate = 115200 + DataBits = 8 + Parity = 'None' + StopBits = 1 + SlaveAddress = 1 + TimeoutMs = 500 + RetryCount = 2 + } + Items = $Items + } + $path = Join-Path $OutputDirectory $FileName + $json = $plan | ConvertTo-Json -Depth 20 + [System.IO.File]::WriteAllText($path, $json, [System.Text.UTF8Encoding]::new($false)) + Get-Item -LiteralPath $path +} + +$c1 = 1 +$c2 = 2 +$a1 = 3 + +# 1. C1/C2同时插入,但依次对单个通道带载。先测A面(CC1),再测B面(CC2)。 +$items = [System.Collections.Generic.List[object]]::new() +$dualCases = @( + [pscustomobject]@{ Label = '5V3A'; Protocol = 'Standard'; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 240; ITol = 300; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '5V3.8A过载'; Protocol = 'Standard'; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 3800; VTol = 0; ITol = 0; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '5V0A空载'; Protocol = 'Standard'; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 0; VTol = 240; ITol = 0; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '5V-QC2.0'; Protocol = 'Qc20'; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 0; VTol = 240; ITol = 0; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '5V短路'; Protocol = 'Standard'; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 0; VTol = 0; ITol = 0; SinkFunction = 'ShortCircuit' }, + [pscustomobject]@{ Label = '5V0A短路恢复'; Protocol = 'Standard'; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 0; VTol = 240; ITol = 0; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '9V3A'; Protocol = 'Pdo'; Voltage = 9000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 440; ITol = 300; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '15V3A'; Protocol = 'Pdo'; Voltage = 15000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 740; ITol = 300; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '20V2.25A'; Protocol = 'Pdo'; Voltage = 20000; ProtocolCurrent = 2250; LoadCurrent = 2250; VTol = 790; ITol = 225; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '20V0A空载'; Protocol = 'Pdo'; Voltage = 20000; ProtocolCurrent = 2250; LoadCurrent = 0; VTol = 790; ITol = 0; SinkFunction = 'StandardLoad' }, + [pscustomobject]@{ Label = '20V2.82A过载'; Protocol = 'Pdo'; Voltage = 20000; ProtocolCurrent = 2250; LoadCurrent = 2820; VTol = 0; ITol = 0; SinkFunction = 'StandardLoad' } +) +foreach ($face in @( + [pscustomobject]@{ Name = 'A面'; CcSelection = 1 }, + [pscustomobject]@{ Name = 'B面'; CcSelection = 2 } +)) { + foreach ($case in $dualCases) { + Add-SequencedPortTest $items $face.Name $face.CcSelection $case 'C1' $c1 + Add-SequencedPortTest $items $face.Name $face.CcSelection $case 'C2' $c2 + } +} +Write-Plan '01_C1-C2双口同时插入测试.json' '01 C1/C2双口同时插入、依次单口负载测试' $items + +# 2. A1单口:5V标准,9V/12V使用QC2.0。 +$items = [System.Collections.Generic.List[object]]::new() +$a1Cases = @( + [pscustomobject]@{ Label = '264VAC'; Protocol = 'Standard'; Voltage = 5000; Current = 3000; VTol = 240; ITol = 300 }, + [pscustomobject]@{ Label = '264VAC'; Protocol = 'Qc20'; Voltage = 9000; Current = 2000; VTol = 440; ITol = 200 }, + [pscustomobject]@{ Label = '264VAC'; Protocol = 'Qc20'; Voltage = 12000; Current = 1500; VTol = 590; ITol = 150 }, + [pscustomobject]@{ Label = '90VAC'; Protocol = 'Standard'; Voltage = 5000; Current = 3000; VTol = 240; ITol = 300 }, + [pscustomobject]@{ Label = '90VAC'; Protocol = 'Qc20'; Voltage = 12000; Current = 1500; VTol = 590; ITol = 150 } +) +foreach ($case in $a1Cases) { + Add-SingleTest $items $case.Label (New-PortConfig 'A1' $a1 $case.Protocol $case.Voltage ` + $case.Current $case.Current $case.VTol $case.ITol) +} +Add-SingleTest $items 'SCP-10V2.25A' (New-PortConfig 'A1' $a1 'Scp' 10000 2250 2250 490 225) +Write-Plan '02_A1单口测试.json' '02 A1单口测试' $items + +# 3. C1/C2单口多协议。先测A面(CC1),再测B面(CC2)。 +# PPS3-5没有规格值,仅做9V空载握手,避免未确认的大电流带载。 +$items = [System.Collections.Generic.List[object]]::new() +$multiProtocolCases = @( + [pscustomobject]@{ Label = '标准5V3A'; Protocol = 'Standard'; Gear = 0; Voltage = 5000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 240; ITol = 300 }, + [pscustomobject]@{ Label = 'PDO9V3A'; Protocol = 'Pdo'; Gear = 0; Voltage = 9000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 440; ITol = 300 }, + [pscustomobject]@{ Label = 'PDO15V3A'; Protocol = 'Pdo'; Gear = 0; Voltage = 15000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 740; ITol = 300 }, + [pscustomobject]@{ Label = 'PDO20V2.25A'; Protocol = 'Pdo'; Gear = 0; Voltage = 20000; ProtocolCurrent = 2250; LoadCurrent = 2250; VTol = 790; ITol = 225 }, + [pscustomobject]@{ Label = 'PPS1-9V4.5A'; Protocol = 'Pps'; Gear = 1; Voltage = 9000; ProtocolCurrent = 4500; LoadCurrent = 4500; VTol = 450; ITol = 225 }, + [pscustomobject]@{ Label = 'PPS2-19V2A'; Protocol = 'Pps'; Gear = 2; Voltage = 19000; ProtocolCurrent = 2000; LoadCurrent = 2000; VTol = 950; ITol = 150 }, + [pscustomobject]@{ Label = 'PPS3-9V空载握手'; Protocol = 'Pps'; Gear = 3; Voltage = 9000; ProtocolCurrent = 1000; LoadCurrent = 0; VTol = 450; ITol = 100 }, + [pscustomobject]@{ Label = 'PPS4-9V空载握手'; Protocol = 'Pps'; Gear = 4; Voltage = 9000; ProtocolCurrent = 1000; LoadCurrent = 0; VTol = 450; ITol = 100 }, + [pscustomobject]@{ Label = 'PPS5-9V空载握手'; Protocol = 'Pps'; Gear = 5; Voltage = 9000; ProtocolCurrent = 1000; LoadCurrent = 0; VTol = 450; ITol = 100 } +) +$avsCases = @( + [pscustomobject]@{ Label = 'AVS-9V3A'; Protocol = 'Avs'; Gear = 0; Voltage = 9000; ProtocolCurrent = 3000; LoadCurrent = 3000; VTol = 150; ITol = 300 }, + [pscustomobject]@{ Label = 'AVS-20V2.25A'; Protocol = 'Avs'; Gear = 0; Voltage = 20000; ProtocolCurrent = 2250; LoadCurrent = 2250; VTol = 650; ITol = 225 } +) +$cPorts = @([pscustomobject]@{ Name = 'C1'; Channel = $c1 }, [pscustomobject]@{ Name = 'C2'; Channel = $c2 }) +foreach ($face in @( + [pscustomobject]@{ Name = 'A面'; CcSelection = 1 }, + [pscustomobject]@{ Name = 'B面'; CcSelection = 2 } +)) { + foreach ($case in $multiProtocolCases) { + foreach ($port in $cPorts) { + Add-SingleTest $items "$($face.Name)-$($case.Label)" (New-PortConfig $port.Name $port.Channel $case.Protocol ` + $case.Voltage $case.ProtocolCurrent $case.LoadCurrent $case.VTol $case.ITol $case.Gear) $face.CcSelection + } + } + foreach ($case in $avsCases) { + Add-SingleTest $items "$($face.Name)-$($case.Label)" (New-PortConfig 'C1' $c1 $case.Protocol ` + $case.Voltage $case.ProtocolCurrent $case.LoadCurrent $case.VTol $case.ITol $case.Gear) $face.CcSelection + } +} +Write-Plan '03_C1-C2单口多协议测试.json' '03 C1/C2单口多协议A/B面测试(含PPS1~5、AVS)' $items + +# 4. 两口组合:只有20V项目使用PDO,其余5V均为标准协议。 +$items = [System.Collections.Generic.List[object]]::new() +Add-CombinationTest $items '组合1-C1+C2' @( + (New-PortConfig 'C1' $c1 'Pdo' 20000 1500 1500 990 150), + (New-PortConfig 'C2' $c2 'Standard' 5000 3000 3000 240 300) +) +Add-CombinationTest $items '组合2-C1+A1' @( + (New-PortConfig 'C1' $c1 'Pdo' 20000 1500 1500 990 150), + (New-PortConfig 'A1' $a1 'Standard' 5000 3000 3000 240 300) +) +Add-CombinationTest $items '组合3-C2+A1' @( + (New-PortConfig 'C2' $c2 'Standard' 5000 1500 1500 240 150), + (New-PortConfig 'A1' $a1 'Standard' 5000 1500 1500 240 150) +) +Write-Plan '04_两口组合输出测试.json' '04 两口组合输出测试' $items + +# 5. 三口组合:C1为PD 20V,C2/A1为标准5V。 +$items = [System.Collections.Generic.List[object]]::new() +Add-CombinationTest $items '三口组合-C1+C2+A1' @( + (New-PortConfig 'C1' $c1 'Pdo' 20000 1500 1500 990 150), + (New-PortConfig 'C2' $c2 'Standard' 5000 1500 1500 240 150), + (New-PortConfig 'A1' $a1 'Standard' 5000 1500 1500 240 150) +) +Write-Plan '05_C1-C2-A1三口组合输出测试.json' '05 C1/C2/A1三口组合输出测试' $items diff --git a/docs/south_regs.h b/docs/south_regs.h new file mode 100644 index 0000000..d535cab --- /dev/null +++ b/docs/south_regs.h @@ -0,0 +1,190 @@ +#ifndef __DigtalPower_ModbusRTU_REG_List_H__ +#define __DigtalPower_ModbusRTU_REG_List_H__ + +#include "USART1_Modbus_RTU.h" +#include "dac.h" + +#define ModbusRTU_SW_Version 0x0301; //01ÊǵçԴ´úºţ¬02ÊǸºÔشúºţ¬03ÊǷǽÚÄÜԴÔشúºţ¬04ÊǽÚÄܵçԴ´úºÅ;µÍ×ֽÚ01Êǰ汾ºţ¬V0.1 + +//ModbusRTU ´ÓվµØַ//¹¤×÷״̬¼°ÔËÐвÎÊýÏÔʾ +#define ModbusRTU_Power_Status 0x00 //R,¹¤×÷״ָ̬ʾ£¬00 Ԥ³äµ磻01¾ÍÐ÷/ֹͣ 02 »ºÆô¶¯£»03 ÔËÐУ»04 ¹ÊÕÏ:03 03 00 00 00 01 85 E8 +#define ModbusRTU_Relay_Status 0x01 //bit0=0 Ö÷¼̵çÆ÷¶Ͽª bit0=1 Ö÷¼̵çÆ÷±պϣ»£»£»bit1=0 Â˲¨¼̵çÆ÷¶Ͽª bit1=1 Â˲¨¼̵çÆ÷±պÏ +#define ModbusRTU_Fault_Status 0x02 //¹ÊÕÏ״̬¼ĴæÆ÷ +#define ModbusRTU_Flash_Status 0x03 //flash´洢״̬ +#define ModbusRTU_Role_ModeFlag 0x04 //0x04 ԴÔØָÁĬÈÏÊǵç×ӸºÔأ¬1ÊÇsupply£¬2ÊÇeload + +//¹ÊÕϼĴæÆ÷ +/*****************************¹ÊÕÏÀàÐÍ*****************/ +#define F_NOERR 0x0000//Î޹ÊÕÏ +#define F_SW_VIN_UVP 0x0001//ÊäÈëǷѹ +#define F_SW_VIN_OVP 0x0002//ÊäÈë¹ýѹ +#define F_SW_VOUT_UVP 0x0004//Êä³öǷѹ +#define F_SW_VOUT_OVP 0x0008//Êä³ö¹ýѹ +#define F_SW_IOUT_OCP 0x0010//Êä³ö¹ýÁ÷ +#define F_SW_SHORT 0x0020//Êä³ö¶Ì· +#define F_SW_OTP 0x0040//¹ýα£»¤ + +//µçԴ¿ØÖÆָÁî +#define ModbusRTU_PowerCmd 0x05 //05 W 0x01 ¿ª»ú£¬0x02 ¹ػú£¬0x03¹ÊÕϸ´λ£»0xAA ´洢Êý¾ݵ½flash£»0xBB »ָ´ĬÈϲÎÊý +#define ModbusRTU_WorkMode 0x06 //06 WR 0x01 ºãѹ£¬0x02 ºãÁ÷£¬0x03¿ª»·£¬04 º㹦Âʣ¬05ºã×è +#define ModbusRTU_Volt 0x07 //07-08 WR ÉèÖÃÊä³öµçѹ uint32£¬0.001V, 0~60V,ĬÈÏ5V£¬²éѯµçѹҲÊÇÕâ¸öָÁî +#define ModbusRTU_Current 0x09 //09-0A WR ÉèÖÃÊä³öµçÁ÷ uint32 £¬0.001A£¬, 0~60A,ĬÈÏֵ1A£¬²éѯµçѹҲÊÇÕâ¸öָÁî + +#define ModbusRTU_ShortTest_VoltShort 0x0B //ÎÞÂÛshort²âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ +#define ModbusRTU_ShortTest_CurrentShort 0x0C //ÎÞÂ۲âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ +#define ModbusRTU_OCPTest_VoltOCP 0x0D //ÎÞÂÛshort²âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ +#define ModbusRTU_OCPTest_CurrentOCP 0x0E //ÎÞÂ۲âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ + +#define ModbusRTU_DUT_Vpkp 0x0F //Á¿²âÕý·åֵ +#define ModbusRTU_DUT_Vpkn 0x10 //Á¿²⸺·åֵ +#define ModbusRTU_DUT_Vpp 0x11 //Á¿²â·å·åֵ +//ÎƲ¨¼ĴæÆ÷ +#define ModbusRTU_Ripple_Value 0x12 //¶ÁȡÎƲ¨ + +#define ModbusRTU_Temp 0x1A //WR 11,µ±ǰÄڲ¿Î¶ȶÁȡµã +#define ModbusADDR_SW_Version 0x1B //10,12µØַ£¬¶Áȡ°汾ºţ¬03´ú±íbuckboost°ëÇţ¬01Êǰ汾ºÅ +#define ModbusADDR_SlaveID_Modify 0x1C //11,13µØַ,Ð޶©SlaveID + + +#define Line_Compensation 0X1D //1F 14dµØַ£¬ÏßË𲹳¥£¬Õý¸º5V£¬1mv£¬¾«¶È0.001£¬·Ŵó1000±¶£¬5000 + +#define Eload_Start_VON 0x20 //10 16¼ĴæÆ÷´洢µç×ӸºÔØÆô¶¯µçѹ£¬¸ºÔؼì²⵽µçѹֵΪÆô¶¯µçѹ£¬²ſªÆôµç×ӸºÔأ» +#define Eload_Stop_Volt 0x21 //11 17¼ĴæÆ÷´洢µç×ӸºÔعرյçѹ£» +#define Eload_Slew_Rate 0x22 //12 18¼ĴæÆ÷´洢µç×ӸºÔØÉÏÉýбÂʲÎÊý£» + +#define ModbusRTU_CPower 0x24 //36-37 ¹¦ÂÊֵ +#define ModbusRTU_CResistor 0x26 //38 µç×èֵ + +//6.3.1µçԴ¿ØÖƲÎÊý +#define ModbusRTU_VoltLimit 0x28 //0x29 40-41 0x2A 42¼ĴæÆ÷Êä³öµçѹÏÞֵ,ĬÈÏÊÇ6.0V,¿ÉÒÔ΢µ÷µ½6.5V£» +#define ModbusRTU_CurrentLimit_H 0x2A //42~43WR Êä³öµçÁ÷ÏÞֵ +#define ModbusRTU_CurrentLimit_L 0x2B //42~43WR Êä³öµçÁ÷ÏÞֵ + +#define ModbusRTU_Volt_CAL_K 0x2C //2C~2D µçѹУ׼Kֵ,±»·ŴóQ16±¶£¬ÔÙÒƶ¯16»Øȥ£»ʹÓÃÁ½¸ö×ֽÚ +#define ModbusRTU_Volt_CAL_K_L 0x2D +#define ModbusRTU_Volt_CAL_B 0x2E //2E µçѹУ׼Bֵ +#define ModbusRTU_Current_CAL_K 0x2F //2F-30 µçÁ÷У׼Kֵ +#define ModbusRTU_Current_CAL_K_L 0x30 + +#define ModbusRTU_Current_CAL_B 0x31 //31 µçÁ÷У׼Bֵ + +#define ModbusRTU_SourceCurrent_CAL_K 0x32 //32-33 µçÁ÷У׼Kֵ +#define ModbusRTU_SourceCurrent_CAL_K_L 0x33 + +#define ModbusRTU_SourceCurrent_CAL_B 0x34 //34 µçÁ÷У׼Bֵ + +#define ModbusRTU_SourceSink_Current_Offset 0x35 //µçԴµĵçÁ÷Éϱ¨offset +//#define ModbusRTU_Sink_Current_Offset 0x33 //¸ºÔصĵçÁ÷Éϱ¨offset + +#define ModbusRTU_CALI_CRC 0x36 //У׼ƫÒÆ6²ÎÊýµÄУÑéֵ£»ʹÓÃCRC16-ModbusУÑ飻 + + +#define ModbusRTU_CAL_WriteEnable 0x37 //ʹÄÜдÈëFLASH£»Ï´ε÷ÓÃʹÓÃÕâ¸ö²ÎÊý£»ÊǸöħÊý£º0x4361 £¨C+a£© +#define ModbusRTU_CAL_WriteRecovery 0x38 //ʹÄÜдÈë³ö³§Êý¾ÝÇøÓò£»0x5763 +#define ModbusRTU_CAL_ReadRecovery 0x39 //ʹÄܶÁ³ö£»½«³ö³§Êý¾ݶÁµ½µ±ǰµÄbankÖУ¬²¢¸üÐÂ6¸ö²ÎÊý + +//short²âÊÔ +#define ModbusRTU_ShortTest_CMD_MODE_FLAG 0x40 //16λ£»0x0101; 01Æô¶¯£¬µÍ×ֽڵÄ01£¬±£³ֵçÁ÷£¬Ϊ0ÊÇÊͷŵçÁ÷£» 20260601ÐÂÔöһ¸öµÍ×ֽڵÄbit7=0ÓÃÓÚÊǻָ´ÒÔǰµÄ״̬£¬bit7=1Êǽ«жÔصçÁ÷¹éÁ㣻 +#define ModbusRTU_ShortTest_Hold_Time 0x41 //µ¥λÊÇms£¬×î´ó65535ms£¬¸ø0ÊÇÁ¢¼´Êͷţ¬¸øһ¸öʱ¼äֵ£¬ÊDZ£³ÖÕâ¸öʱ¼䲻±䣻 +//#define ModbusRTU_ShortTest_CurrentShort 0x42 //ÎÞÂ۲âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ + +//OCP²âÊÔ +#define ModbusRTU_OCPTest_CMD_MODE_FLAG 0x43 //16λ£»0x0101; 01Æô¶¯£¬µÍ×ֽڵÄ01£¬±£³ֵçÁ÷£¬Ϊ0ÊÇÊͷŵçÁ÷£» 20260601ÐÂÔöһ¸öµÍ×ֽڵÄbit7=0ÓÃÓÚÊǻָ´ÒÔǰµÄ״̬£¬bit7=1Êǽ«жÔصçÁ÷¹éÁ㣻 +#define ModbusRTU_OCPTest_StartCurrent 0x44 //ÆðʼµçÁ÷ +#define ModbusRTU_OCPTest_StopCurrent 0x45 //ÖÕֹµçÁ÷ +#define ModbusRTU_OCPTest_StepCurrent 0x46 //ÿһ²½µĵçÁ÷ +#define ModbusRTU_OCPTest_Step_Hold_Time 0x47 //ÿһ²½µı£³Öʱ¼䣻ĬÈÏ10ms£»·¶Χ1msµ½100ms; +#define ModbusRTU_OCPTest_Vtrig_Stop 0x48 //´ﵽ´¥·¢OCPÁ÷µãµĵçѹֵ£» +#define ModbusRTU_OCPTest_Hold_Time 0x49 +//#define ModbusRTU_OCPTest_VoltOCP 0x49 //ÎÞÂÛshort²âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ +//#define ModbusRTU_OCPTest_CurrentOCP 0x4A //ÎÞÂ۲âÊԳɹ¦»òÕßʧ°ܣ¬¶¼¸ø³ö¶Ì·ºãÁ÷ֵµÄʱºòµĵçѹ + +//DYNA µç×ӸºÔص̬͝²âÊÔģʽ£¬Ҳ½ж¯̬¸ºÔػò˲̬²âÊԡ£ÔڸÃģʽÏ£¬¸ºÔػᰴÉ趨µÄбÂʡ¢ƵÂʺÍռ¿ձȣ¬ÔÚÁ½¸öµçÁ÷ֵ£¨Èç¸߸ºÔغ͵͸ºÔأ©֮¼äÖÜÆÚÐÔÌø±䣬ÓÃÀ´²âÊԵçԴµÄ˲̬ÏìӦ¡¢»ָ´ʱ¼äµÈÌØÐԡ£ +#define ModbusRTU_DYNA_CMD_MODE_FLAG 0x4B //¶¯̬²âÊÔ CMDÆô¶¯¡¢Í˳ö¡¢ ÒԼ°ģʽ£ºContinuous¡¢PULSE¡¢Toggle£»LSB8 BIT7 ¶Ì·²âÊÔʱºòµĵçÁ÷£º±£³Ö/¹éÁ㣻bit1 ¹ýÁ÷£» bit0 ³ɹ¦/ʧ°Ü +#define ModbusRTU_DYNA_L1 0x4C //¶¯̬²âÊԵçÁ÷1 +#define ModbusRTU_DYNA_L2 0x4D //¶¯̬²âÊԵçÁ÷2 +#define ModbusRTU_DYNA_T1 0x4E //¶¯̬²âÊԵçÁ÷1µÄʱ¼ä +#define ModbusRTU_DYNA_T2 0x4F //¶¯̬²âÊԵçÁ÷2µÄʱ¼ä +#define ModbusRTU_DYNA_Rise_Slew_Rate 0x50 //¶¯̬²âÊÔÉÏÉýбÂÊ +#define ModbusRTU_DYNA_Fall_Slew_Rate 0x51 //¶¯̬²âÊÔϽµбÂÊ + + +#define ModbusRTU_DUT_ValueReset 0x5D //¸´λÊýֵ + +#define ModbusRTU_SYS_Reset 0x61 //ϵͳ¸´λָÁÈç¹ûÒ쳣¹ýѹ£¬ͨ³£·¢ָÁÈÃϵͳ¸´λ + +//#define ModbusRTU_Volt_Kp 0x30 //48-49 RW µçѹ»·kp +//#define ModbusRTU_Volt_Ki 0x32 //50-51WR µçѹ»·ki +//#define ModbusRTU_Curr_Kp 0x34 //52-53WR µçÁ÷»·kp +//#define ModbusRTU_Curr_Ki 0x36 //54-55WR µçÁ÷»·ki + +//#define ModbusRTU_CP_Kp 0x38 //56-57 RW ¹¦Âʻ·kp +//#define ModbusRTU_CP_Ki 0x3A //58-59 WR ¹¦Âʻ·ki +//#define ModbusRTU_CR_Kp 0x3C //60-61 WR µç×軷kp +//#define ModbusRTU_CR_Ki 0x3E //62-63 WR µç×軷ki + +//112,0x70¼ĴæÆ÷£¬¸ÄΪµ±ǰÄڲ¿Î¶ȶÁȡµ㣻 + +#define ModbusRTU_TempLimit 0x71 //WR ζÈÏÞֵ +#define ModbusRTU_FANSpeed 0x72 //·çËٿØÖƣ¬ÉÏλ»ú¿ÉÒÔÉ趨£»×Զ¯ģʽÏ£¬Î¶ȸßÓÚһ¸öֵ£¬×î´ó·çËÙÅܣ¬µÍÓÚÔò×îС·çËÙÅÜ + +#define SMU_Relay_ON 0x0001 //ԴÔØÖ÷¼̵çÆ÷ +#define SMU_EL_PB15_EMI_Bypass_ON 0x0002 //ÅÔ·Â˲¨¼̵çÆ÷ +//¶¨ÒåµçÁ÷±£»¤ֵµĹØϵ£ºY(VDAC) = K*X(I-real)+B +#define Init_OCP_VALUE 12000 //12A +//ÕýÏò£º +#define K_COEFF_FWD_Q16 (9960) +#define B_Intercept_FWD (1846) +//·´Ïò£º +#define K_COEFF_REV_Q16 (-9960) +#define B_Intercept_REV (1846) + +//½ṹÌå +//6.2.1¹¤×÷״̬¼°ÔËÐвÎÊýÏÔʾ PowerStatusAndParaShow +typedef struct { + uint16_t Power_Status; + uint16_t Relay_Status; + uint16_t Fault_Status; + uint16_t Flash_Status; + uint32_t InputVolt; + uint32_t InputCurr; + uint32_t OutputVolt; + uint32_t OutputCurr; + uint32_t OutputPower; + uint16_t statusRsv1; //ԤÁôλ + uint16_t statusRsv2; +}S_PowerStatusAndParaShow; + +extern S_PowerStatusAndParaShow PowerStatusAndParaShow; + +//6.2.2µçԴ¿ØÖÆָÁîPowerControlCmd +typedef struct{ + uint16_t PowerCmd; + uint16_t WorkMode; + uint32_t Vout; + uint32_t Iout; + uint8_t Remote_Power_ON_Flag; //¿ª¹ػúָʾ + uint8_t Remote_Power_Reset_Flag; //¸´λָʾ +}S_PowerControlCmd; + +extern S_PowerControlCmd PowerControlCmd; + + +//6.3.1µçԴ¿ØÖƲÎÊý +typedef struct{ + uint32_t VoutLimit; + uint32_t IoutLimit; + uint16_t DutyLimit; + uint16_t FrequnceLimit; + float VoltKp; + float VoltKi; + float CurrKp; + float CurrKi; +}S_PowerControlPara; + +extern uint8_t Power_OnOff_Flag,Power_OnOff_Flag; + +extern S_PowerControlPara PowerControlPara; +//Modbus ²ÎÊý³õʼ»¯ +extern void Modbus_Para_Init(void); +#endif diff --git a/docs/修改日志.md b/docs/修改日志.md new file mode 100644 index 0000000..5748045 --- /dev/null +++ b/docs/修改日志.md @@ -0,0 +1,238 @@ +# 修改日志 + +## V1.1.9 - 2026-08-25 + +### Source 复位后的初始化流程 +- 从其他协议切换到 PD 时复位加入断开CC,D+D-,GND逻辑,等断开后再复位,再初始化 +- 增加 PD 切换到非 PD 协议时的完整复位;PD/非PD跨类别切换统一执行复位。 +- PD切PD、非PD切非PD以及仅切换CC线时不复位。 +- 新增 `SOURCE_WORK_INIT_WAIT`,复位回码后重新执行 Source 初始化,并等待南向初始化批次完成。 +- 加入了读取版本号 + +## V1.1.8 - 2026-08-24 + +### 修改主题 +修复了source部分的PD协议长时间运行后会出不来的问题 +对应芯海3221/source版本V_1.07 + +### Source协议复位切换 +- 新增`SOURCE_RESET`命令:发送`06 00 02 18 00 0E`,校验回码`05 00 02 18 16`。 +- 首次发送协议不复位;PD与非PD跨类别切换时先复位。 +- PD切PD、非PD切非PD以及仅切换CC线时不复位。 +- 删除`CC_CLOSE`状态及其共享等待屏障。 +- 协议设置回码有效后更新`last_pd_pro`,避免协议尚未生效时错误判断。 + +### 验证记录 +- 使用1-2,3-4测试自举例程测试俩百次正常 +- git diff --check`:通过。 +- 旧`CC_CLOSE`状态、宏和调用引用:已检查清理。 +- Keil编译:按当前要求未执行,待现场烧录后验证复位回码和协议切换时序。 + +## V1.1.7 - 2026-08-08 + +### 修改主题 + +并行执行Sink完整握手中的南向低电流确认与北向协议/CC设置。 + +### 主要修改 + +- 南向安全批次完成并收到辅助继电器回码、建立量测基线后,立即允许北向设置协议和CC线。 +- 南向实际电流连续两次不大于 `100mA` 的确认过程与北向协议/CC设置并行执行。 +- 北向进入 `GET_SINK_VC` 后,只有低电流确认完成且CC后的 `200ms` 查询保护时间到期,才发送第一条 `0x20`。 +- 电压稳定后再发送 `ALL_REL_ON -> 目标电流 -> WORK_START`,防止协议协商期间提前带载。 + +### 涉及文件 + +- `apps/chrg/applications/thread/chrg_roll_nor.c` +- `apps/chrg/applications/utils/chrg_sink.c` +- `apps/chrg/applications/utils/chrg_sink.h` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 + +## V1.1.5 - 2026-08-08 + +### 修改主题 + +协议电压和协议电流变化统一采用软卸载重发协议流程。 + +### 主要修改 + +- 协议类型、CC线和协议组号变化仍进入完整握手并切换南向继电器。 +- 协议电压、协议电流单独变化或同时变化时,只发送 `WORK_STOP`,不关闭南向继电器。 +- 软卸载不再额外发送 `CURRENT=0`,仅通过实际电流连续两次不大于 `100mA`确认电子负载已停止。 +- 协议重新生效且电压稳定后,由RUNNING先写入实际负载电流,再发送 `WORK_START`。 + +### 涉及文件 + +- `apps/chrg/applications/thread/chrg_comm.c` +- `apps/chrg/applications/utils/chrg_sink.c` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 + +## V1.1.4 - 2026-08-07 + +### 修改主题 + +调整负载低电流启动时的继电器和工作命令顺序。 + +### 主要修改 + +- 设定电流为 `0A` 或低于 `100mA` 时,协议握手仍正常执行。 +- 电压达到目标后发送 `ALL_REL_ON`,确保主继电器打开。 +- 主继电器打开后继续发送 `WORK_STOP` 和 `CURRENT=0`,保持电子负载不带载。 +- 已经处于运行状态切换到低电流时,沿用原有停止流程,不重复切换继电器。 + +### 涉及文件 + +- `apps/chrg/applications/utils/chrg_sink.c` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 + +## V1.1.3 - 2026-08-07 + +### 修改主题 + +并行执行Sink南向初始化与北向握手,并优化握手查询和RUNING继电器时序。 + +### 主要修改 + +- `INIT`将 `FU_REL_ON -> CURRENT=0 -> MODE -> VON` 作为一个有序南向批次提交。 +- 南向初始化批次成功入队后立即打开北向Sink继电器、设置 `enable=1` 并开始协议握手。 +- `SINK_WORK_INIT_AUX_WAIT`改为后台监控南向初始化,不再阻塞北向协议、CC和电压查询。 +- 南向初始化失败重试时保留当前北向 `sub`,不重复拉回协议设置起点。 +- 协议或CC有效回码后,首条 `0x20` 等待 `200ms`;电压未稳定时后续每 `100ms` 查询一次。 +- 一帧通过CRC、ID、命令和配置版本校验的电压达到目标后立即进入 `RUNING`,不再等待第二帧。 +- 进入稳定运行后立即解除查询节流,恢复北向正常轮询速度。 +- `RUNING`删除重复的 `FU_REL_ON`,调整为 `ALL_REL_ON -> 目标负载值 -> WORK_START`。 + +### 涉及文件 + +- `apps/chrg/applications/utils/chrg_sink.c` +- `apps/chrg/applications/thread/chrg_roll_nor.c` +- `apps/chrg/applications/thread/chrg_north.c` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 + +### 硬件验证 + +- 待确认南向初始化命令与北向协议/CC设置并行执行且互不重置状态。 +- 待确认首条和后续 `0x20` 间隔分别约为 `200ms` 和 `100ms`。 +- 待确认进入 `RUNING` 时不再发送 `FU_REL_ON`,主继电器不会被重复断开。 + +## V1.1.2 - 2026-08-07 + +### 修改主题 + +降低Sink协议握手阶段的北向电压电流查询频率。 + +### 主要修改 + +- 新增 `SINK_HANDSHAKE_QUERY_INTERVAL_MS`,设置为 `200ms`。 +- Sink协议或CC设置收到有效回码后,不立即发送或连续快速发送 `0x20` 查询。 +- CC路径的 `200ms` 从有效 `0x17` 回码时刻开始计时,包含原有 `50ms` CC稳定保护。 +- 协议握手、软恢复等待和掉电恢复监控阶段的 `0x20` 查询按每 `200ms` 一次执行。 +- 北向量测连续两次达到稳定条件后解除节流,`RUNING` 和低电流待机恢复正常查询频率。 +- 仅重发协议的 `IDX_SET_SINK_PD_ONLY` 路径同样启动 `200ms` 查询等待。 +- 设置协议和CC命令仍然按有效回码、超时和原有重试机制处理。 + +### 涉及文件 + +- `apps/chrg/applications/thread/chrg_roll_nor.c` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 + +### 硬件验证 + +- 待观察协议/CC设置后 `0x20` 的发送间隔是否稳定为不小于 `200ms`。 +- 待验证电压连续两次稳定后是否恢复正常量测频率。 + +## V1.1.1 - 2026-08-07 + +### 修改主题 + +增加普通负载最低带载电流控制,低电流时保持协议在线但关闭电子负载。 + +### 主要修改 + +- 新增 `SINK_LOAD_ENABLE_MIN_MA`,恒流负载设定低于 `100mA` 时禁止发送 `WORK_START`。 +- 最低带载门限只判断南向负载电流 `loadc`,不影响北向协议电流 `pro_loadc` 和协议握手。 +- 首次启动或完整重握手后为低电流时,发送 `WORK_STOP -> CURRENT=0` 并保持主继电器关闭。 +- 运行中由有效电流降至低电流时,发送 `WORK_STOP -> CURRENT=0`,保持协议、CC和继电器不变。 +- 运行中低电流待机恢复有效电流时,只发送目标电流和 `WORK_START`,不重新握手或切换继电器。 +- 主继电器未打开的低电流待机使用独立 `SINK_WORK_LOAD_START` 恢复入口,避免被 `RUNING` 状态去重。 +- 低电流待机期间仅修改协议电压时不重复卸载,并保持原有主继电器状态语义。 +- 将 COM 端运行电流跨门限判断封装为 `chrg_comm_handle_sink_load_current()`,避免生效分支继续膨胀。 +- 北向解析将两种低电流待机状态作为稳定状态,不因实际电流为零反复进入 `RUNING`。 +- 低电流待机恢复有效电流时,`SINK_WORK_LOAD_START`/`SINK_WORK_LOAD_RESUME` 只作为待启动标记,必须等北向实际电压稳定后才下发 `WORK_START`。 + +### 涉及文件 + +- `apps/chrg/applications/utils/chrg_sink.h` +- `apps/chrg/applications/utils/chrg_sink.c` +- `apps/chrg/applications/thread/chrg_comm.c` +- `apps/chrg/applications/thread/chrg_north.c` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 + +### 硬件验证 + +- 待验证 `20V/3A -> 20V/0A -> 20V/3A` 的南向命令顺序和协议保持情况。 +- 待验证首次以低于 `100mA` 的电流启动时,北向握手正常且南向不执行 `WORK_START`。 + +## V1.1.0 - 2026-08-07 + +### 修改主题 + +负载启动、掉电恢复、协议重握手和协议电压软切换时序调整。 + +### 主要修改 + +- 负载启动时先向南向发送 `FU_REL_ON`,收到有效回码后才打开北向 Sink 继电器。 +- 剩余的负载初始化命令在副继电器确认后入队,北向协议握手与南向初始化并行执行。 +- `RUNING` 阶段的南向命令继续追加到同一队列,保持初始化配置、继电器、目标电流/电压和 `WORK_START` 的顺序。 +- 掉电恢复时执行 `WORK_STOP -> CURRENT=0 -> ALL_REL_OFF -> FU_REL_ON`,副继电器恢复后等待输入电压重新出现。 +- 掉电恢复检测到电压后重新设置协议和 CC,不能使用掉电前的电压样本直接进入 `RUNING`。 +- 协议类型、CC、协议组号或协议电流变化进入完整重握手,关闭主副继电器后重新打开副继电器。 +- 仅协议电压变化使用软卸载,只发送 `WORK_STOP` 和 `CURRENT=0`,不切换继电器。 +- 软卸载恢复时只发送目标电流/电压和 `WORK_START`,不再发送继电器命令。 +- 保留连续两次实际电流不大于 `100mA` 的安全确认,以及连续两次稳定电压确认。 +- 暂时保持南向主副继电器之间的固定延时为屏蔽状态。 + +### 涉及文件 + +- `apps/chrg/applications/utils/chrg_sink.h` +- `apps/chrg/applications/utils/chrg_sink.c` +- `apps/chrg/applications/thread/chrg_comm.c` +- `apps/chrg/applications/thread/chrg_roll_nor.c` +- `apps/chrg/applications/thread/chrg_north.c` + +### 编译验证 + +- Keil MDK 编译结果:`0 Error(s), 0 Warning(s)`。 +- 尚未完成硬件验证,需重点观察南向命令顺序、北向协议握手时刻、掉电恢复和协议电压软切换。 + +### 后续记录格式 + +后续每次修改按以下格式追加: + +```text +## Vx.y.z - YYYY-MM-DD + +### 修改主题 +### 主要修改 +### 涉及文件 +### 编译验证 +### 硬件验证 +``` diff --git a/docs/修改计划.md b/docs/修改计划.md new file mode 100644 index 0000000..15d3a41 --- /dev/null +++ b/docs/修改计划.md @@ -0,0 +1,111 @@ +静态排查后,暂未看到明确的 ABBA 型互斥锁环。当前现象更像“内存破坏/资源泄漏后线程停滞”或“状态机无限重试”,而不是整个 RTOS 调度器死锁。 + +另外,代码中使用的是 STM32 内部 IWDG,不是外部独立看门狗。 + +**可疑点** + +1. **高风险:南北向分包接收存在确定的越界写和泄漏路径** + +[north](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:548) 和 [south](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:309) 在续接分包时计算: + +```text +len_need = len_msg - 帧头长度 +``` + +但此时 `pFrame->length` 已经包含之前收到的数据,正确的剩余长度应基于 `len_msg - pFrame->length`。当前代码却从 `payload[pFrame->length]` 开始写入整个“帧头后的总长度”。 + +例如总长 21 字节,首批已存 8 字节,下一次仍可能从偏移 8 写入 18 字节,最终写到偏移 26,越过 21 字节堆块。这会破坏相邻堆块、邮箱消息甚至 RT-Thread 内核对象,最终表现为随机线程卡死、互斥量异常或堆分配失败。 + +同时,分包超时路径只清除了 `findhead`: + +- [chrg_north.c:627](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:627) +- [chrg_south.c:399](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:399) + +没有释放 `pMB/payload`,也没有清除 `length/len_msg`。而且没有新数据时会提前 `continue`,超时检查根本不会执行。串口噪声、丢字节或正常分包都可能不断积累泄漏。 + +此外,payload 分配失败时只返回,没有释放刚分配的 `pMB`: + +- [chrg_north.c:518](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:518) +- [chrg_south.c:280](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:280) + +这是目前最符合“运行时间越长越容易出现”的可疑点。 + +2. **高风险:看门狗喂法无法覆盖业务线程死锁** + +主线程每秒无条件喂狗:[main.c:36](D:/proj/char/chrg_v/apps/chrg/applications/main.c:36)。 + +北向轮询线程又在每轮无条件喂同一只狗:[chrg_roll_nor.c:421](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_nor.c:421)。 + +因此即使 `thr.comm`、`thr.sou`、`thr.rollsou`、LCD 线程全部停止工作,只要主线程或北向轮询线程仍能调度,IWDG 就永远不会复位。这只是在检测“CPU/调度器是否完全停止”,没有检测业务线程是否健康。 + +驱动最终调用的是 `HAL_IWDG_Refresh()`:[drv_wdt.c:40](D:/proj/char/chrg_v/rt-thread/bsp/stm32/libraries/HAL_Drivers/drivers/drv_wdt.c:40)。代码中没有找到外部 WDI 引脚翻转或外部看门狗驱动。 + +3. **高风险:当前新增的南向批处理可能永久停在 `RUN_COM`** + +南向批处理遇到 CRC 错误或应答超时后,不增加重试次数、不设置截止时间,也不清除任务: + +- CRC 错误:[chrg_roll_sou.c:608](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:608) +- 应答超时:[chrg_roll_sou.c:627](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:627) + +线程会一直重发同一个寄存器,`pending` 永远不清除,`run` 永远保持 `RUN_COM`,正常南向轮询无法恢复。这属于业务状态机死锁,调度器仍正常运行,所以看门狗不会复位。 + +这套批处理逻辑是当前工作区相对 `HEAD` 新增的,优先级很高。 + +4. **高风险:南向共享状态的互斥保护实际无效** + +[chrg_set_sou_reg](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:435) 先在锁外写入: + +```text +pROLL->ch +pROLL->reg +pROLL->value +``` + +随后互斥量只保护 `pROLL->run`。消费者也只在锁内读取 `run`,实际使用 `ch/reg/value` 时已经离开锁。 + +多个生产者包括 LCD、Modbus 和状态机可能交叉覆盖这些字段,形成“通道来自命令 A、寄存器来自命令 B、值来自命令 C”的组合。 + +批处理数组 `g_sou_com_batch` 同样由生产者和 `thr.rollsou` 同时读写,却没有共同的锁:[chrg_roll_sou.c:394](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:394)。 + +5. **中高风险:先切换 `RUN_COM`,再填充批处理数据** + +[chrg_sink_diff_send_reg](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_comm.c:2338) 先调用 `chrg_sou_com_sou_set()` 将状态切到 `RUN_COM`,然后才追加寄存器。 + +`chrg_sink_work()` 也是相同顺序:[chrg_sink.c:541](D:/proj/char/chrg_v/apps/chrg/applications/utils/chrg_sink.c:541)。 + +如果消费者在状态切换后看到尚未填充的批次,会认为没有任务并切回自动模式;随后加入的 `pending` 数据可能一直无人处理。当前线程优先级降低了部分窗口概率,但没有从同步机制上消除竞态。 + +6. **中高风险:应答邮箱没有请求、通道或序号关联** + +邮箱消息只有 `length + payload`:[chrg_def.h:95](D:/proj/char/chrg_v/apps/chrg/applications/utils/chrg_def.h:95)。 + +南北轮询线程默认“收到的下一帧就是刚才请求的响应”。南向批处理只检查 CRC,任何延迟到达的旧响应都可能让当前批次 `cur_idx++`:[chrg_roll_sou.c:574](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:574)。 + +在超时、重发、通道切换或邮箱积压后,请求与响应很容易错位,最终造成状态机停在错误步骤或无限重试。 + +7. **中风险:所有业务互斥量都是永久等待** + +TTY、南北轮询互斥量统一使用 `RT_WAITING_FOREVER`。例如: + +- [chrg_tty.c:457](D:/proj/char/chrg_v/apps/chrg/applications/bsp/chrg_tty.c:457) +- [chrg_comm.c:2246](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_comm.c:2246) +- [chrg_roll_nor.c:439](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_nor.c:439) + +目前没有发现确定的反向加锁环,但一旦锁所有者因堆破坏、状态机卡死或设备写阻塞而不再释放,其他线程会永久挂起,没有超时和故障上报。 + +8. **中风险:串口 RingBuffer 写入结果被忽略** + +[chrg_north.c:623](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:623) 和 [chrg_south.c:383](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:383) 没有检查 `rt_ringbuffer_put()` 实际写入长度。 + +缓冲区满时会静默丢字节,随后触发分包超时、泄漏和上述越界路径,是长期运行后出现问题的重要放大器。 + +**优先判断** + +最可能的两条故障链是: + +1. 串口分包 → 长度计算错误/超时泄漏 → 堆损坏或耗尽 → 某些业务线程永久等待;主线程继续喂狗。 +2. 南向某条命令超时或响应错位 → `RUN_COM` 无限重发 → 南向业务永久停止;主线程和北向线程继续喂狗。 + +现场故障尚能进入 Shell 时,优先保存 `list_thread`、`list_mutex`、`list_mailbox`、`free` 输出,并检查是否持续打印 `com batch timeout, resend current reg`。这几项可以直接区分锁等待、堆耗尽和状态机无限重试。 + +未修改任何文件。 \ No newline at end of file diff --git a/docs/测试日志.md b/docs/测试日志.md new file mode 100644 index 0000000..c55b9ad --- /dev/null +++ b/docs/测试日志.md @@ -0,0 +1,36 @@ +# 测试日志 + +## V1.1.8 - 2026-08-24 + +### 测试对象 + +Source协议切换复位逻辑。 + +### 验证记录 + +| 编号 | 测试项 | 预期结果 | 当前结果 | +| --- | --- | --- | --- | +| 1 | 首次发送Source协议 | 直接发送协议,不发送`SOURCE_RESET` | 静态检查通过 | +| 2 | PD切换到非PD | 先复位,再发送协议 | 静态检查通过 | +| 3 | 非PD切换到PD | 先复位,再发送协议 | 静态检查通过 | +| 4 | PD切PD | 不复位,直接发送协议 | 静态检查通过 | +| 5 | 非PD切非PD | 不复位,直接发送协议 | 静态检查通过 | +| 6 | 仅切换CC线 | 不复位 | 静态检查通过 | +| 7 | 复位命令帧 | 发送`06 00 02 18 00 0E` | 静态检查通过 | +| 8 | 复位回码 | 收到`05 00 02 18 16`后推进状态 | 待硬件验证 | + +### 静态验证 + +- 已执行`git diff --check`,无格式错误。 +- 已检查复位命令、状态映射、回码长度校验和`last_pd_pro`更新路径。 +- Keil编译及硬件实测未执行。 + +### 待补充现场信息 + +- 板卡编号: +- 测试连接方式: +- 测试开始/结束时间: +- 复位发送日志: +- 复位回码日志: +- 协议切换结果: +- 最终结论: diff --git a/docs/程序流程与函数说明.md b/docs/程序流程与函数说明.md new file mode 100644 index 0000000..32df2a4 --- /dev/null +++ b/docs/程序流程与函数说明.md @@ -0,0 +1,325 @@ +# 程序流程与函数说明 + +## 1. 文档范围 + +本文按当前源码(2026-07-19)说明 `apps/chrg/applications/` 的自研函数、运行流程和函数级调用关系,并补充 `apps/boot/` 的启动主链。`rt-thread/`、`libs/`、CubeMX 生成代码、HAL 中断模板以及调试 shell 命令的内部库调用不展开。调用关系由源码静态分析得到;通过 `INIT_*_EXPORT`、线程入口、回调和函数指针发生的调用会单独标明。 + +## 2. 上电与初始化流程 + +```mermaid +flowchart TD + R["芯片复位"] --> B["Boot main()"] + B --> SI["System_Init()"] + SI --> AI["APP_Init()"] + AI --> AR["APP_Running()"] + AR --> CK["boot_CheckAndHandle()"] + CK -->|APP 有效| JA["boot_JumpToAPP()"] + CK -->|需要升级| RX["recvDataHandler()"] + RX --> YH["ymodemHandler()"] + YH --> BP["boot_ProtocolDataHandle()"] + BP --> FM["FM_* 擦写/校验函数"] + FM --> JA + JA --> RTT["RT-Thread 启动"] + RTT --> INIT["INIT_PREV/DEVICE/ENV/APP_EXPORT"] + INIT --> TI["chrg_thread_init()"] + TI --> TC["chrg_thread_create() x 6"] + TC --> MAIN["main() 空闲循环"] +``` + +`main()` 不是业务入口。GPIO、FAL、定时器、串口、继电器和看门狗由 RT-Thread 自动初始化段调用;`chrg_thread_init()` 再创建六个常驻线程。初始化失败目前不会阻止后续线程继续创建,排障时必须查看启动日志。 + +## 3. 运行期线程与数据流 + +| 线程入口 | 职责 | 主要输入 | 主要输出 | +|---|---|---|---| +| `chrg_north_thread_entry` | 接收协议板/Sink/Source 北向帧 | 北向 UART ringbuffer | 更新 `chrgnorth.sw[]`,投递帧 | +| `chrg_south_thread_entry` | 接收功率板 Modbus 响应 | 南向 UART ringbuffer | 投递响应到轮询/通信模块 | +| `chrg_comm_thread_entry` | 上位机 Modbus、寄存器映射和业务命令 | mailbox、Modbus 回调 | 修改目标状态,生成南北向命令 | +| `chrg_lcd_thread_entry` | HMI 报文解析和状态显示 | LCD 串口 | 修改配置、刷新页面 | +| `chrg_roll_nor_thread_entry` | 分时驱动四路 Sink/Source 协议板 | event、通道状态 | 查询/设置协议板,运行 Sink 状态机 | +| `chrg_roll_sou_thread_entry` | 串行执行功率板寄存器读写 | event、批命令队列 | Modbus RTU 请求与完成状态 | + +```mermaid +flowchart LR + PC["上位机"] --> MB["Modbus 回调 func* / mb_reg_*_cb"] + LCD["HMI"] --> LP["chrg_lcd_parse()"] + MB --> CM["chrg_comm_parse_msg()"] + LP --> ST["chrgnorth.sw[] 目标状态"] + CM --> ST + RN["chrg_roll_nor_thread_entry()"] --> SET["chrg_roll_nor_set()"] + ST --> SET + SET --> SK["sink/source 设置函数"] + SK --> NTX["chrg_north_send()"] + DEV["协议板"] --> NRX["chrg_north_thread_entry()"] + NRX --> FD["chrg_north_frame_dispatch()"] + FD --> SP["chrg_sink_parse() / Source 解析"] + SP --> SW["chrg_sink_work()"] + SW --> SR["chrg_set_sou_reg()"] + CM --> SR + SR --> RS["chrg_roll_sou_thread_entry()"] + RS --> STX["chrg_south_send()"] + PWR["功率板"] --> SRX["chrg_south_thread_entry()"] + SRX --> SD["chrg_south_frame_dispatch()"] + SD --> RS +``` + +## 4. Sink 工作状态机 + +```mermaid +stateDiagram-v2 + [*] --> INIT + INIT --> WAIT: 下发停止/安全继电器状态 + WAIT --> RUNING: 电压正常且接近目标 + RUNING --> WAIT: 电压正常但电流连续偏低 + RUNING --> LOW: 电压连续低于 WORK_MIN_VOLT + WAIT --> LOW: 电压连续过低 + LOW --> WAIT: 电压恢复并重新准备 + RUNING --> OFF: 停止工作 + WAIT --> OFF: 停止工作 +``` + +函数级主链:`chrg_roll_nor_thread_entry()` → `chrg_roll_nor_set()` → `sink_get_vc()` → 北向响应 → `chrg_north_frame_dispatch()` → `chrg_sink_parse()` → `chrg_sink_work()` → `chrg_sink_work_init()` / `chrg_sink_work_volt_runing()` / `chrg_sink_nor_work_low()` / `chrg_sink_nor_work_off()`。继电器动作由 `chrg_sink_change_ON()`、`chrg_sink_SHOUT_ON()`、`chrg_nor_sw_rel()` 完成,功率板命令最终经 `chrg_set_sou_reg()` 交给南向轮询线程。 + +## 5. 函数说明:入口与线程基础设施 + +### `main.c` + +| 函数 | 说明 | +|---|---| +| `show_app_version()` | 打印 RT-Thread 版本和固件编译时间,无输入和返回值。 | +| `main()` | 设置 shell 提示符后进入 1 秒延时循环;业务线程由自动初始化段提前创建。 | + +### `thread/chrg_thread.c` + +| 函数 | 说明 | +|---|---| +| `chrg_thread_evt_send()` | 向指定线程发送事件位;线程或事件对象无效时返回错误。 | +| `chrg_thread_mb_free()` | 释放邮箱消息及其 `payload`,仅供本文件失败路径使用。 | +| `chrg_thread_mb_send()` | 向指定线程邮箱转移消息所有权;发送失败时自动释放消息。 | +| `chrg_thread_mq_send()` | 向指定线程消息队列复制定长消息。 | +| `chrg_thread_create()` | 按 `chrgthr[]` 配置创建 IPC 对象、线程并启动线程。 | +| `chrg_thread_init()` | 遍历六个线程配置并创建;由 `INIT_APP_EXPORT` 隐式调用。 | + +## 6. 函数说明:通信线程 + +### `thread/chrg_north.c` + +| 函数 | 说明 | +|---|---| +| `chrg_compare()` | 计算两个有符号量的绝对差值。 | +| `chrg_source_stop()` | 按步骤停止 Source 输出并处理继电器/状态复位。 | +| `chrg_source_standard_output()` | 执行 Source 标准输出控制流程。 | +| `chrg_source_fast_output()` | 执行 Source 快充协议输出流程。 | +| `chrg_source_setting()` | 根据 Source 当前配置选择和推进设置过程。 | +| `chrg_sink_parse()` | 解析 Sink 电压、电流及状态,做低压/无流判定并调用 `chrg_sink_work()`。 | +| `chrg_north_handle_Seting_Volt()` | 解析北向设置电压类响应并更新通道状态。 | +| `chrg_north_switch()` | 选择当前北向物理通道。 | +| `chrg_north_send()` | 通过北向 TTY 发送一帧数据。 | +| `chrg_north_frame_head()` | 从 ringbuffer 搜索合法帧头。 | +| `chrg_north_frame_reset()` | 重置组帧上下文,并按参数决定是否释放缓存。 | +| `chrg_north_frame_append()` | 将接收字节追加到当前帧缓存。 | +| `chrg_north_frame_dispatch()` | 校验完整帧并分派到 Source/Sink 业务解析。 | +| `chrg_north_pickup_frame()` | 驱动“找帧头—收帧体—分派”状态机。 | +| `chrg_north_thread_entry()` | 北向接收线程主循环,从 UART ringbuffer 持续提取帧。 | +| `north_ch()` / `north_diag()` | FinSH 调试命令:切换通道、查看诊断计数。 | + +### `thread/chrg_south.c` + +| 函数 | 说明 | +|---|---| +| `chrg_south_switch_lv1()` / `chrg_south_switch_lv2_1()` / `chrg_south_switch_lv2_2()` | 控制级联选择线,完成南向通道硬件选通。 | +| `chrg_south_switch()` | 校验通道并组合调用三级选择函数。 | +| `chrg_south_send()` | 通过南向 TTY 发送 Modbus RTU 帧。 | +| `chrg_south_frame_head()` | 从 ringbuffer 识别目标地址和功能码。 | +| `chrg_south_frame_reset()` | 清理南向组帧状态和动态缓存。 | +| `chrg_south_frame_append()` | 追加南向接收字节。 | +| `chrg_south_frame_dispatch()` | 校验 CRC 后将响应投递给消费者。 | +| `chrg_south_pickup_frame()` | 推进南向组帧状态机。 | +| `chrg_south_thread_entry()` | 南向接收线程主循环。 | +| `south_ch()` | FinSH 南向通道调试命令。 | + +### `thread/chrg_roll_nor.c` + +| 函数 | 说明 | +|---|---| +| `sink_fill_pd_buf()` | 按协议类型把组号、电压、电流编码为 Sink 参数字节。 | +| `chrg_nor_source_stop()` | 分步骤停止 Source,关闭继电器并清理切换标志。 | +| `chrg_roll_nor_sw_uart()` | 在指定通道选择 Sink 或 Source UART。 | +| `chrg_roll_nor_sink_pro_set()` | 组装并发送 Sink 快充协议设置。 | +| `chrg_roll_nor_source_pro_set()` | 按设置来源选择 Source 协议下发函数。 | +| `chrg_roll_nor_parse()` | 根据角色和子命令生成一次北向查询/设置动作。 | +| `chrg_roll_nor_cmd()` | 北向命令参数检查接口;当前仅做空指针检查。 | +| `chrg_roll_nor_set()` | 北向轮询核心状态机,推进每通道 Source/Sink 子步骤。 | +| `chrg_roll_nor_thread_entry()` | 轮询四个北向通道,等待响应并控制超时/重试。 | +| `sink()` / `source()` | FinSH 手工配置 Sink/Source 的调试入口。 | +| `chrg_trim_crc()` | 计算校准数据 CRC。 | +| `chrg_trim_set_volt()` / `chrg_trim_set_curr()` / `chrg_trim_set_source_curr()` | 写入电压、电流及 Source 电流校准值。 | +| `chrg_trim_set_zero()` | 执行零点校准设置。 | +| `chrg_roll_trim_set()` | 按校准类型分派具体校准函数。 | + +### `thread/chrg_roll_sou.c` + +| 函数 | 说明 | +|---|---| +| `chrg_sou_com_batch_submit()` | 提交一批南向连续写寄存器命令。 | +| `chrg_sou_com_batch_add_reg()` | 向当前批次追加通道、寄存器和值。 | +| `chrg_set_sou_reg()` | 提交单个异步寄存器写命令。 | +| `chrg_set_sou_reg_com()` | 将寄存器写入合并批次。 | +| `sou_com_batch_next()` | 在锁保护下取下一条批命令。 | +| `sou_com_batch_complete()` | 标记批命令完成并推进队列。 | +| `sou_com_batch_cancel()` | 取消指定通道批命令。 | +| `sou_com_batch_pending_locked()` | 判断锁内是否还有待处理批命令。 | +| `sou_single_command_finish()` | 结束单命令,恢复期望运行状态。 | +| `sou_com_response_matches()` | 判断响应是否匹配当前请求。 | +| `sou_msg_free()` | 释放南向邮箱消息。 | +| `sou_com_command_failed()` | 统一处理超时/协议错误和批次回滚。 | +| `chrg_roll_sou_thread_entry()` | 串行发送南向命令、等待响应并处理重试。 | +| `south_send()` / `south_run()` | FinSH 发送寄存器和启停调试命令。 | + +## 7. 函数说明:业务与协议 + +### `utils/chrg_sink.c` + +| 函数 | 说明 | +|---|---| +| `sink_get_vc()` / `sink_get_pd()` | 生成 Sink 电压电流/协议查询帧。 | +| `sink_set_cc_line()` / `sink_set_cc_lvl()` | 设置 CC 线选择和电平。 | +| `sink_set_pd()` | 编码并发送 Sink 快充协议参数。 | +| `chrg_sink_work_init()` | 初始化工作步骤并下发停止/安全继电器命令。 | +| `chrg_sink_work_volt_runing()` | 在电压满足条件时推进 RUNING 动作。 | +| `chrg_sink_nor_work_low()` | 处理持续低压状态并复位运行条件。 | +| `chrg_sink_nor_work_off()` | 执行 Sink 停机收尾。 | +| `chrg_sink_change_ON()` / `chrg_sink_SHOUT_ON()` | 控制换向和短接相关继电器步骤。 | +| `chrg_sink_work()` | 根据 `work_mode` 分派 Sink 工作状态。 | + +### `utils/chrg_source.c` + +| 函数组 | 说明 | +|---|---| +| `source_get_vc()` / `source_get_pd()` | 生成 Source 电参/协议查询帧。 | +| `source_set_cc_lvl()` / `source_set_cc_choose()` | 配置 Source CC 电平和选择方式。 | +| `parse_mulit_gear_One()` / `parse_power_single_gear()` | 解析单组固定档快充能力。 | +| `parse_pd_11group_pdo()` / `parse_power_pdo_gear()` | 解析 PDO/PPS 多档能力。 | +| `parse_mulit_gear_more()` / `parse_multi_gear_more()` | 解析多组多档协议能力。 | +| `parse_ufcs_gear_more()` / `parse_power_ufcs()` | 解析 UFCS 档位。 | +| `pd_fill_single_group()` | 通用单组协议参数编码。 | +| `source_set_pd()` | 按协议、电压和电流生成 Source 设置帧。 | +| `pd_fill_single_group_com()` | 按通信寄存器档位编码单组参数。 | +| `pd_fill_single_group_need_src_com()` / `pd_fill_single_group_no_src_com()` | 分别编码需要/不需要 Source 参数的协议。 | +| `pd_fill_single_group_FCP_AFC()` / `pd_fill_single_group_UFCS()` | 编码 FCP/AFC 和 UFCS 专用参数。 | +| `source_set_pd_com()` / `source_set_pd_new_com()` | 根据寄存器配置或完整通道状态下发 Source 协议。 | + +### `utils/chrg_eload.c` + +| 函数组 | 说明 | +|---|---| +| `chrg_eload_set_ymodem_resp()` / `chrg_eload_get_ymodem_resp()` / `chrg_eload_clear_ymodem_resp()` | 设置、读取和清除电子负载升级响应标志。 | +| `memcmp_8bytes()` | 比较固定 8 字节标识。 | +| `chrg_eload_read_regs()` / `chrg_eload_write_reg()` / `chrg_eload_write_More_reg()` | 生成电子负载 Modbus 读、单写和多写请求。 | +| `chrg_eload_send_ymodem_data()` | 转发 YModem 升级数据。 | +| `chrg_write_cv()` | 写入电子负载 CV 参数。 | +| `chrg_eload_send_relay()` | 下发电子负载继电器动作。 | +| `chrg_eload_refresh()` | 刷新电子负载测量状态。 | +| `chrg_eload_parse()` | 解析电子负载响应并更新通道数据。 | +| `snp_ch()` | 选择电子负载通道。 | +| `Show_Volt()` / `Show_Curr()` / `Show_Ripple()` / `Show_DZF()` | 计算或刷新对应测量显示项。 | +| `Sta_compute()` | 汇总测量状态。 | +| `LCD_SHOW()` | 将电子负载测量结果推送到 LCD。 | + +### `utils/chrg_north_pkg.c` 与 `utils/chrg_utils.c` + +| 函数 | 说明 | +|---|---| +| `chrg_north_pkg_encode()` / `chrg_north_pkg_decode()` | 编码和解码北向协议包,并校验长度/校验值。 | +| `mb_crc16()` | 计算 Modbus CRC16。 | +| `_crc8()` / `calc_crc8()` | 单字节推进及整段 CRC8 计算。 | +| `u16_to_u8v()` / `u16_to_u8v_com()` / `u8v_to_u16()` | 16 位整数与字节序列互转。 | +| `u32_to_u8v()` / `u8v_to_u32()` | 32 位整数与字节序列互转。 | +| `swap_u16()` / `swap_u32()` | 交换 16/32 位整数端序。 | + +## 8. 函数说明:上位机、LCD 与板级封装 + +### `thread/chrg_comm.c` + +| 函数组 | 说明 | +|---|---| +| `chrg_shadow_sync()` / `chrg_shadow_init()` | 同步/初始化 Modbus 寄存器影子数据。 | +| `mb_set_bits()` / `mb_get_bits()` | 在 Modbus 位缓冲区写入/读取指定位段。 | +| `mb_reg_input_cb()` / `mb_reg_holding_cb()` / `mb_reg_test_cb()` | 输入、保持和测试寄存器回调。 | +| `mb_ymodem_updata_cb()` | 处理升级寄存器数据。 | +| `mb_reg_coils_cb()` / `mb_reg_discrete_cb()` | 线圈与离散输入回调。 | +| `funcReadCoils()` / `funcReadDiscreteInputs()` / `funcReadHoldingRegister()` / `funcReadInputRegister()` | 实现对应 Modbus 读功能。 | +| `funcWriteCoil()` / `funcWriteHoldingRegister()` / `funcWriteMultipleCoils()` / `funcWriteMultipleHoldingRegister()` | 校验并执行对应写功能。 | +| `sink_proto_lookup()` / `src_proto_lookup()` / `src_type_lookup()` | 将寄存器枚举映射为内部协议/Source 类型。 | +| `parse_load_no_group()` / `parse_load_with_group()` | 解析无组号/带组号的负载协议参数。 | +| `chrg_com_source_on()` | 根据通信配置启动 Source 流程。 | +| `funcWriteProtoCal()` / `funcSinkTest()` / `funcTrimELoad()` | 处理协议校准、Sink 测试和电子负载校准命令。 | +| `funYmodemUpadata()` | 推进固件升级命令。 | +| `chrg_comm_parse_msg()` | 解析通信线程邮箱消息并分派功能码。 | +| `chrg_comm_updata_north()` | 将实时北向状态同步到寄存器区。 | +| `chrg_comm_thread_entry()` | 初始化 Modbus 从站并循环处理消息/状态同步。 | +| `chrg_mb_write_sink()` / `chrg_mb_write_src()` / `chrg_mb_write_sys()` | 将保持寄存器变化写入 Sink、Source 和系统状态。 | +| `chrg_sink_get_proto_diff()` | 比较目标与实际 Sink 协议参数。 | +| `chrg_sink_diff_send_reg()` | 仅发送存在差异的 Sink 寄存器。 | +| `set_ymodem_update_channel()` / `mb_error()` / `rs485snd()` | 分别设置升级通道、转换错误码和提供 RS485 调试命令。 | + +### `thread/chrg_lcd.c` + +| 函数组 | 说明 | +|---|---| +| `chrg_sou_work()` / `chrg_sou_stop()` | 启动/停止指定功率通道。 | +| `chrg_pro_set()` | 应用 LCD 输入的快充协议配置。 | +| `chrg_set_eload_sink()` / `chrg_set_eload_source()` | 配置电子负载的 Sink/Source 角色。 | +| `chrg_set_CV()` / `chrg_set_CC()` / `chrg_roll_set_cv()` | 设置 CV、CC 及轮询相关目标值。 | +| `chrg_lcd_parse()` | 解析 HMI 命令并修改对应通道配置。 | +| `chrg_lcd_send()` | 向 HMI 发送数据。 | +| `chrg_lcd_thread_entry()` | LCD 串口收发与周期刷新主循环。 | +| `lcdsend()` | FinSH LCD 原始发送调试命令。 | + +### `bsp/` + +| 模块 | 函数说明 | +|---|---| +| `chrg_fal.c` | `chrg_fal_read()`、`chrg_fal_write()`、`chrg_fal_erase()`、`chrg_fal_erase_all()` 封装分区访问;`chrg_fal_init()` 初始化 FAL。 | +| `chrg_gpio.c` | `chrg_gpio_mode_set()`、`chrg_gpio_get()`、`chrg_gpio_set()` 封装 GPIO;`chrg_gpios_init()` 初始化引脚。 | +| `chrg_rel.c` | `chrg_nor_sw_rel()` 控制北向继电器;`chrg_nor_rel_init()` 初始化;`rel_test()` 用于 shell 测试。 | +| `chrg_switch.c` | `chrg_switch_ch()` 驱动通道译码选择线。 | +| `chrg_vcom.c` | `chrg_vcom_init()` 初始化 USB 虚拟串口;`chrg_vcom_console_init()` 注册控制台。 | +| `chrg_wdt.c` | `chrg_wdt_feed()` 喂狗;`chrg_wdt_init()` 初始化并启动看门狗。 | +| `chrg_tmr.c` | `chrg_tmr_get_count()`、`chrg_tmr_start()`、`chrg_tmr_stop()`、`chrg_tmr_release()`、`chrg_tmr_open()` 管理软件秒定时器;`chrg_tmr_1s_callback()` 计时;`chrg_tmrs_init()` 初始化;`uptime()`、`sec_tmr()` 为 shell 命令。 | +| `chrg_led.c` | `chrg_led_get()`、`chrg_led_set()`、`chrg_led_toggle()`、`chrg_led_pulsing()`、`chrg_led_flashing()` 控制 LED;`chrg_led_timer_set_on()`、`chrg_led_timer_set_off()`、`chrg_led_timer_start()`、`chrg_led_timer_delete()`、`chrg_led_timer_timeout_callback()`、`chrg_led_show()` 管理闪烁时序;`chrg_leds_init()` 初始化。 | +| `chrg_tty.c` | `chrg_tty_send_comp_hook()`、`chrg_tty_recv_ind_hook()` 为驱动回调;`chrg_tty_cal_byte_tmo()`、`chrg_tty_mode_set()`、`chrg_tty_dev_open()`、`chrg_tty_config()` 配置串口;`chrg_tty_connect()`、`chrg_tty_disconn()`、`chrg_tty_destory()` 管理连接;`chrg_tty_set_recv_tmo()`、`chrg_tty_set_byte_tmo()` 设置超时;`chrg_tty_recv()`、`chrg_tty_send()`、`chrg_tty_break_recv()`、`chrg_tty_send_then_recv()` 完成阻塞式收发。 | + +## 9. Bootloader 函数级主链 + +```mermaid +flowchart TD + M["main()"] --> SI["System_Init()"] + M --> GPIO["MX_GPIO_Init() / MX_DMA_Init() / MX_USART_Init()"] + M --> AI["APP_Init()"] + AI --> DTI["DT_Init()"] + AI --> YI["ymodem_init()"] + AI --> FI["FM_Init()"] + M --> AR["APP_Running()"] + AR --> RD["recvDataHandler()"] + RD --> DPR["DT_PollingReceive()"] + RD --> YH["ymodemHandler()"] + YH --> BPD["boot_ProtocolDataHandle()"] + AR --> BCH["boot_CheckAndHandle()"] + BCH --> BCF["boot_CheckFirmware()"] + BCH --> BAU["boot_AutoUpdate()"] + BAU --> FMR["FM_ReadFirmwareHead()"] + BAU --> FMV["FM_VerifyFirmware()"] + BAU --> FMU["FM_UpdateToAPP()"] + BCH --> J["boot_JumpToAPP()"] +``` + +Boot 辅助模块职责:`firmware.c` 的 `FM_*` 函数负责分区头、擦写、CRC 校验、版本和搬运;`ymodem.c` 的 `ymodemHandler()`、`ymodemConfig()` 负责升级协议状态机;`transfer.c` 的 `DT_Init()`、`DT_Send()`、`DT_PollingReceive()` 负责串口帧传输;`drv_timer.c` 提供启动、重启、暂停、注销、处理和用户数据绑定;`bsp_uart.c`/`bsp_uart_port.c` 提供 UART、DMA、中断及接收完成回调。 + +## 10. 阅读与调试顺序 + +1. 启动异常先看 `chrg_thread_init()` 和各 `INIT_*_EXPORT` 初始化返回值。 +2. 北向无数据依次跟踪 `chrg_north_thread_entry()`、`chrg_north_frame_head()`、`chrg_north_pickup_frame()`、`chrg_north_frame_dispatch()`。 +3. Sink 不启动依次检查 `chrg_sink_parse()` 的电压/电流判定、`work_mode`、`chrg_sink_work()` 和 `chrg_set_sou_reg()`。 +4. 功率板命令未生效依次检查 `chrg_set_sou_reg()`、`chrg_roll_sou_thread_entry()`、`chrg_south_send()` 和南向响应匹配。 +5. 上位机写入未生效依次检查 Modbus `funcWrite*()`、`chrg_mb_write_*()`、共享状态和两个轮询线程。 diff --git a/rt-thread/components/fal/src/fal_rtt.c b/rt-thread/components/fal/src/fal_rtt.c index 9ae93ea..93c93ee 100644 --- a/rt-thread/components/fal/src/fal_rtt.c +++ b/rt-thread/components/fal/src/fal_rtt.c @@ -890,7 +890,7 @@ static void fal(rt_uint8_t argc, char **argv) { { if (write_data[index] != read_data[index]) { - rt_kprintf("%d %d %02x %02x.\n", i, index, write_data[index], read_data[index]); +// rt_kprintf("%d %d %02x %02x.\n", i, index, write_data[index], read_data[index]); } } diff --git a/rt-thread/components/legacy/usb/usbdevice/core/usbdevice_core.c b/rt-thread/components/legacy/usb/usbdevice/core/usbdevice_core.c index 015473f..ab76558 100644 --- a/rt-thread/components/legacy/usb/usbdevice/core/usbdevice_core.c +++ b/rt-thread/components/legacy/usb/usbdevice/core/usbdevice_core.c @@ -2237,6 +2237,7 @@ static struct rt_thread usb_thread; /* internal of the message queue: every message is associated with a pointer, * so in order to recveive USBD_MQ_MAX_MSG messages, we have to allocate more * than USBD_MQ_MSG_SZ*USBD_MQ_MAX_MSG memory. */ +rt_align(RT_ALIGN_SIZE) static rt_uint8_t usb_mq_pool[(USBD_MQ_MSG_SZ+sizeof(void*))*USBD_MQ_MAX_MSG]; /**