Skip to content

Simplify Stepper::stepMotor() #54

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

edgar-bonet
Copy link

The current version of Stepper::stepMotor() uses one switch statement for each possible pin_count, with one case per step number, for a total of 18 cases. However, on a stepper motor, all pin signals follow the same periodic pattern, differing only by the initial phase, which suggests the signals could be built algorithmically.

This pull request exploits this fact in order to simplify the code. It shortens the method Stepper::stepMotor() by 99 lines. Building MotorKnob.ino for an Uno shows 314 bytes of flash were saved.

Note that, for the 5-pin motors, the pattern is hard-coded in a PROGMEM array, although it could be easily generated as

(thisStep+offset) % 10 >= 5

The array method was chosen because, on devices lacking a hardware divider, that modulo operation would be slow. The array only costs 18 bytes of flash, which is small compared to the flash savings achieved.

Instead of separately coding each step, use the fact that all pins
follow the same periodic pattern, with phase shifts between them.

For the 5-pin steppers, hard-code the pattern in a PROGMEM array, as
this is cheaper than reducing a number modulo 10 on chips lacking a
hardware divider.
Copy link

github-actions bot commented Feb 3, 2025

Memory usage change @ 600c2a6

Board flash % RAM for global variables %
arduino:avr:leonardo 💚 -326 - -326 -1.14 - -1.14 0 - 0 0.0 - 0.0
arduino:avr:mega 💚 -370 - -370 -0.15 - -0.15 0 - 0 0.0 - 0.0
arduino:avr:nano 💚 -326 - -326 -1.06 - -1.06 0 - 0 0.0 - 0.0
arduino:mbed_nano:nano33ble 💚 -240 - -232 -0.02 - -0.02 0 - 0 0.0 - 0.0
arduino:mbed_nano:nanorp2040connect 💚 -328 - -328 -0.0 - -0.0 0 - 0 0.0 - 0.0
arduino:mbed_portenta:envie_m7 N/A N/A N/A N/A
arduino:mbed_portenta:envie_m7:target_core=cm4 N/A N/A N/A N/A
arduino:megaavr:nona4809 💚 -378 - -378 -0.77 - -0.77 0 - 0 0.0 - 0.0
arduino:megaavr:uno2018 💚 -378 - -378 -0.78 - -0.78 0 - 0 0.0 - 0.0
arduino:sam:arduino_due_x_dbg 💚 -272 - -272 -0.05 - -0.05 N/A N/A
arduino:samd:arduino_zero_edbg 💚 -332 - -324 -0.13 - -0.12 0 - 0 0.0 - 0.0
arduino:samd:mkrzero 💚 -332 - -324 -0.13 - -0.12 0 - 0 0.0 - 0.0
arduino:samd:nano_33_iot 💚 -332 - -324 -0.13 - -0.12 0 - 0 0.0 - 0.0
Click for full report table
Board examples/MotorKnob
flash
% examples/MotorKnob
RAM for global variables
% examples/stepper_oneRevolution
flash
% examples/stepper_oneRevolution
RAM for global variables
% examples/stepper_oneStepAtATime
flash
% examples/stepper_oneStepAtATime
RAM for global variables
% examples/stepper_speedControl
flash
% examples/stepper_speedControl
RAM for global variables
%
arduino:avr:leonardo -326 -1.14 0 0.0 -326 -1.14 0 0.0 -326 -1.14 0 0.0 -326 -1.14 0 0.0
arduino:avr:mega -370 -0.15 0 0.0 -370 -0.15 0 0.0 -370 -0.15 0 0.0 -370 -0.15 0 0.0
arduino:avr:nano -326 -1.06 0 0.0 -326 -1.06 0 0.0 -326 -1.06 0 0.0 -326 -1.06 0 0.0
arduino:mbed_nano:nano33ble -232 -0.02 0 0.0 -240 -0.02 0 0.0 -240 -0.02 0 0.0 -232 -0.02 0 0.0
arduino:mbed_nano:nanorp2040connect -328 -0.0 0 0.0 -328 -0.0 0 0.0 -328 -0.0 0 0.0 -328 -0.0 0 0.0
arduino:mbed_portenta:envie_m7 N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
arduino:mbed_portenta:envie_m7:target_core=cm4 N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
arduino:megaavr:nona4809 -378 -0.77 0 0.0 -378 -0.77 0 0.0 -378 -0.77 0 0.0 -378 -0.77 0 0.0
arduino:megaavr:uno2018 -378 -0.78 0 0.0 -378 -0.78 0 0.0 -378 -0.78 0 0.0 -378 -0.78 0 0.0
arduino:sam:arduino_due_x_dbg -272 -0.05 N/A N/A -272 -0.05 N/A N/A -272 -0.05 N/A N/A -272 -0.05 N/A N/A
arduino:samd:arduino_zero_edbg -328 -0.13 0 0.0 -332 -0.13 0 0.0 -324 -0.12 0 0.0 -328 -0.13 0 0.0
arduino:samd:mkrzero -328 -0.13 0 0.0 -332 -0.13 0 0.0 -324 -0.12 0 0.0 -328 -0.13 0 0.0
arduino:samd:nano_33_iot -328 -0.13 0 0.0 -332 -0.13 0 0.0 -324 -0.12 0 0.0 -328 -0.13 0 0.0
Click for full report CSV
Board,examples/MotorKnob<br>flash,%,examples/MotorKnob<br>RAM for global variables,%,examples/stepper_oneRevolution<br>flash,%,examples/stepper_oneRevolution<br>RAM for global variables,%,examples/stepper_oneStepAtATime<br>flash,%,examples/stepper_oneStepAtATime<br>RAM for global variables,%,examples/stepper_speedControl<br>flash,%,examples/stepper_speedControl<br>RAM for global variables,%
arduino:avr:leonardo,-326,-1.14,0,0.0,-326,-1.14,0,0.0,-326,-1.14,0,0.0,-326,-1.14,0,0.0
arduino:avr:mega,-370,-0.15,0,0.0,-370,-0.15,0,0.0,-370,-0.15,0,0.0,-370,-0.15,0,0.0
arduino:avr:nano,-326,-1.06,0,0.0,-326,-1.06,0,0.0,-326,-1.06,0,0.0,-326,-1.06,0,0.0
arduino:mbed_nano:nano33ble,-232,-0.02,0,0.0,-240,-0.02,0,0.0,-240,-0.02,0,0.0,-232,-0.02,0,0.0
arduino:mbed_nano:nanorp2040connect,-328,-0.0,0,0.0,-328,-0.0,0,0.0,-328,-0.0,0,0.0,-328,-0.0,0,0.0
arduino:mbed_portenta:envie_m7,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
arduino:mbed_portenta:envie_m7:target_core=cm4,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
arduino:megaavr:nona4809,-378,-0.77,0,0.0,-378,-0.77,0,0.0,-378,-0.77,0,0.0,-378,-0.77,0,0.0
arduino:megaavr:uno2018,-378,-0.78,0,0.0,-378,-0.78,0,0.0,-378,-0.78,0,0.0,-378,-0.78,0,0.0
arduino:sam:arduino_due_x_dbg,-272,-0.05,N/A,N/A,-272,-0.05,N/A,N/A,-272,-0.05,N/A,N/A,-272,-0.05,N/A,N/A
arduino:samd:arduino_zero_edbg,-328,-0.13,0,0.0,-332,-0.13,0,0.0,-324,-0.12,0,0.0,-328,-0.13,0,0.0
arduino:samd:mkrzero,-328,-0.13,0,0.0,-332,-0.13,0,0.0,-324,-0.12,0,0.0,-328,-0.13,0,0.0
arduino:samd:nano_33_iot,-328,-0.13,0,0.0,-332,-0.13,0,0.0,-324,-0.12,0,0.0,-328,-0.13,0,0.0

@edgar-bonet
Copy link
Author

Note that this version of Stepper::stepMotor() is functionally equivalent to the original one. I used the following test program to check, on my PC, that both versions of the method produce the exact same signal patterns:

Source code (click to expand/collapse)
/*
 * test-simpler.c: Test change in branch `simpler'.
 *
 * Build:
 *   g++ -O2 -Wall -Wextra test-simpler.cpp -o test-simpler
 */

/***********************************************************************
 * Test harness.
 */

#include <cstdint>
#include <cstdio>

#define PROGMEM
#define LOW  0
#define HIGH 1

#define pgm_read_byte(addr) (*(const uint8_t*)(addr))

static uint8_t pin_values[5];

void reset_pins()
{
    for (int i = 0; i < 5; i++)
        pin_values[i] = 8;
}

void digitalWrite(uint8_t pin, uint8_t value)
{
    pin_values[pin] = value ? 1 : 0;
}

void show_pin_states(int count)
{
    for (int i = 0; i < count; i++)
        printf("%d", pin_values[i]);
}

struct Stepper {
    void stepMotor_old(int);
    void stepMotor_new(int);
    int pin_count;
    const int motor_pin_1 = 0;
    const int motor_pin_2 = 1;
    const int motor_pin_3 = 2;
    const int motor_pin_4 = 3;
    const int motor_pin_5 = 4;
} stepper;

void test(int pin_count, int phase_count)
{
    stepper.pin_count = pin_count;
    puts("ph  old  new");
    for (int i = 0; i < phase_count; i++) {
        printf("%d  ", i);
        reset_pins();
        stepper.stepMotor_old(i);
        show_pin_states(pin_count);
        printf("  ");
        reset_pins();
        stepper.stepMotor_new(i);
        show_pin_states(pin_count);
        putchar('\n');
    }
}

int main()
{
    printf("2 pins:\n");
    test(2, 4);
    printf("\n4 pins:\n");
    test(4, 4);
    printf("\n5 pins:\n");
    test(5, 10);
}

/***********************************************************************
 * Original implementation.
 */
#define stepMotor stepMotor_old

void Stepper::stepMotor(int thisStep)
{
  if (this->pin_count == 2) {
    switch (thisStep) {
      case 0:  // 01
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
      break;
      case 1:  // 11
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, HIGH);
      break;
      case 2:  // 10
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
      break;
      case 3:  // 00
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, LOW);
      break;
    }
  }
  if (this->pin_count == 4) {
    switch (thisStep) {
      case 0:  // 1010
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, LOW);
      break;
      case 1:  // 0110
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, LOW);
      break;
      case 2:  //0101
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, HIGH);
      break;
      case 3:  //1001
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, HIGH);
      break;
    }
  }

  if (this->pin_count == 5) {
    switch (thisStep) {
      case 0:  // 01101
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, LOW);
        digitalWrite(motor_pin_5, HIGH);
        break;
      case 1:  // 01001
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, LOW);
        digitalWrite(motor_pin_5, HIGH);
        break;
      case 2:  // 01011
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, HIGH);
        digitalWrite(motor_pin_5, HIGH);
        break;
      case 3:  // 01010
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, HIGH);
        digitalWrite(motor_pin_5, LOW);
        break;
      case 4:  // 11010
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, HIGH);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, HIGH);
        digitalWrite(motor_pin_5, LOW);
        break;
      case 5:  // 10010
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, LOW);
        digitalWrite(motor_pin_4, HIGH);
        digitalWrite(motor_pin_5, LOW);
        break;
      case 6:  // 10110
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, HIGH);
        digitalWrite(motor_pin_5, LOW);
        break;
      case 7:  // 10100
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, LOW);
        digitalWrite(motor_pin_5, LOW);
        break;
      case 8:  // 10101
        digitalWrite(motor_pin_1, HIGH);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, LOW);
        digitalWrite(motor_pin_5, HIGH);
        break;
      case 9:  // 00101
        digitalWrite(motor_pin_1, LOW);
        digitalWrite(motor_pin_2, LOW);
        digitalWrite(motor_pin_3, HIGH);
        digitalWrite(motor_pin_4, LOW);
        digitalWrite(motor_pin_5, HIGH);
        break;
    }
  }
}

/***********************************************************************
 * Updated implementation.
 */
#undef stepMotor
#define stepMotor stepMotor_new

void Stepper::stepMotor(int thisStep)
{
  static PROGMEM const uint8_t phases[] = {
    LOW, LOW, LOW, LOW, HIGH, HIGH, HIGH, HIGH, HIGH,
    LOW, LOW, LOW, LOW, LOW, HIGH, HIGH, HIGH, HIGH
  };

  switch (pin_count) {
    case 2:
      digitalWrite(motor_pin_1, (thisStep+1) & 2);
      digitalWrite(motor_pin_2, (thisStep+2) & 2);
    break;
    case 4:
      digitalWrite(motor_pin_4, (thisStep+0) & 2);
      digitalWrite(motor_pin_2, (thisStep+1) & 2);
      digitalWrite(motor_pin_3, (thisStep+2) & 2);
      digitalWrite(motor_pin_1, (thisStep+3) & 2);
    break;
    case 5:
      digitalWrite(motor_pin_1, pgm_read_byte(&phases[thisStep+0]));
      digitalWrite(motor_pin_4, pgm_read_byte(&phases[thisStep+2]));
      digitalWrite(motor_pin_2, pgm_read_byte(&phases[thisStep+4]));
      digitalWrite(motor_pin_5, pgm_read_byte(&phases[thisStep+6]));
      digitalWrite(motor_pin_3, pgm_read_byte(&phases[thisStep+8]));
    break;
  }
}

The output of this test program is:

2 pins:
ph  old  new
0  01  01
1  11  11
2  10  10
3  00  00

4 pins:
ph  old  new
0  1010  1010
1  0110  0110
2  0101  0101
3  1001  1001

5 pins:
ph  old  new
0  01101  01101
1  01001  01001
2  01011  01011
3  01010  01010
4  11010  11010
5  10010  10010
6  10110  10110
7  10100  10100
8  10101  10101
9  00101  00101

@per1234 per1234 added type: enhancement Proposed improvement topic: code Related to content of the project itself labels Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: code Related to content of the project itself type: enhancement Proposed improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants