itch.io is community of indie game creators and players

Devlogs

VIC-20 Part 5 - Moving a text: How to wait?

Escape From Rexal (VIC-20)
A downloadable game

After reaching the milestone of being able to render the text I started thinking "big" about moving the text up & down instead of just statically displaying it on the top of the screen.

Concept

Here is  the list of tasks that needs to be handled:

  1. The app must work with time to know when to move up and when down.
  2. The app needs a technique to move a text one line down or up.
  3. It is necessary to turn off /IRQ  (called maskable) interrupts.

Using pseudocode it looks like this:

disable_irq();
while (1) 
{     
     wait_some_time();     
     move_text_up_or_down(); 
};

Not surprisingly, this comes with three problems:

  1. How to disable interrupts?
  2. How to wait?
  3. How to move the text?

Let's focus in this Devlog only on the second question only How to wait?  The other two I will address in the following logs.

How to wait?

  1. Considering the 6502 CPU runs on the PAL version of the VIC-20 with a speed of 1.108.405 Hz (i.e. slightly over 1 Mhz), it means it executes 1.108.405 cycles per second. 
  2. It is way too fast for moving a text up and down. We need to slow it a little bit, i.e. the app needs to wait before the position of the text is changed. The "waiting" is achieved by doing "nothing", which in our case means executing nop instructions. 
  3. The nop mnemonic stands for No Operation. For those who do not know the nop instruction - it does nothing for 2 cycles, i.e. it just burns the processor time, which makes it perfect for use in delay loops. The nop instruction is one of the few that is the same across many different CPU architectures - be it the 6502, Z80, x86, Motorola 6800, AVR, and others - take a look at what Wikipedia says about it.

How I used the nop instruction

  1. I decided to create a function that will wait 50 ms. To achieve that, I need to waste 55.420 cycles  (1.108.405 cycles * 0.05), which means the nop has to be called approximately 27.710 times, which is 55.420 divided by 2 as executing nop instruction takes 2 cycles.
  2. In addition to nop there need to be used other instructions to control the loops, which also consume processor cycles.
  3. I decided to have an inner loop with 10 nop instructions called 256 times and an outer loop that will be called 8 times. 
  4. Computation on paper shows that the inner loop consumes 6.911 cycles:
    1. nop : 2 cycles x 10 x 256
    2. dey : 2 cycles x 256
    3. cpy : 2 cycles x 256
    4. bne : 3 cycles x 255 + 2 cycles
    5. Total: 24 x 256 + 3 x 256 + 2 = 6.911 cycles
  5. It is all repeated 8 times, so together with all servicing instructions, it consumes 55.391 cycles, which is approximately what I need.

Here is the routine delay_50ms:

; =========================================================
;
; Around 50 ms break (55.391 cycles) on PAL
;
; =========================================================
delay_50ms:
        AXYpreserve delay_50ms
        ldx #$08                ; Init outer loop counter to 8
.loop_outer:
        ldy #$FF                ; Init inner loop counter to 255, but cycle 256x
.loop_inner:
        nop                     ; 2 cycles x 10 x 256
        nop 
        nop 
        nop
        nop
        nop        
        nop
        nop 
        nop 
        nop       
        dey                     ; Decrease inner loop count
        cpy #$FF                
        bne .loop_inner         
        dex                     ; Decrease outer loop count
        cpx #$00                
        bne .loop_outer         ; Loop 8 times
        AXYrestore delay_50ms
        rts

And here is the proof from the Monitor that it takes 55.391 cycles:

The Monitor shows the number of cycles the execution of routine "delay_50ms" takes.

And finally, a video that shows the moving text:

Leave a comment