How to run a stepper run function along running other functions

Hi,

I'm using a TFT system to control different outputs and receive inputs.

The stepper complete revolution; for example, takes a lot of time to finish.

So, if the code called the function to run the stepper motor, my code is working with setting up the TFT_IRQ pin attached to interrupt, so every time I touch the TFT it immediately process my touch action.

But how to still run the stepper motor function while doing other tasks with the TFT system ?

I thought of using one of the unused timers, and enable this timer only with functions that takes a lot of time to accomplish, I'm thinking of another task_manager function other than the one I'm using to check for TFT functions.

Where this second task_manager is enabled when there's a function that takes a lot of time to process, and register multiple functions too, and call them until they're finished.

See "blink without delay" example, you can run as much parallel processes as you need with this approach.

2 Likes
void runStepper(unsigned long interval){
  static unsigned long lastStep =0;
  unsigned long now = micros();
  if(now - last > interval && stepsToGo > 0)
    digitalWrite(stepPin,HIGH);
    last = now;
    --stepsToGo;
    digitalWrite(stepPin,LOW);
  }
}
1 Like

Make sure the other tasks are quick. If they aren't then break them into smaller steps that are.

A probably better solution would be to use one of the timers to do the quick stuff all the time. You don't want a long timer interrupt. But you could put the code to step the stepper into a timer interrupt so that it gets called at regular intervals no matter what your other code is doing.

That could mess things up if those interrupt handlers are long. If you want things to be immediately responsive to human touch then keep things short and non-blocking. No human can tell the difference between an action happening the instant the touch is registered or 20ms later. So you've got plenty of time to react. Have your interrupt set a flag and register the touch points, but put the code to actually do anything about it out in your loop.

Post your code. I bet there's way better ways to fix the problem you're having.

2 Likes

Combining references by @b707 and @DaveX, here is my Step Without delay() (simulation)

// Step without delay() - a bipolar nod to blink without delay()

byte dirPin  = 2;
byte stepPin = 3;
bool stepState = LOW;  // pulseState will be used to start a single Stepper pulse
unsigned long previousMillis = 0;  // store last step
unsigned long interval = 20;  // interval at which to step (milliseconds)

void setup() {
  Serial.begin(115200);
  pinMode(dirPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  digitalWrite(dirPin, HIGH);
  welcome();
}

void loop() {
  unsigned long currentMillis = millis(); // capture time
  if (currentMillis - previousMillis >= interval) { // compare elapsed time to interval
    previousMillis = currentMillis; // store last step time

    // squarewave(); // HIGH for interval, LOW for interval
    singlepulse(); // HIGH/LOW then wait for interval
  }
}

void squarewave() { // this is how Blink without delay() blinks the LED
  if (stepState)
    stepState = LOW;
  else
    stepState = HIGH;
  digitalWrite(stepPin, stepState);    // step the motor
}

void singlepulse() { // minimum requirement to step a stepper, maybe a 10ms between transits
  digitalWrite(stepPin, HIGH);
  digitalWrite(stepPin, LOW);
}

void welcome() {
  Serial.println("Step without delay() - A bipolar nod to Blink without delay()");
}

@wolfrose - your TFT code can do what it needs as time is being kept inside void loop() for the stepper motor.

1 Like

I tried to keep as much as I can that the code is non-blocking.

I'm using a servo, and using Adafruit-PWM-Servo-Driver-Library-master and I'm working now to set an action to the servo and do other things with the TFT.

But I think the Servo code is blocking, but I'm still testing.

Yep, I sat up a delay for the TFT touch 300ms, I know it's a lot but I'm planning to lower it later.

I've done the TFT interrupt service routine, I guess any other functions can be done the same way, after all, only one instant is going to be processed.

This is my code:

  1. TFT task manager
//#define TFT_IRQ_PIN   36  // using esp32 devkit-v1
#define TFT_IRQ_PIN     PB2 // using stm32 blackpill

//void IRAM_ATTR tft_irq_isr(void);
void tft_irq_isr(void);

void tft_start_setting(void){
  tft_irq.page_index    = HOME_PAGE;
  tft_irq.button_index  = NOT_SELECTED;
  tft_irq.page_update   = false;
  tft_irq.button_update = false;
  tft_irq.tftTouchLock  = false;

  // tft isr setting
  pinMode(TFT_IRQ_PIN, INPUT);
  attachInterrupt(TFT_IRQ_PIN, tft_irq_isr, FALLING);
  
}

//void IRAM_ATTR tft_irq_isr(void) {
void tft_irq_isr(void) {  
  if(!tft_irq.tftTouchLock){
    tft_irq.buttonTimer = millis();
    tft_irq.tftTouchLock = 1;
    tft_irq.irq_request_count++;
  }
}

void tft_task_manager(void){

  print_pages();
  perform_actions();
  read_buttons();

  if(tft_irq.tftTouchLock){
    if(millis() - tft_irq.buttonTimer >= 300){
      tft_irq.tftTouchLock = 0;
    }
  }
}
  1. Main TFT control functions. There are 3 main functions: print_pages, read_buttons and perform_actions:
#include "system_variables.h"
/////////////////////////////////////// MAIN PAGE ////////////////////////////////////////////
// pages and headings

// main ///////////////////////////////////
text          t_main_page        = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "System Menu", CENTERED};
button        p_motor            = {40, 60, 250, 30, TFT_BLUE, TFT_WHITE, "MOTOR CONTROL", MOTOR_PAGE};
button        p_electrical       = {40, 110, 250, 30, TFT_BLUE, TFT_WHITE, "ELECTRICAL CONTROL", ELECTRICAL_PAGE};
button        p_weather          = {40, 160, 250, 30, TFT_BLUE, TFT_WHITE, "WEATHER MONITOR", WEATHER_PAGE};
button        p_led_control      = {40, 210, 250, 30, TFT_BLUE, TFT_WHITE, "LED CONTROL", LED_CONTROL_PAGE};
button        p_new              = {40, 260, 250, 30, TFT_BLUE, TFT_WHITE, "NEW CONTROL", NEW_CONTROL_PAGE};

// electrical /////////////////////////////
text          t_main_electrical  = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "Electrical Type Control", CENTERED};
data_output   d_voltage          = {120, 80, 50, 30,  TFT_YELLOW, TFT_BLACK, TFT_YELLOW, 0, "VOLTAGE", 40, "", 0, 0};
data_output   d_current          = {120, 130, 50, 30, TFT_YELLOW, TFT_BLACK, TFT_YELLOW, 0, "CURRENT", 40, "", 0, 0};
data_output   d_power            = {120, 180, 50, 30, TFT_YELLOW, TFT_BLACK, TFT_YELLOW, 0, "POWER",   40, "", 0, 0};

// weather ////////////////////////////////
text          t_main_weather     = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "Weather Monitor", CENTERED};
slide         s_fan_speed        = {150, 170, 130, 30, TFT_RED, TFT_YELLOW, 0, 0, 0, 0, 0};

// motor //////////////////////////////////
text          t_main_motor       = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "Motor Type Control", CENTERED};

// constant home/sub-home pages
button        p_main_page        = {40, 440, 250, 30, TFT_ORANGE, TFT_BLACK, "HOME PAGE", HOME_PAGE};
button        p_motors_main_page = {40, 390, 250, 30, TFT_ORANGE, TFT_BLACK, "MOTORS MENU", MOTOR_PAGE};

text          t_stepper_main     = {CENTERED_MAIN, 20, TFT_ORANGE, TFT_BLACK, "Stepper Motor", CENTERED};
text          t_servo_main       = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "Servo Motor", CENTERED};
text          t_dc_main          = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "DC Motor", CENTERED};

button        p_servo            = {40, 60,  250, 30, TFT_GREEN, TFT_DARKGREEN,  "SERVO MOTOR CONTROL", SERVO_MOTOR_PAGE};
button        p_dc               = {40, 110, 250, 30, TFT_GREEN, TFT_DARKGREEN,  "DC MOTOR CONTROL", DC_MOTOR_PAGE};
button        p_stepper          = {40, 160, 250, 30, TFT_GREEN, TFT_DARKGREEN,  "STEPPER MOTOR CONTROL", STEPPER_MOTOR_PAGE};

// general motor itmes
text          t_motor_mode       = {THREE_COL_1, 80,  TFT_GREEN, TFT_BLACK, "Mode",      NOT_CENTERED};
text          t_motor_direction  = {THREE_COL_1, 130, TFT_GREEN, TFT_BLACK, "Direction", NOT_CENTERED};
text          t_motor_speed      = {THREE_COL_1, 190, TFT_GREEN, TFT_BLACK, "Speed",     NOT_CENTERED};
button_toggle b_motor_mode       = {THREE_COL_2,   60, 50, 30, 20, TFT_GREEN, TFT_BLUE,  TFT_BLACK, "AT", "MT",    0, 0, 0, 0, 0, 0, 0};
button_toggle b_motor_direction  = {THREE_COL_2,  110, 50, 30, 20, TFT_GREEN, TFT_BLUE,  TFT_BLACK, "CW", "CCW",   0, 0, 0, 0, 0, 0, 0};
button_toggle b_motor_start_stop = {CENTERED_COL, 250, 80, 60, 20, TFT_RED,   TFT_GREEN, TFT_BLACK, "RUN", "STOP", 0, 0, 0, 0, 0, 0, 0};
slide         s_motor_speed      = {150, 170, 130, 30, TFT_RED, TFT_YELLOW, 0, 0, 0, 0, 0};

// servo tools
text          t_servo_angle      = {THREE_COL_1, 190, TFT_GREEN, TFT_BLACK, "Servo Angle", NOT_CENTERED};
slide         s_servo_pos        = {150, 170, 130, 30, TFT_RED, TFT_YELLOW, SERVO_MOTOR_CONTROL, PCA9685_0, 0, 0, 0};

// stepper tools

button_toggle b_stepper_mode      = {THREE_COL_2, 60, 50, 30, 20, TFT_GREEN, TFT_BLUE,  TFT_BLACK, "FULL STEP", "HALF STEP", 0, 0, 0, 0, 0, 0, 0};
button_toggle b_stepper_direction = {THREE_COL_2, 60, 50, 30, 20, TFT_GREEN, TFT_BLUE,  TFT_BLACK, "CW", "CCW", 0, 0, 0, 0, 0, 0, 0};

stepper_4_wires_obj_t stepper1_obj_arduino = {
PB12, PB13, PB14, PB15, STEPPER_START_STEP, STEPPER_360_DEG, STEPPER_CW, MOVE_TO, STEPPER_HALF_STEP, 0, micros(), 2000};


// led control
text t_led_control_main          = {CENTERED_MAIN, 20, TFT_WHITE, TFT_BLACK, "LED CONTROL", CENTERED};
button_toggle b_led_pb8          = {CENTERED_COL, 160, 90, 40, 20, TFT_RED, TFT_GREEN, TFT_BLACK, "LED ON", "LED OFF", LOGIC_CONTROL, PB8, HIGH, LOW, 0, 0, 0};
button_toggle b_led_pb9          = {CENTERED_COL, 210, 90, 40, 20, TFT_RED, TFT_GREEN, TFT_BLACK, "LED ON", "LED OFF", LOGIC_CONTROL, PB9, HIGH, LOW, 0, 0, 0};

slide         s_led_brightness   = {150, 110, 130, 30, TFT_RED, TFT_YELLOW, PWM_CONTROL, PB9, 0, 0, 0};

// new control
text          t_new_control_main = {CENTERED_MAIN, 20, TFT_ORANGE, TFT_BLACK, "NEW CONTROL", CENTERED};
button_toggle b_tgl_btn          = {CENTERED_COL, 160, 90, 40, 20, TFT_RED, TFT_GREEN, TFT_BLACK, "ON", "OFF", 0, 0, 0, 0, 0, 0, 0};
slide         s_brightness       = {150, 110, 130, 30, TFT_RED, TFT_YELLOW, 0, 0, 0, 0, 0};


void main_menu_page(void) {
  tft.fillScreen(TFT_BLACK);
  draw_text(t_main_page);
  draw_button(p_motor);
  draw_button(p_electrical);
  draw_button(p_weather);
  draw_button(p_led_control);
  draw_button(p_new);
}

void motors_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_main_motor);
  draw_button(p_servo);
  draw_button(p_dc);
  draw_button(p_stepper);
  draw_button(p_main_page);
}

void electrical_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_main_electrical);
  draw_button(p_main_page);
  draw_data_output(d_voltage);
  draw_data_output(d_current);
  draw_data_output(d_power);
}

void weather_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_main_weather);
  draw_slide(s_fan_speed);
  draw_button(p_main_page);  
}

void led_control_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_led_control_main);
  draw_button_toggle(b_led_pb9);
  draw_button_toggle(b_led_pb8);
  draw_slide(s_led_brightness);
  draw_button(p_main_page);
}

void new_control_page(void){
  tft.fillScreen(TFT_DARKGREEN);
  draw_text(t_new_control_main);
  draw_button_toggle(b_tgl_btn);
  draw_slide(s_brightness);
  draw_button(p_main_page);
}

void stepper_motor_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_stepper_main);
  draw_text(t_motor_mode);
  draw_button_toggle(b_stepper_mode);
  draw_text(t_motor_direction);
  draw_button_toggle(b_motor_direction);
  
  draw_text(t_motor_speed);
  draw_slide(s_motor_speed);

  draw_button_toggle(b_motor_start_stop);

  draw_button(p_motors_main_page);
  draw_button(p_main_page);
}

void servo_motor_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_servo_main);

  draw_text(t_servo_angle);
  draw_slide(s_servo_pos);
    
  draw_button(p_motors_main_page);
  draw_button(p_main_page);  
}

void dc_motor_page(void){
  tft.fillScreen(TFT_BLACK);
  draw_text(t_dc_main);
  draw_text(t_motor_mode);
  draw_text(t_motor_direction);
  draw_button_toggle(b_motor_direction); 
  draw_text(t_motor_speed);
  draw_slide(s_motor_speed);
  draw_button_toggle(b_motor_start_stop);
  
  draw_button(p_motors_main_page);
  draw_button(p_main_page);  
}


//////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// BUTTON READ / PROCESS ////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
void read_buttons(void){

  if(tft_irq.tft_task_state == READ_BUTTON && tft_irq.page_update == true){

    if(tft_irq.page_index == HOME_PAGE){
      tft_irq.button_index |= read_button(p_motor);
      tft_irq.button_index |= read_button(p_electrical);
      tft_irq.button_index |= read_button(p_weather);
      tft_irq.button_index |= read_button(p_led_control);
      tft_irq.button_index |= read_button(p_new);
    }
    else if(tft_irq.page_index == MOTOR_PAGE){
      tft_irq.button_index |= read_button(p_servo);
      tft_irq.button_index |= read_button(p_dc);
      tft_irq.button_index |= read_button(p_stepper);
      tft_irq.button_index |= read_button(p_main_page);  
    }
    else if(tft_irq.page_index == STEPPER_MOTOR_PAGE){

      tft_irq.button_index |= read_button_toggle(b_stepper_mode);
      tft_irq.button_index |= read_button_toggle(b_motor_direction);
      tft_irq.button_index |= read_button_toggle(b_motor_start_stop);

      // tft_irq.button_index |= // --> not connected to pin/output
      update_slide(s_motor_speed);
      
      tft_irq.button_index |= read_button(p_motors_main_page);
      tft_irq.button_index |= read_button(p_main_page);
    }
    else if(tft_irq.page_index == DC_MOTOR_PAGE){

      tft_irq.button_index |= read_button_toggle(b_motor_mode);
      tft_irq.button_index |= read_button_toggle(b_motor_direction);

      // tft_irq.button_index |= // --> not connected to pin/output
      update_slide(s_motor_speed);
      
      tft_irq.button_index |= read_button(p_motors_main_page);
      tft_irq.button_index |= read_button(p_main_page);
    }
    else if(tft_irq.page_index == SERVO_MOTOR_PAGE){
      
      tft_irq.button_index |= update_slide(s_servo_pos);
      
      tft_irq.button_index |= read_button(p_motors_main_page);
      tft_irq.button_index |= read_button(p_main_page);
    }    
    else if(tft_irq.page_index == ELECTRICAL_PAGE){
      tft_irq.button_index |= read_button(p_main_page);
      update_data_output(d_voltage);
      update_data_output(d_current);
      update_data_output(d_power);

    }
    else if(tft_irq.page_index == WEATHER_PAGE){
      update_slide(s_fan_speed);
      tft_irq.button_index |= read_button(p_main_page);
    }
    else if(tft_irq.page_index == LED_CONTROL_PAGE){
      tft_irq.button_index |= read_button_toggle(b_led_pb8);
      tft_irq.button_index |= read_button_toggle(b_led_pb9);
      
      tft_irq.button_index |= update_slide(s_led_brightness);
      
      tft_irq.button_index |= read_button(p_main_page);
    }    
    else if(tft_irq.page_index == NEW_CONTROL_PAGE){
      tft_irq.button_index |= read_button_toggle(b_tgl_btn);
      
      tft_irq.button_index |= update_slide(s_brightness);
      
      tft_irq.button_index |= read_button(p_main_page);
    }    
    
    
    /////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////
    
    if(tft_irq.button_index){
      if(tft_irq.button_index < PAGE_END_INDEX && tft_irq.button_index > PAGE_START_INDEX){
        tft_irq.tft_task_state = PRINT_PAGE;
        tft_irq.page_index = tft_irq.button_index;
        tft_irq.page_update = false;
      }
      // execute button actions
      else if(tft_irq.button_index > ACTION_START_INDEX && tft_irq.button_index < ACTION_END_INDEX){
        tft_irq.tft_task_state = PERFORM_ACTION;
        tft_irq.button_update = true;
      }
      
//      else if(tft_irq.button_index > ACTIONS_START_INDEX && tft_irq.button_index < ACTIONS_END_INDEX){
//        tft_irq.tft_task_state = PERFORM_ACTION;
//        tft_irq.button_update = true;
//      }      
    }
  }
}

void perform_actions(void){

  if(tft_irq.tft_task_state == PERFORM_ACTION && tft_irq.button_update == true){
    switch(tft_irq.button_index){

      case LOGIC_CONTROL:
        pinMode(tft_irq.mcu_task_pin, OUTPUT);
        digitalWrite(tft_irq.mcu_task_pin, tft_irq.mcu_task_action);
      break;

      case PWM_CONTROL:
        analogWrite(tft_irq.mcu_task_pin, tft_irq.mcu_task_action);
      break;

      case SERVO_MOTOR_CONTROL:
        pwm.setPWM(tft_irq.mcu_task_pin,  0, tft_irq.mcu_task_action);
      break;

      case STEPPER_MOTOR_CONTROL:
        stepper_4_wires_init(stepper1_obj_arduino);
        stepper_4_wires_step_run(stepper1_obj_arduino);
      break;      
  
      case READ_INPUT_DIGITAL:
        tft_irq.mcu_task_action = digitalRead(tft_irq.mcu_task_pin);
      break;
      
      case READ_INPUT_ANALOG:
        tft_irq.mcu_task_action = analogRead(tft_irq.mcu_task_pin);
      break;
    
    }
    tft_irq.tft_task_state  = READ_BUTTON;
    tft_irq.button_index    = NOT_SELECTED;
    tft_irq.button_update   = false;
    tft_irq.mcu_task_pin    = NOT_SELECTED;
    tft_irq.mcu_task_action = NOT_SELECTED;    
  }
}

void print_pages(void) {

  if(tft_irq.tft_task_state == PRINT_PAGE && tft_irq.page_update == false){
    switch (tft_irq.page_index) {

//////////////////////////////////// home page
      case HOME_PAGE:
        main_menu_page();
      break;

//////////////////////////////////// main pages      
      case MOTOR_PAGE:
        motors_page();
      break;
  
      case WEATHER_PAGE:
        weather_page();
      break;
  
      case ELECTRICAL_PAGE:
        electrical_page();
      break;

      case LED_CONTROL_PAGE:
        led_control_page();
      break;

      case NEW_CONTROL_PAGE:
        new_control_page();
      break;
//////////////////////////////////// motor pages
      case SERVO_MOTOR_PAGE:
        servo_motor_page();
      break;
  
      case DC_MOTOR_PAGE:
        dc_motor_page();
      break; 
  
      case STEPPER_MOTOR_PAGE:
        stepper_motor_page();
      break;
//////////////////////////////////// ... pages
    }
    tft_irq.tft_task_state  = READ_BUTTON;
    tft_irq.button_index    = NOT_SELECTED;
    tft_irq.page_update     = true; 
  }
}

My question now is how to set another interrupt based on a timer ?

I appologize. I thought your question was concurrent stepper movement.

1 Like

There's ISR(TIMER0_COMPA_vect){} at ~1kHz for free:

Then you could use it directly, or to set flags for a simple task or hierarchy of periodic processes in non-interrupt space:

1 Like

another approach would be to use a different stepper-motor-library that is based on a timer-interrupt.

The MobaTools-library works this way:
You do a single function-call to
myStepper-move(numberOfSteps);

and creating all the step-pulses is done in the background.
If this really is a solution depends on your microcontroller and how many hardware-timers you were using for other things.

The MobaTools offer support for servos too.

@MicroBahner

How about writing a demo-code that does demonstrate the use of two stepper-motors and servos at the same time?

best regards Stefan

1 Like

I would do it the other way round. That tft code is long, so it doesn't really want to be in an interrupt handler. Put that tft code in loop and use the interrupt to run the motor.

1 Like

That is certainly a good idea. I am just starting to work on MobaTools version 2.5. A lot of problems and wishes have accumulated. I will put your suggestion on my to-do list.

2 Likes

As I'm using STM32 black pill which has a lot of timers.

I searched in its source code in this path:

D:\Program_Files\Arduino\hardware\Arduino_Core_STM32-main\libraries\SrcWrapper\src\HardwareTimer.cpp

, and found these functions:

void setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority);
    //Add interrupt to period update
    void attachInterrupt(callback_function_t callback); // Attach interrupt callback which will be called upon update event (timer rollover)

I liked these functions and I want to do some tests and check how to use them in a sketch code.

That's interesting, I'm going to check this library.

I found this example provided by STM32 team and it's wonderful.

https://github.com/stm32duino/STM32Examples/blob/main/examples/Peripherals/HardwareTimer/Timebase_callback/Timebase_callback.ino

If you use the MobaTools you can do function-calls just with the new absolute coordinate of steps and all the rest to drive there and to update actual position is done by the library
same thing for relative moving: The library keeps track of the position
you can retrieve actual position, actual speed
or if you like you can set a new zero-point whenever you want
The MobaTools have user-configurable acceleration/decceleration
you can make a function-call to a stop-function that includes decceleration and another-one for immidiately stop (=emergency stop)

If you want to write all the stepper-motor-code almost from scratch instead of using a comfortable library like the MobaTools,
Here is a demo-code that creates the step-pulses on the same basic principle by using a timer-interrupt

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);

// this demo-code belongs to the public domain
// this code demonstrates how to blink the  onboard-LED
// of an Arduino-Uno (connected to IO-pin 13)
// in a nonblocking way using timing based on function millis()
// and creating step-pulses for a stepper-motor "in the backround"
// through setting up a timer-interrupt through configuring timer2
// stepper-motors need a tight equalised timing except for acceleration 
// decceleration where the timing is still well defined
// 
// A T T E N T I O N !    R E M A R K
// some other libraries that make use of timer2 may conflict with this

// you may start with a frequency of 200 Hz to make sure you don't stall
// the stepper-motor with a too high starting frequency
// and then work up from there to find the stepper-frequency that still works
// reliable in start/stop-mode without acceleration/decceleration
const byte stepperPulsePin = 3;
const byte stepperDirPin   = 2;
unsigned long stepFrequency = 7000;
// on fullstep-mode on a 1.8° per Step motor 800 steps means 800 / 200 = 4 full turns
// on halfstep-mode on a 1.8° per Step motor 800 steps means 800 / 200 = 2 full turns
unsigned long numberOfSteps = 10000;

//nbt nonblockingtimer
boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}
unsigned long BlinkTimer = 0;
const int     onBoardLED = 13;

unsigned long DebugTimer = 0;

volatile long  HighCounter = 0;
volatile long  StepCounter = 0;
volatile boolean CreateStepSignal = false;

void setupTimerInterrupt(unsigned long ISR_call_frequency) {
  const byte Prescaler___8 = (1 << CS21);
  const byte Prescaler__32 = (1 << CS21) + (1 << CS20);
  const byte Prescaler__64 = (1 << CS22);
  const byte Prescaler_128 = (1 << CS22) + (1 << CS20);
  const byte Prescaler_256 = (1 << CS22) + (1 << CS21);
  const byte Prescaler1024 = (1 << CS22) + (1 << CS21) + (1 << CS20);

  const unsigned long CPU_Clock = 16000000;
  const byte toggleFactor = 2;

  unsigned long OCR2A_value;

  cli();//stop interrupts

  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0

  TCCR2A |= (1 << WGM21); // turn on CTC mode
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt

  // the prescaler must be setup to a value that the calculation
  // of the value for OCR2A is below 256
  TCCR2B = Prescaler___8;
  OCR2A_value = (CPU_Clock / ( 8 * ISR_call_frequency * toggleFactor) )  - 1;
  dbg("setup: timer done",OCR2A_value);

  if (OCR2A_value > 256) {  // if value too big
    TCCR2B = Prescaler__32; // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 32 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 32",OCR2A_value);
  }

  if (OCR2A_value > 256) { // if value too big
    TCCR2B = Prescaler__64;// set higher prescaler
    OCR2A_value = (CPU_Clock / ( 64 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 64",OCR2A_value);
  }

  if (OCR2A_value > 256) { // if value too big
    TCCR2B = Prescaler_128;// set higher prescaler
    OCR2A_value = (CPU_Clock / ( 128 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 128",OCR2A_value);
  }

  if (OCR2A_value > 256) {  // if value too big
    TCCR2B = Prescaler_256; // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 256 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 256",OCR2A_value);
  }

  if (OCR2A_value > 256) {   // if value too big
    TCCR2B = Prescaler1024;  // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 1024 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 1024",OCR2A_value);
  }

  OCR2A = OCR2A_value; // finally set the value of OCR2A

  sei();//allow interrupts
  dbg("setup: timer done",OCR2A_value);
  
}


void setup() {
  //set pins as outputs
  pinMode(stepperPulsePin, OUTPUT);
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  delay(500);
  pinMode(onBoardLED, OUTPUT);
  pinMode(stepperDirPin, OUTPUT);
  digitalWrite(stepperPulsePin, LOW);
  digitalWrite(stepperDirPin, LOW);
  digitalWrite(onBoardLED, LOW);

  setupTimerInterrupt(stepFrequency);
}//end setup

ISR(TIMER2_COMPA_vect) {

  if (CreateStepSignal) {
    if (StepCounter > 0) {
      if (HighCounter < 2) {
        digitalWrite(stepperPulsePin, HIGH);
      }
      else {
        HighCounter = 0;
        digitalWrite(stepperPulsePin, LOW);
        StepCounter--;
      }
      HighCounter++;
    }
    else // StepCounter was counted down to 0
    {
      CreateStepSignal = false;
    }
  }
}


void loop() {
  //Blink onboard-LED (IO-püin 13) at 1 Hz
  if ( TimePeriodIsOver(DebugTimer, 500) ) {
    digitalWrite (onBoardLED, !digitalRead(onBoardLED) ); // Blink OnBoard-LED
    Serial.println( F("Steps left to go ") );
    Serial.println(StepCounter);
  }

  // start new step-Pulse-train every x milliseconds
  if ( TimePeriodIsOver(BlinkTimer, 4000) ) {
    Serial.println("testing set and forget step-output");
    if (StepCounter <= 0) {
      StepCounter = numberOfSteps;
      digitalWrite(stepperDirPin,!digitalRead(stepperDirPin) );
      CreateStepSignal = true;
    }
  }
}

best regards Stefan

1 Like

I like both worlds :slight_smile: I like to use libraries most the time of course, and I also like to write a library whenever I have to.

Here is a demo-code that creates the step-pulses on the same basic principle by using a timer-interrupt

Thank you so much, your answer is rich and on point :+1:

Hi,

I eventually used this method using STM32 blackpill:

  1. Initialization of their wonderful HardwareTimer library:
stepper_4_wires_object_t stepper1_obj = {
PB12, PB13, PB14, PB15, 0, 0, STEPPER_CW, FREE_RUN, STEPPER_HALF_STEP, 0, 0, 0};

TIM_TypeDef *stm32_timer1 = TIM1;
HardwareTimer *stepperTmr = new HardwareTimer(stm32_timer1);
  1. Used it here:
      case STEPPER_MOTOR_RUN:
        stepper_4_wires_init(stepper1_obj);
        stepperTmr->setOverflow(1000, MICROSEC_FORMAT); // 1ms --> stepper speed
        stepperTmr->attachInterrupt(std::bind(stepper_4_wires_run, stepper1_obj));
        if(tft_irq.mcu_task_action){
          stepperTmr->resume();
        }
        else{
          stepperTmr->pause();
        }
      break;

I have to add more control settings but it works for now.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.