Shop OBEX P1 Docs P2 Docs Learn Events
Passing data between objects — Parallax Forums

Passing data between objects

Penguin-fachPenguin-fach Posts: 11
edited 2011-12-22 15:05 in Propeller 1
I have been working through the Propeller labs manual and a couple of other guide books but am finding the explantion of variables fragmented and poorly drawn together. I am sure that this will be basic stuff to everyone else but I am having trouble getting my head around the correct way to pass values between various objects.

Here is a stripped down example that hopefully indicates where I am at.

My top object references another object called 'Switch.spin' that will continuously scan a set of key switches and other input devices in another cog. The first method in the top object calls the start method in this second object.


Top Object

OBJ
scan : "Switch"

PUB main
scan.start
repeat
other stuff happens here

The start method in the 'Switch object' begins by launching a new cog, sets the data direction for the input keys and starts scanning and debouncing (only 1 switch here).

CON
Switch = 1

VAR
long stack[30], cog_okay,

OBJ
delay: "Timing"

PUB start
cog_okay := cognew(ScanSwitch, @stack)

PUB ScanSwitch : Run
dira[Switch]~
repeat
if ina[Switch] == 1 & Run == 0
delay.pause1ms(20)
if ina[Run_Stop_SW] == 1
Run := 1
else if ina[Run_Stop_SW] == 0 & Run == 1
delay.pause1ms(20)
if ina[Run_Stop_SW] == 0
Run := 0

My questions are these.

1. Are global variables declared in the top object accessible to any daughter objects (like 'Switch' or 'Timing')? What about the other way around: can the top object see global variables declared in daughter objects?
2. Have I correctly used the local declaration of the 'Run' variable in ScanSwitch so that the value of this variable would be visible and useable in the top object?
3. Am I correct in thinking that if Scan_Switch were a private method then access is restricted to only the local object and values cannot be passed to another object?
4. If a method were to appear as follows,

PUB ScanSwitch (pin, debounce) : Run | Temp

then am I correct in thinking that the first two values, 'pin' & 'debounce' a local vraibles that nevertheless can be passed in when the method is called, 'Run' is a local variable whose value can be passed back and Temp is a local variable only accessible to the method?

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2011-12-22 07:52
    1) No. None of the named things (variables, constants, methods, other objects) are available in a "child" object.

    2) No. Local variables are local. Their names are only available from within the method. They're allocated in the stack when the method is called and cease to exist when the method exits.

    3) Sort of. A PRI method is known only to the object containing it.

    4) Mostly. 'pin' and 'debounce' are parameters, local variables whose initial values are provided when 'ScanSwitch' is called. 'Run' is an explicit name for the return value. The default name for the return value is RESULT and it's always there whether used or not. It's initialized to zero when the method is called. If the method call is used as a function call, this value is the value of the function call when the method exits. As you mentioned, 'Temp' is local to the method.
  • Penguin-fachPenguin-fach Posts: 11
    edited 2011-12-22 09:39
    Ok, I see how to use 'Result' to return a value and how to use an alias to name the return value as something more useful. I can also see how to pass parameters to the method and am familiar with doing this.

    However in my particular circumstance I have a method in child object that writes three values. Briefly my top object launches a start method from the child object which launches a new cog which then repeats a sequence of three methods. Each of these methods generates a value that needs to be accessible to the top object. I can think of a couple of ways to get around this but none of them are convenient.

    So to clarify, I can pass multiple parameters from a method in the top object to a method in a child object. How do I pass multiple values back?

    Regards, Simon
  • Mike GreenMike Green Posts: 23,101
    edited 2011-12-22 10:23
    The type of parameter passing used in Spin is called 'pass by value'. What you want is 'pass by reference'. This can be done simply by passing (as the value) the address of the variable you want to use. The method becomes:
    PUB sendValueBack( theAddress )
       long[ theAddress ] := someKindOfValue
    
    The caller uses:
    VAR long aLongVariable
       ...
       sendValueBack( @aLongVariable )
    
    If the variable is a word or byte, you can use word or byte in both cases instead of long.

    Another related technique is to pass the address of the first element of an array where each element of the array is a value to pass either into or out of the object. You can use 'long[ address ][ index ]' to access the index'th element of the array.

    Last technique is to use the start method of the object and pass one or more addresses into the object as parameters to the start method which saves the addresses in variables within the object. The object then uses those addresses to pass information back to the caller as a 'side-effect' of the use of the object.
  • Penguin-fachPenguin-fach Posts: 11
    edited 2011-12-22 13:34
    I think I get the gist of what you are describing but I know that I have this wrong and I suspect that I need to use the parameter list option of COGNEW

    VariablePassingTest.spin

    TopObject.spin

    Where am I going wrong in my understanding?
  • S11D336BS11D336B Posts: 14
    edited 2011-12-22 13:54
    I think you're making it a bit too complicated. You can share variables using the stack, but you shouldn't unless your method is in assembly.

    Look at this test code I wrote for you.

    You simply need to declare a hub memory variable in your Object (hub memory variables can be accessed by any cog). Then you need to share that variable using helper methods.

    You can do this one of 2 ways. I have demonstrated both ways in the sample code attached to this post.

    I think I get the gist of what you are describing but I know that I have this wrong and I suspect that I need to use the parameter list option of COGNEW

    VariablePassingTest.spin

    TopObject.spin

    Where am I going wrong in my understanding?
  • Penguin-fachPenguin-fach Posts: 11
    edited 2011-12-22 15:05
    OK, I think the aged fog in my brain is clearing. Let me see if I have this right.

    1. Top object launches a start method in a child object into a new cog.
    2. New cog runs whatever processes are required (my code is grossly simplified in my original example) and return values can be passed to global vars declared in the child object.
    3. Global vars declared in separate objects are not directly accessible to each other but helper methods can be used either to pass the address of the variable or pass the value itself indirectly. These helper methods do not themselves run within the new cog but are in the child object and so have direct access to the global variables in that object.

    Does this sound right?
  • Mike Green wrote: »
    The type of parameter passing used in Spin is called 'pass by value'. What you want is 'pass by reference'. This can be done simply by passing (as the value) the address of the variable you want to use. The method becomes:
    PUB sendValueBack( theAddress )
       long[ theAddress ] := someKindOfValue
    
    The caller uses:
    VAR long aLongVariable
       ...
       sendValueBack( @aLongVariable )
    
    If the variable is a word or byte, you can use word or byte in both cases instead of long.

    Another related technique is to pass the address of the first element of an array where each element of the array is a value to pass either into or out of the object. You can use 'long[ address ][ index ]' to access the index'th element of the array.

    Last technique is to use the start method of the object and pass one or more addresses into the object as parameters to the start method which saves the addresses in variables within the object. The object then uses those addresses to pass information back to the caller as a 'side-effect' of the use of the object.

  • Hi,

    When an initial object (Master Control Progam) activates a new cog to run a new object ( Program for Cog1), I am having a tough time comprehending how to pass values from the new cog object back to the initial cog. Mike Green above was kind to provide a simple explanation along with code, but I must be pretty dense on this issue as I have tried various code lines in both the initial calling object as well as in the activated cog, but I am missing the point. The activated cog monitors a sensor that takes continuous readings and I would like to continuously update the main calling object with these readings so that calculations may be performed on them. Below are stipped down versions of these two objects. If someone would kindly insert the correct format for the calling object to receive the updated range values as well as the code to send these values I would greatly appreciate it. Thank you

    Master_Control_Program
    
    
    OBJ
        
        Sensor1    :  "Z_Program_for_COG1"
    
    
    PUB MAIN
    
    
       Sensor1.Start(7,6,5)
    
    

    
    VAR
            
              LONG      Stack1[256];
    
    
    PUB sendValueBack( theAddress ) 
    
              LONG[ theAddress ] := SomeValue 
     
    
    PUB Start ( first_val_for_Init, second_val_for_Init, third_val_for_Init )
        'Start new range process in a new cog
    
         cognew( Main( first_val_for_Init, second_val_for_Init, third_val_for_Init ), @Stack1 )
         
    PUB Main(first_val_for_Init, second_val_for_Init, third_val_for_Init)  |  range                                                                 
    
    
        REPEAT                                                                        
    
          range:= RangeRoutine
    
    
  • robotics wrote: »
    If someone would kindly insert the correct format for the calling object to receive the updated range values as well as the code to send these values I would greatly appreciate it.

    I'll give this a try and post back here in a bit.

  • Duane DegnDuane Degn Posts: 10,588
    edited 2017-01-14 20:18
    Here's a relatively simple program which illustrates multiple was a parent and child object can communicate.

    The program starts out waiting for the using to press a key.
    PUB MAIN
    
      Pst.Start(BAUD)
       
      repeat
        result := Pst.RxCount
        Pst.Str(string(11, 13, "Press any key to begin."))
        waitcnt(clkfreq / 2 + cnt)
      until result
    

    I like to add this feature to programs which require serial input. This way I can be sure the user didn't miss any output between the time they loaded the program and the time they opened a terminal window.

    The parent has four global variables.
    VAR
    
      long topObjectValueA
      long topObjectValueB
      long readFromChild
      long childCog
    

    The "childCog" variable just stores the cog number returned from the child's "Start" method. This variable is written a single time and then read each loop of the main program.

    The "readFromChild" variable is only written when the user presses the "c" key. This variable is stores the value read from the child's "GetValueFromChild" method.

    The address of the variable "topObjectValueA" is sent to child object as part of the "Start" method. The program offers the user the opportunity to have the child write date to either "topObjectValueA" or "topObjectValueB".

    The child object increments both in internal variable "childVariable" and it also increments a long at an address provided by the parent.
    PRI Count | timer
    
      timer := countInterval + cnt
      repeat
        waitcnt(timer)
        timer += countInterval
        childVariable++
        long[parentVariableAddress]++
    

    If you experiment with this demo program, you'll find the code :
        childVariable++
        long[parentVariableAddress]++
    

    is very different from the following:
        childVariable++
        long[parentVariableAddress] := childVariable
    

    Do you see why? (This difference surprised me initially.)

    There is usually more than one way to accomplish a task when programming. This example program uses multiple ways to retrieve data from a child object. Normally one would use just one of the techniques shown.

    In my opinion the technique shown by the method "GetValue" is the technique most frequently used.

    Normally one doesn't have a child object write data to a variable in the parent object unless the child object is running PASM.

    Of course there are many exceptions to this general trend.

    I'm attaching an archive of the parent object but I'll also post the code inline here.
    DAT programName         byte "ParentObjectExample170114a", 0
    CON '' Version Notes
    {{
      170114a Program to demonstrate ways to pass data back and forth between parent and child
      objects.
      
    }}
    CON
    
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000                                    
    
      ' Below is a trick to define a second based on the clock settings.
      SECOND = ((_clkmode - xtal1) >> 6) * _xinfreq
      MILLISECOND = SECOND / 1000
    
      MAX_INTERVAL = 26000 ' in milliseconds
      
      BAUD = 115200
      QUOTE = 34
      
    VAR
    
      long topObjectValueA
      long topObjectValueB
      long readFromChild
      long childCog
      
    OBJ
        
        Child : "ChildExample170114a"
        Pst : "Parallax Serial Terminal"
    
    PUB Start
    
      Pst.Start(BAUD)
       
      repeat
        result := Pst.RxCount
        Pst.Str(string(11, 13, "Press any key to begin."))
        waitcnt(clkfreq / 2 + cnt)
      until result
       
      Pst.RxFlush ' Clear out the character just entered.
       
      childCog := Child.Start(SECOND, @topObjectValueA)
       
      Pst.Clear
     
      MainLoop
    
    PRI MainLoop
    
      repeat
        MainDisplay
        CheckForInput
        
    PRI CheckForInput
    
      result := Pst.RxCount
    
      if result
        result := Pst.CharIn
        case result
          "a":
            Child.SetOneTimePointer(@topObjectValueA)
          "A":
            Child.ChangeParentPointer(@topObjectValueA)
          "b":
            Child.SetOneTimePointer(@topObjectValueB)
          "B":
            Child.ChangeParentPointer(@topObjectValueB)
          "c", "C":
            readFromChild := Child.GetValue
          "i", "I":
            ChangeCountInterval  
        
    PRI MainDisplay
    
      Pst.ClearEnd
      Pst.NewLine
      Pst.ClearBelow
      Pst.Home
      Pst.Str(string(11, 13, "Parent and child object example program."))
      Pst.Str(string(11, 13, "Parent Object's Name = ", QUOTE))
      Pst.Str(@programName)
      Pst.Char(QUOTE)
      Pst.Str(string(11, 13, "Child Object's Name = ", QUOTE))
      Pst.Str(Child.GetObjectName)
      Pst.Char(QUOTE)
      Pst.Str(string(11, 13, "The child object is running in cog # "))
      Pst.Dec(childCog)
      Pst.Char(".")
      Pst.Str(string(11, 13, "This top object is running in cog # "))
      Pst.Dec(cogid)
      Pst.Char(".")
      Pst.ClearEnd
      Pst.NewLine
      Pst.Str(string(11, 13, "press ", QUOTE, "a", QUOTE, " to load count to "))
      Pst.Str(string(QUOTE, "topObjectValueA", QUOTE, " one time."))
      Pst.Str(string(11, 13, "press ", QUOTE, "A", QUOTE, " to send address of "))
      Pst.Str(string(QUOTE, "topObjectValueA", QUOTE, " to the child object."))
      Pst.Str(string(11, 13, "press ", QUOTE, "b", QUOTE, " to load count to "))
      Pst.Str(string(QUOTE, "topObjectValueB", QUOTE, " one time."))
      Pst.Str(string(11, 13, "press ", QUOTE, "B", QUOTE, " to send address of "))
      Pst.Str(string(QUOTE, "topObjectValueB", QUOTE, " to the child object."))
      Pst.Str(string(11, 13, "press ", QUOTE, "C", QUOTE, " to update value of "))
      Pst.Str(string(QUOTE, "readFromChild", QUOTE, " variable."))
      Pst.Str(string(11, 13, "press ", QUOTE, "I", QUOTE, " to change count interval"))
      Pst.Str(string(11, 13, 11, 13, "topObjectValueA = "))
      Pst.Dec(topObjectValueA)
      Pst.Str(string(11, 13, "topObjectValueB = "))
      Pst.Dec(topObjectValueB)
      Pst.Str(string(11, 13, "readFromChild = "))
      Pst.Dec(readFromChild)
      Pst.Str(string(11, 13, "Child.GetInterval (converted to milliseconds) = "))
      Pst.Dec(Child.GetInterval / MILLISECOND)
      Pst.Str(string(" ms"))
       
    PRI ChangeCountInterval
    
      Pst.Str(string(11, 13, 11, 13, "Please enter new interval time in milliseconds:"))
      result := Pst.DecIn
      Pst.Str(string(11, 13, "You entered ", QUOTE))
      Pst.Dec(result)
      Pst.Str(string(QUOTE, " milliseconds."))
        
      if result > 0 and result < MAX_INTERVAL
        Child.SetInterval(result * MILLISECOND)
        Pst.Str(string(11, 13, 11, 13, "New interval has been set."))
      else
        Pst.Str(string(7, 11, 13, 11, 13, "Interval out of range. Try again.", 7))
        
      waitcnt(clkfreq * 2 + cnt)
    

    Here's the child object:
    DAT objectName         byte "ChildExample170114a", 0 ' remember to add a terminating zero
    CON '' Version Notes
    {{
      170114a This object increments a count and stores the count value in variables defined
      by both this object and the parent object.
      
    }}
    CON
    
      
    VAR
    
      long stack[64]
      long childVariable
    
      'These two variables need to be kept in this order.
      long countInterval
      long parentVariableAddress
      
    PUB Start(clockCyclesPerIncrement, parentPtr)
    
      ' Below is a shortcut to assign multiple longs at once.
      ' This only works is the variables are longs.
      longmove(@countInterval, @clockCyclesPerIncrement, 2)
    
      ' If you don't understand the line of code above you could instead use the following:
      'countInterval := clockCyclesPerIncrement 
      'parentVariableAddress := parentPtr
    
      result := cognew(Count, @stack)
      ' Note many objects add one to the cog number returned.
      ' This example does not add one so if the returned value
      ' is negative one, then a cog was not available.
      ' When one is added, a return value of zero indicates no
      ' cog is available.
      
    PRI Count | timer
    
      timer := countInterval + cnt
      repeat
        waitcnt(timer)
        timer += countInterval
        childVariable++
        long[parentVariableAddress]++
        
    PUB ChangeParentPointer(newAddress)
    
      parentVariableAddress := newAddress
    
    PUB GetValue
    
      result := childVariable
    
    PUB SetOneTimePointer(oneTimePointer)
    
      long[oneTimePointer] := childVariable
    
    PUB GetAddressOfChildVariable
    
      result := @childVariable
    
    PUB GetInterval
    
      result := countInterval
    
    PUB SetInterval(newInterval)
    
      countInterval := newInterval
      
    PUB GetObjectName ' A method I like to add to keep track of versions.
    
      result := @objectName
    

    The program looks more complicated than it is. Most of the code takes care of displaying the data and options for the user. Don't let the serial output section intimidate you.
    Here's some example output from the program:
    Parent and child object example program.
    Parent Object's Name = "ParentObjectExample170114a"
    Child Object's Name = "ChildExample170114a"
    The child object is running in cog # 2.
    This top object is running in cog # 0.
    
    press "a" to load count to "topObjectValueA" one time.
    press "A" to send address of "topObjectValueA" to the child object.
    press "b" to load count to "topObjectValueB" one time.
    press "B" to send address of "topObjectValueB" to the child object.
    press "C" to update value of "readFromChild" variable.
    press "I" to change count interval
    
    topObjectValueA = 134
    topObjectValueB = 115
    readFromChild = 65
    Child.GetInterval (converted to milliseconds) = 1000 ms
    

    Let me know if you have any questions.

    I'm certainly willing to help with code specific for your project but I thought I'd start out with this general code showing multiple ways of doing the same thing.
  • Wow!!!

    Many thanks Duane! There is certainly a lot to digest. I'l be going through it tomorrow and will let you know if any questions.

    Thank you again for your great detail.
  • Hi Duane,

    Your examples helped greatly and my application works now.

    Thanks again for your kind help and offer for additional assistance!
  • You're welcome.
  • Yup Duane, well done and thanks for a very complete and clear explanation and reference for variable-cog handling.
Sign In or Register to comment.