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