Block pin heqder J4 SPI games

Chat about anything CX16 related that doesn't fit elsewhere
Post Reply
BruceRMcF
Posts: 224
Joined: Sat Jan 07, 2023 10:33 pm

Block pin heqder J4 SPI games

Post by BruceRMcF »

As verified elsewhere in the CX16 General Chat, the boards have the J4 pin header promised in the documentation. It is a straight line pin header:

[1] CA1
[2] CA2
[3] PB0
[4] PB1
[5] PB3
[6] CB2

This is enough to support the "4-wire" SPI interface: /Select, SCLK, MISO, MOSI, plus an extra output to send an alert to the SPI bus, and an extra input to receive an alert. If a chip supports both I2C and SPI, you might just use I2C for convenience, since the Kernel routine for I2C communication is already in place. However, if it is an SPI device, the I2C management overheads of accessing an SPI device through an I2C to SPI bridge chip means that using a direct bit banged SPI interface would be substantially more efficient. If you were aiming at a "slotless" TTL UART solution, the MAX3100 offers a cheap solution with 8byte hardware FIFO buffers, and if accesses over a bit-banged SPI interface on the CX16 certainly ought to support 19,200baud for byte-oriented serial UART communications, and perhaps 38,400baud.

J4 does not include power and ground, though, so if you need power and ground, you need them from another source. The most immediately available power tap would be the SNES 3 & 4 pin header, which gives VCC on Pin2 and GND on Pin6. A daughterboard that plugged into both of those (which are on opposite sides of the VIA#1 chip) could tap the power and pass the power/ground plus the other SNES 3 & 4 pins onto a pin header on the board.

The system pins on Port B that are used by the system are used for the IEC port, so if you do not load interrupt driven code that uses the IEC port, and you do not use the IEC port yourself in the middle of an SPI transceive operation, you can assume that the state of any outputs in PB3-PB7 when you call an SPI transfer routines are going to remain the same as the SPI transfer routine executes. This would not be true of PortA, since I2C access to the Keyboard/Mouse MCU and the operations on the SNES ports performed with the PortA GPIO are performed during interrupt servicing, so the state of PortA could indeed change during the processing of an SPI transfer routine.

After running through and clock counting a variety of possible bit banged SPI routines, it turns out that:
  • It is easy to support both Mode0 and Mode3 SPI clocks, for support of a wider range of devices
  • The method of supporting Mode0 means the SPI clock is agnostic about which pin it is on
  • Therefore the SPI Input (or 'MISO" for Master-In, Servant-Out) should be in PB0, for fastest access to the input bit.
  • SPI Output (or "MOSI" for Master-Out, Servant-In), and SPI SCLK should be on GPIO, but with this routine is it arbitrary which is which, so I put the MISO and MOSI pins together, which is the convention on most micro-controllers.
  • Either PA2 or PB2 may be used for /Select. I specify PB2 simply to place the optional auxiliary signal lines /AlertOut and /AlertIn next to each other.

Code: Select all

; PORTB:
; PB0 =: MISO (J4_Pin3)
; PB1 := MOSI (J4_Pin4)
; PB2 =: SCLK (J4_Pin5)
; CB2 =: /SELECT (J4_Pin6)

VIA1 = $9F00
VIA1_PB = VIA1
VIA1_DDRB = VIA1+2
VIA1_PCR = VIA1+$0C

; In the PCR, %110xxxxx is PB2 Low Output, %111xxxxx is Hi Output
; So the initialization sets all three high, /Select toggles bit5
PCR_SPI_SEL = %00100000

; SPI_CLK is in PB2
SPI_CLK = %00000100

; MOSI is in PB1, "TXA|STA" agnostic about position
MOSI0_MASK = %11111001	; AND mask, done first
MOSI1_MASK = %00000010	; ORA mask, done second

; Use API register 16 as ZP Temp
SPI_VALUE = $20
LP_COUNT = $21

; Mode 0 or 3 setting is done with the CPOL byte,
; which is a TRB mask, #SPI_CLK to reset the clock bit or #0 to do nothing.
CPOL:	!byte 0


SPI_Init sets up the desired SPI mode by passing the clock polarity in A. Technically the clock polarity is either 0 or 1, but the routine takes any nonzero value as a request for a clock polarity of 1. The transfer routine implements data latching on the rising edge, so phase is the same as polarity. That is, if polarity is low, rising edge is leading, so latching occurs on the leading edge, which is called phase 0. If polarity is high, the rising edge is trailing, so latching occurs on the trailing edge, which is called phase 1. Therefore CPOL of 0 sets Mode0, and CPOL of 1 sets Mode 3. If you need Mode1 or Mode2, you can run the SCLK line through an inverter before running it into the device, but Mode0 and Mode3 handles the large majority of SPI ICs I have seen.

The Select API has three routines: SPI_Select turns on the select status, SPI_DeSelect turns off the Select status, and SPI_Reselect turns the status off then on (for example, for the MAX3100 UART, the selection is made at the start of each pair of bytes which makes up a MAC3100 operation). The Select and Reselect routine should be passed a Device number of #1, for upward compatibility with the 7-device SPI bus that can be implemented by adding a 74x299 universal serial shift register.

Code: Select all

SPI_INIT:
; Pass polarity in A, 0 for Mode0, 1 for Mode3
; Any non-zero value gives a polarity of 1
	STZ CPOL
	CMP #0
	BNE +
	LDA #SPI_CLK
 	STA CPOL
+	LDA VIA1_DDRB
	AND #%11111000	; Preserve DDPB3-7
	ORA #%00000110	; PB1,2 outputs, PB0 input
	STA VIA1_DDRB
	LDA #%11100000	; CB1 mask for TSB
	TSB VIA1_PCR	; b5-b7 = %111 => /SELECT = H
	LDA #SPI_CLK
	TSB VIA1_PB	; Set clock to Mode3
	LDA CPOL
	TRB VIA1_PB	; Reset clock if Mode0
	RTS

SPI_DESELECT:
	LDA #PCR_SPI_SEL
	TSB VIA1_PCR	; low bit of PCR.CB2 = 1
	RTS
	;
SPI_RESELECT: ; Pass 1 in A
	LDA #PCR_SPI_SEL
	TSB VIA1_PCR	; low bit of PCR.CB2 = 1
	; fall through to select
SPI_SELECT: ; Pass 1 in A
	LDA #PCR_SPI_SEL
	TRB VIA1_PCR	; low bit of PCR.CB2 = 0
	RTS

My shorthand for the SPI transfer routine is "TX", because SPI works as a transceiver -- sending an output byte at the same that it is receives an input byte.

The SPI bit bang routine first works out the byte for outputting a 1 or a 0 bit, putting them in X and Y. They are both created with the Clock bit set to 0. The output byte is shifted left to test the value and select between the 1 output or 0 output legs of the loop. The correct contents for PortB are transferred from register X or Y to register A, stored in PortB, then clock bit raised high, and stored again to give the rising edge of the clock bit. Then Port B is loaded into A, and the input bit is shifted into the carry flag.

By rotating the input bit into the zero page location containing the output value, the storage of the input bit also retrieves the next output bit. The loop is counted down (in zero page, since all count/index registers are in use), and if not finished, the correct leg of the loop is called for the next bit.

Code: Select all

SPI_TX:
	LDY #8
	STY LP_COUNT
	ASL		; Get first MOSI bit
	STA SPI_VALUE	; Store rest of MOSI for later
	LDA VIA1_PB
	AND #MOSI0_MASK
	TAX
	ORA #MOSI1_MASK
	TAY
	BCS LOOP1

LOOP0:
	TXA
	STA VIA1_PB	; Store MOSI bit & drop clock
	ORA #SCLK1_MASK
	STA VIA1_PB	; Raise clock
	LDA VIA1_PB
-	ROR
	ROL SPI_VALUE	; Store input & retrieve output
	DEC LP_COUNT
	BEQ +
	BCC LOOP0

LOOP1:	TYA
	STA VIA1_FB	; Store MOSI bit & drop clock
	ORA #SCLK1_MASK
	STA VIA1_PB	; Raise clock
	LDA VIA1_PB
	ROR
	ROL SPI_VALUE	; Store input & retrieve output
	DEC LP_COUNT
	BEQ +
	BCC LOOP1
	BCS LOOP0
	
+	LDA CPOL
	TRB VIA1_PB
	LDA SPI_VALUE
	RTS
BruceRMcF
Posts: 224
Joined: Sat Jan 07, 2023 10:33 pm

Re: Block pin heqder J4 SPI games

Post by BruceRMcF »

The above would work, for example, for most of Garth Wilson & 6502.org's SPI module specification, SPI-10: http://forum.6502.org/viewtopic.php?p=48167#p48167. It is short one GPIO for the "AUX" line, but as Garth notes, that is only needed for a few SPI ICs. CA1 and CA2 are suitable for /IRQ and /RST, respectively (noting that /IRQ is used in its general master/servant handshake sense, and is not presumed to necessarily be connected to the 6502 /IRQ line), provided that CA1 has a pull-up resister.

However, for the 6502.org Serial Interface Bus, 65SIB, http://forum.6502.org/viewtopic.php?t=1064&start=105, it is necessary to support 7 /Select pins. The 65SIB has the three SPI bus lines, SPI_SCLK, SPI_MOSI and SPI_MISO, 7 select lines, an /IRQ "servant signals" input line, and a /CONF "configure" output line.

Or, multiple SPI10 headers can be put on a single board, with the appropriate /SPI_SELn line connected to each one ... and that can extend to using with a voltage divider circuit on the +5V VCC to provide a +3.3V VCC, and a +5V tolerant, +3.3V hex line driver for /MOSI, /RST, and four /SPI_SELn lines, and a +5v pullup resister on the /MISO line, you could have up to 4 +5V SPI10 headers and up to 4 +3.3V SPI10 headers.

But, either of these requires that the SPI approach sketched above can be extended to 7 or 8 distinct select lines. So, why is the above J4 pinout a viable approach for an SPI bus like the 65SIB, or for multiple SPI10 modules?

Earlier, I was not sure that it would be. When I first saw the J4 pinout, I thought I would fall one output line short.

See, I am not really a hardware hand, but more a software hand that is sometimes "hardware curious", so sometimes when I see a way to do things with one part, there is a better way to do it with another part.

I had been thinking in terms of using a Serial to Parallel shift register, such as a 74x595, to hold additional select lines and the most direct way I saw to use a 74x595 for that purpose involved effectively one line to "select" the shift register for writing and another line to enable the tri-state output.

But a closer look at the Universal Shift Register datasheet -- that is, the 74x299 family -- makes me think that one line is enough for selecting between writing to the serial shift register and enabling output of the shift register contents, if a 74x299 is used as the select line register:

Code: Select all

;  VCC   S1  DS7   Q7  IO7  IO5  IO3  IO1   CP  DS0
;   20   19   18   17   16   15   14   13   12   11
;  [----------------------------------------------]
;  [----------------------------------------------]
;    1    2    3    4    5    6    7    8    9   10
;   S0 /OE1 /OE2  IO6  IO4  IO2  IO0   Q0  /MR  GND

S0 and S1 select mode and /MR is the master reset:
  • (/MR:S1:S0) = %0xx, register states Q0-Q7 = 0
  • (/MR:S1:S0) = %100, Hold, Q0-Q7 are latched
  • (/MR:S1:S0) = %101, shift "left", DS7->Q7, Q7->Q6, etc.
  • (/MR:S1:S0) = %110, shift "right", DS0->Q0, Q0->Q2, etc.
  • (/MR:S1:S0) = %111, load, IO0-IO7 -> Q0-Q7
All shifts occur when the CP line transitions from low to high, and loading the serial input happens as part of the shift ... so that when this part is used as a Serial-In, Parallel-Out shift register taking data from the MOSI line, it is directly compatible with either a Mode0 or Mode3 SPI serial clock.

/OE1 and /OE2 are a logical "NAND", both must be low to enable output. On a 6502 bus, this means that one OE can be decoded from an address, and another can be the R/W line ... but if only one /OE is required, as in this application, the other can simply be tied to Ground.

Also note that the datasheet names for the two shift modes appear to be in 'network" bit order, N:01234567, rather than the computer arithmetic %76543210 that we are used to ... the x299 "shift right" is the equivalent of the 6502 ROL that we are looking for to shift the high selection bit in first and the low selection bit in last.

So wire /MR to VCC, /OE2 and S0 to GND, PB1 (MOSI) to DS0, PB2 (SCLK) to CP, and PB2 (/Select) to both S1 and /OE1, take the /SPI_SEL0-7 lines to IO0-IO7, and put pull-up resisters on IO0-IO7 (which can be done with an eight-resister, "network resister" pack).
  • When PB2 is high, "shift right" is selected, and output is deselected, so all selects go high,
  • If the same device is going to be selected again, leave SPI_MOSI and SPI_SCLK alone
  • If a new device is going to be used, load a bit mask into A with all bits high except for the select for the desired device, and call SPI_TX
  • When it is time to select the device, pull CB2 low, the desired IOn line will be pulled low
So with a 74x299 universal shift register and a network resister, the single device SPI10 module interface sketched above can be extended to a bus that can support 7 device 65SIB or multiple SPI10 modules.
Post Reply