;---------------------------------------------------------------------------; ; RS-232 Data Switch Firmware ; ; ; ; File: ISR.ASM ; ; ; ; This file contains the interrupt service routine which scans for breaks ; ; and DTR events, updates the clock, and handles the timers. ; ;---------------------------------------------------------------------------; isr segment code unit rseg isr $nolist $include(globals.asm) $list extrn code(daysthismonth) public tick_isr ;--------------------------------------------------------------------------- ; Module: tick_isr ; ; Description: ; This code is executed whenever a timer 0 interrupt occurs, which is ; 3600 times a second. It prescales the 3600 interrupts per second using ; counter tickcnt1. 100 times a second, it services the latches which ; detect break conditions on the serial inputs, and routes DTR/DCD inputs ; to their corresponding outputs. Then counter tickcnt2 scales the event ; rate to one per second. Once per second, the clock and timers are ; updated, and DTR on/off events detected. ; ; Inputs: n/a ; Outputs: n/a ; ; Short and sweet. Since we get an interrupt every 256 ; instruction cycles, we want to get out of here quick. When ; the counter does not reach zero, we execute only two ; instructions. tick_isr: djnz tickcnt1,isr_exit ; Reload the counter and save context. mov tickcnt1,#36 push psw push acc push dpl push dph ; More interrupts will occur while we are processing this one, ; so cheat the interrupt system by doing a "reti", even though ; we are going to execute interrupt code. mov dptr,#scan_entry push dpl push dph isr_exit: reti ; This is executed 100 times per second. Select register ; bank 1 (bank 0 will be restored by the "pop psw" at exit). scan_entry: mov psw,#08h ; Allow external commands to hook in here. lcall extisr ; Scan for breaks ; --------------- ; Get the port 1 register and prepare an image of it to be ; used while scanning for breaks. That is, mask out the ; selection bits (0-3), set the latch reset bit to false ; (bit 4 high), and set bits 5 and 6 high so they can be ; used as inputs. mov a,p1 anl a,#0f0h orl a,#070h mov r1,a ; Start the loop. mov r0,#0 mov dptr,#brkcnttbl ; Break scan loop: Load the current port number into ; the port register to select the right latch. scan_loop1: mov a,r0 orl a,r1 mov p1,a ; Check if the latch has been reset. jb p1.5,gotbrk ; No break condition. Reset the latch again. clr p1.4 setb p1.4 ; Check if this is the end of a sufficiently long break. movx a,@dptr cjne a,#0ffh,notbrk ; It is. Set the break flag for this line. If the break flag ; was not already set, set the attention flag. push dpl push dph mov a,#brkflagtbl-256*(brkflagtbl/256) add a,r0 mov dpl,a mov a,#brkflagtbl/256 addc a,#0 mov dph,a movx a,@dptr jnz flagwasset dec a movx @dptr,a setb attnflag flagwasset: pop dph pop dpl ; Reset the break timer for this line to zero, i.e. break has ; not been active for any amount of time. notbrk: clr a movx @dptr,a sjmp loop1end ; The latch is still set, i.e. the line has been in the break ; state for the entire 10 milliseconds. Check the break timer ; for the line. If it is -1 (i.e. incrementing it sets it to ; zero), don't do anything as the break is already long enough. gotbrk: movx a,@dptr inc a jz loop1end ; Store the data pointer and the incremented timer value. push dpl push dph mov r2,a ; Get the break timeout for this line. mov dptr,#brktimtbl mov a,r0 movc a,@a+dptr jnz brk_dotimeout ; The break timeout is zero, i.e. breaks on the line should be ; ignored. Just set the timer to zero again and get out. mov r2,#0 sjmp notdoneyet ; The timeout is not zero. Compare it to the current timer ; value. brk_dotimeout: setb c subb a,r2 jnc notdoneyet ; The break has been active long enough. Load -1 into the ; timer to signal this. mov r2,#0ffh ; Common exit: Load 0, -1, or the updated timer value into ; the timer register. notdoneyet: pop dph pop dpl mov a,r2 movx @dptr,a ; Continue until all ports done. loop1end: inc dptr inc r0 cjne r0,#maxport,scan_loop1 ; Route the DTR/DCD signals to their destinations. ; ------------------------------------------------ ; R2 is used to build the bit image to be loaded into the DTR ; output register. Initialize it and the loop counter to zero. mov r2,#0 mov r0,#0 ; Loop: Find what the current port is connected to. dtrloop2: mov dptr,#c_sourcetbl mov a,r0 movc a,@a+dptr mov r3,a cjne a,#15,not15 ; Port 15 means disconnected. Get DTR from the default ; table in that case. mov dptr,#dtrdeftbl mov a,r0 movc a,@a+dptr rrc a sjmp savedtr ; Continue here for connected ports. See if the source port ; has DTR/DCD ignored (timeout = 0). If it does, pretend that ; its DTR/DCD input is asserted. not15: mov dptr,#dtrtimtbl-1 movc a,@a+dptr jnz dtr_normal setb c sjmp savedtr ; Copy the DTR/DCD state from the source port to the ; destination port. Select the source port, read its DTR/DCD ; input, and save it. dtr_normal: mov a,r3 dec a orl a,r1 mov p1,a mov c,p1.6 ; Continue here for all cases. Shift the DTR/DCD value into ; the bit image. savedtr: mov a,r2 rrc a mov r2,a ; Continue until all 4 ports with DTR/DCD outputs are done. inc r0 cjne r0,#4,dtrloop2 ; This is where we load the DTR/DCD output bits. First, load ; the other half of the register image with the connection ; data for port 6. mov dptr,#connectbl+6 movx a,@dptr orl a,#0f0h xrl a,r2 ; Mask with the DTR mask to implement the "DCD force on" ; feature. anl a,dtr_mask_and ; Finally, load the result into the output register. mov dptr,#connregbase+3 movx @dptr,a ; Load the DTR/DCD output bits into the physical connection ; table also, so that updates of the selection information for ; port 6 don't generate glitches on the DTR/DCD outputs. swap a anl a,#0fh mov dptr,#connectbl+7 movx @dptr,a ; Fix up the P1 register by setting all bits used to high ; (less power consumption that way). mov a,r1 orl a,#7fh mov p1,a ; Get or set the date as requested. ; --------------------------------- jnb date_get_flag,not_date_get mov hours_copy,hours mov minutes_copy,minutes mov seconds_copy,seconds mov day_copy,day mov month_copy,month mov year_copy,year clr date_get_flag not_date_get: jnb date_set_flag,not_date_set mov hours,hours_copy mov minutes,minutes_copy mov seconds,seconds_copy mov day,day_copy mov month,month_copy mov year,year_copy clr date_set_flag ; Count down tickcnt2 to scale the event rate down to 1 per ; second. not_date_set: djnz tickcnt2,int_exit sjmp do1sec ; Restore context and exit. int_exit: pop dph pop dpl pop acc pop psw ret ; The following is executed once per second. First, reload ; the counter. do1sec: mov tickcnt2,#100 ; Scan for DTR/DCD on/off events ; ------------------------------ ; Initialize the loop counter and data table pointer. mov r0,#0 mov dptr,#dtrcnttbl ; Prepare P1 image as before. mov a,p1 anl a,#0f0h orl a,#070h mov r1,a ; Loop: Load the port number into P1 and get the DTR/DCD ; status for that port. dtrloop1: mov a,r0 orl a,r1 mov p1,a jnb p1.6,nodtr ; DTR/DCD is asserted. Record this fact by storing a zero in ; the DTR/DCD timer table, but first get the old value of that ; entry. movx a,@dptr mov r2,a clr a movx @dptr,a ; If the old value was -1, then DTR/DCD has just turned on. cjne r2,#0ffh,dtrloop1_end push dpl push dph ; See if we should respond to a DTR/DCD event by reading the ; timeout table. mov a,r0 mov dptr,#dtrtimtbl movc a,@a+dptr jz dtr_noflag ; The timeout is non-zero, so we should respond. Store a 1 ; in the DTR/DCD flag table entry for this port, and set the ; attention flag. mov a,r0 add a,#dtrflagtbl-256*(dtrflagtbl/256) mov dpl,a clr a addc a,#dtrflagtbl/256 mov dph,a mov a,#1 movx @dptr,a setb attnflag ; Common exit for DTR/DCD on handling. dtr_noflag: pop dph pop dpl sjmp dtrloop1_end ; DTR/DCD is not asserted. Get the timer value for that port ; and increment it. If it is -1 (incrementing it turns it to ; zero), do nothing. nodtr: movx a,@dptr inc a jz dtrloop1_end ; Save timer value and table pointer. push dpl push dph mov r2,a ; Check if this port has a DTR/DCD timeout associated with it, ; i.e. if DTR/DCD events should be reported. mov dptr,#dtrtimtbl mov a,r0 movc a,@a+dptr ; If not, just set the DTR/DCD timeout value to 1, and ; continue. This means that the mainline will see a nonzero ; value and know DTR/DCD is not asserted. jnz dtr_havetime mov r2,#1 sjmp dtr_notyet ; A timeout value exists, so compare the timer to it. dtr_havetime: setb c subb a,r2 jnc dtr_notyet ; DTR/DCD has been deasserted long enough to cause a timeout. ; Report this by storing a 2 in the DTR/DCD flag table and ; setting the attention flag. mov a,r0 add a,#dtrflagtbl-256*(dtrflagtbl/256) mov dpl,a clr a addc a,#dtrflagtbl/256 mov dph,a mov a,#2 movx @dptr,a setb attnflag ; Store a -1 into the timer to indicate that the timeout has ; occurred and the event has been reported. pop dph pop dpl mov a,#0ffh movx @dptr,a sjmp dtrloop1_end ; Common exit: Load the updated timer value into the timer. dtr_notyet: pop dph pop dpl mov a,r2 movx @dptr,a ; Continue until all ports are scanned. dtrloop1_end: inc dptr inc r0 cjne r0,#maxport,dtrloop1 ; Fix up the P1 register as earlier. mov a,r1 orl a,#7fh mov p1,a ; Handle 1-second countdown timers ; -------------------------------- ; Decrement each timer if it is not already zero. mov a,idletimer jz idle_idle dec a mov idletimer,a idle_idle: mov a,waittimer jz wait_idle dec a mov waittimer,a ; Update clock ; ------------ ; Add 1 second to the current time. wait_idle: mov a,#60 inc seconds cjne a,seconds,date_done mov seconds,#0 inc minutes cjne a,minutes,date_done mov minutes,#0 mov a,#24 inc hours cjne a,hours,date_done mov hours,#0 ; Add 1 day to the date. mov a,month lcall daysthismonth cjne a,day,notlastday mov day,#1 mov a,#13 inc month cjne a,month,date_done mov month,#1 inc year sjmp date_done notlastday: inc day ; End of interrupt processing. date_done: ljmp int_exit end