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
}
Siggi