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