Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
EBB Firmware: Add command for pre-computed stepper moves #73
Right now there is a lower limit of
Following on #71, perhaps there should be a similar function that offloads some of the planning to the computer. This function could be a little less "user friendly" -- for example phrased in terms of something like number of interrupt cycles per forward step, rather than total steps -- but would allow for making subsequent moves that are closer together.
Here is a proposed first-pass at such a command description:
LM – Low-level Move
Again, this is a first pass approach to the design. Comments and changes are welcome.
referenced this issue
Jan 8, 2017
Well, it's a bite more 'raw' than that unfortunately.
What we need to send the EBB are six values, three for each axis.
The only other piece of information that the EBB needs is the direction bits, which it can compute based on the sign of the StepsCounter value for each axis.
So, here's what StepAdd is: Internally there is a 32 bit accumulator. Every 40uS (25KHz), StepAdd is added to this accumulator. When the MSb of the accumulator gets set, 0x80000000 is subtracted from the accumulator and a step is taken. The accumulator is cleared on the beginning of each move. Each time a step is taken StepsCounter is decremented, and when it reaches zero on both axes, the move is complete.
The math you can use to compute StepAdd for a given move is as follows:
StepAdd = (StepCounter << 31)/(25 * Duration)
The other value - StepAddInc - is what gets added to StepAdd each 40uS, and this is how we implement acceleration/deceleration during a move. Simply set to zero for no accel/decl.
So the command I envision would be something like:
I'm going to quick prototype this, and do some timing tests, to see how much time this saves us. My guess right now is that we'll be able to get pretty close to 1 or 2 ms per command if we do this. If so, that would be really cool.
Oh, and I should add that no limit checking will be done on the values for the LM command. This means that you should be careful not to set either StepsCounter to zero, StepsCounters should be less than 0xFFFFFF, StepAdds should be less than or equal to 0x80000000.
And yes! This is, in fact, everything necessary to do accel/decel. The AM command is simply the front end math that computes these three values for each axis. So with this new LM command, you can do full accel/decel by using the PC.
Hmm. Well, initial tests are not looking good. I clearly don't understand exactly what takes a lot of time in this system. I think I'm doing something wrong. With the LM command, it takes about 17ms to process the command, so that's our shortest move. Hmmm.
I need to dig into this much further before I can figure out what's taking so long. Unfortunately I'm going to be tied up this week, but I'll try to get back to it next weekend.
Another country heard from - and please let me know if you'd like me to butt out. 8^)
As I understand the current situation ( running version 2.4.5 ), we expect a pause of ~15-20 msec. between each segment of a line. The thing is, I can't seem to actually observe that pause in practice.
Here's my test setup, using eggbot:
Have I misunderstood the phenomenon under discussion, or is there something else going on?
Yeah, you're going to have to go smaller to observe the issue. Until the duration of a move is less than 20ms (for 2.4.5, or 16ms for 2.4.4 and previous), you won't see any gaps at all, and thus you won't have any added time. At 200 steps/sec, you'll never get a move faster than 50ms, which is too big to see the effect. Bump your step rate up to 1000 steps/s, and then create a straight line of 1000 segments, each 1 step long. It should just take 1 second, but actually will take much longer. You'll see the effect then. *Brian…
On Sun, Jan 8, 2017 at 9:26 PM, Windell Oskay ***@***.***> wrote: The issue is that very short moves (moves less than 15 ms, with 2.4.6) may have gaps between them. 2000 segments in 50 s is 25 ms per segment; you should not see an issue there. — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#73 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AAbeCLAz3Ezzo7ez9VyaVm2nd0hkx7Eyks5rQajxgaJpZM4Ld16G> .
OK, great news. I'm stupid, and that's the reason it looked like 15ms or 17ms was the fastest that we could execute moves.
Bottom line - in reality, all versions of EBB firmware (including these latest couple) can run at 3ms move times all day long with no gaps (as long as you have a dedicated USB host port to the EBB - thus no bus contention). And even 2ms moves are very close to being gapless. This is true with SM, XM, or LM commands (it turns out the math doesn't really add much to our command processing overhead).
My big error was in simply copy/pasting large blocks of command text into a terminal emulator. The terminal emulator takes each character of a copy/paste, and puts it in its own USB packet. This makes sense when you are just typing characters into a terminal emulator - you want each character to be transmitting across the USB as soon as you type it. But because USB has a 1KHz maximum packet rate, that means that a 28 byte command (like "LM,85899346,2,0,85899346,2,0") takes a really long time to send. (It turns out that the terminal emulator does sometimes put more than one byte of data into each USB packet, thus it would take about 17ms to send that command, not 28)
The solution was simply to save a long list of commands as a text file, and send the whole file to the EBB. This is then sent as large (32KB maybe?) chunks to the USB host code on the PC, which can then very efficiently make each USB packet have as many bytes as possible. maximizing the available bandwidth.
Lesson To Be Learned : always make sure your custom PC application code is putting as many bytes (or commands) together as it can as a single 'write' command to the lower level USB stack.
Now, here's some timing data to back up the above conclusions:
For each of the following logic analyzer captures-
repeated over and over sent as a file. Each of these commands takes 2ms for the step movement to complete. As you can see if you look closely at the StepISR signal, there are gaps. This indicates times when the stepping of a command is complete, but the next command isn't ready yet to start stepping, and thus there are little gaps (the longest ones are only about 750uS long).
What you should notice here is that there are no gaps. Even if I let this run for 10s of seconds, there are zero gaps in the StepISR trace, which indicates that the next command is always ready before the previous one finishes (just barely in many cases).
The other good sign here is that, after a couple commands, the FIFO Wait signal is going high for significant periods. That indicates that our next command is ready before the previous one finished, and so we have to wait for the FIFO to empty before we can send the next command into it.
When no step command is running, our ISR takes about 15% of our CPU time. When a step command is executing, the ISR takes 43% of our CPU time.
It takes about 1ms for the LM command to read in its parameters and prepare them to be written to the FIFO.
The traces for the SM and XM commands are virtually identical to the above traces - we have no trouble supporting constant 3ms commands with either of those commands.
So - what's next?
Well, the LM command is working as intended. I'm going to release this version with the new LM command. The original use for this command isn't really relevant anymore (since SM and XM can do 3ms gapless commands as well), but somebody could use it to do their own accel/decel if they wanted to.
Absolutely. There are many different layers of stack that the data passes through to get from your PC app to the EBB.
Let's say you perform a single write() call (or whatever the equivalent is in your chosen PC language) with 32KB of serial data (commands) to go to the EBB. First it goes through the serial stack layer, then into the USB stack layer. There, the USB stack code, in conjunction with the USB host controller chip on your PC, will take that data and add it to an outgoing data buffer (which is probably much larger than 32KB). Then, each time the host controller chip asks the EBB USB hardware peripheral "can you accept any new data from me right now" and the EBB answers back "yes", the host controller will send down one packet's worth of USB data. That then gets stored in a RAM buffer in the EBB by the USB peripheral. Once that data is sitting in the RAM in the EBB, the EBB will then answer back "no" when the host asks if it's OK to send more data. (The flow control.)
Then the processor on the EBB gets around to noticing that there is some new USB data sitting in RAM. It then goes through that data, byte by byte, seeing where the / occurs, and when found, sends a single command string to the proper command parser function (parse_LM() in this case). Once that data has been pulled out of the USB RAM buffer, the USB peripheral starts saying "yes" to more data from the PC.
So you can send a command down, and it can get processed and get put in the FIFO and then get pulled out of the FIFO and then the motors start to move. Then you send down another command and it gets processed and gets put into the FIFO, but since the first command isn't done yet, the second command sits in the FIFO. Then you send down a third command, and it gets processed, but the parse_LM() function has to sit and wait until the FIFO is empty before it can place the 3rd command into the FIFO. This prevents other code from running, which prevents the USB peripheral on the EBB from answering back 'yes' to more data from the PC. In that case, the PC simply just keeps asking until it gets a 'yes' before sending more data.
So even though you're sending one command at a time, after the first couple commands (which each fill a different place in this overly complicated pipe), they start to fill the host's USB buffer up. At that point, your write() call will block (if its of a blocking type) in the PC app because the USB device (EBB) Isn't accepting more data, and so the host USB stack doesn't allow more data to be put into its buffer.
Bottom line - if, instead of sending one big file, I had sent individual files which just contained a single command each (in essence like you are doing in your app), everything still would have worked fine. This would have sent a full command in each write() call, and the efficiencies gained there would have allowed things to run smoothly. The problem I had was sending one character at a time.
Sure. I already put up some examples for constant velocity. I'll add the accel/decel ones.…
On Sat, Jan 14, 2017 at 3:57 PM, Windell Oskay ***@***.***> wrote: Got it. Can you please write a few example commands for the LM command, showing how it can be used for constant-velocity movements as well as accelerated/decelerated moves? — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#73 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AAbeCGCQWapEN1itGEHn1ahlX9e-4xf4ks5rSUS2gaJpZM4Ld16G> .
I'm in the process of writing up some halfway decent documentation for the LM command, which includes acceleration examples. I'm also checking each one with a real EBB and my logic analyzer, and I'm seeing some things that I don't quite understand yet. One is an error in the 25KHz ISR rate - it's off by .5%, when it should be a maximum of .15% (max internal oscillator error), and I believe I found the problem there.
The other issue has to do with the way to calculate the accel/decel values. Once I have this all sorted out, I'll get the examples up.
OK, there's a new version (2.5.1) up now. While developing the examples and testing them, I found that there was a bug in 2.5.0 that didn't allow negative values for StepAddInc parameters to the LM command. This is now fixed in 2.5.1. Also the documentation for LM is up and it has equations and everything.