; ; Note: This program controls BSR X10 modules through serial port ; ; Copyright (C) 1990 Jim Phillips ; ; To assemble, Link, and convert this program to a COM file: ; ; C>MASM X10; ; C>LINK X10; ; C>EXE2BIN X10.EXE X10.COM ; C>DEL X10.EXE ; ; Read Serial Port Modem Line Bits ; Interrupt 0C = addr 30 IRQ4 Com1: 03f8h Com3: 03e8h ; Interrupt 0B = addr 2C IRQ3 Com2: 02f8h Com4: 02e8h ; ; WD8250 Registers ; Note: Dlab = Divisor Latch Access Bit. This bit is located in the ; Line Control register bit 7 ; ; Addr Dlab state Read/write Function ; Base + 0 0 read = Receiver buffer register ; Base + 0 0 write = Transmitter holding register ; Base + 1 0 either = Interrupt enable mask register ; Base + 2 X either = Cause for interrupt register ; Base + 3 X either = Line control register ; Base + 4 X either = Modem control register ; Base + 5 X either = Line status register ; Base + 6 X either = Modem status register ; Base + 0 1 either = Divisor latch LSB ; Base + 1 1 either = Divisor latch MSB ; ; ID = 3 Line status changed, 2 Data received, 1 Tx Empty, 0 Modem stat changed ; ; Register bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7 ; Line control reg: WLenLsb WLenMsb StopBit Parity PtyEven PtyStk SndBrk DLAB ; Table of bits that control bit frame in Line cont reg ; 0 1 = Word len 2 = Stop bits 3 4 5 = Parity ; 0 0 = 5 bit 0 = 1 stop 0 X X = None ; 1 0 = 6 bit 1 = 2 stops 1 0 0 = Odd ; 0 1 = 7 bit 1 1 0 = Even ; 1 1 = 8 bit 1 0 1 = Mark ; 1 1 1 = Space ; ; Modem control reg:DTR RTS OUT1 OUT2 LOOP 0 0 0 ; ; Modem status reg: DCTS DDSR TERI DDCD CTS DSR RI DCD ; Note: "D" preceeding a signal name means Delta. ; I.E. DCTS = Delta Clear to Send. Also TERI = Trailing Edge Ring Indicator ; Note: int the table below the symbols <, >, and = show a connection to the ; DB 25 RS232 plug. More specifically > means output from computer, < means ; input to computer, and = is a ground return. ; 1 frame ground ; > 2 xmit data ; < 3 rcv data ; > 4 request to send ; < 5 clear to send ; < 6 data set ready ; = 7 signal ground ; < 8 data carrier detect ; 9 V+ test voltage ; 10 V- test voltage ; 11 DTR Not (inverse data terminal ready) ; 12 2nd data carrier detect ; 13 2nd clear to send ; 14 2nd xmit data ; 15 transmit clock ; 16 2nd rcv data ; 17 recieve clock ; 18 unasigned ; 19 2nd request to send ; > 20 data terminal ready ; 21 signal quality detect ; < 22 ring indicator ; 23 data rate select ; 24 external xmit clock ; 25 unasigned ; CSEG SEGMENT PARA PUBLIC 'CODE' ; ORG 0100H ; ; ASSUME CS:CSEG, DS:CSEG, SS:CSEG, ES:CSEG ; INIT PROC NEAR ; JMP INITST ; Jump arround imbedded parameters ; SPORT DW 003F8H ; Address of serial port Duration DB 008H ; Nunber of times to wiggle X10 module ; ; The following two tables were obtained from the manual of the ; Novation Apple Cat Modem. To send an X10 message you send the ; following sequence. Wait 4 zero crosses to break from any previous ; transmission, send House code, send Device code, Wait, send House code send ; Device code, Wait send House code, send Action code, Wait send House code ; send Action code. Note: If several Actions are to be sent in burst mode to ; the same device the Device need only be sent once. ; Example: ; Wt Hs Dv Wt Hs Dv Wt Hs Ac Wt Hs Ac Wt Hs Ac Wt Hs Ac Wt Hs Ac Wt Hs Ac . . . ; /\ Specify dev /\ /\ Action one /\ /\ Action two /\ /\ Action three/\ Etc. ; The house code is a four bit code. The Device and Action codes are five bit ; codes. Even though they only convey four bits of info. The fifth bit ; indicates which they are. The first entry in the HseDevXltTab table ; corresponds to the House code on the X10 receiver's thumwheel letter "A" ; and the last entry to the letter "P". Similarly the same table's first entry ; corresponds to the Device code on the X10 receiver's thumwheel number 1 and ; the last entry to the number 16. The bits are sent to the device, high bit ; first working down ward until you run out of bits. The Wait for four zero ; crosses is followed immediatly by 3 active half cycles to mark the start of ; transmission. There after an active half cycle followed by an inactive one ; will send a one bit. An inatcive one followed by an active one will send ; a zero bit. An active half cycle is one in which 3 equally spaced ; 1 millisecond bursts of 120 khz Rf is pumped back into the power line. ; The control signal to gate a 120 khz transmitter on and off can be generated ; by setting the baud rate divisor of the WD8250 chip in an IBM or clone to ; 5A hex, setting bit frame to 6 bit No parity One stop bit, and at the proper ; time writing 36 hex to the transmit buffer. As in the following wave form. ; 0 1 2 3 4 5 Bit number ; _ _ _ ; | |_ _| |_ _| |_ Low = 1 High = 0 ; S 1 1 0 1 1 0 s S = start s = stop ; \ B / \1/ 1B hex ; ; Note: the waveform thus generated is out of spec. The pulses are ; 220 microseconds to short. To fix this a pulse stretcher circuit should be ; used between the serial output and the radio transmitter input. ; HseDevXltTab DB 060H, 0E0H, 020H, 0A0H, 010H, 090H, 050H, 0D0H DB 070H, 0F0H, 030H, 0B0H, 000H, 080H, 040H, 0C0H ; ActionXltTab DB 008H, 018H, 028H, 038H, 048H, 058H ; ; These are set by program at run time ZeroCrsDly DW 013CCH ; Length of delay from Zero cross to ; beginning of wave form CpuSpeed DW ? ; Speed of CPU measured at run time OrigonalLnCnt DB ? ; Temporarys hold serial port registers OrigonalMdmCnt DB ? ; so we can restore them when we exit OrigonalDiv DW ? ; Baud rate divisor SIGNON DB 'Signaling X10 device...',0DH,0AH,0DH,0AH DB '$' SIGNONOFF DB 'Finis.',0DH,0AH DB '$' ; INITST: PUSH DS ; Set up for simple .COM file terminate XOR AX,AX ; PUSH AX ; CALL MeasureCPU ; Get CPU speed MOV CpuSpeed,AX ; Save Speed SHR AX,1 ; Divide by eight SHR AX,1 ; SHR AX,1 ; ;*** MOV ZeroCrsDly,AX ; Set delay width ; ; MOV DX,OFFSET SIGNON ;Print identification message MOV AH,09H ; INT 021H ; ; MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00003H ; line control IN AL,DX ; Grab bits that exist now MOV OrigonalLnCnt,AL ; OR AL,080H ; Set high bit OUT DX,AL ; Blast out the bits MOV DX,SPORT ; Setup port address for WD8250 IN AL,DX ; Grab LSB baud rate divisor MOV BYTE PTR OrigonalDiv,AL ; MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00001H ; IN AL,DX ; Grab MSB baud rate divisor MOV BYTE PTR OrigonalDiv + 1,AL ; ; MOV DX,SPORT ; Setup port address for WD8250 IN AL,DX ; Grab LSB baud rate divisor MOV AL,05FH ; Set up for X10 pulse rate (was 5A) OUT DX,AL ; Write LSB MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00001H ; MOV AL,000H ; Set up for X10 pulse rate OUT DX,AL ; Write MSB ; MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00003H ; line control MOV AL,001H ; X10 Bit frame 6 bit No parity 1 Stop OUT DX,AL ; Blast out the bits ; ;*** CLI ; * * * * * * * * ; TrySendAgain: DEC Duration ; JNZ SendNoMoreNot JMP SendNoMore ; SendNoMoreNot: CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00500H ; Device code 1 CALL GenHseDevCod ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00500H ; Device code 1 CALL GenHseDevCod ; ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00502H ; Action code 2 CALL GenActionCod ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00502H ; Action code 2 CALL GenActionCod ; ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00500H ; Device code 1 ; CALL GenHseDevCod ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00500H ; Device code 1 ; CALL GenHseDevCod ; ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00502H ; Action code 2 ; CALL GenActionCod ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00502H ; Action code 2 ; CALL GenActionCod ; ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00500H ; Device code 1 CALL GenHseDevCod ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00500H ; Device code 1 CALL GenHseDevCod ; ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00503H ; Action code 3 CALL GenActionCod ; CALL GenWait ; MOV AX,00400H ; House code "A" CALL GenHseDevCod ; MOV AX,00503H ; Action code 3 CALL GenActionCod ; ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00500H ; Device code 1 ; CALL GenHseDevCod ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00500H ; Device code 1 ; CALL GenHseDevCod ; ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00503H ; Action code 3 ; CALL GenActionCod ; ; CALL GenWait ; ; MOV AX,00400H ; House code "A" ; CALL GenHseDevCod ; ; MOV AX,00503H ; Action code 3 ; CALL GenActionCod ; ; JMP TrySendAgain ; SendNoMore: NOP ; STI ; * * * * * * * * ; MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00004H ; modem control MOV AL,OrigonalMdmCnt ; OUT DX,AL ; Restore origonal bits MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00003H ; line control MOV AL,OrigonalLnCnt ; OUT DX,AL ; Restore origonal bits ; MOV DX,OFFSET SIGNONOFF ; Print message MOV AH,09H ; INT 021H ; ; RET ; End program using simple .COM exit INIT ENDP ; ; When MeasureCPU is called the CPU's simple looping ability is measured ; against the timer irrespective of timer speed (this is compensated for ; after the measurement is taken). Further the timer speed is not affected. ; The result is a figure of merit returned in the AX register. ; This value is 0FF6 hex +/- 000D hex for a 4.77 mhz machine TimerMax DW 00000H ; CpuLoops DD 000000000H ; TicksInCk DB 000H ; CurTickInCk DW 00000H ; PrevISRT DD ? ; TimerIntCnt DW 00000H ; ; MeasureCPU PROC NEAR ; PUSH BX ; Save BX CX and DX PUSH CX ; on entry to proc PUSH DX ; ; PUSH ES ; Save ES before BDOS funct call MOV AH,035H ; Get Inturrupt BDOS function number MOV AL,008H ; Interrupt number for hardware timer INT 021H ; Execute function MOV WORD PTR CS:PrevISRT,BX ; Store target address MOV WORD PTR CS:PrevISRT+2,ES ; in double word POP ES ; Restore ES now that funct concluded MOV AL,008H ; Revector Interrupt 8, timer tick MOV DX,OFFSET ISRT ; vector to address of new ISR MOV AH,025H ; handler by using function 25H INT 021H ; Call DOS and do it ; MOV AX,00000H ; MOV CS:TicksInCk,AL ; Reset number of 18.2 Hz count MOV CS:TimerMax,AX ; Reset max count MOV WORD PTR CS:CpuLoops,AX ; Reset CPU speed count MOV WORD PTR CS:CpuLoops+2,AX ; double word DEC CS:TicksInCk ; Compensate for pre-increment TimerTicked: INC CS:TicksInCk ; CMP CS:TicksInCk,007 ; Have we been trying for 1/3 second JNZ ContnuReaching JMP MesurCpuIndex ; ContnuReaching: MOV AX,WORD PTR CS:TimerIntCnt ; Get 18.2 Hz tick count MOV CS:CurTickInCk,AX ; Set our copy of it ReachForTop: MOV AX,CS:CurTickInCk CMP AX,WORD PTR CS:TimerIntCnt ; Did 18.2 Hz tick count change JNZ TimerTicked ; Wait till it does MOV AL,000H ; control byte latchs current count of ;timer zero. It must be read as a straight binary 16 bit integer as two bytes ;low byte first high byte second. Ref. Intel Microprocessor and peripheral ;Handbook. Volume II-peripheral. Order number 230843-006 page 6-21 ;Note: the handbook lies! it suggests there is another way to read the timer ;if you try it the timer locks up. Presumably waitng for output to it. OUT 043H,AL ; send it PUSH AX ; \____ Kill one microsecond POP AX ; / IN AL,040H ; Read low byte MOV BL,AL ; PUSH AX ; \____ Kill one microsecond POP AX ; / IN AL,040H ; Read high byte MOV AH,AL ; MOV AL,BL ; CMP CS:TimerMax,AX ; Is this count bigger than last time JA ReachForTop ; No? Well try again. MOV CS:TimerMax,AX ; Yes? Make this count the standard. JMP ReachForTop ; and try again. ; MesurCpuIndex: MOV AX,WORD PTR CS:TimerIntCnt ; Get 18.2 Hz tick count CMP AX,WORD PTR CS:TimerIntCnt ; Did 18.2 Hz tick count change JZ MesurCpuIndex ; Wait till it does MOV AX,CS:TimerIntCnt ; Get current 18.2 Hz tick count MOV CX,00000H ; DEC CX ; MesurCpuHiCnt: INC CX ; MOV BX,00000H ; MesurCpuLoCnt: INC BX ; JZ MesurCpuHiCnt ; CMP AX,WORD PTR CS:TimerIntCnt ; Did 18.2 Hz tick count change JZ MesurCpuLoCnt ; MOV WORD PTR CS:CpuLoops,BX ; Set CPU speed count MOV WORD PTR CS:CpuLoops+2,CX ; double word PUSH DS ; Save DS before BDOS funct call MOV AL,008H ; Revector Interrupt 8, timer tick MOV DX,WORD PTR CS:PrevISRT ; vector back to the address of MOV DS,WORD PTR CS:PrevISRT+2 ; the origonal ISR MOV AH,025H ; handler by using function 25H INT 021H ; Call DOS and do it POP DS ; Restore DS now that funct concluded ; MOV AX,WORD PTR CS:CpuLoops ; Get CPU speed count MOV DX,WORD PTR CS:CpuLoops+2 ; double word MOV BX,00400H ; MUL BX ; Mulpiply by 400 hex MOV WORD PTR CS:CpuLoops,AX ; Store result MOV WORD PTR CS:CpuLoops+2,DX ; double word MOV DX,00000H ; MOV AX,WORD PTR CS:TimerMax ; Get timer max MOV BX,00040H ; DIV BX ; Divide by 40 hex MOV BX,AX ; CMP BX,00000H ; Prevent divide by zero JNZ NoDivideZero ; MOV BX,00001H ; NoDivideZero: MOV AX,WORD PTR CS:CpuLoops ; Get normalized CPU speed MOV DX,WORD PTR CS:CpuLoops+2 ; count double word DIV BX ; Divide by normalized TimerMax MOV WORD PTR CS:CpuLoops,AX ; Store result MOV WORD PTR CS:CpuLoops+2,DX ; double word POP DX ; Restore DX CX and BX POP CX ; as we exit proc POP BX ; RET ; Return result in AX reg. MeasureCPU ENDP ; ; ; The only usefull thing this Proc does is bump a counter every time the ; timer chip pulls an interrupt ISRT PROC FAR ; INC WORD PTR CS:TimerIntCnt ; PUSHF ; simulate an INT CLI ; CALL CS:PrevISRT ; Call origonal timer interrupt code IRET ; ISRT ENDP ; ; ; This code generates a series of pulses that Ping the Power line GenPing PROC ; PUSH DX ; Save the world PUSH CX ; PUSH BX ; PUSH AX ; MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00006H ; modem status reg. GenPing1: IN AL,DX ; Read it TEST AL,004H ; Check if Ring Indicator Had a falling JZ GenPing1 ; edge. If so we have Zero Cross MOV BX,ZeroCrsDly ; Get time factor for Delay after ; Zero cross GenPing2: DEC BX ; Wait a while JNZ GenPing2 ; MOV DX,SPORT ; Setup port address for WD8250 MOV AL,01BH ; ; ; 0 1 2 3 4 5 Bit number ; _ _ _ ; | |_ _| |_ _| |_ Low = 1 High = 0 ; S 1 1 0 1 1 0 s S = start s = stop ; \ B / \1/ 1B hex ; OUT DX,AL ; Blast out the bits POP AX ; Restore the world POP BX ; POP CX ; POP DX ; RET ; GenPing ENDP ; GenPause PROC ; PUSH DX ; Save the world PUSH AX ; MOV DX,SPORT ; Setup port address for WD8250 ADD DX,00006H ; modem status reg. GenPause1: IN AL,DX ; Read it TEST AL,004H ; Check if Ring Indicator Had a falling JZ GenPause1 ; edge. If so we have Zero Cross POP AX ; Restore the world POP DX ; RET ; GenPause ENDP ; GenWait PROC ; STI ; CLI ; CALL GenPause ; CALL GenPause ; CALL GenPause ; CALL GenPause ; CALL GenPing ; CALL GenPing ; CALL GenPing ; CALL GenPause ; RET ; GenWait ENDP ; GenOne PROC ; CALL GenPing ; CALL GenPause ; RET ; GenOne ENDP ; GenZero PROC ; CALL GenPause ; CALL GenPing ; RET ; GenZero ENDP ; GenHseDevCod PROC ; PUSH AX ; PUSH BX ; XOR BH,BH ; Zap high half MOV BL,AL ; Form index pointer MOV AL,HseDevXltTab[BX] ; Get actual code from table GenHseDevCodL: CMP AH,000H ; Have we sent all the bit we were JZ GenHseDevCodQt ; ask to? Quit if so DEC AH ; Shoot one dead if not. SHL AL,1 ; Get next one JNC GenHseDevCodZ ; Is it a Zero? CALL GenOne ; No? Then send a one JMP GenHseDevCodL ; Loop till done GenHseDevCodZ: CALL GenZero ; Yes? Then send a zero JMP GenHseDevCodL ; Loop till done GenHseDevCodQt: POP BX ; Put the toys back the way they were POP AX ; so the next kid can find them RET ; GenHseDevCod ENDP ; GenActionCod PROC ; PUSH AX ; PUSH BX ; XOR BH,BH ; Zap high half MOV BL,AL ; Form index pointer MOV AH,005H ; Force to five bit MOV AL,ActionXltTab[BX] ; Get actual code from table GenActionCodL: CMP AH,000H ; Have we sent all the bit we were JZ GenActionCodQt ; ask to? Quit if so DEC AH ; Shoot one dead if not. SHL AL,1 ; Get next one JNC GenActionCodZ ; Is it a Zero? CALL GenOne ; No? Then send a one JMP GenActionCodL ; Loop till done GenActionCodZ: CALL GenZero ; Yes? Then send a zero JMP GenActionCodL ; Loop till done GenActionCodQt: POP BX ; Put the toys back the way they were POP AX ; so the next kid can find them RET ; GenActionCod ENDP ; ; CSEG ENDS ; END INIT