Here are some routines to use the RTC/CMOS chip for serious timing. It's an introductory tutorial, so you'll be given more than enough opportunity to experiment with timing via this method.
About the hardware.
The RTC chip used to be a Motorola MC 146818A chip, but nowadays you
will either find a Dallas 1287 or 1387 style chip, or it is embedded in
the chipset. So far for romance... :o)
I will describe the Dallas DS 1287 since this is the configuration which
is most common for many years now, and the majority of the features are
the same as for the other chips.
The DS 1287 is a clock/RAM with a Lithium battery inside the package.
That's why it stays so big: the battery needs space. If the system is
powered on, the RTC gets its power from the powersupply. When the PC is
off, the RTC goes into power-down mode and slowly drains the Lithium
cell. Expected life for the battery is around 10 years.
The DS 1287 has 64 storage locations, 14 of which are clock and control
registers and the remaining 50 are battery-backed general purpose RAM
cells. This is were the CMOS setup of your PC stores it's system setup
data.
The programmable clock can issue an interrupt, which can be triggered by
three independent events: time of day, periodic signal or end of clockupdate.
The 14 registers inside the DS 1287 are:
address purpose
------- ---------------------------------------
0 current value of seconds
1 alarm setting for seconds
2 current value of minutes
3 alarm setting for minutes
4 current value of hours
5 alarm setting for hours
6 Day of the week [Sunday = 1]
7 Day of the month
8 month [0..12]
9 year of this century [0..99]
10 Control register A
11 Control register B
12 Control register C [read-only]
13 Control register D [read-only]
If you want to know the time of day, or any other date related data,
just select the RTC chip and request the contents of the desired
register.
The alarm registers can be set to generate long-time periodical
interrupts, or for having the chip give a signal when it's time for your
nap. The alarm rate ranges from seconds to weeks.
And since these alarm registers are almost never used, they can also be
used for storing some data for your own software. PTS Partition Manager
for example uses these registers to keep track of where it was, while
reformatting the hard disk. If there is a power-fail, it will just
continue where it left off.
In the PC, the RTC chip is hidden from the programmer. It can only be
accessed in an indirect way. The trick is to first select a register
location and then access that one register as follows:
mov al, <register number>
out 70h, al ; select <register number>
in al, 71h ; for a READ operation
out 71h, ah ; for a WRITE operation
So, we use port 70h for selecting a register or storage location and use
port 71h for doing the actual access to that register. A bit tedious,
but that's how the PC was designed in the first place.
In "old style" RTC chips the century is maintained in software. It
resides in a RAM cell, offset 32h/50d, so it will not be affected by a
year-rollover from 99 to 00. If you update it with a short piece of code
on January first 2000, your PC will be ready for many, many, moons to
come.
The control registers.
Registers A, B, C and D are the registers that control the working of
the RTC clock. They have various functions and register D uses just a
singe bit, which is also read-only....
But this chip is well engineered and all registers have a significant
(although not always logical) influence on the operation of it.
Register A: Timing control.
Register A is layed out as follows:
bit function
--- ------------------------------------------------------------
7 UIP bit: Update In Progress. When there's a ONE in this flag
the timing registers are being updated and it is not safe to
read them. Better to wait until this flag is cleared.
This one bit is read-only!
4-6 DV0-DV2: these three bits control the on-chip oscillator. Do
not experiment too much with this setting. There is only ONE
valid combination for these three bits: 010.
0-3 RS0 - RS3: These are the four Rate Selector bits. They
determine how often the IRQ pin is activated. The following
table shows the meaning of the different values.
RS3 RS2 RS1 RS0 Frequency [Hz] Period [ms]
--- --- --- --- -------------- -----------
0 0 0 0 --- ----
0 0 0 1 256 3.906
0 0 1 0 128 7.813
0 0 1 1 8192 0.122
0 1 0 0 4096 0.244
0 1 0 1 2048 0.488
0 1 1 0 1024 0.977
0 1 1 1 512 1.953
1 0 0 0 256 3.906
1 0 0 1 128 7.813
1 0 1 0 64 15.625
1 0 1 1 32 31.25
1 1 0 0 16 62.5
1 1 0 1 8 125.0
1 1 1 0 4 250.0
1 1 1 1 2 500.0
The default value in the average IBM PC is 0110 or 1024 Hz.
Since no IRQ is enabled, you will not notice any difference
if you change the value.
Register B: Internal operation control.
This is the most important register for controling operation of the RTC
chip. Register A determines timing and oscillator parameters, but the Bregister
determines how the system will notice these conditions.
In a normal PC, only bit 1 (24/12) is set. All other bits are cleared.
bit function
--- ------------------------------------------------------------
7 SET : If you determine to write a ONE in this bit position,
the clockregisters will not be updated anymore. Only when
this bit is ZERO, the clockregisters will be updated.
6 PIE : The Periodic Interrupt Enable bit controls the IRQ
pin. If this bit is ZERO, no IRQ will be given when the
programmable frequency source (selected by RS0 - RS3) times
out.
You need to set this bit to a ONE to enable a periodic IRQ
operation.
5 AIE : Alarm Interrupt Enable. When this bit is ONE, the IRQ
pin is activated when the alarm-time equals the actual time.
4 UIE : "Update Ended" Interrupt Enable. When this bit is set
to ONE, the IRQ line is asserted when the timing registers
have changed contents.
3 SQWE : Put a ONE in this bit to have the programmable
interval timer (which is controlled by RS0 - RS3) output a
square wave on pin 23 of the chip.
Unfortunately this pin 23 is not connected in a PC so for us
this bit has no meaning. But if you are man enough to bring
pin 23 of the DS 1287 to the outside world, you can use it
at will.
2 DM : Data Mode. The timing registers can display their data
in two different modes: binary and BCD. In the PC, this bit
is always ZERO, meaning that BCD is the desired format.
1 24/12 : Controls if hours are shown in 12 or 24 hours mode.
Put a ONE inhere and you have 24 hours in a day. Clear this
bit and you end up with two half days of 12 hours each. In
the 12-hour mode, bit 7 acts as an AM or PM flag.
0 DSE : Daylight Saving Enabled. Always leave this bit cleared
to ZERO. Daylight saving time periods vary worldwide and the
dates of change are determined by politicians and not by
chipmakers. Unfortunately.
Register C: Interrupt sources.
Register C is a status-word only. The bits in this register are readonly
and only have menaing AFTER an IRQ was received.
Since there is just one IRQ pin on the RTC chip, the IRQ can have three
different sources and there's no way to know which one triggered it,
unless there was only one source enabled. The bits mean the following:
bit function
--- ------------------------------------------------------------
7 IRQF : If this bit is ONE, one of the actual interrupt
conditions was enabled and the interrupt condition was met.
6 PF : Periodic interrupt Flag. If this bit is set, the source
of this IRQ source was the programmable interval timer.
5 AF : Alarm interrupt Flag. If this bit is set, the alarm
condition was the same as the actual date/time.
4 UF : The "Update Ended" interrupt Flag. If this bit is set,
the IRQ was issued by an update of the timing registers.
Bits 0 - 3 are meaningless and will always be ZERO.
Register D: Battery status.
On the chip, there is a voltage reference that is constantly being
compared to the battery voltage. If the battery voltage drops below the
reference voltage, the battery is considered empty and bit 7 will be
SET.
If bit 7 is a ONE, the battery has been empty for some period of time
and hence the data in the timing registers and in the RAM locations MAY
have lost their meaning.
Bits 0 - 6 have no meaning in this register and will always return a
ZERO value.
Using the RTC internals.
This, in a nutshell, is what the RTC chip is from the inside. I already
explained some lines above how to access the storage locations and the
timing registers of the DS 1287. This does not mean that everything will
also work the first time.
If you need to change a timing value, you must always first disable
register updates, even if you make sure that the changes you make to the
timing registers will well fit in an RTC timeslot. This means:
- access register B and set the SET flag
- change the timing registers
- access register B and clear the SET flag
Remember, there's not much intelligence inside a DS 1287. More recent
chips might do more tricks for the programmer, but the old beasties just
do as they were told.
In order to set the periodic interrupt rate, we use the following code:
--- Begin ------------------------------------------- SetPIRate -----
SetPIRate: ; Set Periodic Interrupt Rate
mov al, 0A ; ah = rate to set
out 070, al
mov al, ah
out 071, al ; and set it in register A
ret
---- End -------------------------------------------- SetPIRate -----
This code is very straightforward. It relies on the fact that (in the
IBM PC) the contents of register A are always the same:
bit 7 = read-only
bits 4 - 6 = 010
bits 0 - 3 = rate selector
So, it can set the value of bits 4 - 7 in the calling code. It is not
good programming, since we should:
- read in the contents of Register A
- clear bits 0 - 3
- OR in the new value
- write it back to register A
Inside the IBM PC.
The IRQ pin of the RTC is connected to the Intel 8259 PIC (Programmable
Interrupt Controller, although "programmable" is too much honour for
this dumbo). In non-XT machines there are two of them, cascaded. This
means that the second one is connected to what used to be IRQ2. This
gives us a rather stupid PC IRQ priority list:
IRQ Priority IRQ Priority
--- -------- --- --------
0 0 8 2
1 1 9 3
2 10 10 4
3 11 11 5
4 12 12 6
5 13 13 7
6 14 14 8
7 15 15 9
A lower number means a higher priority....
The RTC interrupt line is connected to PC-IRQ8. So it comes in third
place for being serviced. When enabled!
Normally IRQ8 is NOT enabled, so you will first have to settle that with
the PIC, which is far from easy to understand. I use the following code
to enable and disable the IRQ8 processing. Disabling this interrupt is
necessary after your program is unloaded from memory. If you don't do
this, the IRQ service routine vector might point to some random code or
data in the next program loaded (like Command.Com).
----------------------------------------------------- EnableIRQ8 ----
EnableIRQ8: ; enable IRQ 8 in 8259
push ax
in al, 0A1 ; get IRQ mask word
and al, not bit 0
out 0A1, al ; enable IRQ 8
pop ax
ret
----------------------------------------------------- EnableIRQ8 ----
Easy, isn't it? It took some nights to figure this out, 'cause the Intel
databooks are not that clear. I was glad to find some NEC databooks
since these shed some more light. In general, for older chips, NEC is a
good choice of databooks. They used to second source 80x86 chips for
Intel and are still known for their innovations they put into their V20
and V30 chips. The V25, a vastly improved 8088, was contaminated by 8
full banks of 14 registers. Luckily Intel did not copy this. What would
a 386 have been with 250 GP registers?
Here's the code for disabling IRQ8:
--- Begin ------------------------------------------ DisableIRQ8 ----
DisableIRQ8: ; disable IRQ 8 in 8259
push ax
in al, 0A1 ; get IRQ mask word
or al, bit 0
out 0A1, al ; disable IRQ 8
pop ax
ret
---- End ------------------------------------------- DisableIRQ8 ----
Asserting IRQ8 will make the PC generate an INT 70h. So, we need to have
an INT 70h handler ready:
--- Begin ------------------------------------------ NewIRQ8 --------
L0: mov [IrqCount], ax ; and store it
L1: mov al, 020 ; tell stupid PC that IRQ ends here
out 020, al ; EOI to original PIC
out 0A0, al ; EOI to cascaded PIC
pop ds, ax ; restore registers
iret ; and get out
NewIRQ8: push ax, ds
cs mov ds, [DataSeg] ; restore DS
mov al, 0C
out 070, al
in al, 071 ; clear interrupt flags
test [Flags], Running ; are we running?
jz L1 ; if not, get out
test [Flags], FastMode ; Samplerate over 128 Sps?
jz >L2 ; if not, scram
or [Flags], TimeOut ; else set TimeOut flag
jmp L1
L2: mov ax, [IrqCount] ; medium to slow samplerates
dec ax ; are we at correct value?
jnz L0 ; ... if not, wait some more
or [Flags], TimeOut ; ... if so, set TimeOut flag,
mov ax, [MaxCount] ; ... reload time constant register
jmp L0
---- End ------------------------------------------- NewIRQ8 --------
I like to do as little as possible in this kind of routines. In this
case I set a flag and rely on the abillities of the background program
to fork execution based on the state of that flag.
I hate the idea of having an INT routine that actually DOES things, but
which, for some obscure reason, cannot complete before the next INT
comes in. You'll be able to figure out what will happen in most cases.
If this routine sets a flag twice, I don't care too much. OK, I loose a
sample, but the program keeps running and it will still terminate when I
ask it to.
This routine:
- saves registers on the user-stack
- restores correct DS
- accesses the FLAGS register in memory
- consults these flags and acts upon them
- eventually reaches L1 and here an EOI is sent to the PIC's
- pops the stored registers from the userstack
- returns with an IRET.
The PIC needs an EOI to enable lower priority interrupts. And since
there are two PIC's in modern PC's, there also must be two EOI's.
The following routine will enable the new IRQ8 handler:
--- Begin ----------------------------------- EnableNewIRQ8 ---------
EnableNewIRQ8: ; program the RTC chip to 1 kSps
push ax ; and enable the 8259 PIC, channel 8
mov al, 0C
out 070, al
in al, 071 ; check register C first
mov ah, 00100110xB
call SetPIRate ; set PI rate to 1 kSps
mov al, 0B
out 070, al
mov al, 01000010xB ; enable the RTC interrupt pin
out 071, al ; and store it in RTC register B
call EnableIRQ8 ; enable the 8259 PIController
pop ax
ret
---- End ------------------------------------ EnableNewIRQ8 ---------
And before going back to the OS of your choice, make sure there will be
no IRQ8's anymore coming this way:
--- Begin ----------------------------------- ResetNewIRQ8 ----------
ResetNewIRQ8: ; restore default values in RTC
push ax ; and disable 8259 PIC, channel 8
mov al, 0A
out 070, al ; select register A
mov al, 00100110xB
out 071, al ; and set it back to PC default
mov al, 0B
out 070, al
mov al, 00000010xB ; disable interruptions from RTC chip
out 071, al ; via register B
call DisableIRQ8 ; handle the PIC
pop ax
ret
---- End ------------------------------------ ResetNewIRQ8 ----------
In the big program these code fragments are from, I use two timer
interrupts:
- the RTC timer is used for trigger-timing. When the RTC has set the
right flag, the main program will sample the ADC and store the
result in a buffer for later processing.
- the internal PC klok which generates the 55 ms timing signals is
used to set another flag. When this is set, the (DMM style) display
is updated. The digital readout is updated about 3 times per second
and the bargraph display is updated 18 times per second.
Therefore I also need a new IRQ0 handler:
---------------------------------------------------- NewIRQ0 --------
L0: pop ds ; restore register
jmp [cs:OldIRQ0] ; and update DOS clock
NewIRQ0: push ds ; new timer routine (18,2 Hz)
cs mov ds, [DataSeg] ; restore DS
test [Flags], Running
jz L0 ; if not running, eject!
inc [Counter] ; else increment counter,
or [Flags], RefrshBar ; indicate "bargraph refresh"
test [Counter], 07 ; twice per second,
IF Z or [Flags], RefrshDig ; indicate "digits update"
jmp L0 ; and get out
---------------------------------------------------- NewIRQ0 --------
This new routine does the following:
- check if the DMM is running,
- if not, it makes no sense to set any flags,
- if running, set the "update bargraph display" flag,
- if running, check if it is time to update the digital readout,
- restore DS register,
- branch to previous IRQ0 handler.
In the initialisation routine, common to all my programs, I make sure
the right interrupt vectors are stolen:
--- Begin ------------------------------------------ Init -----------
init: call SetVars ; init most import variables
call PowDown ; make sure ADC is OFF
call ClkLo ; prepare ADC for power-up
call ChkTime ; measure minimum sample time
call MaxSps ; determine maximum sample speed
mov ah, 0F
int 010 ; determine existing video mode
mov [VidMode], al ; store it
mov ax, 012
int 010 ; set 640 x 480 graphics mode
push es
mov ax, 0351C ; get old timervector
int 021
mov w [OldIRQ0], bx
mov w [OldIRQ0+2], es
mov dx, offset NewIRQ0
mov ax, 0251C
int 021 ; install new TIMER routine
mov ax, 03570
int 021
mov w [OldClock], bx
mov w [OldClock+2], es
mov dx, offset NewIRQ8
mov ax, 02570
int 021 ; install NewIRQ8 routine
call EnableNewIRQ8 ; and get it to work
pop es
mov ax, 0
int 033 ; init mouse
ShowMouse ; this is a macro....
call FillScreen
or [Flags], RfrshBar + RefrPara + Upd8Digs
call ShowDig
call BrScale
call Update
ret
---- End ------------------------------------------- Init -----------
Not much to explain about this INIT routine I guess.
So, on to the EXIT part of the software. Forget this, and the computer
will hang on random times afterwards....
--- Begin ------------------------------------------ Exit -----------
exit: call PowDown
call ResetNewIRQ8
push ds
lds dx, [OldIRQ0]
mov ax, 0251C
int 021 ; restore timer vector
pop ds
push ds
lds dx, [OldClock]
mov ax, 02570
int 021 ; restore realtime clock vector
pop ds
mov ah, 0
mov al, [VidMode]
int 010 ; back to previous screenmode
mov ax, 0
int 033 ; reset mouse and -driver
mov ax, 04C00
int 021 ; and exit to DOS
---- End ------------------------------------------- Exit -----------
That's all you need to know to get started. The RTC chip has some nice
other possibillities. It can be programmed to interrupt each second. Or
any other number of seconds. It is a truly versatile chip with many
timing functions directly available to systems level programmers.
It might be a good idea to seacrh the web for a datasheet. A good
starting point will be www.dalsemi.com where PDF files will be available
for all DS 1287 style chips. Or else from ftp ftp.dalsemi.com. The latest
versions of this chip that I know of is the DS 17887. This has a Y2K
compliant clock and over 8K of NV (=Non Volatile) RAM.
In the USA Dallas have an Automatic Datasheet FaxBack number:
972 - 371 4441
Have fun exploiting the RTC chip, but be prepared to hit the reset
button now and then. Also, make a backup of the CMOS battery-backup RAM
onto a floppy disk! You'll have corrupted or erased these data before
you know it and it's always a bit of a shock if the system cannot even
find the C: drive anymore....
|