Bit banging SPI in 6502 assembler

During my adventures with my BBC Micro and external hardware I found I needed to read/write to an SPI bus. I ended up writing a bit banging routine to communicate in SPI Mode 0 with the device.


Interfacing the device was a no brainer as the BBC Micro has the ‘User Port’ which is a parallel port comprising 8 data lines and 2 control lines accessable via a 20 way IDC connector under the keyboard. The ‘User Port’ is actually Port B of one of the two 6522 VIA IO chips in the machine. To simplify the code the device was wired as follows:

  • Bit 7 – MISO (Input data from the slave to the computer)
  • Bit 6 – MOSI (Output data from the computer to the slave)
  • Bit 1 – SCS (Output slave chip select)
  • Bit 0 – SCLK (Output SPI clock)

The two registers of the VIA which are of interest are register 0, the input/output data register for Port B, and register 2 which is the direction register. Setting a bit in register 2 means that the corresponding bit in register 0 is an input and clearing the bit means its an output. Writing to register 0 will latch data to be output on Port B. Reading from register 0 will return the value of the lines on Port B. Usefully reading register 0 will return the values of output bits, set on a previous write, as well as the input bits. The code below uses this feature too.

spibyte:
	sta outb
	ldy #0
	sty inb
	ldx #8
spibytelp:
	tya		; (2) set A to 0
	asl outb	; (5) shift MSB in to carry
	bcc spibyte1	; (2)
	ora #mosi	; (2) set MOSI if MSB set
spibyte1:
	sta uservia	; (4) output (MOSI, SCS low, SCLK low)
	tya		; (2) set A to 0 (Do it here for delay reasons)
	inc uservia	; (6) toggle clock high (SCLK is bit 0)
	clc		; (2) clear C (Not affected by bit)
	bit uservia	; (4) copy MISO (bit 7) in to N (and MOSI in to V)
	bpl spibyte2	; (2)
	sec		; (2) set C is MISO bit is set (i.e. N)
spibyte2:
	rol inb		; (5) copy C (i.e. MISO bit) in to bit 0 of result
	dec uservia	; (6) toggle clock low (SCLK is bit 0)
	dex		; (2) next bit
	bne spibytelp	; (2) loop
	lda inb		; get result
	rts

The routine above is called with A containing the byte to be written on to the SPI. When it returns A contains the byte read from the SPI. outb and inb are page zero locations to store the byte being written and the byte being read and uservia is the address of register 0 on the VIA. It is assumed that register 2 is already pre-configured (which is likely when sending/receiving multiple bytes).

The routine starts by setting Y is set 0 (and the input byte zeroed) and X is set to the number of bits to send i.e. 8. Then the loop begins. The accumulator is set to 0 (by copying the value of Y into it). The MSB of the output byte is shifted in to Carry, and if set, the MOSI output bit (bit 6) is set in A. A is then stored in register 0 (to output the MOSI bit). The accumulator is set to 0 again. This instruction also provides 2 cycles in which the MOSI bit can stabilize before the clock is toggled. The clock is toggled by incrementing register 0 of the VIA. This will read the value, add one to it (i.e. set the bottom bit), and write it back again. As I said earlier, reading returns the latched value of the output bits so, apart from the clock bit, no other bits will change.

The Carry is cleared in preparation for the next step. Again this instruction is done here to provide a 2 cycle delay as well. The BIT instruction is then used to compare the accumulator with the value on the IO port. BIT logically ANDs the accumulator and the specified memory setting the Z flag is equal but throwing away the result. However it also copies bit 7 and bit 6 in to the N and V processor flags respectively. Since bit 7 of the IO port is MISO this instruction reads the value on the MISO line and puts it in to N. If N is not clear (i.e. the bit is 1) the Carry flag is set. The Carry flag is then shifted in to the LSB of the output byte and the clock toggled back low again by decrementing the register 0 value.

This loop is repeated 8 times for all 8 bits. By the end the 8 bits of the output byte will have been output MSB first and the 8 bits of the input byte read in MSB first. SCLK is not very symmetrical and this may cause issues. If that is the case additional NOPs either side of the inc uservia instruction may help.

The VIA also contains a shift register and I will be looking in to whether it could be used to speed up the bus.

Tags: , , , ,

Leave a Reply