User Port Dreams: SPI
Posted: Wed Oct 12, 2022 5:13 pm
There isn't going to be a datasette port.
On 10/11/2022 at 9:46 PM, Kalvan said:
Hypothetical question for the development team:
If someone else were to do another Wavicle-style home brew of a Commander X16 motherboard, and the only difference (as far as end-user hardware and software interface is concerned) is that it returns that second VIA to its place on the the motherboard and its place in the port architecture and memory map, and further assuming the screwdriver that the user port sonnected up somehow matches the planned Official User Port expansion card's pinout, voltage, signal, and bandwidth profiles exactly, would either that second VIA in its original position, or the User Port in its position among the circuitry break software compatiblity with the port architecture and/or memory map the Commander X16 system in its latest hardware and systems software specification, and/or would the presence of that second VIA and/or the User Port wired to it steal CPU cycles or otherwise interfere with Audio, Video, or I/O timing in any way, shape or form?
I ask this question because, as there are only four expansion slots expansion, and there is no other analogue to, say, cartridge slots on the motherboard, each individual expansion slot represents precious, extremely limited system real estate. I had several concepts for Commander X16 expansion cards in my head, and relegating the User Port to an expansion card cuts into the basic expandability of the machine by at least 2/5 (1 fifth for the expansion slot itself, the second for the missing User Port/Header on the motherboard itself). I know, one could theoretically reclaim some of that expandability by hacking the Commodore Datasette and/or Floppy Drive ports, but those appear only useful for software medium interface.
___ _ _ _ _ _ _ _ ___ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/
_ _ _ _ _ _ _ _ ___/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \___
; Quad 2-input NAND 74xx00, NAND1 ; ; From Pin 1 to Pin 7 NAND1_1A := /MODE0_SET ; From bit banged GPIO NAND1_1B := NAND1_2Y NAND1_1Y =: NAND1_2A ; QH ; NAND1_2A := NAND1_1Y NAND1_2B := MODE3_CLK NAND1_2Y =: NAND1_1B =: NAND1_3A ; /QH NAND1_GND := GND ; ; From Pin 14 to Pin 8 NAND1_VCC := VCC NAND1_4B := NAND1_2Y = NAND1_1B ; /QH NAND1_4A := MODE3_CLK NAND1_4Y =: NAND1_3B =: /SPI_CLK ; NAND1_3B := NAND1_4Y NAND1_3A := NAND1_4Y NAND1_3Y =: SPI_CLK
The October, 2022 post is more up to date, it specifies
So long as a VIA expansion card has the same arrangement of block pin header as the optional motherboard user-port, the only difference would be the port address for the VIA, where it would be straightforward to simply provide a version of the SPI support routines for each possible expansion slot address in addition to the address in the top 16 bytes of the VIA I/O "slot".The user port remains on the PCB as an optional add-on.
A third main SPI target is mass storage -- whether a second SD card slot to leave one in the main slot and have one for sneakernetting, or a thumb drive MCU that has an SPI interface that offers more speed than the already available 100-150kbits/sec of the I2C channel. That is 12KB-18KB/sec bandwidth, obviously less with channel management and processing overheads ... while the X16 is a lot faster than my old C64, it's not the speed demon we are sometimes thinking of when thinking of negligible processing overheads for I2C.rje wrote: ↑Tue Feb 28, 2023 10:31 pm About speed considerations.
(1) Banked RAM
When I think about the banked RAM, I become keenly interested in faster load speeds.
So 18 kb versus 30 kb read times, for example, are significant. 17 seconds versus 28 seconds for loading 512k of data into banks, though, might not reeeeallllly matter.
(2) Network Proxy
Then there's network proxying. If I'm querying an internet data source across a nanny program running on an RPi bridge, granted I will seriously pre-pack the data I want to return. But it still really ought to be on the order of 1 second to transmit. That limits my transaction response to, say, 20 kilobytes of data. Which really can be quite a lot of data, when you think about it.
But, _faster_ is always enticing, so I don't know. I do think that once we're close to 20 kb per second, I think we're probably at reasonable speeds.
TX_SPI_MODE3: STA VIA2PORTA ASL VIA2PORTA ASL VIA2PORTA ASL VIA2PORTA ASL VIA2PORTA ASL VIA2PORTA ASL VIA2PORTA ASL VIA2PORTA LDA VIA2SERDATA RTS
That's what I'm thinking. I'd be most interested in the thumb drive interface and a faster UART interface than the I2C channel can support, but while SPI is not as widely available for various I/O bridge IC's as it was a decade ago, there is still a good number out there.
; SPI ; VIA#1 CA1 := J4 Pin 1 ; VIA#1 CA2 =: J4 Pin 2 ; VIA#1 PB0 :=: J4 Pin 3 ; VIA#1 PB1 :=: J4 Pin 4 ; VIA#1 PB2 :=: J4 Pin 5 ; VIA#1 CB2 =: J4 Pin 6 ; Note that two selects can generate 3 selects through a 2 to 4 ; decoder with the %11 pin not connected, or 8 or more selects ; through using one select to select the parallel latch for a ; serial in, parallel out shift register and the other select to ; select the output enable of the outputs, with the select lines ; attached to a network pull up resister. ; CA1 can be a general alert line, pulled high through a pull up ; resister, pulled down by a peripheral. ; This assumes two selects. With two selects, I will specify ; CA1 as Select1, and a daughterboard will have a 6 pin block ; header with the Pin1 and Pin3-5 fed straight through, Pin6 ; input fed through to Pin2 output. ; CA1 is SPI_ALERT ; CA2 is SPI_SELECT1 ; PB0 is SPI_CLK ; PB1 is MOSI -- Master Out, Servant In ; PB2 is MISO -- Master In, Servant Out ; CB2 is SPI_SELECT2 SPI_CLK = %00000001 SPI_MOSI = %00000010 SPI_MISO = %00000100 MODE0_SELECT: ; A contains selection, 1 to max, ; A=0 => deselect all ; Returns selection in A, or 0 if no device selected ; Uses X, does not use Y ; Returns Carry Set if select fails LDA #(SPI_CLK|SPI_MOSI) TSB VIA1DDRB ; set SPI_CLK, MOSI to output TSB VIA1PORTB ; "reset" SPI_CLK, MOSI to 0 LDA #SPI_MISO TRB VIA1DDRB ; "reset" MISO to input BRA SPI_SELECT MODE3_SELECT: ; A contains selection, 1 to max, ; A=0 => deselect all ; Returns selection in A, or 0 if no device selected ; Uses X, does not use Y ; Returns Carry Set if select fails LDA #(SPI_CLK|SPI_MOSI) TSB VIA1DDRB ; set SPI_CLK, MOSI to output TSB VIA1PORTB ; "set" SPI_CLK, MOSI to 1 LDA #SPI_MISO TRB VIA1DDRB ; "reset" MISO to input ; fall through to SPI_SELECT SPI_SELECT: ; Re-select same device ; port will be set up & clock idle will match mode ; A contains selection, 1 to max, 0 to deselect all ; Returns selection in A, or 0 if no device selected ; Uses X, does not use Y ; returns Carry Clear on success, Carry Set on fail ; Note that a call to select the same device will ; deselect the device then select it again ; PCR: $9F0C ; * bit0: CA1 control, rising/falling edge detect ; * bit1-3: CA2 control, %110 low output, %111 high output ; * bit4: CB1 control, rising/falling edge detect ; * bit1-3: CB2 control, %110 low output, %111 high output SEC TAX LDA #$EE ; Don't touch CA1/CB1 control settings TSB VIA1PCR ; Deselect lines CPX #0 BEQ SELECT_DONE CPX #1 BNE + LDA #$02 BRA SELECT_SPI CPX #2 BNE SELECT_ERR LDA #$20 SELECT_SPI: TRB VIA1PCR SELECT_DONE: CLC SELECT_ERR: TXA RTS SPI_WORK = $20 ; Kernal ABI scratch location ; move this if interrupt routines use "New ABI" routines MODE0_BYTE ; A has output byte, returns with input byte ; Uses X, not Y ; Uses Mode3_byte routine for most operations, but ; cannot decrement PORTB in first bit if MOSI_b7=1 ; and in either event must return with clock low ASL STA SPI_WORK LDX #8 BCS + ; Do Mode3 loop starting with 0 bit, then drop clock JSR SPI_BIT0 DEC VIA1PORTB RTS ; Do Mode3 loop starting with 1 bit but no leading clock drop ; then drop clock + JSR MODE0_BIT1_ENTRY DEC VIA1PORTB RTS MODE3_BYTE: ; A has output byte, returns with input byte ; Uses X, not Y ASL ; First bit in Carry MODE_BIT0_ENTRY: STA SPI_WORK LDX #8 BCS SPI_BIT1 SPI_BIT0: LDA #(SPI_CLK | SPI_MOSI) TRB VIA1PORTB ; Drop clock & Output Bit = 0 INC VIA1PORTB ; Raise clock LDA #SPI_MISO AND VIAPORTB BEQ + ; Carry is currently clear SEC + ROL SPI_WORK ; save input bit, get next output bit DEX BEQ ++ ; done BCC SPI_BIT0 SPI_BIT1: DEC VIA1PORTB ; Drop clock MODE0_BIT1_ENTRY: LDA #SPI_MOSI TSB VIAPORTB ; Output Bit = 1 INC VIA1PORTB ; Raise clock LDA #SPI_MISO AND VIAPORTB BNE + ; Carry is currently set CLC + ROL SPI_WORK ; save input bit, get next output bit DEX BEQ ++ ; done BCS SPI_BIT1 BCC SPI_BIT0 ++ CLC LDA SPI_WORK RTS
; SPI ; VIA#1 CA1 := J4 Pin 1 ; VIA#1 CA2 =: J4 Pin 2 ; VIA#1 PB0 :=: J4 Pin 3 ; VIA#1 PB1 :=: J4 Pin 4 ; VIA#1 PB2 :=: J4 Pin 5 ; VIA#1 CB2 =: J4 Pin 6 ; Note that two selects can generate 3 selects through a 2 to 4 ; decoder with the %11 pin not connected, or 8 or more selects ; through using one select to select the parallel latch for a ; serial in, parallel out shift register and the other select to ; select the output enable of the outputs, with the select lines ; attached to a network pull up resister. ; CA1 can be a general alert line, pulled high through a pull up ; resister, pulled down by a peripheral. ; This assumes two selects. With two selects, I will specify ; CA1 as Select1, and a daughterboard will have a 6 pin block ; header with the Pin1 and Pin3-5 fed straight through, Pin6 ; input fed through to Pin2 output. ; There is contention for the special PB0 pin, which could be ; used to toggle the serial clock with DEC/INC instructions or ; to extract the input bit with "LDA PORTB : LSR". Placing the ; input bit at PB1 costs only two clock more, so this is the ; approach used here. The MOSI bit will be set using "TSB" or ; "TRB", so it does not cost more to place in PB2 than in PB0 ; or PB1. ; CA1 is SPI_ALERT ; CA2 is SPI_SELECT1 ; PB0 is SPI_CLK ; PB1 is MISO -- Master In, Servant Out ; PB2 is MOSI -- Master Out, Servant In ; CB2 is SPI_SELECT2 SPI_CLK = %00000001 SPI_MISO = %00000010 SPI_MOSI = %00000100 MODE0_SELECT: ; A contains selection, 1 to max, ; A=0 => deselect all ; Returns selection in A, or 0 if no device selected ; Uses X, does not use Y ; Returns Carry Set if select fails LDA #(SPI_CLK|SPI_MOSI) TSB VIA1DDRB ; set SPI_CLK, MOSI to output TRB VIA1PORTB ; "reset" SPI_CLK ; MOSI is a "don't care" at this point LDA #SPI_MISO TRB VIA1DDRB ; "reset" MISO to input BRA SPI_SELECT MODE3_SELECT: ; A contains selection, 1 to max, ; A=0 => deselect all ; Returns selection in A, or 0 if no device selected ; Uses X, does not use Y ; Returns Carry Set if select fails LDA #(SPI_CLK|SPI_MOSI) TSB VIA1DDRB ; set SPI_CLK, MOSI to output TSB VIA1PORTB ; "set" SPI_CLK, MOSI to 1 LDA #SPI_MISO TRB VIA1DDRB ; "reset" MISO to input ; fall through to SPI_SELECT SPI_SELECT: ; Re-select same device ; port will be set up & clock idle will match mode ; A contains selection, 1 to max, 0 to deselect all ; Returns selection in A, or 0 if no device selected ; Uses X, does not use Y ; returns Carry Clear on success, Carry Set on fail ; Note that a call to select the same device will ; deselect the device then select it again ; PCR: $9F0C ; * bit0: CA1 control, rising/falling edge detect ; * bit1-3: CA2 control, %110 low output, %111 high output ; * bit4: CB1 control, rising/falling edge detect ; * bit1-3: CB2 control, %110 low output, %111 high output SEC TAX LDA #$EE ; Don't touch CA1/CB1 control settings TSB VIA1PCR ; Deselect lines CPX #0 BEQ SELECT_DONE CPX #1 BNE + LDA #$02 BRA SELECT_SPI CPX #2 BNE SELECT_ERR LDA #$20 SELECT_SPI: TRB VIA1PCR SELECT_DONE: CLC SELECT_ERR: TXA RTS SPI_WORK = $20 ; Kernal ABI scratch location ; move this if interrupt routines use "New ABI" routines MODE0_BYTE ; A has output byte, returns with input byte ; Uses X, not Y ; Uses Mode3_byte routine for most operations, but ; cannot decrement PORTB in first bit if MOSI_b7=1 ; and in either event must return with clock low ASL STA SPI_WORK LDX #8 BCS + ; Do Mode3 loop starting with 0 bit, then drop clock JSR SPI_BIT0 DEC VIA1PORTB RTS ; Do Mode3 loop starting with 1 bit but no leading clock drop ; then drop clock + JSR MODE0_BIT1_ENTRY DEC VIA1PORTB RTS MODE3_BYTE: ; A has output byte, returns with input byte ; Uses X, not Y ASL ; First bit in Carry MODE_BIT0_ENTRY: STA SPI_WORK LDX #8 BCS SPI_BIT1 SPI_BIT0: LDA #(SPI_CLK | SPI_MOSI) TRB VIA1PORTB ; Drop clock & Output Bit = 0 INC VIA1PORTB ; Raise clock LDA VIA1PORTB LSR LSR ; Input bit DEX BEQ + ; done ROL SPI_WORK ; save input bit, get next output bit BCC SPI_BIT0 SPI_BIT1: DEC VIA1PORTB ; Drop clock MODE0_BIT1_ENTRY: LDA #SPI_MOSI TSB VIA1PORTB ; Output Bit = 1 INC VIA1PORTB ; Raise clock LDA VIA1PORTB LSR LSR DEX BEQ + ; done ROL SPI_WORK ; save input bit, get next output bit BCC SPI_BIT0 ; Repeating 1 bit is faster SPI_RPT1: DEC VIA1PORTB INC VIA1PORTB LDA VIA1PORTB LSR LSR DEX BEQ + ROL SPI_WORK BCC SPI_BIT0 BCS SPI_RPT1 + LDA SPI_WORK ROL ; save input bit, carry cleared RTS
MODE3_BYTES_1LOOP MODE0_READ: JSR MODE3_READ DEC VIA1PORTB RTS MODE3_READ: ; 0 is output as the output byte ; Returns with input byte in A ; Uses X, not Y STZ SPI_WORK LDX #7 LDA #(MOSI | SPI_CLK) ; Send #0 TRB VIA1PORTB ; also ensure clock low, if Mode3 INC VIA1PORTB LDA VIA1PORTB LSR LSR ROL SPI_WORK 8BIT_LP DEC VIA1PORTB INC VIA1PORTB LDA VIA1PORTB LSR LSR ROL SPI_WORK DEX BNE 8BIT_LP ; ; Loop Finished LDA SPI_WORK RTS ; ~~~~~~~~~~~~~~ MODE3_WRITE: ; A has output byte ; Returns with low bit equal to final input bit ; Uses X, not Y ASL ; First bit in Carry STA SPI_WORK LDX #8 BCS SW_BIT1 SW_BIT0: LDA #(SPI_CLK | SPI_MOSI) TRB VIA1PORTB ; Drop clock & Output Bit = 0 INC VIA1PORTB ; Raise clock DEX BEQ + ; done ASL SPI_WORK ; get next output bit BCC SW_BIT0 SW_BIT1: DEC VIA1PORTB ; Drop clock LDA #SPI_MOSI TSB VIA1PORTB ; Output Bit = 1 INC VIA1PORTB ; Raise clock DEX BEQ + ; done ASL SW_WORK ; get next output bit BCC SW_BIT0 BCS SW_BIT1 + LDA VIA1PORTB LSR LSR LDA #0 ROL ; return final input bit RTS