SPI Part 2

In this post we will take a look at the basic SPI code and then subsequently at the more complex SD card and FAT file format implementation. Before we get going some definitions. The way my address decoding logic is set up the VIA registers have the following addresses and names that I will use in the code. Should be easy to modify for your system (see VIA documentation for more details)

VIA1_PORTB      = $F100      ; address of port a
VIA1_PORTA      = $F101      ; address of port b
VIA1_DDRB       = $F102      ; data direction register for port a
VIA1_DDRA       = $F103      ; data direction register for port b
VIA1_T1CL       = $F104      ; timer 1 counter low 
VIA1_T1CH       = $F105      ; timer 1 counter high
VIA1_T1LL       = $F106      ; timer 1 latch low
VIA1_T1LH       = $F107      ; timer 1 latch high
VIA1_T2CL       = $F108      ; timer 2 counter low 
VIA1_T2CH       = $F109      ; timer 2 counter high
VIA1_SR         = $F10A      ; shift register 
VIA1_ACR        = $F10B      ; auxiliary control register 
VIA1_PCR        = $F10C      ; peripheral control register 
VIA1_IFR        = $F10D      ; interrupt flag register 
VIA1_IER        = $F10E      ; interrupt enable register
VIA1_ORA        = $F10F      ; same as port A, except "no handshake"

My initialization code disables the shift register interrupt, sets port B to output and sets the T2 clock frequency (for the fastest speed set SPI_CLKN = 0) .

spi.init        lda #%00000100  ; disable shift-register interrupt
                sta VIA1_IER     
        
                lda #$FF        ; set port B to output
                sta VIA1_DDRB
        
                lda #SPI_CLKN   ; set number of counts for T2/clock
                sta VIA1_T2CL

                rts           

You can set an interrupt to trigger when 8 bits have been sent/received, but I have chosen to have the load/save running in the “main thread”. I have certain kernel update routines in IRQ (e.g. updating screen, cock, etc.) that will then interrupt when needed. Hence the SR interrupt is disabled. You could easily chose to do this differently.

Since we are using the shift-register for both MOSI and MISO communication (see my SPI Part 1 post) we need to set up both the SR register and the buffer chip for either output or input. For the SR register we do this by setting bits 2-4 in the VIA auxiliary control register (ACR) to either %001 (shift in under control of T2) or %101 (shift out under control of T2).

The the input line (connected to PB0) or the output line (connected to PB1) on the buffer is enabled by pulling it low (obviously only pull one low and the other high … I know I really should implement this in HW). This will enable or disable the respective gates on the buffer.

Finally pull the SD cards Slave Select low to indicate it is the device we are communicating to (SS1 is connected to PB2). E.g. writing %xxxxxx10 to Port B will disable output (bit 1 = 1) and enable input (bit 0 = 0). And %xx1110xx will set SS1 = 0 to select the SD card and deselect SS2-4 by setting them to 1.

spi.set_input   lda VIA1_ACR
                and #%11100011    ; mask out SR control bits
                ora #%00000100    ; SR in under control of T2 
                sta VIA1_ACR          
     
                lda #%00111010    ; buffer input & SD card CS = 0 
                sta VIA1_PORTB          
                rts 

                
spi.set_output  lda VIA1_ACR
                and #%11100011    ; mask out SR control bits
                ora #%00010100    ; SR in under control of T2
                sta VIA1_ACR

                lda #%00111001    ; buffer input & SD card CS = 0
                sta VIA1_PORTB
                rts

So far all we have done is to set up things and we need the code to actually send a byte over SPI. Note that any reading or writing to the shift register (SR) clears the respective flag in the IFR. Once 8 bits have been sent or received the flag (bit 2 in IFR) is set to signal that data is ready and if the interrupt is enabled it will also trigger an interrupt.

spi.send_byte   sta VIA1_SR      ; send data to SR (also clears SR flag in IFR)
@wait           lda VIA1_IFR     ; check IFR flags
                and #%00000100   ; isolate SR flag  
                beq @wait        ; wait until done sending byte 
                rts
                

spi.get_byte    lda VIA1_IFR     ; check IFR flag
                and #%00000100   ; isolate SR flag  
                beq spi.get_byte ; wait until SR flag is set (when previous shift operation is completed)
                lda VIA1_SR      ; get data (also clears SR flag in IFR)
                rts 

You have to be a little careful as you can only clear the SR flag in IFR by reading or writing to the shift register. For SD card communication you wilI always send bytes before receiving (more about this in the next blog post where we will dive into the actual SD card and FAT format). You will notice that my send_byte function sends first and then waits (until the byte has been sent), whereas the get_byte function waits first and then sends. This works because of this sequence, but if you are not careful you could be caught in a loop waiting for a flag that is never set or cleared.

Ideally you want to do other things while the SR register is sending/receiving, so for sending the above can certainly be optimized. Later you will see in some of my code where I send or receive large quantities of data that I have made these optimizations.

At the maximal T2 clock speed it will take 32 clock cycles to send/receive a byte, which can conveniently be used to e.g. storing the previous byte in memory. If you count the cycles of the code you execute between reads and writes you wouldn’t even have to check the SR flag before triggering the next byte to be sent/received (but if you are not careful you could trigger the shift register before it is ready for the next byte).

These were the basics of sending and receiving bytes over SPI and in my next blog posts I will go over the specifics of communicating with an SD card in SPI mode.

Note 1: As I was writing this blog post I realized the maximum read/write speed is attained by driving the SPI clock signal from PHI2 rather than Timer 2. The speed is then fixed at half the system clock speed and sending/receiving a byte takes 16 clock cycles, rather than 32 or more with T2. To do this you simply set bits 2-4 of ACR to %010 for receiving and %110 for sending. I tested this with my SD card and it works nicely . However if you need variable speeds for SPI communication with other devices (that might not be as fast) you still want to be able to change the SPI clock speed through T2.

Note 2: The IDE I used for coding supports standard 6502 instructions, but not the extended 65C02 instruction set, so there are places where my code could be optimized. This is a project for me to begin at some point. Suggestions for good 65C02 compilers and/or IDE’s very welcome. From my C64 coding I have been using the excellent C64Studio from Georg Rottensteiner.

14 Replies to “SPI Part 2”

  1. Mark, this is very interesting work for me as my project is behind yours so I can follow your ideas. Thanks for writing it up so clearly – the way you describe the software steps is very helpful because then it is possible to modify it for a different system. It also is then possible to experiment with different settings or code.

    I like the weekly updates – something to look forward to 🙂

  2. Hi Mark!

    Nice work, trying to follow along.
    Now, i noticed that the shift clock on CB1 of the 6522 is active low (so default high, clock pulse is low), which would be okay fo SPI Mode 3 (CPOL=1), but not for Mode 1 (CPOL=0)
    Am i correct in this?
    As such, “some” SD cards will work, some don’t.
    Also, devices requiring Mode 0 will not work either.
    Is there a way to reverse the clock polarity? (other than using an inverter, ofcourse)

    Greetings,

    1. Yes I believe you are correct and I had forgotten to write about that aspect. I am not aware of a way to change this for the shift clock. You could generate a “manual” clock signal using one of the pins from one of the ports on the VIA to simulate all modes, but of course that will significantly decrease the speed of the transmission.

  3. Howdy! This is kind of off topic but I need some guidance from an established blog. Is it very hard to set up your own blog? I’m not very techincal but I can figure things out pretty quick. I’m thinking about setting up my own but I’m not sure where to begin. Do you have any tips or suggestions? With thanks

    1. I simply used WordPress and it was quite easy. I’m sure there must be a ton of great content on this topic on YouTube. Good luck!

  4. Hi there just wanted to give you a quick heads up and let you know a few of the images aren’t loading correctly. I’m not sure why but I think its a linking issue. I’ve tried it in two different browsers and both show the same outcome.

  5. I like the valuable information you supply to your articles. I抣l bookmark your blog and test once more here frequently. I am reasonably sure I抣l learn many new stuff right right here! Best of luck for the next!

  6. I am now not positive where you’re getting your info, but good topic. I needs to spend a while learning more or figuring out more. Thanks for excellent information I was searching for this info for my mission.

  7. I really like your blog.. very nice colors & theme. Did you create this website yourself or did you hire someone to do it for you? Plz respond as I’m looking to construct my own blog and would like to know where u got this from. thank you

Leave a Reply

Your email address will not be published.