Trouble with COG launching
JBWolf
Posts: 405
Hello,
I am trying to write a stepper control program but having trouble launching them into new cogs.
I have 2 steppers going through a ULN 2803.
The goal is to have each stepper running in a cog awaiting variables for distance, direction and speed.
I can move the motors no problem with a simple loop going through the phases... but I cannot get this code to work.
The LCD just returns failure, not sure why though.
I sincerely appreciate any help.
I am trying to write a stepper control program but having trouble launching them into new cogs.
I have 2 steppers going through a ULN 2803.
The goal is to have each stepper running in a cog awaiting variables for distance, direction and speed.
I can move the motors no problem with a simple loop going through the phases... but I cannot get this code to work.
The LCD just returns failure, not sure why though.
I sincerely appreciate any help.
{{ Dual cog control for ULN 2003/2803 & 2 unipolar steppers motor 1 = pins 8-11 motor 2 = pins 12-15 1 revolution = 200 steps }} CON _clkmode = xtal1 + pll16x ' System clock → 80 MHz _xinfreq = 5_000_000 TX_PIN = 2 ' LCD Pin ID BAUD = 19_200 ' LCD Baud OBJ LCD : "FullDuplexSerial.spin" ' LCD Object VAR long stack[200] ' Cog stack space byte cog[3] ' Cog ID byte testctr ' Counter for LCD byte session ' Variable for LCD PUB Main | Success LCD.start(TX_PIN, TX_PIN, %1000, 19_200) ' initialize LCD waitcnt(clkfreq / 100 + cnt) BKLon ' Turn on LCD BackLight Success := StartGalvo 'initialize outputs and start motors in cogs LCD.str(string("Start Galvo:" )) if success == 1 LCD.str(string("Success" )) else LCD.str(string("Failure" )) Success := Calibrate ' Move galvo to home position NewLine LCD.str(string("Calibrate:" )) if success == 1 NewLine LCD.str(string("Success, on cog#" )) LCD.dec(cog[1]) LCD.str(string(" and cog#" )) LCD.dec(cog[2]) else LCD.str(string("Failure" )) PUB StartGalvo : success cog[1] := cognew(Xaxis(0,0,0), @stack[40]) ' Start X-Axis control cog[2] := cognew(Yaxis(0,0,0), @stack[80]) ' Start Y-Axis control PUB Stop cogstop(cog[1]) ' Stop X-axis cogstop(cog[2]) ' Stop Y-Axis PUB Calibrate : Success | stp ' 280deg home positioning dira[8..15]~~ stp := 5 repeat 155 stp := stp - 1 if stp < 1 stp := 4 outa[8..11] := WStep[stp] outa[12..15] := WStep[stp] waitcnt(clkfreq/600 + cnt) PUB Xaxis (dirX, distX, delayX) | index, stp, distX2 ' dir/dist 1=up 2=down dira[8..11]~~ ' Set pins 8-11 output X-Axis repeat distX2 := distX repeat index from 0 to distX2 ' cycle phase distX many times if dirX == 1 ' increment if direction = up stp := stp + 1 if stp == 5 ' reset increment at 5 to 1 stp := 1 if dirX == 2 ' decrement if direction = down stp := stp - 1 if stp := 0 ' reset decrement at 0 to 4 stp := 4 outa[8..11] := FStep[stp] ' send phase pulse waitcnt(clkfreq/delayX + cnt) ' set speed, max speed = 600 PUB Yaxis (dirY, distY, delayY) | index, stp, distY2 dira[12..15]~~ ' Set pins 12-15 output Y-Axis repeat distY2 := distY repeat index from 0 to distY2 ' cycle phase distY many times if dirY == 1 ' increment if direction = up stp := stp + 1 if stp == 5 ' reset increment at 5 to 1 stp := 1 if dirY == 2 ' decrement if direction = down stp := stp - 1 if stp := 0 ' reset decrement at 0 to 4 stp := 4 outa[12..15] := FStep[stp] ' send phase pulse waitcnt(clkfreq/delayY + cnt) ' set speed, max speed = 600 PUB Newline ' Start LCD Commands LCD.tx(13) pub NextLine LCD.tx(10) PUB CLS LCD.tx(12) waitcnt(clkfreq /500 + cnt) PUB Back (n) repeat n LCD.tx(8) PUB BKLon LCD.tx(17) PUB BKLoff LCD.tx(18) PUB BlinkON LCD.tx(23) PUB Default LCD.tx(24) PUB NoCursor LCD.tx(22) PUB LCDoff ' End LCD Commands LCD.tx(21) DAT HStep byte %0000, %1001, %1000, %1100, %0100, %0110, %0010, %0011, %0001 ' Half Step - best resolution FStep byte %0000, %1001, %1100, %0110, %0011 ' Full Step - max torque WStep byte %0000, %1000, %0100, %0010, %0001 ' Wave Step - weak torque
Comments
{Should have put in a manual reference: P194 of v1.2 Propeller Manual as supplied with SpinTool V1.3}
I forgot to power the motors since I was just checking to see if success was returned or not.
It says failure but the motors do move.
Changed it to check for 0 instead of 1... it now shows fine and properly returns the new cog id's!
thanks guys!
Welcome to the forum and the Propeller!
For example a square is pretty simple... up, right, down, left. I'll be able to code that easily.
But what about a circle or more complex shapes?
Say I want it to move like a spirograph in an infinite loop, it would be a major task mapping out every movement. I cant think of a math algorithm for it either off the top of my head.
my routine has the X & Y axis motors moving with 3 variables for each.... direction, distance and speed.
I'd suggest asking that questions in a new thread with a title describing what you want to do. There are a bunch of sharp folks around here that I'm sure are able to give you some helpful ideas for formulas and algorithms for complex movements. They probably won't see the math question under this thread.
I'm just a little hazy on the exact amount of stack space needed....
I believe each cog will need less than 20 stack space?
I declared way more when i was getting failures to be safe
Does cog0 have to be considered in there?
20 longs will usually work for a cog that's only executing a main method that doesn't call anything else or calls something that doesn't call anything else. A call to a method takes about 4 longs plus any parameters plus any local variables. There's also some temporary locations needed for evaluating expressions, subscripts, etc. There's an object in the OBEX that fills a stack area with a unique value, then starts a separate cog to monitor the stack area looking to see how many of those unique values are changed to something else. Your program can display the resulting amount of stack that's actually used during execution.
The revised program is below... the square works as it should, but I cannot get the triangle to work.
Instead of getting up and right movements at the same time from the 2 seperate motors, they go in order one at a time
What about combining and then outputting the outa[8..11] := HStep[stpY] & outa[12..15] := HStep[stpX] instructions.
Cheers
xaxis(right, (dist/2), speed) & yaxis(up, dist, speed)
I get 'error expected end of line'
Since I cannot make the X and Y axis move at the same time, I tried making it step up 1, over 1, up 1, over 1.....
I did some debugging in the triangle routine and found something very odd.
If I send a single call to triangle with the distance set to 1 so it will make the X axis move a single step, it works no prob. now if I tell it to repeat the triangle call more than 2 times, it is showing that the step value starts counting at 114 even with the 'if > 8' condition. stpX should always start at 0 unless already started and in/decremented with a condition to reset stpX to 1 if > 8.
How does it get 114? it shows this value of 114 if I try to repeat the triangle routine any amount of times more than one.
there are no other uses of the stpX variable anywhere in the program
Here is the triangle routine I am trying: here is the xaxis routine:
I wasn't making myself clear.
For instance, the xaxis method instead of performing the outa [8..11] command writes the stepper motor data for each step to a table. The Yaxis method does the same for outa [12..15].
Another method then reads the table (combing both x- and y- data) and performs outa[8..15] effectively synchronising the x- and y- stepping.
Hope this makes more sense.
Can I declare it in the con or var section and just use it as a global? Nothing else uses it so.... I'll give that a try!
I cannot put an stpX := 0 at the beginning of the Xaxis PUB as it would lose track of its current phase position on the next call. I tried this and it resulted in unpredictable movements.
lanternfish:
I think I follow your logic, I will most certainly use it in the future for a dedicated movement script.
I do not find it an easy way to code multiple movement routines however. I believe It would require creating too many of those lines for each and every routine. it would also need counters, which means several nested loops for each.
Maybe my choice of structure is not as efficient as could be for calling over 100 pre-programmed movement routines? I only started learning spin 2 months ago and invite anyone to please correct my ways
My general idea is to have the 2 steppers running in 2 new cogs, then they await 3 variable to be passed: direction, distance and speed. This way I can make geometric shapes and patterns with simple lines in a repeat loop... such as a square: If I could get the 2 cogs to take data at nearly the exact same time, I could code a triangle with 5 simple lines instead of having to repeatedly make a single step up and over. But if I cannot get both data to both cogs at nearly the exact same time...
Just as an exercise, what happens - based on your posting #11 - when the two cogs you started are finished with their initial method calls (Xaxis(0,0,0) and Yaxis(0,0,0))?
While the X and Y axis routines are looping, the global 'distance' variable takes on a new value passed from cog 0... the process then starts over once the last call has completed.
I added waitcnt's after movement calls to allow the X & Yaxis cogs to finish each set of passed instructions before sending another set.
I changed stpX & stpY to VAR and removed the local declarations.
With the following code, I am able to get a triangle... but it does not stop where it starts, not sure yet why
I sent a distilled version of this program to Jeff Martin, just to see what his take was on the situation. After mulling it over he did finally reach a conclusion, but in the process decided to use this as a learning exercise for the rest of us Propeller-Heads within Parallax.
The real 'initial' problem is that the methods being launched were trying to use their respective local variable without initializing it... and local variables are not initialized at all (except for the Result variable, and the parameters... but those should be obvious/intuitive). So... the local variables contained "random" data that was quite-likely outside of the 0 to 3 range, and thus the condition that resets it to zero failed to do anything the majority of time.
Also, a curious "red herring" is that the random value that the local variables happened to contain, ended up causing the memory reference to access a few values that had what seemed to be the correct pattern in them... making you think that the routine was working correctly up until the local variable reached the value 4, when in fact it was never really working properly to begin with.
There are a number of ways to rewrite the code to fix the problem...(also see some of the attached code methods)
Replace the code similar to this ...
...using the modulus operator, like this...
...which has the same intended effect (causing idxa to cycle from 0..3, and then back to 0) but with the added benefit that if idxa is above 4, it will also be reset to 0... so it "corrected" the situation.
Hope this helps to solve part of your mystery.
Attached is three versions of the same code. Download them onto the Propeller Demo Board and watch the light pattern in the LEDs.
The (Bad) code displays unintended patterns on the LEDs. The (Good VAR) and (Good DAT) versions each display the intended patterns. The only difference between them is that the (Bad) uses local variables and the others define those variables in VAR or DAT blocks, respectively. You can open all three and Ctrl+Tab to see the differences between them easily.
-Thanks Jeff for taking a look at this and confirming my suspicion.
To answer your question in #14...
The call to xaxis sets dir to 2, which means the stpX := stpX + 1 .... if stpX > 8 .... stpX := 1 will not be executed.
Instead, your checking for stpX to be less than 1 ... as a result, the value that is returned for stpX is 114 for reasons based on my previous post, and in all likely hood in this situation stpX actually starts out as 115. The line you have that reads ... stpX := stpX - 1 is doing exactly what you are telling it to do ... decrement stpX by 1 so that it becomes 114.
I'm honored that my stupidity has helped others
I have just gone through the solutions you presented and will take a little while yet for me to take it all in.... but a few questions came to mind.
idxa := ++idxa // 4 ' if idxa is above 4, it will be reset to 0.
What about decrementing... if idxa is below 1, it needs to be reset to 9.
idxa := --idxa \\ 9 ???
Is there a way to determine if a value is even or odd without having to make a dedicated counter just for that purpose?
Say I need to repeatedly move up 2, then over 1.
Id like to simplify: a) The 'index' variables in the VAR example are declared as longs, why not use BYTE since it will only retain a single digit decimal value?
b) I have been wondering about instead of passing the X/Yaxis methods 3 variables, using @ to pass 3 memory locations.... or is this basically the same thing but using HEX?
c) Assuming the X/Yaxis methods have been launched into new cogs, if I write a value to variable 'distX' from a method running on cog0, will this value also be present in the 'distX' variable running on the other cog (Xaxis method cog). In other words... if a global variable is changed, do all instances of that variable point to the same value, regardless of what cog is trying to use it? Could a method running on a separate cog in an infinite loop, immediately change its course because the value just changed?
d) I am launching the X&Yaxis methods into two new cogs, then later making a call to those methods from cog0... does this result in the two XY cogs not receiving the data, and instead cog0 runs that method call? This would explain why I cannot make 2 motors move at the exact same time.
I am thinking about restructuring so that the Xaxis & Yaxis methods are launched into two new cogs with @ pointers to memory locations for receiving the necessary variables. Both X&Yaxis methods/cogs would be in an infinite loop, using a local variable to copy the values pulled from memory locations for use as a condition to compare against the value in the last loop.... i.e. - pull value from memory and write to local variable, write 0 to memory value, nested loop once on condition that the local variable > 0
Thank you again for the help, it did clear up a lot
since I could not find any documentation on decrementing with limit, I used "idxa := ++idxa // 8" and swapped the output.
so to increment i would use that math function with outa[8..11], and to decrement I used the same math but with outa[11..8]
I got it to work by using PRI instead of PUB, passing the addresses, and lots of local variables for comparing.
I used the "addressblinker" example from the PEKit as a template.
This does create a diagonal line showing that both motors are moving at the same time.
Lets say using the code from last post, I want to figure out how long to wait for a certain movement to complete before writing the next.
So if I tell the Xaxis to move a distance of 22 with a delay of 1000.... this Xaxis method will loop 22 times with a delay of 1000. now it seems to me this is pretty simple, 22 * 1000 = 22000 delay = 220ms
But do I also need to consider clock ticks in that Xaxis method?
Heres a simple example assuming 80mhz how long should I wait before writing a new value to addr?
Is it exactly 100 seconds? or do I need to add 2 ticks for " if old <> new", another 2 for "old := new" and so on?
I think there might be some confusion as to how the Modulus operator works in conjunction with the pre-increment operator and how the Modulus operator might deal with negative numbers. i.e decrementing after initializing to Zero would create a negative result. My experience is that it's easier to deal with the Modulus operator if you can keep it in the positive integer domain.
Attached is an Example Spin file with 6 different examples that should make things a little clearer. All you need is the Parallax Serial Terminal running (F12 from the IDE) set at 19200 baud.
Hope this helps.
Is it a bad idea to just switch the output order? i.e [8..11] to [11..8]
What do you think about the timing issue?
I would like to make sure I do not write a variable until the Xaxis method is ready, I'm worried that i'm sending them too early.
Is there a general rule for determining time passed on modulus operators and setting values?