├── 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) ";
164 | const char *banner = "DW TERMINAL v0.2\r\n(GPL) APRIL 18, 2020\r\nMICHAEL FURMAN \r\n\r\n";
165 |
166 | void dwtrm_puts(char *s)
167 | {
168 | if (vt100En)
169 | vt100_puts(s);
170 | else
171 | printf(s);
172 | }
173 |
174 | void dwtrm_putchar(char s)
175 | {
176 | if (vt100En)
177 | vt100_putchar(s);
178 | else
179 | putchar(s);
180 | }
181 |
182 | int main()
183 | {
184 | uint8_t i, *p;
185 | uint16_t n;
186 | uint8_t iac = 0;
187 | uint16_t sleep = SLEEP_SLOW;
188 | uint8_t brk = 0;
189 | uint8_t echo = 1;
190 |
191 | initCoCoSupport();
192 | #ifndef LITE
193 | struct HiResTextScreenInit hrinit =
194 | {
195 | 51,
196 | writeCharAt_51cols,
197 | (byte) (0x0E00 / 512),
198 | TRUE
199 | };
200 | if (isCoCo3)
201 | {
202 | setHighSpeed(1);
203 | printf("SELECT SCREEN MODE\r\r(1) COCO3 80X24 ANSI\r(2) PMODE 4 51X24 VT52\r(3) DEFAULT 32X16 ANSI");
204 | i=0;
205 | while(!i) i=inkey();
206 | if(i=='1') {
207 | width(80);
208 | vt100_setup(80, 24, 1, (uint8_t *)0x8000);
209 | vt100En = 1;
210 | } else if(i=='2') {
211 | initHiResTextScreen(&hrinit);
212 | }
213 | else if(i=='3') {
214 | width(32);
215 | vt100_setup(32, 16, 0, (uint8_t *)0x400);
216 | vt100En = 1;
217 | }
218 | }
219 | else
220 | {
221 | #ifdef LITE
222 | vt100En = 0;
223 | #else
224 | printf("SELECT SCREEN MODE\r\r(1) COCOVGA 64X32 ANSI\r(2) PMODE 4 51X24 VT52\r(3) DEFAULT 32X16 ANSI");
225 | i=0;
226 | while(!i) i=inkey();
227 | if(i=='1') {
228 | width64();
229 | vt100_setup(64, 32, 0, (uint8_t *)0xe00);
230 | vt100En = 1;
231 | }
232 | else if(i=='2') {
233 | initHiResTextScreen(&hrinit);
234 | }
235 | else {
236 | vt100_setup(32, 16, 0, (uint8_t *)0x400);
237 | vt100En = 1;
238 | }
239 | #endif
240 |
241 | }
242 | #endif
243 | inkey(); // toss first key
244 | dwtrm_puts(banner);
245 | dwtrm_puts("CLOSE: BREAK-C QUIT: BREAK-Q\r\n");
246 | dwtrm_puts("ECHO: BREAK-E ^C: BREAK-BREAK\r\n");
247 | dwtrm_puts("TYPE DW COMMANDS AT THE PROMPT\r\n\r\n");
248 |
249 | dw_init();
250 | open_channel(1);
251 | get_status(1, buffer);
252 | dwtrm_puts(prompt);
253 | while(1)
254 | {
255 | // Get Keyboard Input
256 | i=inkey();
257 | if( i )
258 | {
259 | if (i == 3) {
260 | if (brk == 0)
261 | brk = i;
262 | else if (brk == 3) {
263 | brk = 0;
264 | }
265 | } else if (brk == 3) {
266 | if ( i == 'Q' || i == 'q' ) {
267 | dwtrm_puts("\r\nCLOSING CHANNEL\r\n");
268 | close_channel(1);
269 | dwtrm_puts("\r\nQUIT\r\n");
270 | break;
271 | } else if ( i == 'C' || i == 'c' ) {
272 | dwtrm_puts("\r\nCLOSING CHANNEL\r\n");
273 | channel_open = 0;
274 | sleep = 1;
275 | } else if ( i == 'E' || i == 'e' ) {
276 | echo ^= 0x01;
277 | dwtrm_puts("\r\nECHO ");
278 | dwtrm_puts(echo ? "ON" : "OFF");
279 | dwtrm_puts("\r\n");
280 | brk = 0;
281 | continue;
282 | } else {
283 | brk = 0;
284 | }
285 |
286 | }
287 | if (brk == 0) {
288 | if (echo)
289 | dwtrm_putchar( i );
290 | writeChannel(1, i);
291 | sleep = SLEEP_FAST;
292 | }
293 | }
294 | // Make the keyboard more reactive by only polling it
295 | // frequently. DW will be polled for data only when sleep
296 | // countdown has reached 0
297 | if (--sleep)
298 | continue;
299 | n = readChannel(1, buffer, BUFFER_SIZE, FALSE);
300 | if (n == 0)
301 | // Go back to polling the keyboard frequently if
302 | // there was no data
303 | sleep = SLEEP_SLOW;
304 | else
305 | // If there was some data poll DW for data more
306 | // quickly
307 | sleep = SLEEP_FAST;
308 | // Process received data
309 | for(p=buffer; n>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 =~ /^\&?) {
1219 | $mc_pc++;
1220 | push @mc_link, [ $byte, $mc_pc ];
1221 | $mc_data .= "\x00";
1222 | } 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 |
--------------------------------------------------------------------------------