This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Properly bit-banging an SPI port using the PRU

Other Parts Discussed in Thread: ADS8331, ADS7945

Hi,

I have been working on a project that collects data from the ADS8331 4 Ch. ADC at moderate sampling rates (~40 kHz/Channel) using an SPI bus. I would like to use the PRU to implement the SPI bus to take some load of the main CPU. 

// Bus definitions
#define MOSI R30.t9 #define MOSI R30.t8 #define MISO R31.t12 #define CS R30.t10

#define A  r1 // Storage register
#define C r2 // Carry register
#define DAT r3 // Data byte, externally supplied 

I figured an efficient, bit-banged implementation would look something like this using ~ASM51 syntax

MOV A, DAT            ; Store data byte in A
MOV, CNT, 8           ; The current tick number
RLC A                      ; Rotate most significant bit into carry bit

SPI_LOOP:

  MOV MOSI, C        ; Move carry bit out out MOSI
  CLR SCLK            ; Bring clock line low
  MOV  C, MISO       ; Move MISO into carry bit
  RLC A                    ; Rotate Carry bit into LSB of A, which is now free,
  DJNZ CNT, SPI_LOOP  ; Decrement counter and jump back to loop start if not finished

MOV DAT, A            ; Supply MISO result and return

However, I am having a hard time translating this type of program to the PRU-ASM instruction set. The biggest problem that I am seeing is that it seems to be impossible to MOV single bits within registers without disrupting the other content of the register. This means that instead of simply MOVing the current MISO bit within the A register (in the example able) to the MISO GPIO pin, I have to do a QBBS to see if the pin is set with the A register and the either SET or CLR the bit. i.e. This command

MOV MOSI, C        ; Move carry bit out out MOSI

is replaced by approximately this command set, using the same register definitions from above

QBBS  MOSI_HIGH DAT, CNT

// Enable MOSI LOW
CLR MOSI
JMP MOS_DONE

// Enable MOSI HIGH
MOSI_HIGH:
SET MOSI
delay 1 

 MOSI_DONE:
... 

I figure that I must be missing something, because this is way too inefficient. Basically, I have three  questions/comment.

1. Is there an example of an efficiently implemented, bit-banged SPI protocol for PRU-ASM?

2. How can I MOV a bit within a register without affected other bits within that register. 

3. Any random thoughts on how to improve my SPI implementation would be very helpful since (clearly) I don't have too much of a clue :).

Thanks for any help.

- Jon

  • Jon,

         I have implemented PRU code that samples from a AD8332 (8 channel version of the same chip.)  

    1. There are no examples I could find, but it was not too hard to get this working from scratch
    2. I couldn't find a way to do this, I agree it would be the best way.  SET/CLR are the only way I could find
    3. I think you've got it :)

    Few general comments:

    • Here is a few snippets form my code from the specific section you were asking about: https://gist.github.com/cmicali/6984888
    • My implementation has PRU0 talking to the 8332 and putting data into PRU shared memory, and then PRU1 reading from that and writing to DDR.  
    • That chip has some pretty strict timing requirements, and it was a pain to get working.  Make sure you delay enough time after CNV that the conversion is complete (EOC goes low or high when done, cant remember which)
    • At 40khz you don't need to worry about efficiency of the PRU code.   My latest code does 1.5Msps from two ADAS3022 and one ADS7945.  
    -c
  • Hi Chris,

    Good to hear from you. I've been tracking your progress on Github, very impressive. Actually, I choose this chip because I was impressed by the specs after being tipped off by your project. We have noticed the timing stuff too. Actually, we have the thing  working almost exactly as you have described.

    Although 40 kHz does not seem too bad,  the problem is that we will be adding 32 extra 16-bit ADC lines that I need to round robin at ~40 kHz (using one of these guys: http://www.intantech.com/files/Intan_RHD2000_series_datasheet.pdf). Then things start adding up. I think that even then I can even get away with this inefficient implementation. However, I just wanted to see if maybe we are both missing something obvious. Maybe we will both get a bit of headroom back in the process.

    Good luck with your project and thanks for the code!

    - Jon