Latest version and ZX81

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Latest version and ZX81

Post by siggi »

Hi
I made a program (midiplayer) for ZX81, using a several years old Z88DK version.
Yesterday I fetched the latest version (for WIN) and compiled again. Now the program behaves differently:
- it is self running (starts automatically)
That is bad in my current situation, where I am debugging the program ..

- it crashes at end (also when run in EO)

Here is the code:
Caution: it calls a driver at $200C. So I have added (just after "main") a POKE to $200C, 201 to poke at RET there. Thus the program can be startet in EO (without real hardware).
Without hardware it runs into error and stops (and waits for a key). After the key is pressed, it crashes (at program end).

Caution: I use a mixture of C and Assembler. And capture the Z80 IX register (NMI-Handler) for my own usage.
At end of the program the ZX81 is switched back to normal display mode (SLOW), which still works.

Here is the code:

Code: Select all

//zcc +zx81 -startup=2 -create-app -nv -O2 -o MidiPlay.bin MidiPlayer.c

// ZX81 Midiplayer V0.93
// Ported from
// "Mr.MidiPlayer v1.0" (see https://www.mikrocontroller.net/topic/48542)
// and adapted to ZX81 .
// NMI is used as timer, counting in 64 us ticks @ 3,25 MHz
// Made by Siggi, March 2018
//
// Hardware: ZX81, RS232 board (8251 compatible, ctrl/data at $EB/E3)
//                         USB-Interface
// Software: USB-Driver at 8192 (READM-routine at $200C to read an open file)
//

#define DEBUG


#include <stdlib.h>
#include <zx81.h>
#include <stdio.h>
#include <input.h>


/* globals */
unsigned long us_per_beat;                  /* Atmel timer register */

unsigned long trk_len = 512;  /* default read block size */

unsigned int block_counter;

unsigned int timer_running;
unsigned long time_stamp_last_event;

/* Variables handled inside interrupt routine: Handle with care!
/* read by interrupt routine */
unsigned char isr_low_count_start;
unsigned int  isr_high_count_start;
/* updated by interrupt routine */
unsigned char isr_low_count;
unsigned int  isr_high_count;
unsigned long isr_time;

unsigned long debug_wait_too_slow = 0;
unsigned long debug_wait_counter = 0;

/* Prototypes */

void send_MIDI_reset();
void key_detect(void);
void cleanup(void);
void setup_NMI_Timer_in_us(unsigned long tickLength);

// MCU initialisieren
void hw_init(void) {
        send_MIDI_reset();
        debug_wait_too_slow = 0;
        debug_wait_counter = 0;

#if 0
        zx_fast();
        setup_NMI_Timer_in_us(1*64L);
        setup_NMI_Timer_in_us(2*64L);
        setup_NMI_Timer_in_us(3*64L);
        setup_NMI_Timer_in_us(254*64L);
        setup_NMI_Timer_in_us(255*64L);
        setup_NMI_Timer_in_us(256*64L);
        setup_NMI_Timer_in_us(257*64L);
    zx_slow();
#endif
}


void __FASTCALL__ sendbyte(unsigned short byte)
{
#asm
                ; byte is in HL
SENDWAIT:
        IN A,($EB)           ;DBEB
        AND 4
        JR Z,SENDWAIT
        LD A, L
        OUT ($E3), A        ;D3E3
#endasm
}


// All midi off
void send_MIDI_reset(void)
{
        sendbyte(0xF0);
        sendbyte(0x7E);
        sendbyte(0x7F);
        sendbyte(0x09);
        sendbyte(0x01);
        sendbyte(0xF7);
}

unsigned char fetchbyte(void)
{
        if (!block_counter)
        {
                block_counter = 1024;
                /* request next block from file */
                #asm
                ld bc,(_block_counter)
                scf
                call $200C
                #endasm
        }

        /* read 1 char */
        block_counter--;
        trk_len--;

        #asm
        and a                ; clear carry
        call $200C
        ld l, a
        ld h, 0                 ; HL holds return value
        #endasm
}

void flush_remaining_bytes(void)
{
        while (block_counter)
        {
                fetchbyte();
        }
}

void checkbyte (unsigned char b)
{
        if (b == fetchbyte())
                return;

        printf("Checkbyte failed: %d\n", b);
        cleanup();
        exit(1);
}


char checkstring(unsigned char *buf, unsigned char len) {
        unsigned char b, cnt=0;

        for (cnt=0; cnt<len; cnt++) {
                b = fetchbyte();
                if (b != buf[cnt])
                {
                        printf("Checkstring failed: %d instead of %d\n", b, buf[cnt]);
                        return 0;
                }
        }
        return 1;
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned long v;
unsigned char c;

unsigned long fetch_varlen(void)
{
        c = fetchbyte();
        v = c & 0x7F;
        while (c & 0x80)
        {
                /* one more byte follows */
                c = fetchbyte();
                v = (v << 7) + (c & 0x7F);
        }
        return v;
}


void key_detect(void)
{
        #asm
        LD A, $FD
        IN A, ($FE)
        BIT 0, A
        RET NZ
        #endasm
        printf("ABORT\n");
        cleanup();
        exit(1); /* key "A" has been pressed */
}
/* global vars used here. Access to globals ist faster than to locals */
unsigned long end_time;

void wait_for_deltatime(void)
{
        /* read dt from file */
        /* Note: Timer is runnig and counts also time during file access! */
        end_time = fetch_varlen();

        if (end_time) /* do nothing if delay is 0 */
        {
                end_time += time_stamp_last_event;
                do
                {
                        key_detect();
                        #asm
                re_check:
                        ld bc,(_isr_time+2)                                ; get high bytes of isr_time
                        ld de,(_isr_time)                                ; get low bytes of isr_time
                        ld hl,(_isr_time)                                ; get low bytes again to detect change by NMI (will affect at least low bytes)
                        and a                                                        ; clear carry bit
                        sbc hl, de                                                ; check for no change
                        jr nz, re_check                                        ; try again if NMI occurred
                        ld (_time_stamp_last_event), de        ; store low bytes
                        ld (_time_stamp_last_event+2), bc ; store high bytes
                        #endasm
                        if (time_stamp_last_event == end_time)
                                return;
                        if (time_stamp_last_event > end_time)
                        {
                                debug_wait_too_slow++;
                                return;
                        }
                        #asm
                        halt
                        #endasm
                        debug_wait_counter++;
                }
                while(1);
//                while (time_stamp_last_event < end_time);
        }
}

/* start NMI timer */
void start_NMI_Timer(void)
{
        zx_fast();
        #asm
        ex af, af
        ld a, (_isr_low_count_start)                        ; set alternate A with low counter
        ex af, af
        ld hl, (_isr_high_count_start)                        ; setup high counter
        ld (_isr_high_count), hl
        ld iy, _TIMER_ISR                                                ; iy will get ix during assembly for ZX81!
        out ($FE), a                                                        ; start NMI generator
        #endasm
        timer_running = 1;
}

/* stop running NMI timer and restore BASIC SLOW mode */
void stop_NMI_Timer(void)
{
        if (timer_running)
        {
                #asm
                out ($FD),a                                                        ; stop NMI generator
                #endasm
                timer_running = 0;
        }
        zx_slow();
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned char low;
unsigned int high;

/* setup the NMI timer in unit usec. May be also be used while NMI counter is running */
void setup_NMI_Timer_in_us(unsigned long tickLength)
{
         /* convert microseconds into ZX81 NMI timing @ 3,25 MHz: divide by 64 (64 usec/NMI) */
        tickLength = tickLength >> 6;
        if (!tickLength)
                printf("Warning: NMI ticklen 0\n");
        // ticklen        |low        |high
        //        0                        0                0        invalid -> 256
        //  1                        FF                0
        //        2                        FE                0
        //        254                        02                0
        //        255                        01                0
        //        256                        00                0        NMI counts A to 0 -> reload value is 0 for next 256 NMIs
        //        257                        FF                1        count 1 by A and 256 by high
        //        258                        FE                1        count 2 by A and 256 by high
        //        511                        01                1
        //        512                        00                1
        //        513                        FF                2

        low = 256 - (unsigned int) (tickLength & 0xFF); /* bit 0..7  */
        high = (tickLength - 1) >> 8;  /* bit 8..23 */
#ifdef DEBUG
        printf("\nNMIs(%lu),low=%u,high=%u\n", tickLength, low, high);
#endif
        if (timer_running)
        {
                #asm
                halt ; update values handled in interrupt routine quickly after interrupt
                #endasm
        }
        isr_low_count_start = low;
        isr_high_count_start = high;
        if (!timer_running)
        {
                /* update also current state of high counter */
                isr_high_count = high;
        }
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned char ev, len, adp;
unsigned long gl_BeatsPerMin_BPM;
unsigned long temp;

unsigned char ddddd @ 0x200C;

void main(void)
{
        ddddd = 201; /* "ret" for usage on Emulator. To be removed of real hardware! */

        printf("---- ZX81 MIDIPLAYER V0.93 ----\n\n");
        hw_init();
        gl_BeatsPerMin_BPM = 120;  /* default MIDI timing */
        us_per_beat = 500000L;     /* correspondens to beat length 0,5 sec = 1/120 min */

        /* for file buffer handling */
        block_counter = 0;

        /* expected MIDI file header MThd */
        ev = 0;
        while ((!checkstring("M", 1)) && (ev < 4))
        {
                ev++;
        }

        if (!checkstring("Thd", 3))
        {
                printf("No MIDI file.\n");
                cleanup();
                exit (EXIT_FAILURE);
        }

                /* expected length: 6 bytes */
        checkbyte(0);
        checkbyte(0);
        checkbyte(0);
        checkbyte(6);

        /* expected MIDI file version: 0 */
        checkbyte(0);
        if ((ev = fetchbyte()) != 0)
        {
                printf("Wrong MIDI file version: %d\n", ev);
                cleanup();
                exit (EXIT_FAILURE);
        }

        /* expected track number: 1 */
        checkbyte(0);
        if ((ev = fetchbyte()) != 1)
        {
                printf("Wrong number of tracks: %d\n", ev);
                cleanup();
                exit (EXIT_FAILURE);
        }

        /* fetch MIDI timing of that track */
        gl_BeatsPerMin_BPM = ((unsigned long) fetchbyte()) << 8;
        gl_BeatsPerMin_BPM |= (unsigned long) fetchbyte();

        printf("Global timeset = %lu\n", gl_BeatsPerMin_BPM);

        /* calc us_per_beat in 2 steps as workaround for Z88DK calc error using constants */
        if (gl_BeatsPerMin_BPM < 0x8000)
        {
                // Delta-Ticks (in usec for one minute
                us_per_beat = 60000000;   /* microseconds in one minute */
                us_per_beat /= gl_BeatsPerMin_BPM;
        }
        else
        {
                // SMPTE-Timeformat
                us_per_beat = 60000000;   /* microseconds in one minute */
                us_per_beat /= (-(char)(gl_BeatsPerMin_BPM>>8))/((unsigned char)gl_BeatsPerMin_BPM);
                printf("SMPTE time format is used.\n");
        }
#if 0
        /* calc OCR1A in 2 steps as workaround for Z88DK calc error using constants */
        if (gl_BeatsPerMin_BPM < 0x8000)
        {
                // Delta-Ticks
                OCR1A = 500000;
                OCR1A /= (unsigned long) gl_BeatsPerMin_BPM;
        }
        else
        {
                // SMPTE-Timeformat
                OCR1A = 1000000;
                OCR1A /= (-(char)(gl_BeatsPerMin_BPM>>8))/(unsigned char)gl_BeatsPerMin_BPM;
        }
#endif
        printf("Beat length in us = %lu(%08x)\n", us_per_beat, us_per_beat);

        /* expect MIDI track MTrk and fetch its length */
        checkstring("MTrk", 4);

        temp = (unsigned long)fetchbyte() << 24;
        temp |= (unsigned long)fetchbyte() << 16;
        temp |= (unsigned long)fetchbyte() << 8;
        temp |= (unsigned long)fetchbyte();
        trk_len = temp;

        printf ("Track Length = %lu(%08x)\n"\
        "Press any key (or A to abort)\n", trk_len, trk_len);
        do
        {
                key_detect();
        }
        while (!in_Inkey());
        while (in_Inkey());  /* wait for key released */

        /* Setup timer variables used by NMI routine */
        time_stamp_last_event = isr_time = 0;

        /* ZX81 NMI: 16000 = 1 sec, 16 = 1msec , 1 = 207 clock cycles/64 usec */
        setup_NMI_Timer_in_us(us_per_beat);

        start_NMI_Timer();

        while (trk_len)
        {
                wait_for_deltatime();
                ev = fetchbyte();
                if (ev == 0xFF) {        // META-EVENT
                        ev = fetchbyte();
                        switch (ev) {
                                case 0x2f:        // End Of Track
                                        checkbyte(0);
                                        trk_len = 0;
                                        break;

                                case 0x51:        // Tempo Change
                                        checkbyte(3);
#if 1
                                        temp = (unsigned long)fetchbyte() << 16;
                                        temp |= (unsigned long)fetchbyte() << 8;
                                        temp |= (unsigned long)fetchbyte();
#endif
#if 0
                                        #asm
                                        call _fetchbyte                ; H is 0, L holds value
                                        ld (_temp+2), HL        ; update temp+3 and temp+2
                                        call _fetchbyte
                                        ld a, l
                                        ld (_temp+1), a
                                        call _fetchbyte
                                        ld a, l
                                        ld (_temp), a
                                        #endasm
#endif
                                        us_per_beat = temp / gl_BeatsPerMin_BPM;

#ifdef DEBUG
                                        printf("us_per_beat=%lu, temp=%08x\nBPM=%08x\n", us_per_beat, temp, gl_BeatsPerMin_BPM);
#endif
                                        setup_NMI_Timer_in_us(us_per_beat);
                                        break;

                                default:                 // Ignore unknown META-EVENT
                                        temp = fetch_varlen();
#ifdef DEBUG
//                                        printf("?ME:%02x/", ev);
#endif
                                        while (temp--)
                                        {
                                                fetchbyte();
                                        }
                        }
                }
                else if (ev == 0xf0)
                {        // SYSEX
                        sendbyte(ev);
                        temp = fetch_varlen();
                        while (temp--)
                        {
                                sendbyte(fetchbyte());
                        }
                }
//                else if (ev >= 0x80) {        // MIDI-EVENT
                else if (ev & 0x80)
                {        // MIDI-EVENT
                        sendbyte(ev);
                        switch (ev)
                        {
                                case 0xf2:
                                        len = 2;
                                        break;
                                case 0xf3:
                                        len = 1;
                                        break;
                                default:
                                        switch (ev & 0xf0)
                                        {
                                                case 0xf0:
                                                        len = 0;
                                                        break;
                                                case 0xc0:        // ProgramChange
                                                case 0xd0:        // ChannelAftertouch
                                                        len = 1;
                                                        break;
                                                case 0x80:        // NoteOff
                                                case 0x90:        // NoteOn
                                                case 0xa0:        // PolyphonicAftertouch
                                                case 0xb0:        // ControlModeChange
                                                case 0xe0:        // PitchWheel
                                                        len = 2;
                                                        break;
                                                default:
                                                        printf("Unknown MIDI event type %d\n", ev);
                                                        cleanup();
                                                        exit(2);
//no break?
                                        }
//no break?
                        }
                        adp = len;
                        while (len--) {
                                sendbyte(fetchbyte());
                        }
                }
                else
                {        // Running Status
                        sendbyte(ev);
                        len = adp-1;
                        while (len--)
                                sendbyte(fetchbyte());
                }
        }
        printf("Feddisch ;-)\n");
        cleanup();
        printf("Total time %lu (%08x) ticks\n", isr_time, isr_time);

        while (!in_Inkey());
        while (in_Inkey());  /* wait for key released */

}

void cleanup(void)
{
        send_MIDI_reset();
        stop_NMI_Timer();
        flush_remaining_bytes();
        printf("debug_wait_too_slow=%lu\n", debug_wait_too_slow);
        printf("debug_wait_counter=%lu\n", debug_wait_counter);
}


/* NMI interrupt routine. Called from ROM via register IX */

TIMER_ISR()
{
#asm
        ; NMI is stopped, alternate reg A active and is 0
        ; count high byte counter down
        ld hl, (_isr_high_count)
        ld a, h
        or l
        dec hl
        ld (_isr_high_count), hl
        ld a, 0                 ; reload A for next 256 NMIs
        jr nz, ISR_CONT
#endasm

        isr_time++;         // count time since start of track

#asm
        ; restart timer
        ld a, (_isr_low_count_start)
        ld hl, (_isr_high_count_start)
        ld (_isr_high_count), hl

ISR_CONT:
        ex af, af
        out ($FE), a
        pop hl
        pop de
        pop bc
        pop af
#endasm
}
Any ideas, why this does not work anymore?

Siggi
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

Addendum: without hardware the program does not start the NMI timer / disable display. If performs "exit (EXIT_FAILURE);" and then crashes
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

Hey Siggi,

I've fixed up a couple of issues around initialisation and now a simple printf(); exit() example works correctly.

Your program ends up looping around HRG_Sync1 with interrupts disabled - this comes via the zx_slow() call.

One thing that looks a bit weird is that there are some out (254),a where a is effectively undefined.

Autorun was added in #225: https://github.com/z88dk/z88dk/issues/225 - adding an option to disable it shouldn't be too tricky
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

Hi Dom
the "out ($FE), a" enables the sync generator inside the ZX81 ULA. Any "out ($FE)" would be sufficient, but no assembler would accept this line ;-)

Siggi
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

"Your program ends up looping around HRG_Sync1 with interrupts disabled - this comes via the zx_slow() call.
"
That worked well using the old Z88DK version .....
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

What version were you using? Time to do a line by line comparison.
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

I think: 15.12.2015
Since then I it was not necessary to fetch an update up to now ;-)

Siggi
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

There?s been a few changes since then to say the least.

I?ll hopefully get a chance over the weekend to see what?s happened.
RobertK
Well known member
Posts: 221
Joined: Mon Feb 26, 2018 12:58 pm

Post by RobertK »

siggi wrote:- it crashes at end (also when run in EO)
I can confirm that any ZX81 program compiled with the nightly build crashes when it terminates, while when compiled with the current stable version 1.99B the program correctly returns to the Basic prompt.

Another problem is that for the ZX81 and ZX80 targets the randomize() function is no longer available.

Dom, thanks in advance for your efforts.
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

There were a few issues:

- The atexit stack wasn't being handled properly
- fclose() was being compiled as fastcall when it's not
- hl' wasn't being saved correctly

The last one was the major issue - it was saved, then overwritten with 0 as bss was cleared, and then the interrupt routine set it again.

Both the 2015 version and current version now behave the same with EightyOne emulator.
RobertK
Well known member
Posts: 221
Joined: Mon Feb 26, 2018 12:58 pm

Post by RobertK »

dom wrote:I've just checked rand() is available for both ZX80 and ZX81.
I replaced the old random number generator (which turned out to be not very random) in issue #579 (https://github.com/z88dk/z88dk/issues/579) so randomise() vanished at that point in time. You can seed with the normal srand() now.
And what seed should I use for srand()? Although srand() shuffles the random values, using a constant seed value will always return the same set of random numbers every time you run the program, while randomize() made rand() always return different values.

A little test program:

Code: Select all

// Compile with
// zcc +zx81 -startup=2 -o rndtest2 -create-app rndtest2.c

#include <stdio.h>

void main()
{
  int i;

  // randomize();  // available in stable release 1.99  
  // or
  srand();                   // for nightly build
  
  // rand() returns a random value between 0 and 32767
  for (i=0;i<10;i++)
  {
        printf("rand value is %d.\n",rand());
  }        
        
}
BTW, it seems that there was a problem with last night's build, here is the compile error you get with the current nightly:

Code: Select all

Error at file 'C:\Misc\z88dk\lib\config\\..\..\\lib\target\zx81\classic\zx81_crt
0.asm' line 57: symbol 'heaplast' not defined
Error at file 'C:\Misc\z88dk\lib\config\\..\..\\lib\target\zx81\classic\zx81_crt
0.asm' line 58: symbol 'heapblocks' not defined
2 errors occurred during assembly
Errors in source file C:\Misc\z88dk\lib\config\\..\..\\lib\target\zx81\classic\z
x81_crt0.asm:
Error at file 'C:\Misc\z88dk\lib\config\\..\..\\lib\target\zx81\classic\zx81_crt
0.asm' line 57: symbol 'heaplast' not defined
                   ^ ----         PUBLIC    heaplast        ;Near malloc heap va
riables
Error at file 'C:\Misc\z88dk\lib\config\\..\..\\lib\target\zx81\classic\zx81_crt
0.asm' line 58: symbol 'heapblocks' not defined
                   ^ ----         PUBLIC    heapblocks
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

That error is a result of https://github.com/z88dk/z88dk/pull/665 being merged - there's some older symbols floating around in some ports by the looks of things. I'll clean them tonight (and get some zx81 examples to build nightly...)

I'm surprised that randomize() actually modified the seed enough - it's based off memory and the r register, so I guess would be reliant on the program not being started immediately the emulator started which isn't necessarily guaranteed.

I suspect a better way to seed it might be to base it off how long it takes a user to press a key - i.e. seed once when the game starts. That way it should be different for every user and every load.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

RobertK wrote:And what seed should I use for srand()?
srand() always requires a seed value. I think you probably avoided an error and got a warning instead because stdlib.h wasn't included. srand(0) corresponds to the default seed value compiled into the binary.

If you need a random seed you need some external entropy. There's no way a deterministic computer can provide that. You can try to use things like time since the computer was turned on (the FRAMES variable) or the z80's internal R register which counts how many opcodes have been fetched % 128 since power on. But in the age of emulators these can be deterministic values too unless the emulator takes special steps. The randomize() that used to be there used the R register and mixed with bytes from the compiled binary but the latter is a deterministic factor so the only sort-of random component was the R register.

Alternatively, you can use some entropy provided by the user. Quite often what is used is the time it takes for the user to leave a menu or press a key to start something. This code comes from a spectrum game that waits after the program has loaded for a keypress to start:

Code: Select all

{
   unsigned int counter;

   while (in_test_key()) ;
   for (counter = 31416; !in_test_key(); counter += 10061) ;
   srand(counter);
}
It waits for no keys pressed, then enters a tight loop that adds a value to the seed (counter) that only exits when the user presses a key. The value 10061 is a prime so counter should hit all numbers between 0 and 65535.
BTW, it seems that there was a problem with last night's build, here is the compile error you get with the current nightly:

Code: Select all

Error at file 'C:\Misc\z88dk\lib\config\\..\..\\lib\target\zx81\classic\zx81_crt
0.asm' line 57: symbol 'heaplast' not defined
Those are errors that are only now being caught since the assembler was updated yesterday or the day before to catch them. I'm not sure why compiling the examples in travis wouldn't have caught them or maybe the errors go away if the heap is enabled for the compile?
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

There you go @alvin, writing more words than me again!

The zx81 examples aren't compiled by the looks of things, neither are the enterprise ones (also has the same issue) and one of ticalcs has it as well.
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

Right, hopefully all ports will build correctly from now onwards - I'm building hello world for all of them apart from rabbit, embedded and test.

But back to the topic, Siggi, I've added -Cz--disable-autorun to prevent zx81 programs from autorunning.
RobertK
Well known member
Posts: 221
Joined: Mon Feb 26, 2018 12:58 pm

Post by RobertK »

The -Cz--disable-autorun works, and the crash-at-the-end problem is fixed now, thanks.

And thanks for the srand() seed idea, that requires only a few more code lines and does the job as well.

The only problem with that is that on the ZX80 target you can't use in_Inkey() to wait for user input because it would halt the program execution (and you need a loop to increase a counter to generate the seed value). in_Inkey() seems to be the only function that makes the ZX80 screen constantly visible. Using in_KeyPressed() and gen_tv_field() within the loop (after an initial gen_tv_field_init(0) call at the start) makes the screen flicker heavily.

Maybe you have some idea how to solve this. I never owned a ZX80 and always loved it's case design and always dreamed of playing action games on it, but now that I have learned about it's Fast-mode-only feature I know that this would have been quite impossible on that machine. :-)

BTW, randomize() really made rand() always return different values, try my test program with z88dk 1.99B and uncomment the randomize() call (and remove the srand() call of course).
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

The latest bugs are removed :)
Thanks

But here is the next one:
my midiplayer did run as expected, until I added a "sscanf" call. Then immediately after LOAD (NOT RUN!!!) the zeddy crashes and the screen flickers. Same problem for "scanf"!
The generated application them seems to be corrupted!

Here is my latest player code: if I uncomment line 539:
// scanf("%d", &new_nominator);

then the Zeddy crashes after LOAD.

Code: Select all

//zcc +zx81 -startup=2 -create-app -Cz--disable-autorun -v -O2 -o MidiPlay.bin MidiPlayer.c

// ZX81 Midiplayer V0.95
// Ported from
// "Mr.MidiPlayer v1.0" (see https://www.mikrocontroller.net/topic/48542)
// and adapted to ZX81 .
// NMI is used as timer, counting in 64 us ticks @ 3,25 MHz
// Made by Siggi, March 2018
//
// Hardware: ZX81, RS232 board (8251 compatible, ctrl/data at $EB/E3)
//                         USB-Interface
// Software: USB-Driver at 8192 (READM-routine at $200C to read an open file)
//

#include <stdlib.h>
#include <zx81.h>
#include <stdio.h>
#include <input.h>


/* globals */
unsigned long us_per_beat;                        /* timing register in microseconds */

unsigned long trk_len = 512;                /* default read block size */

unsigned int block_counter;

unsigned int timer_running;
unsigned long time_stamp_last_event;

/* Variables handled inside interrupt routine: Handle with care!
/* read by interrupt routine */
unsigned char isr_low_count_start;
unsigned int  isr_high_count_start;
/* updated by interrupt routine */
unsigned char isr_low_count;
unsigned int  isr_high_count;
unsigned long isr_time;

/* Variables used for analysis/debugging */
unsigned char debug_enabled = 0;
unsigned int debug_wait_too_slow;
unsigned long debug_min_ticks;
unsigned long debug_max_ticks;
unsigned long debug_min_delays;
unsigned long debug_max_delays;
unsigned long debug_delay_calls;

/* Variables used for gimmicks */
unsigned char gimmicks_enabled = 0;
static unsigned char speed_nominator = 0;

/* For handling of the startup behaviour */
static unsigned char last_cmd = 0;

/* Prototypes */

void send_MIDI_reset();
void key_detect(void);
void cleanup(void);
void setup_NMI_Timer_in_us(unsigned long tickLength);
void start_NMI_Timer(void);
void stop_NMI_Timer(void);

// MCU initialisieren
void hw_init(void)
{
        send_MIDI_reset();
        debug_enabled = 0;
        debug_min_ticks=0XFFFFFFFF;
        debug_max_ticks=0;
        debug_min_delays=0XFFFFFFFF;
        debug_max_delays=0;
        debug_wait_too_slow = 0;
        debug_delay_calls = 0;
}


void __FASTCALL__ sendbyte(unsigned short byte)
{
#asm
                ; byte is in HL
SENDWAIT:
        IN A,($EB)           ;DBEB
        AND 4
        JR Z,SENDWAIT
        LD A, L
        OUT ($E3), A        ;D3E3
#endasm
}


// All midi off
void send_MIDI_reset(void)
{
        sendbyte(0xF0);
        sendbyte(0x7E);
        sendbyte(0x7F);
        sendbyte(0x09);
        sendbyte(0x01);
        sendbyte(0xF7);
}

unsigned char fetchbyte(void)
{
        if (!block_counter)
        {
                block_counter = 4096;
                /* request next block from file */
                #asm
                ld bc,(_block_counter)
                scf
                call $200C
                #endasm
        }
        /* read 1 char */
        #asm
;        block_counter--;
        ld        hl,(_block_counter)
        dec        hl
        ld        (_block_counter),hl
;        trk_len--;
        ld        hl,(_trk_len)
        ld        de,(_trk_len+2)
        call        l_declong
        ld        (_trk_len),hl
        ld        (_trk_len+2),de
        and a                                        ; clear carry
        call $200C
        ld l, a
        ld h, 0                                 ; HL holds return value
        ret z
        ld hl, 0                                ; ABORT detected: clear block counter
        ld        (_block_counter),hl
        call nz,_break_detected        ; keypress A has been detected
        #endasm
}

void flush_remaining_bytes(void)
{
        while (block_counter)
        {
                fetchbyte();
        }
}

void checkbyte (unsigned char b)
{
        if (b == fetchbyte())
                return;

        printf("Checkbyte failed: %d\n", b);
        cleanup();
        exit(5);
}


char checkstring(unsigned char *buf, unsigned char len) {
        unsigned char b, cnt=0;

        for (cnt=0; cnt<len; cnt++) {
                b = fetchbyte();
                if (b != buf[cnt])
                {
                        printf("Checkstring failed: %d instead of %d\n", b, buf[cnt]);
                        return 0;
                }
        }
        return 1;
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned long v;
unsigned char c;

unsigned long fetch_varlen(void)
{
        c = fetchbyte();
        v = c & 0x7F;
        while (c & 0x80)
        {
                /* one more byte follows */
                c = fetchbyte();
                v = (v << 7) + (c & 0x7F);
        }
        return v;
}

void break_detected(void)
{
        printf("ABORT\n");
        cleanup();
        exit(1); /* key "A" has been pressed */
}
void key_detect(void)
{
        #asm
        LD A, $FD
        IN A, ($FE)
        BIT 0, A
        RET NZ
        call _break_detected        ; performs EXIT
        #endasm
}
/* global vars used here. Access to globals ist faster than to locals */
unsigned long end_time;

unsigned char my_in_Inkey(void)
{
        #asm
        ld h,0
        ld a,$EF        ; bit 4 low: decode keyboard line 6-0
        in a,($FE)
        bit 4,a
        ld l,'6'
        ret z          ; key 6
        bit 3,a
        ld l,'7'
        ret z          ; key 7
        bit 2,a
        ld l,'8'
        ret z          ; key 8
        bit 0,a
        ld l,'0'
        ret z          ; key 0
        ld l,$ff       ; no key
        #endasm
}

void wait_for_deltatime(void)
{
        /* read dt from file */
        /* Note: Timer is runnig and counts also time during file access! */
        end_time = fetch_varlen();

        if (end_time) /* do nothing if delay is 0 */
        {
                debug_delay_calls++;
                if (debug_enabled)
                {
                       if (end_time < debug_min_delays)
                               debug_min_delays = end_time;
                       if (end_time > debug_max_delays)
                               debug_max_delays = end_time;
        }
                end_time += time_stamp_last_event;
                do
                {
                        key_detect();
                        #asm
                re_check:
                        ld bc,(_isr_time+2)                                ; get high bytes of isr_time
                        ld de,(_isr_time)                                ; get low bytes of isr_time
                        ld hl,(_isr_time)                                ; get low bytes again to detect change by NMI (will affect at least low bytes)
                        and a                                                        ; clear carry bit
                        sbc hl, de                                                ; check for no change
                        jr nz, re_check                                        ; try again if NMI occurred
                        ld (_time_stamp_last_event), de        ; store low bytes
                        ld (_time_stamp_last_event+2), bc ; store high bytes
                        #endasm
                        if (time_stamp_last_event == end_time)
                                return;
                        if (time_stamp_last_event > end_time)
                        {
                                debug_wait_too_slow++;
                                return;
                        }

                        if (gimmicks_enabled)
                        {
                                /* here we have time for gimmicks */
                                switch (my_in_Inkey())
                                {
                                        case '7':  /* more speed */
                                                if (speed_nominator)
                                                        speed_nominator--;
                                                else
                                                        speed_nominator = 31;
                                                setup_NMI_Timer_in_us(us_per_beat);
                                                in_WaitForNoKey();  /* wait for key released */
                                                break;

                                        case '6':  /*less speed */
                                                if (speed_nominator)
                                                        speed_nominator++;
                                                else
                                                        speed_nominator = 33;
                                                setup_NMI_Timer_in_us(us_per_beat);;
                                                in_WaitForNoKey();  /* wait for key released */
                                                break;

                                        case '0':  /* normal speed */
                                                speed_nominator = 0;
                                                setup_NMI_Timer_in_us(us_per_beat);
                                                break;

                                        case '8':  /* double speed */
                                                speed_nominator = 16;
                                                setup_NMI_Timer_in_us(us_per_beat);;
                                                break;
                                }
                                if (speed_nominator == 32)
                                        speed_nominator = 0;
                        }
                }
                while(1);
        }
}

/* start NMI timer */
void start_NMI_Timer(void)
{
        zx_fast();
        #asm
        ex af, af
        ld a, (_isr_low_count_start)                        ; set alternate A with low counter
        ex af, af
        ld hl, (_isr_high_count_start)                        ; setup high counter
        ld (_isr_high_count), hl
        ld iy, _TIMER_ISR                                                ; iy will get ix during assembly for ZX81!
        out ($FE), a                                                        ; start NMI generator
        #endasm
        timer_running = 1;
}

/* stop running NMI timer and restore BASIC SLOW mode */
void stop_NMI_Timer(void)
{
        if (timer_running)
        {
                timer_running = 0;
        }
        zx_slow();
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned char low;
unsigned int high;

/* setup the NMI timer in unit usec. May be also be used while NMI counter is running */
void setup_NMI_Timer_in_us(unsigned long tickLength)
{
        if (debug_enabled)
        {
                if (tickLength < debug_min_ticks)
                        debug_min_ticks = tickLength;
                if (tickLength > debug_max_ticks)
                        debug_max_ticks = tickLength;
    }
        /*        convert to other speed */
        if (gimmicks_enabled && speed_nominator)
        {
                tickLength = (tickLength * speed_nominator) >> 5; /* denominator is 32 */
        }
         /* convert microseconds into ZX81 NMI timing @ 3,25 MHz:
         /* do some timing corrections and the divide by 64 (64 usec/NMI) */
        if (tickLength & 0xFFFFFE00) /* > 512 */
                tickLength -= 150;
        else
                tickLength -= 96;
        tickLength = tickLength >> 6;
        if(debug_enabled)
        {
                if (!tickLength)
                        printf("Warning: NMI ticklen 0\n");
                if (tickLength > 1000000)
                        printf("Warning: ticklen > 1000000\n");
        }
        // ticklen        |low        |high
        //        0                        0                0        invalid -> 256
        //  1                        FF                0
        //        2                        FE                0
        //        254                        02                0
        //        255                        01                0
        //        256                        00                0        NMI counts A to 0 -> reload value is 0 for next 256 NMIs
        //        257                        FF                1        count 1 by A and 256 by high
        //        258                        FE                1        count 2 by A and 256 by high
        //        511                        01                1
        //        512                        00                1
        //        513                        FF                2

        low = 256 - (unsigned int) (tickLength & 0xFF); /* bit 0..7  */
        high = (tickLength - 1) >> 8;  /* bit 8..23 */
        if (debug_enabled)
                printf("NMIs(%lu),L=%u,H=%u\n", tickLength, low, high);
        if (timer_running)
        {
                #asm
                halt ; update values handled in interrupt routine quickly after interrupt
                #endasm
        }
        isr_low_count_start = low;
        isr_high_count_start = high;
        if (!timer_running)
        {
                /* update also current state of high counter */
                isr_high_count = high;
        }
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned char ev, len, adp;
unsigned int gl_BeatsPerMin_BPM;
unsigned long temp;

                                        unsigned char buffer[20];
                                 int new_nominator;

void main(void)
{
        printf("---- zx81 MIDIPLAYER v0.95 ----\n\n");
        hw_init();
        gl_BeatsPerMin_BPM = 120;  /* default MIDI timing */
        us_per_beat = 500000L;     /* correspondens to beat length 0,5 sec = 1/120 min */

        /* for file buffer handling */
        block_counter = 0;

        /* expected MIDI file header MThd */
        ev = 0;
        while ((!checkstring("M", 1)) && (ev < 4))
        {
                ev++;
        }

        if (!checkstring("Thd", 3))
        {
                printf("No MIDI file.\n");
                cleanup();
                exit (2);
        }

                /* expected length: 6 bytes */
        checkbyte(0);
        checkbyte(0);
        checkbyte(0);
        checkbyte(6);

        /* expected MIDI file version: 0 */
        checkbyte(0);
        if ((ev = fetchbyte()) != 0)
        {
                printf("Wrong MIDI file version: %d\n", ev);
                cleanup();
                exit (3);
        }

        /* expected track number: 1 */
        checkbyte(0);
        if ((ev = fetchbyte()) != 1)
        {
                printf("Wrong number of tracks: %d\n", ev);
                cleanup();
                exit (4);
        }

        /* fetch MIDI timing of that track */
        gl_BeatsPerMin_BPM = ((unsigned int) fetchbyte()) << 8;
        gl_BeatsPerMin_BPM |= (unsigned int) fetchbyte();

        printf("global timeset = %u\n", gl_BeatsPerMin_BPM);

        /* calc us_per_beat in 2 steps as workaround for Z88DK calc error using constants */
        if (gl_BeatsPerMin_BPM < 0x8000)
        {
                // Delta-Ticks (in usec for one minute
                us_per_beat = 60000000;   /* microseconds in one minute */
                us_per_beat /= gl_BeatsPerMin_BPM;
        }
        else
        {
                // SMPTE-Timeformat
                us_per_beat = 60000000;   /* microseconds in one minute */
                us_per_beat /= (-(char)(gl_BeatsPerMin_BPM>>8))/((unsigned char)gl_BeatsPerMin_BPM);
                printf("SMPTE time format is used.\n");
        }
#if 0
        /* calc OCR1A in 2 steps as workaround for Z88DK calc error using constants */
        if (gl_BeatsPerMin_BPM < 0x8000)
        {
                // Delta-Ticks
                OCR1A = 500000;
                OCR1A /= (unsigned long) gl_BeatsPerMin_BPM;
        }
        else
        {
                // SMPTE-Timeformat
                OCR1A = 1000000;
                OCR1A /= (-(char)(gl_BeatsPerMin_BPM>>8))/(unsigned char)gl_BeatsPerMin_BPM;
        }
#endif
        printf("beat length in us = %lu\n", us_per_beat);

        /* expect MIDI track MTrk and fetch its length */
        checkstring("MTrk", 4);

        temp = (unsigned long)fetchbyte() << 24;
        temp |= (unsigned long)fetchbyte() << 16;
        temp |= (unsigned long)fetchbyte() << 8;
        temp |= (unsigned long)fetchbyte();
        trk_len = temp;
        printf ("track length = %lu\n");

    if (in_Inkey())
    {
            last_cmd = 0; /* If key pressed forget old status */
    }
    if (last_cmd)
    {
                debug_enabled = (last_cmd == 'D');
                gimmicks_enabled = (last_cmd == 'G');
    }
    else
    {
                   in_WaitForNoKey();
                printf("press any key or\nAbort, Diag on, Gimmicks on\n", trk_len);
                do
                {
                        ev = in_Inkey();
                }
                while (!ev);
                switch (ev)
                {
                        case 'D':
                                debug_enabled = 1;
                                break;
                        case 'G':
                                gimmicks_enabled = 1;
                                printf("gimmicks enabled. keys are:\n6/7 to dec/inc speed\n8 double speed, 0 normal speed\n");
                                {
                                        if (speed_nominator)
                                                new_nominator = 3200/speed_nominator;
                                        else
                                                new_nominator = 100;
                                        printf("enter speed in %% (RET for %u%%):", new_nominator);
//                                                if (*buffer)
//                                                        sscanf(&buffer, "%d", &new_nominator);
// Problem is "scanf"
//                                        scanf("%d", &new_nominator);
                                        speed_nominator = 3200/new_nominator;
                                        if (speed_nominator == 32)
                                                speed_nominator = 0;
                                }
                                break;
                        case 'A':
                                break_detected();
                                break;
                }
                last_cmd = ev;                 /* save command for next run */
        }
        /* Setup timer variables used by NMI routine */
        time_stamp_last_event = isr_time = 0;

        /* ZX81 NMI: 16000 = 1 sec, 16 = 1msec , 1 = 207 clock cycles/64 usec */
        setup_NMI_Timer_in_us(us_per_beat);
        start_NMI_Timer();

        while (trk_len)
        {
                wait_for_deltatime();
                ev = fetchbyte();
                if (ev == 0xFF) {        // META-EVENT
                        ev = fetchbyte();
                        switch (ev) {
                                case 0x2f:        // End Of Track
                                        checkbyte(0);
                                        trk_len = 0;
                                        break;

                                case 0x51:        // Tempo Change
                                        checkbyte(3);
//                                        temp = (unsigned long)fetchbyte() << 16;
//                                        temp |= (unsigned long)fetchbyte() << 8;
//                                        temp |= (unsigned long)fetchbyte();
                                        #asm
                                        call _fetchbyte                ; H is 0, L holds value
                                        ld (_temp+2), HL        ; update temp+3 and temp+2
                                        call _fetchbyte
                                        ld a, l
                                        ld (_temp+1), a
                                        call _fetchbyte
                                        ld a, l
                                        ld (_temp), a
                                        #endasm
                                        us_per_beat = temp / gl_BeatsPerMin_BPM;

                                        if (debug_enabled)
                                                printf("Tempo=%lu,BPM=%u,usec=%lu\n", temp, gl_BeatsPerMin_BPM, us_per_beat);
                                        setup_NMI_Timer_in_us(us_per_beat);
                                        break;

                                default:                 // Ignore unknown META-EVENT
                                        temp = fetch_varlen();
                                        while (temp--)
                                        {
                                                fetchbyte();
                                        }
                        }
                }
                else if (ev == 0xf0)
                {        // SYSEX
                        sendbyte(ev);
                        temp = fetch_varlen();
                        while (temp--)
                        {
                                sendbyte(fetchbyte());
                        }
                }
                else if (ev & 0x80)
                {        // MIDI-EVENT
                        sendbyte(ev);
                        switch (ev)
                        {
                                case 0xf2:
                                        len = 2;
                                        break;
                                case 0xf3:
                                        len = 1;
                                        break;
                                default:
                                        switch (ev & 0xf0)
                                        {
                                                case 0xf0:
                                                        len = 0;
                                                        break;
                                                case 0xc0:        // ProgramChange
                                                case 0xd0:        // ChannelAftertouch
                                                        len = 1;
                                                        break;
                                                case 0x80:        // NoteOff
                                                case 0x90:        // NoteOn
                                                case 0xa0:        // PolyphonicAftertouch
                                                case 0xb0:        // ControlModeChange
                                                case 0xe0:        // PitchWheel
                                                        len = 2;
                                                        break;
                                                default:
                                                        printf("Unknown MIDI event type %d\n", ev);
                                                        cleanup();
                                                        exit(7);
//no break?
                                        }
//no break?
                        }
                        adp = len;
                        while (len--) {
                                sendbyte(fetchbyte());
                        }
                }
                else
                {        // Running Status
                        sendbyte(ev);
                        len = adp-1;
                        while (len--)
                                sendbyte(fetchbyte());
                }
        }
        printf("Feddisch ;-)\n");
        printf("Total time %lu ticks\n", isr_time);
        cleanup();
        exit(0);
}

void cleanup(void)
{
        send_MIDI_reset();
        stop_NMI_Timer();
        flush_remaining_bytes();
        printf("Delays/overrun:%lu/%u\n", debug_delay_calls, debug_wait_too_slow);
        if (debug_enabled)
        {
                printf("Min ticks(us):%lu,Max:%lu\n", debug_min_ticks, debug_max_ticks);
                printf("Min delay:%lu,Max:%lu\n", debug_min_delays, debug_max_delays);
        }
}


/* NMI interrupt routine. Called from ROM via register IX */

TIMER_ISR()
{
        #asm
        ; NMI is stopped, alternate reg A active and is 0
        ; restart NMI to count own runtime
        out ($FE), a

        ; count high byte counter down
        ld hl, (_isr_high_count)
        ld a, h
        or l
        dec hl
        ld (_isr_high_count), hl
        ld a, 0                 ; reload A for next 256 NMIs
        jr nz, ISR_CONT
;        isr_time++;         // count time since start of track
        ld        hl,(_isr_time)
        ld        de,(_isr_time+2)
        call        l_inclong
        ld        (_isr_time),hl
        ld        (_isr_time+2),de
        ; restart timer
        ld a, (_isr_low_count_start)
        ld hl, (_isr_high_count_start)
        ld (_isr_high_count), hl

ISR_CONT:
        ex af, af
;        out ($FE), a
        pop hl
        pop de
        pop bc
        pop af
        #endasm
}
Problem does NOT occur on my EO!
Maybe some system variables get corrupted, which are maybe ignored by EO?

Problem also occurs using my old Z88DK installation.
Any ideas?

Siggi
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

If it's after load (so before it's run) then no matter what code you put in there it shouldn't make a difference.

To prove it, does the problem happen when you pad out the original file to the same length? What happens if you make the file longer? - There must be a magic length that causes the problem?

Does the problem happen when autorun is enabled?

I can see that the system variables are set by app make - do you mind checking that we're setting them to right values? Source is here: https://github.com/z88dk/z88dk/blob/mas ... ake/zx81.c
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

The problem also occurs then autorun is enabled.
The problem does not occur, after I have decreased the size of the P file (currently 8161 of the P file).
The problem does occur again at length 8275. Maybe 8192 is the limit?

Should I provide some file listings (assembly or other) of both program size versions?

Maybe when linking the code of SCANF some memory (DFILE/screen contents) is overwritten????
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

The problem does also not occur, if the program is > 8192 (9k), but SCANF is not used.
So it's not a problem of the size itself, only in conjunction with the usage of SCANF.

But after I have added the SCANF-call to the 9K program, the problem does also not occur .... ??? ???

Siggi
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

It's all very strange - but I think scanf can be exonerated.

Are there any issues with the screen straddling an 8k boundary?

Map files would be useful to figure out where various bits of memory are sitting (compile with -m)
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

Sorry, I cannot post a map file here:

"Posts cannot be longer that 65535 characters (64 KB)."

What to do now?

Siggi
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

That ist my latest code:

Code: Select all

//zcc +zx81 -startup=2 -create-app -Cz--disable-autorun -v -O2 -o MidiPlay.bin MidiPlayer.c

// ZX81 Midiplayer V1.00
// Ported from
// "Mr.MidiPlayer v1.0" (see https://www.mikrocontroller.net/topic/48542)
// and adapted to ZX81 .
// NMI is used as timer, counting in 64 us ticks @ 3,25 MHz
// Made by Siggi, March 2018
//
// Hardware: ZX81, RS232 board (8251 compatible, ctrl/data at $EB/E3)
//                         USB-Interface
// Software: USB-Driver at 8192 (READM-routine at $200C to read an open file)
//

#include <stdlib.h>
#include <zx81.h>
#include <stdio.h>
#include <input.h>

/* globals */
unsigned long us_per_beat;                        /* timing register in microseconds */

unsigned long trk_len;

unsigned int block_counter;

unsigned int timer_running;
unsigned long time_stamp_last_event;

/* Variables handled inside interrupt routine: Handle with care!
/* read by interrupt routine */
unsigned char isr_low_count_start;
unsigned int  isr_high_count_start;
/* updated by interrupt routine */
unsigned char isr_low_count;
unsigned int  isr_high_count;
unsigned long isr_time;

/* Variables used for analysis/debugging */
static unsigned char debug_enabled = 0;
unsigned int debug_wait_too_slow;
unsigned long debug_min_ticks;
unsigned long debug_max_ticks;
unsigned long debug_min_delays;
unsigned long debug_max_delays;
unsigned long debug_delay_calls;

/* Variables used for gimmicks */
static unsigned char gimmicks_enabled = 0;
static unsigned char speed_nominator = 0;
int new_nominator;

/* For handling of the startup behaviour */
static unsigned char last_cmd = 0;

/* Prototypes */

void send_MIDI_reset();
void cleanup(void);
void setup_NMI_Timer_in_us(unsigned long tickLength);
void start_NMI_Timer(void);
void stop_NMI_Timer(void);

// MCU initialisieren
void hw_init(void)
{
        send_MIDI_reset();
        debug_min_ticks=0XFFFFFFFF;
        debug_max_ticks=0;
        debug_min_delays=0XFFFFFFFF;
        debug_max_delays=0;
        debug_wait_too_slow = 0;
        debug_delay_calls = 0;
}


void __FASTCALL__ sendbyte(unsigned short byte)
{
#asm
                ; byte is in HL
SENDWAIT:
        IN A,($EB)           ;DBEB
        AND 4
        JR Z,SENDWAIT
        LD A, L
        OUT ($E3), A        ;D3E3
#endasm
}


// All midi off
void send_MIDI_reset(void)
{
        sendbyte(0xF0);
        sendbyte(0x7E);
        sendbyte(0x7F);
        sendbyte(0x09);
        sendbyte(0x01);
        sendbyte(0xF7);
}

unsigned char fetchbyte(void)
{
        if (!block_counter)
        {
                block_counter = 4096;
                /* request next block from file */
                #asm
                ld bc,(_block_counter)
                scf
                call $200C
                #endasm
        }
        /* read 1 char */
        #asm
;        block_counter--;
        ld        hl,(_block_counter)
        dec        hl
        ld        (_block_counter),hl
;        trk_len--;
        ld        hl,(_trk_len)
        ld        de,(_trk_len+2)
        call        l_declong
        ld        (_trk_len),hl
        ld        (_trk_len+2),de
        and a                                        ; clear carry
        call $200C
        ld l, a
        ld h, 0                                 ; HL holds return value
        ret z
        ld hl, 0                                ; ABORT detected: clear block counter
        ld        (_block_counter),hl
        call nz,_break_detected        ; keypress A has been detected
        #endasm
}

void flush_remaining_bytes(void)
{
        while (block_counter)
        {
                fetchbyte();
        }
}

void checkbyte (unsigned char b)
{
        if (b == fetchbyte())
                return;

        printf("Checkbyte failed: %d\n", b);
        cleanup();
        exit(5);
}


char checkstring(unsigned char *buf, unsigned char len) {
        unsigned char b, cnt=0;

        for (cnt=0; cnt<len; cnt++) {
                b = fetchbyte();
                if (b != buf[cnt])
                {
                        printf("Checkstring failed: %d instead of %d\n", b, buf[cnt]);
                        return 0;
                }
        }
        return 1;
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned long v;
unsigned char c;

unsigned long fetch_varlen(void)
{
        c = fetchbyte();
        v = c & 0x7F;
        while (c & 0x80)
        {
                /* one more byte follows */
                c = fetchbyte();
                v = (v << 7) + (c & 0x7F);
        }
        return v;
}

void break_detected(void)
{
        printf("ABORT\n");
        cleanup();
        exit(1); /* key "A" has been pressed */
}

unsigned char my_in_Inkey(void)
{
        #asm
        ld h,0
        ld a,$EF        ; bit 4 low: decode keyboard line 6-0
        in a,($FE)
        bit 4,a
        ld l,'6'
        ret z          ; key 6
        bit 3,a
        ld l,'7'
        ret z          ; key 7
        bit 2,a
        ld l,'8'
        ret z          ; key 8
        bit 1,a
        ld l,'9'
        ret z          ; key 9
        bit 0,a
        ld l,'0'
        ret z          ; key 0
        ld l,$ff       ; no key
        #endasm
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned long end_time;

void wait_for_deltatime(void)
{
        /* read dt from file */
        /* Note: Timer is runnig and counts also time during file access! */
        end_time = fetch_varlen();

        if (end_time) /* do nothing if delay is 0 */
        {
                debug_delay_calls++;
                if (debug_enabled)
                {
                       if (end_time < debug_min_delays)
                               debug_min_delays = end_time;
                       if (end_time > debug_max_delays)
                               debug_max_delays = end_time;
        }
                end_time += time_stamp_last_event;
                do
                {
                        #asm
                re_check:
                        ld bc,(_isr_time+2)                                ; get high bytes of isr_time
                        ld de,(_isr_time)                                ; get low bytes of isr_time
                        ld hl,(_isr_time)                                ; get low bytes again to detect change by NMI (will affect at least low bytes)
                        and a                                                        ; clear carry bit
                        sbc hl, de                                                ; check for no change
                        jr nz, re_check                                        ; try again if NMI occurred
                        ld (_time_stamp_last_event), de        ; store low bytes
                        ld (_time_stamp_last_event+2), bc ; store high bytes
                        #endasm
                        if (time_stamp_last_event == end_time)
                                return;
                        if (time_stamp_last_event > end_time)
                        {
                                debug_wait_too_slow++;
                                return;
                        }

                        if (gimmicks_enabled)
                        {
                                /* here we have time for gimmicks */
                                switch (my_in_Inkey())
                                {
                                        case '7':  /* more speed */
                                                if (speed_nominator)
                                                        speed_nominator--;
                                                else
                                                        speed_nominator = 31;
                                                setup_NMI_Timer_in_us(us_per_beat);
                                                in_WaitForNoKey();  /* wait for key released */
                                                break;

                                        case '6':  /*less speed */
                                                if (speed_nominator)
                                                        speed_nominator++;
                                                else
                                                        speed_nominator = 33;
                                                setup_NMI_Timer_in_us(us_per_beat);;
                                                in_WaitForNoKey();  /* wait for key released */
                                                break;

                                        case '0':  /* normal speed */
                                                speed_nominator = 0;
                                                setup_NMI_Timer_in_us(us_per_beat);
                                                break;

                                        case '9':  /* half speed */
                                                speed_nominator = 64;
                                                setup_NMI_Timer_in_us(us_per_beat);
                                                break;

                                        case '8':  /* double speed */
                                                speed_nominator = 16;
                                                setup_NMI_Timer_in_us(us_per_beat);;
                                                break;
                                }
                                if (speed_nominator == 32)
                                        speed_nominator = 0;
                        }
                }
                while(1);
        }
}

/* start NMI timer */
void start_NMI_Timer(void)
{
        zx_fast();
        #asm
        ex af, af
        ld a, (_isr_low_count_start)                        ; set alternate A with low counter
        ex af, af
        ld hl, (_isr_high_count_start)                        ; setup high counter
        ld (_isr_high_count), hl
        ld iy, _TIMER_ISR                                                ; iy will get ix during assembly for ZX81!
        out ($FE), a                                                        ; start NMI generator
        #endasm
        timer_running = 1;
}

/* stop running NMI timer and restore BASIC SLOW mode */
void stop_NMI_Timer(void)
{
        if (timer_running)
        {
                timer_running = 0;
        }
        zx_slow();
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned char low;
unsigned int high;

/* setup the NMI timer in unit usec. May be also be used while NMI counter is running */
void setup_NMI_Timer_in_us(unsigned long tickLength)
{
        if (debug_enabled)
        {
                if (tickLength < debug_min_ticks)
                        debug_min_ticks = tickLength;
                if (tickLength > debug_max_ticks)
                        debug_max_ticks = tickLength;
    }
        /*        convert to other speed */
        if (gimmicks_enabled && speed_nominator)
        {
                tickLength = (tickLength * speed_nominator) >> 5; /* denominator is 32 */
        }
         /* convert microseconds into ZX81 NMI timing @ 3,25 MHz:
         /* do some timing corrections and the divide by 64 (64 usec/NMI) */
        if (tickLength & 0xFFFFFE00) /* > 512 */
                tickLength -= 150;
        else
                tickLength -= 96;
        tickLength = tickLength >> 6;
        if(debug_enabled)
        {
                if (!tickLength)
                        printf("Warning: NMI ticklen 0\n");
                if (tickLength > 1000000)
                        printf("Warning: ticklen > 1000000\n");
        }
        // ticklen        |low        |high
        //        0                        0                0        invalid -> 256
        //  1                        FF                0
        //        2                        FE                0
        //        254                        02                0
        //        255                        01                0
        //        256                        00                0        NMI counts A to 0 -> reload value is 0 for next 256 NMIs
        //        257                        FF                1        count 1 by A and 256 by high
        //        258                        FE                1        count 2 by A and 256 by high
        //        511                        01                1
        //        512                        00                1
        //        513                        FF                2

        low = 256 - (unsigned int) (tickLength & 0xFF); /* bit 0..7  */
        high = (tickLength - 1) >> 8;  /* bit 8..23 */
        if (debug_enabled)
                printf("NMIs(%lu),L=%u,H=%u\n", tickLength, low, high);
        if (timer_running)
        {
                #asm
                halt ; update values handled in interrupt routine quickly after interrupt
                #endasm
        }
        isr_low_count_start = low;
        isr_high_count_start = high;
        if (!timer_running)
        {
                /* update also current state of high counter */
                isr_high_count = high;
        }
}

/* global vars used here. Access to globals ist faster than to locals */
unsigned char ev, len, adp;
unsigned int gl_BeatsPerMin_BPM;
unsigned long temp;


void main(void)
{
        printf("+ zx81 rs232 MIDIPLAYER v1.00 +\n\n");
        hw_init();
        gl_BeatsPerMin_BPM = 120;  /* default MIDI timing */
        us_per_beat = 500000L;     /* correspondens to beat length 0,5 sec = 1/120 min */

        /* for file buffer handling */
        block_counter = 0;

        /* expected MIDI file header "MThd" */
        ev = 0;
        while ((!checkstring("M", 1)) && (ev < 4))
        {
                ev++;
        }

        if (!checkstring("Thd", 3))
        {
                printf("No MIDI file.\n");
                cleanup();
                exit (2);
        }

        /* expected length: 6 bytes as 32 bit number */
        checkbyte(0);
        checkbyte(0);
        checkbyte(0);
        checkbyte(6);

        /* expected MIDI file version: 0 */
        checkbyte(0);
        if ((ev = fetchbyte()) != 0)
        {
                printf("Wrong MIDI file version: %d\n", ev);
                cleanup();
                exit (3);
        }

        /* expected track number: 1 */
        checkbyte(0);
        if ((ev = fetchbyte()) != 1)
        {
                printf("Wrong number of tracks: %d\n", ev);
                cleanup();
                exit (4);
        }

        /* fetch MIDI timing of that track */
        gl_BeatsPerMin_BPM = ((unsigned int) fetchbyte()) << 8;
        gl_BeatsPerMin_BPM |= (unsigned int) fetchbyte();

        printf("global timeset = %u\n", gl_BeatsPerMin_BPM);

        /* calc us_per_beat in 2 steps as workaround for Z88DK calc error using constants */
        if (gl_BeatsPerMin_BPM < 0x8000)
        {
                // Delta-Ticks (in usec for one minute
                us_per_beat = 60000000;   /* microseconds in one minute */
                us_per_beat /= gl_BeatsPerMin_BPM;
        }
        else
        {
                // SMPTE-Timeformat
                us_per_beat = 60000000;   /* microseconds in one minute */
                us_per_beat /= (-(char)(gl_BeatsPerMin_BPM>>8))/((unsigned char)gl_BeatsPerMin_BPM);
                printf("SMPTE time format is used.\n");
        }

        printf("beat length in us = %lu\n", us_per_beat);

        /* expect MIDI track "MTrk" and fetch its length */
        checkstring("MTrk", 4);

        temp = (unsigned long)fetchbyte() << 24;
        temp |= (unsigned long)fetchbyte() << 16;
        temp |= (unsigned long)fetchbyte() << 8;
        temp |= (unsigned long)fetchbyte();
        trk_len = temp;
        printf ("track length = %lu\n", trk_len);

    if (in_Inkey())
    {
            last_cmd = 0; /* If key pressed during startup forget old status */
    }

    if (!last_cmd)
    {
                   in_WaitForNoKey();
                printf("Current status:\ndiag is %s, gimmicks are %s\n",
                        debug_enabled ? "ON" : "OFF", gimmicks_enabled ? "ON" : "OFF");
                if (speed_nominator)
                        new_nominator = 3200/speed_nominator;
                else
                        new_nominator = 100;
                if (gimmicks_enabled)
                {
                        printf("speed in percent: %u\n", new_nominator);
                }
                printf("press any key or\nAbort, Diag %s, Gimmicks %s\n",
                         debug_enabled ? "off" : "on", gimmicks_enabled ? "off" : "on");
                do
                {
                        ev = in_Inkey();
                }
                while (!ev);
                switch (ev)
                {
                        case 'D':
                                debug_enabled = !debug_enabled;
                                break;
                        case 'G':
                                gimmicks_enabled = !gimmicks_enabled;
                                if (gimmicks_enabled)
                                {
                                        unsigned char buffer[20];
                                        printf("gimmicks are enabled.\nkeys are:\n6/7 to dec/inc speed\n8/9/0 double/half/normal speed\n");
                                        printf("enter speed in percent\n(RET for %u):", new_nominator);
// Problem is "scanf"
                                        scanf("%d", &new_nominator);
//                                        gets(buffer);
//                                        if (*buffer)
//                                                new_nominator = atoi(buffer);

                            new_nominator = 100;
                                        speed_nominator = 3200/new_nominator;
                                        if (speed_nominator == 32)
                                                speed_nominator = 0;
                                }
                                break;
                        case 'A':
                                break_detected();
                                break;
                }
                last_cmd = ev;                 /* save command for next run */
        }
        /* Setup timer variables used by NMI routine */
        time_stamp_last_event = isr_time = 0;

        /* ZX81 NMI: 16000 = 1 sec, 16 = 1msec , 1 = 207 clock cycles/64 usec */
        setup_NMI_Timer_in_us(us_per_beat);
        start_NMI_Timer();

        while (trk_len)
        {
                wait_for_deltatime();
                ev = fetchbyte();
                if (ev == 0xFF) {        // META-EVENT
                        ev = fetchbyte();
                        switch (ev) {
                                case 0x2f:        // End Of Track
                                        checkbyte(0);
                                        trk_len = 0;
                                        break;

                                case 0x51:        // Tempo Change
                                        checkbyte(3);
//                                        temp = (unsigned long)fetchbyte() << 16;
//                                        temp |= (unsigned long)fetchbyte() << 8;
//                                        temp |= (unsigned long)fetchbyte();
                                        #asm
                                        call _fetchbyte                ; H is 0, L holds value
                                        ld (_temp+2), HL        ; update temp+3 and temp+2
                                        call _fetchbyte
                                        ld a, l
                                        ld (_temp+1), a
                                        call _fetchbyte
                                        ld a, l
                                        ld (_temp), a
                                        #endasm
                                        us_per_beat = temp / gl_BeatsPerMin_BPM;

                                        if (debug_enabled)
                                                printf("Tempo=%lu,BPM=%u,usec=%lu\n", temp, gl_BeatsPerMin_BPM, us_per_beat);
                                        setup_NMI_Timer_in_us(us_per_beat);
                                        break;

                                default:                 // Ignore unknown META-EVENT
                                        temp = fetch_varlen();
                                        while (temp--)
                                        {
                                                fetchbyte();
                                        }
                        }
                }
                else if (ev == 0xf0)
                {        // SYSEX
                        sendbyte(ev);
                        temp = fetch_varlen();
                        while (temp--)
                        {
                                sendbyte(fetchbyte());
                        }
                }
                else if (ev & 0x80)
                {        // MIDI-EVENT
                        sendbyte(ev);
                        switch (ev)
                        {
                                case 0xf2:
                                        len = 2;
                                        break;
                                case 0xf3:
                                        len = 1;
                                        break;
                                default:
                                        switch (ev & 0xf0)
                                        {
                                                case 0xf0:
                                                        len = 0;
                                                        break;
                                                case 0xc0:        // ProgramChange
                                                case 0xd0:        // ChannelAftertouch
                                                        len = 1;
                                                        break;
                                                case 0x80:        // NoteOff
                                                case 0x90:        // NoteOn
                                                case 0xa0:        // PolyphonicAftertouch
                                                case 0xb0:        // ControlModeChange
                                                case 0xe0:        // PitchWheel
                                                        len = 2;
                                                        break;
                                                default:
                                                        printf("Unknown MIDI event type %d\n", ev);
                                                        cleanup();
                                                        exit(7);
                                                        break;
                                        }
                                        break;
                        }
                        adp = len;
                        while (len--) {
                                sendbyte(fetchbyte());
                        }
                }
                else
                {        // Running Status
                        sendbyte(ev);
                        len = adp-1;
                        while (len--)
                                sendbyte(fetchbyte());
                }
        }
        printf("Feddisch ;-)\n");
        printf("Total time %lu ticks\n", isr_time);
        cleanup();
        exit(0);
}

void cleanup(void)
{
        send_MIDI_reset();
        stop_NMI_Timer();
        flush_remaining_bytes();
        printf("Delays/overrun:%lu/%u\n", debug_delay_calls, debug_wait_too_slow);
        if (debug_enabled)
        {
                printf("Min ticks(us):%lu,Max:%lu\n", debug_min_ticks, debug_max_ticks);
                printf("Min delay:%lu,Max:%lu\n", debug_min_delays, debug_max_delays);
        }
}


/* NMI interrupt routine. Called from ROM via register IX */

TIMER_ISR()
{
        #asm
        ; NMI is stopped, alternate reg A active and is 0
        ; restart NMI to count own runtime
        out ($FE), a

        ; count high byte counter down
        ld hl, (_isr_high_count)
        ld a, h
        or l
        dec hl
        ld (_isr_high_count), hl
        ld a, 0                 ; reload A for next 256 NMIs
        jr nz, ISR_CONT
;        isr_time++;         // count time since start of track
        ld        hl,(_isr_time)
        ld        de,(_isr_time+2)
        call        l_inclong
        ld        (_isr_time),hl
        ld        (_isr_time+2),de
        ; restart timer
        ld a, (_isr_low_count_start)
        ld hl, (_isr_high_count_start)
        ld (_isr_high_count), hl

ISR_CONT:
        ex af, af
;        out ($FE), a
        pop hl
        pop de
        pop bc
        pop af
        #endasm
}
And the batch file to make it:

Code: Select all

zcc +zx81 -startup=2 --c-code-in-asm -a -vn -O2 -o Midiplay.asm MidiPlayer.c
zcc +zx81 -startup=2 -m -create-app -Cz--disable-autorun -vn -O2 -o MidiPlay.bin MidiPlayer.c
rem zcc +zx81 -startup=2 -create-app -vn -O2 -o MidiPlay.bin MidiPlayer.c
Maybe you can make your own map file (current nigthly build)?

Siggi
User avatar
dom
Well known member
Posts: 1362
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

I checked last night and everything looked like it ended up in the right place.

The system variables set by the .p file are correct - and if they were wrong it would affect all file sizes.

So at this point I can?t see anything wrong. Have you got another ZX81 that shows the same behaviour?
stefano
Well known member
Posts: 1662
Joined: Mon Jul 16, 2007 7:39 pm

Post by stefano »

>my midiplayer did run as expected, until I added a "sscanf" call. Then immediately after LOAD (NOT RUN!!!) the zeddy crashes and the >screen flickers. Same problem for "scanf"!
>The generated application them seems to be corrupted!

Could it be a forbidden register being accidentally hit ? a' or iy (translated to ix) ?
Post Reply