Hi,
I am trying to display a timer in the format:
hh:mm:ss
but its not displaying any of its components. It displays a mesg before coming to timer code.
Can somebody plz help me with this prob.?
.MODEL TINY
install macro intNum,Newisr_add
push ax
mov ax, 0
push ds
mov ds,ax
cli ;disable interrupts for the change
mov word ptr ds:[intNum*4],Newisr_add
mov word ptr ds:[intNum*4+2],cs ;word ptr may not be needed here
sti
pop ds
pop ax
endm
.CODE
;----------------------------------------------------------------------------------
LoadOfs EQU 0 ;must match the value in the bootloader source file
INT1C EQU 01CH
;----------------------------------------------------------------------------------
;---------------------- initialize ES segment register
ORG 0
Start: push cs
pop ds
;-----------clear screen
mov ax, 3
int 10h
;---------------------- writing a message on screen at startup, character by character- we can't use int 21h
overdata:
xor di, di
mov ax, 0B800h
mov es, ax
mov si, offset msg0+LoadOfs
mov ah, 41h; attribute byte
cld;
msgloop:
lodsb; loads al with a byte of data pted by ds:si
or al, al
jz Timer
stosw; transfers the contents of al to mem location ptd by es:di
jmp msgloop
;---------------------- done - halt
Timer:install 1Ch, INT1Ch
xor di, di
mov ax, 0B820h
mov es, ax
mov si, offset msgA+LoadOfs
mov ah, 41h; attribute byte
cld;
msgAloop:
lodsb; loads al with a byte of data pted by ds:si
or al, al
jz Halt0
stosw; transfers the contents of al to mem location ptd by es:di
jmp msgAloop
Halt0: hlt
jmp Halt0
;---------------------- data area in code segment
Msg0 db "We be bootin234!",0
msgA db 'Total minutes elapsed since Kernel start is',0
clkcounter db 0
secs db 0
mins db 0
hrs db 0
cnt db 0; Its value represents the digits of Timer
s db 0; selector for secs minutes and hrs used in displayCnt
INT1Ch:
cli ;not needed at the beginning of an interrupt handler - the interrupt clears that flag automatically
inc byte ptr [cs:clkcounter]
cmp byte ptr [cs:clkcounter],18; if clkcounter is 18, it means 1 sec
jz handle_secs
sti
iret
handle_secs:
mov byte ptr[cs:clkcounter],0
inc byte ptr[cs:secs]
cmp byte ptr[cs:secs],60;if secs is 60, it means 1 min
jz handle_mins
push Ax
push BX
mov bh, byte ptr[cs:secs]
mov s,0;--------------- selector for sec
call PrintSMH;--------- displaying seconds
pop BX
pop AX
sti
iret
handle_mins:
mov byte ptr[cs:secs],0
mov s,0;------------------ selector for secs
call PrintSMH; ----------- displaying seconds
inc byte ptr[cs:mins]; we are done, now print the number of minutes elapsed
cmp byte ptr[cs:mins],60
jz handle_hrs
mov s,2;------------------selctor for minutes
call PrintSMH;------------displaying minutes
sti
iret
handle_hrs:
mov byte ptr[cs:mins],0
mov s,2;-----------------selctor for minutes
call PrintSMH;------------displaying minutes
inc byte ptr[cs:hrs]
cmp byte ptr[cs:hrs],24
jz hrsZero
mov s,4;----------------selctor for hrs
call PrintSMH;-----------displaying hours
sti
iret
hrsZero:
mov byte ptr[cs:hrs], 0
mov s,4;-----------------selctor for hrs
call PrintSMH;----------displaying hours
sti
iret
PrintSMH:
mov ax, 100; For two digits, we have 'subtract' =10^1 and 10^0
mov bl, 10
NextDigit: Div BL; AL=AX/BL
mov ah, 0;value of AL would be the value of AX
cmp AL,0; until 'subtract'=0
je done
againSub: Sub bh, AL;e.g to print 58,First print 5,58-10-10-10-10-10-10, count=6, digit=count-1=5
;Note after subtraction bh=-2, add back i.e -2+10=8 which is the next digit
;to print 8,8-1-1-1-1-1-1-1-1-1,count=9, digit=count-1=8
cmp bh,0;when bh=-ve we stop subtraction
jb NoSub
inc cnt
jmp againSub
NoSub: add bh, al; add back
call DisplayCnt
inc s
jmp NextDigit
done:iret
DisplayCnt:
cmp s, 0
je FirstDigitOfSecs
cmp s, 1
je SecondDigitOfSecs
cmp s, 2
je FirstDigitOfMins
cmp s, 3
je SecondDigitOfMins
cmp s,4
je FirstDigitofHrs
cmp s,5
je SecondDigitofHrs
iret
FirstDigitOfSecs: cld
mov ax,0B880h
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
iret
SecondDigitOfSecs:cld;----------------- Displaying colon also
mov ax,0B882h
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
mov ax,0B884h
mov es,ax
xor di,di
mov ah,1Fh
mov al,':'
mov cx,2
rep stosw
iret
FirstDigitOfMins:cld
mov ax,0B886h
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
iret
SecondDigitOfMins:cld;----------------- Displaying colon also
mov ax,0B888h
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
mov ax,0B88Ah
mov es,ax
xor di,di
mov ah,1Fh
mov al,':'
mov cx,2
rep stosw
iret
FirstDigitofHrs:cld
mov ax,0B88Ch
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
iret
SecondDigitOfHrs:cld
mov ax,0B88Eh
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
iret
;----------------------------------------------------------------------------------
END Start
Zulfi.
Zulfi, Zulfi - lol
i see no place where the time is displayed
the string is - not the time, though
and - to display it, it needs to be converted from binary to ASCII numeric
finally, if you count 18 ticks as one second, you will have a fast-running clock
it is much simpler to use the BIOS ticks-since-midnight count and calculate the time from that value
Hi,
The following code is doing the conversion:
PrintSMH:
mov ax, 100; For two digits, we have 'subtract' =10^1 and 10^0
mov bl, 10
NextDigit: Div BL; AL=AX/BL
mov ah, 0;value of AL would be the value of AX
cmp AL,0; until 'subtract'=0
je done
againSub: Sub bh, AL;e.g to print 58,First print 5,58-10-10-10-10-10-10, count=6, digit=count-1=5
;Note after subtraction bh=-2, add back i.e -2+10=8 which is the next digit
;to print 8,8-1-1-1-1-1-1-1-1-1,count=9, digit=count-1=8
cmp bh,0;when bh=-ve we stop subtraction
jb NoSub
inc cnt
jmp againSub
NoSub: add bh, al; add back
call DisplayCnt
inc s
jmp NextDigit
done:iret
Following code and its variants like SecondDigitOfSecs, FirstDigitOfMins and so on is doing the printing.
FirstDigitOfSecs: cld
mov ax,0B880h
mov es,ax
xor di,di
mov ah,1Fh
add bh, 30h
mov al,bh
mov cx,2
rep stosw
iret
Kindly tell me the prob with this code.
Zulfi.
ahhhhhhhhh - lol
i didn't see that code way down there
holy cow - lol
interrupt handlers should be short and sweet
i am not sure i would display anything anything at all during handler execution
i would count the tick, and let the program display the time
in fact, the ticks are already counted for you by BIOS
all you have to do is go get the value and update the display if the count has changed
give me a little time and i will write a little program for you...
Hi,
I have found one mistake in the printing code. Instead of bh, cnt should contain the value to print. But I have replace bh by cnt but its still not printing. Computer becomes very busy as ctrl-alt-del doesnt work and I have to manually reset the system.
Zulfi.
Hi,
I highly appreciate your intentions. It would tell me another approach of solving the problem. Kindly give me some suggestions also to make this code running.
Zulfi.
Zulfi,
One major problem with your interrupt handler is that it modifies registers without preserving them. This in general is not workable for hardware interrupt handlers, or for software interrupt handlers called by hardware interrupt handlers, as is the case for Interrupt 1Ch. Changing register values in a handler can cause severe problems in the code that was executing when the hardware interrupt was called.
Another major problem is that in your handler you call code that instead of returning to the caller executes an IRET. The call places a return address on the stack, and this will prevent the IRET from returning to the correct address.
Also, an STI before an IRET makes no sense because the IRET will restore the flags to what they were when the interrupt was called.
Zulfi
here is a simple program to get the INT 1Ah tick count and display it in HH:MM:SS.FF format
DOS is not used to display it
in your boot-code, you may have to execute INT 1Ah, function 0Fh to initialize the BIOS tick counter from the RTC
EDIT - oops - found a line missing - lol - fixed it
EDIT - dang - fixed another bug - i was sleepy when i wrote it :P
Hi Dave,
Thanks ffor this code. I have downloaded it but I cant figure out its logic.
Hi MichealW,
Quote
Another major problem is that in your handler you call code that instead of returning to the caller executes an IRET. The call places a return address on the stack, and this will prevent the IRET from returning to the correct address.
If I dont use the IRET then how to end the procedures?
Zulfi.
QuoteIf I dont use the IRET then how to end the procedures?
The problem is not the IRET itself, but the state of the stack when the IRET is executed. When the handler receives control, the contents of the stack, relative to SP, are:
[sp+4] value of the flags register at the point the interrupt was called
[sp+2] return CS for the interrupt call
[sp+0] return IP for the interrupt call
The IRET instruction expects the contents of the stack to be as they were when the handler received control, with the return address and flags value at the "top" of the stack, and the instruction will fail if they are not. Here is an example of the problem in your code:
handle_secs:
mov byte ptr[cs:clkcounter],0
inc byte ptr[cs:secs]
cmp byte ptr[cs:secs],60
jz handle_mins
push Ax
push BX
mov bh, byte ptr[cs:secs]
mov s,0
call PrintSMH
...
PrintSMH:
mov ax, 100
mov bl, 10
NextDigit:
Div BL
mov ah, 0
cmp AL,0
je done <===========
againSub:
Sub bh, AL
cmp bh,0
jb NoSub
inc cnt
jmp againSub
NoSub:
add bh, al; add back
call DisplayCnt
inc s
jmp NextDigit
done:
iret <===========
After the call PrintSMH executes, the contents of the stack, relative to SP, are:
[sp+10] value of the flags register at the point the interrupt was called
[sp+8] return CS for the interrupt call
[sp+6] return IP for the interrupt call
[sp+4] preserved AX
[sp+2] preserved BX
[sp+0] return IP for the near call
Obviously, this will not work, because the IRET will attempt to use the return IP for the near call as the return IP for the interrupt call, the preserved value of BX as the return CS for the interrupt call, etc.
You need to ensure that anything your code places on the stack is removed from the stack before you execute the IRET. And the same concept applies for any code that is CALLed, where the return instruction is expecting the return address to be at the "top" of the stack.
i fixed a mistake in the Time_1Ah code above
here it is, adapted to "Zulfi boot code" ...
.MODEL TINY
;---------------------------------------------------------------------------------
LoadOfs EQU 0 ;must match the value in the bootloader source file
;---------------------------------------------------------------------------------
.CODE
ORG 0
_main PROC FAR
;set video mode 3 - clear screen
mov ax,3
int 10h
;set segment registers
push cs
pop ds
mov ax,0B800h
mov es,ax
cld
;time display loop
TLoop0: mov ah,0
int 1Ah
cmp dx,TickLo+LoadOfs
jz TLoop0
mov TickLo+LoadOfs,dx
mov si,offset Hours+LoadOfs
call UpdCk
mov [si+9],ax
xor di,di
call Dsply
jmp TLoop0
_main ENDP
;---------------------------------------------------------------------------------
UpdCk PROC NEAR
;update clock string
;CX:DX = INT 1Ah clock tick
;SI = string address
shl dx,1 ;multiply by 2
rcl cx,1
mov ax,cx
xchg ax,dx
mov bx,ax
shl ax,1 ;multiply by 9
rcl dx,1
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
add ax,bx
adc dx,cx
mov bx,19663
div bx ;divide 28314702 max by 19663
mov cx,60
push dx
xor dx,dx
div cx ;divide 1439 max by 60
call UAscii
mov [si],ax
mov ax,dx
call UAscii
mov [si+3],ax
pop ax
mov dx,6000
mul dx
div bx ;divide 117972000 max by 19663
mov cl,100
xor dx,dx
div cx ;divide 5999 max by 100
call UAscii
mov [si+6],ax
mov ax,dx
UAscii: aam
or ax,3030h
xchg al,ah
ret
UpdCk ENDP
;---------------------------------------------------------------------------------
Dsply PROC NEAR
mov ah,0Ah ;AH = attribute
jmp short Dsply1
Dsply0: stosw
Dsply1: lodsb
or al,al
jnz Dsply0
ret
Dsply ENDP
;---------------------------------------------------------------------------------
TickLo dw 0
Hours db '00:00:00.00',0
;---------------------------------------------------------------------------------
END _main
EDIT - fixed a bug :bg
Any reason for using int 1a? If you want h:m:s just use the cmos clock - all nicely bcd already.
well - he wanted to use the tick counter, so i stayed with that :P
i wanted to show him that it could be done without revectoring the 1Ch interrupt
Thanks for removing the data segment. Great work. However I am not able to under your conversion logic. I would try your code and then modify it according to my understanding. In the meantime I have changed my original code somewhat in the light of MichealW and your's comment but still its not printing anything. Somebody kindly help me with this.
.MODEL TINY
install macro intNum,Newisr_add
push ax
mov ax, 0
push ds
mov ds,ax
cli ;disable interrupts for the change
mov word ptr ds:[intNum*4],Newisr_add
mov word ptr ds:[intNum*4+2],cs ;word ptr may not be needed here
sti
pop ds
pop ax
endm
.CODE
;----------------------------------------------------------------------------------
LoadOfs EQU 0 ;must match the value in the bootloader source file
INT1C EQU 01CH
;----------------------------------------------------------------------------------
;---------------------- initialize ES segment register
ORG 0
Start: push cs
pop ds
;-----------clear screen
mov ax, 3
int 10h
;---------------------- writing a message on screen at startup, character by character- we can't use int 21h
overdata:
xor di, di
mov ax, 0B800h
mov es, ax
mov si, offset msg0+LoadOfs
mov ah, 41h; attribute byte
cld;
msgloop:
lodsb; loads al with a byte of data pted by ds:si
or al, al
jz TimerMesg
stosw; transfers the contents of al to mem location ptd by es:di
jmp msgloop
;---------------------- done - halt
TimerMesg:
xor di, di
mov ax, 0B820h
mov es, ax
mov si, offset msgA+LoadOfs
mov ah, 41h; attribute byte
cld;
msgAloop:
lodsb; loads al with a byte of data pted by ds:si
or al, al
jz DISPLAY_TIMER
stosw; transfers the contents of al to mem location ptd by es:di
jmp msgAloop
mov cx, 1000
DISPLAY_TIMER:install 1Ch, INT1Ch
mov bh, byte ptr[cs:secs]
mov s,0;--------------- selector for sec
call PrintSMH;--------- displaying seconds/minutes/hours
mov bh, byte ptr[cs:mins]
mov s,2;--------------- selector for min
call PrintSMH;--------- displaying seconds/minutes/hours
mov bh, byte ptr[cs:hrs]
mov s,4;--------------- selector for hrs
call PrintSMH;--------- displaying seconds/minutes/hours
Loop Display_TIMER
Halt0: hlt
jmp Halt0
;---------------------- data area in code segment
Msg0 db "We be bootin234!",0
msgA db 'Total minutes elapsed since Kernel start is',0
clkcounter db 0
secs db 0
mins db 0
hrs db 0
cnt db 0; Its value represents the digits of Timer
s db 0; selector for secs minutes and hrs used in displayCnt
INT1Ch:
cli ;not needed at the beginning of an interrupt handler - the interrupt clears that flag automatically
inc byte ptr [cs:clkcounter]
cmp byte ptr [cs:clkcounter],18; if clkcounter is 18, it means 1 sec
jz handle_secs
sti
iret
handle_secs:
mov byte ptr[cs:clkcounter],0
inc byte ptr[cs:secs]
cmp byte ptr[cs:secs],60;if secs is 60, it means 1 min
jz handle_mins
sti
iret
handle_mins:
mov byte ptr[cs:secs],0
inc byte ptr[cs:mins]; we are done, now print the number of minutes elapsed
cmp byte ptr[cs:mins],60
jz handle_hrs
sti
iret
handle_hrs:
mov byte ptr[cs:mins],0
inc byte ptr[cs:hrs]
cmp byte ptr[cs:hrs],24
jz hrsZero
sti
iret
hrsZero:
mov byte ptr[cs:hrs], 0
sti
iret
PrintSMH:
mov ax, 100; For two digits, we have 'subtract' =10^1 and 10^0
mov bl, 10
NextDigit: Div BL; AL=AX/BL
mov ah, 0;value of AL would be the value of AX
cmp AL,0; until 'subtract'=0
je done
againSub: Sub bh, AL;e.g to print 58,First print 5,58-10-10-10-10-10-10, count=6, digit=count-1=5
;Note after subtraction bh=-2, add back i.e -2+10=8 which is the next digit
;to print 8,8-1-1-1-1-1-1-1-1-1,count=9, digit=count-1=8
cmp bh,0;when bh=-ve we stop subtraction
jb NoSub
inc cnt
jmp againSub
NoSub: add bh, al; add back
call DisplayCnt
inc s
jmp NextDigit
done:ret
DisplayCnt:
cmp s, 0
je FirstDigitOfSecs
cmp s, 1
je SecondDigitOfSecs
cmp s, 2
je FirstDigitOfMins
cmp s, 3
je SecondDigitOfMins
cmp s,4
je FirstDigitofHrs
cmp s,5
je SecondDigitofHrs
ret
FirstDigitOfSecs: cld
mov ax,0B880h
mov es,ax
xor di,di
mov ah,1Fh
add cnt, 30h
mov al,bh
mov cx,2
rep stosw
ret
SecondDigitOfSecs:cld;----------------- Displaying colon also
mov ax,0B882h
mov es,ax
xor di,di
mov ah,1Fh
add cnt, 30h
mov al,bh
mov cx,2
rep stosw
mov ax,0B884h
mov es,ax
xor di,di
mov ah,1Fh
mov al,':'
mov cx,2
rep stosw
ret
FirstDigitOfMins:cld
mov ax,0B886h
mov es,ax
xor di,di
mov ah,1Fh
add cnt, 30h
mov al,bh
mov cx,2
rep stosw
ret
SecondDigitOfMins:cld;----------------- Displaying colon also
mov ax,0B888h
mov es,ax
xor di,di
mov ah,1Fh
add cnt, 30h
mov al,bh
mov cx,2
rep stosw
mov ax,0B88Ah
mov es,ax
xor di,di
mov ah,1Fh
mov al,':'
mov cx,2
rep stosw
ret
FirstDigitofHrs:cld
mov ax,0B88Ch
mov es,ax
xor di,di
mov ah,1Fh
add cnt, 30h
mov al,bh
mov cx,2
rep stosw
ret
SecondDigitOfHrs:cld
mov ax,0B88Eh
mov es,ax
xor di,di
mov ah,1Fh
add cnt, 30h
mov al,bh
mov cx,2
rep stosw
ret
;----------------------------------------------------------------------------------
END Start
Zulfi.
Zulfi - i fixed a bug in both the posted code and the d/l attachment
the UpdCk routine simply calculates the hours, minutes, seconds, hundredths from the clock tick count value
i may do as Sinsi suggested and write one to get the time from the RTC (it should be faster)
i think you misunderstood much of what Michael said
interrupt handlers need to preserve all registers used, as well as the state of the machine
the flags are saved for you by the interrupt mechanism
when an interrupt occurs, the current flags are pushed onto the stack, then the interrupt flag is cleared (as though CLI)
after that, the CS and IP registers are pushed just like a normal far call
once the critical part of the interrupt handler has executed, you may use STI to enable
other interrupts during execution of the remainder of your handler code
after an interrupt has been handled, the machine needs to return to it's original execution
it must appear as though nothing has changed - i.e. the registers and flags must remain unaltered
it is also important to note that the FPU registers should remain unaltered, unless perhaps, it is an FPU exception handler
go back and re-read what Michael posted - a lot of good info, there :bg
Hi,
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; BIOS Time Routines, data source "The Undocumented PC".
; Frank van Gilluwe says that if called during a clock update
; that these will fail. Therefore check the carry flag before
; using the returned values. You could do as follows if you
; trust the RTC.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Time of Day
RTime:
MOV AH,2 ; Get CMOS Time
INT 1AH ; Real Time Clock Interrupt
JC RTime
MOV [Hours],CH ; BCD Hours
MOV [Minites],CL ; BCD Minutes
MOV [Seconds],DH ; BCD Seconds
MOV [DST],DL ; 0 = Disabled, 1 = enabled
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Date
RDate:
MOV AH,4 ; Read CMOS Date
INT 1AH ; Real Time Clock Interrupt
JC RDate
MOV [Century],CH ; BCD 19 or 20
MOV [Year],CL ; BCD 1 to 99
MOV [Month],DH ; BCD 1 to 12
MOV [Day],DL ; BCD 1 to 31
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HTH,
Steve
that looks pretty simple Steve :bg
my other code does display hundredths, though (now that i have fixed a couple bugz)
Yes code provided by Steve seems simple. i would check it.
QuoteYou need to ensure that anything your code places on the stack is removed from the stack before you execute the IRET. And the same concept applies for any code that is CALLed, where the return instruction is expecting the return address to be at the "top" of the stack.
I have ensured this by removing the two push statements. However I am not sure about the second part.
Zulfi.
Zulfi, an interrupt handler typically looks something like this....
INTXX PROC FAR
push ax
push bx
push cx
push dx
push si
push di
push bp
;time-critical code goes here
sti
;non-time-critical code goes here
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
iret
INTXX ENDP
of course, if you do not use all the registers, you do not have to push and pop all of them
you only need to push and pop the ones you use
the stack DOES have to be balanced
for every push, there should be an associated pop to go with it
Thanks for your work. I highly appreciate cooperation of you people in my endeavours. However I am a bit slow. I would try what you people have suggested but it may take sometime.
Zulfi.
Quote from: zak100 on January 07, 2010, 02:34:29 PM
Thanks for your work. I highly appreciate cooperation of you people in my endeavours. However I am a bit slow. I would try what you people have suggested but it may take sometime.
Hi Zulfi.
Um. Not sure what you mean, but if this might help?
Rather straight forward code. I hope. Ask if not for you.
Regards,
Steve
PAGE ,132
TITLE Display time and date using BIOS.
NAME RTC
COMMENT *
Display time and date using BIOS functions. Note: data saved by
RTime and RDate is not used here. This is just a display routine to
show functionality.
5-7 January 2010 for MASM32 Forum, SRN.
*
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Set up the code definitions the operating system wants. Stack segment:
STCKSEG SEGMENT STACK
DB 48 DUP('STACK ') ; Stack area, and filler (384 Bytes)
STCKSEG ENDS
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Set up the code definitions the operating system wants. Data segment:
DATASEG SEGMENT PUBLIC
; - - - RTime, 3 BCD Bytes and flag - - -
Hours DB 0
Minutes DB 0
Seconds DB 0
DST DB 0
; - - - RDate, 4 BCD Bytes - - -
Century DB 0
Year DB 0
Month DB 0
Day DB 0
; - - - OutBDate - - -
MONTHS DB ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
DATASEG ENDS
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Set up the code definitions the operating system wants. Code segment:
CODE SEGMENT PUBLIC
ASSUME CS:CODE, DS:DATASEG, SS:STCKSEG
START PROC
MOV AX,SEG DATASEG
MOV DS,AX
CALL CRLF
CALL OutBTime ; Use video BIOS to output RTC Time.
CALL OutBDate ; Use video BIOS to output RTC Date.
CALL CRLF
MOV AL,0 ; Successful return code.
MOV AH,4CH ; DOS 2+ .EXE exit.
INT 21H
START ENDP
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OutBTime: ; Output BIOS time, don't use DOS.
; 6 January 2010, SRN
; INPUT: None
; Uses: AX, CX, DX.
MOV AL,' '
CALL OutChar
CALL RTime ; CH = BCD Hours, CL = BCD Minutes
; DH = BCD Seconds, DL = DST Flag
MOV AL,CH ; - - - Play with hours - - -
CALL OutBCDs
MOV AL,':'
CALL OutChar
MOV AL,CL ; - - - Play with minutes - - -
CALL OutBCD
MOV AL,':'
CALL OutChar
MOV AL,DH ; - - - Play with seconds - - -
CALL OutBCD
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OutBDate: ; Output BIOS Date, don't use DOS.
; 6 January 2010, SRN
; INPUT: None
; Uses: AX, BX, CX, DX.
MOV AL,' '
CALL OutChar
CALL RDate ; CH = BCD Century, CL = BCD Year
; DH = BCD Month, DL = BCD Day
MOV AL,DL ; - - - Play with Day - - -
CALL OutBCDs
MOV AL,DH ; - - - Play with month - - - (first convert to binary)
MOV AH,AL
SHR AH,1 ; "Mask" off high digit
SHR AH,1
SHR AH,1
SHR AH,1
AND AL,0FH ; Mask off low digit
AAD ; Combine two unpacked BCD digits into a binary num.
XOR BH,BH
MOV BL,AL ; Binary Month
SHL BX,1
SHL BX,1 ; Four characters per label.
ADD BX,OFFSET Months ; Point to labels.
MOV AL,[BX]
CALL OutChar
MOV AL,[BX+1]
CALL OutChar
MOV AL,[BX+2]
CALL OutChar
MOV AL,[BX+3]
CALL OutChar
MOV AL,' '
CALL OutChar
MOV AL,CH ; - - - Play with Century - - -
CALL OutBCD
MOV AL,CL ; - - - Play with Year - - -
CALL OutBCD
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Output carriage return and linefeed.
CRLF:
MOV AL,13
CALL OutChar
MOV AL,10
CALL OutChar
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; 6 January 2010, use video BIOS to display a character.
; Use function 0EH as it will process CR and LF as commands.
; INPUT: AL = Character to output.
OutChar:
PUSH AX
PUSH BX
MOV AH,0EH ; Teletype Output Function
MOV BX,7 ; BH = Page, BL = (maybe) Attribute.
INT 10H
POP BX
POP AX
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Take a byte in AL with 2 packed BCD digits, and print to screen.
; 7 Jan 2010, SRN.
OutBCD:
MOV AH,AL ; Duplicate to put digits in both AH and AL.
SHR AH,1 ; "Mask" off high digit.
SHR AH,1
SHR AH,1
SHR AH,1
AND AL,0FH ; Mask off low digit.
OR AX,3030H; Convert binary digits to ASCII characters.
PUSH AX
MOV AL,AH
CALL OutChar ; Output high digit.
POP AX
CALL OutChar ; Output low digit.
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Take a byte in AL with 2 packed BCD digits, and print to screen.
; Suppress leading zero for pretty print. 7 Jan 2010, SRN.
OutBCDs:
MOV AH,AL ; Duplicate to put digits in both AH and AL.
SHR AH,1 ; "Mask" off high digit.
SHR AH,1
SHR AH,1
SHR AH,1
AND AL,0FH ; Mask off low digit.
OR AX,3030H
CMP AH,30H ; Suppress leading zero?
JNZ OBs_1
MOV AH,' '
OBs_1: PUSH AX
MOV AL,AH
CALL OutChar
POP AX
CALL OutChar
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; BIOS Time Routines, data source "The Undocumented PC".
; Frank van Gilluwe says that if called during a clock update
; that these will fail. Therefore check the carry flag before
; using the returned values. I assume minimal errors will occur.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Time of Day
RTime:
MOV AH,2 ; Get CMOS Time
INT 1AH ; Real Time Clock Interrupt
JC RTime
MOV [Hours],CH ; BCD Hours
MOV [Minutes],CL ; BCD Minutes
MOV [Seconds],DH ; BCD Seconds
MOV [DST],DL ; 0 = Disabled, 1 = enabled
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Date
RDate:
MOV AH,4 ; Read CMOS Date
INT 1AH ; Real Time Clock Interrupt
JC RDate
MOV [Century],CH ; BCD 19 or 20
MOV [Year],CL ; BCD 1 to 99
MOV [Month],DH ; BCD 1 to 12
MOV [Day],DL ; BCD 1 to 31
RET
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CODE ENDS
END START
There ought to be a rule that if you wake up at 1:30
with a dumb idea, you should be able to forget it and
go back to sleep. That said, the following code takes a
packed BCD byte and converts it to two unpacked bytes.
MOV AH,AL ; Duplicate to put digits in both AH and AL.
SHR AH,1 ; "Mask" off high digit.
SHR AH,1
SHR AH,1
SHR AH,1
AND AL,0FH ; Mask off low digit.
It can be replaced with this.
DB 0D4H, 10H ; AAM with divisor of 16 instead of 10.
Cheers,
Steve N.
Hi,
I am still working on int 1Ah. Its useful. I am not yet able to print clock values at correct positions using the format:
hh:mm:ss. Its printing decimal values but not at correct positions on screen. The values are scattered on the screen. My screen printing code is given below:
FirstDigitOfSecs:
push ax
cld
mov ax,0B880h
mov es,ax
xor di,di
mov ah,1Fh
add dh, 30h
mov al,dh
stosw
pop ax
ret
SecondDigitOfSecs:
push ax
cld;----------------- Displaying colon also
mov ax,0B882h
mov es,ax
xor di,di
mov ah,1Fh
add dh, 30h
mov al,dh
stosw
mov ax,0B884h
mov es,ax
xor di,di
mov ah,1Fh
mov al,':'
stosw
pop ax
ret
FirstDigitOfMins:
push ax
cld
mov ax,0B886h
mov es,ax
xor di,di
mov ah,1Fh
add dh, 30h
mov al,dh
stosw
pop ax
ret
SecondDigitOfMins:
push ax
cld;----------------- Displaying colon also
mov ax,0B888h
mov es,ax
xor di,di
mov ah,1Fh
add dh, 30h
mov al,dh
stosw
mov ax,0B88Ah
mov es,ax
xor di,di
mov ah,1Fh
mov al,':'
stosw
pop ax
ret
FirstDigitofHrs:
push ax
cld
mov ax,0B88Ch
mov es,ax
xor di,di
mov ah,1Fh
add dh, 30h
mov al,dh
stosw
pop ax
ret
SecondDigitOfHrs:
push ax
cld
mov ax,0B88Eh
mov es,ax
xor di,di
mov ah,1Fh
add dh, 30h
mov al,dh
stosw
pop ax
ret
Somebody kindly guide me in this regard.
Zulfi.
The difference between segment B880 and B882 isn't 2 bytes but 2 paragraphs (32 bytes).
The convention is to leave ES as B800 and use that as a base.
Zulfi,
it is simpler to build the entire string, then display it all at once
try this code....
.MODEL TINY
;---------------------------------------------------------------------------------
LoadOfs EQU 0 ;must match the value in the bootloader source file
;---------------------------------------------------------------------------------
.CODE
ORG 0
_main PROC FAR
;set video mode 3 - clear screen
mov ax,3
int 10h
;set segment registers
push cs
pop ds
mov ax,0B800h
mov es,ax
cld
;date-time display update loop
TLoop0: mov ah,2 ;get BIOS RTC time
int 1Ah
jc TLoop0
cmp dh,Seconds+LoadOfs
jz TLoop0
mov Seconds+LoadOfs,dh
mov si,offset DatTime+LoadOfs
call UpdDt
mov [si],ax ;store month value
xor di,di ;screen buffer address = 0
call Dsply
jmp TLoop0
_main ENDP
;---------------------------------------------------------------------------------
UpdDt PROC NEAR
;update date/time string
;CH = BCD hours
;CL = BCD minutes
;DH = BCD seconds
;SI = address of DatTime string
;update time values
mov al,dh ;AL = BCD seconds
call UAscii
mov [si+12h],ax
mov al,cl ;AL = BCD minutes
call UAscii
mov [si+0Fh],ax
mov al,ch ;AL = BCD hours
call UAscii
mov [si+0Ch],ax
;update date values
push si
UpdDt0: mov ah,4 ;get BIOS RTC date
int 1Ah
jc UpdDt0
pop si
;CH = BCD century
;CL = BCD year
;DH = BCD month
;DL = BCD day
mov al,cl ;AL = BCD year
call UAscii
mov [si+8],ax
mov al,ch ;AL = BCD century
call UAscii
mov [si+6],ax
mov al,dl ;AL = BCD day
call UAscii
mov [si+3],ax
mov al,dh ;AL = BCD month
UAscii:
DB 0D4h,10h ;AAM 10h
or ax,3030h
xchg al,ah
ret
UpdDt ENDP
;---------------------------------------------------------------------------------
Dsply PROC NEAR
mov ah,0Ah ;AH = attribute
jmp short Dsply1
Dsply0: stosw
Dsply1: lodsb
or al,al
jnz Dsply0
ret
Dsply ENDP
;---------------------------------------------------------------------------------
Seconds DB 0FFh ;force initial update
DatTime DB '00-00-0000',32,32,'00:00:00',0
;---------------------------------------------------------------------------------
END _main
I am trying to use an array now. If it doesnt work I would let you know. Thanks for your code. I am trying to improve on my conversion method.
Zulfi.
one thing i would suggest, Zulfi
set the ES register to 0B800h and leave it there
use the DI register to set the screen position
there are a couple reasons for this
1) the ES register can only make steps of 16 bytes - DI can make steps of 1 byte
2) you cannot load an immediate value into a segment register
it has to be put into a general register or in a memory location, first
with DI (or SI or BX), you can load the register directly
this is both smaller and faster
Hi,
Thanks for your guidance. I am encountering following probs.
1) I have to use a different algorithm (alg). The alg which I am using , is used to display decimal values.23:15:56. This is a decimal time. But
when I store it in a register e.g bh in my code, it becomes a hex value. Thus 23 is not 23d but it is 23h which is 35d and this is causing prob.
2) Another prob. is occurring when I am trying to display the values. I am using both si and di and the following code for displaying is not working:
mov ax,0B800h
mov es,ax
xor di,di
mov di,100h
mov cx,8
mov si,offset arr+LoadOfs
mov ah,1Fh
nextval: mov al, [si]
inc si
inc di
stosw
loop nextval
Kindly guide me how to use cld in this case.
Zulfi.
hiya Zulfi,
the values returned by INT 1Ah, functions 2 and 4 are packed BCD
that means that each byte register holds 2 digits of information
the upper 4 bits of the register contain the high order digit
and the lower 4 bits contain the low order digit
for example, the number 20 (decimal) will be 20h
to display the digits, you must do 2 things:
1) split the 2 digits into seperate bytes (i.e. unpack them)
2) convert them from BCD values to ASCII (by adding 30h)
in my code, i used AAM 10h (an undocumented instruction) as Steve mentioned earlier
if you look at his code, he used a more "legal" method
if the packed BCD digits are in AL, it can look like this...
mov ah,al
and ah,0Fh
shr al,1
shr al,1
shr al,1
shr al,1
or ax,3030h ;make them ASCII
the high order digit is in AL and the low order digit is in AH
so that when you put them into memory as a word, the high order digit will be at the lower address
as for using the CLD instruction, you should be able to place it once at the beginning of the code and leave it
unless you use an STD instruction someplace to set the direction flag, it should remain cleared
as i mentioned before, the same is true of loading the ES register with 0B800h
early in the program...
push cs
pop ds ;DS = CS
mov ax,0B800h
mov es,ax ;ES = B800
cld ;DF = 0
after that, the SI register should point to your data string
and DI should point to the location on the screen
remember, there are 2 bytes in the screen buffer for each character (the ASCII character and the color attribute)
there are 80 characters per line in video mode 3
so, to calculate the DI value:
address = 2 * (80 * row + column)
Hi,
Your code here:
mov ah,1Fh
nextval: mov al, [si]
inc si
: inc di
stosw ; <= You are automatically incrementing DI
loop nextval
You need to increment SI "by hand" as you are using a MOV
rather than LODSB. But DI is being incremented by the STOSW.
Regards,
Steve
Hi,
Thanks. Steve I would check your code. Dave I want to use your alg. for conversion but I want to give one more try to the conversion alg. which I am using. Instead of subtracting 10D I should subtract 10h. I think this may solve the prob.
Zulfi.
Hi,
Timer is working now. I appreciate cooperation of all those who provided guidance to me in this prob. I have to now find the time elapsed since the OS started.
Zulfi.
Hi,
Just for future reference: the code I posted to use Int 1AH
failed on one machine. So a correction.
Change the following logic for all similar int 1AH routine.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Time of Day
RTime:
MOV AH,2 ; Get CMOS Time
INT 1AH ; Real Time Clock Interrupt
JC RTime
To incorporate a clear carry instruction.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Time of Day
RTime:
CLC ; At least one BIOS does not clear this, = infinite loop.
MOV AH,2 ; Get CMOS Time
INT 1AH ; Real Time Clock Interrupt
JC RTime
Regards,
Steve N.