


                     Programming the AT Real-Time Clock Chip
        
        
        A Ferrari in Second Gear
        
        A couple of years ago, one of my high-flying friends mortgaged 
        his life and bought a Ferrari--darned nice looking car and faster 
        than anything I'd ever driven.  The doggone thing was a helluva 
        lot more fun than my Rabbit.  We took it out on the back roads, 
        ran it up past 130 and talked about it for months.  Exhilarating.  
        Funny thing though, he didn't normally get places faster than I 
        did.  In fact, many times it took him longer--usually because he 
        was driving at the speed limit.  (Small-town cops get their 
        jollies stopping fancy cars).  After a year or so he sold the 
        Ferrari and bought something more sensible--said there was no 
        reason to have a Ferrari when it never got out of second gear.
        
             Inside every At-compatible computer is a Motorola MC146818 
        Real-Time Clock (RTC) chip or one of its functional equivalents.  
        (The earlier PC/XT class of machines lacks this feature.)  This 
        particular IC provides a real-time clock, 14 bytes of clock, 
        calendar and control registers along with 50 bytes of general-
        purpose RAM all backed up by a battery that allows the 
        information to be retained when the system is shut down.  In 
        addition, the chip is capable of generating a hardware interrupt 
        at a program-specified frequency or time.
        
             In the AT, the primary function of the RTC is to retain 
        time, date and system setup information when the system is shut 
        down.  At power-on, the BIOS power on self-test code verifies the 
        system setup information and sets the system clock from the RTC's 
        date and time.  The chip is then completely ignored unless 
        specifically activated by an application program.
        
             There are BIOS functions that can access the RTC, but they 
        are quite limited.  With the exception of the functions that read 
        and set the RTC's clock, I've found the BIOS functions of limited 
        use, and of questionable value due to their buggy implementation 
        in many manufacturers' BIOS.  The BIOS functions truly leave the 
        RTC chip in second gear.  To access the chip's full power, you 
        must program it at the hardware level.
        
        
        CMOS RAM
        
        The CMOS RAM is divided into 3 areas:  the clock/calendar bytes, 
        the control registers, and general-purpose RAM.  Table 1 shows 
        the CMOS RAM locations and what each is used for.  As you can 
        see, there are 33 bytes--more than half of the total--of 
        "reserved" memory in three areas.  These locations are not 
        currently defined by the AT BIOS and may be used to store 
        information that will be retained after the power is shut down.
        
             The four status registers (A through D), located 
        appropriately at CMOS locations 0AH through 0DH, define the 
        chip's operating parameters and provide information about 






        interrupts and the state of the RTC.  See Figure 1 for 
        information on the particulars of each status register.
        
             With very few restrictions, all CMOS RAM locations may be 
        directly accessed by an application program.  Locations 11H, 13H, 
        and 1BH through 2DH are used in calculating the CMOS checksum 
        that the BIOS stores at locations 2FH and 2EH.  If a program 
        changes these bytes it must also re-calculate the checksum and 
        store the new value.  Changing these bytes without replacing the 
        checksum result in a "CMOS Checksum Error" at boot--forcing you 
        to run Setup before you can access the hard disk.  The reserved 
        memory at locations 34H through 3FH is not used in checksum 
        calculations and may therefore be changed with apparent impunity.  
        I say "apparent" only because individual hardware or BIOS 
        manufacturers may use the reserved CMOS RAM locations for 
        extended system setup information.
        
        
        The Update Cycle
        
        Once each second, for 1,948us, the Time, Calendar, and Alarm 
        (TCA) bytes are switched to the RTC's update logic to be advanced 
        by one second and to check for an alarm condition.  At 
        completion, the RTC sets the UF bit in Status Register C to 
        indicate that an update cycle has completed.  An interrupt will 
        not be asserted unless UIE in Status Register B is also set.
        
             During the update cycle, the TCA bytes are unavailable to 
        the outside world and the result of any attempted access is 
        unpredictable.  The trick is knowing when the TCA bytes are 
        available.  Enter UIP--the Update In Progress flag (bit 7 of 
        status register A).  244us before the start of an update cycle 
        (and before switching the TCA bytes to the internal update 
        logic), the RTC logic sets the UIP flag in Status Register A.  
        The flag is then reset at the completion of the update cycle 
        (2,228us later).  The UIP flag is set whenever the TCA bytes are 
        unavailable.  In addition, when the UIP flag is cleared, the 
        program is guaranteed at least 244us (1,464 machine cycles at 
        6MHz) to perform an access--plenty of time, even with subroutine 
        call overhead.
        
             In order to prevent an update from occurring while setting 
        the time or date, it is possible to halt the update cycle by 
        setting bit 7 (the SET bit) in Status Register B.  Once the 
        program has initialized the TCA bytes, the SET bit is cleared and 
        the update cycle continues.  
        
        
        Hardware Interrupts
        
        The RTC is capable of generating three different types of 
        interrupts.  The periodic interrupt can be programmed to occur at 
        frequencies ranging from 2 Hz to 8.192 KHz.  The update-ended 
        interrupt, if enabled, will occur once each second, after the 
        update cycle has completed.  And the alarm interrupt can be 
        programmed to occur at any specified time.






        
             Status Register B defines which events within the RTC can 
        generate an interrupt, but the AT's 2nd Programmable Interrupt 
        Controller (PIC) determines whether or not the RTC can interrupt 
        the processor.  In order for RTC-generated interrupts to be 
        recognized, bit 0 of the PIC's mask register must be set to 0.  
        Before this interrupt is enabled, you must have a working INT 70H 
        ISR installed and the RTC should be programmed to function as you 
        require.
        
             All three interrupts (periodic, alarm and update-ended) are 
        asserted on a single pin and cause the INT 70H ISR to be 
        executed.  It is the ISR's responsibility to identify which 
        interrupts have occurred and to dispatch to the proper routine.  
        Determining which interrupts have occurred is a simple matter of 
        reading Status Register C and "AND-ing" with Status Register B.  
        Bits that remain set reflect which events caused the hardware 
        interrupt.
        
        
        Regular as Clockwork
        
        Setting the UIE bit in Status Register B enables the once-per-
        second update ended interrupt that occurs at the end of the 
        update cycle.  The ISR that processes this interrupt must 
        complete within one second.
        
             One use of this interrupt is to keep the current RTC time in 
        RAM so an applications program doesn't have to wait for an update 
        cycle before reading CMOS RAM.  Each time the update-ended 
        interrupt occurs, the ISR reads the RTC time and date and stores 
        it in memory.  Any program that requires the time can then read 
        it directly from memory rather than going to CMOS RAM and 
        possibly having to wait for an entire update cycle.
        
        
        Periodically Speaking
        
        The RTC can be programmed to generate a periodic interrupt at 
        several different rates, as shown in Table 2.  When the PIE bit 
        is set in Status Register B, the RTC will generate an interrupt 
        at the frequency defined by the RS bits in Status Register A.  
        The ISR that processes this interrupt must complete before the 
        next periodic interrupt occurs.
        
             Accessing the TCA bytes during a periodic interrupt ISR is 
        not recommended and, at higher interrupt frequencies, can cause 
        strange behavior due to interrupts occurring while waiting for an 
        update cycle to complete.
        
        
        There's one born every...
        
        The most versatile of the interrupts provided by the RTC is the 
        alarm interrupt.  In it's most basic form, the RTC is programmed 
        to generate an interrupt once per day at the hour, minute and 






        second specified by the alarm time in CMOS RAM.  However, 
        Motorola provided for "don't care" conditions that allow the 
        alarm interrupt to be generated on a more frequent and/or 
        irregular basis.
        
             Setting any of the alarm bytes to FFH creates a "don't care" 
        condition for that particular byte, which means it will match ANY 
        value.  So, for example, an alarm time of FF:00:00 will generate 
        an interrupt once each hour, on the hour.  Similarly, FF:FF:00 
        generates an interrupt once each minute, and FF:FF:FF causes an 
        interrupt to occur once each second.
        
             Odd combinations of "don't care" conditions can create some 
        strange and possibly quite useful interrupt frequencies.  For 
        example, an alarm time of FF:00:FF will generate an interrupt 
        once per second during the first minute of each hour, and 
        00:FF:00 will generate an interrupt once each minute for the 
        first hour of the day.
        
        
        Using the RTC Functions
        
        RTC.H (Listing 1) contains function  prototypes and defined 
        constants used in accessing the functions and RTCHDW.C (Listing 
        2) contains the functions that access the RTC.
        
             CMOS.C (Listing 3) is a small program that dumps the 
        contents of CMOS RAM to the screen and adds 1 to the value at 
        location 1BH.  The CMOS checksum is then re-calculated and the 
        program exits.  If you turn your computer off, re-boot and run 
        the program again, you will see that the value you stored at 
        location 1BH is still there.
        
             HDWTST.C (Listing 4) sets up a simple clock and a 4,096 
        ticks-per-second counter that displays the number of ticks once 
        each second.  Pressing any key will exit the program.
        
        
        Timing Considerations
        
        Using the precision Zen timer from Michael Abrash's excellent 
        book Zen of Assembly Language, I timed the NewRTCint() routine.  
        On my 10MHz Kaypro AT, the routine takes approximately 110us, or 
        about 1,100 machine cycles, to execute when all of the interrupt 
        routines are called.  Using this information, we can calculate 
        the maximum supportable interrupt rate for a given 80286 
        processor by dividing the clock rate by 1,100.  For example, a 
        12Mhz 80286 can support a maximum interrupt frequency of 10.909 
        KHz (12,000,000 / 1,100).
        
             Be aware, however, that this is the absolute maximum 
        supportable frequency.  To paraphrase the auto ads, your actual 
        rate will probably be less.  Why?  Because this calculation does 
        not take into account other work that the processor will probably 
        be doing.  It assumes that the processor is doing nothing but 
        servicing the RTC interrupts.






        
             NewRTCint(), could be sped up a great deal by replacing the 
        calls to ReadCMOS() with in-line assembly language code.  The two 
        calls to ReadCMOS() take a total of approximately 54us--almost 
        one-half of the total execution time.  Further speed gains can be 
        realized by replacing the two calls to outportb() with in-line 
        assembly language.  I've elected not to make these modifications 
        in order to more clearly illustrate the concepts without muddying 
        up the waters with efficiency considerations.
        
        
        Other Considerations
        
        You'll notice that quite a lot of the code for NewRTCint() deals 
        with storing the caller's context and preventing stack overflow.  
        This is required because the RTC will sometimes generate a 
        periodic interrupt asynchronously--while the alarm interrupt is 
        being processed.  Without this stack check logic, NewRTCint() is 
        not re-entrant and the asynchronous interrupt will cause a system 
        crash.  The only other way to handle this potential problem is to 
        keep interrupts off for the duration of the routine--possibly 
        losing other interrupts in the process.
        
             RTCHDW.C must be compiled with standard stack frames enabled 
        (-k switch with Borland C++).  If you compile without standard 
        stack frames, the NewRTCInt() routine will not exit properly.  
        If, for some reason, you want to compile without standard stack 
        frames, replace the pop bp instruction in NewRTCInt() (right 
        before the iret instruction) with a line that reads:
        
             asm mov bp,[word ptr cs:bp+6]
        
             As with any other device, it is possible that another 
        program is using the RTC when your program wants to.  The easiest 
        way to tell if another program is using the RTC is to examine bit 
        0 of the 2nd PIC's mask register.  If this bit is 0, then some 
        other program is (or, in the case of one buggy BIOS that I 
        encountered, was) using the RTC chip.  If this bit is 0, 
        examining the interrupt-enable flags in Status Register B will 
        determine which (if any) RTC interrupts are being allowed.  If 
        none of the interrupt enable flags is set, then RTC interrupts 
        are not being processed and your program can take control of the 
        RTC without causing any problems.



