kopia lustrzana https://gitlab.com/sane-project/backends
499 wiersze
16 KiB
C
499 wiersze
16 KiB
C
/* sane - Scanner Access Now Easy.
|
|
Copyright (C) Marian Eichholz 2001
|
|
This file is part of the SANE package.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
|
MA 02111-1307, USA.
|
|
|
|
As a special exception, the authors of SANE give permission for
|
|
additional uses of the libraries contained in this release of SANE.
|
|
|
|
The exception is that, if you link a SANE library with other files
|
|
to produce an executable, this does not by itself cause the
|
|
resulting executable to be covered by the GNU General Public
|
|
License. Your use of that executable is in no way restricted on
|
|
account of linking the SANE library code into it.
|
|
|
|
This exception does not, however, invalidate any other reasons why
|
|
the executable file might be covered by the GNU General Public
|
|
License.
|
|
|
|
If you submit changes to SANE to the maintainers to be included in
|
|
a subsequent release, you agree by submitting the changes that
|
|
those changes may be distributed with this exception intact.
|
|
|
|
If you write modifications of your own for SANE, it is your choice
|
|
whether to permit this exception to apply to your modifications.
|
|
If you do not wish that, delete this exception notice.
|
|
*/
|
|
|
|
/* ======================================================================
|
|
|
|
Userspace scan tool for the Microtek 3600 scanner
|
|
|
|
slider movement
|
|
|
|
(C) Marian Eichholz 2001
|
|
|
|
====================================================================== */
|
|
|
|
#include "sm3600-scantool.h"
|
|
|
|
#include <math.h>
|
|
|
|
/* tuning constants for DoOriginate */
|
|
#define CCH_BONSAI 60
|
|
#define BLACK_HOLE_GRAY 30
|
|
#define BLACK_BED_LEVEL 10
|
|
|
|
/* changed by user request from 100, there are probably darker stripes */
|
|
#define CHASSIS_GRAY_LEVEL 75
|
|
|
|
typedef enum { ltHome, ltUnknown, ltBed, ltError } TLineType;
|
|
|
|
#define INST_ASSERT2() { if (this->nErrorState) return ltError; }
|
|
|
|
static unsigned char auchRegsSingleLine[]={
|
|
0x00 /*0x01*/, 0x00 /*0x02*/, 0x3F /*0x03*/,
|
|
0xB4 /*!!0x04!!*/, 0x14 /*!!0x05!!*/, 0,0,
|
|
0x00 /*0x08*/, 0x3F /*!!0x09!!*/,
|
|
1,0,
|
|
0x6D /*0x0C*/,
|
|
0x70 /*0x0D*/, 0x69 /*0x0E*/, 0xD0 /*0x0F*/,
|
|
0x00 /*0x10*/, 0x00 /*0x11*/, 0x40 /*0x12*/,
|
|
0x15 /*0x13*/, 0x80 /*0x14*/, 0x2A /*0x15*/,
|
|
0xC0 /*0x16*/, 0x40 /*0x17*/, 0xC0 /*0x18*/,
|
|
0x40 /*0x19*/, 0xFF /*0x1A*/, 0x01 /*0x1B*/,
|
|
0x88 /*0x1C*/, 0x40 /*0x1D*/, 0x4C /*0x1E*/,
|
|
0x50 /*0x1F*/, 0x00 /*0x20*/, 0x0C /*0x21*/,
|
|
0x21 /*0x22*/, 0xF0 /*0x23*/, 0x40 /*0x24*/,
|
|
0x00 /*0x25*/, 0x0A /*0x26*/, 0xF0 /*0x27*/,
|
|
0x00 /*0x28*/, 0x00 /*0x29*/, 0x4E /*0x2A*/,
|
|
0xF0 /*0x2B*/, 0x00 /*0x2C*/, 0x00 /*0x2D*/,
|
|
0x4E /*0x2E*/, 0x88 /*R_CCAL*/, 0x88 /*R_CCAL2*/,
|
|
0x84 /*R_CCAL3*/, 0xEA /*R_LEN*/, 0x24 /*R_LENH*/,
|
|
0x63 /*0x34*/, 0x29 /*0x35*/, 0x00 /*0x36*/,
|
|
0x00 /*0x37*/, 0x00 /*0x38*/, 0x00 /*0x39*/,
|
|
0x00 /*0x3A*/, 0x00 /*0x3B*/, 0xFF /*0x3C*/,
|
|
0x0F /*0x3D*/, 0x00 /*0x3E*/, 0x00 /*0x3F*/,
|
|
0x01 /*0x40*/, 0x00 /*0x41*/, 0x00 /*R_CSTAT*/,
|
|
0x03 /*R_SPD*/, 0x01 /*0x44*/, 0x00 /*0x45*/,
|
|
0x59 /*!!R_CTL!!*/, 0xC0 /*0x47*/, 0x40 /*0x48*/,
|
|
0x96 /*!!0x49!!*/, 0xD8 /*0x4A*/ };
|
|
|
|
/* ======================================================================
|
|
|
|
GetLineType()
|
|
|
|
Reads a scan line at the actual position and classifies it as
|
|
"on the flatbed area" or "at home position" or "elsewhere".
|
|
This can be used to calculate the proper stepping width
|
|
|
|
====================================================================== */
|
|
|
|
static TLineType GetLineType(TInstance *this)
|
|
{
|
|
unsigned char achLine[CCH_BONSAI+1];
|
|
unsigned char *puchBuffer;
|
|
int cchBulk,i,iHole;
|
|
int axHoles[3];
|
|
long lSum;
|
|
TBool bHolesOk;
|
|
int lMedian;
|
|
bHolesOk=false;
|
|
RegWriteArray(this,R_ALL, 74, auchRegsSingleLine);
|
|
INST_ASSERT2();
|
|
/* dprintf(DEBUG_SCAN,"originate-%d...",iStripe); */
|
|
RegWrite(this,R_CTL, 1, 0x59); /* #2496[062.5] */
|
|
RegWrite(this,R_CTL, 1, 0xD9); /* #2497[062.5] */
|
|
i=WaitWhileScanning(this,5); if (i) return i;
|
|
|
|
cchBulk=MAX_PIXEL_PER_SCANLINE;
|
|
/*
|
|
cchBulk=RegRead(this,R_STAT, 2);
|
|
if (cchBulk!=MAX_PIXEL_PER_SCANLINE)
|
|
return SetError(this,SANE_STATUS_INVAL,
|
|
"illegal scan line width reported (%d)",cchBulk);
|
|
*/
|
|
puchBuffer=(unsigned char*)calloc(1,cchBulk);
|
|
CHECK_POINTER(puchBuffer);
|
|
if (BulkReadBuffer(this,puchBuffer, cchBulk)!=cchBulk)
|
|
{
|
|
free(puchBuffer);
|
|
return SetError(this,SANE_STATUS_IO_ERROR,"truncated bulk");
|
|
}
|
|
lSum=0;
|
|
for (i=0; i<cchBulk; i++)
|
|
lSum+=puchBuffer[i]; /* gives total white level */
|
|
for (i=0; i<CCH_BONSAI; i++)
|
|
{
|
|
int iBulk=i*(cchBulk)/CCH_BONSAI;
|
|
achLine[i]=puchBuffer[iBulk+40]; /* simple, basta */
|
|
}
|
|
/* the bonsai line is supported only for curiosity */
|
|
for (i=0; i<CCH_BONSAI; i++)
|
|
achLine[i]=achLine[i]/26+'0'; /* '0'...'9' */
|
|
achLine[CCH_BONSAI]='\0';
|
|
|
|
i=200;
|
|
iHole=0;
|
|
dprintf(DEBUG_ORIG,"");
|
|
while (i<MAX_PIXEL_PER_SCANLINE && iHole<3)
|
|
{
|
|
int c;
|
|
while (i<MAX_PIXEL_PER_SCANLINE && puchBuffer[i]>BLACK_HOLE_GRAY) i++; /* not very black */
|
|
c=0;
|
|
dprintf(DEBUG_ORIG,"~ i=%d",i);
|
|
while (i<MAX_PIXEL_PER_SCANLINE && puchBuffer[i]<=BLACK_HOLE_GRAY) { i++; c++; }
|
|
dprintf(DEBUG_ORIG,"~ c=%d",c);
|
|
if (c>90) /* 90% of min hole diameter */
|
|
{
|
|
axHoles[iHole]=i-c/2; /* store the middle of the hole */
|
|
dprintf(DEBUG_ORIG,"~ #%d=%d",iHole,axHoles[iHole]);
|
|
iHole++;
|
|
i+=10; /* some hysteresis */
|
|
}
|
|
}
|
|
if (iHole==3)
|
|
{
|
|
bHolesOk=true;
|
|
for (i=0; i<2; i++)
|
|
{
|
|
int xDistance=axHoles[i+1]-axHoles[i];
|
|
if (xDistance<1050 || xDistance>1400)
|
|
bHolesOk=false;
|
|
}
|
|
if (axHoles[0]<350 || axHoles[0]>900) /* >2 cm tolerance */
|
|
bHolesOk=false;
|
|
}
|
|
else
|
|
bHolesOk=false;
|
|
lMedian=lSum/cchBulk;
|
|
/* this is *definitly* dirty style. We should pass the information
|
|
by other means... */
|
|
if (bHolesOk)
|
|
{
|
|
/* black reference */
|
|
this->calibration.nHoleGray=puchBuffer[axHoles[0]];
|
|
switch (this->model)
|
|
{
|
|
case sm3600:
|
|
/* bed corner */
|
|
this->calibration.xMargin=axHoles[0]-480;
|
|
this->calibration.yMargin=413;
|
|
break;
|
|
case sm3700:
|
|
case sm3750: /* basically unknown sub-brand */
|
|
default:
|
|
this->calibration.xMargin=axHoles[0]-462;
|
|
this->calibration.yMargin=330;
|
|
break;
|
|
} /* switch */
|
|
}
|
|
dprintf(DEBUG_ORIG,"~ %s - %d\n",
|
|
achLine,
|
|
lMedian);
|
|
free(puchBuffer);
|
|
i=WaitWhileBusy(this,2); if (i) return i;
|
|
if (bHolesOk && lMedian>CHASSIS_GRAY_LEVEL)
|
|
return ltHome;
|
|
if (lMedian<=BLACK_BED_LEVEL)
|
|
return ltBed;
|
|
return ltUnknown;
|
|
}
|
|
|
|
#ifdef INSANE_VERSION
|
|
|
|
/* **********************************************************************
|
|
|
|
FakeCalibration()
|
|
|
|
If DoOriginate() and this Calibration code is skipped,
|
|
we should at least provide for some fake measurements.
|
|
Thus a test scan of the scanner's inside is possible.
|
|
|
|
********************************************************************** */
|
|
|
|
__SM3600EXPORT__
|
|
TState FakeCalibration(TInstance *this)
|
|
{
|
|
if (this->calibration.bCalibrated)
|
|
return SANE_STATUS_GOOD;
|
|
this->calibration.bCalibrated=true;
|
|
if (!this->calibration.achStripeY)
|
|
{
|
|
this->calibration.achStripeY=calloc(1,MAX_PIXEL_PER_SCANLINE);
|
|
if (!this->calibration.achStripeY)
|
|
return SetError(this,SANE_STATUS_NO_MEM,"no memory for calib Y");
|
|
}
|
|
memset(this->calibration.achStripeY,0xC0,MAX_PIXEL_PER_SCANLINE);
|
|
/* scan *every* nonsense */
|
|
this->calibration.xMargin=this->calibration.yMargin=0;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* **********************************************************************
|
|
|
|
DoCalibration() and friends
|
|
|
|
********************************************************************** */
|
|
|
|
#define SM3600_CALIB_USE_MEDIAN
|
|
#define SM3600_CALIB_APPLY_HANNING_WINDOW
|
|
|
|
#ifdef SM3600_CALIB_USE_MEDIAN
|
|
typedef int (*TQSortProc)(const void *, const void *);
|
|
|
|
static
|
|
int CompareProc(const unsigned char *p1, const unsigned char *p2)
|
|
{
|
|
return *p1 - *p2;
|
|
}
|
|
#endif
|
|
|
|
#define MAX_CALIB_STRIPES 8
|
|
|
|
__SM3600EXPORT__
|
|
TState DoCalibration(TInstance *this)
|
|
{
|
|
#ifdef SM3600_CALIB_USE_RMS
|
|
long aulSum[MAX_PIXEL_PER_SCANLINE];
|
|
#endif
|
|
#ifdef SM3600_CALIB_USE_MEDIAN
|
|
unsigned char aauchY[MAX_CALIB_STRIPES][MAX_PIXEL_PER_SCANLINE];
|
|
unsigned char auchRow[MAX_CALIB_STRIPES];
|
|
#endif
|
|
#ifdef SM3600_CALIB_APPLY_HANNING_WINDOW
|
|
unsigned char auchHanning[MAX_PIXEL_PER_SCANLINE];
|
|
#endif
|
|
|
|
int iLine,i;
|
|
int yStart,cStripes,cyGap;
|
|
TState rc;
|
|
if (this->calibration.bCalibrated)
|
|
return SANE_STATUS_GOOD;
|
|
|
|
switch (this->model)
|
|
{
|
|
case sm3600:
|
|
yStart=200;
|
|
cStripes=MAX_CALIB_STRIPES;
|
|
cyGap=10;
|
|
break;
|
|
case sm3700: /* in fact, the 3600 calibration should do!!! */
|
|
case sm3750:
|
|
default:
|
|
yStart=100; /* 54 is perimeter */
|
|
cStripes=MAX_CALIB_STRIPES;
|
|
cyGap=10;
|
|
break;
|
|
} /* switch */
|
|
|
|
DoJog(this,yStart);
|
|
/* scan a gray line at 600 DPI */
|
|
if (!this->calibration.achStripeY)
|
|
{
|
|
this->calibration.achStripeY=calloc(1,MAX_PIXEL_PER_SCANLINE);
|
|
if (!this->calibration.achStripeY)
|
|
return SetError(this,SANE_STATUS_NO_MEM,"no memory for calib Y");
|
|
}
|
|
#ifdef SM3600_CALIB_USE_RMS
|
|
memset(aulSum,0,sizeof(aulSum));
|
|
#endif
|
|
for (iLine=0; iLine<cStripes; iLine++)
|
|
{
|
|
dprintf(DEBUG_CALIB,"calibrating %i...\n",iLine);
|
|
RegWriteArray(this,R_ALL, 74, auchRegsSingleLine);
|
|
INST_ASSERT();
|
|
RegWrite(this,R_CTL, 1, 0x59); /* #2496[062.5] */
|
|
RegWrite(this,R_CTL, 1, 0xD9); /* #2497[062.5] */
|
|
rc=WaitWhileScanning(this,5); if (rc) { return rc; }
|
|
if (BulkReadBuffer(this,
|
|
#ifdef SM3600_CALIB_USE_RMS
|
|
this->calibration.achStripeY,
|
|
#endif
|
|
#ifdef SM3600_CALIB_USE_MEDIAN
|
|
aauchY[iLine],
|
|
#endif
|
|
MAX_PIXEL_PER_SCANLINE)
|
|
!=MAX_PIXEL_PER_SCANLINE)
|
|
return SetError(this,SANE_STATUS_IO_ERROR,"truncated bulk");
|
|
#ifdef SM3600_CALIB_USE_RMS
|
|
for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
|
|
aulSum[i]+=(long)this->calibration.achStripeY[i]*
|
|
(long)this->calibration.achStripeY[i];
|
|
#endif
|
|
DoJog(this,cyGap);
|
|
}
|
|
#ifdef SM3600_CALIB_USE_RMS
|
|
for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
|
|
this->calibration.achStripeY[i]=(unsigned char)(int)sqrt(aulSum[i]/cStripes);
|
|
#endif
|
|
#ifdef SM3600_CALIB_USE_MEDIAN
|
|
/* process the collected lines rowwise. Use intermediate buffer for qsort */
|
|
for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
|
|
{
|
|
for (iLine=0; iLine<cStripes; iLine++)
|
|
auchRow[iLine]=aauchY[iLine][i];
|
|
qsort(auchRow,cStripes, sizeof(unsigned char), (TQSortProc)CompareProc);
|
|
this->calibration.achStripeY[i]=auchRow[(cStripes-1)/2];
|
|
}
|
|
#endif
|
|
#ifdef SM3600_CALIB_APPLY_HANNING_WINDOW
|
|
memcpy(auchHanning,this->calibration.achStripeY,sizeof(auchHanning));
|
|
for (i=1; i<MAX_PIXEL_PER_SCANLINE-1; i++)
|
|
this->calibration.achStripeY[i]=(unsigned char)
|
|
((2*(int)auchHanning[i]+auchHanning[i-1]+auchHanning[i+1])/4);
|
|
#endif
|
|
|
|
DoJog(this,-yStart-cStripes*cyGap);
|
|
INST_ASSERT();
|
|
this->calibration.bCalibrated=true;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* **********************************************************************
|
|
|
|
DoOriginate()
|
|
|
|
*shall* one time move the slider safely back to its origin.
|
|
No idea, hoiw to achieve this, for now...
|
|
|
|
********************************************************************** */
|
|
|
|
__SM3600EXPORT__
|
|
TState DoOriginate(TInstance *this, TBool bStepOut)
|
|
{
|
|
TLineType lt;
|
|
if (this->bVerbose)
|
|
fprintf(stderr,"carriage return...\n");
|
|
DBG(DEBUG_INFO,"DoOriginate()\n");
|
|
INST_ASSERT();
|
|
lt=GetLineType(this);
|
|
/* if we are already at home, fine. If not, first jump a bit forward */
|
|
DBG(DEBUG_JUNK,"lt1=%d\n",(int)lt);
|
|
if (lt!=ltHome && bStepOut) DoJog(this,150);
|
|
while (lt!=ltHome && !this->state.bCanceled)
|
|
{
|
|
lt=GetLineType(this);
|
|
DBG(DEBUG_JUNK,"lt2=%d\n",(int)lt);
|
|
INST_ASSERT();
|
|
switch (lt)
|
|
{
|
|
case ltHome: continue;
|
|
case ltBed: DoJog(this,-240); break; /* worst case: 1 cm */
|
|
default: DoJog(this,-15); break; /* 0.X mm */
|
|
}
|
|
}
|
|
DoJog(this,1); INST_ASSERT(); /* Correction for 1 check line */
|
|
DBG(DEBUG_JUNK,"lt3=%d\n",(int)lt);
|
|
if (this->state.bCanceled)
|
|
return SANE_STATUS_CANCELLED;
|
|
return DoCalibration(this);
|
|
}
|
|
|
|
/* **********************************************************************
|
|
|
|
DoJog(nDistance)
|
|
|
|
The distance is given in 600 DPI.
|
|
|
|
********************************************************************** */
|
|
|
|
__SM3600EXPORT__
|
|
TState DoJog(TInstance *this, int nDistance)
|
|
{
|
|
int cSteps;
|
|
int nSpeed,nRest;
|
|
dprintf(DEBUG_SCAN,"jogging %d units...\n",nDistance);
|
|
if (!nDistance) return 0;
|
|
RegWrite(this,0x34, 1, 0x63);
|
|
RegWrite(this,0x49, 1, 0x96);
|
|
WaitWhileBusy(this,2);
|
|
RegWrite(this,0x34, 1, 0x63);
|
|
RegWrite(this,0x49, 1, 0x9E); /* that is a difference! */
|
|
INST_ASSERT();
|
|
cSteps=(nDistance>0) ? nDistance : -nDistance;
|
|
{
|
|
unsigned char uchRegs2587[]={
|
|
0x00 /*0x01*/, 0x00 /*0x02*/, 0x3F /*0x03*/,
|
|
0x40 /*!!0x04!!*/, 0x00 /*!!0x05!!*/,
|
|
0,0, /* steps */
|
|
0x00 /*0x08*/, 0x00 /*!!0x09!!*/,
|
|
0,0, /* y count */
|
|
0x6D /*0x0C*/,
|
|
0x70 /*0x0D*/, 0x69 /*0x0E*/, 0xD0 /*0x0F*/,
|
|
0x00 /*0x10*/, 0x00 /*0x11*/, 0x40 /*0x12*/,
|
|
0x15 /*0x13*/, 0x80 /*0x14*/, 0x2A /*0x15*/,
|
|
0xC0 /*0x16*/, 0x40 /*0x17*/, 0xC0 /*0x18*/,
|
|
0x40 /*0x19*/, 0xFF /*0x1A*/, 0x01 /*0x1B*/,
|
|
0x88 /*0x1C*/, 0x40 /*0x1D*/, 0x4C /*0x1E*/,
|
|
0x50 /*0x1F*/, 0x00 /*0x20*/, 0x0C /*0x21*/,
|
|
0x21 /*0x22*/, 0xF0 /*0x23*/, 0x40 /*0x24*/,
|
|
0x00 /*0x25*/, 0x0A /*0x26*/, 0xF0 /*0x27*/,
|
|
0x00 /*0x28*/, 0x00 /*0x29*/, 0x4E /*0x2A*/,
|
|
0xF0 /*0x2B*/, 0x00 /*0x2C*/, 0x00 /*0x2D*/,
|
|
0x4E /*0x2E*/, 0x88 /*R_CCAL*/, 0x88 /*R_CCAL2*/,
|
|
0x84 /*R_CCAL3*/, 0xEA /*R_LEN*/, 0x24 /*R_LENH*/,
|
|
0x63 /*0x34*/, 0x29 /*0x35*/, 0x00 /*0x36*/,
|
|
0x00 /*0x37*/, 0x00 /*0x38*/, 0x00 /*0x39*/,
|
|
0x00 /*0x3A*/, 0x00 /*0x3B*/, 0xFF /*0x3C*/,
|
|
0x0F /*0x3D*/, 0x00 /*0x3E*/, 0x00 /*0x3F*/,
|
|
0x01 /*0x40*/, 0x00 /*0x41*/, 0x80 /*R_CSTAT*/,
|
|
0x03 /*R_SPD*/, 0x01 /*0x44*/, 0x00 /*0x45*/,
|
|
0x79 /*!!R_CTL!!*/, 0xC0 /*0x47*/, 0x40 /*0x48*/,
|
|
0x9E /*!!0x49!!*/, 0xD8 /*0x4A*/ };
|
|
RegWriteArray(this,R_ALL, 74, uchRegs2587);
|
|
} /* #2587[065.4] */
|
|
INST_ASSERT();
|
|
RegWrite(this,R_STPS,2,cSteps);
|
|
/* do some magic for slider acceleration */
|
|
if (cSteps>600) /* only large movements are accelerated */
|
|
{
|
|
RegWrite(this,0x34, 1, 0xC3);
|
|
RegWrite(this,0x47, 2, 0xA000); /* initial speed */
|
|
}
|
|
/* start back or forth movement */
|
|
if (nDistance>0)
|
|
{
|
|
RegWrite(this,R_CTL, 1, 0x39); /* #2588[065.4] */
|
|
RegWrite(this,R_CTL, 1, 0x79); /* #2589[065.4] */
|
|
RegWrite(this,R_CTL, 1, 0xF9); /* #2590[065.4] */
|
|
}
|
|
else
|
|
{
|
|
RegWrite(this,R_CTL, 1, 0x59);
|
|
RegWrite(this,R_CTL, 1, 0xD9);
|
|
}
|
|
INST_ASSERT();
|
|
/* accelerate the slider each 100 us */
|
|
if (cSteps>600)
|
|
{
|
|
nRest=cSteps;
|
|
for (nSpeed=0x9800; nRest>600 && nSpeed>=0x4000; nSpeed-=0x800)
|
|
{
|
|
nRest=RegRead(this,R_POS, 2);
|
|
usleep(100);
|
|
/* perhaps 40C0 is the fastest possible value */
|
|
RegWrite(this,0x47, 2, nSpeed>0x4000 ? nSpeed : 0x40C0);
|
|
}
|
|
}
|
|
INST_ASSERT();
|
|
usleep(100);
|
|
return WaitWhileBusy(this,1000); /* thanks Mattias Ellert */
|
|
}
|