Passing data between objects
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?
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
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.
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
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.
VariablePassingTest.spin
TopObject.spin
Where am I going wrong in my understanding?
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.
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?
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:= RangeRoutineI'll give this a try and post back here in a bit.
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 resultI 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.
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] := childVariableDo 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 := @objectNameThe 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:
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.
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.
Your examples helped greatly and my application works now.
Thanks again for your kind help and offer for additional assistance!