Shop Learn P1 Docs P2 Docs Events
SPI communication between Parallax P2 and Raspberry Pi — Parallax Forums

SPI communication between Parallax P2 and Raspberry Pi

Hello,
I'm trying to transfer data from Raspberry Pi (As a master) to Parallax P2. I've attached Rpi code rpi_master_spi.cpp I have tried with Arduino Mega as a slave, I'm able to communicate bidirectionally from this code. Now I want to use Parallax P2 as a slave. I've checked shift_in() and shift_out() functions, but it's not working for me. I have used below pins of parallax P2

SCK 24
DATA 25
SS 26

So, is there any sample code, example or reference link available for P2 to work with SPI?

Comments

  • For the P2 you will want to look into smart pins. In Spin2, you would start with something like this

    con
    
      SS   = 26
      MISO = 25
      SCLK = 24
    

    Assuming the RPi CPOL is 0, you could setup the SPI input like this:

      m := P_SYNC_RX                                                ' spi rx mode
      m |= ((SCLK-MISO) & %111) << 24                               ' add SCLK offset (B pin)
      x := %0_00000 | (8-1)                                         ' sample ahead of b pin rise, 8 bits
      pinstart(MISO, m, x, 0)                                       ' configure smart pin
      pinf(MISO)                                                    ' reset/disable until used
    

    This SUGGESTED code (two versions) is based on other things I've worked on, but I have not used the P2 as a SPI slave with another device. Ultimately, you'll probably want a cog that monitors the SPI input and updates a buffer on the fly (like serial objects do).

    pub shiftin(count, p_buf) | value
    
      repeat count
        pinl(MISO)                                                  ' enable MISO  
        repeat until pinr(MISO)                                     ' wait for a byte
        value := rdpin(MISO)                                        ' get the value
        pinf(MISO)                                                  ' reset MISO      
        value rev= 31                                               ' restore MSBFIRST order
        byte[p_buf++] := value 
    
    
    pub shiftin_pasm(count, p_buf) | value
    
      org
    .loop                   drvl      #MISO                         ' enable MISO   
                            testp     #MISO                 wc      ' wait for a byte
            if_nc           jmp       #$-1
                            rdpin     pr0, #MISO                    ' get the value
                            fltl      #MISO                         ' reset MISO
                            rev       pr0                           ' restore MSBFIRST order
                            wrbyte    pr0, p_buf                    ' write to buffer
                            add       p_buf, #1                     ' update buffer pointer
                            djnz      count, #.loop
      end
    
  • @JonnyMac said:
    For the P2 you will want to look into smart pins. In Spin2, you would start with something like this

    con
    
      SS   = 26
      MISO = 25
      SCLK = 24
    

    Assuming the RPi CPOL is 0, you could setup the SPI input like this:

      m := P_SYNC_RX                                                ' spi rx mode
      m |= ((SCLK-MISO) & %111) << 24                               ' add SCLK offset (B pin)
      x := %0_00000 | (8-1)                                         ' sample ahead of b pin rise, 8 bits
      pinstart(MISO, m, x, 0)                                       ' configure smart pin
      pinf(MISO)                                                    ' reset/disable until used
    

    This SUGGESTED code (two versions) is based on other things I've worked on, but I have not used the P2 as a SPI slave with another device. Ultimately, you'll probably want a cog that monitors the SPI input and updates a buffer on the fly (like serial objects do).

    pub shiftin(count, p_buf) | value
    
      repeat count
        pinl(MISO)                                                  ' enable MISO  
        repeat until pinr(MISO)                                     ' wait for a byte
        value := rdpin(MISO)                                        ' get the value
        pinf(MISO)                                                  ' reset MISO      
        value rev= 31                                               ' restore MSBFIRST order
        byte[p_buf++] := value 
    
    
    pub shiftin_pasm(count, p_buf) | value
    
      org
    .loop                   drvl      #MISO                         ' enable MISO   
                            testp     #MISO                 wc      ' wait for a byte
            if_nc           jmp       #$-1
                            rdpin     pr0, #MISO                    ' get the value
                            fltl      #MISO                         ' reset MISO
                            rev       pr0                           ' restore MSBFIRST order
                            wrbyte    pr0, p_buf                    ' write to buffer
                            add       p_buf, #1                     ' update buffer pointer
                            djnz      count, #.loop
      end
    

    Hi JonnyMac,

    Thanks for reply. I tried this code. However I'm unable to communicate.
    I want to set clock frequency of P2 same as Raspberry Pi. And receive data from Raspberry Pi.
    Can you please explain in details about how should the m and x be selected. And what is the role of SCLK particularly.

    I've saved above code as testcode.spin2 and used it in c code as below.

    #include "Simpletools.h"
    #include "stdio.h"
    
    struct __using ("testcode.spin2") test;
    
    int main(){
        int buff[50] = {0};
        test.setup();
        while(1){
            test.shiftin(50, buff);
            for (int i=0; i<50; i++){
                printf("%d \n", buff[i]);
            }
        }
    }
    
  • JonnyMacJonnyMac Posts: 8,606
    edited 2022-08-23 03:32

    The details can be found here:
    -- https://docs.google.com/document/d/1gn6oaT5Ib7CytvlZHacmrSbVBJsD9t_-kmvjd7nUR6o/edit#heading=h.53grmlqytmbv

    I use variable m for the smart pin mode (in this case synchronous RX), and x and y are the registers used by the smart pin (y is 0 in this case). I have used smart pin synchronous RX many times, but always with an internally generated clock. This should not be an issue, though, because the smart pin setup links the clock pin (an input) to the MISO pin. The only restriction is the delta in pin #s. You need to make sure that your clock pin is within 3 pins of the MISO and MOSI that it might be affecting.

    There is no reason to run the P2 at the same clock speed as the RPi. In fact, you might try slowing your SPI output on the RPi. When a byte is received by the P2, it has to fix and store it before the next rising clock edge. A logic analyzer will be helpful here. Also, why not write test code to get one byte from RPi to P2 before you try 50? This is a common problem with programmers: jumping directly to the end goal instead of working their way there. I've been coding the Propeller for a long time, and I still do it very incrementally. The process only seems slower -- what I find is that incremental development eliminates bugs before they can become buried in a lot of code.

  • JonnyMacJonnyMac Posts: 8,606
    edited 2022-08-23 03:54

    Have a look at the attached demo. It uses one Spin cog to simulate the RPi:

    pri master_spi() | m, x, value                                  ' launch wit cogspin()
    
    ' setup smart pins for spi output
    
      pinhigh(CS0)                                                  ' make output/high (deactivate)
    
      m := P_SYNC_TX | P_OE                                         ' spi tx mode
      m |= ((SCLK0-MOSI0) & %111) << 24                             ' add clk offset (B pin)
      x := %1_00000 | (8-1)                                         ' start/stop mode, 8 bits
      pinstart(MOSI0, m, x, 0)                                      ' activate smart pin
      pinfloat(MOSI0)                                               ' reset/disable until used
    
      m := P_PULSE | P_OE                                           ' spi clock mode (CPOL = 0)
      x.word[0] := 2 #> (clkfreq / 31_250_000) <# $FFFF             ' ticks in period (32.25 MHz)
      x.word[1] := x.word[0] >> 1                                   ' ticks in low cycle (50%)
      pinstart(SCLK0, m, x, 0)                                      ' initialize smart pin
    
      waitms(500)                                                   ' let other cog start
    
    ' Send byte every 250 ms
    ' -- order is MSBFIRST
    
      repeat
        repeat outcount from 0 to 255
          pinlow(CS0)
          pinlow(MOSI0)                                             ' enable spi output
          wypin(MOSI0, outcount rev 7)                              ' send count MSBFIRST
          wypin(SCLK0, 8)                                           ' 8 clock pulses
          repeat until pinr(SCLK0)                                  ' wait until done
          wypin(MOSI0, (255 - outcount) rev 7)                      ' flip value, send MSBFIRST
          wypin(SCLK0, 8)                                           ' 8 clock pulses
          repeat until pinr(SCLK0)                                  ' wait until done
          pinfloat(MOSI0)                                           ' reset tx
          pinhigh(CS0)
          waitms(333)
    

    After I got things working with one byte at a time I added a second byte of output to the master.

    The slave cog uses an external clock (from the master cog) and monitors its CS; when this drops it waits for a byte to come it or CS to go back high. When a byte comes in it is read from the smart pin, fixed (for MSBFIRST), and written to the buffer. Here's the Spin-only version.

    pri slave_spi() | m, x, value, p_buf                            ' launch with cogspin()
    
      pinclear(CS1)                                                 ' make input
    
      m := P_SYNC_RX                                                ' spi rx mode
      m |= ((SCLK1-MOSI1) & %111) << 24                             ' add clock offset (B pin)
      x := %0_00000 | (8-1)                                         ' sample ahead of b pin rise, 8 bits
      pinstart(MOSI1, m, x, 0)                                      ' configure smart pin
      pinfloat(MOSI1)                                               ' reset/disable until used
    
    ' Wait for CS drop
    ' -- get 8 bits, convert to MSBFIRST
    
      repeat
        p_buf := @incount                                           ' reset input buffer pointer
        repeat while pinread(CS1)                                   ' wait for chip select
        pinlow(MOSI1)                                               ' enable spi rx
        repeat
          if pinread(MOSI1)                                         ' byte received?
            byte[p_buf++] := rdpin(MOSI1) rev 31                    ' convert to MSBFIRST and save
          if pinread(CS1)                                           ' slave deselected?
            pinfloat(MOSI1)                                         ' disable/reset
            quit
    

    After the SPI RX pin is setup (associated with the external clock), it drops into a loop that waits for CS to drop and then writes incoming bytes to the buffer. It works, but is only good for the demo because we know two bytes are coming.

    As I said earlier, doing things incrementally is a good way to go. Once all the Spin2 code was working, I converted the ending repeat loop to this inline pasm fuction. It does the same, but will return the number of bytes the were received while CS was low.

    pri shift_in(p_buf) : count
    
    '' Wait for cs to drop
    '' -- read bytes from SPI to p_buf
    '' -- returns count when CS goes high
    '' -- SPI bit order is MSBFIRST
    '' -- blocks until CS transitions low, then high
    
      org
    .spi_in                 testp     #CS1                  wc      ' monitor chip select
            if_c            jmp       #.spi_in                      ' wait for drop
    
                            drvl      #MOSI1                        ' enable spi rx
    
    .get_byte               testp     #MOSI1                wc      ' data from master?
            if_nc           jmp       #.check_cs                    ' no, check cs
                            rdpin     pr0, #MOSI1                   ' get the value
                            rev       pr0                           ' fix MSBFIRST
                            wrbyte    pr0, p_buf                    ' save to hub
                            add       p_buf, #1                     ' point to next addr
                            add       count, #1                     ' update return count
    
    .check_cs               testp     #CS1                  wc      ' check state of cs
            if_nc           jmp       #.get_byte                    ' still active?
                            fltl      #MOSI1                        ' disable/reset
      end
    

    All the code is in the demo. Please, please, please, give it a try before converting it to another language. Go slowly.

    As you already owe me a beer or an ice cream , I will leave adding the MISO mechanism to you. It should be no problem. Ultimately, you could create a SPI slave object with circular buffers and the ability to read as many bytes as one wants. Have a look at jm_fullduplexserial.spin2 for ideas on circular buffers and using head and tail pointers.

  • Hi @JonnyMac
    Thank you for the solution. I have run this demo code. Got an error pr0 register.

    error: Undefined symbol pr0

    I replaced pr0 with its address as given below

    rdpin $1D8, #MOSI1 ' get the value
    rev $1D8 ' fix MSBFIRST
    wrbyte $1D8, p_buf ' save to hub

    But, I am not getting exact data sent from Raspberry-pi. So what am I doing wrong here? Can you please suggest?

  • JonnyMacJonnyMac Posts: 8,606
    edited 2022-08-23 14:03

    PR0..PR7 are user registers in the Spin2 interpreter -- I thought FlexProp understood these registers. That said, it's easy to add a local variable (I wouldn't use a hard address as you're doing; no telling what's at that address under FlexProp). As you can see, with a small adjustmet for FlexProp, the demo works there, too.

    Now I suggest you get a logic analyzer and see what's coming out of your Raspberry Pi. I set the SPI clock back to 1MHz and captured the output from my master cog which sends a count value, and 255-count in each CS frame. Here's the capture:

    I think I've done all I can given your limited information and feedback.
    -- "I am not getting exact data sent from Raspberry-pi" is not helpful to those from whom you're asking for help.

    Programming can be fiddly. Time to fiddle until you understand what's going on with your hardware. Good luck with your project.

  • Thank you for your valuable help. :)

  • Kundan_jhaKundan_jha Posts: 12
    edited 2022-08-26 13:27

    Hello @disha_sudra,

    You can try this "C programming language" method to receive the data from Rpi to propeller 2 using SPI protocol.

    #include "simpletools.h"   
    #include <stdio.h>
    #include "spi-c.h"
    #define NSAMPS_MAX 8
    
    int data[NSAMPS_MAX];
    void main() {
    
      high(38);  // This is to provide 3.3v to logic level converter.
      int i = 0;
      DIRA = 0;
      DIRA |= MOSI ; // set to outputs
      while (1) {
             int value = 0, decimal = 0, base = 1;
    
             while(_pinr(CS) == 1);
             while(_pinr(CS) == 0)
             {
                     if(_pinr(CLK) == 1)
                             ((_pinr(CLK) == 1))&&((_pinr(MOSI) == 1)) ? (data[i++] = 1) : (data[i++] = 0);
             }
             for(int count = 7; count >=0; (decimal = decimal + (data[count] * base)),(base *= 2),count --);
             printf("%d\n",decimal);
             i = 0;
      }
     }
    
  • evanhevanh Posts: 14,263
    edited 2022-08-26 13:47

    PR0..PR7 symbols were added to Flex suite as of v5.9.15 - the latest release.

    v5.9.15 fixes a bug or two with inline Pasm too.

  • JonnyMacJonnyMac Posts: 8,606
    edited 2022-08-26 15:38

    You can try this "C programming language" method to receive the data from Rpi to propeller 2 using SPI protocol.

    Have you tested that with physical signals? It seems like when the CS line drops and the CLK line goes high, the code is going to be in a very tight loop that blasts values to the data[] array -- there seems to be nothing stopping the code from generating a bunch of samples from the same clock pulse, and this will continue as long as CS is low. The P2 smart pin SPI feature takes care of this; we simply wait check to see if a byte as been received. When that is not the case, we can check the state of the CS line to see if the master is done sending. And with mode 0 SPI, you want to sample right before the rising clock edge, not while it's high. If I'm wrong, please tell me -- I understand C, but I don't use it with the Propeller.

    I don't like to post untested code (especially in a language I don't use regularly, and never on the Propeller), but maybe the C version with smart pin SPI looks like this:

    int shift_in(uint8_t buf[])
    {
        int count = 0;                                              // bytes received
    
        while(_pinr(CS) == 1);                                      // wait while CS high
        _pinl(MOSI);                                                // enable smart pin SPI RX
    
        while(_pinr(CS) == 0) {                                     // while CS is low
            if (_pinr(MOSI))                                        // if byte waiting
                buf[count++] = _rev(_rdpin(MOSI));                  //  read, fix MSB, save to array
        }
        _pinf(MOSI);                                                // disable/reset SPI RX
    
        return count;
    }
    
  • @evanh said:
    PR0..PR7 symbols were added to Flex suite as of v5.9.15 - the latest release.

    v5.9.15 fixes a bug or two with inline Pasm too.

    I have .14 -- will look for the latest. Thanks for the tip.

  • But, I am not getting exact data sent from Raspberry-pi. So what am I doing wrong here? Can you please suggest?

    Can you add a small delay on the RPi between CS dropping and bits going out? Maybe there is a race condition.

  • Hi @JonnyMac
    As per your suggestion I've used smart pins and created a function for receiving.
    I am using P_SYNC_RX as per your suggestion in previous post.
    I am attaching my RPi code and P2 code in c++.

    RPI Frequency: 5 MHZ
    P2 Frequency: 200 MHZ

    With above mentioned frequency setup I'm getting proper data for buffer size from 10 to 255 bytes, but when I increases buffer size on RPi from 255 to 500 or more then I am getting garbage data on P2. same thing happen if I change the clock frequency of RPI or P2. I need to setup RPI spi Frequency to 10 MHZ for high speed data transfer. So if some one can point what I am doing wrong here then it will be great help. I am new to P2 and smart pin configuration. Thank you.

    Code for P2:

    #include "spi-c.h"
    unsigned char data[4096];
    enum { _clkfreq = 200000000 };
    int my_shift_in(void)
    {
        int count = 0;                                              // bytes received
        while(_pinr(CS) == 1);                                      // wait while CS high
        _pinl(MOSI);                                                // enable smart pin SPI RX
        while(_pinr(CS) == 0) {                                     // while CS is low
            if (_pinr(MOSI))                                        // if byte waiting
                data[count++] = _rev(_rdpin(MOSI));                  //  read, fix MSB, save to array
        }
        _pinf(MOSI);                                                // disable/reset SPI RX
       return count;
    }
    void main() {
        DIRA = 0;
        int m = 0, x = 0, ret = 0;
        DIRA |= MOSI ;
        _pinclear(CS);
        m = P_SYNC_RX;
        m |= ((CLK-MOSI) & 0b111) << 24;
        x = 0b0_00000 | (8-1);
        _pinstart(MOSI, m, x, 0);
        while(1){
                ret = 0;
                ret = my_shift_in();
                if(ret>0)
                        for(int i = 0; i<ret; i++){
                             printf("%d ",data[i]);
                                }
        }
    } 
    

    Code for R-pi:

    #include <iostream>
    #include "/home/admin/Documents/SPI/wiringPiSPI.h"
    
    #define SPI_CHANNEL 0
    #define SPI_CLOCK_SPEED 5000000
    
    
    int main(int argc, char **argv)
    {
        int fd = wiringPiSPISetupMode(SPI_CHANNEL, SPI_CLOCK_SPEED, 0);
        if (fd == -1) {
            std::cout << "Failed to init SPI communication.\n";
            return -1;
        }
    
         unsigned char buf[4096] = {0};
        for(int i=0, count = 0,k = 0;i<4096;i++){
            buf[count++] = k++;
        if(k==256)
            k=0;
        }
    
        wiringPiSPIDataRW(SPI_CHANNEL, buf, 10);
    
    
        return 0;
    }
    
    h
    h
    1K
  • evanhevanh Posts: 14,263
    edited 2022-09-14 11:35

    Disha,
    Give this a try. It compiles but I've not tested it with any hardware.

    PS: The inner loop is 14 sysclock ticks. That's fast enough to keep up with the smallest recommended oversampling of 3. Therefore max bitrate is 200 MHz / 3 = 66 Mbit/s. PPS: Although sysclock/5 is probably the more reliable figure, which means up to 200 / 5 = 40 Mbit/s.

    PPPS: Oversampling is needed factor here because the Prop2 doesn't provide external synchronous clocking for any I/O. The SPI clock input pin is just another data I/O pin, same as the SPI data pins. If the clocking source (RPi in this case) is not common to the Prop2's internal clock then the I/O has to be treated as asynchronously sampled. Which means all the usual sampling aliasing problems rare up.

  • @evanh said:
    Disha,
    Give this a try. It compiles but I've not tested it with any hardware.

    PS: The inner loop is 14 sysclock ticks. That's fast enough to keep up with the smallest recommended oversampling of 3. Therefore max bitrate is 200 MHz / 3 = 66 Mbit/s. PPS: Although sysclock/5 is probably the more reliable figure, which means up to 200 / 5 = 40 Mbit/s.

    PPPS: Oversampling is needed factor here because the Prop2 doesn't provide external synchronous clocking for any I/O. The SPI clock input pin is just another data I/O pin, same as the SPI data pins. If the clocking source (RPi in this case) is not common to the Prop2's internal clock then the I/O has to be treated as asynchronously sampled. Which means all the usual sampling aliasing problems rare up.

    I have tested this code with hardware setup. I have setup Rpi frequency to 10MHz. First I have sent 10 Bytes of buffer from Rpi to P2 but most of time receiving garbage data and also tested with large buffer size (Approx 100KB) but result was same. I've to send 1 to 5 Mbyte of image data.
    Thank you so much for your valuable reply.

  • evanhevanh Posts: 14,263

    Hmm, maybe still need to sort out CPOL/CPHA and the likes. Any idea what the RPi is sending as?

  • evanhevanh Posts: 14,263
    edited 2022-09-14 16:04

    Oh, with the DIRH placed after CS low detection, it does need a small amount of time from CS low until smartpin is accepting serial data. Maybe 10 sysclocks, 50 ns post-CS-low. If SPI clocks start in less time then it'll be a mess.

    Maybe better to enable the smartpin earlier ...

    EDIT: I've added an extra step to check for a prior low CS level. This allows for an earlier enable of the smartpin.
    EDIT2: After reading some of Jon's earlier postings I noticed another timing improvement - check for rising CS loop exit only when smartpin buffer is empty. Updated my code to incorporate that too.

    PS: The forum's formatting doesn't work. Attached file instead:

  • evanhevanh Posts: 14,263
    edited 2022-10-02 01:06

    Final slave code is presented in other topic - https://forums.parallax.com/discussion/comment/1543798/#Comment_1543798

    'Twas a much more intensive exercise than I was expecting. It really kind of caught me off guard. I didn't dig out the scope partly because of the fiddliness of hooking it up, which cost me more time chasing my tail.

Sign In or Register to comment.