Program Architecture Best Practices with Multiple Objects and Cogs
SteveWoodrough
Posts: 190
Happy Thanksgiving Everyone
After three years of study on and off Im finally on the cusp of doing something interesting with a Robo-Magellan type bot. As an introduction, I came to the Prop from the BS2 as a 45 year old mechanical engineer with limited programming experience and nothing in the way of familiarity with object oriented programming. Hannos 12Blocks was a confidence builder, and the Kick Start book an epiphany. I understand at a beginners level how to do the following each within a one or two level object set. For example, I can from my own Top object, send messages to an LCD using the Full Duplex Serial library object, drive a servo using the servo32V7 object, control a speed controller with PID feedback, get compass data, get GPS data, etc. I feel like I have a fundamental tool box to finally get started and integrate my Robo-Magellan project. I want to build the right foundation to support the project that will require multiple objects and multiple cogs.
My questions are: What are the best practices to structure a program that requires multiple objects and multiple cogs? I understand that there may be no one single best practice, since each application may have its own demands. What is best for one project may not be the best for another. Generally speaking, should the Top object reference all relevant objects required for the project, excluding objects referenced by an object for its own use? An example of the exclusion would be the need to reference only Servo32V7 in the top object and not Servo32_Ramp_v2 since the latter is referenced by the former.
Likewise is a best practice that all I/O pin assignments etc. be in the Top object or distributed within referenced objects?
That is the short version of my question. Below is an abbreviated description of what I want to accomplish and the knot of concepts Ive formed in my thinking.
Thank You for your time and responses.
Regards,
Steve
What I want to avoid is a Main / Top that becomes some galactic wad of methods. In my mind, and very possibly an errant fantasy, the Top object should consist of a single Main method that is set of instructions and likely in the form of a repeating loop. In pseudo code the Top object might look something like this:
Pub Main
Repeat
Do you want to calibrate compass? Y/N
Compass calibrated
Enter Heading
Enter Distance
Repeat
Bot Ready to go? Y/N
Bot Ready, so go distance at heading
Run Complete
Bot stops
Enter Heading
Enter Distance
Time for Dinner (leave loop)
In this architecture plan all the details about driving servos (separate cog), sending messages to LCDs (separate cog), monitoring keypads, monitoring encoder pulses (separate cog), monitoring compass data, I/O pin assignments etc. are accomplished in separate dedicated objects. This is all done in the interest of keeping everything neat and tidy and compartmentalizing functions within the relevant objects. Servo stuff in servo objects, LCD display stuff in LCD objects, and so on. Continuing down an object level in this architecture plan, the LCD display pin assignment is part of an LCD object and the LCD object references and Starts the Full Duplex Serial (FDS) object in its own cog.
Is the above description a wolf in sheeps wool? By embedding FDS in a lower object, dedicated to the LCD function, have I completely cut myself off from using the functionality of FDS elsewhere in the overall program even though FDS is started and is running in its own cog? Could I, in another object, somehow functionally use the expression FDS.tx(128) to more the cursor to the first position on the LCD screen? My own experiments suggest that I cannot, but maybe there is something I have not yet learned.
I suppose that the LCD object, that it self references the FDS object, could be written with methods that referenced the functionality of the FDS object. With that approach parameters are passed from any object to the appropriate corresponding LCD object method which then go to the FDS object running in its own cog. It just seems a bit extreme to have to write methods referencing methods that already exist in the referenced object. On further reflection, passing data strings such as Hello World as parameters to access a method such as: FDS.str(string("Hello World")) may be impossible or so convoluted as to negate the intended purpose of simplicity.
After three years of study on and off Im finally on the cusp of doing something interesting with a Robo-Magellan type bot. As an introduction, I came to the Prop from the BS2 as a 45 year old mechanical engineer with limited programming experience and nothing in the way of familiarity with object oriented programming. Hannos 12Blocks was a confidence builder, and the Kick Start book an epiphany. I understand at a beginners level how to do the following each within a one or two level object set. For example, I can from my own Top object, send messages to an LCD using the Full Duplex Serial library object, drive a servo using the servo32V7 object, control a speed controller with PID feedback, get compass data, get GPS data, etc. I feel like I have a fundamental tool box to finally get started and integrate my Robo-Magellan project. I want to build the right foundation to support the project that will require multiple objects and multiple cogs.
My questions are: What are the best practices to structure a program that requires multiple objects and multiple cogs? I understand that there may be no one single best practice, since each application may have its own demands. What is best for one project may not be the best for another. Generally speaking, should the Top object reference all relevant objects required for the project, excluding objects referenced by an object for its own use? An example of the exclusion would be the need to reference only Servo32V7 in the top object and not Servo32_Ramp_v2 since the latter is referenced by the former.
Likewise is a best practice that all I/O pin assignments etc. be in the Top object or distributed within referenced objects?
That is the short version of my question. Below is an abbreviated description of what I want to accomplish and the knot of concepts Ive formed in my thinking.
Thank You for your time and responses.
Regards,
Steve
What I want to avoid is a Main / Top that becomes some galactic wad of methods. In my mind, and very possibly an errant fantasy, the Top object should consist of a single Main method that is set of instructions and likely in the form of a repeating loop. In pseudo code the Top object might look something like this:
Pub Main
Repeat
Do you want to calibrate compass? Y/N
Compass calibrated
Enter Heading
Enter Distance
Repeat
Bot Ready to go? Y/N
Bot Ready, so go distance at heading
Run Complete
Bot stops
Enter Heading
Enter Distance
Time for Dinner (leave loop)
In this architecture plan all the details about driving servos (separate cog), sending messages to LCDs (separate cog), monitoring keypads, monitoring encoder pulses (separate cog), monitoring compass data, I/O pin assignments etc. are accomplished in separate dedicated objects. This is all done in the interest of keeping everything neat and tidy and compartmentalizing functions within the relevant objects. Servo stuff in servo objects, LCD display stuff in LCD objects, and so on. Continuing down an object level in this architecture plan, the LCD display pin assignment is part of an LCD object and the LCD object references and Starts the Full Duplex Serial (FDS) object in its own cog.
Is the above description a wolf in sheeps wool? By embedding FDS in a lower object, dedicated to the LCD function, have I completely cut myself off from using the functionality of FDS elsewhere in the overall program even though FDS is started and is running in its own cog? Could I, in another object, somehow functionally use the expression FDS.tx(128) to more the cursor to the first position on the LCD screen? My own experiments suggest that I cannot, but maybe there is something I have not yet learned.
I suppose that the LCD object, that it self references the FDS object, could be written with methods that referenced the functionality of the FDS object. With that approach parameters are passed from any object to the appropriate corresponding LCD object method which then go to the FDS object running in its own cog. It just seems a bit extreme to have to write methods referencing methods that already exist in the referenced object. On further reflection, passing data strings such as Hello World as parameters to access a method such as: FDS.str(string("Hello World")) may be impossible or so convoluted as to negate the intended purpose of simplicity.
Comments
Don't get hung up on having the top object just containing the main method. By moving everything else to "lower" objects, you may be needlessly complicating your program when it might make much more sense to have the whole upper levels of your program in a single object. Objects are really intended to encapsulate some kind of abstract functionality. Most of the time, these are I/O drivers where the interface methods provide the abstraction to the rest of the world and, internally, the object may use all sorts of resources including internal variables and cogs that the "outside world" doesn't have to know about. If done properly, the "outside world" doesn't have to know whether other cogs are used or how much and what kinds of memory are used. It all looks the same to the caller (or should) whether the I/O driver is actually written in PASM or Spin or C for that matter.
Normally when you reference an object in another object (like: OBJ FDS:"FullDuplexSerial"), a new instance of that object is created complete with its own copy of the object's local variables. This is different from any other declaration of an object with the same file name. Multiple object instances share the same code bytes since those are read-only and they share any DAT areas which are usually PASM code ... also read-only. It's possible to have multiple instances of an object that keep all their variables in DAT sections so that's shared and there are such objects in the Object Exchange so multiple objects can use them for common I/O.
Now, on to your specific questions:
In general, I like to structure my program with a 1:1 mapping between cogs and objects. The way this works out is usually where only the top level object has sub objects. Still, as long as the organization makes sense and is clear then whatever method you use is fine.
I like to define all pins in the top level object. That way, you can order them all numerically and make sure that there are no conflicts, and this design makes it easy to change hardware. I make each pin a constant.
Thank you both for your time and respones. As I prepared my question, I began to reach the same conclusion, but I wanted to be sure I had the correct perspective. I dreaded laying down some kind of uninformed foundation only to discover later in the process that I would need to dig it all up and begin again.
Best Regards,
Steve
Functionally everything so far works individually, but as an integrated program, I'm missing some concept. The program executes the LCD_CruiseControl method but never gets down to the BlinkLED method referenced in the main method.
The assembled parts so far consist of driving a RC truck motor controller that is using PID feedback to control its speed. Both servo32V7 object and the CruiseControl (PID) method run in two separate cogs, and work just great. FDS also runs in a separate cog and sends the data to my LCD screen just fine. The BlinkLED method works as long as the LCD method is commented out. For some reason the cog that the Main method is running is stuck in the repeat loop of the LCD method. I thought that since FDS is running in a separate cog, this repeat loop would be contained in the cog running FDS.
Looking at the code, I can sense a subtle difference in how I started the CruiseControl method in its own cog, but LCD_CruiseControl is running in the starting cog, even though FDS is running in a separate cog, via FDS.start.
What's the fix? Do I start another cog to execute the LCD_CruiseControl method, just to send one message? At the rate Im using cogs, Ill need 32! LOL!
Seriously though, how does one best avoid Cog Clogs? Am I correct to start FDS.start as part of an initialization scheme, or should I wait, to start FDS only when I want to send a message to the LCD then shut the cog down with FDS.stop? If I want messages to stream to the LCD independent of other processes, do I need two cogs, one to run FDS and a second to send the commands to the LCD?
Lastly, If I need to start and stop a separate cog to handle the LCD message fuctions do I need a Stop method independant of the Stop method referenced by my CruiseControl method?
Thanks
Steve
Both of those are spin methods that are being run in your main cog interpreter. Both of have repeat loops with no exit. So once your program hits one of them, it will stay there. If you want the blinkLED to happen too, there are a few ways to go about it. 1) include them both in one longer repeat main loop; 2) start a cog counter for the LED; 3) give the blinkLED or the LCD its own cog.
"I thought that since FDS is running in a separate cog, this repeat loop would be contained in the cog running FDS. "
No, while the machine language portion of FDS runs within a separate cog, both your LCD_CruiseControl and the Spin methods of FDS are being interpreted by the main cog. LCD_Cruise control sends variables to spin methods of FDS, which in turn put those variables into a buffer which is acted upon by the machine language component of FDS running in the other cog.
"Am I correct to start FDS.start as part of an initialization scheme, or should I wait, to start FDS only when I want to send a message to the LCD then shut the cog down with FDS.stop? "
Yes, initialize it, and no, it should not be necessary to shut it down. However, as you project develops, it sounds like you may need serial ports for several purposes. You have the LCD, but you also mentioned a GPS, and a compass, and maybe you also need a serial port for debugging direct to your computer during the process of development. In that case, you will need to open several serial ports. There are several ways to manage that, one of which is to start multiple instances of FDS.
"If I want messages to stream to the LCD independent of other processes, do I need two cogs, one to run FDS and a second to send the commands to the LCD?"
That would be one way to do it. There would be an interpreter cog running for the LCD commands and FDS spin, and a machine language cog for the pasm part of FDS. It should be possible to include other tasks such as the blinkLED in the loop with the LCD.
"Lastly, If I need to start and stop a separate cog to handle the LCD message fuctions do I need a Stop method independant of the Stop method referenced by my CruiseControl method?"
Yes. Each cog you start will need its own stop method and its own cogID variable.
Mike,
Thank You for your response. What Ive shown so far is only the basic start of the overall program. Within the program version I shown, Im trying to understand just how things need to be to run as intended. What I FAILED to reveal in my earlier post was the need to monitor distance traveled via the same Hall Effect sensor. If I am not mistaken, continuously monitoring a sensor input is something I can only do if I run in parallel with the Main method.
LCD could be part of the Cruise_Control method except there may be instances where I do not want to display the information on the LCD. Im displaying the information now as part of an exercise to understand, but in the future, Cruise_Control should just run in the background monitoring the Hall Effect sensor.
I suppose that before I ask for directions I should give a better idea on where it is I want to go. Below is a functional description / specification for my project. The first three items are included in the current program version. The remaining items are future functions that I intend to add one I get a handle on the matter currently under discussion.
Support the use of hobby grade servos and speed controllers.
Support an LCD display to display real time data of selected information depending upon the mode of the program. There will be multiple screen displays depending upon the operation performed. The LCD screen displayed at startup will be different than the display during compass calibration which will be different than the LCD displayed while traversing to a way point, etc.
Support the monitoring of actual motor speed via the Hall Effect sensor Cruise_Control so that the bot speed is some what constant over grass or pavement
Future Functionality
Support the ability to determine distance traveled via the Hall effect sensor.
Support collision avoidance via PING sensor.
Support the use on an onboard compass module and its calibration at program start.
Support the use of GPS to determine location, speed, heading, etc.
Support un-tethered programming and real time monitoring of selected program variables. This is to be accomplished using VP and the Blue Tooth radio at some point. My use of the term real time may not be precise in engineering terms but its close enough for my purposes.
Support the use of an SD card reader to store and retrieve way point data.
Support the use of a 4x4 keypad to enter data as required.
Thank you Tracy for your time and consideration.
BlinkLED was a bit of a red herring. BlinkLED was a crude representation of a Main method that would operate as a repeating loop in the primary cog. Therein would be contained all the If Then statements etc. for navigation, etc. Thanks to you and Mike, I see the error of my ways. I have a ton of questions, but feel I have enough information feel my way along to the next impass!
Best Regards,
Steve
If you're using more than one instance of Full Duplex Serial, you may want to consider switching to a four port object. Tracy Allen's version is the best I know of.
I haven't looked at the GPS objects for a while, but a while back (probably more than a year ago) I worked on a GPS data logger. I was disappointed how some of the GPS objects were structured. They started serial drivers in multiple places from various locations withing the object tree. These multiple serial drivers used cogs that weren't needed. I was able to trim away three cogs by moving code from several of the child objects to the top object. This made for a rather large top object buy it solved the problem of redundant serial objects.
Make sure and take advantage of the "Summary" mode in the Propeller Tool. It makes large objects easier to navigate.
Edit: There are lots of ways to blink a LED. I kind of like the object I made for this purpose. It lets you blink any number of LEDs at any speed (within reason). It does require a cog but it may be possible to add other housekeeping processes within the object. Since it's all written in Spin, it shouldn't be very hard to modify.
THANK YOU. That certainly is significant. I was just looking over some experiments I had done with the GPS module a few months back and was agast at addtional references to FDS. More COGS? I thought, Oh no! Then I got a bright idea: Can I use one instance of FDS to listen to the GPS and talk to the LCD? Is that pratical?
I will certainly download and explore Tracy's FDS object and attempt to integrate into the project.
Best Regards
Steve
Yes, as long as they are both using the same baud.
The other demo does the same thing, but breaks the main program and the string generator into two separate objects, just to show how it is done. "topObject" and "childObject". Still 5 cogs. In either case there is only one copy of the spin code for fullDuplexSerial, and its methods are used by of the spin cogs, but there are three pasm cogs to support the 3 serial ports. You don't see much change in the HUB footprint as you add instances of FDS. But the cog usage is increasing rapidly.
Duane mentioned the 4-port object that I worked on. My projects (instrumentation) tend to be serial port intensive. The 4-port pasm for 4 serial ports resides in one single cog. The startup code is a bit more complicated, and the object itself has a larger footprint, but it does make efficient use of cog power.
Your original question is about architecture of objects and cogs, and the difference between the uses of VAR vs DAT are one important aspect of that.
I downloaded your FDS4Port object and I am reading the notes and getting familiar with the object functionality. This is going to take me a few hours to digest to the level I need.
A few months back when I was experimenting with the GPS module, I learned that I needed to use the "Numbers" object to translate the string data from the GPS into something useable by the program. Does your DataIO4 object perform the same function as the Numbers object?
Thank You
Steve
With regard to your question, you might want to take a look at Tim's original OBEX object, pcfullDuplexSerial4fc, the demo for which is a complete project called "gpstest". It does in fact include a GPS parser using those methods that are in dataIO4port
The "Numbers" object by Jeff Martin is a marvel of condensed Spin programming. It allows conversion of numbers in any base 2--16 to a string, and vice versa, with a wide variety of formatting options. It is powerful, but hard to appreciate due to its flexibility. A great example to study. There are other "simple_numbers" options that only allow limited formatting options on decimal, hex and binary numbers. The methods in dataIO4port are of that type. Good for GPS.