wAxfer

Modding and Technical Issues

Moderator: Moderators

User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

wAxfer

Post by chysn »

I've been working on a MIDI/control voltage user port interface and hardware, and I took a slight detour and repurposed the MIDI interface as a sort of terminal program. I've been wanting to find some way to transfer programs from my MacBook to my VIC-20 without swapping SD cards around, and this might be it.

This is a plug-in for wAx that sets up an interrupt handler to listen for data from the User Port. As characters come in, it adds them to the keyboard buffer.

Right now, since I'm just writing to the keyboard buffer, I have to do transfers as a BASIC loader because wAx adds a prompt when it's in direct mode. I think I'll be able to code around this limitation at some point, though, with the eventual goal of sending PRG files more-or-less directly.



The hardware is literally an Arduino Nano, a female edge connector, and some wires*. But at some point, I'll probably want there to be a Bluetooth connection to transfer code assembled with XA directly to my VIC-20.

* And a diode, if you want to power it with the VIC-20. But there's little point of that with this particular device.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

wAxfer now has three transfer modes:

Code: Select all

'K
'P
'{address}
(1) Keyboard: That's the one I already showed. It adds incoming data to the keyboard buffer, allowing a remote terminal to program the VIC-20 or run commands.

(2) PRG: Incoming data is treated as a PRG file, with the first two bytes indicating the starting address for the rest of the file.

(3) Address: Incoming data is placed directly at the specified address, which increments with each byte. So if you want to "ignore" a two-byte header, you'd have to provide Address-2.

PRG and Address modes just wait for STOP to be pressed, as the interrupt routine handles the data. The border color toggles as each byte comes in. Once STOP is pressed, wAxfer shows the starting and ending addresses for the received data.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

It seems like the highest 100% reliable baud rate is 300, which is still useful but not ideal. I've got some investigation and experimentation to do.
User avatar
R'zo
Vic 20 Nerd
Posts: 514
Joined: Fri Jan 16, 2015 11:48 pm

Re: wAxfer

Post by R'zo »

This would be so wonderful for developing. I get so tired of popping sd cards out and in.
R'zo
I do not believe in obsolete...
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

Here's a demo of the address override mode. I specify an address with

Code: Select all

'1C00
and transfer the first 512 bytes from my C64 character ROM from the terminal application. The screen border alternates between black and white as bytes are received, and then I point the VIC chip at the new character set.

User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

I did manage to break the 300 baud barrier, and I've had successful file transfers at up to 9600 baud. At 9600, transfers sometimes fail, though.

I'm kind of trying 1800 baud as the "normal" speed. Unexpanded games only take about 20 seconds or so at this speed, which is certainly faster than cassette, and comparable to a quick SD card swap.

What was happening was the normal jiffy-speed ISR was getting in the way. There were probably bytes coming in during the IRQ servicing (when SEI), and the VIA interrupt would fail to trigger and that byte would be lost. So during file transfers, I chop the ISR down to nothing, which basically means checking for the STOP key and nothing else. This way, I can wring pretty good speed out of the thing. A VIA actually is quite snappy by itself.

The bottom line is, now that transfer of an unexpanded game can be measured in seconds and not minutes, I feel better about wAxfer actually being worth using in real life.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Re: wAxfer

Post by tlr »

I didn't read everything through here, but if you just want reliable .prg transfers over serial port you might want to have a look at tinyrs. It does 38k4. I've been using this a lot for my own development.

If you want USB it should be just a matter of adding a 5V FTDI-cable to the user port.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

tlr wrote: Wed Sep 15, 2021 12:17 am I didn't read everything through here, but if you just want reliable .prg transfers over serial port you might want to have a look at tinyrs. It does 38k4. I've been using this a lot for my own development.

If you want USB it should be just a matter of adding a 5V FTDI-cable to the user port.
Woah! I didn't push wAxfer that far because I thought there was no way in hell it would work. But damn, it works at 38400! It took about two seconds to transfer Dungeon of Dance! This changes everything.

Thanks for the tip! The VIA really is quite snappy.

Note: It also works reliably at 57600. At 115200, though, things fall apart.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Re: wAxfer

Post by tlr »

chysn wrote: Wed Sep 15, 2021 5:28 am Woah! I didn't push wAxfer that far because I thought there was no way in hell it would work. But damn, it works at 38400! It took about two seconds to transfer Dungeon of Dance! This changes everything.

Thanks for the tip! The VIA really is quite snappy.

Note: It also works reliably at 57600. At 115200, though, things fall apart.
In tinyrs I do pure bit banging and get 38k4 so if you have added hw and can actually use parallel bits with hw handshaking you should be able to go way faster. More than 115200 for sure.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

tlr wrote: Wed Sep 15, 2021 9:01 am
chysn wrote: Wed Sep 15, 2021 5:28 am Woah! I didn't push wAxfer that far because I thought there was no way in hell it would work. But damn, it works at 38400! It took about two seconds to transfer Dungeon of Dance! This changes everything.

Thanks for the tip! The VIA really is quite snappy.

Note: It also works reliably at 57600. At 115200, though, things fall apart.
In tinyrs I do pure bit banging and get 38k4 so if you have added hw and can actually use parallel bits with hw handshaking you should be able to go way faster. More than 115200 for sure.
I'm not using parallel bits, or the shift register, or hardware handshaking. I'm just grabbing port data on CB2 NMI interrupt. 57600 is reliable, and if it remains reliable across at least 8KB files, I'll be good with that. This is sort of a dress rehearsal for MIDI In code, and that just needs to support 31250bps.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Re: wAxfer

Post by tlr »

chysn wrote: Wed Sep 15, 2021 9:44 am I'm not using parallel bits, or the shift register, or hardware handshaking. I'm just grabbing port data on CB2 NMI interrupt. 57600 is reliable, and if it remains reliable across at least 8KB files, I'll be good with that. This is sort of a dress rehearsal for MIDI In code, and that just needs to support 31250bps.
Ok, maybe I misunderstood. So you are capturing a single clocked bit-lane?
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

tlr wrote: Wed Sep 15, 2021 9:49 am
chysn wrote: Wed Sep 15, 2021 9:44 am I'm not using parallel bits, or the shift register, or hardware handshaking. I'm just grabbing port data on CB2 NMI interrupt. 57600 is reliable, and if it remains reliable across at least 8KB files, I'll be good with that. This is sort of a dress rehearsal for MIDI In code, and that just needs to support 31250bps.
Ok, maybe I misunderstood. So you are capturing a single clocked bit-lane?
I'm unsure of the terminology there, so here's the actual code (see the ISR routine for relevant technical bits):

Code: Select all

; wAxfer Plug-In
; 'T         ; Terminal
; 'P         ; PRG
; 'address   ; Address
;
; Start the wAxfer IRQ process and start listening for data on
; the serial port. When data is received, it can be handled in three
; ways, depending on the option used
;
; * Terminal mode adds incoming data to the keyboard buffer
; * PRG mode uses a PRG file's two-byte header, and places the rest of the
;   data starting at that location
; * Address mode stores the data starting at the specified address
; 
; End Terminal mode with STOP/RESTORE
; End PRG and Address modes by pressing STOP
;

; wAx API
EFADDR      = $a6               ; Effective Address
X_PC        = $03               ; External Persistent Counter
Buff2Byte   = $7000             ; Get 8-bit hex number from input buffer to A
CharGet     = $7003             ; Get character from input buffer to A
CharOut     = $7006             ; Write character in A to output buffer
Hex         = $7009             ; Write value in A to output buffer 8-bit hex
IncAddr     = $700c             ; Increment Effective Address, store value in A
IncPC       = $700f             ; Increment Persistent Counter
Lookup      = $7012             ; Lookup 6502 instruction with operand in A
PrintBuff   = $7015             ; Flush output buffer to screen
ResetIn     = $7018             ; Reset input buffer index
ResetOut    = $701b             ; Reset output buffer index
ShowAddr    = $701e             ; Write Effective Address to output buffer
ShowPC      = $7021             ; Write Persistent Counter to output buffer
EAtoPC      = $7024             ; Effective Address to X_PC
PrintStr    = $7027             ; Print string at A (low), Y (high)

; VIA Registers
DDR         = $9112             ; Data Direction Register
UPORT       = $9110             ; User Port
PCR         = $911c             ; Peripheral Control Register
IFR         = $911d             ; Interrupt flag register

; Interrupt
NMI         = $0318             ; NMI Vector
HWNMI       = $feb2             ; Default NMI
RFI         = $ff56             ; Return from interrupt

; Keyboard
KEYBUFF     = $0277             ; Keyboard buffer and size, for automatically
KBSIZE      = $c6               ;   advancing the assembly address
ISCNTC      = $ffe1             ; Check Stop key

; Vector
READY       = $c002             ; BASIC warm start with READY.

; wAxfer Storage
RECEIVED    = $0245             ; Received a byte
HEADER      = $0246             ; Header byte index (0,1,2=done)
PRGMODE     = $0247             ; Memory pointer mode (bit 7 S=PRG, C=Term)

* = $7500
Main:       bcc ch_pk           ; If no address is provided, check for P or T
            lda #2              ; Set header as though the header has already
            sta HEADER          ;   been read
            jsr EAtoPC          ; Program counter will be start of memory
            sec                 ; Set PRG mode
            ror PRGMODE         ; ,,
            jmp setup           ; Continue to hardware setup
ch_pk:      lda #0              ; Header byte count clear
            sta HEADER          ; ,,
            sec                 ; Default to PRG mode
            ror PRGMODE         ; ,,
            jsr ResetIn         ; Get first character
            jsr CharGet         ; ,,
            cmp #"P"            ; PRG mode
            beq setup           ; ,,
            cmp #"T"            ; Check for Terminal mode
            bne Man             ; No valid option picked, so show usage info
            lsr PRGMODE         ; Set Terminal mode
setup:      lda #0              ; Set DDR to listen to all 8 data lines,
            sta DDR             ; ,,
            sta PCR             ; And set peripheral control to interrupt input
            sta RECEIVED        ; And reset received flag
            lda #<ISR           ; Install the new service routine, which will
            sta NMI             ;   listen for data on the User Port
            lda #>ISR           ;   ,,
            sta NMI+1           ;   ,,
            lda #%10001010      ; Enable CB2 interrupt
            sta $911e           ; ,,
            bit PRGMODE         ; If this plug-in was invoked with an address or
            bmi standby         ;   PRG mode, just wait for STOP to be pressed
            jmp (READY)         ; For Terminal mode, just exit
standby:    lda #171            ; Add a prompt
            jsr $ffd2           ; ,,
wait:       jsr ISCNTC          ; Check for BREAK key
            beq Info            ; If it's pressed, end data receive, show info
            bit RECEIVED        ; If data receive flag is off, just wait
            bpl wait            ; ,,
            lsr RECEIVED        ; Turn the flag off
            lda EFADDR          ; Are we at a multiple of 256 bytes?
            bne wait            ;   If not, wait
            lda #192            ; If so, show progress bar
            jsr $ffd2           ; ,,
            jmp wait            ; Back to start of wait loop
            
; Print Usage Instructions
; When input is bad
Man:        lda #<Usage
            ldy #>Usage
            jmp PrintStr

; Show Received Data Range            
Info:       jsr ResetOut        ; Show final locations...
            lda #$0d            ;    Drop to next line
            jsr CharOut         ;    ,,
            lda #"$"            ;    Hex
            jsr CharOut         ;    ,,
            jsr ShowPC          ;    Starting address
            lda #"-"            ;      to
            jsr CharOut         ;      ,,
            jsr ShowAddr        ;    Ending address
            jmp PrintBuff       ; ,,
  
; Interrupt Service Routine
; Handles NMI Interrupt triggered by CB2 high-to-low transition                                  
ISR:        pha
            txa
            pha
            tya
            pha
            lda #%00001000      ; Check the interrupt flag bit 3
            bit IFR             ; If it's set, User Port data is available
            bne recv            ; Otherwise, it's a normal hardware interrupt
hw:         jmp HWNMI           ; This is a normal old NMI
recv:       lda UPORT           ; Get the User Port byte
            bit PRGMODE         ; If the user did not specify a load address,
            bpl termmode        ;   handle terminal mode
            ldx HEADER          ; Get current header count
            cpx #2              ; If both header bytes have been received,
            beq prg             ;   handle PRG data
            sta EFADDR,x        ; Otherwise, store header bytes in effective
            sta X_PC,x          ;   address and persistent counter
            inc HEADER          ; Advance header
            jmp RFI             ; Return from interrupt
prg:        ldx #0              ; Store PRG data byte in current location
            sta (EFADDR,x)      ; ,,
            sec                 ; Set byte received flag for caller
            ror RECEIVED        ; ,,
next_addr:  jsr IncAddr         ; Increment effective address
            jmp RFI             ; Return from interrupt
termmode:   cmp #127            ; Convert terminal DEL to PETSCII delete
            bne addchar         ; ,,
            lda #20             ; ,,
addchar:    ldy KBSIZE          ; Add the character received to keyboard buffer
            cpy #10             ; ,, (avoid buffer overwrite)
            bcs ser_r           ; ,,
            sta KEYBUFF,y       ; ,,
            inc KBSIZE          ; Increment the buffer size
ser_r:      jmp RFI             ; Return from the User Port interrupt
            
Usage:      .asc "'TERM",$0d,"'PRG",$0d,"'[ADDR]",$0d,0
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Re: wAxfer

Post by tlr »

chysn wrote: Wed Sep 15, 2021 10:06 am
tlr wrote: Wed Sep 15, 2021 9:49 am
chysn wrote: Wed Sep 15, 2021 9:44 am I'm not using parallel bits, or the shift register, or hardware handshaking. I'm just grabbing port data on CB2 NMI interrupt. 57600 is reliable, and if it remains reliable across at least 8KB files, I'll be good with that. This is sort of a dress rehearsal for MIDI In code, and that just needs to support 31250bps.
Ok, maybe I misunderstood. So you are capturing a single clocked bit-lane?
I'm unsure of the terminology there, so here's the actual code (see the ISR routine for relevant technical bits):
...
Hmm... to me this looks like you get all 8 bits at a time each interrupt and store them somewhere, no?
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: wAxfer

Post by chysn »

tlr wrote: Wed Sep 15, 2021 10:11 am Hmm... to me this looks like you get all 8 bits at a time each interrupt and store them somewhere, no?
Yep, exactly. The location is based on the first two bytes in PRG mode, or the address can be overridden by providing a hex address. Reading the port clears the interrupt so I'm ready for the next one.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Re: wAxfer

Post by tlr »

chysn wrote: Wed Sep 15, 2021 10:25 am
tlr wrote: Wed Sep 15, 2021 10:11 am Hmm... to me this looks like you get all 8 bits at a time each interrupt and store them somewhere, no?
Yep, exactly. The location is based on the first two bytes in PRG mode, or the address can be overridden by providing a hex address. Reading the port clears the interrupt so I'm ready for the next one.
So for simplicity, let's assume the machine is ~1 MHz. A byte on a serial line takes 10 bits, so 115200 bps on the serial line corresponds to one byte roughly every 85 cycles. This is plenty, even using NMI as you do here.

If you don't need other stuff running during transfer you can switch to polled mode. I'd say that ~30 cycles per byte would be enough, but lets say 40. With 40 cycles per byte you'd be able to sustain the equivalent of 250k. I you'd have hardware handshake enabled you'd be able to push the speed even further, because the external hw could send the next byte as soon as the vic read the previous one out.

For comparison: In tinyrs I just poll the serial line, one bit at a time so there I have roughly 26-27 cycles per bit. The tricky part isn't during the bit collection, rather between bytes when it has to be stored somewhere before starting on the next byte.
Post Reply