PDA

View Full Version : Right way to share memory



Steve Hicks (N5AC)
11-06-2006, 02:46 PM
I have several situations in which I want to share data between objects and cogs, but I'm not sure of the "right" way to do this... should I create the variables at the top level and pass them down into objects?· For example, I have· GPS object that is going to collect a number of pieces from the GPS and store/update them in memory.· I hate to pass a dozen variables in and out of the object... should I put the variables in the GPS VAR declaration and create set/get routines for all of them? Should I define a common block (structure) in memory and define it in both places along with an address so they are "overlaid" and handle it like this?

Thanks,
Steve

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Steve, N5AC

cgracey
11-06-2006, 04:18 PM
I find it most useful to place shared variables in the object that is going to be manipulating them the most. You can do it however you want, of course, and you'll figure out the best way. I don't believe there is a universal "right" way, though, since cases vary and each requires some thought. Just experiment and you'll get there. If it feels like you are taking 2 steps forward and 1 step back a lot of the time, that's normal.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Chip Gracey
Parallax, Inc.

Post Edited (Chip Gracey (Parallax)) : 11/6/2006 8:53:46 AM GMT

parsko
11-06-2006, 04:59 PM
Steve,

I've thought long and hard about this myself. From the past few months, I have determined one of the better, and seemingly more common, techniques is to pass the location of a block of memory (using the "@" symbol).

AKA
Let's say that you've got 16 arbitrary variables you want shared amonst different cogs, some SPIN, some PASM. You "create" a memory location in your primary cog.



var
long Memoryblock[16]




When starting new cogs, send the address, like:



cognew (@table, @Memoryblock)



One main issue is cross-cog clobbering of variable (reading the same variable by two cogs = bad). You can avoid this due to one main important factor; as long as the variable is ONLY being read by one COG and written by another COG will you have no problems, due to the HUB access window. Any more access than this will lead to problems. (you can read more about this in the manual)

Regardless, you can use this block of memory in all 8 Cog's provided you are clever. AKA- each actual memory location is only read by one cog and written by another (which seems to be the most common use). For instance, using a Cog to update an ADC value that is used to compute some output value in another cog. Now, if you set it up such that each memory location is being used via the rule I explained, you could assign your 16 locations like this:




long Memoryblock[16] 'an array 16 longs long
Memoryblck[0] 'Cog 1 counter
Memoryblck 'Cog 2 ADC value
Memoryblck 'Cog 3 ADC value
Memoryblck 'Cog 4 temperature value
.
.
.




That code is not literal, only conceptual.

As long as Cog0 is reading, and Cog1 is writing, you will always seemlessly be able to access Memoryblck[0].

There are other well documented techniques you can search for, this is only one, and works best when working with Cogs running aseembly code...

-Parsko

Steve Hicks (N5AC)
11-06-2006, 10:37 PM
I guess the thing I'm still missing is how to "share" the variable.· More literally, one way is this -- first in the object itself



VAR
long shared_variable

PUB Start
....

PUB getvariable : value
value := shared_variable

PUB setvariable(value)
shared_variable := value

and then in the calling routine:


PUB start
...
my_callers_variable := OBJ.getvariable

...
OBJ.setvariable(my_callers_variable)

This seems like common object oriented methodology and assumes a loosely coupled interaction between both modules·and maximum reusability.· I am currently using this and it works well.· It is rather tedious if you have a lot of variables and it's not terribly efficient and arguably a bit of overkill for a bit of·embedded software!

I do see the method for passing the address of a block of memory around, but it seems to preclude really typing or naming these in a way that makes the code very readable ... you end up referring to a variable as memoryblock[13] or something like that... what I'm wondering is: is there a better way?· For example, make a DAT section, use an ORG to go to a specific location (high memory?) and then define the variables in a subsequent variable block --- like an overlay of days gone by.· I tried this, and received the error that I couldn't go above $1F0 with my ORG... so I now understand that assembly ends up in cog memory, not main memory, so that's not going to work since cog memory is not shared.

Is there a way to specify the address that variables in a SPIN VAR block would be located?· Am I barking up the wrong tree?

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Steve, N5AC

Mike Green
11-06-2006, 11:13 PM
Steve,
Have a look at my Propeller OS <http://forums.parallax.com/forums/default.aspx?f=25&m=147980>. The initialization routine allocates a block of pointers at the end of RAM, then allocates work areas below that and puts pointers to the work areas in the block of pointers. Various I/O drivers (like display, keyboard, etc.) use those areas rather than VAR allocations. The offsets from the pointers are declared in a CON section in each object and referred to with the obj#const notation in other objects.
Mike

Cliff L. Biffle
11-07-2006, 12:07 AM
N5AC said...
This seems like common object oriented methodology and assumes a loosely coupled interaction between both modules and maximum reusability.


Spin will treat you better if you don't try to use traditional OO development practices. It's really more of a module-based language than an object-based one. With some features missing (passing of objects, structures, polymorphism) and without any optimizations in the compiler, practices like encapsulation and delegation are only going to slow you down.

I know, it bugs me too. http://forums.parallax.com/images/smilies/smile.gif

73s,
W7CLB

Steve Hicks (N5AC)
11-07-2006, 11:04 PM
OK, I've settled on a solution (largley based on Mike Green's idea).· I've decided to avoid the "getter/setter" of traditional object oriented fare and instead did it the following way ... here's the code fragments from the object:



CON

GPStime = 0
GPSlatitude = GPStime + 4
GPSlatitude_frac = GPSlatitude + 4
GPSlatitude_c = GPSlatitude_frac + 4
GPSlongitude = GPSlatitude_c + 1
GPSlongitude_frac = GPSlongitude + 4
GPSlongitude_c = GPSlongitude_frac + 4
GPSfix = GPSlongitude_c + 1
GPSsatellites = GPSfix + 1
GPSspeed = GPSsatellites + 16
GPScourse = GPSspeed + 1
GPSaltitude = GPScourse + 1
GPSlock = GPSaltitude + 4

VAR

{ GPS Data Structure --- DO NOT ALTER WITHOUT CHANGING CONSTANTS }
long time
long latitude
long latitude_frac
byte latitude_c
long longitude
long longitude_frac
byte longitude_c
byte fix
byte satellites[16]
byte speed
byte course
long altitude
byte lock

PUB init(rx,tx) : address
cog := cognew(start(rx,tx),@stack2) + 1
cog2 := cognew(watchdog,@stack3) + 1
address := @time



So there's a block of variables and then constants·are set to match the locations in memory.· They just have to be kept in sync.··The address of the first variable (start of the block) is then passed back to the consumer of the object.· Now a look at the consumer of the object:



VAR
long GPSbase

byte lock

OBJ
GPS: "GPS"


PUB Start

GPSbase := GPS.init(DX_I,DX_O)
lock = byte[GPSbase + GPS#GPSlock]


In the initialization call to the object, the address of the block of variables is stored in memory (GPSbase).· It is subsequently used as an offset when getting data out of the block of memory.

This seems to work fine, it's a good compromise and it's readable.· Note that the data I'm using is almost all longs or smaller -- the exception is the list of GPS satellites.· A lock (see LOCKNEW in Propeller manual v1.0 p230) may be need to be used in some situations where data larger than a long is being written by one entity and read by another (for example in this scenario, one satellite may drop off the start of the list as I'm reading the middle and the cascade ripple might cause me to lose one satellite as I read ... just an example).

Maybe this will help someone else struggling with the best way to do this!

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Steve, N5AC

cgracey
11-08-2006, 02:57 AM
Steve,

Here is a cleaner way to enumerate those offsets:

CON
· #0
· GPStime·········· [4]
· GPSlatitude······ [4]
· GPSlatitude_frac· [4]
· GPSlatitude_c
· GPSlongitude····· [4]
· GPSlongitude_frac [4]
· GPSlongitude_c
· GPSfix
· GPSsatellites···· [16]
· GPSspeed
· GPScourse
· GPSaltitude······ [4]
· GPSlock


▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔



Chip Gracey
Parallax, Inc.

Cliff L. Biffle
11-08-2006, 03:01 AM
Chip,

Is it just me, or is that some undocumented syntax you just posted?

Glad I'm not trying to write a SPIN parser, with these surprises. http://forums.parallax.com/images/smilies/smile.gif

cgracey
11-08-2006, 03:33 AM
Cliff L. Biffle said...
Chip,

Is it just me, or is that some undocumented syntax you just posted?

Glad I'm not trying to write a SPIN parser, with these surprises. http://forums.parallax.com/images/smilies/smile.gif
Cliff, it must be in the English manual, because it was in that castellano manual you found on Google:

CON
#0, None, Left, Right, Front, Back ‘Enumeracion de Direcciones


▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Chip Gracey
Parallax, Inc.

Phil Pilgrim (PhiPi)
11-08-2006, 03:35 AM
Cliff,

Check the manual, pages 197ff. (The only thing new here is the size declaration in brackets. But I many not have the most recent manual either.)

-Phil

Paul Baker
11-08-2006, 04:10 AM
It's there, you just have to piece it together. All the array declaration does is a post offset of the memory pointer, this works the same way as declaring VAR arrays, only they are in the compiler space and resolved into values at compile time.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Paul Baker (mailto:pbaker@parallax.com)
Propeller Applications Engineer
[/url][url=http://www.parallax.com] (http://www.parallax.com)
Parallax, Inc. (http://www.parallax.com)

Cliff L. Biffle
11-08-2006, 06:59 AM
Chip,

The autoincrement-constant pattern for enums is in there, but (as Phil stated) the ability to apply array dimensions to them isn't explicitly stated.

Weeeeeird. http://forums.parallax.com/images/smilies/smile.gif

cgracey
11-08-2006, 07:52 AM
Yep, you're right. I just talked to Jeff about this, who wrote the manual, and he didn't know this was possible, so it didn't go into the manual. This is my fault. Sorry about the inconvenience. And thank you for pointing this out.


Cliff L. Biffle said...
Chip,

The autoincrement-constant pattern for enums is in there, but (as Phil stated) the ability to apply array dimensions to them isn't explicitly stated.

Weeeeeird. http://forums.parallax.com/images/smilies/smile.gif


▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Chip Gracey
Parallax, Inc.

Post Edited (Chip Gracey (Parallax)) : 11/7/2006 11:57:13 PM GMT

parsko
11-09-2006, 01:28 AM
Steve (edited cause I made a mistake),

I'm sorry, I could have also done this, but wasn't at home. The following links might help:

What is the fastest method for data transfer between COGS? (http://forums.parallax.com/showthread.php?p=595611)
Using Semaphores (http://forums.parallax.com/showthread.php?p=598900)
Object Instances (http://forums.parallax.com/showthread.php?p=592564)
ASM ref to Main memory OTHER than PAR passing address (http://forums.parallax.com/showthread.php?p=590363)
How to pass data between multiable objects in multiple COGs? (http://forums.parallax.com/showthread.php?p=587108)
Arrays (http://forums.parallax.com/showthread.php?p=586819)
Stacks (http://forums.parallax.com/showthread.php?p=586554)

They are from my "internal communications" folder in bookmarks(I hope I didn't mess up the name order). Not sure if all apply, but they all contributed to my understandings.

-Parsko

Post Edited (parsko) : 11/8/2006 5:32:07 PM GMT

Mike Green
11-09-2006, 03:27 AM
Parsko,
(All for assembly language) The absolutely fastest way to transfer data between cogs is to use I/O pins, but that's a valuable and very limited resource. The next fastest way is to use a common area in main memory, either known to both cogs (fastest) or relative to PAR (almost as fast if you use instruction modification for locations other than the one actually in PAR).
Once you use SPIN, raw speed is not that important (different techniques don't save you much over code clarity).
Semaphores are also a limited resource and are only needed when more than one cog is going to access the same variables at the same time. The exception is the "single producer, single consumer" where only one cog can change a variable, but another cog will refer to the variable (like a buffer pointer). You don't need a semaphore for that.

Steve Hicks (N5AC)
11-10-2006, 04:06 AM
OK -- hold the phone ... I found something very interesting.· Apparently, the propeller tool groups together variables of like data type when it decides where to place them in memory.· If you look back to the GPS example I have above, I found that I was losing the variable "lock." after I implemented this structure, but other variables were OK.· To see what was going on, I moved lock to the top of the list and it appeared, but other variables moved around and were giving bad results.· In frustration, I ended up writing a debug routine to dump out a serial port the address of these variables and their contents.· I found that when lock and time are longs and are next to each other, they appear together in memory.· When I change lock to a byte, however, it ends up somewhere else -- but it is not just about alignment as with lock in the list FIRST and as a byte, time appears at a location BEFORE lock in memory...· I spent about 8 hours looking for a memory leak of some kind before it ocurred to me what might be happening.

VAR
{ GPS Data Structure --- DO NOT ALTER WITHOUT CHANGING CONSTANTS }
long lock
long time


In this example, lock ends up at $2484 and time ends up at $2488 ... just like I would expect.· But when I do it like this:



VAR
{ GPS Data Structure --- DO NOT ALTER WITHOUT CHANGING CONSTANTS }
byte lock
long time


lock ends up at $25A1 and time at $2484 ... BEFORE lock in memory (and where lock was before)!

All this would be more evident if I could just print out a symbol table after the compile so this is a request!· I'd like to be able to print a symbol table out after the compile process.· Heck, seeing how everything fits in memory (not just symbols but sizes of routines and start/end addresses, long count) would be instructional.

Can someone explain what is going on here, what the rules are and if there is a dependable behavior?· For example, if all longs get grouped together in memory in incremental order (as they appear in source), I'll just make everything longs and be done with it.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Steve, N5AC

cgracey
11-10-2006, 04:18 AM
Steve Hicks (N5AC) said...
OK -- hold the phone ...

Steve,

I'm sorry this has not been documented, but the compiler groups all longs, then words, then bytes, to avoid gaps between variables, thereby crunching VAR space. So, all VAR longs will be in the order that they were declared, as will all words and bytes.

I think we will add some kind of post-compile report, like you're talking about, to the Propeller Tool in the future. Sorry you had to spend so much time on this.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Chip Gracey
Parallax, Inc.

Post Edited (Chip Gracey (Parallax)) : 11/9/2006 8:26:06 PM GMT

cgracey
11-10-2006, 04:23 AM
Steve,

Another thing to keep in mind is that the first 8 VAR longs and the first 8 local longs (beginning with RESULT, then parameters, then local vars) have special single-byte Spin codes for reading and writing them. While a byte may be all that's required to store your variable, it could be more efficient to use a long, instead. Bytes are only worthwhile if you have an array. The same·goes for·words. If it's just·a single variable, use a long.


▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Chip Gracey
Parallax, Inc.

Post Edited (Chip Gracey (Parallax)) : 11/9/2006 8:27:46 PM GMT

Steve Hicks (N5AC)
11-10-2006, 04:25 AM
Hey, no sweat. It's just one of things you go through using a new language/tool/etc. So if I make everything longs can I count on this as a stable behavior for the future? In other words, Parallax isn't going to go alphabetize them at a later date rather than order as they are found in source code?

In either case, I think my recommendation will be to avoid the confusion and just pass longs unless there's a real memory issue.

Thanks!

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Steve, N5AC

cgracey
11-10-2006, 04:29 AM
Steve Hicks (N5AC) said...
Hey, no sweat. It's just one of things you go through using a new language/tool/etc. So if I make everything longs can I count on this as a stable behavior for the future? In other words, Parallax isn't going to go alphabetize them at a later date rather than order as they are found in source code?

In either case, I think my recommendation will be to avoid the confusion and just pass longs unless there's a real memory issue.

Thanks!


Don't worry about them getting alphabetized, they will always be in the order of declaration. The ordering is not going to change.

I made another post, above, that I hope you see. It adds a little more.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔


Chip Gracey
Parallax, Inc.