/****************************************************************************** 版权所有: 文件名称: xdljd.c 文件版本: 01.01 创建作者: sunxi 创建日期: 2016-12-22 功能说明: 小电流接地检测 其它说明: !!!!!在不了解整个原理的情况下,不要轻易修改此文件中的算法!!!!!! 修改记录: */ /*------------------------------- 头文件 -------------------------------------- */ #include "head.h" #ifdef XDL_ZT /*------------------------------- 宏定义 -------------------------------------- */ #define XDL_ZERO_LEN (ADC_REC_SAMPLE/4) #define XDL_SEG_NUM 10 enum { XDL_ST_IDLE, XDL_ST_QT, XDL_ST_END, }; /*------------------------------ 类型结构 ------------------------------------- */ struct point { s32 v; u32 p; }; struct segment { u32 bgn; // 段开始 u32 end; // 段结束 u32 acc; // 累加值 u32 max_p; // 最大值出现的位置 u32 max_v; // 最大值的绝对值 s32 dir; // 方向 }; struct xdl { // 状态 int st; unsigned long qd_us0; unsigned long dz_us0; unsigned long gj_us0; unsigned long dz_fh_us0; // unsigned long gj_fh_us0; // 动作 int dz; // 动作值记忆 int dz_i0[2]; int dz_u0[2]; int dz_Ia[2]; int dz_Ib[2]; int dz_Ic[2]; // 零偏 s16 zero_sum; s16 zero_cnt; }; struct i0_argument { s16 ref_v_set; bool scale_is_1A; }; /*------------------------------ 全局变量 ------------------------------------- */ // 零偏 //s16 g_xdl_zero_sum[SWITCH_NUM_MAX]; // 零偏和 //s16 g_xdl_zero_cnt[SWITCH_NUM_MAX]; // 零偏计数 //int g_xdl_qd[SWITCH_NUM_MAX]; // 小电流接地启动 //int g_xdl_dz[SWITCH_NUM_MAX]; // 小电流接地动作 //unsigned long g_xdl_qd_us0[SWITCH_NUM_MAX]; // 小电流接地启动时间 struct xdl g_xdl[SWITCH_NUM_MAX]; struct i0_argument g_i0_arg[SWITCH_NUM_MAX]; // 滤波后数据 s16 g_u0[XDL_SAMPLE_LEN]; s16 g_i0[XDL_SAMPLE_LEN]; // 原始的数据 s16 g_u0_raw[XDL_SAMPLE_LEN]; s16 g_i0_raw[XDL_SAMPLE_LEN]; // 小波处理的数据 float g_u0_dwt[XDL_SAMPLE_LEN]; float g_i0_dwt[XDL_SAMPLE_LEN]; struct segment g_u0_seg[XDL_SEG_NUM]; struct segment g_i0_seg[XDL_SEG_NUM]; // 小电流接地零压零流定时器 #ifdef RECORD_SW_U0TB TIMERELAY g_tXDLTime_U0TB[SWITCH_NUM_MAX]; //零压突变定时器 #else TIMERELAY g_tXDLTime_U[2]; #endif TIMERELAY g_tXDLTime_I[SWITCH_NUM_MAX]; int g_cur_direction_adaptaion = 0; //临时在此定义 EWen // TODO 方向自适应处理 int XDLCHZWC = 0; char * g_xdl_rcd_a[] = { "U0_原始", "U0_滤波", "U0_区域", "U0_最大", "I0_原始", "I0_滤波", "I0_区域", "I0_最大", }; #define XDL_RCD_A_NUM (sizeof(g_xdl_rcd_a)/sizeof(char *)) const char * g_line_type[] = { "界外动作:", "界内动作:", }; const char * g_tbl_type[] = { "小电流突变", "零压突变", }; /*------------------------------ 函数声明 ------------------------------------- */ #if 0 static void xdl_ar_dataset(void); static void xdl_ar_recharge(DWORD dStep); static void xdl_ar_disable(void); static void xdl_ar_start(DWORD dStep); static void xdl_ar_logic(DWORD dStep); static void xdl_ar_reset(DWORD dStep); static int xdl_ar_check(DWORD mode); // 检同期 #endif static u32 xdl_get_LLTB_waves(int sw); /*------------------------------ 外部函数 ------------------------------------- 外部函数供其它实体文件引用,必须仔细检查传入参数的合法性. */ #if 0 /************************************************************************** 函数名称: xdl_auto_reclose 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸功能块 输入参数: 输出参数: 返回值: ***************************************************************************/ void xdl_auto_reclose(DWORD dStep) { xdl_ar_dataset(); // 小电流重合闸数据准备 xdl_ar_recharge(dStep); // 小电流重合闸充电 xdl_ar_disable(); // 小电流重合闸闭锁 xdl_ar_start(dStep); // 小电流重合闸启动 xdl_ar_logic(dStep); // 小电流重合闸逻辑 xdl_ar_reset(dStep); // 小电流重合闸复归 return; } /************************************************************************** 函数名称: xdl_ac_check 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸加速块 输入参数: 输出参数: 返回值: ***************************************************************************/ void xdl_ac_check(int sw,DWORD dStep) { static bool bFZOut, bQD=false; bool bQDD; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW=&pR->tXDLCHZ; TSWST *pSWST =&pR->tSWST; TSETSW *pSet = &pRunSet->tSwSet[sw]; /***********启动 **********************************/ // 开放后加速允许有效延时3S展宽 RunTR(&pSW->tTXDLCHJLY_HJS, XDLCHZWC, dStep); // 时间到 if (pSW->tTXDLCHJLY_HJS.boolTrip) { XDLCHZWC =0; ResetTR(&pSW->tTXDLCHJLY_HJS); if (bFZOut) { soe_record_ev(EV_OVERU0+sw*EV_SW_NUM, 0, 0,0,0 ); bFZOut = 0; } } if(pRunSet->tSwSet[sw].bTT_Power_v2) { bQDD = OverRelay(g_ui[PUB_AC_U02].m2[0],pRunSet->dOverU0,pRunSet->dOverU0_fh,bQD); } else { bQDD = OverRelay(g_ui[PUB_AC_U01].m2[0],pRunSet->dOverU0,pRunSet->dOverU0_fh,bQD); } bQD = BH_ALL_EN(sw) && pSet->bTT_xdltz//投退字 && pSWST->uSWST.bFlag.bHZWZ //在合闸位置 && bQDD && XDLCHZWC && !pSW->tTXDLCHJLY_HJS.boolTrip; /************事件记录及出口****************************/ if(bQD) { if (!bFZOut) // 保证只动作一次未进行跳闸失败检测 { DWORD U0; sw_do(sw,SW_DO_BHT,SW_DO_TYPE_ON); if(pRunSet->tSwSet[sw].bTT_Power_v2) { U0 = _Mul_Div_U(sqrt_32fix(g_ui[PUB_AC_U02].m2[0]), 256, g_ui[PUB_AC_U02].m2_factor_k); } else { U0 = _Mul_Div_U(sqrt_32fix(g_ui[PUB_AC_U01].m2[0]), 256, g_ui[PUB_AC_U01].m2_factor_k); } soe_record_ev( EV_OVERU0+sw*EV_SW_NUM, 1, U0,0,0 ); bFZOut = 1; rt_printf("开关%d小电流检零压跳闸\r\n", sw); } } } /************************************************************************** 函数名称: xdl_ar_dataset 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸数据准备 输入参数: 输出参数: 返回值: ***************************************************************************/ static void xdl_ar_dataset(void) { u32 sw = g_protect.sw; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW =&pR->tXDLCHZ; InitTR_Time(&pSW->tTXDLCHJD_ARDelay, 10*T_1s, 0 ); // 小电流重合闸延时 InitTR_Time(&pSW->tTXDLCHFAIL_TW, 11*T_1s, 0 ); // 小电流重合闸延时 } /************************************************************************** 函数名称: xdl_ar_disable 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸闭锁 输入参数: 输出参数: 返回值: ***************************************************************************/ static void xdl_ar_disable(void) { u32 sw = g_protect.sw; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW =&pR->tXDLCHZ; pSW->sta.bFlag.bBSCH = pR->tOC[OC_LX1].sta.bFlag.bBSCH // 零序1闭锁重合闸 || pR->tOC[OC_LX2].sta.bFlag.bBSCH // 零序2闭锁重合闸 || pR->tOC[OC_LX3].sta.bFlag.bBSCH // 零序3闭锁重合闸 || pR->tLostVot.uLostVot.bFlag.bTz // 失压跳闸闭锁重合 || pR->tXXTZ.uZOV.bFlag.bTz || pR->tU0TZ.uZOV.bFlag.bTz ; // 零压分闸闭锁重合 } /************************************************************************** 函数名称: xdl_ar_recharge 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸充电 输入参数: 输出参数: 返回值: ***************************************************************************/ static void xdl_ar_recharge(DWORD dStep) { u32 sw = g_protect.sw; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW =&pR->tXDLCHZ; TSETSW *pSet = &pRunSet->tSwSet[sw]; bool bHCD1,bHCD2,bHCD3,bHCD4; // 放电条件 RunTR(&pSW->tTXDLCHFAIL_TW, pR->tSWST.uSWST.bFlag.bTZWZ, dStep); // 三相跳位延时 bHCD1 = pSW->tTXDLCHFAIL_TW.boolTrip; // 放电标志1:三相跳位时间到 bHCD2 = (pR->tSWST.uSWST.bFlag.bTZWZ || pR->uBHDZ.bFlag.bBHT) // 此处小电流动作标志或者有保护动作 && (!pSW->sta.bFlag.bCDWC); // 放电标志2:充电未完成,小电流动作 bHCD3 = pR->uRmtSW.bHandTz // 手动跳闸 || pR->uRmtSW.bYTExecute // 遥控跳闸 || pR->uBHDZ.bFlag.bTZSB // 跳闸失败 || pR->tSWST.uSWST.bFlag.bTWYL // 跳位有流 || pR->tSWST.uSWST.bFlag.bWCN // 弹簧未储能告警 || pSW->sta.bFlag.bBSCH // 重合闸闭锁条件 || pSW->sta.bFlag.bCHFD; // 重合闸动作放电 pSW->sta.bFlag.bFDBZ = (!BH_ALL_EN(sw)) // 总保护未投入 || (!pSet->bTT_xdltz) // 小电流跳闸: 未投入 || (!pR->run_stu.chz) // 重合闸硬压板未投入 || bHCD1 || bHCD2 || bHCD3; // 充电条件 bHCD4 = !pSW->sta.bFlag.bFDBZ // 重合放电标 && (((!pR->uBHQD.bFlag.bZQD) && pR->tSWST.uSWST.bFlag.bHZWZ ) // 非保护启动 && 合闸位置 || pSW->sta.bFlag.bCDWC); // 充电完成自保持 RunTR(&pSW->tTXDLCHCD15s0s, bHCD4, dStep); // 充电15秒 pSW->sta.bFlag.bCDWC = pSW->tTXDLCHCD15s0s.boolTrip; if(pSW->sta.bFlag.bCDWC) { // TODO:液晶应增加小电流重合闸标志 if((g_disp_flag.xdl_chz_cd & (1<sta.bFlag.bXDLDZ = false; pR->tXDLCHZ.sta.wfFlag=0; pR->tXDLCHZ.wAR_ActTimes=0; ResetTR(&pR->tXDLCHZ.tTXDLCHJD_HJS); rt_printf_time("开关%02d小电流放电完成!\r\n",sw+1); } } } /************************************************************************** 函数名称: xdl_ar_start 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸启动 输入参数: 输出参数: 返回值: ***************************************************************************/ static void xdl_ar_start(DWORD dStep) { bool bCHQD_CHQD; // 三相启动重合闸 u32 sw = g_protect.sw; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW =&pR->tXDLCHZ; TSETSW *pSet = &pRunSet->tSwSet[sw]; bool bCQD1,bCQD2,bCQD3,bCQD4,bCQD5,bCQD6; // 不对应启动 // 开关位置从合闸位置切换至分闸位置完成后,保留50ms展宽 RunTR(&pSW->tTXDLCHQDHW0ms50ms, pR->tSWST.uSWST.bFlag.bHZWZ, dStep); bCQD1 = pSW->tTXDLCHQDHW0ms50ms.boolTrip; // 偷跳条件准备 bCQD2 = pSet->bTT_TTCH && bCQD1 && (!pR->tSWST.uSWST.bFlag.bHZWZ); // 保护启动 // 保护动作后至保护动作返回,保留50ms展宽 RunTR(&pSW->tTXDLCHQD0ms50ms, pR->uBHDZ.bFlag.bBHT, dStep); bCQD3 = pSW->tTXDLCHQD0ms50ms.boolTrip; // 重合已启动或保护启动,用于闭锁不对应启动 bCQD4 = pSW->sta.bFlag.bCHQD ||bCQD3; // 此处保护跳信号,是实时信号,即保护已返回 bCQD5 = bCQD3 && (!pR->uBHDZ.bFlag.bBHTrip); // 重合启动自保持 // 重合闸出口条件,用于防止重合闸合闸过程中,TWJ没有返回时,再次误发启动重合闸报文。 bCQD6 = pSW->sta.bFlag.bCDWC // 重合充电完成 && pSW->sta.bFlag.bCHQD // 三相启动重合闸 && (!pSW->sta.bFlag.bCHCK); // 重合闸出口,取反 // 偷跳启动重合闸 pSW->sta.bFlag.bTTQD = pSW->sta.bFlag.bCDWC // 重合闸充电完成 && bCQD2 // 偷跳判断完成 && (!bCQD4); // 重合未启动,无保护跳过程 // 重合闸检同期 // bCQD7 =xdl_ar_check(pSet->dCKModes); // 三跳启动重合闸 #if 0 pSW->sta.bFlag.bTLQD = pSW->sta.bFlag.bCDWC // 重合闸充电完成 &&bCQD7 // 湖南专检重合闸检同期 &&pSW->sta.bFlag.bXDLDZ // && (!pSW->sta.bFlag.bCHQD); // 三相启动重合闸未启动 #else pSW->sta.bFlag.bTLQD = pSW->sta.bFlag.bCDWC // 重合闸充电完成 &&pSW->sta.bFlag.bXDLDZ // && (!pSW->sta.bFlag.bCHQD); // 三相启动重合闸未启动 #endif // 三相启动重合闸 bCHQD_CHQD = pSW->sta.bFlag.bTTQD // 偷跳启动重合闸 || pSW->sta.bFlag.bTLQD // 三跳启动重合闸 || bCQD6; // 三相启动重合闸 if( bCHQD_CHQD ) { if( !pSW->sta.bFlag.bCHQD ) //重合开出标尚未置位 { pSW->sta.bFlag.bCHQD = true; //此标志会一直维持到重合闸出口或充电标志放电 pSW->sta.bFlag.bQDFG=true; //整组复归时有效 soe_record_ev(EV_AR_START+sw*EV_SW_NUM, 1, 0,0,0);//重合启动SOE //rcd_start(sw,RECORD_TYPE_CHZ, RECORD_LEN_TZQD); //录波类型:跳闸类 } } else { if( pSW->sta.bFlag.bCHQD ) { pSW->sta.bFlag.bCHQD = false; soe_record_ev(EV_AR_START+sw*EV_SW_NUM,0 ,0,0,0); } } } /************************************************************************** 函数名称: xdl_ar_logic 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸逻辑 输入参数: 输出参数: 返回值: ***************************************************************************/ static void xdl_ar_logic(DWORD dStep) { bool bWCN; u32 sw = g_protect.sw; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW =&pR->tXDLCHZ; TSETSW *pSet = &pRunSet->tSwSet[sw]; bool bHZL1,bHZL2,bHZL3,bHZL4,bHZL5,bHZL6,bHZL8,bHZL9; // 重合闸条件准备 bHZL1 = BH_ALL_EN(sw) // 总投退 && pSet->bTT_xdltz // 小电流跳闸投入 && pR->run_stu.chz // 重合闸硬压板投入 && pSW->sta.bFlag.bCHQD // 三相启动重合闸 && pSW->sta.bFlag.bCDWC; // 重合闸充电完成 RunTR(&pSW->tTXDLCHJD_ARDelay, bHZL1, dStep); //重合延时 bHZL2 = pSW->tTXDLCHJD_ARDelay.boolTrip; // 弹簧未储能下降沿展宽30ms bWCN = pR->run_stu.wcn && pSet->bTT_WCN; RunTR(&pSW->tTXDLCHJD_0ms30ms, bWCN, dStep); bHZL3 = pSW->tTXDLCHJD_0ms30ms.boolTrip; // 重合闸检同期 bHZL9 =xdl_ar_check(pSet->dCKModes); // 重合闸条件准备好,弹簧储能OK,重合闸出口 pSW->sta.bFlag.bCHCK = (!bHZL3) && bHZL2 && bHZL9; // 重合闸开出自保持 bHZL4 = pSW->sta.bFlag.bHz || pSW->sta.bFlag.bCHCK; // 重合开出持续200ms RunTR(&pSW->tTXDLCHJD_200ms0ms, bHZL4, dStep); bHZL5 = pSW->tTXDLCHJD_200ms0ms.boolTrip; // 重合闸开出,经过200ms后开出解除 pSW->sta.bFlag.bHz =bHZL4 && (!bHZL5); // 重合闸指令发出后,且开关已经跳闸完成 bHZL6 = pSW->sta.bFlag.bHz || pSW->sta.bFlag.bHZL7; pSW->sta.bFlag.bHZL7 = pR->tSWST.uSWST.bFlag.bTZWZ && bHZL6; // 开放后加速允许有效延时200ms展宽 RunTR(&pSW->tTXDLCHJD_HJS, pSW->sta.bFlag.bHZL7, dStep); bHZL8 = pSW->tTXDLCHJD_HJS.boolTrip; pSW->sta.bFlag.bCHJS = pSW->tTXDLCHJD_HJS.boolTrip && (!pR->tSWST.uSWST.bFlag.bTZWZ); //出口及发信 if(pSW->sta.bFlag.bHz) { if(!pSW->sta.bFlag.bEvent) //重合开出标尚未置位 { // 检零压后加速标识 XDLCHZWC = 1; //亮重合动作灯 pSW->sta.bFlag.bCHZLed = true; pSW->wAR_ActTimes++; //重合动作次数 pSW->sta.bFlag.bEvent = true; //pSW->uCHFG.wfFlag=0; //每次重合出口后,清重合复归标志 修改时注意,此处需对标志字特殊处理 pSW->sta.bFlag.bCHDZ=false; //重合闸动作 pSW->sta.bFlag.bBYSD=false; //重合闸闭锁时间到 pSW->sta.bFlag.bCHFD=false; //重合闸动作放电 pSW->sta.bFlag.bCHBS=false; //重合闸闭锁 pSW->sta.bFlag.bCK=false; //放电条件1 if(pSW->wAR_ActTimes==1) { soe_record_ev(EV_AR1+sw*EV_SW_NUM, 1, 0,0,0); //重合动作SOE } else if(pSW->wAR_ActTimes==2) { soe_record_ev(EV_AR2+sw*EV_SW_NUM, 1, 0,0,0); //重合动作SOE } else { soe_record_ev(EV_AR3+sw*EV_SW_NUM, 1, 0,0,0); //重合动作SOE } rcd_start(sw,RECORD_TYPE_CHZ,RECORD_LEN_TZQD); ResetTR(&pSW->tTXDLCHFAIL_TW); // 重合出口后,重新复归时间继电器 ResetTR(&pSW->tTXDLCHOKTime); //重合成功时间继电器,在重合出口后重新计数 ResetTR(&pSW->tTXDLCHBSDelay); //闭锁重合延时继电器重新计数 } } else { if( pSW->sta.bFlag.bEvent ) //重合开出标尚未置位 { pSW->sta.bFlag.bEvent = false; if(soe_check(EV_AR1+sw*EV_SW_NUM)) { soe_record_ev(EV_AR1+sw*EV_SW_NUM, 0, 0,0,0 ); //重合1动作SOE返回 } if(soe_check(EV_AR2+sw*EV_SW_NUM)) { soe_record_ev(EV_AR2+sw*EV_SW_NUM, 0, 0,0,0 ); //重合2动作SOE返回 } if(soe_check(EV_AR3+sw*EV_SW_NUM)) { soe_record_ev(EV_AR3+sw*EV_SW_NUM, 0, 0,0,0 ); //重合3动作SOE返回 } } } } /************************************************************************** 函数名称: xdl_ar_reset 函数版本:1.00 作者: 赵海洋 创建日期:2021.6.10 函数功能说明:小电流重合闸复归 输入参数: 输出参数: 返回值: ***************************************************************************/ static void xdl_ar_reset(DWORD dStep) { u32 sw = g_protect.sw; TRELAY_T *pR=&g_tRelay[sw]; TXDLCH_T *pSW =&pR->tXDLCHZ; bool bFD1; // 重合闸动作后,即启动重合闸闭锁时间,在此时间内,有保护动作即闭锁重合闸 // 重合闸出口且重合闸动作次数<整定动作次数,重合闸闭锁 pSW->sta.bFlag.bCHDZ = (pSW->sta.bFlag.bCHCK && (pSW->wAR_ActTimes<1)) || pSW->sta.bFlag.bCHBS; // 重合闸闭锁延时,从重合闸动作开始统计 RunTR(&pSW->tTXDLCHBSDelay, pSW->sta.bFlag.bCHDZ, dStep); pSW->sta.bFlag.bBYSD = pSW->tTXDLCHBSDelay.boolTrip; pSW->sta.bFlag.bCHBS = (pSW->wAR_ActTimes<1) // 未到最后一次重合 && pSW->sta.bFlag.bCHDZ // 一次重合闸动作 && (!pSW->sta.bFlag.bBYSD); // 重合闸闭锁时间未到 // 重合闸出口后,经过重合闸确认时间,无保护动作,即放电,完成该次重合过程 // 如果在确认时间内,重合闭锁时间外,有保护动作,即进入下一计数重合 pSW->sta.bFlag.bCK = ((!pR->uBHDZ.bFlag.bBHT) && (pSW->sta.bFlag.bCHCK || pSW->sta.bFlag.bCK)); //重合出口及自保持 RunTR(&pSW->tTXDLCHOKTime, pSW->sta.bFlag.bCK, dStep); bFD1 = pSW->tTXDLCHOKTime.boolTrip; // 重合闸闭锁时间内,有保护动作,放电 // 重合闸次数已到最大,放电 // 重合成功,重合闸确认时间内无保护动作,放电 pSW->sta.bFlag.bCHFD = (pR->uBHDZ.bFlag.bBHT && pSW->sta.bFlag.bCHBS) || (pSW->sta.bFlag.bCHCK && (pSW->wAR_ActTimes>=1)) || bFD1; // 整组复归条件:(1)重合闸放电(2)重合已启动,但充电标志已清零 if(pSW->sta.bFlag.bCHFD || (!pSW->sta.bFlag.bCDWC && pSW->sta.bFlag.bQDFG)) { pSW->sta.bFlag.bQDFG = false; } } /************************************************************************** 函数名称: xdl_ar_check 函数版本:1.00 函数版本:1.00 作者: 赵海洋 创建日期:2021.06.10 函数功能说明:重合闸检同期 0:无检定;1:检一侧有压一侧无压;2:检同期;3:检一侧有压一侧无压+检同期 输入参数: 输出参数: 返回值: ***************************************************************************/ #if 1 static int xdl_ar_check(DWORD mode) { // 0:无检定;1:检一侧有压一侧无压;2:检同期;3:检一侧有压一侧无压+检同期 u32 sw = g_protect.sw; TSETSW *pSet = &pRunSet->tSwSet[sw]; TRELAY_T *pR=&g_tRelay[sw]; DWORD deltaU1=0,deltaU2=0,deltaU3=0; bool Volt,Volt1,Volt2,Volt3,angle1,bFEQ=false; bool bY1 ,bY2 , sp , bLock = false; int ret = 0; bY1= ((!g_run_stu.pt1yy) && g_run_stu.pt2yy); bY2= (g_run_stu.pt1yy && (!g_run_stu.pt2yy)); bLock = bY1 || bY2; switch (mode) { case 0: ret = 1 ; break; case 1: /* 检一侧有压一侧无压 */ ret = bLock; break; case 2: /* 检同期 */ // 分别取出两侧各线电压压差 deltaU1 = _AbsL(g_ui[PUB_AC_UAB1].fz - g_ui[PUB_AC_UAB2].fz); deltaU2 = _AbsL(g_ui[PUB_AC_UBC1].fz - g_ui[PUB_AC_UBC2].fz); deltaU3 = _AbsL(g_ui[PUB_AC_UCA1].fz - g_ui[PUB_AC_UCA2].fz); // 角差与定值比对 angle1 = SynAngle(g_ui[PUB_AC_UAB1].p, g_ui[PUB_AC_UAB2].p,pSet->dTQ_ANGLE_FZ); // 频差与定值比对 bFEQ = SynFrequency(g_sw_pub.ac_in[PUB_AC_IN_F1],g_sw_pub.ac_in[PUB_AC_IN_F2],pSet->dTQ_FREQ_FZ); // 压差与定值比对 Volt1 = LowRelay(deltaU1, pSet->dTQ_U_FZ, pSet->dTQ_U_FZ_fh, false); Volt2 = LowRelay(deltaU2, pSet->dTQ_U_FZ, pSet->dTQ_U_FZ_fh, false); Volt3 = LowRelay(deltaU3, pSet->dTQ_U_FZ, pSet->dTQ_U_FZ_fh, false); Volt = Volt1&&Volt2&&Volt3; sp = Volt&&angle1&&bFEQ&&(!pR->tSWST.uSWST.bFlag.bDIHW); ret = sp; break; case 3: /* 检一侧有压一侧无压+检同期 */ // 分别取出两侧各线电压压差 deltaU1 = _AbsL(g_ui[PUB_AC_UAB1].fz - g_ui[PUB_AC_UAB2].fz); deltaU2 = _AbsL(g_ui[PUB_AC_UBC1].fz - g_ui[PUB_AC_UBC2].fz); deltaU3 = _AbsL(g_ui[PUB_AC_UCA1].fz - g_ui[PUB_AC_UCA2].fz); // 角差与定值比对 angle1 = SynAngle(g_ui[PUB_AC_UAB1].p, g_ui[PUB_AC_UAB2].p,pSet->dTQ_ANGLE_FZ); // 频差与定值比对 bFEQ = SynFrequency(g_sw_pub.ac_in[PUB_AC_IN_F1],g_sw_pub.ac_in[PUB_AC_IN_F2],pSet->dTQ_FREQ_FZ); // 压差与定值比对 Volt1 = LowRelay(deltaU1, pSet->dTQ_U_FZ, pSet->dTQ_U_FZ_fh, false); Volt2 = LowRelay(deltaU2, pSet->dTQ_U_FZ, pSet->dTQ_U_FZ_fh, false); Volt3 = LowRelay(deltaU3, pSet->dTQ_U_FZ, pSet->dTQ_U_FZ_fh, false); Volt = Volt1&&Volt2&&Volt3; sp = Volt&&angle1&&bFEQ&&(!pR->tSWST.uSWST.bFlag.bDIHW); if (bLock) { ret =1; } else if ((!bLock) && sp) { ret =1; } else { ret = 0; } // ret = (!bLock) && sp; break; default: ret = 0 ; break; } return ret; } #endif #endif int xdl_get_chan_cnt(void) { return XDL_RCD_A_NUM; } char * xdl_get_chan_name(int chan_no) { if(chan_no >= XDL_RCD_A_NUM) { return NULL; } return g_xdl_rcd_a[chan_no]; } // 小电流录波点值获取函数 s16 xdl_rcd_a(int chn,int idx) { int i; //s16 v; switch(chn) { case 0: return g_u0_raw[idx]; case 1: return g_u0[idx]; case 2: for(i=0;i=g_u0_seg[i].bgn && idx <= g_u0_seg[i].end) { return g_u0[idx]; } #else if(idx == (g_u0_seg[i].end + g_u0_seg[i].bgn)/2) { v = g_u0_seg[i].acc/2; v = v > 32767 ? 32767 : v; return v*g_u0_seg[i].dir; } else if(idx>=g_u0_seg[i].bgn && idx <= g_u0_seg[i].end) { v = g_u0_seg[i].acc/4; //v = g_u0_seg[i].acc/(g_u0_seg[i].end - g_u0_seg[i].bgn +1); v = v > 32767 ? 32767 : v; return v*g_u0_seg[i].dir; } #endif } } return 0; case 3: for(i=0;i=g_i0_seg[i].bgn && idx <= g_i0_seg[i].end) { return g_i0[idx]; } #else if(idx == (g_i0_seg[i].end + g_i0_seg[i].bgn)/2) { v = g_i0_seg[i].acc/2; v = v > 32767 ? 32767 : v; return v*g_i0_seg[i].dir; } else if(idx>=g_i0_seg[i].bgn && idx <= g_i0_seg[i].end) { v = g_i0_seg[i].acc/4; // v = g_i0_seg[i].acc/(g_i0_seg[i].end - g_i0_seg[i].bgn +1); v = v > 32767 ? 32767 : v; return v*g_i0_seg[i].dir; } #endif } } return 0; case 7: for(i=0;i0 && v1<0) || (v0<0 && v1>0)) { return 1; } return 0; } // 方向相同:两个数同时为3个方向值中的一个,包含0 // 主要用在判断区域是否完整。 static inline int _xdl_dir_equ(int v0,int v1) { if((v0>0 && v1>0) || (v0<0 && v1<0) || (v0==0 && v1==0)) { return 1; } return 0; } // 区域更新,保留最大值最大的几个区域 void _xdl_seg_update(struct segment * seg_a,struct segment * seg,struct segment * u_seg, uint *updateNum) { int i,j; int space_num = 0; // s32 u_end = u_seg->end + 4; //扩大4个点 if(seg->dir == 0) { return; } //上下两部分:重点->上半部主要处理以“电压段范围内”索引的电流区域 // 下半部为大于“电压段范围内”索引的电压区域,通常不会用到 // 如果u_end < seg->end,当u_seg->bgn == seg->bgn,则判定为稳态触发,需放入上半部 // if((u_end < seg->end) && (u_seg->bgn != seg->bgn)) // space_num = XDL_SEG_NUM>>1; // else // space_num = 0; for(i=space_num;imax_v > seg_a[i].max_v) { if( (*updateNum)++ >= XDL_SEG_NUM) /**< 更新满XDL_SEG_NUM后,不再更新 */ { return; } for(j=XDL_SEG_NUM-1;j>i;j--) { seg_a[j] = seg_a[j-1]; } seg_a[i] = *seg; return; } } return; } // 区域排序,根据时间顺序排序 void _xdl_seg_sort(struct segment * seg_a) { u32 i,j,index; struct segment s; for(i=0;ibgn -1]-zero,dot[seg->bgn]-zero)) { return 0; } if(_xdl_dir_equ(dot[seg->end]-zero,dot[seg->end+1]-zero)) { return 0; } return 1; } // 如果mode为1,扩大区域范围(0算在范围内),电压使用。 int _xdl_seg_search(s16 *dot,u32 bgn,u32 end,struct segment * seg,int mode) { s32 v,dir; u32 i; memset(seg,0,sizeof(*seg)); if(mode) { seg->bgn = bgn; } for(i=bgn;i<=end;i++) { // 得到值和方向 v = dot[i]; if(v) { dir = v > 0 ? 1 : -1; } else { dir = 0; } v = abs(v); // 初始化区间 if(seg->dir == 0) { if(dir) { seg->dir = dir; seg->acc += v; seg->max_v = v; if(mode == 0) { seg->bgn = i; } seg->end = i; seg->max_p = i; } } // 当前区间处理 else { // 反向,当前区间结束 if(mode) { if(dir == -seg->dir) { return 1; } } else { if(dir != seg->dir) { return 1; } } // 同相,扩大区间 seg->acc += v; seg->end = i; if(v > seg->max_v) { seg->max_v = v; seg->max_p = i; } } } seg->dir = 0; return 0; } // 微分增速段搜索,微分0在前面 int _xdl_seg_search_ds_f(s16 *dot,u32 u_set,u32 bgn,u32 end,struct segment * seg) { s32 v,dir,dir_old; u32 i; s32 next_v; s32 zct = 0, zct_pre = 0, zct_i = 0; memset(seg,0,sizeof(*seg)); dir = 0; dir_old = 0; seg->bgn = bgn; for(i=bgn;i<=end;i++) { // 得到值和方向 dir_old= dir; v = dot[i]-dot[i-1]; if(v) { dir = v > 0 ? 1 : -1; } else { dir = 0; } v = abs(v); // 初始化区间 if(seg->dir == 0) { if(dir) { seg->acc = v; seg->dir = dir; seg->end = i; zct_i = i; //by ygl 电压需要逼近突变点,第一个微分不为0的点 } zct_pre = (dot[i] > 0 ? 1 : -1); } // 当前区间处理 else { /* 寻找过零点,为了开始平缓转突变的数据*/ zct = (dot[i] > 0 ? 1 : -1); if(zct != zct_pre){ zct_i = i; zct_pre = zct; } if((dir == -seg->dir)|| ((dir == 0) && (dir_old==0))) { if (i < end) { next_v = (dot[i+1]-dot[i]) > 0? 1:-1; //ygl 再往下推算一个点的方向 if(dir == -next_v) //如果和当前方向不一致,则判为瞬变 { dir = next_v; //保存方向 注意此时:next_v == seg->dir seg->acc += v; seg->end = i; continue; //继续搜索下一个点 } } // 退一步,退回本区域内 i--; seg->max_p = i; seg->max_v = abs(dot[i]); // 最大值计算忽略末尾微分为0的点 while(i>seg->bgn) { if(dot[i] != dot[i-1]) { break; } i--; } break; } // 同相,扩大区间 seg->acc += v; seg->end = i; } } if(seg->dir){ if( abs(dot[seg->max_p] - dot[seg->bgn-1]) < u_set) { seg->dir = 0; } /* 开始点大于定值,则电压差值必须是开始点的4倍以上。否则清零 */ v = abs(dot[seg->bgn-1]); if(v > u_set && (v*4 > abs(dot[seg->max_p] - dot[seg->bgn-1]))) { seg->dir = 0; } /*如果方向有效,则调整电流开始点为“过零点”或“第一个微分不为0的点”开始*/ if(zct_i != 0 && seg->dir != 0){ seg->bgn = zct_i-1; // if(zct == zct_pre) //如果没产生过零点 // seg->bgn -= 1; } return 1; } return 0; } // 微分增速段搜索,微分0在后面 int _xdl_seg_search_ds_r(s16 *dot,u32 i_set,u32 bgn,u32 end,struct segment * seg,struct segment * u_seg) { s32 v,dir,dir_old,b_add; u32 i; s32 next_v = 0; s32 U_mean = 0; //对应零压均值 memset(seg,0,sizeof(*seg)); u_seg->acc = 0; dir = 0; dir_old = 0; b_add = 0; for(i=bgn;i<=end;i++) { if(u_seg->acc) // 有首次后,累加 u_seg->acc += g_u0[i-1]; // 得到值和方向 dir_old= dir; v = dot[i]-dot[i-1]; if(v) { dir = v > 0 ? 1 : -1; } else { dir = 0; } v = abs(v); // 初始化区间 if(seg->dir == 0) { if(dir) { seg->acc += v; u_seg->acc += g_u0[i-1]; //by ygl 记录首零压值 seg->dir = dir; seg->bgn = i; seg->end = i; } } // 当前区间处理 else { // 反向,当前区间结束 // if((dir == -seg->dir)|| (dir && (dir_old==0))) if((dir == -seg->dir && seg->dir)|| ((dir == 0) && (dir_old==0))) { if (i < end) { next_v = (dot[i+1]-dot[i]) > 0? 1:-1; //ygl 再往下推算一个点的方向 //1、如果启动方向和当前方向一致,则判为瞬变 例:5、3、3、2、3、3、4、、、、 //2、连续微分为0的点,则判阶梯波形 例:1、2、3、3、3、3、4、、、、 if(seg->dir == next_v || ((next_v == 0) && (dir == 0))) { dir = next_v; //保存方向 注意此时:next_v == seg->dir seg->acc += v; seg->end = i; continue; //继续搜索下一个点 } } U_mean = (abs(u_seg->acc)-abs(g_u0[i-1]))/(i-seg->bgn); //ygl 根据当前零流段,计算对应区间零压均值 if(U_mean) u_seg->max_v = abs(g_u0[seg->end] - g_u0[seg->bgn])*10/U_mean; //零压区间变化率 // 退一步,退回本区域内 i--; seg->max_p = i; seg->end = i; seg->max_v = abs(dot[i] - dot[seg->bgn-1]); // seg->max_v = abs(dot[i]); - ygl // 最大值计算忽略末尾微分为0的点 while(i>seg->bgn) { if(dot[i] != dot[i-1]) { break; } i--; } break; //return 1; } // 同相,扩大区间 seg->acc += v; seg->end = i; } } #if 0 // 如果区域不是以微分0结尾,延长到下一个微分最大值的位置 // 否则有可能判不到电压方向 if(dir_old) { for(i++;i<=end;i++) { if(_xdl_dir_equ(dir,dot[i] - dot[i-1]) == 0) { seg->end = i; break; } } } #endif if(seg->dir) { #if 1 // 零流首半波条件: // 1、开始点小于定值或电流差值是开始点的4倍以上。其中第二点是考虑在定值尽量小的情况下, // 如果有大的零序电流出现,其开始点可能大于定值,但其结束点拥有更大的值,有利于提高 // 判断的准确性。 // 2、开始点结束点之差大于定值。使用此条件,导致启动后可能找不到首半波, // 但如果不使用此条件,仅使用最大值大于定值条件,则可能导致极小电流差值启动判断, // 最终导致判断失误。 v = abs(dot[seg->bgn-1]); if(v == 0) { v = 1; } // if(v > i_set && (v*4 > abs(dot[seg->max_p] - dot[seg->bgn-1]))) // { // seg->dir = 0; // } //如果已经电流区域结束点超载了“电压区域结束点”,则要增加差值 if(u_seg->end < seg->end){ i_set *= 2; // rt_printf("丢弃1:bgn=%d,end=%d,dot=%d, i_set=%d.\r\n",seg->bgn,seg->end,dot[seg->bgn-1],i_set); } //零压变化率小于30%”,定值尽量小,则要增加差值 if(u_seg->max_v < 3){ i_set *= 6; // rt_printf("丢弃2:bgn=%d,end=%d,dot=%d, i_set=%d.\r\n",seg->bgn,seg->end,dot[seg->bgn-1],i_set); } if( abs(dot[seg->max_p] - dot[seg->bgn-1]) < i_set) { seg->dir = 0; // rt_printf("丢弃3:bgn=%d,end=%d,dot=%d, i_set=%d.\r\n",seg->bgn,seg->end,dot[seg->bgn-1],i_set); } #else if(i_set == 0) { if((abs(dot[seg->bgn-1])*4 > abs(dot[seg->max_p] - dot[seg->bgn-1]))) { seg->dir = 0; } } else { if(abs(dot[seg->bgn-1]) > i_set) { // rt_printf("丢弃:bgn=%d,end=%d,dot=%d.\r\n",seg->bgn,seg->end,dot[seg->bgn-1]); seg->dir = 0; } } #endif return 1; } return 0; } // 微分增速段搜索 int _xdl_seg_search_ds(s16 *dot,u32 bgn,u32 end,struct segment * seg) { s32 v,dir,dir_old; u32 i; memset(seg,0,sizeof(*seg)); dir = 0; dir_old = 0; for(i=bgn;i<=end;i++) { // 得到值和方向 dir_old= dir; v = dot[i]-dot[i-1]; if(v) { dir = v > 0 ? 1 : -1; } else { dir = 0; } v = abs(v); // 初始化区间 if(seg->dir == 0) { if(dir) { seg->acc += v; seg->dir = dir; seg->bgn = i; seg->end = i; } } // 当前区间处理 else { // 反向,当前区间结束 // if(dir != seg->dir) if((dir == -seg->dir)|| (dir && (dir_old==0))) { i--; if(_xdl_dir_inv(dot[i],dot[bgn-1])) { // seg->max_v = abs(dot[i]); seg->max_v = abs(dot[i])*10/(i-seg->bgn+1); } else { seg->max_v = abs(dot[i] - dot[seg->bgn-1])*10/(i-seg->bgn+1); // seg->max_v = abs(dot[i] - dot[seg->bgn-1]); } seg->max_p = i; // rt_printf("v=%d,p=%d.\r\n",seg->max_v,seg->max_p); return 1; } // 同相,扩大区间 seg->acc += v; seg->end = i; } } seg->dir = 0; return 0; } // 处理波形的微分和零轴移动 int _xdl_proc_diff_zero(s16 *ui0,s16 *ui0_d,int wave_len,u32 set,struct point *max_d) { int i,v0,v; u32 min_d; // 最小微分值 struct point max_ui; // 求导,并得到最大差分值 min_d = -1; max_d->p = 0; max_d->v = 0; max_ui.p = 0; max_ui.v = abs(ui0[0]); for(i=1; i abs(max_d->v)) { max_d->v = v0; max_d->p = i; } // 计算原值最大值 if(abs(ui0[i]) > abs(max_ui.v)) { max_ui.v = ui0[i]; max_ui.p = i; } } max_d->v = max_ui.v; v = set == 0 ? (5*min_d) : set; if(abs(max_ui.v) < v) { return -2; } return 0; } // 三点线性平滑 void xdl_f_smooth3 ( short in[], float out[], int N ) { int i; if ( N < 3 ) { for ( i = 0; i <= N - 1; i++ ) { out[i] = in[i]; } } else { out[0] = ( 5 * in[0] + 2 * in[1] - in[2]) / 6; for ( i = 1; i <= N - 2; i++ ) { out[i] = ( in[i - 1] + in[i] + in[i + 1]) / 3; } out[N - 1] = ( 5 * in[N - 1] + 2 * in[N - 2] - in[N - 3]) / 6; } } // 三点线性平滑 void xdl_smooth3 ( short in[], short out[], int N ) { int i; if ( N < 3 ) { for ( i = 0; i <= N - 1; i++ ) { out[i] = in[i]; } } else { out[0] = ( 5 * in[0] + 2 * in[1] - in[2]) / 6; for ( i = 1; i <= N - 2; i++ ) { out[i] = ( in[i - 1] + in[i] + in[i + 1]) / 3; } out[N - 1] = ( 5 * in[N - 1] + 2 * in[N - 2] - in[N - 3]) / 6; } } //五点线性平滑 void xdl_smooth5 ( short in[], short out[], int N ) { int i; if ( N < 5 ) { for ( i = 0; i <= N - 1; i++ ) { out[i] = in[i]; } } else { out[0] = ( 3 * in[0] + 2 * in[1] + in[2] - in[4] +3) / 5; out[1] = ( 4 * in[0] + 3 * in[1] + 2 * in[2] + in[3] +5) / 10; for ( i = 2; i <= N - 3; i++ ) { out[i] = ( in[i - 2] + in[i - 1] + in[i] + in[i + 1] + in[i + 2] +3) / 5; } out[N - 2] = ( 4 * in[N - 1] + 3 * in[N - 2] + 2 * in[N - 3] + in[N - 4] +5) / 10; out[N - 1] = ( 3 * in[N - 1] + 2 * in[N - 2] + in[N - 3] - in[N - 5] +3) / 5; } } // 零序电压启动后,调用此函数查找故障线路 // 采用暂态零序电流方向法: // 1、零序电压求导。 // 2、暂态零序电流与零序电压导数比较极性。 // 3、极性相反的选定为故障线路。 // 4、此判据只在首半波内有效。 // int xdl_proc_u0(int len,u32 u_set) { int bgn,ret; struct segment seg; struct point max_d; // memset(g_u0_raw,0,sizeof(g_u0_raw)); memset(g_u0_seg,0,sizeof(g_u0_seg)); // 零序电压求导 ret = _xdl_proc_diff_zero(g_u0_raw,0,len,0,&max_d); if(ret < 0) { return ret; } // 搜索几个电压变化最大的区间 bgn = 1; // while(_xdl_seg_search(g_u0_raw,bgn,len-1,&seg,1)) while(_xdl_seg_search_ds_f(g_u0,u_set,bgn,len-1,&seg)) { _xdl_seg_update(g_u0_seg,&seg,NULL, NULL); #if 1 bgn = seg.end + 1; #else // 得到下一个区域的开始点,和上一个区域的零结束点重合 for(bgn = seg.end;bgn>0;bgn--) { if(g_u0_raw[bgn] != 0) { break; } } bgn++; #endif } // 将搜索出的区域按时间排序 _xdl_seg_sort(g_u0_seg); return 0; } #define seg_extend_dot 8 int xdl_seg_u0_ok(int len,struct segment * seg, u32 u_set) { u32 bgn,end,i; s16 v_bgn; s16 max_v = 0; s16 limit_end = seg->end + seg_extend_dot; //扩展首半波至少范围, // 扩展首半波范围,电压延后16个点,45度。 bgn = seg->bgn > 0 ? (seg->bgn -1) : seg->bgn; end = seg->end + (seg->end - seg->bgn); //seg_extend_dot -> (seg->end - seg->bgn) end = end > limit_end ? end : limit_end; if(end >= len) { end = len; } // 检查电压幅值,在此区域后,必须有电压大于设定值 // v_bgn = g_u0[bgn]; max_v = v_bgn = g_u0[bgn]; // rt_printf("u0 check fail bgn=%d,end =%d,max_v = %d,v_bgn = %d,set = %d\r\n",bgn,end,max_v,v_bgn,u_set); for(i=bgn;i u_set) { // rt_printf("u0 check ok bgn=%d,end =%d,max_v = %d,v_bgn = %d,v_p = %d,set = %d\r\n",bgn,end,max_v,v_bgn,i,u_set); return 1; } } return 0; } int xdl_seg_i0_ok(int len,struct segment * seg, u32 i_set) { u32 bgn,end,i; s16 v_bgn; // 扩展首半波范围,电流延后16个点,45度。 bgn = seg->bgn > 0 ? (seg->bgn -1) : seg->bgn; end = seg->end + 8; if(end >= len) { end = len; } // 检查电压幅值,在此区域后,必须有电压大于设定值 v_bgn = g_i0[bgn]; for(i=bgn;i i_set) { return 1; } } return 0; } #define U_SET_COEF 1 int xdl_proc_i0(uint32_t sw,int len,u32 i_set,u32 u_set,int *ps,int h_sign) { int i,s,bgn=1,end,ret,dir=0; int j =0, hilbert_count =0; u32 updateNum =0, zero_set; s32 zero_max; struct point di_max; struct segment seg,u_seg; // 全局电流数据清零 // memset(g_i0_raw,0,sizeof(g_i0_raw)); memset(g_i0_seg,0,sizeof(g_i0_seg)); // 零序电流求导,并得到最大差分值 if(g_i0_arg[sw].scale_is_1A) { // 零流采样电阻51Ω更换为了300Ω,经过系数转换为码值后,值变大了6倍左右,直接用i_set判断 zero_set = i_set; } else { //没换电阻还是按照原本方式处理 if (h_sign > 0) //H变换计算成功后,需要判断全段有流值大于4倍定值,防止无流误判 { zero_set = i_set*4; } else { zero_set = i_set*2; } if(zero_set < 18) /**< zero_set判值不能小于18,保证小电流阀值不能过低 */ zero_set = 18; } zero_max = _xdl_proc_diff_zero(g_i0,0,len,zero_set,&di_max); if(zero_max < 0) { rt_printf("第%d行, %s无流(zero_max=%d) zero_set=%d.\r\n",__LINE__, g_line_type[0],zero_max,zero_set); return -1; } // 零压触发时,搜索第一个电压突变的区间,精确捕捉过零点 while(_xdl_seg_search_ds_f(g_u0,u_set,bgn,len-1,&u_seg)) { if(u_seg.dir) break; bgn = u_seg.end + 1; } if(pRunSet->bTT_TBLTYPE) { u_seg.bgn -= 1 ; //电压突变沿作为电流首半波始启点,需再回退一个采样点。 bgn = u_seg.bgn; } else bgn = u_seg.bgn; // rt_printf("v0=%d,p=%d.u_dir=%d,u_bgn=%d,u_end=%d.\r\n",u_seg.max_v,u_seg.max_p,u_seg.dir,bgn,u_seg.end); // 方法2: 电流微分首半波判断 // 搜索几个电流变化最大的区间 while(_xdl_seg_search_ds_r(g_i0,i_set,bgn,len-1,&seg,&u_seg)) { bgn = seg.end+1; // 区间有效,且区间电压有效才更新 // 零压定值过大会导致暂态零压设定 // 值过高进而导致索引零压失败最终 // 误判或者无法启动小电流接地判断 if(seg.dir && xdl_seg_u0_ok(len,&seg,u_set)) { // rt_printf("v1=%d,p=%d.dir=%d,bgn=%d,end=%d,ubgn=%d,uend=%d,updateNum=%d.\r\n",seg.max_v,seg.max_p,seg.dir,seg.bgn,seg.end,u_seg.bgn,u_seg.end,updateNum); _xdl_seg_update(g_i0_seg,&seg,&u_seg,&updateNum); } } // 将搜索出的区域按时间排序 _xdl_seg_sort(g_i0_seg) ; // 寻找电流首半波,条件是最大值最大的区域,是其前面区域最大值的8倍以上 s = 0; if(pRunSet->bTT_GZJD != 1) //选高阻接地方式,不考虑最大区域选择 { for(i=1; i<(XDL_SEG_NUM>>1);i++) //XDL_SEG_NUM ->暂时只使用前5组区域 { if(g_i0_seg[i].max_v > (g_i0_seg[s].max_v <<3)) { s = i; } } } // 如果首半波条件成立,寻找零序电压方向。 if(g_i0_seg[s].dir) { // 扩展首半波范围 if(g_i0_seg[s].bgn > 0) { g_i0_seg[s].bgn--; } // 检查电压幅值,在此区域后,必须有电压大于设定值 { s16 v,v_bgn,v_max; // 延长电压检查区间 ,延长为180度+额外增加8个点 end = g_i0_seg[s].bgn + ADC_REC_SAMPLE/2+8; //极端情况下会进这里,例如零流:为长而平衡的小波,造成区段划分过长,长度超越了半个周波(180度) if(g_i0_seg[s].end > end) { end = g_i0_seg[s].end + 16; } if(end > len) { end = len; } v_max = 0; v_bgn = g_u0[g_i0_seg[s].bgn]; for(i=g_i0_seg[s].bgn;i<=end;i++) { v = g_u0[i] - v_bgn; if(abs(v) > abs(v_max)) { v_max = v; } if (i == g_i0_seg[s].end) //区间内搜索,采用区域中的最大值计算 { // 区域电压差值必须大于定值 if(abs(v_max) > u_set*U_SET_COEF) { // dir = v; // 如果电流区间起点的电压的绝对值减终点的电压的绝对值大于定值,反向 // if(abs(g_u0[g_i0_seg[s].bgn]) < abs(g_u0[i])) // 此算法跨零点时容易失误 if(abs(g_u0[g_i0_seg[s].bgn]) < (abs(g_u0[g_i0_seg[s].end])+u_set)) { dir = g_u0[g_i0_seg[s].end]; //使用区域结束点,判趋势 rt_printf("电压正向v_max:bgn=%d,end=%d,u_set=%d,v_bgn=%d,v_end=%d,dir=%d.\r\n", g_i0_seg[s].bgn,g_i0_seg[s].end,u_set,g_u0[g_i0_seg[s].bgn],g_u0[g_i0_seg[s].end],dir); } else { dir = -g_u0[g_i0_seg[s].end]; rt_printf("电压反向v_max:bgn=%d,end=%d,u_set=%d,v_bgn=%d,v_end=%d,dir =%d.\r\n", g_i0_seg[s].bgn,g_i0_seg[s].end,u_set,g_u0[g_i0_seg[s].bgn],g_u0[g_i0_seg[s].end],dir); } break; } } else if(i > g_i0_seg[s].end) //延长区间搜索 { // 区域电压差值必须大于定值 if(abs(v) > u_set*U_SET_COEF) { dir = v; // 如果电流区间起点的电压的绝对值减终点的电压的绝对值大于定值,反向 // if(abs(g_u0[g_i0_seg[s].bgn]) < abs(g_u0[i])) // 此算法跨零点时容易失误 if(abs(g_u0[g_i0_seg[s].bgn]) < (abs(g_u0[g_i0_seg[s].end])+u_set)) { dir = v; } else { dir = -v; rt_printf("电压反向v:bgn=%d,end=%d,u_set=%d,v_bgn=%d,v_end=%d,dir =%d.\r\n", g_i0_seg[s].bgn,g_i0_seg[s].end,u_set,g_u0[g_i0_seg[s].bgn],g_u0[g_i0_seg[s].end],dir); } break; } } } if(i == end) { rt_printf("电压欠幅:seg_bgn=%d,seg_end=%d,end=%d,u_set=%d,v_max=%d.\r\n",g_i0_seg[s].bgn,g_i0_seg[s].end,end,u_set,v_max); *ps = s; ret = -2; goto RET; } else if(i > g_i0_seg[s].end) { rt_printf("电压查找超范围:bgn=%d,end=%d,uv0=%d,uvd=%d,uv=%d,up=%d,u_set=%d.\r\n", g_i0_seg[s].bgn,g_i0_seg[s].end,g_u0[g_i0_seg[s].bgn],g_u0[g_i0_seg[s].end],g_u0[i],i,u_set); } } // 判电压方向 if(dir) { ret=_xdl_dir_inv(dir,g_i0_seg[s].dir); if(ret == 0 && pRunSet->bTT_HILEBERT) //H 变换 { for(j = (g_i0_seg[s].bgn&0x7f); j < XDL_SAMPLE_LEN ; j+=ADC_REC_SAMPLE ) { hilbert_count++; ret = (hilbert_filter(&g_u0_dwt[j], &g_i0_dwt[j], ADC_REC_SAMPLE)>0)? 1:0; if(ret) { rt_printf("hilbert activation,begin at %d,计算:%d 次 \r\n",(g_i0_seg[s].bgn&0x7f),hilbert_count); break; } } if(!ret) { rt_printf("hilbert not find,begin at %d,计算:%d 次 \r\n",(g_i0_seg[s].bgn&0x7f),hilbert_count); } } rt_printf("%s(s=%d,dir=%d,bgn=%d,iv=%d,ip=%d,uv=%d,up=%d,i_set=%d,u_set=%d).\r\n", g_line_type[ret],s,dir,g_i0_seg[s].bgn,g_i0[g_i0_seg[s].max_p],g_i0_seg[s].max_p,g_u0[i],i,i_set,u_set); *ps = s; return ret; } else { rt_printf("电压无方向:s=%d,bgn=%d,end=%d,i=%d,u0=%d.\r\n", s,g_i0_seg[s].bgn,g_i0_seg[s].end,i,g_u0[i]); *ps = s; ret = -3; goto RET; } } else { if(h_sign > 0 && pRunSet->bTT_HILEBERT) //H变换,缩小区间再判一次 { for(j = (ADC_REC_SAMPLE<<0) ; j < XDL_SAMPLE_LEN ; j+=ADC_REC_SAMPLE ) { hilbert_count++; ret = (hilbert_filter(&g_u0_dwt[j], &g_i0_dwt[j], ADC_REC_SAMPLE)>0)? 1:0; if(ret) { rt_printf("%s hilbert activation,计算:%d 次 \r\n",g_line_type[ret],hilbert_count); return ret; } } if(!ret) { rt_printf("%s hilbert not find,计算:%d 次 \r\n",g_line_type[ret],hilbert_count); } } rt_printf("首半波未找到:s=%d,dir=%d,max=%d.\r\n",s,g_i0_seg[s].dir,g_i0_seg[s].max_v); *ps = s; ret = -4; } RET: return ret; } #define COEF 1.0 int xdl_proc_adc(u32 sw,u32 dt,struct timespec *ts) { int i,ret,seg; int ch0,cha,chb,chc; int i0,u0; char Hilbert_Sign = 0; //H变换计算成功标志 float H_ret = 0; //H变换计算返回值 // 三项电流 int ui_begin; DWORD Ia,Ib,Ic; wave_object obj=NULL; wt_object wt=NULL; char *method = "modwt"; if(pRunSet->bTT_FILTER == 2) { // char *name = "haar"; obj = wave_init(pRunSet->bTT_WAVE);// Initialize the wavelet wt = wt_init(obj, method, XDL_SAMPLE_LEN, pRunSet->bTT_WAVE_ORDER);// Initialize the wavelet transform object } // 得到零序电压采样值 if(pRunSet->tSwSet[sw].bTT_Power_v2 == 0) { ch0 = (short)g_sw_pub.ac_cfg_index[PUB_AC_U01]; cha = (short)g_sw_pub.ac_cfg_index[PUB_AC_UA1]; chb = (short)g_sw_pub.ac_cfg_index[PUB_AC_UB1]; chc = (short)g_sw_pub.ac_cfg_index[PUB_AC_UC1]; } else { ch0 = (short)g_sw_pub.ac_cfg_index[PUB_AC_U02]; cha = (short)g_sw_pub.ac_cfg_index[PUB_AC_UA2]; chb = (short)g_sw_pub.ac_cfg_index[PUB_AC_UB2]; chc = (short)g_sw_pub.ac_cfg_index[PUB_AC_UC2]; } if(ch0 != INDEX_INVALLID) { ch0=equ_get_ac_channel(g_equ_config_ac[ch0].slot, g_equ_config_ac[ch0].index); for(i=0; ibTT_GZJD != 1) //选高阻接地方式,则小波计算不考虑:三点滤波算法 { memset(g_u0_dwt,0,sizeof(g_u0_dwt)); xdl_f_smooth3(g_u0_raw, g_u0_dwt, XDL_SAMPLE_LEN); } switch (pRunSet->bTT_FILTER) { case 0: xdl_smooth3(g_u0_raw, g_u0, XDL_SAMPLE_LEN); break; case 1: DWT_Matrix_Transform(g_u0_dwt, g_u0, XDL_SAMPLE_LEN); break; case 2: modwt(wt, g_u0_dwt);// Perform MODWT for(i=0; ioutput[i]*COEF); } break; default: memcpy(g_u0, g_u0_raw, XDL_SAMPLE_LEN); break; } } else if( (cha != INDEX_INVALLID) && (chb != INDEX_INVALLID) && (chc != INDEX_INVALLID)) { cha=equ_get_ac_channel(g_equ_config_ac[cha].slot, g_equ_config_ac[cha].index); chb=equ_get_ac_channel(g_equ_config_ac[chb].slot, g_equ_config_ac[chb].index); chc=equ_get_ac_channel(g_equ_config_ac[chc].slot, g_equ_config_ac[chc].index); for(i=0; itSwSet[sw]; TSDHZ_T *pSW=&g_tRelay[sw].tSDHZ; int zero; if((BH_ALL_EN(sw) && (pSet->bTT_xdlgj || pSet->bTT_xdltz)) == 0) { ret = -12; goto RET; } zero = 0; if(!pRunSet->bTT_TBLTYPE) //使用电流突变 { if(g_xdl[sw].zero_cnt) { zero = g_xdl[sw].zero_sum/g_xdl[sw].zero_cnt; } } // 得到零序电流采样值 ch0 = (short)g_sw[sw].ac_cfg_index[SW_AC_I0]; if(ch0 != INDEX_INVALLID) { ch0=equ_get_ac_channel(g_equ_config_ac[ch0].slot, g_equ_config_ac[ch0].index); for(i=0; ibTT_GZJD != 1) //选高阻接地方式,则小波计算不考虑:三点滤波算法 { memset(g_i0_dwt,0,sizeof(g_i0_dwt)); xdl_f_smooth3(g_i0_raw, g_i0_dwt, XDL_SAMPLE_LEN); } switch (pRunSet->bTT_FILTER) { case 0: xdl_smooth3(g_i0_raw, g_i0, XDL_SAMPLE_LEN); break; case 1: DWT_Matrix_Transform(g_i0_dwt, g_i0, XDL_SAMPLE_LEN); break; case 2: modwt(wt, g_i0_dwt);// Perform MODWT for(i=0; ioutput[i]*COEF); } break; default: memcpy(g_i0, g_i0_raw, XDL_SAMPLE_LEN); break; } } else if( (cha != INDEX_INVALLID) && (chb != INDEX_INVALLID) && (chc != INDEX_INVALLID)) { cha=equ_get_ac_channel(g_equ_config_ac[cha].slot, g_equ_config_ac[cha].index); chb=equ_get_ac_channel(g_equ_config_ac[chb].slot, g_equ_config_ac[chb].index); chc=equ_get_ac_channel(g_equ_config_ac[chc].slot, g_equ_config_ac[chc].index); for(i=0; i ADC_REC_DOTS_CHANNEL) { rt_printf("处理超时:n=%d,now=%d,old=%d.\r\n",n,(g_adc_dots_count<<3),dt); ret = -14; goto RET; } } // 零序电流处理 seg = -1; if(pRunSet->bTT_TBLTYPE) //选零压突变,使用H变换计算以增加准确率 { for(i = 0 ; i < XDL_SAMPLE_LEN ; i+=ADC_REC_SAMPLE ) //(ADC_REC_SAMPLE<<1) { H_ret = hilbert_filter(&g_u0_dwt[i], &g_i0_dwt[i], ADC_REC_SAMPLE); if(H_ret > 0) { Hilbert_Sign++; // break; } // rt_printf("i= %d,status =%d,hilbert_filter ret = %f.\r\n",i,Hilbert_Sign,H_ret); } ret = xdl_proc_i0(sw,XDL_SAMPLE_LEN,pSet->dI_xdl_zt,pRunSet->dU_xdl_u0_zt[pRunSet->tSwSet[sw].bTT_Power_v2],&seg,Hilbert_Sign); } else // 零序电流处理 ret = xdl_proc_i0(sw,XDL_SAMPLE_LEN,pSet->dI_xdl_zt,pRunSet->dU_xdl_u0_zt[pRunSet->tSwSet[sw].bTT_Power_v2],&seg,0); //如果电流反向,结果反向。 if((pRunSet->tSwSet[sw].bTT_Current_Inv || pRunSet->tSwSet[sw].bTT_lxpolar || g_cur_direction_adaptaion) && (ret >= 0)) { ret = !ret; } // 计算I0、U0 i0 = 0; u0 = 0; if(seg >= 0) { for(i=g_i0_seg[seg].bgn; i abs(i0)) { i0 = g_i0[i]; } if(abs(g_u0[i]) > abs(u0)) { u0 = g_u0[i]; } } i0 = i0*(g_ui[UI_SW_INDEX(sw,SW_AC_I0)].e_factor0*1.4142)*Q16_BASE; u0 = u0*(g_ui[PUB_AC_U01].e_factor0*1.4142)*Q16_BASE; } // 计算三项电流 ui_begin = UI_SW_INDEX_BEGIN(sw); Ia = _Mul_Div_U(sqrt_32fix(g_ui[ui_begin + SW_AC_IA].m2[0]), 256, g_ui[ui_begin + SW_AC_IA].m2_factor_k); Ib = _Mul_Div_U(sqrt_32fix(g_ui[ui_begin + SW_AC_IB].m2[0]), 256, g_ui[ui_begin + SW_AC_IB].m2_factor_k); Ic = _Mul_Div_U(sqrt_32fix(g_ui[ui_begin + SW_AC_IC].m2[0]), 256, g_ui[ui_begin + SW_AC_IC].m2_factor_k); // 启动事件 soe_record_ev(EV_LX_XDL_QD+sw*EV_SW_NUM, 1, i0,u0,ret); if(ret>0) //小电流接地:启动[界内动作] { g_xdl[sw].dz = 1; // 提前打开启动继电器,以节省硬件耗用时间 // 并等待超时释放 sw_do(sw,SW_DO_BHT,SW_DO_TYPE_SELECT_ON); if(pSet->dT_xdl_T) //暂态时间不为0时,才启动间歇性接地逻辑 { // jxjd_set_JDGZ(sw); //间歇接地故障(用于计次) // TODO } if (!pSet->dT_xdl_GT) // 告警 { // 告警 if(pSet->bTT_xdlgj) { soe_record_ev(EV_LX_XDL_GJ+sw*EV_SW_NUM, 1, i0,u0,0 ); soe_record_ev(EV_ABC_JDTZ+sw*EV_SW_NUM, 1, i0,u0,0 ); pSW->uSdhz_S.bFlag.bJD = true; } } else { pSW->uSdhz_S.bFlag.bJD = true; g_xdl[sw].gj_us0 = g_xdl[sw].qd_us0; g_xdl[sw].dz_i0[0] = i0; g_xdl[sw].dz_u0[0] = u0; g_xdl[sw].dz_Ia[0] = Ia; g_xdl[sw].dz_Ib[0] = Ib; g_xdl[sw].dz_Ic[0] = Ic; } if (!pSet->dT_xdl_T) // 跳闸 { // 跳闸 if(pSet->bTT_xdltz) { sw_do(sw,SW_DO_BHT,SW_DO_TYPE_ON); soe_record_ev(EV_LX_XDL_TZ+sw*EV_SW_NUM, 1, i0,u0,0 ); soe_record_ev(EV_ABC_JDTZ+sw*EV_SW_NUM, 1, i0,u0,0 ); if ((Ia>Ib)&&(Ia>Ic)) { soe_record_ev(EV_A_JDTZ+sw*EV_SW_NUM, 1, Ia,0,0 ); } else if ((Ib>Ia)&&(Ib>Ic)) { soe_record_ev(EV_B_JDTZ+sw*EV_SW_NUM, 1, Ib,0,0 ); } else if ((Ic>Ia)&&(Ic>Ib)) { soe_record_ev(EV_C_JDTZ+sw*EV_SW_NUM, 1, Ic,0,0 ); } pSW->uSdhz_S.bFlag.bJD = true; // g_tRelay[sw].tXDLCHZ.sta.bFlag.bXDLDZ = true; //TODO EWen //jxjd_sw_reset(sw); } } else { pSW->uSdhz_S.bFlag.bJD = true; g_xdl[sw].dz_us0 = g_xdl[sw].qd_us0; g_xdl[sw].dz_i0[1] = i0; g_xdl[sw].dz_u0[1] = u0; g_xdl[sw].dz_Ia[1] = Ia; g_xdl[sw].dz_Ib[1] = Ib; g_xdl[sw].dz_Ic[1] = Ic; } } rcd_start_xdl(NULL,sw,g_xdl_rcd_a,XDL_RCD_A_NUM,XDL_SAMPLE_LEN,ts); g_xdl[sw].st = XDL_ST_END; ret = 0; } else { ret = -15; } RET: if(ret != 0) { soe_record_ev(EV_LX_XDL_QD+sw*EV_SW_NUM, 1, 0,0,ret); } rt_printf_time("小电流接地完成(sw=%d,ret=%d,inv=%d,dir=%d).\r\n",sw,ret,pRunSet->tSwSet[sw].bTT_Current_Inv, g_cur_direction_adaptaion); return ret; } // 零流突变判断定值 #define REV_F_CT_DEFAULT 20 // CT默认,额定5A #define REV_F_CT_1A 10 // CT_1V/1A, 额定1A // 小电流突变量启动,放在156us中断中 void xdl_tbl_qd(int mod) { s16 v,v0,v1,zero; s16 iv,iv0,iv1,ref_i,ref_v=0; //零压触发,电流参考辅助触发 u32 sw,ui; u32 xdl_zt_v; int ret; TSETSW *pSet = NULL; // u32 index = g_adc_dots_index_rec - 1; u32 index = ((g_adc_dots_count-1)<<3) + mod; for (sw=0; swtSwSet[sw]; //突变量启动前提条件:保护总投入,小电流接地告警或出口投入或录波投入。 if(BH_ALL_EN(sw) == 0 || (pSet->bTT_xdlgj || pSet->bTT_xdltz)== 0) { continue; } // 如果索引通道不存在,不需计算 if(pRunSet->bTT_TBLTYPE) //高阻接地方式,零序电流较小,可以采用电压突变启动 { ui = PUB_AC_U01; xdl_zt_v = pRunSet->dU_xdl_u0_zt[0]; } else { ui = UI_SW_INDEX(sw,SW_AC_I0); xdl_zt_v = pRunSet->tSwSet[sw].dI_xdl_zt; } if(g_ui[ui].chn_index == CFG_ADC_CHANNEL_ZERO) { continue; } // 检查硬件通道是否正常 if(equ_ac_channel_is_ok(g_ui[ui].chn_index) == 0) { continue; } // 得到当前值和一个周期前的值 v0 = g_adc_dots_rec[(index - ADC_REC_SAMPLE*2) & ADC_REC_DOTS_MASK][g_ui[ui].chn_index]; v1 = g_adc_dots_rec[(index - ADC_REC_SAMPLE) & ADC_REC_DOTS_MASK][g_ui[ui].chn_index]; v = g_adc_dots_rec[(index) & ADC_REC_DOTS_MASK][g_ui[ui].chn_index]; //电流零偏处理,由于在DTU3.0电磁式的设计中,AD一个码值代表6mA,10个码值代表60mA,所以必须修正 // 电流零偏计算 g_xdl[sw].zero_sum += v1; if(g_xdl[sw].zero_cnt < ADC_REC_SAMPLE) { g_xdl[sw].zero_cnt++; return; } else { g_xdl[sw].zero_sum -= v0; } // 根据zero,修正v值, zero = g_xdl[sw].zero_sum/g_xdl[sw].zero_cnt; v -= zero; // 如果大于定值,启动录波 间隔电流大于无流值 if((abs(v) > xdl_zt_v)) { // if(sw > 0) /**< sw = 0 默认间隔不判无流值,其它间隔要判 */ { // if(g_sw[sw].m2_max < pRunSet->dIWL_SQR[sw]) // continue; if(pRunSet->bTT_TBLTYPE){ /**< 改为所有间隔都要判零流 */ ref_i = UI_SW_INDEX(sw,SW_AC_I0); iv0 = g_adc_dots_rec[(index - ADC_REC_SAMPLE*2) & ADC_REC_DOTS_MASK][g_ui[ref_i].chn_index]; iv1 = g_adc_dots_rec[(index - ADC_REC_SAMPLE) & ADC_REC_DOTS_MASK][g_ui[ref_i].chn_index]; iv = g_adc_dots_rec[(index) & ADC_REC_DOTS_MASK][g_ui[ref_i].chn_index]; ref_v = abs(iv0 - iv1*2 + iv); if(ref_v < g_i0_arg[sw].ref_v_set) continue; } } // 置启动标志,不能放在录波启动成功的条件里,避免启动失败后频繁打印 g_xdl[sw].st = XDL_ST_QT; g_xdl[sw].qd_us0 = ustimer_get_origin(); ret = rcd_start(sw, RECORD_TYPE_XDLJD, xdl_get_LLTB_waves(sw)); if(ret == 0) { rt_printf("%s启动:sw=%d,v=%d,ref_v=%d,i_m2=%d,nor_i=%d,i_set=%d,zero=%d.\r\n",g_tbl_type[pRunSet->bTT_TBLTYPE],sw,v,ref_v,g_sw[sw].m2_max,pRunSet->dIWL_SQR[sw],xdl_zt_v,zero); } else { rt_printf("录波启动失败(ret=%d,sw=%d.\r\n)",ret,sw); } // if(pRunSet->bTT_TBLTYPE){ //零压触发每次只判一间隔,下次进来再判下一间隔单元 // return; // } } } } /****************************************************************************** 函数名称: max3 函数版本: 01.01 创建作者: 赵海洋 创建日期: 2022-03-22 函数说明: 三值比较函数 参数说明: 无 返回值: 无. 修改记录: */ #if 0 static int max3 (int a, int b, int c) { if (a>=b) if (a>=c) return a; else return c; else if (b>=c) return b; else return c; } #endif #define XDL_FG_INTERVAL (1) //(5) //复归间隔 unit: s //小电流跳闸、复归 static void _xdl_protect_TZ_FG(int sw, DWORD dStep) { int soeno; bool bQD; static uint8_t gj_flag[SWITCH_NUM_MAX]={0}; static uint8_t dz_flag[SWITCH_NUM_MAX]={0}; // 电流闭锁复归 bQD = OverRelay(g_ui[UI_SW_INDEX(sw,SW_AC_I0)].m2[0], pRunSet->tSwSet[sw].dI_xdl,pRunSet->tSwSet[sw].dI_xdl_fh, g_tXDLTime_I[sw].boolTrip); RunTR(&g_tXDLTime_I[sw], bQD, dStep); // 小电流告警延时动作 if (pRunSet->tSwSet[sw].dT_xdl_GT) { if((g_xdl[sw].gj_us0) && (ustimer_get_duration(g_xdl[sw].gj_us0) >= (pRunSet->tSwSet[sw].dT_xdl_GT*USTIMER_SEC))) { if (gj_flag[sw]==0) { g_xdl[sw].gj_us0 = 0; #ifdef RECORD_SW_U0TB if(g_tXDLTime_U0TB[sw].boolTrip || g_tXDLTime_I[sw].boolTrip) #else if(g_tXDLTime_U[pRunSet->tSwSet[sw].bTT_Power_v2].boolTrip || g_tXDLTime_I[sw].boolTrip) #endif { // 告警 if(pRunSet->tSwSet[sw].bTT_xdlgj) { // g_xdl[sw].gj_fh_us0 = ustimer_get_origin(); soe_record_ev(EV_LX_XDL_GJ+sw*EV_SW_NUM, 1, g_xdl[sw].dz_i0[0],g_xdl[sw].dz_u0[0],0 ); if(!soe_check(EV_ABC_JDTZ +sw*EV_SW_NUM)) { soe_record_ev(EV_ABC_JDTZ +sw*EV_SW_NUM, 1, g_xdl[sw].dz_i0[0],g_xdl[sw].dz_u0[0],0 ); } } gj_flag[sw] = 1; } } } else { gj_flag[sw] = 0; } } // 小电流跳闸延时动作 if (pRunSet->tSwSet[sw].dT_xdl_T) { if((g_xdl[sw].dz_us0) && (ustimer_get_duration(g_xdl[sw].dz_us0) >= (pRunSet->tSwSet[sw].dT_xdl_T*USTIMER_SEC))) { if (dz_flag[sw]==0) { g_xdl[sw].dz_us0 = 0; #ifdef RECORD_SW_U0TB if(g_tXDLTime_U0TB[sw].boolTrip || g_tXDLTime_I[sw].boolTrip) #else if(g_tXDLTime_U[pRunSet->tSwSet[sw].bTT_Power_v2].boolTrip || g_tXDLTime_I[sw].boolTrip) #endif { // 跳闸 if(pRunSet->tSwSet[sw].bTT_xdltz) { sw_do(sw,SW_DO_BHT,SW_DO_TYPE_SELECT_ON); sw_do(sw,SW_DO_BHT,SW_DO_TYPE_ON); // g_xdl[sw].dz_fh_us0 = ustimer_get_origin(); soe_record_ev(EV_LX_XDL_TZ+sw*EV_SW_NUM, 1, g_xdl[sw].dz_i0[1],g_xdl[sw].dz_u0[1],0 ); if(!soe_check(EV_ABC_JDTZ +sw*EV_SW_NUM)) { soe_record_ev(EV_ABC_JDTZ +sw*EV_SW_NUM, 1, g_xdl[sw].dz_i0[1],g_xdl[sw].dz_u0[1],0 ); } if ((g_xdl[sw].dz_Ia[1]>g_xdl[sw].dz_Ib[1])&&(g_xdl[sw].dz_Ia[1]>g_xdl[sw].dz_Ic[1])) { soe_record_ev(EV_A_JDTZ+sw*EV_SW_NUM, 1, g_xdl[sw].dz_Ia[1],0,0 ); } else if ((g_xdl[sw].dz_Ib[1]>g_xdl[sw].dz_Ia[1])&&(g_xdl[sw].dz_Ib[1]>g_xdl[sw].dz_Ic[1])) { soe_record_ev(EV_B_JDTZ+sw*EV_SW_NUM, 1, g_xdl[sw].dz_Ib[1],0,0 ); } else if ((g_xdl[sw].dz_Ic[1]>g_xdl[sw].dz_Ia[1])&&(g_xdl[sw].dz_Ic[1]>g_xdl[sw].dz_Ib[1])) { soe_record_ev(EV_C_JDTZ+sw*EV_SW_NUM, 1, g_xdl[sw].dz_Ic[1],0,0 ); } // g_tRelay[sw].tXDLCHZ.sta.bFlag.bXDLDZ = true; //TODO } dz_flag[sw] = 1; } } } else { dz_flag[sw] = 0; } } // 小电流启动重合闸 #if 0 // TODO if((g_xdl[sw].st) && g_tRelay[sw].tXDLCHZ.sta.bFlag.bXDLDZ) { #ifdef RECORD_SW_U0TB if((!g_tXDLTime_U0TB[sw].boolTrip) && (!g_tXDLTime_I[sw].boolTrip)) #else if((!g_tXDLTime_U[pRunSet->tSwSet[sw].bTT_Power_v2].boolTrip) && (!g_tXDLTime_I[sw].boolTrip)) #endif { g_tRelay[sw].tXDLCHZ.sta.bFlag.bXDLDZ = false; g_tRelay[sw].tXDLCHZ.sta.bFlag.bXDLCH = true; } } #endif // 此标志置1时可以确定产生了界内故障 if(g_xdl[sw].dz) { if(g_xdl[sw].dz_fh_us0 == 0) { g_xdl[sw].dz_fh_us0 = ustimer_get_origin(); } else { #ifdef RECORD_SW_U0TB if(g_tXDLTime_U0TB[sw].boolTrip || g_tXDLTime_I[sw].boolTrip) #else if(g_tXDLTime_U[pRunSet->tSwSet[sw].bTT_Power_v2].boolTrip || g_tXDLTime_I[sw].boolTrip) #endif { // 故障还没消失,更新计时 g_xdl[sw].dz_fh_us0 = ustimer_get_origin(); } } } // 小电流信号延迟复归 if((g_xdl[sw].dz_fh_us0) && (ustimer_get_duration(g_xdl[sw].dz_fh_us0) >= (uint32_t)(tRunPara.fT_LBXH_RST*USTIMER_SEC))) { g_xdl[sw].dz_fh_us0 = 0; // 告警 soeno = EV_LX_XDL_GJ+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } // 跳闸 soeno = EV_LX_XDL_TZ+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } // 接地故障总 soeno = EV_ABC_JDTZ+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } // A相 soeno = EV_A_JDTZ+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } // B相 soeno = EV_B_JDTZ+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } // C相 soeno = EV_C_JDTZ+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } //间歇接地故障消失 // jxjd_clr_JDGZ(sw);//TODO EWen } // 两次判断最小间隔为5S. if((g_xdl[sw].st) && (ustimer_get_duration(g_xdl[sw].qd_us0) > (XDL_FG_INTERVAL*USTIMER_SEC))) { // 如果接地启动,必须等小电流零压零流消失后才能复归 #ifdef RECORD_SW_U0TB if(g_tXDLTime_U0TB[sw].boolTrip || g_tXDLTime_I[sw].boolTrip) #else if(g_tXDLTime_U[pRunSet->tSwSet[sw].bTT_Power_v2].boolTrip || g_tXDLTime_I[sw].boolTrip) #endif { return; } rt_printf_time("小电流接地复归(sw=%d,st=%d)。\r\n",sw,g_xdl[sw].st); g_xdl[sw].st = XDL_ST_IDLE; g_xdl[sw].dz = 0; g_xdl[sw].zero_sum = 0; g_xdl[sw].zero_cnt = 0; // g_tRelay[sw].tXDLCHZ.sta.bFlag.bXDLCH = false; // 启动 soeno = EV_LX_XDL_QD+sw*EV_SW_NUM; if(soe_check(soeno)) { soe_record_ev(soeno, 0, 0,0,0 ); } //间歇接地故障消失 // jxjd_clr_JDGZ(sw); //TODO EWen } } #ifndef RECORD_SW_U0TB //保留原有逻辑 void xdl_protect(DWORD dStep) { int sw,soeno; bool bQD; // 小电流零序电压过压逻辑1 bQD = OverRelay(g_ui[PUB_AC_U01].m2[0], pRunSet->dU_xdl_u0[0], pRunSet->dU_xdl_u0_fh[0], g_tXDLTime_U[0].boolTrip); RunTR(&g_tXDLTime_U[0], bQD, dStep); // 记录事件 soeno = EV_XDL_U0GJ1; if(g_tXDLTime_U[0].boolTrip && pRunSet->dU_xdl_u0[0]) { if(soe_check(soeno)==false) { DWORD u0; u0 = _Mul_Div_U(sqrt_32fix(g_ui[PUB_AC_U01].m2[0]), 256, g_ui[PUB_AC_U01].m2_factor_k); soe_record_ev(soeno, 1, u0,0,0); // 小电流零压启动录波,但不进行接地判断 for(sw=0;swtSwSet[sw].bTT_Power_v2 == 0) { rcd_start(sw,RECORD_TYPE_LXDY,RECORD_LEN_TZQD); } } } } else { if(soe_check(soeno)==true) { soe_record_ev( soeno, 0, 0,0,0 ); } } // 小电流零序电压过压逻辑2 bQD = OverRelay(g_ui[PUB_AC_U02].m2[0], pRunSet->dU_xdl_u0[1], pRunSet->dU_xdl_u0_fh[1], g_tXDLTime_U[1].boolTrip); RunTR(&g_tXDLTime_U[1], bQD, dStep); // 记录事件 soeno = EV_XDL_U0GJ2; if(g_tXDLTime_U[1].boolTrip && pRunSet->dU_xdl_u0[1]) { if(soe_check(soeno)==false) { DWORD u0; u0 = _Mul_Div_U(sqrt_32fix(g_ui[PUB_AC_U02].m2[0]), 256, g_ui[PUB_AC_U02].m2_factor_k); soe_record_ev(soeno, 1, u0,0,0); // 小电流零压启动录波,但不进行接地判断 for(sw=0;swtSwSet[sw].bTT_Power_v2) { rcd_start(sw,RECORD_TYPE_LXDY,RECORD_LEN_TZQD); } } } } else { if(soe_check(soeno)==true) { soe_record_ev( soeno, 0, 0,0,0 ); } } // 小电流启动延时复归 for(sw=0;swtSwSet[sw]; // 小电流零序电压过压 bQD = OverRelay(g_ui[PUB_AC_U01].m2[0], pSet->dU_xdl_u0TB, pSet->dU_xdl_u0TB_fh, g_tXDLTime_U0TB[sw].boolTrip); RunTR(&g_tXDLTime_U0TB[sw], bQD, dStep); // 记录事件 soeno = EV_XDL_U0TB + sw*EV_SW_NUM; if(g_tXDLTime_U0TB[sw].boolTrip && pSet->dU_xdl_u0TB) { if(soe_check(soeno)==false) { DWORD u0; u0 = _Mul_Div_U(sqrt_32fix(g_ui[PUB_AC_U01].m2[0]), 256, g_ui[PUB_AC_U01].m2_factor_k); soe_record_ev(soeno, 1, u0,0,0); // 小电流零压启动录波,但不进行接地判断 rcd_start(sw, RECORD_TYPE_LXDY, RECORD_LEN_TZQD); } } else { if(soe_check(soeno)==true) { soe_record_ev( soeno, 0, 0,0,0 ); } } */ _xdl_protect_TZ_FG(sw, dStep); } #endif // 查询小电流接地是否动作过 int xdl_is_dz(int sw) { if(g_xdl[sw].dz == 0) { return 0; } return 1; } static u32 xdl_get_LLTB_waves(int sw) { #if(EN_REC_MORE_WAVES==1) float set_xdl_T; u32 waves; set_xdl_T = pRunSet->tSwSet[sw].dT_xdl_T; waves = (u32)(set_xdl_T *1000 /20); // 周波数= X毫秒/20ms if(waves > XDL_RECORD_WAVES) { waves = XDL_RECORD_WAVES; //最大5秒 } waves += RECORD_LEN_TZQD; return waves; #else return RECORD_LEN_TZQD; #endif } int xdl_proc_file(char *szname,int *ok_num,int * err_num,int *skip_num,int *question_num) { static char cfg_file[128]; static char dat_file[128]; static int ui[32]; int fd; struct stat s; char *pdata,*name; int ret,seg; int A,D,N; int i,j,k; int d_bytes; //数字开关数占用的字节数 int flag = 0; sprintf(cfg_file, "%s.cfg", szname); sprintf(dat_file, "%s.dat", szname); //获取文件属性 if(sys_newstat(cfg_file, &s)) { rt_printf("%s:sys_newstat error\n", __func__); return -1; } pdata = rt_malloc(s.st_size+1); if(!pdata) { return -1; } pdata[s.st_size] = 0; fd = sys_open(cfg_file, O_RDONLY, 0); if(fd < 0) { rt_printf("%s:sys_open(%s) error\r\n", __func__, cfg_file); rt_free(pdata); return -1; } ret = sys_read(fd, pdata, s.st_size); if(ret != s.st_size) { rt_printf("%s:sys_read(%d) error\r\n", __func__, ret); rt_free(pdata); return -1; } sys_close(fd); ret = rcd_get_A_D_N(pdata, &A, &D, &N, ui); if(N > XDL_SAMPLE_LEN) { N = XDL_SAMPLE_LEN; } if(ret!=0) { return -1; } rt_free(pdata); //获取文件属性 if(sys_newstat(dat_file, &s)) { rt_printf("%s:sys_newstat error\n",__func__); return -1; } pdata = rt_malloc(s.st_size); if(!pdata) { return -1; } fd = sys_open(dat_file, O_RDONLY, 0); if(fd < 0) { rt_printf("%s:sys_open(%s) error\r\n", __func__, dat_file); rt_free(pdata); return -1; } ret = sys_read(fd, pdata, s.st_size); if(ret != s.st_size) { rt_printf("%s:sys_read(%d) error\r\n", __func__, ret); rt_free(pdata); return -1; } sys_close(fd); //查找零序电压 for(k=0;k0) { int r = j*(8+2*A+d_bytes)+8+2*i; g_i0_raw[j] = (s16)(u8)pdata[r]; g_i0_raw[j] |= (s16)((u8)pdata[r+1] << 8); g_i0_dwt[j] = (float)g_i0_raw[j]; } } else { g_i0_raw[j] = 0; g_i0_dwt[j] = 0; } } //xdl_smooth3(g_i0_raw, g_i0, XDL_SAMPLE_LEN); // memcpy(g_i0,g_i0_raw,sizeof(g_i0)); DWT_Matrix_Transform(g_i0_dwt, g_i0, XDL_SAMPLE_LEN); if(ui[i]>0) { int ret,jd; struct timespec ts; TSETSW *pSet = &pRunSet->tSwSet[0]; memset(&ts,0,sizeof(ts)); // 零序电流处理 // ret = xdl_proc_i0(N,0,7,&seg); ret = xdl_proc_i0(0,N,pSet->dI_xdl_zt,pRunSet->dU_xdl_u0_zt[0],&seg,0); jd = ui[i]-1; if(ui[i] >10) { *question_num = *question_num + 1; rt_printf("线路%s_%d:波形异常(ret=%d,jd=%d)!\r\n",name,i,ret,ui[i]-11); } else if(ret < 0 && jd == 0) { *skip_num = *skip_num + 1; //rt_printf("线路%s_%d:无算法(ret=%d,jd=%d)!\r\n",name,i,ret,jd); } else if(((ret-1) == jd)|| (ret == jd)) { *ok_num = *ok_num + 1; //rt_printf("线路%s_%d:判断正确(ret=%d,jd=%d)!\r\n",name,i,ret,jd); } else { *err_num = *err_num + 1; rt_printf("线路%s_%d:判断错误(ret=%d,jd=%d)!\r\n",name,i,ret,jd); // xdl_info_printf(); rt_printf("\r\n"); } rcd_start_xdl(name,i,g_xdl_rcd_a,XDL_RCD_A_NUM,XDL_SAMPLE_LEN,&ts); } } rt_free(pdata); return 0; } /** * @brief xdl_i0_argument_init_by_scale * @details 通过零流采样通道CT类型初始化小电流i0判断参数 * @param none * @return none * @author EW * @date 2024-01-16 * @remarks 根据采样电阻的不同,小电流用不同的突变判断定值,目的是兼容未改电阻的采样板 */ void xdl_i0_argument_init_by_scale(void) { int cfg_index; uint32_t sw = 0; memset(&g_i0_arg, 0,sizeof(g_i0_arg)); for(sw = 0; sw < g_sw_num; sw++) { cfg_index = g_sw[sw].ac_cfg_index[SW_AC_I0]; // TODO 待确认是否更改硬件 // if(g_equ_config_ac[cfg_index].scale == EQU_SCALE_CT_20A_300) // { // g_i0_arg[sw].ref_v_set = REV_F_CT_1A; // g_i0_arg[sw].scale_is_1A = true; // // rt_printf("sw: %d,i0 CT is CT_1V/1A\n", sw+1); // } // else { g_i0_arg[sw].ref_v_set = REV_F_CT_DEFAULT; g_i0_arg[sw].scale_is_1A = false; } } } /*------------------------------ 内部函数 ------------------------------------- 内部函数以下划线‘_’开头,不需要检查参数的合法性. */ /*------------------------------ 测试函数 ------------------------------------- 一个实体文件必须带一个本模块的测试函数来进行单元测试,如果的确不方便在本模块中 进行单元测试,必须在此注明实际的测试位置(例如在哪个实体文件中使用哪个测试函数). */ int xdl_info_printf(void) { int i; rt_printf("小电流接地信息:\r\n"); rt_printf("名称\t方向\t开始\t结束\t累加\t最大值\t最大位置\r\n"); for(i=0;idU_xdl_22V[pRunSet->tSwSet[sw].bTT_Power_v2]; i0_300ma=pRunSet->tSwSet[sw].dI_xdl_300ma; i0_30ma=i0_300ma/9; v_max = INT_MIN; for(i=0; i v_max ) { v_max =v; } } if(v_max>i0_300ma||v_maxu0_22v)break; } if(i>=XDL_SAMPLE_LEN-ADC_REC_SAMPLE/2)return -1; //电压值太小 v_max = INT_MIN; v_min=INT_MAX; bgn=i; end=bgn+ADC_REC_SAMPLE/2; dotmax=bgn; dotmin=bgn; for(i=bgn;i v_max ) { v_max = v; dotmax=i; } if(v< v_min ) { v_min = v; dotmin=i; } } if(dotmax>dotmin) { dotv=dotmin; } else { dotv=dotmax; } bgn=dotv-ADC_REC_SAMPLE/4; if(bgn<0)bgn=0; end=dotv+ADC_REC_SAMPLE/4; v_max = INT_MIN; v_min=INT_MAX; for(i=bgn;i v_max ) { v_max = v; dotmax=i; } if(v< v_min ) { v_min = v; dotmin=i; } } rt_printf("\r\ndot:bgn=%d,top=%d ,low=%d,vol=%d, top差值=%d low差值=%d\r\n ",dotv,dotmax,dotmin,dotv,abs(g_i0[dotv]-g_i0[dotmax]),abs(g_i0[dotv]-g_i0[dotmin])); // 判断电压极值点是否也是电流极值点 if((abs(g_i0[dotv]-g_i0[dotmax])<=i0_30ma) ||(abs(g_i0[dotv]-g_i0[dotmin])<=i0_30ma) ||(abs(dotmax-dotv)<=10) ||(abs(dotmin-dotv)<=10) ) // 极值点相同,为界内 { rt_printf("\r\ngz界内故障\r\n"); ret=1; } else { rt_printf("\r\ngz界外故障\r\n"); } return ret; } #endif /*------------------------------ 文件结束 ------------------------------------- */