Shop OBEX P1 Docs P2 Docs Learn Events
Parent/Child relationship question — Parallax Forums

Parent/Child relationship question

jcwrenjcwren Posts: 44
edited 2010-01-31 16:59 in Propeller 1
Here's my scenario: The top level cog, Scrooge, gets launched and in turn launches 3 additional cogs, Huey, Dewey and Louie. Although all three are launched, only one is active at any time. However, there's a common resource they all want to use when they're active, called Quack. When Huey is active, he may need to Quack. When Dewey is active, he made need to Quack, and the same for Louie.

In C, for example, I'd create Quack first, then as I launched Huey, Dewey, and Louie, I'd pass a pointer to Quack, and when they need to use it, they'd simply (*quack) (parameter1, parameter2, etc).

As near as I can tell in Spin, there's no way to do something similar? I can have Huey, Dewey and Lewey each launch Quack when they become active, and stop it when they go inactive, but this seems less efficient.

So is there a way to pass an object created at the top level to a child process for it to use? If not, other than launching and aborting Quack as needed, any suggestions?

--jc

Comments

  • MagIO2MagIO2 Posts: 2,243
    edited 2010-01-31 00:08
    Do we talk about PASM or SPIN COGs?

    You have full access to any HUB-RAM. So, you could use a buffer as communication buffer between Huey, Louie, Dewey and Quack. Quack is watching the buffer to see if he has to do something. Huey, Louie and Dewey simply put the parameters to this buffer.
    You can't create objects and pass it to child processes. But you can use the same buffers and send messages. Either you hardcode the buffer address in all modules or you pass it as parameter to all childs.
  • Mike GreenMike Green Posts: 23,101
    edited 2010-01-31 00:11
    There is no way to pass an object to another object in Spin.

    I guess I don't understand why you're organizing things this way. Since Huey, Dewey, and Louie never execute at the same time, why do they have to be in separate cogs?

    If there's a good reason for the separate cogs (like when Huey, Dewey, and Louie pause for the others to act, they're way in the middle of something, so you're using the cogs to hold the state of the processes), then you could start up Quack in its own cog and have it wait for parameters to be placed in shared memory. Typically you would have one long with a flag in it. If false, Quack is idle waiting for data. You can store the other parameters in adjacent locations whenever it's idle. The last item you'd set is to make the flag a -1. Quack would see that and process the data. When it's done, it would set the flag back to zero (false).
  • kwinnkwinn Posts: 8,697
    edited 2010-01-31 00:18
    MagIO2 is correct. You need to use hub ram to pass data to quack. I can see three approaches to this. 1 - Run quack as part of the parent process. 2 - Run quack as part of one of the child processes when they are idle since they are never active simultaneously. 3 = Run quack in it's own cog.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2010-01-31 00:40
    I think we're confusing cogs and objects here. They're not the same thing.

    If Huey, Dewey, and Louie are Spin cogs in the same object as Scrooge, along with Quack, they can simply call Quack. Or, if Quack is in a separate object, that object can be declared in an OBJ section, and each nephew can call objref.Quack.

    If Huey, Dewey, and Louie are separate Spin objects, each can declare the object containing Quack (not Scrooge, though) and call objref.Quack. The code and DAT variables in Quack's object are not replicated for each of the nephews, only the VAR variables.

    If the nephews are PASM cogs and/or if Quack is a PASM cog, then as others have indicated, Quack will need to monitor a hub variable to know when to quack.

    -Phil

    Cripes! That's got to be confusing to a newcomer. I wish there were a better way to explain it.
  • jcwrenjcwren Posts: 44
    edited 2010-01-31 02:13
    I think the correct solution is to have Huey, Dewey or Louie start a Quack when they're selected, and stop it when they're deselected. This mostly boils down to keeping a clean interface between everything.

    For the record, here's the real application. I have 3 amateur radios in my car, a Kenwood TK-931HD 900Mhz radio, an Alinco DR-235T 220Mhz radio, and an Icom IC-208H 2m/70cm. This also means there are three microphones, and I find that to be awkward and space consuming (small car). Also, the Kenwood microphone is a commercial job. Very heavy, very stout. I refer to it as a "weaponized microphone". You could easily beat someone to death with it. It also doesn't have a DTMF pad, which is handy for controlling the repeater.

    My solution to this is to design a microphone consolidator. The microphone for the Icom radio is called an HM-133. It has a bunch of handy buttons for controlling the radio. It does this via a digital interface. Unlike older radios, when DTMF is sent, the microphone just sends a "DTMF key 'x' pressed" message to the radio, and the radio actually synthesizes the DTMF. Unfortunately, the Alinco and the Kenwood don't. They generate the DTMF in the microphone and ship down as audio, muting the mic element in the process, so that voice isn't mixed with the DTMF.

    Each radio has it's own cog that manages it. These are all started when the system first comes up. A cog is also started that listens and decodes the data stream from the HM-133. These keys are passed up to cog 0 (the application). As keys come in from the HM-133, they're first checked to see if it's a meta-key (F2, one of two function keys on the mic) that's used to determine which radio the mic is "connected" to. When F2 is seen, the next 1, 2, or 3 key selects which radio the microphone is connected to. If the key is not needed, it's passed on to the currently selected radio.

    In the case of the IC-208H, it's merely repeated out, since the mic is native to the radio.

    If the DR-235T is selected (F2, then 2), then received keys are passed to the DR-235T cog who remaps what keys it can. Some keys on the mic aren't applicable, since the radio doesn't support equivalent functions. If it's a DTMF key, the DTMF generator should start generating the appropriate tone. The exact same is true for the TK-931 cog, except if uses F2, '3' to be selected.

    Even when the mic is not "connected" to a particular radio, that cog is still doing some monitoring and statistics collections. It just means that since the mic isn't connected to it, it'll never need to generated a DTMF key at that point. Only the currently selected radio needs DTMF generated, and only then if it's not the IC-208H.

    For the cleanest interface, the idea was to have the DTMF method be self-contained. The DR-235T cog doesn't want to worry about knowing that a '1' key is a 697Hz tone and a 1209Hz tone. It just wants to pass a '1' to the DTMF method and have it done.

    Now here's where you can correct me if I'm wrong. If I have the top level cog (the application cog) create the DTMF object, the other cogs cannot call methods in the parent object (right?). This in effect would amount to a callback, which SPIN doesn't support. I can pass a pointer to a DTMF control buffer as each radio's cog is created, but then it requires each radio cog to know the structure of the buffer (however simple or complex it may be), and while workable, it's not clean. Or lastly (and this is what looks like is the cleanest interface), as each radio's cog is selected (the microphone "connected" to it), it can start the DTMF cog, and treat as a proper object, using the DTMF objects methods to remap '1' to 697Hz/1209Hz, put things in buffers, etc. It will, of course, release the DTMF cog when it's deselected.

    Nothing in the last area is particularly time critical. We're dealing with human reaction times here, and as I select a radio by pressing F2 then '2', there's going to be at least another second before I'd be pressing a key to send DTMF. I.e., more than enough time to stop the DTMF cog assigned to the TK-931, and restart it assigned to the DR-235T.

    Just theorizing here: I suppose it's possible for the application cog to start the DTMF cog, and pass a control buffer pointer to each radio cog. Each radio object (cog) can then create a DTMF object, but never call the method that actually starts it into its own cog. The methods in the DTMF object could accept a buffer pointer, and manipulate it as necessary, isolating the radio object from knowing the internals of the DTMF object. The only thing I'd need to keep aware of is not using any of the elements in the VAR section of the DTMF object.

    I am using cogs and objects slightly interchangeably here. The main purpose of that was to indicate what was off running it's own cog as opposed to just being an object in the top level application.
  • StefanL38StefanL38 Posts: 2,292
    edited 2010-01-31 11:30
    Hello jc,

    your understanding of cogs and objects is wrong.

    ONE object can use 1-8 cogs. And 1-8 cogs can share 1-64 methods (don't know if 64 is the exact limit but just want to say 1-n methods)

    Now I would like to describe the things as code-segments. Because you can NOT say app-cog as the part of the code that you call "application" can use one cog or three cogs or eight cogs


    You have the following code-segments

    code-segment 1: toplevel app-segment

    code-segment 2: TK-931-segment

    code-segment 3: DR-235-segment

    code-segment 4: IC-208H-segment

    Now if I understand right
    the app-segment is connected to the HM-133-microphone

    the app-segment receives digital data from the HM-133-microphone and has to process the digital data

    If I understand right this is a onedirection-flow of data

    HM-133--->Propeller
    >DR-235

    OR

    HM-133--->Propeller
    >TK-931

    OR

    HM-133--->Propeller
    >IC-208H

    Am I right if I assume that NO data or any signals go into the opposite direction?

    If this is the case ONE cog is enough! And using just one cog has the advantage that everything is processed sequential WITHOUT using busy-flags.
    If you start different cogs each waiting for commands to be processed you have to use busy-flags to make sure that things are not processed cross-over.

    Things become different if you are a microphone and radio-amateur artist that changes a channel on radio-A, quickly changes to radio-B saying a short message to person B, quickly changes to radio-C
    changing the channel while listining to the answer of person B on radio-B. Saying a sort message to person C on radio-C and then calling person A on radio-A

    If it is Ok for you that you have to wait until a DTMF-sequence has been transmitted before the next mic-command will be processed you only need a command-buffer in the app-segment
    that stores commands (=stores keystrokes on the mic)

    If you want to keep the code clean you create three objects.
    the TK_931-object

    the DR_235-object

    and the IC_208H-object

    then you "connect" these three objects in the app-segment

    
    
    OBJ
      TK931 : "TK-931HD"   'separate *.SPIN-file that contains all the code for the TK-931HD 
      DR235 : "DR-235T"    'separate *.SPIN-file that contains all the code for the DR-235T
      IC208 : "IC-208H-2m" 'separate *.SPIN-file that contains all the code for the IC-208H-2m
    
    
    CON
      c_TK931 = 1
      c_DR235 = 2
      c_IC208 = 3
    
    
    VAR
      long Mic_Strokes [noparse][[/noparse]20]
    
      
    PUB Receive_HM_133_commands
      
      repeat
        get_MicStrokes
        
        case MicStrokes[noparse][[/noparse]0]  'first long contains value to choose the radio
          c_TK931 : TK931.DoBlabla
          c_DR235 : DR235.ProcessCommand
          c_IC208 : IC208.repeatOut
    
    



    with this code you would have to wait until the keystroke-processing has finsihed before the app-segment can listen again to the Mic

    If you want to go on with a second sequence or with stroke-sequences that have different numbers of keystrokes

    you would start only ONE more cog that listens to the Mic all the time storing the keystrokes to a buffer and the app-segment reads out the buffer

    
    
    OBJ
      TK931 : "TK-931HD"   'separate *.SPIN-file that contains all the code for the TK-931HD 
      DR235 : "DR-235T"    'separate *.SPIN-file that contains all the code for the DR-235T
      IC208 : "IC-208H-2m" 'separate *.SPIN-file that contains all the code for the IC-208H-2m
    
    
    CON
      c_TK931 = 1
      c_DR235 = 2
      c_IC208 = 3
    
    
    VAR
      long Mic_Strokes [noparse][[/noparse]20]
      long stack[noparse][[/noparse]50]
      
    PUB Main
    
      cognew(Receive_HM_133_commands,@stack)
    
      
      repeat
        Read_MicStrokeBuffer
        
        case MicStrokes[noparse][[/noparse]0]  'first long contains value to choose the radio
          c_TK931 : TK931.DoBlabla
          c_DR235 : DR235.ProcessCommand
          c_IC208 : IC208.repeatOut
    
    



    where the method "Receive_HM_133_commands" continously listens to the Mic and stores the strokes into a FIFO-buffer

    the method "Read_MicStrokeBuffer" reads the keystroke-buffer and the case structure directs the strokes to the right radio

    this is a raw concept that needs to be corrected for things like you don't want to type F2 for every command but the basic structure how many cogs you need and which cog is doing what stays the same

    once again:

    one *-SPIN-file can start 1 to 7 additional cogs.
    If the commands "cognew" are inside the SAME *.spin-file all methods and all cogs have access to all global variables defined in the VAR-section

    This means every method from the same *.spin-file has access to all other methods regardless which cog calls the method
    every cog that is launched from the same *.spin-file has access to all variables in the same *.spin-file

    If you want to have access to variables across OBJECTS (= another *.spin-file) you need to define methods get_value and set_value INSIDE the object so you can do a
    myVar := objref.get_value and objref.set_value(MyVar)

    best regards

    Stefan
  • jcwrenjcwren Posts: 44
    edited 2010-01-31 15:21
    Stefan,

    Your latter diagram is, in effect, what I have. Except that each radio object (TK931, DR235, IC208) is run in it's own cog. There are methods provided for the parent object to pass in messages. These are run in individual cogs because there's some background processing for each radio that's constantly done. Checking if there's a new message from the parent is just part of that.

    However, this wasn't the question I was asking. First, the currently selected radio remains selected until the next 'F2' 'n' is seen. That's just a clarification, and not really relevant.

    The issue is that as a radio is selected, it wants the DTMF generator assigned to it. If the app-segment (as you called it) creates the DTMF object (let's call it 'dtmf'), there's no way for the DR235, IC208 or TK931 object to call 'dtmf.MakeTone', because child objects don't have access to the parents methods. The best you could hope to do in that case is to provide a method called 'PleaseSendDTMF' in each of the radio objects, and have the parent object call the method in the currently selected radio constantly to see if there's anything to send to the DTMF object.

    Alternatively, each radio object can have a 'dtmf : "DTMF"' object. As each radio object is first selected (when the 'F2' '1' arrives, let's say we're selecting the TK931), the app-segment can call DR235.Deselect and IC208.Deselect, then TK931.Select. The 'Deselect' methods call 'dtmf.Stop', and if it was running, it does a cogstop(). If not, nothing happens. The 'Select' method calls 'dtmf.Start', and starts the DTMF generator in a new cog. Later, if any actual DTMF keys come from the app-segment, the radio object calls 'dtmf.StartTone (key)' and the tone plays until 'dtmf.StopTone' is called.

    The disadvantages to the first technique is that it requires the parent to shuffle data and adds latency between putting the mic key into the radio objects buffer and noticing there's a message for a DTMF request (the app-segment could (theoretically) be doing something else, like processing a command from a serial port. The downside to the second method is that each time the radio is selected, there's the shutdown and start-up time of managing the DTMF cog when the radio object 'Select' method is called.

    Somewhere I saw a post that indicated what the start-up time for a cognew() is. I can't seem to find that again. Any recollection what the approximately time can be?

    --jc
  • KyeKye Posts: 2,200
    edited 2010-01-31 15:46
    If you want to share an object between child and parent objects make sure that all its memory is declared as DAT and not as VAR.

    VAR images are copied per instance of each object.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nyamekye,
  • Mike GreenMike Green Posts: 23,101
    edited 2010-01-31 15:53
    Startup time is roughly 512 (# of longs to copy to cog) x 16 (system clocks per hub access) x system clock time = 512 x 16 x 12.5ns = 104us (for an 80MHz system clock).
  • StefanL38StefanL38 Posts: 2,292
    edited 2010-01-31 16:17
    OK continous backroundprocessing is a reason

    Nevertheless I want to suggest again to do it sequentially. If it really works sequentially depends on how fast the backroundprocessing has to be done.

    For example if you have low-high-low pulses that are VERY short of course you have to dedicate an extra cog to detect these pulses properly.
    How fast does it has to be in the worst case and how long does a complete loop take?

    If you keep ALL methods in ONE spin-file EVERY method regardless of which cog is calling the method has access to any other method and any variable.

    Maybe you believe me if a post a democode here

    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
    VAR
      long CogStack1[noparse][[/noparse] 20]
      long CogStack2[noparse][[/noparse] 20]
      
      long MyTestVar
    
    OBJ
      debug : "FullDuplexSerial"
    
    PUB Main
    'the FIRST PUB-Method inside a spinfile is ALWAYS the startpoint where the program starts to run
      'heart.Start(27, 200)
      debug.start(31, 30, 0, 9600)
      
      MyTestVar := 100
      debug.str(string("Start MyTestVar="))
      debug.dec(MyTestVar)
      debug.Tx(13)
    
      'as long as the cognew is in the SAME *.spin-file
      'methods and variables can be shared even across cogs
      cognew(M1,@CogStack1)  
      cognew(M2,@CogStack2)
      
      repeat
        waitcnt(clkfreq + cnt)
        debug.str(string("MyTestVar="))
        debug.dec(MyTestVar)
        debug.Tx(13)
    
    PUB M1
      repeat
        waitcnt(ClkFreq * 3 + cnt)
        Shared_Method(1)
    
    PUB M2
      repeat
        waitcnt(ClkFreq * 5 + cnt)
        Shared_Method(2)
    
    PUB Shared_Method(parameter)
        MyTestVar := parameter   
    
    



    if you run this code you should see an output in PST.exe similar to this

    Start MyTestVar=100
    MyTestVar=100
    MyTestVar=100
    MyTestVar=1
    MyTestVar=1
    MyTestVar=2
    MyTestVar=1
    MyTestVar=1
    MyTestVar=1
    MyTestVar=1
    MyTestVar=2
    MyTestVar=2
    MyTestVar=1
    MyTestVar=1 
    
    



    If the baclroundprocessing has to be done continously you have to implement a flag like "dtmf_in_use" to avoid crossing access to the dtmf-method

    If You want to have only ONE DTMF-generator that changes his connection you have to use a busy-flag regardless of which version you use to realise that.
    If you start a DTMF-cog each time you need it you have to take care that it isn't still in use by a former command. It is the same if you let the dtmf-cog run all the time.

    Whenever two DTMF-activities came across the signals get messed up

    If you define a DTMF-object in its own *.spin-file you could add a DTMF_Busy-method that gives back true or false

    First thing that the send_dtmf-method does is to set the flag DTMF_busy to true and last thing it does is to set the flag DTMF_busy to false

    the method that wants to call send_dtmf has to wait

      repeat until not (DTMF.dtmf_busy)
      DTMF.Send_dtmf(parameterlist)
    
    



    If you can't wait all the time code a repeat-loop

      repeat
        if (not (DTMF.dtmf_busy) ) and (not (dtmf_sended) )
          DTMF.Send_dtmf(parameterlist)
          dtmf_sended := true 
    
        DoBackroundprocessing
    
    



    Maybe the logic could be done more elegant. If so I invite everybody to post the more elegant code

    best regards

    Stefan

    Post Edited (StefanL38) : 1/31/2010 4:40:09 PM GMT
  • mparkmpark Posts: 1,305
    edited 2010-01-31 16:59
    It's not standard Spin, but you can pass object pointers in Homespun (after a fashion).

    http://forums.parallax.com/showthread.php?p=777920

    Scroll down to "simpler example".
Sign In or Register to comment.