#include #include #include #include #include #include /* EDACS control channel monitoring program... */ /* Scanner / system specific stuff needed for "trunk tracking" mode */ static double baudrate=1200.0;/* baud rate of computer to scanner link*/ /* This is the table equating logical channel numbers with actual */ /* frequencies. The first entry, chan[0], gives the frequency for */ /* logical channel number 1; chan[1] give the frequency for LCN 2, */ /* et cetera. You will need to change this table to match those */ /* systems in your own area unless you happen to live in Huntsville */ /* Alabama and want to monitor this 5 channel commercial SMR trunk */ static double chan[30] = { 856.0375, 857.0375, 858.0375, 859.0375, 860.0375 }; /* this identifies the control channel in the above table */ int control = 1; /* this number is the lcn of the control channel - 1 */ int nucha = 20; /* number of channels in system; for the above */ /* system it should be set to five, but it was put to*/ /* 20 for people who don't want to change the default*/ /* global variables */ int lc=0; int fobp=0; /* pointer to current position in array fob */ char ob[1000]; /* buffer for raw data frames */ int obp=0; /* pointer to current position in array ob */ int invfla = 0; /* bit inversion flag */ static int comport = 0x3f8; /* serial port base address; set in main*/ static int sport = 1; /* serial port number to use */ static int dotting = 0; /* dotting sequence detection flag */ static int mode = 0; /* mode flag; 0 means we're on control */ /* channel; 1 means active frequency */ /* array for raw data coming off the serial port */ static unsigned int buflen= 15000; /* length of data buffer */ static volatile unsigned int cpstn = 0; /* current position in buffer */ static volatile unsigned int fdata[15001] ; /* timing data array */ void interrupt (*oldfuncc) (); /* vector to old com port interrupt */ /*--------------------------------------------------------------------*/ /* A BUNCH OF LOW LEVEL STUFF FOLLOWS */ /*--------------------------------------------------------------------*/ /**********************************************************************/ /* comint */ /* */ /* this is serial com port interrupt */ /* we assume here that it only gets called when one of the status */ /* lines on the serial port changes (that's all you have hooked up). */ /* All this handler does is find the number of system timer ticks */ /* since the last call and stores it in the fdata array. The MSB */ /* is set to indicate whether the status line is zero. In this way */ /* the fdata array is continuously updated with the appropriate */ /* length and polarity of each data pulse for further processing by */ /* the main program. */ void interrupt comint() { static unsigned int d1,d2,ltick,tick,dtick; /* the system timer is a 16 bit counter whose value counts down */ /* from 65535 to zero and repeats ad nauseum. For those who really */ /* care, every time the count reaches zero the system timer */ /* interrupt is called (remember that thing that gets called every */ /* 55 milliseconds and does housekeeping such as checking the */ /* keyboard. */ outportb (0x43, 0x00); /* latch counter until we read it */ d1 = inportb (0x40); /* get low count */ d2 = inportb (0x40); /* get high count */ /* get difference between current, last counter reading */ tick = (d2 << 8) + d1; dtick = ltick - tick; ltick = tick; /* set MSB to reflect state of input line */ if ((inportb(comport + 6) & 0xF0) > 0) dtick = dtick | 0x8000; else dtick = dtick & 0x3fff; fdata[cpstn] = dtick; /* put freq in fdata array */ cpstn ++; /* increment data buffer pointer */ if (cpstn>buflen) cpstn=0; /* make sure cpstn doesnt leave array */ d1 = inportb (comport + 2); /* clear IIR */ d1 = inportb (comport + 5); /* clear LSR */ d1 = inportb (comport + 6); /* clear MSR */ d1 = inportb (comport); /* clear RX */ outportb (0x20, 0x20); /* this is the END OF INTERRUPT SIGNAL */ } /************************************************************************/ /* SERIAL PORT INITIALIZATION */ /************************************************************************/ /* basic purpose: enable modem status interrupt and set serial port */ /* output lines to supply power to interface */ void set8250 (double bps) /* sets up the 8250 UART */ { static unsigned int t,dv,tp; dv = (int) 1843200.0/(16.0*bps); /* how to configure your serial port setup: */ /* do you want two stop bits? then make sure you set bit 2 in tp */ /* do you want a parity bit generated? then set bit bit 3 in tp */ /* do you want even parity? then set bit 4 in tp */ /* do you want an 8 bit word length? set bits 0 and 1 in tp */ /* do you want an 7 bit word length? set bit 1 in tp */ tp = 0x80 + 0x02 + 0x01; outportb (comport+3, tp); /* set line control register */ outport (comport , dv); /* output brg divisor latch */ tp = tp & 0x7f; /* switch out brg divisor reg */ outportb (comport+3, tp); outportb (comport+1, 0x08); /* enable MODEM STATUS INTERRUPT */ outportb (comport+4, 0x0a); /* push up RTS, DOWN DTR */ t = inportb(comport + 5); /* clear LSR */ t = inportb(comport); /* clear RX */ t = inportb(comport + 6); /* clear MSR */ t = inportb(comport + 2); /* clear IID */ t = inportb(comport + 2); /* clear IID - again to make sure */ } /* this routine allows the RTS output line to be set either high or */ /* low depending on whether sta = 0. */ void rts_state(int sta) { static int rv; rv = inportb(comport + 4); rv = rv & 0x01; /* save DTR state */ rv = rv | 0x08; /* enable interrupt to reach PIC */ if (sta != 0) rv = rv | 0x02; outportb(comport + 4, rv); } /* this routine allows the DTR output line to be set either high or */ /* low depending on whether sta = 0. */ void dtr_state(int sta) { static int rv; rv = inportb(comport + 4); rv = rv & 0x02; /* save RTS state */ rv = rv | 0x08; /* make sure interrupts can reach PIC */ if (sta != 0) rv = rv | 0x01; outportb(comport + 4, rv); } /************************************************************************/ /* send_char */ /* */ /* This routine sends a character out on the serial port TxD line. */ /* It makes sure the previous character has been completely sent and */ /* then sends puts the current character into the transmit register. */ /************************************************************************/ void send_char(int c) { /* wait for transmit reg to empty */ while ( (inportb(comport+0x05) & 0x20) == 0x00); /* send character */ outportb(comport,c); } /************************************************************************/ /* set_freak */ /* */ /* This routine must be written by the user for his/her specific radio. */ /* The input is the desired frequency the scanner should go to. */ /* Characters are sent to the scanner using the send_char routine. */ /************************************************************************/ void set_freak(double freq) { /* put your code here */ /* call send_char() to send a byte to your scanner */ } /************************************************************************/ /* TIMER CHIP INITIALIZATION */ /************************************************************************/ /* purpose: make sure computer timekeeper is set up properly. This */ /* routine probably isn't necessary - it's just an insurance */ /* policy. */ void set8253() /* set up the 8253 timer chip */ { /* NOTE: ctr zero, the one we are using*/ /* is incremented every 840nSec, is */ /* main system time keeper for dos */ outportb (0x43, 0x34); /* set ctr 0 to mode 2, binary */ outportb (0x40, 0x00); /* this gives us the max count */ outportb (0x40, 0x00); } /**********************************************************************/ /* higher level stuff follows */ /**********************************************************************/ /* structure for storing the control channel data frame information */ struct ccdat{ int cmd; /* command */ int lcn; /* logical channel number */ int stat; /* status bits */ int id; /* agency/fleet/subfleet id */ int bad; /* CRC check flag - when set to 1 it means data is corrupt */ }; /* array acn[] indicates which channels are currently active; aid[] */ /* indicates which id is using the active channel */ /* acn[] is actually a countdown timer decremented each time a */ /* control channel command is received and is reset whenever that */ /* channel appears in control channel data stream. When this reaches */ /* zero the channel is assumed to have become inactive. */ /* aid[] is used to make sure a new group appearing on an active */ /* channel is recognized even if acn[] had not yet reached zero */ int acn[33],aid[33],nacn=0; /************************************************************************/ /* show_setup */ /* */ /* Purpose: setup things for screen display */ /************************************************************************/ void show_setup() { static int i; clrscr(); for (i=0; i<33; i++) { acn[i] = 0; aid[i] = 0xffff; } gotoxy(1,1); textcolor(YELLOW); cprintf("LCN ID ST COMMAND"); gotoxy(1,2); cprintf("--- ---- --- ---------"); textcolor(LIGHTGRAY); gotoxy(65,2); cprintf("Using COM%1i",sport); gotoxy(63,3); cprintf("# channels: %2i",nucha); gotoxy(60,4); cprintf("Exit: Space or Esc "); gotoxy(60,6); cprintf("Misc Control data"); gotoxy(60,7); cprintf("LCN ID ST CMD"); gotoxy(75,1); textcolor(WHITE); cprintf("-"); nacn=0; } /************************************************************************/ /* show_active */ /* */ /* this routine gets called ONCE whenever a new ID becomes active on */ /* a given channel number */ /************************************************************************/ void show_active(struct ccdat info) { static int linx,liny,cm; /* this is where you will want to insert some code that decides if */ /* you want to switch to this particular active frequency. Right */ /* now this commented out code fragment would have sent your scanner */ /* off to any active frequency. */ /* you'll probably also want to filter out data channel assignemnts */ /* set_freak(chan[info.lcn-1]); */ /* mode = 1; */ /* dotting = 0; */ linx = 1; liny = 2 + info.lcn; if (liny > 22) { linx = 31; liny -= 20; } gotoxy(linx,liny); cm = info.cmd; cprintf("%2i: %03X %01X %02X ",info.lcn,info.id,info.stat,cm); textcolor(LIGHTGRAY); if (cm == 0xEE) cprintf ("VOICE"); else if (cm == 0xEC) cprintf ("PHONE"); else if (cm == 0xF6) cprintf ("VOICE"); else if (cm == 0xA0) cprintf ("DATA"); else if (cm == 0xA1) cprintf ("DATA"); textcolor(WHITE); } /************************************************************************/ /* show_inactive gets called when a channel is no longer active */ /* it simply writes out a bunch of spaces to erase the old */ /* information */ /************************************************************************/ void show_inactive(int lcn) { static int linx,liny; linx = 4; liny = 2 + lcn; if (liny > 22) { linx = 34; liny -= 20; } gotoxy(linx,liny); aid[lcn] = 0xffff; cprintf(" "); } /* scroll raw commands on window in right hand side of display */ void show_raw(struct ccdat inf) { static int lc=8; if (lc == 24) movetext(60,9,79,24,60,8); else lc++; gotoxy(61,lc); cprintf("%02X %03X %01X %02X",inf.lcn,inf.id,inf.stat,inf.cmd); } /************************************************************************/ /* proc_cmd */ /* */ /* This routine figures out when a group becomes active on a new */ /* channel and when a channel is has become inactive. This info is */ /* passed to the two routines above which show the changes on the */ /* screen */ /************************************************************************/ void show(struct ccdat info) { static int idup=0,cdup=0,i,aid[8],sysid=0x0000; static char dup[4] = { 45 , 47 , 124 , 92}; /* update the "I'm still receiving data" spinwheel character on screen */ cdup++; if (cdup > 9) { gotoxy(75,1); idup = (idup + 1) & 0x03; putch(dup[idup]); cdup = 0; } /* process only good information blocks */ if (info.bad == 0) { /* try to display all non idle information blocks */ if (info.cmd < 0x80) { show_raw(info); } else if ( ((info.cmd >> 1) != 0x7E) ) { if ( (info.lcn > 0) & (info.lcn <= nucha) ) { /* check to see if ID is in active list... if not it must be new */ if ( aid[info.lcn] != info.id) { /* if (aid[info.lcn] != 0xffff) show_inactive(info.lcn); */ /* a new ID has become active */ show_active(info); /* add to activity list */ aid[info.lcn] = info.id; } /* update activity timer for this active channel */ acn[info.lcn] = 8 + (nacn << 2); } } else if (info.cmd == 0xFD) { /* look at background / idle stuff to find system id */ if (sysid != info.id) { sysid = info.id; textcolor(YELLOW); gotoxy(35,1); cprintf("SYS ID: %03X",sysid); textcolor(WHITE); } } } /* see if any channel numbers have dropped off */ /* this is done by waiting for a certain number of control channel */ /* commands to go by before assuming the channel is no longer */ /* active. */ nacn = 0; /* nacn holds the number of active channels */ for (i=1; i<=nucha; i++) { if (acn[i] != 0) { nacn++; acn[i]--; if (acn[i] == 0) { /* LCN has become inactive */ show_inactive(i); aid[i] = 0xffff; } } } } /************************************************************************/ /* proc_cmd */ /* */ /* This routine processes a data frame by checking the CRC and nicely */ /* formatting the resulting data into structure info */ /************************************************************************/ void proc_cmd(int c) { static int ecc=0,sre=0,cc=0,ud=0xA9C,nbb=0,tb,orf,i; static char oub[50]; static struct ccdat info; if (c < 0) { cc = 0; sre = 0x7D7; ecc = 0x000; oub[28] = 0; /* pick off, store command (eight bits) */ for (i=0; i<=7; i++) { orf = orf << 1; orf += oub[i]; } orf = orf & 0xff; info.cmd = orf; /* pick off LCN (five bits) */ for (i=8; i<=12; i++) { orf = orf << 1; orf += oub[i]; } orf = orf & 0x1f; info.lcn = orf; /* pick off four status bits */ for (i=13; i<=16; i++) { orf = orf << 1; orf+=oub[i]; } orf = orf & 0x0f; info.stat = orf; /* pick off 11 ID bits */ for (i=17; i<=27; i++) { orf = orf << 1; orf+=oub[i]; } orf = orf & 0x07ff; info.id = orf; if (nbb == 0) info.bad = 0; else info.bad = 1; if (nbb == 0) { show(info); } nbb = 0; } else { cc++; /* bits 1 through 28 will be run through crc routine */ if (cc <29) { oub[cc-1] = c; if ( c == 1) ecc = ecc ^ sre; if ( (sre & 0x01) > 0) sre = (sre >> 1) ^ ud; else sre = sre >> 1; } else { /* for the rest of the bits - check if they match calculated crc */ /* and keep track of the number of wrong bits in variable nbb */ if ( (ecc & 0x800) > 0) tb = 1; else tb = 0; ecc = (ecc << 1) & 0xfff; if (tb != c) nbb++; } } } /************************************************************************/ /* proc_frame */ /* */ /* This routine processes the two raw data frames stored in array ob[]. */ /* Each data frame is repeated three times with the middle repetition */ /* inverted. So bits at offsets of 0, 40, and 80 should all be carrying */ /* the same information - either 010 or 101 if no errors have occured). */ /* Error correction is done by ignoring any single wayward bit in each */ /* triplet. For example a 000 triplet is assumed to have actually been */ /* a 010; a 001 -> 101; 011 -> 010; et cetera. Array tal[] holds a table*/ /* giving the "corrected" bit value for every possible triplet. Two */ /* or three wrong bits in a triplet cannot be corrected. The resulting */ /* data bits are send on the proc_cmd routine for the remaining */ /* processing. */ /************************************************************************/ void proc_frame() { static int i,l; static int tal[9]={0,1,0,0,1,1,0,1}; /* do the first data frame */ proc_cmd(-1); /* reset proc_cmd routine */ for (i=0; i<40; i++) { l = (int) ( (ob[i]<<2) + (ob[i+40]<<1) + ob[i+80]); /* form triplet */ l = tal[l]; /* look up the correct bit value in the table */ proc_cmd(l); /* send out bit */ } /* do the second data frame */ proc_cmd(-2); for (i=0; i<40; i++) { l = (int) ( (ob[i+120]<<2) + (ob[i+160]<<1) + ob[i+200]); l = tal[l]; proc_cmd(l); } } /************************************************************************/ /* frame_sync */ /* */ /* This routine takes the raw bit stream and tries to find the 48 bit */ /* frame sync sequence. When found it stores the next 240 raw data bits */ /* that make up a data frame and stores them in global array ob[]. */ /* Routine proc_frame is then called to process the data frame and the */ /* cycle starts again. When mode = 1 it will only look for the dotting */ /* sequence and updat the dotting flag. */ /************************************************************************/ void frame_sync(char gin) { static int sr0=0,sr1=0,sr2=0,hof = 300,xr0,xr1,xr2,i,nh=0,ninv=0; static int fsy[3][3]={ 0x1555,0x5712,0x5555, /* control channel frame sync */ 0x5555,0x5555,0x5555, /* dotting sequence */ 0xAAAA,0xAAAA,0x85D3 /* data channel frame sync (not used) */ }; /* update registers holding 48 bits */ sr0 = sr0 << 1; if ( (sr1 & 0x8000) != 0) sr0 = sr0 ^ 0x01; sr1 = sr1 << 1; if ( (sr2 & 0x8000) != 0) sr1 = sr1 ^ 0x01; sr2 = sr2 << 1; sr2 = sr2 ^ (int) gin; /* update ob array */ if (obp <600) { ob[obp] = gin; obp++; } /* find number of bits not matching sync pattern */ xr0 = sr0 ^ fsy[mode][0]; xr1 = sr1 ^ fsy[mode][1]; xr2 = sr2 ^ fsy[mode][2]; nh = 0; for (i=0; i<16; i++) { if ( (xr0 & 0x01) > 0) nh++; xr0 = xr0 >> 1; if ( (xr1 & 0x01) > 0) nh++; xr1 = xr1 >> 1; if ( (xr2 & 0x01) > 0) nh++; xr2 = xr2 >> 1; } /* if there are less than 3 mismatches with sync pattern and we aren't */ /* inside a data frame (hold-off counter less than 288) sync up */ if ( nh < 4) { /* mode zero means we're monitoring control channel */ if ( (hof > 287) && (mode == 0) ) { proc_frame(); obp = 0; hof = 0; ninv = 0; } /* mode 1 means we're on voice channel and so we just found dotting */ else if ((mode == 1) && (nh < 2) ) { dotting++; } } /* check for polarity inversion - if all frame sync bits mismatch 12 */ /* times in a row without ever getting a good match assume that one */ /* must invert the bits */ if ((nh == 48) && (mode == 0)) { ninv++; if (ninv > 12) { invfla ^= 0x01; gotoxy (65,1); if (invfla == 1) cprintf("INVERT"); else cprintf(" "); ninv = 0; } } if (hof < 1000) hof++; } /************************************************************************/ /* DISPLAY HELP SCREEN */ /************************************************************************/ void help() { printf("\n EDACS control channel monitoring program\n"); printf(" Command line arguement summary\n\n"); printf(" /NC:x - set x to number of channels used in system you \n"); printf(" are monitoring. Minimum value = 3; max = 31 \n"); printf(" /COM:y - set y = 1,2,3,4 to set com port you want to use.\n"); printf("\nExample: if your program is called edacs.exe and you wish to \n"); printf(" monitor a 10 channel system using COM2 you should type \n"); printf(" the following in at the DOS prompt:\n\n"); printf(" EDACS /nc:10 /com:2\n"); printf("\n Interface requirements: Hamcomm type data slicer circuit.\n"); printf(" The program automatically determines the property polarity of \n"); printf(" the incoming data. When everything is working properly you will\n"); printf(" see the the character on the upper right hand corner of your \n"); printf(" screen spinning around indicating that EDACS control channel \n"); printf(" information is being successfully processed.\n\n"); printf(" See text file for further details...\n\n"); printf(" press any key to continue...\n"); getch(); } void main (int argc,char *argv[],char *env[]) { static unsigned int n,i=0,j; static int irqv = 0x0c,ef=0,ch; FILE *out; static char s=48,temp[20]; static double dt,exc=0.0,clk=0.0,xct,dto2; for (n=1; n31) { nucha = 31; j = 30; } if (nucha < 3) { nucha = 3; j = 30; } if (sport > 4) { sport = 4; j = 30; } if (sport < 1) { sport = 1; j = 30; } if ( (j+1) != argc) help(); clrscr(); /* dt is the number of expected clock ticks per bit */ dt = 1.0/(9600.0*838.22e-9); dto2 = 0.5*dt; /* set up items related to serial port */ n = inportb (0x21); if ( (sport == 1) | (sport == 3)) { irqv = 0x0c; oldfuncc = getvect(irqv); /* save COM Vector */ setvect (irqv, comint); /* Capture COM vector */ outportb(0x21, n & 0xef); if (sport == 1) comport = 0x3f8; else comport = 0x3e8; } else { irqv = 0x0b; oldfuncc = getvect(irqv); /* save COM Vector */ setvect (irqv, comint); /* Capture COM vector */ outportb(0x21, n & 0xf7); if (sport == 2) comport = 0x2f8; else comport = 0x2e8; } set8253(); /* set up 8253 timer chip */ set8250(baudrate); /* set up 8250 UART */ printf("Checking for data coming in on COM%i... \n",sport); printf("If program just sits here press any key to exit...\n"); while ( (cpstn < 3) & (kbhit() == 0) ); if (cpstn < 3) { printf("HEY - no data seems to be coming in over your interface.\n\n"); outportb (0x21, n); /* disable IRQ interrupt */ setvect (irqv, oldfuncc); /* restore old COM Vector */ exit(1); } else printf("Interface seems to work properly...\n\n"); if (nucha >31) nucha = 31; if (nucha < 3) nucha = 3; show_setup(); while (ef == 0) { /* check if key has been hit */ if (kbhit() != 0) { ch = getch(); /* a space or Esc key press sets the exit flag */ if ((ch == 32) | (ch == 27)) ef = 1; else { /* any other key press returns the scanner to the control */ /* channel if it had switched to an active channel */ set_freak(chan[control]); mode = 0; i = cpstn; } } if (i != cpstn) { s = (char) ((fdata[i] >> 15) ^ invfla ); /* add in new number of cycles to clock */ clk += (fdata[i] & 0x7fff); xct = exc + dto2; /* exc is current boundary */ while ( clk >= xct ) { frame_sync(s); clk = clk - dt; } /* clk now holds new boundary position. update exc slowly... */ /* 0.005 sucks; 0.02 better; 0.06 mayber even better; 0.05 seems pretty good */ exc = exc + 0.050*(clk - exc); i++; if( i >buflen) i = 0; /* If we are sitting on an active channel and the dotting sequence */ /* is detected, we should switch back to the control channel */ if ( (mode == 1) && (dotting > 0)) { set_freak(chan[control]); mode = 0; dotting = 0; i = cpstn; } } } outportb (0x21, n); /* disable IRQ interrupt */ setvect (irqv, oldfuncc); /* restore old COM Vector */ gotoxy(1,23); printf("\n"); }