pixelperfect Posted August 19, 2016 Share Posted August 19, 2016 (edited) I'm trying to figure out how to track elapsed time (in seconds preferably). For example, in MonoGame when Update is called every frame by the engine you get a float elapsedTime that gives you the seconds since the last Update. Using that you can easily track elapsed time for different things (say wait 1 sec before spawning something, or a timer endlessly counting upwards). How can I do this in C on Lynx? From what I understand the VBL needs to be used? Edited August 20, 2016 by pixelperfect Quote Link to comment Share on other sites More sharing options...
+karri Posted August 20, 2016 Share Posted August 20, 2016 Actually you cannot track the time from C. But you can do it very easily by creating an interruptor. The idea behind an interruptor is that you can add any number of routines that all will be called whenever the Lynx receives an interrupt. Every interruptor should return with Carry flag cleared if the interrupt was not handled for good. All the interrupts in Lynx tend to leave the carry cleared. This means that you can write on interruptor that counts real time by checking for VBL interrupts. There is also sound interrupts and ComLynx interrupts. Here is my time counter from Shaken, not stirredhttps://bitbucket.org/karri/shaken/src Look at resident/gametime.s It will count time based on interrupts 75 times/second. Quote Link to comment Share on other sites More sharing options...
pixelperfect Posted August 20, 2016 Author Share Posted August 20, 2016 (edited) Thanks! Of course now I realize there was a section in the beginner's guide for Interrupts I don't know assembly, but I'm trying to figure out what this is doing here. Would you mind explaining line by line? inc _interruptOccurred lda _playtime ina sta _playtime cmp #75 bne @goon stz _playtime inc _playtime+1 bne @goon inc _playtime+2 I'm assuming you're increasing interruptOccured every time one occurs, then only increasing playTime every 75 times (per second)? I'm not sure what some of those lines are doing exactly though.. Edited August 20, 2016 by pixelperfect Quote Link to comment Share on other sites More sharing options...
Shawn Jefferson Posted August 21, 2016 Share Posted August 21, 2016 I looks like he is just counting up to 75 in _playtime, and when that reaches 75, he increments _playtime+1, and when that rolls over to zero, he increments _playtime+2. That's a pretty common method of time keeping, used in the Atari 8-bit computer OS too. You then just have to grab the value of _playtime in your mainline code, and then grab it again, subtract the values and do some division to get elapsed time. It's easier if the VBI is running at 60hz. Quote Link to comment Share on other sites More sharing options...
+karri Posted August 22, 2016 Share Posted August 22, 2016 (edited) Yes. Counting to 75 was exactly what I was doing. .export _interruptOccurred ; Export a byte called interruptOccurred for C-code .interruptor _gametime ; Tell the linker to call _gametime on every interrupt .export _playtime ; Export the address for 3 bytes where the time is stored .export _paused ; Export a byte "paused" to tell if the game is paused .export _getscore ; int __fastcall__ getscore() returns number of seconds 16-bit .export _getspeedtime ; int __fastcall__ getspeedtime() returns number of interrupts 16-bit .export _zerotime ; void zerotime() sets the seconds to zero .segment "DATA" ; The stuff declared here will get initialized at startup TIMER2_INTERRUPT = $04 ; Timer 2 is wired to VBL (Vertical blank) VBL_INTERRUPT = TIMER2_INTERRUPT INTSET = $FD81 ; An address for dealing with interrupt hardware _interruptOccurred: .byte $00 _playtime: .byte $00 .byte $00 .byte $00 _speedtime: .byte $00 .byte $00 _paused: .byte $00 .segment "CODE" ; Declare the stuff read-only executable things ; Like getscore() but tells you how many interrupts have passed (for adjusting speed of the game) .proc _getspeedtime: near lda _speedtime ldx _speedtime+1 rts .endproc ; --------------------------------------------------------------- ; void __near__ getscore (void) ; Read two bytes into registers A and X and return the value to the caller ; The value tells how many seconds has passed since the start 0-65535 ; --------------------------------------------------------------- .proc _getscore: near lda _playtime+1 ldx _playtime+2 rts .endproc ; Set the seconds to zero .proc _zerotime: near lda #0 sta _playtime+1 sta _playtime+2 rts .endproc ; _gametime is automaticalle called during every interrupt .proc _gametime: near lda INTSET ; Who interrupted? and #VBL_INTERRUPT ; was it VBL? beq @goon2 ; if no, jump to goon2 lda _paused ; is the game paused? bne @goon2 ; if it was paused jump to goon2 inc _interruptOccurred ; increment interruptOccurred lda _playtime ; increment 75 counter ina sta _playtime cmp #75 ; was it 75? bne @goon ; if not jump to goon stz _playtime ; zero 75 counter inc _playtime+1 ; increment seconds bne @goon ; if seconds did not wrap (255->0) jump to goon inc _playtime+2 ; increment high byte of seconds @goon: inc _speedtime ; increment the speedtime counter bne @goon2 ; if speedtime did not wrap over jomp to goon2 inc _speedtime+1 ; increment high byte of speedtime @goon2: clc ; mark the interrupt "not handled" rts .endproc The code itself is poorly written. I wrote it during a 7DRL competition so I just tried to get everything together - did not care about efficiency or saving bytes. Nobody uses "interruptOccurred" for anything. It could be deleted. I used speedtime for timing events in the game. The playtime was needed to count down a bomb. You had 10 minutes time to save the world before the big badaboom in this game. The name "getscore()" is misleading. It should really be called "getseconds()". It is funny that I have declared "paused" but there is no code for setting it Of course I may have written some C-code elsewhere to set the paused byte. Talk about bad programming. Edited August 22, 2016 by karri 2 Quote Link to comment Share on other sites More sharing options...
pixelperfect Posted August 22, 2016 Author Share Posted August 22, 2016 Oh man, legendary! Thanks a bunch, this really helps me. I will study it and try to implement it myself. Quote Link to comment Share on other sites More sharing options...
+karri Posted September 9, 2016 Share Posted September 9, 2016 (edited) Hi, I feel a little silly now. Greg has implemented time functions to the Lynx. #include <time.h> typedef unsigned long int clock_t; clock_t _clk_tck(void); #define CLOCKS_PER_SEC _clk_tck() clock_t clock(void); So forget everything I wrote in this thread. The clock() works with all screen refresh rates (50, 60, 75) I mean you get number of ticks (VBL interrupts). To get seconds you need to divide the clock()/CLOCKS_PER_SEC Edited September 9, 2016 by karri Quote Link to comment Share on other sites More sharing options...
pixelperfect Posted September 9, 2016 Author Share Posted September 9, 2016 (edited) Nice! I tried including those lines but I'm getting the error: Error 1 error : Multiple definition for `clock_t' Maybe my version of CC65 is different, haha.. Edited September 10, 2016 by pixelperfect Quote Link to comment Share on other sites More sharing options...
+karri Posted September 10, 2016 Share Posted September 10, 2016 I just realized that the interrupt handler for clock() does not clear the carry flag. It means that using clock() kills the display. Perhaps it is time to start fixing bugs again... Quote Link to comment Share on other sites More sharing options...
pixelperfect Posted September 10, 2016 Author Share Posted September 10, 2016 So where do you fix the bug, your repo or the official? Quote Link to comment Share on other sites More sharing options...
+karri Posted September 11, 2016 Share Posted September 11, 2016 I fix all the bugs in the atarilynx repo at bitbucket. There is actually quite a lot of these fixes there already. The good thing is that all Lynx related fixes are in libsrc/lynx - for Lynx dependent routines src/sp65 - for Lynx specific sprite packer. I believe that shaped sprites are only available here - not in the latest cc65.org repo include/ - we may have different content for lynx.h, _suzy.h and _mikey.h The problem with this bug is that the original author relied on a segment called LOWCODE that does not exist in the Lynx cart builds. The clock() routine is written to work even if the screen is not in use. So I may just delete the extra stuff and let the code go into the basic CODE segment. On the other hand... Perhaps I need to educate me on what a LOWCODE segment is. Why was it created? Could anyone shed some light here. Shawn? Greg? Quote Link to comment Share on other sites More sharing options...
+karri Posted September 11, 2016 Share Posted September 11, 2016 I fixed the clock stuff and a working version is in the repository. Here is a test program that works with it. #include <lynx.h> #include <tgi.h> #include <6502.h> #include <time.h> #include <stdlib.h> #include <string.h> void main(void) { tgi_install(&lynx_160_102_16); tgi_init(); CLI(); while (1) { clock_t now; clock_t hours, minutes, seconds; char buf[32]; while (tgi_busy()) ; tgi_clear(); tgi_setcolor(COLOR_GREEN); now = clock(); hours = now / (3600 * 75); now -= hours * 3600 * 75; minutes = now / (60 * 75); now -= minutes * 60 * 75; seconds = now / 75; now -= seconds * 75; memset(buf, 0, sizeof(buf)); ultoa(hours, strchr(buf, 0), 10); buf[strlen(buf)] = ':'; ultoa(minutes, strchr(buf, 0), 10); buf[strlen(buf)] = ':'; ultoa(seconds, strchr(buf, 0), 10); buf[strlen(buf)] = ':'; ultoa(now, strchr(buf, 0), 10); tgi_outtextxy(0, 0, buf); tgi_updatedisplay(); } } In order to compile it type: cl65 -t lynx main.c -o game.lnx Quote Link to comment Share on other sites More sharing options...
Turbo Laser Lynx Posted September 11, 2016 Share Posted September 11, 2016 Nice stuff! This can be useful in many games. I'm going to try this out too later today when I have the time. Quote Link to comment Share on other sites More sharing options...
+karri Posted September 11, 2016 Share Posted September 11, 2016 You also need to pull the latest version from bitbucket. cd lynx git pull or clone it to an empty machine git clone https://bitbucket.org/atarilynx/lynx.git If you have an old version installed, remove it sudo dpkg -r cc65 Then build and install it cd lynx/tools sudo make -f Makefile.deb I have not used Windows since XP times. So I am a bit lost in how you would create the Windows binaries. Quote Link to comment Share on other sites More sharing options...
+karri Posted September 11, 2016 Share Posted September 11, 2016 (edited) Of course I should have used the function _clk_tck() to return the VBL refresh rate 75, 60 or 50 instead of using 75 as a constant. The clock also starts from 0 as seen in the output of the code above. This screenshot reminds me of changes in the cc65.org version. They decided that printing a character at 0,0 puts the lower left pixel at 0,0. In the Lynx the hot spot of the character sprite is upper left. So essentially all old software breaks when you compile it with the cc65.org compiler. Edited September 11, 2016 by karri Quote Link to comment Share on other sites More sharing options...
Turbo Laser Lynx Posted September 11, 2016 Share Posted September 11, 2016 (edited) I wonder if updating would break my demo, hmm, is there a way to check which version of cc65 I have on linux? (It's the retropie image). I think I'm gonna get a new sd card next week just to re-learn/remember how to install cc65 on linux/raspi, because I would need that to be able to write some beginners Lynx guide anyway, and maybe I could even update the readme in the Lynx bit-bucket. I'm just afraid it will be a waste of one free space if I join because I barely know what a repository is, let alone updating some assembler code in cc65 :/ Edited September 11, 2016 by Turbo Laser Lynx Quote Link to comment Share on other sites More sharing options...
+karri Posted September 11, 2016 Share Posted September 11, 2016 (edited) You have the same version. It is not really a version. It is the trunk. Changes every now and then but mainly bugfixes. I just found out that the restriction of team members applies only on private repositories. The lynx repository is public so we can have any number of contributors for free. Edited September 11, 2016 by karri Quote Link to comment Share on other sites More sharing options...
Turbo Laser Lynx Posted September 11, 2016 Share Posted September 11, 2016 Ok, nice, I'm gonna go on and test it tonight then. Hey that's swell, I'll join there soon then! Quote Link to comment Share on other sites More sharing options...
pixelperfect Posted September 11, 2016 Author Share Posted September 11, 2016 Sounds great, now I just have to figure out how to compile in Windows I was reading CC65 site, but weird thing is I don't see the sln they mention.. https://github.com/cc65/wiki/wiki/Microsoft-Windows Quote Link to comment Share on other sites More sharing options...
Shawn Jefferson Posted September 24, 2016 Share Posted September 24, 2016 (edited) The problem with this bug is that the original author relied on a segment called LOWCODE that does not exist in the Lynx cart builds. On the other hand... Perhaps I need to educate me on what a LOWCODE segment is. Why was it created? Could anyone shed some light here. Shawn? Greg? Hi Karri, LOWCODE is used on quite a few targets that have banking windows, like the Atari 8-bit. It just ensures that code that needs to be "low" in memory, is in fact in low memory. The Atari 8-bit has standard banking windows at $4000, $9000 and $A000. I believe that IRQ code in the library would be in LOWCODE so hopefully it doesn't get banked out during an interrupt. On the Lynx it isn't really required since there's no concept of memory banks, but this became standard in cc65 since quite a few of the targets have the same issue. I'm not entirely sure when that happened. You could just change that to "CODE" on the lynx if that's an issue. Edited September 24, 2016 by Shawn Jefferson Quote Link to comment Share on other sites More sharing options...
LordKraken Posted September 26, 2016 Share Posted September 26, 2016 Correct me if I'm wrong (and I'm probably...) but if we rely on the VBL interrupt for a timer, isn't the timer dependant on the game "charge", i.e. the more stuff the game displays, the less VBL interrupts occur? Quote Link to comment Share on other sites More sharing options...
+karri Posted September 26, 2016 Share Posted September 26, 2016 No. The interrupts come at a steady rate. Drawing the buffer may take lots of time. Perhaps 10 interrupts. This means that if the VBL interrupts 75 times per second. But the game update rate would only be 7.5 frames per second. The line tgi_busy() tells the CPU that we are still waiting for the end of the previous tgi_updatedisplay(). The updates will only occur at the VBL interrupt to reduce flicker. while (1); if (!tgi_busy()) { tgi_setcolor(COLOR_WHITE); tgi_bar(0, 0, 159, 101); // Make screen white tgi_setcolor(rand() & 0x0f); // Choose a random colour tgi_bar(10, 10, 149, 91); // Draw box with random colour tgi_updatedisplay(); // Swap display buffer with drawing buffer } } Quote Link to comment Share on other sites More sharing options...
LordKraken Posted March 10, 2017 Share Posted March 10, 2017 Hi Karri, I'm bumping this topic because I'm trying to program some kind of timer in... the old "newcc65" (yeah ok, right, I'm stubborn, I know ^^). I'm using the exact same method that the one discussed in this topic (increasing a variable in the VBL interrupt). It works (no rocket science there) but it's a bit shaky since I have to assume that the interrupt frequency is 60hz. So here are my questions: 1) how can the interrupt have different frequencies... Is it related to some hardware difference between lynx I and II? In Handy it seems to be 60hz. 2) I know that clk_tck() solves the problem in cc65 but for obvious reason I can't use it. I found the following asm code in clock.s but is there a way to make that work with newcc65? __clk_tck: ldx #$00 ldy PBKUP lda #<75 cpy #$20 + 1 blt @ok lda #<60 cpy #$29 + 1 blt @ok lda #<50 @ok: stz sreg stz sreg+1 rts Thanks a lot! Quote Link to comment Share on other sites More sharing options...
+karri Posted March 10, 2017 Share Posted March 10, 2017 (edited) Actually the newcc65 was really amazing compiler as you could code your interrupts in C. /* Using drawPending and swapping buffers in the interrupt creates a flicker-free display. Idea borrowed from Tom Schenks. */ char drawPending; VBL() interrupt { if (drawPending) { SwapBuffers(); drawPending = 0; } update_music(); } int main() { uchar ret; InitIRQ(); /* To save the cart connector during developement The next line allows you to upload the next version without swapping carts. */ InstallUploader(_9600Bd); CLI; SetBuffers(SCREEN1, SCREEN2, 0); _SetRGB(pal); InstallIRQ(2,VBL); EnableIRQ(2); silence(); ret = 1; abcmusic(0,abcmusic0); abcmusic(1,abcmusic1); abcmusic(2,abcmusic2); abcmusic(3,abcmusic3); while (ret) { if (!drawPending) { /* DrawSprite(Sbackgrnd); */ drawPending = 1; } } /* Jump to cart loader */ } So you could just add you time counter inside the VBL interrupt and look at it updates by itself. No need to dig into assembly language if you don't want to do it. I add a small template with double buffered display using VBL interrupts and abcmusic. The compiler is a resource hog. Keep to small games Edit: also note that the interrupt is run at whatever the VBL refresh rate happens to be. Probably 60Hz. But there is no mutex locking or anything else. So if you change a variable in the VBL and also at your main program there is no guarantee which one wins. Parallell programming with no protection whatsoever. abcmini.zip Edited March 10, 2017 by karri Quote Link to comment Share on other sites More sharing options...
LordKraken Posted March 10, 2017 Share Posted March 10, 2017 Thanks, this is exactly what I'm doing actually (I was a bit confused since you wrote earlier in this discussion that it was not possible in C... but it is ). After a couple of test, the VBL frequency happens to be 60hz, and if I understand you correctly, it will always be 60hz, right? I guess the call to clk_tck to get the frequency in cc65 is because of the support of multiple systems? (just trying to close this door and make space for the next problem ahah). Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.