moved from old repository

This commit is contained in:
antoniovillena 2021-06-06 09:09:54 +02:00
parent 9f9327bee2
commit fa046b7b47
24 changed files with 3499 additions and 0 deletions

BIN
software/joyconf/JOYCONF Normal file

Binary file not shown.

677
software/joyconf/joyconf.c Normal file
View File

@ -0,0 +1,677 @@
/*
Compilar con:
sdcc -mz80 --reserve-regs-iy --opt-code-size --max-allocs-per-node 10000
--nostdlib --nostdinc --no-std-crt0 --code-loc 8192 joyconf.c
*/
typedef unsigned char BYTE;
typedef unsigned short WORD;
enum {IBLACK=0, IBLUE, IRED, IMAG, IGREEN, ICYAN, IYELLOW, IWHITE};
enum {PBLACK=0, PBLUE=8, PRED=16, PMAG=24, PGREEN=32, PCYAN=40, PYELLOW=48, PWHITE=56};
#define BRIGHT 64
#define FLASH 128
__sfr __at (0xfe) ULA;
__sfr __at (0xff) ATTR;
__sfr __at (0x1f) KEMPSTONADDR;
__sfr __at (0x7f) FULLERADDR;
__sfr __banked __at (0xf7fe) SEMIFILA1;
__sfr __banked __at (0xeffe) SEMIFILA2;
__sfr __banked __at (0xfbfe) SEMIFILA3;
__sfr __banked __at (0xdffe) SEMIFILA4;
__sfr __banked __at (0xfdfe) SEMIFILA5;
__sfr __banked __at (0xbffe) SEMIFILA6;
__sfr __banked __at (0xfefe) SEMIFILA7;
__sfr __banked __at (0x7ffe) SEMIFILA8;
#define ATTRP 23693
#define ATTRT 23695
#define BORDR 23624
#define LASTK 23560
#define WAIT_VRETRACE __asm halt __endasm
#define WAIT_HRETRACE while(ATTR!=0xff)
#define SETCOLOR(x) *(BYTE *)(ATTRP)=(x)
#define LASTKEY *(BYTE *)(LASTK)
#define ATTRPERMANENT *((BYTE *)(ATTRP))
#define ATTRTEMPORARY *((BYTE *)(ATTRT))
#define BORDERCOLOR *((BYTE *)(BORDR))
__sfr __banked __at (0xfc3b) ZXUNOADDR;
__sfr __banked __at (0xfd3b) ZXUNODATA;
#define MASTERCONF 0
#define SCANCODE 4
#define KEYBSTAT 5
#define JOYCONF 6
#define COREID 255
void __sdcc_enter_ix (void) __naked;
void cls (BYTE);
void border (BYTE);
void locate (BYTE, BYTE);
void puts (BYTE *);
void putdec (WORD);
void u16todec (WORD, char *);
void u16tohex (WORD, char *);
void u8tohex (BYTE, char *);
void memset (BYTE *, BYTE, WORD);
void memcpy (BYTE *, BYTE *, WORD);
int abs (int);
signed char sgn (int);
long frames (void) __naked;
void pause (BYTE);
void wait_key (void);
BYTE inkey (BYTE, BYTE);
void beep (WORD, BYTE) __critical;
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
BYTE printconf (void);
void printstatictext (void);
void getcoreid(BYTE *s);
void interactivemode (void);
void usage (void);
void commandlinemode (char *p);
BYTE main (char *p);
void init (void) __naked
{
__asm
push hl
call _main
pop af
or a ;reset carry: clean exit
ld b,h
ld c,l
ret
__endasm;
}
BYTE main (char *p)
{
if (!p)
interactivemode();
else
commandlinemode(p);
return 0;
}
void commandlinemode (char *p)
{
BYTE kbdconf, db9conf;
ZXUNOADDR = JOYCONF;
kbdconf = ZXUNODATA & 0xf;
db9conf = (ZXUNODATA>>4) & 0xf;
while (*p!=0 && *p!=0xd && *p!=':')
{
if (*p==' ')
{
p++;
continue;
}
if (*p=='-')
{
p++;
if (*p=='k')
{
p++;
kbdconf &= 0x8;
switch (*p)
{
case 'd': kbdconf |= 0; break;
case 'k': kbdconf |= 1; break;
case '1': kbdconf |= 2; break;
case '2': kbdconf |= 3; break;
case 'c': kbdconf |= 4; break;
case 'f': kbdconf |= 5; break;
default: usage(); return;
}
p++;
if (*p=='1')
{
kbdconf |= 0x8;
p++;
}
else if (*p=='0')
{
kbdconf &= 0x7;
p++;
}
}
else if (*p=='j')
{
p++;
db9conf &= 0x8;
switch (*p)
{
case 'd': db9conf |= 0; break;
case 'k': db9conf |= 1; break;
case '1': db9conf |= 2; break;
case '2': db9conf |= 3; break;
case 'c': db9conf |= 4; break;
case 'f': db9conf |= 5; break;
case 'o': db9conf |= 6; break;
default: usage(); return;
}
p++;
if (*p=='1')
{
db9conf |= 0x8;
p++;
}
else if (*p=='0')
{
db9conf &= 0x7;
p++;
}
}
}
else
{
usage();
return;
}
}
ZXUNODATA = db9conf<<4 | kbdconf;
}
void usage (void)
{
// 01234567890123456789012345678901
puts ("Configures/tests protocols for\xd"
"keyb joystick and DB9 joystick\xd\xd"
"Usage: JOYCONF [-kAx] [-jBx]\xd"
" where A,B can be:\xd"
" d: Disabled\xd"
" k: Kempston\xd"
" 1: Sinclair port 1\xd"
" 2: Sinclair port 2\xd"
" c: Cursor/Protek/AGF\xd"
" f: Fuller\xd"
" o: OPQA-SPACE-M (DB9 only)\xd"
" where x can be:\xd"
" 0: disable autofire\xd"
" 1: enable autofire\xd"
" other/none: keep setting\xd"
" No arguments: interactive mode\xd\xd"
"Example: .joyconf -kc0 -jk1\xd"
"Sets Cursor, no autofire for the"
"keyboard joystick, and Kempston\xd"
"w/autofire for the DB9 joystick.\xd");
}
void interactivemode (void)
{
BYTE bkbor, bkattr;
bkbor = BORDERCOLOR;
bkattr = ATTRPERMANENT;
border(IBLACK);
cls(BRIGHT|PBLACK|IWHITE);
printstatictext();
while(!printconf());
border(bkbor);
cls(bkattr);
}
void printstatictext (void)
{
char coreid[32];
locate(0,0);
puts ("\x4\x78JOYSTICK CONFIGURATION AND TEST ");
locate(2,0);
puts ("KBD joystick: ");
locate(3,0);
puts ("DB9 joystick: ");
locate(5,0);
puts("\x4\x45T/G to change KBD/DB9 protocol");
locate(6,0);
puts("\x4\x45W/S to change KBD/DB9 autofire");
locate(20,0);
puts("\x4\x47ZX-UNO Core ID: ");
coreid[0]=0x4;
coreid[1]=0x46;
getcoreid(coreid+2);
if (coreid[2]==0)
puts("\x4\x46NOT AVAILABLE");
else
puts(coreid);
locate(21,0);
puts("\x4\x70 Press 'E' to exit ");
}
void getcoreid(BYTE *s)
{
BYTE cont;
volatile BYTE letra;
s[0]='\0';
ZXUNOADDR = COREID;
cont=0;
while (ZXUNODATA!=0 && cont<32) cont++;
if (cont==32)
return;
cont=0;
do
{
letra = ZXUNODATA;
cont++;
}
while (letra==0 && cont<32);
if (cont==32)
return;
*(s++) = letra;
do
{
letra = ZXUNODATA;
*(s++) = letra;
}
while (letra!=0);
}
void printjoystat (char *proto, BYTE joy) // FUDLR
{
puts (proto);
if (joy&0x8)
puts ("\x4\x78 U ");
else
puts (" U ");
if (joy&0x4)
puts ("\x4\x78 D ");
else
puts (" D ");
if (joy&0x2)
puts ("\x4\x78 L ");
else
puts (" L ");
if (joy&0x1)
puts ("\x4\x78 R ");
else
puts (" R ");
if (joy&0x10)
puts ("\x4\x78 F ");
else
puts (" F ");
if (joy&0x20)
puts ("\x4\x78 B2 ");
else
puts (" B2 ");
}
BYTE printconf (void)
{
BYTE kbconf, db9conf, kbdis=0, db9dis=0;
BYTE joy, joy1, joy2, joyb2, joyOP, joyQ, joyA, joySPCM;
WAIT_VRETRACE;
ZXUNOADDR = JOYCONF;
kbconf = ZXUNODATA & 0x0f;
db9conf = (ZXUNODATA >> 4) & 0x0f;
locate(2,14);
switch (kbconf&0x7)
{
case 1: puts("\x4\x46""KEMPSTON"); break;
case 2: puts("\x4\x46""SINCL P1"); break;
case 3: puts("\x4\x46""SINCL P2"); break;
case 4: puts("\x4\x46""CURSOR "); break;
case 5: puts("\x4\x46""FULLER "); break;
default: puts("\x4\x46""DISABLED"); kbdis=1; break;
}
if (!kbdis && kbconf&0x8)
puts("\x4\x45 AUTOFIRE");
else
puts(" ");
locate(3,14);
switch (db9conf&0x7)
{
case 1: puts("\x4\x46""KEMPSTON"); break;
case 2: puts("\x4\x46""SINCL P1"); break;
case 3: puts("\x4\x46""SINCL P2"); break;
case 4: puts("\x4\x46""CURSOR "); break;
case 5: puts("\x4\x46""FULLER "); break;
case 6: puts("\x4\x46""OPQASPCM"); break;
default: puts("\x4\x46""DISABLED"); db9dis=1; break;
}
if (!db9dis && db9conf&0x8)
puts("\x4\x45 AUTOFIRE");
else
puts(" ");
if (LASTKEY == 't' || LASTKEY == 't')
{
kbconf = kbconf&0x8 | (((kbconf&7)+1==6)? 0 : (kbconf&7)+1);
LASTKEY = 0;
}
else if (LASTKEY == 'g' || LASTKEY == 'G')
{
db9conf = db9conf&0x8 | (((db9conf&7)+1==7)? 0 : (db9conf&7)+1); //q
LASTKEY = 0;
}
else if (LASTKEY == 'w' || LASTKEY == 'W')
{
kbconf ^= 0x8;
LASTKEY = 0;
}
else if (LASTKEY == 's' || LASTKEY == 'S')
{
db9conf ^= 0x8;
LASTKEY = 0;
}
ZXUNODATA = db9conf<<4 | kbconf;
joy = KEMPSTONADDR;
locate(8,0); printjoystat("\x4\x7Kempston : ", joy);
joyb2 = ~SEMIFILA7; // -----2-- a --2FUDLR
joy = ~SEMIFILA2; // LRDUF a --2FUDLR
joy = (joyb2&4)<<3 | (joy&1)<<4 | (joy&2)<<2 | (joy&4) | (joy&0x10)>>3 | (joy&8)>>3;
locate(10,0); printjoystat("\x4\x7Sinclair 1: ", joy);
joyb2 = ~SEMIFILA7; // ------2- a --2FUDLR
joy = ~SEMIFILA1; // FUDRL a --2FUDLR
joy = (joyb2&2)<<4 | (joy&0x1c) | (joy&2)>>1 | (joy&1)<<1;
locate(12,0); printjoystat("\x4\x7Sinclair 2: ", joy);
joy1 = ~SEMIFILA2; // DUR2F a --2FUDLR
joy2 = ~SEMIFILA1; // L---- a --2FUDLR
joy = (joy1&2)<<4 | (joy1&1)<<4 | (joy1&8) | (joy1&0x10)>>2 | (joy2&0x10)>>3 | (joy1&4)>>2;
locate(14,0); printjoystat("\x4\x7""Cursor : ", joy);
joy = ~FULLERADDR; // F2--RLDU a --2FUDLR
joy = (joy&0x40)>>1 | (joy&0x80)>>3 | (joy&1)<<3 | (joy&2)<<1 | (joy&4)>>1 | (joy&8)>>3;
locate(16,0); printjoystat("\x4\x7""Fuller : ", joy);
//Nuevo modo joy OPQASPCM
joyOP = ~SEMIFILA4; // ------LR a --2FUDLR
joyQ = ~SEMIFILA3; // -------U a --2FUDLR
joyA = ~SEMIFILA5; // -------D a --2FUDLR
joySPCM = ~SEMIFILA8; // -----2-F a --2FUDLR
joy = (joySPCM&4)<<3 | (joySPCM&1)<<4 | (joyQ&1)<<3 | (joyA&1)<<2 | (joyOP&2) | (joyOP&1);
locate(18,0); printjoystat("\x4\x7""OPQASPCM : ", joy);
//if (LASTKEY==' ')
if (LASTKEY == 'e' || LASTKEY == 'E')
return 1;
else
return 0;
}
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
#pragma disable_warning 85
int abs (int n)
{
return (n>=0)?n:-n;
}
signed char sgn (int n)
{
return (n>=0)?1:-1;
}
long frames (void) __naked
{
__asm
ld a,(#23672)
ld l,a
ld a,(#23673)
ld h,a
ld a,(#23674)
ld e,a
ld d,#0
ret
__endasm;
}
void pause (BYTE frames)
{
BYTE i;
for (i=0;i!=frames;i++)
{
WAIT_VRETRACE;
}
}
void memset (BYTE *dir, BYTE val, WORD nby)
{
__asm
push bc
push de
ld l,4(ix)
ld h,5(ix)
ld a,6(ix)
ld c,7(ix)
ld b,8(ix)
ld d,h
ld e,l
inc de
dec bc
ld (hl),a
ldir
pop de
pop bc
__endasm;
}
void memcpy (BYTE *dst, BYTE *fue, WORD nby)
{
__asm
push bc
push de
ld e,4(ix)
ld d,5(ix)
ld l,6(ix)
ld h,7(ix)
ld c,8(ix)
ld b,9(ix)
ldir
pop de
pop bc
__endasm;
}
void cls (BYTE attr)
{
memset((BYTE *)16384,0,6144);
memset((BYTE *)22528,attr,768);
SETCOLOR(attr);
}
void border (BYTE b)
{
ULA=(b>>3)&0x7;
*((BYTE *)BORDR)=b;
}
void puts (BYTE *str)
{
__asm
push bc
push de
ld a,(#ATTRT)
push af
ld a,(#ATTRP)
ld (#ATTRT),a
ld l,4(ix)
ld h,5(ix)
buc_print:
ld a,(hl)
or a
jp z,fin_print
cp #4
jr nz,no_attr
inc hl
ld a,(hl)
ld (#ATTRT),a
inc hl
jr buc_print
no_attr:
rst #16
inc hl
jp buc_print
fin_print:
pop af
ld (#ATTRT),a
pop de
pop bc
__endasm;
}
void locate (BYTE row, BYTE col)
{
__asm
push bc
push de
ld a,#22
rst #16
ld a,4(ix)
rst #16
ld a,5(ix)
rst #16
pop de
pop bc
__endasm;
}
// void putdec (WORD n)
// {
// BYTE num[6];
//
// u16todec (n,num);
// puts (num);
// }
//
// void u16todec (WORD n, char *s)
// {
// BYTE i=4;
// WORD divisor=10, resto;
//
// memset(s,' ',5);
// do
// {
// resto=n%divisor;
// n/=divisor;
// s[i--]=resto+'0';
// }
// while (n);
// s[5]='\0';
// }
void u16tohex (WORD n, char *s)
{
u8tohex((n>>8)&0xFF,s);
u8tohex(n&0xFF,s+2);
}
void u8tohex (BYTE n, char *s)
{
BYTE i=1;
BYTE resto;
resto=n&0xF;
s[1]=(resto>9)?resto+55:resto+48;
resto=n>>4;
s[0]=(resto>9)?resto+55:resto+48;
s[2]='\0';
}
void wait_key (void)
{
while ((ULA&0x1f)==0x1f);
}
BYTE inkey (BYTE semif, BYTE pos)
{
BYTE teclas;
BYTE posbit;
switch (semif)
{
case 1: teclas=SEMIFILA1; break;
case 2: teclas=SEMIFILA2; break;
case 3: teclas=SEMIFILA3; break;
case 4: teclas=SEMIFILA4; break;
case 5: teclas=SEMIFILA5; break;
case 6: teclas=SEMIFILA6; break;
case 7: teclas=SEMIFILA7; break;
case 8: teclas=SEMIFILA8; break;
default: teclas=ULA; break;
}
posbit=1<<(pos-1);
return (teclas&posbit)?0:1;
}
void beep (WORD durmili, BYTE freq) __critical
{
volatile BYTE cborde;
cborde=(*(BYTE *)(BORDR));
__asm
push bc
push de
ld l,6(ix) ;se desplaza dos byte por ser "critical".
ld h,7(ix)
ld d,-1(ix)
ld b,8(ix)
xor a
sub b
ld b,a
ld c,a
bucbeep:
ld a,d
xor #0x18
ld d,a
out (#254),a
ld b,c
bucperiodobeep:
djnz bucperiodobeep
dec hl
ld a,h
or l
jr nz,bucbeep
pop de
pop bc
__endasm;
}
void __sdcc_enter_ix (void) __naked
{
__asm
pop hl ; return address
push ix ; save frame pointer
ld ix,#0
add ix,sp ; set ix to the stack frame
jp (hl) ; and return
__endasm;
}

View File

@ -0,0 +1,630 @@
/*
Compilar con:
sdcc -mz80 --reserve-regs-iy --opt-code-size --max-allocs-per-node X
--alow-unsafe-reads --nostdlib --nostdinc --no-std-crt0 --out-fmt-s19
--port-mode=z80 --code-loc 8192 --data-loc 0 --stack-loc 65535 joyconf.sdcc
*/
typedef unsigned char BYTE;
typedef unsigned short WORD;
enum {IBLACK=0, IBLUE, IRED, IMAG, IGREEN, ICYAN, IYELLOW, IWHITE};
enum {PBLACK=0, PBLUE=8, PRED=16, PMAG=24, PGREEN=32, PCYAN=40, PYELLOW=48, PWHITE=56};
#define BRIGHT 64
#define FLASH 128
__sfr __at (0xfe) ULA;
__sfr __at (0xff) ATTR;
__sfr __at (0x1f) KEMPSTONADDR;
__sfr __banked __at (0xf7fe) SEMIFILA1;
__sfr __banked __at (0xeffe) SEMIFILA2;
__sfr __banked __at (0xfbfe) SEMIFILA3;
__sfr __banked __at (0xdffe) SEMIFILA4;
__sfr __banked __at (0xfdfe) SEMIFILA5;
__sfr __banked __at (0xbffe) SEMIFILA6;
__sfr __banked __at (0xfefe) SEMIFILA7;
__sfr __banked __at (0x7ffe) SEMIFILA8;
#define COORDS 23296
#define SPOSN 23298
#define ATTRP 23300
#define BORDR 23301
#define CHARS 23606
#define LASTK 23560
#define COLUMN (SPOSN)
#define ROW (SPOSN+1)
#define WAIT_VRETRACE __asm halt __endasm
#define WAIT_HRETRACE while(ATTR!=0xff)
#define SETCOLOR(x) *(BYTE *)(ATTRP)=(x)
#define LASTKEY *(BYTE *)(LASTK)
__sfr __banked __at (0xfc3b) ZXUNOADDR;
__sfr __banked __at (0xfd3b) ZXUNODATA;
#define MASTERCONF 0
#define SCANCODE 4
#define KEYBSTAT 5
#define JOYCONF 6
#define COREID 255
void __sdcc_enter_ix (void) __naked;
void cls (BYTE);
void border (BYTE);
void locate (BYTE, BYTE);
void puts (BYTE *);
void putdec (WORD);
void u16todec (WORD, char *);
void u16tohex (WORD, char *);
void u8tohex (BYTE, char *);
void memset (BYTE *, BYTE, WORD);
void memcpy (BYTE *, BYTE *, WORD);
int abs (int);
signed char sgn (int);
long frames (void) __naked;
void pause (BYTE);
void wait_key (void);
BYTE inkey (BYTE, BYTE);
void beep (WORD, BYTE) __critical;
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
BYTE printconf (void);
void printstatictext (void);
void getcoreid(BYTE *s);
void main (void)
{
border(IBLACK);
cls(BRIGHT|PBLACK|IWHITE);
printstatictext();
while(!printconf());
border(*(BYTE *)(23624));
cls(*(BYTE *)(23693));
}
void printstatictext (void)
{
char coreid[32];
locate(0,0);
puts ("\x4\x78\x6JOYSTICK CONFIGURATION AND TEST ");
locate(2,0);
puts ("KBD joystick: ");
locate(3,0);
puts ("DB9 joystick: ");
locate(5,0);
puts("\x4\x45Q/A to change KBD/DB9 protocol");
locate(6,0);
puts("\x4\x45W/S to change KBD/DB9 autofire");
locate(20,0);
puts("\x4\x47ZX-UNO Core ID: ");
coreid[0]=0x4;
coreid[1]=0x46;
getcoreid(coreid+2);
if (coreid[2]==0)
puts("\x4\x46NOT AVAILABLE");
else
puts(coreid);
locate(22,0);
puts("\x4\x70 Press SPACE to exit ");
}
void getcoreid(BYTE *s)
{
BYTE cont;
volatile BYTE letra;
s[0]='\0';
ZXUNOADDR = COREID;
cont=0;
while (ZXUNODATA!=0 && cont<32) cont++;
if (cont==32)
return;
cont=0;
do
{
letra = ZXUNODATA;
cont++;
}
while (letra==0 && cont<32);
if (cont==32)
return;
*(s++) = letra;
do
{
letra = ZXUNODATA;
*(s++) = letra;
}
while (letra!=0);
}
void printjoystat (char *proto, BYTE joy) // FUDLR
{
puts (proto);
if (joy&0x8)
puts ("\x4\x78 U ");
else
puts (" U ");
if (joy&0x4)
puts ("\x4\x78 D ");
else
puts (" D ");
if (joy&0x2)
puts ("\x4\x78 L ");
else
puts (" L ");
if (joy&0x1)
puts ("\x4\x78 R ");
else
puts (" R ");
if (joy&0x10)
puts ("\x4\x78 F ");
else
puts (" F ");
}
BYTE printconf (void)
{
BYTE kbconf, db9conf, kbdis=0, db9dis=0;
BYTE joy, joy1, joy2;
WAIT_VRETRACE;
ZXUNOADDR = JOYCONF;
kbconf = ZXUNODATA & 0x0f;
db9conf = (ZXUNODATA >> 4) & 0x0f;
locate(2,14);
switch (kbconf&0x7)
{
case 1: puts("\x4\x46""KEMPSTON"); break;
case 2: puts("\x4\x46""SINCL P1"); break;
case 3: puts("\x4\x46""SINCL P2"); break;
case 4: puts("\x4\x46""CURSOR "); break;
default: puts("\x4\x46""DISABLED"); kbdis=1; break;
}
if (!kbdis && kbconf&0x8)
puts("\x4\x45 AUTOFIRE");
else
puts(" ");
locate(3,14);
switch (db9conf&0x7)
{
case 1: puts("\x4\x46""KEMPSTON"); break;
case 2: puts("\x4\x46""SINCL P1"); break;
case 3: puts("\x4\x46""SINCL P2"); break;
case 4: puts("\x4\x46""CURSOR "); break;
default: puts("\x4\x46""DISABLED"); db9dis=1; break;
}
if (!db9dis && db9conf&0x8)
puts("\x4\x45 AUTOFIRE");
else
puts(" ");
if (LASTKEY == 'q' || LASTKEY == 'Q')
{
kbconf = kbconf&0x8 | (((kbconf&7)+1==5)? 0 : (kbconf&7)+1);
LASTKEY = 0;
}
else if (LASTKEY == 'a' || LASTKEY == 'A')
{
db9conf = db9conf&0x8 | (((db9conf&7)+1==5)? 0 : (db9conf&7)+1);
LASTKEY = 0;
}
else if (LASTKEY == 'w' || LASTKEY == 'W')
{
kbconf ^= 0x8;
LASTKEY = 0;
}
else if (LASTKEY == 's' || LASTKEY == 's')
{
db9conf ^= 0x8;
LASTKEY = 0;
}
ZXUNODATA = db9conf<<4 | kbconf;
joy = KEMPSTONADDR;
locate(9,0); printjoystat("\x4\x7Kempston : ", joy);
joy = ~SEMIFILA2; // LRDUF a FUDLR
joy = (joy&1)<<4 | (joy&2)<<2 | (joy&4) | (joy&0x10)>>3 | (joy&8)>>3;
locate(11,0); printjoystat("\x4\x7Sinclair 1: ", joy);
joy = ~SEMIFILA1; // FUDRL a FUDLR
joy = (joy&0x1c) | (joy&2)>>1 | (joy&1)<<1;
locate(13,0); printjoystat("\x4\x7Sinclair 2: ", joy);
joy1 = ~SEMIFILA2; // DUR-F a FUDLR
joy2 = ~SEMIFILA1; // L---- a FUDLR
joy = (joy1&1)<<4 | (joy1&8) | (joy1&0x10)>>2 | (joy2&0x10)>>3 | (joy1&4)>>2;
locate(15,0); printjoystat("\x4\x7""Cursor : ", joy);
if (LASTKEY==' ')
return 1;
else
return 0;
}
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
#pragma disable_warning 85
int abs (int n)
{
return (n>=0)?n:-n;
}
signed char sgn (int n)
{
return (n>=0)?1:-1;
}
long frames (void) __naked
{
__asm
ld a,(#23672)
ld l,a
ld a,(#23673)
ld h,a
ld a,(#23674)
ld e,a
ld d,#0
ret
__endasm;
}
void pause (BYTE frames)
{
BYTE i;
for (i=0;i!=frames;i++)
{
WAIT_VRETRACE;
}
}
void memset (BYTE *dir, BYTE val, WORD nby)
{
__asm
push bc
push de
ld l,4(ix)
ld h,5(ix)
ld a,6(ix)
ld c,7(ix)
ld b,8(ix)
ld d,h
ld e,l
inc de
dec bc
ld (hl),a
ldir
pop de
pop bc
__endasm;
}
void memcpy (BYTE *dst, BYTE *fue, WORD nby)
{
__asm
push bc
push de
ld e,4(ix)
ld d,5(ix)
ld l,6(ix)
ld h,7(ix)
ld c,8(ix)
ld b,9(ix)
ldir
pop de
pop bc
__endasm;
}
void cls (BYTE attr)
{
#ifdef USEROM
__asm
push bc
push de
ld a,4(ix)
ld (#ATTRP),a
call #0x0d6b
ld a,#0xfe
call #0x1601
pop de
pop bc
__endasm;
#else
memset((BYTE *)16384,0,6144);
memset((BYTE *)22528,attr,768);
SETCOLOR(attr);
*((WORD *)SPOSN)=0;
#endif
}
void border (BYTE b)
{
ULA=(b>>3)&0x7;
*((BYTE *)BORDR)=b;
}
void puts (BYTE *str)
{
volatile BYTE over=0;
volatile BYTE bold=0;
volatile BYTE backup_attrp = *(BYTE *)(ATTRP);
__asm
push bc
push de
ld l,4(ix)
ld h,5(ix)
buc_print:
ld a,(hl)
or a
jr nz,no_fin_print
jp fin_print
no_fin_print:
cp #22
jr nz,no_at
inc hl
ld a,(hl)
ld (#ROW),a
inc hl
ld a,(hl)
ld (#COLUMN),a
inc hl
jr buc_print
no_at:
cp #13
jr nz,no_cr
xor a
ld (#COLUMN),a
ld a,(#ROW)
inc a
ld (#ROW),a
inc hl
jr buc_print
no_cr:
cp #4
jr nz,no_attr
inc hl
ld a,(hl)
ld (#ATTRP),a
inc hl
jr buc_print
no_attr:
cp #5
jr nz,no_pr_over
ld -1(ix),#0xff
inc hl
jr buc_print
no_pr_over:
cp #6
jr nz,no_pr_bold
ld -2(ix),#0xff
inc hl
jr buc_print
no_pr_bold:
cp #32
jr nc,imprimible
ld a,#32
imprimible:
push hl
ld hl,(#COLUMN)
push hl
push af
ld de,#16384
add hl,de
ld a,h
and #7
rrca
rrca
rrca
or l
ld l,a
ld a,#248
and h
ld h,a
pop af
push hl
ld de,(#CHARS)
ld l,a
ld h,#0
add hl,hl
add hl,hl
add hl,hl
add hl,de
pop de
ld b,#8
print_car:
ld a,(hl)
sra a
and -2(ix)
or (hl)
ld c,a
ld a,(de)
and -1(ix)
xor c
ld (de),a
inc hl
inc d
djnz print_car
pop hl
push hl
ld de,#22528
ld b,h
ld h,#0
add hl,de
xor a
or b
jr z,fin_ca_attr
ld de,#32
calc_dirat:
add hl,de
djnz calc_dirat
fin_ca_attr:
ld a,(#ATTRP)
ld (hl),a
pop hl
inc l
bit 5,l
jr z,no_inc_fila
res 5,l
inc h
no_inc_fila:
ld (#COLUMN),hl
pop hl
inc hl
jp buc_print
fin_print:
pop de
pop bc
__endasm;
SETCOLOR(backup_attrp);
}
void locate (BYTE row, BYTE col)
{
*((BYTE *)ROW)=row;
*((BYTE *)COLUMN)=col;
}
// void putdec (WORD n)
// {
// BYTE num[6];
//
// u16todec (n,num);
// puts (num);
// }
//
// void u16todec (WORD n, char *s)
// {
// BYTE i=4;
// WORD divisor=10, resto;
//
// memset(s,' ',5);
// do
// {
// resto=n%divisor;
// n/=divisor;
// s[i--]=resto+'0';
// }
// while (n);
// s[5]='\0';
// }
void u16tohex (WORD n, char *s)
{
u8tohex((n>>8)&0xFF,s);
u8tohex(n&0xFF,s+2);
}
void u8tohex (BYTE n, char *s)
{
BYTE i=1;
BYTE resto;
resto=n&0xF;
s[1]=(resto>9)?resto+55:resto+48;
resto=n>>4;
s[0]=(resto>9)?resto+55:resto+48;
s[2]='\0';
}
void wait_key (void)
{
while ((ULA&0x1f)==0x1f);
}
BYTE inkey (BYTE semif, BYTE pos)
{
BYTE teclas;
BYTE posbit;
switch (semif)
{
case 1: teclas=SEMIFILA1; break;
case 2: teclas=SEMIFILA2; break;
case 3: teclas=SEMIFILA3; break;
case 4: teclas=SEMIFILA4; break;
case 5: teclas=SEMIFILA5; break;
case 6: teclas=SEMIFILA6; break;
case 7: teclas=SEMIFILA7; break;
case 8: teclas=SEMIFILA8; break;
default: teclas=ULA; break;
}
posbit=1<<(pos-1);
return (teclas&posbit)?0:1;
}
void beep (WORD durmili, BYTE freq) __critical
{
volatile BYTE cborde;
cborde=(*(BYTE *)(BORDR));
__asm
push bc
push de
ld l,6(ix) ;se desplaza dos byte por ser "critical".
ld h,7(ix)
ld d,-1(ix)
ld b,8(ix)
xor a
sub b
ld b,a
ld c,a
bucbeep:
ld a,d
xor #0x18
ld d,a
out (#254),a
ld b,c
bucperiodobeep:
djnz bucperiodobeep
dec hl
ld a,h
or l
jr nz,bucbeep
pop de
pop bc
__endasm;
}
void __sdcc_enter_ix (void) __naked
{
__asm
pop hl ; return address
push ix ; save frame pointer
ld ix,#0
add ix,sp ; set ix to the stack frame
jp (hl) ; and return
__endasm;
}

14
software/joyconf/make.bat Normal file
View File

@ -0,0 +1,14 @@
@echo off
rem sdcc -mz80 --reserve-regs-iy --opt-code-size --max-allocs-per-node 30000 ^
rem --nostdlib --nostdinc --no-std-crt0 --out-fmt-s19 ^
rem --code-loc 32768 --data-loc 0 --stack-loc 65535 joyconf.c
rem s19tozx -i joyconf.s37 -o joyconf.tap
rem cgleches joyconf.tap joyconf.wav
sdcc -mz80 --reserve-regs-iy --opt-code-size --max-allocs-per-node 10000 ^
--nostdlib --nostdinc --no-std-crt0 --code-loc 8192 joyconf.c
makebin -p joyconf.ihx joyconf.bin
dd if=joyconf.bin of=JOYCONF bs=1 skip=8192 status=noxfer

205
software/joyconf/s19tozx.c Normal file
View File

@ -0,0 +1,205 @@
/*
Utilidad de conversión de ficheros S-Record de Motorola, creados con SDCC, a TAP de Spectrum
(C)2007-2008 Miguel Angel Rodriguez Jodar (McLeod/IdeaFix). Dept. Arquitectura y Tecnología de Computadores. Universidad de Sevilla. rodriguj@atc.us.es
Licencia: GPL
Se puede compilar con cualquier compilador para Windows/UNIX (gcc, mingw32, etc...)
Uso:
s19tozx -i archivo.s19 -o archivo.tap
El archivo S19 se habrá generado con SDCC, que debe haberse usado con los siguientes parámetros:
sdcc -mz80 --opt-code-speed --nostdlib --nostdinc --no-std-crt0 --out-fmt-s19 --code-loc XXXX --data-loc 0 fichero1.c fichero2.c ...
Donde XXXX es la dirección de memoria donde comenzará nuestro código.
*/
#include <stdio.h>
#define hextodec(x) ((x)<='9'?(x)-'0':(x)-'A'+10)
#define D(x) if (verbose) x
typedef struct
{
unsigned int address;
unsigned int lbytes;
unsigned char bytes[65536];
} Tbloque;
Tbloque ElBloque;
int verbose=0;
void SalvarBloqueTAP (FILE *fs, unsigned short int address, unsigned short int lbytes, unsigned char *bytes)
{
unsigned char cabecera[19];
unsigned short int dummy;
int i;
unsigned char chk;
cabecera[0]=0; // Flag 0 para indicar cabecera estándar
cabecera[1]=3; // Bytes:
sprintf(cabecera+2,"datos-%4.4X",address);
cabecera[12]=lbytes&0xFF;
cabecera[13]=(lbytes>>8)&0xFF;
cabecera[14]=address&0xFF;
cabecera[15]=(address>>8)&0xFF;
cabecera[16]=0;
cabecera[17]=0x80;
cabecera[18]=0;
for (i=0;i<=17;i++)
cabecera[18]^=cabecera[i];
dummy=19;
fwrite(&dummy,1,2,fs);
fwrite(cabecera,1,19,fs);
dummy=lbytes+2;
fwrite(&dummy,1,2,fs);
fputc(0xFF,fs); // FF para indicar datos
fwrite(bytes,1,lbytes,fs);
chk=0xFF;
for (i=0;i<lbytes;i++)
chk^=bytes[i];
fputc(chk,fs);
}
void SalvarLoaderTAP (FILE *fs, unsigned short int address)
{
/* Este cargador corresponde a: (32 columnas por linea)
10 CLEAR 0: LOAD ""CODE : PRIN
T AT 11,0;" Pulsa una tecla para
ejecutar ": PAUSE 0: RANDOMIZE
USR 0
*/
unsigned char loader[]={19,0,0,0,108,111,97,100,101,114,32,32,32,32,91,0,1,
0,91,0,16,93,0,255,0,10,87,0,253,48,14,0,0,0,0,0,58,
239,34,34,175,58,245,172,49,49,14,0,0,11,0,0,44,48,
14,0,0,0,0,0,59,34,32,80,117,108,115,97,32,117,110,
97,32,116,101,99,108,97,32,112,97,114,97,32,101,106,
101,99,117,116,97,114,32,32,34,58,242,48,14,0,0,0,0,0,
58,249,192,48,14,0,0,0,0,0,13,0};
int offpdata=23;
int offclear=33;
int offrand=111;
int i,chks;
if (address>=25000)
{
loader[offclear]=(address-1)&0xFF;
loader[offclear+1]=((address-1)>>8)&0xFF;
}
loader[offrand]=address&0xFF;
loader[offrand+1]=(address>>8)&0xFF;
for (chks=0,i=offpdata;i<115;i++)
chks^=loader[i];
loader[i]=chks;
fwrite (loader, 1, 116, fs);
}
int main (int argc, char *argv[])
{
char *fsalida=NULL;
char *fentrada=NULL;
char *opc;
char linea[10001];
int llinea,indact;
unsigned char dato;
unsigned short int dir;
int i,p;
FILE *fe, *fs;
for (i=1;i<argc;i++)
{
opc=argv[i];
if (opc[0]=='-' && opc[1]=='o')
{
fsalida=argv[i+1];
i++;
}
else if (opc[0]=='-' && opc[1]=='i')
{
fentrada=argv[i+1];
i++;
}
else if (opc[0]=='-' && opc[1]=='v')
verbose=1;
}
if (!fentrada)
fe=stdin;
else
fe=fopen(fentrada,"rt");
if (!fsalida)
fs=stdout;
else
fs=fopen(fsalida,"wb");
if (!fs || !fe)
{
printf ("No se pudo abrir alguno de los ficheros especificados\n");
return 1;
}
fgets (linea, 10000, fe);
indact=0;
ElBloque.lbytes=0;
while (!feof(fe))
{
if (linea[0]!='S')
continue;
if (linea[1]=='9')
break;
if (linea[1]!='1')
continue;
D(printf("%s\n",linea));
llinea=((hextodec(linea[2])<<4) | hextodec(linea[3])) - 3;
dir=(hextodec(linea[4])<<12) |
(hextodec(linea[5])<<8) |
(hextodec(linea[6])<<4) |
(hextodec(linea[7]));
if (indact==0)
ElBloque.address=dir;
if (dir!=ElBloque.address+ElBloque.lbytes)
{
SalvarLoaderTAP(fs,ElBloque.address);
SalvarBloqueTAP(fs,ElBloque.address,ElBloque.lbytes,ElBloque.bytes);
indact=0;
ElBloque.address=dir;
ElBloque.lbytes=0;
}
D(printf("dir: %4.4X indact: %d llinea: %d\n",dir,indact,llinea));
for (i=0,p=8;i<llinea;i++,p+=2)
{
dato=(hextodec(linea[p])<<4) | hextodec(linea[p+1]);
ElBloque.bytes[indact++]=dato;
ElBloque.lbytes++;
}
fgets (linea,10000,fe);
}
if (indact)
{
SalvarLoaderTAP(fs,ElBloque.address);
SalvarBloqueTAP(fs,ElBloque.address,ElBloque.lbytes,ElBloque.bytes);
}
return 0;
}

Binary file not shown.

BIN
software/keymap/KEYMAP Normal file

Binary file not shown.

120
software/keymap/keymap.asm Normal file
View File

@ -0,0 +1,120 @@
; API de ESXDOS.
include "esxdos.inc"
include "errors.inc"
; KEYMAP. Una utilidad para cargar un mapa de teclado en el ZX-Uno
; Necesita sólamente un nombre de fichero de mapa, que debe estar
; guardado en /SYS/KEYMAPS dentro de la tarjeta SD donde esté ESXDOS.
;Para ensamblar con PASMO como archivo binario (no TAP)
ZXUNOADDR equ 0fc3bh
ZXUNODATA equ 0fd3bh
org 2000h ;comienzo de la ejecución de los comandos ESXDOS.
Main proc
ld a,h
or l
jr z,PrintUso ;si no se ha especificado nombre de fichero, imprimir uso
call RecogerNFile
call ReadMap
ret
PrintUso ld hl,Uso
BucPrintMsg ld a,(hl)
or a
ret z
rst 10h
inc hl
jr BucPrintMsg
endp
RecogerNFile proc ;HL apunta a los argumentos (nombre del fichero)
ld de,BufferNFich
CheckCaracter ld a,(hl)
or a
jr z,FinRecoger
cp " "
jr z,FinRecoger
cp ":"
jr z,FinRecoger
cp 13
jr z,FinRecoger
ldi
jr CheckCaracter
FinRecoger xor a
ld (de),a
inc de ;DE queda apuntando al buffer este que se necesita en OPEN, no sé pa qué.
ret
endp
ReadMap proc
xor a
rst 08h
db M_GETSETDRV ;A = unidad actual
ld b,FA_READ ;B = modo de apertura
ld hl,MapFile ;HL = Puntero al nombre del fichero (ASCIIZ)
rst 08h
db F_OPEN
ret c ;Volver si hay error
ld (FHandle),a
ld bc,ZXUNOADDR
ld a,7
out (c),a ;select KEYMAP register
ld b,4 ;4 chunks of 4096 bytes each to load
BucReadMapFromFile push bc
ld bc,4096
ld hl,Buffer
ld a,(FHandle)
rst 08h
db F_READ
jr c,PrematureEnd ;si error, fin de lectura
ld hl,Buffer
ld bc,ZXUNODATA
ld de,4096
BucWriteMapToFPGA ld a,(hl)
out (c),a
inc hl
dec de
ld a,d
or e
jr nz,BucWriteMapToFPGA
pop bc
djnz BucReadMapFromFile
jr FinReadMap
PrematureEnd pop bc
push af
ld a,(FHandle)
rst 08h
db F_CLOSE
pop af
ret
FinReadMap ld a,(FHandle)
rst 08h
db F_CLOSE
or a ;Volver sin errores a ESXDOS
ret
endp
; 01234567890123456789012345678901
Uso db " KEYMAP file",13,13
db "Loads the specified keymap from",13
db "/SYS/KEYMAPS and enables it.",13,0
FHandle db 0
Buffer ds 4096 ;4KB para buffer de lectura
MapFile db "/SYS/KEYMAPS/"
BufferNFich equ $ ;resto de la RAM para el nombre del fichero

BIN
software/loadpzx/LOADPZX Normal file

Binary file not shown.

725
software/loadpzx/loadpzx.c Normal file
View File

@ -0,0 +1,725 @@
/*
Compilar con:
sdcc -mz80 --reserve-regs-iy --opt-code-size --max-allocs-per-node 10000
--nostdlib --nostdinc --no-std-crt0 --code-loc 8192 loadpzx.c
*/
typedef unsigned char BYTE;
typedef unsigned short WORD;
enum {IBLACK=0, IBLUE, IRED, IMAG, IGREEN, ICYAN, IYELLOW, IWHITE};
enum {PBLACK=0, PBLUE=8, PRED=16, PMAG=24, PGREEN=32, PCYAN=40, PYELLOW=48, PWHITE=56};
#define BRIGHT 64
#define FLASH 128
__sfr __at (0xfe) ULA;
__sfr __at (0xff) ATTR;
__sfr __at (0x1f) KEMPSTONADDR;
__sfr __at (0x7f) FULLERADDR;
__sfr __banked __at (0xf7fe) SEMIFILA1;
__sfr __banked __at (0xeffe) SEMIFILA2;
__sfr __banked __at (0xfbfe) SEMIFILA3;
__sfr __banked __at (0xdffe) SEMIFILA4;
__sfr __banked __at (0xfdfe) SEMIFILA5;
__sfr __banked __at (0xbffe) SEMIFILA6;
__sfr __banked __at (0xfefe) SEMIFILA7;
__sfr __banked __at (0x7ffe) SEMIFILA8;
#define ATTRP 23693
#define ATTRT 23695
#define BORDR 23624
#define LASTK 23560
#define WAIT_VRETRACE __asm halt __endasm
#define WAIT_HRETRACE while(ATTR!=0xff)
#define SETCOLOR(x) *(BYTE *)(ATTRP)=(x)
#define LASTKEY *(BYTE *)(LASTK)
#define ATTRPERMANENT *((BYTE *)(ATTRP))
#define ATTRTEMPORARY *((BYTE *)(ATTRT))
#define BORDERCOLOR *((BYTE *)(BORDR))
#define MAKEWORD(d,h,l) { ((BYTE *)&(d))[0] = (l) ; ((BYTE *)&(d))[1] = (h); }
__sfr __banked __at (0xfc3b) ZXUNOADDR;
__sfr __banked __at (0xfd3b) ZXUNODATA;
#define MASTERCONF 0
#define SCANCODE 4
#define KEYBSTAT 5
#define JOYCONF 6
#define SRAMDATA 0xf2
#define SRAMADDRINC 0xf1
#define SRAMADDR 0xf0
#define COREID 0xff
/* Some ESXDOS system calls */
#define HOOK_BASE 128
#define MISC_BASE (HOOK_BASE+8)
#define FSYS_BASE (MISC_BASE+16)
#define M_GETSETDRV (MISC_BASE+1)
#define F_OPEN (FSYS_BASE+2)
#define F_CLOSE (FSYS_BASE+3)
#define F_READ (FSYS_BASE+5)
#define F_WRITE (FSYS_BASE+6)
#define F_SEEK (FSYS_BASE+7)
#define F_GETPOS (FSYS_BASE+8)
#define M_TAPEIN (MISC_BASE+3)
#define M_AUTOLOAD (MISC_BASE+8)
#define FMODE_READ 0x1 // Read access
#define FMODE_WRITE 0x2 // Write access
#define FMODE_OPEN_EX 0x0 // Open if exists, else error
#define FMODE_OPEN_AL 0x8 // Open if exists, if not create
#define FMODE_CREATE_NEW 0x4 // Create if not exists, if exists error
#define FMODE_CREATE_AL 0xc // Create if not exists, else open and truncate
#define SEEK_START 0
#define SEEK_CUR 1
#define SEEK_BKCUR 2
#define BUFSIZE 2048
BYTE errno;
char buffer[BUFSIZE];
void __sdcc_enter_ix (void) __naked;
void cls (BYTE);
void puts (BYTE *);
void u16tohex (WORD n, char *s);
void u8tohex (BYTE n, char *s);
void print8bhex (BYTE n);
void print16bhex (WORD n);
void memset (BYTE *, BYTE, WORD);
void memcpy (BYTE *, BYTE *, WORD);
BYTE open (char *filename, BYTE mode);
void close (BYTE handle);
WORD read (BYTE handle, BYTE *buffer, WORD nbytes);
WORD write (BYTE handle, BYTE *buffer, WORD nbytes);
void seek (BYTE handle, WORD hioff, WORD looff, BYTE from);
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
BYTE main (char *p);
void getcoreid(BYTE *s);
void usage (void);
BYTE commandlinemode (char *p);
void getfilename (char *p, char *fname);
BYTE printheader (BYTE handle);
void readpzx (BYTE handle);
WORD readblocktag (BYTE handle);
WORD readword (BYTE handle);
void convertpaus2puls (BYTE handle);
void skipblock (BYTE handle, WORD hiskip, WORD loskip);
void copyblock (BYTE handle, WORD hicopy, WORD locopy);
void rewindsram (void);
void writesram (BYTE v);
void incaddrsram (void);
void copysram (BYTE *p, WORD l);
void init (void) __naked
{
__asm
xor a
ld (#_errno),a
push hl
call _main
inc sp
inc sp
ld a,l
or a
jr z,preparaload
cp #255
jr z,noload
scf
ret
noload:
or a
ret
preparaload:
;Cierra TAPE.IN
ld b,#1
rst #8
.db #M_TAPEIN
;Auto LOAD ""
xor a
rst #8
.db #M_AUTOLOAD
or a
ret
;Codigo antiguo para hacer LOAD ""
; ld bc,#3
; ld hl,(#23641)
; push hl
; rst #0x18
; .dw 0x1655
; ld hl,#comando_load
; pop de
; ld bc,#3
; ldir
; ld hl,#0x12cf
; .db 0xc3, 0xfb, 0x1f
;comando_load:
; .db 239,34,34
__endasm;
}
BYTE main (char *p)
{
if (!p)
{
usage();
return 255;
}
else
return commandlinemode(p);
}
BYTE commandlinemode (char *p)
{
char fname[32];
BYTE handle, res;
BYTE noautoload;
noautoload = 0;
while (*p==' ')
p++;
if (*p=='-')
{
p++;
if (*p=='n')
{
noautoload = 255;
p++;
}
}
while (*p==' ')
p++;
getfilename (p, fname);
handle = open (fname, FMODE_READ);
if (handle==0xff)
return errno;
res = printheader(handle);
if (res)
readpzx(handle);
close (handle);
if (noautoload == 255)
puts ("\xd\xdType LOAD \"\" and press PLAY\xd");
else
{
ZXUNOADDR = 0xf3;
ZXUNODATA = 0x1; // software assisted PLAY press
ZXUNODATA = 0x0;
}
return noautoload;
}
void usage (void)
{
// 01234567890123456789012345678901
puts (" LOADPZX [-n] file.pzx\xd\xd"
"Loads a PZX file into the PZX\xd"
"player.\xd"
"-n : do not autoplay.\xd");
}
void getfilename (char *p, char *fname)
{
while (*p!=':' && *p!=0xd && *p!=' ')
*fname++ = *p++;
*fname = '\0';
}
BYTE printheader (BYTE handle)
{
WORD lblock;
readblocktag (handle);
if (buffer[0]!='P' ||
buffer[1]!='Z' ||
buffer[2]!='X' ||
buffer[3]!='T')
{
puts ("This is not a PZX valid file\xd");
return 0;
}
MAKEWORD(lblock,buffer[5],buffer[4]);
read (handle, buffer, lblock);
if (buffer[0]!=1 || buffer[1]!=0)
{
puts ("PZX format not supported\xd");
return 0;
}
if (lblock>3)
{
puts ("Loading: ");
puts(buffer+2);
puts ("\xd");
}
return 1;
}
void readpzx (BYTE handle)
{
WORD hi,lo,res;
BYTE scandblctrl;
ZXUNOADDR = 0xb;
scandblctrl = ZXUNODATA;
ZXUNODATA = scandblctrl | 0xc0;
rewindsram();
while(1)
{
*((BYTE *)(23692)) = 0xff; // para evitar el mensaje de scroll
res = readblocktag (handle);
if (!res)
break;
if (buffer[0]=='P' &&
buffer[1]=='U' &&
buffer[2]=='L' &&
buffer[3]=='S')
{
puts ("P");
MAKEWORD(lo,buffer[5],buffer[4]);
MAKEWORD(hi,buffer[7],buffer[6]);
writesram(0x2);
incaddrsram();
copysram(&buffer[4],4);
copyblock (handle,hi,lo);
}
else if (buffer[0]=='D' &&
buffer[1]=='A' &&
buffer[2]=='T' &&
buffer[3]=='A')
{
puts ("D");
MAKEWORD(lo,buffer[5],buffer[4]);
MAKEWORD(hi,buffer[7],buffer[6]);
writesram(0x3);
incaddrsram();
copysram(&buffer[4],4);
copyblock (handle,hi,lo);
}
else if (buffer[0]=='P' &&
buffer[1]=='A' &&
buffer[2]=='U' &&
buffer[3]=='S')
{
puts ("A");
MAKEWORD(lo,buffer[5],buffer[4]);
MAKEWORD(hi,buffer[7],buffer[6]);
convertpaus2puls (handle);
//writesram(0x4);
//incaddrsram();
//copysram(&buffer[4],4);
//copyblock (handle,hi,lo);
}
else if (buffer[0]=='S' &&
buffer[1]=='T' &&
buffer[2]=='O' &&
buffer[3]=='P')
{
puts ("S");
res = readword (handle);
//MAKEWORD(lo,buffer[5],buffer[4]);
//MAKEWORD(hi,buffer[7],buffer[6]);
writesram(0x1);
incaddrsram();
copysram (&res, 2);
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
//skipblock(handle,hi,lo);
}
else if (buffer[0]=='B' &&
buffer[1]=='R' &&
buffer[2]=='W' &&
buffer[3]=='S')
{
puts ("B");
writesram(0x4);
incaddrsram();
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
MAKEWORD(lo,buffer[5],buffer[4]);
MAKEWORD(hi,buffer[7],buffer[6]);
skipblock(handle,hi,lo);
}
else // skip unsupported block
{
puts ("x");
MAKEWORD(lo,buffer[5],buffer[4]);
MAKEWORD(hi,buffer[7],buffer[6]);
skipblock(handle,hi,lo);
}
}
// pequeña pausa de 20ms para evitar errores de carga
// a causa de la conmutación brusca de la señal de EAR
// del player virtual a la entrada real
// Add full stop mark to the tape
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
writesram(0);
incaddrsram();
ZXUNOADDR = 0xb;
ZXUNODATA = scandblctrl;
}
WORD readblocktag (BYTE handle)
{
return read (handle, buffer, 8);
}
WORD readword (BYTE handle)
{
WORD res;
read (handle, &res, 2);
return res;
}
void skipblock (BYTE handle, WORD hiskip, WORD loskip)
{
seek (handle, hiskip, loskip, SEEK_CUR);
}
void copyblock (BYTE handle, WORD hicopy, WORD locopy)
{
//print16bhex(hicopy);
//print16bhex(locopy); puts("\xd");
while (hicopy!=0 || locopy>=BUFSIZE)
{
read (handle, buffer, BUFSIZE);
copysram (buffer, BUFSIZE);
if ((locopy-BUFSIZE)>locopy)
hicopy--;
locopy -= BUFSIZE;
//print16bhex(hicopy);
//print16bhex(locopy); puts("\xd");
}
if (locopy>0)
{
//print16bhex(hicopy);
//print16bhex(locopy); puts("\xd");
read (handle, buffer, locopy);
copysram (buffer, locopy);
}
}
void convertpaus2puls (BYTE handle)
{
BYTE pausa[10] = {0x6,0,0,0,0x1,0x80,0,0,0,0};
writesram(0x2);
incaddrsram();
read (handle, buffer, 4);
pausa[6] = buffer[2];
pausa[7] = buffer[3] | 0x80;
pausa[8] = buffer[0];
pausa[9] = buffer[1];
copysram (pausa,10);
}
void rewindsram (void)
{
ZXUNOADDR = SRAMADDR;
ZXUNODATA = 0;
ZXUNODATA = 0;
ZXUNODATA = 0;
}
void writesram (BYTE v)
{
ZXUNOADDR = SRAMDATA;
ZXUNODATA = v;
}
void incaddrsram (void)
{
ZXUNOADDR = SRAMADDRINC;
ZXUNODATA = 0;
}
void copysram (BYTE *p, WORD l)
{
while (l--)
{
ZXUNOADDR = SRAMDATA;
ZXUNODATA = *p++;
ZXUNOADDR = SRAMADDRINC;
ZXUNODATA = 0;
}
}
void getcoreid(BYTE *s)
{
BYTE cont;
volatile BYTE letra;
ZXUNOADDR = COREID;
cont=0;
do
{
letra = ZXUNODATA;
*(s++) = letra;
cont++;
}
while (letra!=0 && cont<32);
*s='\0';
}
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------- */
#pragma disable_warning 85
#pragma disable_warning 59
void memset (BYTE *dir, BYTE val, WORD nby)
{
__asm
push bc
push de
ld l,4(ix)
ld h,5(ix)
ld a,6(ix)
ld c,7(ix)
ld b,8(ix)
ld d,h
ld e,l
inc de
dec bc
ld (hl),a
ldir
pop de
pop bc
__endasm;
}
void memcpy (BYTE *dst, BYTE *fue, WORD nby)
{
__asm
push bc
push de
ld e,4(ix)
ld d,5(ix)
ld l,6(ix)
ld h,7(ix)
ld c,8(ix)
ld b,9(ix)
ldir
pop de
pop bc
__endasm;
}
void puts (BYTE *str)
{
__asm
push bc
push de
ld a,(#ATTRT)
push af
ld a,(#ATTRP)
ld (#ATTRT),a
ld l,4(ix)
ld h,5(ix)
buc_print:
ld a,(hl)
or a
jp z,fin_print
cp #4
jr nz,no_attr
inc hl
ld a,(hl)
ld (#ATTRT),a
inc hl
jr buc_print
no_attr:
rst #16
inc hl
jp buc_print
fin_print:
pop af
ld (#ATTRT),a
pop de
pop bc
__endasm;
}
// void u16tohex (WORD n, char *s)
// {
// u8tohex((n>>8)&0xFF,s);
// u8tohex(n&0xFF,s+2);
// }
//
// void u8tohex (BYTE n, char *s)
// {
// BYTE i=1;
// BYTE resto;
//
// resto=n&0xF;
// s[1]=(resto>9)?resto+55:resto+48;
// resto=n>>4;
// s[0]=(resto>9)?resto+55:resto+48;
// s[2]='\0';
// }
//
// void print8bhex (BYTE n)
// {
// char s[3];
//
// u8tohex(n,s);
// puts(s);
// }
//
// void print16bhex (WORD n)
// {
// char s[5];
//
// u16tohex(n,s);
// puts(s);
// }
void __sdcc_enter_ix (void) __naked
{
__asm
pop hl ; return address
push ix ; save frame pointer
ld ix,#0
add ix,sp ; set ix to the stack frame
jp (hl) ; and return
__endasm;
}
BYTE open (char *filename, BYTE mode)
{
__asm
push bc
push de
xor a
rst #8
.db #M_GETSETDRV ;Default drive in A
ld l,4(ix) ;Filename pointer
ld h,5(ix) ;in HL
ld b,6(ix) ;Open mode in B
rst #8
.db #F_OPEN
jr nc,open_ok
ld (#_errno),a
ld a,#0xff
open_ok:
ld l,a
pop de
pop bc
__endasm;
}
void close (BYTE handle)
{
__asm
push bc
push de
ld a,4(ix) ;Handle
rst #8
.db #F_CLOSE
pop de
pop bc
__endasm;
}
WORD read (BYTE handle, BYTE *buffer, WORD nbytes)
{
__asm
push bc
push de
ld a,4(ix) ;File handle in A
ld l,5(ix) ;Buffer address
ld h,6(ix) ;in HL
ld c,7(ix)
ld b,8(ix) ;Buffer length in BC
rst #8
.db #F_READ
jr nc,read_ok
ld (#_errno),a
ld bc,#65535
read_ok:
ld h,b
ld l,c
pop de
pop bc
__endasm;
}
WORD write (BYTE handle, BYTE *buffer, WORD nbytes)
{
__asm
push bc
push de
ld a,4(ix) ;File handle in A
ld l,5(ix) ;Buffer address
ld h,6(ix) ;in HL
ld c,7(ix)
ld b,8(ix) ;Buffer length in BC
rst #8
.db #F_WRITE
jr nc,write_ok
ld (#_errno),a
ld bc,#65535
write_ok:
ld h,b
ld l,c
pop de
pop bc
__endasm;
}
void seek (BYTE handle, WORD hioff, WORD looff, BYTE from)
{
__asm
push bc
push de
ld a,4(ix) ;File handle in A
ld c,5(ix) ;Hiword of offset in BC
ld b,6(ix)
ld e,7(ix) ;Loword of offset in DE
ld d,8(ix)
ld l,9(ix) ;From where: 0: start, 1:forward current pos, 2: backwards current pos
rst #8
.db #F_SEEK
pop de
pop bc
__endasm;
}

12
software/loadpzx/make.bat Normal file
View File

@ -0,0 +1,12 @@
@echo off
del loadpzx.bin
del loadpzx.ihx
del LOADPZX
sdcc -mz80 --reserve-regs-iy --opt-code-size --max-allocs-per-node 10000 ^
--nostdlib --nostdinc --no-std-crt0 --code-loc 8192 --data-loc 12288 loadpzx.c
makebin -p loadpzx.ihx loadpzx.bin
dd if=loadpzx.bin of=LOADPZX bs=1 skip=8192

View File

@ -0,0 +1,499 @@
PZX (Perfect ZX Tape) file format version 1.0
=============================================
The PZX file format consists of a sequence of blocks. Each block has the
following uniform structure:
offset type name meaning
0 u32 tag unique identifier for the block type.
4 u32 size size of the block in bytes, excluding the tag and size fields themselves.
8 u8[size] data arbitrary amount of block data.
The block tags are four ASCII letter identifiers indicating how to interpret the
block data. The first letter is stored first in the file (i.e., at
offset 0 of the block), the last letter is stored last (i.e., at offset 3).
This means the tag may be internally conveniently represented either as 32bit
multicharacter constants stored in big endian order or 32bit reversed
multicharacter constant stored in little endian order, whichever way an
implementation prefers.
All integral values specified by this format are stored in little endian
format, that is, the least significant byte at lower offsets.
All values specified are in decimal, unless prefixed with 0x,
in which case they are hexadecimal. Any bits specified as unused should be
set to zero by encoding implementations and ignored by decoding implementations.
All strings specified by this format are stored in UTF-8 encoding. However
implementations are not required to render Unicode data at all. Any
implementation may choose to render only the characters it understands and
replace the rest with either a question mark character, or distinctive
hexadecimal representation of the character, whatever the implementor prefers.
For example an implementation which understands ASCII characters only may
render only those ASCII characters in the 32-126 range it understands. Also
note that the minimal compliant implementation is not required to render any
strings at all.
The sole purpose of the format is to encode sequence of pulses of certain duration.
Pulse level is defined to be either low or high. When loading, low level
corresponds to the bit 6 of port 0xFE reset (EAR off) and high level set (EAR on).
Similarly, when saving, low level corresponds to the bit 3 of port 0xFE reset
(MIC off) and high level to set (MIC on). (Note that O'Hara's ROM Disassembly
book got these EAR/MIC on/off terms incorrectly swapped). The block descriptions below
define the initial level at start of each block (and would do so even for any future
block which might be ever introduced, so an implementation may take this for
granted), and assume the level changes after each pulse generated (even if
its duration is zero). However note that implementations may as well decide
to invert that initial level and invert the level before each pulse, if it
makes them easier to implement. All that matters is that the level of the
pulses themselves remain the same.
Any durations are expressed in T cycles of standard 48k Spectrum CPU. This means
one T cycle equals 1/3500000 second. It's up to an implementation to convert
the durations appropriately if it may not or does not want to use the T cycles directly.
However note that this is not necessary in case of 128k emulation. In that
case the CPU speed is not substantially different and the ROM I/O routines
use the same timing constants anyway, so an implementation may safely use
the T cycles directly as well.
Overall regarding duration precision, note that the pulses generated by the
original Spectrum ROM saving routine itself vary by +1, -3, and -1 T cycles
in case of first pulse of first bit of leader, regular, and checksum bytes,
respectively. And memory contention can increase this by another 6 T cycles.
This means that an encoding implementation may choose to consider pulses
which do not vary by more than about 2% as reasonably equal for comparison
purposes when encoding pulse durations.
Blocks
------
There are four mandatory block types which any implementation must implement in order
to claim PZX compatibility, namely PZXT, PULS, DATA and PAUS blocks. All
other block types may be safely ignored, although implementors are welcome
to implement them as they see fit. However whenever an implementation
encounters a block it doesn't understand, it must skip it and continue as if
the block was not there at all.
In case the specified block size is smaller than the minimum size possible
for given block type, it is an error and an application may either ignore the
block and try to process the next one or to stop processing the file
entirely, whatever an implementor finds more appropriate. Similarly, if some
data inside the block indicate that the block should have at least some
minimum size but the specified block size is smaller than this, this is an
error as well and the above applies as well.
To allow custom extensions, anyone is free to define his own custom block,
however to prevent clashes with the standard, such blocks may use only
lowercase tag names, while any standardized blocks use uppercase tag names.
The core blocks which must be supported are:
PZXT - PZX header block
-----------------------
offset type name meaning
0 u8 major major version number (currently 1).
1 u8 minor minor version number (currently 0).
2 u8[?] info tape info, see below.
This block distinguishes the PZX files from other files and may provide
additional info about the file as well. This block must be always present as
the first block of any PZX file.
Any implementation should check the version number before processing the
rest of the PZX file or even the rest of this block itself. Any
implementation should accept only files whose major version it implements
and reject anything else. However an implementation may report a warning in
case it encounters minor greater than it implements for given major, if the
implementor finds it convenient to do so.
Note that this block also allows for simple concatenation of PZX files. Any
implementation should thus check the version number not only in case of the
first block, but anytime it encounters this block anywhere in the file. This
in fact suggests that an implementation might decide not to treat the first
block specially in any way, except checking the file starts with this block
type, which is usually done because of file type recognition anyway.
The rest of the block data may provide additional info about the tape. Note
that an implementation is not required to process any of this information in
any way and may as well safely ignore it.
The additional information consists of sequence of strings, each terminated
either by character 0x00 or end of the block, whichever comes first.
This means the last string in the sequence may or may not be terminated.
The first string (if there is any) in the sequence is always the title of
the tape. The following strings (if there are any) form key and value pairs,
each value providing particular type of information according to the key.
In case the last value is missing, it should be treated as empty string.
The following keys are defined (for reference, the value in brackets is the
corresponding type byte as specified by the TZX standard):
Publisher [0x01] - Software house/publisher
Author [0x02] - Author(s)
Year [0x03] - Year of publication
Language [0x04] - Language
Type [0x05] - Game/utility type
Price [0x06] - Original price
Protection [0x07] - Protection scheme/loader
Origin [0x08] - Origin
Comment [0xFF] - Comment(s)
Note that some keys (like Author or Comment) may be used more than once.
Any encoding implementation must use any of the key names as they are listed
above, including the case. For any type of information not covered above, it
should use either the generic Comment field or invent new sensible key name
following the style used above. This allows any decoding implementation to
classify and/or localize any of the key names it understands, and use any
others verbatim.
Overall, same rules as for use in TZX files apply, for example it is not
necessary to specify the Language field in case all texts are in English.
PULS - Pulse sequence
---------------------
offset type name meaning
0 u16 count bits 0-14 optional repeat count (see bit 15), always greater than zero
bit 15 repeat count present: 0 not present 1 present
2 u16 duration1 bits 0-14 low/high (see bit 15) pulse duration bits
bit 15 duration encoding: 0 duration1 1 ((duration1<<16)+duration2)
4 u16 duration2 optional low bits of pulse duration (see bit 15 of duration1)
6 ... ... ditto repeated until the end of the block
This block is used to represent arbitrary sequence of pulses. The sequence
consists of pulses of given duration, with each pulse optionally repeated
given number of times. The duration may be up to 0x7FFFFFFF T cycles,
however longer durations may be achieved by concatenating the pulses by use
of zero pulses. The repeat count may be up to 0x7FFF times, however more
repetitions may be achieved by simply storing the same pulse again together
with another repeat count.
The optional repeat count is stored first. When present, it is stored as
16 bit value with bit 15 set. When not present, the repeat count is considered to be 1.
Note that the stored repeat count must be always greater than zero, so when
decoding, a value 0x8000 is not a zero repeat count, but prefix indicating the
presence of extended duration, see below.
The pulse duration itself is stored next. When it fits within 15 bits, it is
stored as 16 bit value as it is, with bit 15 not set. Otherwise the 15 high
bits are stored as 16 bit value with bit 15 set, followed by 16 bit value
containing the low 16 bits. Note that in the latter case the repeat count
must be present unless the duration fits within 16 bits, otherwise the
decoding implementation would treat the high bits as a repeat count.
The above can be summarized with the following pseudocode for decoding:
count = 1 ;
duration = fetch_u16() ;
if ( duration > 0x8000 ) {
count = duration & 0x7FFF ;
duration = fetch_u16() ;
}
if ( duration >= 0x8000 ) {
duration &= 0x7FFF ;
duration <<= 16 ;
duration |= fetch_u16() ;
}
The pulse level is low at start of the block by default. However initial
pulse of zero duration may be easily used to make it high. Similarly, pulse
of zero duration may be used to achieve pulses lasting longer than
0x7FFFFFFF T cycles. Note that if the repeat count is present in case of
zero pulse for some reason, any decoding implementation must consistently
behave as if there was one zero pulse if the repeat count is odd and as if
there was no such pulse at all if it is even.
For example, the standard pilot tone of Spectrum header block (leader < 128)
may be represented by following sequence:
0x8000+8063,2168,667,735
The standard pilot tone of Spectrum data block (leader >= 128) would be:
0x8000+3223,2168,667,735
For the record, the standard ROM save routines create the pilot tone in such
a way that the level of the first sync pulse is high and the level of the
second sync pulse is low. The bit pulses then follow, each bit starting with
high pulse. The creators of the PZX files should use this information to
determine if they got the polarity of their files right. Note that although
most loaders are not polarity sensitive and would work even if the polarity
is inverted, there are some loaders which won't, so it is better to always
stick to this scheme.
DATA - Data block
-----------------
offset type name meaning
0 u32 count bits 0-30 number of bits in the data stream
bit 31 initial pulse level: 0 low 1 high
4 u16 tail duration of extra pulse after last bit of the block
6 u8 p0 number of pulses encoding bit equal to 0.
7 u8 p1 number of pulses encoding bit equal to 1.
8 u16[p0] s0 sequence of pulse durations encoding bit equal to 0.
8+2*p0 u16[p1] s1 sequence of pulse durations encoding bit equal to 1.
8+2*(p0+p1) u8[ceil(bits/8)] data data stream, see below.
This block is used to represent binary data using specified sequences of
pulses. The data bytes are processed bit by bit, most significant bits first.
Each bit of the data is represented by one of the sequences, s0 if its value
is 0 and s1 if its value is 1, respectively. Each sequence consists of
pulses of specified durations, p0 pulses for sequence s0 and p1 pulses for
sequence s1, respectively.
The initial pulse level is specified by bit 31 of the count field. For data
saved with standard ROM routines, it should be always set to high, as
mentioned in PULS description above. Also note that pulse of zero duration
may be used to invert the pulse level at start and/or the end of the
sequence. It would be also possible to use it for pulses longer than 65535 T
cycles in the middle of the sequence, if it was ever necessary.
For example, the sequences for standard ZX Spectrum bit encoding are:
(initial pulse level is high):
bit 0: 855,855
bit 1: 1710,1710
The sequences for ZX81 encoding would be (initial pulse level is high):
bit 0: 530, 520, 530, 520, 530, 520, 530, 4689
bit 1: 530, 520, 530, 520, 530, 520, 530, 520, 530, 520, 530, 520, 530, 520, 530, 520, 530, 4689
The sequence for direct recording at 44100kHz would be (assuming initial pulse level is low):
bit 0: 79,0
bit 1: 0,79
The sequence for direct recording resampled to match the common denominator
of standard pulse width would be (assuming initial pulse level is low):
bit 0: 855,0
bit 1: 0,855
After the very last pulse of the last bit of the data stream is output, one
last tail pulse of specified duration is output. Non zero duration is
usually necessary to terminate the last bit of the block properly, for
example for data block saved with standard ROM routine the duration of the
tail pulse is 945 T cycles and only then goes the level low again. Of course
the specified duration may be zero as well, in which case this pulse has no
effect on the output. This is often the case when multiple data blocks are
used to represent continuous stream of pulses.
PAUS - Pause
------------
offset type name meaning
0 u32 duration bits 0-30 duration of the pause
bit 31 initial pulse level: 0 low 1 high
This block may be used to produce pauses during which the pulse level is not
particularly important. The pause consists of pulse of given duration and
given level. However note that some emulators may choose to simulate random
noise during this period, occasionally toggling the pulse level. In such case
the level must not be toggled during the first 70000 T cycles and then more
than once during each 70000 T cycles.
For example, in case of ZX Spectrum program saved by standard SAVE command
there is a low pulse of about one second (49-50 frames) between the header
and data blocks. On the other hand, there is usually no pause between the
blocks necessary at all, as long as the tail pulse of the preceding block was
used properly.
Additional blocks which should be supported:
BRWS - Browse point
-------------------
offset type name meaning
0 u8[?] text text describing this browse point
This block may be used when it is desirable to mark some point on the tape
as target for tape browsing. The text is the single line describing this
target.
If an implementation supports tape browsing, it should implement a mode in
which it allows jumping to PZXT or BRWS blocks only. These blocks are the
reasonable points to which jumping makes sense, and both provide descriptive
name of that point. In case of the PZXT block it is the tape title (or "Tape
start" if no name is specified) and in case of BRWS block it is the the
description of that browse point. An implementation may choose to also
implement a mode in which it allows jumping to arbitrary blocks as well. In
such case the blocks may be referred to by their tag names as well, and an
implementation may choose to attempt to extract any additional information
from the block itself as it sees fit.
Creators of the PZX files are urged to put the BRWS blocks to wherever it makes
sense. They are often not needed, but they should be inserted prior any
level data block to which a user might eventually need to fast forward or
rewind to, together with appropriately descriptive name.
STOP - Stop tape command
------------------------
offset type name meaning
0 u16 flags when exactly to stop the tape (1 48k only, other always).
This block is used to instruct an emulator to stop the virtual tape deck.
The flags field is used to specify under which conditions this should
happen. In case the value is 0, the tape should be always stopped, in case
the value is 1, it should be stopped only if 48k Spectrum is being emulated.
Other values are not defined yet, however for future compatibility, any
implementation should treat any value it doesn't understand as 0.
Creators of the PZX files are urged to put these blocks to whichever position the program
asks the user to stop the tape at, and use the flags field appropriately to
the situation as well.
Issues
======
Q: Some pulse sequences are more common than the others. Shall we include
some of them in the format definition and provide a way of simply specifying
them in the blocks? For example, PULS block of zero size might refer to the
standard pilot sequence. Similarly, in DATA block, setting p0 to 0 might
indicate that p1 specifies one of the default sets of sequences, say 0 for
standard ZX Spectrum and 1 for ZX81. Do we want to do either or both of
this or is it just a feature creep?
A: It is better not to hard code any specific knowledge of this type into an
implementation. This way an implementation doesn't have to deal with special
cases and it has a better chance of understanding any future revisions of the
format. The size savings alone are not worth it.
F.A.Q.
======
Q: How do I recognize that a DATA block is suitable for flashloading?
A: By testing the pulse sequences. What exactly you need to test may depend
on what loaders your implementation can flashload. In the common case of the
standard loaders you would simply test that each sequence consists of two
non-zero pulses, and that the total duration of the sequence s0 is less than
the total duration of sequence s1.
Mapping of TZX blocks to PZX blocks:
====================================
Here is a brief roadmap how each of the TZX blocks may be mapped to
corresponding PZX block(s):
ID 10 - Standard speed data block
Trivially encoded with PULS and DATA blocks.
ID 11 - Turbo speed data block
Trivially encoded with PULS and DATA blocks.
ID 12 - Pure tone
Trivially encoded with PULS block.
ID 13 - Sequence of pulses of various lengths
Trivially encoded with PULS block.
ID 14 - Pure data block
Trivially encoded with DATA block.
ID 15 - Direct recording block
Encoded with DATA block, following the example sequences in DATA block
description.
ID 18 - CSW recording block
Encoded either as PULS block or DATA block, using the DRB like encoding with
appropriate denominator in the latter case. In either case, it would help if
the pulse durations are cleaned from the noise caused by interfering sampling frequency.
ID 19 - Generalized data block
Encoded as PULS block and DATA block for alphabets with no more than 2
symbols. In case of 3+ symbols for the data the pilot would be encoded as PULS
block, and for data an encoding same as for CSW above would be used.
ID 20 - Pause (silence) or 'Stop the tape' command
Encoded either as PAUS block or STOP block.
ID 21 - Group start
Encoded as BRWS block.
ID 22 - Group end
Not needed.
ID 23 - Jump to block
ID 24 - Loop start
ID 25 - Loop end
ID 26 - Call sequence
ID 27 - Return from sequence
ID 28 - Select block
All these were dropped as not really needed.
ID 2A - Stop the tape if in 48K mode
Encoded as STOP block.
ID 2B - Set signal level
Not needed, every block defines pulse levels regardless of other blocks.
ID 30 - Text description
Encoded as BRWS block.
ID 32 - Archive info
Included as part of PZXT block.
ID 31 - Message block
ID 33 - Hardware type
ID 35 - Custom info block
All these were dropped as not needed, although they might be easily included
via custom blocks if anyone really has some use for them.
ID 5A - "Glue" block (90 dec, ASCII Letter 'Z')
Not exactly needed, although PZXT block may be used in the same way as well.
History
=======
1.0 (28.6.2007)
* Changed ZXTP tag to PZXT to eliminate any possible problems with draft implementations.
0.3 draft (20.5.2007)
* Some values are now intentionally limited to 31 bits to prevent overflow issues while decoding.
* Number of pulses of DATA block bit sequences is now stored as 8 bits only.
0.2 draft (12.5.2007)
* The ZXTP block now stores the info types verbosely (suggested by Kio and AndyC).
* The encoding of PULS block now allows 32 bit durations and optional repeat count.
* The DATA block and PAUS blocks now contain explicit initial pulse level.
* The PAUS block now consists of single pulse, and specifies how random noise should be used.
0.1 draft (28.4.2007)
+ Patrik Rak presents the initial draft release.

BIN
software/playzxm/PLAYZXM Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,49 @@
#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#define BW
//#define COLOR
int main ()
{
char header[256];
FILE *fi, *fo;
char name[100];
char *frame;
int fr = 1;
memcpy (header,"ZXM",3);
#ifdef COLOR
header[18] = 14;
#endif
#ifdef BW
header[18] = 12;
#endif
frame = malloc(6912);
fo = fopen ("badapple.zxm", "wb");
fwrite (header, 1, 256, fo);
memset (header, 0, 256);
while(1)
{
sprintf (name, "%09.9d.scr", fr++);
fi = fopen (name, "rb");
if (!fi)
break;
#ifdef COLOR
fread (frame, 1, 6912, fi);
fwrite (header, 1, 256, fo);
fwrite (frame, 1, 6912, fo);
#endif
#ifdef BW
fread (frame, 1, 6144, fi);
fwrite (frame, 1, 6144, fo);
#endif
printf ("%s\n", name);
fclose (fi);
}
fclose (fo);
free(frame);
}

View File

@ -0,0 +1,266 @@
; API de ESXDOS.
include "esxdos.inc"
include "errors.inc"
; PLAYZXM : un comando para ESXDOS 0.8.5 que permite reproducir videos en formato
; ZXM en blanco y negro y color.
; Video: cabecera + secuencia lineal de frames.
; Cabecera (256 bytes):
; Offset Contenido
; 0 'Z'
; 1 'X'
; 2 'M'
; 16-17 Numero de frames que tiene el video (no usado en esta rutina)
; 18 Numero de sectores (de 512 bytes) que ocupa cada frame: 12 para BW, 14 para color
; Resto de posiciones: reservado
; Cada frame: en blanco y negro, 6144 bytes en formato pantalla de Spectrum. Un pixel a 1 se muestra en negro. A 0, en blanco.
; en color, 256 bytes de relleno + 6912 bytes en formato pantalla de Spectrum
; NOTA: el relleno es simplemente para que cada frame ocupe un número entero de sectores. El relleno se pone a 0
; o a lo que se quiera, ya que no se hace nada con él.
;Para ensamblar con PASMO como archivo binario (no TAP)
BORDERCLR equ 23624
PAPERCOLOR equ 23693
BANKM equ 7ffdh
PILA equ 3deah ;valor sugerido por Miguel Ângelo para poner la pila en el área de comando
org 2000h ;comienzo de la ejecución de los comandos ESXDOS.
Main proc
ld a,h
or l
jr z,PrintUso ;si no se ha especificado nombre de fichero, imprimir uso
call RecogerNFile
di
ld (BackupSP),sp
ld sp,PILA
ei
call PlayFichero
push af
call Cls
pop af
ld sp,(BackupSP)
ret
PrintUso ld hl,Uso
BucPrintMsg ld a,(hl)
or a
ret z
rst 10h
inc hl
jr BucPrintMsg
endp
RecogerNFile proc ;HL apunta a los argumentos (nombre del fichero)
ld de,BufferNFich
CheckCaracter ld a,(hl)
or a
jr z,FinRecoger
cp " "
jr z,FinRecoger
cp ":"
jr z,FinRecoger
cp 13
jr z,FinRecoger
ldi
jr CheckCaracter
FinRecoger xor a
ld (de),a
inc de ;DE queda apuntando al buffer este que se necesita en OPEN, no sé pa qué.
ret
endp
PlayFichero proc
xor a
rst 08h
db M_GETSETDRV ;A = unidad actual
ld b,FA_READ ;B = modo de apertura
ld hl,BufferNFich ;HL = Puntero al nombre del fichero (ASCIIZ)
rst 08h
db F_OPEN
ret c ;Volver si hay error
ld (FHandle),a
call SetupVideoMemory
call ReadHeader
jr c,FinPlay
BucPlayVideo ld hl,(StartScreen)
ld bc,(LFrame)
ld a,(FHandle)
rst 08h
db F_READ
jr c,FinPlay ;si error, fin de lectura
ld a,b
or c
jr z,FinPlay ;si no hay más que leer, fin de lectura
call SwitchScreens
ld bc,7ffeh
in a,(c) ;Detectar si se ha pulsado SPACE
and 1
jr z,FinPlay
jr BucPlayVideo
FinPlay ld a,(FHandle)
rst 08h
db F_CLOSE
call RestoreVideoMemory
or a ;Volver sin errores a ESXDOS
ret
endp
ReadHeader proc
ld a,(FHandle)
ld bc,256
ld hl,32768
rst 08h
db F_READ
ret c
ld hl,32768
ld a,'Z'
cpi
jr nz,NoZXM
ld a,'X'
cpi
jr nz,NoZXM
ld a,'M'
cpi
jr nz,NoZXM
ld hl,32768+18
ld a,(hl)
add a,a
ld b,a
ld c,0
ld (LFrame),bc
ld bc,49152 ;Principio buffer pantalla para BW
ld (StartScreen),bc
ld a,(hl)
cp 12 ;B/W ?
jr z,ZXMOk
ld bc,49152-256 ;Principio buffer pantalla para color
ld (StartScreen),bc
ZXMOk or a
ret
NoZXM scf
ret
endp
SetupVideoMemory proc
di
ld bc,BANKM
ld a,00010111b ;banco 7, pantalla normal, ROM 3
ld (Banco),a
out (c),a
ld hl,0c000h + 6144
ld de,04000h + 6144
ld bc,768
SetAttr ld a,64+56
ld (hl),a
ld (de),a
inc hl
inc de
dec bc
ld a,b
or c
jr nz,SetAttr
xor a
ld bc,0fc3bh
out (c),a
inc b
in a,(c)
ld (BackupConfig),a
or 20h
out (c),a
xor a
out (254),a
ei
ret
endp
RestoreVideoMemory proc
di
xor a
ld bc,0fc3bh
out (c),a
inc b
ld a,(BackupConfig)
out (c),a
ld bc,BANKM
ld a,00010000b ;banco 0, pantalla normal, ROM 3
out (c),a
ei
ret
endp
SwitchScreens proc
halt
ld a,(Banco)
xor 00001010b ;conmutamos de la pantalla normal a la shadow y de la 7 a la 5
ld (Banco),a
ld bc,BANKM
out (c),a
ret
endp
Cls proc
ld a,(BORDERCLR)
sra a
sra a
sra a
and 7
out (254),a
ld hl,16384
ld de,16385
ld bc,6143
ld (hl),l
ldir
inc hl
inc de
ld bc,767
ld a,(PAPERCOLOR)
ld (hl),a
ldir
ret
endp
; 01234567890123456789012345678901
Uso db " playzxm moviefile.zxm",13,13
db "Plays a video file encoded in",13
db "ZXM (colour/BW) format.",13,0
FHandle db 0
Banco db 0
BackupSP dw 0
BackupConfig db 0
StartScreen dw 0
LFrame dw 0
BufferNFich equ $ ;resto de la RAM para el nombre del fichero

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,77 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OFSBMP 0x76
#define OFSPAL 0x36
void AddFrame (FILE *fmov, FILE *fbmp)
{
unsigned char paleta[16][4];
unsigned char bmp[96][64];
int i;
unsigned char color;
fseek (fbmp, OFSPAL, SEEK_SET);
fread (paleta, 1, 64, fbmp);
fseek (fbmp, OFSBMP, SEEK_SET);
fread (bmp, 96, 64, fbmp);
for (i=95;i>=0;i--)
{
fwrite (bmp[i], 1, 64, fmov);
}
for (i=0;i<16;i++)
{
color = (paleta[i][0]/64) | (paleta[i][1]/32)<<5 | (paleta[i][2]/32)<<2;
fwrite (&color, 1, 1, fmov);
}
}
int main (int argc, char *argv[])
{
FILE *fbmp, *fmov;
int i;
char nfile[80];
char *prefix;
int paso;
if (argc<2)
{
fprintf (stderr, "USO: makevideoradas prefijo [paso]\n\n");
fprintf (stderr, "Donde: 'prefijo' es el comienzo del nombre de cada uno de los ficheros.\n");
fprintf (stderr, " 'paso' es opcional e indica el incremento en frames (defecto: 1)\n");
fprintf (stderr, "El nombre completo de cada fichero sera prefijo + codigo de 5 digitos\ncomenzando en 0 + .BMP\n\n");
fprintf (stderr, "Ejemplo: con el prefijo 'vid' se procesaran los ficheros:\nvid00000.bmp , vid00001.bmp , vid00002.bmp , etc...\n");
fprintf (stderr, "Si se indica un valor para paso distinto de 1, por ejemplo 2, se\nprocesaran los ficheros: vid00000.bmp , vid00002.bmp , vid00004.bmp , etc...\n");
return 0;
}
prefix = argv[1];
if (argc>=3)
paso = atoi(argv[2]);
else
paso = 1;
sprintf (nfile, "%s.rdm", prefix);
fmov = fopen (nfile, "wb");
i=0;
while (1)
{
sprintf (nfile, "%s%05.5d.bmp", prefix, i);
fbmp = fopen (nfile, "rb");
if (!fbmp)
break;
fprintf (stderr, "Procesando frame %5d \r", i);
AddFrame (fmov, fbmp);
fclose (fbmp);
i+=paso;
}
fclose (fmov);
puts("");
return 0;
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,225 @@
; API de ESXDOS.
include "esxdos.inc"
include "errors.inc"
; PLAYRMOV : un comando para ESXDOS 0.8.5 que permite reproducir videos en formato
; Radastaniano (ficheros .RDM) usando DIVMMC en el ZX-Uno.
; Video: secuencia lineal de frames.
; Cada frame: 6144 bytes con el bitmap en el formato radastaniano +
; 16 bytes para la paleta (entradas 0-15)
; Version 0.2 : arreglado el problema del stack. Gracias a Miguel Ângelo Guerreiro
; Se borra la pantalla al terminar la reproducción
; Version 0.1 : necesita que el stack esté por debajo de 49152
; (ej. CLEAR 49151 antes de ejecutar el comando)
;Para ensamblar con PASMO como archivo binario (no TAP)
BORDERCLR equ 23624
PAPERCOLOR equ 23693
ULAPLUSADDR equ 0bf3bh
ULAPLUSDATA equ 0ff3bh
ZXUNOADDR equ 0fc3bh
ZXUNODATA equ 0fd3bh
RADASCTRL equ 40h
BANKM equ 7ffdh
PILA equ 3deah ;valor sugerido por Miguel Ângelo para poner la pila en el área de comando
org 2000h ;comienzo de la ejecución de los comandos ESXDOS.
Main proc
ld a,h
or l
jr z,PrintUso ;si no se ha especificado nombre de fichero, imprimir uso
call RecogerNFile
di
ld (BackupSP),sp
ld sp,PILA
ei
call PlayFichero
call Cls
ld sp,(BackupSP)
ret
PrintUso ld hl,Uso
BucPrintMsg ld a,(hl)
or a
ret z
rst 10h
inc hl
jr BucPrintMsg
endp
RecogerNFile proc ;HL apunta a los argumentos (nombre del fichero)
ld de,BufferNFich
CheckCaracter ld a,(hl)
or a
jr z,FinRecoger
cp " "
jr z,FinRecoger
cp ":"
jr z,FinRecoger
cp 13
jr z,FinRecoger
ldi
jr CheckCaracter
FinRecoger xor a
ld (de),a
inc de ;DE queda apuntando al buffer este que se necesita en OPEN, no sé pa qué.
ret
endp
PlayFichero proc
xor a
rst 08h
db M_GETSETDRV ;A = unidad actual
ld b,FA_READ ;B = modo de apertura
ld hl,BufferNFich ;HL = Puntero al nombre del fichero (ASCIIZ)
rst 08h
db F_OPEN
ret c ;Volver si hay error
ld (FHandle),a
call SetupVideoMemory
BucPlayVideo ld hl,0c000h
ld bc,6144+16 ;Bitmap + paleta
ld a,(FHandle)
rst 08h
db F_READ
jr c,FinPlay ;si error, fin de lectura
ld a,b
or c
jr z,FinPlay ;si no hay más que leer, fin de lectura
call SwitchScreens
ld bc,7ffeh
in a,(c) ;Detectar si se ha pulsado SPACE
and 1
jr z,FinPlay
jr BucPlayVideo
FinPlay ld a,(FHandle)
rst 08h
db F_CLOSE
call RestoreVideoMemory
or a ;Volver sin errores a ESXDOS
ret
endp
SetupVideoMemory proc
di
ld bc,ZXUNOADDR
ld a,RADASCTRL
out (c),a
ld bc,ZXUNODATA
ld a,3 ;modo radastaniano
out (c),a
ld bc,BANKM
ld a,00010111b ;banco 7, pantalla normal, ROM 3
ld (Banco),a
out (c),a
ei
ret
endp
RestoreVideoMemory proc
di
ld bc,BANKM
ld a,00010000b ;banco 0, pantalla normal, ROM 3
out (c),a
ld bc,ZXUNOADDR
ld a,RADASCTRL
out (c),a
ld bc,ZXUNODATA
ld a,0 ;modo ULA normal
out (c),a
ei
ret
endp
SwitchScreens proc
halt
ld ix,0c000h + 6144 ;apuntamos a donde está la paleta
ld d,0 ;D es el indice a la paleta que se está escribiendo
ld h,0ffh ;H contiene el color más oscuro, para poner en el borde después
ld l,0 ;L contiene el índice al color más oscuro
BucUpdPaleta ld bc,ULAPLUSADDR
out (c),d
ld b,0ffh
ld a,(ix)
out (c),a
ld a,d
cp 8 ;Solo hacemos el test para los indices 0-7
jr nc,NoTestColorOscuro
ld a,(ix)
cp h
jr nc,NoTestColorOscuro
ld h,a
ld l,d
NoTestColorOscuro inc ix
inc d
ld a,16
cp d
jr nz,BucUpdPaleta
ld a,l
out (254),a ;El borde lo más oscuro posible
ld a,(Banco)
xor 00001010b ;conmutamos de la pantalla normal a la shadow y de la 7 a la 5
ld (Banco),a
ld bc,BANKM
out (c),a
ret
endp
Cls proc
ld a,(BORDERCLR)
sra a
sra a
sra a
and 7
out (254),a
ld hl,16384
ld de,16385
ld bc,6143
ld (hl),l
ldir
inc hl
inc de
ld bc,767
ld a,(PAPERCOLOR)
ld (hl),a
ldir
ret
endp
; 01234567890123456789012345678901
Uso db " playrmov moviefile.rdm",13,13
db "Plays a video file encoded for",13
db "the ",34,"Radastan",34," video mode.",13,0
FHandle db 0
Banco db 0
BackupSP dw 0
BufferNFich equ $ ;resto de la RAM para el nombre del fichero

Binary file not shown.