Shop OBEX P1 Docs P2 Docs Learn Events
Program runs, but hangs after 3-4 minutes — Parallax Forums

Program runs, but hangs after 3-4 minutes

I have tried to create a loop that searches pin state for two pins, and if there is no change, continues to loop and search inputs. I am monitoring an encoder, and if it is turned, the states change, and it leaves the loop to turn a stepper motor. I have tried various code in the loop, but nothing has been successful. The code works and does what it is suppose to do, but it will not run more than 4 minutes without locking up. What am I doing wrong? I have attached the spin program.

Comments

  • ElectrodudeElectrodude Posts: 1,621
    edited 2020-02-17 22:52
    Probably a stack overflow. You have written your code such that each function calls the next function, but you can't do this, because every function call is made with the expectation that the called function will return to the parent, and not just keep jumping somewhere else. Eventually, memory gets filled up with return information, and the program crashes.

    Try rewriting your program using repeat and if statements, instead of having each function just call the next one.

    You should clean up your indentation; you're asking for trouble doing otherwise. It's inconsistent everywhere, and Spin relies on indentation to represent flow control structure.

    For example, move the contents of the "pause" function to be inside an infinite "repeat" at the end of "Main". At one point in "ENC1", you call "pause" again - replace this with a return, so that flow control returns to the main loop, instead of starting yet another instance of "pause".
  • Thanks for the clue. Not sure if I understood comment about repeat and if statements. Changed to the following code using repeat and if ....with same results.


  • RaymanRayman Posts: 13,883
    Hmm... I wonder if it's the old FT232 reset glitch...

    If your code uses serial, but you don't have a PC connected to the serial port, the FT232 is not powered.
    But, the TX pin of Prop can slowly power up the FT232. Once it powers up, first thing it does is reset the Prop....
  • What is the FT232?
  • You still have the same problem mentioned by Electrodude.

    Main calls the method pause which calls enc1 which can call pause again. This sort of structure will cause the Propeller to run out of memory.
  • You might want to read JonnyMac's Spin Zone article "Column #6, May 2010: Spinning Up Fun with Encoders." This article really helped me when I was working on an encoder project. Here's a link to the download for the zipped articles.
  • jcfjr wrote: »
    What is the FT232?

    FT232 refers to the FTDI chip which converts between USB and 3.3V signals. If a program sends serial data to this chip when the USB isn't connected, it can cause a program to reset. This is only a problem when the board isn't connected to a PC over USB. Your program has serious structuring issues. I don't think you need to worry about the FT232 right now.
  • Duane Degn wrote: »
    You still have the same problem mentioned by Electrodude.

    Main calls the method pause which calls enc1 which can call pause again. This sort of structure will cause the Propeller to run out of memory.

    I see what you are saying, but I am struggling to think of a proper structure. Instead of sending it to another method from an if statement, should I put the whole Pub method code under the if, or return it to main and have main send it to Cwenc or ccwenc? Is proper structure to have main do all the directing to methods, and methods return to main? I will think more on this and try some things. I would think the loop would run if it never got out to enc1 where it was directed someplace else. Thanks for the ideas, I will try to digest.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2020-02-18 14:08
    Here's an example of using three encoders.
    It shows an example of using methods and how to use arrays for multiple instances of similar data.
    {Read three encoders}
    
    
    CON
      _clkmode = xtal1 + pll16x                                              
      _xinfreq = 5_000_000
    
    VAR
      
      long oldState[3], newState[3], reverseFlag[3]
      long encoderPosition[3]
    
    OBJ
    
      Pst : "Parallax Serial Terminal"
                  
    PUB Setup | localIndex
    
      Pst.Start(115_200)               
      
      waitcnt(800000 + cnt)
      
      repeat localIndex from 0 to 2
        oldState[localIndex] := ina[pinA[result]..pinB[localIndex]]
       
      MainLoop  
    
    PUB MainLoop | localIndex  
    
      repeat
        repeat localIndex from 0 to 2
          result := CheckForChange(localIndex)
          if result  ' Any non-zero value will be satisfy condition
            ProcessChange(localIndex)
    
            'It would probably be good to debug from a different cog.
            Pst.Str(string(pst#NL,"Encoder # "))
            Pst.Dec(localIndex)
            Pst.Str(string(" = "))
            Pst.Dec(encoderPosition[localIndex])
            if reverseFlag[localIndex]
              Pst.Str(string(" (Reverse)"))
            else
              Pst.Str(string(" (Forward)"))
              
    PUB CheckForChange(localIndex)
    ' result starts as zero. Zero is also considered false.
    ' Returns zero if no change. Returns true (aka -1) if change is found.
    
      newState[localIndex] := ina[pinA[localIndex]..pinB[localIndex]]
      result := newState[localIndex] <> oldState[localIndex]
    
    PUB ProcessChange(localIndex)
    
      case oldState[localIndex]
        %00:
          case newState[localIndex]
            %01:
              reverseFlag[localIndex] := false ' false equals zero
              encoderPosition[localIndex]++
            %11:
              'Error. Add flag to indicate encoder skipped a position.
            %10:
              reverseFlag[localIndex] := true
              encoderPosition[localIndex]--
        %01:
          case newState[localIndex]
            %11:
              reverseFlag[localIndex] := false ' false equals zero
              encoderPosition[localIndex]++
            %10:
              'Error. Add flag to indicate encoder skipped a position.
            %00:
              reverseFlag[localIndex] := true
              encoderPosition[localIndex]--
        %11:
          case newState[localIndex]
            %10:
              reverseFlag[localIndex] := false ' false equals zero
              encoderPosition[localIndex]++
            %00:
              'Error. Add flag to indicate encoder skipped a position.
            %01:
              reverseFlag[localIndex] := true
              encoderPosition[localIndex]--
              
        %10:
          case newState[localIndex]
            %00:
              reverseFlag[localIndex] := false ' false equals zero
              encoderPosition[localIndex]++
            %10:
              'Error. Add flag to indicate encoder skipped a position.
            %11:
              reverseFlag[localIndex] := true
              encoderPosition[localIndex]--
    
      oldState[localIndex] := newState[localIndex]
      
    DAT
    
    pinA                    byte 22, 20, 18
    pinB                    byte 23, 21, 19
    
    
  • I just had a similar problem. I had a main menu on the LCD screen. With a repeat loop waiting for a push button input INA[12]==1 for example. After 15 minutes the LCD would go blank but the prop was still running. If I unplugged the LCD serial cable and plugged it back in the screen reset.
    What the issue was, the loop was called PUB Main and the end of it, outside of the repeat loop I had another Main statement. When I deleted the Main it worked perfect. See Spin
  • DigitalBob wrote: »
    I just had a similar problem. I had a main menu on the LCD screen. With a repeat loop waiting for a push button input INA[12]==1 for example. After 15 minutes the LCD would go blank but the prop was still running. If I unplugged the LCD serial cable and plugged it back in the screen reset.
    What the issue was, the loop was called PUB Main and the end of it, outside of the repeat loop I had another Main statement. When I deleted the Main it worked perfect. See Spin

    Thanks for your input. As it turned out, I was having the same problem. I have re-written my code per Duane's input(attached), and it is now running. But in the meantime, Duane has written a method that is far more efficient, and he has used an array to shorten. Most likely will use his. Thanks again for taking the time to help.
  • Duane Degn wrote: »
    Here's an example of using three encoders.
    It shows an example of using methods and how to use arrays for multiple instances of similar data.

    /quote]

    I rewrote my program as per your suggestion (attached), and it now runs...Thank you! Also thank you for the code you wrote and sent for three encoders, I was wandering how to add the second two to my program. Yours is much more efficient and elegant than mine, so will most likely use your code. What is the advantage of using localindex in each method? Could you also declare it in the VAR block? Why sometimes (), and others | ? I have read the Prop manual many times on variables and still struggle with using in methods. I was thinking of running this is its own cog, but could not figure out how to pass variables back to main Object. Oh, by the way, in your first repeat loop.... ina[pinA[result]..., should that be ..ina[pinA[localindex]... ?
    Thanks again for taking the time and teaching me some things and sharing your code.
    Jim
  • Duane DegnDuane Degn Posts: 10,588
    edited 2020-02-19 00:07
    Why sometimes (), and others | ? I have read the Prop manual many times on variables and still struggle with using in methods

    I use "localIndex" instead of "i" as often seen in "C" programs just because it's easier to search and replace the variable name. It's usually considered good practice to minimize the use global variables. The program certainly could have used a "globalIndex" instead of having each method use it's own "localIndex".

    When I used this index variable with square brackets, it's being used as an array index. Example: "encoderPosition[localIndex]"
    When used with parentheses it's either calling a function or used in declaring the method.
    The code below calls the method "ProcessChange" using the value of "localIndex". In this code "localIndex" is local to "MainLoop".
        if result 
            ProcessChange(localIndex)
    

    The method "ProcessChange" could be declared using a different variable name than "localIndex".
    PUB ProcessChange(localIndex)
    

    The two sets of code below will behave exactly the same.
    The first code block below uses "localIndex" as the local variable. This version is the same as the code I posted originally.
    PUB MainLoop | localIndex  
    
      repeat
        repeat localIndex from 0 to 2
          result := CheckForChange(localIndex)
          if result  ' Any non-zero value will be satisfy condition
            ProcessChange(localIndex)
    
            'It would probably be good to debug from a different cog.
            Pst.Str(string(pst#NL,"Encoder # "))
            Pst.Dec(localIndex)
            Pst.Str(string(" = "))
            Pst.Dec(encoderPosition[localIndex])
     
              
    PUB CheckForChange(localIndex)
    
      newState[localIndex] := ina[pinA[localIndex]..pinB[localIndex]]
      result := newState[localIndex] <> oldState[localIndex]
    
    PUB ProcessChange(localIndex)
    
      case oldState[localIndex]
        %00:
          case newState[localIndex]
            %01:
              reverseFlag[localIndex] := false ' false equals zero
              encoderPosition[localIndex]++
            %11:
              'Error. Add flag to indicate encoder skipped a position.
            %10:
              reverseFlag[localIndex] := true
              encoderPosition[localIndex]--
        'other states removed from example code
    
      oldState[localIndex] := newState[localIndex]
    

    Below is a copy of the same code but with a different name for the local variable in each of the three methods. The code below should result in exactly the same byte code stored in RAM.
    PUB MainLoop | i  
    
      repeat
        repeat i from 0 to 2
          result := CheckForChange(i)
          if result  ' Any non-zero value will be satisfy condition
            ProcessChange(i)
    
            'It would probably be good to debug from a different cog.
            Pst.Str(string(pst#NL,"Encoder # "))
            Pst.Dec(i)
            Pst.Str(string(" = "))
            Pst.Dec(encoderPosition[i])
     
              
    PUB CheckForChange(j)
    
      newState[j] := ina[pinA[j]..pinB[j]]
      result := newState[j] <> oldState[j]
    
    PUB ProcessChange(k)
    
      case oldState[k]
        %00:
          case newState[k]
            %01:
              reverseFlag[k] := false ' false equals zero
              encoderPosition[k]++
            %11:
              'Error. Add flag to indicate encoder skipped a position.
            %10:
              reverseFlag[k] := true
              encoderPosition[k]--
        'other states removed from example code
    
      oldState[k] := newState[k]
    

    Each method has a local variable "result" which is initialized to zero. I often use this as a temporary variable. I had originally used "result" in many places where I ended up using "localIndex". I forgot to change one of the "result" variables for "localIndex" in the Setup method as you noticed.
    I have read the Prop manual many times on variables and still struggle with using in methods. I was thinking of running this is its own cog, but could not figure out how to pass variables back to main Object.

    Global variables can be used from any cog just fine. As long as these variables are in the same Object, there's no need to pass them. You can have multiple cogs running in a single object. Passing variables from a child object to a parent object requires a bit more work but this gets easy once you're used to it (like most things).

    I'll modify the three encoder and move the encoder part of the program in its own cog. This will let you see how easy it is for cogs to share variables. I'll post this modified code in a bit.
  • msrobotsmsrobots Posts: 3,704
    edited 2020-02-19 05:18
    I try my luck in describing the differences. It is all about the SCOPE of a Variable, that means WHO is able to access the Variable.

    All of this is bound to a Object (file), your Main Program file has its own SCOPE and a included (sub-)object bound to a file has its own SCOPE, but with the same rules.

    So lets start with the different SCOPES Spin supports.

    DAT for GLOBAL Variables needing a Value at program start.
    VAR for GLOBAL Variables not needing a Value at program start.

    GLOBAL means that every method in that file can access the Variable, no need to pass it around as Parameter of the method

    Next thing are LOCAL Variables, that what is defined after a | those Variables can JUST be used inside of that method, you can NOT access them for outside of that method.

    A method has a implicit defined RETURN Value, you do not need to use it, but you can RETURN one long to the caller of that method.

    Those things between () are the PARAMETER, so Variables you give the Method as INPUT.

    Here comes maybe your confusion a PARAMETER is a COPY of what you put there when calling.

    say you have a method to add two numbers
    
    Pub TestAdd | a,b,c
     a:=1
     b:=2
     c:=addNumbers(a,b)
    …
    
    PUB addNumbers(num1, num2) | num3
     num3:= num1+num2
     num1:=0
     num2:=0
    RETURN num3
    
    If you call TestAdd, you have 3 LOCAL Variables a,b,c and their SCOPE is in TestAdd, so the Main Program can not see them and addNumbers can't see them either, just TestAdd knows about them.

    I now set a to 1, b to 2 and call addNumbers expecting the result to be put into my Variable c.

    internally now following is happening: num1 from addNum gets a COPY from a, a does not change at all, and num2 gets a COPY from b.

    addNum does its addition and RETURNS a COPY of num3 into c, our Result of the addition.

    If you want change a PARAMETER of a Method inside of that method, you can do that, a PARAMETER is just a LOCAL Variable, but has already a Value.

    But it's just the LOCAL copy you overwrite, so in my example where I do that (not needed, just to show) I do NOT change a or b.

    The basic Idea of local Variables is that you can't shoot yourself in the foot too easy.

    Mike
  • Duane DegnDuane Degn Posts: 10,588
    edited 2020-02-20 04:42
    Here's the program with several modifications.

    The main modification is the use of an additional cog to monitor the encoders. Rather than one loop, this code now has two loops in the top objects.

    The encoders are continuously monitored and at the same time data is streamed to the serial terminal.

    I also changed the code in the "ProcessChange" method. Rather than have nested "case" statements, the code now uses "forward" and "reverse" arrays for comparisons. This shortens the code significantly. I always like it when the code gets shorter while doing the same thing. Hopefully you'll be able to follow the new logic by comparing it with the previous code. (I added a short explanation at the end of this post.)

    I made several style changes. I used constants for most of the values rather than using "magic numbers." Now the purpose of each number should be more apparent from the name of the constant.

    I had originally planned for the program to update the serial terminal once a second. Hence the use of "DEBUG_INTERVAL". This interval is now used to count seconds. I left comments on how to change the code back to the one second update version if you want to switch the way data is updated. I mainly wanted to demonstrate how one can use timed events without blocking the code. The commonly used "waitcnt" statement blocks the execution of the code.

    When having the serial terminal writing on top of previous text, it's a good idea to clear the end of line to get rid of text which may be left over from earlier debug statements. I use the control character "11" to clear the end of the line prior to using a carriage return.
        Pst.Str(string(11, 13, "Encoder # "))
    

    I generally use the "11, 13" combo with the Parallax Serial Terminal.exe program. When I use TeraTerm, I use the combo "10, 13" to properly format serial output.

    In general, you don't want to output serial from multiple cogs. Using serial output from multiple cogs generally requires the use of locks.

    Using multiple cogs comes with unique challenges but it's also what makes programming the Propeller so fun.

    Here's the new program (also attached as a Spin file).
    DAT programName         byte "EncodersInCog200218a", 0 ' I like knowing which program is loaded in Propeller.
    CON
    {{Start separate cog to read three encoders.
      The original cog will display debugging information.
      Debug data updated once a second. *Edit: This line is incorrect.*
    }}
    CON
      _clkmode = xtal1 + pll16x                                              
      _xinfreq = 5_000_000
    
      'First of two pins for each encoder.
      'Encoders must use two sequential pins.
      ENCODER_0_PIN = 22
      ENCODER_1_PIN = 20
      ENCODER_2_PIN = 18
    
      NUMBER_OF_ENCODERS_3 = 3 'I often include value of constant in constant name.
      MAX_ENCODER_INDEX_2 = NUMBER_OF_ENCODERS_3 - 1
      
      ENCODER_STACK_SIZE_50 = 50
    
      DEBUG_INTERVAL = 80_000_000 '  one second at 80MHz
      
    VAR
    
      long encoderStack[ENCODER_STACK_SIZE_50]
      long oldState[NUMBER_OF_ENCODERS_3], newState[NUMBER_OF_ENCODERS_3], reverseFlag[NUMBER_OF_ENCODERS_3]
      long encoderPosition[NUMBER_OF_ENCODERS_3]
      long secondsActive
      long encoderCog
      long encoderLoops, encoderChanges
      
    OBJ
    
      Pst : "Parallax Serial Terminal"
                  
    PUB Setup | setupIndex
    
      Pst.Start(115_200)               
      
      waitcnt(800000 + cnt)
      
      repeat setupIndex from 0 to MAX_ENCODER_INDEX_2
        oldState[setupIndex] := ina[pinA[setupIndex]..(pinA[setupIndex] + 1)]
    
      'It's not necessary to save cog ID if cog isn't going to be stopped later.
      encoderCog := cognew(EncoderLoop, @encoderStack)
       
      CogZeroLoop  
    
    PUB CogZeroLoop | localIndex, debugTimer  
    
      debugTimer := cnt
      
      repeat
        DebugData 'comment this line out to update once a second.
        if cnt - debugTimer > DEBUG_INTERVAL
          debugTimer += DEBUG_INTERVAL
          secondsActive++
          'DebugData 'Uncomment to update once a second.
        ' This cog can do other tasks between debug statements.
          
    PRI DebugData
    
      Pst.Home
    
      ' I sometimes forget which version of code is running.
      ' I often include the program name in the output when possible.
      
      Pst.Str(string(11, 13, "programName = "))
      Pst.Str(@programName)
      Pst.ClearEnd
      Pst.NewLine
      
      repeat result from 0 to MAX_ENCODER_INDEX_2
        Pst.Str(string(11, 13, "Encoder # "))
        Pst.Dec(result)
        Pst.Str(string(" = "))
        Pst.Dec(encoderPosition[result])
        if reverseFlag[result]
          Pst.Str(string(" (Reverse)"))
        else
          Pst.Str(string(" (Forward)"))
    
      'Extra info in case you're interested.
      'All the variables displayed below could be deleted from program.
      Pst.ClearEnd
      Pst.NewLine
      Pst.Str(string(11, 13, "secondsActive = "))
      Pst.Dec(secondsActive)
      Pst.Str(string(11, 13, "debug cog = "))
      Pst.Dec(cogid)
      Pst.Str(string(11, 13, "encoderCog = "))
      Pst.Dec(encoderCog)
      Pst.Str(string(11, 13, "encoderLoops = "))
      Pst.Dec(encoderLoops)
      Pst.Str(string(11, 13, "encoderChanges = "))
      Pst.Dec(encoderChanges)
      
      Pst.ClearEnd  'Clear end of line of previous debug data.
      Pst.NewLine  ' Move down one line can clear all lines below.
      Pst.ClearBelow
        
    PRI EncoderLoop | encoderIndex  ' Runs in its own cog.
    
      repeat
        repeat encoderIndex from 0 to MAX_ENCODER_INDEX_2
          result := CheckForChange(encoderIndex)
          if result  ' Any non-zero value will be satisfy condition
            ProcessChange(encoderIndex)
            encoderChanges++
          encoderLoops++
        
    PUB CheckForChange(checkIndex)
    ' result starts as zero. Zero is also considered false.
    ' Returns zero if no change. Returns true (aka -1) is change is found.
      
      newState[checkIndex] := ina[pinA[checkIndex]..(pinA[checkIndex] + 1)]
      result := newState[checkIndex] <> oldState[checkIndex]
      
    PUB ProcessChange(processIndex)
    
      if newState[processIndex] == forward[oldState[processIndex]]
        reverseFlag[processIndex] := false 
        encoderPosition[processIndex]++
      elseif newState[processIndex] == reverse[oldState[processIndex]]
        reverseFlag[processIndex] := true 
        encoderPosition[processIndex]--
    
      oldState[processIndex] := newState[processIndex]
      
    DAT
                          'index %00  %01  %10  %11
    forward                 byte %01, %11, %00, %10
    reverse                 byte %10, %00, %11, %01
    
    pinA                    byte ENCODER_0_PIN, ENCODER_1_PIN, ENCODER_2_PIN
    

    I'll attempt to explain this bit of code in a bit more detail.
    DAT
                          'index %00  %01  %10  %11
    forward                 byte %01, %11, %00, %10
    reverse                 byte %10, %00, %11, %01
    

    I'm taking advantage of the fact the bit pattern of the encoder state can be used to index an array.

    Edit: There was an error in the patterns below. This has been corrected.
    I chose the encoder pattern for forward rotation as %00, %01, %11, %10, %00, %01, %11, %10 . . .. Written in decimal notation this is 0, 1, 3, 2, 0, 1, 3, 2 . . ..
    As you can see, there are four possible values for the encoder states when interpreted as numbers. These four states (0 through 3) can be used as an index to identify the next state in forward rotation.
    When the encoder state is %00 (aka 0) the next state (in forward rotation) is %01 (aka 1). So forward[0] = 1.

    The code bellows checks to see if the new state matches the next expected forward state.
    if newState[processIndex] == forward[oldState[processIndex]]
    

    The same technique is used to identify the next expected state with reverse rotation.

    You could substitute either "ProcessChange" method in either program and the code should work just as well. I'm not sure which technique is fastest.

    There are a lot of quadrature encoder examples on the forum. The way I demonstrated is just one of many ways to accomplish this task.
  • Duane, thanks for the code that runs on a separate cog. I haven't had time to really study it, but first glance it appears to use another cog to only monitor for change. In my application the main program monitors a signal, if one is seen it calculates a number(SWR) from two voltages read by an ADC(50 samples avg), and updates my display (approx .18sec), and continues the loop as long as it sees a signal. Ideally the whole monitoring and turning steppers can go on without interrupting the number calculation on a separate cog, because I need to move the steppers and watch the number vary to minimize the number. I think if I turn a stepper, then calculate my number, before the next stepper step, the delay would be too much. Would it be possible to have the entire attached spin code run independently on a separate cog, without interrupting my main program that is calculating the number? I use lcdserial in this program to talk to display, I also use lcdserial in the main program to talk to the display. Can two cogs use the same pins to talk to the display, and run the same lcdserial? Would you init the second lcdserial in the second cog running encoders? My application would have to send numbers to the display from the main program as well as send stepper positions from the second cog. How would I prevent both cogs trying to use the same two pins at the same time? These are problems that I ran into when trying to use another cog, but as I think about my application I think I have to run the steppers independent of my main program. Is this possible?
  • Duane, had a little time to look at your new code for a separate cog. I love what you have done to ProcessChange, but I can't follow the logic i.e. forward[oldstate[processindex]] How does the lookup it DAT determine forward or reverse?
  • jcfjr wrote: »
    Duane, had a little time to look at your new code for a separate cog. I love what you have done to ProcessChange, but I can't follow the logic i.e. forward[oldstate[processindex]] How does the lookup it DAT determine forward or reverse?

    I just noticed I had a serious error in my earlier explanation. I just make the appropriate corrections. Hopefully corrected earlier description and the description below will help you understand the logic.

    When turned in the forward direction, we get the patterns %00, %01, %11, %10.
    This in decimal notation this sequence is 0, 1, 3, 2.
    So the following transitions are all forward.
    0 to 1. 1 to 3. 3 to 2. 2 to 0.
    We use the four pairs above to fill our "forward" array. We use the first number of the pair as the array index and second number of the pair as the value of the array at that index.
    So forward[0] should be 1.
    forward[1] should be 3.
    forward[3] should be 2.
    forward[2] should be 0.

    The above list of pairs can also be written as:
    forward[0] = 1
    forward[1] = 3
    forward[3] = 2
    forward[2] = 0

    Of course arrays increment sequentially so we don't list them in the order 0, 1, 3, 2. We instead have to list them 0, 1, 2, 3.
    So we reorder the list to:

    forward[0] = 1
    forward[1] = 3
    forward[2] = 0
    forward[3] = 2

    We don't explicitly add the index value to the array in the DAT section so the list of pairs become:

    forward byte 1, 3, 0, 2

    or in binary:

    forward byte %01, %11, %00, %10

    The same technique is used to fill the "reverse" array.

    I'll answer your other questions as time permits.
  • Just to add to the forward/reverse array discussion, I'll add the following.

    If we know the current encoder state, can we know what the next state will be moving forward? This is what the "forward" array is. Use the current encoder state as the index, and corresponding forward array element will be the next state.
    We can check if the encoder is moving forward by comparing the current state to the forward state predicted using the encoder's previous state.
      if newState[processIndex] == forward[oldState[processIndex]]
    

    The code above has the same meaning as the pseudo code below.

    if presentEncoderState equals the predictedForwardStateBasedOnLastEncoderState then preform forward motion tasks
  • jcfjr wrote: »
    Can two cogs use the same pins to talk to the display, and run the same lcdserial?

    This is possible but a bad idea in general. When two cogs use the same serial line, the text often gets garbled. You generally want to use one cog to manage debugging information.

    Sending debugging information generally takes a bit of time so you don't want other time critical tasks running in the same cog.

    I often send debug info from multiple cogs but only during development. The final program also uses a single cog to receive and send serial messages. The multiple cog serial use requires locks in order not to produce gabled text.

    You need to think about which tasks are time critical and require fast loop times. You don't want time critical tasks and slow tasks sharing a cog.

    If you can't afford to miss any encoder transitions, then you wouldn't want much else going on in the cog monitoring the encoders. This probably isn't going to much of a problem for a hand turned knob but encoders on a motor would be a different story.

    Stepper motors can be controlled from Spin but Spin is much slower than assembly. You'll probably want to look at one of many stepper examples on the forum or OBEX.

    Sharing variables within one object (Spin file) is easy. As long as the code of multiple Spin cogs is in the same object, the cogs can all access the same variables. When cogs are running in a different object, there needs to be some sort of mailbox for these objects to share variables.

    I used to maintain a list of links to interesting forum posts. There's a link to this list in my signature. Unfortunately the links in the forum all got scrambled. I'm slowly attempting to repair some of these links but the vast majority are still broken (I try not to think about what a catastrophe this really is).

    Here are a few links which may help you.
    Discussion about my attempt at making a CNC controller (this is still a work in progress).(Not a very good CNC thread but I haven't found the ones I like more yet.)
    Zanadu's treaded vehicle project with encoder discussion.
    Another thread about encoder and motor control.

    There's an encoder object by Kye with problems. Kye generally writes fantastic code and I've learned a lot from him but there's a serious bug in his QEDEngine. I discuss this bug here.
    I single this one object out because Kye has a reputation of writing great stuff and I'd normally assume something of his would be the best choice to add to a project. This is one time I'd avoid his object.

    There's lots of great code on the forum and OBEX. I've learned a lot from reading other people's code. I also recommend JonnyMac's Spin Zone articles I mentioned earlier.
Sign In or Register to comment.