; Internal memory allocation
; 00-07: Register bank 0 -- used for non-interrupt code
; 08-0F: Register bank 1 -- used for interrupt code
; 10-17: Register bank 2 \
; 18-1F: Register bank 3 > misc. variables go here
; 20-2F: Bit addressable space /
; 30-7F: Generic memory -- used for stack
; Constants
.equ maxflash,160 ; max. time on hook in 1/200 secs
.equ minflash,50 ; min. time for flash in 1/200 secs
.equ digitpause,40 ; min. interdigit pause in 1/200 secs
.equ limbotimeout,60 ; dial tone timeout in seconds
.equ dialtonedelay,50 ; delay until dialtone in 1/200 secs
.equ answerdelay,4 ; delay from answer to connect
; in 1/200 seconds
.equ dialqsize,23 ; Can buffer up 22 digits for pulsing
; out on external line
.equ redialsavesize,21 ; Can re-dial up to 20 digits
.equ xforwardsize,21 ; Can forward to external numbers
; up to 20 digits long.
.equ speeddialsize,21 ; Can speed-dial up to 20 digits.
.org 0x3b00
redialtbl: .skip redialsavesize*8 ; Must not go over a page boundary
.org 0x3c00
xforwardtbl: .skip xforwardsize*8 ; Ditto
.org 0x3d00
speeddialtbl: .skip speeddialsize*10 ; Ditto
; Constants for the digit outpulser
.equ dgmaketime,12 ; digit pulse time in 1/200 sec
.equ dgbreaktime,8 ; interdigitpulse time in 1/200 sec
.equ dgdigitpause,160 ; interdigit time in 1/200 sec
.equ dgflashtime,100 ; flash time in 1/200 second
.equ dtmfdigittime,18 ; DTMF digit make time in 1/200 sec
.equ dtmfdigitpause,12 ; DTMF digit break time in 1/200 sec
.equ hookreg,0x4000 ; hookswitch status bits
.equ net1reg,0x4200
.equ net0reg,0x4400
.equ ringreg,0x4600
.equ tonereg,0x4800
.equ miscreg,0x4000
.equ dtmfregs,0x6000 ; spaced 0x400 apart
.equ dtmfregpage,0x60
.equ tickcnt1,0x10
.equ ringscaler,0x11
.equ ringphase,0x12
.equ channelflags,0x13
.equ lineflags,0x14
.equ lineptr,0x15
.equ channelptr,0x16
.equ tickcnt2,0x17
.equ dtmf_flags,0x18
.equ pulsegentimer,0x19
.flag hookflag,0x20.0
.flag difflag,0x20.1
.flag extlineflag,0x20.2
.flag lastchan,0x20.3
.flag dialqflag,0x20.4
.flag dialdoneflag,0x20.5
.flag dtmftransflag,0x20.6
.equ tonephase,0x28
; External variables
.org 0x3a00
; This table has four bytes for each line: Timer, scan
; state, mainline flag, and digit pulse counter.
; It must not cross a 256-byte page boundary so we can
; move up and down in it by just manipulating DPL.
hookscantbl: .skip 8*4
ringtable: .skip 8*4
ringshadowreg: .skip 1
toneshadowreg: .skip 1
miscshadowreg: .skip 1
net0shadowreg: .skip 1
net1shadowreg: .skip 1
tonetbl: .skip 3
call_states: .skip 3
call_origs: .skip 3
call_dests: .skip 3
call_timers: .skip 3
limboflags: .skip 1
dtmf_digits: .skip 3
extringtimer: .skip 1
extlflags: .skip 1
extringenab: .skip 1
dialqueue: .skip 2+dialqsize
pulsegenstate: .skip 1
callvartbl: .skip 3*3
ringagainqueue: .skip 9
extinchannel: .skip 1
holdtable: .skip 8
parktable: .skip 8
forwardtable: .skip 8
speedowntbl: .skip 10
.org 0
ljmp start
.org 11
ljmp tick_isr
; CPU initialization
; ------------------
; Initialize CPU registers.
start: mov SP,#0x2f ; Stack at 30H
mov PSW,#0 ; Registers at 0H
mov TCON,#0 ; Disable timers
mov IE,#0 ; Disable all interrupts
mov IP,#0
mov PCON,#0 ; Initialize other registers.
mov P1,#0xfd
mov P3,#0xff
; Hardware initialization
; -----------------------
; Clear all relays and tones
clr a
mov dptr,#net1reg
movx @dptr,a
mov dptr,#net0reg
movx @dptr,a
mov dptr,#ringreg
movx @dptr,a
mov dptr,#miscreg
movx @dptr,a
mov dptr,#0x4A00
movx @dptr,a
; Data area initialization
; ------------------------
; Initialize the tables used by the line scanner.
mov dptr,#hookscantbl
mov r1,#8*4
clr a
clearline: movx @dptr,a
inc dptr
djnz r1,clearline
; Initialize all lines to "not ringing".
mov dptr,#ringtable
mov r1,#8
mov a,#0xff
clearring: movx @dptr,a
inc dptr
inc dptr
inc dptr
movx @dptr,a
inc dptr
djnz r1,clearring
; Initialize the tone table to "all tones off"
mov dptr,#tonetbl
clr a
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
movx @dptr,a
; Initialize the call state table to "all channels idle"
mov dptr,#call_states
clr a
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
movx @dptr,a
; Initialize the call timers
mov dptr,#call_timers
clr a
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
movx @dptr,a
; Initialize the external ring variables
clr a
mov dptr,#extringtimer
movx @dptr,a
mov dptr,#extlflags
movx @dptr,a
clr extlineflag
; Initialize the shadow registers.
clr a
mov dptr,#ringshadowreg
movx @dptr,a
mov dptr,#toneshadowreg
movx @dptr,a
mov dptr,#net0shadowreg
movx @dptr,a
mov dptr,#net1shadowreg
movx @dptr,a
mov dptr,#miscshadowreg
movx @dptr,a
; Initialize the timers maintained by the interrupt routine.
mov tickcnt1,#1
mov tickcnt2,#1
mov ringscaler,#1
mov ringphase,#0
mov tonephase,#0
; Initialize the channel flags to "all channels available"
mov channelflags,#0x07
; External incoming calls should ring all lines by default.
mov a,#0xff
mov dptr,#extringenab
movx @dptr,a
; Initialize the ring again queue
mov dptr,#ringagainqueue
clr a
movx @dptr,a
; Initialize hold table.
mov dptr,#holdtable
lcall clear8
; Initialize park table.
mov dptr,#parktable
lcall clear8
; Initialize call forward table.
mov dptr,#forwardtable
lcall clear8
; Initialize the redial save table
mov dptr,#redialtbl
mov r0,#8
rdcloop: clr a
movx @dptr,a
mov a,#redialsavesize
add a,DPL
mov DPL,a
djnz r0,rdcloop
; Initialize the speed dial table
mov dptr,#speeddialtbl
mov r0,#10
sdcloop: clr a
movx @dptr,a
mov a,#speeddialsize
add a,DPL
mov DPL,a
djnz r0,sdcloop
; No incoming outside call is in progress.
mov dptr,#extinchannel
clr a
movx @dptr,a
; Initialize the limbo flags
clr a
mov dptr,#limboflags
movx @dptr,a
; Initialize the line flags to "no lines in use".
mov lineflags,#0
; Start scanning at the first line
mov lineptr,#0
; Initialize the DTMF flags
mov dtmf_flags,#0
mov dptr,#dtmf_digits
clr a
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
movx @dptr,a
; Interrupt and serial port initialization
; ----------------------------------------
; Set both timers to free running, 8 bit auto reload.
mov TMOD,#0x22
; Set up timer 0 to generate tick interrupts at the
; slowest possible rate (3600/sec).
mov TH0,#0
setb IP.1
setb IE.1
setb IE.7
setb TCON.4
; Reset the outgoing digit queue. This must be done
; here because the subroutine disables and re-enables
; interrupts.
lcall resetdigitq
; Start baud rate generator and initialize serial port.
mov TH1,#0xfd
setb TCON.6
mov SCON,#0x52
sjmp linescan
; Subroutine to clear 8 bytes to zero at DPTR
clear8: mov r1,#8
clr a
cl8l: movx @dptr,a
inc dptr
djnz r1,cl8l
ret
;--------------------------------------------------------
; Mainline
; If channels are available, scan through the lines to
; find one which is offhook and not seized by anything.
linescan: mov a,channelflags
jnz linescan2a
ljmp lsnochan ; No channels
linescan2a: mov r0,lineptr ; Next line to scan
lcall computer0mask ; Convert to mask
mov r0,a
mov r1,lineptr
; Now we have R0 = bit mask for line to look at,
; R1 = number of line to look at
lscanloop: mov a,r0
anl a,lineflags
jnz lsnext ; Line already seized.
; Line not seized, see if it is offhook.
mov a,r1
lcall pointtoline
movx a,@dptr
; Offhook lines go from 0 to 3 then to 1. While in 3,
; they have not been offhook long enough to warrant
; dialtone (we don't want a bouncing switchhook to cause
; false dialing). So ignore lines whose state is 3.
cjne a,#3,lscheck0
clr a
lscheck0: jz lsnext ; Line on hook.
; Found a line which is not seized but is offhook. Get
; a free channel and assign to it. Keep scanning if more
; channels remain. Otherwise bump the line pointer by
; 1 so that when channels become available again, the next
; line is scanned. This assures fair allocation of channels
; in case of overload.
; R1 = line number, R0 = line mask bit
; Seize the line
mov a,r0
orl lineflags,a
; Seize a channel
lcall grabchan
; Initialize the channel data.
mov dptr,#call_states
mov a,r2
add a,DPL
mov DPL,a
mov a,#1
movx @dptr,a ; State 1
mov dptr,#call_origs
mov a,r2
add a,DPL
mov DPL,a
mov a,r1
movx @dptr,a ; Originating line number.
; If more channels remain, finish the scan. Otherwise
; save where we are and stop scanning.
clr a
cjne a,channelflags,lsnext
inc r1
cjne r1,#8,lsnext3
mov r1,#0
lsnext3: mov lineptr,r1
sjmp lsnochan
lsnext: mov a,r0
rl a
mov r0,a
inc r1
cjne r1,#8,lsnext2
mov r1,#0
lsnext2: mov a,r1
cjne a,lineptr,lscanloop
; Fall through here after scanning all 8 lines.
; A channel is available or we wouldn't have finished
; scanning.
; Check if the outside line is ringing and not already
; being handled.
jb P1.0,lsnochan
jb extlineflag,lsnochan
; Fire up a channel.
setb extlineflag
lcall grabchan
mov dptr,#call_states
mov a,r2
add a,DPL
mov DPL,a
mov a,#7
movx @dptr,a ; State 7
mov a,#200
mov dptr,#extringtimer
movx @dptr,a ; 5 second timeout
clr a
mov dptr,#extlflags
movx @dptr,a ; no lines grabbed yet
; Loop through the three channels.
lsnochan: clr lastchan
mov channelptr,#0
; Here is where the call processing occurs. The current
; channel is in "channelptr", its state is in the table
; "call_states". When finished with the channel, we must
; jump to "nextchan".
chanloop: mov a,channelptr
mov dptr,#call_states
movc a,@a+dptr
cjne a,#0,cnotstate0a
sjmp cstate0
cnotstate0a: ljmp cnotstate0
;--------------------------------------------------------
; State 0: Idle channel.
;
; Traverse the ring again queue, and if an entry is found
; where both ends are available, go to work on it.
;
; Note: Only idle channels should be in state 0, so don't
; even bother checking the channel allocation flag.
cstate0: mov dptr,#ringagainqueue
raloop: movx a,@dptr
jz radone
push DPL
push DPH
mov r2,a
anl a,#0x0f
lcall pokeline
jnc rabusy
mov a,r2
swap a
anl a,#0x0f
lcall pokeline
jc rafree
rabusy: pop DPH
pop DPL
inc dptr
sjmp raloop
radone: ljmp nextchan
; Found an entry which can be processed. Remove it
; from the list.
rafree: pop DPH
pop DPL
rafloop: inc DPL
movx a,@dptr
dec DPL
movx @dptr,a
inc dptr
jnz rafloop
; Set up originating and destination line numbers, grab
; the originating line.
mov a,r2
anl a,#0x0f
mov r1,a
lcall setcalldest ; Note: 1-9, not 0-7.
mov a,r2
swap a
anl a,#0x0f
dec a
mov r1,a
mov r0,a
lcall setcallorig
lcall seizeline
; Seize this channel.
mov r0,channelptr
lcall computer0mask
cpl a
anl channelflags,a
; Ring the originating line with a single 1/2 second ring.
mov dptr,#ringtable
mov a,r1
rl a
rl a
add a,DPL
mov DPL,a
clr IE.7
mov a,ringphase
inc a
cjne a,#240,sr0_notwrap
clr a
sr0_notwrap: movx @dptr,a
inc dptr
mov a,#1
movx @dptr,a
inc dptr
inc dptr
mov a,#240
movx @dptr,a
setb IE.7
; Wait for 15 seconds for the line to respond.
mov a,#15
lcall setcalltimer
mov a,#11
ljmp cnewstate
cnotstate0: cjne a,#1,cnotstate1
;--------------------------------------------------------
; State 1: No dial tone yet. Give dial tone and continue.
; Must also set up the network connection here.
; Start the call timer here to limit the amout
; of time dialtone is given.
state1: lcall getdtmfdigit ; Clear out old garbage
lcall settonerelay
lcall getorignum
mov r0,a
mov r1,channelptr
lcall connect
; If the originator has other lines on hold, remind him
; by a double stutter on the dial tone.
s1setups2: lcall getorignum
mov r3,a
lcall getholdnum
jz s1nothold
mov a,#0x31
sjmp s1setdtone
; If the originator's extension has a call parked on it,
; remind him with a break every 1/2 second.
s1nothold: mov dptr,#parktable
mov a,r3
movc a,@a+dptr
jz s1notpark
mov a,#0xf1
sjmp s1setdtone
; If the originator's phone is forwarded, remind him with
; dial tone that has a break every second.
s1notpark: mov dptr,#forwardtable
mov a,r3
movc a,@a+dptr
jz s1setregtone
mov a,#0xe1
sjmp s1setdtone
s1setregtone: mov a,#1 ; Dial tone
s1setdtone: lcall settone
mov a,#limbotimeout ; Dial tone timeout
lcall setcalltimer
clr a ; Dial state = 0
mov r0,a
lcall setcallvar
mov a,#1 ; Default ring code = 0
mov r0,#0
lcall setcallvar
mov a,#2
ljmp cnewstate ; Go to state 2.
cnotstate2a: ljmp cnotstate2
cnotstate1: cjne a,#2,cnotstate2a
;--------------------------------------------------------
; State 2: Has dial tone but no digit dialed yet.
lcall getorignum
lcall checklinestate
jnz nothungup2
; This "hungup" routine can be called to clear a
; call which hasn't progressed beyond the originating
; line yet.
hungup2: lcall getorignum
mov r0,a
lcall unseizeline ; Free line
; From here on it can be used for a line going into limbo.
hungup2a: lcall freechan ; Free channel
lcall getorignum
mov r3,a
clr a
lcall setholdnum
lcall getorignum
mov r0,a
lcall disconnect ; Disconnect line
lcall cleartonerelay ; Clear tone relay
clr a
lcall settone ; Tone generator off
; The "lastchan" flag is a kludge. When set, the channel
; processing loop aborts without handling the remaining
; channels so that the next thing that happens is the
; line scanner. This is so when a call is hung up at
; one end, the line scanner can give dial tone to the
; other line before a greedy "ring all" state can grab it.
; If we didn't do this, the end that didn't hung up would
; instantly answer the "ring all" call.
setb lastchan
clr a ; Go to state 0
ljmp cnewstate
; Still offhook. Did we get a digit?
nothungup2: jb ACC.2,haveevent2
; Did we get a DTMF digit?
checkdtmf2: lcall getdtmfdigit
mov r0,a
jc havedigit2a
; Check for timeout.
nodigit2a: lcall getcalltimer
jnz nottimeout2
; The line has had dialtone for the maximum allowable
; time without dialing. Release the channel and put the
; line in "limbo", where it remains seized (so it can't
; get dial tone again) until hung up.
limbo2: lcall getorignum
mov r0,a
lcall computer0mask
mov r0,a
mov dptr,#limboflags
movx a,@dptr
orl a,r0
movx @dptr,a
sjmp hungup2a
nottimeout2: ljmp nextchan ; No digit, keep waiting
haveevent2: xrl a,#0x05
swap a
mov r0,a
anl a,#0xf0
jz havedigit2a
; Check for flash. 5 XOR 6 = 3. If we get a flash,
; and a call is on hold, rejoin it. Otherwise
; reset things as if we had just picked up the phone.
cjne a,#0x30,checkdtmf2
lcall tryrejoin
jz s2rejoined
ljmp s1setups2
s2rejoined: ljmp cnewstate
; Now R0 has the digit that was dialed. Handle it depending
; on the digit collection state.
havedigit2a: mov a,#limbotimeout ; Reset dial tone timeout
lcall setcalltimer
clr a
lcall getcallvar ; Get dial state
jnz not2s0
cjne r0,#10,callnum2a ; State 0
sjmp state20
callnum2a: cjne r0,#12,callnum2b
; A "#" was dialed. Set DPTR to point at the redial buffer
; for the current line and dial the digit string there.
redial2: lcall getorignum
mov B,#redialsavesize
mul AB
mov dptr,#redialtbl
add a,DPL
mov DPL,a
ljmp s2dialstring
callnum2b: cjne r0,#11,callnum2c
; A "*" was dialed. Go to state 7 to get the speed dial
; number.
clr a
mov r0,#7
lcall setcallvar
ljmp nextchan
callnum2c: ljmp callnum2
; A "0" was dialed. Kill dialtone, advance to state 1 and
; get another digit.
state20: clr a
lcall settone
clr a
mov r0,#1
lcall setcallvar
ljmp nextchan
not2s1a: ljmp not2s1
not2s0: cjne a,#1,not2s1a ; State 1
cjne r0,#10,not2s00
; Code 00 - ring all stations
ljmp s2ringall
not2s00: cjne r0,#1,not2s01
; Code 01x - select ring code. Go to state 2 to accept
; the ring code.
clr a
mov r0,#2
lcall setcallvar
ljmp nextchan
; Unimplemented 0x code - give fast busy.
not2s01: cjne r0,#2,not2s02
; Code 02 - Ring Again. The ring again queue is a string
; of bytes where each contains the called station in the
; low 4 bits and the calling one in the high 4 bits.
; Station numbers are 1-8, the outside line is 9.
; The terminating byte is zero.
lcall getorignum
inc a
mov r3,a
dec a
mov B,#redialsavesize
mul AB
mov dptr,#redialtbl
movc a,@a+dptr
jz de2s02
mov r4,a
cjne a,3,notsamenum
; Don't allow ring again on own number.
de2s02: ljmp dialerror
; The other number is valid and is not our own. Try calling
; it now.
notsamenum: lcall pokeline
jnc notavail
; We got it now! Go and complete the call.
cjne r4,#9,notext2
; For the external line, do a full redial of the last number.
ljmp redial2
notext2: mov a,r4
dec a
mov r1,a ; Number to call
mov r0,a
lcall computer0mask
mov r0,a
ljmp notbusy2
notavail: mov a,r3
swap a
orl 4,a ; R4 has the byte to enqueue
; Scan the queue. If we are already in the queue, remove
; the old entry on the way up. Count other stations
; waiting for the same line in R2.
mov r2,#0
mov dptr,#ringagainqueue
mov P2,DPH
mov r1,DPL ; Two pointers.
sqloop: movx a,@dptr
jz sqgotend
xrl a,r4
anl a,#0xf0
jz sameorig
movx a,@dptr ; Not same originator -- copy entry
movx @r1,a
inc r1
xrl a,r4
anl a,#0x0f
jnz sameorig
inc r2 ; Same dest -- count entry
sameorig: inc dptr
sjmp sqloop
; (P2,R1) points to free byte -- add our own.
sqgotend: mov a,r4
movx @r1,a
inc r1
clr a
movx @r1,a
; Confirm with dial tone stutters -- one stutter plus an
; extra one for each other line waiting.
mov a,r2
inc a
inc a
swap a
orl a,#1
ljmp dialtone2
not2s02: cjne r0,#3,not2s03
; Code 03. Speed dial.
clr a
mov r0,#7
lcall setcallvar
ljmp nextchan
not2s03: cjne r0,#4,not2s04
; Code 04 - Call park/retrieve against any extension
clr a
mov r0,#4
lcall setcallvar
ljmp nextchan
not2s04: cjne r0,#5,not2s05
; Code 05 - Call park/retrieve against own extension
lcall getorignum
mov r3,a
; Common to 04x and 05 ... park/unpark against line R3.
; First check if the line already has a call parked against
; it. If so, retrieve that call.
parkr3: lcall getparknum
jz nopark2s05
lcall unpark
ljmp cnewstate
; No call was parked against the extension. Park the call
; we have on hold there. If nothing on hold, give fast
; busy.
nopark2s05: push 3
lcall getorignum
mov r3,a
lcall getholdnum
pop 3
jz error2s05
lcall setparknum
lcall getorignum
mov r3,a
clr a
lcall setholdnum
ljmp dial1stut2
error2s05: ljmp dialerror
not2s05: cjne r0,#6,not2s06
; Code 06 - set call forward ... go to dial state 5.
clr a
mov r0,#5
lcall setcallvar
ljmp nextchan
not2s06: cjne r0,#8,not2s08
; Code 08 - Configuration options, etc.
clr a
mov r0,#3
lcall setcallvar
ljmp nextchan
not2s08: cjne r0,#9,not2s09
; Code 09. Try to join in on the incoming outside call
; that is in progress.
mov dptr,#extinchannel
movx a,@dptr
jnz gotextcall2a
code09error: ljmp dialerror
; We can't join an incoming outside call if we're already
; part of it, i.e. have it on hold.
gotextcall2a: mov r0,a
lcall getorignum
mov r3,a
lcall getholdnum
cjne a,0,gotextcall2
sjmp code09error
; There is a call. Clean up everything here first.
gotextcall2: lcall freechan
mov r0,3
lcall disconnect ; Disconnect line
lcall cleartonerelay ; Clear tone relay
clr a
lcall settone ; Tone generator off
; Now join up the new call
lcall getorignum
mov r0,a
mov dptr,#extinchannel
movx a,@dptr
dec a
mov r1,a
mov dptr,#call_origs
mov a,DPL
add a,r1
mov DPL,a
movx a,@dptr
mov r2,a
lcall computer0mask
orl a,r2
movx @dptr,a
; Connect to it too.
lcall getorignum
mov r0,a
lcall connect
; Bye bye for this channel
clr a
ljmp cnewstate
not2s09: ljmp dialerror
; Dial state 2 -- current digit is the ring code.
not2s1: cjne a,#2,not2s2
cjne r0,#10,not2s110
mov r0,#0
not2s110: mov a,r0
clr c
subb a,#10
jnc s22dialerror
mov a,#1
lcall setcallvar
; This can be called from elsewhere.
dial1stut2: mov a,#0x21 ; Dial tone with 1 stutter
dialtone2: lcall settone
clr a
mov r0,a
lcall setcallvar
mov a,#limbotimeout
lcall setcalltimer
ljmp nextchan
s22dialerror: ljmp dialerror
not2s2: cjne a,#3,not2s3
; Dial state 3 - Code 08x
cjne r0,#1,not2s081
; Code 081 - Set speed dial. Go to dial state 6 to get the
; number of the speed dial to set.
clr a
mov r0,#6
lcall setcallvar
ljmp nextchan
not2s081: cjne r0,#2,not2s082
; Enable/disable external rings at this phone.
lcall getorignum
mov r0,a
lcall computer0mask
mov r0,a
mov dptr,#extringenab
movx a,@dptr
xrl a,r0
movx @dptr,a
anl 0,a
mov a,#0x31 ; 2 stutters
cjne r0,#0,not2stut
mov a,#0x21 ; only one stutter if disabled
not2stut: ljmp dialtone2
not2s082: ljmp dialerror
not2s3: cjne a,#4,not2s4
; Dial state 4 - Code 04x. Check that 'x' is a valid
; extension, then join up the park/retrieve code.
mov a,r0
jz error2s3
clr c
subb a,#9
jnc error2s3
mov r3,0
dec r3
ljmp parkr3
error2s3: ljmp dialerror
; Dial state 5 - code 06x. Write 0-9 into the call forward
; table. If 9 (external forward) proceed to collect the
; external number.
not2s4: cjne a,#5,not2s5
mov a,r0
cjne a,#10,not2s40
mov r0,#0
sjmp s2s4ok
not2s40: lcall getorignum
inc a
cjne a,0,not2s4own
mov r0,#0
sjmp s2s4ok
not2s4own: mov a,r0
clr c
subb a,#10
jnc error2s4
s2s4ok: lcall getorignum
mov dptr,#forwardtable
add a,DPL
mov DPL,a
mov a,r0
movx @dptr,a
cjne r0,#9,s2s4notext
; Forward to an external number. Initialize call variables
; 0 and 1 with DPL/DPH respectively pointing to the
; originator's external forward table entry, and call
; variable 2 with the number of digits that may be stored
; there.
lcall getorignum
mov B,#xforwardsize
mul ab
mov dptr,#xforwardtbl
add a,DPL
mov DPL,a
mov r0,a
clr a
movx @dptr,a ; Initialize to empty
push DPH
lcall setcallvar
pop 0
mov a,#1
lcall setcallvar
mov r0,#xforwardsize-1
mov a,#2
lcall setcallvar
; Go to state 13 to gather the digits.
mov a,#13
ljmp cnewstate
s2s4notext: ljmp dial1stut2
error2s4: ljmp dialerror
not2s5: cjne a,#6,not2s6
; Dial state 6: Code 081x -- Set speed dial 'x'
; First get the owner of that speed dial entry.
mov a,r0
mov r3,a ; R3 = speed dial #
mov dptr,#speedowntbl-1
movc a,@a+dptr
mov r1,a ; R1 = owner
lcall getorignum
mov r2,a ; R2 = orig. number
; Compute pointer to the table entry
mov a,r3
dec a
mov B,#speeddialsize
mul ab
mov dptr,#speeddialtbl
add a,DPL
mov DPL,a
; If not null and not owned by this line, give fast busy.
movx a,@dptr
jz sdisfree
mov a,r2
clr c
subb a,r1
jz sdisfree
ljmp dialerror
; OK, we can change the table entry. First null it out and
; store the pointer to it. Note: A=0
sdisfree: movx @dptr,a
mov r0,DPL
push DPH
lcall setcallvar
pop 0
mov a,#1
lcall setcallvar
; Set ownership of the table entry
mov dptr,#speedowntbl
mov a,r3
dec a
add a,DPL
mov DPL,a
mov a,r2
movx @dptr,a
; Set number of digits allowed to store, and go to state
; 13 to collect them.
mov r0,#speeddialsize-1
mov a,#2
lcall setcallvar
mov a,#13
ljmp cnewstate
; Dial state 7: Speed dial. Look up the appropriate table
; entry.
not2s6: mov dptr,#speeddialtbl
mov a,r0
dec a
mov B,#speeddialsize
mul ab
add a,DPL
mov DPL,a
; If the entry is not set, give fast busy.
movx a,@dptr
jnz ok2s6
ljmp dialerror
; Call the number.
ok2s6: ljmp s2dsext
; Continue here if the digit was the number to call.
callnum2: mov a,r0
jz dialerror
clr c
subb a,#10
jnc dialerror
; Number is in range 1-9. Stash it in the redial table.
lcall getorignum
mov B,#redialsavesize
mul AB
mov dptr,#redialtbl
add a,DPL
mov DPL,a
mov a,r0
movx @dptr,a
clr a ; Null-terminate the entry
inc dptr
movx @dptr,a
mov a,r0
cjne a,#9,notdialout
; Dialing '9' while already having the external line on
; hold means to send a flash out the external line. See if
; that is what is wanted.
lcall getorignum
mov r3,a
lcall getholdnum ; Is a channel on hold?
jz dialout2a
mov r1,channelptr
dec a
mov channelptr,a
mov a,#1
lcall getcallvar ; Get its ext. line flag
mov channelptr,r1
jz dialout2a
; Enqueue the "flash" code.
mov a,#15
lcall enqueue
jnc deflashout2
; Go back to the external call.
flashout2ok: lcall tryrejoin
jz rejoinok
deflashout2: ljmp dialerror
rejoinok: ljmp cnewstate
; Dial out. If the external line is not free, give busy.
dialout2a: jb extlineflag,busy2
dialout2: setb extlineflag ; Grab it
setb P1.1 ; Take it offhook
lcall cleartonerelay ; Remove internal dial tone
clr a
lcall settone
; Wait approximately 1/20 second before connecting the
; call through. That way the originator doesn't get the
; "click" from the external line being picked up.
mov dptr,#extringtimer
mov a,#2
movx @dptr,a
mov a,#8
ljmp cnewstate
; Bad digit. Turn on fast busy tone and go to state 3.
dialerror: mov a,#0x13
sjmp busy2a
; Have a digit 1-8 in R0. Test the destination line.
; If seized or offhook, give busy.
; Handle call forward first. Allow no more than 8 call
; forward "hops"; this is a simple method to prevent infinite
; call forward loops.
notdialout: mov r1,#8
fwloop: mov a,r0
mov dptr,#forwardtable-1
movc a,@a+dptr
cjne a,#9,notforwardout
; The forward is to an outside line. If this is the
; case, dial the outside number.
mov a,r0
dec a
mov B,#xforwardsize
mul ab
mov dptr,#xforwardtbl
add a,DPL
mov DPL,a
ljmp s2dsext
notforwardout: jz fwdone
mov r0,a
djnz r1,fwloop
sjmp busy2
fwdone: mov a,r0
dec a
mov r0,a
mov r1,a ; Line number now 0-7 in R0
rl a
rl a
mov dptr,#hookscantbl+2
movc a,@a+dptr ; Get dest. line state
jnz busy2
lcall computer0mask
mov r0,a ; Save mask
anl a,lineflags
jz notbusy2 ; Busy because line seized
; Give regular busy tone and go to state 3.
busy2: mov a,#0x03
busy2a: lcall settone
mov a,#limbotimeout ; Busy tone timeout
lcall setcalltimer
mov a,#3
ljmp cnewstate ; Go to state 3.
; The destination line is not busy. Seize it, using the
; mask still stored in R0.
notbusy2: mov r3,#4 ; State to go to.
notbusy2a: mov a,r0
orl lineflags,a ; Seize dest. line
lcall setcalldest
; Start the destination line ringing, give ringback tone
; to the source line, and go to state 4.
mov a,#1
lcall getcallvar
mov dptr,#ringcodetbl
movc a,@a+dptr
mov r2,a
mov r0,1 ; Dest. line number
lcall startring
mov a,r1
swap a
orl a,#2 ; Ringback + line #
lcall settone
; Convert the originating number to a bitmap.
lcall getorignum
mov r0,a
lcall computer0mask
mov r1,a
lcall setcallorig
; Indicate that the outside line is not involved
mov a,#1
mov r0,#0
lcall setcallvar
mov a,r3
ljmp cnewstate
; Ring all stations
; -----------------
; Grab the first available station, start ringing it, and
; go to state 10. State 10 will grab all the other stations
; and ring them too.
s2ringall: lcall getorignum
mov r0,a
lcall computer0mask
orl a,lineflags
cpl a
mov r1,#-1
s2rafindloop: jz s2ranolines
clr c
rrc a
inc r1
jnc s2rafindloop
; Got a line, number in R1. Call it but instead of going
; to state 4, go to state 10.
mov r0,1
lcall computer0mask
mov r0,a
clr a
lcall setcallvar
mov r3,#10 ; State 10
ljmp notbusy2a
s2ranolines: ljmp busy2
; Ring codes (1 char = 1/4 second)
;
; 0 ---.---. (default)
; 1 --.--.--
; 2 -.----..
; 3 ----.-..
; 4 ---.....
; 5 -.......
; 6 -.-.....
; 7 -.-.-...
; 8 -.-.-.-.
; 9 --------
ringcodetbl: .byte 0x77,0xdb,0x3d,0x2f,0x07,0x01,0x05,0x15,0x55,0xff
; Dial a digit string from DPTR
; -----------------------------
; The string is either a single digit 1-8, or 9 followed
; by digits to dial on the outside line.
s2dialstring: movx a,@dptr
mov r0,a
jnz s2dsnot0
; Back to dial tone if no string saved here.
ljmp s1setups2
s2dsnot0: clr c
subb a,#9
inc dptr
jz s2dsext
; If it's an internal number, go to the regular call code.
; This needs the digit in R0.
ljmp notdialout
s2dsext: jnb extlineflag,s2dsextok
ljmp busy2
s2dsextok: setb extlineflag ; Grab external line
push DPL
push DPH
; The following is an edited version of the state 8 -> 9
; transition code.
; Read the DIP switch.
mov dptr,#dtmfregs
movx a,@dptr
mov c,ACC.7
mov dtmftransflag,c
lcall resetdigitq
clr a
lcall setcalltimer
mov a,#60
mov dptr,#extringtimer
movx @dptr,a
; Take the outside line offhook but don't connect it -- it
; will be connected (unmuted) when dialing is finished.
setb P1.1
clr a
lcall settone
lcall settonerelay
; Don't save any digits in the redial table.
clr a
mov r0,a
lcall setcallvar
; Finally, copy the redial number into the dial queue.
; Don't check for queue overflow since we start with the
; queue known empty and it must be big enough to hold any
; re/speed-dialable number.
sdnext: pop DPH
pop DPL
movx a,@dptr
jz sddone
inc dptr
push DPL
push DPH
mov c,dtmftransflag
mov ACC.4,c
lcall enqueue
sjmp sdnext
; If nothing was enqueued for dialing out (the redial number
; was just "9" for some reason), then un-mute the speech
; path right away.
sddone: jb dialqflag,sddone2
mov a,channelptr
inc a
swap a
lcall connectext
lcall cleartonerelay
sddone2: clr dtmftransflag
mov a,#9
ljmp cnewstate
cnotstate2: cjne a,#3,cnotstate3
;--------------------------------------------------------
; State 3: Has busy tone (fast or regular), await
; hangup. A "flash" here returns us to dial
; tone or a held call.
lcall getorignum
lcall checklinestate
jz hungup3
cjne a,#6,notflash3
lcall tryrejoin
jz cns3
mov a,#1
cns3: ljmp cnewstate
notflash3: lcall getcalltimer
jnz nottimeout3
ljmp limbo2
hungup3: ljmp hungup2
nottimeout3: ljmp nextchan
cnotstate3: cjne a,#4,cnotstate4
;--------------------------------------------------------
; State 4: Destination line ringing. Continue until
; number of callers is zero or the callee
; picks up.
lcall checkcallers
jnz stillhere4
; The caller(s) hung up. Stop ringing the destination line,
; free the destination line, and leave.
lcall getdestnum ; Get dest. line
push ACC
mov r0,a
lcall stopring ; Stop ringing it.
pop 0 ; Get dest. line
lcall unseizeline ; Unseize it
; Clear holds on the current channel.
lcall clearholds
lcall clearparks
; Give up the channel
clr a
lcall settone
lcall cleartonerelay
lcall freechan
clr a
ljmp cnewstate
; Check the state of the destination line. State 1
; means picked up and the normal dial tone delay expired.
; State 3 means still counting through the dial tone
; delay. Rather than add more states to the switchhook
; scanner, just monitor its internal timer from here and
; switch the connection through partway through state 3.
stillhere4: lcall getdestnum
lcall checklinestate
cjne a,#3,not4s3
; DPTR is still pointing at the switchhook data structure.
dec DPL
dec DPL
movx a,@dptr
subb a,#answerdelay
jc notpickup4
sjmp pickup4
not4s3: jz notpickup4
; The destination line has been picked up. Connect the
; voice path through.
pickup4: lcall cleartonerelay
clr a
lcall settone
lcall getdestnum
mov r0,a
mov r1,channelptr
lcall connect
; Add the called number to the set of callers and go to
; the conversation state.
lcall getdestnum
mov r0,a
lcall computer0mask
mov r1,a
lcall getorignum
orl 1,a
lcall setcallorig
; Indicate that we do NOT own the outside line.
clr a
mov r0,a
lcall setcallvar
mov a,#12
ljmp cnewstate
notpickup4: ljmp nextchan
cnotstate7a: ljmp cnotstate7
cnotstate4: cjne a,#7,cnotstate7a
;--------------------------------------------------------
; State 7. The outside line is ringing. Stay in this
; state until it stops or until one of the inside
; lines being rung picks up.
; Check if it's still ringing.
mov dptr,#extringtimer
jb P1.0,nord7
mov a,#200
movx @dptr,a
sjmp gotrd7
nord7: movx a,@dptr
jnz gotrd7
; No ring detect for 5 seconds. It must have stopped
; ringing. Release all seized lines, release the channel
; and exit.
mov r0,channelptr ; Free channel
lcall computer0mask
orl channelflags,a
mov dptr,#extlflags ; Free lines
movx a,@dptr
push ACC
clr a
movx @dptr,a
pop ACC
lcall cancelset
clr extlineflag
clr a ; Go to state 0
ljmp cnewstate
; Continue here if the external line is still ringing.
; Seize (newly) available lines first.
gotrd7: mov dptr,#extringenab ; Lines to ring
movx a,@dptr
lcall greedy
mov R0,a
mov dptr,#extlflags
movx a,@dptr ; Lines already grabbed
orl a,R0
movx @dptr,a
; Look for an offhook on one of the seized lines.
lcall checkset
jc pickup7
ljmp nextchan
; One of the lines has been picked up. Stop ringing and
; get rid of the rest.
pickup7: mov r3,1
mov r0,1 ; Picked-up line
lcall computer0mask
cpl a
mov r0,a
mov dptr,#extlflags
movx a,@dptr
anl 0,a
clr a
movx @dptr,a ; Zero extlflags
mov a,r0
lcall cancelset ; Cancel other lines
; Connect the inside line
mov r0,3
mov r1,channelptr
lcall connect
; Initialize the set of lines talking
mov r0,3
lcall computer0mask
mov r1,a
lcall setcallorig
; Take the outside line offhook and connect it to the
; appropriate network channel.
setb P1.1
mov a,channelptr
inc a
swap a
lcall connectext
clr dialdoneflag
; Indicate that there is a group call in progress that
; others can join by dialing 09.
mov dptr,#extinchannel
mov a,channelptr
inc a
movx @dptr,a
; Register that state 12 has ownership of the outside line.
mov a,#1
mov r0,a
lcall setcallvar
mov a,#12
ljmp cnewstate
cnotstate7: cjne a,#8,cnotstate8
;--------------------------------------------------------
; State 8. The "originating" line wants to connect to the
; outside line, which is seized and offhook.
; The extringtimer must run down before we
; connect through.
mov dptr,#extringtimer
movx a,@dptr
jnz s8nextchan
; Waited long enough. Connect through. Read the DIP switch
; to find out whether to translate DTMF to dial pulses or
; not.
mov dptr,#dtmfregs
movx a,@dptr
mov c,ACC.7
mov dtmftransflag,c
lcall resetdigitq
mov a,#10
lcall setcalltimer
; Wait 1.5 seconds before passing any dialing through
; to the outside so it can get dialtone first.
; NOTE: The ISR monitors this timer directly and will not
; dial unless it has run down to zero.
mov a,#60
mov dptr,#extringtimer
movx @dptr,a
; Take the outside line offhook and connect it to the
; appropriate network channel.
setb P1.1
mov a,channelptr
inc a
swap a
lcall connectext
; In state 9, call variable 0 is the number of digits
; already saved in the redial table. The "9" that got us
; this far is already there.
mov r0,#1
clr a
lcall setcallvar
mov a,#9
ljmp cnewstate
s8nextchan: ljmp nextchan
cnotstate9a: ljmp cnotstate9
cnotstate8: cjne a,#9,cnotstate9a
;--------------------------------------------------------
; State 9. Calling out on the outside line.
; Remain here until the inside line hangs up
; since we can't tell anything about the
; state of the outside line.
lcall getorignum
lcall checklinestate
jnz nothungup9
; Inside line hung up. Cancel outgoing dialing if
; in progress.
lcall resetdigitq
; Hang up and disconnect the external line.
hungup9: clr P1.1
clr a
lcall connectext
clr extlineflag
ljmp hungup2
; The connection is still up and A contains the return
; code from "checklinestate". Handle pulse-dialed digits
; by queuing them up for resending on the outside line.
nothungup9: mov r0,a
anl a,#0x0f
cjne a,#6,notflash9
; Flash. First of all, verify if we already have someone
; on hold, and if so, join that call.
lcall tryrejoin
jnz notjoined9
; It worked. This channel is ready to set to state 0 but
; first we must move over the external line.
mov r1,channelptr
mov channelptr,r2
mov a,#1
mov r0,a
lcall setcallvar
mov channelptr,r1
; If the external line is not currently muted, reconnect it
; too.
mov dptr,#miscshadowreg
movx a,@dptr
anl a,#0x30
jz s9ismuted
mov a,r2
inc a
swap a
lcall connectext
s9ismuted: clr a
ljmp cnewstate
; Go to state 12 to hang on to the external line
; and provide a point for the caller to return to. Pulse
; dialing out is not possible after this event.
; First, verify that a channel is available and if not,
; ignore the flash.
notjoined9: mov a,channelflags
jz notdigit9
; Remember in the current channel that we are being held.
lcall getorignum
mov r3,a
mov a,channelptr
inc a
lcall setholdnum
; Disconnect from the current channel
mov r0,3
lcall disconnect
; Get a new channel for the caller.
lcall grabchan
; Initialize channel R2 to state 1 and set the originating
; number.
mov dptr,#call_states
mov a,DPL
add a,2
mov DPL,a
mov a,#1
movx @dptr,a ; State 1
lcall getorignum
mov r3,a
mov dptr,#call_origs
mov a,DPL
add a,2
mov DPL,a
mov a,r3
movx @dptr,a ; Originator R3
; Indicate to state 12 that we have the outside line and
; no inside lines.
mov a,#1
mov r0,a
lcall setcallvar ; We have the outside line
mov r1,#0
lcall setcallorig ; No inside lines
mov a,#12
ljmp cnewstate
notflash9: cjne a,#5,notdigit9
; We got a pulse dial digit. Enqueue it for repeating on
; the outside line. If the outside line supports DTMF,
; re-send it as DTMF.
mov a,#10
lcall setcalltimer
mov dptr,#dtmfregs
movx a,@dptr
mov c,ACC.7
mov a,r0
swap a
anl a,#0x0f
mov ACC.4,c
sjmp enqdigit
; Check for DTMF digits.
notdigit9: lcall getdtmfdigit
jnc notdtmf9
mov r1,a
mov a,#10
lcall setcalltimer
mov a,r1
jnb dtmftransflag,transdtmf9
lcall savedigit
sjmp notdtmf9
; Have a DTMF digit. If 0-9 (codes 1-10) enqueue it.
; Otherwise turn off the DTMF-pulse translation.
transdtmf9: jz badtone9
clr c
subb a,#11
jc goodtone9
badtone9: setb dtmftransflag
clr a
mov r0,a
lcall setcallvar ; Don't save any more digits.
ljmp notdtmf9
goodtone9: mov a,r1
; Mute audio. Disconnect the external line from the network
; but connect the tone source (turned off) instead, so that
; the audio coupling capacitor is bled. Otherwise it will
; glitch the offhook relay when it is reconnected.
enqdigit: push ACC
lcall savedigit
clr a
lcall connectext
clr a
lcall settone
lcall settonerelay
pop ACC
lcall enqueue
jc notdtmf9
; Queue overflow! Release the outside line and give
; fast busy to the inside line.
lcall resetdigitq
clr P1.1
clr a
lcall connectext
clr extlineflag
lcall settonerelay
ljmp dialerror
; If the dialdoneflag is set, the ISR has finished pulsing
; out digits.
notdtmf9: jnb dialdoneflag,nextchan9
; However, we don't unmute the audio path until
; pulse-dialing stops on the inside line. This is to
; reduce the danger of a glitch in the offhook relay state.
lcall getorignum
lcall pointtoline
dec DPL
movx a,@dptr
cjne a,#1,nextchan9
mov a,channelptr
inc a
swap a
lcall connectext
lcall cleartonerelay
clr dialdoneflag
; If the call timer runs down, turn off DTMF-pulse dial
; translation.
nextchan9: lcall getcalltimer
jnz nextchan9a
setb dtmftransflag ; Don't translate DTMF any more
clr a
mov r0,a
lcall setcallvar ; Don't save any more digits.
nextchan9a: ljmp nextchan
; Subroutine to save a digit in the redial table.
savedigit: mov r0,a
clr a
lcall getcallvar
jz sdnosave
mov r2,a
lcall getorignum
mov B,#redialsavesize
mul AB
mov dptr,#redialtbl
add a,DPL
add a,r2
mov DPL,a
mov a,r0
movx @dptr,a
inc dptr ; Null terminate
clr a
movx @dptr,a
mov a,r2
inc a
mov r0,a
clr c
subb a,#redialsavesize-1
jc sdsaveptr
mov r0,#0
sdsaveptr: clr a
lcall setcallvar
sdnosave: ret
cnotstate10a: ljmp cnotstate10
cnotstate9: cjne a,#10,cnotstate10a
;--------------------------------------------------------
; State 10. Ringing all available stations. Grab any
; stations that become available too.
; Find any newly available stations
mov a,#0xff ; All stations
lcall greedy
; A is a bitmap of newly seized lines.
mov r0,a ; Newly grabbed lines
mov r1,a ; Save for later
clr a
lcall getcallvar ; Get set we already have
orl 0,a
clr a
lcall setcallvar
lcall getdestnum
rl a
rl a
mov dptr,#ringtable+2
add a,DPL
mov DPL,a
movx a,@dptr
mov r2,a ; Ring pattern
inc dptr
movx a,@dptr
mov r3,a ; Ring start time
; R1 = bitmap, R2 = ring pattern, R3 = ring start time.
mov dptr,#ringtable
rsloop: mov a,r1
jz ringstartdone
clr c
rrc a
mov r1,a
jnc skipentry
mov a,r3
movx @dptr,a
inc dptr
mov a,r2
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
mov a,r3
movx @dptr,a
sjmp rsskiplast
skipentry: inc dptr
inc dptr
inc dptr
rsskiplast: inc dptr
sjmp rsloop
; Ringing has been started on all the newly grabbed lines.
ringstartdone: lcall checkcallers
jnz nothungup10
; The caller(s) all hung up. Stop ringing all the owned
; lines, free them all, then go away.
lcall clearholds
lcall clearparks
clr a
lcall getcallvar
lcall cancelset
clr a
lcall settone
lcall cleartonerelay
lcall freechan
clr a
ljmp cnewstate
; Now check if any of the destination lines picked up.
nothungup10: clr a
lcall getcallvar ; Get line flags
lcall checkset
jnc nextchan10
; Line in R1 picked up. Stop ringing and release the rest.
push 1
clr a
lcall getcallvar ; Lines owned
mov r2,a
mov r0,1
lcall computer0mask
cpl a
anl a,2
lcall cancelset
pop 1
lcall setcalldest
ljmp pickup4 ; Go connect it
nextchan10: ljmp nextchan
cnotstate10: cjne a,#11,cnotstate11
;--------------------------------------------------------
; State 11: Originating line has received "ring again"
; signal and we are waiting for it to be
; picked up.
lcall getorignum
lcall checklinestate
jnz pickup11
lcall getcalltimer
jnz notimeout11
; Timeout. Release the originating line and exit.
lcall getorignum
mov r0,a
lcall unseizeline
lcall freechan
clr a
ljmp cnewstate
notimeout11: ljmp nextchan
; The originating line has picked up. Make a call to
; the number previously stored.
pickup11: lcall settonerelay
lcall getorignum
mov r0,a
mov r1,channelptr
lcall connect
lcall getdestnum
mov r0,a
ljmp callnum2
cnotstate12a: ljmp cnotstate12
cnotstate11: cjne a,#12,cnotstate12a
;--------------------------------------------------------
; State 12. A "set" of inside lines (bitmap in
; "call_orig") and maybe the outside line
; (flag in call variable #0) are talking.
;
; Stay here until just one participant remains (if no
; holds) or no participants (if holds).
lcall checkcallers
mov r1,a
mov a,#1
mov r2,a
lcall getcallvar
jz s12noext1
dec r2
s12noext1: mov a,r1
lcall holdmagic
mov r0,a ; Internal lines
mov a,#1
lcall getcallvar
jz s12noext
mov a,#1 ; Count external line too
s12noext: add a,r0
jz s12bye ; If nobody here, go away.
cjne a,#1,s12stay
; Exactly one caller remains. Is he on hold?
lcall amiheld
jc s12stay
lcall amiparked
jc s12stay
; Not on hold. Take down the call. If the single remaining
; line is the outside one, get rid of everything.
mov a,#1
lcall getcallvar
jnz s12intbye
; The single remaining line is an inside one. Return it
; to the "dial tone" state. First, find which caller
; remains.
lcall getorignum
lcall log2
; Convert back to a regular originating number, and give
; dial tone.
mov r1,0
lcall setcallorig
ljmp state1
; If we had the external line, get rid of it now and
; indicate that there is no longer an external call to join.
s12intbye: lcall resetdigitq
clr P1.1
clr a
lcall connectext
clr extlineflag
mov dptr,#extinchannel
clr a
movx @dptr,a
; Ditch the channel. Make sure the tone stuff is off too.
s12bye: lcall clearholds
lcall clearparks
lcall freechan
lcall cleartonerelay
clr a
lcall settone
clr a
ljmp cnewstate
; If we own the external line, handle un-muting it
; after dialing here.
s12stay: jnb dialdoneflag,s12notextun
mov a,#1
lcall getcallvar
jz s12notextun
mov a,channelptr
inc a
swap a
lcall connectext
lcall cleartonerelay
clr dialdoneflag
s12notextun: ljmp nextchan
cnotstate12: cjne a,#13,cnotstate13
;--------------------------------------------------------
; State 13: Gathering digits into a buffer. Call
; variables are: 0 - addr low, 1 - addr high,
; 2 - number of bytes remaining.
lcall getorignum
lcall checklinestate
jnz nothungup13
ljmp hungup2
nothungup13: cjne a,#6,notflash13
; Flash.
lcall tryrejoin
ljmp s1setups2
s13fdone: ljmp cnewstate
notflash13: mov r1,a
anl a,#0x0f
cjne a,#5,checkdtmf13
; Pulse digit.
mov a,r1
swap a
anl a,#0x0f
mov r1,a
sjmp s13havedigit
; Did we get a DTMF digit?
checkdtmf13: lcall getdtmfdigit
mov r1,a
jc s13havedigit
; Check for timeout.
lcall getcalltimer
jz limbo13
ljmp nextchan
limbo13: ljmp limbo2
; Have a digit in A and R1. Store 0-9 in the table.
; Return to dial tone if 12 (DTMF #). Go to fast busy
; for any other value or if table overflows.
s13havedigit: jz error13
cjne a,#12,not12s13
dtone13: ljmp dial1stut2
not12s13: clr c
subb a,#11
jc storedigit13
error13: ljmp dialerror
; Decrement number of digits remaining to be stored, go to
; fast busy if already zero.
storedigit13: mov a,#2
lcall getcallvar
jz error13
dec a
mov r0,a
mov a,#2
lcall setcallvar
; Retrieve the pointer, store the digit, increment the
; pointer, and store it again.
clr a
lcall getcallvar
push ACC
mov a,#1
lcall getcallvar
mov DPH,a
pop DPL
mov a,r1
movx @dptr,a
inc dptr
clr a
movx @dptr,a ; Null terminate
mov r0,DPL
clr a
push DPH
lcall setcallvar
pop 0
mov a,#1
lcall setcallvar
; Reset the call timer
mov a,#limbotimeout
lcall setcalltimer
ljmp nextchan
cnotstate13: ljmp nextchan
cnewstate: mov dptr,#call_states
push ACC
mov a,channelptr
add a,DPL
mov DPL,a
pop ACC
movx @dptr,a
; Kludge: If the "lastchan" flag is set, don't give turns
; to any more channels. See comments elsewhere.
jbc lastchan,handlelimbo
nextchan: inc channelptr
mov a,#3
cjne a,channelptr,nextchan2
sjmp handlelimbo
nextchan2: ljmp chanloop
; Here we check for lines "in limbo" finally being hung up.
handlelimbo: mov dptr,#limboflags
movx a,@dptr
mov r2,#1 ; Mask
mov r0,a ; Flags
mov r1,#0 ; Counter
limboloop: mov a,r2
anl a,r0
jz limboskip ; Line not in limbo
mov a,r1
lcall checklinestate
jnz limboskip ; Line still in limbo
; Found a limboed line that is now hung up. Clear the
; limbo flag and also the line seize flag.
mov a,r2
cpl a
anl 0,a ; R0
anl lineflags,a
limboskip: inc r1
clr c
mov a,r2
rlc a
mov r2,a
jnc limboloop
; All lines done. Write the limbo flags back.
mov dptr,#limboflags
mov a,r0
movx @dptr,a
ljmp linescan
; Set call variable R0 for current channel to A.
setcallvar: add a,channelptr
add a,channelptr
add a,channelptr
mov dptr,#callvartbl
add a,DPL
mov DPL,a
mov a,r0
movx @dptr,a
ret
getcallvar: add a,channelptr
add a,channelptr
add a,channelptr
mov dptr,#callvartbl
add a,DPL
mov DPL,a
movx a,@dptr
ret
; Reset the digit queue
resetdigitq: clr dialqflag
mov dptr,#pulsegenstate
clr a
clr IE.7
movx @dptr,a
mov dptr,#dialqueue
clr a
movx @dptr,a
inc dptr
movx @dptr,a
mov dptr,#0x4A00
clr a
movx @dptr,a
setb IE.7
ret
; Set the network connection of the external line.
connectext: mov r0,a
mov dptr,#miscshadowreg
movx a,@dptr
anl a,#0xcf
orl a,r0
movx @dptr,a
mov dptr,#miscreg
movx @dptr,a
ret
; Seize a channel, return channel number in R2 and channel
; mask bit in R3. The caller must make sure that a channel
; is available before calling this.
grabchan: mov a,#1
mov r2,#0
lsfindchan: mov r3,a
anl a,channelflags
jnz gotchan
mov a,r3
rl a
inc r2
sjmp lsfindchan
gotchan: cpl a
anl channelflags,a
ret
; Look for a DTMF digit. Returns C set if A contains
; a valid digit.
getdtmfdigit: mov a,channelptr
mov dptr,#dtmf_digits
add a,DPL
mov DPL,a
clr c
movx a,@dptr
jz gdt_ret
anl a,#0x0f
push ACC
clr a
movx @dptr,a
pop ACC
setb c
gdt_ret: ret
; Set the call timer
setcalltimer: push ACC
mov a,channelptr
mov dptr,#call_timers
add a,DPL
mov DPL,a
pop ACC
movx @dptr,a
ret
; Get the call timer
getcalltimer: mov dptr,#call_timers
mov a,channelptr
movc a,@a+dptr
ret
; Get the originating line number
getorignum: mov dptr,#call_origs
sjmp getdest2
; Get the destination line number
getdestnum: mov dptr,#call_dests
getdest2: mov a,channelptr
movc a,@a+dptr
ret
; Start ringing line R0. Mask interrupts and initialize
; its table entry so that the pattern is begun on the very
; next ring update.
; The ring pattern is supplied in R2.
startring: mov dptr,#ringtable
mov a,r0
rl a
rl a
add a,DPL
mov DPL,a
clr IE.7
mov a,ringphase
add a,#10
mov r0,a
clr c
subb a,#240
jnc sr_notwrap
mov a,r0
sr_notwrap: movx @dptr,a
push ACC
inc dptr
mov a,r2
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
pop ACC
movx @dptr,a
setb IE.7
ret
; Stop ringing line R0. Write 240 into its table entries
; and also turn off its ring relay. The interrupt routine
; will take care of turning off the ring voltage supply.
stopring: mov dptr,#ringtable
mov a,r0
rl a
rl a
add a,DPL
mov DPL,a
lcall computer0mask
cpl a
mov r0,a
mov a,#240
clr IE.7
movx @dptr,a ; Clear start time
inc dptr
inc dptr
inc dptr
movx @dptr,a
mov dptr,#ringshadowreg
movx a,@dptr
anl a,r0
movx @dptr,a
mov dptr,#ringreg
movx @dptr,a ; Clear relay
setb IE.7
ret
settone: push ACC
mov a,channelptr
mov dptr,#tonetbl
add a,DPL
mov DPL,a
pop ACC
movx @dptr,a
ret
pointtoline: rl a
rl a
mov dptr,#hookscantbl+2
add a,DPL
mov DPL,a ; DPTR points to line state
ret
checklinestate: lcall pointtoline
clr IE.7
movx a,@dptr
push ACC
clr ACC.2
movx @dptr,a ; Ackowledge digit/flash
setb IE.7
pop ACC
ret
unseizeline: lcall computer0mask
cpl a
anl lineflags,a
ret
seizeline: lcall computer0mask
orl lineflags,a
ret
freechan: mov r0,channelptr
lcall computer0mask
orl channelflags,a
ret
; This subroutine connects line R0 with channel R1.
; The line must be previously unconnected.
connect: lcall computer0mask
connectset: inc r1
mov r0,a
mov a,r1
jnb ACC.1,notcnet1
mov dptr,#net1shadowreg
movx a,@dptr
orl a,r0
movx @dptr,a
mov dptr,#net1reg
movx @dptr,a
notcnet1: mov a,r1
jnb ACC.0,notcnet0
mov dptr,#net0shadowreg
movx a,@dptr
orl a,r0
movx @dptr,a
mov dptr,#net0reg
movx @dptr,a
notcnet0: ret
disconnect: lcall computer0mask
disconnectset: cpl a
mov r0,a
mov dptr,#net1shadowreg
movx a,@dptr
anl a,r0
movx @dptr,a
mov dptr,#net1reg
movx @dptr,a
mov dptr,#net0shadowreg
movx a,@dptr
anl a,r0
movx @dptr,a
mov dptr,#net0reg
movx @dptr,a
ret
settonerelay: mov r0,channelptr
lcall computer0mask
mov r0,a
mov dptr,#miscshadowreg
movx a,@dptr
orl a,r0
movx @dptr,a
mov dptr,#miscreg
movx @dptr,a
ret
cleartonerelay: mov r0,channelptr
lcall computer0mask
mov r0,a
mov dptr,#miscshadowreg
movx a,@dptr
xch a,r0
cpl a
anl a,r0
movx @dptr,a
mov dptr,#miscreg
movx @dptr,a
ret
computer0mask: clr a
inc r0
setb c
cr0mloop: rlc a
djnz r0,cr0mloop
ret
printhex2: mov R0,a
swap a
acall prtdig
mov a,R0
prtdig: anl a,#0fh
cjne a,#10,prt2
prt2: jc notalpha
add a,#7
notalpha: add a,#'0'
putchar: jnb SCON.1,putchar
mov SBUF,A
clr SCON.1
ret
; Subroutine to see if one of several lines got picked up.
; Bitmap of the lines to look at is in A. Returns carry set
; and picked-up line in R1, or carry clear if none picked up.
checkset: mov R0,a ; Line flags
mov R1,#-1
cslsloop: inc R1
mov a,R0
clr c
jz csret
rrc a
mov R0,a
jnc cslsloop ; Not this line
mov a,R1
lcall checklinestate
cjne a,#3,csnots3
; DPTR is still pointing at the switchhook data structure.
dec DPL
dec DPL
movx a,@dptr
subb a,#answerdelay
jc cslsloop
sjmp cspickup
csnots3: jz cslsloop
; Line R1 picked up.
cspickup: setb c
csret: ret
; Subroutine to store R1 in the call_dest variable of the
; current channel.
setcalldest: mov dptr,#call_dests
mov a,channelptr
add a,DPL
mov DPL,a
mov a,r1
movx @dptr,a ; Store dest. line #
ret
; Ditto but for call_orig.
setcallorig: mov dptr,#call_origs
mov a,channelptr
add a,DPL
mov DPL,a
mov a,r1
movx @dptr,a
ret
; Subroutine to stop ringing and release a set of lines.
; The set is given as a bitmap in A.
cancelset: mov r1,a
cpl a
anl lineflags,a ; Unseize lines
mov r2,#0
cstloop: mov a,r1
jz cstdone
clr c
rrc a
mov r1,a
jnc cstskip
mov r0,2
lcall stopring
cstskip: inc r2
sjmp cstloop
cstdone: ret
; Subroutine to grab available lines from a set that we
; want. A is the bitmap of the set we want.
greedy: cpl a
orl a,lineflags ; AND with available lines
cpl a
orl lineflags,a ; Seize them
ret
crlf: mov a,#13
lcall putchar
mov a,#10
ljmp putchar
puts: movx a,@dptr
jz pdone
lcall putchar
inc dptr
sjmp puts
pdone: ret
; Subroutine to test a line whose number is in A.
pokeline: cjne a,#9,pnotext
jb extlineflag,pokefail
setb c
ret
pnotext: dec a
mov r1,a
mov r0,a
lcall computer0mask
anl a,lineflags
jnz pokefail
mov a,r1
lcall pointtoline
movx a,@dptr
jnz pokefail
setb c
ret
pokefail: clr c
ret
; Subroutine to find the lowest-order set bit in a byte.
; Return carry clear if no bits set.
log2: mov r0,#0
clr c
log2loop: jz log2done
rrc a
jc log2done
inc r0
sjmp log2loop
log2done: ret
; GROUP CALL LOGIC
; ----------------
; In the "group call" states, there is no originator or
; destination. There is just a group of equal participants,
; a bitmap of which is in the call_orig variable. The
; subroutine below manages people dropping out of the
; group. It checks the state of all the participants,
; deletes participants who have hung up, and returns the
; number of participants still present.
checkcallers: lcall getorignum
mov r1,a ; Bitmap
mov r2,#1 ; Mask
mov r3,#0 ; Counter
mov r4,#0 ; Counter of participants
ccloop: mov a,r1
anl a,r2
jz ccnext
; Found a participant. Check his state.
mov a,r3
lcall checklinestate
cjne a,#6,ccnotflash
sjmp ccflash
ccnotflash: jnz cccount
; He's gone.
clr a
lcall setholdnum
; Disconnect his network connection, free up his line.
mov r0,3
lcall disconnect
mov r0,3
lcall computer0mask
cpl a
anl 1,a
anl lineflags,a
sjmp ccnext
cccount: inc r4
ccnext: inc r3
mov a,r2
clr c
rlc a
mov r2,a
jnc ccloop
lcall setcallorig ; From R1
mov a,r4
ret
; The participant has pressed "flash". See if another call
; is already on hold.
ccflash: lcall getholdnum
jz ccnothold
; Nobody on hold any more here.
push ACC
clr a
lcall setholdnum
pop ACC
; Merge calls. Move all the participants from this one
; to the other call.
dec a
mov r2,a ; R2 = new channel
lcall getorignum
mov r3,a ; R3 = participants here
mov dptr,#call_origs
mov a,DPL
add a,r2
mov DPL,a
movx a,@dptr
orl a,r3
movx @dptr,a
; If the current channel has the "have external line" flag,
; then (a) move it to the other channel, (b) connect the
; external line to the other channel.
mov a,#1
lcall getcallvar
jz ccnoext
mov r1,channelptr
mov channelptr,r2
mov a,#1
mov r0,a
lcall setcallvar
mov channelptr,r1
mov a,r2
inc a
swap a
lcall connectext
; Move everyone over to the other channel
ccnoext: mov a,r3
lcall disconnectset
mov r1,2
mov a,r3
lcall connectset
; Nobody left here
mov r1,#0
lcall setcallorig
mov a,#1
mov r0,#0
lcall setcallvar
; Return with the caller count set to 0.
clr a
ret
; The caller is trying to "flash out" of a call. Try to
; get a channel, if none available, ignore the flash.
ccnothold: mov a,channelflags
jz cccount
; Remove the caller from the current session
mov r0,3
lcall disconnect
mov r0,3
lcall computer0mask
cpl a
anl 1,a
; Get the channel. This trashes R2 and R3 so stash them.
push 2
push 3
lcall grabchan
pop 3
mov a,channelptr
inc a
lcall setholdnum
; Initialize channel R2 to state 1 and set the originating
; number.
mov dptr,#call_states
mov a,DPL
add a,2
mov DPL,a
mov a,#1
movx @dptr,a ; State 1
mov dptr,#call_origs
mov a,DPL
add a,2
mov DPL,a
mov a,r3
movx @dptr,a ; Originator R3
; All done with this line.
pop 2
ljmp ccnext
; This subroutine is called when a caller aborts call
; setup (dialing, busy signal) with "flash".
tryrejoin: lcall getorignum
mov r3,a
lcall getholdnum
jnz tryok
inc a
ret
; Re-joining a held call is possible. Release this channel,
; reconnect the caller to the new channel and add him to
; the set talking on that channel. Return the number of
; the new channel in R2.
tryok: lcall getorignum
mov r3,a ; R3 = my line
lcall getholdnum
dec a
mov r2,a ; R2 = channel to join
clr a
lcall setholdnum
tryok2: lcall cleartonerelay
clr a
lcall settone
lcall freechan
mov r0,3
lcall computer0mask
mov r1,a ; R1 = line mask
mov dptr,#call_origs
mov a,DPL
add a,r2
mov DPL,a
movx a,@dptr ; Add to other channel's
orl a,r1 ; set of lines
movx @dptr,a
mov r0,3
lcall disconnect ; Disconnect
mov r0,3
mov r1,2
lcall connect ; Connect to other channel
xch a,r1
clr a
ret
; This subroutine unparks a call and joins it. It's the
; same procedure as rejoining a held call and as much as
; possible the same code. The caller supplies the number
; against whom the call is parked in R3.
unpark: lcall getparknum
dec a
mov r2,a ; R2 = channel to join
clr a
lcall setparknum
lcall getorignum
mov r3,a ; R3 = my line
; Sanity requirement: We must never get into a call that
; we also have on hold. If unparking a call that is also
; on hold, clear the hold too.
lcall getholdnum
dec a
cjne a,2,tryok2
clr a
lcall setholdnum
sjmp tryok2
; This subroutine provides the periodic "beep beep" tone
; to a line that is on hold. It is called directly after
; "checkcallers" and must preserve ACC. R2 is the number
; of internal callers that must be present for the hold
; tone to be generated (1 for internal, 0 for outside calls).
holdmagic: mov r1,a
mov r0,channelptr
lcall computer0mask
mov r0,a
mov dptr,#miscshadowreg
movx a,@dptr
anl 0,a
mov a,r1
cjne a,2,hmnot1
cjne r0,#0,hmret
; There is exactly one caller and the tone relay is not
; set. Set it and apply hold tone.
lcall settonerelay
mov a,#0x23
lcall settone
sjmp hmret
hmnot1: mov a,r0
jz hmret
; There is not exactly one caller and the tone relay is
; still set. Clear it again and clear the tone generator.
lcall cleartonerelay
clr a
lcall settone
hmret: mov a,r1
ret
getparknum: mov dptr,#parktable
sjmp getholdnum1
getholdnum: mov dptr,#holdtable
getholdnum1: mov a,r3
movc a,@a+dptr
ret
setparknum: mov dptr,#parktable
sjmp setholdnum1
setholdnum: mov dptr,#holdtable
setholdnum1: push ACC
mov a,r3
add a,DPL
mov DPL,a
pop ACC
movx @dptr,a
ret
amiparked: mov dptr,#parktable
sjmp amiheld1
amiheld: mov dptr,#holdtable
amiheld1: mov a,channelptr
inc a
mov r1,a
mov r0,#8
ahloop: movx a,@dptr
cjne a,1,ahloopbot
setb c
ret
ahloopbot: inc dptr
djnz r0,ahloop
clr c
ret
clearparks: mov dptr,#parktable
sjmp clearholds1
clearholds: mov dptr,#holdtable
clearholds1: mov a,channelptr
inc a
mov r1,a
mov r0,#8
chloop: movx a,@dptr
cjne a,1,chloopbot
clr a
movx @dptr,a
chloopbot: inc dptr
djnz r0,chloop
ret
; Subroutine to put a byte on the digit dial queue for the
; external line.
enqueue: clr dialdoneflag
; Enqueue the digit for pulsing out by the interrupt
; service routine. Returns carry set if successful, clear
; if the queue was full and the byte was not enqueued.
push ACC
mov dptr,#dialqueue
movx a,@dptr ; Get head ptr
push ACC ; Save uninc'd pointer
inc a
cjne a,#dialqsize,enq_ok
clr a
enq_ok: mov r1,a
inc dptr
clr IE.7 ; Interrupts off
movx a,@dptr ; Get tail ptr
clr c
subb a,r1 ; If equal to inc'd head ptr
jz enqfull ; then the queue is full
dec DPL
mov a,r1
movx @dptr,a
pop ACC ; Restore uninc'd pointer
inc a ; Adjust for DPTR offset
inc a
add a,DPL
mov DPL,a ; Point to queue entry
pop ACC ; Restore data to write
movx @dptr,a ; Write in queue
setb dialqflag
setb IE.7 ; Interrupts on
setb c
ret
; Error exit
enqfull: setb IE.7
pop ACC
pop ACC
clr c
ret
;-----------------------------------------------------------
; Interrupt service routine
;
tick_isr: djnz tickcnt1,isr_exit
; Reload the counter and save context.
mov tickcnt1,#18
push PSW
push ACC
push DPL
push DPH
mov dptr,#real_entry
push DPL
push DPH
isr_exit: reti
; This is executed 200 times per second. Select register
; bank 1 (bank 0 will be restored by the "pop psw" at exit).
real_entry: mov PSW,#08h
; ============= LINE SCAN ================================================
; Since there are only 8 lines, we might as well scan
; all of them 200 times a second, not just the ones
; where dial pulses might occur.
mov dptr,#hookreg
movx a,@dptr ; Get hookswitch states
mov r1,a
mov r3,a ; Save for later
mov r0,#8 ; Line counter
mov dptr,#hookscantbl
lsloop: mov a,r1
rrc a
mov r1,a
mov hookflag,c
; DPTR points table entry, hookflag contains hookswitch
; status. Table entry contains
; 0 - timer (counts down to zero, then stops)
; 1 - state
; 2 - mainline communication flag
; 4 - dial pulse counter
; First, handle the timer. Timers run up until they hit
; 0xff, then they stay there.
movx a,@dptr
mov r2,a
inc a
jz hst0
movx @dptr,a
inc r2 ; R2 contains timer value
; Line loop. R0 counts down the lines. R1 saves the
; hookswitch bits. R2 is the timer value associated with
; the current line.
hst0: inc DPL ; Point to byte 1
movx a,@dptr
jnz notstate0
; -----------------------------------------------
; State 0: Line is on hook.
jb hookflag,s0_offhook
s0done: inc dptr ; byte 2
inc dptr
ljmp lloop_end
s0_offhook: dec DPL ; Zero the timer
clr a
movx @dptr,a
inc dptr
mov a,#5 ; Go to state 5
movx @dptr,a
inc dptr
mov a,#3
movx @dptr,a ; Signal mainline
inc dptr
ljmp lloop_end
notstate0: cjne a,#5,notstate5
; -----------------------------------------------
; State 5: Line has just gone offhook.
; The mainline has gotten the "3" flag indicating
; that the line is indeed offhook. This is so
; a call answer can be connected right away.
; But the line has not been offhook long enough
; to warrant dial tone -- we don't want a noisy
; switchhook dialing a bogus digit by bouncing.
; If the line is onhook again, return to state 0
; and reset the host flag.
jb hookflag,s5stilloff
clr a
movx @dptr,a
inc dptr
movx @dptr,a
inc dptr
ljmp lloop_end
; Still offhook. Check the timer, stay in this
; state until it times out.
s5stilloff: dec DPL
movx a,@dptr
inc dptr
cjne a,#dialtonedelay,s0done
; The line has been offhook long enough. Set
; the state to 1. Set the host flag to 1 only
; if it's still 3.
mov a,#1
movx @dptr,a
inc dptr
movx a,@dptr
cjne a,#3,s5not3
mov a,#1
movx @dptr,a
s5not3: inc dptr
ljmp lloop_end
notstate5: cjne a,#1,notstate1
; -----------------------------------------------
; State 1: Line is offhook
jb hookflag,s0done
; Line gone onhook again. Advance to state 2 but
; don't tell the mainline anything yet. Start
; the timer.
dec DPL ; Zero the timer
clr a
movx @dptr,a
inc dptr
mov a,#2 ; Go to state 2
movx @dptr,a
sjmp s0done
notstate1: cjne a,#2,notstate2
; -----------------------------------------------
; State 2: Line has gone on hook again, timer is
; running.
; First of all, if the line has been on hook for
; >maxflash time units, then it's hung up, not
; signaling anything.
jb hookflag,s2_offhook
mov a,r2 ; Timer value
clr c
subb a,#maxflash
jc s0done ; Not yet...
; Hung up. Set both the host flag and the state to 0.
clr a
movx @dptr,a
inc dptr
movx @dptr,a ; Signal mainline
inc dptr
ljmp lloop_end
; Gone offhook again in state 2. Check for the minimum
; flash threshold.
s2_offhook: mov a,r2
clr c
subb a,#minflash
jnc s2_flash
; Too short for a flash, so it's the first pulse of a digit.
; Start the timer again and set the count to 1.
dec DPL ; Timer
clr a
movx @dptr,a
inc dptr ; State
mov a,#3
movx @dptr,a
inc dptr ; Host flag
inc dptr ; Pulse count
mov a,#1
movx @dptr,a
ljmp lloop_end
; This was a flash. Signal it and return to state 1.
s2_flash: mov a,#1
movx @dptr,a
inc dptr ; Host flag
mov a,#6
movx @dptr,a
s2_finishup: inc dptr ; Pulse count
ljmp lloop_end
notstate2: cjne a,#3,notstate3
; -----------------------------------------------
; State 3: Line is offhook between digit pulses.
jnb hookflag,s3_onhook
mov a,r2
clr c
subb a,#digitpause
jnc s3_gotpause
inc dptr
inc dptr
ljmp lloop_end
; Still offhook after digit timeout. Signal the mainline
; and go to state 1.
s3_gotpause: inc dptr
inc dptr
movx a,@dptr ; Get count
swap a
orl a,#5
dec DPL
movx @dptr,a ; Signal mainline
dec DPL
mov a,#1
s3_setstate: movx @dptr,a
s3_done: inc dptr
inc dptr
ljmp lloop_end
; On hook again after something less than an interdigit
; pause. Restart the timer and go to state 4.
s3_onhook: clr a
dec DPL
movx @dptr,a ; Clear timer
inc dptr
mov a,#4
sjmp s3_setstate
; -----------------------------------------------
; State 4: Onhook again while counting digit
; pulses.
notstate3: jb hookflag,s4_offhook
mov a,r2
clr c
subb a,#maxflash
jc s3_done
clr a
movx @dptr,a
inc dptr
movx @dptr,a ; Signal mainline
inc dptr
ljmp lloop_end
; Offhook again. Clear the timer, return to state 3,
; increment the digit pulse counter.
s4_offhook: dec DPL
clr a
movx @dptr,a
inc dptr
mov a,#3
movx @dptr,a
inc dptr
inc dptr
; Advance the pulse count but not past 11 pulses.
movx a,@dptr
inc a
cjne a,#12,s4_storecount
dec a
s4_storecount: movx @dptr,a
; fall through
lloop_end: inc dptr ; Next timer
djnz r0,cont_scan
sjmp scan_exit
cont_scan: ljmp lsloop
; ============= RING TRIP ================================================
; Handle ring trip. Any line that is offhook must have
; ringing cancelled immediately.
scan_exit: mov dptr,#ringshadowreg
movx a,@dptr ; Ring relay states
anl a,r3 ; Offhook states
; Any bits now asserted are lines that are offhook and
; ringing.
jz rt_exit ; No ring trips
cpl a
mov r4,a ; R4 has 0s for tripped lines
movx a,@dptr
anl a,r4
movx @dptr,a
mov dptr,#ringreg
movx @dptr,a
; Ring is now off on the tripped lines. Set the
; ring start and next event times of all offhook
; lines to 240 to prevent future rings.
rt_exit: mov dptr,#ringtable
mov a,r3
mov r1,#8
rtloop: rrc a
jnc rt_ok
push ACC
mov a,#240
movx @dptr,a
inc dptr
inc dptr
inc dptr
movx @dptr,a
pop ACC
sjmp inc_last
rt_ok: inc dptr
inc dptr
inc dptr
inc_last: inc dptr
djnz r1,rtloop
; ============= DTMF RECEIVER SCAN =======================================
; Poll the DTMF receivers. Do this at interrupt level for
; mainline convenience. Also do it 200 times/sec because
; there are plenty of processor cycles.
mov dptr,#dtmfregs
movx a,@dptr
anl a,#0x70 ; Get the three "valid" bits
mov r0,a
xrl a,dtmf_flags ; Compare to previous state
jz dtmf_done
; The DTMF valid bits have changed. Update the saved
; version, then for all bits which have changed and are
; now "off", collect the digit. Do it that way so that
; nothing happens until the touch tone button is released,
; just like in the real phone system. The DTMF decoders
; keep the previous valid code latched.
mov dtmf_flags,r0 ; Update previous states
xch a,r0
cpl a
anl a,r0
swap a ; New digit flags in A<2:0>
mov r0,a
mov r1,#0
push P2
mov P2,#dtmfregpage
mov dptr,#dtmf_digits
dtmfloop: mov a,r0
jz dtmf_done2
clr c
rrc a
mov r0,a
jnc dtmf_nodigit
movx a,@r1 ; Get digit
movx @dptr,a ; Store in table
dtmf_nodigit: inc P2 ; Go to next DTMF register
inc P2
inc P2
inc P2
inc DPTR ; Go to next table entry
sjmp dtmfloop
dtmf_done2: pop P2
; ============= DIAL PULSE GENERATION ====================================
; Handle outpulsing of digits on a pulse-dial external line.
dtmf_done: jnb dialqflag,pgs0exit
mov dptr,#pulsegenstate
movx a,@dptr ; Get state
jnz pgnotstate0
; State 0: Ready for action. Check if it is OK to dial yet.
mov dptr,#extringtimer
movx a,@dptr
jnz pgs0exit
; Check the digit queue.
mov dptr,#dialqueue
movx a,@dptr
mov r0,a
inc dptr
movx a,@dptr
cjne a,8,pghavework
; The queue is empty. Reset the queue flag and set the
; "done" flag so the speech path can be unmuted.
clr dialqflag
setb dialdoneflag
pgs0exit: ljmp outdig_done
; Save the queue pointer, bump it up, and read the digit
; from the queue.
pghavework: mov r0,a
inc a
cjne a,#dialqsize,pgnowrap
clr a
pgnowrap: movx @dptr,a
inc dptr
mov a,DPL
add a,r0
mov DPL,a
movx a,@dptr
; If 15, it's a flash.
cjne a,#15,pgnotflash
clr P1.1
mov dptr,#pulsegenstate
mov a,#11
movx @dptr,a
mov pulsegentimer,#dgflashtime
ljmp outdig_done
; If bit 4 is set, it's a DTMF digit to send.
pgnotflash: jnb ACC.4,pgdopulse
anl a,#0x0f
mov dptr,#dtmfgentbl-1
movc a,@a+dptr
mov dptr,#0x4A00
movx @dptr,a
mov dptr,#pulsegenstate
mov a,#13
movx @dptr,a
mov pulsegentimer,#dtmfdigittime
ljmp outdig_done
; Translation table of DTMF digits (1-12 valid) to values
; to write to the DTMF generator register.
dtmfgentbl: .byte 0x4,0x8,0xc,0x5,0x9,0xd,0x6,0xa,0xe,0xb,0x7,0xf
; Else it's a digit to pulse out
pgdopulse: clr P1.1
mov dptr,#pulsegenstate
movx @dptr,a
mov pulsegentimer,#dgmaketime+dgbreaktime
ljmp outdig_done
pgnotstate0: cjne a,#11,pgnotstate11
; State 11. Timing out a flash. Decrement the timer
; until it's zero
djnz pulsegentimer,pgs11done
setb P1.1
interdigpause: mov pulsegentimer,#dgdigitpause
mov dptr,#pulsegenstate
mov a,#12
movx @dptr,a
pgs11done: sjmp outdig_done
pgnotstate11: cjne a,#12,pgnotstate12
; State 12. Timing out an interdigit pause.
djnz pulsegentimer,pgs12done
clr a
mov dptr,#pulsegenstate
movx @dptr,a
pgs12done: sjmp outdig_done
pgnotstate12: cjne a,#13,pgnotstate13
; State 13. Generating a DTMF tone.
djnz pulsegentimer,outdig_done
mov dptr,#0x4A00
clr a
movx @dptr,a
mov dptr,#pulsegenstate
mov a,#12
movx @dptr,a
mov pulsegentimer,#dtmfdigitpause
sjmp outdig_done
; Any other state means that a digit is being pulsed out.
pgnotstate13: djnz pulsegentimer,pgnottimeout
; Timer has reached 0, this means both the pulse and
; the interdigit time have been generated. Decrement the
; state and start another pulse.
dec a
jnz pgdopulse
sjmp interdigpause
; When the timer passes "dgbreaktime", end the pulse.
pgnottimeout: mov a,#dgbreaktime
cjne a,pulsegentimer,outdig_done
setb P1.1
; fall through
; Scale the 200/sec interrupt rate down to 40/sec for the
; ring timing.
outdig_done: djnz ringscaler,int_exit
mov ringscaler,#5
sjmp int40
int_exit: pop DPH
pop DPL
pop ACC
pop PSW
ret
; The following is executed 40 times a second.
; ============= RING CADENCE =============================================
int40: mov dptr,#ringshadowreg
movx a,@dptr
mov r0,a ; Relay settings
mov r1,#1 ; Relay set mask
mov dptr,#ringtable
; R0: Relay settings
; R1: Bit corresponding to current line
; DPTR: Points to current table entry
r_loop: movx a,@dptr ; Get event time
cjne a,ringphase,r_nextl0 ; Nothing to do right now
; Event for this line. Get next bit.
inc dptr
movx a,@dptr
mov r2,a
clr c
rrc a
; Set/clear relay as appropriate
mov a,r1
jnc ringoff
orl 8,a ; Relay on
sjmp reldone
ringoff: cpl a
anl 8,a
; If more bits remain in the pattern, then schedule next
; event 12 ticks from now, and write the shifted pattern
; back.
reldone: mov a,r2
jz nomorebits
clr c
rrc a
movx @dptr,a
dec DPL
mov a,ringphase
clr c
subb a,#228
jnc writeback
add a,#240
writeback: movx @dptr,a
sjmp r_nextl0
; The shifted pattern ran out. Restore the start time
; and full pattern.
nomorebits: inc dptr
inc dptr
movx a,@dptr
push ACC
dec DPL
movx a,@dptr
dec DPL
movx @dptr,a
pop ACC
dec DPL
movx @dptr,a
; fall through
r_nextl0: inc dptr
inc dptr
inc dptr
inc dptr
mov a,r1
clr c
rlc a
mov r1,a
jnc r_loop
; ============= RING HOUSEKEEPING ========================================
; R0 now has the enable bits for all lines that are being
; rung from other internal lines. Now handle ringing from
; the external line.
mov dptr,#extlflags
movx a,@dptr
cpl a
anl 8,a ; R0 in reg bank 1
jb P1.0,exroff
cpl a
orl 8,a ; R0 in reg bank 1
; Done. Update the ring relays, bump the ring phase up
; by one and exit.
exroff: mov a,r0
mov dptr,#ringreg
movx @dptr,a
mov dptr,#ringshadowreg
movx @dptr,a
; If any ring relay is on, turn on the ring voltage
; generator. Otherwise turn it off.
jz r_toneoff
mov a,#0x40
r_toneoff: mov r0,a
mov dptr,#toneshadowreg
movx a,@dptr
anl a,#0xbf
orl a,r0
movx @dptr,a
mov dptr,#tonereg
movx @dptr,a
inc ringphase
mov a,ringphase
cjne a,#240,r_exit
clr a
mov ringphase,a
; ============= CALL PROGRESS TONES ======================================
; Handle call progress tones. The tone table has one
; byte entry for each tone generator, encoded as follows:
;
; 00 - no tone
; 01 - continuous dial tone
; F1 - dial tone with a break every 1/2 second
; E1 - dial tone with break every full second
; x1 - x-1 dial tone "stutters" followed by continuous
; (5 stutters/sec)
; x2 - ringback, following the ring relay of line x
; 03 - regular busy (1/sec)
; 13 - fast busy (2/sec)
; 23 - hold (two stutters of "busy" every 6 seconds)
;
; Pulsating tones do not turn on in mid-pulse but do turn
; off in mid-pulse.
r_exit: mov dptr,#toneshadowreg
movx a,@dptr
mov r0,a ; Tone reg image
mov dptr,#tonetbl
mov r1,#3 ; Tone generator counter
; Scan the table
tonelooptop: movx a,@dptr ; Get tone mode
jz tone_off ; Mode 0: Tone off
anl a,#0x0f
cjne a,#1,notdial
; Mode x1: Dial tone, stuttering or otherwise.
movx a,@dptr
swap a
anl a,#0x0f ; Number of stutters.
cjne a,#0x0f,notbreak1
; Continuous with break every 1/2 second. That is, off from
; 0-3 and from 20-23.
mov a,tonephase
anl a,#0xfc
jz tone_off
cjne a,#20,tone_on
sjmp tone_off
notbreak1: cjne a,#0x0e,notbreak
mov a,tonephase
anl a,#0xfc
jz tone_off
sjmp tone_on
notbreak: jz tone_on ; On immediately if none.
; Stutter "off" periods are 4-7, 12-15, etc (i.e. bit 3
; is set).
jb tonephase.2,tone_off
; Stutter "on" times are 0, 8, 16, 24, 32 (i.e. lower 3 bits
; clear). Already checked bit 2.
jb tonephase.1,toneloopbot
jb tonephase.0,toneloopbot
; It's a stutter-on time. Decrement the stutter counter
; and turn the tone on. If the counter goes to 0, the tone
; will stay on because the mode is now "continuous dial
; tone".
dec a
swap a
orl a,#1
movx @dptr,a
sjmp tone_on
notdial: cjne a,#2,notringback
; Ringback tone. Fetch the number of the line to monitor.
movx a,@dptr
swap a
anl a,#0x0f
mov r2,a
inc r2
clr a
setb c
rbtloop: rlc a
djnz r2,rbtloop
; Check the appropriate bit in the ring relay shadow
; register.
mov r2,a
push DPL
push DPH
mov dptr,#ringshadowreg
movx a,@dptr
pop DPH
pop DPL
anl a,r2
jnz tone_on
sjmp tone_off
notringback: movx a,@dptr
cjne a,#0x03,notslowbusy
; Slow busy: On at 0, off at 20-39.
mov a,tonephase
jz tone_on ; On at 0
clr c
subb a,#20
jnc tone_off ; Off at >= 20
sjmp toneloopbot
; Fast busy: 0.3 sec (12 ticks) on, 0.2 sec (8 ticks) off.
notslowbusy: cjne a,#0x13,notfastbusy
mov a,tonephase
clr c
subb a,#20
jnc nsb_ok
mov a,tonephase
nsb_ok: jz tone_on
clr c
subb a,#12
jc toneloopbot
sjmp tone_off
notfastbusy: mov a,ringphase
anl a,#0xf4
jz tone_on
; fall through
tone_off: mov a,#0xfc
anl a,r0
mov r0,a
sjmp toneloopbot
not13: jnz toneloopbot
tone_on: movx a,@dptr
anl a,#0x03
xch a,r0
anl a,#0xfc
orl a,r0
mov r0,a
toneloopbot: inc dptr ; Next table entry
mov a,r0
rr a
rr a
mov r0,a
dec r1
mov a,r1
jz toneloopend
ljmp tonelooptop
toneloopend: mov a,r0
rr a
rr a
mov dptr,#toneshadowreg
movx @dptr,a
mov dptr,#tonereg
movx @dptr,a
; bump the tone phase counter
inc tonephase
mov a,#40
cjne a,tonephase,t_nowrap0
mov tonephase,#0
; ============= MISC. TIMERS =============================================
; handle the 40 tick/sec external ring timer
t_nowrap0: mov dptr,#extringtimer
movx a,@dptr
jz t_nowrap
dec a
movx @dptr,a
; Scale the event rate down to 1 per second
t_nowrap: djnz tickcnt2,t_exit
mov tickcnt2,#40
; Go through the call timers, decrementing any that aren't
; at zero.
mov dptr,#call_timers
mov r0,#3
ctloop: movx a,@dptr
jz ct_is0
dec a
movx @dptr,a
ct_is0: inc dptr
djnz r0,ctloop
t_exit: ljmp int_exit
Back