PiTrex baremetal: vectrexInterface.c

Pitrex baremetal FAQ: PiTrexBM_FAQ.txt

Date: 12th of April 2021

If you want to program games or such for the piTrex baremetal, you need some knowledge about vectrexInterface.
In the last couple of weeks the routine collection somewhat evolved, so now I would like to write down a current “view” on things.

baremetal versus raspbian

  1. RULE
    In your programs always include <vectrex/vectrexInterface.h>.
  2. RULE
    Always include AFTER all other standard includes, and before your own “includes”.

If you do the above you are a huge step toward overall compatibilty between OS-using programs and baremetal programs. You can e.g. include following std headers and they should (mostly) work:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>

(this is not necessarily a full list)
This means you can open files, print to stdio or stderr, use malloc/free/exit, use string functions, mem functions and math routines… etc

Things that don’t work are stuff like “threads”, signaling, device handling and other exotic stuff. You can also not use “normal” timers, timing functions. The later mainly because I had no need for them, and I did not implement “stubs” of any kind. If you need something like that, tell me – I think we can easily figure that out.

E.g. just today for Rogers “chess”, I substituted for <time.h>, the following lines:

-----
#define CLOCKS_PER_SEC 1000000
#define clock_t unsigned int
 unsigned int clock()
 {
   unsigned int val;
   CCNT0(val);
   return val;
 }
-----

Which has for all purposes the exact behaviour as the standard definitions in <time.h>.
(very short explanation, CCNT0() gets the current systemtimer 0, a 1Mhz timer, every vectrex round this timer is reseted to 0, but within one “round” the time is measured exactly – but there also is another timer, which I don’t reset… so that could have been used also)

Hello world

 #include <vectrex/vectrexInterface.h>
 int main(int argc, char *argv[])
 {
   vectrexinit(1);
   v_init();
   while (1)
   {
     v_WaitRecal();
     v_printStringRaster(-30, 0, "HELLO WORLD", 40, -5, 0);
   }
   return 0;   
 }

Using the current “Makefile.baremetal” (copy and paste from any other project and just insert a helloWorld as the only “user – source”) – above is ALL you need!

You can execute the makefile, and a baremetal image will be created. If you copy that to a SD card and set it as a boot image, the piTrex will start, boot and display a “HELLO WORLD” in the middle of the screen on your vectrex.

You do not REALLY have to concern yourself with the inner workings of the baremetal implementation!

What you “must” learn, is how to use the vectrexInterface, what it offers and what not, what it can do and what not.

Different “modes” of the Interface

As mentioned, the vectrexInterface library has been growing and evolving. From a “tiny” thing 1 1/2 years ago, up to know, where it is pretty sophisticated (but probably not mature yet).

These different stages of “evolving” resulted in three different usage scenarios. Each following scenario is for the “user” easier, and usually also looks/feels better.

0) Library? Who needs a library? I do everything on my own!

I do not cover this here. Already with adding an additional layer of abstraction – it looks dangerous:

   SET_YSH_IMMEDIATE_8(yLen);
   SET_XSH_IMMEDIATE_8(xLen);
   START_T1_TIMER();
   WAIT_T1_END();

Above statements execute a “MOVE_TO”, here I use “wrapper” macros to make life easier. For example, the SET_YSH_IMMEDIATE_8(yLen); expands to:

define SET_YSH_IMMEDIATE_8(v) do{ \
 I_SET(VIA_port_b, 0x80); \
 DELAY_PORT_B_BEFORE_PORT_A(); \
 currentYSH=currentPortA=(uint8_t)(v); \ 
 I_SET(VIA_port_a, currentPortA); \ 
 DELAY_YSH();} while (0)

Which again includes wrapper macros, … this goes on a few steps… and finaly somehwere Kevins library calls are made, which again expand. The simple moveTo() – fully expanded would probably result in something around 100 lines of code. You do NOT want to use that! Believe me! THAT is the reason vectrexInterface was built!

But still, this is where “WE” started from!

1) Immediate: I draw my vectors! – the classic I do everything myself approach!

There are functions in the library

  • v_deflok()
  • v_moveToImmediate8(), v_drawToImmediate8(), v_immediateDraw32Patterned(), v_immediateDraw32Patterned()

These functions access the vectrex hardware (VIA chip) IMMEDIATELY, if you call such a draw function… than the vectrex will draw – and when the function returns, the draw will be done.
This is very “near” the usual assembler programming of the vectrex, you have total control of what you do, when you do it.

I am also not going “into” this any further, you as a “simple” programmer of PiTrex should not use these functions!

2) Pipeline: Please order my stuff and draw good!

For a “long” time this was the top of the evolution.
This was also the DEFAULT mode. The library has a configuration variable: usePipeline
If this is set to 1, than the pipeline is used with following functions:
(if not set, than these functions als work in immediate mode)

  • void v_directDraw32()
  • void v_directDraw32Patterned()
  • int v_printStringRaster()
  • void v_printString()

With these functions, you can probably do MOST of what is “needed” concerning screen output (the old “bitmap” routines have not been updated to use the pipeline yet!).
What the pipeline does:

  • it “collects” all vector/print instructions within a queue
  • once you call waitRecal() (which you must do every round), it examines the queue, optimizes it and puts the optimized results in a secondary queue
  • the secondary queue is than optimal printed to screen*

* ok, the optimization can probably still be improved… but at least we collected all the data!

Using the pipeline – even as it adds an addional computational stage – is usually a speed increase of 30-50% – and the drawing often is “cleaner”.

The “draw” functions use absolute values within the range of -32000 to +32000 (although only about -20000 to + 20000 is visible).

The pipeline approach additionally enables:

  • the possibilty to use a global scale for ALL output (excluding raster text)
  • the possibility to change orientation (for now still excluding raster text)
  • the possibility to do “global” clipping
  • the possibility to reposition the complete output

… and this at virtually no cost.

3) IRQ – Mode: Or more understandable – Output to Vectrex is done in a different Thread

This is an extension of the pipeline mode. This will be the future standard mode, but as of today you still have to enable it manually with:

v_setupIRQHandling();

The functions described above also can be used in IRQMode. You can switch between the modes if need be.
(for a detailed description of what this is pls look at: 18th of March 2021 Baremetal PiTrex again).

The upside is:

  • additional speed increase of often more than 20%
  • usage of sample playing possible
  • you can run the vectrex output in a different timing synchronization as your program
  • this also means, you e.g. do not have “blank screens” during long calculations
    (the output thread always draws the last finished pipeline contens, also redisplays it if there is no new data)

Attention:
While IRQ Mode is active:

  • all output to the vectrex must be made by the output “thread”. The thread “knows” and “remembers” what state the VIA is in, if you change that under its nose, things can get messy!
  • if the output thread must output “way to much” (thousands of tiny vectors + sound + joystick…) than it CAN be, that the system appears “locked”, since the output thread has highest priority, if it doesn’t return to the main program than effectively your program “stands still”
  • if that happens, switch the IRQ mode off, optimize your program while running in pipeline mode, and than switch the IRQ mode on again (this rarely happens though)

Library entities you must know

vectrexinit(1)
initializes Kevins piTrex access routines, must be called once at the start.

void v_init()
initializes the vectrex interface to default values, also reads the “vectrexInterface.ini” file.
This function usually must be preceded by a “vectrexinit(1)” call!

void v_WaitRecal()
this must be called once in your game round. This call synchronizes the game loop to a fixed timing (if possible).
all modes but IRQ mode:
– the timing is the refresh of the vectrex screen (can be set with v_setRefresh(), defaults to 50Hz)
– if you want to output sound, and read input from vectrex IO devices, these must be called seperately once each round
IRQMode:
– sound output and IO handling is also done in the vectrex thread, you have to enable these by calling appropriate enabler functions (v_enableSoundOut(), v_enableButtons(), …)
– IRQMode makes it possible to have different synchronization timings for vectrex refresh and game speed
(v_setRefresh() sets the vectrex refresh, v_setClientHz() sets the synch timing for your program, per default both are 50Hz)
– in IRQMode only the doubleBuffered sound function are allowed to be used (the other functions access VIA directly)

uint8_t v_readButtons()
void v_readJoystick1Digital()
void v_readJoystick1Analog()
void v_doSound()

Are all functions which queery vectrex or output to vectrex ( meaning VIA usage). In non IRQMode, these should be called once per round. If IRQMode is used, you can “leave” the functions configured, but they behave like stubs and immediatly return. The vectrex thread handles all those things, the appropriate enable/disable functions are:
void v_enableSoundOut()
void v_enableButtons()
void v_enableJoystickDigital()
void v_enableJoystickAnalog()

void v_directDraw32() [all modes]
void v_directDraw32Patterned()
[all modes]
Both functions draw a line (or a point), for the second function a “pattern” can be given.
The “32” in the function name hints, that the coordinates are given in the range from
-32000 to + 32000.

int v_printStringRaster() [all modes]
Prints text using the standard vectrex raster font. Coordinates are in vectrex 8 bit: -128 to 127!
The last “set” brightness is used, if none was ever set, than 0x50 is used.

void v_printString() [all modes]
Prints text using vectors, coordinates are in vectrex 8 bit: -128 to 127!

Library entities that are “interesting”

void v_playSFXCont()
void v_playSFXStop()
void v_playSFXStart()

void v_playAllSFX()
These play/stop AYFX type of sound effects, v_playAllSFX() should than be called once per round, to update the psg.

void v_initYM()
int v_playYM()

These init/play ym sound files.

void v_playIRQSample()
void v_stopSamplePlaying()

int v_loadRAW()
Only in IRQMode – these load/start/stop sample playing.
Samples can be created using Vide (or converted).

int loadFromZip()
load one file from a zip archive to a memory block

int crc32()
calculates a crc32 for a memory block

char *getLoadParameter()
gets a string[4] array of parameters that may have been passed by the piTrex loader.

int ini_parse()
Can be used to parse an ini file, additionally an iniHandler must be provided (just one easy function, which connects key/value pairs). As an example see diverse files or even vectrexInterface.c.

exit()
Replaces the standard “exit” and prints an error message on the vectrex.

macro: CCNT0(v)
Reads the system timer 0 to a unsigned int variable given. Timer 0 is reset each vectrex round.
The timer is a 1Mhz timer, one “unit” is than 1 micro second!

UART (115200 8n1)
Using a terminal you can interact with the piTrex (best usage is in pipeline mode, not IRQ mode).
While in the terminal you can type “h” for a list of commands.
Programs can use this to inject their own commands via a function pointer “userCommandList” (e.g. one can use the debugger of the “sim” emulator).
For more info on this look at the file “commands.i” and at “vectrexInterface.h”. An example to set this up can be seen in “sim/vx_interface.c”.

Configuration variables

which are set from the ini file, but can also be overwritten by the client program directly:

extern uint8_t beamOffBetweenConsecutiveDraws;
extern uint8_t useDoubleTimer;
extern uint8_t usePipeline;
extern uint8_t keepDotsTogether;
extern uint8_t orientation;
extern int16_t offsetX;
extern int16_t offsetY;
extern float sizeX;
extern float sizeY;

extern int logOutput;

Other stuff to know

printf()
outputs to a connected UART device, so you can see all output directly via a connected terminal.
If logOutput = 1, than all output is also saved on the SD (slows everything down), but if you do not have an UART interface, that may be the only output you may be able to see doing baremetal.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.