Shop OBEX P1 Docs P2 Docs Learn Events
Cog Confusion — Parallax Forums

Cog Confusion

I thought I had figured it out, but no...

I have four stepper motors that I want to run as parallel processes. From a cold start everything works. All four motors run according to their parameters, independently. Each motor runs its number of steps and then stops. I can repeat sending parameters to the motors over and over with no problem as long as I keep the same sequence of controlling the motors. But when I restart a motor out of the sequence I used at the cold start, another motor that was running stops. Here is my attempt to post the code; first the main program controlling the launch of the motor sequences (only the pertinent details posted):

==============================================

OBJ

PLCSerial : "FullDuplexSerialPlus"
M1 : "Stepper1"
M2 : "Stepper2"
M3 : "Stepper3"
M4 : "Stepper4"


main


pub main | pos

case motor

1 : M1.Start (Counts[motor], Speed[motor], Direction[motor])

2 : M2.Start (Counts[motor], Speed[motor], Direction[motor])

3 : M3.Start (Counts[motor], Speed[motor], Direction[motor])

4 : M4.Start (Counts[motor], Speed[motor], Direction[motor])
===============================================

And the four stepper controller cogs look like this (only the pertinent details posted):

===================================================

Pub Start (cnts,spd,dir)

Stop

Cog := (cognew(Step (cnts,spd,dir),@Stack) + 1)


Pub Stop
if Cog
cogstop(Cog~ - 1)

================================================


Any words of advice?

Comments

  • There are a number of things I don't understand from what you've posted. For a start ... why do you have 4 separate objects: Stepper1, Stepper2, Stepper3, Stepper4? Do they not do the same thing? I assume that Stack and Cog are declared within each of the objects ... is that not so?

    In any event, there's not enough information provided to answer your question or advise you.
  • Thanks Mike,

    I will try to post better, For what it is worth, the four separate object are identical except for the I/O they address and the values they return. Cog space is set at 128 in each object, and each object has a 'cog' variable declared.

    I appreciate your response! You've given me things to think about...

    Dave
  • I think Mike was indicating that you could code it something like this:

    OBJ
    M[4] : "Stepper"

    PUB Main
    M[motor].Start (ioPin[motor], Counts[motor], Speed[motor], Direction[motor])

    Each object in the array will have its own private instance of any variables declared by the object (like Cog and Stack for example). Hence, if the "object" works by itself, in its entirety creating an array of them will all work as well (within hardware limits of course). The best thing about this is you only have one block of code to debug instead of four in your case.

    I use this approach frequently for coding convenience, for example to set up simple bidirectional serial communications between a master Prop and two slaves I have the master declare:

    OBJ
    FDS[2] : "FullDuplexSerial"

    and the slaves declare:

    OBJ
    FDS : "FullDuplexSerial"

    then the master simply calls FDS.Start(...) for each of them with appropriate I/O pin parameters and the slave FDS.Start(...)
  • This is very useful as it revisits a problem that a lot of new users confront.

    You can certainly have four independent steppers on four individual cogs, but it is primarily a matter of cleaning up your coding style to allow you to properly evoke them.
    You can even have a fifth cog oversee the four, if you so desire.

    I've successfully done something similar in Forth, but you really can and should stick with Spin unless you desire interactive control of the motors.
  • Dave MatthewsDave Matthews Posts: 93
    edited 2015-11-22 13:52
    I found the code button! (I am very slow on the uptake :lol: )

    I appreciate all of the comments, and I understand how a single object can contain the code to launch the four cogs I need just by adding the I/O pins to the exchange. That should have been obvious to me!

    I will implement that technique soon, but first I want to understand why what I am doing fails.

    Is this a proper method to launch a cog from within an object? Would other subsequent cog launches stop this cog?

    VAR
      long  idx, stpIdx, stpDelay
      long Stack[128] 
      byte Done
      byte Cog
                                      
    Pub Start (cnts,spd,dir) 
     
    Stop
    
    Cog := (cognew(FullStep (cnts,spd,dir),@Stack) + 1)
    
    
    Pub Stop
    if Cog > -1
      cogstop(Cog)
    
      
    PUB FullStep (Counts, Speed, Direction)                           
    dira[15..0]~~                           'make pins outputs    
    
    Done := 0  
    
    stpDelay := 40
                                                   
     If direction == 0
          
         repeat counts                           'Distance
          stpDelay := stpDelay +3 <#(Speed)      'ramp      
          stepFwd      
          waitcnt(clkfreq/stpDelay + cnt)    
    
          outa[3..0] := 0
     else
          repeat counts                           'Distance
           stpDelay := stpDelay +3 <#(Speed)                          
           stepRev      
           waitcnt(clkfreq/stpDelay + cnt)    
    
           outa[3..0] := 0
          
    Done:= 1
    
    
    Pub DoneCheck : Complete
        Complete := Done      'When the done flag is set, the calling program sees Complete
                            
    PRI stepFwd
    
      stpIdx := ++stpIdx // 4                    ' point to next step
      outa[3..0] := Steps[stpIdx]                ' update outputs
      
    
    PRI stepRev
    
      stpIdx := (stpIdx + 3) // 4                ' point to previous step
      outa[3..0] := Steps[stpIdx]                ' update outputs
      
    
    DAT
    
      Steps       byte  %0011, %0110, %1100, %1001                           
    
    

    I feel I have a typo or something I need to learn from!
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-11-22 17:22
    The biggest problem with that object code is that it doesn't allow you to pass pin numbers -- a huge violation in the idea of reusable objects.

  • So, for example, you'd have
    Pub Start (firstPin, cnts, spd, dir)
       Stop
       Cog := cognew( FullStep (firstPin, cnts, spd, dir), @Stack) + 1
    
    Pub FullStep (firstPin, cnts, spd, dir)
       dira[ firstPin+3..firstPin] ~~
    
    ...
    
       outa[ firstPin+3..firstPin] := 0
    

    For some reason, you're setting 16 I/O pins to outputs. This particular cog only should affect a set of 4 I/O pins. Other cogs might setup other I/O pins. That doesn't explain what you're seeing.

    You're not resetting Cog in your Stop method. When you call Start again later, it will first call Stop which uses the "old" cog number saved in Cog to stop that cog which now is running something else. You want to use
    Pub Stop
      if cog > 0
        cogstop(cog~~ - 1)
    
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-11-22 17:02
    Your stop routine needs work:
    Pub Stop
      if Cog > 0
        cogstop(Cog - 1)
        Cog := 0
    

    -Phil
  • Okay... my ride to a trade show is running 15 minutes late so I was able to knock-up this little driver that is much more flexible.
  • Mike GreenMike Green Posts: 23,101
    edited 2015-11-22 20:32
    JonnyMac's Stop method has the same issue. It should be
    pub stop
    
    '' Stop and unload stepper driver if running
    
      if (cog)
        cogstop(cog~ - 1)                                           ' stop the cog
    
      longfill(@nsteps, 0, 3)
    

    You probably want to have the following as well
    pub running   '  is stepper still running?
       result := nsteps > 0
    

    Note that you can't call run_steps if nsteps > 0 because you'll change sdir and sdelay while run_stepper is still using the old values (isn't multi-processor coding fun!). You want to put "repeat while running" at the beginning of run_steps as well as step_forward and step_reverse to prevent this.
  • JonnyMac wrote: »
    The biggest problem with that object code is that it doesn't allow you to pass pin numbers -- a huge violation in the idea of reusable objects.

    I understand... Thanks for the reply and for yet again another code example!
    :blush:

  • Dave MatthewsDave Matthews Posts: 93
    edited 2015-11-22 20:11
    Mike Green wrote: »

    You're not resetting Cog in your Stop method. When you call Start again later, it will first call Stop which uses the "old" cog number saved in Cog to stop that cog which now is running something else. You want to use
    Pub Stop
      if cog > 0
        cogstop(cog~~ - 1)
    

    Thanks for your help Mike, I'm trying to understand this:

    When Stop is called, IF the variable 'cog' has a value greater than 0, COGSTOP is invoked stopping the cog who's number is one less than the value held in the variable 'cog'. Then the variable 'cog' is Post-Set to -1.

    When COGNEW is then invoked the variable 'cog' gets set to the the address of the newly started cog +1 so the value of 'cog' is from 1 to 8.

    Am I close?
  • Mike GreenMike Green Posts: 23,101
    edited 2015-11-22 20:34
    I mis-typed the "~" operator as "~~". Sorry. You do want to set cog to zero if the cog is stopped. You're correct
  • Dave MatthewsDave Matthews Posts: 93
    edited 2015-11-23 12:16
    Thanks to everyone for the help. I have a much better understanding of this technique than I did before. Also I learned more about passing parameters when launching a cog. A great experience for me, thanks again.

    I have modified the stop code to no avail, and I have determined that there is something in the main object that could be the culprit. I have written test code that just sends hard coded parameters to the cogs, over and over, and that works without a failure. Nope, that only works if the sequence of calling the motors in order is not changed!

    Before I go further I will find that bug, then I will re-write the stepper code to use one reusable object.

    I appreciate the time you all have taken!
  • Be sure to use a separate Cog variable for each cog you start, otherwise each time you start one, it will overwrite the value of the last one, and when you try to stop, only the last one started will be stopped.
  • Sapphire wrote:
    Be sure to use a separate Cog variable for each cog you start, otherwise each time you start one, it will overwrite the value of the last one, and when you try to stop, only the last one started will be stopped.
    I'm assuming he's writing this as an object that will have several instances invoked externally. In that case, the VAR variables (including Cog) get replicated for each instance.

    -Phil
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-11-22 23:59
    Sorry, I was trying to blast that out and do a quick test before my ride arrived. This is my fix (post ~ and ~~ are not used in my code, ever)
    pub stop
    
    '' Stop and unload stepper driver if running
    
      if (cog)
        cogstop(cog - 1)                                             ' stop the cog
        cog := 0                                                     ' mark stopped
    
      longfill(@nsteps, 0, 3)
    

    FWIW, I started two instances of my object and ran them on a QuickStart using the LEDs to show me what the pins were doing. Other than leaving that line out of the stop() method, everything else seems to be working.
    Note that you can't call run_steps if nsteps > 0 because you'll change sdir and sdelay while run_stepper is still using the old values
    Yes, you can, because the background cog uses global variables for steps, direction, and timing. That said, you may have to wait for the current step delay to time out. I ran this bit of code as a test and it behaved as expected.
      m1.run_steps(100, 1, 1000 * MS_001)
      time.pause(5000)
      m1.run_steps(100, 1, 250 * MS_001)
      time.pause(2000)
      m1.run_steps(100, 1, 1000 * MS_001)
    

    It may not be perfect, but for simple applications I think the object is workable.
  • Cluso99Cluso99 Posts: 18,069
    Not looking too closely, but I see that you are using the same stack for all cogs!

    You require a separate stack for each cog.
  • Cluso99 wrote:
    Not looking too closely, but I see that you are using the same stack for all cogs!
    No, he isn't. Each instance of the object he's writing allocates a separate set of VAR variables, including the stack.

    -Phil
  • Cluso99Cluso99 Posts: 18,069
    Thanks Phil. I didn't realise that it was an object posted, so yes they are a different set of VARs for each object instance.
  • I am still having problems with my cog starts and stops. This is not the final version that invokes cognew to launch 4 cogs that are then never stopped but rather my abandoned method to start and restart a cog every time I wanted a motor to run. So this is not a request for a cure or code example for the code I will post below, but merely to describe what I have found and don't understand.

    I made some test code to report which cogs were started when each motor was started, each time invoking cognew via the start/stop technique. If I start the 4 motors over and over again in the same sequence , the cogs seem to behave as I think is correct. When the motor routine is begun, each starts the 'next available cog' in this case the sequence of 3 to 6. The next time the 4 motors routine is run, each cog that was running stops its cog, zeros it, then cognew reassigns the same cog to it, since that is the next available I guess. Here is a list of three iterations of the 4 motors being started and the cogs assigned.

    M1 C3
    M2 C4
    M3 C5
    M4 C6

    M1 C3
    M2 C4
    M3 C5
    M4 C6

    M1 C3
    M2 C5
    M3 C5
    M4 C6


    If I change the order of the motors starting the cogs do not behave the same way. The first time the routine is run the cogs are assigned as I would expect based upon which one was started first.

    M2 C3
    M3 C4
    M4 C5
    M1 C6

    The next motor run routine the motors do not seem to get assigned unique cogs. The first two motor routines get the same cog assigned .

    M1 C3 (I would expect M1 to stop C6 from above, and perhaps re-issue it)
    M2 C3 STOPS M1
    M3 C4
    M4 C5

    The third time is much worse:

    M3 C3
    M4 C4
    M1 C3 STOPS M3
    M2 C3 STOPS M1

    So why is this? it appears that my stop method is not working correctly, but it works when the motors are called in the same order.




    Here's the code I used to test this situation this is the top level object:
    CON
    
      _CLKMODE      = XTAL1 + PLL16X
      _XINFREQ      = 5_000_000
    
      CR = $0D
      LF = $0A
      CRSRLF = $03
         
    
    VAR
       
       long counts[5]
       long speed[5]
       long motor
       byte PLCstring[20]
       byte direction[5]
       byte DoneFlag[5]
      
                                         
    OBJ
       
        PLCSerial  :  "FullDuplexSerialPlus"
    
        M[5] : "Stepper"
      
    
    
                                      
    PUB init
    
    {Initialize PLC Unit Communications Parameters}
    
       PLCSerial.start(26, 27, 0, 9600)   'Serial Lines and Baud Rate
       waitcnt(clkfreq/4+cnt)
    
       
       main
     
    pub main 
    
                                        
    PLCSerial.rxflush
    
    repeat
    
          
       
       Repeat until PLCSerial.rxcheck == 77   'ASCII 'M'
    
       
    
      
      M[2].Start (2,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)                                                                       
      M[3].Start (3,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)   
      M[4].Start (4,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)        
      M[1].Start (1,4000, 200, 0)
      
      PLCSerial.tx(CR)   
      PLCSerial.str(string("Motor 1 Cog = "))  
      PLCSerial.dec(M[1].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 2 Cog = "))
      PLCSerial.dec(M[2].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 3 Cog = "))
      PLCSerial.dec(M[3].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 4 Cog = "))
      PLCSerial.dec(M[4].CogCheck )
      PLCSerial.tx(CR)
     
      
      
        waitcnt(clkfreq*30+cnt)
    
       
          
      M[1].Start (1,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)                                                                       
      M[2].Start (2,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)   
      M[3].Start (3,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)        
      M[4].Start (4,4000, 200, 0)
    
      PLCSerial.tx(CR) 
      PLCSerial.str(string("Motor 1 Cog = "))
      PLCSerial.dec(M[1].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 2 Cog = "))
      PLCSerial.dec(M[2].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 3 Cog = "))
      PLCSerial.dec(M[3].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 4 Cog = "))
      PLCSerial.dec (M[4].CogCheck )
      PLCSerial.tx(CR)
    
    
    
      
          waitcnt(clkfreq*30+cnt)
    
                                                                                                                           
      M[3].Start (3,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)                                                                          
      M[4].Start (4,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)   
      M[1].Start (1,4000, 200, 0)
      waitcnt(clkfreq*2+cnt)        
      M[2].Start (2,4000, 200, 0)
    
                        
      PLCSerial.str(string("Motor 1 Cog = ")) 
      PLCSerial.dec(M[1].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 2 Cog = "))
      PLCSerial.dec(M[2].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 3 Cog = "))
      PLCSerial.dec(M[3].CogCheck )
      PLCSerial.tx(CR)
    
      PLCSerial.str(string("Motor 4 Cog = "))
      PLCSerial.dec(M[4].CogCheck )
      PLCSerial.tx(CR)
    


    And here is the stepper object:
    VAR
      long  idx, stpIdx, stpDelay
      long Stack[128] 
      byte Done
      byte cog
      byte firstpin
    
    
      
    
    
    Pub Start (mtr,cnts,spd,dir) 
     
    Stop
    
    Cog := (cognew(FullStep (mtr,cnts,spd,dir),@Stack) + 1)
    
    
    Pub Stop
    if cog > 0
      cogstop(cog - 1)
       cog := 0
    
      
    PUB FullStep (Motor,Counts, Speed, Direction)
    
    firstpin := (Motor - 1) * 4
    
    dira[firstpin..firstpin+3]~~
    
    
    Done := 0  
    
    stpDelay := 40
                                                   
     If direction == 0
          
         repeat counts                           'Distance
          stpDelay := stpDelay +3 <#(Speed)      'ramp      
          stepFwd      
          waitcnt(clkfreq/stpDelay + cnt)    
    
          outa[firstpin..firstpin+3] := 0
     else
          repeat counts                           'Distance
           stpDelay := stpDelay +3 <#(Speed)                          
           stepRev      
           waitcnt(clkfreq/stpDelay + cnt)    
    
           outa[firstpin..firstpin+3] := 0
          
    Done:= 1
    
    Pub CogCheck : Cognum
        Cognum := cog      
    
    Pub DoneCheck : Complete
        Complete := Done      'When the done flag is set, the calling program sees Complete
                            
    PRI stepFwd
    
      stpIdx := ++stpIdx // 4                    ' point to next step
      outa[firstpin..firstpin+3] := Steps[stpIdx]      ' update outputs
      
    
    PRI stepRev
    
      stpIdx := (stpIdx + 3) // 4                ' point to previous step
      outa[firstpin..firstpin+3] := Steps[stpIdx]      ' update outputs
      
    
    DAT
    
      Steps       byte  %0011, %0110, %1100, %1001                           
    

  • What is the state of outa[firstpin...firstpin+3] when dira[...] is executed in FullStep? The moment that dira[...]~~ is executed whatever the outa[...] register is set to becomes the io state on the pins, so if you don't define it explicitly you get whatever is there.
    PUB FullStep (Motor,Counts, Speed, Direction)
    firstpin := (Motor - 1) * 4
    dira[firstpin..firstpin+3]~~
    
  • Thanks for the reply mmowen, but I am not having any issue with the outa steps. The issue I have is with unrelated cogs being stopped by cognew calls. The outputs are set explictly by:
    stpIdx := ++stpIdx // 4                    ' point to next step
      outa[firstpin..firstpin+3] := Steps[stpIdx]      ' update outputs
    

    I hope you had a great holiday weekend, thanks again.

    Dave
  • So for grins I stripped your example down to just the start/stop and waitcnt code as follows and ran a couple tests:

    "FakeMotor.spin" an object:
    VAR
      byte  cog
      long  stack[128]
    
    PUB Start(motor,counts,Speed,dir)
      Stop
      cog := cognew(FullStep(motor,counts,Speed,dir),@stack)+1
    
    PUB Stop
      if cog
        cogstop(cog~ - 1)
    
    PUB ThisCog
      return cog
    
    PRI FullStep(motor,counts,Speed,dir) | stpDelay
      repeat counts                           'Distance
          stpDelay := stpDelay +3 <#(Speed)      'ramp      
          waitcnt(clkfreq/stpDelay + cnt)    
    

    Driven by "FakeMotorTest.spin" the top level object:
    CON
      _clkmode = xtal1 + pll16x                  ' System clock → 80 MHz
      _xinfreq = 5_000_000                       ' external crystal 5MHz
    
    OBJ
      PST   : "Parallax Serial Terminal"
      M[5]  : "FakeMotor"
      
    PUB Main | i
      PST.Start(115_200)
      repeat 2
        waitcnt(clkfreq+cnt)
    
      M[2].Start (2,4000, 200, 0)
      M[3].Start (3,4000, 200, 0)
      M[4].Start (4,4000, 200, 0)
      M[1].Start (1,4000, 200, 0)
    
      repeat i from 1 to 4
        PST.str(string("M["))
        PST.dec(i)
        PST.str(string("] Cog = "))  
        PST.dec(M[i].ThisCog )
        PST.Newline
    
      M[1].Start (1,4000, 200, 0)
      M[2].Start (2,4000, 200, 0)
      M[3].Start (3,4000, 200, 0)
      M[4].Start (4,4000, 200, 0)
    
      PST.Newline
      repeat i from 1 to 4
        PST.str(string("M["))
        PST.dec(i)
        PST.str(string("] Cog = "))  
        PST.dec(M[i].ThisCog )
        PST.Newline
    
      M[3].Start (3,4000, 200, 0)
      M[4].Start (4,4000, 200, 0)
      M[1].Start (1,4000, 200, 0)
      M[2].Start (2,4000, 200, 0)
    
      PST.Newline
      repeat i from 1 to 4
        PST.str(string("M["))
        PST.dec(i)
        PST.str(string("] Cog = "))  
        PST.dec(M[i].ThisCog )
        PST.Newline
    

    Here's the output:
    M[1] Cog = 6
    M[2] Cog = 3
    M[3] Cog = 4
    M[4] Cog = 5
    
    M[1] Cog = 6
    M[2] Cog = 3
    M[3] Cog = 4
    M[4] Cog = 5
    
    M[1] Cog = 6
    M[2] Cog = 3
    M[3] Cog = 4
    M[4] Cog = 5
    

    Note that the cog numbers are all consistent.
  • I appreciate the time you are taking with this!

    I will try this and learn....

    Dave

Sign In or Register to comment.