Subject: (LONG!!!) EDACS control channel monitoring information How to monitor the EDACS trunked radio control channel ------------------------------------------------------------------------ INTRODUCTION This posting gives a rather sketchy summary of the operation of Ericsson GE's EDACS trunked radio control channel, describes an op amp interface that connects between your PC compatible's serial port and your radio scanner's discriminator, and lastly includes a C program listing that decodes and displays the trunked radio system activity on your screen. This single op amp interface along with some slight program modifications described later will allow one to construct an "EDACS TrunkTracker" that passes active working frequencies to your scanner, automatically detects the end of voice transmissions (incidentally also eliminating the anti-scanner tones), and returns to the control channel to await the next frequency assignment. It is hoped that someone else will be able to pick up the ball, correct the inevitable misconceptions and errors in this posting, and develop a much nicer feature laden package (as has happened with the trunker program for monitoring Motorola's trunked radio control channel). As with the original trunker program posting, anyone who so desires should feel free to take anything useful from this posting in developing their own freeware / shareware / whatever product. --------------------------------------------------------------------- EDACS Control Channel Information Modulation : It looks like GMSK (Gaussian Minimum Shift Keying) which is basically the same thing as two level FSK keying except that the data stream is passed through a low pass filter before modulating the carrier. This reduces the high frequency components allowing the control channel to fit within a 12.5KHz channel. Accordingly, a simple data-slicer circuit can be used to receive the control channel information. Baud Rate : 9600 Baud Frame Sync : Frame synchronization is achieved by sending the following 48 bit sequence: 000101010101010101010111000100100101010101010101 or 0x155557125555 Hexadecimal Data Frames : After transmission of the frame synchronization sequence TWO data frames will be transmitted and then the whole cycle repeats ad infinitum. Each data frame is 120 bits long; this means 288 bits are transmitted in each cycle (2x120 bits for the data frames and 48 bits for frame sync). Each data frame consists of 40 bits of information transmitted three times in a row. In the second transmission the data is inverted. This seems to serve as some sort of primitive error correction scheme - in case of a receive error one may assume that the one bit that doesn't match the other two is bad. The 40 information bits in each data frame seem to have the following functions: Bits Function ------- ---------------------------------------- 0 - 7 : These eight bits are the command bits 8 - 12 : Logical channel number 13 : Status bit 14 : Status bit 15 : Status bit 16 : Status bit; possible use: group call if set to zero, individual call if set to one (radio ID then is given by bits 17 - 27) 17 - 19 : Agency ID (3 bits) for group call 20 - 23 : Fleet ID (4 bits) for group call 24 - 27 : Subfleet ID (4 bits) for group call 28 - 39 : Error detection polynomial The above tentative table seems to work well for the few systems the authors have monitored. The exact use of the status bits are at present unknown to the authors. The basic call type in an EDACS system is a group call, although individual radios IDs can also be sent for such tasks as a phone patch. Commands : The few commands the authors have figured out are: CMD Function --- -------------------------------------- A0h : Data channel assignment A1h : Data channel assignment ECh : Phone patch channel assigment EEh : Voice channel assignment FCh : IDLE FDh : ID bits give system ID; don't know if status bits should be part of system ID There must be a number of additional commands for such tasks as dynamic regrouping or disabling radios. ----------------------------------------------------------------------- A few words about those digital data transmissions you might sometimes hear It would be of interest to be able to monitor those digital data transmissions that can be an integral part of some EDACS systems. This program is a first step in that direction since it allows one to determine when and on which channels these transmissions take place. These transmissions take place at 9600 baud and can be monitored with the same interface used for the control channel. The following information was gathered from monitoring the data transmissions on a single commercial SMR trunk (unknown use), some of it might be relevant to other systems. The first part of the transmission consists of 144 bit frames. The first 48 bits are the following frame sync sequence: 101010101010101010101010101010101000010110110011 The next 96 bits are 4 bytes encoded as follows: byte number one is sent three times in a row (all bits inverted the second time), then bytes two through four follow similarly. The same frame is repeated a number of times, presumably to allow enough time for radios to switch to the data channel and achieve bit and frame synchronization. During this period the transmitted bytes are 0xD4, 0x00, 0x1A, and 0x33; presumably indicating an idle / background state. After enough time has elapsed, the four bytes are changed to indicate that actual data follows. For the all the messages the authors have monitored these bytes are 0xC4, 0x00, 0x18, and 0xF2; presumably indicating what type of data follows. At this point the actual data transmission starts, the frame sync sequence will not be sent until after the actual data transmission is complete. The first part of the data tranmission consists of two 40 bit blocks, each transmitted three times with all bits inverted the second time around. Next, 72 bytes are transmitted - each byte is sent twice (not three times) with all bits inverted the second time. Some additional redundancy is built since the first 27 bytes are always the same as the last 27 bytes. Since this concludes the actual data transmission the system starts transmitting the background / idle 144 bit frame again. For the authors' system this frame is sent 11 times, then a frame containing data bytes 0x9A, 0xAA, 0xA2, 0xC0 is transmitted 6 times; presumably commanding the radio to return to the control channel. ----------------------------------------------------------------------- THE INTERFACE Due to the 9600 baud rate, you must have access to the discriminator output on your scanner. The interface is basically a one op amp Hamcomm type which can be used for a variety of other decoding tasks as well. The following is an ascii drawing lifted without permission out of the documentation for Peter Baston's PD203 Pocsag pager decoding shareware package with some slight modifications (basically to eliminate the Schmitt trigger). This happens to be the circuit the authors are currently using. > > 0.1 uF |\ +12v > ---||-----------------------|- \| >DISC IN | |LF \ > ---- | |411 /--------------------- Data Out > | \ ------|+ /| CTS (pin 5/8) > | / 1M | |/-12v or DSR (pin 6/6) > | \ | > GND / | GND ------ GND (pin 7/5) > | | > | | N.B. Pin Numbers for com port are > GND GND given as x/y, where x is for a 25 > way, y for a 9 way. > > If you want this device to obtain power as a parasite off the serial port you can try the following circuit (also from the PD203 documention). It is slightly modified to get rid of the connection to the TxD line which one presumably wants to use to send control information to a radio scanner. > > > > --------------|<-------------------------------------- -12v > | | | > | RTS (4/7) --------|<-------- GND - - > | | | _ + 10uF > | --------->|------- - - | > | Diodes 1N4148 | - + 10uF GND > | | | > DTR (20/4) --------------->|-------------------------------------- +12v > ------------------------------------------------------------------------ Things you need to know before you can make your own "EDACS TrunkTracker" There are a few problems that need to be worked out before the interested reader could actually build his or her own "EDACS TrunkTracker". First, the EDACS system refers to channels in the trunked system by logical channel numbers which cannot be directly converted into megahertz. One therefore must have a table translating logical channel numbers into an actual frequencies for each EDACS system one plans to monitor. The easy way to get this table seems to be by monitoring the control channel activity with one scanner while using a second scanner to figure out which frequency always becomes active in step with which logical channel number. The interested reader will also have to figure out how to determine when the transmission is finished on a particular frequency so that the scanner can be sent back to the control channel. If you have several scanners you might want to dedicate one to continuously monitor the control channel while a second scanner is sent to active frequencies. The advantage here is that you will immediately know that the party you are monitoring has stopped transmitting or jumped to a new frequency since the appropriate ID has either disappeared or gone to a new channel number in the control channel data stream. For those who would prefer to use a single scanner: one could low pass filter the raw discriminator audio output and detect the presence of the subaudible data stream. Your scanner should return to the control channel once this data stream terminates. Simplest of all, one can look for the 9600 baud dotting sequence (the long series of 10101010101010... which comes out as a 4800Hz tone) that always seems to be sent at the end of each voice transmission with the same interface used to monitor the control channel. This dotting sequence is sent before, after, and in between the anti-scanner tones. The authors have found that the program will detect this dotting sequence reliably and quickly enough to completely eliminate the anti-scanner tones. Finally, one quickly tires of listening to the control channel during idle periods using the single scanner setup. The interested reader may want to use some of the serial port handshaking lines as an external mute control instead of supplying power to the op amp interface. In summary, the reader should be able to monitor control channel activity without having to make any changes to the supplied program. If one wants to start experimenting with "trunktracking" an EDACS system, the reader will have to hook the serial port TxD line up to a scanner with any necessary level translation circuitry, place some code into subroutine set_freak that sends the correct sequence of bytes to your scanner telling it to change frequencies, uncomment a few sections of code in subroutine show_active, make sure all serial port parameters are set correctly (these are the baudrate and the parameters in routine set8250), place a translation table relating logical channel numbers with actual frequencies for the specific system you plan to monitor into array chan[], and tell the program on which logical number the control channel happens to be. With these changes the program will jump to any active channel (without regard to the group ID) and jump back to the control channel when the dotting sequence is detected or any key other than space or escape is pressed. Improvements are an excercise left for the reader. ------------------------------------------------------------------------ THE PROGRAM Windows users beware: this program will probably only work running from DOS. This is because Windows may interfere with the program's low level access to your computer's hardware and disrupt the exacting timing needed to decode the incoming data. This paragraph gives a short description of how the program works. Every time the incoming data stream from your scanner changes state, the op amp data slicer's output also changes polarity. Each time this happens an interrupt is generated since the data slicer is connected to one of the serial port status lines. The interrupt handler (comint) stores the number of system timer clock ticks since the last interrupt and the data line polarity in a buffer. The main program extracts a raw bit stream from this buffer by synchronizing a 9600 baud receive "clock" with the the timing of the transitions and then looking at the data polarity halfway between the bit transitions. This raw bit stream is passed on to the frame_sync routine. In addition to checking for the dotting sequence, routine frame_sync examines this raw bit stream and extracts the actual data frames which are sent to routine proc_frame which peforms the error correcting on the raw data. Routine proc_frame passes its data on to routine proc_cmd which does the CRC check and formats the received data. The output of proc_cmd is a stream of raw control channel commands which are sent to routine show. Routine show figures out changes in system activity. It informs other routines when a group becomes active and on which channel (routine show_active) or when a channel is no longer active (routine show_inactive). Currently these other routines only format and display this data on the screen. On start up the program quickly checks to see if any data is coming from the interface. If none is detected it will inform the user of the problem upon termination. If the interface is working properly the program immediately starts trying to decode the control channel information and will also automatically determine the polarity of the incoming data. When it has locked onto the incoming data the spinwheel in the upper right hand corner of the screen should start spinning. If that doesn't happen: make sure you're actually sitting on an EDACS control channel, see if your interface works with other decoding packages such as the PD203 POCSAG pager decoder shareware, and if all else fails look at your discriminator and interface outputs with an oscilloscope to make sure the data getting into your computer looks like a relatively clean 9600 baud waveform. You can use a command line option to tell the program which COM port to use ("/COM:y" where y is the COM port number) and how many channels are used in your EDACS system ("/NC:x" where x is the number of channels), although there is no real reason not to leave the default number of channels at 20. Compiling the program: snip out bottom portion of this posting, save it to a file, and feed it into your C compiler with appropriate settings for a program that uses low level interrupts (in the author's Borland Turbo C compiler one should disable the "use register variables" optimization and the stack warning). The program will definitely compile with Turbo and Borland C compilers. And now for the listing: /**************************** SNIP HERE ******************************/ #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"); } /**** END OF POSTING ****/