├── font4x8.d ├── width64.h ├── dwterm.h ├── font4x8.h ├── coco3.c ├── vt100.h ├── drivewire.h ├── coco3.h ├── drivewire.c ├── README.md ├── coco2.h ├── dwinit.asm ├── Makefile ├── width64.c ├── dwdefs.d ├── hirestxt.h ├── writeCharAt_51cols.c ├── dwterm.c ├── font4x8.c ├── dwwrite.asm ├── hirestxt.c ├── dwread.asm ├── vt100.c └── bin2cas /font4x8.d: -------------------------------------------------------------------------------- 1 | font4x8.o font4x8.d : font4x8.c font4x8.h 2 | -------------------------------------------------------------------------------- /width64.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDTH64_H 2 | #define WIDTH64_H 3 | 4 | void width64(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /dwterm.h: -------------------------------------------------------------------------------- 1 | #ifndef _DWTERM_H 2 | #define _DWTERM_H 3 | void writeChannel(uint8_t, uint8_t); 4 | #endif 5 | 6 | -------------------------------------------------------------------------------- /font4x8.h: -------------------------------------------------------------------------------- 1 | /* font4x8.h - ISO-8859-1 font for a 51x24 software text screen. 2 | 3 | By Pierre Sarrazin 4 | This file is in the public domain. 5 | */ 6 | 7 | #ifndef _font4x8_h_ 8 | #define _font4x8_h_ 9 | 10 | 11 | // Characters 32 to 127 and 160 to 255. 12 | // Only the 5 high bits of each byte are part of the glyph. 13 | // The 3 low bits of each byte are zero. 14 | // 15 | extern const unsigned char font4x8[1536]; 16 | 17 | 18 | #endif /* _font4x8_h_ */ 19 | -------------------------------------------------------------------------------- /coco3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "coco3.h" 4 | 5 | uint8_t palette[8] = { 6 | 0x00, // Black 7 | 0x24, // Red 8 | 0x12, // Green 9 | 0x36, // Brown 10 | 0x09, // Blue 11 | 0x2d, // Magenta 12 | 0x1b, // Cyan 13 | 0x3f // White 14 | }; 15 | 16 | void setupPalette(void) 17 | { 18 | int i; 19 | char *p = (char *)0xffb0; 20 | 21 | // Background (0-7) 22 | for (i=0; i<8; i++) 23 | *p++ = palette[i]; 24 | // Foreground (8-15) 25 | for (i=0; i<8; i++) 26 | *p++ = palette[i]; 27 | } 28 | -------------------------------------------------------------------------------- /vt100.h: -------------------------------------------------------------------------------- 1 | #ifdef _CMOC_VERSION_ 2 | #include 3 | #endif 4 | 5 | #ifndef VT100_H 6 | #define VT100_H 7 | 8 | void printline(char *, int); 9 | void move_cursor(uint8_t, uint8_t); 10 | void erase_to_here(); 11 | void erase_from_here(); 12 | void erase_line_from_here(); 13 | void erase_line_to_here(); 14 | void clear_line(); 15 | 16 | extern uint8_t vt100buf[16]; 17 | 18 | void vt100_init(void); 19 | void vt100_setup(uint8_t, uint8_t, uint8_t, uint8_t *); 20 | int vt100(char); 21 | void vt100_putchar(char); 22 | void vt100_puts(char *); 23 | void vt100_putstr(char *, size_t); 24 | void sgrClear(void); 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /drivewire.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef DRIVEWIRE_H 5 | #define DRIVEWIRE_H 6 | 7 | #ifndef DW_BECKER 8 | #define DW_BECKER 0 9 | #endif 10 | 11 | #ifndef DW_JMPCPBCK 12 | #define DW_JMPCPBCK 0 13 | #endif 14 | 15 | #ifndef DW_BECKERTO 16 | #define DW_BECKERTO 0 17 | #endif 18 | 19 | #ifndef DW_ARDUINO 20 | #define DW_ARDUINO 0 21 | #endif 22 | 23 | #ifndef DW_BAUD38400 24 | #define DW_BAUD38400 0 25 | #endif 26 | 27 | #ifndef DW_NOINTMASK 28 | #define DW_NOINTMASK 0 29 | #endif 30 | 31 | #ifndef DW_H6309 32 | #define DW_H6309 0 33 | #endif 34 | 35 | #ifndef DW_SY6551N 36 | #define DW_SY6551N 0 37 | #endif 38 | 39 | #ifndef DW_COCO3FPGAWIFI 40 | #define DW_COCO3FPGAWIFI 0 41 | #endif 42 | 43 | #ifndef DW_MEGAMINIMPI 44 | #define DW_MEGAMINIMPI 0 45 | #endif 46 | 47 | #ifndef DW_MMMUART 48 | #define DW_MMMUART 0 49 | #endif 50 | 51 | int dw_read(uint8_t *buf, uint16_t count); 52 | int dw_write(uint8_t *buf, uint16_t count); 53 | void dw_init(void); 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /coco3.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef COCO3_H 5 | #define COCO3_H 6 | 7 | #define ATTR_BLACK 0b00000000 8 | #define ATTR_RED 0b00000001 9 | #define ATTR_GREEN 0b00000010 10 | #define ATTR_BROWN 0b00000011 11 | #define ATTR_BLUE 0b00000100 12 | #define ATTR_MAGENTA 0b00000101 13 | #define ATTR_CYAN 0b00000110 14 | #define ATTR_WHITE 0b00000111 15 | 16 | #define ATTR_BG_BLACK 0b00000000 17 | #define ATTR_BG_RED 0b00000001 18 | #define ATTR_BG_GREEN 0b00000010 19 | #define ATTR_BG_BROWN 0b00000011 20 | #define ATTR_BG_BLUE 0b00000100 21 | #define ATTR_BG_MAGENTA 0b00000101 22 | #define ATTR_BG_CYAN 0b00000110 23 | #define ATTR_BG_WHITE 0b00000111 24 | #define ATTR_BG_MASK 0b00000111 25 | #define ATTR_BG_CLR 0b11111000 26 | 27 | #define ATTR_FG_BLACK 0b00000000 28 | #define ATTR_FG_RED 0b00001000 29 | #define ATTR_FG_GREEN 0b00010000 30 | #define ATTR_FG_BROWN 0b00011000 31 | #define ATTR_FG_BLUE 0b00100000 32 | #define ATTR_FG_MAGENTA 0b00101000 33 | #define ATTR_FG_CYAN 0b00110000 34 | #define ATTR_FG_WHITE 0b00111000 35 | #define ATTR_FG_MASK 0b00111000 36 | #define ATTR_FG_CLR 0b11000111 37 | 38 | #define ATTR_COLOR_MASK 0b11000000 39 | 40 | #define ATTR_ULINE 0b01000000 41 | #define ATTR_BLINK 0b10000000 42 | #define ATTR_MASK 0b11000000 43 | #define ATTR_CLR 0b00111111 44 | 45 | #define ATTR_DEFAULT ATTR_FG_WHITE|ATTR_BG_BLACK 46 | 47 | void setupPalette(); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /drivewire.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "drivewire.h" 5 | 6 | asm void dw_init(void) { 7 | asm 8 | { 9 | * DW Defines 10 | BECKER EQU DW_BECKER 11 | JMCPBCK EQU DW_JMPCPBCK 12 | BECKERTO EQU DW_BECKERTO 13 | ARDUINO EQU DW_ARDUINO 14 | BAUD38400 EQU DW_BAUD38400 15 | NOINTMASK EQU DW_NOINTMASK 16 | H6309 EQU DW_H6309 17 | SY6551N EQU DW_SY6551N 18 | COCO3FPGAWIFI EQU DW_COCO3FPGAWIFI 19 | MEGAMINIMPI EQU DW_MEGAMINIMPI 20 | IFEQ 1-DW_MMMUART 21 | MMMUARTB EQU MMMU1A 22 | ENDC 23 | IFEQ 2-DW_MMMUART 24 | MMMUARTB EQU MMMU2A 25 | ENDC 26 | 27 | use "dwdefs.d" 28 | IntMasks EQU $50 29 | * BBIN equ $FF22 30 | * BBOUT equ $FF20 31 | * BCKCTRL equ $FF41 32 | * BCKDATA equ $FF42 33 | 34 | lbra dwinitbegin 35 | use "dwinit.asm" 36 | dwinitbegin 37 | lbsr DWInit 38 | } 39 | } 40 | 41 | asm int dw_read(uint8_t *buf, uint16_t count) 42 | { 43 | asm 44 | { 45 | 46 | lbra dwrbegin 47 | use "dwread.asm" 48 | dwrbegin 49 | pshs x,y 50 | ldx 6,s buf 51 | ldy 8,s count 52 | lbsr DWRead 53 | bne allread@ 54 | bcs frameerr@ 55 | ldd #0 nothing read 56 | bra done@ 57 | frameerr@ 58 | ldd #-1 framing error 59 | bra done@ 60 | allread@ 61 | tfr y,d checksum 62 | done@ 63 | puls x,y 64 | } 65 | } 66 | 67 | asm int dw_write(uint8_t *buf, uint16_t count) 68 | { 69 | asm 70 | { 71 | lbra dwwbegin 72 | use "dwwrite.asm" 73 | dwwbegin 74 | * DW Defines above in dw_read 75 | pshs x,y 76 | ldx 6,s buf 77 | ldy 8,s count 78 | lbsr DWWrite 79 | tfr x,d 80 | subd 2,s buf 81 | subd #-1 82 | puls x,y 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DwTerm v0.2 2 | A RSDOS DriveWire 4 Terminal Program 3 | 4 | DwTerm is a terminal program for RS-DOS on the TRS-80 Color Computer 1/2/3. This program uses the DriveWire protocol for communication. A connection to a DriveWire server is _required_. 5 | 6 | * (new for v0.2) ANSI Support for CoCo1/2/3 on 32x16 VDG 7 | * (new for v0.2) ANSI Support for CoCo1/2 64x32 on CoCoVGA 8 | * (new for v0.2) ANSI Support for CoCo3 on 80x25 GIME 9 | * (new for v0.2) Break-C to hang up connection 10 | * (new for v0.2) Break-Q to Quit 11 | * (new for v0.2) Break-Break to send Ctrl-C 12 | 13 | ### Download the latest version from: 14 | [https://github.com/n6il/DwTerm/releases/latest](https://github.com/n6il/DwTerm/releases/latest) 15 | 16 | ### Documentation & GitHub Site: 17 | [https://github.com/n6il/DwTerm](https://github.com/n6il/DwTerm) 18 | 19 | ### Using it 20 | At the `DWTERM>` prompt you can type `dw` commands. Here are some examples: 21 | 22 | * Dial out to the internet: `ATD:` or `telnet ` 23 | * `dw disk show` 24 | * `dw disk insert ` 25 | * `help` for more commands 26 | * Break-C to hang up connection 27 | * Break-Q to Quit 28 | * Break-Break to send Ctrl-C 29 | 30 | 31 | ## ZIP File Distribution ## 32 | The Zip file Contains both: 33 | 34 | 1. `DWTERM.dsk` -- Disk image with all the BIN files listed below 35 | 2. All of the individual BIN/CAS/WAV versions listed below 36 | 37 | ## Standard Version for Bit-Banger ## 38 | 39 | Standard Versions for all Cocos, Minimum 32k RAM 40 | 41 | * `DWTRMBB1.BIN` - Bit-Banger (CoCo1 38400) 42 | * `DWTRMBB.BIN` - Bit-Banger (CoCo2 57600/CoCo3 115200) 43 | * `DWTRMB63.BIN` - Bit-Banger HD6309 (CoCo2 57600/CoCo3 115200) 44 | 45 | ## Lite Version (Minimum 16k RAM) 46 | 47 | Lite Version for Cocos with minimum 16k RAM 48 | 49 | Bit-Banger (38400) 50 | 51 | * `DWTBB1LT.BIN` 52 | * `DWTBB1LT.CAS` 53 | * `DWTBB1LT.WAV` 54 | 55 | Becker Port - Emulators, Coco3FPGA 56 | 57 | * `DWTBCKLT.BIN` 58 | * `DWTBB1LT.CAS` 59 | * `DWTBB1LT.WAV` 60 | 61 | ## Other Versions ## 62 | RS-232 Pak 63 | 64 | * `DWTRM232.BIN` -- All Cocos Minimum 32k RAM 65 | 66 | Becker Port 67 | 68 | * `DWTRMBCK.BIN` - Becker Port (32k Coco and up; Emulators; Coco3FPGA) 69 | * `DWTRMWI.BIN` - Coco3FPGA WIFI Module 70 | 71 | -------------------------------------------------------------------------------- /coco2.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef COCO2_H 5 | #define COCO2_H 6 | 7 | uint8_t xlate6847[256] = { 8 | // FIXME: do something with control characters ? 9 | 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, // 0x00 - 0x07 10 | 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, // 0x08 - 0x0f 11 | 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, // 0x10 - 0x17 12 | 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, // 0x18 - 0x1f 13 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // 0x20 - 0x27 14 | 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // 0x28 - 0x2f 15 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // 0x30 - 0x37 16 | 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, // 0x38 - 0x3f 17 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // 0x40 - 0x47 18 | 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // 0x48 - 0x4f 19 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // 0x50 - 0x57 20 | 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, // 0x58 - 0x5f 21 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x60 - 0x67 22 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // 0x68 - 0x6f 23 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // 0x70 - 0x77 24 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // 0x78 - 0x7f 25 | 26 | // FIXME: Adjust so line drawing looks ok 27 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, // 0x80 - 0x87 28 | 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, // 0x88 - 0x8f 29 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, // 0x90 - 0x97 30 | 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, // 0x98 - 0x9f 31 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, // 0xa0 - 0xa7 32 | 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, // 0xa8 - 0xaf 33 | 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, // 0xb0 - 0xb7 34 | 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, // 0xb8 - 0xbf 35 | 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, // 0xc0 - 0xc7 36 | 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, // 0xc8 - 0xcf 37 | 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, // 0xd0 - 0xd7 38 | 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, // 0xd8 - 0xdf 39 | 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, // 0xe0 - 0xe7 40 | 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, // 0xe8 - 0xef 41 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, // 0xf0 - 0xf7 42 | 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, // 0xf8 - 0xff 43 | 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /dwinit.asm: -------------------------------------------------------------------------------- 1 | ******************************************************* 2 | * 3 | * DWInit 4 | * Initialize DriveWire for CoCo Bit Banger 5 | 6 | DWInit 7 | IFNE ARDUINO 8 | 9 | * setup PIA PORTA (read) 10 | clr $FF51 11 | clr $FF50 12 | lda #$2C 13 | sta $FF51 14 | 15 | * setup PIA PORTB (write) 16 | clr $FF53 17 | lda #$FF 18 | sta $FF52 19 | lda #$2C 20 | sta $FF53 21 | rts 22 | 23 | ELSE 24 | 25 | pshs a,x 26 | 27 | IFNE COCO3FPGAWIFI 28 | clr BCKCTRL 115200 baud 29 | more@ 30 | lda BCKCTRL 31 | bita #$02 32 | beq done@ 33 | lda BCKDATA 34 | bra more@ 35 | done@ 36 | ENDC 37 | 38 | IFNE MEGAMINIMPI 39 | pshs b,cc 40 | orcc #$50 clear interrupts 41 | 42 | lda MPIREG save mpi settings 43 | pshs a 44 | anda #CTSMASK Save previous CTS, clear off STS 45 | ora #MMMSLT Set STS for MMMPI Uart Slot 46 | sta MPIREG 47 | 48 | sta MMMUARTB+RESET Reset UART 49 | 50 | lda #LCR8BIT|LCRPARN LCR: 8N1,DLAB=0 51 | sta MMMUARTB+LCR 52 | 53 | clr MMMUARTB+IER IER: disable interrupts 54 | 55 | lda #FCRFEN|FCRRXFCLR|FCRTXFCLR|FCRTRG8B FCR: enable,clear fifos, 8-byte trigger 56 | sta MMMUARTB+FCR 57 | 58 | lda #MCRDTREN|MCRRTSEN|MCRAFEEN MCR: DTR & Auto Flow Control 59 | sta MMMUARTB+MCR 60 | 61 | lda MMMUARTB+LCR enable DLAB 62 | ora #DLABEN 63 | sta MMMUARTB+LCR 64 | 65 | ldd #B921600 Set Divisor Latch 66 | ; std MMMUARTB+DL16 16-bit DL helper 67 | sta MMMUARTB+DLM 68 | stb MMMUARTB+DLL 69 | 70 | lda MMMUARTB+LCR disable DLAB 71 | anda #DLABDIS 72 | sta MMMUARTB+LCR 73 | 74 | clrloop@ lda MMMUARTB+LSR check RX FiFo Status 75 | bita #LSRDR 76 | beq restore@ its empty 77 | lda MMMUARTB dump any data that's there 78 | bra clrloop@ 79 | restore@ 80 | 81 | puls a restore mpi settings 82 | sta MPIREG 83 | puls b,cc 84 | ENDC 85 | 86 | IFDEF PIA1Base 87 | ldx #PIA1Base $FF20 88 | clr 1,x clear CD 89 | lda #%11111110 90 | sta ,x 91 | lda #%00110100 92 | sta 1,x 93 | lda ,x 94 | ENDC 95 | puls a,x,pc 96 | 97 | ENDC 98 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = cmoc 2 | CFLAGS = 3 | 4 | OTHEROBJS = hirestxt.o font4x8.o writeCharAt_51cols.o vt100.o coco3.o width64.o 5 | BINS = DWTRMBCK.BIN DWTRMBB.BIN DWTRMBB1.BIN DWTRM232.BIN DWTRMWI.BIN \ 6 | DWTRMB63.BIN DWTBCKLT.BIN DWTBB1LT.BIN DWTMMPI1.BIN DWTMMPI2.BIN 7 | 8 | all: DWTERM.dsk DWTERM.zip 9 | 10 | 11 | clean: 12 | rm -f *.o *.s *.lst *.map *.i *.link 13 | rm -f DWT*.BIN 14 | rm -f DWT*.CAS 15 | rm -f DWT*.WAV 16 | rm -f DWTERM.dsk 17 | rm -f DWTERM.zip 18 | 19 | dw_becker.o: drivewire.c dwread.asm dwwrite.asm 20 | $(CC) $(CFLAGS) -DDW_BECKER=1 -c -o $@ $< 21 | 22 | DWTRMBCK.BIN: dwterm.c $(OTHEROBJS) dw_becker.o 23 | $(CC) $(CFLAGS) -DDW_BECKER=1 -o $@ $^ 24 | 25 | DWTBCKLT.BIN: dwterm.c dw_becker.o 26 | $(CC) $(CFLAGS) --org=1800 -DLITE=1 -DDW_BECKER=1 -o $@ $^ 27 | bin2cas -r 44100 -C -n DWTERM -l 0x1800 -e 0x1800 -o $(@:BIN=WAV) $@ 28 | bin2cas -C -n DWTERM -l 0x1800 -e 0x1800 -o $(@:BIN=CAS) $@ 29 | 30 | dw_bb.o: drivewire.c dwread.asm dwwrite.asm 31 | $(CC) $(CFLAGS) -c -o $@ $< 32 | 33 | DWTRMBB.BIN: dwterm.c $(OTHEROBJS) dw_bb.o 34 | $(CC) $(CFLAGS) -o $@ $^ 35 | 36 | dw_bb6309.o: drivewire.c dwread.asm dwwrite.asm 37 | $(CC) $(CFLAGS) -DDW_H6309 -c -o $@ $< 38 | 39 | DWTRMB63.BIN: dwterm.c $(OTHEROBJS) dw_bb.o 40 | $(CC) $(CFLAGS) -DDW_H6309 -o $@ $^ 41 | 42 | dw_bb1.o: drivewire.c dwread.asm dwwrite.asm 43 | $(CC) $(CFLAGS) -DDW_BAUD38400=1 -c -o $@ $< 44 | 45 | DWTBB1LT.BIN: dwterm.c dw_bb1.o 46 | $(CC) $(CFLAGS) --org=1800 -DLITE=1 -DDW_BAUD38400=1 -o $@ $^ 47 | bin2cas -r 44100 -C -n DWTERM -l 0x1800 -e 0x1800 -o $(@:BIN=WAV) $@ 48 | bin2cas -C -n DWTERM -l 0x1800 -e 0x1800 -o $(@:BIN=CAS) $@ 49 | 50 | DWTRMBB1.BIN: dwterm.c $(OTHEROBJS) dw_bb1.o 51 | $(CC) $(CFLAGS) -DDW_BAUD38400=1 -o $@ $^ 52 | 53 | dw_rs232pak.o: drivewire.c dwread.asm dwwrite.asm 54 | $(CC) $(CFLAGS) -DDW_SY6551N=1 -c -o $@ $< 55 | 56 | DWTRM232.BIN: dwterm.c $(OTHEROBJS) dw_rs232pak.o 57 | $(CC) $(CFLAGS) -DDW_SY6551N=1 -o $@ $^ 58 | 59 | dw_coco3fpgawifi.o: drivewire.c dwread.asm dwwrite.asm 60 | $(CC) $(CFLAGS) -DDW_COCO3FPGAWIFI=1 -DDW_BECKER=1 -c -o $@ $< 61 | 62 | DWTRMWI.BIN: dwterm.c $(OTHEROBJS) dw_coco3fpgawifi.o 63 | $(CC) $(CFLAGS) -DDW_COCO3FPGAWIFI=1 -DDW_BECKER=1 -o $@ $^ 64 | 65 | # MegaMiniMPI - has 2 uarts, DW_MMMUART=1 or DW_MMMUART=2 66 | dw_megaminimpi1.o: drivewire.c dwread.asm dwwrite.asm 67 | $(CC) $(CFLAGS) -DDW_MEGAMINIMPI=1 -DDW_MMMUART=1 -c -o $@ $< 68 | 69 | DWTMMPI1.BIN: dwterm.c $(OTHEROBJS) dw_megaminimpi1.o 70 | $(CC) $(CFLAGS) -DDW_MEGAMINIMPI=1 -DDW_MMMUART=1 -o $@ $^ 71 | 72 | dw_megaminimpi2.o: drivewire.c dwread.asm dwwrite.asm 73 | $(CC) $(CFLAGS) -DDW_MEGAMINIMPI=1 -DDW_MMMUART=2 -c -o $@ $< 74 | 75 | DWTMMPI2.BIN: dwterm.c $(OTHEROBJS) dw_megaminimpi1.o 76 | $(CC) $(CFLAGS) -DDW_MEGAMINIMPI=1 -DDW_MMMUART=2 -o $@ $^ 77 | 78 | 79 | DWTERM.dsk: $(BINS) 80 | rm -f DWTERM.dsk 81 | decb dskini DWTERM.dsk 82 | for i in $^; do decb copy $$i DWTERM.dsk,$$i -b -2; done 83 | decb dir DWTERM.dsk, 84 | 85 | DWTERM.zip: $(BINS) DWTERM.dsk 86 | zip $@ *.BIN *.CAS *.WAV DWTERM.dsk 87 | -------------------------------------------------------------------------------- /width64.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "width64.h" 4 | 5 | /* 6 | * 7 | * The following is copied from: 8 | * 9 | * http://www.cocovga.com/documentation/software-mode-control/ 10 | * 11 | */ 12 | 13 | #define WIDTH64ADDR 0x2000 14 | #define WIDTH64DLEN 9 15 | uint8_t width64_reg_data[WIDTH64DLEN] = { 16 | 0x00, // reset register - reset no register banks 17 | 0x81, // edit mask - modify enhanced modes and font registers 18 | 0x00, // reserved 19 | 0x02, // font - force lowercase (use #$03 for both lowercase and T1 character set) 20 | 0x00, // artifact 21 | 0x00, // extras 22 | 0x00, // reserved 23 | 0x00, // reserved 24 | 0x02, // enhanced modes - 64-column enable 25 | }; 26 | 27 | void width64(void) 28 | { 29 | // 1. Set up a 512-byte-aligned memory region with register values you want to stream into CoCoVGA. For this example, 30 | // we are arbitrarily selecting address $2000 as the start address. 31 | memcpy((void *)WIDTH64ADDR, (const void*)width64_reg_data, WIDTH64DLEN); 32 | 33 | asm { 34 | * 2. Wait for VSYNC, which signals the start of the vertical blanking region. 35 | 36 | PSHS CC save CC 37 | ORCC #$50 mask interrupts 38 | 39 | LDA $FF03 40 | PSHS A save PIA configuration 41 | 42 | LDA $FF03 43 | ORA #$04 ensure PIA 0B is NOT setting direction 44 | STA $FF03 45 | 46 | LDA $FF03 47 | ANDA #$FD vsync flag - trigger on falling edge 48 | STA $FF03 49 | 50 | LDA $FF03 51 | ORA #$01 enable vsync IRQ (although interrupt itself will be ignored via mask) 52 | STA $FF03 53 | 54 | LDA $FF02 clear any pending VSYNC 55 | @L1 LDA $FF03 wait for flag to indicate... 56 | BPL @L1 ...falling edge of FS (Field Sync) 57 | LDA $FF02 clear VSYNC interrupt flag 58 | 59 | * 3. During VSYNC, point SAM to 512 byte page set up in step 1 (via SAM page select registers $FFC6-$FFD3). For this 60 | * example, this page is at $7C00. Divide by 512 to get page number: 61 | * $FC00/512 = $3E = 011 1110 62 | * SAM page selection is performed by writing a single address to set or clear each bit. In this case we want to 63 | * clear bits 0 and 6, so write to even addresses for those, and write to odd addresses to set bits 1 through 5. 64 | 65 | * STA $FFC6 clear SAM_F0 66 | * STA $FFC9 set SAM_F1 67 | * STA $FFCB set SAM_F2 68 | * STA $FFCD set SAM_F3 69 | * STA $FFCF set SAM_F4 70 | * STA $FFD1 set SAM_F5 71 | * STA $FFD2 clear SAM_F6 72 | 73 | * $2000 / 512 = $10 = 001 0000 74 | STA $FFC6 clear SAM_F0 75 | STA $FFC8 clear SAM_F1 76 | STA $FFCA clear SAM_F2 77 | STA $FFCC clear SAM_F3 78 | STA $FFCF set SAM_F4 79 | STA $FFD0 clear SAM_F5 80 | STA $FFD2 clear SAM_F6 81 | 82 | * 4. Also during VSYNC, write 6847 VDG combination lock via PIA1B ($FF22) bits 7:3. 83 | 84 | LDA $FF22 get current PIA value 85 | ANDA #$07 mask off bits to change 86 | ORA #$90 set combo lock 1 87 | STA $FF22 write to PIA 88 | ANDA #$07 mask off bits to change 89 | ORA #$48 set combo lock 2 90 | STA $FF22 write to PIA 91 | ANDA #$07 mask off bits to change 92 | ORA #$A0 set combo lock 3 93 | STA $FF22 write to PIA 94 | ANDA #$07 mask off bits to change 95 | ORA #$F8 set combo lock 4 96 | STA $FF22 write to PIA 97 | 98 | * 5. Still during VSYNC, configure VDG and SAM back to mode 0. (In this case, the desired CoCoVGA register page to program is 0.) 99 | 100 | LDA $FF22 get current PIA value 101 | ANDA #$07 mask off bits to change 102 | ORA #$00 select CoCoVGA register page 0 103 | STA $FF22 write to PIA 104 | STA $FFC0 clear SAM_V0 105 | STA $FFC2 clear SAM_V1 106 | STA $FFC4 clear SAM_V2 107 | 108 | * 6. Wait for next VSYNC while SAM and VDG stream in the entire page of register values to CoCoVGA and CoCoVGA 109 | * displays the previous frame of video. 110 | 111 | @L2 LDA $FF03 wait for flag to indicate... 112 | BPL @L2 ...falling edge of FS (Field Sync) 113 | LDA $FF02 clear VSYNC interrupt flag 114 | PULS A from stack... 115 | STA $FF03 ...restore original PIA configuration 116 | PULS CC restore ability to see interrupts 117 | 118 | * 7. Point SAM page select to video page you want to display. For this example, lets assume that this is at $E00 119 | * which (divided by 512 bytes) is page 7. 120 | 121 | STA $FFC7 set SAM_F0 122 | STA $FFC9 set SAM_F1 123 | STA $FFCB set SAM_F2 124 | STA $FFCC clear SAM_F3 125 | STA $FFCE clear SAM_F4 126 | STA $FFD0 clear SAM_F5 127 | STA $FFD2 clear SAM_F6 128 | 129 | * 8. Program SAM and VDG to the appropriate video mode. As the final gate to enabling 64-column text mode, CoCoVGA 130 | * recognizes the VDGs only 2kB mode, CG2 ($2). 131 | 132 | LDA $FF22 get current PIA value 133 | ANDA #$0F mask off bits to change 134 | ORA #$A0 set VDG to CG2 135 | STA $FF22 write to PIA 136 | STA $FFC0 clear SAM_V0 137 | STA $FFC3 set SAM_V1 138 | STA $FFC4 clear SAM_V2 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /dwdefs.d: -------------------------------------------------------------------------------- 1 | ******************************************************************** 2 | * dwdefs - DriveWire Definitions File 3 | * 4 | * $Id: dwdefs.d,v 1.10 2010/02/21 06:24:47 aaronwolfe Exp $ 5 | * 6 | * Ed. Comments Who YY/MM/DD 7 | * ------------------------------------------------------------------ 8 | * 1 Started BGP 03/04/03 9 | * 2 Added DWGLOBS area BGP 09/12/27 10 | 11 | nam dwdefs 12 | ttl DriveWire Definitions File 13 | 14 | IFNDEF DWDEFS_D 15 | DWDEFS_D equ 1 16 | 17 | * Addresses 18 | BBOUT equ $FF20 19 | BBIN equ $FF22 20 | IFNE COCO3FPGAWIFI 21 | BCKCTRL equ $FF6C 22 | BCKDATA equ $FF6D 23 | ELSE 24 | BCKCTRL equ $FF41 25 | BCKDATA equ $FF42 26 | ENDC 27 | 28 | MPIREG equ $FF7F MPI Register 29 | CTSMASK equ $F0 Get CTS 30 | CTSSHIFT equ $4 Number of shifts for CTS 31 | SCSMASK equ $0F Get SCS 32 | 33 | IFNE MEGAMINIMPI 34 | MMMSLT equ $05 MPI Slot(SCS) for MegaMiniMPI 35 | MMMU1A equ $FF40 Address for UART 1 36 | MMMU2A equ $FF50 Address for UART 2 37 | THR equ $00 Transmit Holding Register 38 | RHR equ $00 Recieve Holding Resister 39 | IER equ $01 Interrupt Enable Register 40 | IIR equ $02 Interrupt Identification Register 41 | FCR equ $02 FIFO Control Register 42 | LCR equ $03 Line Control Register 43 | MCR equ $04 Modem Control Register 44 | LSR equ $05 Line Status Register 45 | MSR equ $06 Modem Status Register 46 | SCR equ $07 Scratch Register 47 | RESET equ $08 Reset 48 | DLL equ $00 Divisor Latch LSB 49 | DLM equ $01 Divisor Latch MSB 50 | DL16 equ $0A 16-bit divisor window 51 | 52 | * 16550 Line Control Register 53 | LCR5BIT equ %00000000 54 | LCR6BIT equ %00000001 55 | LCR7BIT equ %00000010 56 | LCR8BIT equ %00000011 57 | LCRPARN equ %00000000 58 | LCRPARE equ %00000100 59 | LCRPARO equ %00001100 60 | * BREAK 61 | BRKEN equ %01000000 62 | BRKDIS equ %10111111 63 | * 16550 DLAB 64 | DLABEN equ %10000000 65 | DLABDIS equ %01111111 66 | 67 | * 16550 Baud Rate Definitions 68 | B600 equ 3072 69 | B1200 equ 1536 70 | B2400 equ 768 71 | B4800 equ 384 72 | B9600 equ 192 73 | B19200 equ 96 74 | B38400 equ 48 75 | B57600 equ 32 76 | B115200 equ 16 77 | B230400 equ 8 78 | B460800 equ 4 79 | B921600 equ 2 80 | B1843200 equ 1 81 | 82 | * 16550 Line Status Register Defs 83 | LSRDR equ %00000001 LSR:Data Ready 84 | LSRTHRE equ %00100000 LSR:Transmit Holding Register/FIFO Empty 85 | LSRTE equ %01000000 LSR:Transmit Empty 86 | 87 | * 16550 Fifo Control Register 88 | FCRFEN equ %00000001 Enable RX and TX FIFOs 89 | FCRFDIS equ %11111110 Disable RX and TX FIFOs 90 | FCRRXFCLR equ %00000010 Clear RX FIFO 91 | FCRTXFCLR equ %00000100 Clear TX FIFO 92 | FCRTRG1B equ %00000000 1-Byte FIFO Trigger 93 | FCRTRG4B equ %01000000 4-Byte FIFO Trigger 94 | FCRTRG8B equ %10000000 8-Byte FIFO Trigger 95 | FCRTRG14B equ %11000000 14-Byte FIFO Trigger 96 | 97 | * 16550 Modem Control Register 98 | MCRDTREN equ %00000001 Enable DTR Output 99 | MCRDTRDIS equ %11111110 Disable DTR Output 100 | MCRRTSEN equ %00000010 Enable RTS Output 101 | MCRRTSDIS equ %11111101 Disable RTS Output 102 | MCRAFEEN equ %00100000 Enable Auto Flow Control 103 | MCRAFEDIS equ %11011111 Disable Auto Flow Control 104 | ENDC 105 | 106 | * Opcodes 107 | OP_NOP equ $00 No-Op 108 | OP_RESET1 equ $FE Server Reset 109 | OP_RESET2 equ $FF Server Reset 110 | OP_RESET3 equ $F8 Server Reset 111 | OP_DWINIT equ 'Z DriveWire dw3 init/OS9 boot 112 | OP_TIME equ '# Current time requested 113 | OP_INIT equ 'I Init routine called 114 | OP_READ equ 'R Read one sector 115 | OP_REREAD equ 'r Re-read one sector 116 | OP_READEX equ 'R+128 Read one sector 117 | OP_REREADEX equ 'r+128 Re-read one sector 118 | OP_WRITE equ 'W Write one sector 119 | OP_REWRIT equ 'w Re-write one sector 120 | OP_GETSTA equ 'G GetStat routine called 121 | OP_SETSTA equ 'S SetStat routine called 122 | OP_TERM equ 'T Term routine called 123 | OP_SERINIT equ 'E 124 | OP_SERTERM equ 'E+128 125 | 126 | * Printer opcodes 127 | OP_PRINT equ 'P Print byte to the print buffer 128 | OP_PRINTFLUSH equ 'F Flush the server print buffer 129 | 130 | * Serial opcodes 131 | OP_SERREAD equ 'C 132 | OP_SERREADM equ 'c 133 | OP_SERWRITE equ 'C+128 134 | OP_SERGETSTAT equ 'D 135 | OP_SERSETSTAT equ 'D+128 136 | 137 | * for dw vfm 138 | OP_VFM equ 'V+128 139 | 140 | * WireBug opcodes (Server-initiated) 141 | OP_WIREBUG_MODE equ 'B 142 | * WireBug opcodes (Server-initiated) 143 | OP_WIREBUG_READREGS equ 'R Read the CoCo's registers 144 | OP_WIREBUG_WRITEREGS equ 'r Write the CoCo's registers 145 | OP_WIREBUG_READMEM equ 'M Read the CoCo's memory 146 | OP_WIREBUG_WRITEMEM equ 'm Write the CoCo's memory 147 | OP_WIREBUG_GO equ 'G Tell CoCo to get out of WireBug mode and continue execution 148 | 149 | * VPort opcodes (CoCo-initiated) 150 | OP_VPORT_READ equ 'V 151 | OP_VPORT_WRITE equ 'v 152 | 153 | * Error definitions 154 | E_CRC equ $F3 Same as NitrOS-9 E$CRC 155 | 156 | ENDC 157 | -------------------------------------------------------------------------------- /hirestxt.h: -------------------------------------------------------------------------------- 1 | /* hirestxt.h - 51x24 black-on-green PMODE 4 text screen. 2 | 3 | By Pierre Sarrazin 4 | This file is in the public domain. 5 | 6 | Quick Guide: 7 | - Call initHiResTextScreen() first. 8 | - End with a call to closeHiResTextScreen(). 9 | - #define HIRESTEXT_NO_VT52 to avoid compiling the VT-52 code. 10 | */ 11 | 12 | #ifndef _hirestxt_h_ 13 | #define _hirestxt_h_ 14 | 15 | #include 16 | 17 | 18 | // Writes a character at position (x, y) on a 42x24 text screen. 19 | // 20 | void writeCharAt_42cols(byte x, byte y, byte asciiCode); 21 | 22 | 23 | // Writes a character at position (x, y) on a 42x24 text screen. 24 | // 25 | void writeCharAt_51cols(byte x, byte y, byte asciiCode); 26 | 27 | 28 | // Pointer to a function that writes a character at position (x, y). 29 | // 30 | typedef void (*WriteCharAtFuncPtr)(byte x, byte y, byte asciiCode); 31 | 32 | 33 | // Initializer to be used when calling initHiResTextScreen(). 34 | // 35 | // numColumns: 42 or 51 (characters per line). 36 | // 37 | // writeCharAtFuncPtr: Either writeCharAt_42cols or writeCharAt_51cols. 38 | // Must be consistent with numColumns. 39 | // 40 | // textScreenPageNum: 512-byte page index in 0..127. 41 | // Actual graphics address becomes textScreenPageNum * 512. 42 | // To position the screen at $0E00, divide this address by 512, 43 | // which gives 7. 44 | // 45 | // If redirectPrintf is true, printf() will print to the 46 | // hi-res screen until closeHiResTextScreen() is called. 47 | // This option has no effect if HIRESTEXT_NO_VT52 is #defined. 48 | // 49 | struct HiResTextScreenInit 50 | { 51 | byte numColumns; 52 | WriteCharAtFuncPtr writeCharAtFuncPtr; 53 | byte textScreenPageNum; 54 | BOOL redirectPrintf; 55 | }; 56 | 57 | 58 | enum 59 | { 60 | HIRESHEIGHT = 24, // number of text lines 61 | PIXEL_COLS_PER_SCREEN = 256, 62 | PIXEL_ROWS_PER_SCREEN = 192, 63 | PIXEL_ROWS_PER_TEXT_ROW = 8, 64 | BYTES_PER_PIXEL_ROW = 32, 65 | }; 66 | 67 | 68 | // Width in characters of the high-resolution text screen. 69 | // 70 | extern byte hiResWidth; 71 | 72 | // Text cursor position. 73 | // textPosX is allowed to be equal to hiResWidth. When this many characters 74 | // have been written on a line, the cursor is considered to be not on the 75 | // next line, but past the last displayed column on the original line. 76 | // Then when a '\n' is written, the cursor goes to column 0 of the next line. 77 | // This avoids having the '\n' insert an empty line. 78 | // See how '\n' is processed by writeChar(). 79 | // 80 | // To change this position, call moveCursor(). 81 | // 82 | extern byte textPosX; // 0..hiResWidth 83 | extern byte textPosY; // 0..HIRESHEIGHT-1 84 | 85 | // Location of the 6k PMODE 4 screen buffer. 86 | // Must be a multiple of 512. 87 | // 88 | extern byte *textScreenBuffer; 89 | 90 | 91 | void moveCursor(byte x, byte y); 92 | void setTextScreenAddress(byte pageNum); 93 | 94 | #ifndef HIRESTEXT_NO_VT52 95 | void initVT52(); 96 | #endif 97 | 98 | 99 | // Call this first, with a non null pointer to an initializer struct. 100 | // 101 | // See struct HiResTextScreenInit above. 102 | // 103 | // Does not keep a reference of the HiResTextScreenInit object. 104 | // 105 | // The screen is cleared to green. The font is black on green. 106 | // 107 | void initHiResTextScreen(struct HiResTextScreenInit *init); 108 | 109 | 110 | // Call this last. 111 | // 112 | void closeHiResTextScreen(); 113 | 114 | 115 | // Clear text rows from y (0..HIRESHEIGHT-1) to the end of the screen. 116 | // Does nothing if y is out of range. 117 | // 118 | void clearRowsToEOS(byte byteToClearWith, byte textRow); 119 | 120 | 121 | // pageNum: 512-byte page index in 0..127. 122 | // Actual graphics address becomes pageNum * 512. 123 | // 124 | void setTextScreenAddress(byte pageNum); 125 | 126 | 127 | // Writes a PRINTABLE 4x8 character at column x and row y of a 51x24 text screen. 128 | // x: 0..hiResWidth-1. 129 | // y: 0..HIRESHEIGHT-1. 130 | // asciiCode: MUST be in the range 32..127 or 160..255, except if 0, 131 | // which means invert colors at position (x, y). 132 | // Uses textScreenBuffer, frameByteAddrTable[], frameBitOffsetTable[] and 133 | // frameMaskTable[]. 134 | // 135 | // Does NOT advance the cursor. 136 | // 137 | void writeCharAt(byte x, byte y, byte asciiCode); 138 | 139 | 140 | void invertPixelsAtCursor(); 141 | 142 | 143 | // Scrolls the 51x24 screen one text row up. 144 | // Fills the bottom text row with set pixels. 145 | // 146 | void scrollTextScreenUp(); 147 | 148 | 149 | // x: Column number (0..hiResWidth-1). 150 | // y: Row number (0..HIRESHEIGHT-1). 151 | // Does nothing if x or y are out of range. 152 | // 153 | void moveCursor(byte x, byte y); 154 | 155 | 156 | // Puts the cursor in the upper left position. 157 | // 158 | #define home() (moveCursor(0, 0)) 159 | 160 | 161 | // Fills the screen with spaces (does not move the cursor). 162 | // 163 | #define clear() (clearRowsToEOS(0xff, 0)) 164 | 165 | 166 | // Homes the cursor and clears the screen. 167 | // 168 | #define clrscr() do { home(); clear(); } while (0) 169 | 170 | 171 | // Writes a character at the current cursor position and advances the cursor. 172 | // Scrolls the screen up one row if the cursor would go off the bottom. 173 | // Ignores non-printable characters. 174 | // str: Supports \a, \b, \t, \n, \f, \r. 175 | // 176 | void writeChar(byte ch); 177 | 178 | 179 | // Calls writeChar() for each character in the given string, 180 | // up to and excluding the terminating '\0'. 181 | // 182 | void writeString(const char *str); 183 | 184 | 185 | // row: 0..HIRESHEIGHT-1 186 | // line: String of at most hiResWidth characters. 187 | // Leaves the cursor at the end of the line. 188 | // 189 | void writeCenteredLine(byte row, const char *line); 190 | 191 | 192 | // Writes unsigned word 'w' to the screen in decimal. 193 | // 194 | void writeDecWord(word w); 195 | 196 | // Writes spaces from the cursor to the end of the current line. 197 | // Does NOT move the cursor. 198 | // 199 | void clrtoeol(); 200 | 201 | 202 | // Writes spaces from the cursor to the end of the screen. 203 | // Does NOT move the cursor. 204 | // 205 | void clrtobot(); 206 | 207 | 208 | // Removes the cursor if present (it is typically displayed 209 | // by animateCursor()). 210 | // 211 | void removeCursor(); 212 | 213 | 214 | // To be called periodically. 215 | // Call removeCursor() when the animation must stop. 216 | // 217 | void animateCursor(); 218 | 219 | 220 | // Returns ASCII code of pressed key (using inkey(), which calls Color Basic). 221 | // Uses textPosX, textPosY. 222 | // 223 | byte waitKeyBlinkingCursor(); 224 | 225 | 226 | /////////////////////////////////////////////////////////////////////////////// 227 | 228 | 229 | #ifndef HIRESTEXT_NO_VT52 230 | 231 | // Resets the VT52 state machine. 232 | // 233 | void initVT52(); 234 | 235 | 236 | #endif /* HIRESTEXT_NO_VT52 */ 237 | 238 | 239 | // Calls writeChar() and moveCursor() to execute the VT52 sequence 240 | // provided by the calls to this function. 241 | // 242 | // Source: http://bitsavers.trailing-edge.com/pdf/dec/terminal/gigi/EK-0GIGI-RC-001_GIGI_Programming_Reference_Card_Sep80.pdf 243 | // 244 | void processConsoleOutChar(byte ch); 245 | 246 | 247 | #endif /* _hirestxt_h_ */ 248 | -------------------------------------------------------------------------------- /writeCharAt_51cols.c: -------------------------------------------------------------------------------- 1 | /* writeCharAt_51cols.c 2 | 3 | By Pierre Sarrazin 4 | This file is in the public domain. 5 | */ 6 | 7 | #include "hirestxt.h" 8 | 9 | #include "font4x8.h" 10 | 11 | 12 | // About the 51x24 mode: 13 | // 14 | // Column Byte Bit Mask 1 Mask 2 15 | // 16 | // 0 0 0 00000111 11111111 17 | // 1 0 5 11111000 00111111 18 | // 2 1 2 11000001 11111111 19 | // 3 1 7 11111110 00001111 20 | // 4 2 4 11110000 01111111 21 | // 5 3 1 10000011 11111111 22 | // 6 3 6 11111100 00011111 23 | // 7 4 3 11100000 11111111 24 | // 25 | // A frame is a 5-byte (40-bit) region. 26 | // In 51x24 mode, 8 characters (5 pixels wide each) fit in a frame. 27 | 28 | 29 | // frameByteAddrTable_51cols[c] is the byte offset in a frame of 30 | // the c-th (0..7) character in that frame. 31 | // frameBitOffsetTable_51cols[c] is the number of right shifts to apply 32 | // to position bits at the c-th (0..7) character of a frame. 33 | // frameMaskTable_51cols[c] is the AND mask that applies at the c-th (0..7) 34 | // character of a frame. 35 | // 36 | static const byte frameByteAddrTable_51cols[8] = { 0, 0, 1, 1, 2, 3, 3, 4 }; 37 | static const byte frameBitOffsetTable_51cols[8] = { 0, 5, 2, 7, 4, 1, 6, 3 }; 38 | static const word frameMaskTable_51cols[8] = { 0x07ff, 0xf83f, 0xc1ff, 0xfe0f, 0xf07f, 0x83ff, 0xfc1f, 0xe0ff }; 39 | 40 | 41 | void putBitmaskInScreenWord(byte asciiCode, word *screenWord, 42 | const byte *charBitmask, word charWordShifts, 43 | word mask) 44 | { 45 | word charWord, d, invMask, temp, leftTerm, row; 46 | asm 47 | { 48 | ; Cache an inverted mask. 49 | ldd :mask 50 | coma 51 | comb 52 | std :invMask 53 | 54 | ; Initialize for() loop counter. 55 | clrb ; B caches :row 56 | stb :row ; stb is 2 cycles less than clr 57 | 58 | @writeCharAt_for 59 | ; charWord = ((word) charBitmask[row]) << 8; 60 | ldx :charBitmask 61 | lda b,x ; B caches :row 62 | clrb ; D is now charWord 63 | ; 64 | ; charWord >>= charWordShifts; 65 | ldx :charWordShifts ; counter 66 | beq @writeCharAt_shifts_done 67 | @writeCharAt_shift_loop ; shift D right X times 68 | lsra 69 | rorb 70 | leax -1,x 71 | bne @writeCharAt_shift_loop 72 | @writeCharAt_shifts_done 73 | std :charWord 74 | ; 75 | ; word d = *screenWord; ; read screen bits (big endian word read) 76 | ldx :screenWord 77 | ldd ,x 78 | std :d 79 | ; if (asciiCode) 80 | tst :asciiCode 81 | beq @writeCharAt_nul 82 | ; 83 | ; d &= mask; 84 | ; d |= charWord; 85 | anda :mask 86 | andb :mask[1] ; adds one to offset applied on U 87 | ora :charWord 88 | orb :charWord[1] 89 | std :d 90 | bra @writeCharAt_after_if 91 | ; 92 | ; else: d = (d & mask) | (d ^ invMask); 93 | @writeCharAt_nul 94 | std :temp 95 | anda :mask 96 | andb :mask[1] 97 | std :leftTerm 98 | ; 99 | ldd :temp 100 | eora :invMask 101 | eorb :invMask[1] 102 | ; 103 | ora :leftTerm 104 | orb :leftTerm[1] 105 | std :d 106 | ; 107 | @writeCharAt_after_if 108 | ; 109 | ; *screenWord = d; 110 | ; screenWord += 16; 111 | std [:screenWord] 112 | leax BYTES_PER_PIXEL_ROW,x ; N.B.: X still contains :screenWord, so increment it 113 | stx :screenWord 114 | ; 115 | ldb :row 116 | incb 117 | stb :row 118 | cmpb #PIXEL_ROWS_PER_TEXT_ROW 119 | blo @writeCharAt_for 120 | } 121 | } 122 | 123 | 124 | void writeCharAt_51cols(byte x, byte y, byte asciiCode) 125 | { 126 | #if 0 // Original (tested) code in C: 127 | const byte frameCol = x % 8; 128 | word *screenWord = (word *) (textScreenBuffer + ((word) y * 256) + x / 8 * 5 + frameByteAddrTable_51cols[frameCol]); 129 | // 256 = 8 rows per character, times 32 bytes per pixel row 130 | // 8 = 8 chars par frame. 5 = 5 bytes per frame. 131 | 132 | const byte charWordShifts = frameBitOffsetTable_51cols[frameCol]; 133 | const word mask = frameMaskTable_51cols[frameCol]; 134 | 135 | // In charBitmask, only the high 5 bits of each byte are significant. 136 | // The others must be 0. 137 | // 138 | const byte *charBitmask = font4x8 + (((word) asciiCode - (asciiCode < 128 ? 32 : 64)) << 3); 139 | 140 | for (byte row = 0; row < PIXEL_ROWS_PER_TEXT_ROW; ++row) 141 | { 142 | word charWord = ((word) charBitmask[row]) << 8; // load into A, reset B; high nybble of D now init 143 | charWord >>= charWordShifts; 144 | 145 | word d = *screenWord; // read screen bits (big endian word read) 146 | if (asciiCode) 147 | { 148 | d &= mask; 149 | d |= charWord; 150 | } 151 | else 152 | { 153 | d = (d & mask) | (d ^ ~mask); // invert colors 154 | } 155 | *screenWord = d; 156 | 157 | screenWord += 16; // point to next row (32 bytes down) 158 | } 159 | #else // Equivalent code in assembler: 160 | byte frameCol; 161 | word *screenWord; 162 | byte *charBitmask; 163 | word charWordShifts; 164 | 165 | asm 166 | { 167 | ldb :x 168 | andb #7 ; % 8 169 | stb :frameCol 170 | 171 | ; word *screenWord = (word *) (textScreenBuffer + ((word) y * 256) + x / 8 * 5 + frameByteAddrTable_51cols[frameCol]); 172 | ldb :frameCol 173 | leax :frameByteAddrTable_51cols 174 | ldb b,x 175 | pshs b 176 | 177 | ldb :x 178 | lsrb 179 | lsrb 180 | lsrb ; x / 8 181 | lda #5 182 | mul 183 | 184 | addb ,s+ 185 | adca #0 186 | 187 | adda :y ; add y * 256 to D 188 | addd :textScreenBuffer 189 | std :screenWord 190 | 191 | ; byte *charBitmask = font4x8 + (((word) asciiCode - (asciiCode < 128 ? 32 : 64)) << 3); 192 | ldb :asciiCode 193 | bpl @writeCharAt_sub32 ; if 0..127 194 | subb #64 ; assuming B in 160..255 195 | bra @writeCharAt_sub_done 196 | @writeCharAt_sub32 197 | subb #32 198 | @writeCharAt_sub_done 199 | clra ; D = result of subtraction; shift this 3 bits left 200 | lslb 201 | rola 202 | lslb 203 | rola 204 | lslb 205 | rola 206 | leax :font4x8 207 | leax d,x 208 | stx :charBitmask 209 | 210 | ; word charWordShifts = frameBitOffsetTable_51cols[frameCol]; 211 | ldb :frameCol 212 | leax :frameBitOffsetTable_51cols 213 | ldb b,x 214 | clra 215 | std :charWordShifts 216 | 217 | ; word mask = frameMaskTable_51cols[frameCol]; 218 | ldb :frameCol 219 | lslb ; index in array of words 220 | leax :frameMaskTable_51cols 221 | ldd b,x 222 | 223 | ; Call putBitmaskInScreenWord(byte asciiCode, word *screenWord, const byte *charBitmask, word charWordShifts, word mask) 224 | ; 225 | pshs b,a 226 | ldx :charWordShifts 227 | ldd :charBitmask 228 | pshs x,b,a 229 | ldx :screenWord 230 | ldb :asciiCode 231 | pshs x,b,a ; A is garbage: does not matter 232 | lbsr putBitmaskInScreenWord 233 | leas 10,s 234 | } 235 | #endif 236 | } 237 | -------------------------------------------------------------------------------- /dwterm.c: -------------------------------------------------------------------------------- 1 | /* DriveWire4 CLI and Terminal */ 2 | /* */ 3 | /* By Michael Furman, July 2 2017 */ 4 | /* */ 5 | /* This file is in the public domain */ 6 | 7 | #include 8 | #include 9 | 10 | #ifndef LITE 11 | #include "hirestxt.h" 12 | // #include "hirestxt.c" 13 | #include "font4x8.h" 14 | // #include "font4x8.c" 15 | #include "vt100.h" 16 | #include "width64.h" 17 | #endif 18 | 19 | #include "dwterm.h" 20 | #include "drivewire.h" 21 | 22 | // DW Commands 23 | #define OP_SERSETSTAT 0xC4 24 | #define OP_SERREAD 0x43 25 | #define OP_SERREADM 0x63 26 | #define OP_FASTWRITE 0x80 27 | // DW SETSERSTAT Codes 28 | #define SS_Open 0x29 29 | #define SS_Close 0x2A 30 | 31 | #ifdef LITE 32 | #define vt100_puts printf 33 | #define vt100_putchar putchar 34 | #define vt100 putchar 35 | #define sgrClear() 36 | #endif 37 | 38 | //#define DEBUG 39 | 40 | uint8_t channel_open = 0; 41 | #define BUFFER_SIZE 1024 42 | uint8_t buffer[BUFFER_SIZE]; 43 | uint8_t vt100En = 0; 44 | 45 | void dw_putb(uint8_t c) 46 | { 47 | uint8_t buf[1]; 48 | buf[0] = c; 49 | dw_write(buf, 1); 50 | } 51 | 52 | uint8_t dw_getb() 53 | { 54 | uint8_t buf[1]; 55 | int checksum; 56 | checksum = dw_read(buf, 1); 57 | if (checksum <= 0) 58 | return 0; 59 | return buf[0]; 60 | } 61 | 62 | 63 | // open_channel 64 | // Open a DW Serial Channel 65 | // channel - channel to open 66 | void open_channel(uint8_t channel) 67 | { 68 | dw_putb(OP_SERSETSTAT); 69 | dw_putb(channel); 70 | dw_putb(SS_Open); 71 | #ifdef DEBUG 72 | printf( "OPEN CHANNEL " ); 73 | printf("%d", channel); 74 | putchar( '\r' ); 75 | #endif 76 | channel_open = channel; 77 | } 78 | 79 | // get_status 80 | // Get channel status from DW Server 81 | void get_status(uint8_t channel, uint8_t *status) 82 | { 83 | // printf( "GET STATUS " ); 84 | // printf("%d", channel); 85 | // putchar( '\r' ); 86 | dw_putb(OP_SERREAD); 87 | // dw_putb(channel); 88 | 89 | dw_read(status, 2); 90 | 91 | #ifdef DEBUG 92 | printf("%x %x\n", status[0], status[1]); 93 | #endif 94 | // return (uint16_t)*buf; 95 | } 96 | 97 | void close_channel(uint8_t channel) 98 | { 99 | uint8_t buf[2]; 100 | dw_putb(OP_SERSETSTAT); 101 | dw_putb(channel); 102 | dw_putb(SS_Close); 103 | get_status(channel, buf); 104 | } 105 | 106 | void writeChannel(uint8_t channel, uint8_t c) 107 | { 108 | dw_putb(OP_FASTWRITE+channel); 109 | dw_putb(c); 110 | } 111 | 112 | uint16_t readChannel(uint8_t channel, uint8_t *buf, uint16_t size, uint8_t wait) 113 | { 114 | uint16_t read=0; 115 | uint8_t status[2] = {0, 0}; 116 | get_status(channel, status); 117 | if (wait) 118 | while(status[0] == 0) 119 | get_status(channel, status); 120 | else 121 | if (status[0] == 0) 122 | return 0; 123 | while(status[0] != 0 && read16 && status[1] > 0) 128 | { 129 | uint16_t count = (size-read)0; n--) 310 | { 311 | i = *p++; 312 | if (iac) { 313 | writeChannel(1, 255); 314 | writeChannel(1, iac); 315 | writeChannel(1, i); 316 | iac = 0; 317 | continue; 318 | } 319 | switch(i) 320 | { 321 | // Process Telnet IAC Protocol 322 | case 251: 323 | iac = 253; 324 | break; 325 | case 252: 326 | iac = 254; 327 | break; 328 | case 253: 329 | case 254: 330 | iac = 252; 331 | break; 332 | case 255: 333 | // Console has auto newline so dump it 334 | // case '\n': 335 | break; 336 | // Write any other chars to the console 337 | default: 338 | if (vt100En) 339 | vt100(i); 340 | else if (i != '\n') 341 | putchar(i); 342 | } 343 | // if (i != '\n') 344 | // putchar(i); 345 | } 346 | // Re-open the DW channel if it was closed. DriveWire4 will 347 | // close the channel after interactive DW commands 348 | if (!channel_open) 349 | { 350 | close_channel(1); 351 | get_status(1, buffer); 352 | open_channel(1); 353 | //asm("emubrk"); 354 | get_status(1, buffer); 355 | if (vt100En) 356 | sgrClear(); 357 | dwtrm_puts("\r\n"); 358 | dwtrm_puts(prompt); 359 | brk = 0; 360 | } 361 | }; 362 | 363 | return 0; 364 | } 365 | 366 | -------------------------------------------------------------------------------- /font4x8.c: -------------------------------------------------------------------------------- 1 | /* font4x8.c - ISO-8859-1 font for a 51x24 software text screen. 2 | 3 | By Pierre Sarrazin 4 | This file is in the public domain. 5 | */ 6 | 7 | #include "font4x8.h" 8 | 9 | 10 | const unsigned char font4x8[1536] = 11 | { 12 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 32 (0x20) 13 | 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xf8, 0xd8, 0xf8, // 33 (0x21) 14 | 0xa8, 0xa8, 0xa8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 34 (0x22) 15 | 0x98, 0x98, 0x08, 0x98, 0x08, 0x98, 0x98, 0xf8, // 35 (0x23) 16 | 0xd8, 0x88, 0x78, 0x98, 0xe8, 0x18, 0xd8, 0xf8, // 36 (0x24) 17 | 0xf8, 0x28, 0x28, 0xd8, 0xb8, 0x48, 0x48, 0xf8, // 37 (0x25) 18 | 0xb8, 0x58, 0xb8, 0x78, 0x28, 0x58, 0xa8, 0xf8, // 38 (0x26) 19 | 0xd8, 0xd8, 0xb8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 39 (0x27) 20 | 0xe8, 0xd8, 0xb8, 0xb8, 0xb8, 0xd8, 0xe8, 0xf8, // 40 (0x28) 21 | 0x78, 0xb8, 0xd8, 0xd8, 0xd8, 0xb8, 0x78, 0xf8, // 41 (0x29) 22 | 0xf8, 0x68, 0x98, 0x98, 0x98, 0x68, 0xf8, 0xf8, // 42 (0x2a) 23 | 0xf8, 0xd8, 0xd8, 0x88, 0xd8, 0xd8, 0xf8, 0xf8, // 43 (0x2b) 24 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xd8, 0xd8, 0xb8, // 44 (0x2c) 25 | 0xf8, 0xf8, 0xf8, 0x08, 0xf8, 0xf8, 0xf8, 0xf8, // 45 (0x2d) 26 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x98, 0x98, 0xf8, // 46 (0x2e) 27 | 0xf8, 0xe8, 0xe8, 0xd8, 0xb8, 0x78, 0x78, 0xf8, // 47 (0x2f) 28 | 0x98, 0x68, 0x48, 0x28, 0x68, 0x68, 0x98, 0xf8, // 48 (0x30) 29 | 0xd8, 0x98, 0xd8, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 49 (0x31) 30 | 0x98, 0x68, 0xe8, 0x98, 0x78, 0x78, 0x08, 0xf8, // 50 (0x32) 31 | 0x98, 0x68, 0xe8, 0x98, 0xe8, 0x68, 0x98, 0xf8, // 51 (0x33) 32 | 0xe8, 0xc8, 0xa8, 0x68, 0x08, 0xe8, 0xe8, 0xf8, // 52 (0x34) 33 | 0x08, 0x78, 0x78, 0x18, 0xe8, 0xe8, 0x18, 0xf8, // 53 (0x35) 34 | 0x98, 0x68, 0x78, 0x18, 0x68, 0x68, 0x98, 0xf8, // 54 (0x36) 35 | 0x08, 0xe8, 0xe8, 0xd8, 0xb8, 0xb8, 0xb8, 0xf8, // 55 (0x37) 36 | 0x98, 0x68, 0x68, 0x98, 0x68, 0x68, 0x98, 0xf8, // 56 (0x38) 37 | 0x98, 0x68, 0x68, 0x88, 0xe8, 0x68, 0x98, 0xf8, // 57 (0x39) 38 | 0xf8, 0xf8, 0x98, 0x98, 0xf8, 0x98, 0x98, 0xf8, // 58 (0x3a) 39 | 0xf8, 0xf8, 0x98, 0x98, 0xf8, 0xd8, 0xd8, 0xb8, // 59 (0x3b) 40 | 0xe8, 0xd8, 0xb8, 0x78, 0xb8, 0xd8, 0xe8, 0xf8, // 60 (0x3c) 41 | 0xf8, 0xf8, 0xf8, 0x08, 0xf8, 0x08, 0xf8, 0xf8, // 61 (0x3d) 42 | 0x78, 0xb8, 0xd8, 0xe8, 0xd8, 0xb8, 0x78, 0xf8, // 62 (0x3e) 43 | 0x98, 0x68, 0xe8, 0xd8, 0xd8, 0xf8, 0xd8, 0xf8, // 63 (0x3f) 44 | 0x98, 0x68, 0xe8, 0x88, 0x68, 0x68, 0x98, 0xf8, // 64 (0x40) 45 | 0x98, 0x68, 0x68, 0x08, 0x68, 0x68, 0x68, 0xf8, // 65 (0x41) 46 | 0x18, 0x68, 0x68, 0x18, 0x68, 0x68, 0x18, 0xf8, // 66 (0x42) 47 | 0x98, 0x68, 0x78, 0x78, 0x78, 0x68, 0x98, 0xf8, // 67 (0x43) 48 | 0x18, 0x68, 0x68, 0x68, 0x68, 0x68, 0x18, 0xf8, // 68 (0x44) 49 | 0x08, 0x78, 0x78, 0x18, 0x78, 0x78, 0x08, 0xf8, // 69 (0x45) 50 | 0x08, 0x78, 0x78, 0x18, 0x78, 0x78, 0x78, 0xf8, // 70 (0x46) 51 | 0x98, 0x68, 0x78, 0x78, 0x48, 0x68, 0x98, 0xf8, // 71 (0x47) 52 | 0x68, 0x68, 0x68, 0x08, 0x68, 0x68, 0x68, 0xf8, // 72 (0x48) 53 | 0x88, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 73 (0x49) 54 | 0xc8, 0xe8, 0xe8, 0xe8, 0xe8, 0x68, 0x98, 0xf8, // 74 (0x4a) 55 | 0x68, 0x58, 0x38, 0x78, 0x38, 0x58, 0x68, 0xf8, // 75 (0x4b) 56 | 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x08, 0xf8, // 76 (0x4c) 57 | 0x68, 0x08, 0x08, 0x68, 0x68, 0x68, 0x68, 0xf8, // 77 (0x4d) 58 | 0x68, 0x28, 0x48, 0x68, 0x68, 0x68, 0x68, 0xf8, // 78 (0x4e) 59 | 0x98, 0x68, 0x68, 0x68, 0x68, 0x68, 0x98, 0xf8, // 79 (0x4f) 60 | 0x18, 0x68, 0x68, 0x18, 0x78, 0x78, 0x78, 0xf8, // 80 (0x50) 61 | 0x98, 0x68, 0x68, 0x68, 0x68, 0x58, 0xa8, 0xf8, // 81 (0x51) 62 | 0x18, 0x68, 0x68, 0x18, 0x68, 0x68, 0x68, 0xf8, // 82 (0x52) 63 | 0x98, 0x68, 0x78, 0x98, 0xe8, 0x68, 0x98, 0xf8, // 83 (0x53) 64 | 0x88, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xf8, // 84 (0x54) 65 | 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x98, 0xf8, // 85 (0x55) 66 | 0x68, 0x68, 0x68, 0x68, 0x58, 0x58, 0xb8, 0xf8, // 86 (0x56) 67 | 0x68, 0x68, 0x68, 0x68, 0x08, 0x08, 0x68, 0xf8, // 87 (0x57) 68 | 0x68, 0x68, 0x68, 0x98, 0x68, 0x68, 0x68, 0xf8, // 88 (0x58) 69 | 0xa8, 0xa8, 0xa8, 0xd8, 0xd8, 0xd8, 0xd8, 0xf8, // 89 (0x59) 70 | 0x08, 0xe8, 0xd8, 0xb8, 0x78, 0x78, 0x08, 0xf8, // 90 (0x5a) 71 | 0x88, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0x88, 0xf8, // 91 (0x5b) 72 | 0xf8, 0x78, 0x78, 0xb8, 0xd8, 0xe8, 0xe8, 0xf8, // 92 (0x5c) 73 | 0x18, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0x18, 0xf8, // 93 (0x5d) 74 | 0xd8, 0xa8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 94 (0x5e) 75 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x08, 0xf8, // 95 (0x5f) 76 | 0xb8, 0xb8, 0xd8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 96 (0x60) 77 | 0xf8, 0xf8, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 97 (0x61) 78 | 0x78, 0x78, 0x18, 0x68, 0x68, 0x68, 0x18, 0xf8, // 98 (0x62) 79 | 0xf8, 0xf8, 0x88, 0x78, 0x78, 0x78, 0x88, 0xf8, // 99 (0x63) 80 | 0xe8, 0xe8, 0x88, 0x68, 0x68, 0x68, 0x88, 0xf8, // 100 (0x64) 81 | 0xf8, 0xf8, 0x98, 0x68, 0x08, 0x78, 0x98, 0xf8, // 101 (0x65) 82 | 0xc8, 0xb8, 0xb8, 0x08, 0xb8, 0xb8, 0xb8, 0xf8, // 102 (0x66) 83 | 0xf8, 0xf8, 0x88, 0x68, 0x68, 0x08, 0xe8, 0x18, // 103 (0x67) 84 | 0x78, 0x78, 0x18, 0x68, 0x68, 0x68, 0x68, 0xf8, // 104 (0x68) 85 | 0xd8, 0xf8, 0x98, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 105 (0x69) 86 | 0xd8, 0xf8, 0x98, 0xd8, 0xd8, 0xd8, 0xd8, 0x38, // 106 (0x6a) 87 | 0x78, 0x78, 0x68, 0x58, 0x38, 0x58, 0x68, 0xf8, // 107 (0x6b) 88 | 0x98, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 108 (0x6c) 89 | 0xf8, 0xf8, 0x18, 0x08, 0x08, 0x68, 0x68, 0xf8, // 109 (0x6d) 90 | 0xf8, 0xf8, 0x18, 0x68, 0x68, 0x68, 0x68, 0xf8, // 110 (0x6e) 91 | 0xf8, 0xf8, 0x98, 0x68, 0x68, 0x68, 0x98, 0xf8, // 111 (0x6f) 92 | 0xf8, 0xf8, 0x18, 0x68, 0x68, 0x18, 0x78, 0x78, // 112 (0x70) 93 | 0xf8, 0xf8, 0x88, 0x68, 0x68, 0x88, 0xe8, 0xe8, // 113 (0x71) 94 | 0xf8, 0xf8, 0x18, 0x68, 0x78, 0x78, 0x78, 0xf8, // 114 (0x72) 95 | 0xf8, 0xf8, 0x88, 0x78, 0x98, 0xe8, 0x18, 0xf8, // 115 (0x73) 96 | 0xb8, 0xb8, 0x08, 0xb8, 0xb8, 0xb8, 0xc8, 0xf8, // 116 (0x74) 97 | 0xf8, 0xf8, 0x68, 0x68, 0x68, 0x68, 0x88, 0xf8, // 117 (0x75) 98 | 0xf8, 0xf8, 0x68, 0x68, 0x68, 0x58, 0xb8, 0xf8, // 118 (0x76) 99 | 0xf8, 0xf8, 0x68, 0x68, 0x08, 0x08, 0x18, 0xf8, // 119 (0x77) 100 | 0xf8, 0xf8, 0x68, 0x68, 0x98, 0x68, 0x68, 0xf8, // 120 (0x78) 101 | 0xf8, 0xf8, 0x68, 0x68, 0x68, 0x88, 0xe8, 0x18, // 121 (0x79) 102 | 0xf8, 0xf8, 0x08, 0xd8, 0xb8, 0x78, 0x08, 0xf8, // 122 (0x7a) 103 | 0xe8, 0xd8, 0xd8, 0xb8, 0xd8, 0xd8, 0xe8, 0xf8, // 123 (0x7b) 104 | 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xf8, // 124 (0x7c) 105 | 0x78, 0xb8, 0xb8, 0xd8, 0xb8, 0xb8, 0x78, 0xf8, // 125 (0x7d) 106 | 0xa8, 0x58, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 126 (0x7e) 107 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 127 (0x7f) 108 | 109 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 160 (0xa0) 110 | 0xd8, 0xf8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xf8, // 161 (0xa1) 111 | 0xf8, 0xd8, 0x88, 0x78, 0x78, 0x88, 0xd8, 0xf8, // 162 (0xa2) 112 | 0x98, 0x68, 0x78, 0x38, 0x78, 0x78, 0x08, 0xf8, // 163 (0xa3) 113 | 0x68, 0x08, 0x68, 0x68, 0x08, 0x68, 0xf8, 0xf8, // 164 (0xa4) 114 | 0xa8, 0xa8, 0xa8, 0xd8, 0x88, 0xd8, 0xd8, 0xf8, // 165 (0xa5) 115 | 0xd8, 0xd8, 0xd8, 0xf8, 0xd8, 0xd8, 0xd8, 0xf8, // 166 (0xa6) 116 | 0x88, 0x78, 0x08, 0x68, 0x08, 0xe8, 0x18, 0xf8, // 167 (0xa7) 117 | 0xa8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 168 (0xa8) 118 | 0x88, 0x78, 0x78, 0x88, 0xf8, 0xf8, 0xf8, 0xf8, // 169 (0xa9) 119 | 0x88, 0x68, 0x68, 0x88, 0xf8, 0xf8, 0xf8, 0xf8, // 170 (0xaa) 120 | 0xf8, 0xf8, 0xa8, 0x58, 0xa8, 0xf8, 0xf8, 0xf8, // 171 (0xab) 121 | 0xf8, 0xf8, 0xf8, 0x08, 0xe8, 0xe8, 0xf8, 0xf8, // 172 (0xac) 122 | 0xf8, 0xf8, 0xf8, 0x98, 0xf8, 0xf8, 0xf8, 0xf8, // 173 (0xad) 123 | 0x88, 0x78, 0x78, 0x78, 0xf8, 0xf8, 0xf8, 0xf8, // 174 (0xae) 124 | 0x98, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 175 (0xaf) 125 | 0x18, 0x58, 0x18, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 176 (0xb0) 126 | 0xb8, 0xb8, 0x18, 0xb8, 0xb8, 0xf8, 0x18, 0xf8, // 177 (0xb1) 127 | 0x18, 0xd8, 0x18, 0x78, 0x18, 0xf8, 0xf8, 0xf8, // 178 (0xb2) 128 | 0x18, 0xd8, 0x18, 0xd8, 0x18, 0xf8, 0xf8, 0xf8, // 179 (0xb3) 129 | 0xd8, 0xb8, 0x78, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // 180 (0xb4) 130 | 0xf8, 0xf8, 0x68, 0x68, 0x68, 0x48, 0x28, 0x78, // 181 (0xb5) 131 | 0x88, 0x28, 0x28, 0x28, 0xa8, 0xa8, 0xa8, 0xf8, // 182 (0xb6) 132 | 0xf8, 0xf8, 0xf8, 0x98, 0x98, 0xf8, 0xf8, 0xf8, // 183 (0xb7) 133 | 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xb8, 0xd8, 0x38, // 184 (0xb8) 134 | 0xb8, 0x38, 0xb8, 0xb8, 0x18, 0xf8, 0xf8, 0xf8, // 185 (0xb9) 135 | 0x98, 0x68, 0x68, 0x98, 0xf8, 0xf8, 0xf8, 0xf8, // 186 (0xba) 136 | 0xf8, 0xf8, 0x58, 0xa8, 0x58, 0xf8, 0xf8, 0xf8, // 187 (0xbb) 137 | 0x78, 0x68, 0x58, 0xb8, 0x48, 0xc8, 0xe8, 0xf8, // 188 (0xbc) 138 | 0x78, 0x68, 0x58, 0xb8, 0x48, 0xc8, 0xc8, 0xf8, // 189 (0xbd) 139 | 0x38, 0x28, 0x18, 0xb8, 0x48, 0xc8, 0xe8, 0xf8, // 190 (0xbe) 140 | 0xb8, 0xf8, 0xb8, 0xb8, 0x78, 0x68, 0x98, 0xf8, // 191 (0xbf) 141 | 0xb8, 0xd8, 0x98, 0x68, 0x08, 0x68, 0x68, 0xf8, // 192 (0xc0) 142 | 0xd8, 0xb8, 0x98, 0x68, 0x08, 0x68, 0x68, 0xf8, // 193 (0xc2) 143 | 0xd8, 0xa8, 0x98, 0x68, 0x08, 0x68, 0x68, 0xf8, // 194 (0xc3) 144 | 0xa8, 0x58, 0x98, 0x68, 0x08, 0x68, 0x68, 0xf8, // 195 (0xc4) 145 | 0x68, 0xf8, 0x98, 0x68, 0x08, 0x68, 0x68, 0xf8, // 196 (0xc4) 146 | 0x98, 0x98, 0x98, 0x68, 0x08, 0x68, 0x68, 0xf8, // 197 (0xc5) 147 | 0x80, 0x58, 0x58, 0x08, 0x58, 0x58, 0x40, 0xf8, // 198 (0xc6) 148 | 0x98, 0x68, 0x78, 0x78, 0x68, 0x98, 0xd8, 0xf8, // 199 (0xc7) 149 | 0xb8, 0xd8, 0x08, 0x78, 0x18, 0x78, 0x08, 0xf8, // 200 (0xc8) 150 | 0xd8, 0xb8, 0x08, 0x78, 0x18, 0x78, 0x08, 0xf8, // 201 (0xc9) 151 | 0xd8, 0xa8, 0x08, 0x78, 0x18, 0x78, 0x08, 0xf8, // 202 (0xca) 152 | 0x68, 0xf8, 0x08, 0x78, 0x18, 0x78, 0x08, 0xf8, // 203 (0xcb) 153 | 0xb8, 0xd8, 0x88, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 204 (0xcc) 154 | 0xd8, 0xb8, 0x88, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 205 (0xcd) 155 | 0xd8, 0xb8, 0x88, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 206 (0xce) 156 | 0xa8, 0xf8, 0x88, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 207 (0xcf) 157 | 0x18, 0x68, 0x68, 0x28, 0x68, 0x68, 0x18, 0xf8, // 208 (0xd0) 158 | 0xa8, 0x58, 0x68, 0x28, 0x48, 0x68, 0x68, 0xf8, // 209 (0xd1) 159 | 0xb8, 0xd8, 0x08, 0x68, 0x68, 0x68, 0x08, 0xf8, // 210 (0xd2) 160 | 0xd8, 0xb8, 0x08, 0x68, 0x68, 0x68, 0x08, 0xf8, // 211 (0xd3) 161 | 0xd8, 0xa8, 0x08, 0x68, 0x68, 0x68, 0x08, 0xf8, // 212 (0xd4) 162 | 0xa8, 0x58, 0x08, 0x68, 0x68, 0x68, 0x08, 0xf8, // 213 (0xd5) 163 | 0x68, 0xf8, 0x08, 0x68, 0x68, 0x68, 0x08, 0xf8, // 214 (0xd6) 164 | 0xf8, 0xf8, 0x58, 0xb8, 0x58, 0xf8, 0xf8, 0xf8, // 215 (0xd7) 165 | 0x88, 0x68, 0x48, 0x28, 0x68, 0x68, 0x18, 0xf8, // 216 (0xd8) 166 | 0xb8, 0xd8, 0x68, 0x68, 0x68, 0x68, 0x98, 0xf8, // 217 (0xd9) 167 | 0xd8, 0xb8, 0x68, 0x68, 0x68, 0x68, 0x98, 0xf8, // 218 (0xda) 168 | 0xd8, 0xa8, 0x68, 0x68, 0x68, 0x68, 0x98, 0xf8, // 219 (0xdb) 169 | 0x68, 0xf8, 0x68, 0x68, 0x68, 0x68, 0x98, 0xf8, // 220 (0xdc) 170 | 0xd8, 0xb8, 0xa8, 0xa8, 0xd8, 0xd8, 0xd8, 0xf8, // 221 (0xdd) 171 | 0x78, 0x18, 0x68, 0x68, 0x68, 0x18, 0x78, 0xf8, // 222 (0xde) 172 | 0xb8, 0x58, 0x58, 0x38, 0x58, 0x68, 0x18, 0xf8, // 223 (0xdf) 173 | 0xb8, 0xd8, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 224 (0xe0) 174 | 0xd8, 0xb8, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 225 (0xe1) 175 | 0xd8, 0xa8, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 226 (0xe2) 176 | 0xa8, 0x58, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 227 (0xe3) 177 | 0x68, 0xf8, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 228 (0xe4) 178 | 0x98, 0x98, 0x98, 0xe8, 0x88, 0x68, 0x08, 0xf8, // 229 (0xe5) 179 | 0xf8, 0xf8, 0x58, 0xa8, 0x08, 0x38, 0x88, 0xf8, // 230 (0xe6) 180 | 0xf8, 0xf8, 0x88, 0x78, 0x78, 0x78, 0x88, 0xd8, // 231 (0xe7) 181 | 0xb8, 0xd8, 0x98, 0x68, 0x08, 0x78, 0x98, 0xf8, // 232 (0xe8) 182 | 0xd8, 0xb8, 0x98, 0x68, 0x08, 0x78, 0x98, 0xf8, // 233 (0xe9) 183 | 0xd8, 0xa8, 0x98, 0x68, 0x08, 0x78, 0x98, 0xf8, // 234 (0xea) 184 | 0x68, 0xf8, 0x98, 0x68, 0x08, 0x78, 0x98, 0xf8, // 235 (0xeb) 185 | 0xb8, 0xd8, 0x98, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 236 (0xec) 186 | 0xd8, 0xb8, 0x98, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 237 (0xed) 187 | 0xd8, 0xa8, 0x98, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 238 (0xee) 188 | 0xa8, 0xf8, 0x98, 0xd8, 0xd8, 0xd8, 0x88, 0xf8, // 239 (0xef) 189 | 0x58, 0xb8, 0x58, 0xe8, 0x08, 0x68, 0x98, 0xf8, // 240 (0xf0) 190 | 0xa8, 0x58, 0x18, 0x68, 0x68, 0x68, 0x68, 0xf8, // 241 (0xf1) 191 | 0xb8, 0xd8, 0x98, 0x68, 0x68, 0x68, 0x98, 0xf8, // 242 (0xf2) 192 | 0xd8, 0xb8, 0x98, 0x68, 0x68, 0x68, 0x98, 0xf8, // 243 (0xf3) 193 | 0xd8, 0xa8, 0x98, 0x68, 0x68, 0x68, 0x98, 0xf8, // 244 (0xf4) 194 | 0xa8, 0x58, 0x98, 0x68, 0x68, 0x68, 0x98, 0xf8, // 245 (0xf5) 195 | 0x68, 0xf8, 0x98, 0x68, 0x68, 0x68, 0x98, 0xf8, // 246 (0xf6) 196 | 0xf8, 0xb8, 0xf8, 0x18, 0xf8, 0xb8, 0xf8, 0xf8, // 247 (0xf7) 197 | 0xf8, 0xf8, 0x98, 0x68, 0x48, 0x28, 0x98, 0xf8, // 248 (0xf8) 198 | 0xb8, 0xd8, 0x68, 0x68, 0x68, 0x68, 0x88, 0xf8, // 249 (0xf9) 199 | 0xd8, 0xb8, 0x68, 0x68, 0x68, 0x68, 0x88, 0xf8, // 250 (0xfa) 200 | 0xd8, 0xa8, 0x68, 0x68, 0x68, 0x68, 0x88, 0xf8, // 251 (0xfb) 201 | 0x68, 0xf8, 0x68, 0x68, 0x68, 0x68, 0x88, 0xf8, // 252 (0xfc) 202 | 0xd8, 0xb8, 0x68, 0x68, 0x68, 0x88, 0xe8, 0x18, // 253 (0xfd) 203 | 0x78, 0x78, 0x78, 0x18, 0x68, 0x68, 0x18, 0x78, // 254 (0xfe) 204 | 0x68, 0xf8, 0x68, 0x68, 0x68, 0x88, 0xe8, 0x18, // 255 (0xff) 205 | }; 206 | -------------------------------------------------------------------------------- /dwwrite.asm: -------------------------------------------------------------------------------- 1 | ******************************************************* 2 | * 3 | * DWWrite 4 | * Send a packet to the DriveWire server. 5 | * Serial data format: 1-8-N-1 6 | * 4/12/2009 by Darren Atkinson 7 | * 8 | * Entry: 9 | * X = starting address of data to send 10 | * Y = number of bytes to send 11 | * 12 | * Exit: 13 | * X = address of last byte sent + 1 14 | * Y = 0 15 | * All others preserved 16 | * 17 | 18 | 19 | IFNE ARDUINO 20 | DWWrite pshs a ; preserve registers 21 | txByte 22 | lda ,x+ ; get byte from buffer 23 | sta $FF52 ; put it to PIA 24 | loop@ tst $FF53 ; check status register 25 | bpl loop@ ; until CB1 is set by Arduino, continue looping 26 | tst $FF52 ; clear CB1 in status register 27 | leay -1,y ; decrement byte counter 28 | bne txByte ; loop if more to send 29 | 30 | puls a,pc ; restore registers and return 31 | 32 | ELSE 33 | 34 | IFNE MEGAMINIMPI 35 | IFNE 0 36 | ************************************************************************ 37 | * Original Unoptimized Version 38 | DWWrite pshs d,cc ; preserve registers 39 | IFEQ NOINTMASK 40 | orcc #IntMasks ; mask interrupts 41 | ENDC 42 | lda MPIREG ; Get Current MPI Status 43 | pshs a ; Save it 44 | anda #CTSMASK ; Mask out SCS, save CTS 45 | ora #MMMSLT ; SCS Slot Selection 46 | sta MPIREG ; write the info to MPI register 47 | txByte 48 | lda MMMUARTB+LSR ; read status register to check 49 | anda #LSRTHRE ; if transmit fifo has room 50 | beq txByte ; if not loop back and check again 51 | lda ,x+ ; load byte from buffer 52 | sta MMMUARTB ; and write it to data register 53 | leay -1,y ; decrement byte counter 54 | bne txByte ; loop if more to send 55 | puls a ; Get original MPI Register back 56 | sta MPIREG ; Restore it 57 | puls cc,d,pc ; restore registers and return 58 | ************************************************************************ 59 | ENDC 60 | IFNE 1 61 | ************************************************************************ 62 | * DP Optmizied - v2 63 | DWWrite pshs d,dp,cc ; preserve registers 64 | 65 | IFEQ NOINTMASK 66 | orcc #IntMasks ; mask interrupts 67 | ENDC 68 | 69 | lda #$ff 70 | tfr a,dp 71 | 72 | lda 4 | This file is in the public domain. 5 | 6 | Quick Guide: 7 | - Call initHiResTextScreen() first. 8 | - End with a call to closeHiResTextScreen(). 9 | - #define HIRESTEXT_NO_VT52 to avoid compiling the VT-52 code. 10 | */ 11 | 12 | #include "hirestxt.h" 13 | 14 | 15 | byte hiResWidth; 16 | byte textPosX; 17 | byte textPosY; 18 | byte *textScreenBuffer; 19 | byte hiResTextCursorPresent; 20 | ConsoleOutHook oldCHROOT; 21 | static void (*pfWriteCharAt)(byte x, byte y, byte asciiCode); 22 | 23 | 24 | void initHiResTextScreen(struct HiResTextScreenInit *init) 25 | { 26 | initCoCoSupport(); 27 | 28 | hiResWidth = (init->numColumns == 42 ? 42 : 51); // required by moveCursor() 29 | pfWriteCharAt = init->writeCharAtFuncPtr; 30 | 31 | width(32); // PMODE graphics will only appear from 32x16 (does nothing on CoCo 1&2) 32 | moveCursor(0, 0); 33 | setTextScreenAddress(init->textScreenPageNum); 34 | pmode(4, textScreenBuffer); 35 | pcls(255); 36 | screen(1, 0); // green/black 37 | #ifndef HIRESTEXT_NO_VT52 38 | initVT52(); 39 | #endif 40 | hiResTextCursorPresent = FALSE; 41 | if (init->redirectPrintf) 42 | oldCHROOT = setConsoleOutHook(hiResTextConsoleOutHook); 43 | else 44 | oldCHROOT = 0; 45 | } 46 | 47 | 48 | void closeHiResTextScreen() 49 | { 50 | if (oldCHROOT) 51 | setConsoleOutHook(oldCHROOT); 52 | } 53 | 54 | 55 | asm void clearRowsToEOS(byte byteToClearWith, byte textRow) 56 | { 57 | // Start of cleared buffer: textScreenBuffer + ((word) textRow * 32 * 8); 58 | 59 | // Performance notes: 60 | // std ,x++: 8 cycles. 61 | // pshu b,a: 7 cycles. 62 | // pshu y,b,a: 9 cycles to write 4 bytes, i.e., 4.5 cycles per 2 bytes. 63 | // To push 32 bytes: 72 cycles. 64 | 65 | asm 66 | { 67 | // Do nothing if textRow invalid. 68 | lda 5,s // textRow 69 | cmpa #HIRESHEIGHT 70 | bhs clearRowsToEOS_end 71 | 72 | // Iteration will go from end of screen buffer to start of target row. 73 | // Compute address of start of target row. 74 | clrb // D = textRow * 32 bytes per pixel row 75 | // * 8 pixel rows per text row 76 | addd textScreenBuffer 77 | 78 | pshs u,b,a // put address in stack for loop, preserve U 79 | 80 | // Compute address of end of screen buffer. Put it in U, to use PSHU. 81 | ldu textScreenBuffer 82 | leau PIXEL_COLS_PER_SCREEN*PIXEL_ROWS_PER_SCREEN/8,u 83 | 84 | lda 7,s // byteToClearWith (mind the PSHS) 85 | tfr a,b 86 | tfr d,x // D & X contain 4 copies of byte to clear with 87 | clearRowsToEOS_loop: 88 | pshu x,b,a // decrement U by 4, write 4 bytes at U 89 | pshu x,b,a // repeat enough to write 64 bytes per iteration 90 | pshu x,b,a 91 | pshu x,b,a 92 | pshu x,b,a 93 | pshu x,b,a 94 | pshu x,b,a 95 | pshu x,b,a 96 | pshu x,b,a 97 | pshu x,b,a 98 | pshu x,b,a 99 | pshu x,b,a 100 | pshu x,b,a 101 | pshu x,b,a 102 | pshu x,b,a 103 | pshu x,b,a 104 | cmpu ,s // has U reached start of target row? 105 | bhi clearRowsToEOS_loop // loop if not 106 | 107 | puls a,b,u,pc // restore CMOC's stack frame pointer in U 108 | clearRowsToEOS_end: 109 | } 110 | } 111 | 112 | 113 | void setTextScreenAddress(byte pageNum) 114 | { 115 | textScreenBuffer = (byte *) ((word) pageNum << 9); 116 | } 117 | 118 | 119 | 120 | 121 | // Called by PUTCHR, which is called by printf() et al. 122 | // The character to print is in A. 123 | // It is sent to writeChar() (the 51x24 screen) instead of 124 | // the regular CoCo screen. 125 | // 126 | // MUST preserve B, X, U and Y. 127 | // 128 | void asm hiResTextConsoleOutHook() 129 | { 130 | asm 131 | { 132 | pshs x,b 133 | 134 | // CMOC's printf() converts \n to \r for Color Basic's PUTCHR, 135 | // but processConsoleOutChar() expects \n to mean newline. 136 | // 137 | cmpa #13 138 | bne @notCR 139 | lda #10 140 | @notCR: 141 | pshs a 142 | clr ,-s // push argument as a word 143 | lbsr processConsoleOutChar 144 | leas 2,s 145 | 146 | puls b,x 147 | } 148 | } 149 | 150 | 151 | void writeCharAt(byte x, byte y, byte asciiCode) 152 | { 153 | (*pfWriteCharAt)(x, y, asciiCode); 154 | } 155 | 156 | 157 | void invertPixelsAtCursor() 158 | { 159 | byte x = textPosX; 160 | byte y = textPosY; 161 | 162 | if (x >= hiResWidth) // logical position that gets mapped to start of next line 163 | { 164 | x = 0; 165 | ++y; 166 | if (y >= HIRESHEIGHT) 167 | y = HIRESHEIGHT - 1; 168 | } 169 | 170 | (*pfWriteCharAt)(x, y, 0); 171 | } 172 | 173 | 174 | void scrollTextScreenUp() 175 | { 176 | word *end = (word *) (textScreenBuffer 177 | + (word) BYTES_PER_PIXEL_ROW 178 | * (PIXEL_ROWS_PER_SCREEN 179 | - PIXEL_ROWS_PER_TEXT_ROW)); 180 | // start of last row: 32 bytes * (192 rows - 8) 181 | asm 182 | { 183 | ldx textScreenBuffer 184 | @scrollTextScreen_loop1: 185 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 186 | std ,x++ 187 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 188 | std ,x++ 189 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 190 | std ,x++ 191 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 192 | std ,x++ 193 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 194 | std ,x++ 195 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 196 | std ,x++ 197 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 198 | std ,x++ 199 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 200 | std ,x++ 201 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 202 | std ,x++ 203 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 204 | std ,x++ 205 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 206 | std ,x++ 207 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 208 | std ,x++ 209 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 210 | std ,x++ 211 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 212 | std ,x++ 213 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 214 | std ,x++ 215 | ldd BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW,x 216 | std ,x++ 217 | ; 218 | cmpx end 219 | blo @scrollTextScreen_loop1 220 | 221 | tfr x,d 222 | addd #BYTES_PER_PIXEL_ROW*PIXEL_ROWS_PER_TEXT_ROW 223 | std end 224 | ldd #$FFFF 225 | @scrollTextScreen_loop2: 226 | std ,x++ 227 | std ,x++ 228 | std ,x++ 229 | std ,x++ 230 | std ,x++ 231 | std ,x++ 232 | std ,x++ 233 | std ,x++ 234 | std ,x++ 235 | std ,x++ 236 | std ,x++ 237 | std ,x++ 238 | std ,x++ 239 | std ,x++ 240 | std ,x++ 241 | std ,x++ 242 | cmpx end 243 | blo @scrollTextScreen_loop2 244 | } 245 | } 246 | 247 | 248 | void moveCursor(byte x, byte y) 249 | { 250 | if (x >= hiResWidth) 251 | return; 252 | if (y >= HIRESHEIGHT) 253 | return; 254 | 255 | textPosX = x; 256 | textPosY = y; 257 | } 258 | 259 | 260 | void goToNextRowIfPastEndOfRow() 261 | { 262 | if (textPosX < hiResWidth) // if not past end of current row 263 | return; 264 | 265 | textPosX = 0; 266 | ++textPosY; 267 | if (textPosY < HIRESHEIGHT) // if not past bottom of screen 268 | return; 269 | 270 | scrollTextScreenUp(); 271 | textPosY = HIRESHEIGHT - 1; 272 | } 273 | 274 | 275 | void writeChar(byte ch) 276 | { 277 | if (ch == '\a') 278 | sound(1, 1); 279 | else if (ch == '\b') 280 | { 281 | // Non-destructive backspace. At (0, 0), does not scroll screen up. 282 | // 283 | if (textPosX > 0) 284 | --textPosX; 285 | else if (textPosY > 0) 286 | { 287 | textPosX = hiResWidth - 1; 288 | --textPosY; 289 | } 290 | } 291 | else if (ch == '\t') 292 | { 293 | // If past end of current row, start by putting cursor at start of next row. 294 | // Scroll screen up one row if needed. 295 | // Then tab processing can start at column 0. 296 | // 297 | goToNextRowIfPastEndOfRow(); 298 | 299 | byte newX = (textPosX | 7) + 1; 300 | if (newX > hiResWidth) // tab at 48..50 leads to 56: clamp; similarly in 42x24 mode 301 | newX = hiResWidth; 302 | for (byte numSpaces = newX - textPosX; numSpaces; --numSpaces) 303 | writeString(" "); 304 | } 305 | else if (ch == '\n') 306 | { 307 | // Note that textPosX may be hiResWidth at this point. 308 | // 309 | textPosX = 0; 310 | ++textPosY; 311 | if (textPosY >= HIRESHEIGHT) 312 | { 313 | scrollTextScreenUp(); 314 | textPosY = HIRESHEIGHT - 1; 315 | } 316 | } 317 | else if (ch == '\f') 318 | { 319 | clrscr(); 320 | } 321 | else if (ch == '\r') 322 | { 323 | textPosX = 0; 324 | } 325 | else if (ch >= ' ' && ch != 127) // printable character: 326 | { 327 | // If past end of current row, put cursor at start of next row. 328 | // Scroll screen up one row if needed. 329 | // 330 | goToNextRowIfPastEndOfRow(); 331 | 332 | // Write char at cursor, advance cursor, go to next line if needed. 333 | // 334 | (*pfWriteCharAt)(textPosX, textPosY, ch); 335 | ++textPosX; 336 | 337 | // textPosX may now be hiResWidth, which is not a writable column, 338 | // but we tolerate it for easier management of a newline that comes 339 | // after writing 51 (or 42) printable chars on a row. 340 | } 341 | } 342 | 343 | 344 | void writeString(const char *str) 345 | { 346 | for (;;) 347 | { 348 | byte ch = *str; 349 | if (!ch) 350 | break; 351 | ++str; 352 | 353 | writeChar(ch); 354 | } 355 | } 356 | 357 | 358 | void writeCenteredLine(byte row, const char *line) 359 | { 360 | moveCursor((byte) ((hiResWidth - strlen(line)) / 2), row); 361 | writeString(line); 362 | } 363 | 364 | 365 | void writeDecWord(word w) 366 | { 367 | char buf[11]; 368 | writeString(dwtoa(buf, 0, w)); 369 | } 370 | 371 | 372 | void clrtoeol() 373 | { 374 | for (byte x = textPosX; x != hiResWidth; ++x) 375 | (*pfWriteCharAt)(x, textPosY, ' '); 376 | } 377 | 378 | 379 | void clrtobot() 380 | { 381 | clrtoeol(); 382 | clearRowsToEOS(0xff, textPosY + 1); 383 | } 384 | 385 | 386 | void removeCursor() 387 | { 388 | if (hiResTextCursorPresent) 389 | { 390 | invertPixelsAtCursor(); 391 | hiResTextCursorPresent = 0; 392 | } 393 | } 394 | 395 | 396 | void animateCursor() 397 | { 398 | // Look at bits 4 and 5 of TIMER. 399 | // Values 0..2 mean displayed cursor, 3 means no cursor. 400 | // Resetting TIMER at 0 will display the cursor. 401 | // 402 | byte currentCursorState = (getTimer() & 0x30) != 0x30; 403 | if (currentCursorState != hiResTextCursorPresent) 404 | { 405 | invertPixelsAtCursor(); 406 | hiResTextCursorPresent ^= 1; 407 | } 408 | } 409 | 410 | 411 | byte waitKeyBlinkingCursor() 412 | { 413 | byte key; 414 | while (!(key = inkey())) 415 | animateCursor(); 416 | removeCursor(); 417 | return key; 418 | } 419 | 420 | 421 | /////////////////////////////////////////////////////////////////////////////// 422 | 423 | 424 | #ifndef HIRESTEXT_NO_VT52 425 | 426 | 427 | // VT52 state machine states. 428 | // 429 | enum 430 | { 431 | VT52_TEXT = 0, // Chars interpreted as text to display, except Escape. 432 | VT52_GOT_ESC = 1, // Escape has just been received. 433 | VT52_WANT_LINE = 2, // Y command waiting for line character. 434 | VT52_WANT_COL = 3, // Y command waiting for column character. 435 | VT52_IGNORE_NEXT = 4, // Ignore next byte(s), depending on 'vt52NumBytesToIgnore'. 436 | }; 437 | 438 | 439 | byte vt52State = VT52_TEXT; 440 | byte vt52Line = 0; // 0..HIRESHEIGHT-1, unlike VT52 spec which starts at 1. 441 | byte vt52NumBytesToIgnore = 0; // Number of coming bytes that will be ignored (see VT52_IGNORE_NEXT). 442 | 443 | 444 | void initVT52() 445 | { 446 | vt52State = VT52_TEXT; 447 | vt52Line = 0; 448 | } 449 | 450 | 451 | #endif /* HIRESTEXT_NO_VT52 */ 452 | 453 | 454 | // Calls writeChar() and moveCursor() to execute the VT52 sequence 455 | // provided by the calls to this function. 456 | // 457 | // Source: http://bitsavers.trailing-edge.com/pdf/dec/terminal/gigi/EK-0GIGI-RC-001_GIGI_Programming_Reference_Card_Sep80.pdf 458 | // 459 | void processConsoleOutChar(byte ch) 460 | { 461 | #ifndef HIRESTEXT_NO_VT52 462 | if (vt52State == VT52_IGNORE_NEXT) 463 | { 464 | --vt52NumBytesToIgnore; 465 | if (!vt52NumBytesToIgnore) 466 | vt52State = VT52_TEXT; 467 | return; 468 | } 469 | #endif 470 | 471 | removeCursor(); 472 | 473 | #ifndef HIRESTEXT_NO_VT52 474 | if (vt52State == VT52_TEXT) 475 | { 476 | if (ch == 27) // if Escape char 477 | vt52State = VT52_GOT_ESC; 478 | else 479 | #endif 480 | writeChar(ch); 481 | return; 482 | #ifndef HIRESTEXT_NO_VT52 483 | } 484 | 485 | if (vt52State == VT52_GOT_ESC) 486 | { 487 | // Most common commands should be tested first. 488 | // 489 | if (ch == 'Y') // direct cursor address (expecting 2 more bytes) 490 | { 491 | vt52State = VT52_WANT_LINE; 492 | return; 493 | } 494 | if (ch == 'K') // erase to end of line 495 | { 496 | clrtoeol(); 497 | vt52State = VT52_TEXT; 498 | return; 499 | } 500 | if (ch == 'D') // cursor left 501 | { 502 | if (textPosX) 503 | --textPosX; 504 | vt52State = VT52_TEXT; // end of sequence 505 | return; 506 | } 507 | if (ch == 'H') // cursor home 508 | { 509 | home(); 510 | vt52State = VT52_TEXT; 511 | return; 512 | } 513 | if (ch == 'J') // erase to end of screen 514 | { 515 | clrtobot(); 516 | vt52State = VT52_TEXT; 517 | return; 518 | } 519 | if (ch == 'A') // cursor up 520 | { 521 | if (textPosY) 522 | --textPosY; 523 | vt52State = VT52_TEXT; // end of sequence 524 | return; 525 | } 526 | if (ch == 'B') // cursor up 527 | { 528 | if (textPosY < HIRESHEIGHT - 1) 529 | ++textPosY; 530 | vt52State = VT52_TEXT; // end of sequence 531 | return; 532 | } 533 | if (ch == 'C') // cursor right 534 | { 535 | if (textPosX < hiResWidth - 1) 536 | ++textPosX; 537 | vt52State = VT52_TEXT; // end of sequence 538 | return; 539 | } 540 | if (ch == 'S') // mysterious sequence: ESC S O (used by vi...) 541 | { 542 | vt52State = VT52_IGNORE_NEXT; 543 | vt52NumBytesToIgnore = 1; 544 | return; 545 | } 546 | if (ch == 'G') // select ASCII char set 547 | { 548 | vt52State = VT52_TEXT; 549 | return; 550 | } 551 | 552 | // Any other sequence is not supported. Return to text mode. 553 | // G (select ASCII char set) is not supported. 554 | // 555 | //writeString("\n\n*** INVALID VT52 COMMAND\n"); 556 | vt52State = VT52_TEXT; 557 | return; 558 | } 559 | 560 | if (vt52State == VT52_WANT_LINE) 561 | { 562 | vt52Line = ch - 32; 563 | vt52State = VT52_WANT_COL; 564 | return; 565 | } 566 | 567 | if (vt52State == VT52_WANT_COL) 568 | moveCursor(ch - 32, vt52Line); 569 | 570 | vt52State = VT52_TEXT; 571 | #endif /* HIRESTEXT_NO_VT52 */ 572 | } 573 | -------------------------------------------------------------------------------- /dwread.asm: -------------------------------------------------------------------------------- 1 | ******************************************************* 2 | * 3 | * DWRead 4 | * Receive a response from the DriveWire server. 5 | * Times out if serial port goes idle for more than 1.4 (0.7) seconds. 6 | * Serial data format: 1-8-N-1 7 | * 4/12/2009 by Darren Atkinson 8 | * 9 | * Entry: 10 | * X = starting address where data is to be stored 11 | * Y = number of bytes expected 12 | * 13 | * Exit: 14 | * CC = carry set on framing error, Z set if all bytes received 15 | * X = starting address of data received 16 | * Y = checksum 17 | * U is preserved. All accumulators are clobbered 18 | * 19 | 20 | IFNE ARDUINO 21 | * Note: this is an optimistic routine. It presumes that the server will always be there, and 22 | * has NO timeout fallback. It is also very short and quick. 23 | DWRead clra ; clear Carry (no framing error) 24 | pshs u,x,cc ; preserve registers 25 | leau ,x 26 | ldx #$0000 27 | loop@ tst $FF51 ; check for CA1 bit (1=Arduino has byte ready) 28 | bpl loop@ ; loop if not set 29 | ldb $FF50 ; clear CA1 bit in status register 30 | stb ,u+ ; save off acquired byte 31 | abx ; update checksum 32 | leay ,-y 33 | bne loop@ 34 | 35 | leay ,x ; return checksum in Y 36 | puls cc,x,u,pc ; restore registers and return 37 | 38 | ELSE 39 | 40 | IFNE MEGAMINIMPI 41 | * NOTE: There is no timeout currently on here... 42 | IFNE 1 43 | ************************************************************************ 44 | * Original Unoptimized Version 45 | DWRead clra ; clear Carry (no framing error) 46 | deca ; clear Z flag, A = timeout msb ($ff) 47 | tfr cc,b 48 | pshs u,x,dp,b,a ; preserve registers, push timeout msb 49 | leau ,x ; buffer pointer 50 | ldx #$0000 51 | IFEQ NOINTMASK 52 | orcc #IntMasks 53 | ENDC 54 | 55 | lda MPIREG ; Get Current MPI Status 56 | pshs a ; Save it 57 | anda #CTSMASK ; Mask out SCS, save CTS 58 | ora #MMMSLT ; SCS Slot Selection 59 | sta MPIREG ; write the info to MPI register 60 | 61 | opt c 62 | opt ct 63 | loop@ ldb MMMUARTB+LSR ; Check status register 64 | andb #LSRDR ; RX Fifo status 65 | beq loop@ ; loop until data 66 | ldb MMMUARTB ; Read data 67 | stb ,u+ ; save it 68 | abx ; update checksum 69 | leay ,-y ; counter = counter - 1 70 | bne loop@ 71 | opt cc 72 | puls a ; Get original MPI Register back 73 | sta MPIREG ; Restore it 74 | 75 | tfr x,y 76 | ldb #0 77 | lda #3 78 | leas 1,s ; remove timeout msb from stack 79 | inca ; A = status to be returned in C and Z 80 | ora ,s ; place status information into the.. 81 | sta ,s ; ..C and Z bits of the preserved CC 82 | leay ,x ; return checksum in Y 83 | puls cc,dp,x,u,pc ; restore registers and return 84 | ************************************************************************ 85 | ENDC 86 | IFNE 0 87 | ************************************************************************ 88 | * Optimized 1 byte - v2 89 | DWRead pshs u,x,dp,cc ; preserve registers 90 | leau ,x ; buffer pointer 91 | ldx #$0000 ; Initialize Checksum 92 | IFEQ NOINTMASK 93 | orcc #IntMasks ; Disable Interrupts 94 | ENDC 95 | 96 | lda #$ff ; Set up Direct Page Register 97 | tfr a,dp 98 | 99 | lda 4 | #include 5 | #include "dwterm.h" 6 | #include "coco3.h" 7 | #include "coco2.h" 8 | #else 9 | #include 10 | #include 11 | #include 12 | #include 13 | #endif 14 | 15 | #define RETURN_OK 0 16 | 17 | #define COLS 80 18 | #define ROWS 24 19 | 20 | #define HRESSCRN 0x2000 21 | #define defScreenAddr 0x8000 22 | #define defScreenEnd (screenAddr - 1 + (160*ROWS)) 23 | #define scrDiff 0x6000 24 | #define H_CRSLOC 0xfe00 25 | #define H_CRSX 0xfe02 26 | #define H_CRSY 0xfe03 27 | #define MAP_IN_SCREEN (*(uint8_t *)0xffa4 = 0x36) 28 | #define MAP_OUT_SCREEN (*(uint8_t *)0xffa4 = 0x3c) 29 | 30 | 31 | 32 | #ifndef NULL 33 | #define NULL 0 34 | #endif 35 | 36 | uint8_t vt100buf[16] = {}; 37 | uint8_t *vt100bufp = vt100buf; 38 | uint8_t vt100bufi = 0; 39 | uint8_t vt100nums[10]; 40 | int8_t vt100numi; 41 | uint8_t *vt100nump = 0; // NULL; 42 | 43 | uint8_t savedCol, savedRow; 44 | uint8_t currCol, currRow; 45 | uint8_t *currAddr = 0x8000; 46 | uint8_t currAttr = ATTR_DEFAULT; 47 | uint8_t savedAttr = 0; 48 | uint8_t currAttrUline = 0; 49 | uint8_t currAttrBlink = 0; 50 | uint8_t currAttrReverse = 0; 51 | uint8_t defAttrFg = ATTR_WHITE; 52 | uint8_t defAttrBg = ATTR_BLACK; 53 | uint8_t defAttr; 54 | uint8_t currAttrFg = ATTR_WHITE; 55 | uint8_t currAttrBg = ATTR_BLACK; 56 | 57 | uint8_t display_cols = COLS; 58 | uint8_t display_rows = ROWS; 59 | uint8_t hasAttr = 0; 60 | uint8_t bytesCol = 2; 61 | uint8_t bytesRow = 2*COLS; 62 | uint8_t *screenAddr = defScreenAddr; 63 | uint8_t *screenEnd = defScreenEnd; 64 | 65 | char prevCh; 66 | 67 | enum vt100_state_e { 68 | STATE_START, 69 | STATE_ESC, 70 | STATE_CSI, 71 | STATE_NUM, 72 | STATE_SEMI, 73 | STATE_FINISH, 74 | STATE_ERROR 75 | }; 76 | 77 | enum vt100_state_e vt100state = -1; 78 | 79 | #ifdef _CMOC_VERSION_ 80 | int isgraph(int c) 81 | { 82 | return (c>=' ' && c<=0x7f); 83 | } 84 | #endif 85 | 86 | void printline(char *data, int N) 87 | { 88 | uint8_t i; 89 | 90 | for(i=0;i %d\n", c, isprint(c)? c:'.', vt100state, ns); 121 | vt100state = ns; 122 | } 123 | */ 124 | #define STATE_CHANGE(c, ns) vt100state = ns 125 | 126 | // #ifdef _CMOC_VERSION_ 127 | #if 0 128 | int isdigit(unsigned char c) 129 | { 130 | return ((c-48) < 10); 131 | } 132 | #endif 133 | 134 | 135 | uint8_t *get_pos_address(uint8_t col, uint8_t row) 136 | { 137 | uint16_t a; 138 | /* 139 | col -= 1; 140 | row -= 1; 141 | a = screenAddr; 142 | a +=( ((uint16_t)2 * display_cols) * row); 143 | a += (col*2); 144 | */ 145 | a = screenAddr; 146 | asm 147 | { 148 | * a += display_cols * (row-1) * 2 149 | lda :display_cols 150 | ldb :row 151 | decb 152 | mul 153 | tst :hasAttr 154 | beq cont@ 155 | aslb 156 | rola 157 | cont@ 158 | addd :a 159 | std :a 160 | 161 | * a += (col-1) * 2 162 | clra 163 | ldb :col 164 | decb 165 | tst :hasAttr 166 | beq cont@ 167 | aslb 168 | rola 169 | cont@ 170 | addd :a 171 | std :a 172 | } 173 | return (uint8_t *)a; 174 | } 175 | 176 | void mapMmu(uint8_t task, uint8_t bank, uint8_t block) 177 | { 178 | *((uint8_t *)0xffa0 + (task*8) + bank) = block; 179 | } 180 | 181 | 182 | void move_cursor(uint8_t col, uint8_t row) { 183 | uint8_t *newAddr; 184 | 185 | // wrapCheck(col, row); 186 | asm 187 | { 188 | clra 189 | 190 | ldb :col 191 | bne colhi@ 192 | incb 193 | stb :col 194 | colhi@: 195 | cmpb :display_cols 196 | bls coldone@ 197 | ldb :display_cols 198 | stb :col 199 | coldone@: 200 | stb :currCol 201 | 202 | ldb :row 203 | bne rowhi@ 204 | incb 205 | stb :row 206 | rowhi@ 207 | cmpb :display_rows 208 | bls rowdone@ 209 | ldb :display_rows 210 | stb :row 211 | rowdone@ 212 | stb :currRow 213 | } 214 | 215 | newAddr = get_pos_address(col, row); 216 | 217 | // doMoveCursor(newAddr); 218 | disableInterrupts(); 219 | if (hasAttr) { 220 | MAP_IN_SCREEN; // mapMmu(0, 4, 0x36); // Map in text screen block 6.6 at 0x8000 221 | } 222 | 223 | asm { 224 | tst :hasAttr 225 | beq noAttr 226 | * *(currAddr + 1) = savedAttr; // currAttr; 227 | ldx :currAddr 228 | ldb :savedAttr 229 | stb 1,x 230 | 231 | * savedAttr = *(newAddr+1); 232 | ldx :newAddr 233 | ldb 1,x 234 | stb :savedAttr 235 | 236 | * *(newAddr+1) ^= ATTR_ULINE; 237 | eorb #$40 238 | stb 1,x 239 | bra storeNewAddr 240 | 241 | noAttr: 242 | ldx :currAddr 243 | ldb :savedAttr 244 | stb ,x 245 | 246 | ldx :newAddr 247 | ldb ,x 248 | stb :savedAttr 249 | 250 | ldb #$af 251 | stb ,x 252 | 253 | * currAddr = newAddr; 254 | storeNewAddr: 255 | stx :currAddr 256 | } 257 | 258 | if (hasAttr) { 259 | MAP_OUT_SCREEN; // mapMmu(0, 4, 0x3c); // Restore basic block 7.4 at 0x8000 260 | } 261 | enableInterrupts(); 262 | 263 | // currCol = col; 264 | // currRow = row; 265 | // currAddr = newAddr; 266 | } 267 | 268 | void move_window(int n) { 269 | } 270 | 271 | void get_curr_pos() { 272 | currRow = 1 + (*(uint8_t *)H_CRSX); 273 | currCol = 1 + (*(uint8_t *)H_CRSY); 274 | } 275 | 276 | 277 | void move_cursor_relative(uint8_t col, uint8_t row) { 278 | move_cursor(currCol + col, currRow + row); 279 | } 280 | 281 | void doScroll() 282 | { 283 | uint16_t *src = (uint16_t *)(screenAddr + bytesRow); 284 | uint16_t *dst = (uint16_t *)screenAddr; 285 | uint16_t *end = (uint16_t *)screenEnd; 286 | 287 | disableInterrupts(); 288 | if (hasAttr) { 289 | MAP_IN_SCREEN; // mapMmu(0, 4, 0x36); // Map in text screen block 6.6 at 0x8000 290 | *(currAddr+1) = savedAttr; 291 | } else { 292 | *(currAddr) = savedAttr; 293 | } 294 | 295 | asm 296 | { 297 | * copy 298 | ldx :src 299 | ldy :dst 300 | bra @check 301 | @loop: 302 | ldd ,x++ 303 | std ,y++ 304 | @check: 305 | cmpx :end 306 | blt @loop 307 | 308 | * clear line 309 | * ldd #$2000 310 | tst :hasAttr 311 | beq @noAttr 312 | lda #$20 313 | ldb :defAttr 314 | bra @check 315 | @noAttr 316 | ldd #$6060 317 | bra @check 318 | @loop: 319 | std ,y++ 320 | @check: 321 | cmpy :end 322 | blt @loop 323 | } 324 | if (hasAttr) { 325 | savedAttr = *(currAddr+1); 326 | *(currAddr+1) ^= ATTR_ULINE; 327 | MAP_OUT_SCREEN; // mapMmu(0, 4, 0x3c); // Restore basic block 7.4 at 0x8000 328 | } else { 329 | savedAttr = *(currAddr); 330 | } 331 | enableInterrupts(); 332 | } 333 | 334 | void vt100_putchar_a() 335 | { 336 | char ch; 337 | uint8_t print = 0; 338 | uint8_t doLf = 0; 339 | 340 | asm 341 | { 342 | pshs x,b // preserve registers used by this routine 343 | sta :ch 344 | } 345 | 346 | switch(ch) 347 | { 348 | case 0x08: 349 | move_cursor_relative(-1, 0); 350 | break; 351 | 352 | case 0x09: 353 | move_cursor((currCol+8)%8, currRow); 354 | break; 355 | 356 | case 0x0a: 357 | doLf = 1; 358 | break; 359 | 360 | case 0x0d: 361 | move_cursor(1, currRow); 362 | // doLf = 1; 363 | break; 364 | 365 | default: 366 | print = 1; 367 | } 368 | 369 | if (doLf) 370 | { 371 | if (currRow == display_rows) 372 | doScroll(); 373 | else 374 | move_cursor_relative(0, 1); 375 | } 376 | if (print) 377 | { 378 | prevCh = ch; 379 | disableInterrupts(); 380 | if (hasAttr) { 381 | MAP_IN_SCREEN; // mapMmu(0, 4, 0x36); // Map in text screen block 6.6 at 0x8000 382 | // *(currAddr+1) = currAttr; 383 | // savedAttr = currAttr; 384 | // *(currAddr) = ch; 385 | asm { 386 | ldx :currAddr 387 | lda :currAttr 388 | sta 1,x 389 | sta :savedAttr 390 | ldb :ch 391 | stb ,x 392 | } 393 | } else { 394 | ch = xlate6847[ch]; 395 | savedAttr = ch; 396 | *(currAddr) = ch; 397 | } 398 | if (hasAttr) 399 | MAP_OUT_SCREEN; // mapMmu(0, 4, 0x3c); // Restore basic block 7.4 at 0x8000 400 | enableInterrupts(); 401 | 402 | if (currCol == display_cols) { 403 | if (currRow == display_rows) { 404 | doScroll(); 405 | move_cursor(1,currRow); 406 | } else { 407 | move_cursor(1,currRow+1); 408 | } 409 | } else { 410 | move_cursor_relative(1,0); 411 | } 412 | } 413 | 414 | asm 415 | { 416 | puls b,x 417 | } 418 | } 419 | 420 | void vt100_putchar(char ch) 421 | { 422 | asm 423 | { 424 | lda :ch 425 | lbsr _vt100_putchar_a 426 | } 427 | } 428 | 429 | void vt100_puts(char *s) 430 | { 431 | while(*s) 432 | vt100_putchar(*s++); 433 | } 434 | 435 | void vt100_putstr(char *s, size_t n) 436 | { 437 | for(; n>0; n--) 438 | vt100_putchar(*s++); 439 | } 440 | 441 | void erase_block(uint8_t *start, uint8_t *end) 442 | { 443 | uint8_t c; 444 | uint8_t *p; 445 | 446 | disableInterrupts(); 447 | if (hasAttr) { 448 | MAP_IN_SCREEN; // mapMmu(0, 4, 0x36); // Map in text screen block 6.6 at 0x8000 449 | c = 0x20; 450 | } else { 451 | c = 0x60; 452 | } 453 | for (p=start; p<=end; ) 454 | { 455 | *p++ = c; 456 | if (hasAttr) 457 | *p++ = currAttr; 458 | } 459 | if (hasAttr) 460 | MAP_OUT_SCREEN; // mapMmu(0, 4, 0x3c); // Restore basic block 7.4 at 0x8000 461 | enableInterrupts(); 462 | } 463 | 464 | 465 | void erase_from_here() 466 | { 467 | // printf ("ED: Erase Display: From here (%d, %d)\n", currRow, currCol); 468 | erase_block(currAddr, screenEnd); 469 | } 470 | void erase_to_here() 471 | { 472 | // printf ("ED: Erase Display: To here (%d, %d)\n", currRow, currCol); 473 | erase_block(screenAddr, currAddr); 474 | } 475 | 476 | void clear_screen() 477 | { 478 | // printf ("ED: Erase Display: Clear Screen\n"); 479 | erase_block(screenAddr, screenEnd); 480 | } 481 | 482 | void erase_line_from_here() 483 | { 484 | // printf ("ED: Erase Display: Line From here (%d, %d)\n", currRow, currCol); 485 | erase_block(currAddr, get_pos_address(display_cols-1, currRow)); 486 | } 487 | 488 | void erase_line_to_here() 489 | { 490 | // printf ("ED: Erase Display: Line To here (%d, %d)\n", currRow, currCol); 491 | erase_block(get_pos_address(0, currRow), currAddr); 492 | } 493 | 494 | void clear_line() 495 | { 496 | // printf ("ED: Erase Display: Clear Line: %d\n", currRow); 497 | erase_block(get_pos_address(0, currRow), get_pos_address(display_cols-1, currRow)); 498 | } 499 | 500 | void delete_chars_in_line(uint8_t n) 501 | { 502 | uint8_t *dst = currAddr; 503 | uint8_t *src = get_pos_address(currCol + n, currRow); 504 | uint8_t count = display_cols - currCol - n; 505 | uint8_t c; 506 | 507 | 508 | if (hasAttr) { 509 | count *= bytesCol; 510 | c = 0x20; 511 | } else { 512 | c = 0x60; 513 | } 514 | 515 | disableInterrupts(); 516 | if (hasAttr) 517 | MAP_IN_SCREEN; 518 | for ( ; count>0; count--) 519 | *(dst++) = *(src++); 520 | 521 | for ( ; n>0; n--) { 522 | *(dst++) = c; 523 | if (hasAttr) 524 | *(dst++) = defAttr; 525 | } 526 | if (hasAttr) 527 | MAP_OUT_SCREEN; 528 | enableInterrupts(); 529 | } 530 | 531 | 532 | /* 533 | * parse VT100 codes 534 | * Input: char to process 535 | * Output: 536 | * 0 - parsing successful, caller should skip processing this character 537 | * n - Number of characters stored in the buffer which caller needs to 538 | * process. After processing the buffer caller should vt100_init. 539 | * After this caller should process the current character normally. 540 | * References: 541 | * vt100 user guide chapter 3 542 | */ 543 | 544 | int vt100getnum(char *p1, char *p2) { 545 | int n; 546 | char p2temp = *p2; 547 | *p2 = 0; 548 | n = atoi(p1); 549 | *p2 = p2temp; 550 | return n; 551 | } 552 | 553 | void sgrClear(void) 554 | { 555 | if (!hasAttr) 556 | return; 557 | currAttr = 0; 558 | currAttr |= defAttrBg; 559 | currAttr |= (defAttrFg << 3); 560 | currAttrBg = defAttrBg; 561 | currAttrFg = defAttrFg; 562 | currAttrReverse = 0; 563 | currAttrUline = 0; 564 | currAttrBlink = 0; 565 | } 566 | 567 | void sgrProc(void) 568 | { 569 | uint8_t i,n; 570 | if (!hasAttr) 571 | return; 572 | for (i=0 ; i=30 && n<38 ) { 594 | currAttrFg = n - 30; 595 | } else if ( n == 38 ) { 596 | currAttrUline = ATTR_ULINE; 597 | currAttrFg = defAttrFg; 598 | currAttrBg = defAttrBg; 599 | } else if ( n == 39 ) { 600 | currAttrUline = 0; 601 | currAttrFg = defAttrFg; 602 | currAttrBg = defAttrBg; 603 | } else if ( n>=40 && n<48 ) { 604 | currAttrBg = n - 40; 605 | } else if ( n == 49 ) { 606 | currAttrBg = defAttrBg; 607 | } 608 | } 609 | 610 | if (currAttrReverse) { 611 | currAttr &= ATTR_COLOR_MASK; 612 | currAttr |= currAttrFg; 613 | currAttr |= (currAttrBg << 3); 614 | } else { 615 | currAttr &= ATTR_COLOR_MASK; 616 | currAttr |= currAttrBg; 617 | currAttr |= (currAttrFg << 3); 618 | } 619 | 620 | currAttr &= ATTR_CLR; 621 | currAttr |= (currAttrUline & ATTR_ULINE); 622 | currAttr |= (currAttrBlink & ATTR_BLINK); 623 | savedAttr = currAttr; 624 | } 625 | 626 | void vt100_init(void) { 627 | STATE_CHANGE(0, STATE_START); 628 | vt100bufp = vt100buf; 629 | vt100bufi = 0; 630 | vt100nump = NULL; 631 | vt100numi = 0; 632 | } 633 | 634 | void setupBorder(void) 635 | { 636 | *(uint8_t *)0xff9a = defAttrBg; 637 | } 638 | 639 | void vt100_setup(uint8_t cols, uint8_t rows, uint8_t attrFlag, uint8_t* startAddr) { 640 | display_cols = cols; 641 | display_rows = rows; 642 | 643 | hasAttr = attrFlag; 644 | if (hasAttr) { 645 | defAttr = (defAttrFg << 3) | defAttrBg; 646 | setupPalette(); 647 | setupBorder(); 648 | sgrClear(); 649 | savedAttr = currAttr; 650 | bytesCol = 2; 651 | } else { 652 | bytesCol = 1; 653 | } 654 | bytesRow = display_cols * bytesCol; 655 | 656 | screenAddr = startAddr; 657 | currAddr = startAddr; 658 | screenEnd = startAddr + ((uint16_t)bytesRow * display_rows) - 1; 659 | 660 | clear_screen(); 661 | move_cursor(1,1); 662 | vt100_init(); 663 | setConsoleOutHook(vt100_putchar_a); 664 | } 665 | 666 | int vt100(char c) { 667 | uint8_t i, j; 668 | char *p; 669 | 670 | // If the previous state was STATE_NUM and the incoming character is 671 | // not numeric, then the number string starting at vt100nump can be 672 | // read in now. 673 | if (vt100state == STATE_NUM && !isdigit(c)) { 674 | *vt100bufp = 0; 675 | vt100nums[vt100numi++] = (uint8_t)atoi((const char *)vt100nump); 676 | } 677 | 678 | // Main parser logic 679 | if ( vt100state == STATE_START ) { 680 | // Start of an escape sequence 681 | if ( c == '\x1b' ) { 682 | if (vt100state == STATE_START) { 683 | STATE_CHANGE(c, STATE_ESC); 684 | } else { 685 | STATE_CHANGE(c, STATE_ERROR); 686 | } 687 | } else { 688 | STATE_CHANGE(c, STATE_ERROR); 689 | } 690 | } else { 691 | switch(c) { 692 | // Start of an escape sequence 693 | case '\x1b': 694 | if (vt100state == STATE_START) { 695 | STATE_CHANGE(c, STATE_ESC); 696 | } else { 697 | STATE_CHANGE(c, STATE_ERROR); 698 | } 699 | break; 700 | 701 | // Start of a CSI sequence 702 | case '[': 703 | if (vt100state == STATE_ESC) { 704 | STATE_CHANGE(c, STATE_CSI); 705 | } else { 706 | STATE_CHANGE(c, STATE_ERROR); 707 | } 708 | break; 709 | 710 | // Numeric parameters: must follow ESC, CSI, or ; 711 | // vt100nump points to the start of the string 712 | case '1': 713 | case '2': 714 | case '3': 715 | case '4': 716 | case '5': 717 | case '6': 718 | case '7': 719 | case '8': 720 | case '9': 721 | case '0': 722 | if (vt100state == STATE_ESC || vt100state == STATE_CSI || 723 | vt100state == STATE_SEMI) { 724 | vt100nump = vt100bufp; 725 | STATE_CHANGE(c, STATE_NUM); 726 | } else if (vt100state == STATE_NUM) { 727 | STATE_CHANGE(c, STATE_NUM); 728 | } else { 729 | STATE_CHANGE(c, STATE_ERROR); 730 | } 731 | break; 732 | 733 | // Semicolons delimit numeric parameters. Valid if a semicolon 734 | // immediately follows a number 735 | case ';': 736 | if (vt100state == STATE_NUM) { 737 | STATE_CHANGE(c, STATE_SEMI); 738 | } else { 739 | STATE_CHANGE(c, STATE_ERROR); 740 | } 741 | break; 742 | 743 | // ICH insert blank characters 744 | case '@': 745 | if (vt100state == STATE_NUM) { 746 | for (i=0; i display_cols ? display_cols : j; 804 | // printf("CUF: Forward %d" , i); 805 | move_cursor_relative(j, 0); 806 | STATE_CHANGE(c, STATE_FINISH); 807 | break; 808 | 809 | // CUB Cursor backward 810 | case 'D': 811 | if (vt100state == STATE_CSI) { 812 | i = - 1; 813 | } else if (vt100state == STATE_NUM) { 814 | i = - vt100nums[0]; 815 | } else { 816 | STATE_CHANGE(c, STATE_ERROR); 817 | break; 818 | } 819 | 820 | move_cursor_relative(i, 0); 821 | // printf("CUB: Backward %d\n" , i); 822 | STATE_CHANGE(c, STATE_FINISH); 823 | break; 824 | 825 | // NEL - Next Line, CNL Cursor Next Line 826 | case 'E': 827 | if (vt100state == STATE_ESC || vt100state == STATE_CSI) { 828 | j = currRow + 1; 829 | } else if (vt100state == STATE_NUM) { 830 | j = currRow + vt100nums[0]; 831 | } else { 832 | STATE_CHANGE(c, STATE_ERROR); 833 | break; 834 | } 835 | 836 | // i = j > display_rows ? display_rows : j; 837 | move_cursor(1, j); 838 | // printf("CNL: Next Line %d\n", i); 839 | STATE_CHANGE(c, STATE_FINISH); 840 | break; 841 | 842 | // CPL Cursor Preceeding Line 843 | case 'F': 844 | if (vt100state == STATE_CSI) { 845 | j = currRow - 1; 846 | } else if (vt100state == STATE_NUM) { 847 | j = currRow - vt100nums[0]; 848 | } else { 849 | STATE_CHANGE(c, STATE_ERROR); 850 | break; 851 | } 852 | 853 | i = j < 1 ? 1 : j; 854 | // printf("CPL: Prev Line %d\n", i); 855 | move_cursor(1, i); 856 | STATE_CHANGE(c, STATE_FINISH); 857 | break; 858 | 859 | // CHA Cursor Character Absolute 860 | case 'G': 861 | if (vt100state == STATE_CSI) { 862 | i = 1; 863 | } else if (vt100state == STATE_NUM) { 864 | i = vt100nums[0]; 865 | } else { 866 | STATE_CHANGE(c, STATE_ERROR); 867 | break; 868 | } 869 | 870 | // printf("CHA: Cursor Character Absolute: %d\n", i); 871 | move_cursor(i, currRow); 872 | STATE_CHANGE(c, STATE_FINISH); 873 | break; 874 | 875 | // CUP (Cursor Position) 876 | case 'f': 877 | case 'H': 878 | if (vt100state == STATE_NUM && vt100numi == 2) { 879 | // printf("CUP: Cursor Position\n"); 880 | move_cursor(vt100nums[1], vt100nums[0]); 881 | STATE_CHANGE(c, STATE_FINISH); 882 | } else if (vt100state == STATE_CSI) { 883 | // printf("CUP: Cursor Position\n"); 884 | move_cursor(1, 1); 885 | STATE_CHANGE(c, STATE_FINISH); 886 | } else { 887 | STATE_CHANGE(c, STATE_ERROR); 888 | } 889 | break; 890 | 891 | // ED Erase In Page (Erase Display) 892 | case 'J': 893 | if (vt100state == STATE_CSI) { 894 | erase_from_here(); 895 | STATE_CHANGE(c, STATE_FINISH); 896 | } else if (vt100state == STATE_NUM) { 897 | i = vt100nums[0]; 898 | if (i == 0) { 899 | erase_from_here(); 900 | STATE_CHANGE(c, STATE_FINISH); 901 | } else if (i == 1) { 902 | erase_to_here(); 903 | STATE_CHANGE(c, STATE_FINISH); 904 | } else if (i == 2 || i == 3 /* Linux */ ) { 905 | clear_screen(); 906 | move_cursor(1,1); 907 | STATE_CHANGE(c, STATE_FINISH); 908 | } else { 909 | STATE_CHANGE(c, STATE_ERROR); 910 | } 911 | } else { 912 | STATE_CHANGE(c, STATE_ERROR); 913 | } 914 | break; 915 | 916 | // EL Erase Line (ANSI.SYS) 917 | case 'K': 918 | if (vt100state == STATE_CSI) { 919 | erase_line_from_here(); 920 | STATE_CHANGE(c, STATE_FINISH); 921 | } else if (vt100state == STATE_NUM) { 922 | i = vt100nums[0]; 923 | if (i == 0) { 924 | erase_line_from_here(); 925 | STATE_CHANGE(c, STATE_FINISH); 926 | } else if (i == 1) { 927 | erase_line_to_here(); 928 | STATE_CHANGE(c, STATE_FINISH); 929 | } else if (i == 2 || i == 3 /* Linux */ ) { 930 | clear_line(); 931 | STATE_CHANGE(c, STATE_FINISH); 932 | } else { 933 | STATE_CHANGE(c, STATE_ERROR); 934 | } 935 | } else { 936 | STATE_CHANGE(c, STATE_ERROR); 937 | } 938 | break; 939 | 940 | // DCH - Delete Character 941 | case 'P': 942 | if (vt100state == STATE_CSI) { 943 | i = 1; 944 | } else if (vt100state == STATE_NUM) { 945 | i = vt100nums[0]; 946 | } else { 947 | STATE_CHANGE(c, STATE_ERROR); 948 | break; 949 | } 950 | 951 | delete_chars_in_line(i); 952 | STATE_CHANGE(c, STATE_FINISH); 953 | break; 954 | 955 | // SEE - Select Editing Extent 956 | case 'Q': 957 | STATE_CHANGE(c, STATE_FINISH); 958 | break; 959 | 960 | // DA (Device Attributes) (VT100) 961 | // Must follow a CSI or a number (number is ignored) 962 | case 'c': 963 | if (vt100state == STATE_CSI || vt100state == STATE_NUM ) { 964 | // 0 - no options 2 advanced video 965 | vt100numi = 0; 966 | sprintf(vt100buf, "\x1b[?1;%dc", vt100numi); 967 | dw_puts(vt100buf); 968 | STATE_CHANGE(c, STATE_FINISH); 969 | } else { 970 | STATE_CHANGE(c, STATE_ERROR); 971 | } 972 | break; 973 | 974 | // REP - Repeat 975 | case 'b': 976 | if (vt100state == STATE_CSI) { 977 | i = 1; 978 | } else if (vt100state == STATE_NUM) { 979 | i = vt100nums[0]; 980 | } else { 981 | STATE_CHANGE(c, STATE_ERROR); 982 | break; 983 | } 984 | 985 | for ( ; i>0; i--) 986 | vt100_putchar(prevCh); 987 | STATE_CHANGE(c, STATE_FINISH); 988 | break; 989 | 990 | // HPA - CHARACTER POSITION ABSOLUTE 991 | case 'd': 992 | if (vt100state == STATE_CSI) { 993 | i = 1; 994 | } else if (vt100state == STATE_NUM) { 995 | i = vt100nums[0]; 996 | } else { 997 | STATE_CHANGE(c, STATE_ERROR); 998 | break; 999 | } 1000 | 1001 | move_cursor(currCol, i); 1002 | STATE_CHANGE(c, STATE_FINISH); 1003 | break; 1004 | 1005 | // SGR (Set Graphic Rendition 1006 | case 'm': 1007 | if (vt100state == STATE_NUM) { 1008 | // for (i=0; i. 7 | # This is free software: you are free to change and redistribute it. 8 | # There is NO WARRANTY, to the extent permitted by law. 9 | # 10 | # Special exception: Code included in output (such as "dunzip") is not 11 | # considered to be part of this program, so the GPL doesn't apply to the 12 | # results. 13 | 14 | # See the output of "bin2cas.pl --help" for detailed usage information, with 15 | # examples. 16 | 17 | # Pre-v2.0: really rubbish 18 | # Pre-v3.0: getting better, but still limited 19 | # v3.0: complete overhaul 20 | # supports multiple parts 21 | # builds reasonably arbitrary autorun loader 22 | # update VDG & SAM configs during loading 23 | # manual cursor flasher for when video offset changed 24 | # v3.0a: tweak default samplerate to 12kHz if fast mode used 25 | # slightly increase speed in fast mode 26 | # v3.1: default timing uses varying pulse widths to account for ROM delays 27 | # --timing option selects between "rom" and "simple" timing 28 | # fast cycle timing better at 9600Hz, so dropped default back 29 | # slightly faster WAV creation by caching runs of samples 30 | # v3.2: don't redirect STDOUT 31 | # new option --lds inserts stack adjustment code into autorun 32 | # license foo, --version 33 | # v3.3: actually drop default to 9600Hz 34 | # dzip using temp files under windows 35 | 36 | use strict; 37 | 38 | require v5.10; 39 | 40 | use File::Temp qw/tempfile tempdir/; 41 | use Math::Trig; 42 | require bytes; 43 | 44 | use constant VERSION => "3.3"; 45 | 46 | use constant { 47 | TYPE_BASIC => 0, 48 | TYPE_DATA => 1, 49 | TYPE_BINARY => 2, 50 | }; 51 | 52 | use constant { 53 | DDOS_TYPE_BASIC => 1, 54 | DDOS_TYPE_BINARY => 2, 55 | }; 56 | 57 | use constant { 58 | ENCODING_BINARY => 0, 59 | ENCODING_ASCII => 0xff, 60 | }; 61 | 62 | use constant { 63 | GAP_FALSE => 0, 64 | GAP_TRUE => 0xff, 65 | }; 66 | 67 | use constant { 68 | BLOCK_NAMEFILE => 0, 69 | BLOCK_DATA => 1, 70 | BLOCK_EOF => 0xff, 71 | }; 72 | 73 | use constant { 74 | INPUT_RAW => 0, 75 | INPUT_DRAGONDOS => 1, 76 | INPUT_COCO => 2, 77 | }; 78 | 79 | sub suggest_help { 80 | print STDERR "Try '$0 --help' for more information'\n"; 81 | exit 1; 82 | } 83 | 84 | sub help_text { 85 | print <. 208 | This is free software: you are free to change and redistribute it. 209 | There is NO WARRANTY, to the extent permitted by law. 210 | 211 | Special exception: Code included in output (such as "dunzip") is not 212 | considered to be part of this program, so the GPL doesn't apply to the 213 | results. 214 | EOF 215 | exit 0; 216 | } 217 | 218 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 219 | 220 | # Map DragonDOS filetypes to tape filetypes. 221 | 222 | my %ddos_to_tape_type = ( 223 | DDOS_TYPE_BASIC => TYPE_BASIC, 224 | DDOS_TYPE_BINARY => TYPE_BINARY, 225 | ); 226 | 227 | # Wave data. 228 | 229 | my @wav_header = ( 230 | 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 231 | 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 232 | 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 233 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 234 | 0x01, 0x00, 0x08, 0x00, 0x64, 0x61, 0x74, 0x61, 235 | 0x00, 0x00, 0x00, 0x00 236 | ); 237 | 238 | # Timings 239 | 240 | # 'cycles' is the nominal number of CPU cycles (SAM / 16) in the waveform for 241 | # each bit (0, 1), the rest are pulse specs. 242 | 243 | # Pulse spec is three pairs of pulse delays (first pulse, second pulse): 244 | # - first bit of first byte 245 | # - remaining bits in each byte 246 | # - first bit of subsequent bytes 247 | 248 | # 'leader' is pulse spec while writing leader bytes and the sync byte 249 | # 'first' is pulse spec for the first byte in a block 250 | # 'rest' is pulse spec for remaining bytes in a block (until next leader) 251 | 252 | # The "targetted" spec is intended to be the "best fit" for the Dragon ROM - 253 | # that is, the ROM will count an "ideal" pulse width for each bit. This should 254 | # improve reliability. 255 | 256 | my %timing_simple = ( 257 | 'cycles' => [ 813, 435 ], 258 | 'leader' => [ 0, 0, 0, 0, 0, 0 ], 259 | 'first' => [ 46, 0, 0, 0, 46, 0 ], 260 | 'rest' => [ 46, 0, 0, 0, 46, 0 ], # 818.75, 440.75 -> 629.75 261 | ); 262 | 263 | my %timing_rom = ( 264 | 'cycles' => [ 26*28, 13*28 ], 265 | 'leader' => [ 205, 5, 68, 5, 68, 5 ], 266 | 'first' => [ 81, 5, 67, 5, 120, 5 ], 267 | 'rest' => [ 120, 5, 67, 5, 120, 5 ], # 806.625, 442.625 -> 625.625 268 | ); 269 | 270 | my %timing_fast = ( 271 | 'cycles' => [ 18*28, 7*28 ], 272 | 'leader' => [ 205, 5, 68, 5, 68, 5 ], 273 | 'first' => [ 81, 5, 67, 5, 120, 5 ], 274 | 'rest' => [ 120, 5, 67, 5, 120, 5 ], # 582.625, 274.625 -> 428.125 275 | ); 276 | 277 | my %timing_by_name = ( 278 | 'simple' => \%timing_simple, 279 | 'rom' => \%timing_rom, 280 | ); 281 | 282 | # Autorun headers can include optional parts, concatenated and subject to 283 | # linking. 284 | 285 | my @code_load_0 = ( 286 | "load_part", 287 | 0x9f, 0x7e, # stx <$7e 288 | 0xad, 0x9f, 0xa0, 0x04, # jsr [CSRDON] 289 | "l0", 290 | ); 291 | 292 | my @code_load_flash = ( 293 | 0xb6, ">flash_addr", # lda >flash_addr 294 | 0x88, 0x40, # eora #$40 295 | # flash code starts off disabled (first load will still be in text mode) 296 | "mod_flash", 297 | 0x8c, ">flash_addr", # cmpx >flash_addr 298 | ); 299 | 300 | my @code_load_1 = ( 301 | 0xad, 0x9f, 0xa0, 0x06, # jsr [BLKIN] 302 | 0x26, "\&$ff21 309 | 0x84, 0xf7, # anda #$f7 310 | 0xb7, 0xff, 0x21, # sta >$ff21 311 | "do_rts", 312 | 0x39, # rts 313 | "do_io_error", 314 | 0x8d, "\&io_error", # ldx #io_error 316 | 0x8d, "\&mod_flash", # sta mod_flash 333 | ); 334 | 335 | my @code_enable_flasher = ( 336 | 0x86, 0xb7, # lda #$8c ; sta 337 | 0xb7, ">mod_flash", # sta mod_flash 338 | ); 339 | 340 | my @code_test_arch = ( 341 | 0xb6, 0xa0, 0x00, # lda $a000 342 | 0x84, 0x20, # anda #$20 343 | 0x97, 0x10, # sta <$10 344 | ); 345 | 346 | my @code_fast = ( 347 | 0xcc, ">fast_pw", # ldd #fast_pw 348 | 0x0d, 0x10, # tst <$10 349 | 0x26, "\& "set_vdg", 'value' => $v }; 538 | } elsif ($opt eq '--sam-v') { 539 | my $v = eval_int(shift @ARGV); 540 | push @files, { 'code' => "set_sam_v", 'value' => $v }; 541 | } elsif ($opt eq '--sam-f') { 542 | my $v = eval_int(shift @ARGV); 543 | push @files, { 'code' => "set_sam_f", 'value' => $v }; 544 | } elsif ($opt eq '--lds') { 545 | my $v = eval_int(shift @ARGV); 546 | push @files, { 'code' => "lds", 'value' => $v }; 547 | } elsif ($opt eq '--no-flasher') { 548 | $want_flasher = 0; 549 | } elsif ($opt eq '--flasher') { 550 | $want_flasher = 1; 551 | $any_flasher = 1; 552 | } elsif ($opt eq '--help') { 553 | help_text(); 554 | } elsif ($opt eq '--version') { 555 | version_text(); 556 | } elsif ($opt =~ /^-/) { 557 | print STDERR "Unrecognised option '$opt'\n"; 558 | suggest_help(); 559 | } else { 560 | my $file = input_file($opt); 561 | push @files, $file; 562 | if (!$autorun) { 563 | undef $want_name; 564 | undef $want_exec; 565 | } 566 | undef $want_load; 567 | undef $want_zload; 568 | } 569 | } 570 | 571 | # Prepare output stream. 572 | 573 | die "No output filename specified\n" unless (defined $out_filename); 574 | open($out_fd, ">", $out_filename) or die $!; 575 | binmode $out_fd; 576 | if ($out_filename =~ /\.cas$/i) { 577 | $wav_out //= 0; 578 | } elsif ($out_filename =~ /\.wav$/i) { 579 | $wav_out //= 1; 580 | } 581 | 582 | $sample_rate //= 9600; 583 | my $sam_rate = 14318180; 584 | my $bytes_per_sample = $bits_per_sample >> 3; 585 | 586 | # WAV header? 587 | if ($wav_out) { 588 | # NumChannels 589 | $wav_header[22] = $num_channels; 590 | $wav_header[23] = ($num_channels >> 8) & 0xff; 591 | # SampleRate 592 | $wav_header[24] = $sample_rate & 0xff; 593 | $wav_header[25] = ($sample_rate >> 8) & 0xff; 594 | $wav_header[26] = ($sample_rate >> 16) & 0xff; 595 | $wav_header[27] = ($sample_rate >> 24) & 0xff; 596 | # ByteRate 597 | my $byte_rate = $sample_rate * $num_channels * $bytes_per_sample; 598 | $wav_header[28] = $byte_rate & 0xff; 599 | $wav_header[29] = ($byte_rate >> 8) & 0xff; 600 | $wav_header[30] = ($byte_rate >> 16) & 0xff; 601 | $wav_header[31] = ($byte_rate >> 24) & 0xff; 602 | # BlockAlign 603 | my $block_align = ($num_channels * $bits_per_sample) / 8; 604 | $wav_header[32] = $block_align & 0xff; 605 | $wav_header[33] = ($block_align >> 8) & 0xff; 606 | # BitsPerSample 607 | $wav_header[34] = $bits_per_sample & 0xff; 608 | $wav_header[35] = ($bits_per_sample >> 8) & 0xff; 609 | print $out_fd pack("C*", @wav_header); 610 | } 611 | 612 | # Write file(s). 613 | 614 | if ($autorun) { 615 | write_autorun(\@files); 616 | } else { 617 | for my $file (@files) { 618 | write_file($file); 619 | } 620 | } 621 | 622 | # Close output. 623 | 624 | if ($wav_out) { 625 | # rewrite Subchunk2Size 626 | seek($out_fd, 40, 0); 627 | print $out_fd pack("C", $sample_count & 0xff); 628 | print $out_fd pack("C", ($sample_count >> 8) & 0xff); 629 | print $out_fd pack("C", ($sample_count >> 16) & 0xff); 630 | print $out_fd pack("C", ($sample_count >> 24) & 0xff); 631 | # rewrite ChunkSize 632 | $sample_count += 36; 633 | seek($out_fd, 4, 0); 634 | print $out_fd pack("C", $sample_count & 0xff); 635 | print $out_fd pack("C", ($sample_count >> 8) & 0xff); 636 | print $out_fd pack("C", ($sample_count >> 16) & 0xff); 637 | print $out_fd pack("C", ($sample_count >> 24) & 0xff); 638 | } 639 | 640 | exit 0; 641 | 642 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 643 | 644 | # Read a file, applying current defaults. 645 | 646 | sub input_file { 647 | my ($filename,$file) = @_; 648 | $file //= {}; 649 | 650 | # apply current selections as file defaults 651 | $file->{'fnblock'} //= $want_fnblock; 652 | $file->{'eof'} //= $want_eof; 653 | $file->{'eof_data'} //= $want_eof_data; 654 | $file->{'fast'} //= $want_fast; 655 | 656 | $file = do { 657 | if ($input_mode == INPUT_DRAGONDOS) { 658 | read_dragondos($filename, $file); 659 | } elsif ($input_mode == INPUT_COCO) { 660 | read_coco($filename, $file); 661 | } else { 662 | read_raw($filename, $file); 663 | } 664 | }; 665 | 666 | $file->{'name'} = $want_name if (defined $want_name); 667 | $file->{'load'} = $want_load if (defined $want_load); 668 | $file->{'exec'} = $want_exec if (defined $want_exec); 669 | $file->{'zload'} = $want_zload if (defined $want_zload); 670 | $file->{'flasher'} = $want_flasher; 671 | $file->{'name'} //= "BINARY"; 672 | 673 | die "Internal error\n" unless defined $file; 674 | die "No data\n" unless exists $file->{'segments'}; 675 | 676 | # XXX only deal with single-segment binaries for now 677 | coalesce_file($file); 678 | 679 | dzip_file($file) if ($want_dzip); 680 | 681 | return $file; 682 | } 683 | 684 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 685 | 686 | # File readers 687 | 688 | sub open_file { 689 | my ($filename,$file) = @_; 690 | $file //= {}; 691 | open(my $in, "<", $filename) or die "failed to open $filename: $!\n"; 692 | binmode $in; 693 | if ($filename =~ /^([^\.]{1,8})/) { 694 | $file->{'name'} //= uc $1; 695 | } 696 | return $in; 697 | } 698 | 699 | # Raw binary: just slurp data into one segment 700 | sub read_raw { 701 | my ($filename,$file) = @_; 702 | $file //= {}; 703 | my $in = open_file($filename, $file); 704 | my %segment = ( 705 | 'start' => 0, 706 | 'size' => 0, 707 | 'data' => "", 708 | ); 709 | my $data; 710 | my $rsize; 711 | do { 712 | $rsize = read $in, $data, 0x10000; 713 | $segment{'data'} .= $data; 714 | $segment{'size'} += $rsize; 715 | } while ($rsize == 0x10000); 716 | $file->{'segments'} = [ \%segment ]; 717 | close $in; 718 | return $file; 719 | } 720 | 721 | # DragonDOS binary - single segment only 722 | sub read_dragondos { 723 | my ($filename,$file) = @_; 724 | my $in = open_file($filename, $file); 725 | 726 | getc($in); # skip $55 727 | my $type = unpack("C", getc($in)); 728 | my $start = (unpack("C", getc($in)) << 8) | unpack("C", getc($in)); 729 | my $size = (unpack("C", getc($in)) << 8) | unpack("C", getc($in)); 730 | my $exec = (unpack("C", getc($in)) << 8) | unpack("C", getc($in)); 731 | getc($in); # skip $aa 732 | 733 | my $data; 734 | my $rsize = read $in, $data, $size; 735 | if ($rsize != $size) { 736 | print STDERR "Warning: short read from DragonDOS binary\n"; 737 | } 738 | my %segment = ( 739 | 'start' => $start, 740 | 'data' => $data, 741 | 'size' => $rsize, 742 | ); 743 | $file->{'segments'} = [ \%segment ]; 744 | close $in; 745 | 746 | $file->{'type'} //= $ddos_to_tape_type{$type} // TYPE_BINARY; 747 | $file->{'exec'} //= $exec; 748 | return $file; 749 | } 750 | 751 | # CoCo (DECB) - binaries can contain multiple segments 752 | 753 | # BASIC files are: $ff size>>8 size data* 754 | # BINARY files are: ($00 size>>8 size data*)+ $ff 00 00 exec>>8 exec 755 | # (binaries can contain multiple segments) 756 | 757 | sub read_coco { 758 | my ($filename,$file) = @_; 759 | my $in = open_file($filename, $file); 760 | 761 | my $type; 762 | my $exec; 763 | 764 | while (my $stype = getc($in)) { 765 | $stype = unpack("C", $stype); 766 | 767 | my $start; 768 | my $size = (unpack("C", getc($in)) << 8) | unpack("C", getc($in)); 769 | 770 | if ($stype == 0x00) { 771 | $type //= TYPE_BINARY; 772 | $start = (unpack("C", getc($in)) << 8) | unpack("C", getc($in)); 773 | } elsif (!defined $type && $stype == 0xff) { 774 | $type = TYPE_BASIC; 775 | $start = 0; 776 | $exec = 0; 777 | } elsif ($stype == 0xff) { 778 | if ($size != 0) { 779 | # XXX is this dodgy? 780 | printf STDERR "Warning: EXEC segment with non-zero size in CoCo binary\n"; 781 | } 782 | $exec = (unpack("C", getc($in)) << 8) | unpack("C", getc($in)); 783 | } else { 784 | printf STDERR "Warning: skipping data in CoCo binary\n"; 785 | last; 786 | } 787 | 788 | if ($size > 0) { 789 | my $data; 790 | my $rsize = read $in, $data, $size; 791 | if ($rsize != $size) { 792 | print STDERR "Warning: short read from CoCo binary\n"; 793 | } 794 | push @{$file->{'segments'}}, { 795 | 'start' => $start, 796 | 'data' => $data, 797 | 'size' => $rsize, 798 | }; 799 | } 800 | } 801 | close $in; 802 | 803 | $file->{'type'} //= $type // TYPE_BINARY; 804 | $file->{'exec'} //= $exec if (defined $exec); 805 | return $file; 806 | } 807 | 808 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 809 | 810 | # Replace zero or more segments with exactly one zero-padded segment. All 811 | # other file information preserved. 812 | 813 | sub coalesce_file { 814 | my $file = shift; 815 | return if (exists $file->{'segments'} && scalar(@{$file->{'segments'}}) == 1); 816 | my $old_segments = $file->{'segments'} // []; 817 | my %segment = (); 818 | my $end; 819 | for my $s (sort { $a->{'start'} <=> $b->{'start'} } @{$old_segments}) { 820 | my $start = $s->{'start'}; 821 | $segment{'start'} //= $start; 822 | $end //= $start; 823 | my $size = bytes::length($s->{'data'}); 824 | my $new_end = $start + $size; 825 | # TODO 826 | if ($start < $end) { 827 | die "Can't handle overlapping segments\n"; 828 | } 829 | # TODO 830 | if ($end >= 0x10000) { 831 | die "Segment too large\n"; 832 | } 833 | if ($start > $end) { 834 | $segment{'data'} .= "\0" x ($start - $end); 835 | } 836 | $segment{'data'} .= $s->{'data'}; 837 | $end = $new_end; 838 | } 839 | $segment{'data'} //= ""; 840 | $segment{'start'} //= 0; 841 | $end //= $segment{'start'}; 842 | $segment{'size'} = $end - $segment{'start'}; 843 | $file->{'segments'} = [ \%segment ]; 844 | } 845 | 846 | # Pass file data to dzip. Replaces data, preserves original file metadata and 847 | # "osize" records original data size. 848 | 849 | sub dzip_file { 850 | my $file = shift; 851 | coalesce_file($file); # single-segment only 852 | my $segment = $file->{'segments'}[0]; 853 | my $osize = $segment->{'size'} // bytes::length($segment->{'data'}); 854 | my $zdata = ""; 855 | my $cfd; 856 | my $tmp_filename; 857 | if ($^O eq 'MSWin32') { 858 | # So windows doesn't support forked pipes or list open(). Ack! 859 | # Ok, write binary to a temporary file, dzip it, then read in 860 | # the results. This is dumb. 861 | my $tmp_dir = tempdir(CLEANUP => 1); # auto-clean on exit 862 | my $tmp_fd; 863 | ($tmp_fd, $tmp_filename) = tempfile(DIR => $tmp_dir); 864 | binmode $tmp_fd; 865 | print $tmp_fd $segment->{'data'}; 866 | close $tmp_fd; 867 | system("dzip", "-k", $tmp_filename); 868 | open($cfd, "<", "$tmp_filename.dz") or die "Can't open dzipped tempfile\n"; 869 | } else { 870 | # For everything else, just pass the binary out to a fork 871 | # running dzip. No race. 872 | my $pid = open($cfd, "-|") // die "Failed to open pipe to dzip\n"; 873 | if ($pid == 0) { 874 | open(my $zfd, "|-", "dzip", "-c") // exit 0; 875 | binmode $zfd; 876 | print $zfd $segment->{'data'}; 877 | close $zfd; 878 | exit 0; 879 | } 880 | } 881 | binmode $cfd; 882 | { 883 | local $/ = undef; 884 | $zdata = <$cfd>; 885 | } 886 | close $cfd; 887 | if ($^O eq 'MSWin32') { 888 | unlink "$tmp_filename.dz"; 889 | } 890 | # reposition... 891 | my $zsize = bytes::length($zdata); 892 | die "No data from pipe to dzip\n" unless ($zsize > 0); 893 | $segment->{'dzip'} = 1; 894 | $segment->{'data'} = $zdata; 895 | $segment->{'size'} = $zsize; 896 | $segment->{'osize'} = $osize; 897 | } 898 | 899 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 900 | 901 | sub speed_normal { 902 | $timing = $timing_by_name{$timing_normal}; 903 | } 904 | 905 | sub speed_fast { 906 | $timing = \%timing_fast; 907 | } 908 | 909 | # Write a file. 910 | 911 | sub write_file { 912 | my $file = shift; 913 | 914 | if ($file->{'fast'}) { 915 | speed_fast(); 916 | } else { 917 | speed_normal(); 918 | } 919 | 920 | # XXX only deal with single-segment files 921 | coalesce_file(); 922 | my $segment0 = $file->{'segments'}[0]; 923 | 924 | # Write filename block if required. 925 | if ($file->{'fnblock'}) { 926 | my $name = $file->{'name'} // "BINARY"; 927 | my $type = $file->{'type'} // TYPE_BINARY; 928 | my $encoding = ENCODING_BINARY; 929 | my $gap = GAP_FALSE; 930 | my $load = $file->{'load'} // $segment0->{'start'} // 0x0e00; 931 | my $exec = $file->{'exec'} // $load; 932 | my $fndata = sprintf("\%-8s", $name); 933 | $fndata .= pack("CCCnn", $type, $encoding, $gap, $exec, $load); 934 | write_leader(); 935 | block_out(BLOCK_NAMEFILE, $fndata); 936 | } 937 | 938 | # Write file data. 939 | write_leader(); 940 | my $data = $segment0->{'data'}; 941 | my $size = $segment0->{'size'}; 942 | my $ptr = 0; 943 | while ($size > 0) { 944 | my $bsize = ($size > 255) ? 255 : $size; 945 | $size -= $bsize; 946 | if ($size == 0 && !$file->{'no_eof'}) { 947 | if ($file->{'eof_data'}) { 948 | block_out(BLOCK_EOF, bytes::substr($data, $ptr, $bsize)); 949 | } else { 950 | block_out(BLOCK_DATA, bytes::substr($data, $ptr, $bsize)); 951 | block_out(BLOCK_EOF, ""); 952 | } 953 | } else { 954 | block_out(BLOCK_DATA, bytes::substr($data, $ptr, $bsize)); 955 | } 956 | $ptr += $bsize; 957 | } 958 | 959 | bytes_out("U", $timing->{'leader'}); 960 | } 961 | 962 | # Write an autorun file, using data from a list of files. 963 | 964 | sub write_autorun { 965 | my $files = shift; 966 | 967 | # Ensure autorun program is stored at normal speed. 968 | speed_normal(); 969 | 970 | my $vdg = 0; 971 | my $sam_v = 0; 972 | my $sam_f = 2; # 0x0400 973 | my $flasher = 0; 974 | $mc_label{'flash_addr'} = 0x0400; 975 | 976 | my $name = $want_name // "BINARY"; 977 | 978 | # Construct a special filename block to autorun. This builds the data 979 | # out of blocks of code and then "links" it, as the contents can vary 980 | # (multiple files to load, fast loading, dunzipping data). 981 | 982 | # The standard part of the filename block. 983 | mcdata_org(0x01da); 984 | mcdata_add(\sprintf("\%-8s", $name)); # filename 985 | mcdata_add([ 986 | TYPE_BINARY, # file type: machine code 987 | ENCODING_BINARY, # ascii flag: binary 988 | GAP_FALSE, # gap flag: continuous 989 | "colon", 990 | 0x3a, 0x00, # exec address (convenient ':') 991 | 0x00, 0xa6, # load address: BASIC next 992 | "exec_loader", # main code starts at $01e9 993 | ]); 994 | 995 | # Include fast loader setup if necessary. 996 | if ($any_fast && $wav_out) { 997 | mcdata_add(\@code_test_arch); 998 | mcdata_add(\@code_fast); 999 | } 1000 | 1001 | # For each file, add loading instructions. 1002 | my $last_exec = 0; 1003 | for my $file (@{$files}) { 1004 | # a "file" might just be some specific code instructions! 1005 | my $code = $file->{'code'} // ""; 1006 | my $value = $file->{'value'} // 0; 1007 | if ($code eq "set_vdg") { 1008 | $value &= 0xf8; 1009 | if ($vdg != $value) { 1010 | mcdata_add([ 1011 | 0x86, $file->{'value'}, # lda #value 1012 | 0xb7, 0xff, 0x22, # sta >$ff22 1013 | ]); 1014 | } 1015 | $vdg = $value; 1016 | } elsif ($code eq "set_sam_v") { 1017 | for my $i (0..2) { 1018 | my $bit = (1 << $i); 1019 | next if ((($sam_v ^ $value) & $bit) == 0); 1020 | my $addr = 0xc0 + $i * 2; 1021 | $addr++ if ($value & $bit); 1022 | mcdata_add([ 1023 | 0xb7, 0xff, $addr, # sta >$ffxx 1024 | ]); 1025 | } 1026 | $sam_v = $value & 7; 1027 | } elsif ($code eq "set_sam_f") { 1028 | for my $i (0..6) { 1029 | my $bit = (1 << $i); 1030 | next if ((($sam_f ^ $value) & $bit) == 0); 1031 | my $addr = 0xc6 + $i * 2; 1032 | $addr++ if ($value & $bit); 1033 | mcdata_add([ 1034 | 0xb7, 0xff, $addr, # sta >$ffxx 1035 | ]); 1036 | } 1037 | $sam_f = $value & 0x7f; 1038 | $mc_label{'flash_addr'} = $sam_f * 512; 1039 | } elsif ($code eq "lds") { 1040 | mcdata_add([ 1041 | 0x10, 0xce, $value>>8, $value&0xff, # lds #value 1042 | ]); 1043 | } else { 1044 | my $segment0 = $file->{'segments'}[0]; 1045 | my $load = $file->{'load'} // $segment0->{'start'}; 1046 | my $size = $segment0->{'size'}; 1047 | my $oload = 0; 1048 | if ($segment0->{'dzip'}) { 1049 | my $osize = $segment0->{'osize'}; 1050 | $oload = $load; 1051 | $load = $file->{'zload'} // $oload + $osize + 1 - $size; 1052 | } 1053 | my $end = $load + $size; 1054 | my $fflasher = $file->{'flasher'} // 0; 1055 | if ($flasher && !$fflasher) { 1056 | mcdata_add(\@code_disable_flasher); 1057 | } elsif (!$flasher && $fflasher) { 1058 | mcdata_add(\@code_enable_flasher); 1059 | } 1060 | $flasher = $fflasher; 1061 | mcdata_add([ 1062 | 0x8e, $load>>8, $load&0xff, # ldx #load 1063 | 0x8d, "\&{'dzip'}) { 1066 | mcdata_add([ 1067 | 0x8e, $load>>8, $load&0xff, # ldx #load 1068 | 0xcc, $end>>8, $end&0xff, # ldd #end 1069 | 0xce, $oload>>8, $oload&0xff, # ldu #oload 1070 | 0x8d, "\&{'exec'}; 1074 | } 1075 | } 1076 | $mc_label{'exec'} = $last_exec // 0; 1077 | 1078 | # Finally, JMP to the program. 1079 | mcdata_add([ 1080 | 0x7e, ">exec", # jmp exec 1081 | ]); 1082 | 1083 | # Optional and non-optional chunks of code in support of above. 1084 | mcdata_add(\@code_load_0); 1085 | mcdata_add(\@code_load_flash) if ($any_flasher); 1086 | mcdata_add(\@code_load_1); 1087 | mcdata_add(\@code_dunzip) if ($any_dzip); 1088 | 1089 | # Link - replace all symbol references with actual addresses. 1090 | mcdata_link(); 1091 | 1092 | # Write filename block. 1093 | write_leader(); 1094 | block_out(BLOCK_NAMEFILE, $mc_data); 1095 | 1096 | # Now the data portion of the loader. Tiny, just overwrites "next 1097 | # basic token with a pointer to that convenient ':' in the filename 1098 | # block, and makes BASIC JMP to the loader code. 1099 | mcdata_org(0x00a6); 1100 | mcdata_add([ 1101 | ">colon", # fdb colon 1102 | 0x7e, ">exec_loader", # jmp exec_loader 1103 | ]); 1104 | mcdata_link(); 1105 | write_leader(); 1106 | block_out(BLOCK_EOF, $mc_data); 1107 | 1108 | # Now write file data, each without a filename block. 1109 | for my $file (@{$files}) { 1110 | next if (exists $file->{'code'}); 1111 | $file->{'fnblock'} = 0; 1112 | write_file($file); 1113 | } 1114 | } 1115 | 1116 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1117 | 1118 | sub write_leader { 1119 | if ($wav_out) { 1120 | sample_out(0x80, 0xda5c * 8); 1121 | } else { 1122 | bytes_out("U" x 94, $timing->{'leader'}); 1123 | # NOTE: CAS files can't be fast, but if they were, typical 1124 | # pause would be 136 leader bytes 1125 | } 1126 | bytes_out("U" x $leader, $timing->{'leader'}); 1127 | } 1128 | 1129 | sub block_out { 1130 | my ($btype,$data) = @_; 1131 | my $bsize = bytes::length($data); 1132 | my $sum = $btype + $bsize;; 1133 | bytes_out(pack("C*", 0x55, 0x3c), $timing->{'leader'}); 1134 | bytes_out(pack("C*", $btype, bytes::length($data)), $timing->{'first'}); 1135 | bytes_out($data, $timing->{'rest'}); 1136 | for (unpack("C*", $data)) { 1137 | $sum += $_; 1138 | } 1139 | bytes_out(pack("C", $sum & 0xff), $timing->{'rest'}); 1140 | bytes_out(pack("C", 0x55), $timing->{'leader'}); 1141 | } 1142 | 1143 | sub ao_period { 1144 | my $cycles = shift; 1145 | my $period = $ao_error + ($sample_rate * $cycles * 16) / $sam_rate; 1146 | $ao_error = $period - int($period + 0.5); 1147 | return int($period + 0.5); 1148 | } 1149 | 1150 | sub bytes_out { 1151 | my ($data,$pspec) = @_; 1152 | my $delay0 = $pspec->[0]; 1153 | my $delay1 = $pspec->[1]; 1154 | if (!$wav_out) { 1155 | print $out_fd $data; 1156 | return; 1157 | } 1158 | for my $byte (unpack("C*", $data)) { 1159 | for (0..7) { 1160 | my $cycles = $timing->{'cycles'}->[$byte &1] / 2; 1161 | my $period0 = ao_period($cycles + $delay0); 1162 | my $period1 = ao_period($cycles + $delay1); 1163 | print $out_fd sinewave($period0, $period1); 1164 | $sample_count += $period0 + $period1; 1165 | $byte >>= 1; 1166 | $delay0 = $pspec->[2]; 1167 | $delay1 = $pspec->[3]; 1168 | } 1169 | $delay0 = $pspec->[4]; 1170 | $delay1 = $pspec->[5]; 1171 | } 1172 | } 1173 | 1174 | sub sample_out { 1175 | my $samp = shift; 1176 | my $cycles = shift; 1177 | my $period = ao_period($cycles); 1178 | print $out_fd pack("C", $samp) x $period; 1179 | $sample_count += $period; 1180 | } 1181 | 1182 | sub sinewave { 1183 | my $period0 = shift; 1184 | my $period1 = shift // $period0; 1185 | if (exists $sinewaves{"$period0,$period1"}) { 1186 | return $sinewaves{"$period0,$period1"}; 1187 | } 1188 | my $sw = pack("C*", 1189 | (map { 1190 | my $v = sin((pi*$_)/($period0+1)); 1191 | int(($v * 115) + 128); } (1..$period0)), 1192 | (map { 1193 | my $v = sin(pi+(pi*$_)/($period1+1)); 1194 | int(($v * 115) + 128); } (1..$period1))); 1195 | $sinewaves{"$period0,$period1"} = $sw; 1196 | return $sw; 1197 | } 1198 | 1199 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1200 | 1201 | # Initialise machine code data. Note, does not clear label table. 1202 | 1203 | sub mcdata_org { 1204 | $mc_org = shift; 1205 | $mc_pc = $mc_org; 1206 | @mc_link = (); 1207 | $mc_data = ""; 1208 | } 1209 | 1210 | # Add machine code data. 1211 | 1212 | sub mcdata_add { 1213 | my $text = shift; 1214 | if (ref($text) eq 'ARRAY') { 1215 | for my $byte (@{$text}) { 1216 | if ($byte =~ /^[a-z]/) { 1217 | $mc_label{$byte} = $mc_pc; 1218 | } elsif ($byte =~ /^\&?/) { 1223 | $mc_pc += 2; 1224 | push @mc_link, [ $byte, $mc_pc ]; 1225 | $mc_data .= "\x00\x00"; 1226 | } else { 1227 | $mc_pc++; 1228 | $mc_data .= pack("C", $byte); 1229 | } 1230 | } 1231 | } elsif (ref($text) eq 'SCALAR') { 1232 | $mc_data .= $$text; 1233 | $mc_pc += bytes::length($$text); 1234 | } 1235 | } 1236 | 1237 | # "Link" the machine code - replace all the entries in @mc_link with computed 1238 | # values. 1239 | 1240 | sub mcdata_link { 1241 | for my $r (@mc_link) { 1242 | my $rlabel = $r->[0]; 1243 | my $pc = $r->[1]; 1244 | my $off = $pc - $mc_org; 1245 | if ($rlabel =~ /^(\&)?([<>])(.*)/) { 1246 | my ($rel,$size,$label) = ($1,$2,$3); 1247 | my $addr = $mc_label{$label} or die "missing label: $label\n"; 1248 | $size = ($size eq '<') ? 1 : 2; 1249 | $off -= $size; 1250 | $addr -= $pc if ($rel); 1251 | my $subdata; 1252 | if ($size == 1) { 1253 | $subdata = pack("C", $addr & 0xff); 1254 | } else { 1255 | $subdata = pack("n", $addr & 0xffff); 1256 | } 1257 | bytes::substr($mc_data, $off, $size, $subdata); 1258 | } 1259 | } 1260 | } 1261 | --------------------------------------------------------------------------------