1 /*
2  *  Hamlib Rotator backend - ARS parallel port backend
3  *  Copyright (c) 2010 by Stephane Fillod
4  *  This code is inspired by work from Pablo GARCIA - EA4TX
5  *
6  *
7  *   This library is free software; you can redistribute it and/or
8  *   modify it under the terms of the GNU Lesser General Public
9  *   License as published by the Free Software Foundation; either
10  *   version 2.1 of the License, or (at your option) any later version.
11  *
12  *   This library is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *   Lesser General Public License for more details.
16  *
17  *   You should have received a copy of the GNU Lesser General Public
18  *   License along with this library; if not, write to the Free Software
19  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include <stdlib.h>
28 #include <string.h>  /* String function definitions */
29 #include <unistd.h>  /* UNIX standard function definitions */
30 #ifdef HAVE_SYS_IOCTL_H
31 #include <sys/ioctl.h>
32 #endif
33 
34 #ifdef HAVE_PTHREAD
35 #include <pthread.h>
36 #endif
37 
38 #include "hamlib/rotator.h"
39 #include "parallel.h"
40 #include "misc.h"
41 #include "register.h"
42 
43 #include "ars.h"
44 
45 #define CTL_PIN_CS  PARPORT_CONTROL_AUTOFD /* pin14: Control Linefeed */
46 #define CTL_PIN_CLK PARPORT_CONTROL_STROBE /* pin01: Control /Strobe  */
47 #define STA_PIN_D0  PARPORT_STATUS_BUSY    /* pin11: Status  Busy     */
48 #define STA_PIN_D1  PARPORT_STATUS_ERROR   /* pin15: Status  /Error   */
49 
50 #define DTA_PIN02   0x01    /* Data0 */
51 #define DTA_PIN03   0x02    /* Data1 */
52 #define DTA_PIN04   0x04    /* Data2 */
53 #define DTA_PIN05   0x08    /* Data3 */
54 #define DTA_PIN06   0x10    /* Data4 */
55 #define DTA_PIN07   0x20    /* Data5 */
56 #define DTA_PIN08   0x40    /* Data6 */
57 #define DTA_PIN09   0x80    /* Data7 */
58 
59 #define CTL_PIN16   PARPORT_CONTROL_INIT
60 #define CTL_PIN17   PARPORT_CONTROL_SELECT
61 
62 #define ARS_BRAKE_DELAY  (100*1000) /* usecs */
63 #define ARS_SETTLE_DELAY (500*1000) /* usecs */
64 #define PP_IO_PERIOD (25) /* usecs */
65 #define NUM_SAMPLES 3
66 
67 /* TODO: take into account ADC res (8 bits -> 1.4 deg at 360 deg)
68  * Rem: at 360 deg/mn, that's 6 deg/sec.
69  */
70 #define AZ_RANGE 3.
71 #define EL_RANGE 2.
72 
73 /* Check return value, with appropriate unlocking upon error.
74  * Assumes "rot" variable is current ROT pointer.
75  */
76 #define CHKPPRET(a) \
77     do { int _retval = a; if (_retval != RIG_OK) \
78         {par_unlock (&rot->state.rotport);return _retval; }} while(0)
79 
80 static int ars_init(ROT *rot);
81 static int ars_cleanup(ROT *rot);
82 static int ars_open(ROT *rot);
83 static int ars_close(ROT *rot);
84 static int ars_stop(ROT *rot);
85 static int ars_move(ROT *rot, int direction, int speed);
86 static int ars_set_position_sync(ROT *rot, azimuth_t az, elevation_t el);
87 static int ars_set_position(ROT *rot, azimuth_t az, elevation_t el);
88 static int ars_get_position(ROT *rot, azimuth_t *az, elevation_t *el);
89 
90 #ifdef HAVE_PTHREAD
91 static void *handle_set_position(void *);
92 #endif
93 
94 /* ************************************************************************* */
95 
ars_clear_ctrl_pin(ROT * rot,unsigned char pin)96 static int ars_clear_ctrl_pin(ROT *rot, unsigned char pin)
97 {
98     hamlib_port_t *pport = &rot->state.rotport;
99     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
100 
101     priv->pp_control &= ~pin;
102 
103     return par_write_control(pport, priv->pp_control);
104 }
105 
ars_set_ctrl_pin(ROT * rot,unsigned char pin)106 static int ars_set_ctrl_pin(ROT *rot, unsigned char pin)
107 {
108     hamlib_port_t *pport = &rot->state.rotport;
109     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
110 
111     priv->pp_control |= pin;
112 
113     return par_write_control(pport, priv->pp_control);
114 }
115 
ars_clear_data_pin(ROT * rot,unsigned char pin)116 static int ars_clear_data_pin(ROT *rot, unsigned char pin)
117 {
118     hamlib_port_t *pport = &rot->state.rotport;
119     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
120 
121     priv->pp_data &= ~pin;
122 
123     return par_write_data(pport, priv->pp_data);
124 }
125 
ars_set_data_pin(ROT * rot,unsigned char pin)126 static int ars_set_data_pin(ROT *rot, unsigned char pin)
127 {
128     hamlib_port_t *pport = &rot->state.rotport;
129     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
130 
131     priv->pp_data |= pin;
132 
133     return par_write_data(pport, priv->pp_data);
134 }
135 
ars_has_el(const ROT * rot)136 static int ars_has_el(const ROT *rot)
137 {
138     return (rot->caps->rot_type & ROT_FLAG_ELEVATION);
139 }
140 
141 /* ************************************************************************* */
142 
143 int
ars_init(ROT * rot)144 ars_init(ROT *rot)
145 {
146     struct ars_priv_data *priv;
147 
148     if (!rot)
149     {
150         return -RIG_EINVAL;
151     }
152 
153     rot->state.priv = (struct ars_priv_data *)malloc(sizeof(struct ars_priv_data));
154 
155     if (!rot->state.priv)
156     {
157         /* whoops! memory shortage! */
158         return -RIG_ENOMEM;
159     }
160 
161     priv = rot->state.priv;
162 
163     priv->pp_control = 0;
164     priv->pp_data = 0;
165 
166     /* Always use 10 bit resolution, which supports also 8 bits
167      * at the cost of 2 potentially wrong lsb */
168     priv->adc_res = 10; /* 8 bits vs. 10 bits ADC */
169     priv->brake_off = 0; /* i.e. brake is ON */
170     priv->curr_move = 0;
171 
172     return RIG_OK;
173 }
174 
175 int
ars_cleanup(ROT * rot)176 ars_cleanup(ROT *rot)
177 {
178     if (!rot)
179     {
180         return -RIG_EINVAL;
181     }
182 
183     if (rot->state.priv)
184     {
185         free(rot->state.priv);
186         rot->state.priv = NULL;
187     }
188 
189     return RIG_OK;
190 }
191 
192 int
ars_open(ROT * rot)193 ars_open(ROT *rot)
194 {
195     /* make it idle, and known state */
196     ars_stop(rot);
197 
198 #ifdef HAVE_PTHREAD
199     {
200         struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
201         pthread_attr_t attr;
202         int retcode;
203 
204         pthread_attr_init(&attr);
205         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
206 
207         /* create behind-the-scene thread doing the ars_set_position_sync() */
208         retcode = pthread_create(&priv->thread, &attr, handle_set_position, rot);
209 
210         if (retcode != RIG_OK)
211         {
212             rig_debug(RIG_DEBUG_ERR, "%s: pthread_create: %s\n", __func__,
213                       strerror(retcode));
214             return -RIG_ENOMEM;
215         }
216     }
217 #endif
218 
219     return RIG_OK;
220 }
221 
222 int
ars_close(ROT * rot)223 ars_close(ROT *rot)
224 {
225 #ifdef HAVE_PTHREAD
226     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
227 
228     pthread_cancel(priv->thread);
229 #endif
230 
231     /* leave it in safe state */
232     ars_stop(rot);
233 
234     return RIG_OK;
235 }
236 
237 int
ars_stop(ROT * rot)238 ars_stop(ROT *rot)
239 {
240     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
241     hamlib_port_t *pport = &rot->state.rotport;
242 
243     rig_debug(RIG_DEBUG_TRACE, "%s called, brake was %s\n", __func__,
244               priv->brake_off ? "OFF" : "ON");
245 
246 #ifdef HAVE_PTHREAD
247     priv->set_pos_active = 0;
248 #endif
249 
250     par_lock(pport);
251 
252     priv->brake_off = 0;
253     priv->curr_move = 0;
254 
255     // Relay AUX -> Off
256     CHKPPRET(ars_clear_data_pin(rot, DTA_PIN02 | DTA_PIN04 | DTA_PIN08));
257     CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17 | CTL_PIN16));
258 
259     // Elevation Relays -> Off
260     CHKPPRET(ars_clear_data_pin(rot, DTA_PIN03 | DTA_PIN07));
261 
262     par_unlock(pport);
263 
264     return RIG_OK;
265 }
266 
267 int
ars_move(ROT * rot,int direction,int speed)268 ars_move(ROT *rot, int direction, int speed)
269 {
270     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
271     hamlib_port_t *pport = &rot->state.rotport;
272     int need_settle_delay = 0;
273 
274     rig_debug(RIG_DEBUG_TRACE, "%s called%s%s%s%s%s\n", __func__,
275               (direction & ROT_MOVE_LEFT)  ? " LEFT" : "",
276               (direction & ROT_MOVE_RIGHT) ? " RIGHT" : "",
277               (direction & ROT_MOVE_UP)    ? " UP" : "",
278               (direction & ROT_MOVE_DOWN)  ? " DOWN" : "",
279               (direction == 0)             ? " STOP" : "");
280 
281     par_lock(pport);
282 
283     /* Allow the rotator to settle down when changing direction */
284     if (((priv->curr_move & ROT_MOVE_LEFT) && (direction & ROT_MOVE_RIGHT)) ||
285             ((priv->curr_move & ROT_MOVE_RIGHT) && (direction & ROT_MOVE_LEFT)))
286     {
287 
288         // Relay AUX -> Off
289         CHKPPRET(ars_clear_data_pin(rot, DTA_PIN02 | DTA_PIN04 | DTA_PIN08));
290         CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17 | CTL_PIN16));
291         need_settle_delay = 1;
292         priv->brake_off = 0;
293     }
294 
295     if (ars_has_el(rot) &&
296             (((priv->curr_move & ROT_MOVE_UP) && (direction & ROT_MOVE_DOWN)) ||
297              ((priv->curr_move & ROT_MOVE_DOWN) && (direction & ROT_MOVE_UP))))
298     {
299 
300         // Elevation Relays -> Off
301         CHKPPRET(ars_clear_data_pin(rot, DTA_PIN03 | DTA_PIN07));
302         need_settle_delay = 1;
303     }
304 
305     if (need_settle_delay)
306     {
307         rig_debug(RIG_DEBUG_TRACE, "%s need settling delay\n", __func__);
308 
309         hl_usleep(ARS_SETTLE_DELAY);
310     }
311 
312     priv->curr_move = direction;
313 
314 
315     /* Brake handling, only for AZ */
316     if (!priv->brake_off && (direction & (ROT_MOVE_LEFT | ROT_MOVE_RIGHT)))
317     {
318         /* release the brake */
319         if (ars_has_el(rot))
320         {
321             // RCI Model Azim & Elev
322             // Deactivated CCW/CW relays
323             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17 | CTL_PIN16));
324             // Relay Aux
325             CHKPPRET(ars_set_data_pin(rot, DTA_PIN02 | DTA_PIN04 | DTA_PIN06 | DTA_PIN08));
326             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN09));
327         }
328         else
329         {
330             // RCI Model Azimuth only
331             // Deactivated CCW/CW relays
332             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17 | CTL_PIN16));
333             // Relay Aux
334             CHKPPRET(ars_set_data_pin(rot,
335                                       DTA_PIN02 | DTA_PIN04 | DTA_PIN06 | DTA_PIN07 | DTA_PIN08 | DTA_PIN09));
336             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN03 | DTA_PIN05));
337         }
338 
339         priv->brake_off = 1;
340         hl_usleep(ARS_BRAKE_DELAY);
341     }
342 
343 
344     if (ars_has_el(rot))
345     {
346         if (direction & ROT_MOVE_UP)
347         {
348             /* UP */
349             CHKPPRET(ars_set_data_pin(rot, DTA_PIN03 | DTA_PIN06 | DTA_PIN07));
350             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN05 | DTA_PIN09));
351         }
352         else if (direction & ROT_MOVE_DOWN)
353         {
354             CHKPPRET(ars_set_data_pin(rot, DTA_PIN03 | DTA_PIN05 | DTA_PIN06 | DTA_PIN07));
355             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN09));
356         }
357         else
358         {
359             // Elevation Relays -> Off
360             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN03 | DTA_PIN07));
361         }
362     }
363 
364     if (direction & ROT_MOVE_LEFT)
365     {
366         // Relay Left
367         if (ars_has_el(rot))
368         {
369             // RCI Model Azim & Elev
370             CHKPPRET(ars_set_data_pin(rot, DTA_PIN02 | DTA_PIN04 | DTA_PIN06));
371             CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN16));
372             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN09));
373             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17));
374         }
375         else
376         {
377             // RCI Model Azimuth only
378             CHKPPRET(ars_set_data_pin(rot,
379                                       DTA_PIN02 | DTA_PIN04 | DTA_PIN06 | DTA_PIN07 | DTA_PIN08));
380             CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN16));
381             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN03 | DTA_PIN05));
382             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17));
383         }
384     }
385     else if (direction & ROT_MOVE_RIGHT)
386     {
387         // Relay Right
388         if (ars_has_el(rot))
389         {
390             // RCI Model Azim & Elev
391             CHKPPRET(ars_set_data_pin(rot, DTA_PIN02 | DTA_PIN04 | DTA_PIN06));
392             CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN17));
393             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN09));
394             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN16));
395         }
396         else
397         {
398             // RCI Model Azimuth only
399             CHKPPRET(ars_set_data_pin(rot,
400                                       DTA_PIN02 | DTA_PIN04 | DTA_PIN06 | DTA_PIN07 | DTA_PIN08));
401             CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN17));
402             CHKPPRET(ars_clear_data_pin(rot, DTA_PIN03 | DTA_PIN05));
403             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN16));
404         }
405     }
406     else
407     {
408         // Relay AUX -> Off
409         CHKPPRET(ars_clear_data_pin(rot, DTA_PIN02 | DTA_PIN04 | DTA_PIN08));
410         CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN17 | CTL_PIN16));
411         // AZ stop
412     }
413 
414     par_unlock(pport);
415 
416     return RIG_OK;
417 }
418 
angle_in_range(float angle,float angle_base,float range)419 static int angle_in_range(float angle, float angle_base, float range)
420 {
421     return (angle >= angle_base - range && angle <= angle_base + range);
422 }
423 
424 /*
425  * Thread handler
426  */
427 #ifdef HAVE_PTHREAD
handle_set_position(void * arg)428 static void *handle_set_position(void *arg)
429 {
430     ROT *rot = (ROT *) arg;
431     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
432     int retcode;
433 
434     while (1)
435     {
436 
437         if (!priv->set_pos_active)
438         {
439             /* TODO: replace polling period by cond var */
440             hl_usleep(100 * 1000 - 1);
441             continue;
442         }
443 
444         retcode = ars_set_position_sync(rot, priv->target_az, priv->target_el);
445         priv->set_pos_active = 0;
446 
447         if (retcode != RIG_OK)
448         {
449             rig_debug(RIG_DEBUG_WARN, "%s: ars_set_position_sync() failed: %s\n",
450                       __func__, rigerror(retcode));
451             hl_usleep(1000 * 1000);
452             continue;
453         }
454     }
455 
456     return NULL;
457 }
458 #endif
459 
460 /*
461  * ars_set_position_sync() is synchronous.
462  * See handle_set_position_async() for asynchronous thread handler,
463  * with wait loop in background
464  */
465 int
ars_set_position_sync(ROT * rot,azimuth_t az,elevation_t el)466 ars_set_position_sync(ROT *rot, azimuth_t az, elevation_t el)
467 {
468     azimuth_t curr_az, prev_az;
469     elevation_t curr_el, prev_el;
470     int retval;
471     int az_move, el_move;
472     struct timeval last_pos_az_tv, last_pos_el_tv;
473 
474     rig_debug(RIG_DEBUG_TRACE, "%s called: %.1f %.1f\n", __func__, az, el);
475 
476     ars_stop(rot);
477 
478     retval = ars_get_position(rot, &curr_az, &curr_el);
479 
480     if (retval != RIG_OK)
481     {
482         return retval;
483     }
484 
485     /* watchdog init */
486     prev_az = curr_az;
487     prev_el = curr_el;
488     gettimeofday(&last_pos_az_tv, NULL);
489     last_pos_el_tv = last_pos_az_tv;
490 
491     while (!angle_in_range(curr_az, az, AZ_RANGE) ||
492             (ars_has_el(rot) && !angle_in_range(curr_el, el, EL_RANGE))
493           )
494     {
495 
496         if (curr_az < (az - AZ_RANGE))
497         {
498             az_move = ROT_MOVE_RIGHT;
499         }
500         else if (curr_az > (az + AZ_RANGE))
501         {
502             az_move = ROT_MOVE_LEFT;
503         }
504         else
505         {
506             az_move = 0;
507         }
508 
509         if (ars_has_el(rot))
510         {
511             if (curr_el < (el - EL_RANGE))
512             {
513                 el_move = ROT_MOVE_UP;
514             }
515             else if (curr_el > (el + EL_RANGE))
516             {
517                 el_move = ROT_MOVE_DOWN;
518             }
519             else
520             {
521                 el_move = 0;
522             }
523         }
524         else
525         {
526             el_move = 0;
527         }
528 
529         retval = ars_move(rot, az_move | el_move, 0);
530 
531         if (retval != RIG_OK)
532         {
533             ars_stop(rot);
534             return retval;
535         }
536 
537         /* wait a little */
538         hl_usleep(10 * 1000);
539 
540         retval = ars_get_position(rot, &curr_az, &curr_el);
541 
542         if (retval != RIG_OK)
543         {
544             ars_stop(rot);
545             return retval;
546         }
547 
548         /* Watchdog detecting when rotor is blocked unexpectedly */
549 #define AZ_WATCHDOG 5000 /* ms */
550 #define EL_WATCHDOG 5000 /* ms */
551 
552         if (az_move != 0 && angle_in_range(curr_az, prev_az, AZ_RANGE))
553         {
554             if (rig_check_cache_timeout(&last_pos_az_tv, AZ_WATCHDOG))
555             {
556                 ars_stop(rot);
557                 return -RIG_ETIMEOUT;
558             }
559         }
560         else
561         {
562             prev_az = curr_az;
563             gettimeofday(&last_pos_az_tv, NULL);
564         }
565 
566         if (el_move != 0 && ars_has_el(rot) &&
567                 angle_in_range(curr_el, prev_el, EL_RANGE))
568         {
569             if (rig_check_cache_timeout(&last_pos_el_tv, EL_WATCHDOG))
570             {
571                 ars_stop(rot);
572                 return -RIG_ETIMEOUT;
573             }
574         }
575         else
576         {
577             prev_el = curr_el;
578             gettimeofday(&last_pos_el_tv, NULL);
579         }
580     }
581 
582     return ars_stop(rot);
583 }
584 
585 int
ars_set_position(ROT * rot,azimuth_t az,elevation_t el)586 ars_set_position(ROT *rot, azimuth_t az, elevation_t el)
587 {
588 #ifdef HAVE_PTHREAD
589     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
590 
591     /* will be picked by handle_set_position() next polling tick */
592     priv->target_az = az;
593     priv->target_el = el;
594     priv->set_pos_active = 1;
595 
596     return RIG_OK;
597 #else
598     return ars_set_position_sync(rot, az, el);
599 #endif
600 }
601 
comparunsigned(const void * a,const void * b)602 static int comparunsigned(const void *a, const void *b)
603 {
604     const unsigned ua = *(const unsigned *)a, ub = *(const unsigned *)b;
605 
606     return ua == ub ? 0 : ua < ub ? -1 : 1;
607 }
608 
609 int
ars_get_position(ROT * rot,azimuth_t * az,elevation_t * el)610 ars_get_position(ROT *rot, azimuth_t *az, elevation_t *el)
611 {
612     struct ars_priv_data *priv = (struct ars_priv_data *)rot->state.priv;
613     struct rot_state *rs = &rot->state;
614     hamlib_port_t *pport = &rs->rotport;
615     int i, num_sample;
616     unsigned az_samples[NUM_SAMPLES], az_value;
617     unsigned el_samples[NUM_SAMPLES], el_value;
618     unsigned char status;
619 
620     par_lock(pport);
621 
622     /* flush last sampled value, with a dummy read */
623     CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CLK));
624     hl_usleep(PP_IO_PERIOD);
625 
626     CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CS));
627     hl_usleep(PP_IO_PERIOD);
628 
629     for (i = 0; i < priv->adc_res; i++)
630     {
631         CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN_CLK));
632         hl_usleep(PP_IO_PERIOD);
633 
634         CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CLK));
635         hl_usleep(PP_IO_PERIOD);
636     }
637 
638     CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CLK));
639     hl_usleep(PP_IO_PERIOD);
640 
641     CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN_CS));
642     /* end of dummy read */
643 
644     for (num_sample = 0; num_sample < NUM_SAMPLES; num_sample++)
645     {
646 
647         /* read ADC value TLC(1)549 (8/10 bits), by SPI bitbanging */
648 
649         hl_usleep(PP_IO_PERIOD);
650         CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CLK));
651         hl_usleep(PP_IO_PERIOD);
652 
653         CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CS));
654         hl_usleep(PP_IO_PERIOD);
655 
656         az_samples[num_sample] = 0;
657         el_samples[num_sample] = 0;
658 
659         for (i = 0; i < priv->adc_res; i++)
660         {
661             CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN_CLK));
662             hl_usleep(PP_IO_PERIOD);
663 
664             CHKPPRET(par_read_status(pport, &status));
665 
666             az_samples[num_sample] <<= 1;
667             az_samples[num_sample] |= (status & STA_PIN_D0) ? 1 : 0;
668 
669             el_samples[num_sample] <<= 1;
670             el_samples[num_sample] |= (status & STA_PIN_D1) ? 1 : 0;
671 
672             CHKPPRET(ars_clear_ctrl_pin(rot, CTL_PIN_CLK));
673             hl_usleep(PP_IO_PERIOD);
674         }
675 
676         CHKPPRET(ars_set_ctrl_pin(rot, CTL_PIN_CS));
677 
678         rig_debug(RIG_DEBUG_TRACE, "%s: raw samples: az %u, el %u\n",
679                   __func__, az_samples[num_sample], el_samples[num_sample]);
680 
681         hl_usleep(PP_IO_PERIOD);
682     }
683 
684     par_unlock(pport);
685 
686     qsort(az_samples, NUM_SAMPLES, sizeof(unsigned), comparunsigned);
687     qsort(el_samples, NUM_SAMPLES, sizeof(unsigned), comparunsigned);
688 
689     /* select median value in order to rule out glitches */
690     az_value = az_samples[NUM_SAMPLES / 2];
691     el_value = el_samples[NUM_SAMPLES / 2];
692 
693     *az = rs->min_az + ((float)az_value * (rs->max_az - rs->min_az)) / ((
694                 1 << priv->adc_res) - 1);
695     *el = rs->min_el + ((float)el_value * (rs->max_el - rs->min_el)) / ((
696                 1 << priv->adc_res) - 1);
697 
698     rig_debug(RIG_DEBUG_TRACE, "%s: az=%.1f el=%.1f\n", __func__, *az, *el);
699 
700     return RIG_OK;
701 }
702 
703 
704 /* ************************************************************************* */
705 /*
706  * ARS rotator capabilities.
707  */
708 
709 /*
710  * RCI/RCI-SE, with Elevation daugtherboard/unit.
711  */
712 const struct rot_caps rci_azel_rot_caps =
713 {
714     ROT_MODEL(ROT_MODEL_RCI_AZEL),
715     .model_name =     "ARS RCI AZ&EL",
716     .mfg_name =       "EA4TX",
717     .version =        "20200112.0",
718     .copyright =      "LGPL",
719     .status =         RIG_STATUS_BETA,
720     .rot_type =       ROT_TYPE_AZEL,  /* AZ&EL units */
721     .port_type =      RIG_PORT_PARALLEL,
722     .write_delay =    0,
723     .post_write_delay =  10,
724     .timeout =  0,
725     .retry =  3,
726 
727     .min_az =     0,
728     .max_az =     360,
729     .min_el =     0,
730     .max_el =     180,    /* TBC */
731 
732     .rot_init     = ars_init,
733     .rot_cleanup  = ars_cleanup,
734     .rot_open     = ars_open,
735     .rot_close    = ars_close,
736     .set_position = ars_set_position,
737     .get_position = ars_get_position,
738     .stop         = ars_stop,
739     .move         = ars_move,
740 };
741 
742 /*
743  * RCI/RCI-SE, without Elevation daugtherboard/unit.
744  * Azimuth only
745  */
746 const struct rot_caps rci_az_rot_caps =
747 {
748     ROT_MODEL(ROT_MODEL_RCI_AZ),
749     .model_name =     "ARS RCI AZ",
750     .mfg_name =       "EA4TX",
751     .version =        "20200112.0",
752     .copyright =      "LGPL",
753     .status =         RIG_STATUS_BETA,
754     .rot_type =       ROT_TYPE_AZIMUTH,    /* AZ-only unit */
755     .port_type =      RIG_PORT_PARALLEL,
756     .write_delay =    0,
757     .post_write_delay =  10,
758     .timeout =  0,
759     .retry =  3,
760 
761     .min_az =     0,
762     .max_az =     360,
763     .min_el =     0,
764     .max_el =     180,    /* TBC */
765 
766     .rot_init     = ars_init,
767     .rot_cleanup  = ars_cleanup,
768     .rot_open     = ars_open,
769     .rot_close    = ars_close,
770     .set_position = ars_set_position,
771     .get_position = ars_get_position,
772     .stop         = ars_stop,
773     .move         = ars_move,
774 };
775 
776 
777 /* ************************************************************************* */
778 
DECLARE_INITROT_BACKEND(ars)779 DECLARE_INITROT_BACKEND(ars)
780 {
781     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
782 
783     rot_register(&rci_az_rot_caps);
784     rot_register(&rci_azel_rot_caps);
785 
786     return RIG_OK;
787 }
788 
789 /* ************************************************************************* */
790 /* end of file */
791