/******************************************************************************
版权所有：	
文件名称：	dido.c
文件版本：	01.01
创建作者:	   sunxi
创建日期：	2013-02-28
功能说明：	开入开出
其它说明：
// 启动继电器
1、启动继电器可以重复多次启动，每次启动都将重新计算超时时间。
2、启动继电器有2种关闭方式:
	1) 超时自动关闭，超时时间为装置参数的遥控超时时间。
	2) 通过函数调用关闭。
3、有保护启动标志时，选择继电器不允许关闭。
4、需提供启动继电器的超时设置函数.
5、只所以采用超时自动关闭，是因为以前的程序关闭方式存在问题，可能由于资源竞争的关系，
	导致不该关闭时却关闭了，虽然概率很小，但理论上存在。
修改记录：
*/
/*------------------------------- 头文件 --------------------------------------
*/
#include "head.h"
#include "gpio.h"
#include "ad7616.h"


/*------------------------------- 宏定义 --------------------------------------
*/
#define DO_PWM_SW_HZ	0
#define DO_PWM_SW_QD	1
#define DO_PWM_SW_NULL	2
#define DO_PWM_SW_FZ	3
#define DO_QD_KC	DO_OUT4
/*------------------------------ 类型结构 -------------------------------------
*/


/*------------------------------ 全局变量 -------------------------------------
*/
struct di  g_di[EQU_SLOT_NUM_MAX];
struct _do g_do[EQU_SLOT_NUM_MAX];
struct di_struct g_di_st[EQU_SLOT_NUM_MAX][DIDO_MAX_DI_PER_SLOT];
struct do_struct g_do_st[DO_NUM];

u16	g_do_status;        // 开出状态，因为仅主板有开出
u32 g_do_start_time;

int g_do_flag;

extern u32 g_brd_type_kz; 
struct rt_stat 	g_stat_di_delay;
struct rt_stat 	g_stat_do_delay;

static u32 di_error_count = 0;
static u32 error_count = 0;

struct do_time g_do_time[EQU_SLOT_NUM_MAX];


/*------------------------------ 函数声明 -------------------------------------
*/
static int _dido_do(u32 slot,u16 v,int is_on);
static int _get_di(int slot,u16 di_num, u32 di, struct timespec ts);
static inline void _io_do_status_update(int on, unsigned int i);
unsigned int change_di_ch(unsigned int di);


/*------------------------------ 外部函数 -------------------------------------
外部函数供其它实体文件引用，必须仔细检查传入参数的合法性.
*/
/******************************************************************************
函数名称：	dido_init
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	开入开出初始化
参数说明：	无
返回值：  	无
修改记录：
*/

void dido_init_di(void)
{

    //return ret;
    
	u32 i,slot;
	u32 di0,di,di_num;

	di0 = gpio_get_di();
	// 主板
	slot = EQU_SLOT_KZ;
	di = (di0 & 0x7FFFF);
	g_di[slot].value[0] = di^g_di[slot].inv[0];

	di_num = equ_get_di_num(slot);
	for(i=0; i<di_num; i++)
	{
		if(di&(1<<i))
		{
			g_di_st[slot][i].b_on = 1;
		}
		else
		{
			g_di_st[slot][i].b_on = 0;
		}		
		if(g_di[slot].value[0]&(1<<i))
		{
			sw_di_set(g_di_st[slot][i].owner,g_di_st[slot][i].type,SW_DI_TYPE_ON);
			g_di[slot].ts_v[i][g_di[slot].ts_i[i].n] = 1;
		}

		g_di[slot].ts_t[i][g_di[slot].ts_i[i].n] = 0;
		g_di[slot].ts_i[i].n++;

	}



}


int dido_init(void)
{
	u32 i;

	// 检查装置配置
	if(equ_config_null())
	{
		return -1;
	}

	// 初始化取反标志
	for(i=0; i< g_equ_config->di_num; i++)
	{
		if(g_equ_config_di[i].is_Inverse == 0)
		{
			continue;
		}

		if(g_equ_config_di[i].slot < EQU_SLOT_NUM_MAX && g_equ_config_di[i].index < DIDO_MAX_DI_PER_SLOT)
		{
			if(g_equ_config_di[i].index < 32)
			{
				g_di[g_equ_config_di[i].slot].inv[0] |= 1<<g_equ_config_di[i].index;
			}
			else
			{
				g_di[g_equ_config_di[i].slot].inv[1] |= 1<<(g_equ_config_di[i].index - 32);
			}
		}
	}

	dido_init_di();

	dido_stat_reset();

	return 0;
}

// 只有分合闸有选择启动继电器
int dido_do_have_select(u32 slot,u32 index)
{
	int i = 0;

	i = equ_get_do_channel(index);
	if(i==DO_OUT0||i==DO_OUT1||i==DO_OUT5||i==DO_OUT6)
	{
		return 1;
	}

	return 0;
}

/******************************************************************************
函数名称：	dido_do
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	板卡开出动作
参数说明：	无
返回值：
修改记录：
*/
int dido_do(u32 slot,u16 v,int is_on)
{
	//unsigned long flags;

	// 检查DO资源
	if(equ_get_do_num(slot) == 0)
	{
		return -2;
	}

	// 保存输出值，在5ms中断中输出
	rt_irq_save(flags);
	g_do_flag = 1;
	if(is_on)
	{
		g_do[slot].on |= v;
	}
	else
	{
		g_do[slot].off |= v;
	}
	rt_irq_restore(flags);

	return 0;
}


/******************************************************************************
函数名称：	do_kout_check
函数版本：
创建作者：
创建日期：	2013-03-13
函数说明：	开出反校。
参数说明：	无
返回值：  	结果
修改记录：
*/
void io_do_check(void)
{
	u16 now,fj;
	u32 us;

#ifdef DO_KOUT_CHECK
	u8 index;

	// 取返校值，必须在下面时间点检查之
	index=g_equ_config_di[g_sw[0].di_cfg_index[SW_DI_KOCHK]].index;
	fj = gpio_di_fj(index)&0x01;//0x02;

	// 返回值只判断启动、分闸、合闸
	now = 0;
	if(g_do_status &(1<<DO_QD_KC))//预置反校DO_PWM_SW_QD
	{
		now |= 0x01;
	}
	//if(g_do_status &((1<<DO_PWM_SW_FZ)|(1<<DO_PWM_SW_HZ)))//动作反校
	//{
		//now |= 0x01;//0x02;
	//}
#else
	// 取返校值，必须在下面时间点检查之
	fj = gpio_di_fj()&0x02;

	// 返回值只判断启动、分闸、合闸
	now = 0;
	if(g_do_status &(1<<DO_PWM_SW_QD))
	{
		//now |= 0x01;
	}

	if(g_do_status &((1<<DO_PWM_SW_FZ)|(1<<DO_PWM_SW_HZ)))
	{
		now |= 0x02;
	}
#endif
	//开出动作20ms内不进行开出自检,
	//经实测启动继电器因为是继电器触点返校，其返回时间最长，但也小于10ms。
	//分合闸继电器是光耦返校，本身需要的时间可以忽略不计，但受启动继电器的影响
	us = ustimer_get_duration(g_do_start_time);
	if(us < 20*1000)
	{
		return ;
	}
	if (fj !=  now)
	{
		// rt_printf("返校失败:fj=%04x,now=%04x, g_do_status=%04x(us=%d)\r\n",fj,now, g_do_status,us);
		rt_err_set(ERR_CODE_DO_CHECK,0);
		return ;
	}
}

/******************************************************************************
函数名称：	io_do_return
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	开出保持时间
参数说明：	无
返回值：  	无
修改记录：
*/
void io_do_return(void)
{
	int i;
	u32 dly;

	for(i=0; i<DO_NUM; i++) //开出保持
	{
		if(g_do_status & (1<<i))	//正在开出
		{
			dly = ustimer_get_duration(g_do_st[i].us_on);

			// +2500是为了提高脉宽精度，因为此函数是在5ms(5000)中断中调用的.
			// 如果us_keep为0，表示此开出没有自动返回的需求，应忽略。
			if (((dly + 2500) > g_do_st[i].us_keep) && (g_do_st[i].us_keep>0))
			{
				dido_do_kz(0,i);
			}
		}
	}

 }


/******************************************************************************
函数名称：	dido_do_poll
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	是否开出巡检
参数说明：	无
返回值：  	无
修改记录：
*/
void dido_do_poll(void)
{
#if 1
	int i;

	// 真正开出
	if(g_do_flag)
	{
		g_do_flag = 0;
		for(i=0; i<EQU_SLOT_NUM_MAX; i++)
		{
			if(g_do[i].on)
			{
				_dido_do(i,g_do[i].on,1);
				g_do[i].on = 0;
			}

			if(g_do[i].off)
			{
				_dido_do(i,g_do[i].off,0);
				g_do[i].off = 0;
			}
		}
	}

	// 开出返回
	io_do_return();

	// 开出返校
	if(g_board_info[EQU_SLOT_KZ].do_num != 0)
	{
    #ifdef DO_KOUT_CHECK
		if((short)g_sw[0].di_cfg_index[SW_DI_KOCHK] != INDEX_INVALLID)
		{
			io_do_check();
		}
	#else
		//io_do_check();
	#endif
	}
	return;
#endif
}


/******************************************************************************
函数名称：	dido_qd_set_keeptime
函数版本：	01.01
创建作者:	   sunxi
创建日期：	2013-06-10
函数说明：	设置启动继电器的保持时间
参数说明：	无
返回值：  	无
修改记录：
*/
void dido_qd_set_keeptime(u32 us)
{
	g_do_st[DO_QD_KC].us_keep = us;
}


/******************************************************************************
函数名称：	get_di
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	开入防抖处理
参数说明：	无
返回值：  	无
修改记录：
*/
//unsigned int di_val;

void dido_di_poll(SHM_SAMPLE_T *sample_data)
{
	u32 di,di_num,slot;
	u64 us0;

	// 如果交流延时失电信号有，闭锁遥信
	if(pRunSet->dSDYX_T && g_run_stu.dcjlsd_t)
	{
		return;
	}

	us0 = ustimer_get_origin();

	// 主板,19个遥信
	slot = EQU_SLOT_KZ;
	di_num = equ_get_di_num(slot);
//	di = gpio_get_di();
	di = change_di_ch(~sample_data->yx_buf[0]);

	rt_printf("gpio_get_di:yx_buf[0]=%x di=%x\r\n",sample_data->yx_buf[0],di);

	_get_di(slot, di_num, di, sample_data->ts);
	rt_stat_other_in(6,ustimer_get_duration(us0));

}

/******************************************************************************
函数名称：	dido_di_is_on
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	开入状态
参数说明：	无
返回值：  	1:有开入，0:无开入
修改记录：
*/
int dido_di_is_on(u8 slot,u8 index)
{
	if(slot >= EQU_SLOT_NUM_MAX || index >= 64)
	{
		return 0;
	}

	if(index < 32)
	{
		if(g_di[slot].value[0]&(1<<index))
		{
			return 1;
		}
	}
	else
	{
		index -= 32;
		if(g_di[slot].value[1]&(1<<index))
		{
			return 1;
		}

	}
	return 0;
}

// 查看某个时间点下的开入值
int dido_di_is_on_ts(u8 slot,u8 index,u32 ts)
{
	int i,r;
	struct ts_index ts_i;

	if(slot >= EQU_SLOT_NUM_MAX || index >= 32)
	{
		return 0;
	}

#if 0
	{
		static int cnt = 0;
		if(cnt++ < 16)
			rt_printf("slot=%d,index=%d,ts=%d.\r\n",slot,index,ts);
	}
#endif

	ts_i.n = g_di[slot].ts_i[index].n - 1; //ts_i下是无值的，所以应该减1
	for(i=0;i<DIDO_TS_NUM;i++)
	{
		r = (int)(ts - g_di[slot].ts_t[index][ts_i.n]);
		if(r >= 0)
		{
			return g_di[slot].ts_v[index][ts_i.n];
		}

		ts_i.n--;
	}

	if(g_di[slot].value[0]&(1<<index))
	{
		return 1;
	}

	return 0;
}

int dido_do_kz(int on, unsigned int i)
{
	if(on)  // 开出，需启动启动继电器打开电源
	{
		if(i==DO_OUT0||i==DO_OUT1||i==DO_OUT5||i==DO_OUT6)
		{
			gpio_kout_do(on, DO_QD_KC-DO_OUT0);	// 对应X4端子的DO0~DO7
			_io_do_status_update(1,DO_QD_KC);
		}
	}
	// if(i<=DO_PWM3)
	// {
	// 	gpio_pwm_do(on, i);		// 对应X3端子的OUT3,分闸
	// 	_io_do_status_update(on,i);
	// }
	// else 
	if(i==DO_QD_KC)  //启动继电器出口
	{
		if(!on)
		{
			if(g_tRelay[0].zqd) 		// 如果保护启动，禁止关闭启动继电器
			{
				return -2;
			}
			if(g_do_status&(~(1<<DO_QD_KC)))  //有其他开出，不收启动继电器
			{
				return -3;
			}
		}
		gpio_kout_do(on, i-DO_OUT0);	// 对应X4端子的DO0~DO7
		_io_do_status_update(on,i);
	}
	else if ((i >= DO_OUT0) && (i <= DO_OUT7))
	{
		gpio_kout_do(on, i-DO_OUT0);	// 对应X4端子的DO0~DO7
		_io_do_status_update(on,i);
	}
	else
	{
		rt_printf("dido_do_kz err(on=%d,i=%d).\r\n",on,i);
	}
	return 0;
}


/*------------------------------ 内部函数 -------------------------------------
内部函数以下划线‘_’开头，不需要检查参数的合法性.
*/
static inline void _io_do_status_update(int on, unsigned int i)
{
	if(on)
	{
		g_do_status |= (1<<i);
		g_do_st[i].us_on= ustimer_get_origin();
	}
	else
	{
		g_do_status &= ~(1<<i);
	}

	// 更新返校检查时间
	g_do_start_time = ustimer_get_origin();
}


static int _dido_do(u32 slot,u16 v,int is_on)
{
	int i;

	// 循环检查开出位
	for(i=0; i<g_board_info[slot].do_num; i++)
	{
		// 如果对应开出位有效，开出。
		if(((v>>i) & 0x1))
		{
			dido_do_kz(is_on,equ_get_do_channel(i));
		}
	}
	if(((v>>g_board_info[slot].do_num) & 0x1))  // 启动继电器
	{
		dido_do_kz(is_on,DO_QD_KC);

	}
	return 0;
}

/******************************************************************************
函数名称：	_get_di
函数版本：	01.01
创建作者：
创建日期：	2013-03-13
函数说明：	开入防抖处理
参数说明：	无
返回值：
修改记录：
*/
#ifdef CN_AREA_GUANGXI//遥信全部为0
u32 kktime_0=0;
#endif
static int _get_di(int slot,u16 di_num, u32 di, struct timespec ts)
{
	int i,b_on;
	struct rtc_time_t ct;
	uint32 diff_nsec;

//	struct timespec ts;
#if 0//#ifdef CN_AREA_GUANGXI//遥信全部为0,则关闭屏幕或者关机
	if(di==0)
	{
		kktime_0++;
		if(kktime_0 > 10)
		{
		#if 1
			g_run_stu.yxError=true;	//遥信错误，关闭屏幕
		#else
			char *argv[] = {"poweroff", NULL};
			char *envp[] = { NULL };
			call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);//关机
		#endif
			return 0;
		}
	}
	else
	{
		g_run_stu.yxError=0;
		kktime_0=0;
	}
#endif

	// 获取遥信值，并记录时间戳。
	// 用秒的低8位和纳秒的高24位合成32位时间戳
//	clk_time_get(&ts);

	// 循环处理每一个遥信
	for(i=0; i<di_num; i++)
	{
		struct di_struct *pt=&g_di_st[slot][i];

		// 处理当前变位遥信。
		b_on = (di&(1<<i))? 1:0;
		if(b_on^pt->b_on)
		{
			if(!pt->b_first_change)
			{
				pt->b_first_change=true;
				pt->tm_back=0;
				pt->tm_keep=0;

				// 记录变位的起始时刻点
				pt->ts_di = ts;
				pt->dt=g_adc_dots_data_count;
#ifdef DIDO_TS_MODE
				pt->ts_tmp = ts;
#endif
			}
			else
			{
				// 累计保持时间
#ifdef DIDO_TS_MODE
				if(ts.tv_nsec >= pt->ts_tmp.tv_nsec)
				{
					diff_nsec = ts.tv_nsec - pt->ts_tmp.tv_nsec;
				}
				else
				{
					diff_nsec = 1000000000ul - pt->ts_tmp.tv_nsec + ts.tv_nsec;
				}
				if(diff_nsec > 312500ul) 
				{
					diff_nsec = 156250ul;
				}
				pt->acc_nsec += diff_nsec;
				while(pt->acc_nsec >= 156250ul)
				{
					pt->tm_keep++; 
					pt->acc_nsec -= 156250ul;
				}
				pt->ts_tmp = ts;
				
		//		rt_printf("diff_nsec:%d, pt->tm_keep:%d\r\n", diff_nsec, pt->tm_keep);
#else
				pt->tm_keep++;
#endif				
				pt->tm_back=0; // 现在返回门限是0.5us，所有必须在此清零。
				// 保持时间到，处理变位
				if(pt->tm_keep > pt->tm_filter)
				{
					// 记录遥信状态
				#if defined CN_AREA_GUANGXI || defined YX_DI_ERROR	//广西遥信异常后，遥信屏蔽不保存，不上报
					if( g_run_stu.yxEnable==0 && b_on==0)
					{
						pt->type = SOE_TYPE_TST;
						pt->b_first_change=false;
						pt->tm_back=0;
						pt->tm_keep=0;
						return 0;
					}
				#endif
					pt->b_on = b_on;

					if(g_di[slot].inv[0] & (1<<i))
					{
						b_on = !b_on;
					}
					if(b_on)
					{
						g_di[slot].value[0] |= (1<<i);
					}
					else
					{
						g_di[slot].value[0] &= ~(1<<i);
					}

					// 记录SOE
					soe_record_yx((WORD)(i+(slot<<8)),b_on,&pt->ts_di);
					timespec_to_rtc(ts, &ct, 1);

					rt_printf("di=%x value[0]=%x\r\n",di,g_di[slot].value[0]);

					// 更新逻辑模型中的值
					sw_di_set(g_di_st[slot][i].owner,g_di_st[slot][i].type,b_on ? SW_DI_TYPE_ON : SW_DI_TYPE_OFF);

					// 更新记录
					g_di[slot].ts_t[i][g_di[slot].ts_i[i].n] = pt->dt;
					g_di[slot].ts_v[i][g_di[slot].ts_i[i].n] = b_on;
					g_di[slot].ts_i[i].n++;


					//滤波处理数据复位
					pt->b_first_change=false;
					pt->tm_back=0;
					pt->tm_keep=0;

				}
			}
		}
		else if(pt->b_first_change)
		{
			// 变位返回处理
			pt->tm_back++;
			// 屏蔽下一行，
			// 最新逻辑，有效电平不连续可计数累加；
			// 无效电平连续超0.5ms复归，且不连续累加。
			//pt->tm_keep=0;

//			if(pt->tm_back > pt->tm_filter)
			if(pt->tm_back>4) // 针对高频振荡波测试优化，返回门限设置为500us附件(4*156=624us)。
			{
				pt->tm_back = 0;
				pt->tm_keep=0;
				pt->b_first_change = false;
			}
		}
    }

	return 0;
}
/*------------------------------ 测试函数 -------------------------------------
一个实体文件必须带一个本模块的测试函数来进行单元测试，如果的确不方便在本模块中
进行单元测试，必须在此注明实际的测试位置（例如在哪个实体文件中使用哪个测试函数）.
*/
// SOE信号发生器控制全局变量
extern int g_soe_gen_state;	// 状态，0停止，1初始化，2运行
extern int g_soe_gen_on_us;    // 导通时间
extern int g_soe_gen_off_us;	// 断开时间
extern int g_soe_gen_seq_us;   // 通道序列间隔时间

#define SOE_GEN_NUM			8

u32 g_soe_gen_count;
u32 g_soe_gen_us0[SOE_GEN_NUM];
u8  g_soe_gen_on[SOE_GEN_NUM];
void dido_soe_gen(void)
{
	int i;

	if(g_soe_gen_state == 0)
	{
		return;
	}
	else if(g_soe_gen_state == 1)
	{
		pit_156us_period_set(78.125);
		g_soe_gen_count = 0;

		for(i=0; i<SOE_GEN_NUM; i++)
		{
			g_soe_gen_us0[i] = g_soe_gen_seq_us*(i);
			g_soe_gen_on[i] = 0;
			g_soe_gen_state = 2;
		}
	}
	else if(dido_di_is_on(1,0))
	{
		for(i=0; i<SOE_GEN_NUM; i++)
		{
			if(g_soe_gen_on[i] == 0)
			{
				if((int)(g_soe_gen_count - g_soe_gen_us0[i]) >= g_soe_gen_off_us)
				{
					g_soe_gen_on[i] = 1;
					gpio_kout_do(1,i);
					g_soe_gen_us0[i] = g_soe_gen_count;
				}
			}
			else
			{
				if((int)(g_soe_gen_count - g_soe_gen_us0[i]) >= g_soe_gen_on_us)
				{
					g_soe_gen_on[i] = 0;
					gpio_kout_do(0,i);
					g_soe_gen_us0[i] = g_soe_gen_count;
				}
			}
		}
	}
	else
	{
		g_soe_gen_count = 0;

		for(i=0; i<SOE_GEN_NUM; i++)
		{
			g_soe_gen_us0[i] = g_soe_gen_seq_us*(i);
			g_soe_gen_on[i] = 0;
			g_soe_gen_state = 2;
		}
	}

	g_soe_gen_count++;
}

int dido_printf(void)
{
	int i,slot,index;
	struct ts_index ts_i;

	rt_printf("槽号\t值\r\n");
	for(i=0; i<EQU_SLOT_NUM_MAX; i++)	// xj 2015-5-4
	{
		rt_printf("%02d:\tvalue=0x%08lx_%08lx,inv=0x%08lx_%08lx\r\n",i,g_di[i].value[1],g_di[i].value[0],g_di[i].inv[1],g_di[i].inv[0]);
	}

	rt_printf("\r\n");

	slot = EQU_SLOT_MAIN;
	index = 1;
	ts_i.n = g_di[slot].ts_i[index].n;
	rt_printf("开入时间戳记录:slot=%d,index=%d,ts_i=%d.\r\n",slot,index,ts_i.n);
	rt_printf("序号\t值\t时间戳\r\n");
	ts_i.n = 0;
	for(i=0;i<DIDO_TS_NUM;i++)
	{
		rt_printf("%02d\t%d\t%08lu\r\n",ts_i.n,g_di[slot].ts_v[index][ts_i.n],g_di[slot].ts_t[index][ts_i.n]);
		ts_i.n++;
	}

	rt_printf("开出状态:0x%04x\r\n\r\n",g_do_status);

	rt_stat_printf(&g_stat_di_delay);
	rt_stat_printf(&g_stat_do_delay);

	return 0;
}

int dido_stat_reset(void)
{
	rt_stat_init(&g_stat_di_delay,"di_delay");
	rt_stat_init(&g_stat_do_delay,"do_delay");

	return 0 ;
}

int dido_single_test(u8 slot, u8 point)
{
	dido_do(slot,(1<<point),1);
	ustimer_delay(250*USTIMER_MS);
	dido_do(slot,(1<<point),0);

	return 0;
}

int dido_test(void)
{
	static unsigned long us0 = 0;
	static char dido_test_pair[3][2] =
	{
		{1,3},
		{2,4},
		{5,5},
	};

	static char dido_test_di[32] =
	{
		0,1,2,3,
		0,1,2,3,
		4,5,6,7,
		4,5,6,7,
		8,9,10,11,
		8,9,10,11,
		12,13,10,11,
		12,13,12,13
	};

	int i,j,k,err_once,di_num;
	u32 slot_di,slot_do;

	if(g_test_on == 0)
	{
		return 0;
	}
	// 5秒处理一次
	if(ustimer_delay_origin2(&us0,10*USTIMER_SEC) != 1)
	{
		return 0;
	}

	rt_printf("dido_test begin...\r\n");

	ustimer_delay(1*USTIMER_SEC);

#if 1
	// 测试开出板、开入板
	// 开出板逐一开出
	rt_printf("dido_test single.\r\n");
	err_once = 0;
	for(i=0; i<3; i++)
	{
		slot_do = dido_test_pair[i][0];
		slot_di = dido_test_pair[i][1];

		for(j=0;j<equ_get_do_num(slot_do); j++)
		{
			dido_do(slot_do,(1<<j),1);
			ustimer_delay(250*USTIMER_MS);

			di_num = equ_get_di_num(slot_di);
			if(slot_di == equ_get_slot_by_type(BOARD_TYPE_AUX))
			{
				di_num -= 1;
			}
			for(k=0;k<di_num; k++)
			{
				// 是否对应的开出点
				if(dido_test_di[k] == j)
				{
					if((g_di[slot_di].value[0] & (1<<k)) == 0)
					{
						err_once++;
						error_count++;
						rt_printf("dido_test on err(err=%d):i=%d,j=%d,k=%d,di=0x%08lx.\r\n",err_once,i,j,k,g_di[slot_di].value[0]);
					}
				}
				else
				{
					if((g_di[slot_di].value[0] & (1<<k)) != 0)
					{
						err_once++;
						error_count++;
						rt_printf("dido_test off err(err=%d):i=%d,j=%d,k=%d,di=0x%08lx.\r\n",err_once,i,j,k,g_di[slot_di].value[0]);
					}
				}
			}

			dido_do(slot_do,(1<<j),0);
			ustimer_delay(20*USTIMER_MS);
			watchdog_feed_mainloop();

		}
	}
#endif

	rt_printf("\r\n开入变位时间超出次数:%lu\r\n",di_error_count);
	rt_printf("name\t\t\tmin\tmax\tavg\tsum\t\tcnt\n");
	rt_stat_printf(&g_stat_di_delay);

	rt_printf("\r\n");

	for(i=0;i<EQU_SLOT_NUM_MAX;i++)
	{
		if(g_do_time[i].slot != 0)
		{
			rt_printf("do_time[%d]:diff_max=%lu,diff_out_count=%lu\r\n",
					g_do_time[i].slot,g_do_time[i].diff_max,g_do_time[i].diff_out_count);
		}
	}

	rt_printf("\r\n");

	rt_printf("dido_test end:err_once=%d,error_count=%lu!\r\n\r\n\r\n",err_once,error_count);

	return 0;
}

bool check_board_di_valid(u8 slot,u8 index)
{
	u32 i;
	// 初始化取反标志
	index-=1;
	for(i=0; i< g_equ_config->di_num; i++)
	{
		if((g_equ_config_di[i].slot==1)&&(g_equ_config_di[i].index==index))
		{
			if(g_equ_config_di[i].type==0)return true;
		}
	}
	return false;
}

bool check_board_kc6_close(u8 slot,u8 index)
{
	u32 i;
	// 初始化取反标志
	index-=1;
	for(i=0; i< g_equ_config->do_num; i++)
	{
		if((g_equ_config_do[i].slot==slot)&&(g_equ_config_do[i].index==index))
		{
			if(g_equ_config_do[i].type!=0)return true;
		}
	}
	return false;
}

#define DIGROUP  4
#define V3_OUT_NUM  5
int dido_auto_test_for_v3(char *buf,int num)
{
	// 12个开入,用5个开出进行测试
	static char test_di[6][DIGROUP] =
	{
		{1, 6, 	11,0},
		{2, 7, 	12,0},
		{3, 8, 	13,0},
		{4, 9, 	14,0},
		{16, 0, 0,0},
		{5, 10, 15,0},
	};

	int i,j,k,err_once,flag,flag1,di_num, do_num;
	u32 slot_di,slot_do, di_result;
	u8 tmp[5],tmp_data[5];
	u8 di_ok[4] = {0};
	u8 do_errflag = 0;

	char dido_test_pair[num][2];
	unsigned long us_begin = 0;
	bool bkc6close=false;
	slot_do=0;
	bkc6close=check_board_kc6_close(1,6);

	dido_stat_reset();

	if(bkc6close)
	{
		dido_do(slot_do,(1<<5),1);  // 开出6若焊接为常闭输出，先开出，打开节点
		ustimer_delay(100*USTIMER_MS);
	}

	// 取出板卡槽位
	for(i=0;i<(num*2);)
	{
		for(j=0;j<2;j++)
		{
			dido_test_pair[i/2][j] = buf[i];
			i++;
		}
	}

	flag = 0;
	flag1 = 0;
	memset(tmp,0,sizeof(tmp));
	memset(tmp_data,0,sizeof(tmp_data));
	memset(dido_buf,0,sizeof(dido_buf));

	rt_printf("dido_test begin...\r\n");

	err_once = 0;

	// 以组为循环
	for(i=0; i<num; i++)
	{
		slot_do = dido_test_pair[i][0];
		slot_di = dido_test_pair[i][1];

		// 槽位暂存
		dido_buf[i+i*13] = slot_do;
		dido_buf[i+i*13+5] = slot_di;

		// 取出对应槽位的板卡中DI和DO的数量
		di_num = equ_get_di_num(slot_di);
		do_num = equ_get_do_num(slot_do);

		//dido_do(slot_do,(1<<5),1);  //磁保持继电器
		// 启动记时
		us_begin = ustimer_get_origin();

		// 逐一输出开出点
		for(j=0; j<V3_OUT_NUM; j++)
		{
			dido_do(slot_do,(1<<j),1);
			if(j==5&&bkc6close)
			{
				dido_do(slot_do,(1<<5),0);  // 开出6若焊接为常闭输出，反操作为闭合
			}
			ustimer_delay(150*USTIMER_MS);

			memset(di_ok,0,sizeof(di_ok));
			do_errflag = DIGROUP;

			for (k=0; k<4; k++)
			{
				bool bIgnore=false;
				di_result = (g_di[slot_di].value[0] ^ g_di[slot_di].inv[0]);

				k+=i*2;
				if(test_di[j][k]==17)
				{
					bIgnore=check_board_di_valid(slot_di,17);
				}
				if(test_di[j][k]==18)
				{
					bIgnore=check_board_di_valid(slot_di,18);
				}
				if (!test_di[j][k]||bIgnore)
				{
					do_errflag--;
				}
				else
				{
					if (!(di_result & (1<<(test_di[j][k]-1))))	//对应开入无值
					{
						do_errflag--;
						di_ok[k] = test_di[j][k];
					}
				}
			}
			if (!do_errflag)	//开出对应的所有开入都没有值，即代表该开出有问题
			{
				if (j<8)
				{
					dido_buf[i+i*13+1] |= (1<<j);		//开出错误标志
				}
				else if (j<16)
				{
					dido_buf[i+i*13+2] |= (1<<(j-8));		//开出错误标志
				}

				rt_printf("dido_test do err:i=%d,j=%d,di=%d,di_l=0x%04lx,di_h=0x%04lx.\r\n",
						i,j,di_ok[0], g_di[slot_di].value[0],g_di[slot_di].value[1]);
			}
			else
			{
				for (k=0; k<4; k++)
				{
					if (!di_ok[k])
						continue;
					if ((di_ok[k]-1) < 8)
					{
						dido_buf[i+i*13+6] |= (1<<(di_ok[k]-1));
					}
					else if ((di_ok[k]-1) < 16)
					{
						dido_buf[i+i*13+7] |= (1<<(di_ok[k]-1-8));
					}
					else if ((di_ok[k]-1) < 24)
					{
						dido_buf[i+i*13+8] |= (1<<(di_ok[k]-1-16));
					}
					else if ((di_ok[k]-1) < 32)
					{
						dido_buf[i+i*13+9] |= (1<<(di_ok[k]-1-24));
					}
					else if ((di_ok[k]-1) < 40)
					{
						dido_buf[i+i*13+10] |= (1<<(di_ok[k]-1-32));
					}
					else if ((di_ok[k]-1) < 48)
					{
						dido_buf[i+i*13+11] |= (1<<(di_ok[k]-1-40));
					}
					else if ((di_ok[k]-1) < 56)
					{
						dido_buf[i+i*13+12] |= (1<<(di_ok[k]-1-48));
					}
					else if ((di_ok[k]-1) < 64)
					{
						dido_buf[i+i*13+13] |= (1<<(di_ok[k]-1-56));
					}
					rt_printf("dido_test di err:i=%d,j=%d,di=%d,di_l=0x%04lx,di_h=0x%04lx.\r\n",
							i,j,di_ok[k], g_di[slot_di].value[0],g_di[slot_di].value[1]);
				}
			}

			dido_do(slot_do,(1<<j),0);
			if(j==5&&bkc6close)
			{
				dido_do(slot_do,(1<<5),1);  // 开出6若焊接为常闭输出，反操作为闭合
			}

			//if(j==4)
			//{
				//dido_do(slot_do,(1<<5),1);  //磁保持继电器
			//}
			ustimer_delay(20*USTIMER_MS);
			watchdog_feed_mainloop();
		}
	}


	for(i=0;i<EQU_SLOT_NUM_MAX;i++)
	{
		if(g_do_time[i].slot != 0)
		{
			rt_printf("do_time[%d]:diff_max=%lu,diff_out_count=%lu\r\n",
					g_do_time[i].slot,g_do_time[i].diff_max,g_do_time[i].diff_out_count);

		}
	}

	rt_printf("dido_test end:err_once=%d,error_count=%lu!\r\n\r\n\r\n",err_once,error_count);


	return 0;
}

int dido_auto_test(char *buf,int num)
{
	#define KC_COUNT 11
	// 12个开入,用5个开出进行测试
#if 0
	static char test_di[6][DIGROUP] =
	{
		{1, 6, 	11,0},
		{2, 7, 	12,0},
		{3, 8, 	13,0},
		{4, 9, 	14,0},
		{16, 0, 	0,0},
		{5, 10, 	15,0},
	};
#endif
	static char test_di[KC_COUNT][DIGROUP] =
	{
		{1, 12, 23,0},
		{2, 13, 24,0},
		{3, 14, 0,0},
		{4, 15, 0,0},
		{5, 16, 0,0},
		{6, 17, 0,0},
		{7, 18, 0,0},
		{8, 19, 0,0},
		{9, 20, 0,0},
		{10,21, 0,0},
		{11,22, 0,0},
	//	{ 0, 0, 0,0}
	};
	u8 brd_v3[] = {22, 23};
	int i,j,k,err_once,flag,flag1,di_num, do_num;
	u32 slot_di,slot_do, di_result;
	u8 tmp[5],tmp_data[5];
	u8 di_ok[4] = {0};
	u8 do_errflag = 0;

	char dido_test_pair[num][2];
	unsigned long us_begin = 0;
	bool bkc6close=false;

	rt_printf("g_brd_type_kz = %lu\r\n ", g_brd_type_kz);
	rt_printf("g_equ_config->do_num = %lu\r\n ", g_equ_config->do_num);

	for(i=0; i<(int)sizeof(brd_v3); i++)
	{
		if(g_brd_type_kz+BOARD_TYPE_DFTU_KC00==brd_v3[i])//
		{
			rt_printf("auto_test_for_v3\r\n ");
			return (dido_auto_test_for_v3(buf, num));
		}
	}
	rt_printf("auto_test_for_v4\r\n ");
	//bkc6close=check_board_kc6_close(1,6);

	dido_stat_reset();

	if(bkc6close)
	{
		dido_do(1,(1<<5),1);  // 开出6若焊接为常闭输出，先开出，打开节点
		ustimer_delay(100*USTIMER_MS);
	}

	// 取出板卡槽位
	for(i=0;i<(num*2);)
	{
		for(j=0;j<2;j++)
		{
			dido_test_pair[i/2][j] = buf[i];
			i++;
		}
	}

	flag = 0;
	flag1 = 0;
	memset(tmp,0,sizeof(tmp));
	memset(tmp_data,0,sizeof(tmp_data));
	memset(dido_buf,0,sizeof(dido_buf));

	rt_printf("dido_test begin...\r\n");

	err_once = 0;

	// 以组为循环
	for(i=0; i<num; i++)
	{
		slot_do = dido_test_pair[i][0];
		slot_di = dido_test_pair[i][1];

		// 槽位暂存
		dido_buf[i+i*13] = slot_do;
		dido_buf[i+i*13+5] = slot_di;

		// 取出对应槽位的板卡中DI和DO的数量
		di_num = equ_get_di_num(slot_di);
		do_num = equ_get_do_num(slot_do);

		//dido_do(slot_do,(1<<5),1);  //磁保持继电器
		// 启动记时
		us_begin = ustimer_get_origin();

		// 逐一输出开出点
		for(j=0; j<KC_COUNT; j++)
		{
			dido_do(slot_do,(1<<j),1);
			if(j==5&&bkc6close)
			{
				dido_do(slot_do,(1<<5),0);  // 开出6若焊接为常闭输出，反操作为闭合
			}
			ustimer_delay(150*USTIMER_MS);

			memset(di_ok,0,sizeof(di_ok));
			do_errflag = DIGROUP;

			for (k=0; k<4; k++)
			{
				bool bIgnore=false;
				di_result = (g_di[slot_di].value[0] ^ g_di[slot_di].inv[0]);

				k+=i*2;
				if(test_di[j][k]==17)
				{
					bIgnore=check_board_di_valid(slot_di,17);
				}
				if(test_di[j][k]==18)
				{
					bIgnore=check_board_di_valid(slot_di,18);
				}
				if (!test_di[j][k]||bIgnore)
				{
					do_errflag--;
				}
				else
				{
					if (!(di_result & (1<<(test_di[j][k]-1))))	//对应开入无值
					{
						do_errflag--;
						di_ok[k] = test_di[j][k];
					}
				}
			}
			if (!do_errflag)	//开出对应的所有开入都没有值，即代表该开出有问题
			{
				if (j<8)
				{
					dido_buf[i+i*13+1] |= (1<<j);		//开出错误标志
				}
				else if (j<16)
				{
					dido_buf[i+i*13+2] |= (1<<(j-8));		//开出错误标志
				}

				rt_printf("dido_test do err:i=%d,j=%d,di=%d,di_l=0x%04lx,di_h=0x%04lx.\r\n",
						i,j,di_ok[0], g_di[slot_di].value[0],g_di[slot_di].value[1]);
			}
			else
			{
				for (k=0; k<4; k++)
				{
					if (!di_ok[k])
						continue;
					if ((di_ok[k]-1) < 8)
					{
						dido_buf[i+i*13+6] |= (1<<(di_ok[k]-1));
					}
					else if ((di_ok[k]-1) < 16)
					{
						dido_buf[i+i*13+7] |= (1<<(di_ok[k]-1-8));
					}
					else if ((di_ok[k]-1) < 24)
					{
						dido_buf[i+i*13+8] |= (1<<(di_ok[k]-1-16));
					}
					else if ((di_ok[k]-1) < 32)
					{
						dido_buf[i+i*13+9] |= (1<<(di_ok[k]-1-24));
					}
					else if ((di_ok[k]-1) < 40)
					{
						dido_buf[i+i*13+10] |= (1<<(di_ok[k]-1-32));
					}
					else if ((di_ok[k]-1) < 48)
					{
						dido_buf[i+i*13+11] |= (1<<(di_ok[k]-1-40));
					}
					else if ((di_ok[k]-1) < 56)
					{
						dido_buf[i+i*13+12] |= (1<<(di_ok[k]-1-48));
					}
					else if ((di_ok[k]-1) < 64)
					{
						dido_buf[i+i*13+13] |= (1<<(di_ok[k]-1-56));
					}
					rt_printf("dido_test di err:i=%d,j=%d,di=%d,di_l=0x%04lx,di_h=0x%04lx.\r\n",
							i,j,di_ok[k], g_di[slot_di].value[0],g_di[slot_di].value[1]);
				}
			}

			dido_do(slot_do,(1<<j),0);
			if(j==5&&bkc6close)
			{
				dido_do(slot_do,(1<<5),1);  // 开出6若焊接为常闭输出，反操作为闭合
			}

			//if(j==4)
			//{
				//dido_do(slot_do,(1<<5),1);  //磁保持继电器
			//}
			ustimer_delay(20*USTIMER_MS);
			watchdog_feed_mainloop();
		}
	}


	for(i=0;i<EQU_SLOT_NUM_MAX;i++)
	{
		if(g_do_time[i].slot != 0)
		{
			rt_printf("do_time[%d]:diff_max=%lu,diff_out_count=%lu\r\n",
					g_do_time[i].slot,g_do_time[i].diff_max,g_do_time[i].diff_out_count);

		}
	}

	rt_printf("dido_test end:err_once=%d,error_count=%lu!\r\n\r\n\r\n",err_once,error_count);


	return 0;
}


/*------------------------------ 文件结束 -------------------------------------
*/
