Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

L3: 3-D (axes+pen servo) FIFO-enqueued synchronized low-level motion command #101

Open
lsemprini opened this issue Jun 4, 2018 · 20 comments
Open

Comments

@lsemprini
Copy link

@lsemprini lsemprini commented Jun 4, 2018

Hi,

Thank you for your awesome EBB.

We would love to use our AxiDraw for 3-D drawing where the vertical (pen) axis is as accurate as possible given the capabilities of the servo mechanism.

We have many applications for this in mind, but one of them is simply to control a soft brush pen with varying vertical positions to give expressive brush thickness effects that vary during strokes---a particularly good application because vertical resolution is not critical.

Currently it appears that the firmware only has the concept of 0/1 up/down with a programmable servo position given to the SC command, and indeed we can program using SC to achieve a variety of positions...BUT...the SC command is documented as having immediate effect. So unless I'm missing something, I don't see a way to synchronize pen servo changes with a/b or x/y changes.

Is there a way to do it now?

If not, I'd like to propose an L3 command that extends LM.

I am proposing to extend LM rather than XM/SM because it will be more powerful for users (albeit more complex) and I suspect it will be LESS work for EmbeddedMan :)

The details no doubt will depend on how the servo control works but here is a rough idea:

L3 – 3-D (axes + pen servo) Low-level Move

  • Command: RateTerm1,AxisSteps1,DeltaR1,RateTerm2,AxisSteps2,DeltaR2,RateTerm3,AxisSteps3,DeltaR3
  • Response: OK
  • Firmware versions: XXX and newer
  • Execution: Added to FIFO motion queue
  • Arguments: (expand to explain how the servo can be controlled: units, step size, etc.)
  • Description: (expand to explain how the servo can be controlled: units, step size, etc.)
@oskay
Copy link
Contributor

@oskay oskay commented Jun 4, 2018

Might be simpler to add an alternate form of SC that is added to the motion queue.

@oskay
Copy link
Contributor

@oskay oskay commented Jun 4, 2018

Also, here are some known approaches to doing 3D moves with the existing firmware:

  • Use shorter movements. The FIFO is only one command deep... so if you care to treat it that way, it can function as a system where your 3D move consists of many short moves, each with a different Z value. (This is much like how movements in XY are composed of many linear segments.) If your SM moves are much shorter in duration than the servo movement time (say under 20 ms), you can simply alternate updating the servo position and sending new movements. You can use the QM command if necessary to verify when the FIFO queue is empty in order to ensure that things remain synchronized.
  • You can send a pen lift (or lower) command directly before an XY move command. For example, the command SP,1\rSM,1000,0,3200 will begin the Z move and then (almost immediately) begin the XY move, such that the two happen simultaneously in practice. Note that this use of the SP command does not specify a delay time. If you do specify a delay time (as in SP,1,1000\rSM,1000,0,3200), then the XY move will not begin until the Z move completes. The rate of the pen lift can be specified with the SC command.
  • You can directly change the definition of (for example) the pen-down Z position, by giving a command like SC,4,10000\rSP,1 that both configures the pen down position and moves to that new position. Note that the value of Z position that you send (10000 here) should be kept in the range specified in axidraw_conf.py, 7500 to 28000, in order to prevent damage to the mechanism. This type of command also be executed before an XY move, to give the same type of simultaneous motion.
@lsemprini
Copy link
Author

@lsemprini lsemprini commented Jun 4, 2018

Hi oskay! Thanks for your response.

A queued version of SC would also be OK (the only disadvantage being no support for an acceleration parameter, but not sure that would even be physically possible to implement with the pen servo).

As to your known approaches (thanks for the list!), we had tried some of those but kept running into different issues:

  • the strategy of sending a large number of SC+SP+tiny move commands, and waiting for a response before proceeding so that all 3 commands are basically treated as unqueued/immediate, is the first thing we tried, but because there is so much latency getting from our python software process through the OS USB layers, through the hardware USB, and getting the response back again, this led to pretty crazy bad performance. Because the hardware has no motion queued up ever when using this strategy, it spends most of its time (most of its duty cycle) not moving, waiting for another motion command. That unqueued approach also lead to such jerky movement in all axes on the actual device that the picture drawn by the pen was affected and notably not smooth (maybe due to extra vibration?). It would be way, way better if we could somehow queue up movements in X, Y, and Z well ahead of time to keep the device happy and not have to worry about the USB latency. Note that in this case it is high latency (as opposed to unpredictable latency) that is the problem; contrast that with the last strategy described below.

  • the strategy of firing off a long-duration set of XY move commands at about the same time as a single long pen-up/pen-down command works if we want to move the pen linearly during that period of time, but of course that depends on the content being rendered. If we want the motion in the Z direction to be "curved" (anything but a straight line over time), as we often see when stroking with a brush, it won't work. But thanks for mentioning this strategy.

  • another strategy we thought of was whether we could issue the usual high-performance, very-very-queued up XY motion commands ahead of time and then try to issue the immediate pen commands at the "right" time. This has two big problems. One is that no modern OS allows our software process reliable scheduling so that we can run the code to send the pen commands at the right time. Another is that even if we had real-time scheduling in the OS, in order to know when to send the pen commands we would have to know the exact value of the latency through the whole system (OS USB software/hardware, device USB software/hardware). But that latency is unknown (to clarify, for this strategy, the latency could be very large with no problem, but the fact that its value is unknown is a big problem). One could possibly rig up a closed-loop test using the QM command to try to measure the latency at a given moment, but I'm sure sometimes the latency changes due to different congestion conditions at different points in the pipeline. It seems basically hopeless to get this approach working, even if we had proper OS scheduling, but since we don't even have real-time scheduling, it's moot.

So thanks again for sending those "known approaches." I appreciate the time you're taking to help us and others, and when I wrote this comment I just wanted to write down the issues with those approaches to help motivate why doing a queued pen-movement command would be worthwhile.

Thanks!

@EmbeddedMan
Copy link
Contributor

@EmbeddedMan EmbeddedMan commented Jun 4, 2018

@oskay
Copy link
Contributor

@oskay oskay commented Jun 4, 2018

If we do, it would be nice to add matching S3 and X3 commands as well.

@EmbeddedMan
Copy link
Contributor

@EmbeddedMan EmbeddedMan commented Jun 4, 2018

@EmbeddedMan
Copy link
Contributor

@EmbeddedMan EmbeddedMan commented Jun 5, 2018

@oskay
Copy link
Contributor

@oskay oskay commented Jun 5, 2018

What about the idea of making a version of SC that goes into the FIFO?
SC, value1, value2[, addtoFIFO]<CR>

@EmbeddedMan
Copy link
Contributor

@EmbeddedMan EmbeddedMan commented Jun 5, 2018

@oskay
Copy link
Contributor

@oskay oskay commented Jun 5, 2018

As one more alternative approach, what about adding the third axis directly to the SM command? (S3?)

It could be something like:
S3,duration,AxisSteps1[,AxisSteps2][,servoFinal]

The advantage of this would be that it is time definite, so when it comes time to execute the S3 command from the FIFO, it could be modeled as doing the following existing things:

(1) Execute an immediate change of the pen-up or pen-down position (whichever we're not in), the same way that we do with SC,4/SC,5
(2) Execute an immediate change of the pen slew rate, the same way that we do with SC,10. This should be straightforward, since we know both the total time duration in ms and the difference between the pen-up and pen-down positions.
(3) Execute an immediate start of the pen vertical motion, the same way that we do with TP.
(4) Execute the remainder of the S3 command, as one would normally with an SM.

@EmbeddedMan
Copy link
Contributor

@EmbeddedMan EmbeddedMan commented Jun 5, 2018

@lsemprini
Copy link
Author

@lsemprini lsemprini commented Jun 5, 2018

Glad to hear from you both! L3 sounds great as EmbeddedMan describes.

As to the multiple servo issue, we can certainly dream up uses for the other 7 servo signals (first one that comes to mind is 2 servos for pen tilt), but for now we're only looking for xy+pen motion.

For some future-proofing, you could define the command as:

L3, termX,stepsX,deltaX, termY,stepsY,deltaY, servo_id0,term0,steps0,delta0, servo_id1,term1,steps1,delta1, ...

where the caller provides between 1 and 8 groups of servo_id,term,steps,delta for the servos that the user wants to move with the command.

If variable-numbers-of-arguments is a pain, then you can simply require us to provide all 8 with steps==0 for the servos we don't want to move, in which case no need for servo_id:

L3, termX,stepsX,deltaX, termY,stepsY,deltaY, term0,steps0,delta0, term1,steps1,delta1, term2,steps2,delta2, term3,steps3,delta3, term4,steps4,delta4, term5,steps5,delta5, term6,steps6,delta6, term7,steps7,delta7

Though in this case you can think about whether or not you're always going to have 8 servos on your hardware.

Or you could also think about separating the commands for XY and servo (retaining LM and adding another servo-only queued command "SV - Queued Servo Move" that simply takes 4 fixed args servo_id,term,steps,delta) and that lets the app decide which servos to move by which commands they send. So in our case we would queue up a stream of LM,SV,LM,SV,LM,SV to achieve synchronized 3-D motion. Not sure if that makes it easier or harder for you to weave together the command effects on the firmware, but it does solve the multi-servo issue more cleanly.

With all proposals above, you could support exactly 1 servo for now, and later you would still have room to implement multiple servos.

I assume there's a way that we can know or query which servo is being used for the pen?

I need to be careful not to change the behavior of the existing SP commands at all, so when I get this code done I'll want you guys to test it out in multiple real world situations before we call it good.

Definitely we're eager to test! We don't have a logic analyzer or anything here but we can test the behavior for sure.

@oskay
Copy link
Contributor

@oskay oskay commented Jun 5, 2018

I'd err on the side of keeping it as simple as possible. Making an S3 variant that supports a single third axis will benefit more users than the L3, most likely.

@EmbeddedMan
Copy link
Contributor

@EmbeddedMan EmbeddedMan commented Jun 5, 2018

@lsemprini
Copy link
Author

@lsemprini lsemprini commented Jun 6, 2018

Any APIs above fine with us!

By the way, any ideas where to look to learn the model number and/or capabilities of the particular servo used for the AxiDraw pen (vertical resolution, accuracy, etc.)? Assuming the EBB outputs the correct pulse width every 24ms, it will be interesting to compare the actual performance against what is spec'ed, but I'm not sure where to begin in looking for the spec. Couldn't find servo specs on AxiDraw's website.

@oskay
Copy link
Contributor

@oskay oskay commented Jun 6, 2018

Standard AxiDraw pen lift servo is a Tower Pro 9g servo motor.

@lsemprini
Copy link
Author

@lsemprini lsemprini commented Jun 7, 2018

Cool thanks. Looks like even Tower Pro doesn't document their accuracy/precision/etc. Chalk that up to the $4 price I guess. Have you ever heard of anyone modding their AxiDraw with a different, more expensive servo with more accuracy?

For others wanting to do 3-D work, here's what I found on the TowerPro SG90 9g servo's behavior:

https://github.com/tardate/LittleArduinoProjects/tree/master/playground/ServoTest

https://forum.arduino.cc/index.php?topic=353826.0

https://www.quora.com/Is-there-any-better-alternative-to-TowerPro-SG90-servo-motors

https://servodatabase.com/servo/towerpro/sg90

http://akizukidenshi.com/download/ds/towerpro/SG90.pdf

@oskay
Copy link
Contributor

@oskay oskay commented Jun 7, 2018

SG90s are basic, but widely used for reasons including the low cost.

They are well modeled by assuming that they have 10-20 available positions that they can move smoothly between. We have tried a lot of other servos, including some upwards of $25 each (wholesale cost), but you really have to go much higher in cost before getting to a significant performance advantage. Higher end servos tend to get heavier and slower (metal gears, etc), then to digital, well before getting to faster motors, then digital then, finally, to encoders which are what would make the biggest difference.

@lsemprini
Copy link
Author

@lsemprini lsemprini commented Jun 10, 2018

Also, for others who are waiting for the new command that moves all 3 axes at once with perfect sync, here are some pointers about how to sort of do it (with big limitations noted above) using existing commands:

I tried various orders of commands but the only order that actually made the AxiDraw move in XY and Z simultaneously for me was:

SC,10,servo_rate
SC,4,pen_down_position
SP,1,0 # pen down: third arg says duration is 0ms
XM,milliseconds_to_do_xy_move,$x_change,$y_change
... immediately transmit N more copies of this sequence for the next moves ...

I found that if I attempted to issue the XM first, the SP always waited for the XM to finish, even if XM duration was 0. Secondly, I found that I had to issue the SP with a delay of 0, otherwise the XM would wait for the SP to finish.

Finally, and most confusingly, somehow magically if I issue the above series of commands in order repeatedly, the device does moves in the right sequence (that is, the next Z+XY move does not begin until the previous one has finished) even though that would seem to contradict the documentation, which states that XM is queued, so in theory the SC,SC,SP for the next move should take effect immediately, ruining the previous move. But somehow it works. Not sure why.

This will all become so much cleaner with a queued 3-D command!

@lsemprini
Copy link
Author

@lsemprini lsemprini commented Jul 9, 2018

Howdy. We created a website showing videos and images of some of our early results with the existing 3-D support to pique people's interest about what is possible with full 3-D support, provide sample code, and get people thinking about other possible applications for 3-D AxiDraw. Enjoy!

https://lurkertech.com/3daxi/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.