Thursday, 20 February 2020

Making the Real-Time Clock Tick

The Real-Time Clock (RTC) chip that Trenz chose for the MEGA65 main board is different to the one we have on the MEGAphone.  While the one on the MEGAphone proved easy to set, the one in the MEGA65 is resisting my efforts to be able to set the time.

The datasheet explains that you have to set the WRTC (Write Real-Time Clock) bit, before the time can be set.  The only problem is, is that this doesn't actually work. My best guess, is that the RTC registers have to be all set in a single write, for the write to take effect, whereas my SPI controller writes only one byte per transaction.

Although, it might not be required after all.  This Linux driver for the RTC chip writes one byte at a time, and seems to indicate that writing to any of the RTC clock registers should start it running.  However, nothing I have tried actually works to trigger this to occur.

Reading through the data sheet again, I was reminded that the RTC chip is very picky about writing to the clock registers:  It requires that an I2C STOP signal occurs immediately after writing to the clock registers, so that partial writes will be ignored to help protect the integrity of the clock.

This led me through quite an adventure of tracking down and fixing a bunch of I2C management errors in mega65_i2c.vhdl, which collectively caused that problem, along with having the potential to cause some other I2C glitches.   What it boiled down to, was that I was not allowing the I2C bus to go idle between the loop that reads all register values for displaying in the memory map, and when it writes a new value when requested by the CPU -- and vice versa.

With that fixed, I was finally seeing the STOP condition, which consists of the SDA line going high, while SCL stays high, as indicated by the vertical red-ish line in this simulation trace:


With that fixed and synthesised, I was then finally able to write to the RTC clock, and set it ticking (it doesn't start ticking until it has been initially set).

To make it easier for people to use, I have added getrtc() and setrtc() functions to the MEGA65 libc that I am writing. I have also added some initial documentation of the registers to the MEGA65 Book.  I also updated the i2cstatus test program to show the current RTC (and other target specific information).  It also allows editing of the RTC value:






 Thanks to the libc functions that I wrote, the code to read and display the current time and date is quite simple:

void show_rtc(void)
{
    getrtc(&tm);
    

    printf("Real-time clock: %02d:%02d.%02d",
           tm.tm_hour,tm.tm_min,tm.tm_sec);
    printf("\n");

    printf("Date:            %02d-",tm.tm_mday+1);
    switch(tm.tm_mon) {
    case 1: printf("jan"); break;
    case 2: printf("feb"); break;
    case 3: printf("mar"); break;
    case 4: printf("apr"); break;
    case 5: printf("may"); break;
    case 6: printf("jun"); break;
    case 7: printf("jul"); break;
    case 8: printf("aug"); break;
    case 9: printf("sep"); break;
    case 10: printf("oct"); break;
    case 11: printf("nov"); break;
    case 12: printf("dec"); break;
    default: printf("invalid month"); break;
    }
    printf("-%04d\n",tm.tm_year+1900);

}


The main trick there, is that we will need to use a MEGA65 Enhanced DMA operation to fetch the RTC registers, because the RTC registers sit above the 1MB barrier, which is the limit of the C65's normal DMA operations.  The easiest way to do this is to construct a little DMA list in memory somewhere, and make an assembly language routine that uses it.  Something like this (using BASIC 10 in C65 mode):

10 RESTORE 110:FORI=0TO43:READA$:POKE1024+I,DEC(A$):NEXT:BANK 128:SYS1042
20 S=PEEK(1024):M=PEEK(1025):H=PEEK(1026)
30 D=PEEK(1027):MM=PEEK(1028):Y=PEEK(1029)+DEC("2000")
40 IF H AND 128 GOTO 80
50 PRINT "THE TIME IS ";RIGHT$(HEX$(H AND 63),1);":";RIGHT$(HEX$(M),2);".";RIGHT$(HEX$(S),2)
60 IF H AND 32 THEN PRINT "PM": ELSE PRINT "AM"
70 GOTO 90
80 PRINT "THE TIME IS ";RIGHT$(HEX$(H AND 63),1);":";RIGHT$(HEX$(M),2);".";RIGHT$(HEX$(S),2)
90 PRINT "THE DATE IS";RIGHT$(HEX$(D),2);".";RIGHT$(HEX$(MM),2);".";HEX$(Y)
100 END
100 DATA 0B,80,FF,81,00,00,00,08,00,10,71,0D,20,04,00,00,00,00
110 DATA A9,47,8D,2F,D0,A9,53,8D,2F,D0,A9,00,8D,02,D7,A9
120 DATA 04,8D,01,D7,A9,00,8D,05,D7,60

This program works by setting up a DMA list in memory at $0400 (unused normally on the C65), followed by a routine at $1012 ( = 1,042 in decimal) which ensures we have MEGA65 registers unhidden, and then sets the DMA controller registers appropriately to trigger the DMA job, and then returns.  The rest of the BASIC code PEEKs out the RTC registers that the DMA job copied to $0400 -- $0407, and interprets them appropriately to print the time.
The curious can use the MONITOR command, and then D1012 to see the routine.

If you want a running clock, you could replace line 100 with GOTO 10.  Doing that, you will get a result something like the following:



If you first POKE0,65 to set the CPU to full speed, the whole program can run many times per second. There is an occasional glitch, if the RTC registers are read while being updated by the machine, so we really should de-bounce the values by reading the time a couple of times in succession, and if the values aren't the same both times, then repeat the process until they are. This is left as an exercise for the reader.

Finally, I updated the auto-documentation in the VHDL source, so that the new registers will be automatically documented in the MEGA65 Book, which is already getting close to 500 pages.  I'm expecting that the MEGA65 Book will be close to 1,000 pages when complete -- not that this should be scary, but rather reflect the depth of documentation that we want to provide potential users and developers of the machine.

2 comments:

  1. Hi Paul,

    one thing got me totally puzzled: You have C sample code above, with an (apparently) working printf statement.

    How is this done? Is there a Mega65 runtime for CC65 somewhere? If yes, I'd be delighted to port and expand all my 8 bit projects to the Mega65 (I imagine DragonRock would feel much more at home on the Mega65...)

    Kind regards,
    Stephan

    ReplyDelete
    Replies
    1. Hello,
      for now I am just using the C64-mode runtime of CC65, while slowly building up our own library of functions (see github.com/MEGA65/mega65-libc) -- so it is just the C64's printf() for now. But that's not a big problem, because you can use all MEGA65 features from C64 mode if you want -- you just have to unlock them (there is a section in the MEGA65 Book on how to do this). But of course we would LOVE to see DragonRock ported to the MEGA65. I'd be happy to help provide any technical info you need. If you wanted to get really excited, you could even use the anti-aliased proportional text rendering code we have written for the MEGA65 under CC65, that I used in the slide presentation software for the MEGA65.

      Paul.

      Delete