Should I depend on Locals? (Spin coding)
Erlend
Posts: 612
Often the Objects I write need quite a number of parameters. In the object method itself the values of the parameters from the parent caller need to be distributed to the actual PUBs and PRIVs to be acted upon. This can be done by passing the parameters on (and on, and on...), or by copying the parameter values into Local Variables for 'everyone' to share. I am uncertain to what is the best approach? For the enclosed code I started out with passing on the parameters, but the result was that a lot of the PUBs and PRIVs included more or less the same endless list of parameters, e.g. PUB doit (<endless list of param>). This is what will happen when different methods do different things to more or less the same set of parameters. So, I rewrote the code using the other approach - to copy parameter values into local varables early on, so that the PUBs and PRIVs would not need to have them passed as parameters. The code is easier to read now, but-- Is this a good approach? What's the snag?
By the way, this is code under construction based on a tip from @Ariba of how to 'multitask' within one Cog.
Erlend
----snip---- PUB Start(PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol): Success Stop Success:= (cog:= COGNEW(HotWcontrol(PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol), @HWCstack) +1) PUB Stop IF Cog COGSTOP(cog~ - 1) PUB HotWcontrol(PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol) LptrTempWater:= ptrTempWater 'Make life easier by copying into local public variables LptrPressWater:= ptrPressWater LptrFlameRate:= ptrFlameRate LptrLevelWater:= ptrLevelWater LptrOpmode:= ptrOpmode LPINpump:= PINpump LPINignt:= PINignt LPINservo:= PINservo LPINfreqCount:= PINfreqCount LiSPtemp:= iSPtemp LiSPpress:= iSPpress LiSPtime:= iSPtime LiSPvol:= iSPvol ReadGlobals CASE iOpmode 0: Shutdown 1: HWcontrol 2: Time 3: Warm 4: Cold 5: Pon 6: Poff 7: Pilot OTHER: Shutdown PRI Shutdown 'Shutdown pump, close servo, shut off igniter float outputs, COGSTOP OUTA[LPINpump]:= 0 DIRA[LPINpump]~ ServoPos(0) OUTA[LPINservo]:= 0 DIRA[LPINservo]~ if cog COGSTOP(cog~ - 1) PRI HWcontrol 'Setting up the first run of 'Milestones' as CNT values '------------------------------------------------------------------------------------------------------------------------------------------------ iServoRate := 20*mSec 'Servo every 20ms iServoPuls := 2200*uSec {0%} 'Servo Pos 1000..2000uS nominal pulse length ** will be recalculated by control algorithm iServoPulsStart := CNT + iServoRate 'Define 'milestone' as CNT value iServoPulsEnd := iServoPulsStart + iServoPuls 'Define 'milestone' as CNT value iPumpRate := 100*mSec {10Hz} 'Pump Freq 5..60Hz = 200mS..16mS periode ** will be recalculated by control algorithm iPumpPuls := 10*mSec 'Pump pulse length 10mS iPumpPulsStart := CNT + iPumpRate 'Define 'milestone' as CNT value iPumpPulsEnd := iPumpPulsStart + iPumpPuls 'Define 'milestone' as CNT value iFreqCountIval:= 500*mSec 'Read, store and reset counter every 500mS iFreqCountNow:= CNT + iFreqCountIval 'Define 'milestone' as CNT value iAlgorithmIval:= 1000*mSec 'Run control algorithm /sec and update values iAlgoritmNow:= CNT + iAlgorithmIval 'Define 'milestone' as CNT value '------------------------------------------------------------------------------------------------------------------------------------------------- DIRA[LPINservo]:= 1 'Define as outputs DIRA[LPINpump]:= 1 iPumpStrokes:= 0 'Set volume counter to zero 'Configure the counter and do a first frequency count to measure flame rate '------------------------------------------------------------------------------------------------------------------------------------------------- CTRA:= %01010 << 26 + LPINfreqCount 'Set up counter to POSEDGE mode (bit26-30, using pin LPINfreqCount (bit 0-5), and FRQA:= 1 'using one count per edge to accumulate into PHSA WAITCNT(clkfreq/2 + cnt) FreqCount IF iFlameRate < CiMinFlame 'Light the flame if not burning Ignite(1) 'Wait with starting pump until initial heating of water is finished '------------------------------------------------------------------------------------------------------------------------------------------------- iTempWater:= LONG[LptrTempWater] REPEAT UNTIL iTempWater > LiSPtemp 'Wait until water is hot enough for brewing iTempWater:= LONG[LptrTempWater] 'Update temperature measurements data 'Now start 'milestone' based hot water control routines '-------------------------------------------------------------------------------------------------------------------------------------------------- REPEAT UNTIL iPumpStrokes > LiSPvol 'Continously check CNT to detect 'milestones' and perform timed actions - until setpoint volume 'Do the Servo IF (CNT - iServoPulsStart) > 0 'If the iServoPulsStart milestone has been reached OUTA[LPINservo]:= 1 'do what has to be done, iServoPulsStart+= iServoRate 'then set up the next time milestone IF (CNT - iServoPulsEnd) > 0 OUTA[LPINservo]:= 0 iServoPulsEnd:= iServoPulsStart + iServoPuls 'Do the pump IF (CNT - iPumpPulsStart) > 0 OUTA[LPINpump]:= 1 iPumpPulsStart += iPumpRate iPumpStrokes += 1 'Keep tally of total pump strokes (=volume) IF (CNT - iPumpPulsEnd) > 0 OUTA[LPINpump]:= 0 iPumpPulsEnd:= iPumpPulsStart + iPumpPuls 'Do the frequency counter IF (CNT - iFreqCountNow) > 0 FreqCount iFreqCountNow += iFreqCountIval 'Do the control algorithm IF (CNT - iAlgoritmNow) > 0 '!! PRI needed ControlAlgorithm iAlgoritmNow += iAlgorithmIval ----snip---- '--------------------- supporting subs ---------------------- PRI Ignite(Sec) 'Method to ignite gas burner DIRA[LPINignt]~~ 'Set I/O pin to output direction OUTA[LPINignt]:= 1 'Energize igniter WAITCNT(clkfreq + cnt) 'Warm up igniter tip ServoPos(950) 'Open gas valve fully WAITCNT(Sec*clkfreq + cnt) 'Let glow for Sec OUTA[LPINignt]:= 0 'Switch off Ignt DIRA[LPINignt]~ 'Set I/O pin to input, ie. float ServoPos(1500) 'Set gas valve low burn PRI ServoPos(position) 'Method to run a servo just long enough, then let it free, assuming the friction of the valves prevents movement DIRA[LPINservo]~~ 'Set I/O pin to output direction repeat 25 '1/2 S to allow servo to move to new pos WAITCNT(clkfreq/50 + cnt) '50 hz update frequency OUTA[LPINservo]:= 1 'Pulse high WAITCNT(position*uSec + cnt) 'Servo pulse duration 0% eq 2220 uS, 100% eq 950mS OUTA[LPINservo]:= 0 'Puse low DIRA[LPINservo]~ 'Set I/O pin to input, ie. float PRI ReadGlobals 'Method to copy values over from global variables ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptropmode iTempWater:= LONG[LptrTempWater] iPressWater:= LONG[LptrPressWater] iFlameRate:= LONG[LptrFlameRate] iLevelWater:= LONG[LptrLevelWater] iOpmode:= LONG[LptrOpmode] PRI FreqCount iFreqUV:= PHSA~ 'Capture counter accumulation and then post-zero iFlamerate:= LONG[LptrFlameRate]:= iFreqUV * 2 'Calculate real frequency (read 2/Sec) and store in local and global variable PRI ControlAlgorithm IF iOpmode== 0 'Check for stop command from parent Shutdown IF iFlameRate < CiMinFlame 'Flameout shutdown Shutdown ----snip----
By the way, this is code under construction based on a tip from @Ariba of how to 'multitask' within one Cog.
Erlend
Comments
Erlend
Your case (and much of Spin) is different in that you initialize the object with pointers to other variables, then you throw the object into a parallel cog where it watches those values. After the initialization call other functions never call on this object, which is unique in the programming world.
So, yes, I think you're doing it the best way that you can at this point.
On a personal preference, when copying values from method parameters to object instance values I like to give the parameter name a trailing underscore, and the instance value the root name. Eg ptrTempWater_ and ptrTempWater.
EDIT: The h... methods in fsrwx.spin require a pointer to a file struct, which is 556 bytes in size. The 556 bytes include the 512-byte sector buffer plus 11 longs used to store the file information. The file structure should be initialized to all zeroes.
About the underscore_ syntax - is that a widely used convention or your own style only?
Erlend
Thanks for the code example, but I am too slow to understand how this is a problem-solving technique in my case?
Erlend
There is a way around this. Create an object to service your parameters. Within this object define a DAT array which contains all your parameters. Also create a bunch of constant pointers to indexes within this array so you can refer to stuff you want by name. You can then drop this object wherever you need it as a child to other objects, and because of the DAT storage they're all accessing hte same parameter list. With SET and READ methods in the service object, you're then good to go.
I did this to implement a generic string library which doesn't require everyone using it to define buffers; the generic common buffer is of course global but it's DAT and all the instances of the string object have access to the same buffer and pointers.
The OP needs a global array. It's the cleanest solution to his problem, which is to make a parameter list available at several different layers of abstraction within an application.
You need GOTO (and badly) when handling error conditions in a complicated application where they can occur at mutiple levels of abstraction and you want to re-enter the control loop after handling them.
There is Spin code out there riddled with all kinds of dodges dealing with the fact that these features aren't available in the edge cases where they are really needed. My string object is a good example; the PropTool won't even compile it because there are so many instances of the small handler object, almost literally as a child to every other object in the system. (BST compiles it fine.)
It's fine to say that globals and GOTO are dangerous and overused, but it's stupid to pretend that they're never the best solution to a particular problem. There really is such a thing as global data.
1. Computation based on input, result of logic, or some combination serves as index into table of addresses or, in the case of PASM, likely just JMP instructions.
2. Computation, based on the same, results in JMP address directly.
These may be GOSUB type routines too, and they may do other things, like fall through, or have common exit point.
In BASIC, these are typically expressed as either: ON X GOTO 100, label, label1, 200, etc... depending on whether or not line numbers are needed
, or
GOTO X, where X references a line number, in number oriented languages.
Where there are a small number of possible branches, the lack of these isn't a big deal. Where there are a large number, such as an interpreter, emulator, etc... or a lot of logic tied to something like a character value, the cost difference is dramatic.
Of course, I think it's perfectly silly we frown on these when the CPU itself is setup to do them nice and quick. Always have.
Like UNIX, where you get a lot of power, and you can make a big mess, and that means you also get a lot of responsibility, having these sorts of constructs available means some messes will be made, but some really awesome things will be made too. Works about the same, IMHO.