Shop OBEX P1 Docs P2 Docs Learn Events
SPI library or driver? — Parallax Forums

SPI library or driver?

amossamamossam Posts: 35
edited 2012-09-12 13:22 in Propeller 1
Heya!

Is there any SPI library, function or driver, but written in C?

I have written a small function that "should" be generic, but I'm not totally satisfied with it...
Here is a code
int writeReadSPI(int SPI_cs, int SPI_clk, int SPI_do,int SPI_di, int data, int size)
{
    pinLow(SPI_clk);
    pinHigh(SPI_cs);
    pinOutput(SPI_do);
    pinLow(SPI_do);
    pinInput(SPI_di);

    int mask = 0b1 << 30;
    int reg = (0b0100 << 26) + SPI_clk;
    int result = 0;

    CTRA = reg;
    FRQA = 4194304; //2^22

    waitpne((1 << SPI_clk), (1 << SPI_clk)); //low
    waitpeq((1 << SPI_clk), (1 << SPI_clk)); //high
    pinLow(SPI_cs);

    int i;
    for (i = 0; i < 32; i++)
    {
        waitpne((1 << SPI_clk), (1 << SPI_clk)); //low
        pinWrite(SPI_do, ((data & mask) == mask));

        waitpeq((1 << SPI_clk), (1 << SPI_clk)); //high
        result = (result << 1) | pinRead(SPI_di);

        data <<= 1;
    }
    pinHigh(SPI_cs);
    CTRA = 0; //disable counter
    result >>= size;
    return result;
}

If I understand SPI protocol correctly, data is sent on rising edge and read on falling edge, and with following call:
writeReadSPI(CS, CLK, DO, DI, (0b011000 << 26), 13);
I get data from MCP3208 with this function, but I can't test it anything else currently...

And I need simple SPI read/write functions, because I ordered few wireless board on 433MHz based on CC1101 chip which I plan to use with propeller as master node and few attiny's as clients...

thx!

P.S. Would i gain anything if I put writeReadSPI into .cogc file and use mailbox for communication with SPI devices? (and I'm not sure yet how to do that, but if needed I'll figure it out! :) )

Comments

  • jazzedjazzed Posts: 11,803
    edited 2012-09-11 13:02
    amossam wrote: »

    Is there any SPI library, function or driver, but written in C?

    Some library functions are a work in progress. We have a list, but it is slow to be provided.
    amossam wrote: »
    P.S. Would i gain anything if I put writeReadSPI into .cogc file and use mailbox for communication with SPI devices? (and I'm not sure yet how to do that, but if needed I'll figure it out! :) )

    This is a good question. With propeller-gcc supporting CMM, a cogstart thread driver might be smaller and just as effective (though slower) than a driver written for COGC.

    Here is a simple example of how to use a cogstart thread. https://sites.google.com/site/propellergcc/documentation/libraries/propeller-h-library/#TOC-cogstart

  • dnalordnalor Posts: 223
    edited 2012-09-11 13:22
    amossam wrote: »
    Heya!
    If I understand SPI protocol correctly, data is sent on rising edge and read on falling edge,

    That I think is wrong.
    I wrote something together quickly:
    #define SPI_CLK     0
    #define SPI_DI      1
    #define SPI_DO      2
    #define SPI_CS      3
    
    #define SPI_CLOCKMASK   (1 << SPI_CLK)
    #define SPI_DIMASK      (1 << SPI_DI) 
    #define SPI_DOMASK      (1 << SPI_DO)
    #define SPI_CSMASK      (1 << SPI_CS)
    
    #define SPI_OUTMASK     (SPI_CLOCKMASK | SPI_DOMASK | SPI_CSMASK)
    
    
    #define SPI_INIT()      (DIRA |= SPI_OUTMASK)
    #define GET_SPI_IN()    ((INA & SPI_DIMASK) >> SPI_DI)
    #define SPI_ENABLE()    OUTA &= ~(SPI_CSMASK)
    #define SPI_DISABLE()   OUTA |= SPI_CSMASK
    
    unsigned char ubPutByte(unsigned char b)
    {
            
        unsigned char in = 0,i;
        
        for(i = 0; i < 8; i++)
        {        
            if(b & 0x80)                            // MSB High
            {
                OUTA |= SPI_DOMASK;                 // Data High
            }
            else
            {
               OUTA &= ~(SPI_DOMASK);               // Data Low
            }
            
            OUTA |= SPI_CLOCKMASK;                  // Clock High
            
            in <<= 1;
            in |= GET_SPI_IN();
             
            OUTA &= ~(SPI_CLOCKMASK);               // Clock Low
            
            b <<= 1;                                // next Bit
        }
        
        return(in);
    }
    
    void main (void)
    {
        unsigned char byte,cmd;
        
        SPI_INIT();  
         
        SPI_ENABLE();
            
        byte = ubPutByte(cmd);
        //do something 
        byte = ubPutByte(cmd);
        
        //.....
        
        SPI_DISABLE(); 
        
        
        
    }
    

    I would try it first without the timer. This device seems to be quite complicated.
  • amossamamossam Posts: 35
    edited 2012-09-11 16:22
    jazzed wrote: »
    This is a good question. With propeller-gcc supporting CMM, a cogstart thread driver might be smaller and just as effective (though slower) than a driver written for COGC.

    Here is a simple example of how to use a cogstart thread. https://sites.google.com/site/propellergcc/documentation/libraries/propeller-h-library/#TOC-cogstart
    well, i dont see a reason to use cmm model, because, currently when i compile with -lcog file size is around 450 bytes, and when I add mailbox and some more logic, I don't believe it will grove more than few 100's bytes...

    dnalor wrote: »
    That I think is wrong.
    I wrote something together quickly:
    ....
    

    I would try it first without the timer. This device seems to be quite complicated.

    well, with your suggestion i got data back (after few small adaptations), but as with my function from first post I would occasionally got stray data every 5000-6000 samples. for ex. result sould be 4086, and I would get 4465 back! and for using your function in other situations I would need to change #define's....
    But your code gave me few ideas, and now I can get around 10k samples per second!
    Also, it seems (at least when this adc is asked) that data _is_ sent to adc on rising edge of clk and read from adc on falling edge...

    here is current version of function, which now doesn't use any other functions, it's completely self-contained!
    int writeReadSPI(int SPI_cs, int SPI_clk, int SPI_do,int SPI_di, int data, int size)
    {
        DIRA |= (1 << SPI_clk); //set output
        DIRA |= (1 << SPI_cs); //set output
    
        OUTA &= ~(1 << SPI_clk); //set low
        OUTA |= (1 << SPI_cs); //set high
    
        DIRA |= (1 << SPI_do); //set output
        OUTA &= ~(1 << SPI_do); //set low
    
        DIRA &= ~(1 << SPI_di); //set input
    
        int mask = 1 << 31; //mask for comparing bits
        int reg = (0b0100 << 26) + SPI_clk;//set counter mode
        int result = 0;
    
        CTRA = reg;
        FRQA = 33554432; //2^25
    
        waitpeq((1 << SPI_clk), (1 << SPI_clk)); //high
        waitpne((1 << SPI_clk), (1 << SPI_clk)); //low
        OUTA &= ~(1 << SPI_cs);
    
        int i;
        for (i = 0; i < 32; i++)
        {
            if ((data & mask) == mask)
                OUTA |= (1 << SPI_do); //set high
            else
                OUTA &= ~(1 << SPI_do); //set low
            data <<= 1; //shift data left for next bit
    
            waitpeq((1 << SPI_clk), (1 << SPI_clk)); //high
    
    
            waitpne((1 << SPI_clk), (1 << SPI_clk)); //low
            result = (result << 1) | ((INA & (1 << SPI_di)) ? 1 : 0);
    
        }
        OUTA |= (1 << SPI_cs); // cs set to high
    
        CTRA = 0; //disable counter
        result >>= (size + 1);
        return result;
    }
    
    #define CS 2
    #define DI 3
    #define DO 1
    #define CLK 0
    int main (int argc, char* argv[])
    {
        while(1) {
            int i, k = 0;
            // average of 5000 samples (overkill, but ftdi drivers on osx obviously have buffering issues,
            // because if i'm getting data back too fast, complete USB subsystem on osx goes down!
            for (i = 0; i < 5000; i++)
                k += writeReadSPI(CS, CLK, DO, DI, (0b011000 << 26), 12);
            k /= 5000;
            printf("Res = %i\n", k);
            //sleep(3);
        }
        return 0;
    }
    
    
    currently, function is defined to use 32 bit packet, but that can be easily changed (that's next TODO!)...

    what I would like is to someone tries this function on something other than MCP320x, and reports how it's working...

    thx!
  • dnalordnalor Posts: 223
    edited 2012-09-12 12:56
    Ah! Yes you are right. Send on rising edge and read on falling edge. Sorry.
    But is it right to clock 32 bits at once?
    Maybe I do not understand your code completly.
  • amossamamossam Posts: 35
    edited 2012-09-12 13:22
    dnalor wrote: »
    Ah! Yes you are right. Send on rising edge and read on falling edge. Sorry.
    But is it right to clock 32 bits at once?
    Maybe I do not understand your code completly.

    Well, since "data" parameter of function is int, I automatically put in loop 32, but now that you mentioned it, I see that i'm loosing 15 (or 14?) cycles for nothing! (5 for setup, 12 for reading data in this example!) hmm, will try later to use "size" parameter for packet (send + receive data) size, not for result size! so, you could pass (byte << 24) (shifting left because it's sending MSB first) as "data" and size of 8, and i would sent and receive packet of size byte (8 bits)!

    thx!

    edit: said and done! when I set function (code below) for 18 bit packet (this size is explained in code), I can read 10k samples in 0.488364 sec!! (if used function for timing is correct! I took it from post improving CMM performance )
    here is complete code, with timing:
    #include <propeller.h>
    #include <stdio.h>
    #include <time.h> //used only for timing
    #include <sys/time.h> //used only for timing
    
    int writeReadSPI(int SPI_cs, int SPI_clk, int SPI_do,int SPI_di, int data, int size)
    {
        DIRA |= (1 << SPI_clk); //set output
        DIRA |= (1 << SPI_cs); //set output
    
        OUTA &= ~(1 << SPI_clk); //set low
        OUTA |= (1 << SPI_cs); //set high
    
        DIRA |= (1 << SPI_do); //set output
        OUTA &= ~(1 << SPI_do); //set low
    
        DIRA &= ~(1 << SPI_di); //set input
    
        int mask = 1 << 31; //mask for comparing bits
        int reg = (0b0100 << 26) + SPI_clk;//set counter mode
        int result = 0;
    
        CTRA = reg;
        FRQA = 33554432; //2^25
        //FRQA = 4194304; // 2^22
        waitpeq((1 << SPI_clk), (1 << SPI_clk)); //high
        waitpne((1 << SPI_clk), (1 << SPI_clk)); //low
        OUTA &= ~(1 << SPI_cs);
    
        int i;
        for (i = 0; i < size; i++)
        {
            if ((data & mask) == mask)
                OUTA |= (1 << SPI_do); //set high
            else
                OUTA &= ~(1 << SPI_do); //set low
            data <<= 1; //shift data left for next bit
    
            waitpeq((1 << SPI_clk), (1 << SPI_clk)); //high
    
    
            waitpne((1 << SPI_clk), (1 << SPI_clk)); //low
            result = (result << 1) | ((INA & (1 << SPI_di)) ? 1 : 0); //read value
    
        }
        OUTA |= (1 << SPI_cs); // cs set to high
    
        CTRA = 0; //disable counter
        return result;
    }
    
    double dtime()
    {
        struct timeval tv;
        gettimeofday(&tv, NULL);
        return (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
    }
    
    #define CS 2
    #define DI 3
    #define DO 1
    #define CLK 0
    int main (int argc, char* argv[])
    {
        int i, k = 0;
        double elapsed;
        while(1) {
            k = 0;
            elapsed = dtime();
            for (i = 0; i < 10000; i++)
                k += ((writeReadSPI(CS, CLK, DO, DI, (0b11000 << 27), 18)) & 0xFFF);
            // 18 for size = 5 for setup + 1 for sample period + 12 for result...
            // 0xFFF - we are interested only in last 12 bits...
            k /= 10000;
            elapsed = dtime() - elapsed;
            printf("Res = %i, in %f sec\n", k, elapsed);
        }
        return 0;
    }
    
    results in:
    Res = 2752, in 0.488367 sec
    Res = 2752, in 0.488368 sec
    Res = 2752, in 0.488366 sec
    Res = 2752, in 0.488370 sec
    Res = 2752, in 0.488367 sec
    

    :D
Sign In or Register to comment.