Example: 3d Imager

For the 3d Imager no "Code Generation" was done, since any generated code would have been an extensive example on its own.

Instead I provide an example in form of a "code snippet" within codi. (see:
codelib/Snippets/Malban/3dImager/3dImager.asm
and
codelib/Snippets/Malban/3dImager/3dImager.i
)

Much of the example is reused code found in the Narrow Escape binary and the great disassembly of Fred Taft.

I enhanced his disassembly in two ways:

  1. added further comments

  2. extracted a couple of subroutines, which can be used "out of the box" without sorting out to much stuff

Anyone interested in programming imager stuff should find it (I hope) to be quite easy to get going.

In the example I do not do any fancy 3d calculation, I only print 6 messages for the combination color/eye. Movement and placing of vectors to acchive 3d effects I leave out as an excercise for the avid reader.



Imager example


In general

Narrow Escape: how the imager is programmed and how PWM is realized to control the motor

  1. Startup The official imager games have a startup phase. During this phase the imager is "powered" up with high frequency short pwm duty pulses until the speed of the wheel matches the above mentioned time frame. The pulse duty is about 67% [542 cycles of low pulse, with 805 cylces wavelength].

During that powerup button 4 is polled to see if the sync hole of the wheel is triggered (button 4) - no usage of interrupts in the startup phase. The measurement taken is thus that one 360 degree spin of the wheel (one sync cycle) must be done completely within the wanted time frame [time frame see below] - if that is achieved three times, the program assumes that the wheel is up to speed.

The powerup loop only contains the above mentioned routines, if the power up does not reach its goal, from a user point of view the program just "hangs", since no other output of any kind gives any feedback.

  1. Ingame Once the game runs, the PWM is realized differently. Vectrex "assumes" that the wheel is spinning about "right" and sends exactly one pulse (of a calculated length, which is adjusted all the time) per sync cycle.

Each official game runs the game loop in a time reference frame. The frequency of the wheel is set in accordance to that timing frame (different games have different timing parameters).

The developer chose a frequency they would like to realize the game with (in Narrow Escape that timing value is $E000, which results in a frequency of 1/(1/1500000 * 0xe000) = 26,1579241... Hz wheel spin).
(
Narrow Escape: $e000 → 26.157Hz
Minestorm 3d: $fc00 → 23.251Hz
3d Crazy Coaster: $f000 → 24.414Hz
)

Timer T2 is set to that value. T2 is started after the sync triggered the interrupt and thus "counted down" throughout the game loop, the game loop finishes with a CWAI instruction, which waits for another interrupt to occur.

During the game T2 is compared to reference values:
$f000 right eye blue
$BD00 right eye green
$9600 right eye red
$6800 left eye blue
$4000 left eye green
$2000 left eye red

When the timer has reached the value - the corresponding vectors are drawn.

The pulse modulation is handled in a two fold manner:

Interrupt

With each occurrence of an interrupt a compare value is stored to a "global" variable (which I call "PWM_T2_Compare_current"). While running the main loop in the beginning the pulse is switched on (put to low), T2 (hi) is compared against the reference every now and than and if T2 is lower than the reference, the pulse is switched off.

The interrupt is triggered each time the index hole of the wheel passes the photo transistor and the in opposition placed LED.

The interrupt handles the calculation of the PWM. In general:

Within the interrupt two compare values are generated which I call

As you might have guessed those two values are stored to the above mentioned "global" variable (PWM_T2_Compare_current).
The calculated value "PWM_T2_Compare_slower" is used when no timeout occured and "PWM_T2_Compare_faster" when a timeout occured. "Switching" between those two values all the time, the speed of the motor is held more or less constant (in emulation the difference is about 0.01 Hz, I have not been able to measure the real thing).

The two mentioned values are checked from time to time if they also need adjustment. Therefor eight interrupt "sampling" are bundled together and are further investigated.

The timeout "failures" from above are counted and remembered. After 8 interrupt calls the ratio of "timeouts" and "not timeouts" are stored and further examined.

The current sampling (of 8 interrupt measurements) is stored in a variable which might be called:

for "long term" measurement, these samples are additionally stored for the last 3 samplings, in variables which I call:

(So all together we have access to the last 4 * 8 = 32 timeout/non timeout IRQ values).

The calculation of the new values for PWM_T2_Compare_faster/PWM_T2_Compare_slower is three fold:

  1. adjustment of "PWM_T2_Compare_faster"

  2. adjustment of "PWM_T2_Compare_slower"

  3. it is ensured, that both values are not to close to each other
    - if the difference between "PWM_T2_Compare_faster" and "PWM_T2_Compare_slower" is smaller than $1a than the "newer" value of "PWM_T2_Compare_faster" is taken as a reference and "PWM_T2_Compare_slower" = "PWM_T2_Compare_faster" - $1a is used.

In game

The "in game" handling of PWM is quite simple (once the IRQ has handled all the setup). At the beginning of the game loop (after joystick buttons have been read, this must be done beforehand, otherwise the button requests would interfere with the imager-communication), the duty pulse is enabled (set to low).

Than each time after some "time consuming" work is done, the variable "PWM_T2_Compare_current" is compared to T2 timer (hi byte). If T2 is lower than the reference value, the duty pulse is finished (set to high) and the "PWM_T2_Compare_current" set to 0 as indication that the pwm control is done for this mainloop.