• Please review our updated Terms and Rules here

[8086 Assembler] Reading from an 8253 timer "in motion"

Hak Foo

Experienced Member
Joined
Jul 18, 2021
Messages
213
I've been trying to set up a fixed-duration timer using a PC-compatible 8253 timer #2. Basically I don't want to use a dumb countdown loop because I know it's CPU-dependent.

I'm in a situation where I can't just use the normal timer interrupt (it may not be initialized, early BIOS bring-up), but the 8253 should be available.

* I start by modifying port 61-- clear the last two bits to deactivate the speaker and counter.
* Then I send 10111000 to port 43 -- which should set up timer #2 in software-triggered mode, and feed in my countdown to port 42, LSB then MSB.
* Then turn on port 61 lowest bit to enable the counter.

Where it goes wrong: I want to read back the counter to determine when it reaches 0000.

I've tried:

* Send 10000000 to port 43 -- to "latch" counter 0
* Read in port 42 twice.

What I seem to get is a lot of very random data. If I'm initializing the timer at 0x100, I feel like I shouldn't be getting values over 0x100!

I figure if it was some sort of timing mismatch, and it was reading the LSB twice, you'd see a lot of repeated values (i.e. 7F7F), but that doesn't seem common. If it were a count-up by accident, I'd never see values below 0x100, but the coverage is pretty good.

It eventually DOES get to zero, and the loop ends, and different start values seem subjectively to affect the delay, but is there something wrong with my probing logic? I don't feel confident in a countdown that doesn't count down.

Code as it stands. AX is supposed to be the timer start The outermost indented code is debugging-- it will try to draw a mark on the screen offset "current counter" positions from the top left, of character "original start value / 0x100 + 3".

I'd expect it to fill the screen from the bottom up , potentially with gaps, if ran with a high value of AX, and if ran with a low number (i. e. 0x100) only do the first 0x100 characters. Instead, we get almost the whole screen covered with all characters tried.

Code:
PUSH DS
PUSH CX
PUSH BX
    PUSH AX
    PUSH AX                ; Save another copy for the count-down quantity
MOV CL, AH
INC CL
INC CL
INC CL
MOV AX, 0xb800
MOV DS, AX


    IN AL, 0x61
    AND AL, 0b11111100    ; Force counter and speaker off
    OUT 0x61, AL
    MOV AL, 0b10111000  ; Put timer #2 into soft-trigger mode
    OUT 0x43, AL
    POP AX
    NOP
    NOP
    OUT 0x42, AL
    MOV AL, AH
    NOP
    NOP
    OUT 0x42, AL
    IN AL, 0x61
    PUSH AX                ; Save the control flags
    OR AL, 0b00000001    ; Turn on counter but not speaker itself
    OUT 0x61, AL
    WAIT_FOR_COUNTDOWN:
        MOV AL, 0b10001000 ; Latch counter for timer #2
        OUT 0x43, AL

        NOP
        NOP
    
        IN AL, 0x42            ; Yes this is backwards, read LSB then MSB, but since we're looking for 0, it's equal.
        MOV AH, AL
        
        NOP
        NOP

        IN AL, 0x42
MOV BL, AH
MOV BH, AL
SHL BX, 1
MOV [DS:BX],CL

        CMP AX, 0000       
        JNE WAIT_FOR_COUNTDOWN
        
    POP AX
    OUT 0x61, AL        ; Restore control flags
    POP AX
POP BX
POP CX
POP DS
    RET
 
I think I figured out the issue-- it seems like the timers, even "one-shot" timers, count down to zero and reset. So I was expecting to wait and see "0000" when reading from port 42, but instead it would proceed onto the next run. This lead to poor reliability (we wanted to catch "exactly 0000"). Instead, I'm now waiting based on a mindset of "if we see a value of 0, or a higher value than the lowest value we saw before, the timer must have at least one through zero."
 
One of the improvements of the 8254. Easiest on the 8253 is to stop the timer, latch the value , then restart the timer and read the latched value. You may lose a few microseconds, but that doesn't matter in many cases.
 
Some time ago I wrote a code to implement a time counter relaying on the PIT, independent of the CPU speed. It's not a countdown as yours but I post it if it's somewhat useful. I wrote 2 versions.

The first one reads the timer from port 40h instead of 42h:

Code:
    mov     dx,40h
    in      al,dx
    xchg    al,ah
    in      al,dx
    xchg    al,ah
    xchg    cx,ax
    add     cx,36                                  ; Current timer + the delay we want
                                                          ; in ticks/second. 1 sec := 18.2 ticks
    
@@pollTimer:
    in      al,dx
    xchg    al,ah
    in      al,dx
    xchg    al,ah
    cmp     ax,cx
    jb      @@pollTimer                        ; until n tick pass

The second version reads the timer value from the BIOS instead of polling the timer port directly:

Code:
    mov     ax,40h
    mov     es,ax
    mov     bx,6ch              ; 40:6C -> BIOS area to read system time
    mov     dx,es:[bx]          ; Loading low part of time on DX
    xchg    cx,dx               ;  CX:=DX
    add     cx,36               ; +2 secs
@@pollTimer:
    mov     dx,es:[bx]          ; Poll BIOS timer
    cmp     dx,cx
    jb      @@pollTimer       ; until n tick pass

If you would need more precision than the 18.2 part of a second, you can always reprogram the PIT from default to a custom frequency.
 
I think I figured out the issue-- it seems like the timers, even "one-shot" timers, count down to zero and reset. So I was expecting to wait and see "0000" when reading from port 42, but instead it would proceed onto the next run.
This actually seems like a feature. Not only do you know that the timer has counted down (for sufficiently low timer numbers) but you also know how "long ago" the timer counted down. Most of the time, it likely doesn't matter. But if it does, you have the info available to you.
 
I think I figured out the issue-- it seems like the timers, even "one-shot" timers, count down to zero and reset. So I was expecting to wait and see "0000" when reading from port 42, but instead it would proceed onto the next run. This lead to poor reliability (we wanted to catch "exactly 0000"). Instead, I'm now waiting based on a mindset of "if we see a value of 0, or a higher value than the lowest value we saw before, the timer must have at least one through zero."
Recollection is that the counter never reaches 0000, in mode 2. Wikipedia seems to agree with my wetware:

Note that the values in the COUNT register range from n {\displaystyle n} n to 1; the register never reaches zero.
 
Back
Top