Shop OBEX P1 Docs P2 Docs Learn Events
Running MCP3208.spin continously in a cog — Parallax Forums

Running MCP3208.spin continously in a cog

ErlendErlend Posts: 612
edited 2013-01-25 06:22 in Propeller 1
I want to structure my program such that in Main there are a number of global variables that hold I/O input values. Most of them are acquired by objects running in separate cogs, eg quad encoder, RTC, IR sensor, and ADC MCP3208. Therefore, I also want to have the MCP3208 continously running in a cog, do specific scaling of each input, and - a guess using LONGMOVE - the values in engineering units to the global values in Main. It could also - as I progress - be coded to do all sorts of things like monitoring levels, raise flags, sound warnings, etc.
e.g. PUB Main could do something like this:
IF gTemperature > 32
LCD("Human detected")

I am not good at assembly, and I need advice how to do this. Maybe it can even be done in the spin part of the objects code.
I guess there is no point in posting the obex code since it is part of the standard library.

Erlend

Comments

  • StefanL38StefanL38 Posts: 2,292
    edited 2013-01-18 00:44
    Hi Erlend,

    the MCP3208.spin starts one cog with a PASM-driver to read-out the ADC whenever you call method "In" or method "Average"

    To update variables continuosly you just start another cog with a method that has an infinite loop that is doing
    PUB DeliverADCValues
      repeat
        MyGlobalVar1 := ADC.In(1)
        MyGlobalVar2 := ADC.Average(2,50)
        etc
    

    Variables can be updated this way across cogs. variables of the VAR-section are accessible from all methods in the same *.SPIN-file
    where the varables are defined. It doesn't matter which cog is executing the method.
    here is a demo how this works
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
      
    VAR
      long CogStack1[ 20]
      long CogStack2[ 20]
      
      long MyTestVar
    
    OBJ
      debug : "FullDuplexSerial"
    
    
    PUB Main
    'the FIRST PUB-Method inside a spinfile is ALWAYS the startpoint where the program starts to run
      debug.start(31, 30, 0, 115200)
      
      MyTestVar := 100
      debug.str(string("Start MyTestVar="))
      debug.dec(MyTestVar)
      debug.Tx(13)
    
      'all methods and all cognew-commands are in the SAME file as the variables 
      'in this case you can access variables simply by the variable-name
      cognew(M1,@CogStack1)
      cognew(M2,@CogStack2)
      
      repeat
        waitcnt(clkfreq + cnt)
        debug.str(string("MyTestVar="))
        debug.dec(MyTestVar)
        debug.Tx(13)
    
    
    
    PUB M1
      repeat
        waitcnt(ClkFreq * 3 + cnt)
        MyTestVar := 1
    
    
    PUB M2
      repeat
        waitcnt(ClkFreq * 5 + cnt)
        MyTestVar := 2
          
    

    best regards
    Stefan
  • SRLMSRLM Posts: 5,045
    edited 2013-01-18 01:16
    I wrote an object that does almost exactly what you specify. It doesn't do any of the scaling, but if you wanted you could add that easily enough. What it does is read up to three MCP3208 chips on all 8 channels each (for a total of 24), and stores those values into the HUB. This is all done automatically, behind the scenes.

    ps: please don't duplicate post.
  • ErlendErlend Posts: 612
    edited 2013-01-18 08:04
    Stefan,

    So do I understand it correctly that your code start two new cogs to run M1 and M2, and since they are defined in the top level spin code, the adresses of the variables are passed along to the new cogs? -makes sense to me. And, in your first example the deliverADCvalues is defined in and object code to be a repeat-forever reading of ADC channels, so that in the main code I can do
    OBJ
    adc: "deliverADCvalues"
    PUB M1
    adc.start(gValue1, gValue2,gValue3,..gValue8)
    - is this correct?
  • ErlendErlend Posts: 612
    edited 2013-01-18 08:14
    SRLM
    Thanks, I will read your code and see if it makes me wiser. Reading and writing to the HUB is new to me though, and I am not sure I want to go there yet. I still believe that somehow using LONGMOVE from within an object running in an other cog is what I need. I am thinking something like adc_read_forever.start(@gValue1, gValue2...) early in my Main code.
    But how to do that?
  • JonnyMacJonnyMac Posts: 9,108
    edited 2013-01-18 09:09
    You can actually do this in Spin, depending on how fast you want to be able to read from the ADC. For a simple joystick application I did using code like this:
    pri mcp3208(p_analog) | ch, mux, level                          ' launch with cognew()
    
      outa[ADC_CS] := 1                                             ' output high
      dira[ADC_CS] := 1
    
      outa[ADC_CLK] := 0                                            ' output low
      dira[ADC_CLK] := 1
    
      repeat                                                        ' run forever
        repeat ch from 0 to 7                                       ' loop through channels
          outa[ADC_CS] := 0                                         ' activate adc  
    
          ' output mux bits, MSBFIRST
    
          dira[ADC_DIO] := 1                                        ' dio is output
          mux := (%11000 + ch) << (32-5)                            ' single-ended mode
          repeat 5                                                  ' send mux bits
            outa[ADC_DIO] := (mux <-= 1) & 1                        ' output a bit
            outa[ADC_CLK] := 1                                      ' clock the bit
            outa[ADC_CLK] := 0      
              
          ' input data bits, MSBPOST
           
          dira[ADC_DIO] := 0                                        ' dio is input
          level := 0                                                ' clear work var  
          repeat 13                                                 ' null + 12 data bits
            outa[ADC_CLK] := 1                                      ' clock a bit
            outa[ADC_CLK] := 0                                           
            level := (level << 1) | ina[ADC_DIO]                    ' input data bit
                                                                     
          outa[ADC_CS] := 1                                         ' de-activate adc
                                                                     
          long[p_analog][ch] := level & $FFF                        ' update results array
    


    The parameter required is a pointer (the hub address of) the array (or group of eight longs) to store the readings. This method can be launched like this:
    cognew(mcp3208(@pots), @adcstack)
    


    You also need to define an array for the stack. As this method doesn't call anything outside itself and only has one parameter and three locals, 16 longs should be enough.


    * Follow-up: I did a little timing test and on an 80MHz system that loop that reads all eight channels and reports them to the hub takes about 5.6ms.
  • HarpritHarprit Posts: 539
    edited 2013-01-18 10:09
    Here is the code for reading 4 potentiometers from a 3208 in PASM, fully documented
    It is from my forthcoming book for beginners.
    You can run this program as a stand alone.
    The read routine can be called as a method from outside this Object
    Much more will be in my book for beginners.
    {{
    022 PASM Read4Pots.spin
    Program to read 4 pots
    Installs in separate cog.
    Reads resistances to 12 bits appears in P_Val global variables
    15 Dec 2011
    By Harprit Sandhu
    }}
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
      bits=12
      pots=4
      
    VAR
      long P_Val[4]      'there will be 4 variables starting at P_Val[0]
      long  pot_sub      'subscript variable for identifying potentiometers
      
    OBJ                                                            
      fds : "FullDuplexSerial" 
      
    PUB Main|clear                            'clear is a local variable here
    fds.start(31,30,0,115200)           'start console at 115200 for debug output       
    Read4Pots (@P_Val)                  'start new cog at "generate", read var to P_Val
    waitcnt(clkfreq/60 +cnt)
    dira[0..1]~~                        'set pin directions to outputs
    fds.tx(16)                          'clear screen
      repeat                            'endless loop 
        pot_sub:=0                      'reset id of pot to be printed
        fds.tx($1)                      'home to 0,0
        repeat pots                     'loop to display data
          fds.bin(P_val[pot_sub], bits) 'print to the PST in binary                                 
          fds.str(string(" "))          'spaces to erase old data overflow             
          fds.dec(P_val[pot_sub])       'print value as decimal value     
          fds.str(string(" "))          'spaces to erase old data overflow             
          fds.dec(pot_sub)              'print value as decimal value     
          fds.str(string(" "))          'spaces to erase old data overflow
          fds.tx($d)                    'new line
          pot_sub:=(pot_sub)+1          'increment display counter
        waitcnt(clkfreq/60+cnt)         'flicker free wait
        clear:=clear+1                  'increment counter. This routine clears up screen
        if clear>10                     'decision point     by erasing extra lines of bot
          fds.tx(16)                    'clear screen       of PST display every 10 loops
          clear:=0                      'reset counter
          
    '----------------------------------end of first Cog-----------------------------------
    PUB Read4Pots(value)
      cognew(@Read_Pots, value)
                                                                                          
    '----------------------------------end of second Cog-----------------------------------
    DAT           org       0                        'sets the starting point in Cog
    Read_Pots     'first set up all the counters and addresses needed
                  mov       pots_read, #4           'number of pots to read [also change data] 
                  mov       pot_id,    #0           'first pot read is pot 0
                  mov       bits_read, #12          'bit resolution of 3208 IC, can be less
                  mov       mem,       par          'get address of mem for PAR
                  mov       dira,      set_dira     'sets direction of the prop pins 
                                             
    Next_pot      or        outa ,   chs_Bit        'makes Chip select high
                  andn      outa,    chs_Bit        'makes chip select low 
                  andn      outa ,   clk_bit        'ANDN it with the Clock Bit to make low
                  or        outa ,   din_Bit        'makes the Din high
                  call      #Tog_clk                'toggle clock line hi-low to read data
                  or        outa ,   din_Bit        'makes the Din high 
                  call      #Tog_Clk                'toggle clock line hi-low to read data
                  
                  mov       temp2,     pot_id       'we will read three bits from this pot
                  call      #get_third_bit          'get bit
           if_nz  jmp       #its_one                'jump to set it as needed
                  jmp       #its_zero               'jump to set it as needed
    its_one       call      #Set_next_bit1          'setting bit subroutine call
                  jmp       #continue1              'go to next bit
    its_zero      call      #Set_next_bit0          'setting bit subroutine call
    continue1     call      #get_second_bit         'get bit
           if_nz  jmp       #its_one1               'jump to set it as needed
                  jmp       #its_zero1              'jump to set it as needed
    its_one1      call      #Set_next_bit1          'setting bit subroutine call
                  jmp       #continue2              'go to next bit
    its_zero1     call      #Set_next_bit0          'setting bit subroutine call
    continue2     call      #get_first_bit          'get bit
           if_nz  jmp       #its_one2               'jump to set it as needed
                  jmp       #its_zero2              'jump to set it as needed
    its_one2      call      #Set_next_bit1          'setting bit subroutine call
                  jmp       #continue3              'go to next bit
    its_zero2     call      #Set_next_bit0          'setting bit subroutine call
    
    continue3     andn      outa ,   din_Bit        'makes Din low              
                  call      #Tog_Clk                'toggle clock line hi-low to read data
                  call      #Tog_Clk                'toggle clock line hi-low to read data                        
                  mov       Data_read,  #0          'clear register we will read data into             
                  mov       bit_count, bits_read    'counter for number of bits we will read
    read_bit      mov       temp,     ina           'read in what is in all the input lines
                  andn      temp,     mask26 wz     'mask off except Dout line. Set Z flag    
                  shl       Data_read,  #1          'shift register left 1 bit for next bit
            if_nz add       Data_read,  #1          'if value + add 1 to data register    
                  call      #Tog_Clk                'toggle clock get next bit ready in Dout
                  sub       bit_count, #1 wz        'decr the "bits read" counter. Set Z flag
            if_nz jmp       #read_bit               'go up do it again if counter not yet 0 
                  wrlong    Data_read,  mem         'write it in PAR to share it as P.Val
                  add       mem,  #4                'add 4 to hub address for next long
                  add       pot_id, #1              'so we can look at next pot
                  mov       temp2, pot_id           'recall what pot we are reading
                  sub       temp2, pots_Read   wz   'check if it is how many we want to read
          if_nz   jmp       #Next_pot               'if it is not 0 go up and read next pot
                  jmp       #Read_pots              'go back beginning and do all pots again
                 
    'subroutines used
    Set_next_bit0  andn     outa ,    din_Bit       'makes Din low in 000 for line
                  call      #Tog_Clk                'toggle clock line hi-low to read data              
    Set_next_bit0_ret       ret                     'return from this subroutine
    
    Set_next_bit1 or        outa ,    din_Bit       'makes Din high in 000 for line
                  call      #Tog_Clk                'toggle clock line hi-low to read data              
    Set_next_bit1_ret       ret                     'return from this subroutine
    
    Tog_Clk       nop                               'nop to settle signals
                  or        outa,      clk_bit      'make clock bit high
                  nop                               'nop to settle signals
                  andn      outa,      clk_bit      'make clock bit low
                  nop                               'nop to settle signals
    Tog_Clk_ret   ret                               'return from this subroutine         
    
    Get_first_bit  mov      temp2,     pot_id       'get current pot number
                   andn     temp2,     mask0 wz     'get last bit
    Get_first_bit_ret       ret                     'return
    
    Get_second_bit mov      temp2,     pot_id       'get current pot number
                   shr      temp2,     #1           'shift right 1 bit to get second bit
                   andn     temp2,     mask0 wz     'return   
    Get_second_bit_ret      ret                
    
    Get_third_bit  mov      temp2,     pot_id       'get current pot number
                   shr      temp2,     #2           'shift right 2 bits to get third bit
                   andn     temp2,     mask0 wz     'return 
    Get_third_bit_ret        ret                               
     
    Set_dira      long      001011_00000000_00000000_00001111   'Set dira register                                 
    Chs_Bit       long      000001_00000000_00000000_00000000   'Chip select bit     24
    Din_Bit       long      000010_00000000_00000000_00000000   'Data in bit         25
    Dout_Bit      long      000100_00000000_00000000_00000000   'Data out bit        26
    Clk_Bit       long      001000_00000000_00000000_00000000   'Clock bit           27
    mask26        long      111011_11111111_11111111_11111111   'Mask to read Dout bit 26
    mask0         long      111111_11111111_11111111_11111110   'Mask to read bit 0                                                                                         
    
    mem            res      1       'Par location
    temp           res      1       'temporary storage variable, misc
    temp2          res      1       'temporary storage variable, misc
    bit_count      res      1       'temporary storage variable, read bit counter
    Data_Read      res      1       'temporary storage variable, data being read 
    pot_id         res      1       'current pot id number
    pots_read      res      1       'total number of pots to be read.
    bits_Read      res      1       'resolution of data read, 12 bits max
    
    

    Harprit
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2013-01-18 10:46
    You could also use my object (in my signature) which dedicates a cog to collecting ADC info, you supply it some variable to store the values and you can access the information however you want.
  • ErlendErlend Posts: 612
    edited 2013-01-19 01:50
    This is excellent help. I am studying all the code posted, and think I am getting a grasp of it. Because I like to stay in a high level language, I will take JonnyMac's code and tweak it: Change the "long[p_analog][ch]:= level & $FFF" into a CASE structure which does scaling, flag setting, and LONG[nameofVariable] - instead of using an array. I guess this code could also be put in an Object, but then is should be coded as PUB start, instead of PRI mcp3208 like this code is? Maybe I need to go back to the book (Programming and customizing...Parallax) and read again.
    Think I will manage now. Thanks to all of you!

    Btw, all AI's are slow-changing, so speed is not an issue.
  • JonnyMacJonnyMac Posts: 9,108
    edited 2013-01-19 11:19
    I guess this code could also be put in an Object

    I'll save you the trouble (see attached) as I pulled that example from an object I use. Now... I am of the opinion that when you create an object it should be very general-purpose -- don't put specifics in it for one program that will just get in the way of another. Also, an array is simply a block of variables. My friend uses this object in a professional pan/tilt/dolley camera controller. In the variables section of his program we have this:
    long  joyx
      long  joyy
      long  joyz
      long  speedpot
      long  ramppot
    


    We instantiate the adc object like this:
    adc.startx(ADC_CS, ADC_CLK, ADC_DIO, 5, @joyx)
    


    As you can see, I'm telling it how many channels to read and pointing to the first variable in a list; I use those names in the program.
  • ErlendErlend Posts: 612
    edited 2013-01-19 12:26
    Trouble saved. I didn't think of that LONGs declared after each other would be in a contigous block. I fully agree that objects should be general-purpose, but I don't see how I can do the scaling and stuff wich is specific to each channel, without running it in the same cog. Shure, I can put all of that into one PRI method - which is called (by your code, in your object) just before returning a result, but it would still mean messing with a general purpose object - or I can similarily put it into another Object below the MCP3208 object, but still a modification is required. Hmm.
    What about if I extend your object with something like PRI (1) EU_eTape, (2) EU_lm34, (3)EU_dsspd300, etc. Make it into a (almost) general purpose object with conversion of raw adc into engineering units? To control which channel gets what kind of conversion, the adc.startx() could pass 8 parameters that specifies conversion method for each channel, e.g. adc.startx(ADC_CS, ADC_CLK, ADC_DIO, 8, @firstvalue, 1, 2, 2, 1, 3, 3, -1, -1) - and in this instance the two last channels gets no conversion, with parameter= -1. Obviously these parameters can be packed into one array too.
    I appreciate the help, and also all advice on 'coding ethics'.
  • JonnyMacJonnyMac Posts: 9,108
    edited 2013-01-19 19:03
    I didn't think of that LONGs declared after each other would be in a contigous block.

    The compiler organizes RAM by type, starting with longs, then words, then bytes. Within a type (e.g., longs) the variables are put in the order they appear in your listing.

    What about if I extend your object...

    You're free to do anything you like, of course, but if you decide to republish your modifications please take my name off of the listing. There's nothing in it that I feel the need to have credit for, but I don't like my name on listings that I would not have produced myself. If you want the listing to say "based on code by Jon McPhalen" then that is fine.

    Another idea you could use for your new object: instead of passing so many parameters you could past the address of a DAT block that has your conversion values (I'm still not clear on what you're doing with that). Let's say, for example, you just want multiply by some value. You could express those values in 0.001 units and do something like this at the end of the adc loop:
    long[p_adcvals][ch] := (level & $FFF) * long[p_multiplier][ch] / 1_000
    


    After cleaning-up the newly read level, it grabs the adjustment factor from the table (p_multiplier is a pointer to it), then divides by 1000 to get back to whole units. By doing it this way you still have a general-purpose object and the program (you or whomever you share it with) can adjust the behavior by pointing to their own conversion table; building a table into the object is not a good idea in my opinion.
  • ErlendErlend Posts: 612
    edited 2013-01-25 06:22
    JonnyMac wrote: »
    Another idea you could use for your new object: instead of passing so many parameters you could past the address of a DAT block that has your conversion values(..)
    I like this idea. Have finally managed to put togheter a small set of methods for scaling, offsetting, linearizing, etc. Will test them out, using the @DAT way of passing parameters.Here are the parameters for a full-featured set - I think:

    ADCbit resolution
    Scaling: positve means multiply, negative means divide
    Offset: in scaled units
    Linearisation amount log e: negative means inverse
    Linerarisation amount log 10: negative means inverse
    Averaging period: positive means in n samples, negative means in n mS
    Noise removal filter: percent of outlier samples removed from average
    FloorL: minimum value to be allowed
    FloorH: maximum level to be allowed
    Time tagging y/n - seconds since yr 2000 ( will be put in msw)

    -have I forgot anything?
Sign In or Register to comment.