Shop OBEX P1 Docs P2 Docs Learn Events
SPI & MCP3208 in C - Page 2 — Parallax Forums

SPI & MCP3208 in C

2»

Comments

  • Heater.Heater. Posts: 21,230
    edited 2014-07-02 01:48
    SwimDude0614,
    C would be faster development time (than assembly) of course, but laughably slow performance.
    propgcc contains full_duplex_serial_ht, in the demos directory. That is a full duplex serial driver entirely written in C. It works up to 115200 baud.

    It was not meant for production use, I don't think the edge timings at those higher speeds are very good. Although I have never really checked. Rather it's an example of how to make threads in C running in a cog.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2014-07-02 13:41
    Yep. I'm definitely eating my words on that one :P

    I may find myself using that module as inspiration for my own buffered version of the UART class in PropWare. I hadn't seen that before today.
  • jazzedjazzed Posts: 11,803
    edited 2014-07-02 14:11
    Hope you didn't eat 'em all.
    I may find myself using that module as inspiration for my own buffered version of the UART class in PropWare. I hadn't seen that before today.


    Last I checked Heater's full_duplex_serial_ht was broken. It really did work a long time ago though.
  • Heater.Heater. Posts: 21,230
    edited 2014-07-02 14:41
    Jazzed,
    Last I checked Heater's full_duplex_serial_ht was broken.
    Oh Smile, what is wrong with it? I have not tried it in a long while but it did at least compile:)
  • jazzedjazzed Posts: 11,803
    edited 2014-07-02 15:08
    Heater. wrote: »
    Jazzed,

    Oh Smile, what is wrong with it? I have not tried it in a long while but it did at least compile:)


    I mentioned it several years ago.

    Should have nagged I guess, but I don't have an apron, hair curlers, a broom, or a dough roller. LOL
  • Heater.Heater. Posts: 21,230
    edited 2014-07-02 23:04
    Jazzed,

    full_duplex_serial_ht does compile here still. I don't have any Propellers around me at the moment so I can't test it.

    "Dough roller", must be American for "rolling pin". You might do better with a cattle prod on this one:)
  • ChrisL8ChrisL8 Posts: 129
    edited 2014-08-02 11:15
    I am successfully using the .h file from post 18 (http://forums.parallax.com/showthread.php/141893-SPI-amp-MCP3208-in-C?p=1119736&viewfull=1#post1119736) to read from an MCP3208 on my Propeller Activity board,
    however, I had to change every instance of:
    delay(10)
    

    in the code to:
    delay(15)
    

    because 10 was too short causing waitcnt to stall for a "full wrap" or whatever it is that happens when you give waitcnt a time that is too short.

    Is there any particular reason why others were able to use 10 and I can only use 15?

    Also, it didn't seem entirely clear to me from these posts, but you connect your "dinout" pin on the Propeller to the dIN pin on the MCP3201, then you connect the dOUT pin on the MCP3208 to the dIN pin with a 3.3K Ohm resistor.
    The point is to protect the prop from high output voltages from the MCP3208's dOUT pin. The CLK, DIN and CS pins can be connected directly to the prop with no resistor even if you are feeding the MCP3208 with 5V on VDD & VREF.

    This post has a good diagram of how to connect the MCP3208 to the Propeller in the most simple way possible:
    http://forums.parallax.com/showthread.php/138090-What-am-I-doing-wrong-(mcp3208)?p=1076335&viewfull=1#post1076335
    Although I did not use a capacitor in the power feed. Maybe I should? My readings seem stable enough.

    Also, while I'm adding information, here is a link to the data sheet for the MCP3208:
    http://ww1.microchip.com/downloads/en/DeviceDoc/21298c.pdf
  • jazzedjazzed Posts: 11,803
    edited 2014-08-02 15:33
    Where is delay() defined? Copy/paste the function here?

    Also LMM or COGC code is faster than CMM code, so that could be a factor.
  • ChrisL8ChrisL8 Posts: 129
    edited 2014-08-03 10:11
    Sorry, I should have posted more details.

    The "delay" funciton is from the mcp3208.h file from Post #18: http://forums.parallax.com/showthread.php/141893-SPI-amp-MCP3208-in-C?p=1119736&viewfull=1#post1119736
    Here is the full code:
    /*
     * mcp3208.c
     *
     *  Created on: Aug 18, 2012
     *      Author: AmosSam
     *     Version: 0.1
     */
    
    
    #include <propeller.h>
    
    
    /**
     * @brief sets pin state, int pin, to high
     */
    void pinHigh(int pin)
    {
        OUTA |= (1 << pin);
        DIRA |= (1 << pin);
    }
    
    
    /**
     * @brief sets pin state, int pin, to low
     */
    void pinLow(int pin)
    {
        OUTA &= ~(1 << pin);
        DIRA |= (1 << pin);
    }
    
    
    /**
     * @brief sets pin direction, int pin, to input
     */
    void pinInput(int pin)
    {
        DIRA &= ~(1 << pin);
    }
    
    
    /**
     * @brief sets pin direction, int pin, to output
     */
    void pinOutput(int pin)
    {
        DIRA |= (1 << pin);
    }
    
    
    /**
     * @brief reads pin state, int pin
     */
    int pinRead(int pin)
    {
        DIRA &= ~(1 << pin);
        return (INA & (1 << pin)) ? 1 : 0;
    }
    
    
    /**
     * @brief sets pin state, int pin, to int state
     */
    int pinWrite(int pin, int state)
    {
        if (state)
            OUTA |= (1 << pin);
        else
            OUTA &= ~(1 << pin);
        DIRA |= (1 << pin);
        return (OUTA & (1 << pin)) != 0;
    }
    
    
    /**
     * @brief delay function, more precise on Propeller (?)
     */
    void delay(int us)
    {
        waitcnt(us*(CLKFREQ/1000000)+CNT);
    }
    
    
    /**
     * @brief sets pin, int pin, state to high, delays specified time, int d,
     * and then puts pin low, and delays specified time, int d1
     */
    void pinPulseHL(int pin, int d, int d1)
    {
       pinHigh(pin);
       if (d > 10)
           delay(d);
       pinLow(pin);
       if (d1 > 10)
           delay(d1);
    }
    
    
    /**
     * @brief sets pin, int pin, state to low, delays specified time, int d,
     * and then puts pin high, and delays specified time, int d1
     */
    void pinPulseLH(int pin, int d, int d1)
    {
       pinHigh(pin);
       if (d > 10)
           delay(d);
       pinLow(pin);
       if (d1 > 10)
           delay(d1);
    }
    
    
    /**
     * @brief Reads value from mcp3208/mcp3204 ADC
     * @details This function reads data from mcp3208/mcp3204 ADC
     * over SPI, with DIN and DOUT pins tied together to single
     * pin on Propeller with 3.3K resistor in between pins on ADC
     *
     * @param channel What channel to read? 0 - 7
     * @param dinout Pin number on which DIN and DOUT are connected
     * @param clk Pin number on which CLK is connected
     * @param cs Pin number on which CS is connected
     * @returns Value read from mcp3208/mcp3204 ADC
     */
    int readADC(int channel, int dinout, int clk, int cs)
    {
        int i;
        int AdcResult;
        int setup;
    
    
        //Setting up pins
    
    
        // In case pin was already been low, we put it high
        // so we can initiate communication after setting up pins
        pinOutput(cs);
        pinHigh(cs);
    
    
        pinOutput(dinout);
        pinLow(dinout);
    
    
        pinOutput(clk);
        pinLow(clk);
    
    
        pinLow(cs);      // Active chip select by setting pin low
        delay(10);
    
    
        // Sending configuration to device
        setup = channel | 0b11000;
        for(i=0; i < 5;i++) {
            pinPulseHL(clk, 10, 0);
            if ((setup & 0b10000) == 0b10000)
                pinHigh(dinout);
            else
                pinLow(dinout); // is MSB != 0
            setup <<= 1;  // shift left
            delay(10);
        }
    
    
        pinPulseHL(clk, 10, 10); //Empty clock, for sampling
    
    
        pinPulseHL(clk, 10, 10); //Device returns low, NULL bit, we ignore it...
    
    
        pinInput(dinout);
    
    
        // read ADC result 12 bit
        AdcResult=0;
        for(i=0;i<12;i++) {
            // We are sending pulse, clock signal, to ADC, because on falling edge it will return data...
            pinPulseHL(clk, 10, 0);
            // Shifting bit to left, to make room for current one...
            AdcResult<<=1;
            AdcResult=AdcResult | (pinRead(dinout) & 0x01);
            delay(10);
        }
        pinHigh(cs);
        return(AdcResult);
    }
    
    
    /**
     * @brief Reads specified number of values from mcp3208/mcp3204 ADC
     * @details This function reads specified number of times
     * data from mcp3208/mcp3204 ADC over SPI,
     * with DIN and DOUT pins tied together to single
     * pin on Propeller with 3.3K resistor in between pins on ADC
     *
     * @param channel What channel to read? 0 - 7
     * @param dinout Pin number on which DIN and DOUT are connected
     * @param clk Pin number on which CLK is connected
     * @param cs Pin number on which CS is connected
     * @param samples Number of samples that will be gathered, and then taken average before it's returned
     * @returns Value read from mcp3208/mcp3204 ADC
     */
    int readADCAverage(int channel, int dinout, int clk, int cs, int samples)
    {
        int i, k = 0;
    
    
        for (i=0;i<samples;i++)
            k = k + readADC(channel, dinout, clk, cs);
        return k/samples;
    }
    

    Here is just the "delay" function:
    /**
     * @brief delay function, more precise on Propeller (?)
     */
    void delay(int us)
    {
        waitcnt(us*(CLKFREQ/1000000)+CNT);
    }
    

    I had to change all instances of "delay(10)" to "delay(15)" and then it worked fine. Actually, "delay(11)" worked, but I was afraid it might be unstable?

    I have just been writing in C with the default settings in SimpleIDE, so I am using whatever the default memory model is I guess. I don't understand what the benefits/drawbacks are of the various memory models are yet, but if you have some guidance on choosing a memory model I'd be happy to read it.

    Thank you!
  • DavidZemonDavidZemon Posts: 2,973
    edited 2014-08-03 10:23
    I only have one guess for your delay function. I don't have high hopes but it's worth trying:
    void delay(int us){
        waitcnt(us*(CLKFREQ/1000000L)+CNT);
    }
    
    It's possible the compiler is cutting 1000000 short and adding the 'L' will fix that.

    As for the memory models, LOTS of good info here. In short:

    COG = Entire program fits in a single cog and runs in native assembly
    Execution speed is fantastic. A surprising amount of code can fit in this memory model due to the lack of any overhead

    CMM = compressed memory model
    Execution speed ~ 40% slower than LMM
    Code size ~ 2kB + LMM/2

    LMM = long memory model
    Execution speed is close to COG with minimal overhead (negligible?)
    Code size is for a given program is similar to COG but with some overhead. Difference is, you get all 32kB of shared RAM instead of just a single COG's 2kB.

    XMM* = extended memory model
    Execution speed takes a huge hit
    Code size is no longer a concern because you now have access to a large EEPROM or flash memory (like an SD card)
  • jazzedjazzed Posts: 11,803
    edited 2014-08-03 10:32
    ChrisL8 wrote: »
    I had to change all instances of "delay(10)" to "delay(15)" and then it worked fine. Actually, "delay(11)" worked, but I was afraid it might be unstable?

    Whether or not the fpucog.c was being used may play a part in that because fpucog.c speeds up multiplication and division, but has been found to be unreliable in some cases with release_1_0 propeller-gcc.

    An experiment would be to move CNT. I'm not really sure when CNT is fetched in this case. You can find out for sure by looking at the ASM output for the file by opening the Project Manager panel, doing right-click on the file, and Show Assembly.

    SwimDude thanks for posting the memory model info.
  • ChrisL8ChrisL8 Posts: 129
    edited 2014-08-04 07:21
    I am using CMM, and I think that is probably the issue.

    I went ahead and used the code with "delay(15)" and, based on my testing, it doesn't not appear that increasing the time has any affect on the data retrieved from the MCP3208.

    I'm guessing the only "down side" is a reduction in the speed at which readings can be retrieved?

    Since I am only using this to get data from IR distance sensors, the speed is plenty fast enough with "delay(15)".

    Thanks for all of the input and help. I like understanding why something is happening, which I think you have explained.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2014-08-04 13:45
    ChrisL8 wrote: »
    I'm guessing the only "down side" is a reduction in the speed at which readings can be retrieved?

    That's exactly it. SPI is serial comms - you get one bit of data at a time. You're simply increasing the amount of time it takes to get each bit of data (decreasing clock frequency for the communication protocol). I skimmed the code as you posted it and i would bet you could run the whole program without any delays at all, but don't quote me on that. SPI devices are generally very fast - much faster than the Propeller.

    The code you're using looks like it will work beautifully and is definitely easy to use. If, however, you ever get to the point where you need more SPI devices or higher performance, let me know. I'm always looking for more people to test PropWare's peripherals.
  • tony68tony68 Posts: 1
    edited 2014-09-23 03:29
    ChrisL8,
    Hello, Are you sure of this part of your driver?
    { pinPulseHL(clk, 10, 0);
    AdcResult<<=1;
    AdcResult=AdcResult | (pinRead(dinout) & 0x01);
    delay(10); }
    with special attention to attempting to read the value "immediately" after pulling clock low (I understand that the last parameter of your pinPulseHL stands for zero delay after pulling clk low)

    I could enhance it, I think.
    I would rather think to first do the "delay 10" and only then attempt to read. Perhaps simply putting the delay into the "pinPulseHL" routine.
    I propose like this:
    { inPulseHL(clk, 10, 10);
    AdcResult<<=1;
    AdcResult=AdcResult | (pinRead(dinout) & 0x01);
    }

    This way you increase certainty of output becoming the new value you intend to read. Then, you may proceed to the next CLK HI without further delay.
    Basically, I'm referring to the "t DO" (="Clock Fall to Output Data Valid") per MCP's specification, Figure 1-1. I think you need to wait at least 200ns (nanosecs) which is specified as the max time needed for MCP to react to Clk low and effectively change its output. And then, cycle can be finished (of course, the Min Clk Lo time as the known 250 nanosec will apply).
    In other words, "...data is output on the falling edge oth the clock..." per specs - which I believe is intended for reading near the rising edge of the clock.

    What do you think?

    No doubt, the "AdcResult<<=1;" operation (including other potential processor instructions till actual reading) takes some time, probably more than 200nanos, this way the original code works on some MCU.

    fyi - I managed to achieve +/-0.3 (less than 1) digit accuracy with mach.lang code on an old SC-62015 proc, using delays ~half of the 10microsecs here. I had other problem though, that's why I'm reading here.
Sign In or Register to comment.