1 /****************************************************************/
\r
3 * Description: API layer for MF614 card. *
\r
4 * Dependency: Windows 32, Windows 64 or Linux *
\r
5 * Copyright 2006-2007 Humusoft s.r.o. *
\r
6 ****************************************************************/
\r
8 #if defined(_WIN32) || defined(_WIN64)
\r
14 #include "hudaqlib.h"
\r
15 #include "hudaq_internal.h"
\r
18 #define MASTERFREQUENCY 20000000 ///< Master frequency of MF614 is 20MHz
\r
21 /** Counter control register */
\r
24 unsigned int OutputControl:3; ///< 000-Inactive, Output Low; 001-Active, high on TC; 010-TC toggled; 011-illegal
\r
25 ///< 100-Inactive, Output High Z; 101-Active, low on TC; 110-illegal; 111-illegal
\r
26 unsigned int Direction:1; ///< 0-Down; 1-Up
\r
27 unsigned int CountMode:1; ///< 0-Binary; 1-BCD
\r
28 unsigned int RepeatMode:1; ///< 0-once; 1-repeat
\r
29 unsigned int ReloadMode:1; ///< 0-load; 1-both
\r
30 unsigned int GateMode:1; ///< 0-off; 1-on
\r
31 unsigned int CountSource:4; ///< 0000-TC(n-1)/F6; 0001-Source1/F7; 0010-Source2/FOUT1; 0011-Source3/FOUT2
\r
32 ///< 0100-Source4/??; 0101-Source5/??; 0110-Gate1/??; 0111-Gate2/??
\r
33 ///< 1000-Gate3/??; 1001-Gate4/??; 1010-Gate5/??; 1011-F1/??
\r
34 ///< 1100-F2/??; 1101-F3/??; 1110-F4/??; 1111-F5/??
\r
35 unsigned int EdgeMode:1; ///< 0-disabled; 1-enabled
\r
36 unsigned int GateControl:3; ///< 00-1; 01-IN; 10-Out_n-1; 11-Out_n+1
\r
37 unsigned int CountSourceEx:1; ///< extends one bit for Count source
\r
38 unsigned int InterruptMode:1; ///< 0-pulse; 1-latch
\r
39 unsigned int InterruptPolarity:1; ///< 0-low; 1-high
\r
40 } Counter614Control;
\r
42 /** Configuration structure for one analog input. */
\r
45 unsigned int Range:1; ///< 0-5V; 1-10V
\r
46 unsigned int Bipolar:1; ///< 0 - <0;range>; 1 - <-range;+range>
\r
47 unsigned int Raw:1; ///< 0 - volts; 1 - raw
\r
51 /** Configuration structure for one analog output. */
\r
54 unsigned __int8 LoCache;
\r
55 unsigned __int8 HiCache;
\r
57 unsigned int Raw:1; ///< 0-volts; 1-raw
\r
61 /** Configuration structure for one encoder. */
\r
64 unsigned __int8 Filter;
\r
66 unsigned int ResetOnRead:1; ///< 0-no reset; 1-reset
\r
67 unsigned int Polarity:1; ///< 0-negative polarity; 1-positive polarity
\r
68 unsigned int Index:1; ///< 0-disable index; 1-enable index
\r
69 unsigned int Level:1; ///< 0-edge; 1-level
\r
70 unsigned int Quadrature:2; ///< NonQuadrature / Quadrature mode
\r
74 /** This enum contains internal state info. */
\r
78 PWM, ///< PWM is output from counter
\r
79 OUT_0, ///< counter is generating 0 permanently
\r
80 OUT_1, ///< counter is generating 1 permanently
\r
81 Counting, ///< counting on external signal
\r
82 StepperPWM, ///< Part of stepper motor that generates output
\r
83 StepperMaster, ///< Part of stepper motors that counts pulses
\r
84 custom ///< counter contains externally defined value
\r
85 } Internal614CtrMode;
\r
88 /** One record related to counter. */
\r
91 __int16 ACache; ///< Counter A register cache; Load register
\r
92 __int16 BCache; ///< Counter B register cache; Hold register
\r
93 Internal614CtrMode Mode; ///< Flag of subsystem that uses counter
\r
94 Counter614Control CTC; ///< Counter mode regiser
\r
95 unsigned int ResetOnRead:1; ///< 0-no reset; 1-reset
\r
99 /** One record related to stepper motor. */
\r
102 double Fmin; ///< Minimal frequency of stepper motor
\r
103 double Fmax; ///< Maximal frequency of stepper motor
\r
104 signed char Direction; ///< Direction of stepping motor
\r
105 __int32 LastPosition; ///< Stepper motor target position
\r
106 __int32 ChainedPosition; ///< Stepper motor position
\r
107 DWORD TimeStamp; ///< Last refresh time mark call of HudaqStepout
\r
108 double Frequency; ///< Current speed (frequency) of stepper motor
\r
109 double Acc; ///< Acceleration of stepping motor in steps/s^2
\r
113 /** Ranges for both Analog Inputs and Analog Outputs - only one item for MF624. */
\r
114 static const HudaqRange MF614RangeAIO[4] =
\r
115 {{-10,+10},{-5,+5},{0,+10},{0,+5}};
\r
117 #define MAXIMAL_STEP_INCREMENT 500
\r
119 #define DA_CHANNELS 4 ///< Amount of Analog Outputs
\r
120 #define AD_CHANNELS 8 ///< Amount of Analog Inputs
\r
121 #define ENC_CHANNELS 4 ///< Amount of Encoders
\r
122 #define CTR_CHANNELS 5 ///< Amount of counters
\r
123 #define STEP_CHANNELS 2 ///< Amount of steppers
\r
127 /** Cache of MF614 state */
\r
130 UserDataHeader Hdr; ///< General device setup
\r
132 /* Digital inputs/outputs */
\r
133 __int8 DoutCache; ///< digital outputs to be cached
\r
135 /* Analog inputs/outputs */
\r
137 ADSetup AIOptions[AD_CHANNELS]; ///< Options for analog inputs
\r
139 DASetup DAOptions[DA_CHANNELS]; ///< DA values to be cached
\r
141 /* Counters and encoders */
\r
142 EncSetup EncOptions[ENC_CHANNELS]; ///< Encoder (IRC counter) state register
\r
144 Counter614 counter[CTR_CHANNELS]; ///< Structure that holds state of one counter
\r
146 Stepper614 step[STEP_CHANNELS]; ///< Stepper motor table
\r
150 #define LOOP_AD_TIMEOUT 0xFFFF ///< timeout per loop for endless AD conversion
\r
153 /** Symbolic aliases for available registers inside BADR0 read. */
\r
169 /** Symbolic aliases for available registers inside BADR1 write. */
\r
184 /** MF614 specific conversion values from A/D convertor to a voltage. */
\r
185 static __inline double MF614_AD2VOLT(signed __int16 x, char Gain)
\r
187 if(Gain & 8) // bipolar range
\r
189 if(Gain & 16) // <-10V;+10V>
\r
190 return 10.0*((signed __int16)(x<<4))/(double)0x8000;
\r
192 return 5.0*((signed __int16)(x<<4))/(double)0x8000;
\r
194 else // unipolar range
\r
196 if(Gain & 16) // <0V;+10V>
\r
197 return 10.0*((signed __int16)(x & 0xFFF))/(double)0x1000;
\r
199 return 5.0*((signed __int16)(x & 0xFFF))/(double)0x1000;
\r
204 /** Convert voltage to a raw values for D/A convertor. */
\r
205 static __inline __int16 MF614_VOLT2DA(double y)
\r
207 y = 0x1000*(y+10)/20;
\r
208 if (y>=0x0FFF) return(0x0FFF);
\r
209 if (y<=0) return(0x0000);
\r
210 return( (__int16)y );
\r
215 /** write to memory mapped device byte wise, bytes are stored on every 4th position. */
\r
216 static __inline void StoreByte(size_t Ptr, int Offset, unsigned __int8 value)
\r
218 ((volatile unsigned __int8 *)(Ptr))[4*Offset] = value;
\r
221 /** write to memory mapped device byte wise through cache. */
\r
222 static __inline void StoreCachedByte(size_t Ptr, int Offset, unsigned __int8 value, unsigned __int8 *CachedValue)
\r
224 if (*CachedValue != value)
\r
226 *CachedValue = value;
\r
227 StoreByte(Ptr,Offset,value);
\r
231 /** Read from memory mapped device byte wise, bytes are stored on every 4th position.
\r
232 This is specific to OX9162 board registers. */
\r
233 static __inline unsigned __int8 GetByte(size_t Ptr, int Offset)
\r
235 return ((volatile unsigned __int8 *)(Ptr))[4*Offset];
\r
239 /** Packed word from two 8bit reads. */
\r
240 static __inline unsigned __int16 GetPackedWord(size_t Ptr, int Offset)
\r
243 RetVal = GetByte(Ptr,Offset);
\r
244 RetVal += 256*GetByte(Ptr,Offset);
\r
249 /** This inline is used for reading tightly packed OX9162 local configuration registers. */
\r
250 static __inline unsigned __int8 GetBytePlain(size_t Ptr, int Offset)
\r
252 return ((volatile unsigned __int8 *)(Ptr))[Offset];
\r
256 /****************************************************************
\r
258 * DIGITAL INPUTS & OUTPUTS *
\r
260 ****************************************************************/
\r
263 /** Get data from digital input. */
\r
264 static int MF614DIRead(const DeviceRecord *DevRecord, unsigned channel)
\r
266 if (channel!=0) return 0;
\r
267 return GetByte(DevRecord->DrvRes.Resources.MemResources[1].Base,DIN);
\r
271 /** Write data to digital output. */
\r
272 static HUDAQSTATUS MF614DOWrite(const DeviceRecord *DevRecord, unsigned channel, unsigned value)
\r
274 if (channel!=0) return HUDAQBADARG;
\r
275 StoreCachedByte(DevRecord->DrvRes.Resources.MemResources[1].Base, DOUT, value,
\r
276 & ((MF614_Private *)(DevRecord->DrvRes.DriverData))->DoutCache );
\r
277 return HUDAQSUCCESS;
\r
281 /** Write one bit to digital output. */
\r
282 static void MF614DOWriteBit(const DeviceRecord *DevRecord, unsigned channel, unsigned bit, int value)
\r
286 if (channel!=0) return;
\r
287 if (bit>=8) return; //there are only 8 bits available in MF624
\r
289 DoutByte = ((MF614_Private *)(DevRecord->DrvRes.DriverData))->DoutCache;
\r
290 if (value) DoutByte |= (1<<bit);
\r
291 else DoutByte &= ~(1<<bit);
\r
293 StoreCachedByte(DevRecord->DrvRes.Resources.MemResources[1].Base, DOUT, DoutByte,
\r
294 & ((MF614_Private *)(DevRecord->DrvRes.DriverData))->DoutCache );
\r
298 static void MF614DOWriteMultipleBits(const DeviceRecord *DevRecord, unsigned channel, unsigned mask, unsigned value)
\r
302 if (channel!=0) return;
\r
304 DoutByte = ((MF614_Private *)(DevRecord->DrvRes.DriverData))->DoutCache;
\r
305 DoutByte = (DoutByte & ~mask) | (value & mask);
\r
307 StoreCachedByte(DevRecord->DrvRes.Resources.MemResources[1].Base, DOUT, DoutByte,
\r
308 &((MF614_Private *)(DevRecord->DrvRes.DriverData))->DoutCache );
\r
312 static double MF614DIOGetParameter(unsigned channel, HudaqParameter param)
\r
316 case HudaqDINUMBITS:
\r
317 return (channel==0) ? 8 : WRONG_VALUE;
\r
318 case HudaqDONUMBITS:
\r
319 return (channel==0) ? 8 : WRONG_VALUE;
\r
320 case HudaqDINUMCHANNELS:
\r
322 case HudaqDONUMCHANNELS:
\r
326 return WRONG_VALUE;
\r
330 /****************************************************************
\r
332 * ANALOG INPUTS & OUTPUTS *
\r
334 ****************************************************************/
\r
336 static HUDAQSTATUS MF614AISetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
338 MF614_Private *Cache;
\r
340 if (channel>=AD_CHANNELS) return HUDAQBADARG;
\r
341 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
346 if (value<0 || value>=2) return HUDAQBADARG;
\r
347 Cache->AIOptions[channel].Raw = (int)value;
\r
348 return HUDAQSUCCESS;
\r
353 case 0: Cache->AIOptions[channel].Bipolar = 1;
\r
354 Cache->AIOptions[channel].Range = 1;
\r
356 case 1: Cache->AIOptions[channel].Bipolar = 1;
\r
357 Cache->AIOptions[channel].Range = 0;
\r
359 case 2: Cache->AIOptions[channel].Bipolar = 0;
\r
360 Cache->AIOptions[channel].Range = 1;
\r
362 case 3: Cache->AIOptions[channel].Bipolar = 0;
\r
363 Cache->AIOptions[channel].Range = 0;
\r
365 default:return HUDAQBADARG;
\r
367 return HUDAQSUCCESS;
\r
370 return HUDAQNOTSUPPORTED;
\r
374 static double MF614AIGetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
376 MF614_Private *Cache;
\r
378 if (channel>=AD_CHANNELS) return WRONG_VALUE;
\r
379 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
384 return(Cache->AIOptions[channel].Raw);
\r
386 case HudaqAIRange: // see ::MF614RangeAIO for range indices
\r
387 if(Cache->AIOptions[channel].Bipolar==1) // bipolar ranges
\r
389 if(Cache->AIOptions[channel].Range==1) return 0; // <-10V;+10V>
\r
390 return 1; // <-5V;+5V>
\r
392 else // unipolar ranges
\r
394 if(Cache->AIOptions[channel].Range==1) return 2; // <0V;+10V>
\r
395 return 3; // <0V;+5V>
\r
398 case HudaqAINUMCHANNELS:
\r
399 return AD_CHANNELS;
\r
402 return WRONG_VALUE;
\r
406 /** Get data from analog input MF614 specific. */
\r
407 static double MF614AIRead(const DeviceRecord *DevRecord, unsigned channel)
\r
411 unsigned TimeoutCounter;
\r
413 ADSetup *ChanAiCache;
\r
415 if (channel>=AD_CHANNELS) return UNDEFINED_VALUE;
\r
416 ChanAiCache = &((MF614_Private *)DevRecord->DrvRes.DriverData)->AIOptions[channel];
\r
418 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
419 Ptr2 = DevRecord->DrvRes.Resources.MemResources[0].Base;
\r
421 Gain = channel | 0x40;
\r
422 if(ChanAiCache->Range) Gain |= 16;
\r
423 if(ChanAiCache->Bipolar) Gain |= 8;
\r
424 StoreByte(Ptr0, ADCTRL, Gain); // select channel and start conversion
\r
428 while ( (GetBytePlain(Ptr2,DATAREAD) & 0x04) != 0 ) // wait for conversion complete
\r
430 if (TimeoutCounter++>LOOP_AD_TIMEOUT)
\r
431 return UNDEFINED_VALUE; /*timeout*/
\r
434 ad = GetByte(Ptr0,AIN) + 256*GetByte(Ptr0,AIN+1); // read the value
\r
436 if(ChanAiCache->Raw)
\r
439 return(MF614_AD2VOLT(ad,Gain));
\r
443 static double MF614AOGetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
445 MF614_Private *Cache;
\r
447 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
448 if (channel>=DA_CHANNELS) return WRONG_VALUE;
\r
453 return(Cache->DAOptions[channel].Raw);
\r
455 return(0); // only bipolar range -10:10V is supported
\r
456 case HudaqAONUMCHANNELS:
\r
457 return DA_CHANNELS;
\r
460 return WRONG_VALUE;
\r
464 /** Setup for MF614 analog outputs. */
\r
465 static HUDAQSTATUS MF614AOSetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
467 MF614_Private *Cache;
\r
469 if (channel>=DA_CHANNELS) return HUDAQBADARG;
\r
470 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
475 if (value<0 || value>=2) return HUDAQBADARG;
\r
476 Cache->DAOptions[channel].Raw = (int)value;
\r
477 return HUDAQSUCCESS;
\r
480 if((int)value == 0) return HUDAQSUCCESS; // only one range "0" -10:10V is supported
\r
481 return HUDAQBADARG;
\r
483 return HUDAQNOTSUPPORTED;
\r
487 /** Write data to analog output. */
\r
488 static void MF614AOWrite(const DeviceRecord *DevRecord, unsigned channel, double value)
\r
491 unsigned __int16 RawValue;
\r
492 MF614_Private *Cache;
\r
495 if (channel>=DA_CHANNELS) return;
\r
496 Cache = DevRecord->DrvRes.DriverData;
\r
498 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
500 if(Cache->DAOptions[channel].Raw)
\r
501 RawValue = (int)value;
\r
503 RawValue = MF614_VOLT2DA(value);
\r
505 StoreCachedByte(Ptr0, DA0LO+2*channel, RawValue & 0xFF, &Cache->DAOptions[channel].LoCache);
\r
506 StoreCachedByte(Ptr0, DA0HI+2*channel, (RawValue>>8) & 0x0F, &Cache->DAOptions[channel].HiCache);
\r
508 RawValue=GetByte(Ptr0, DALATCHEN); // update the output
\r
512 /** Write multiple data to analog output synchronized. */
\r
513 static HUDAQSTATUS MF614AOWriteMultiple(const DeviceRecord *DevRecord, unsigned number, const unsigned* channels, const double* values)
\r
516 unsigned __int16 RawValue;
\r
517 MF614_Private *Cache;
\r
518 int RetVal = HUDAQSUCCESS;
\r
521 if (channels==NULL || values==NULL || number<=0) return HUDAQBADARG;
\r
522 Cache = DevRecord->DrvRes.DriverData;
\r
524 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
528 if (*channels>=DA_CHANNELS)
\r
529 RetVal = HUDAQPARTIAL;
\r
532 if(Cache->DAOptions[*channels].Raw)
\r
533 RawValue = (unsigned __int16)*values;
\r
535 RawValue = MF614_VOLT2DA(*values);
\r
537 StoreCachedByte(Ptr0, DA0LO + 2 * *channels, RawValue & 0xFF, &Cache->DAOptions[*channels].LoCache);
\r
538 StoreCachedByte(Ptr0, DA0HI + 2 * *channels, (RawValue>>8) & 0x0F, &Cache->DAOptions[*channels].HiCache);
\r
543 RawValue = GetByte(Ptr0, DALATCHEN); // update the output
\r
548 /****************************************************************
\r
552 ****************************************************************/
\r
555 static double MF614EncGetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
557 MF614_Private *Cache;
\r
558 EncSetup *xEncOptions;
\r
560 if (channel>=ENC_CHANNELS) return WRONG_VALUE;
\r
561 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
562 xEncOptions = &Cache->EncOptions[channel];
\r
566 case HudaqEncFILTER:
\r
567 return((xEncOptions->Filter>0) ? 1 : 0);
\r
569 case HudaqEncRESETONREAD:
\r
570 return(xEncOptions->ResetOnRead);
\r
572 case HudaqEncRESETMODE:
\r
573 if(xEncOptions->Index == 0)
\r
574 return(HudaqEncRESNONE);
\r
575 if(xEncOptions->Level == 1)
\r
576 return(HudaqEncRESI0);
\r
578 if(xEncOptions->Polarity == 1)
\r
579 return(HudaqEncRESIRISING);
\r
581 return(HudaqEncRESIFALLING);
\r
583 case HudaqEncCOUNTCONTROL:
\r
584 if(xEncOptions->Index==1) return(HudaqEncCOUNTENABLE);
\r
585 return(HudaqEncCOUNTI1);
\r
588 return(GetByte(DevRecord->DrvRes.Resources.MemResources[1].Base, IRC0CMDRD)>>7);
\r
590 case HudaqEncNUMCHANNELS:
\r
591 return ENC_CHANNELS;
\r
594 return WRONG_VALUE;
\r
598 static HUDAQSTATUS MF614EncSetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
601 MF614_Private *Cache;
\r
603 EncSetup *xEncOptions;
\r
605 if (channel>=ENC_CHANNELS) return HUDAQBADARG;
\r
606 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
607 xEncOptions = &Cache->EncOptions[channel];
\r
608 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
612 case HudaqEncFILTER:
\r
615 if(xEncOptions->Filter == 0) return HUDAQSUCCESS;
\r
616 xEncOptions->Filter = 0;
\r
620 if(xEncOptions->Filter > 0) return HUDAQSUCCESS;
\r
621 xEncOptions->Filter = 9; //limit to 250kHz
\r
623 StoreByte(Ptr0, IRC0CMDWR+2*channel, 0x01); // reset BP
\r
624 StoreByte(Ptr0, IRC0DATAWR+2*channel, xEncOptions->Filter); // PR0; program filter to xxx
\r
625 StoreByte(Ptr0, IRC0CMDWR+2*channel, 0x18); // PR0 -> PSC
\r
626 return HUDAQSUCCESS;
\r
628 case HudaqEncRESETONREAD:
\r
629 xEncOptions->ResetOnRead = ((int)value==0) ? 0 : 1;
\r
630 return HUDAQSUCCESS;
\r
632 case HudaqEncRESETMODE:
\r
636 case HudaqEncRESNONE:
\r
637 xEncOptions->Index = 0;
\r
639 case HudaqEncRESPERMANENT:
\r
640 return HUDAQFAILURE;
\r
641 case HudaqEncRESI0: ///< Reset Encoder when I=0
\r
642 xEncOptions->Index = 1;
\r
643 xEncOptions->Polarity = 0;
\r
644 xEncOptions->Level = 1;
\r
646 case HudaqEncRESIRISING:
\r
647 xEncOptions->Index = 1;
\r
648 xEncOptions->Polarity = 1;
\r
649 xEncOptions->Level = 0;
\r
651 case HudaqEncRESIFALLING:
\r
652 xEncOptions->Index = 1;
\r
653 xEncOptions->Polarity = 0;
\r
654 xEncOptions->Level = 0;
\r
656 case HudaqEncRESI1: ///< Reset Encoder when I=1
\r
657 case HudaqEncRESIEITHER:
\r
658 return HUDAQNOTSUPPORTED; // these modes are not supported by MF614
\r
660 return HUDAQBADARG;
\r
664 if(xEncOptions->Index)
\r
666 StoreByte(Ptr0,IRC0CMDWR+2*channel,0x41); // IOR 0 10 00 0 0 1
\r
668 if(xEncOptions->Polarity) Register|=2;
\r
669 if(!xEncOptions->Level) Register|=1;
\r
671 else //no index mode, gate must be active
\r
673 StoreByte(Ptr0,IRC0CMDWR+2*channel,0x45); // IOR 0 10 00 1 0 1
\r
675 StoreByte(Ptr0,IRC0CMDWR+2*channel,Register); // IDR
\r
677 return HUDAQSUCCESS;
\r
683 case HudaqEncMODEIRC: ///< Decode IRC connected to inputa A and B (default)
\r
684 xEncOptions->Quadrature = 3;
\r
686 case HudaqEncMODERISING: ///< Count up on A rising edge
\r
687 xEncOptions->Quadrature = 0;
\r
690 case HudaqEncMODEFALLING:
\r
691 case HudaqEncMODEEITHER:
\r
692 return HUDAQNOTSUPPORTED; // these modes are not supported by MF614
\r
694 return HUDAQBADARG;
\r
696 Register = 0x20 | (xEncOptions->Quadrature<<3); // CMR 0 01 QQ 0 0 0
\r
697 StoreByte(Ptr0,IRC0CMDWR+2*channel,Register); // CMR
\r
698 return HUDAQSUCCESS;
\r
700 case HudaqEncCOUNTCONTROL:
\r
701 if(HudaqEncCOUNTENABLE || HudaqEncCOUNTI1) return HUDAQSUCCESS;
\r
702 return HUDAQFAILURE;
\r
706 return HUDAQNOTSUPPORTED;
\r
710 static HUDAQSTATUS MF614EncReset(const DeviceRecord *DevRecord, unsigned channel)
\r
712 if (channel>=ENC_CHANNELS) return HUDAQBADARG;
\r
714 StoreByte(DevRecord->DrvRes.Resources.MemResources[1].Base,
\r
715 IRC0CMDWR+2*channel,0x03); // reset BP & CNTR 0 00 00 01 1
\r
716 return HUDAQSUCCESS;
\r
720 static int MF614EncRead(const DeviceRecord *DevRecord, unsigned channel)
\r
727 if (channel>=ENC_CHANNELS) return 0;
\r
729 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
731 StoreByte(Ptr0, IRC0CMDWR + 2*channel, 0x11); // CNTR -> OL, reset BP
\r
733 ircaddr = IRC0DATARD + 2*channel;
\r
734 IrcRead = GetByte(Ptr0,ircaddr);
\r
735 IrcRead += GetByte(Ptr0,ircaddr)<<8;
\r
736 IrcRead += GetByte(Ptr0,ircaddr)<<16;
\r
737 if (IrcRead>8388607) IrcRead -= 16777216;
\r
739 if (((MF614_Private *)DevRecord->DrvRes.DriverData)->EncOptions[channel].ResetOnRead)
\r
741 StoreByte(Ptr0,IRC0CMDWR+2*channel,0x03); // reset BP & CNTR 0 00 00 01 1
\r
748 /****************************************************************
\r
750 * COUNTERS PWM + Count + Step *
\r
752 ****************************************************************/
\r
754 /** This PWM procedure automatically sets prescallers. Change of a prescaller
\r
755 causes glitch. But a glitch is never emitted when a duty cycle is changed. */
\r
756 static HUDAQSTATUS MF614PWMWrite(const DeviceRecord *DevRecord, unsigned channel, double frequency, double dutycycle)
\r
758 unsigned __int32 T1,T2;
\r
762 Counter614 *pCtrCache;
\r
763 HUDAQSTATUS RetCode = HUDAQSUCCESS;
\r
766 if (channel>=CTR_CHANNELS) return HUDAQBADARG;
\r
767 pCtrCache = &((MF614_Private *)DevRecord->DrvRes.DriverData)->counter[channel];
\r
769 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
773 if (pCtrCache->Mode != OUT_1)
\r
775 if (pCtrCache->Mode != PWM)
\r
776 { /* Counter Mode Register must be initialized before switching output to 1. */
\r
777 *(int *)&pCtrCache->CTC = 0xB62; // 000 0 1011 | 0 1 1 0 0 010
\r
778 pCtrCache->CTC.CountSource = 0x0F; // maximal acceptable prescaller
\r
779 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
780 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
781 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
783 pCtrCache->Mode = OUT_1;
\r
784 StoreByte(Ptr0, TCP, 0xC0|(1<<channel)); // disarm
\r
785 StoreByte(Ptr0, TCP, 0xE8|(channel+1)); // set toggle out
\r
787 return HUDAQSUCCESS;
\r
791 if (pCtrCache->Mode != OUT_0)
\r
793 pCtrCache->Mode = OUT_0;
\r
794 StoreByte(Ptr0, TCP, 0xC0|(1<<channel)); // disarm
\r
795 StoreByte(Ptr0, TCP, 0xE0|(channel+1)); // clear toggle out
\r
797 return HUDAQSUCCESS;
\r
800 T = MASTERFREQUENCY / frequency;
\r
801 if(T<1) RetCode = HUDAQPARTIAL;
\r
804 while(T>65535) // T1+T2
\r
808 if(Prescaler>0x0F) //prescaller overflow (0x0F; 0x11 when aux mode is introduced)
\r
809 { // 0x0F means 3mHz -> 5.5min
\r
810 T=65535; // maximal acceptable T; minimal frequency
\r
811 Prescaler=0x0F; // maximal acceptable prescaller
\r
812 RetCode = HUDAQPARTIAL;
\r
817 T1 = max(1, (long)floor(T*dutycycle + 0.5));
\r
818 T2 = max(1, (long)(T-T1) );
\r
820 /* A counter must be disarmed before reloading when prescaler is changed. */
\r
821 if(Prescaler != pCtrCache->CTC.CountSource)
\r
823 if (pCtrCache->Mode!=OUT_0 && pCtrCache->Mode!=OUT_1)
\r
824 StoreByte(Ptr0, TCP, 0xC0|(1<<channel)); // disarm (OUT_0 and OUT_1 modes are already disarmed)
\r
827 if(pCtrCache->ACache!=T2) // Load register
\r
829 StoreByte(Ptr0, TCP, (0x09+channel));
\r
830 StoreByte(Ptr0, TDP, T2);
\r
831 StoreByte(Ptr0, TDP, ((T2)>>8));
\r
832 pCtrCache->ACache=T2;
\r
835 if(pCtrCache->BCache!=T1) // Hold register
\r
837 StoreByte(Ptr0, TCP, (0x11+channel));
\r
838 StoreByte(Ptr0, TDP, T1);
\r
839 StoreByte(Ptr0, TDP, ((T1)>>8));
\r
840 pCtrCache->BCache=T1;
\r
843 if (pCtrCache->Mode != PWM || Prescaler != pCtrCache->CTC.CountSource)
\r
845 *(int *)&pCtrCache->CTC = 0xB62; // 000 0 1011 | 0 1 1 0 0 010
\r
846 pCtrCache->CTC.CountSource = Prescaler;
\r
847 if(Prescaler>0xF) pCtrCache->CTC.CountSourceEx = 1;
\r
849 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
850 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
851 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
853 //I am too lazy to set aux mode register
\r
854 //StoreByte(Ptr0, TCP, AUX_TABLE[channel])
\r
855 //StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>16);
\r
856 //StoreByte(Ptr0, TDP, 0);
\r
858 StoreByte(Ptr0, TCP, 0xE0|(channel+1)); // clear toggle out
\r
859 StoreByte(Ptr0, TCP, 0x60 | (1<<channel)); // load & arm the selected counter
\r
861 pCtrCache->Mode = PWM;
\r
868 static HUDAQSTATUS MF614CtrReset(const DeviceRecord *DevRecord, unsigned channel)
\r
871 Counter614 *pCtrCache;
\r
873 if (channel>=CTR_CHANNELS) return HUDAQBADARG;
\r
874 pCtrCache = &((MF614_Private *)DevRecord->DrvRes.DriverData)->counter[channel];
\r
876 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
878 if (pCtrCache->Mode != Counting)
\r
880 StoreByte(Ptr0, TCP, 0xC0| (1<<channel)); // disarm
\r
882 if(pCtrCache->ACache!=0) // store 0 into A register
\r
884 StoreByte(Ptr0, TCP, (0x9+channel)); // Load Register
\r
885 StoreByte(Ptr0, TDP, 0);
\r
886 StoreByte(Ptr0, TDP, 0);
\r
887 pCtrCache->ACache=0;
\r
890 *(int *)&pCtrCache->CTC = 0x12A; // 000 0 0001 | 0 0 1 0 1 010
\r
891 pCtrCache->CTC.CountSource = channel+1;
\r
893 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
894 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
895 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
897 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
898 pCtrCache->Mode = Counting;
\r
901 StoreByte(Ptr0, TCP, 0x40 | (1<<channel)); // load the selected counter, A register contains zero
\r
903 return HUDAQSUCCESS;
\r
907 static double MF614CtrGetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
910 Counter614 *pCtrCache;
\r
912 if (channel>=CTR_CHANNELS) return WRONG_VALUE;
\r
913 pCtrCache = &((MF614_Private *)DevRecord->DrvRes.DriverData)->counter[channel];
\r
915 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
919 case HudaqCtrRESETONREAD:
\r
920 return(pCtrCache->ResetOnRead);
\r
922 case HudaqPwmCLOCKSOURCE:
\r
923 case HudaqCtrCLOCKSOURCE:
\r
924 if (pCtrCache->CTC.CountSource == channel+1)
\r
926 if (pCtrCache->CTC.EdgeMode==1)
\r
927 return HudaqCtrCLOCKINFALLING;
\r
929 return HudaqCtrCLOCKINRISING;
\r
931 switch(pCtrCache->CTC.CountSource)
\r
933 case 0: if (pCtrCache->CTC.EdgeMode==1)
\r
934 return HudaqCtrCLOCKPREVFALLING;
\r
936 return HudaqCtrCLOCKPREVRISING;
\r
938 case 11: return HudaqCtrCLOCK20MHz; //1011-F1
\r
939 case 12: return HudaqCtrCLOCK2MHz; //1100-F2
\r
940 case 13: return HudaqCtrCLOCK200kHz; //1101-F3
\r
941 case 14: return HudaqCtrCLOCK20kHz; //1110-F4
\r
942 case 15: return HudaqCtrCLOCK2kHz; //1111-F5
\r
944 return -1; /*Unknown mode*/
\r
946 case HudaqPwmGATESOURCE:
\r
947 case HudaqCtrGATESOURCE:
\r
948 if (pCtrCache->CTC.GateMode==0) return HudaqCtrGATEHIGH;
\r
949 return HudaqCtrGATEINPUT;
\r
951 case HudaqPwmGATEPOLARITY:
\r
952 case HudaqCtrGATEPOLARITY:
\r
953 switch (pCtrCache->CTC.GateControl)
\r
956 case 5: return 0; // 0 - low level gate disables counting;
\r
957 case 4: return 1; // 1 - high level gate disables counting.
\r
958 default: return 0; // ??? Hudaqlib does not support these modes.
\r
961 case HudaqCtrDIRECTION:
\r
962 return pCtrCache->CTC.Direction;
\r
964 case HudaqCtrLOADTOGGLE:
\r
965 return pCtrCache->CTC.ReloadMode;
\r
967 case HudaqCtrREPETITION:
\r
968 return pCtrCache->CTC.RepeatMode;
\r
970 case HudaqPwmOUTPUTCONTROL:
\r
971 case HudaqCtrOUTPUTCONTROL:
\r
972 return HudaqCtrOUTPUTNORMAL;
\r
974 case HudaqCtrTRIGSOURCE:
\r
975 return HudaqCtrTRIGDISABLE;
\r
977 case HudaqPwmFILTER:
\r
978 case HudaqCtrFILTER:
\r
981 case HudaqPwmNUMCHANNELS:
\r
982 case HudaqCtrNUMCHANNELS:
\r
983 return CTR_CHANNELS;
\r
986 return WRONG_VALUE;
\r
990 static HUDAQSTATUS MF614CtrSetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
993 Counter614 *pCtrCache;
\r
994 Counter614Control ShadCTC;
\r
996 if (channel>=CTR_CHANNELS) return HUDAQBADARG;
\r
997 pCtrCache = &((MF614_Private *)DevRecord->DrvRes.DriverData)->counter[channel];
\r
999 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
1001 if (pCtrCache->Mode != Counting)
\r
1002 MF614CtrReset(DevRecord,channel); // Enforce switching into a counter mode
\r
1006 case HudaqCtrRESETONREAD:
\r
1007 pCtrCache->ResetOnRead = (int)value;
\r
1008 return HUDAQSUCCESS;
\r
1010 case HudaqPwmCLOCKSOURCE:
\r
1011 case HudaqCtrCLOCKSOURCE:
\r
1012 ShadCTC = pCtrCache->CTC;
\r
1013 switch((int)value)
\r
1015 case HudaqCtrCLOCKINRISING:
\r
1016 ShadCTC.EdgeMode = 0;
\r
1017 ShadCTC.CountSource = channel+1;
\r
1019 case HudaqCtrCLOCKINFALLING:
\r
1020 ShadCTC.EdgeMode = 1;
\r
1021 ShadCTC.CountSource = channel+1;
\r
1023 case HudaqCtrCLOCKPREVRISING:
\r
1024 ShadCTC.EdgeMode = 0;
\r
1025 ShadCTC.CountSource = 0;
\r
1027 case HudaqCtrCLOCKPREVFALLING:
\r
1028 ShadCTC.EdgeMode = 1;
\r
1029 ShadCTC.CountSource = 0;
\r
1031 case HudaqCtrCLOCK20MHz: //1011-F1
\r
1032 ShadCTC.CountSource=11;
\r
1034 case HudaqCtrCLOCK2MHz: //1100-F2
\r
1035 ShadCTC.CountSource=12;
\r
1037 case HudaqCtrCLOCK200kHz: //1101-F3
\r
1038 ShadCTC.CountSource=13;
\r
1040 case HudaqCtrCLOCK20kHz: //1110-F4
\r
1041 ShadCTC.CountSource=14;
\r
1043 case HudaqCtrCLOCK2kHz: //1111-F5
\r
1044 ShadCTC.CountSource=15;
\r
1048 return HUDAQFAILURE;
\r
1051 if(*(int *)&pCtrCache->CTC != *(int *)&ShadCTC)
\r
1053 StoreByte(Ptr0, TCP, 0xC0| (1<<channel)); // disarm
\r
1055 *(int *)&pCtrCache->CTC = *(int *)&ShadCTC;
\r
1056 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
1057 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
1058 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
1060 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
1062 return HUDAQSUCCESS;
\r
1064 case HudaqCtrOUTPUTCONTROL:
\r
1065 if( (int)value == HudaqCtrOUTPUTNORMAL) return HUDAQSUCCESS;
\r
1066 return HUDAQBADARG;
\r
1068 case HudaqPwmGATEPOLARITY:
\r
1069 case HudaqCtrGATEPOLARITY:
\r
1070 switch ((int)value)
\r
1072 case 0: if (pCtrCache->CTC.GateMode==0 || pCtrCache->CTC.GateMode==5) return HUDAQSUCCESS;
\r
1073 pCtrCache->CTC.GateMode=5;
\r
1075 case 1: if (pCtrCache->CTC.GateMode==4) return HUDAQSUCCESS;
\r
1076 pCtrCache->CTC.GateMode=4;
\r
1078 default: return HUDAQBADARG;
\r
1080 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
1081 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
1082 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
1084 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
1085 return HUDAQSUCCESS;
\r
1087 case HudaqPwmGATESOURCE:
\r
1088 case HudaqCtrGATESOURCE:
\r
1089 switch ((int)value)
\r
1091 case HudaqCtrGATEHIGH:
\r
1092 if (pCtrCache->CTC.GateMode==0) return HUDAQSUCCESS;
\r
1093 pCtrCache->CTC.GateMode=0;
\r
1095 case HudaqCtrGATEINPUT:
\r
1096 if (pCtrCache->CTC.GateControl==0)
\r
1097 pCtrCache->CTC.GateControl=5; //activate low level gate N
\r
1100 if (pCtrCache->CTC.GateMode==1) return HUDAQSUCCESS; //gate is now active
\r
1102 pCtrCache->CTC.GateMode=1;
\r
1105 return HUDAQBADARG;
\r
1108 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
1109 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
1110 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
1112 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
1113 return HUDAQSUCCESS;
\r
1115 case HudaqCtrDIRECTION:
\r
1116 if(value>=2) return HUDAQBADARG;
\r
1117 if (pCtrCache->CTC.Direction!=(int)value);
\r
1119 pCtrCache->CTC.Direction = (int)value;
\r
1120 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
1121 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
1122 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
1124 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
1126 return HUDAQSUCCESS;
\r
1128 case HudaqCtrLOADTOGGLE:
\r
1129 if(value>=2) return HUDAQBADARG;
\r
1130 if (pCtrCache->CTC.ReloadMode!=(int)value);
\r
1132 pCtrCache->CTC.ReloadMode=(int)value;
\r
1133 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
1134 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
1135 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
1137 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
1139 return HUDAQSUCCESS;
\r
1141 case HudaqCtrREPETITION:
\r
1142 if(value>=2) return HUDAQBADARG;
\r
1143 if (pCtrCache->CTC.RepeatMode!=(int)value);
\r
1145 pCtrCache->CTC.RepeatMode = (int)value;
\r
1146 StoreByte(Ptr0, TCP, channel+1); // select counter mode register
\r
1147 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC);
\r
1148 StoreByte(Ptr0, TDP, *(int *)&pCtrCache->CTC>>8);
\r
1150 StoreByte(Ptr0, TCP, 0x20 |(1<<channel)); // arm
\r
1152 return HUDAQSUCCESS;
\r
1154 case HudaqCtrTRIGSOURCE:
\r
1155 if ((int)value == HudaqCtrTRIGDISABLE) return HUDAQSUCCESS;
\r
1156 return HUDAQBADARG;
\r
1158 case HudaqPwmFILTER:
\r
1159 case HudaqCtrFILTER:
\r
1160 if ((int)value == 0) return HUDAQSUCCESS;
\r
1161 return HUDAQBADARG;
\r
1164 return HUDAQNOTSUPPORTED;
\r
1168 static int MF614CtrRead(const DeviceRecord *DevRecord, unsigned channel)
\r
1171 Counter614 *pCtrCache;
\r
1173 if (channel>=CTR_CHANNELS) return 0;
\r
1174 pCtrCache = &((MF614_Private *)DevRecord->DrvRes.DriverData)->counter[channel];
\r
1176 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
1178 if (pCtrCache->Mode != Counting)
\r
1180 MF614CtrReset(DevRecord,channel);
\r
1184 StoreByte(Ptr0, TCP, 0xA0|(1<<channel)); // save counter
\r
1185 StoreByte(Ptr0, TCP, 0x11+channel); // read value hold
\r
1186 pCtrCache->BCache = GetPackedWord(Ptr0,TDP); // Hold register is influenced
\r
1188 if(pCtrCache->ResetOnRead)
\r
1190 StoreByte(Ptr0, TCP, 0x40 | (1<<channel)); // load the selected counter, A register contains zero
\r
1193 return(pCtrCache->BCache);
\r
1197 /** Handle stepping motor through MF614. */
\r
1198 static HUDAQSTATUS MF614StepWrite(const DeviceRecord *DevRecord, unsigned channel, int position)
\r
1201 int maskC, maskF, status, count;
\r
1206 MF614_Private *Cache;
\r
1207 Stepper614 *CacheStep;
\r
1209 double NewFrequency;
\r
1211 NewTime=timeGetTime();
\r
1213 if (channel>=STEP_CHANNELS) return HUDAQBADARG;
\r
1214 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
1216 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
1217 CacheStep = &(Cache->step[channel]);
\r
1219 maskF=1<<(2*channel);
\r
1222 if (Cache->counter[2*channel].Mode != StepperPWM)
\r
1223 { //check for the first time initialization of slave counter
\r
1224 Cache->counter[2*channel].Mode = StepperPWM;
\r
1226 StoreByte(Ptr0, TCP, 2*channel+1); // select counter 1 mode register
\r
1227 StoreByte(Ptr0, TDP, 0x22);
\r
1228 StoreByte(Ptr0, TDP, 0x8C); // mode E, F2 source (2MHz), toggle output
\r
1230 N = (int) (MASTERFREQUENCY/(2*10*CacheStep->Fmin));
\r
1231 if(Cache->counter[2*channel].ACache!=N)
\r
1233 StoreByte(Ptr0, TCP, (0x9+2*channel)); //Load register
\r
1234 StoreByte(Ptr0, TDP, N);
\r
1235 StoreByte(Ptr0, TDP, N>>8);
\r
1236 Cache->counter[2*channel].ACache = N;
\r
1239 StoreByte(Ptr0, TCP, 0xE1+2*channel); // reset output
\r
1244 if (Cache->counter[2*channel+1].Mode != StepperMaster)
\r
1245 { //check for the first time initialization of master counter
\r
1246 Cache->counter[2*channel+1].Mode = StepperMaster;
\r
1248 StoreByte(Ptr0, TCP, 2*channel+2); // select counter 2 mode register
\r
1249 StoreByte(Ptr0, TDP, 0x02);
\r
1250 StoreByte(Ptr0, TDP, 0x12+2*channel); // mode A, output TC toggle
\r
1251 // Internal TC(n-1) do not work, I suspect internal hazards.
\r
1253 if(Cache->counter[2*channel+1].ACache!=1)
\r
1255 StoreByte(Ptr0, TCP, (0x9+2*channel+1)); //Load register
\r
1256 StoreByte(Ptr0, TDP, 1);
\r
1257 StoreByte(Ptr0, TDP, 0);
\r
1258 Cache->counter[2*channel+1].ACache = 1;
\r
1261 StoreByte(Ptr0, TCP, 0xE1 + 2*channel+1); // reset output
\r
1266 if (State!=0) //check for the first time initialization
\r
1268 CacheStep->Direction=0;
\r
1269 CacheStep->LastPosition=0;
\r
1270 CacheStep->ChainedPosition=0;
\r
1271 CacheStep->Frequency=0;
\r
1273 StoreByte(Ptr0, TCP, 0x4F); // load counters
\r
1274 StoreByte(Ptr0, TCP, 0x25); // arm counters
\r
1277 //********* check for end switches **********
\r
1278 digin = GetByte(DevRecord->DrvRes.Resources.MemResources[1].Base,DIN);
\r
1279 if( ((digin & (1<<(2*channel)))==0 && (CacheStep->Direction>0)) ||
\r
1280 ( ((digin & (1<<(2*channel+1)))==0) && (CacheStep->Direction<0)) ) // end switch
\r
1282 StoreByte(Ptr0, TCP, 0xA0|maskF|maskC); // save both counters
\r
1283 StoreByte(Ptr0, TCP, 0x11+2*channel+1); // read count hold
\r
1284 NewSteps = GetPackedWord(Ptr0,TDP);
\r
1285 Cache->counter[2*channel+1].BCache = NewSteps++;
\r
1287 StoreByte(Ptr0, TCP, 0x1F); // read status register
\r
1288 status=GetPackedWord(Ptr0,TDP);
\r
1289 if((status & (0x04<<2*channel)) == 0) // stopped?
\r
1292 StoreByte(Ptr0, TCP, 0xE2+2*channel); // reset output
\r
1293 StoreByte(Ptr0, TCP, 0xC0|maskC); // disarm
\r
1295 CacheStep->LastPosition -= NewSteps * Cache->step[channel].Direction; //actualize real position
\r
1297 CacheStep->Frequency = 0;
\r
1298 CacheStep->Direction = 0;
\r
1299 CacheStep->TimeStamp = NewTime;
\r
1300 return HUDAQSUCCESS;
\r
1303 //***** actualize step count ******
\r
1304 if (CacheStep->LastPosition!=position)
\r
1305 { //amount of steps to do
\r
1306 if( (State&3)!=0 ) //the first time initialization
\r
1310 else // counter already runs
\r
1312 StoreByte(Ptr0, TCP, 0x1F);
\r
1313 status=GetPackedWord(Ptr0,TDP);
\r
1314 if((status & (0x04<<2*channel)) ==0) // stopped
\r
1318 StoreByte(Ptr0, TCP, 0xA0|maskF|maskC); // save both counters
\r
1319 StoreByte(Ptr0, TCP, 0x11+2*channel); // read frequency hold
\r
1320 Cache->counter[2*channel].BCache =
\r
1321 status = count = GetPackedWord(Ptr0,TDP);
\r
1322 while(count < 40) // wait until safe to change pulse count
\r
1324 StoreByte(Ptr0, TCP, 0xA0|maskF|maskC); // save both counters
\r
1325 StoreByte(Ptr0, TCP, 0x11+2*channel); // read frequency hold
\r
1326 Cache->counter[2*channel].BCache =
\r
1327 count = GetPackedWord(Ptr0,TDP);
\r
1330 Cache->counter[2*channel+1].BCache = 0;
\r
1332 goto UnexpectedStop; //unexpected stop of frequency counter
\r
1336 StoreByte(Ptr0, TCP, 0x11+2*channel+1); // read count hold
\r
1337 NewSteps = GetPackedWord(Ptr0,TDP);
\r
1338 Cache->counter[2*channel+1].BCache = NewSteps++;
\r
1345 CacheStep->Direction=0; //motor is already stopped.
\r
1346 CacheStep->Frequency=0;
\r
1349 // CurrentPosition = LastPosition - |x|*Direction
\r
1350 // CurrentPosition = NewPosition - NewSteps
\r
1351 // ===> NewSteps = NewPosition - LastPosition + |x|*Direction
\r
1352 NewSteps = position - CacheStep->LastPosition + NewSteps*CacheStep->Direction;
\r
1355 if (NewSteps>2) //It is impossible to realise less than 3 steps???
\r
1357 if (CacheStep->Direction==-1)
\r
1358 { //direction has been reversed, calculate a false target
\r
1359 //NewSteps = Fmin(channel)*Fmin(channel)+Frequency[channel]*Frequency[channel])/(2*Acc(channel)) - 1;
\r
1360 // \todo Study behaviour of this feature. It lets stepper to finish in oposite direction.
\r
1361 goto TargetIsFixed;
\r
1363 CacheStep->LastPosition = position; //actualize position cache
\r
1364 CacheStep->Direction=1;
\r
1365 if( NewSteps > 0xFFFF )
\r
1367 CacheStep->LastPosition -= NewSteps-0xFFFF;
\r
1368 NewSteps = 0xFFFF;
\r
1370 StoreByte(Ptr0, TCP, 0xC0|maskC); // disarm count
\r
1371 StoreByte(Ptr0, TCP, 0x0A+2*channel); // count load register
\r
1372 StoreByte(Ptr0, TDP, NewSteps-1);
\r
1373 StoreByte(Ptr0, TDP, (NewSteps-1)>>8);
\r
1374 State |= 4; //reload & run counter
\r
1377 if (NewSteps<-2) //It is impossible to realise less than 3 steps???
\r
1379 if (CacheStep->Direction==1) //if (Frequency[channel]>Fmin[channel])
\r
1380 { //direction has been reversed, calculate a false target
\r
1381 //NewSteps = Fmin(channel)*Fmin(channel)+Frequency[channel]*Frequency[channel])/(2*Acc(channel)) - 1;
\r
1382 // \todo Study behaviour of this feature. It lets stepper to finish in oposite direction.
\r
1383 goto TargetIsFixed;
\r
1385 CacheStep->LastPosition = position; //actualize position cache
\r
1386 CacheStep->Direction = -1;
\r
1387 NewSteps = -NewSteps;
\r
1388 if( NewSteps > 0xFFFF )
\r
1390 CacheStep->LastPosition += NewSteps-0xFFFF;
\r
1391 NewSteps = 0xFFFF;
\r
1393 StoreByte(Ptr0, TCP, 0xC0|maskC); // disarm count
\r
1394 StoreByte(Ptr0, TCP, 0x0A+2*channel); // count load register
\r
1395 StoreByte(Ptr0, TDP, NewSteps-1);
\r
1396 StoreByte(Ptr0, TDP, (NewSteps-1)>>8);
\r
1397 State |= 4; //reload & run counter
\r
1402 //CacheStep->LastPosition; does not need to be actualised
\r
1403 CacheStep->Direction=0;
\r
1408 CacheStep->ChainedPosition = position; // chained position makes sense only if direction is reversed
\r
1411 //************ set frequency - (lastreq contains amount of remainning steps) ***********
\r
1412 if (CacheStep->Acc<=0)
\r
1413 CacheStep->Frequency=CacheStep->Fmax; // full acceleration when acceleration is negative
\r
1416 if (CacheStep->Frequency==0)
\r
1418 CacheStep->Frequency = CacheStep->Fmin;
\r
1422 if ((CacheStep->Acc > 0) && (NewSteps > (CacheStep->Fmin*CacheStep->Fmin+CacheStep->Frequency*CacheStep->Frequency)/(2*CacheStep->Acc)))
\r
1423 NewFrequency = CacheStep->Frequency + CacheStep->Acc*abs((long)NewTime-(long)CacheStep->TimeStamp)/1000.0;
\r
1425 NewFrequency = CacheStep->Frequency + CacheStep->Acc*abs((long)NewTime-(long)CacheStep->TimeStamp)/1000.0;
\r
1427 if (NewFrequency > CacheStep->Fmax) NewFrequency = CacheStep->Fmax;
\r
1428 if (NewFrequency < CacheStep->Fmin) NewFrequency = CacheStep->Fmin;
\r
1430 if (NewFrequency>(CacheStep->Frequency+MAXIMAL_STEP_INCREMENT)) //maximal increment threshold
\r
1432 CacheStep->Frequency += MAXIMAL_STEP_INCREMENT;
\r
1435 CacheStep->Frequency = NewFrequency;
\r
1439 N = (__int32)(MASTERFREQUENCY / (10*CacheStep->Frequency)); //set a real frequency
\r
1441 if(N>65535) N=65535;
\r
1443 if(Cache->counter[2*channel].ACache != N)
\r
1445 StoreByte(Ptr0, TCP, 0x09+2*channel);
\r
1446 StoreByte(Ptr0, TDP, N);
\r
1447 StoreByte(Ptr0, TDP, N>>8);
\r
1448 Cache->counter[2*channel].ACache = N;
\r
1451 //write stepper direction to digital output
\r
1452 if(CacheStep->Direction!=0) // only when stepper is expected to be running
\r
1453 MF614DOWriteBit(DevRecord,0,channel,CacheStep->Direction>0);
\r
1456 if ( (State&4) != 0 )
\r
1458 StoreByte(Ptr0, TCP, 0x60|maskC); // load and arm count
\r
1459 StoreByte(Ptr0, TCP, 0xEA+2*channel); // set toggle out -> start
\r
1462 CacheStep->TimeStamp = NewTime; //refresh timestamp
\r
1463 return HUDAQSUCCESS;
\r
1468 static HUDAQSTATUS MF614StepSetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
1470 MF614_Private *Cache;
\r
1472 int status, RemainingSteps;
\r
1474 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
1475 if (channel>=STEP_CHANNELS) return HUDAQBADARG;
\r
1477 if (Cache->counter[2*channel].Mode != StepperPWM || Cache->counter[2*channel+1].Mode != StepperMaster)
\r
1478 MF614StepWrite(DevRecord,channel,0); //call stepper initialization if it is not called before
\r
1480 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
1484 case HudaqStepFMIN:
\r
1485 Cache->step[channel].Fmin = value;
\r
1486 return HUDAQSUCCESS;
\r
1487 case HudaqStepFMAX:
\r
1488 Cache->step[channel].Fmax = value;
\r
1489 return HUDAQSUCCESS;
\r
1490 case HudaqStepACCELERATION:
\r
1491 Cache->step[channel].Acc = value;
\r
1492 return HUDAQSUCCESS;
\r
1494 case HudaqStepCURRENTPOSITION:
\r
1495 StoreByte(Ptr0, TCP, 0xA0|(2<<(2*channel))); // save counter
\r
1496 StoreByte(Ptr0, TCP, 0x11+2*channel+1); // read count hold
\r
1497 RemainingSteps = GetPackedWord(Ptr0,TDP);
\r
1498 Cache->counter[2*channel+1].BCache = RemainingSteps++;
\r
1500 StoreByte(Ptr0, TCP, 0x1F); // read status register
\r
1501 status = GetPackedWord(Ptr0,TDP);
\r
1502 if((status & (0x04<<2*channel)) == 0) // stopped?
\r
1503 RemainingSteps = 0;
\r
1505 // CurrentPosition = LastPosition - Direction * AmountOfSteps
\r
1506 // NewPosition = NewLastPosition - Direction * AmountOfSteps
\r
1507 // ==> NewLastPosition = NewPosition + Direction * AmountOfSteps
\r
1508 Cache->step[channel].LastPosition =
\r
1509 (int)value + Cache->step[channel].Direction * RemainingSteps;
\r
1510 return HUDAQSUCCESS;
\r
1514 return HUDAQNOTSUPPORTED;
\r
1518 static double MF614StepGetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
1520 MF614_Private *Cache;
\r
1522 int status, RetVal;
\r
1524 Cache = (MF614_Private *)DevRecord->DrvRes.DriverData;
\r
1525 if (channel>=2) return WRONG_VALUE;
\r
1527 if (Cache->counter[2*channel].Mode != StepperPWM || Cache->counter[2*channel+1].Mode != StepperMaster)
\r
1528 MF614StepWrite(DevRecord,channel,0); //call stepper initialization if it is not called before
\r
1530 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
1534 case HudaqStepFMIN:
\r
1535 return(Cache->step[channel].Fmin);
\r
1536 case HudaqStepFMAX:
\r
1537 return(Cache->step[channel].Fmax);
\r
1538 case HudaqStepACCELERATION:
\r
1539 return(Cache->step[channel].Acc);
\r
1540 case HudaqStepCURRENTPOSITION: // CurrentPosition = LastPosition - Direction * AmountOfSteps
\r
1541 StoreByte(Ptr0, TCP, 0xA0|(2<<(2*channel))); // save counter
\r
1542 StoreByte(Ptr0, TCP, 0x11+2*channel+1); // read count hold
\r
1543 RetVal = GetPackedWord(Ptr0,TDP);
\r
1544 Cache->counter[2*channel+1].BCache = RetVal++;
\r
1546 StoreByte(Ptr0, TCP, 0x1F); // read status register
\r
1547 status=GetPackedWord(Ptr0,TDP);
\r
1548 if((status & (0x04<<2*channel)) == 0) // stopped?
\r
1551 return(Cache->step[channel].LastPosition -
\r
1552 Cache->step[channel].Direction * RetVal);
\r
1554 case HudaqStepREMAININGSTEPS:
\r
1555 StoreByte(Ptr0, TCP, 0xA0|(2<<(2*channel))); // save counter
\r
1556 StoreByte(Ptr0, TCP, 0x11+2*channel+1); // read count hold
\r
1557 RetVal = GetPackedWord(Ptr0,TDP);
\r
1558 Cache->counter[2*channel+1].BCache = RetVal++;
\r
1560 StoreByte(Ptr0, TCP, 0x1F); // read status register
\r
1561 status=GetPackedWord(Ptr0,TDP);
\r
1562 if((status & (0x04<<2*channel)) == 0) // stopped?
\r
1566 case HudaqStepTARGETPOSITION:
\r
1567 return(Cache->step[channel].ChainedPosition);
\r
1568 case HudaqStepNUMCHANNELS:
\r
1569 return STEP_CHANNELS;
\r
1572 return WRONG_VALUE;
\r
1576 /****************************************************************
\r
1578 * GET/SET PARAMETERS *
\r
1580 ****************************************************************/
\r
1582 static HUDAQSTATUS MF614SetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
1584 switch(param & HudaqSubsystemMASK)
\r
1588 case HudaqAI: return MF614AISetParameter(DevRecord,channel,param,value);
\r
1589 case HudaqAO: return MF614AOSetParameter(DevRecord,channel,param,value);
\r
1590 case HudaqEnc: return MF614EncSetParameter(DevRecord,channel,param,value);
\r
1591 case HudaqCtr: return MF614CtrSetParameter(DevRecord,channel,param,value);
\r
1592 case HudaqStep:return MF614StepSetParameter(DevRecord,channel,param,value);
\r
1594 return HUDAQNOTSUPPORTED;
\r
1598 static HUDAQSTATUS AD612SetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param, double value)
\r
1600 switch(param & HudaqSubsystemMASK)
\r
1604 case HudaqAI: return MF614AISetParameter(DevRecord,channel,param,value);
\r
1605 case HudaqAO: return MF614AOSetParameter(DevRecord,channel,param,value);
\r
1610 return HUDAQNOTSUPPORTED;
\r
1614 static double MF614GetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
1616 switch(param & HudaqSubsystemMASK)
\r
1619 case HudaqDO: return MF614DIOGetParameter(channel,param);
\r
1620 case HudaqAI: return MF614AIGetParameter(DevRecord,channel,param);
\r
1621 case HudaqAO: return MF614AOGetParameter(DevRecord,channel,param);
\r
1622 case HudaqEnc: return MF614EncGetParameter(DevRecord,channel,param);
\r
1623 case HudaqCtr: return MF614CtrGetParameter(DevRecord,channel,param);
\r
1624 case HudaqStep:return MF614StepGetParameter(DevRecord,channel,param);
\r
1626 return WRONG_VALUE;
\r
1630 static double AD612GetParameter(const DeviceRecord *DevRecord, unsigned channel, HudaqParameter param)
\r
1632 switch(param & HudaqSubsystemMASK)
\r
1635 case HudaqDO: return MF614DIOGetParameter(channel,param);
\r
1636 case HudaqAI: return MF614AIGetParameter(DevRecord,channel,param);
\r
1637 case HudaqAO: return MF614AOGetParameter(DevRecord,channel,param);
\r
1642 return WRONG_VALUE;
\r
1646 static const HudaqRange *MF614QueryRange(const DeviceRecord *DevRecord, HudaqSubsystem S, unsigned item)
\r
1648 switch(S & HudaqSubsystemMASK)
\r
1650 case HudaqAI: if(item>=4) return NULL;
\r
1651 return &MF614RangeAIO[item];
\r
1652 case HudaqAO: if(item>=1) return NULL;
\r
1653 return &MF614RangeAIO[0];
\r
1661 /****************************************************************
\r
1663 * INITIALIZATION *
\r
1665 ****************************************************************/
\r
1667 /** Initialize subset of hardware that is supported by both MF614 & AD612. */
\r
1668 static void InitAiAoDiDo(size_t Ptr0, size_t Ptr1, MF614_Private *Cache)
\r
1672 /* Initialization of AD's */
\r
1673 for(i=0;i<AD_CHANNELS;i++)
\r
1675 Cache->AIOptions[i].Range=1; // 10V
\r
1676 Cache->AIOptions[i].Bipolar=1; // + -
\r
1677 Cache->AIOptions[i].Raw=0;
\r
1680 /* Initialization of DA's */
\r
1681 for(i=0;i<DA_CHANNELS;i++)
\r
1683 Cache->DAOptions[i].Raw = 0;
\r
1684 Cache->DAOptions[i].LoCache = 0;
\r
1685 Cache->DAOptions[i].HiCache = 0x8;
\r
1687 StoreByte(Ptr0, DA0LO + 2*i, 0);
\r
1688 StoreByte(Ptr0, DA0HI + 2*i, 0x8);
\r
1690 i = GetByte(Ptr0, DALATCHEN); // update the output
\r
1692 /* Initialization of digital outputs */
\r
1693 Cache->DoutCache = 0;
\r
1694 StoreByte(Ptr0, DOUT, 0);
\r
1698 /** Internal initialization function that should be called from HudaqOpenDevice
\r
1699 * \todo Exclusive access is only partially implemented. */
\r
1700 static int Init614(DeviceRecord *DevRecord, int IniOptions)
\r
1702 size_t Ptr0, Ptr1, Ptr2;
\r
1703 MF614_Private *Cache;
\r
1706 if (DevRecord==NULL) return -1;
\r
1708 if (DevRecord->DrvRes.Resources.NumMemResources<2) return -2; //insufficient amount of resources
\r
1709 Cache = DevRecord->DrvRes.DriverData;
\r
1710 if (Cache==NULL) return -3;
\r
1711 if (DevRecord->DrvRes.DriverDataSize<sizeof(MF614_Private)) return -4;
\r
1713 DevRecord->pCT = &CT614;
\r
1715 if ((IniOptions & HudaqOpenNOINIT)==0)
\r
1716 { //initialize hardware only if no other application is running
\r
1717 Ptr0 = DevRecord->DrvRes.Resources.MemResources[1].Base;
\r
1718 Ptr1 = DevRecord->DrvRes.Resources.MemResources[1].Base + 0x400;
\r
1719 Ptr2 = DevRecord->DrvRes.Resources.MemResources[0].Base;
\r
1721 /* Initialization of AD's, DA's, DI and DO. */
\r
1722 InitAiAoDiDo(Ptr0, Ptr1, Cache);
\r
1724 /* initialization IRC */
\r
1725 for (i=0; i<ENC_CHANNELS; i++)
\r
1727 StoreByte(Ptr0,IRC0CMDWR+2*i,0x03); // reset BP & CNTR 0 00 00 01 1
\r
1728 StoreByte(Ptr0, IRC0DATAWR+2*i, 9); // PR0=9; program filter to 250 kHz (20MHz/9)
\r
1729 StoreByte(Ptr0, IRC0CMDWR+2*i, 0x18); // PR0 -> PSC
\r
1730 Cache->EncOptions[i].Filter = 9;
\r
1732 StoreByte(Ptr0,IRC0CMDWR+2*i,0x38); // CMR 0 01 11 0 0 0
\r
1733 Cache->EncOptions[i].Quadrature = 3; // ----------^
\r
1735 StoreByte(Ptr0,IRC0CMDWR+2*i,0x45); // IOR 0 10 00 1 0 1 (use gate)
\r
1736 Cache->EncOptions[i].Index = 0;
\r
1738 StoreByte(Ptr0,IRC0CMDWR+2*i,0x60); // IDR 0 11 00 0 0 0 (index pulse polarity)
\r
1739 Cache->EncOptions[i].Polarity = 0; // --------------^ ^
\r
1740 Cache->EncOptions[i].Level = 1; // ----------------|
\r
1742 Cache->EncOptions[i].ResetOnRead = 0;
\r
1745 /* Initialization of counters */
\r
1746 /* set counters mode */
\r
1747 StoreByte(Ptr0,TCP,0xFF); // master reset
\r
1748 StoreByte(Ptr0,TCP,0x17); // master mode register
\r
1749 StoreByte(Ptr0,TDP,0x00);
\r
1750 StoreByte(Ptr0,TDP,0x80); // BCD prescaler
\r
1751 for(i=0;i<CTR_CHANNELS;i++)
\r
1753 Cache->counter[i].Mode = Unspecified;
\r
1754 Cache->counter[i].ACache = 0;
\r
1755 Cache->counter[i].BCache = 0;
\r
1758 /* Initialization of stepper motors courtesy */
\r
1759 for(i=0;i<STEP_CHANNELS;i++)
\r
1761 Cache->step[i].LastPosition = 0;
\r
1762 Cache->step[i].ChainedPosition = 0;
\r
1763 Cache->step[i].Direction = 0;
\r
1765 Cache->step[i].Fmin = 1000;
\r
1766 Cache->step[i].Fmax = 5000;
\r
1767 Cache->step[i].Acc = 1000;
\r
1772 return 1; //success
\r
1776 /** Internal cleanup procedure for MF614 & AD612 */
\r
1777 static void Done614(DeviceRecord *DevRecord)
\r
1779 if (DevRecord==NULL) return;
\r
1781 DevRecord->pCT = &CtDummy;
\r
1785 const CallTable CT614 =
\r
1787 "MF614", 0x186C, 0x0614,
\r
1791 MF614SetParameter,
\r
1792 MF614GetParameter,
\r
1795 // INITIALIZE DI callers
\r
1797 GenericDIReadBit, //Generic implementation
\r
1798 GenericOneDIReadMultiple,
\r
1799 // INITIALIZE DO callers
\r
1802 MF614DOWriteMultipleBits,
\r
1803 GenericDOWriteMultiple,
\r
1804 // INITIALIZE AI callers
\r
1806 GenericAIReadMultiple,
\r
1807 // INITIALIZE AO callers
\r
1809 MF614AOWriteMultiple,
\r
1810 // INITIALIZE Enc callers
\r
1813 // INITIALIZE Ctr callers
\r
1816 // INITIALIZE PWM callers
\r
1820 // INITIALIZE Step callers
\r
1825 /* Initialization procedure for AD612 card. */
\r
1826 static int Init612(DeviceRecord *DevRecord, int IniOptions)
\r
1828 MF614_Private *Cache;
\r
1830 if (DevRecord==NULL) return -1;
\r
1832 if (DevRecord->DrvRes.Resources.NumMemResources<2) return -2; //insufficient amount of resources
\r
1833 Cache = DevRecord->DrvRes.DriverData;
\r
1834 if (Cache==NULL) return -3;
\r
1835 if (DevRecord->DrvRes.DriverDataSize<sizeof(MF614_Private)) return -4;
\r
1837 DevRecord->pCT = &CT612;
\r
1839 if ((IniOptions & HudaqOpenNOINIT)==0)
\r
1840 { //initialize hardware only if no other application is running
\r
1841 /* Initialization of AD's, DA's, DI and DO. */
\r
1842 InitAiAoDiDo(DevRecord->DrvRes.Resources.MemResources[1].Base, //Ptr0
\r
1843 DevRecord->DrvRes.Resources.MemResources[1].Base + 0x400, //Ptr1
\r
1847 return 1; //success
\r
1851 const CallTable CT612 =
\r
1853 "AD612", 0x186C, 0x0612,
\r
1857 AD612SetParameter,
\r
1858 AD612GetParameter,
\r
1861 // INITIALIZE DI callers
\r
1863 GenericDIReadBit, //Generic implementation
\r
1864 GenericOneDIReadMultiple,
\r
1865 // INITIALIZE DO callers
\r
1868 MF614DOWriteMultipleBits,
\r
1869 GenericDOWriteMultiple,
\r
1870 // INITIALIZE AI callers
\r
1872 GenericAIReadMultiple,
\r
1873 // INITIALIZE AO callers
\r
1875 MF614AOWriteMultiple,
\r
1876 // INITIALIZE Enc callers
\r
1877 NULL, // Not available
\r
1879 // INITIALIZE Ctr callers
\r
1880 NULL, // Not available
\r
1882 // INITIALIZE PWM callers
\r
1883 NULL, // Not available
\r
1886 // INITIALIZE Step callers
\r
1887 NULL, // Not available
\r