The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

The New Hotness

  • Stepper Sync Wheel: Group Sync

    A small tweak to that code produces a sync pulse for each full sine wave of microstepping current, aligned with the step pulses. The sync pulse occurs on the rising edge of the current waveform (because I set it up that way) and has 50% duty cycle to allow triggering at either zero-current microstep.

    Then pix like this happen:

    Microstepping Group Sync
    Microstepping Group Sync

    The traces:

    • top = 1/group sync
    • middle = winding current at 500 mA/div
    • bottom = step pulse, 1/microstep

    The big jump just before the zero-current microstep on the decreasing-current sides of the sine wave indicates that it’s hard to get all the current out of the windings at 12 V. A detail view of those steps shows that the current is 50% higher than it should be at the start of the zero-current microstep, having completely missed the last microstep:

    Decreasing Current
    Decreasing Current

    Which, of course, is why I’m doing all this: to explore that kind of behavior.

    You may find the generated sync pulses are off by ±1 microstep from the expected start of the zero-current microstep, because the optical 1/rev signal threshold may line up perversely with the start of a microstep. You can twist the sync wheel just slightly on the shaft, but it’s pretty much a matter of shaking the dice to see if a better combination comes up. Doesn’t make any real difference to the scope triggering, though, as any stable sync alignment is as good as any other.

    The code uses the 1/rev optical sync pulse once, to get the initial alignment, so whacking the wheel as it rotates may cause the generated sync pulses to skip a beat or twenty. The result remains stable, just at a different alignment.

    One could argue that you really don’t need the 1/rev optical signal at all, but I find it comforting to use an absolute rotational reference to lock the pulses in (pretty nearly) the same place every time. If you’re stuck with an in-place motor, then you probably don’t have a 1/rev signal and you must wing it.

    The Arduino source code:

    // Stepper motor driver synchronization
    // Ed Nisley KE4ZNU June 2011
    
    //-- Stepper parameters
    
    #define SYNC_OFFSET            8        // steps from 1/rev pulse to start of first 4-full-step group
    
    #define FULL_STEPS_PER_REV    200
    #define MICROSTEPPING        8
    
    #define GROUPS_PER_REV (FULL_STEPS_PER_REV / 4)
    #define STEPS_PER_GROUP (MICROSTEPPING * 4)
    
    //-- Pin definitions, all of which depend on internal hardware: do *not* change
    
    #define PIN_REV        2                // INT0 = positive 1/rev pulse from optical switch
    #define PIN_STEP    5                // T1 = positive 1/step pulse from stepper driver
    #define PIN_TRIGGER    9                // OC1A = positive trigger pulse to scope
    
    //-- Trace outputs may be chosen freely
    
    #define PIN_TRACE_A    10
    #define PIN_TRACE_B    11
    #define PIN_TRACE_C    12
    
    #define PIN_LED        13                // standard Arduino LED
    
    //---------------------
    // State Variables
    
    word PulseCounter;
    
    //---------------------
    // Useful routines
    
    //--- Input & output pins
    
    void TogglePin(char bitpin) {
    digitalWrite(bitpin,!digitalRead(bitpin));    // toggle the bit based on previous output
    }
    
    //----------------
    // Initializations
    
    void setup() {
    
    pinMode(PIN_REV,INPUT);        // INT0 1/rev pulse from wheel
    
    pinMode(PIN_STEP,INPUT);        // T1 step pulse from stepper driver
    
    pinMode(PIN_LED,OUTPUT);
    digitalWrite(PIN_LED,LOW);
    
    pinMode(PIN_TRACE_A,OUTPUT);
    pinMode(PIN_TRACE_B,OUTPUT);
    pinMode(PIN_TRACE_C,OUTPUT);
    
    //--- Prepare Timer1 to count external stepper drive pulses
    
    TCCR1B = B00001000;                // Timer1: Mode 4 = CTC, TOP = OCR1A, clock stopped
    
    pinMode(PIN_TRIGGER,OUTPUT);        // OC1A to scope trigger
    
    //-- Wait for rising edge of 1/rev pulse from optical switch
    
    TCCR1A = B11000000;                        // COM1A set on compare
    TCNT1 = 0;                                // ensure we start from zero
    OCR1A = SYNC_OFFSET;                        // set step counter
    
    while(!digitalRead(PIN_REV)) {            // stall until 1/rev input rises
    TogglePin(PIN_TRACE_A);
    }
    
    //-- Got it, fire up the timer to count steps to start of first group
    
    TCCR1B |= B00000111;                        // enable clock from T1 pin, rising edge
    
    digitalWrite(PIN_LED,HIGH);                // show we got here
    digitalWrite(PIN_TRACE_A,LOW);
    
    while(!(TIFR1 & _BV(OCF1A))) {            // wait for compare
    digitalWrite(PIN_TRACE_B,digitalRead(PIN_STEP));
    continue;
    }
    TIFR1 |= _BV(OCF1A);                        // clear match flag
    
    //-- Scope sync pulse is now active, we can enter the main loop
    
    }
    
    //----------------
    // The main event
    
    void loop() {
    
    //-- Scope sync pulse active
    
    digitalWrite(PIN_LED,LOW);                // show we got here
    digitalWrite(PIN_TRACE_B,LOW);
    
    //-- Set up for first half of the group, sync high -> low
    
    TCCR1A = B10000000;                        // COM1A clear on compare
    OCR1A = (STEPS_PER_GROUP / 2) - 1;
    
    while(!(TIFR1 & _BV(OCF1A))) {            // wait for compare
    digitalWrite(PIN_TRACE_B,digitalRead(PIN_STEP));
    continue;
    }
    TIFR1 |= _BV(OCF1A);                        // clear match flag
    digitalWrite(PIN_TRACE_B,LOW);
    
    //-- Set up for the second half, sync low -> high
    
    TCCR1A = B11000000;                        // COM1A set on compare
    OCR1A = (STEPS_PER_GROUP - (STEPS_PER_GROUP / 2)) - 1;    // may be odd, so allow for that
    
    while(!(TIFR1 & _BV(OCF1A))) {            // wait for compare
    digitalWrite(PIN_TRACE_B,digitalRead(PIN_STEP));
    continue;
    }
    TIFR1 |= _BV(OCF1A);                        // clear match flag
    digitalWrite(PIN_TRACE_B,LOW);
    
    //-- Shut down counter and wait for end of 1/rev pulse
    
    #if 0
    TCCR1B &= ~B00000111;                        // turn off timer clock
    
    while(digitalRead(PIN_REV)) {                // stall until 1/rev pulse goes low again
    TogglePin(PIN_TRACE_C);
    }
    digitalWrite(PIN_TRACE_B,LOW);
    #endif
    
    }