-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAtik16Drv.cpp
1171 lines (982 loc) · 40.3 KB
/
Atik16Drv.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
Astronomy USB Appliance - Control Library
Copyright (C) 2009 Martin Burri ([email protected])
This program is free software licensed under LGPL;
you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* @file Atik16Drv.cpp
*****************************************************************************
* class Atik16Drv
*
* Astronomy USB Appliance driver for Linux and Win32
* The basic driver for Artemis/ATIK cameras with USB 1.1 interface
* supported cameras are: see AtikVidPid.h
*
* This is part of the Driver Library of wxAstroCapture
*
* Copyright (C) 2009 Martin Burri ([email protected])
*
*<hr>
*
* @b Project wxAstroCapture<br>
*
* @author M. Burri
* @date 01-Feb-2009
*
*****************************************************************************
*<hr>
* @b Updates
* - dd-mmm-yyyy V. Name: Description
*
*****************************************************************************/
#include "Atik16Drv.h"
/// There is a derived version used for the INDI framework
/// which has some files in another place than wxAstroCapture
#ifdef INDI
#include "GenericDebHelp.h"
#else
//#include "../GenericDebHelp.h"
#include "GenericDebHelp.h"
#endif
// defines a helper usually found in wxWidgets for INDI usage
#ifndef WXUNUSED
#define WXUNUSED(identifier) /* identifier */
#endif
/// end INDI compatibility
#include "Atik16Thread.h"
#include "Atik16Cmd.h"
//! ////////////////////////////////////////
// defines the usage of either the Linux ftdi_sio or libusb driver
// for Artemis/ATIK cameras
// undef USE_LIBUSB for the ftdi_sio driver
#define USE_LIBUSB
#ifdef USE_LIBUSB
#define FTD245DRIVER Ftd245Lib
#define FTD245TECH EAV_ArtLibUsb
#include "Ftd245Lib.h"
#else
#define FTD245DRIVER Ftd245IOUX
#define FTD245TECH EAV_ArtSio
#include "Ftd245IOUX.h"
#endif
//! end USE_LIBUSB ///////////////////////////
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
// local shortcuts
typedef unsigned char _byte;
typedef unsigned short _word;
typedef unsigned long _dword;
/// ///////////////////////////////////////////////////////////////////
/// Singleton handler
/// ///////////////////////////////////////////////////////////////////
Atik16Drv* Atik16Drv::pinstance = NULL;// initialize pointer
Atik16Drv* Atik16Drv::Instance ()
{
if (pinstance == 0) { // is it the first call?
pinstance = new Atik16Drv(); // create sole instance
}
return pinstance; // address of sole instance
}
/// ///////////////////////////////////////////////////////////////////
/// Trivial Thread Wrapper
/// ///////////////////////////////////////////////////////////////////
#include <pthread.h>
struct AtikThread
{
private:
ArtWorker& m_artWorker; // generic worker object
pthread_t id;
pthread_attr_t attr;
// holds c asked callback routine
union call_back {
void * (*address)(void *); // pthread expected callback
} routine; // local var holds and translates the callback
public:
// only public for the callback use !!
void go();
public:
AtikThread(ArtWorker& p_work);
~AtikThread();
//! Task entry
int Run();
//! pthread Join exposed
int Join();
};
//! @brief Implements a c style function to be sent as callback ptr to pthread_create
/// we have to submit this in pthread_create so it is sent as obj (context) here
/// so this is generic up to the point it casts to AtikThread somehow
extern "C" void* ARTthread_routine(void* obj) throw() {
try {
static_cast<AtikThread*>(obj)->go(); // cast and call member from context given
return obj; //or whatever else you might need
}
catch(...) {
// return error code or just exit
return NULL;
}
}//externC
//! @brief Start function wrapped in thread_routine()
void AtikThread::go() { m_artWorker(); } // call operand () of our thread argument
// constructor
AtikThread::AtikThread(ArtWorker& p_work)
: m_artWorker(p_work)
{
routine.address = ARTthread_routine; // assign callback address
pthread_attr_init(&attr);
}
// destructor
AtikThread::~AtikThread() {
pthread_attr_destroy(&attr);
}
//! @brief Thread entry (pthread_create exposed)
int AtikThread::Run() {
return pthread_create (&id, &attr, routine.address, this);
// this one callsback above and therefore calls our go()
// which in turn runs the real instance of AtikWorker submitted
// while creating this AtikThread object
}
//! @brief Thread Join (pthread_join exposed)
int AtikThread::Join() {
return pthread_join(id, NULL);
}
/// ///////////////////////////////////////////////////////////////////
/// USB Enum Driver Interface
/// ///////////////////////////////////////////////////////////////////
//! @brief Return true if Nth USB device exists and is a camera.
bool Atik16Drv::DeviceIsCamera(unsigned short Device)
{
return Ftd245IO::DeviceIsCCDCamera(Device);
}
//! @brief Get USB Identifier of Nth USB device. Return false if no such device.
//! @brief buffer provided with pName must be at least 40 chars long.
bool Atik16Drv::DeviceName(unsigned short Device, char pName[])
{
return Ftd245IO::DeviceName(Device, pName);
}
//! @brief Get USB Serial number of Nth USB device. Return false if no such device.
//! @brief buffer provided with pSerial must be at least 40 chars long.
bool Atik16Drv::DeviceSerial(unsigned short Device, char pSerial[])
{
return Ftd245IO::DeviceSerial(Device, pSerial);
}
/// ///////////////////////////////////////////////////////////////////
/// Construction/Destruction
/// ///////////////////////////////////////////////////////////////////
Atik16Drv::Atik16Drv()
: m_ezCmd(NULL)
, m_thread(NULL)
, m_receiver(NULL)
, m_camState(CAMERA_ERROR)
, m_connected(false)
, m_ampSwitched(true)
, m_imageAquisition(false)
, m_timedWaiting(false)
, m_downloadPercent(0)
, m_downloadBps(0)
{
m_artSample.imageMem = NULL;
m_artSample.binX = 1; m_artSample.binY = 1;
m_artSample.dataLength = 0;
m_artSample.ready = false;
}
Atik16Drv::~Atik16Drv()
{
Disconnect();
if (m_artSample.imageMem) delete m_artSample.imageMem; m_artSample.imageMem=NULL;
if (m_receiver) delete m_receiver;
if (m_thread) delete m_thread;
}
void Atik16Drv::SetVerbose(bool verbose)
{
DebHelp::SetVerboseLog();
}
//! @brief Exposes the video technology used by the driver
Atik16Drv::EArtVideoTechnology Atik16Drv::VideoTechnology() const
{
return FTD245TECH; // defined in the header section (depends on libusb or ftd_sio usage)
}
//! @brief Connect to the first available device
//! @return bool: true if successful
bool Atik16Drv::Connect()
{
// InitBoard with all known cams, test some functions
bool retVal=false;
for (int i=0; i<10;i++) {
retVal = Connect(i); // tries to connect the cam board
if (retVal) break; // exit if true
}
return retVal;
}
//! @brief Connect if we know the deviceIndex
//! @param int deviceIndex: the device to attempt to connect
//! @return bool: true if successful
bool Atik16Drv::Connect(unsigned short deviceIndex)
{
_V_ ::fprintf(stderr, "Atik16Drv::Connect(%hd) \n", deviceIndex);
if (m_connected) {
if (m_ezCmd) if (deviceIndex==m_ezCmd->DeviceIndex()) return true; // already connected
::fprintf(stderr, "Atik16Drv::Connect() - already connected - disconnect first\n");
return false; // cannot connect - disconnect first
}
// create the driver
m_ezCmd = new FTD245DRIVER; // defined in the header - usage depends on USE_LIBUSB !!
// InitBoard, test some functions
bool retVal=false;
retVal = m_ezCmd->InitBoard(deviceIndex); // tries to connect the cam board
if (!retVal) {
// did not work....
if (m_ezCmd) delete m_ezCmd; m_ezCmd = NULL; // drop the driver
m_camState = CAMERA_ERROR;
::fprintf(stderr, "Atik16Drv::Connect() - InitBoard failed\n");
return false; // ERROR EXIT - cannot connect
}
m_dev.ftd = m_ezCmd ; // we use m_dev as handle to the IO system
m_expoRecord.Init();
m_caps.Init();
// cleanup any data that is still there
ClearCamIO();
// read serial no
_byte serBuffer[20]; // actual get only 6 + 1
retVal = retVal && Atik16Cmd::CMD_GetSerialNo(m_dev, serBuffer);
if (!retVal) ::fprintf(stderr, "Atik16Drv::Connect() - GetSerialNo failed\n");
for (unsigned i=0;i<sizeof(m_camSerial);i++) {
m_camSerial[i] = serBuffer[i];
}
m_hwRevIndex = serBuffer[sizeof(m_camSerial)]; // next byte is hw rev index: 0 = beta
// first thing is to read caps (and init cam accord. to manual)
retVal = retVal && RequestCAPS();
if (!retVal) ::fprintf(stderr, "Atik16Drv::Connect() - RequestCAPS failed\n");
if (retVal) {
/*
// send byte to expansion port (taken from ArtCapture Dump) ?!
serBuffer[0] = 0xc9; serBuffer[1] = 0xf4; serBuffer[2] = 0x61; serBuffer[3] = 0xb1;
retVal = retVal && Atik16Cmd::CMD_SendExtPort(m_dev, serBuffer, 4);
if (!retVal) ::fprintf(stderr, "Atik16Drv::Connect() - SendExtPort failed - is ok for some cams\n");
*/
m_connected = true;
m_camState = CAMERA_WAITING;
// create the receiver worker object - drop old ones first
if (m_receiver) delete m_receiver;
m_receiver = new Atik16Thread(this, m_dev);
// create the thread helper - drop old ones first
if (m_thread) delete m_thread;
m_thread = new AtikThread(*m_receiver);
m_thread->Run(); // run the thread
::fprintf(stderr, "Atik16Drv::Connect() - driver thread started\n");
}
else {
//cannot get camera CAPS!!
::fprintf(stderr, "Atik16Drv::Connect() - Shutdown camera connection, device.closed!\n");
m_ezCmd->ShutConnection();
m_camState = CAMERA_ERROR;
}
return (m_connected && m_thread && m_receiver);
}
//! @brief Disconnect from given device.
//! @brief Returns true if disconnected as requested
bool Atik16Drv::Disconnect()
{
_V_ ::fprintf(stderr, "Atik16Drv::Disconnect() \n");
if (m_connected) {
m_dev.ftd->Sleep_ms(500); // allow some time to settle
m_connected = false; // not longer
_V_ ::fprintf(stderr, "Atik16Drv::Disconnect() - Issue camera reset\n");
Atik16Cmd::CMD_RESET(m_dev); // FULL reset
}
if (m_receiver) {
_V_ ::fprintf(stderr, "Atik16Drv::Disconnect() - killing driver thread now\n");
m_receiver->AbortThread();
// first wait for the thread proc to terminate
assert(m_thread);
if (m_thread) {
_V_ ::fprintf(stderr, "Atik16Drv::Disconnect() - wait for driver thread end\n");
m_thread->Join(); // waits until it is gone
delete m_thread; m_thread = NULL;
_V_ ::fprintf(stderr, "Atik16Drv::Disconnect() - driver thread has ended, reclaiming resources\n");
}
// kill worker obj
delete m_receiver; m_receiver = NULL;
}
return true;
}
//! @brief Fills in pProp with camera properties
ARTEMISERROR Atik16Drv::Properties(ARTEMISPROPERTIES& pProp)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
if (m_caps.ProtocolRevMaj==0) {
// must gather those properties again
if (Atik16Cmd::CMD_RequestCAPS(m_dev, m_caps))
m_connected = true;
}
// convert from DrvCmd Format to ARTEMISPROPERTIES
pProp.Protocol = m_caps.ProtocolRevMaj*100 + m_caps.ProtocolRevMin;
pProp.nPixelsX = m_caps.TotalPixelsX;
pProp.nPixelsY = m_caps.TotalPixelsY;
pProp.PixelMicronsX = m_caps.PixelSizeX;
pProp.PixelMicronsY = m_caps.PixelSizeY;
pProp.cameraflags = m_caps.FIFOfitted ? ARTEMIS_PROPERTIES_CAMERAFLAGS_FIFO : ARTEMIS_PROPERTIES_CAMERAFLAGS_NULL;
pProp.ccdflags = m_caps.Interlaced ? ARTEMIS_PROPERTIES_CCDFLAGS_INTERLACED : ARTEMIS_PROPERTIES_CCDFLAGS_NULL;
strncpy(pProp.Description, m_caps.ID_String.c_str(), 39); pProp.Description[39]=0;
strncpy(pProp.Manufacturer, m_caps.UserID_String.c_str(), 39);pProp.Manufacturer[39]=0;
_V_ ::fprintf(stderr, "Atik16Drv::Properties() - %s\n",pProp.Description);
_V_ ::fprintf(stderr, "Atik16Drv::Properties() - %s\n",pProp.Manufacturer);
strncpy(pProp.CamModel, m_caps.CamModel.c_str(), 19); pProp.Description[19]=0;
strncpy(pProp.SensorType, m_caps.SensorType.c_str(), 19);pProp.Manufacturer[19]=0;
_V_ ::fprintf(stderr, "Atik16Drv::Properties() - %s\n",pProp.CamModel);
_V_ ::fprintf(stderr, "Atik16Drv::Properties() - %s\n",pProp.SensorType);
if (m_connected)
return ARTEMIS_OK;
else
return ARTEMIS_NO_RESPONSE;
}
//! @brief Set download thread to high or normal priority
ARTEMISERROR Atik16Drv::HighPriority(bool bHigh)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
return ARTEMIS_NOT_IMPLEMENTED;
}
/// All coords are unbinned camera pixels
//! @brief Get the pos and size of imaging subframe
void Atik16Drv::Subframe(unsigned short& x, unsigned short& y, unsigned short& w, unsigned short& h) const
{
x = m_expoRecord.XStart; y = m_expoRecord.YStart;
w = m_expoRecord.NumX; h = m_expoRecord.NumY;
}
//! @brief set the pos and size of imaging subframe inunbinned coords
ARTEMISERROR Atik16Drv::SetSubframe(unsigned short x, unsigned short y, unsigned short w, unsigned short h)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
if ( (x+w) > m_caps.TotalPixelsX ) return ARTEMIS_INVALID_PARAMETER;
if ( (y+h) > m_caps.TotalPixelsY ) return ARTEMIS_INVALID_PARAMETER;
m_expoRecord.XStart = x; m_expoRecord.YStart = y;
m_expoRecord.SetNum(w, h);
return ARTEMIS_OK;
}
//! @brief Set the start x,y coords for imaging subframe., X,Y in unbinned coordinates
ARTEMISERROR Atik16Drv::SetSubframePos(unsigned short x, unsigned short y)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
if ( (x+m_expoRecord.NumX) > m_caps.TotalPixelsX ) return ARTEMIS_INVALID_PARAMETER;
if ( (y+m_expoRecord.NumY) > m_caps.TotalPixelsY ) return ARTEMIS_INVALID_PARAMETER;
m_expoRecord.XStart = x; m_expoRecord.YStart = y;
return ARTEMIS_OK;
}
//! @brief Set the width and height of imaging subframe, W,H in unbinned coordinates
ARTEMISERROR Atik16Drv::SetSubframeSize(unsigned short w, unsigned short h)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
if ( (m_expoRecord.XStart+w) > m_caps.TotalPixelsX ) return ARTEMIS_INVALID_PARAMETER;
if ( (m_expoRecord.YStart+h) > m_caps.TotalPixelsY ) return ARTEMIS_INVALID_PARAMETER;
m_expoRecord.SetNum(w, h);
return ARTEMIS_OK;
}
//! @brief Get the x,y binning factors
void Atik16Drv::Bin(unsigned short&x, unsigned short& y) const
{
x = m_expoRecord.XBin; y = m_expoRecord.YBin;
}
//! @brief Set the x,y binning factors
ARTEMISERROR Atik16Drv::SetBin(unsigned short x, unsigned short y)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
if ( x > m_caps.MaxBinX ) return ARTEMIS_INVALID_PARAMETER;
if ( y > m_caps.MaxBinY ) return ARTEMIS_INVALID_PARAMETER;
if (m_caps.SyncBinning) {
m_expoRecord.XBin = x; m_expoRecord.YBin = x; // BinY=X
}
else {
m_expoRecord.XBin = x; m_expoRecord.YBin = y;
}
return ARTEMIS_OK;
}
//! @brief Get the max x,y binning factors
void Atik16Drv::MaxBin(unsigned short& x, unsigned short& y)
{
x = m_caps.MaxBinX;
if (m_caps.SyncBinning) {
y = m_caps.MaxBinX;
}
else {
y = m_caps.MaxBinY;
}
}
//! @brief Set whether amp is switched off during exposures
ARTEMISERROR Atik16Drv::SetAmplifierSwitched(bool bSwitched)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
m_ampSwitched = bSwitched;
// this is for short expo only
if (m_ampSwitched)
m_expoRecord.ImageConfig |= EICB_AmpOn;
else
m_expoRecord.ImageConfig &= ~EICB_AmpOn;
return ARTEMIS_OK;
}
//! @brief Set the CCD amplifier on or off
ARTEMISERROR Atik16Drv::SetAmp(bool bOn)
{
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
return (Atik16Cmd::CMD_Amplifier(m_dev, bOn))?ARTEMIS_OK:ARTEMIS_NO_RESPONSE;
}
//! @brief Return duration of last exposure, in seconds
float Atik16Drv::LastExposureDuration() const
{
return (float)m_artSample.expotime / 1000.0;
}
//! @brief Start an exposure
ARTEMISERROR Atik16Drv::StartExposure(float Seconds)
{
_V_ ::fprintf(stderr, "Atik16Drv::StartExposure() \n");
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
if (m_camState!=CAMERA_IDLE) return ARTEMIS_NO_RESPONSE;
// get this data as late as possible as it may have changed from setup
// due to pixel adjustment for interlace and binning (cam does not like odd sizes)
_V_ ::fprintf(stderr, "Atik16Drv::StartExposure() - ready\n");
m_expoRecord.Exposure = (_dword)(Seconds *1000.0); // used in ms steps
m_artSample.ready = false;
m_artSample.expotime = m_expoRecord.Exposure;
if (m_artSample.expotime<Atik16Cmd::SHORTEXPOLIMIT) {
_V_ ::fprintf(stderr, "Atik16Drv::StartExposure() - short exposure \n");
m_receiver->TimedWait(0); // dummy wait
}
else {
_V_ ::fprintf(stderr, "Atik16Drv::StartExposure() - regular exposure \n");
// if (m_ampSwitched) Atik16Cmd::CMD_Amplifier(m_dev, 0);
// do it like the others do ?!
Atik16Cmd::CMD_GuideOff(m_dev);
Atik16Cmd::CMD_Amplifier(m_dev, false);
Atik16Cmd::CMD_ClearCCD(m_dev);
Atik16Cmd::CMD_Shutter(m_dev);
m_receiver->TimedWait(m_artSample.expotime-300); // start the expo cycle; sub fudge time 300ms for now
// return ( TIMER(m_artSample.expotime-300) )?ARTEMIS_OK:ARTEMIS_NO_RESPONSE; // sub fudge time 300ms for now
}
return ARTEMIS_OK;
}
//! @brief Return time remaining in current exposure, in seconds
float Atik16Drv::ExposureTimeRemaining() const
{
if (!m_connected) return 0.0;
float tr = m_receiver->WaitTimeRemaining();
return tr / 1000.0;
}
//! @brief Percentage downloaded
int Atik16Drv::DownloadPercent() const
{
return m_downloadPercent;
}
//! @brief Prematurely end an exposure, collecting image data.
ARTEMISERROR Atik16Drv::StopExposure()
{
_V_ ::fprintf(stderr, "Atik16Drv::StopExposure() \n");
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
m_receiver->StopExposure();
return ARTEMIS_OK;
}
//! @brief Abort exposure, if one is in progress
ARTEMISERROR Atik16Drv::AbortExposure()
{
_V_ ::fprintf(stderr, "Atik16Drv::AbortExposure() \n");
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
m_receiver->AbortExposure();
return ARTEMIS_OK;
}
//! @brief Return true if an image is ready to be retrieved
bool Atik16Drv::ImageReady() const
{
if (!m_connected) return false;
return m_artSample.ready;
}
//! @brief Retrieve image dimensions and binning factors.
//! @brief x,y are actual CCD locations. w,h are pixel dimensions of image
void Atik16Drv::ImageData(unsigned short& x, unsigned short& y,
unsigned short& w, unsigned short& h,
unsigned short& binx, unsigned short& biny)
{
x=0;y=0;w=0;h=0;
if (m_artSample.imageMem) m_artSample.imageMem->GetFrameRect(x,y,w,h);
binx = m_artSample.binX; biny = m_artSample.binY;
}
//! @brief Return pointer to internal image buffer (actually unsigned shorts)
const unsigned short* Atik16Drv::ImageBuffer() const
{
if (!m_connected) return NULL;
if (m_artSample.ready && m_artSample.imageMem)
return m_artSample.imageMem->WordMemPtr();
else
return NULL;
}
//! @brief cooling support
void Atik16Drv::TemperatureSensorInfo(unsigned short WXUNUSED(sensor), int& temperature) // °C *100
{
temperature=-990;
}
void Atik16Drv::CoolingInfo(ECamCoolingInfo& flags, int& WXUNUSED(level), int& WXUNUSED(minlvl), int& WXUNUSED(maxlvl), int& WXUNUSED(setpoint))
{
flags=(ECamCoolingInfo)0; // returns no cooling
}
ARTEMISERROR Atik16Drv::SetCooling(int WXUNUSED(setpoint))
{
return ARTEMIS_NOT_IMPLEMENTED;
}
ARTEMISERROR Atik16Drv::SetCooling(double WXUNUSED(temperature))
{
return ARTEMIS_NOT_IMPLEMENTED;
}
ARTEMISERROR Atik16Drv::CoolerWarmUp()
{
return ARTEMIS_NOT_IMPLEMENTED;
}
/// Tools
ARTEMISERROR Atik16Drv::ReconnectUSB()
{
// connecting a cam resets the USB interface
// so we will not do anything here
if (!m_connected) return ARTEMIS_NOT_CONNECTED;
return ARTEMIS_OK;
}
//! @brief Return the last FT USB error condition seen
//! @brief Calling this function clears the internal error state (FT_OK=0)
int Atik16Drv::DiagnosticUSBError()
{
if (m_dev.DevNULL()) return Ftd245IO::FTS_INVALID_HANDLE;
if (!m_connected) return Ftd245IO::FTS_INVALID_HANDLE; //FT_INVALID_HANDLE
if (m_receiver) {
if (m_receiver->IsReadError()) return Ftd245IO::FTS_IO_ERROR; //FT_IO_ERROR
}
if (!m_dev.ftd->BoardAvailable()) return Ftd245IO::FTS_IO_ERROR; //FT_IO_ERROR
return Ftd245IO::FTS_OK; // FT_OK
}
//! @brief Send a diagnostic message to the camera
//! @param int send: the message
//! @return int: returns the sent message if OK
int Atik16Drv::DiagnosticPing(int send)
{
if ( m_dev.DevNULL() ) return -1;
if (!m_connected) return -1;
if (!m_dev.ftd->BoardAvailable()) return -1;
_byte c = send & 0xff;
bool retVal = Atik16Cmd::CMD_AppPing(m_dev, c);
return (retVal && (c==(send & 0xff))?send:-1);
}
/// ///////////////////////////////////////////////////////////////////
/// Cam Commands
/// ///////////////////////////////////////////////////////////////////
//! @brief Query the camera capabilities into the drivers own store
bool Atik16Drv::RequestCAPS()
{
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() \n");
bool retVal = Atik16Cmd::CMD_RequestCAPS(m_dev, m_caps);
if (!retVal) {
::fprintf(stderr, "Atik16Drv::RequestCAPS() - Error in CMD_RequestCAPS()\n");
m_camState = CAMERA_ERROR;
m_caps.Init();
m_expoRecord.CamCaps = m_caps; // contents are used !!
return false;
}
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() - CMD_RequestCAPS() OK \n");
// initialize the expo record for full image capture
m_expoRecord.CamCaps = m_caps; // contents are used !!
m_expoRecord.XStart = 0; m_expoRecord.YStart = 0;
m_expoRecord.SetNum(m_caps.TotalPixelsX, m_caps.TotalPixelsY);
m_expoRecord.XBin = 1; m_expoRecord.YBin = 1;
// cam dependent flags
m_expoRecord.ImageConfig |= (m_caps.Interlaced)?EICB_DeInterlace:0;
if (m_hwRevIndex>0) {
m_expoRecord.ImageConfig |= EICB_UseFIFO;
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() - hwIndex forces use of FIFO \n");
}
else {
// FIFO is in question only for beta cams ??
if (! m_caps.FIFOfitted) {
// if we don't get it from the caps - we try the query
bool fifo;
retVal = Atik16Cmd::CMD_FIFO_Fitted(m_dev, fifo);
if (!retVal) {
::fprintf(stderr, "Atik16Drv::RequestCAPS() - Error in CMD_FIFO_Fitted()\n");
fifo = false; // still no FIFO
}
m_expoRecord.ImageConfig |= (fifo)?EICB_UseFIFO:0;
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() - FIFO query defines use of FIFO \n");
}
else {
m_expoRecord.ImageConfig |= EICB_UseFIFO;
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() - caps defines use of FIFO \n");
}
}
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() - ImageConfig: 0x%04x \n", m_expoRecord.ImageConfig);
if (m_artSample.imageMem)
{
delete m_artSample.imageMem;
m_artSample.imageMem = NULL;
m_artSample.dataLength = 0;
}
//! the one and only place to create the image mem for the Art Driver
//! creates memory to hold a full image (and a helper for deinterlaceing if needed)
// make a new image buffer if we know the ccd i.e. call this before reading the ccd
// this is not going to change while connected to a cam
m_artSample.dataLength = m_caps.TotalPixelsX * m_caps.TotalPixelsY; // WORDS full cam size
if (m_caps.Interlaced) {
m_artSampleHelper = new WordImageMem(m_artSample.dataLength); // read sample is in the helper space
m_artSample.imageMem = new WordImageMem(m_artSample.dataLength); // will get deinterlaced to here
}
else {
m_artSampleHelper = new WordImageMem(m_artSample.dataLength);
m_artSample.imageMem = m_artSampleHelper;// read sample is already the proper one
}
_V_ ::fprintf(stderr, "Atik16Drv::RequestCAPS() - mem alloc for image sample OK length %d \n", (int)m_artSample.dataLength);
m_artSample.dataLength *= 2; // make BYTES (this is what is needed to be read
m_camState = CAMERA_IDLE;
return true;
}
//! @brief Prepare reading out the CCD
bool Atik16Drv::PrepareReadCCD()
{
_V_ ::fprintf(stderr, "Atik16Drv::PrepareReadCCD() \n");
Atik16Cmd::PrepReadCCD(m_expoRecord, m_rccdBuf);
m_artSample.expotime = m_expoRecord.Exposure;
// get this data as late as possible as it may have changed from setup
// due to pixel adjustment for interlace and binning (cam does not like odd sizes)
// m_artSample carries positive binned CCD coords i.e. mirrored but positive oriented realworld coords
// m_artSample carries positive binned CCD coords i.e. mirrored but positive oriented realworld coords
m_artSample.imageMem->SetFrameRect(m_expoRecord.XStart, m_expoRecord.YStart,
m_expoRecord.NumX/m_expoRecord.XBin, m_expoRecord.NumY/m_expoRecord.YBin);
m_artSample.binX = m_expoRecord.XBin;
m_artSample.binY = m_expoRecord.YBin;
m_artSample.dataLength = m_expoRecord.ImageBytes();
if (m_caps.Interlaced) {
// interlaced CCD
m_artSample.toDeinterlace = true; // must be deinterlaced before usage
}
// ready now
_V_ ::fprintf(stderr, "Atik16Drv::PrepareReadCCD() - width / height = %u / %u \n",
m_artSample.imageMem->width(), m_artSample.imageMem->height());
// let the thread start capturing - command execution is in thread routine
m_receiver->Aquire(m_artSample);
m_imageAquisition = true;
// once the thread is ready it will trigger the READCCD with the
// parameters set up above i.e. sending rccdBuf
return true;
}
//! @brief read all Input from the camera that might still be there
void Atik16Drv::ClearCamIO()
{
_V_ ::fprintf(stderr, "Atik16Drv::ClearCamIO - IN ... ");
unsigned long rt,wt;
m_dev.ftd->GetTimeouts(rt, wt);
m_dev.ftd->SetTimeouts(500, wt); // wait only little here
m_dev.ftd->SetMaxBuffer();
size_t readErr = 5; // try some to be sure to empty the cam
size_t read = 0;
do {
m_dev.ftd->ReadBYTEsNIL(read);
if (read==0)
readErr--;
else
readErr = 5; // reset end detector
} while (readErr>0);
m_dev.ftd->SetDefaultBuffer();
m_dev.ftd->SetTimeouts(rt, wt); // reset to what it was before
_V_ ::fprintf(stderr, " - OUT \n");
Atik16Cmd::CMD_RESET(m_dev);
}
/// ///////////////////////////////////////////////////////////////////
/// Thread Callback implementation
/// ///////////////////////////////////////////////////////////////////
//! @brief Callback from the ReceiverThread when timer expires with success
void Atik16Drv::ProcessTimeElapsed()
{
_V_ ::fprintf(stderr, "Atik16Drv::ProcessTimeElapsed()\n");
// REMARK: beware of the context ... this is a callback of the HighPrio Thread
m_timedWaiting = false;
PrepareReadCCD();
}
//! @brief Callback from the ReceiverThread to start the camera readout
//! @brief the command must be setup with PrepareReadCCD before
void Atik16Drv::ProcessTriggerRDCCD()
{
_V_ ::fprintf(stderr, "Atik16Drv::ProcessTriggerRDCCD()\n");
// REMARK: beware of the context ... this is a callback of the HighPrio Thread
Atik16Cmd::CMD_GuideOff(m_dev);
Atik16Cmd::CMD_Amplifier(m_dev, true);
Atik16Cmd::CMD_ReadCCD(m_dev, m_rccdBuf); // either complete timing (short expo) or only readout
}
//! @brief Callback from the ReceiverThread while downloading data
//! @param long downloadPercent: download completion % value 0.. 100
void Atik16Drv::ProcessTriggerDownload(long downloadPercent)
{
// REMARK: beware of the context ... this is a callback of the HighPrio Thread
m_downloadPercent = downloadPercent;
}
//! @brief Callback from the ReceiverThread when DL is finished
//! @param bool readError: true if a read error occured
//! @param long realExpotime: the measured exposure time
//! @param long downloadBps: the measured download rate in bytes/sec
// note: this is called in any case
// readerror could be set or camState FLUSHING or DOWNLOADING (which is OK)
void Atik16Drv::ProcessImageRead(bool readError, long realExpotime, long downloadBps)
{
_V_ ::fprintf(stderr, "Atik16Drv::ProcessImageRead() \n");
// REMARK: beware of the context ... this is a callback of the HighPrio Thread
m_downloadPercent = 100;
m_downloadBps = downloadBps;
::fprintf(stderr, "DL Rate is: %d Bytes/sec \n", (int)m_downloadBps);
if (m_artSample.expotime>=Atik16Cmd::SHORTEXPOLIMIT) m_artSample.expotime = realExpotime; // gather the real exposuretime
::fprintf(stderr, "Expotime is: %d millisec \n", (int)m_artSample.expotime);
m_imageAquisition = false;
m_artSample.ready = ( (m_camState==CAMERA_DOWNLOADING) && (!readError) );
if (m_artSample.ready && m_artSample.toDeinterlace) {
//! we have to deinterlace the image
// the sample is collected in artSample but we need to deinterlace it
// so the artSample.imageMem is switched with the helper
m_artSampleHelper->SetFrameRect(m_artSample.imageMem); // copy frame info from master
WordImageMem* tmp = m_artSample.imageMem;
m_artSample.imageMem = m_artSampleHelper;
m_artSampleHelper = tmp;
// Deinterlace by combining the two single frames that arrived one after the other
// must use binned data here
_word ccdw = m_artSample.imageMem->width();
_word ccdh = m_artSample.imageMem->height();
_word* s1Ptr = m_artSampleHelper->WordMemPtrRef() + ((ccdh/2)*ccdw); // source lower (1st) half
_word* s2Ptr = m_artSampleHelper->WordMemPtrRef(); // source upper half
// the cam does gather exposures <2.46 sec in two rounds
// to get rid of the differences we calculate the ratio of the two interlaced images
// and equalize frames using the first 7 lines as scale (or 1 if no 7 are avail)
size_t nPix = (ccdh>2)?ccdw*7:ccdw;
double mean1=0.0; for (size_t x=0;x<nPix; x++, s1Ptr++) {mean1 += *s1Ptr;}; mean1 /= nPix;
double mean2=0.0; for (size_t x=0;x<nPix; x++, s2Ptr++) {mean2 += *s2Ptr;}; mean2 /= nPix;
double eq = mean2/mean1;
s1Ptr = m_artSampleHelper->WordMemPtrRef() + ((ccdh/2)*ccdw); // source lower (1st) half
s2Ptr = m_artSampleHelper->WordMemPtrRef(); // source upper half
_word* dPtr = m_artSample.imageMem->WordMemPtrRef(); // dest start
for (_word y=0; y<(ccdh/2); y++) {
for (_word x=0; x<ccdw; x++) {*dPtr++=(_word)(eq * (*s1Ptr++));};
for (_word x=0; x<ccdw; x++) {*dPtr++=*s2Ptr++;};
/*
memcpy(dPtr, s1Ptr, ccdw*2); // copy a line in BYTE size
dPtr += ccdw; s1Ptr += ccdw; // src is next line
memcpy(dPtr, s2Ptr, ccdw*2); // copy a line in BYTE size
dPtr += ccdw; s2Ptr += ccdw; // src is next line
*/
}
m_artSample.toDeinterlace = false; // not longer
}
m_artSample.ready =true; // force to true in the end (state behavior)
}
//! @brief Callback from the ReceiverThread when the worker is about to start its main loop
// setup the infrastructure
void Atik16Drv::ProcessThreadStart()
{
_V_ ::fprintf(stderr, "Atik16Drv::ProcessThreadStart - IN\n");
// REMARK: beware of the context ... this is a callback of the HighPrio Thread
assert( m_dev.DevOK() );
m_camState = CAMERA_IDLE;
}
//! @brief Callback from the ReceiverThread when the worker is about to exit
// clean up the infrastructure
void Atik16Drv::ProcessThreadEnd(int exitCode)
{
_V_ ::fprintf(stderr,"Atik16Drv::ProcessThreadEnd - exitCode %d\n", exitCode);
// REMARK: beware of the context ... this is a callback of the HighPrio Thread
m_connected = false;
m_timedWaiting = false;
m_imageAquisition = false;
m_camState = CAMERA_WAITING;
assert( m_dev.DevOK() );
ClearCamIO();
if ( m_dev.DevOK() ) {
if (m_ezCmd) delete m_ezCmd; m_ezCmd = NULL;
}
m_dev.Init(); // reset this too
}
/// ///////////////////////////////////////////////////////////////////
/// Trivial FITS Writer
/// ///////////////////////////////////////////////////////////////////
/// byte order
#define xBIG_ENDIAN 4321
#define xLITTLE_ENDIAN 1234
#ifdef WORDS_BIGENDIAN
#define xBYTE_ORDER xBIG_ENDIAN
#else
#define xBYTE_ORDER xLITTLE_ENDIAN
#endif
#define xUINT16_SWAP_ALWAYS(val) \
((_word) ( \
(((_word) (val) & (_word) 0x00ffU) << 8) | \
(((_word) (val) & (_word) 0xff00U) >> 8)))
#define xINT16_SWAP_ALWAYS(val) \
((_word) ( \
(((_word) (val) & (_word) 0x00ffU) << 8) | \
(((_word) (val) & (_word) 0xff00U) >> 8)))
#define xUINT32_SWAP_ALWAYS(val) \
((_dword) ( \