/******************************************************************** * Description: hal_stg.c * This is the driver for Servo-To-Go Model I & II board. * * Author: Alex Joni * License: GPL Version 2 * * Copyright (c) 2004 All rights reserved. * see below for aditional notes * * Last change: ********************************************************************/ /** This is the driver for Servo-To-Go Model I & II board. The board includes 8 channels of quadrature encoder input, 8 channels of analog input and output, 32 bits digital I/O, and an interval timer with interrupt. Installation of the driver only realtime: insmod hal_stg num_chan=8 dio="IIOO" - autodetects the address or insmod hal_stg base=0x200 num_chan=8 dio="IIOO" Check your Hardware manual for your base address. The digital inputs/outputs configuration is determined by a config string passed to insmod when loading the module. The format consists by a four character string that sets the direction of each group of pins. Each character of the direction string is either "I" or "O". The first character sets the direction of port A (Port A - DIO.0-7), the next sets port B (Port B - DIO.8-15), the next sets port C (Port C - DIO.16-23), and the fourth sets port D (Port D - DIO.24-31). The following items are exported to the HAL. Encoders: Parameters: float stg..position-scale (counts per unit) Pins: s32 stg..counts float stg..position /todo bit stg..index-enable /todo bit stg..enc-reset-count Functions: void stg..capture_position DACs: Parameters: float stg..dac-offset float stg..dac-gain Pins: float stg..dac-value Functions: void stg..dac-write ADC: Parameters: float stg..adc-offset float stg..adc-gain Pins: float stg..adc-value Functions: void stg..adc-read Digital In: Pins: bit stg.in- bit stg.in--not Functions: void stg.digital-in-read Digital Out: Parameters: bit stg.out--invert Pins: bit stg.out- Functions: void stg.digital-out-write */ /** Copyright (C) 2004 Alex Joni */ /** Copyright (C) 2003 John Kasunich */ /* Based on STGMEMBS.CPP from the Servo To Go Windows drivers - Copyright (c) 1996 Servo To Go Inc and released under GPL Version 2 */ /* Also relates to the EMC1 code (very similar to STGMEMBS.CPP) work done by Fred Proctor, Will Shackleford */ /** This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR ANY HARM OR LOSS RESULTING FROM ITS USE. IT IS _EXTREMELY_ UNWISE TO RELY ON SOFTWARE ALONE FOR SAFETY. Any machinery capable of harming persons must have provisions for completely removing power from all motors, etc, before persons enter any danger area. All machinery must be designed to comply with local and national safety codes, and the authors of this software can not, and do not, take any responsibility for such compliance. This code was written as part of the EMC HAL project. For more information, go to www.linuxcnc.org. */ #include #include "rtapi.h" /* RTAPI realtime OS API */ #include "rtapi_app.h" /* RTAPI realtime module decls */ #include "hal.h" /* HAL public API decls */ #include "hal_stg.h" /* STG related defines */ /* module information */ MODULE_AUTHOR("Alex Joni"); MODULE_DESCRIPTION("Driver for Servo-to-Go Model I & II for EMC HAL"); MODULE_LICENSE("GPL"); static int base = 0x00; /* board base address, 0 means autodetect */ RTAPI_MP_INT(base, "board base address, don't use for autodetect"); static int model = 0; /* board model, leave empty and autodetect */ RTAPI_MP_INT(model, "board model, use with caution. it overrides the detected model"); static int num_chan = MAX_CHANS; /* number of channels - default = 8 */ RTAPI_MP_INT(num_chan, "number of channels"); static char *dio = "IIOO"; /* dio config - default = port A&B inputs, port C&D outputs */ RTAPI_MP_STRING(dio, "dio config string - expects something like IIOO"); #define EPSILON 1e-20 /*********************************************************************** * STRUCTURES AND GLOBAL VARIABLES * ************************************************************************/ typedef struct { hal_bit_t *data; /* basic pin for input or output */ union { hal_bit_t *not; /* pin for inverted data (input only) */ hal_bit_t invert; /* param for inversion (output only) */ } io; } io_pin; typedef struct { /* counter data */ hal_s32_t *count[MAX_CHANS]; /* captured binary count value */ hal_s32_t offset[MAX_CHANS]; /* offset to hold latched position from index pulse */ hal_float_t *pos[MAX_CHANS]; /* scaled position (floating point) */ hal_float_t pos_scale[MAX_CHANS]; /* parameter: scaling factor for pos */ hal_bit_t *index_enable[MAX_CHANS]; /* pins for index homing */ hal_bit_t *index_latch[MAX_CHANS]; /* value of the index latch for the axis */ // hal_s32_t check_index[MAX_CHANS]; /* internal marker for two stage index pulse check */ hal_bit_t *index_polarity[MAX_CHANS]; /* Polarity of index pulse */ /* dac data */ hal_float_t *dac_value[MAX_CHANS]; /* value to be written to dac */ hal_float_t dac_offset[MAX_CHANS]; /* offset value for DAC */ hal_float_t dac_gain[MAX_CHANS]; /* gain to be applied */ /* adc data */ hal_float_t *adc_value[MAX_CHANS]; /* value to be read from adc */ hal_float_t adc_offset[MAX_CHANS]; /* offset value for ADC */ hal_float_t adc_gain[MAX_CHANS]; /* gain to be applied */ int adc_current_chan; /* holds the currently converting channel */ /* dio data */ io_pin port[4][8]; /* holds 4 ports each 8 pins, either input or output */ unsigned char dir_bits; /* remembers config (which port is input which is output) */ unsigned char model; } stg_struct; static stg_struct *stg_driver; /* other globals */ static int comp_id; /* component ID */ static int outpinnum=0, inputpinnum=0; //const int STG_MSG_LEVEL = RTAPI_MSG_ALL; const int STG_MSG_LEVEL = RTAPI_MSG_INFO; #define DATA(x) (base + (2 * x) - (x % 2)) /* Address of Data register 0 */ #define CTRL(x) (base + (2 * (x+1)) - (x % 2)) /* Address of Control register 0 */ /*********************************************************************** * LOCAL FUNCTION DECLARATIONS * ************************************************************************/ /* helper functions, to export HAL pins & co. */ static int export_counter(int num, stg_struct * addr); static int export_dac(int num, stg_struct * addr); static int export_adc(int num, stg_struct * addr); static int export_pins(int num, int dir, stg_struct * addr); static int export_input_pin(int pinnum, io_pin * pin); static int export_output_pin(int pinnum, io_pin * pin); /* Board specific functions */ /* initializes the STG, takes care of autodetection, all initialisations */ static int stg_init_card(void); /* sets up interrupt to be used */ static int stg_set_interrupt(short interrupt); /* scans possible addresses for STG cards */ static unsigned short stg_autodetect(void); /* counter related functions */ static int stg_counter_init(int ch); static long stg_counter_read(int i); static void stg_counter_latch(int i); static void stg1_select_index_axis(void *arg, unsigned int chan); static void stg1_reset_index_latch(void *arg, unsigned int chan); static unsigned short stg1_get_index_pulse_latch(void *arg, unsigned int chan); static void stg2_reset_all_index_latches( void *arg ); static void stg2_select_index_axes( void *arg, unsigned char mask ); static unsigned char stg2_get_all_index_pulse_latches( void *arg ); /* dac related functions */ static int stg_dac_init(int ch); static int stg_dac_write(int ch, short value); /* adc related functions */ static int stg_adc_init(int ch); static int stg_adc_start(void *arg, unsigned short wAxis); static short stg_adc_read(void *arg, int ch); /* dio related functions */ static int stg_dio_init(void); /* periodic functions registered to HAL */ static void stg_adcs_read(void *arg, long period); //reads adc data from the board, check long description at the beginning of the function static void stg_dacs_write(void *arg, long period); //writes dac's to the STG static void stg_counter_capture(void *arg, long period); //captures encoder counters static void stg_di_read(void *arg, long period); //reads digital inputs from the STG static void stg_do_write(void *arg, long period); //writes digital outputs to the STG //static void stg_debug_print( void *, long ); /*********************************************************************** * INIT AND EXIT CODE * ************************************************************************/ #define MAX_CHAN 8 int rtapi_app_main(void) { int n, retval, mask, m; unsigned char dir_bits; int msg; msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* test for number of channels */ if ((num_chan <= 0) || (num_chan > MAX_CHAN)) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: invalid num_chan: %d\n", num_chan); return -1; } /* test for config string */ if ((dio == 0) || (dio[0] == '\0')) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: no dio config string\n"); return -1; } /* have good config info, connect to the HAL */ comp_id = hal_init("hal_stg"); if (comp_id < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: hal_init() failed\n"); return -1; } /* allocate shared memory for stg data */ stg_driver = hal_malloc(num_chan * sizeof(stg_struct)); if (stg_driver == 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: hal_malloc() failed\n"); hal_exit(comp_id); return -1; } /* takes care of all initialisations, also autodetection and model if necessary */ if ((retval=stg_init_card()) != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg_init_card() failed\n"); hal_exit(comp_id); return retval; } /* dio should be a string of 4 'I" or "O" characters */ dir_bits = 0; mask = 0x01; for ( m = 0 ; m < 4 ; m++ ) { /* test character and set/clear bit */ if ((dio[m] == 'i') || (dio[m] == 'I')) { /* input, set mask bit to zero */ dir_bits &= ~mask; } else if ((dio[m] == 'o') || (dio[m] == 'O')) { /* output, set mask bit to one */ dir_bits |= mask; } else { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: bad config info for port %d\n", m); return -1; } /* shift mask for next bit */ mask <<= 1; } /* we now should have directions figured out, next is exporting the pins based on that */ mask = 0x01; for ( m = 0 ; m < 4 ; m++ ) { /* port, direction, driver */ export_pins(m, (dir_bits & mask), stg_driver); /* shift mask for next bit */ mask <<= 1; } stg_driver->dir_bits = dir_bits; /* remember direction of each port, will be used in the write / read functions */ stg_dio_init(); /* export all the variables for each counter, dac */ for (n = 0; n < num_chan; n++) { /* export all vars */ retval = export_counter(n, stg_driver); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: counter %d var export failed\n", n + 1); hal_exit(comp_id); return -1; } /* init counter */ *(stg_driver->count[n]) = 0; stg_driver->offset[n] = 0; *(stg_driver->pos[n]) = 0.0; /* By default the index pulse is not processed/used */ *(stg_driver->index_enable[n]) = 0; /* Default polarity for the index pulse is active high */ if( stg_driver->model == 1 ) { *(stg_driver->index_polarity[n]) = 1; } /* Default value for the index latch output is false */ *(stg_driver->index_latch[n]) = 0; stg_driver->pos_scale[n] = 1.0; /* init counter chip */ stg_counter_init(n); retval = export_dac(n, stg_driver); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: dac %d var export failed\n", n + 1); hal_exit(comp_id); return -1; } /* init counter */ *(stg_driver->dac_value[n]) = 0; stg_driver->dac_offset[n] = 0.0; stg_driver->dac_gain[n] = 1.0; /* init dac chip */ stg_dac_init(n); retval = export_adc(n, stg_driver); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: adc %d var export failed\n", n + 1); hal_exit(comp_id); return -1; } /* init counter */ *(stg_driver->adc_value[n]) = 0; stg_driver->adc_offset[n] = 0.0; stg_driver->adc_gain[n] = 1.0; stg_driver->adc_current_chan = -1; /* notify that no conversion has been started yet */ /* init adc chip */ stg_adc_init(n); } /* export functions */ retval = hal_export_funct("stg.capture-position", stg_counter_capture, stg_driver, 1, 0, comp_id); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg.counter-capture funct export failed\n"); hal_exit(comp_id); return -1; } rtapi_print_msg(RTAPI_MSG_INFO, "STG: installed %d encoder counters\n", num_chan); retval = hal_export_funct("stg.write-dacs", stg_dacs_write, stg_driver, 1, 0, comp_id); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg.write-dacs funct export failed\n"); hal_exit(comp_id); return -1; } rtapi_print_msg(RTAPI_MSG_INFO, "STG: installed %d dacs\n", num_chan); retval = hal_export_funct("stg.read-adcs", stg_adcs_read, stg_driver, 1, 0, comp_id); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg.read-adcs funct export failed\n"); hal_exit(comp_id); return -1; } rtapi_print_msg(RTAPI_MSG_INFO, "STG: installed %d adcs\n", num_chan); retval = hal_export_funct("stg.di-read", stg_di_read, stg_driver, 0, 0, comp_id); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg.di-read funct export failed\n"); hal_exit(comp_id); return -1; } rtapi_print_msg(RTAPI_MSG_INFO, "STG: installed %d digital inputs\n", inputpinnum); retval = hal_export_funct("stg.do-write", stg_do_write, stg_driver, 0, 0, comp_id); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg.do-write funct export failed\n"); hal_exit(comp_id); return -1; } rtapi_print_msg(RTAPI_MSG_INFO, "STG: installed %d digital outputs\n", outpinnum); /* retval = hal_export_funct("stg.debug_print", stg_debug_print, stg_driver, 0, 0, comp_id); if (retval != 0) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: stg.debug_print funct export failed\n"); hal_exit(comp_id); return -1; } rtapi_print_msg(RTAPI_MSG_INFO, "STG: installed periodic debug print\n"); */ hal_ready(comp_id); /* restore saved message level */ rtapi_set_msg_level(msg); return 0; } void rtapi_app_exit(void) { hal_exit(comp_id); // FIXME, check for return code ? return; } /*********************************************************************** * REALTIME ENCODER COUNTING AND UPDATE FUNCTIONS * ************************************************************************/ static void stg_counter_capture(void *arg, long period) { stg_struct *stg = arg; int n; // int msg; unsigned char mask; unsigned char index_pulse_latches; if( stg->model == 1 ) { /* * STG Model 1, stg1 */ for( n = 0; n < num_chan; n++ ) { /* reset and then select current axes pair to be reset by index * Because they index polarity is configurable for the stg2 card be select * even for the odd axes */ stg1_select_index_axis(arg, n); if (stg1_get_index_pulse_latch(arg, n)) { *(stg->index_latch[n]) = 1; if ( *(stg->index_enable[n]) == 1 ) { // read the value without latching, latching was done on index // remember this as an offset, it will be substracted from nominal stg->offset[n] = stg_counter_read(n); /* set index-enable false, so outside knows we found the index, and reset the position */ *(stg->index_enable[n]) = 0; /* msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); rtapi_print_msg(RTAPI_MSG_DBG, "STG: Index pulse detected on channel %1d\n", n ); rtapi_set_msg_level(msg); */ } else { /* NOP, no action needed, since the selection of an index pair is just valid until the next * pair is selected */ } } else { *(stg->index_latch[n]) = 0; } } } else if( stg->model == 2 ) { /* * STG Model 2, stg2 */ // Set IDLEN for( mask = 0, n = 0; n < num_chan; n++ ) { if( *(stg->index_enable[n]) == 1 ) { mask |= ( 1<index_latch[n]) = 1; if ( *(stg->index_enable[n]) == 1 ) { // read the value without latching, latching was done on index // remember this as an offset, it will be substracted from nominal stg->offset[n] = stg_counter_read(n); /* set index-enable false, so outside knows we found the index, and reset the position */ *(stg->index_enable[n]) = 0; /* msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); rtapi_print_msg(RTAPI_MSG_DBG, "STG: Index pulse detected on channel %1d\n", n ); rtapi_set_msg_level(msg); */ } else { /* NOP, no action needed, since all index latches will be clearer for the next iteration anyway */ } } else { *(stg->index_latch[n]) = 0; } } // Reset all latches stg2_reset_all_index_latches( arg ); } else { // NOP, only models stg1 and stg2, thus should never be reached */ } for (n = 0; n < num_chan; n++) { /* capture raw counts to latches */ stg_counter_latch(n); /* read raw count, and substract the offset (determined by indexed homing) */ *(stg->count[n]) = stg_counter_read(n) - stg->offset[n]; /* make sure scale isn't zero or tiny to avoid divide error */ if (stg->pos_scale[n] < 0.0) { if (stg->pos_scale[n] > -EPSILON) stg->pos_scale[n] = -1.0; } else { if (stg->pos_scale[n] < EPSILON) stg->pos_scale[n] = 1.0; } /* scale count to make floating point position */ *(stg->pos[n]) = *(stg->count[n]) / stg->pos_scale[n]; } /* done */ return; } /* stg_debug_print * run this function from a very slow, * e.g. 1sec thread and it will give some information */ /* static void stg_debug_print( void *arg, long period ) { stg_struct *stg=arg; int msg; static int counter; // model 2 encoder index registers unsigned char idlen_reg; unsigned char seldi_reg; //model 1 encoder index registers unsigned char intc_reg; msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); if( stg->model == 1 ) { intc_reg = inb(base + INTC); rtapi_print_msg(RTAPI_MSG_DBG, "STG: %04d: IXS1 is %s\n", counter, ( intc_reg & IXS1 ) ? "TRUE" : "FALSE" ); rtapi_print_msg(RTAPI_MSG_DBG, "STG: %04d: IXS0 is %s\n", counter, ( intc_reg & IXS0 ) ? "TRUE" : "FALSE" ); rtapi_print_msg(RTAPI_MSG_DBG, "STG: %04d: IXLVL is active %s\n", counter, ( intc_reg & IXLVL ) ? "TRUE" : "FALSE" ); } else if (stg->model == 2 ) { idlen_reg = inb( base + IDLEN ); seldi_reg = inb( base + SELDI ); rtapi_print_msg(RTAPI_MSG_DBG, "STG: %04d: IDLEN is 0x%02x\n", counter, idlen_reg ); rtapi_print_msg(RTAPI_MSG_DBG, "STG: %04d: SELDI is 0x%02x\n", counter, seldi_reg ); } else { // NOP, should never be reached } // restore saved message level rtapi_set_msg_level(msg); counter++; return; } */ /* stg_dacs_write() - writes all dac's to the board - calls stg_dac_write() */ static void stg_dacs_write(void *arg, long period) { stg_struct *stg; double volts; short ncounts, i; stg=arg; for (i=0;i < num_chan; i++) { /* scale the voltage to be written based on offset and gain */ volts = (*(stg->dac_value[i]) - stg->dac_offset[i]) * stg->dac_gain[i]; /* clamp the scaled voltage value to the -10V to 10V output range of the STG */ if (volts < -10.0) volts = -10.0; if (volts > 10.0) volts = 10.0; /* compute the value for the DAC, the extra - in there is STG specific */ ncounts = (short) ((((-10.0 - volts) * 0x1FFF) / 20.0) - 1 ); /* write it to the card */ stg_dac_write(i, ncounts); } return; } /* stg_adcs_read() - reads one adc at a time from the board to hal - calls stg_adc_read() */ /* long description : Because the conversion takes a while (first mux to the right channel ~5usecs, then start the conversion, and wait for it to finish ~16-20 usecs) for all the 8 channels, it would be too much to start the conversion, wait for the result for all the 8 axes. Instead a different approach is chosen: - on the beginning of the function the conversion should already be done - it gets read and sent to HAL - the new channel gets mux'ed - and at the end of the function the new conversion is started, so that the data will be available at the next run. This way 8 periods are needed to read 8 ADC's. It is possible to set the board to do faster conversions (AZ bit on INTC off), but that would make it less reliable (autozero takes care of temp. errors).*/ /*! \todo STG_ADC_Improvement (if any user requests it). Another improvement might be to let the user chose what channels he would like for ADC (having only 2 channels might speed things up considerably). */ static void stg_adcs_read(void *arg, long period) { stg_struct *stg; double volts; short ncounts; int i; stg = arg; i = stg->adc_current_chan; if ((i >= 0) && (i < num_chan)) { /* we should have the conversion done for adc_num_chan */ ncounts = stg_adc_read(stg,i); volts = ncounts * 10.0 / 4096; *(stg->adc_value[i]) = volts * stg->adc_gain[i] - stg->adc_offset[i]; } /* if adc_num_chan < 0, it's the first time this routine runs thus we don't have any ready data, we simply start the next conversion */ if (stg->adc_current_chan++ >= num_chan) stg->adc_current_chan=0; //increase the channel, and roll back to 0 after all chans are done /* select the current channel with the mux, and start the conversion */ stg_adc_start(stg,stg->adc_current_chan); /* the next time this function runs, the result should be available */ return; } // helper function to extract the data out of a char and place it into HAL data // written by JMK static void split_input(unsigned char data, io_pin *dest, int num) { int b; unsigned char mask; /* splits a byte into 'num' HAL pins (and their NOTs) */ mask = 0x01; for (b = 0 ; b < num ; b++ ) { if ( data & mask ) { /* input high, which means FALSE (active low) */ *(dest->data) = 0; *(dest->io.not) = 1; } else { /* input low, which means TRUE */ *(dest->data) = 1; *(dest->io.not) = 0; } mask <<= 1; dest++; } return; } // helper function to extract the data out of HAL and place it into a char // written by JMK unsigned char build_output(io_pin *src, int num) { int b; unsigned char data, mask; data = 0x00; mask = 0x01; /* assemble output byte for data port from 'num' source variables */ for (b = 0; b < num; b++) { /* get the data, add to output byte */ if ( *(src->data) ) { if ( !(src->io.invert) ) { data |= mask; } } else { if ( (src->io.invert) ) { data |= mask; } } mask <<= 1; src++; } return data; } static void stg_di_read(void *arg, long period) //reads digital inputs from the STG { stg_struct *stg; unsigned char val; stg=arg; if ( (stg->dir_bits & 0x01) == 0) { // if port A is set as input, read the bits if (stg->model == 1) val = inb(base + DIO_A); else val = inb(base + PORT_A); split_input(val, &(stg->port[0][0]), 8); } if ( (stg->dir_bits & 0x02) == 0) { // if port B is set as input, read the bits if (stg->model == 1) val = inb(base + DIO_B); else val = inb(base + PORT_B); split_input(val, &(stg->port[1][0]), 8); } if ( (stg->dir_bits & 0x04) == 0) { // if port C is set as input, read the bits if (stg->model == 1) val = inb(base + DIO_C); else val = inb(base + PORT_C); split_input(val, &(stg->port[2][0]), 8); } if ( (stg->dir_bits & 0x08) == 0) { // if port D is set as input, read the bits if (stg->model == 1) val = inb(base + DIO_D); else val = inb(base + PORT_D); split_input(val, &(stg->port[3][0]), 8); } } static void stg_do_write(void *arg, long period) //writes digital outputs to the STG { stg_struct *stg; unsigned char val; stg=arg; if ( (stg->dir_bits & 0x01) != 0) { // if port A is set as output, write the bits val = build_output(&(stg->port[0][0]), 8); if (stg->model == 1) outb(val, base + DIO_A); else outb(val, base + PORT_A); } if ( (stg->dir_bits & 0x02) != 0) { // if port B is set as output, write the bits val = build_output(&(stg->port[1][0]), 8); if (stg->model == 1) outb(val, base + DIO_B); else outb(val, base + PORT_B); } if ( (stg->dir_bits & 0x04) != 0) { // if port C is set as output, write the bits val = build_output(&(stg->port[2][0]), 8); if (stg->model == 1) outb(val, base + DIO_C); else outb(val, base + PORT_C); } if ( (stg->dir_bits & 0x08) != 0) { // if port D is set as output, write the bits val = build_output(&(stg->port[3][0]), 8); if (stg->model == 1) outb(val, base + DIO_D); else outb(val, base + PORT_D); } } /*********************************************************************** * BOARD SPECIFIC FUNCTIONS * * execute board related things (write/read to/from the stg) * ************************************************************************/ /*********************************************************************** * INIT FUNCTIONS * ************************************************************************/ /* stg_counter_init() - Initializes the channel works the same for both cards (STG & STG2) */ static int stg_counter_init(int ch) { /* Set Counter Command Register - Master Control, Master Reset (MRST), */ /* and Reset address pointer (RADR). */ outb(0x23, CTRL(ch)); /* Set Counter Command Register - Input Control, OL Load (P3), */ /* and Enable Inputs A and B (INA/B). */ outb(0x68, CTRL(ch)); /* Set Counter Command Register - Output Control */ outb(0x80, CTRL(ch)); /* Set Counter Command Register - Quadrature */ outb(0xC3, CTRL(ch)); return 0; } /* stg_dac_init() - Initializes the dac channel works the same for both cards (STG & STG2) */ static int stg_dac_init(int ch) { int i; /* set all DAC's to 0 on startup */ for (i=0; i < num_chan; i++) { stg_dac_write(i, 0x1000); //by Xuecheng, 0x1000 coresponds to 0V } return 0; } /* stg_adc_init() - Initializes the adc channel */ static int stg_adc_init(int ch) { /* not much to setup for the ADC's */ /* only select the mode of operation we will work with AutoZero */ if (stg_driver->model == 1) outb(0x0f, base + MIO_2); // the second 82C55 is already configured (by running stg_dio_init) // we only set bit 8 (AZ) to 1 to enable it return 0; } /* stg_dio_init() - Initializes the dio's */ static int stg_dio_init(void) { /* we will select the directions of each port */ unsigned char control, tempINTC, tempIMR, tempCtrl0, tempCtrl1; control = 0x80; //set up |1|0|0|A|CH|0|B|CL| if ( (stg_driver->dir_bits & 0x01) == 0) // if port A is set as input, set bit accordingly control |= 0x10; if ( (stg_driver->dir_bits & 0x02) == 0) // if port B is set as input, set bit accordingly control |= 0x02; if ( (stg_driver->dir_bits & 0x04) == 0) // if port C is set as input, set bits accordingly control |= 0x09; if (stg_driver->model == 1) { // write the computed control to MIO_1 outb(control, base+MIO_1); } else { //model STG2 // write port A,B,C direction to ABC_DIR outb(control, base+ABC_DIR); } tempINTC = inb(base + INTC); if (stg_driver->model == 1) { // next compute the directions for port D, located on the second 82C55 control = 0x82; if ( (stg_driver->dir_bits & 0x08) == 0)// if port D is set as input, set bits accordingly control = 0x92; tempIMR = inb(base + IMR); // get the current interrupt mask outb(0xff, base + OCW1); //mask off all interrupts // write the computed control to MIO_2 outb(control, base+MIO_2); outb(tempINTC, base + INTC); //restore interrupt control reg. outb(tempIMR, base+ OCW1); //restore int mask } else { //model STG2 // save contents of CNTRL0, it will get reinitialized tempCtrl0 = inb(base+CNTRL0); tempCtrl1 = inb(base+CNTRL1); // CNTRL0 output, BRDTST input, D output control = 0x82; if ( (stg_driver->dir_bits & 0x08) == 0)// if port D is set as input, set bits accordingly control = 0x8b; outb(0xff, base + CNTRL1); // disable interrupts outb(control, base + D_DIR); // set port D direction, also resets CNTRL0 outb(tempCtrl0, base + CNTRL0); outb( (tempCtrl1 & 0x0f) | 0xf0, base + CNTRL1); } return 0; } /*********************************************************************** * ACTION FUNCTIONS * * these do the actual data exchange with the board * ************************************************************************/ static void stg_counter_latch(int i) { outb(0x03, CTRL(i)); } /* stg_counter_read() - reads one channel FIXME - todo, extend to 32 bits in software works the same for both cards (STG & STG2) */ static long stg_counter_read(int i) { union pos_tag { long l; struct byte_tag { char b0; char b1; char b2; char b3; } byte; } pos; pos.byte.b0 = inb(DATA(i)); pos.byte.b1 = inb(DATA(i)); pos.byte.b2 = inb(DATA(i)); if (pos.byte.b2 < 0) { pos.byte.b3 = -1; } else { pos.byte.b3 = 0; } return pos.l; } static void stg1_select_index_axis(void *arg, unsigned int channel) { stg_struct *stg = arg; unsigned char byIntc,byAxis; unsigned char byPol = 1; if (stg->model == 1) { /* * Set polarity to low active if that is requested */ if( *(stg->index_polarity[channel]) == 0 ) { byPol = 0; } /* Stg manual p. 21: "The bits are level triggered and cannot be reset if they are active" * So it is save to reset them and only those which are really active will remain */ stg1_reset_index_latch(arg, channel); // routine for Model 1 // initialize stuff to poll index pulse byAxis = channel; byAxis &= 0x6; // ignore low bit, we check 2 axes at a time byAxis <<= 3; // shift into position for IXS1, IXS0 byIntc = inb(base + INTC); // get a copy of INTC, we'll change // some bits in it, not all byIntc &= ~(IXLVL | IXS1 | IXS0); // zero bits for axis and polarity byIntc |= byAxis; // put axes address in INTC if (byPol != 0) // is index pulse active high? byIntc |= IXLVL; outb(byIntc, base + INTC); } } static void stg2_select_index_axes( void *arg, unsigned char mask ) { /* stg2 manual, p.21 * writing 0 to the corresponding bit disables the index pulse * writing 1 enables it */ outb( mask, base + IDLEN ); return; } /* Note that for stg1 cards this function will clear the index pulse latches for the two selected axes, */ static void stg1_reset_index_latch(void *arg, unsigned int channel) { stg_struct *stg = arg; if (stg->model == 1) { // routine for Model 1 inb(base + ODDRST); //reset index pulse latch for ODD axis inb(base + BRDTST); //reset index pulse latch for EVEN axis } return; } static void stg2_reset_all_index_latches( void *arg ) { stg_struct *stg = arg; if( stg->model == 2 ) { /* * stg2 manual p.22, * writing 0 to IDL resets the index latch, * writing 1 has no effect */ outb( 0x00, base + IDL); } return; } unsigned char stg1_get_current_IRR(void) { outb(base + OCW3, 0x0a); // IRR on next read return inb(base + IRR); } static unsigned short stg1_get_index_pulse_latch(void *arg, unsigned int chan) { // routine for Model 1 board stg_struct *stg = arg; unsigned char byIRR, byAxisMask; if (stg->model == 1){ // routine for Model 1 byIRR = stg1_get_current_IRR(); byAxisMask = (chan & 1) ? LIXODD : LIXEVN; // even or odd axis? if (byIRR & byAxisMask) // check latched index pulse return 1; return 0; } else if (stg->model == 2) { //FIXME: return if latched index pulse is there return 0; } return 0; } static unsigned char stg2_get_all_index_pulse_latches( void *arg ) { stg_struct *stg = arg; unsigned char indexRegister = 0; if( stg-> model == 2 ) indexRegister = inb( base + IDL ); return indexRegister; } /* stg_dac_write() - writes a dac channel works the same for both cards (STG & STG2) */ static int stg_dac_write(int ch, short value) { /* write the DAC */ outw(value, base + DAC_0 + (ch << 1)); return 0; } int stg_adc_start(void *arg, unsigned short wAxis) { stg_struct *stg; unsigned char tempCtrl0; stg = arg; if (stg->model == 1) { /* do a dummy read from the ADC, just to set the input multiplexer to the right channel */ inw(base + ADC_0 + (wAxis << 1)); /* wait 4 uS for settling time on the multiplexer and ADC. You probably shouldn't really have a delay in a driver */ outb(0, 0x80); outb(0, 0x80); outb(0, 0x80); outb(0, 0x80); /* now start conversion */ outw(0, base + ADC_0 + (wAxis << 1)); } else { //model STG2 tempCtrl0 = inb(base+CNTRL0) & 0x07; // save IRQ tempCtrl0 |= (wAxis << 4) | 0x88; //autozero & cal cycle outb(tempCtrl0, base + CNTRL0); // select channel /* wait 4 uS for settling time on the multiplexer and ADC. You probably shouldn't really have a delay in a driver */ outb(0, 0x80); outb(0, 0x80); outb(0, 0x80); outb(0, 0x80); /* now start conversion */ outw(0, base + ADC_0); } return 0; }; static short stg_adc_read(void *arg, int axis) { short j; stg_struct *stg; stg = arg; /* there must have been a delay between stg_adc_start() and stg_adc_read(), of 19 usec if autozeroing (we are), 4 usecs otherwise. In code that calls this, make sure you split these calls up with some intervening code */ if (stg->model == 1) { /* make sure conversion is done, assume polling delay is done. EOC (End Of Conversion) is bit 0x08 in IIR (Interrupt Request Register) of Interrupt Controller. Don't wait forever though bail out eventually. */ for (j = 0; !(inb(base + IRR) & 0x08) && (j < 1000); j++); j = inw(base + ADC_0 + (axis << 1)); } else { //model 2 for (j = 0; (inb(base + BRDTST) & 0x08) && (j < 1000); j++); j = inw(base + ADC_0 + (axis << 1)); } if (j & 0x1000) /* is sign bit negative? */ j |= 0xf000; /* sign extend */ else j &= 0xfff; /* make sure high order bits are zero. */ return j; }; /*********************************************************************** * BOARD INIT FUNCTIONS * * functions for autodetec, irq init * ************************************************************************/ static int stg_set_interrupt(short interrupt) { unsigned char tempINTC; if (stg_driver->model == 1) tempINTC=0x80; else tempINTC=0x88;//also CAL low, don|t want ADC to calibrate switch (interrupt) { case 3: break; case 5: tempINTC |= 4;break; case 7: tempINTC |= 2;break; case 9: tempINTC |= 6;break; case 10: tempINTC |= 5;break; case 11: tempINTC |= 7;break; case 12: tempINTC |= 3;break; case 15: tempINTC |= 1;break; default: tempINTC |= 4;break; } if (stg_driver->model == 1) outb(tempINTC, base + INTC); else outb(tempINTC, base + CNTRL0); return 0; } static int stg_init_card() { int msg; msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* * If both stg card model and base address are set * then no autodetecting is necessary. * Else we need to autodetect */ if ( (model != 0) && (base != 0) ) { stg_driver->model = model; } else { base = stg_autodetect(); } /* * Now check if the settings for a card a ok */ if ( (base == 0x00) || (stg_driver->model == 0) ) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: no stg1 or stg2 card could be initialised\n"); return -ENODEV; } if (stg_driver->model == 1) { /* * STG1 */ // initialize INTC as output outb(0x92, base + MIO_2); stg_set_interrupt(5); // initialize it to smthg, we won't use it anyways outb(0x1a, base + ICW1); // initialize the 82C59 as single chip (STG docs say so:) outb(0x00, base + ICW2); // ICW2 not used, must init it to 0 outb(0xff, base + OCW1); // mask off all interrupts rtapi_print_msg(RTAPI_MSG_INFO, "STG: Initialised stg%1d card at address %x\n", stg_driver->model, base); } else if (stg_driver->model == 2 ) { /* * STG2 */ outb(0x8b, base + D_DIR); // initialize CONTRL0 output, BRDTST input /* stg2 manual, p.21 * writing 0 to the corresponding bit disables the index pulse * writing 1 enables it */ outb( 0x00, base + IDLEN ); /* stg2 manual, p.21 * writing 0 to the corresponding bit selects the index pulse to latch the counter * writing 1 to the corresponding bit selects EXLATCH to latch the counter */ outb( 0x00, base + SELDI ); stg_set_interrupt(5); // initialize it to something, we won't use it anyways rtapi_print_msg(RTAPI_MSG_INFO, "STG: Initialised stg%1d card at address %x\n", stg_driver->model, base); } else { rtapi_print_msg(RTAPI_MSG_ERR, "STG: ERROR: The model stg%1d is not correct\n", stg_driver->model ); return -ENODEV; } /* restore saved message level */ rtapi_set_msg_level(msg); // all ok return 0; } /* scans possible addresses for STG cards */ unsigned short stg_autodetect() { short i, j, k, ofs; unsigned short address; unsigned short retval = 0; int msg; msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* search all possible addresses */ for (i = 15; i >= 0; i--) { address = i * 0x20 + 0x200; /* does jumper = i? */ //if ((inb(address + BRDTST) & 0x0f) == i) { // by Xuecheng, not necessary k = 0; // var for getting the serial for (j = 0; j < 8; j++) { ofs = (inb(address + BRDTST) >> 4); if (ofs & 8) { /* is SER set? */ ofs = ofs & 7; /* mask for Q2,Q1,Q0 */ k += (1 << ofs); /* shift bit into position specified by Q2, Q1, Q0 */ } } if (k == 0x75) { rtapi_print_msg(RTAPI_MSG_INFO, "STG: Autodetected stg1 card at address %x\n", address); stg_driver->model=1; retval = address; /* SER sequence is 01110101 */ break; } if (k == 0x74) { rtapi_print_msg(RTAPI_MSG_INFO, "STG: Autodetected stg2 card at address %x\n", address); stg_driver->model=2; retval = address; break; } //} } if ( ( retval == 0 ) || ( stg_driver->model == 0 ) ) { rtapi_print_msg(RTAPI_MSG_ERR, "STG: stg_autodetect() did not find any stg1 or stg2 card\n"); } /* restore saved message level */ rtapi_set_msg_level(msg); return retval; } /*********************************************************************** * LOCAL FUNCTION DEFINITIONS * * these are functions used for exporting various HAL pins/prams * ************************************************************************/ static int export_counter(int num, stg_struct *addr) { int retval, msg; /* This function exports a lot of stuff, which results in a lot of logging if msg_level is at INFO or ALL. So we save the current value of msg_level and restore it later. If you actually need to log this function's actions, change the second line below */ msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* export pin for counts captured by update() */ retval = hal_pin_s32_newf(HAL_OUT, &addr->count[num], comp_id, "stg.%d.counts", num); if (retval != 0) { return retval; } /* export pin for scaled position captured by update() */ retval = hal_pin_float_newf(HAL_OUT, &addr->pos[num], comp_id, "stg.%d.position", num); if (retval != 0) { return retval; } /* export parameter for scaling */ retval = hal_param_float_newf(HAL_RW, &addr->pos_scale[num], comp_id, "stg.%d.position-scale", num); if (retval != 0) { return retval; } /* export pin for index homing */ retval = hal_pin_bit_newf(HAL_IO, &addr->index_enable[num], comp_id, "stg.%d.index-enable", num); if (retval != 0) { return retval; } /* export pin for reading the index latch */ retval = hal_pin_bit_newf(HAL_OUT, &addr->index_latch[num], comp_id, "stg.%d.index-latch", num); if (retval != 0) { return retval; } /* * The index polarity is configurable for the stg1 cards only, * but not for the stg2 cards */ if( addr->model == 1 ) { /* export read only HAL pin for index pulse polarity */ retval = hal_pin_bit_newf(HAL_IN, &addr->index_polarity[num], comp_id, "stg.%d.index-polarity", num); if (retval != 0) { return retval; } } /* restore saved message level */ rtapi_set_msg_level(msg); return 0; } static int export_dac(int num, stg_struct *addr) { int retval, msg; /* This function exports a lot of stuff, which results in a lot of logging if msg_level is at INFO or ALL. So we save the current value of msg_level and restore it later. If you actually need to log this function's actions, change the second line below */ msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* export pin for voltage received by the board() */ retval = hal_pin_float_newf(HAL_IN, &addr->dac_value[num], comp_id, "stg.%d.dac-value", num); if (retval != 0) { return retval; } /* export parameter for offset */ retval = hal_param_float_newf(HAL_RW, &addr->dac_offset[num], comp_id, "stg.%d.dac-offset", num); if (retval != 0) { return retval; } /* export parameter for gain */ retval = hal_param_float_newf(HAL_RW, &addr->dac_gain[num], comp_id, "stg.%d.dac-gain", num); if (retval != 0) { return retval; } /* restore saved message level */ rtapi_set_msg_level(msg); return 0; } static int export_adc(int num, stg_struct *addr) { int retval, msg; /* This function exports a lot of stuff, which results in a lot of logging if msg_level is at INFO or ALL. So we save the current value of msg_level and restore it later. If you actually need to log this function's actions, change the second line below */ msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* export pin for voltage received by the board() */ retval = hal_pin_float_newf(HAL_OUT, &addr->adc_value[num], comp_id, "stg.%d.adc-value", num); if (retval != 0) { return retval; } /* export parameter for offset */ retval = hal_param_float_newf(HAL_RW, &addr->adc_offset[num], comp_id, "stg.%d.adc-offset", num); if (retval != 0) { return retval; } /* export parameter for gain */ retval = hal_param_float_newf(HAL_RW, &addr->adc_gain[num], comp_id, "stg.%d.adc-gain", num); if (retval != 0) { return retval; } /* restore saved message level */ rtapi_set_msg_level(msg); return 0; } static int export_pins(int num, int dir, stg_struct *addr) { int retval, msg, i; /* This function exports a lot of stuff, which results in a lot of logging if msg_level is at INFO or ALL. So we save the current value of msg_level and restore it later. If you actually need to log this function's actions, change the second line below */ msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); for (i=0; i<8; i++) { if (dir != 0) retval=export_output_pin(outpinnum++, &(addr->port[num][i]) ); else retval=export_input_pin(inputpinnum++, &(addr->port[num][i]) ); if (retval != 0) { return retval; } } /* restore saved message level */ rtapi_set_msg_level(msg); return 0; } static int export_input_pin(int pinnum, io_pin * pin) { int retval; int msg; /* This function exports a lot of stuff, which results in a lot of logging if msg_level is at INFO or ALL. So we save the current value of msg_level and restore it later. If you actually need to log this function's actions, change the second line below */ msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* export read only HAL pin for input data */ retval = hal_pin_bit_newf(HAL_OUT, &(pin->data), comp_id, "stg.in-%02d", pinnum); if (retval != 0) { return retval; } /* export additional pin for inverted input data */ retval = hal_pin_bit_newf(HAL_OUT, &(pin->io.not), comp_id, "stg.in-%02d-not", pinnum); /* initialize HAL pins */ *(pin->data) = 0; *(pin->io.not) = 1; /* restore saved message level */ rtapi_set_msg_level(msg); return retval; } static int export_output_pin(int pinnum, io_pin * pin) { int retval; int msg; /* * This function exports a lot of stuff, which results in a lot of * logging if msg_level is at INFO or ALL. So we save the current value * of msg_level and restore it later. If you actually need to log this * function's actions, change the second line below */ msg = rtapi_get_msg_level(); rtapi_set_msg_level( STG_MSG_LEVEL ); /* export read only HAL pin for output data */ retval = hal_pin_bit_newf(HAL_IN, &(pin->data), comp_id, "stg.out-%02d", pinnum); if (retval != 0) { return retval; } /* export parameter for polarity */ retval = hal_param_bit_newf(HAL_RW, &(pin->io.invert), comp_id, "stg.out-%02d-invert", pinnum); /* initialize HAL pin and param */ *(pin->data) = 0; pin->io.invert = 0; /* restore saved message level */ rtapi_set_msg_level(msg); return retval; }