RTC

From OSDev.wiki
Revision as of 03:13, 30 January 2010 by osdev>Bewing (Added a link for just reading date/time from RTC)
Jump to navigation Jump to search

Introduction

Most OSs use the PIT for timing purposes. However, I believe the RTC works just as well. RTC stands for Real Time Clock. It is the chip that keeps your computers clock up-to-date. Within the chip is also the CMOS 64 bytes of RAM.

If you simply want information about reading the date/time from the RTC, then please see the CMOS article. The rest of this article covers the use of RTC interrupts.

Capabilities

The RTC is capable of multiple frequencies. The base time update is pre-programmed at 32.768 kHz. It is possible to change this value, however, this is the only base frequency capable of keeping proper time. For this reason, it is strongly recommended that you NOT change the frequency of the RTC, or your computers clock will no longer be accurate. The output(interrupt) divider frequency is by default set so that there is an interrupt rate of 1024 Hz.If you need an interrupt frequency higher than 1024 Hz, it is possible to reprogram just the rate of the interrupt. The RTC can handle 15 interrupt rates between 2 Hz and 32767 Hz, theoretically. On most machines however, the interrupt rate can not go higher than 8 kHz.

Avoiding the NMI

When programming the RTC, it is important that the NMI (non-maskable-interrupt) be disabled. This is because if an NMI happens, then your programming code will be interrupted. This would usually not be a big deal, but the RTC is never initialized by BIOS. Actually, BIOS initializes itself from the CMOS. Note, just cause the computer reboots, doesn't mean the RTC does. The RTC is powered by an internal battery and is separate from the rest of the computer. See the NMI page for more information about disabling and enabling it, and the effects of it.

Ports

The 2 ports used for the RTC and CMOS is 0x70 and 0x71. Port 0x70 is used to specify an index. Port 0x71 is used to read or write to/from that byte of CMOS configuration space. Each byte in the 14 bytes used by the RTC in CMOS is called a register. Byte 0x0C is called register C. So, to write 0x20 to register A, you would do this.

disable_ints(); //important that no interrupts happen, including NMI
outportb(0x70, 0x0A); //write to index
outportb(0x71, 0x20); //write to actual RAM
enable_ints();

Above byte 14 of the CMOS RAM is not used by the RTC, but by the BIOS and other such services.

IRQ Danger

First off, it is important to note that the IRQ number is 8. This means it has a lower priority than most of the other IRQs. You must keep this in mind when programming other IRQ handlers. If you have a harddisk IRQ handler that waits for time to pass inside the IRQ handler, then your machine will be stuck in an infinite loop. An IRQ of higher number will not interrupt an IRQ of lower number.

Turning on IRQ 8

To turn on the periodic interrupt, all you have to do is:

disable_ints(); //disable interrupts include NMI
outportb(0x70, 0x0B); //set the index to register B
char prev=inportb(0x71); //read the current value of register B
outportb(0x70, 0x0B); //set the index again(a read will reset the index to register D)
outportb(0x71, prev | 0x40); //write the previous value or'd with 0x40. This turns on bit 6 of register D
enable_ints();

This will turn on the IRQ with the basic 1024 Hz rate. Be sure that you install the IRQ handler before you enable the RTC IRQ. The interrupt will happen almost immediately.

Changing Interrupt Rate

Changing the output divider changes the interrupt rate, *without* interfering with the RTC's ability to keep proper time. The lower 4 bits of register A are the interrupt divider. The default is 0110b, or 6. The frequency must be a value from 1 to 15. A value of 0 disables the interrupt. To calculate a new frequency, you would do

frequency =  32768 >> (rate-1);

Rate is the divider. If you select a rate of 1 or 2, the RTC will have problems and "roll over" so that it generates interrupts of .81 mS and 3.91 mS, rather than the expected interrupts of 61.0 uS or 30.5 uS. So, the fastest rate you can select is 3. This will generate interrupts of 8 kHz or 122 uS.This is how to change the rate:

rate &= 0x0F; //rate must be above 2 and not over 15. (this is a safe-guard to be sure it isn't over 15)
disable_ints();
outportb(0x70, 0x0A); //set index to register A
char prev=inportb(0x71); //get initial value of register A
outportb(0x70, 0x0A); //reset index to A
outportb(0x71, (prev & 0xF0) | rate); //write only our rate to A. Note, rate is the bottom 4 bits.
enable_ints();


Interrupts and Register C

It is important to know that upon a IRQ 8, register C will contain a bitmask telling which interrupt happened. The RTC is capable of producing a periodic interrupt(what we use), an update ended interrupt, and an alarm interrupt. If you are only using the RTC as a simple timer, this is not important. What is important is that if register C is not read after an IRQ 8, then the interrupt will not happen again. So, even if you don't care about what type of interrupt it is, just attach this code to the bottom of your IRQ handler to be sure you get another interrupt.

outportb(0x70, 0x0C); //select register C
inportb(0x71); //just throw away contents.

Possible Uses

Now, you may be wondering what is so useful about this. Well, there are quite a few possible uses for this. You could use just the RTC and not use the PIC (the RTC is easier to program in my opinion). My favorite use is to use the RTC for my main kernel clock (controls scheduling and all), and then use the PIC to provide a more accurate wait() function. A wait function that can work in certain degrees of micro-seconds.

Problems?

I have recently began going around testing my OS on machines, and my timer code seems to be broken on some machines. I have only found about one out of 5 computers that does not work right. The observed problem is a timer tick happened about once every second. I'm not sure why this is, and am trying to find a solution. The machine I observed this on was a 2005 or so Dell(which also has issues USB booting...)

See Also