SPI blues - need a guru's eye
Erlend
Posts: 612
in Propeller 1
Some time back i wrote my own SPI driver because the only one I could find on OBEX was providing SHIFTIN and SHIFTOUT routines, but nothing in the fashion of: rxData:= Shift(txData. How I understand the principle of SPI, data is read in as it is written out. Therefore I wrote a SPI library to provide that ( rx:= Transfer(tx)). This excersise was a stretch for me at my skill level, so I was quite proud too.
Now I supect there is a bug hidden in there. I am at present working at getting up and running with a powerSTEP01 stepper motor controller, and the first or last '1' bit goes missing when writing to the chip, depending on SPI mode.
Needless to say I would want that bug in my SPI code to be killed, but to get on with talking to the chip I would like if there is some other SPI object with the same functionality that I can use.
Thanks,
Erlend
Now I supect there is a bug hidden in there. I am at present working at getting up and running with a powerSTEP01 stepper motor controller, and the first or last '1' bit goes missing when writing to the chip, depending on SPI mode.
Needless to say I would want that bug in my SPI code to be killed, but to get on with talking to the chip I would like if there is some other SPI object with the same functionality that I can use.
Thanks,
Erlend

Comments
I know some of the timing diagrams seem to indicate read/write which only leads to this confusion. Figure 28 is fairly typical, send a command byte then with the MOSI low just read the 3 data bytes etc.
Thanks, but isn't iMode & %10 having the same effect? I tried your suggestion, but the bug is still there.
Erlend
Agree, but some chips do behave as per SPI (duplex), ( e.g. the one that got me started on SPI, the nRF24L01+).
I would prefer to do the SPI comms similar across different chips.
Erlend
pri spi_readwrite(datain) | dataout '' Shifts datain into MCP2515, receives and returns dataout (from MCP2515) datain <<= constant(32-8) ' prep for MSB output repeat 8 outa[mosi] := (datain <-= 1) & 1 ' datain msb --> MOSI dataout := (dataout << 1) | ina[miso] ' MISO <-- dataout msb outa[sck] := 1 ' clock high outa[sck] := 0 ' clock low return dataout & $FFiMode & %10 just clears the LSB. For mode 0/1, iIdleCLK will be %00. For mode 2/3, iIdleCLK will be %10. When you assign OUTA[PINclk], it is only to iIdleCLK[0], which is always zero.
* CLK is Idle High.
* Write on the falling edge
* Read on the rising edge
I think the problem you are running into is that you are performing the write too early (as if PHA=0). Try the following modification:
REPEAT bitpos FROM iFirstPos TO iLastPos 'follow the direction set by choice of MSB first or LSB first IF iPhaseCLK== 0 OUTA[PINmosi]:= FRAMEtx >>bitpos 'if the bit at at that postion is one, output 1 WAITCNT(500 + cnt) 'allow the slave time to react OUTA[PINclk]:= iActiveCLK 'leading edge of clock (whether Active is 1 or 0 is defined by SPI mode) IF iPhaseCLK== 0 'if acting on leading edge read the input now: IF INA[PINmiso]== 1 'if incoming data bit is 1 Framerx |= 1<<bitpos 'set bit in the buffer at respective position to 1, otherwise leavi it at 0 ELSE OUTA[PINmosi]:= FRAMEtx >>bitpos 'if the bit at at that postion is one, output 1 WAITCNT(500 + cnt) 'allow the slave time to react OUTA[PINclk]:= iIdleCLK 'trailing edge of clock (whether Idle is 1 or 0 is defined by SPI mode) IF iPhaseCLK== 1 'if acting on trailing edge read the input now: IF INA[PINmiso]== 1 'if incoming data bit is 1 Framerx |= 1<<bitpos 'set bit in the buffer at respective position to 1, otherwise leave it at 0Do you remember what command(s) in the MCP2515 use full-duplex SPI?
This isn't about the MCP2515, it's about code and my belief that sometimes it's not worth having a "this object does everything in every possible circumstance" object when few lines of code will do the trick.
Ah, now I see. Thanks!
Erlend
Yes, that is a perspective I sometimes need - each time I get hung up in developing 'the ideal code' instead of just something that gets the job done. Thanks for that PRI, I will put it into my toolbox, for sure.
Erlend
Seairth,
I am very grateful for that you have taken the time to review my code and suggest how to fix the bugs! With the modifications you have suggested, it works nicely. I will have to wait for a moment when my head is clear (at least clearer...) to write up the SPI mode 3 timing diagram and step through the old and new code - to fully understand what your changes do.
Erlend
I think that's why @JonnyMac's comment is worth paying attention to. In SPI, there are only four timings (the four modes). Any one of them is easy to understand on their own. It's when we try to come up with an algorithm that covers all four timings that things get difficult. However! We are interacting with hardware that will only ever make use of one of those timings. It will never change. All we need is enough code to handle that one timing scenario. This leads us toward the idea that it can be easier (at least, in this case) to have four separate code snippets, each of which are simple and understandable, that we choose from. And, as an added benefit, it will be faster (if only marginally), which is often an important design factor for this sort of work.
pub spi_io(outVal, p_inVal, mode, bits) case mode 0 : result := spi_mode0(outVal, bits) 1 : result := spi_mode1(outVal, bits) 2 : result := spi_mode2(outVal, bits) 3 : result := spi_mode3(outVal, bits) other : return if (p_inVal > 0) ' do we care about read? case bits 1..8 : byte[p_inVal] := result 9..16 : word[p_inVal] := result 24..32 : long[p_inVal] := result pri spi_mode0 pri spi_mode1 pri spi_mode2 pri spi_mode3