[Example?] How to read the RTC..

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

[Example?] How to read the RTC..

Post by mystran »

This should go to the Wiki, I guess, but since I don't know whether to make an RTC page (as suggested?) or drop it in the current Time&Date page, or what... I thought I'd post it here, for those that have trouble figuring this out with just the ASM example and datasheets out there somewhere (I used the datasheet for bq3285ED/LD which claims to be compatible with the classic, whatever the number of the original chip).

The routine returns Unix time, which is UTC by definition, so it'll go wrong almost certainly (most PCs have RTC in localtime, which is stupid), but as the comment states, it's easier to adjust it later (with a system call from userspace) by the correct offset, than try to make kernel worry about timezones.

The code (supposedly) works between the years 2001 and 2099.

Code: Select all

#define RTC_BASE 0x70
#define RTC_DATA 0x71

//
// Return Unix time in seconds, reading current time and date from RTC.
//
// This assumes that RTC is in UTC, which it typically isn't, but since
// the timezone isn't known, just let some process adjust it after boot.
//
unsigned clock_rtc_read() {

    int update = 1; // update in progress flag
    while(update) {
        // check if there's update in progress
        out8_p(RTC_BASE, 0xA); // RTC register A
        unsigned char c = in8_p(RTC_DATA);
        if(!(c & 0x80)) {
            update = 0;
        }
    }

    // read the various RTC fields
    
    out8_p(RTC_BASE, 0);
    unsigned char sec = in8_p(RTC_DATA);

    out8_p(RTC_BASE, 2);
    unsigned char min = in8_p(RTC_DATA);

    out8_p(RTC_BASE, 4);
    unsigned char hour = in8_p(RTC_DATA);

    out8_p(RTC_BASE, 7); // day of month
    unsigned char day = in8_p(RTC_DATA);

    out8_p(RTC_BASE, 8);
    unsigned char month = in8_p(RTC_DATA);

    out8_p(RTC_BASE, 9); // two digits, we assume 2000-2099
    unsigned char year = in8_p(RTC_DATA);

    // read RTC register B to figure out how to deal with the data
    out8_p(RTC_BASE, 0xB);
    unsigned char format = in8_p(RTC_DATA);

    // convert all fields from BDC to binary if bit[2] is clear
    if(!(format & 0x4)) {

        sec = (sec & 0xf) + (sec>>4)*10;
        min = (min & 0xf) + (min>>4)*10;
        hour = (hour & 0xf) + ((hour&0x70)>>4)*10 + (hour&0x80); // keep bit-7
        day = (day & 0xf) + (day>>4)*10;
        month = (month & 0xf) + (month>>4)*10;
        year = (year & 0xf) + (year>>4)*10;

    }
    
    // convert hours from 12 to 24 format if bit[1] is clear
    if(!(format & 0x2)) {
        int ampm = hour&0x80;
        hour = hour&0x7f;
        // fix 24:00 = 12am oddity
        if(hour == 12) hour = 0;
        if(ampm) hour += 12;
    }

    // Figure out the number of days after the beginning of 2000

    // Our calculation is simplified by the fact that 2000 is a leap year,
    // and after that every 4th year is a leap year, until 2100 before which
    // this routine will have to be updated anyway, because RTC wraps to 00.
    //
    // So first calculate days as if there was no leap years, then add
    // 1 day for each leap year that has passed, and one for leap back in 2000.
    //
    // This will actually give bogus values if year is 2000 (or before that),
    // but for code written in 2007 that shouldn't matter that much...
    //
    unsigned fullyeardays = year * 365 + ((year-1)/4) + 1;

    // Next get the number of (full) days for this year, using the lookup
    // table for months. RTC day and month count from 1. Adjust month.
    // Only adjust day if it's not leap year, or if leap day hasn't passed.
    //
    static unsigned daysbeforemonth[12] // for normal, non-leap year
        = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
    unsigned thisyeardays = daysbeforemonth[month-1] + day
        - (((year%4)==0 && month > 2) ? 0 : 1); // only if no leap day passed

    // Sum days before and during this year to get date, then adjust EPOCH
    // to stay compatible with Unix style 1970-01-01 00:00.
    //
    // The number 10957 was arrived at by first using GNU date:
    //   date -d '2000-01-01 00:00:00 UTC' '+%s' -> 946684800
    // and dividing the result with 24*60*60 to get the number of days.
    //
    unsigned date = fullyeardays + thisyeardays + 10957;


    // Finally build system time, unix format seconds since 1970-01-01 00:00
    unsigned time = sec + 60 * (min + 60 * (hour + 24 * date));

    return time;
    
}
I haven't found a PC here that had RTC set to 12-hour clock, so I haven't tested that part, so if somebody knows it's wrong, please tell.

edit: oops, forgot the port addresses from the post
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post by Kevin McGuire »

This is a perfect example. I will stash this in my brain box for later.

*kmcguire opens the little side door with a knob shaped just like a ear, and inserts the square shaped code function.
*Gears and sprockets being to turn making a clinkety clank sound.
*Suddenly a turd pops out in a compacted form ready to be placed neatly on a shelf for later use.
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

This is really good! Thanks for this, I've been looking for it for quite a while.
User avatar
Alboin
Member
Member
Posts: 1466
Joined: Thu Jan 04, 2007 3:29 pm
Location: Noricum and Pannonia

Post by Alboin »

Kevin McGuire wrote:*Suddenly a turd pops out in a compacted form ready to be placed neatly on a shelf for later use.
Wouldn't a turd smell after being stored on a shelf for awhile?
C8H10N4O2 | #446691 | Trust the nodes.
User avatar
Candy
Member
Member
Posts: 3882
Joined: Tue Oct 17, 2006 11:33 pm
Location: Eindhoven

Post by Candy »

Turds dry out quickly and then become mostly smell-less, although they do become more fragile.

Mystran, nice code example although there are of course a few bits I'm going to whine about (but I'm still going to base my cmos code on this bit):

- It's BCD, not BDC (typo)
- Since you're using an unsigned, it's not actually unix time but a slightly modified version of it. You'd have to rethink your date format in 2100 anyway since the unsigned will overflow in 2108.
- Too bad you don't fully account for leap years, but given the above point I can see why you don't bother.

Thanks for the great code example!
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

I implemented this code, no problems whatsoever.

Now that I have this code I can study documentation and learn why it works.

Wouldn't it be better to use unsigned longs, or even just longs?

How would you make this work for any date since January 1 1970?
User avatar
Candy
Member
Member
Posts: 3882
Joined: Tue Oct 17, 2006 11:33 pm
Location: Eindhoven

Post by Candy »

pcmattman wrote:I implemented this code, no problems whatsoever.

Now that I have this code I can study documentation and learn why it works.

Wouldn't it be better to use unsigned longs, or even just longs?

How would you make this work for any date since January 1 1970?
Theoretical answer: by using an infinitely long integer.

Practical answer: either by using an expanding code of sorts or agreeing on what "any date" really should mean - as in, what limit is good enough?


Mystran: strictly speaking, iirc, unix time said "every 4th year is a leap year" which will work in your code until the unsigned overflows.

Isn't the leap year adjustment with the month calculation the wrong way around?
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

Candy wrote: Isn't the leap year adjustment with the month calculation the wrong way around?
Huh? Don't think so. It's just strange:

We count the number of full days elapsed. Now months and days count from 1, so mounths-1 gives the correct index for the table. The number of days elapsed this month, then, is "days - 1". If it's leap year and at least 3rd month though, we must add one for 1.

So I just combine the two adjustments:

Code: Select all

 .... days - 1 + (leapyeard && month > 2) ? 1 : 0;
=>
 .... days + (leapyeard && month > 2) ? (1 - 1) : (0 - 1)
=>
 .... days + (leapyeard && month > 2) ? 0 : -1
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
Candy
Member
Member
Posts: 3882
Joined: Tue Oct 17, 2006 11:33 pm
Location: Eindhoven

Post by Candy »

Wikipedia wrote: The POSIX committee was swayed by arguments against complexity in the library functions, and firmly defined the Unix time in a simple manner in terms of the elements of UTC time. Unfortunately, this definition was so simple that it didn't even encompass the entire leap year rule of the Gregorian calendar, and would make 2100 a leap year.

The 2001 edition of POSIX.1 rectified the faulty leap year rule in the definition of Unix time, but retained the essential definition of Unix time as an encoding of UTC rather than a linear time scale.
So, yes, it was defined without a leap year but recently changed to match reality.
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

I especially like the rule about leap-seconds, and how they are mostly violated all the time by instead following the NTP rule, which differ by less than second. ;)

edit: I personally expect to not handle leap seconds anytime soon. Instead I plan to eventually let NTP speed-up/slow-down the system clock such that it resynchronizes to correct time after leap seconds. The reason for this is that then I can have continuous time, so every routine that needs to compare times doesn't need to figure out what to do with repeated seconds.

If I later feel like it's a problem, I'm going to change the rule such, that system time updates half (or twice) as fast during the leap second. Userspace NTP-client can do that as well, so it's not a kernel issue. Ofcourse that'll be a bigger problem for anything that depends on relative times, but will still keep times continuous.
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
Post Reply