Hello VERA (BASIC vs C vs Assembly)

This short tutorial is a very simple comparison of three main languages used to develop software on Commander X16. Instead of printing standard Hello World message, let’s do something more interesting and specific to the platform but still try to keep it as simple as possible. There is nothing more unique to Commander X16 than using VERA and let's throw in reading a mouse .

I will not go into details on how to install development tools and what editor to use etc. but will focus on coding to highlight differences between approaches to make it easier to choose or transition between languages regardless of what your background is.

The only background required is a basic understanding of VERA (Versatile Embedded Retro Adapter). If you need a quick overview, I wrote a post here.


Goal

We will write simple drawing programs in default text mode. We will use the mouse for drawing and pressing the left mouse button will draw by filling the character over which we are hovering and the right button will erase it. That is it. It should be simple right?

The steps or algorithm that we will follow are as follows:

  1. Setup
  2. Turn on the mouse
  3. Read the mouse status
  4. Calculate the position of mouse in Video memory (VRAM)
  5. If left button is pressed draw the block
  6. If right button is pressed erase the block

Video of all three versions in action:


Setup

First step is to write some boilerplate preparation code both for C and Assembly programs, no need for any preparation for BASIC programs.

BASIC C Assembly
#include <cx16.h>
#include <mouse.h>

void main(void) {
    struct mouse_info info;
    static unsigned int m;

    mouse_load_driver(&mouse_def_callbacks, mouse_stddrv);
    mouse_install(&mouse_def_callbacks, mouse_static_stddrv);

}
.org $080D
.segment "STARTUP"
.segment "INIT"
.segment "ONCE"
.segment "CODE"

; I/O Registers
VERA_LOW     = $9F20
VERA_MID     = $9F21
VERA_HIGH    = $9F22
VERA_DATA0   = $9F23
VERA_CTRL    = $9F25
; Mouse Kernal routines
MOUSE_CONF   = $FF68
MOUSE_GET    = $FF6B
No preparation needed for BASIC programs. Because mice were not something that was standard on computers in the 8 bit era the mouse implementation is in separate driver that needs to be linked and declarations included as separate header file. In typical assembly program we have to define at what memory address it will start, that is what the .org directive is for. It is pointing to the beginning of the BASIC memory. We will not go into detail what each of the .segment directives mean but the result is that compiled program generates one SYS line in BASIC followed by the machine code. Therefore we can just run it using BASIC command RUN.
Second section is for readability only, we could just use actual addresses when storing values to VERA registers or calling Kernal routines but it is good practice to make Assembly code more readable.


Turn the mouse on

OK, we have everything ready and we can turn the mouse on. If we did everything correct during setup that should be fairly simple step.

BASIC C Assembly
10 MOUSE 1 mouse_show(); lda #1
ldx #0
jsr MOUSE_CONF
MOUSE command is used to turn on or off the mouse pointer.
0 - Hide mouse
1 - Display default shape
255 - display but dont define the shape (custom mouse pointer)
A register contains setting defining the visibility and shape of mouse pointer, values a re the same as in BASIC.
Register X defines the scale (size) of the pointer.


Read the mouse

Interestingly, actual reading the mouse position and state of the buttons is the simplest of all operations in this short exercise. Let's take a look:

BASIC C Assembly
20 TX=MX:TY=MY:TB=MB mouse_info(&info) ldx #2
jsr MOUSE_GET
Commander X16 uses special variables that contain mouse values:
MX - X position (from the left edge) in pixels
MY - Y position (from the top edge) in pixels
MB - Button state 0-no buttons, 1-left, 2-right, 4-middle
In C the function mouse_info popluates struct containing position and button values. These structs are defined in mouse.h header file. Kernal function MOUSE_GET requires one parameter in X register.
It is pointing to Zero Page location where upon return from the function will contain four bytes:
- Low and Hi byte for X position in pixels
- Low and Hi byte for Y position in pixels
Register A fill contain state of the buttons.

Calculate

In order to draw at the position of the mouse on screen we have to calculate it in relation to the memory map. One thing to consider is the fact that mouse is essentially a sprite that moves on the screen in pixel precision. That in practice means that on default screen it can have X position value in range 0 - 639 and Y position in range 0-479 because default screen has resolution of 80 x 60 characters or 640 x 480 pixels.

The formula to calculate memory position of the character the mouse is pointing to is therefore:

Memory = Y/8 * 256 + X/8 * 2

Obviously we have to divide both X and Y with 8 because each character is 8x8 pixels in size.

Y has to be them multiplied by 256 because each line of text takes 256 bytes in memory.

X has to be multiplied by 2 because each character is followed by attribute byte defining it's color.

We also have to be aware that X and Y are 2 byte or 16 bit values and very importantly that BASIC does all calculations internally in floating point so we have to make sure to cut away any fractions when dividing X and Y positions using INT() function.

BASIC C Assembly
40 M=INT(TY/8)*256+INT(TX/8)*2 m = ((info.pos.y & 0xff8) << 5 ) | (( info.pos.x & 0xff8 ) >> 2); ; Divide X by 4 and use as LOW BYTE
lsr 3
ror 2

lsr 3
ror 2

lda 2
and #$FE
sta VERA_LOW

; Divide Y by 8 and use as MID BYTE
lsr 5
ror 4

lsr 5
ror 4

lsr 5
ror 4

lda 4
sta VERA_MID
The formula used in BASIC is essentially the same as we defined in the description above.
We do have to use INT() function to get rid of remainder of the division with 8 though.
In C we could use similar formula to the one in BASIC but we can do it also using bitwise operations that if properly optimized should be faster than division and multiplication. As expected the Assembly version has most code but in my opinion is the most elegant one.
Since we are already dealing with integers we can just use bitwise operations lsr and ror - to divide by two.

X has to be divided by 4 (/8*2) to get the Low byte for VERA so we divide by 2 twice.
Y has to be divided by 8 to get the Middle byte for VERA so we divide it by 2 three times meaning shift bits by tri bits.
Because we are using the value as Middle byte it is automatically multiplied by 256.

This is very clean and fast solution.

Draw

After all the preparations and calculations we are finally ready to draw to the screen. The only other thing remaining is to implement IF statement to differentiate between left and right button to draw or delete the character.

BASIC C Assembly
50 IF TB=1 THEN VPOKE 0,M,160
60 IF TB=2 THEN VPOKE 0,M,32
if (info.buttons & MOUSE_BTN_LEFT)
    vpoke(160,m);
else if (info.buttons & MOUSE_BTN_RIGHT)
    vpoke(32,m);
    lda #32
    ldx 6
    cpx #1
    bne :+
    lda #160
:   sta VERA_DATA0
The code is self explanatory.
If left button is pressed draw PETSCII screen code 160.
If right button is pressed then draw PETSCII screen code for space.
In C we also have vpoke function which is unique when building for Commander X16 platform.

Alternatively we could write directly to VERA like in Assembly but in this instance using vpoke is appropriate.
Assembly solution is again very simple and elegant.
We load register A with default value and then check the buttons value in ZP location 6. If right button was pressed replace value in A with 160 and send to VERA.


Build

Source code for each of the examples listed in full below and can be built using cc65 compiler using options below. If you just want to use binary feel free to download it from the links here:

BASIC C Assembly
cl65 -t cx16 Cdraw.c -o CDRAW.PRG cl65 -t cx16 Adraw.asm -o ADRAW.PRG
Tokenized BASIC file download BDRAW.BAS Compiled C file CDRAW.PRG Compiled Assembly file ADRAW.PRG


Complete Source Code

BASIC


C


Assembly



Comments

  1. Replies
    1. You are correct Felipe, good catch. It was correct in the source code but in the snippet in the beginning I had a typo. I fixed it already.

      Delete

Post a Comment

Popular posts from this blog

Default Palette

Direct VERA Access