Jump to content
IGNORED

Real time clock to seconds - assembly


Recommended Posts

A look into cc65s library is often helpful:

 

;

; Piotr Fusik, 04.11.2001

; originally by Ullrich von Bassewitz and Sidney Cadot

;

; clock_t clock (void);

; unsigned _clocks_per_sec (void);

;

 

.export _clock, __clocks_per_sec

.importzp sreg

 

.include "atari.inc"

 

 

.proc _clock

 

ldx #5 ; Synchronize with Antic, so the interrupt won't change RTCLOK

stx WSYNC ; while we're reading it. The synchronization is done same as

@L1: dex ; in SETVBLV function in Atari OS.

bne @L1

stx sreg+1 ; Byte 3 is always zero

lda RTCLOK+2

ldx RTCLOK+1

ldy RTCLOK

sty sreg

rts

 

.endproc

 

 

.proc __clocks_per_sec

 

ldx #$00 ; Clear high byte of return value

lda PAL ; use hw register, PALNTS is only supported on XL/XE ROM

and #$0e

bne @NTSC

lda #50

rts

@NTSC: lda #60

rts

 

.endproc

Link to comment
Share on other sites

I tried doing the divide-by-50 with the floating point ROM routines:

 

mwa #$100 FR0
jsr IFP
jsr FMOVE
mva RTCLOK FR0+1
mva RTCLOK+1 FR0
jsr IFP
jsr FMUL
jsr FMOVE
mva RTCLOK+2 FR0
mva #0 FR0+1
jsr IFP
jsr FADD
mwa #jiffy_fp FLPTR
jsr FST0P
mwa #jiffies_per_second FR0
jsr IFP
jsr FMOVE
mwa #jiffy_fp FLPTR
jsr FLD0P
jsr FDIV
jsr FASC
jsr print

 

Full source and executable:

 

seconds.zip

 

This is quite slow though. It takes nearly a whole frame to do the computation.

 

This thread has fast integer divide routines for many divisors. I don't if they're at all applicable to 24 bit dividends.

 

It would be much more efficient to just have a VBI repeatedly count up to 50 and then increment your seconds counter.

Link to comment
Share on other sites

Two iterations of a 16/8->8 unsigned division routine will give you a 24/8->16 divide. Use the remainder byte of the high division as the high dividend byte of the low division. IIRC it's a few hundred clocks.

 

The VBI does indeed tick a bit slower than 50/60Hz. I debugged an emulation speed problem in ForemXEP that turned out to be due to this issue, which caused the FXEP clock to lose a couple of seconds each day. Probably the best solution for tracking accurate seconds would be to step seconds as Xuel notes, but with a fixed-point counter.

  • Like 1
Link to comment
Share on other sites

The problem with using the FP is that it only handles 16 bit binary to packed BCD conversion. So you'd need to handle the high byte seperately - multiply by 65536 then add back in.

 

Best method might be to do your own - if you want the true accuracy add a leap function that periodically increments the frame count to compensate for the slower than 50/60 Hz actual rate.

 

Or do a routine that handles the required division of 24 bit binary - in all probability it wouldn't be a great deal faster than using the OS FP routines.

Link to comment
Share on other sites

This is slightly off-topic from the original question, but I just wrote a little batari Basic program for the Atari 2600 with some inline assembly to count and display seconds, minutes, and hours based on the number of color clocks per frame and color clocks per second. The inline assembly routine is applicable to the Atari 8-bit computers.

 

I chose color clocks because I don't think floating point numbers are (generally speaking) as accurate as mixed numbers with integer parts-- e.g., 5.33333333 is an imperfect expression of 5 and 1 / 3. And rather than deal with whole frames and a fraction of a frame, I decided it was simpler to just use color clocks per frame and color clocks per second. This assumes (rightly or wrongly) that the oscillator rate shown on the Atari 2600 schematics-- 3579575 Hz-- is an exact integer value, as opposed to having some fractional part that's been rounded or truncated.

 

I use two 3-byte values stored in ROM-- the number of color clocks per frame (59736) and color clocks per second (3579575), both stored in little-endian order. I use three bytes of RAM to count the number of color clocks after each frame, and three more bytes of RAM to store the seconds, minutes, and hours. After a frame is drawn I add the clocks per frame to the counter, then compare the result to the clocks per second. If the counter is equal to or greater than the clocks per second, I subtract the clocks per second from the counter and increment the seconds, adjusting the seconds, minutes, and hours as needed. I haven't run it on a real console yet to see how accurate it is, but it seems accurate enough in an emulator (although it's slightly fast because the emulator draws each frame at 60 Hz rather than at the actual frame rate of circa 59.92 Hz.

 

For PAL the numbers for the Atari 2600 should be 71136 (color clocks per frame) and 3546894 (color clocks per second). I'm not sure what the numbers should be for the Atari 8-bit computers-- most of the references I've seen online say the oscillator for the NTSC model is 3579545 Hz (presumably with a .45 repeating 45 after that), although one post at AtariAge says "the 400 schematic clearly shows 3,579,575 MHz" (sic). I haven't seen the Atari 400 schematics (or 800, etc.), but if Atari used an oscillator rate of 3579575 Hz for the NTSC 2600 then I suppose they might have used the same rate for their NTSC 8-bit computers.

 

Anyway, here's the inline assembly portion of my program with a few comments, in case anyone wants to try it for the 8-bit computers. Adjust the color clock values as you deem necessary:

 

  CLC
  LDA color_clocks ; LSB of the counter
  ADC per_frame ; LSB of the color-clocks-per-frame
  STA color_clocks
  LDA color_clocks+1
  ADC per_frame+1
  STA color_clocks+1
  LDA color_clocks+2 ; MSB of the counter
  ADC per_frame+2
  STA color_clocks+2
  CMP per_second+2 ; MSB of the color-clocks-per-second
  BCS Check_Color_Clocks_1
  JMP .Main_Loop ; not yet equal or greater, so exit-- .Main_Loop was too far away to branch to so I had to reverse the compare and jump instead
Check_Color_Clocks_1
  BNE Reduce_Color_Clocks ; greater, so no need to check the other bytes
  LDA color_clocks+1 ; equal, so need to check the next byte
  CMP per_second+1
  BCS Check_Color_Clocks_2
  JMP .Main_Loop
Check_Color_Clocks_2
  BNE Reduce_Color_Clocks
  LDA color_clocks
  CMP per_second
  BCS Reduce_Color_Clocks
  JMP .Main_Loop
Reduce_Color_Clocks ; subtract the clocks-per-second from the counter
  SEC
  LDA color_clocks
  SBC per_second
  STA color_clocks
  LDA color_clocks+1
  SBC per_second+1
  STA color_clocks+1
  LDA color_clocks+2
  SBC per_second+2
  STA color_clocks+2
  INC seconds ; now increment the seconds
  LDA seconds
  CMP #60
  BCS Increment_Minutes
  JMP .Main_Loop
Increment_Minutes ; adjust for 60 seconds
  LDA #0
  STA seconds
  INC minutes
  LDA minutes
  CMP #60
  BCS Increment_Hours
  JMP .Main_Loop
Increment_Hours ; adjust for 60 minutes
  LDA #0
  STA minutes
  INC hours
  LDA hours
  CMP #24
  BCS Clear_Hours
  JMP .Main_Loop
Clear_Hours ; roll the hours
  LDA #0
  STA hours
  JMP .Main_Loop
per_frame ; color clocks per frame (NTSC)
  HEX 58 E9 00 ; 59736 in little-endian
per_second ; color clocks per second (NTSC)
  HEX B7 9E 36 ; 3579575 in little-endian

 

Obviously that's a lot of work if you don't care about keeping exact time-- much simpler to just count frames!

  • Like 1
Link to comment
Share on other sites

This is slightly off-topic from the original question, but I just wrote a little batari Basic program for the Atari 2600 with some inline assembly to count and display seconds, minutes, and hours based on the number of color clocks per frame and color clocks per second. The inline assembly routine is applicable to the Atari 8-bit computers.

 

I chose color clocks because I don't think floating point numbers are (generally speaking) as accurate as mixed numbers with integer parts-- e.g., 5.33333333 is an imperfect expression of 5 and 1 / 3. And rather than deal with whole frames and a fraction of a frame, I decided it was simpler to just use color clocks per frame and color clocks per second. This assumes (rightly or wrongly) that the oscillator rate shown on the Atari 2600 schematics-- 3579575 Hz-- is an exact integer value, as opposed to having some fractional part that's been rounded or truncated.

 

I use two 3-byte values stored in ROM-- the number of color clocks per frame (59736) and color clocks per second (3579575), both stored in little-endian order. I use three bytes of RAM to count the number of color clocks after each frame, and three more bytes of RAM to store the seconds, minutes, and hours. After a frame is drawn I add the clocks per frame to the counter, then compare the result to the clocks per second. If the counter is equal to or greater than the clocks per second, I subtract the clocks per second from the counter and increment the seconds, adjusting the seconds, minutes, and hours as needed. I haven't run it on a real console yet to see how accurate it is, but it seems accurate enough in an emulator (although it's slightly fast because the emulator draws each frame at 60 Hz rather than at the actual frame rate of circa 59.92 Hz.

.

.

.

Obviously that's a lot of work if you don't care about keeping exact time-- much simpler to just count frames!

 

I hate to be a nay sayer but that's way overkill.

 

You won't split a second to any better than a frame.

You can keep the seconds / frames accurate.

but even an inaccurate 24 bit fraction will be better

than the best crystal clock where they think they're

doing really good if they keep it to one part in 100000.

 

It would be a lot simpler just to accumulate seconds/frame.

32 bits would be off by something like a second / 10 days

 

And a 32 bit (or what ever) fraction could actually be more

accurate than 32 bits. In the case of 59736/3579575

seconds/frame it's more like 35 bits

Edited by bogax
Link to comment
Share on other sites

There's another consideration here, which is that if the clock needs to be maintained across disk reads, the timing source has to be maintained in VBI stage 1 processing to avoid losing ticks. That puts it in the critical path for SIO transfers. In that case I would recommend basing it on RTCLOK as the OP originally started with. From there, the direct approach would be to use fixed point multiplies to pull out seconds, minutes, and hours. Carina BBS optimizes this further by computing the RTCLOK value for the next seconds jump and then waiting for the clock to tick to that value.

  • Like 1
Link to comment
Share on other sites

I hate to be a nay sayer but that's way overkill.

 

Granted.

 

You won't split a second to any better than a frame.

 

I *am* updating only once each frame-- I'm not trying to split it smaller than a frame. I was just counting in color clocks per frame and then comparing it to color clocks per second to try to keep things on an integer basis. But since I don't know whether the 3579575 Hz value is rounded or truncated or whatever, I guess trying to stick with integers is pointless.

 

It would be a lot simpler just to accumulate seconds/frame.

 

How's this instead? The fraction represents 59736 / 3579575 stored as a 32-bit FP number in little-endian order-- or really 40 bits, since it's just the portion after the decimal, with the other 8 bits being the byte of RAM for the seconds.

 

  CLC
  LDA counter ; LSB of a 4-byte counter in RAM, stored little-endian
  ADC fraction ; LSB of the fraction (see below)
  STA counter
  LDA counter+1
  ADC fraction+1
  STA counter+1
  LDA counter+2
  ADC fraction+2
  STA counter+2
  LDA counter+3
  ADC fraction+3
  STA counter+3
  LDA seconds ; the 5th byte
  ADC #0 ; only the carry is added
  STA seconds
  CMP #60
  BCS Increment_Minutes
  JMP .Main_Loop
Increment_Minutes
  LDA #0
  STA seconds
  INC minutes
  LDA minutes
  CMP #60
  BCS Increment_Hours
  JMP .Main_Loop
Increment_Hours
  LDA #0
  STA minutes
  INC hours
  LDA hours
  CMP #24
  BCS Clear_Hours
  JMP .Main_Loop
Clear_Hours
  LDA #0
  STA hours
  JMP .Main_Loop
fraction ; 59736/3579575
  HEX 6D AA 45 04 ; 4/256 + 69/256/256 + 170/256/256/256 + 109/256/256/256/256

 

I don't trust my math right now, but I think this might be accurate for 1200+ years? Assuming the fraction is correct and the frame rate is steady.

Link to comment
Share on other sites

Turns out that 3579575/59736 reduces to 27325/456, so it's shorter and faster just to step the exact fraction directly:

 

   lda    frac
   sec
   sbc    #456-256
   sta    frac
   dec    frac+1
   bcs    no_carry
   dec    frac+1
no_carry:
   bpl    done
   clc
   adc    #<27325
   sta    frac
   lda    frac+1
   adc    #>27325
   sta    frac+1

   lda    #59
   ldy    #0

   inc    seconds
   cmp    seconds
   bcs    done
   sty    seconds

   inc    minutes
   cmp    minutes
   bcs    done
   sty    minutes

   inc    hours
   lda    #23
   cmp    hours
   bcs    done
   sty    hours
done:

 

The equivalent PAL fraction should be 3546894/71136 = 3939/79.

 

Now, the fun part: from what I've been able to tell, the 2600, 400, 800 used a 3.579575MHz crystal, whereas the XL/XE machines used 3.579545MHz. Therefore, if you want to be a stickler for accuracy, the fraction needs to be 13183/220 for XL/XE machines. The difference is about 10^-7, which doesn't sound like much until you realize that it amounts to 0.7 seconds a day. I wonder how much the clocks vary in practice.

  • Like 2
Link to comment
Share on other sites

  • 7 years later...

Good examples but can anyone provide a complete example based on a Gr.0 output the Seconds to the screen or at least into a adress or register in plain mac/65 assembler please. im not exactly sure on how to base this example on the RTCLOK. Would be Nice With a commented listing for us not so Advanced programmers...

 

The last example here seem to be the best to use. but im not sure abou the LDA FRAC.      FRAC=adress 20 or 19 or 18 ?    

Edited by Grevle
Link to comment
Share on other sites

Heres an assembly example, this is has a BASIC part to set the clock time which could be deleted after

the time had been set.

 

While running, the time is displayed on the top line of the screen, the code takes care of the PAL frame rate.

I wrote this way back in the 80's so I hope the comments in the code help.

 

It uses TIMER2 interrupt to count the seconds, so could easily be changed if you use NTSC machine

 

Obviously a lot of the code will be of no use, but hope it helps

 

0100 SETVBV = $E45C  ;SET TIMER ROUTINE
0110 ;EXITVBV = $E462
0120 CDTMA2 = $0228  ;TIMER 2 JUMP ADDRESS
0130 CDTMV2 = $021A  ;TIMER 2
0140 HOURT = $06D0   ;HOURS TENS
0150 HOURL = $06D1   ;HOURS UNITS
0160 TENM =  $06D3   ;MINUTES TENS
0170 MINS =  $06D4   ;MINUTES UNITS
0180 TENS =  $06D6   ;SECONDS TENS
0190 SECS =  $06D7   ;SECONDS UNITS
0200 FLAG =  $00     ;DISPLAY ON/OFF,0=ON 1=OFF
0210     *=  $0600
0220     PLA 
0230     PLA 
0240     STA ADD+2   ;HIGH ADDRESS OF TIME$
0250     PLA 
0260     STA ADD+1   ;LOW ADDRESS OF TIME$
0270     LDX #0
0280 ADD LDA $2400,X ;DUMMY ADDRESS (WILL BE CHANGED LATER)
0290     AND #$DF    ;SUBTRACT HEX 20 FROM ASCII CHARACTERS TO PRODUCE
0300 ;CORRECT DISPLAY
0310     STA HOURT,X ;STORE TIME$ INTO CORRECT LOCATION
0320     INX 
0330     CPX #8
0340     BNE ADD     ;BRANCH IF NOT ALL OF TIME$
0350     LDA #START&255 ;TIMER 2 INTERRUPT VECTOR LOW ADDRESS
0360     STA CDTMA2
0370     LDA #START/256 ;HIGH ADDRESS
0380     STA CDTMA2+1 ;SET TIMER VECTOR
0390     LDY #50     ;SET TIMER 2 TO 1 SECOND DELAY
0391 ;***NOTE*** VBLANK OCCURS 50 TIMES A SECOND HENCE 50 NOT 60
0400     LDX #0      ;NO HIGH VALUE
0410 ;***NOTE*** VBLANK OCCURS 50 TIMES A SECOND HENCE 50 NOT 60
0420     LDA #2      ;TIMER 2
0430     JSR SETVBV  ;START TIMER
0440     LDA #0
0450     STA FLAG    ;SET DISPLAY FLAG TO ON
0460     RTS         ;BACK TO BASIC
0470 START INC SECS  ;TIMER ROUTINE START
0480     LDA SECS
0490     CMP #$1A
0500     BEQ TENSECS
0510     JMP DISPLAY
0520 TENSECS LDA #$10
0530     STA SECS
0540     INC TENS
0550     LDA TENS
0560     CMP #$16
0570     BEQ MINUTES
0580     JMP DISPLAY
0590 MINUTES LDA #$10
0600     STA TENS
0610     INC MINS
0620     LDA MINS
0630     CMP #$1A
0640     BEQ MINSTENS
0650     JMP DISPLAY
0660 MINSTENS LDA #$10
0670     STA MINS
0680     INC TENM
0690     LDA TENM
0700     CMP #$16
0710     BEQ HOURS
0720     JMP DISPLAY
0730 HOURS LDA #$10
0740     STA TENM
0750     INC HOURL
0760     LDA HOURL
0770     CMP #$1A
0780     BEQ HOURH
0790     CMP #$14
0800     BEQ CHECK
0810     JMP DISPLAY
0820 HOURH LDA #$10
0830     STA HOURL
0840     INC HOURT
0850     JMP DISPLAY
0860 RESET LDA #$10
0870     STA HOURT
0880     STA HOURL
0890 DISPLAY LDA $54 ;CHECH IF CURSOR IS ON LINE 0
0900     BNE DISPLAY1 ;NO SO OK TO DISPLAY
0910     STA $11     ;YES SO 'SOFT' PRESS OF BREAK KEY TO MOVE CURSOR DOWN
0920 DISPLAY1 LDA FLAG ;CHECK IF DISPLAY REQUIRED
0930     BNE OUT     ;NO SO GET OUT
0940     LDY #40     ;YES SO CLEAR TOP LINE OF SCREEN
0950     LDA #0
0960 CLR DEY 
0970     STA ($58),Y ;LOW ADDRESS OF SCREEN RAM
0980     BNE CLR
0990     LDY #16     ;PUT DISPLAY 16 PLACES OUT
1000     LDX #0
1010 LOOP LDA HOURT,X ;PUT TIME ON SCREEN
1020     STA ($58),Y
1030     INY 
1040     INX 
1050     CPX #8
1060     BNE LOOP
1070 OUT LDA #50     ;ALL THIS OCCURED BECAUSE OF TIMER 2 REACHING 0
1071 ;SO SET TIMER 2 BACK TO 50 AGAIN
1080     STA CDTMV2
1090     RTS 
1100 CHECK LDA HOURT ;CHECK TO SEE IF 24:00:00,IF SO SET TIME TO 00:00:00
1110     CMP #$12
1120     BEQ RESET
1130     JMP DISPLAY

 

 

This is the BASIC Program

 

100 REM ******************************
110 REM *    TIME DISPLAY PROGRAM    *
120 REM *            BY              *
130 REM *        T.Bailey.           *
140 REM ******************************
150 REM ***NOTE TO TURN OFF THE TIME DISPLAY,POKE 0,1
160 REM THE TIME DISPLAY IS A DIGITAL 24 HOUR DISPLAY ON LINE 0
170 REM TO TURN THE DISPLAY BACK ON POKE 0,0
180 REM AFTER THIS PROGRAM HAS BEEN RUN,YOU MAY TYPE NEW TO CLEAR IT FROM
190 REM MEMORY.
200 REM IF SYSTEM RESET IS PRESSED,THE TIME WILL HAVE TO BE RESET AS
210 REM FOLLOWS:-
220 REM TYPE DIM A$(8):A$="XX:XX:XX":A=USR(1536,ADR(A$))
230 REM XX:XX:XX = THE TIME BETWEEN 00:00:00 AND 24:00:00
240 Q=1536:W=0:RESTORE 
250 ? "}":POSITION 10,5:? "ONE MOMENT PLEASE":REM <ESC-CTRL-CLR>
260 READ A:IF A=-1 THEN 280
270 POKE Q+W,A:W=W+1:GOTO 260
280 ? "}":REM <ESC-CTLR-CLEAR>
290 DIM TIME$(8)
300 ? "INPUT THE TIME AS:-";CHR$(34);"XX:XX:XX";CHR$(34)
310 ? : ? "WHERE XX=HOURS,MINS,SECS"
320 INPUT TIME$
330 A=USR(1536,ADR(TIME$))
340 END 
350 DATA 104,104,141,13,6,104,141,12
360 DATA 6,162,0,189,0,36,41,223
370 DATA 157,208,6,232,224,8,208,243
380 DATA 169,48,141,40,2,169,6,141
390 DATA 41,2,160,50,162,0,169,2
400 DATA 32,92,228,169,0,133,0,96
410 DATA 238,215,6,173,215,6,201,26
420 DATA 240,3,76,156,6,169,16,141
430 DATA 215,6,238,214,6,173,214,6
440 DATA 201,22,240,3,76,156,6,169
450 DATA 16,141,214,6,238,212,6,173
460 DATA 212,6,201,26,240,3,76,156
470 DATA 6,169,16,141,212,6,238,211
480 DATA 6,173,211,6,201,22,240,3
490 DATA 76,156,6,169,16,141,211,6
500 DATA 238,209,6,173,209,6,201,26
510 DATA 240,7,201,20,240,62,76,156
520 DATA 6,169,16,141,209,6,238,208
530 DATA 6,76,156,6,169,16,141,208
540 DATA 6,141,209,6,165,84,208,2
550 DATA 133,17,165,0,208,24,160,40
560 DATA 169,0,136,145,88,208,251,160
570 DATA 16,162,0,189,208,6,145,88
580 DATA 200,232,224,8,208,245,169,50
590 DATA 141,26,2,96,173,208,6,201
600 DATA 18,240,201,76,156,6,-1
 

Edited by TGB1718
Add BASIC Part
  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...