/* xuridiag.c * General Purpose USB Radio Interface Diagnostic Utility * * "Steven Henke" * All Rights Reserved. Copyright (C)2009 XELATEC LLC http://www.xelatec.com * * Derived from uridiag.c by Jim Dixon . * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * */ /* TO DO write sweep test for output linearity test input attenuation change chan_usbradio for audio taper input attenuation KNOWN LIMITATIONS, DEFECTS AND OMISSIONS */ #define DEBUGX 1 /* set 1 for development */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux #include #elif defined(__FreeBSD__) #include #else #include #endif #define C108_VENDOR_ID 0x0d8c #define C108_PRODUCT_ID 0x000c #define C108A_PRODUCT_ID 0x013c #define C119A_PRODUCT_ID 0x013a #define VENDOR_ID_SMSC 0x0424 #define PRODUCT_ID_2514 0x2514 #define HID_REPORT_GET 0x01 #define HID_REPORT_SET 0x09 #define HID_RT_INPUT 0x01 #define HID_RT_OUTPUT 0x02 /* audio block size must match the frags value !!! */ #define AUDIO_BLOCKSIZE 4096 #define AUDIO_SAMPLES_PER_BLOCK (AUDIO_BLOCKSIZE / 4) #define NFFT 1024 #define NFFTSQRT 10 #define AUDIO_IN_SETTING 800 #define AUDIO_GETLEVEL_WAIT 1000000 #define MIXER_PARAM_MIC_PLAYBACK_SW "Mic Playback Switch" #define MIXER_PARAM_MIC_PLAYBACK_VOL "Mic Playback Volume" #define MIXER_PARAM_MIC_CAPTURE_SW "Mic Capture Switch" #define MIXER_PARAM_MIC_CAPTURE_VOL "Mic Capture Volume" #define MIXER_PARAM_MIC_BOOST "Auto Gain Control" #define MIXER_PARAM_SPKR_PLAYBACK_SW "Speaker Playback Switch" #define MIXER_PARAM_SPKR_PLAYBACK_VOL "Speaker Playback Volume" #define PASSBAND_LEVEL 550.0 #define STOPBAND_LEVEL 117.0 #define LOG_FILENAME "xuridiag.log" #define SIG_FILENAME "xuridiag.sig" #define MAX_URIS 32 #define STRLEN_MED 32 #define STRLEN_LONG 128 int verbose=0; /* how verbose to make the output */ int onboardsnd=1; /* set 1 if the host server has onboard sound */ char firstonly=0; const char caProgramVersion[]="00.06"; // PROGRAM VERSION /* information about each connected USB Radio Interface */ struct probe_type { int maxval; int minval; int deviation; int result; }; struct uri { int devnum; // device or card number /dev/dspX struct usb_device *dev; char devstr[STRLEN_MED]; int idVendor; int idProduct; struct usb_dev_handle *handle; pthread_t soundthread; int soundfd; int shutdown; int valid; int dirty; int pass; int errorcode; int spkrmax; int micmax; float freq1; /* output frequency in Hz */ float freq2; int outvala; /* output amplitude 0-1000 */ float dacadjusta; short int idacadjusta; int spkra; int outvalb; float dacadjustb; short int idacadjustb; int spkrb; int mic; int bitsneg; int bitspos; int bitspp; int tgena; int tgenb; int sigcap; /* capture stream to file */ int state; int ptt; int cor; int ledgreen; int ledyellow; int ledflash; int pttcor; /* test results -1 unknown, 0 fail, 1 ok*/ int noisefloor; int loopback1; int loopback2; }; struct uri uris[MAX_URIS]; struct probe_type probe; char strser[STRLEN_LONG]; /* board serial number string */ struct tonevars { float mycr; float myci; } ; int lognum=12; float logstep[]={1, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2}; enum {DEV_C108,DEV_C108AH,DEV_C119A}; char *devtypestrs[] = {"CM108","CM108AH", "CM119A"} ; void cdft(int, int, double *, int *, double *); float myfreq1 = 0.0, myfreq2 = 0.0,lev = 0.0,lev1 = 0.0,lev2 = 0.0; /* upper 16 bits is the maximum number of fragments to be allocated lower 16 bits is the fragment size. The fragment size cannot be larger than half of the available buffer space. */ unsigned int frags = ( ( (6 * 5) << 16 ) | 0xc ); //unsigned int frags = ( ( (4) << 16 ) | 0xc ); int numuris=-1; int foundsmsc=0; int devtype = 0; int devnum = -1; int logcap=1; /* log file */ FILE *hlogfile=NULL; int sigcap=1; /* sample stream */ FILE *hsigfile=NULL; int sigcapnow=0; /* turn on sample stream only during a test */ char sigcapchan = 2; /* Call with: devnum: alsa major device number, param: ascii Formal Parameter Name, val1, first or only value, val2 second value, or 0 if only 1 value. Values: 0-99 (percent) or 0-1 for baboon. Note: must add -lasound to end of linkage */ int shutdown = 0; int urinow = 0; char uutsn[STRLEN_MED]; char uline[STRLEN_LONG]; char testpos[STRLEN_MED]; /* test position */ /* Function Declarations */ int stopall (void); int paktc (void); /* Functions */ int FormatTimeStamp (time_t timeTime, char *caTime) { struct tm *tmTime; char caTemp[64]; tmTime = localtime(&timeTime); //sprintf(caTime, "%4d/%02d/%02d ", 1900+tmTime->tm_year,1+tmTime->tm_mon,tmTime->tm_mday); sprintf(caTime, "%04d/%02d/%02d ", 1900+tmTime->tm_year,1+tmTime->tm_mon,tmTime->tm_mday); sprintf(caTemp, "%02d:%02d:%02d ", tmTime->tm_hour,tmTime->tm_min,tmTime->tm_sec); strcat(caTime,caTemp); return(0); } /* opt 0=no extra, 1=timestamp, 2=timestamp and serial number */ static int logline(char *str,int opt) { if(hlogfile>0) { char caTemp[STRLEN_LONG],lbuf[STRLEN_LONG]; if(opt) { FormatTimeStamp (time(NULL), caTemp); if(opt==2) sprintf(lbuf,"%s SN:%s %s",caTemp, uutsn, str); else sprintf(lbuf,"%s %s",caTemp, str); } else { sprintf(lbuf,"%s",str); } fwrite(lbuf,1,strlen(lbuf),hlogfile); fflush(hlogfile); } return 0; } /* determine device mixer maximum setting values and other parameters */ static int amixer_max(int devnum, char *param) { int rv,type; char str[100]; snd_hctl_t *hctl; snd_ctl_elem_id_t *id; snd_hctl_elem_t *elem; snd_ctl_elem_info_t *info; sprintf(str,"hw:%d",devnum); if (snd_hctl_open(&hctl, str, 0)) return(-1); snd_hctl_load(hctl); snd_ctl_elem_id_alloca(&id); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_name(id, param); elem = snd_hctl_find_elem(hctl, id); if (!elem) { snd_hctl_close(hctl); return(-1); } snd_ctl_elem_info_alloca(&info); snd_hctl_elem_info(elem,info); type = snd_ctl_elem_info_get_type(info); rv = 0; switch(type) { case SND_CTL_ELEM_TYPE_INTEGER: rv = snd_ctl_elem_info_get_max(info); break; case SND_CTL_ELEM_TYPE_BOOLEAN: rv = 1; break; } snd_hctl_close(hctl); return(rv); } /* Call with: devnum: alsa major device number, param: ascii Formal Parameter Name, val1, first or only value, val2 second value, or 0 if only 1 value. Values: 0-99 (percent) or 0-1 for baboon. Note: must add -lasound to end of linkage */ static int setamixer(int devnum, char *param, int v1, int v2) { int type; char str[100]; snd_hctl_t *hctl; snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; snd_hctl_elem_t *elem; snd_ctl_elem_info_t *info; sprintf(str,"hw:%d",devnum); if (snd_hctl_open(&hctl, str, 0)) return(-1); snd_hctl_load(hctl); snd_ctl_elem_id_alloca(&id); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_name(id, param); elem = snd_hctl_find_elem(hctl, id); if (!elem) { snd_hctl_close(hctl); return(-1); } snd_ctl_elem_info_alloca(&info); snd_hctl_elem_info(elem,info); type = snd_ctl_elem_info_get_type(info); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_value_set_id(control, id); switch(type) { case SND_CTL_ELEM_TYPE_INTEGER: snd_ctl_elem_value_set_integer(control, 0, v1); if (v2 > 0) snd_ctl_elem_value_set_integer(control, 1, v2); break; case SND_CTL_ELEM_TYPE_BOOLEAN: snd_ctl_elem_value_set_integer(control, 0, (v1 != 0)); break; } if (snd_hctl_elem_write(elem, control)) { snd_hctl_close(hctl); return(-1); } snd_hctl_close(hctl); return(0); } static void set_outputs(struct usb_dev_handle *handle, unsigned char *outputs) { usleep(1500); usb_control_msg(handle, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, HID_REPORT_SET, 0 + (HID_RT_OUTPUT << 8), 3, (char*)outputs, 4, 5000); } /* set USB GPIO3 OUTPUT (PTT) */ static void setptt(struct usb_dev_handle *usb_handle,unsigned char c) { unsigned char buf[4]; buf[0] = buf[3] = 0; buf[2] = 0x04; /* set GPIO3 as output */ if(c) buf[1] = 0x04; else buf[1] = 0x00; usb_control_msg(usb_handle, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, HID_REPORT_SET, 0 + (HID_RT_OUTPUT << 8), 3, (char*)buf, 4, 5000); } static void setout(struct usb_dev_handle *usb_handle,unsigned char c) { unsigned char buf[4]; buf[0] = buf[3] = 0; buf[2] = 0x04; /* set GPIO 3 as output */ buf[1] = c; /* set GPIO 3 outputs as requested */ set_outputs(usb_handle,buf); usleep(100000); } static void get_inputs(struct usb_dev_handle *handle, unsigned char *inputs) { usleep(1500); usb_control_msg(handle, USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE, HID_REPORT_GET, 0 + (HID_RT_INPUT << 8), 3, (char*)inputs, 4, 5000); } unsigned char getin(struct usb_dev_handle *usb_handle) { unsigned char buf[4]; unsigned short c; buf[0] = buf[1] = 0; get_inputs(usb_handle,buf); c = buf[1] & 0xf; c += (buf[0] & 3) << 4; /* in the AN part, the HOOK comes in on buf[0] bit 4, undocumentedly */ if (devtype == DEV_C108AH) { c &= 0xfd; if (!(buf[0] & 0x10)) c += 2; } return(c); } /* Get USB VOLDN Input (COR) */ unsigned char getcor(struct usb_dev_handle *usb_handle) { unsigned char buf[4]; unsigned short c; buf[0] = buf[1] = 0; usleep(1500); usb_control_msg(usb_handle, USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE, HID_REPORT_GET, 0 + (HID_RT_INPUT << 8), 3, (char*)buf, 4, 5000); c = buf[0]&0x02; if(c) c=0; else c=1; return(c); } /* from USB Product ID return an index to the Product string */ static int id2type(int idProduct) { int retval=-1; if(idProduct==0x000c)retval=0; if(idProduct==0x013c)retval=1; if(idProduct==0x013a)retval=2; return retval; } /* Scan USB bus, find USB Radio devices. */ static int device_init(void) { struct usb_bus *usb_bus; struct usb_device *dev; char devstr[200],str[200],desdev[200],*cp; int i,retval; FILE *fp; foundsmsc=0; numuris=0; for(i=0;inext) { for (dev = usb_bus->devices; dev; dev = dev->next) { if ( (dev->descriptor.idVendor == VENDOR_ID_SMSC) && (dev->descriptor.idProduct == PRODUCT_ID_2514) ) { foundsmsc=1; if(verbose) printw("Found 2514 USB Hub\n"); } if ((dev->descriptor.idVendor == C108_VENDOR_ID) && ((dev->descriptor.idProduct == C108_PRODUCT_ID) || (dev->descriptor.idProduct == C108A_PRODUCT_ID) || (dev->descriptor.idProduct == C119A_PRODUCT_ID) )) { sprintf(devstr,"%s/%s", usb_bus->dirname,dev->filename); for(i = 0; i < MAX_URIS; i++) { sprintf(str,"/proc/asound/card%d/usbbus",i); fp = fopen(str,"r"); if (!fp) continue; if ((!fgets(desdev,sizeof(desdev) - 1,fp)) || (!desdev[0])) { fclose(fp); continue; } fclose(fp); if (desdev[strlen(desdev) - 1] == '\n') desdev[strlen(desdev) -1 ] = 0; if (strcasecmp(desdev,devstr)) continue; if (i) sprintf(str,"/sys/class/sound/dsp%d/device",i); else strcpy(str,"/sys/class/sound/dsp/device"); memset(desdev,0,sizeof(desdev)); if (readlink(str,desdev,sizeof(desdev) - 1) == -1) { sprintf(str,"/sys/class/sound/controlC%d/device",i); memset(desdev,0,sizeof(desdev)); if (readlink(str,desdev,sizeof(desdev) - 1) == -1) continue; } cp = strrchr(desdev,'/'); if (cp) *cp = 0; else continue; cp = strrchr(desdev,'/'); if (!cp) continue; cp++; break; } if (i >= MAX_URIS) continue; numuris++; uris[i].devnum = i; uris[i].dev=dev; uris[i].idVendor=dev->descriptor.idVendor; uris[i].idProduct=dev->descriptor.idProduct; memset(uris[i].devstr,0,STRLEN_MED); memcpy(uris[i].devstr,cp,strlen(cp)); uris[i].valid=1; if(verbose) printw("Found %s USB Radio Interface device /dev/dsp%i at %s\n", devtypestrs[id2type(uris[i].idProduct)],i,cp); //return dev; } } } return numuris; } /* pass integer and return char 1 if non-zero */ static inline char *baboons(int v) { if (v) return "1"; return "0"; } static int dioerror(unsigned char got, unsigned char should) { unsigned char err = got ^ should; int n = 0; if (err & 0x2) { printw("Error on GPIO1/GPIO2, got %s, should be %s\n", baboons(got & 2),baboons(should & 2)); n++; } if (err & 0x10) { printw("Error on GPIO3/PTT/COR IN, got %s, should be %s\n", baboons(got & 0x10),baboons(should & 0x10)); n++; } if (err & 0x20) { printw("Error on GPIO4/TONE IN, got %s, should be %s\n", baboons(got & 0x20),baboons(should & 0x20)); n++; } return(n); } static int testio(struct usb_dev_handle *usb_handle,unsigned char toout, unsigned char toexpect) { unsigned char c; setout(usb_handle,toout); /* should readback 0 */ c = getin(usb_handle) & 0xf2; return(dioerror(c,toexpect)); } /* USB Device Audio Output Block Generation tone generation is done here when called, a block of samples is generated and sent to the audio device the output amplitude is fixed at full scale. */ static int outaudio(struct uri *myuri) // static int outaudio(int fd,float freq1, float freq2) { unsigned short buf[AUDIO_SAMPLES_PER_BLOCK * 2]; float f; int i; static float phase1,phase2; float phasestep1,phasestep2; int fd = myuri->soundfd; float freq1=myuri->freq1; float freq2=myuri->freq2; phasestep1=freq1*2.0*M_PI/48000.0; phasestep2=freq2*2.0*M_PI/48000.0; for(i = 0; i < AUDIO_SAMPLES_PER_BLOCK * 2; i += 2) { if (freq1 > 0.0) { phase1+=phasestep1; if(phase1>=(2.0*M_PI))phase1-=(2.0*M_PI); f=sin(phase1); buf[i] = f * myuri->idacadjusta; // printw("%i %6.0f %6.3f 0x%05x %6.0f %6.0f %i\n",urinow,freq1,f,buf[i],ddr1,ddi1,uris[urinow].idacadjusta ); } else buf[i] = 0; if (freq2 > 0.0) { phase2+=phasestep2; if(phase2>=(2.0*M_PI))phase2-=(2.0*M_PI); f=sin(phase2); buf[i + 1] = f * myuri->idacadjustb; } else buf[i + 1] = 0; } if (write(fd,buf,AUDIO_BLOCKSIZE) != AUDIO_BLOCKSIZE) { if(verbose){ printw("ERROR: WRITE BLOCK\n"); } return(-1); } return 0; } /* open the sound device */ static int soundopen(int devicenum) { int fd,res,fmt,desired; char device[200]; if(verbose>=2) printw("soundopen(%i) start\n",devicenum); strcpy(device,"/dev/dsp"); if (devicenum) sprintf(device,"/dev/dsp%d",devicenum); fd = open(device, O_RDWR | O_NONBLOCK); if (fd < 0) { printw("Unable to re-open DSP device %d: %s\n", devicenum,device); return -1; } #if __BYTE_ORDER == __LITTLE_ENDIAN fmt = AFMT_S16_LE; #else fmt = AFMT_S16_BE; #endif res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); if (res < 0) { printw("Unable to set format to 16-bit signed\n"); return -1; } res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); /* Check to see if duplex set (FreeBSD Bug) */ res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt); if ((res != 0) || (!(fmt & DSP_CAP_DUPLEX))) { printw("Doesn't have full duplex mode\n"); return -1; } fmt = 1; res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt); if (res < 0) { printw("Failed to set audio device to mono\n"); return -1; } fmt = desired = 48000; res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt); if (res < 0) { printw("Failed to set audio device to 48k\n"); return -1; } if (fmt != desired) { printw("Requested %d Hz, got %d Hz -- sound may be choppy\n", desired, fmt); } /* * on Freebsd, SETFRAGMENT does not work very well on some cards. * Default to use 256 bytes, let the user override */ if (frags) { fmt = frags; res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt); if (res < 0) { printw("Unable to set fragment size -- sound may be choppy\n"); } } /* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */ res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res); /* it may fail if we are in half duplex, never mind */ return fd; } /* the sound thread generate tones and send them to the output tone frequencies are set by the global variables. take the input samples and measure their amplitudes the measured input level is set in global variables. */ void *soundthread(void *arg) { int fd; struct uri *myuri = (struct uri *) arg; if(verbose>=2) printw("soundthread() start for device %i\n",myuri->devnum); myuri->soundfd = fd = soundopen(myuri->devnum); myuri->micmax = amixer_max(myuri->devnum,MIXER_PARAM_MIC_CAPTURE_VOL); myuri->spkrmax = amixer_max(myuri->devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL); setamixer(myuri->devnum,MIXER_PARAM_MIC_PLAYBACK_SW,0,0); setamixer(myuri->devnum,MIXER_PARAM_MIC_PLAYBACK_VOL,0,0); setamixer(myuri->devnum,MIXER_PARAM_SPKR_PLAYBACK_SW,1,0); myuri->spkra=myuri->spkrb=myuri->spkrmax; setamixer(myuri->devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,myuri->spkra,myuri->spkrb); setamixer(myuri->devnum,MIXER_PARAM_MIC_CAPTURE_VOL, AUDIO_IN_SETTING * myuri->micmax / 1000,0); setamixer(myuri->devnum,MIXER_PARAM_MIC_BOOST,0,0); setamixer(myuri->devnum,MIXER_PARAM_MIC_CAPTURE_SW,1,0); // printw("devnum=%i micmax=%i spkrmax=%i \n",devnum,micmax,spkrmax); while(!myuri->shutdown) { fd_set rfds,wfds; int res; char buf[AUDIO_BLOCKSIZE]; float mylev,mylev1,mylev2; FD_ZERO(&rfds); FD_ZERO(&wfds); FD_SET(fd,&rfds); FD_SET(fd,&wfds); res = select(fd + 1,&rfds,&wfds,NULL,NULL); if (!res) continue; if (res < 0) { perror("poll"); exit(255); } if (FD_ISSET(fd,&wfds)) { // write tone output // outaudio(fd,myuri->freq1,myuri->freq2); outaudio(myuri); continue; } if (FD_ISSET(fd,&rfds)) { short *sbuf = (short *) buf; // points to the raw input samples static double afft[(NFFT + 1) * 2 + 1],wfft[NFFT * 5 / 2]; float buck; float gfac; static int ipfft[NFFTSQRT + 2],i; res = read(fd,buf,AUDIO_BLOCKSIZE); if (res < AUDIO_BLOCKSIZE) { printw("Warning: Short read in soundthread.\n"); continue; } /* get peak value from the raw input data */ for(i = 0; i < res / 2; i++) { if(sbuf[i] > myuri->bitspos)myuri->bitspos=sbuf[i]; if(sbuf[i] < myuri->bitsneg)myuri->bitsneg=sbuf[i]; } myuri->bitspp=myuri->bitspos-myuri->bitsneg; if(hsigfile>=0 && sigcapnow && myuri->devnum==sigcapchan) { fwrite(sbuf,1,res,hsigfile); } #if 0 memset(afft,0,sizeof(double) * 2 * (NFFT + 1)); /* gfac or gain factor adjusts for device characteristics */ gfac = 1.0; if (devtype == DEV_C108AH) gfac = 0.7499; for(i = 0; i < res / 2; i++) { sbuf[i] = (int) (((float)sbuf[i] + 32768) * gfac) - 32768; } for(i = 0; i < NFFT * 2; i += 2) { afft[i] = (double)(sbuf[i] + 32768) / (double)65536.0; } ipfft[0] = 0; cdft(NFFT * 2,-1,afft,ipfft,wfft); mylev = 0.0; mylev1 = 0.0; mylev2 = 0.0; for(i = 1; i < NFFT / 2; i++) { float ftmp; ftmp = (afft[i * 2] * afft[i * 2]) + (afft[i * 2 + 1] * afft[i * 2 + 1]); mylev += ftmp; buck = (float) i * 46.875; if (myfreq1 > 0.0) { if (fabs(buck - myfreq1) < 1.5 * 46.875) mylev1 += ftmp; } if (myfreq2 > 0.0) { if (fabs(buck - myfreq2) < 1.5 * 46.875) mylev2 += ftmp; } } lev = (sqrt(mylev) / (float) (NFFT / 2)) * 4096.0; lev1 = (sqrt(mylev1) / (float) (NFFT / 2)) * 4096.0; lev2 = (sqrt(mylev2) / (float) (NFFT / 2)) * 4096.0; #endif } } close(fd); pthread_exit(NULL); } static int digital_test(struct usb_dev_handle *usb_handle) { int nerror = 0; printw("Testing digital I/O (PTT,COR,TONE and GPIO)....\n"); nerror += testio(usb_handle,8,0); /* NONE */ nerror += testio(usb_handle,9,2); /* GPIO1 -> GPIO2 */ nerror += testio(usb_handle,0xc,0x10); /* GPIO3/PTT -> CTCSS */ nerror += testio(usb_handle,0,0x20); /* GPIO4 -> COR */ nerror += testio(usb_handle,8,0); /* NONE */ if (!nerror) printw("Digital I/O passed!!\n"); else printw("Digital I/O had %d errors!!\n",nerror); return(nerror); } #if 0 static int analog_test_one(float freq1,float freq2,float dlev1, float dlev2,int v) { int nerror = 0; myfreq1 = freq1; myfreq2 = freq2; printw("Testing Analog at %1.f (and %1.f) Hz...\n",freq1,freq2); usleep(1000000); if (fabs(lev1 - dlev1) > (dlev1 * 0.2)) { printw("Analog level on left channel for %.1f Hz (%.1f) is out of range!!\n",freq1,lev1); printw("Must be between %.1f and %.1f\n",dlev1 * .8, dlev1 * 1.2); nerror++; } else if (v) printw("Left channel level %.1f OK at %.1f Hz\n",lev1,freq1); if (fabs(lev2 - dlev2) > (dlev2 * 0.2)) { printw("Analog level on right channel for %.1f Hz (%.1f) is out of range!!\n",freq2,lev2); printw("Must be between %.1f and %.1f\n",dlev2 * .8, dlev2 * 1.2); nerror++; } else if (v) printw("Right channel level %.1f OK at %.1f Hz\n",lev2,freq2); return(nerror); } static int analog_test(int v) { int nerror = 0; nerror += analog_test_one(204.0,700.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(504.0,700.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(1004.0,700.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(2004.0,700.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(3004.0,700.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(5004.0,700.0,STOPBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(700.0,204.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(700.0,504.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(700.0,1004.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(700.0,2004.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(700.0,3004.0,PASSBAND_LEVEL,PASSBAND_LEVEL,v); nerror += analog_test_one(700.0,5004.0,PASSBAND_LEVEL,STOPBAND_LEVEL,v); if (!nerror) printw("Analog Test Passed!!\n"); return(nerror); } #endif int getlevel(int devnum) { char lbuf[STRLEN_LONG],rstr[STRLEN_LONG]; float freq; usleep(AUDIO_GETLEVEL_WAIT); uris[devnum].bitspos = uris[devnum].bitsneg = uris[devnum].bitspp = 0; if (uris[devnum].freq1>=1) freq=uris[devnum].freq1; else freq=uris[devnum].freq2; if (freq < 5.0) usleep(1000000); else if (freq < 100.0) usleep(500000); else if (freq < 500.0) usleep(200000); else usleep(110000); probe.result=0; if(uris[devnum].bitsppprobe.maxval) probe.result=1; #if 0 sprintf(lbuf,"dev freq1 outval spkr dack bitsneg bitspos bitspp %i %8.2f %5i %4i %6i %6i %6i %3i\n", devnum, uris[devnum].freq1, uris[devnum].outvala, uris[devnum].spkra,uris[devnum].idacadjusta, uris[devnum].bitsneg, uris[devnum].bitspos, uris[devnum].bitspp); #else sprintf(lbuf,"getlevel %i %7.1f %7.1f %3i %3i %5i %5i %7i %6i %6i %3i\n", devnum, uris[devnum].freq1, uris[devnum].freq2, uris[devnum].spkra, uris[devnum].spkrb, uris[devnum].idacadjusta,uris[devnum].idacadjustb, uris[devnum].bitsneg, uris[devnum].bitspos, uris[devnum].bitspp,probe.result); #endif if(verbose) { printw("%s",lbuf); logline(lbuf,0); } return uris[devnum].bitspp; } /* Calculate and optionally set the chip output attenuator and software factors. Input val is 0-1000 which should yield a linear 0 to full scale signal output. For CM119 devices which have a log/audio taper output this must be converted into linearized values for the device output attenuator and software multiplier. */ int calc_atten(int val, int devnum, int option) { const int maxval=1000; const int maxset=151; int a,kq,spkr; float db, dbpstep,dbremain,k; if(val>0) { dbpstep=-0.95; db=20*log10((float)(val)/(float)(maxval)); a=db/dbpstep; spkr=maxset-(a*4); dbremain=db-(a*dbpstep); k=pow(10,dbremain/20); kq=k*32767; } else { k=kq=spkr=a=dbremain=db=0; } //printw("val db a spkr k kq %4i %8.3f %8.3f %6i %6i %8.3f %6i\n",val,db,dbremain,a,spkr,k,kq); if(option) { uris[devnum].outvala=val; if(uris[devnum].spkra!=spkr) { uris[devnum].idacadjusta=0; usleep(50000); uris[devnum].spkra=spkr; setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,uris[devnum].spkra,0); } uris[devnum].idacadjusta=kq; } return 0; } /* just print the results of the calculation don't actually program the chip or measure the result */ int test_atten(int devnum) { int i; for(i=1000; i>=0; i-=10) { calc_atten(i,devnum,0); } return 0; } /* sweep the signal output with linearization */ int sweep_amp_lin(int devnum) { int i; uris[devnum].freq1=1004; uris[devnum].freq2=0; calc_atten(1000,devnum,1); usleep(2000000); sigcapnow=1; for(i=1000;i>=0;i-=10) { calc_atten(i,devnum,1); getlevel(devnum); } sigcapnow=0; return 0; } /* This function shows how the CM119 device output attenuator does not jump from value to value but rather automatically ramps or slews between values. This takes some time and must be considered when making measurements */ int ramp_amp(int devnum) { int i; uris[devnum].freq1=1004; uris[devnum].idacadjusta=32766; uris[devnum].idacadjustb=0; setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,i,0); sigcapnow=1; for(i=0;i<4;i++) { setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,151,0); usleep(4000000); setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,0,0); usleep(4000000); } sigcapnow=0; return 0; } /* sweep output attenuator amplitude and measure its transfer function show it as log or lin */ int sweep_amp(int devnum) { int i; uris[devnum].freq1=1004; uris[devnum].idacadjustb=0; uris[devnum].idacadjusta=0; setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,151,0); usleep(2000000); uris[devnum].idacadjusta=32766; uris[i].spkrb=0; sigcapnow=1; for(i=151;i>=0;i--) { uris[i].spkra = i; setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,uris[i].spkra,uris[i].spkrb); usleep(500000); getlevel(devnum); } sigcapnow=0; return 0; } /* Sweep the mic input attenuator and measure its transfer function. The CM119 has a log attenuator. */ int sweep_mic(int devnum) { int i,val; uris[devnum].freq1=1004; uris[devnum].idacadjusta=0; uris[devnum].idacadjustb=0; uris[i].spkra=135; uris[i].spkrb=0; setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,uris[i].spkra,uris[i].spkrb); usleep(2000000); uris[devnum].idacadjusta=32767; setamixer(devnum,MIXER_PARAM_MIC_CAPTURE_VOL,uris[devnum].micmax,0); sigcapnow=1; for(i=uris[devnum].micmax;i>=0;i--) { setamixer(devnum,MIXER_PARAM_MIC_CAPTURE_VOL,i,0); usleep(500000); getlevel(devnum); } sigcapnow=0; return 0; } /* Sweep the output in frequency and via a loopback plug measure the combined output and input transfer functions of the device. This should be sufficiently linear for both sub-audible channel coding, voice band and noise squelch operation. (e.g. 0.5 to 15,000 Hz +/- 3 dB) */ int sweep_freq(int devnum) { int i; uris[devnum].idacadjusta=32766; uris[devnum].idacadjustb=0; uris[devnum].spkra=uris[devnum].spkrmax; uris[devnum].spkrb=0; setamixer(devnum,MIXER_PARAM_SPKR_PLAYBACK_VOL,uris[devnum].spkra,uris[devnum].spkrb); uris[devnum].freq1=0; uris[devnum].freq2=0; usleep(500000); sigcapnow=1; for(i=0;i<(lognum*4);i++) { uris[devnum].freq1=logstep[i%lognum]*pow(10,(int)(i/lognum)); getlevel(devnum); } sigcapnow=0; return 0; } int test_loopback(int devnum, int channel) { int val,retval=0; uris[devnum].noisefloor=0; uris[devnum].loopback1=0; uris[devnum].loopback2=0; /* test no signal condition noise floor */ probe.minval=5; probe.maxval=67; uris[devnum].idacadjusta=uris[devnum].idacadjustb=0; uris[devnum].freq1=uris[devnum].freq2=0; val=getlevel(devnum); if(probe.result) uris[devnum].noisefloor=1; probe.minval=28000; probe.maxval=36000; /* do left / txmixa */ uris[devnum].freq1=1004; uris[devnum].idacadjusta=32766; val=getlevel(devnum); if(probe.result) uris[devnum].loopback1=1; // paktc(); // maw maw sph /* this is a little lower because of high freq rolloff */ uris[devnum].freq1=9040; val=getlevel(devnum); if(probe.result) uris[devnum].loopback1=3; /* this is a little higher because of the load reactance at low freqs */ uris[devnum].freq1=123; val=getlevel(devnum); if(probe.result) uris[devnum].loopback1=2; /* do right / txmixb */ uris[devnum].freq1=0; uris[devnum].freq2=1004; uris[devnum].idacadjusta=0; uris[devnum].idacadjustb=32766; val=getlevel(devnum); if(probe.result) uris[devnum].loopback2=1; /* this is a little higher because of the load reactance at low freqs */ uris[devnum].freq2=10; val=getlevel(devnum); if(probe.result) uris[devnum].loopback2=1; uris[devnum].idacadjusta=uris[devnum].idacadjustb=0; uris[devnum].freq1=uris[devnum].freq2=0; return (retval); } /* Loopback test of PTT and COR lines Very high impedance of COR line and means long delay times. When connected to a radio the impedance is much lower and response is much faster. */ int test_ptt_cor(int devnum) { int error=0; unsigned char c; struct usb_dev_handle *usb_handle; usb_handle=uris[devnum].handle; setptt(usb_handle,0); usleep(100000); c=getcor(usb_handle); if(c!=1)error=1; if(verbose) printw("dev %i PTT OFF inputs=0x%02x error=%i\n",devnum,c,error); setptt(usb_handle,1); usleep(100000); c=getcor(usb_handle); if(c!=0)error=1; if(verbose) printw("dev %i PTT ON inputs=0x%02x error=%i\n",devnum,c,error); usleep(100000); setptt(usb_handle,0); usleep(100000); c=getcor(usb_handle); if(c!=1)error=1; if(verbose) printw("dev %i PTT OFF inputs=0x%02x error=%i\n",devnum,c,error); if(error==0) uris[devnum].pttcor=1; else uris[devnum].pttcor=0; return(error); } /* Loopback test of PTT LED */ int test_ptt_led(int devnum) { int error=0; struct usb_dev_handle *usb_handle; usb_handle=uris[devnum].handle; if(verbose) printw("dev %i Testing PTT LED\n",devnum); setptt(usb_handle,0); usleep(50000); setptt(usb_handle,1); usleep(1000000); setptt(usb_handle,0); usleep(50000); return(error); } int claimem(void) { int i,tval,retval=0; if(verbose>=2) printw("claimem() start\n"); for(i=0;i=2) printw("startem() start\n"); for(i=0;i\n\n"); return (retval); } int testone(int devnum) { int retval=0; printw("\nTesting Device SN:%s Channel:%i\n",uutsn,devnum); refresh(); retval=test_ptt_cor(devnum); if(devnum==sigcapchan)sigcapnow=1; test_loopback(devnum,0); sigcapnow=0; if(verbose) { paktc(); clear(); } return retval; } int paktc(void) { int ch; printw("Press Any Key to Continue: "); refresh(); ch=getchar(); printw("%c\n",(char)ch); return(ch); } int testall(void) { int i,ch,tval,retval=0; clear(); printw("Start Test\n\n"); printw("Enter Device Serial Number: "); refresh(); getstr(uutsn); //printw("You entered: %s\n",uutsn); //paktc(); sprintf(uline,"xuridiag vers:%s Start \n",caProgramVersion); logline(uline,2); if(numuris) { if(verbose) printw("Shutting down previously registered device.\n"); stopall(); for(i=0;i0) { // usleeps allow buffers to flush in other threads ioctl(uris[devnum].soundfd, SNDCTL_DSP_RESET, 0); usleep(100000); ioctl(uris[devnum].soundfd, SNDCTL_DSP_RESET, 0); usleep(100000); close(uris[devnum].soundfd); usleep(100000); uris[devnum].soundfd=0; } uris[devnum].shutdown = 0; uris[devnum].state = 0; uris[devnum].soundthread=0; return retval; } int stopall(void) { int i,retval=0; for(i=0;i0) { printw("----------------------------------------------------\n"); printw("ERRORS DETECTED. BOARD FAILED TESTS. DO NOT PASS!\n"); printw("FORWARD TO REPAIR WITH ERROR CODE %i/%i \n",failcount,failcode); printw("----------------------------------------------------\n"); sprintf(uline,"Test Complete. FAIL. Error Code = %i/%i \n",failcount,failcode); } else { printw("BOARD PASSED TESTS. COMPLETE OTHER PROCESS STEPS.\n"); sprintf(uline,"Test Complete. PASSED. \n",failcount,failcode); } logline(uline,2); printw("\n"); printw("End of Report. "); paktc(); return retval; } /* Get and Parse Command Line Arguements */ void get_args(int argc, char** argv) { int i; float *x; int iTemp; char programarg[256]; strncpy(programarg,&argv[0][0],64); /* Start at i = 1 to skip the command name. */ for (i = 1; i < argc; i++) { /* Check for a leading "-" */ if(argv[i][0] == '?') { dohelp(0); exit(0); } if (argv[i][0] == '-') { /* Use the next character to decide what to do. */ switch (argv[i][1]) { case 'a': // *a_value = atoi(argv[++i]); // get an integer value break; case 'b': // *b_value = atof(argv[++i]); break; case 'c': // initialize config file and terminate break; case 'k': break; case '?': case 'h': dohelp(0); break; case 'i': break; case 'x': break; case 'd': break; case 's': break; case 't': break; case 'n': break; case 'e': //ulTimerExitDelay = atoi(argv[++i]); //iTemp = atoi(argv[++i]); //ulTimerExitDelay=iTemp; break; case 'm': break; case 'v': verbose=1; break; default: printf(stderr,"Unknown switch %s\n", argv[i]); } } } } /* ************************************************************************ main function loop with user interface */ int main(int argc, char **argv) { struct usb_dev_handle *usb_handle; int i,ch,retval = 1; char c; char uresp[64]; get_args(argc,argv); if(logcap>0) { hlogfile=fopen(LOG_FILENAME,"a"); if(hlogfile<0) { printw("ERROR: Unable to open log file %s\n\n",LOG_FILENAME); paktc(); exit(255); } } sprintf(uline,"xuridiag Version %s Start \n",caProgramVersion); logline(uline,1); for(i=0;i0) { hsigfile=fopen(SIG_FILENAME,"w"); } initscr(); /* Start curses mode */ for(;;) { char str[80]; //clear(); printw("xuridiag - USB Radio Interface Diagnostic Program Version %s \n\n",caProgramVersion); printw("h - help.\n"); if(verbose) printw("s - search for attached USB Radio Devices.\n"); printw("t - test an attached USB Radio Interface for proper operation.\n"); printw("q - quit program\n\n"); printw("Press the key for your selection: "); refresh(); ch=getchar(); printw("%c\n",(char)ch); refresh(); #if 0 //refresh(); printw("Enter Device Serial Number: "); getstr(uutsn); printw("You entered: %s\n",uutsn); //refresh(); /* Print it on to the real screen */ printw("Press any key to continue. "); getch(); /* Wait for user input */ #endif //def_prog_mode(); //endwin(); //reset_prog_mode() //refresh(); switch (ch) { case 'x': case 'q': goto exit; case 'h': dohelp(0); continue; case 'i': digital_test(usb_handle); continue; case 's': printw("\n"); if(numuris) { printw("Shutting down previously registered device.\n"); stopall(); for(i=0;i0) fclose(hlogfile); endwin(); /* End curses mode */ printf("\nxuridiag - program ends\n\n"); shutdown = 0; return retval; } /* end of file */