Program runs, but hangs after 3-4 minutes
jcfjr
Posts: 74
in Propeller 1
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.
spin
11K
Comments
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".
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....
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.
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.
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.
It shows an example of using methods and how to use arrays for multiple instances of similar data.
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.
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".
The method "ProcessChange" could be declared using a different variable name than "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.
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.
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.
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.
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 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
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.
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).
I'll attempt to explain this bit of code in a bit more detail.
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.
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.
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.
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.
The code above has the same meaning as the pseudo code below.
if presentEncoderState equals the predictedForwardStateBasedOnLastEncoderState then preform forward motion tasks
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.