Skip to content
Snippets Groups Projects
Select Git revision
  • master
1 result

yeolden.md

Blame
  • Network Stepper Motor Coordination

    Oddly enough, the first thing we want to do with networked hardware is probably one of the most difficult. This is because of the differences between timescales that the network operates on (a packet originating in a high-level program takes on the order of 1ms to arrive at it's destination) and the timescale that stepping needs to (typically) occur on. I.E. for a stepper motor having 32 microsteps, with 200 steps per revolution, on a 16-tooth pulley having 2mm pitch, to travel at 400mm/s (fast as heck), we'll see 80k steps/s, corresponding to an inter-step period of 125us, so 100us is a good design target. What's more, to achieve coordinated motion at the resolution of these steps, we'll also want steps happening at different network endpoints to happen within the same time-domain so, in order to do this effectively, we either need a clever algorithm and to be OK with some error, or we need some method for synchronizing clocks across the network.

    Solving this problem is more or less what I'll be documenting through this project.

    Layers!

    Here's a video of all of the layers chatting with eachother, where I'm sending serial commands to the network using a socket server and mods

    video

    screenshot

    Beginner Acceleration Planning

    The first thing I have to do before running network steppers is to write an algorithm for planning the motions that each motor will make. There are a number of ways to slice this pie, and now that I've run through it once I'm curious if what I did was the right answer. Mostly, my beef is that most algorithms assume that systems are pretty static and motion paths are decided well in advance. I.E. pausing, increasing speeds, accelerations, is difficult to do with most existing algorithms. It would be cool to have a more dynamic base for motion control.

    How this works at the moment:

    • A Sequence Arrives (lists of points, and target speeds to be travelling between those points). These point->point segments, we call segments.
    • Using the Junction Deviation algorithm, we decide what are permissible exit and entry speeds to these segments. JD is a bit of a hack, and causes some instantaneous acceleration at junctions. It calculates permissible instantaneous acceleration using a method that 'pretends' the motors will in fact be deviating from the junction by some distance. Hence, the name.
    • Each segment then has a Max. Entry Speed, Max. Exit Speed, Total Length, and Cruise Speed (the target speed).
    • However, JD is not the whole story. We also need to determine if we can even reach those junction speeds during the length of a move. So we do a 'walk' of the queue of moves, assuming the end of the queue has an exit speed of zero. Then we work backwards, determining, with a full stop succeeding it, what the previous block's maximum exit speed is given acceleration limits. Etc. Now we have a series of moves with actual Entry and Exit Speeds, Lengths, and Cruise Speeds.
    • From here we can generate individual-axis' segments. Now we can write moves to individual steppers.

    Networking Acceleration Planning

    So, the algorithm we use for acceleration planning has a queue of moves at it's fundament. Basically, some lookahead has to go on. This is 'motion planning' after all. I yearn for something more dynamic, but here we are. It's almost working.

    Now we have to have a scheme to deliver these segments of moves to the network. Because we'll have to link moves on potentially a 100us basis (i.e. the last step of one segment being only 100us before the first step of the next segment) and our network has a round-trip time of ~ 2ms we can't simply ping the planner at the end of each segment and ask for a new block. Welp. We need buffers.

    So, each stepper motor has a queue of moves to make, I believe at the time of writing the queue is up to 32 moves long, but we typically don't need that many. So to start, we send ~ 4 moves to every motor, and when they finish one move, they reply, and we send another. This is akin to 'windowed transmission' as used in TCP/IP, but the window is around the time duration of the physics, not just the netwwork transport.

    Here's a very rough cut of two axis of this just barely working.

    videoo

    Ye Olden Motion Control Notes

    For reference, Smoothieware Block, Smoothieware Planner, Smoothieware Robot and Config. Also grbl planner (contains junction deviation example) and grbl motion control. Also grbl stepper.c including ramp-time calculation from blocks.

    It took me a while to dig through this problem, and with big help from Sam I think I've figured out how to go about doing it. For my sanity, it's worth trying to document this. Hopefully also a useful first-pass at an explanation for others who get into the same problem later on.

    Also, see grbl's planner for an example of all of this at work in a single 8-bit uc.

    To start, looking at this three-segment path (because we've decided to split everything up into linear segments) for each axis (I'll just do X and Y to start) there's an ideal speed - this is just trig from the speed along the line -> speed per axis.

    architecture

    Each axis has a different acceleration limit - some will have more torque, others less, more weight, different gantries, etc. To coordinate a ramped speed profile for the two axis, we'll want to find which one is acceleration limited. Then, for the whole segment, we use that axis to determine an acceleration time, cruise time (at the set feed speed) and deceleration time. We stretch the accel and decel phases so that the total position travelled is equal to the segment length.

    Then we match these accel times to the other motor and can generate complimentary speed profiles.

    architecture

    Now, to truly 'make' every corner segment, we would have to decelerate to zero velocity at each corner. This seems strange, and it's not what most cnc machines actually do. So this is where things get interesting! This is explained best if we line the first segment of our trajectory up with an axis: we see that if we start ramping the 2nd axis up before we come to a full stop at the corner, we deviate from the actual end position.

    architecture

    The mechanism we use to get past this is an approximation of cornering referred to as 'junction deviaton' (this nomenclature comes from most open source firmwares for motion control, starting with GRBL, it's also used in Smoothieware and Marlin, to name a few). With junction deviation, we allow some instantaneous change in velocity at corners, based on an approximation of the machine travelling around an arc whose start and endpoints are tangential to the two segments. We use this 'path' to limit 'acceleration' through the curve based on the centripital acceleration we would see at that corner.

    architecture

    architecture

    As the 'junction deviation' is normally treated to be very small (0.01mm) we can 'compress' the acceleration that would happen through this arc into the instantaneous moment where the steppers both reach the endpoint of a segment and start the next one. GRBL notes that it's also possible to actually follow this arc segment, but would take more doing on the CPU. In either case, the end of a segment can be seen to have two ramping segments - one where we ramp both motors such that the ratios between their speeds stays the same (so we stay on the ideal trajectory) and the second segment, through the corner, where the ratio changes to reflect the ratio required to move tangentially on the next segment. To actually travel along the arc, we would ramp such that the speeds are proportional to (some trig) through the arc. To do this the quick and dirty way, we 'compress' the arc segment into the instant that we turn the corner.

    OK, after some time I've implemented this in MODS... at least the planning bit. I now have an array of segments that appear to happily ramp into one another:

    ramping

    Things I don't yet have:

    • next-block requesting
    • a real running queue (right now I just do it once, for a given set of segments)
    • per-axis acceleration limiting (I just use a global accel)

    Things I need to consider:

    • there exists a better overall architecture to do this, surely
    • steps: when do we go from floats to discrete steps? before the stepper? where do we track the small errors between those moves?
    • step timing: code I have right now easily accumulates error, we should update the timer on each step and step exactly at the right time, rather than checking with a 2nd clock

    Block - Chaining

    Now that I have planned segments, I need to turn these into per-axis commands for steppers. I have more code to write:

    • MOD: takes blocks of n-axis moves, finds per-axis moves, converts units into step values, makes stepper packets, ships those
    • Stepper Embedded: takes segment moves with the units mentioned below, executes them. I need a queue of these as well, so that when the stepper finishes one block it can move on to the next, and reply to the planner that it's eaten the block. That means I need a ringbuffer of blocks! oh my. really flexing these prgmeming skills, thought I was a MECHE but look at me now

    So we can generate each block based on their start speed, cruise speed, and exit speed. We use the junction deviation algorithm to determine a maximum permissible exit speed, which is the entry speed of the next block.

    Typically, planners take chunks of a trajectory - say, 25 moves, and 'walk' those moves to plan acceleration linking - as the exit speed of one block is the entry speed of the other.

    architecture

    Once we've done that we can split each block up into N blocks, N being the number of axis.

    These blocks contain:

    • Entry Speed
    • Nominal Rate (cruise rate / ideal speed: steps/s)
    • Steps to Make
    • Acceleration (+ve an -ve, rate: steps/s^2)
    • Exit Speed
    • Direction

    And they are what we send to our stepper motors.

    Kinematics

    In making this modular, there's lots of interest in generalizing to non-cartesian machines. So we also want to introduce some computation that would let us do abstract kinematics to work between actuator positions (at the motors) and world positions (at the end effector). Trouble is, we'll be acceleration limited based on both (motors have inertias, so do end effectors) so this seems tricky, but there's maybe a fast workaround - or it's maybe safe to take only one of these to be the limiting case (probably the end effector in world space) and assume that matching motor accelerations will be OK.

    Networks

    This is all harder when we do it across a network. It's important that all blocks start simultaneously on each motor. At a first pass, I'm going to implement the above and assume the network is instantaneous - it will be interesting to measure the deviation that comes from a ~ 1ms packet delay. Next up would be to measure delivery times to each motor, and set 'start delays' for the first segments of each trajectory, then load each motor with one-or-two moves (the chain) to link. Then we can use acks to determine when each stepper has 'eaten' the most recent block, and measure average ack-times to determine if any of their clocks are slightly faster or slower, issuing them slightly slower / faster speeds to make up for it. I expect these adjustments would be quite small, and hopefully not necessary over jobs up to 2-4 hours.

    However, none of this seems like a really robust way of doing it. Another cool solution would be to just make the network blazing fast (I want FPGAs) and continue to assume that things happen instantaneously.

    Some Architecture

    • planner should track moves back: so zero-step moves don't require an ack to trigger next step? this in planner or state machine?
    • what about jogging and single-line gcodes? right into state machine?
    • so then ack type should request new gcode, so that everything else can be event based not otherwise...
    • or ack event goes to both gcode and planner, so that planner knocks one in, and gets one knocked on top as well
    • then jog buttons can just be dumping moves into that event system
    • start and stop conditions?

    GCode Parser Mod

    • makes GCodes into List of Segments and 'Other Instructions'
    • http://linuxcnc.org/docs/html/gcode.html
    • G0 G1: Segments
    • type: segment
    • cruise speed, entry speed, exit speed
    • p1, p2 (pt arrays)
    • Instruction
    • M3 Sx: start clockwise at Sx speed
    • M5: stop the spindle
    • Maybe state machine spits these out into 'other gcode handlers' - parser just makes them into objects like 'type' - 'm3' etc

    State Machine

    • has machine state & plan state
    • steps/unit
    • axis

    • path pointers: finished, on network, in planner, to go
    • last speed out to network is starting speed for planner
    • on packet acks
    • counts, and sends new planner moves, if next move is not path, does other things?
    • updates three.js viewer with new pointers

    Planner

    • gets new slice() of segments, and 0th element (or something) is start speed
    • does accel planning for the chunk, sends 0th element to motors

    THREE path visualizer

    • window-in-mods
    • takes gcode path and draws arrows
    • takes pointer update and does arrow colour update
    • has orbit etc

    How to Get There

    Project building wise, not step planning.

    • finish stepper

    • formats packet and ships it

    • takes reply and de-muxes, if u, send your axis # back on block request channel ... that should be it

    • planner, 1st, just do 1 window: events are already setup to do this

    • next, do 'start' to send 2 packets, then window at 2 (code could be generalized to make arbitrary window size)

    • actually mill something

    • make a new machine

    • try adding in rotary axis, build mods planner to include arbitrary, additional axis. - the othercutter reborn