1 /* sane - Scanner Access Now Easy.
2    Copyright (C) Marian Eichholz 2001
3    This file is part of the SANE package.
4 
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 
18    As a special exception, the authors of SANE give permission for
19    additional uses of the libraries contained in this release of SANE.
20 
21    The exception is that, if you link a SANE library with other files
22    to produce an executable, this does not by itself cause the
23    resulting executable to be covered by the GNU General Public
24    License.  Your use of that executable is in no way restricted on
25    account of linking the SANE library code into it.
26 
27    This exception does not, however, invalidate any other reasons why
28    the executable file might be covered by the GNU General Public
29    License.
30 
31    If you submit changes to SANE to the maintainers to be included in
32    a subsequent release, you agree by submitting the changes that
33    those changes may be distributed with this exception intact.
34 
35    If you write modifications of your own for SANE, it is your choice
36    whether to permit this exception to apply to your modifications.
37    If you do not wish that, delete this exception notice.
38 */
39 
40 /* ======================================================================
41 
42 Userspace scan tool for the Microtek 3600 scanner
43 
44 slider movement
45 
46 (C) Marian Eichholz 2001
47 
48 ====================================================================== */
49 
50 #include "sm3600-scantool.h"
51 
52 #include <math.h>
53 
54 /* tuning constants for DoOriginate */
55 #define CCH_BONSAI              60
56 #define BLACK_HOLE_GRAY         30
57 #define BLACK_BED_LEVEL         10
58 
59 /* changed by user request from 100, there are probably darker stripes */
60 #define CHASSIS_GRAY_LEVEL      75
61 
62 typedef enum { ltHome, ltUnknown, ltBed, ltError } TLineType;
63 
64 #define INST_ASSERT2() { if (this->nErrorState) return ltError; }
65 
66 static unsigned char auchRegsSingleLine[]={
67   0x00 /*0x01*/, 0x00 /*0x02*/, 0x3F /*0x03*/,
68   0xB4 /*!!0x04!!*/, 0x14 /*!!0x05!!*/, 0,0,
69   0x00 /*0x08*/, 0x3F /*!!0x09!!*/,
70   1,0,
71   0x6D /*0x0C*/,
72   0x70 /*0x0D*/, 0x69 /*0x0E*/, 0xD0 /*0x0F*/,
73   0x00 /*0x10*/, 0x00 /*0x11*/, 0x40 /*0x12*/,
74   0x15 /*0x13*/, 0x80 /*0x14*/, 0x2A /*0x15*/,
75   0xC0 /*0x16*/, 0x40 /*0x17*/, 0xC0 /*0x18*/,
76   0x40 /*0x19*/, 0xFF /*0x1A*/, 0x01 /*0x1B*/,
77   0x88 /*0x1C*/, 0x40 /*0x1D*/, 0x4C /*0x1E*/,
78   0x50 /*0x1F*/, 0x00 /*0x20*/, 0x0C /*0x21*/,
79   0x21 /*0x22*/, 0xF0 /*0x23*/, 0x40 /*0x24*/,
80   0x00 /*0x25*/, 0x0A /*0x26*/, 0xF0 /*0x27*/,
81   0x00 /*0x28*/, 0x00 /*0x29*/, 0x4E /*0x2A*/,
82   0xF0 /*0x2B*/, 0x00 /*0x2C*/, 0x00 /*0x2D*/,
83   0x4E /*0x2E*/, 0x88 /*R_CCAL*/, 0x88 /*R_CCAL2*/,
84   0x84 /*R_CCAL3*/, 0xEA /*R_LEN*/, 0x24 /*R_LENH*/,
85   0x63 /*0x34*/, 0x29 /*0x35*/, 0x00 /*0x36*/,
86   0x00 /*0x37*/, 0x00 /*0x38*/, 0x00 /*0x39*/,
87   0x00 /*0x3A*/, 0x00 /*0x3B*/, 0xFF /*0x3C*/,
88   0x0F /*0x3D*/, 0x00 /*0x3E*/, 0x00 /*0x3F*/,
89   0x01 /*0x40*/, 0x00 /*0x41*/, 0x00 /*R_CSTAT*/,
90   0x03 /*R_SPD*/, 0x01 /*0x44*/, 0x00 /*0x45*/,
91   0x59 /*!!R_CTL!!*/, 0xC0 /*0x47*/, 0x40 /*0x48*/,
92   0x96 /*!!0x49!!*/, 0xD8 /*0x4A*/ };
93 
94 /* ======================================================================
95 
96 GetLineType()
97 
98 Reads a scan line at the actual position and classifies it as
99 "on the flatbed area" or "at home position" or "elsewhere".
100 This can be used to calculate the proper stepping width
101 
102 ====================================================================== */
103 
GetLineType(TInstance * this)104 static TLineType GetLineType(TInstance *this)
105 {
106   unsigned char  achLine[CCH_BONSAI+1];
107   unsigned char *puchBuffer;
108   int            cchBulk,i,iHole;
109   int            axHoles[3];
110   long           lSum;
111   TBool          bHolesOk;
112   int            lMedian;
113   bHolesOk=false;
114   RegWriteArray(this,R_ALL, 74, auchRegsSingleLine);
115   INST_ASSERT2();
116   /*     dprintf(DEBUG_SCAN,"originate-%d...",iStripe); */
117   RegWrite(this,R_CTL, 1, 0x59);    /* #2496[062.5] */
118   RegWrite(this,R_CTL, 1, 0xD9);    /* #2497[062.5] */
119   i=WaitWhileScanning(this,5); if (i) return i;
120 
121   cchBulk=MAX_PIXEL_PER_SCANLINE;
122   /*
123   cchBulk=RegRead(this,R_STAT, 2);
124   if (cchBulk!=MAX_PIXEL_PER_SCANLINE)
125     return SetError(this,SANE_STATUS_INVAL,
126 		    "illegal scan line width reported (%d)",cchBulk);
127   */
128   puchBuffer=(unsigned char*)calloc(1,cchBulk);
129   CHECK_POINTER(puchBuffer);
130   if (BulkReadBuffer(this,puchBuffer, cchBulk)!=cchBulk)
131     {
132       free(puchBuffer);
133       return SetError(this,SANE_STATUS_IO_ERROR,"truncated bulk");
134     }
135   lSum=0;
136   for (i=0; i<cchBulk; i++)
137     lSum+=puchBuffer[i]; /* gives total white level */
138   for (i=0; i<CCH_BONSAI; i++)
139     {
140       int iBulk=i*(cchBulk)/CCH_BONSAI;
141       achLine[i]=puchBuffer[iBulk+40]; /* simple, basta */
142     }
143   /* the bonsai line is supported only for curiosity */
144   for (i=0; i<CCH_BONSAI; i++)
145     achLine[i]=achLine[i]/26+'0'; /* '0'...'9' */
146   achLine[CCH_BONSAI]='\0';
147 
148   i=200;
149   iHole=0;
150   dprintf(DEBUG_ORIG,"");
151   while (i<MAX_PIXEL_PER_SCANLINE && iHole<3)
152     {
153       int c;
154       while (i<MAX_PIXEL_PER_SCANLINE && puchBuffer[i]>BLACK_HOLE_GRAY) i++; /* not very black */
155       c=0;
156       dprintf(DEBUG_ORIG,"~ i=%d",i);
157       while (i<MAX_PIXEL_PER_SCANLINE && puchBuffer[i]<=BLACK_HOLE_GRAY) { i++; c++; }
158       dprintf(DEBUG_ORIG,"~ c=%d",c);
159       if (c>90) /* 90% of min hole diameter */
160 	{
161 	  axHoles[iHole]=i-c/2; /* store the middle of the hole */
162 	  dprintf(DEBUG_ORIG,"~ #%d=%d",iHole,axHoles[iHole]);
163 	  iHole++;
164 	  i+=10; /* some hysteresis */
165 	}
166     }
167   if (iHole==3)
168     {
169       bHolesOk=true;
170       for (i=0; i<2; i++)
171 	{
172 	  int xDistance=axHoles[i+1]-axHoles[i];
173 	  if (xDistance<1050 || xDistance>1400)
174 	    bHolesOk=false;
175 	}
176       if (axHoles[0]<350 || axHoles[0]>900) /* >2 cm tolerance */
177 	bHolesOk=false;
178     }
179   else
180     bHolesOk=false;
181   lMedian=lSum/cchBulk;
182   /* this is *definitely* dirty style. We should pass the information
183      by other means... */
184   if (bHolesOk)
185     {
186       /* black reference */
187       this->calibration.nHoleGray=puchBuffer[axHoles[0]];
188       switch (this->model)
189 	{
190 	case sm3600:
191 	  /* bed corner */
192 	  this->calibration.xMargin=axHoles[0]-480;
193 	  this->calibration.yMargin=413;
194 	  break;
195 	case sm3700:
196 	case sm3750: /* basically unknown sub-brand */
197 	default:
198 	  this->calibration.xMargin=axHoles[0]-462;
199 	  this->calibration.yMargin=330;
200 	  break;
201 	} /* switch */
202     }
203   dprintf(DEBUG_ORIG,"~ %s - %d\n",
204 	  achLine,
205 	  lMedian);
206   free(puchBuffer);
207   i=WaitWhileBusy(this,2); if (i) return i;
208   if (bHolesOk && lMedian>CHASSIS_GRAY_LEVEL)
209     return ltHome;
210   if (lMedian<=BLACK_BED_LEVEL)
211     return ltBed;
212   return ltUnknown;
213 }
214 
215 #ifdef INSANE_VERSION
216 
217 /* **********************************************************************
218 
219 FakeCalibration()
220 
221 If DoOriginate() and this Calibration code is skipped,
222 we should at least provide for some fake measurements.
223 Thus a test scan of the scanner's inside is possible.
224 
225 ********************************************************************** */
226 
227 __SM3600EXPORT__
FakeCalibration(TInstance * this)228 TState FakeCalibration(TInstance *this)
229 {
230   if (this->calibration.bCalibrated)
231     return SANE_STATUS_GOOD;
232   this->calibration.bCalibrated=true;
233   if (!this->calibration.achStripeY)
234     {
235       this->calibration.achStripeY=calloc(1,MAX_PIXEL_PER_SCANLINE);
236       if (!this->calibration.achStripeY)
237 	return SetError(this,SANE_STATUS_NO_MEM,"no memory for calib Y");
238     }
239   memset(this->calibration.achStripeY,0xC0,MAX_PIXEL_PER_SCANLINE);
240   /* scan *every* nonsense */
241   this->calibration.xMargin=this->calibration.yMargin=0;
242   return SANE_STATUS_GOOD;
243 }
244 
245 #endif
246 
247 /* **********************************************************************
248 
249 DoCalibration() and friends
250 
251 ********************************************************************** */
252 
253 #define SM3600_CALIB_USE_MEDIAN
254 #define SM3600_CALIB_APPLY_HANNING_WINDOW
255 
256 #ifdef SM3600_CALIB_USE_MEDIAN
257 typedef int (*TQSortProc)(const void *, const void *);
258 
259 static
CompareProc(const unsigned char * p1,const unsigned char * p2)260 int CompareProc(const unsigned char *p1, const unsigned char *p2)
261 {
262   return *p1 - *p2;
263 }
264 #endif
265 
266 #define MAX_CALIB_STRIPES 8
267 
268 __SM3600EXPORT__
DoCalibration(TInstance * this)269 TState DoCalibration(TInstance *this)
270 {
271 #ifdef SM3600_CALIB_USE_RMS
272   long   aulSum[MAX_PIXEL_PER_SCANLINE];
273 #endif
274 #ifdef SM3600_CALIB_USE_MEDIAN
275   unsigned char aauchY[MAX_CALIB_STRIPES][MAX_PIXEL_PER_SCANLINE];
276   unsigned char auchRow[MAX_CALIB_STRIPES];
277 #endif
278 #ifdef SM3600_CALIB_APPLY_HANNING_WINDOW
279   unsigned char auchHanning[MAX_PIXEL_PER_SCANLINE];
280 #endif
281 
282   int    iLine,i;
283   int    yStart,cStripes,cyGap;
284   TState rc;
285   if (this->calibration.bCalibrated)
286     return SANE_STATUS_GOOD;
287 
288   switch (this->model)
289     {
290     case sm3600:
291       yStart=200;
292       cStripes=MAX_CALIB_STRIPES;
293       cyGap=10;
294       break;
295     case sm3700: /* in fact, the 3600 calibration should do!!! */
296     case sm3750:
297     default:
298       yStart=100;  /* 54 is perimeter */
299       cStripes=MAX_CALIB_STRIPES;
300       cyGap=10;
301       break;
302     } /* switch */
303 
304   DoJog(this,yStart);
305   /* scan a gray line at 600 DPI */
306   if (!this->calibration.achStripeY)
307     {
308       this->calibration.achStripeY=calloc(1,MAX_PIXEL_PER_SCANLINE);
309       if (!this->calibration.achStripeY)
310 	return SetError(this,SANE_STATUS_NO_MEM,"no memory for calib Y");
311     }
312 #ifdef SM3600_CALIB_USE_RMS
313   memset(aulSum,0,sizeof(aulSum));
314 #endif
315   for (iLine=0; iLine<cStripes; iLine++)
316     {
317       dprintf(DEBUG_CALIB,"calibrating %i...\n",iLine);
318       RegWriteArray(this,R_ALL, 74, auchRegsSingleLine);
319       INST_ASSERT();
320       RegWrite(this,R_CTL, 1, 0x59);    /* #2496[062.5] */
321       RegWrite(this,R_CTL, 1, 0xD9);    /* #2497[062.5] */
322       rc=WaitWhileScanning(this,5); if (rc) { return rc; }
323       if (BulkReadBuffer(this,
324 #ifdef SM3600_CALIB_USE_RMS
325 			 this->calibration.achStripeY,
326 #endif
327 #ifdef SM3600_CALIB_USE_MEDIAN
328 			 aauchY[iLine],
329 #endif
330 			 MAX_PIXEL_PER_SCANLINE)
331 	  !=MAX_PIXEL_PER_SCANLINE)
332 	return SetError(this,SANE_STATUS_IO_ERROR,"truncated bulk");
333 #ifdef SM3600_CALIB_USE_RMS
334       for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
335 	aulSum[i]+=(long)this->calibration.achStripeY[i]*
336 	  (long)this->calibration.achStripeY[i];
337 #endif
338       DoJog(this,cyGap);
339     }
340 #ifdef SM3600_CALIB_USE_RMS
341   for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
342     this->calibration.achStripeY[i]=(unsigned char)(int)sqrt(aulSum[i]/cStripes);
343 #endif
344 #ifdef SM3600_CALIB_USE_MEDIAN
345   /* process the collected lines rowwise. Use intermediate buffer for qsort */
346   for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
347     {
348       for (iLine=0; iLine<cStripes; iLine++)
349 	auchRow[iLine]=aauchY[iLine][i];
350       qsort(auchRow,cStripes, sizeof(unsigned char), (TQSortProc)CompareProc);
351       this->calibration.achStripeY[i]=auchRow[(cStripes-1)/2];
352     }
353 #endif
354 #ifdef SM3600_CALIB_APPLY_HANNING_WINDOW
355   memcpy(auchHanning,this->calibration.achStripeY,sizeof(auchHanning));
356   for (i=1; i<MAX_PIXEL_PER_SCANLINE-1; i++)
357     this->calibration.achStripeY[i]=(unsigned char)
358       ((2*(int)auchHanning[i]+auchHanning[i-1]+auchHanning[i+1])/4);
359 #endif
360 
361   DoJog(this,-yStart-cStripes*cyGap);
362   INST_ASSERT();
363   this->calibration.bCalibrated=true;
364   return SANE_STATUS_GOOD;
365 }
366 
367 /* **********************************************************************
368 
369 DoOriginate()
370 
371 *shall* one time move the slider safely back to its origin.
372 No idea, hoiw to achieve this, for now...
373 
374 ********************************************************************** */
375 
376 __SM3600EXPORT__
DoOriginate(TInstance * this,TBool bStepOut)377 TState DoOriginate(TInstance *this, TBool bStepOut)
378 {
379   TLineType lt;
380   if (this->bVerbose)
381     fprintf(stderr,"carriage return...\n");
382   DBG(DEBUG_INFO,"DoOriginate()\n");
383   INST_ASSERT();
384   lt=GetLineType(this);
385   /* if we are already at home, fine. If not, first jump a bit forward */
386   DBG(DEBUG_JUNK,"lt1=%d\n",(int)lt);
387   if (lt!=ltHome && bStepOut) DoJog(this,150);
388   while (lt!=ltHome && !this->state.bCanceled)
389     {
390       lt=GetLineType(this);
391       DBG(DEBUG_JUNK,"lt2=%d\n",(int)lt);
392       INST_ASSERT();
393       switch (lt)
394 	{
395 	case ltHome: continue;
396 	case ltBed:  DoJog(this,-240); break; /* worst case: 1 cm */
397 	default:     DoJog(this,-15); break; /* 0.X mm */
398 	}
399     }
400   DoJog(this,1); INST_ASSERT(); /* Correction for 1 check line */
401   DBG(DEBUG_JUNK,"lt3=%d\n",(int)lt);
402   if (this->state.bCanceled)
403     return SANE_STATUS_CANCELLED;
404   return DoCalibration(this);
405 }
406 
407 /* **********************************************************************
408 
409 DoJog(nDistance)
410 
411 The distance is given in 600 DPI.
412 
413 ********************************************************************** */
414 
415 __SM3600EXPORT__
DoJog(TInstance * this,int nDistance)416 TState DoJog(TInstance *this, int nDistance)
417 {
418   int cSteps;
419   int nSpeed,nRest;
420   dprintf(DEBUG_SCAN,"jogging %d units...\n",nDistance);
421   if (!nDistance) return 0;
422   RegWrite(this,0x34, 1, 0x63);
423   RegWrite(this,0x49, 1, 0x96);
424   WaitWhileBusy(this,2);
425   RegWrite(this,0x34, 1, 0x63);
426   RegWrite(this,0x49, 1, 0x9E); /* that is a difference! */
427   INST_ASSERT();
428   cSteps=(nDistance>0) ? nDistance : -nDistance;
429   {
430     unsigned char uchRegs2587[]={
431       0x00 /*0x01*/, 0x00 /*0x02*/, 0x3F /*0x03*/,
432       0x40 /*!!0x04!!*/, 0x00 /*!!0x05!!*/,
433       0,0, /* steps */
434       0x00 /*0x08*/, 0x00 /*!!0x09!!*/,
435       0,0, /* y count */
436       0x6D /*0x0C*/,
437       0x70 /*0x0D*/, 0x69 /*0x0E*/, 0xD0 /*0x0F*/,
438       0x00 /*0x10*/, 0x00 /*0x11*/, 0x40 /*0x12*/,
439       0x15 /*0x13*/, 0x80 /*0x14*/, 0x2A /*0x15*/,
440       0xC0 /*0x16*/, 0x40 /*0x17*/, 0xC0 /*0x18*/,
441       0x40 /*0x19*/, 0xFF /*0x1A*/, 0x01 /*0x1B*/,
442       0x88 /*0x1C*/, 0x40 /*0x1D*/, 0x4C /*0x1E*/,
443       0x50 /*0x1F*/, 0x00 /*0x20*/, 0x0C /*0x21*/,
444       0x21 /*0x22*/, 0xF0 /*0x23*/, 0x40 /*0x24*/,
445       0x00 /*0x25*/, 0x0A /*0x26*/, 0xF0 /*0x27*/,
446       0x00 /*0x28*/, 0x00 /*0x29*/, 0x4E /*0x2A*/,
447       0xF0 /*0x2B*/, 0x00 /*0x2C*/, 0x00 /*0x2D*/,
448       0x4E /*0x2E*/, 0x88 /*R_CCAL*/, 0x88 /*R_CCAL2*/,
449       0x84 /*R_CCAL3*/, 0xEA /*R_LEN*/, 0x24 /*R_LENH*/,
450       0x63 /*0x34*/, 0x29 /*0x35*/, 0x00 /*0x36*/,
451       0x00 /*0x37*/, 0x00 /*0x38*/, 0x00 /*0x39*/,
452       0x00 /*0x3A*/, 0x00 /*0x3B*/, 0xFF /*0x3C*/,
453       0x0F /*0x3D*/, 0x00 /*0x3E*/, 0x00 /*0x3F*/,
454       0x01 /*0x40*/, 0x00 /*0x41*/, 0x80 /*R_CSTAT*/,
455       0x03 /*R_SPD*/, 0x01 /*0x44*/, 0x00 /*0x45*/,
456       0x79 /*!!R_CTL!!*/, 0xC0 /*0x47*/, 0x40 /*0x48*/,
457       0x9E /*!!0x49!!*/, 0xD8 /*0x4A*/ };
458     RegWriteArray(this,R_ALL, 74, uchRegs2587);
459   }    /* #2587[065.4] */
460   INST_ASSERT();
461   RegWrite(this,R_STPS,2,cSteps);
462   /* do some magic for slider acceleration */
463   if (cSteps>600) /* only large movements are accelerated */
464     {
465       RegWrite(this,0x34, 1, 0xC3);
466       RegWrite(this,0x47, 2, 0xA000);    /* initial speed */
467     }
468   /* start back or forth movement */
469   if (nDistance>0)
470     {
471       RegWrite(this,R_CTL, 1, 0x39);    /* #2588[065.4] */
472       RegWrite(this,R_CTL, 1, 0x79);    /* #2589[065.4] */
473       RegWrite(this,R_CTL, 1, 0xF9);    /* #2590[065.4] */
474     }
475   else
476     {
477       RegWrite(this,R_CTL, 1, 0x59);
478       RegWrite(this,R_CTL, 1, 0xD9);
479     }
480   INST_ASSERT();
481   /* accelerate the slider each 100 us */
482   if (cSteps>600)
483     {
484       nRest=cSteps;
485       for (nSpeed=0x9800; nRest>600 && nSpeed>=0x4000; nSpeed-=0x800)
486 	{
487 	  nRest=RegRead(this,R_POS, 2);
488 	  usleep(100);
489 	  /* perhaps 40C0 is the fastest possible value */
490 	  RegWrite(this,0x47, 2, nSpeed>0x4000 ? nSpeed : 0x40C0);
491 	}
492     }
493   INST_ASSERT();
494   usleep(100);
495   return WaitWhileBusy(this,1000); /* thanks Mattias Ellert */
496 }
497