Adding capabilities to my ELS system

I can't generate 400 RPM on my stepper as that is 187.5us, between micro-steps, and the timer only does 1us steps. (Unless I change the clock base.). So it is running at 188us. I suppose I could ping pong between 188 and 187 every timeout? Nah, not worth it.
Perhaps, as you say, not worth it. But whenever there is a precision timing requirement like this, I will calculate it to quite a bit more bits than is usable, increment it based on that calculation, and then use the rounded version of that to set the timer. Basically fixed point decimal calculations.

For example, in your case, calculate your timeout as a multiple of 125 ns (1/8 of a us). Call that timeout_125ns. Then you can increment timeout_125ns by whatever amount your RPM calls for in 125ns units and preserve 125ns accuracy. And set your actual microsecond timer to fire at (timeout_125ns>>3). Adjust accordingly if you want to use more bits of accuracy. This gives you a distribution that will average much closer to your desired period (within 125ns in this case?).

Biggest pain is keeping track of units of each variable, which is where having the _125ns appended to the name is almost mandatory. And of course this doesn't work if you are trying to use a hardware based repeating timer. And you often need to go to the next bigger size variable (such as int64 instead of int32) which can slow things down.
 
Perhaps, as you say, not worth it. But whenever there is a precision timing requirement like this, I will calculate it to quite a bit more bits than is usable, increment it based on that calculation, and then use the rounded version of that to set the timer. Basically fixed point decimal calculations.

For example, in your case, calculate your timeout as a multiple of 125 ns (1/8 of a us). Call that timeout_125ns. Then you can increment timeout_125ns by whatever amount your RPM calls for in 125ns units and preserve 125ns accuracy. And set your actual microsecond timer to fire at (timeout_125ns>>3). Adjust accordingly if you want to use more bits of accuracy. This gives you a distribution that will average much closer to your desired period (within 125ns in this case?).

Biggest pain is keeping track of units of each variable, which is where having the _125ns appended to the name is almost mandatory. And of course this doesn't work if you are trying to use a hardware based repeating timer. And you often need to go to the next bigger size variable (such as int64 instead of int32) which can slow things down.
The stepper I'm referring to is just a spindle simulator. There's nothing very magical about 400 RPM, I just wanted it moderately fast to test with. On my lathe I bet it would be difficult to set the RPM exactly to that value. As you know when working, 390 or 420 RPM hardly matters most of the time.

However, you provided a good tip on getting exact values, which is very helpful. I'll keep it in mind if I alter my simple simulator stepper pulser. If I get frustrated with my logging attempts, I'll see if I can improve it. That means I'll be doing that tomorrow :p !
 
Fixed something that bothered me, my print statement timing was 5us different than the HW timing. How did I fix it? By paying attention to when I did a digitalWriteFast. Now the command is BEFORE the print statement. Yes, the print statement took 5us extra! Now the SW and HW timings are within 10-15ns, so now the sync to first pulse time is 39.93us according to the scope and 39.94 us on the SW timer, close enough for what I need.

Code crawl for bounds check is scheduled for tomorrow. Supposed to be a crummy, rainy day, might hit 49F tomorrow. Today hit 80F, and the sun came out in the afternoon, so I mowed the lawn after getting my cars registered. By 7PM it was 62F raining and occasional lightning.
 
Fixed something that bothered me, my print statement timing was 5us different than the HW timing. How did I fix it? By paying attention to when I did a digitalWriteFast. Now the command is BEFORE the print statement. Yes, the print statement took 5us extra!
sprintf()?

I avoid them as much as possible when speed is a concern. Actually this is the first processor (teensy) where I haven't stuck to that rule. On smaller microchip micros, I avoid them all together. I have my own library to spit text out, including converting variables to strings. In some cases a I tried a single sprintf(), and it blew up the code so much it wouldn't fit in the flash memory!

Yeah, critical timing in code requires paying close attention. And sometimes a lot of testing. I'm a big fan of setting and clearing pins to time code using a scope. I always try to sneak a few IO dedicated for scope points.
 
Last edited:
sprintf()?

I avoid them as much as possible when speed is a concern. Actually this is the first processor (teensy) where I haven't stuck to that rule. On smaller microchip micros, I avoid them all together. I have my own library to spit text out, including converting variables to strings. In some cases a I tried a single sprintf(), and it blew up the code so much it wouldn't fit in the flash memory!

Yeah, critical timing in code requires paying close attention. And sometimes a lot of testing. I'm a big fan of setting and clearing pins to time code using a scope. I always try to sneak a few IO dedicated for scope points.
No, but it's cousin printf. print statements are evil, but there's not much one can do in an "ICE-less" development. Not when there's hundreds of variables and ~20 odd available IO pins.

I/O pins and scopes are fine at one's desk, but turn into a royal PIA at the lathe when you are doing real testing with chips flying. My board is in a housing (enclosure) there's no spare pins. It's more of a finished product than a dev platform... So it's painful to add debug stuff, and to be able to button things up again to prevent junk landing on sensitive points. Kind of the price to pay when you add on features...
 
Spent a little time cleaning up code. Found 30 or so stale variables that aren't used anywhere besides their definition, and deep sixed them. Good to clean out unused stuff, including some functions. Was good to slowly mark up variables and ask, WTF is that for, do I use it anymore? What was I thinking? grep showed me if there was any use in the folder. Was a sidetrack from diagramming, but, have to clean up some of the cruft.

Back into the angle calculations. I find them immensely confusing whether I need to advance or retard angle to get what I want. I think my angle adjust at least has the correct sign now. Angle correction has so many ways to mess up! Angles, complements, whether to use -180 to180, or 0-360... Now I need to figure out how to check my while statement is evaluating as I expect. Wouldn't surprise me this is totally messed up. Maybe I ought to think of it as absolute non wrapping angle to make it easier. Then the phase difference is a lot more obvious. I have to think about that...

Found a bug, err, I was testing and I did something wrong, I started the threadtostop too soon and the carriage was in the wrong place, and it rightfully said, "you can't do that". But it also erased from the display the Zpark value and Zstop For RH threading one has to be to the right of Zpark for this to work. The background of the display overwrote the value. So I need a way to update it. The value hasn't been reset, but the display area is overwritten, so it's invisible.

For all I know, the serial console is telling me the error, but I'm not yet recognizing it yet.
 
Maybe I have the angle corrections the correct sign, finally. Seems to make some sense, at least with the 10 different trials that I made. All I know is I wasn't even close before. I'll know within a couple days. Looks fine upstairs. Hope to at least get to a smaller error band. This was driving me nuts.

Looked at the invisible Zpark & Zstop "bug". It did what I told it to do. Think there's a simple fix.
 
So far, no joy. It seems it's a little bit closer, with it hitting the mark maybe 1 out of 4 times, but there's something still going on that is not being accounted for. I've tried several varied concepts and they all fail.

I'd think that one could describe the thread helix with just a couple equations. This is the "thread line"
C-like:
r = constant;
theta(t) = RPM(t)/60 * 2*pi * t;
Z(t) = P/(2*pi) * theta(t);
P = pitch of the thread. Theta is the spindle angle. R is the radius of the circle.

If we were to start at Zpark consistently, I'd think all that is necessary is to restart a thread cut exactly is to start carriage movement when theta = sync_angle. No matter what I try, I come back to this, but something is flawed, either the concept or my implementation (or both). Been beating my head against a wall for quite a while.

A second thing that bothers me is I now seem to have inconsistencies in the time from achieving sync to the first pulse emitted. They used to be pretty short, on the order of 36us, now they seem to be kind of crazy, from a minimum of 25us (ok range) to a maximum of 162us. I don't understand this at all! I'm running constant RPM, so I'd expect sync to first pulse to be uniformly distributed from 0 to 1/(400/60*4096*15/64) seconds. (0-156.25 us) at 400 RPM.

4096 count encoder, N=15, D=64 (10 TPI).

I seem to be getting pulse widths outside the expected range. I suppose I could goose the accumulator to generate a pulse immediately, but I don't know if it matters. Maybe it does, as it puts in a variable delay to starting Z movement. But we are talking about 1 step, that's 2.5um, sorry, that's not the issue.

The spindle can desync a little bit, because it retarded up to 156.25us * -2400 deg/sec = -0.375 degrees. Or from 0 to -0.375 degrees per start. Don't know if it is cumulative. Compared to what I'm seeing, I'd be glad to have that kind of error. (Kidding, I'm striving for perfection...). I may see this tiny error, when things are good, but I have to run a lot of starts to see it. But when things are not good, the error is on the order of 10s of degrees. 1 out of 4 or 5 times, the error seems to be low. I might have to take a video of the starts so I can quantify the errors. (Effective thread start error in degrees.)

I don't know where the big error is coming from yet, so it's hard to tell how to correct it. This is all on a 12 TPI lead screw cutting 10 TPI threads. Using the same mark, doesn't seem to help. Clearly missing something (important). May have to let this percolate a while.
 
Where is your rotary encoder located in the gear chain?
There's a 40T gear on the spindle, which connects to a 60T gear on an intermediate shaft. On the intermediate shaft is a 60T timing pulley. There is a 3MT timing belt to the rotary encoder which has a 40T timing pulley. The total gear ratio from spindle to encoder is 1:1. The spindle angle is always measured, whether or not the lead screw is running.
 
Back
Top