标定,主要是指使用标准的计量仪器对所使用仪器的准确度(精度)进行检测是否符合标准,一般大多用于精密度较高的仪器。标定也可以认为是校准。因此,也可以认为标定包含以上两方面的意思。
1、确定仪器或测量系统的输入—输出关系,赋予仪器或测量系统分度值;
2、确定仪器或测量系统的静态特性指标;
3、消除系统误差,改善仪器或系统的精确度。
4、在科学测量中,标定是一个不容忽视的重要步骤
标定同时也是法定量值传递的一种规程,也是数字信号转换成物理值得一种必要手段,标定的原理主要是设置一个内在得数字信号和物理量值间的关系,然后验证测量的误差是否满足要求,根据测量误差再修正对应关系得一个调节过程。由于采集得传感器型号本身具有多样性,往往也不是一种线性对应关系,为了更准确得表达对应关系,采用分段方法来拟合曲线对应关系,减少运算负担,以下是一个通用的多路传感器分段线性标定的实现方法和转换运算得api,当然你也可以进一步采用最小二乘法进行曲线拟合,将标定精度进一步提高。欢迎行业专家共同交流指正。
//************************************************************
//Copyright(C)2010
// 数据采集标定配置源文件
//文件名称:calibermanage.c
//文件标识:(内参)
//摘 要:
// 1.配合头文件使用;
// 2.硬件参考作品;
// 3.
//
//当前版本:1.0
//作 者:xxd
//完成日期:2010.6.23
//
//取代版本:无
//原 作 者:无
//完成日期:无
//
//硬件说明:
//
//*******************************************************************
//通道标定配置相关
//*********************************************************************
#define CLIBERMANAGE_GLOBALES
#include "includes.h"
#include "MAX197DRIVER.h"
#include "digin.h"
#include "digout.h"
#include "dac.h"
#include "calibermanage.h"
#include "parameter.h"
//Digital In Config
#if (CONTROL_FOR_APP==CHASSIS_DYNAMOMETER)
static char *ADChannel[]=
{
"底盘测功机力矩模拟输入通道",//CHASSISDYNAMOMETERTORQUEAD
"电涡流电流模拟输入通道",
"保留模拟输入通道"//RESERVEAD
};
static char* DIOChannel[]=
{
"底盘测功机举升上数字输出通道",//DIODYNAMOMETERLIFTERUP=0,
"底盘测功机制动举升下数字输出通道",//DIODYNAMOMETERLIFTERDN,
"底盘测功机左电机启动数字输出通道",//DIODYNAMOMETERMOTORLSTART ,
"底盘测功机右电机启动数字输出通道",//DIODYNAMOMETERMOTORRSTART ,
"底盘测功机左电机停机数字输出通道",//DIODYNAMOMETERMOTORLSTOP,
"底盘测功机左电机停机数字输出通道",//DIODYNAMOMETERMOTORRSTOP,
"保留数字输出通道",//DIORESERVEOUTSW, must
"遥控输入数字输入通道",//DIOREMOTESW,
"底盘测功测试入数字输入通道",//DIOCHASSPOWERINSW,
"保留数字输入通道"//DIORESERVEINSW
};
static char *DAChannel[]=
{
"涡流机控制"//ControlSPEED
};
#endif
#if (CONTROL_FOR_APP==AUTO_TEST_LINE)
static char *ADChannel[]=
{
"SPEAKERAD", //声级模拟输入通道
"COAD", //一氧化碳模拟输入通道
"HCAD", //碳氢模拟输入通道
"CO2AD", //二氧化碳模拟输入通道
"O2AD", //氧气模拟输入通道
"NOxAD", //氮氧化物模拟输入通道
"LUNTAD", //烟度模拟输入通道
"ITENSTYAD", //光强模拟输入通道
"LRAD", //大灯左右偏模拟输入通道
"UDAD", //大灯上下偏模拟输入通道
"LIGHTHEIGHAD", //大灯灯高模拟输入通道//2000.10.8 by xxd
"SLIDEAD", //侧滑模拟输入通道
"WEIGHTLAD", //左轴重模拟输入通道
"WEIGHTRAD", //右轴重模拟输入通道
"BRAKELAD", //左制动模拟输入通道
"BRAKERAD", //右制动模拟输入通道
"FOOTFORCEAD", //制动踏板力模拟输入通道
"SPEEDAD", //速度模拟输入通道
"CHASSISAD", //底盘模拟输入通道
"RESERVEAD" //保留模拟输入通道
};
static char* DIOChannel[]=
{
"DIOLIGHTUP", //0大灯仪上行数字输出通道
"DIOLIGHTDN", //1大灯仪下行数字输出通道
"DIOLIGHTLF", //2大灯仪左行数字输出通道
"DIOLIGHTRT", //3大灯仪右行数字输出通道
"DIOLIGHTTEST", //4大灯仪进入测定数字输出通道
"DIOSPEEDLIFTERUP", //5速度举升上数字输出通道
"DIOSPEEDLIFTERDN", //6速度举升下数字输出通道
"DIOBRAKELIFTERUP", //7制动举升上数字输出通道
"DIOBRAKELIFTERDN", //8制动举升下数字输出通道
"DIOBRAKEMOTORLF" , //9左制动电机启动数字输出通道
"DIOBRAKEMOTORRT" , //10右制动电机启动数字输出通道
"DIOCUTBRAKEMOTORLF", //11左制动电机停机数字输出通道
"DIOCUTBRAKEMOTORRT", //12右制动电机停机数字输出通道
"DIOFREEROLLERTIGHT", //13自由滚筒紧数字输出通道
"DIOFREEROLLERRELEASE", //14自由滚筒松数字输出
"DIORESERVEOUTSW", //15保留数字输出通道
"DIOTHIRDROLLERLFSW", //16左制动第三滚筒光电数字输入通道
"DIOTHIRDROLLERRTSW", //17右制动第三滚筒光电数字输入通道
"DIOSPEEDINSW", //18速度测试入光电数字输入通道
"DIOSLIDEINSW", //19侧滑测试入光电数字输入通道
"DIOSLIDEOUTSW", //20侧滑测试出光电数字输入通道
"DIOWEIGHTINSW", //21轴重测试入光电数字输入通道
"DIOBRAKEINSW", //22制动测试入光电数字输入通道
"DIOBRAKESTARTSW", //23制动测试台准备好数字输入通道
"DIOLIGHTINASW", //24大灯测试入A光电数字输入通道
"DIOLIGHTINBSW", //25大灯测试入B光电数字输入通道
"DIOLIGHTINAREASW", //26大灯测试进入光照区数字输入通道
"DIOSPEAKERINASW", //27声级测试入A光电数字输入通道
"DIOSPEAKERINBSW", //28声级测试入B光电数字输入通道
"DIOGASINSW", //29废气测试入光电数字输入通道
"DIOLUNTINSW", //30烟度测试入光电数字输入通道
"DIOCHASSISINSW", //31底盘测试入光电数字输入通道
"DIOREMOTESW", //32遥控输入数字输入通道
"DIOPRINTPROMPTSW", //33烟度测试数据准备好数字输入通道
"DIOOILCONSUMPTIONINSW", //34油耗测试入数字输入通道
"DIOCHASSPOWERINSW", //35底盘测功测试入数字输入通道
"DIOENGINEINSW", //36发动机分析测试入数字输入通道
"DIOALIGNINSW", //37前轮定位测试入数字输入通道
"DIOBALANCEINSW", //38动平衡测试入数字输入通道
"DIOOILANALYTICALINSW", //39油质分析测试入数字输入通道
"DIORESERVEINSW", //40测试入数字输入通道
"DIOWAITINSW" //41等待到位入数字输入通道
};
static char *DAChannel[]=
{
"ControlSPEED"
};
#endif
static INT8U DO_A_Status=0x00;
static INT8U DO_B_Status=0x00;
void _LogADCName()
{
int i=0;
char logstr[60];
_log("ADC Logic channel:\r\n");
for(i=0;i=MINADCSAMPLERATE)&&(nSampleRate<=MAXADCSAMPLERATE)) ADRes[nIdx].SampleRate=nSampleRate;
else return FALSE;
if((nSampleDot>=MINADCSAMPLEDOT)&&(nSampleDot<=MAXADCSAMPLEDOT)) ADRes[nIdx].SampleDot=nSampleDot;
else return FALSE;
//配置物理通道
ADC_Sample_Config(nPhysicsChannel,nSampleRate,nSampleDot,TRUE);
return TRUE;
}
return FALSE;
}
BOOLEAN ChannelDIOSet(DIOName nIdx,INT8U nPhysicsChannel,HILOW hlStdStatus)
{
if(nIdxADRes[Idx].CoEfficient.SampleValue[i])
&& ( TestValue<=ADRes[Idx].CoEfficient.SampleValue[i+1] ))
{
X1= ADRes[Idx].CoEfficient.SampleValue[i];
X2= ADRes[Idx].CoEfficient.SampleValue[i+1];
Y1= ADRes[Idx].CoEfficient.InputValue[i];
Y2= ADRes[Idx].CoEfficient.InputValue[i+1];
if((X2-X1)==0)
break;
KK=(Y2-Y1)/(X2-X1);
Bb=(X2*Y1-X1*Y2)/(X2-X1);
Value=KK*TestValue+Bb-ZeroValue;
break;
}
//low of the Min Caliber Value
if( (TestValue<=ADRes[Idx].CoEfficient.SampleValue[0]) )
{
X1= ADRes[Idx].CoEfficient.SampleValue[0];
X2= ADRes[Idx].CoEfficient.SampleValue[1];
Y1= ADRes[Idx].CoEfficient.InputValue[0];
Y2= ADRes[Idx].CoEfficient.InputValue[1];
if((X2-X1)==0)
break;
KK=(Y2-Y1)/(X2-X1);
Bb=(X2*Y1-X1*Y2)/(X2-X1);
Value=KK*TestValue+Bb-ZeroValue;
break;
}
//high of the Max Caliber Value
if( (TestValue>=ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-1]) )
{
X1= ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-2];
X2= ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-1];
Y1= ADRes[Idx].CoEfficient.InputValue[ADRes[Idx].CoEfficient.Num-2];
Y2= ADRes[Idx].CoEfficient.InputValue[ADRes[Idx].CoEfficient.Num-1];
if((X2-X1)==0)
break;
KK=(Y2-Y1)/(X2-X1);
Bb=(X2*Y1-X1*Y2)/(X2-X1);
Value=KK*TestValue+Bb-ZeroValue;
break;
}
}
}
//2 Mode
//First > Last 降幂
if(ADRes[Idx].CoEfficient.SampleValue[0]>ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-1])
{
for( i=0;i=ADRes[Idx].CoEfficient.SampleValue[i+1] ))
{
X1= ADRes[Idx].CoEfficient.SampleValue[i+1];
X2= ADRes[Idx].CoEfficient.SampleValue[i];
Y1= ADRes[Idx].CoEfficient.InputValue[i+1];
Y2= ADRes[Idx].CoEfficient.InputValue[i];
if((X2-X1)==0)
break;
KK=(Y2-Y1)/(X2-X1);
Bb=(X2*Y1-X1*Y2)/(X2-X1);
Value=KK*TestValue+Bb-ZeroValue;
break;
}
if( (TestValue>=ADRes[Idx].CoEfficient.SampleValue[0]) )
{
X1= ADRes[Idx].CoEfficient.SampleValue[1];
X2= ADRes[Idx].CoEfficient.SampleValue[0];
Y1= ADRes[Idx].CoEfficient.InputValue[1];
Y2= ADRes[Idx].CoEfficient.InputValue[0];
if((X2-X1)==0)
break;
KK=(Y2-Y1)/(X2-X1);
Bb=(X2*Y1-X1*Y2)/(X2-X1);
Value=KK*TestValue+Bb-ZeroValue;
break;
}
if( TestValue<=ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-1] )
{
X1= ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-1];
X2= ADRes[Idx].CoEfficient.SampleValue[ADRes[Idx].CoEfficient.Num-2];
Y1= ADRes[Idx].CoEfficient.InputValue[ADRes[Idx].CoEfficient.Num-1];
Y2= ADRes[Idx].CoEfficient.InputValue[ADRes[Idx].CoEfficient.Num-2];
if((X2-X1)==0)
break;
KK=(Y2-Y1)/(X2-X1);
Bb=(X2*Y1-X1*Y2)/(X2-X1);
Value=KK*TestValue+Bb-ZeroValue;
break;
}
}
}
return Value;
}
int GetCalculateValue( ADName Idx , float *InDatBuf,int DataLen,float *OutDataBuf )
{
float value=0;
int count=0;
for(count=0;countAIOScaleOut=Caculate(paio->nIdex,paio->AIOScaleIn);
}
#endif
int ConfigLogicAD(ADName Idx,INT8U nadchannel,INT16U nSampleRate,INT8U nSampleDot)
{
ADRes[Idx].ADCh=nadchannel;
ADRes[Idx].SampleRate=nSampleRate;
ADRes[Idx].SampleDot=nSampleDot;
ADC_Sample_Config(ADRes[Idx].ADCh,ADRes[Idx].SampleRate,ADRes[Idx].SampleDot,TRUE);
}
// The follow function designed for add AD channel
// including set channel valid
// reading caliber parameters and channel's number
// return code: 0-->通道正常 1--->零点电压过正 2--->零点电压过负
// 3-->满量程 -1-->读数据错误、通道不可用
int AddAD( ADName Idx )
{
//读模型数据
if(ReadCaliber( Idx )!=0 )
{
return -1; //read rigister error
}
//判断通道可用性
if( ADRes[Idx].Valid==0)
{
return -1; //AD channel don't caliber
}
//取当前零点电压判断
float fCurrentZeroVoltage;
//配置本通道的采样参数
#if MODULE_AIO==1
//INT8U AICfgCal(INT8U n, FP32 gain, FP32 offset);
//INT8U AICfgConv(INT8U n, FP32 gain, FP32 offset, INT8U pass);
//ADRes[Idx].SampleRate是1ms单位的倍数,即这么多毫秒后采样一次
AIO *paio;
paio = &AITbl[ADRes[Idx].ADCh]; /* Point at first analog input channel */
paio->nIdex=Idx;
AICfgConv(ADRes[Idx].ADCh, ADC_GAIN, ADC_OFFSET, ADRes[Idx].SampleRate);
AICfgCal(ADRes[Idx].ADCh, 1.00, 0.00);
AICfgScaling(ADRes[Idx].ADCh,GetMyEU,paio);
#else
//void ADC_Sample_Config(INT8U nChannel,INT32U samplerate,INT8U sampledot,BOOLEAN bstart);
ADC_Sample_Config(ADRes[Idx].ADCh,ADRes[Idx].SampleRate,ADRes[Idx].SampleDot,TRUE);
#endif
if(Sample(Idx,&fCurrentZeroVoltage)!=0)
return -1;
ADRes[Idx].AdSelfTest.fCurrentZeroVoltageValue=fCurrentZeroVoltage;
if((fCurrentZeroVoltage>5.0f)||(fCurrentZeroVoltage<-5.0f))
return 3; //满量程
if(fCurrentZeroVoltage>(ADRes[Idx].AdSelfTest.fStdZeroVoltageValue+ADRes[Idx].AdSelfTest.fVoltageRangeValue/1000))
return 1;//零点电压过正
if(fCurrentZeroVoltage<(ADRes[Idx].AdSelfTest.fStdZeroVoltageValue-ADRes[Idx].AdSelfTest.fVoltageRangeValue/1000))
return 2;//零点电压过负
return 0;
}
// The follow function designed for add DIO channel
// including set channel valid
// reading channel'number
// return code : 1----完全没对准 0----正常 2---闪烁
int AddDIO( DIOName Idx )
{
int nNumber=0;
int nInvalidCount=0;
int nTotalTestCount=10;
int nTestCount=0;
memcpy((void*)&DIORes[Idx], (void *)(DIO_PARAMETER_ADDR+DIORES_SIZE*((int)Idx)), DIORES_SIZE);
//if(DIORes[Idx]
//读自检状态
if(Idx<=DIORESERVEOUTSW) //输出通道则直接退出
{
#if MODULE_DIO==1
DOCfgMode(DIORes[Idx].DIOCh,DO_MODE_DIRECT,FALSE);
#endif
return 0;
}
#if MODULE_DIO==1
DICfgMode(DIORes[Idx].DIOCh,DI_MODE_DIRECT);
#endif
for(nTestCount=0;nTestCount0))
return 2;//闪烁
//do
return 0;
}
int AddAO( DAName Idx )
{
//int nNumber=0;
memcpy((void*)&DARes[Idx], (void *)(DAC_PARAMETER_ADDR+DACRES_SIZE*((int)Idx)), DACRES_SIZE);
/*
if(ReadChannel(DAChannel[Idx],nNumber)!=0)
{
//MessageBox ( NULL, "Read Channel Failed...", "Error", MB_OK );
return -1;
}
DARes[Idx].DACh=nNumber;
if(ReadChannelValid(DAChannel[Idx],nNumber)!=0)
{
//MessageBox ( NULL, "Read ChannelValid Failed...", "Error", MB_OK );
return -1;
}
DARes[Idx].Valid=nNumber;
*/
return 0;
}
/*
int AddFreq( FRQName Idx )
{
int nNumber=0;
if(ReadChannel(FRQChannel[Idx],nNumber)!=0)
{
//MessageBox ( NULL, "Read Channel Failed...", "Error", MB_OK );
return -1;
}
FRQRes[Idx].FRQCh=nNumber;
if(ReadChannelValid(FRQChannel[Idx],nNumber)!=0)
{
//MessageBox ( NULL, "Read ChannelValid Failed...", "Error", MB_OK );
return -1;
}
FRQRes[Idx].Valid=nNumber;
return 0;
}
*/
int SetDO(DIOName Idx, HILOW DOStatus )
{
INT8U RX;
if(DIORes[Idx].Valid!=1)
{
// wsprintf(szErrMsg,"数字输出通道-%s不可用 ...",DIOChannel[Idx]);
// MessageBox ( NULL,szErrMsg, "标定参数配置错误", MB_OK );
return -1;
}
if(DIORes[Idx].DIOCh<0)
{
//MessageBox ( NULL,"非法数字输出物理通道号!", "标定参数配置错误", MB_OK );
return -1;
}
if(DIORes[Idx].DIOCh>31)
{
//MessageBox ( NULL,"非法数字输出物理通道号!", "标定参数配置错误", MB_OK );
return -1;
}
#if MODULE_DIO==1
if(DOStatus==HI)//置高
DOSet(DIORes[Idx].DIOCh,TRUE);
else
DOSet(DIORes[Idx].DIOCh,FALSE);
#else
if(DIORes[Idx].DIOCh>7)
{
//读出状态,然后将所设置的位置
//RX = Read23017(MCP_OPW, MCP_OPR, GPIOB);
if(DOStatus==HI)//置高
Write23017(MCP_OPW, GPIOB,(BIT_MASK_HI(DIORes[Idx].DIOCh-8)|DO_B_Status));
else//置低
Write23017(MCP_OPW, GPIOB,(BIT_MASK_LOW(DIORes[Idx].DIOCh-8)&DO_B_Status));
DO_B_Status = Read23017(MCP_OPW, MCP_OPR, OLATB);
}
else
{
//读出状态,然后将所设置的位置
//RX = Read23017(MCP_OPW, MCP_OPR, GPIOA);
if(DOStatus==HI)//置高
Write23017(MCP_OPW, GPIOA,(BIT_MASK_HI(DIORes[Idx].DIOCh)|DO_A_Status));
else//置低
Write23017(MCP_OPW, GPIOA,(BIT_MASK_LOW(DIORes[Idx].DIOCh)&DO_A_Status));
DO_A_Status = Read23017(MCP_OPW, MCP_OPR, OLATA);
}
#endif
return RX;
}
int GetDIStatus( DIOName Idx, HILOW * DIStatus )
{
INT8U Status=0;
if(DIORes[Idx].Valid!=1)
{
//sprintf(szErrMsg,"数字输入通道-%s不可用 ...",DIOChannel[Idx]);
//MessageBox ( NULL,szErrMsg, "标定参数配置错误", MB_OK );
return -1;
}
if(DIORes[Idx].DIOCh<0)
{
//( NULL,"非法数字输入物理通道号", "标定参数配置错误"
return -1;
}
if(DIORes[Idx].DIOCh>31)
{
//MessageBox ( NULL,"非法数字输入物理通道号!", "标定参数配置错误", MB_OK );
return -1;
}
#if MODULE_DIO==0
switch(DIORes[Idx].DIOCh)
{
case 0:
Status=DIN0;
break;
case 1:
Status=DIN1;
break;
case 2:
Status=DIN2;
break;
case 3:
Status=DIN3;
break;
case 4:
Status=DIN4;
break;
case 5:
Status=DIN5;
break;
case 6:
Status=DIN6;
break;
case 7:
Status=DIN7;
break;
case 8:
Status=DIN8;
break;
case 9:
Status=DIN9;
break;
case 10:
Status=DIN10;
break;
case 11:
Status=DIN11;
break;
case 12:
Status=DIN12;
break;
case 13:
Status=DIN13;
break;
case 14:
Status=DIN14;
break;
case 15:
Status=DIN15;
break;
default:
break;
}
*DIStatus=(HILOW)(Status);
#else
*DIStatus=(HILOW)DIGet(DIORes[Idx].DIOCh);
#endif
return 0;
}
//*******************************************
//函数名称:MultiFilter( float* DatBuf, int DatLen )
//函数功能:中值滤波算法
//形式参数:
//行参说明:
//返回参数:
//使用说明:
//*******************************************
float MultiFilter( float* DatBuf, int DatLen )
{
int count=0;
float MaxValue=-10,MinValue=10;
float AverangeValue=0,TotalValue=0;
//if(DatLen<3||DatLen>14) return -1 ;
if(DatLen<=2)
return 0.0f;
for(count=0;countMaxValue )
MaxValue=DatBuf[count];
TotalValue+=DatBuf[count];
}
AverangeValue=(TotalValue-MaxValue-MinValue)/(DatLen-2);
return(AverangeValue);
}
//注意本采样程序根据配置采样频率和点数进行采样,同时采用平滑滤波算法
//调用时可在采样任务中直接调用本函数实现通道采样,如果需要精确的话则采用定时器中断进行
int Sample(ADName Idx,float * value)
{
INT8U err;
int count=0;
float MaxValue=-10.0f,MinValue=10.0f;
float TotalValue=0.0f;
float tempvalue=0.0f;
#if MODULE_AIO==1
err = AIGet(0, value); /* Read AI channel #0 */
#else
return GetSampleValue(ADRes[Idx].ADCh,value);
/*
int delay=ADRes[Idx].SampleRate;
for(count=0;countMaxValue )
MaxValue=tempvalue;
//TotalValue+=tempvalue;
}
*value=(TotalValue-MaxValue-MinValue)/(ADRes[Idx].SampleDot-2);
*/
//GetSampleValue()
#endif
return 0;
}
int SetAO(DAName Idx,float fVout)
{
INT16U nDacData=(INT16U)DACLSBConvert(fVout);
if(DARes[Idx].Valid!=1) return -1;
#if MODULE_AIO==1
#else
singlewrite_mcp4728(nDacData,DARes[Idx].DACh);
#endif
}
void DigOut(INT8U ndoch ,HILOW status)
{
INT8U RX;
if(ndoch>7)
{
//读出状态,然后将所设置的位置
RX = Read23017(MCP_OPW, MCP_OPR, OLATB);
if(status==HI)//置高
{
if(!Write23017(MCP_OPW, GPIOB,(BIT_MASK_HI(ndoch-8)|RX)))
{
_log("Write 23017 failure!\r\n");
}
}
else//置低
{
if(!Write23017(MCP_OPW, GPIOB,(BIT_MASK_LOW(ndoch-8)&RX)))
{
_log("Write 23017 failure!\r\n");
}
}
// RX = Read23017(MCP_OPW, MCP_OPR, OLATB);
}
else
{
//读出状态,然后将所设置的位置
RX = Read23017(MCP_OPW, MCP_OPR, OLATA);
if(status==HI)//置高
{
if(!Write23017(MCP_OPW, GPIOA,(BIT_MASK_HI(ndoch)|RX)))
{
//_log("Write 23017 failure!\r\n");
}
}
else//置低
{
if(!Write23017(MCP_OPW, GPIOA,(BIT_MASK_LOW(ndoch)&RX)))
{
//_log("Write 23017 failure!\r\n");
}
}
// RX = Read23017(MCP_OPW, MCP_OPR, OLATA);
}
}