Page 1 of 1

repetitive timerAlarmWrite problem?

Posted: Fri Dec 18, 2020 11:04 am
by so99999
Hello everyone,

I am trying to implement a lead screw for a small lathe using an encoder and a stepper motor, both controlled by a esp32 board.
The encoder has 1000ppr. My idea was to make the speed of the screw to match the speed of the encoder.
The stepper motor is rotated using a hardware timer. I put the timer code to work in core0 and all encoder code in core1.

An isr is attached to the encoder pin and after a number of pulses the encoder speed is calculated and the stepper motor speed is adjusted accordingly, changing the frequency of the pulses, using "timerAlarmWrite" function.

The code works, but not well. If the speed of the encoder is high and the timer frequency updates are too many or too fast, the timer stops and remains blocked until the esp32 board is reset. From what I can figure the calling of the "timerAlarmWrite" function too many times, or too fast, blocks the timer.
The code works well if the frequency update is performed only twice in a full encoder rotation (or after 500 of encoder pulses). But this could be insufficient in a threading operation. I would like to be able to update the lead screw speed at least ten times in one encoder rotation.
My question is: is it a software issue with the repetitive call of timerAlarmWrite function?
Any other ideas about how this problem could be solved will be greatly appreciated.

Code: Select all

//for TMC 2130 driver spi comm.
#include <TMCStepper.h>
#define CS_PIN           5 // Chip select
#define SW_MOSI          23 // Software Master Out Slave In (MOSI)
#define SW_MISO          19 // Software Master In Slave Out (MISO)
#define SW_SCK           18 // Software Slave Clock (SCK)
#define R_SENSE 0.11f

//the esp32 pins used to control the TMC2130 driver
#define pinPulse BIT25
#define pinDir 26
#define en          27  // LOW: Driver enabled. 
#define EN_PIN    BIT27
TMC2130Stepper driver(CS_PIN, R_SENSE);

//timer used to generate pulses for the stepper driver
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

//esp32 encoder pins
#define pinEncoder_A 32
#define pinEncoder_B 14
#define pinEncoder_Z 33

//encoder variables
volatile unsigned long count_encoder_rotations = 0;
volatile unsigned long count_A = 0;
volatile int count_update_freq = 0;
volatile int B_state = LOW;
volatile unsigned long previous_encoder_Time = 0;
volatile int encoder_direction = 0;
volatile double encoder_speed = 0;

//timer/stepper parameters
double ticks = 0.0;
volatile int state;
volatile unsigned long counted_pulses = 0;
boolean timer_started = false;

double pulsesToDo = 2e32; //forever 
double GearRatio = 2.0;
double microStepping = 16.0;
double pulsePerRev = 400.0;

unsigned int prv = 0; //display time interval


///////////////////////////////////////////////////////////
//timer isr - generates pulses for the stepper driver and count them
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  if (state == LOW)
  {
    counted_pulses++;
    state = HIGH;
    REG_WRITE(GPIO_OUT_W1TS_REG, pinPulse);//GPIO25 HIGH (set)
    if (counted_pulses >= pulsesToDo) ////in program to simply switch to free movemnt set pulsesToDo=maxpulses - avoid another if here
    {
      timerAlarmDisable(timer);
      timerDetachInterrupt(timer);
      counted_pulses = 0;
      timer_started = false;
    }
  }
  else
  {
    state = LOW;
    REG_WRITE(GPIO_OUT_W1TC_REG, pinPulse);//GPIO25 LOW (clear)
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}
///end timer isr
/////////////////////////////////////////


//////////////////////////////////////////
//ENCODER SECTION//
//the encoder operation will be performed in core 1 and the timer/stepper driver operation in core0
TaskHandle_t TaskEncoder;

void IRAM_ATTR DO_encoder_A() {
  count_update_freq++;

  if (B_state == LOW)
  {
    count_A++;
    encoder_direction = 1;
  }
  else
  {
    count_A--;
    encoder_direction = 0;
  }

  //!!!! - the section which doesn't work well !!!
  //this is the section which presumably keeps the speed of the stepper motor the same with
  //the speed of the encoder
  if (timer_started == true)
  {
    if (count_update_freq >= 1) //the frequency of pulses sent to stepper driver is updated 10 times in one encoder revolution
    {
      unsigned long encoder_now = micros();
      encoder_speed = (((double)count_update_freq) / (((double)(encoder_now - previous_encoder_Time)) / 1000000.0)) / 1000.0; //rot/sec
      count_update_freq = 0;
      previous_encoder_Time = encoder_now;
      encoder_speed = encoder_speed * microStepping * pulsePerRev; //pulse frequency
      ticks = 20000000.0 / encoder_speed;
      digitalWrite(pinDir, encoder_direction);
      //timerAlarmDisable(timer);
      //timer = timerBegin(0, 1, true);  //prescaler 1
      //timerAttachInterrupt(timer, &onTimer, true);
      portENTER_CRITICAL(&timerMux);
      timerAlarmWrite(timer, (unsigned long)ticks, true);
      //timerAlarmEnable(timer);
      portEXIT_CRITICAL(&timerMux);
    }
  }
}

void IRAM_ATTR DO_encoder_B_CHANGE() {
  B_state = digitalRead(pinEncoder_B); //B_state=!B_state;
}

void IRAM_ATTR DO_encoder_Z() {
  count_encoder_rotations++;
}

//Taskencodercode: all encoder operations will work in the core no 1; all
void TaskEncoderCode( void * pvParameters ) {
  pinMode(pinEncoder_A, INPUT_PULLUP);
  pinMode(pinEncoder_B, INPUT_PULLUP);
  pinMode(pinEncoder_Z, INPUT_PULLUP);
  B_state = digitalRead(pinEncoder_B);
  attachInterrupt(pinEncoder_Z, DO_encoder_Z, RISING);
  attachInterrupt(pinEncoder_A, DO_encoder_A, RISING);
  attachInterrupt(pinEncoder_B, DO_encoder_B_CHANGE, CHANGE);
  for (;;) {
    vTaskDelay(1);
  }
}

//END ENCODER SECTION
/////////////////////////////////////////////


void setup() {
  xTaskCreatePinnedToCore(
    TaskEncoderCode,   /* Task function. */
    "TaskEncoder",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &TaskEncoder,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);

  Serial.begin(115200);

  REG_WRITE(GPIO_ENABLE_REG, pinPulse); //pin 25 as output - pulse pin
  pinMode(pinDir, OUTPUT);
  pinMode(en, OUTPUT);
  REG_WRITE(GPIO_OUT_W1TC_REG, EN_PIN);//STEP_PINX LOW  driver enabled

  SPI.begin();
  driver.begin();
  driver.toff(5);                 // Enables driver in software
  driver.rms_current(1000);        // Set motor RMS current
  driver.microsteps(microStepping);          // Set microsteps to 1/16th
  driver.en_pwm_mode(true);       // Toggle stealthChop on TMC2130/2160/5130/5160
  driver.pwm_autoscale(true);     // Needed for stealthChop

  //start timer and the stepper motor with a very low speed; only for testing!!!
  ticks = 20000000.0;
  digitalWrite(pinDir, HIGH);
  timer = timerBegin(0, 1, true);  //prescaler 1
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, (unsigned long)ticks, true);
  timerAlarmEnable(timer);
  timer_started = true;
}


void loop() {
  if ((millis() - prv) > 1000) {
    prv = millis();
    Serial.print("Z=");
    Serial.println(count_encoder_rotations);
    Serial.print("A=");
    Serial.println(count_A);
    Serial.print("enc. speed");
    Serial.println(encoder_speed);
    Serial.println("_______________________");
  }
}

Re: repetitive timerAlarmWrite problem?

Posted: Fri Dec 18, 2020 11:10 am
by so99999
This short video shows the problem:
https://www.youtube.com/watch?v=concZ-D ... e=youtu.be

Re: repetitive timerAlarmWrite problem?

Posted: Fri Dec 18, 2020 12:43 pm
by idahowalker
take the code out of the isr
volatile bool ItrippedOut=false;
isr my thing()
{ ItrippedOut= true;
better yet use a freeRTOS event trigger to trigger the task to be ran.}

loop()
{
If Itrippedout then run the code that was in the isr and set iTrippedOut = false; ready for the next isr}

So now loop runs the actual isr code and not the very busy isr.

better yet use freeRTOS, tasks, and event triggers to trigger the task to run the code.

But seriously read about freeRTOS and why to NOT put code in the loop() when running freeRTOS on a ESP32.

Re: repetitive timerAlarmWrite problem?

Posted: Fri Dec 18, 2020 2:20 pm
by so99999
Thank you. I will investigate the rtos timer. I am not sure but I think its maximum resolution is 1microsecond. This could not be enough for higher micro-stepping settings and for some thread pitches.

I tried to put that code into the loop, but the problem still persists. Actually, I don't think the problem is that the encoder isr is too busy. Even with the frequency update code in the loop, the timerAlarmWrite function called too many times or too fast successively, seems to shut down the timer. I added timerAlarmEnable(timer); after the timerAlarmWrite function and this prevents the timer to be shut down definitively, but it restarts only after a few seconds. However, this is not enough fast.

the new code:

Code: Select all

//for TMC 2130 driver spi comm.
#include <TMCStepper.h>
#define CS_PIN           5 // Chip select
#define SW_MOSI          23 // Software Master Out Slave In (MOSI)
#define SW_MISO          19 // Software Master In Slave Out (MISO)
#define SW_SCK           18 // Software Slave Clock (SCK)
#define R_SENSE 0.11f

//the esp32 pins used to control the TMC2130 driver
#define pinPulse BIT25
#define pinDir 26
#define en          27  // LOW: Driver enabled. 
#define EN_PIN    BIT27
TMC2130Stepper driver(CS_PIN, R_SENSE);

//timer used to generate pulses for the stepper driver
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

//esp32 encoder pins
#define pinEncoder_A 32
#define pinEncoder_B 14
#define pinEncoder_Z 33

//encoder variables
volatile unsigned long count_encoder_rotations = 0;
volatile unsigned long count_A = 0;
volatile int count_update_freq = 0;
volatile int B_state = LOW;
volatile unsigned long previous_encoder_Time = 0;
volatile int encoder_direction = 0;
volatile double encoder_speed = 0;
volatile bool ItrippedOut=false;

//timer/stepper parameters
double ticks = 0.0;
volatile int state;
volatile unsigned long counted_pulses = 0;
boolean timer_started = false;

double pulsesToDo = 2e32; //forever beacause longinteger switchwes to 0 after 2e32-1
double GearRatio = 2.0;
double microStepping = 16.0;
double pulsePerRev = 400.0;

//display timne interval
unsigned int prv = 0;

///////////////////////////////////////////////////////////
//timer isr - generates pulses for the stepper driver and count them
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  if (state == LOW)
  {
    counted_pulses++;
    state = HIGH;
    REG_WRITE(GPIO_OUT_W1TS_REG, pinPulse);//GPIO25 HIGH (set)
    if (counted_pulses >= pulsesToDo) ////in program to simply switch to free movemnt set pulsesToDo=maxpulses - avoid another if here
    {
      timerAlarmDisable(timer);
      timerDetachInterrupt(timer);
      counted_pulses = 0;
      timer_started = false;
    }
  }
  else
  {
    state = LOW;
    REG_WRITE(GPIO_OUT_W1TC_REG, pinPulse);//GPIO25 LOW (clear)
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}
///end timer isr
/////////////////////////////////////////


//////////////////////////////////////////
//ENCODER SECTION//
//the encoder operation will be performed in core 1 and the timer/stepper driver operation in core0
TaskHandle_t TaskEncoder;

void IRAM_ATTR DO_encoder_A() {
  ItrippedOut=true;
  count_update_freq++;

  if (B_state == LOW)
  {
    count_A++;
    encoder_direction = 1;
  }
  else
  {
    count_A--;
    encoder_direction = 0;
  }
}


void IRAM_ATTR DO_encoder_B_CHANGE() {
  B_state = digitalRead(pinEncoder_B); //B_state=!B_state;
}


void IRAM_ATTR DO_encoder_Z() {
  count_encoder_rotations++;
}


//Taskencodercode: all encoder operations will work in the core no 1; all
void TaskEncoderCode( void * pvParameters ) {
  pinMode(pinEncoder_A, INPUT_PULLUP);
  pinMode(pinEncoder_B, INPUT_PULLUP);
  pinMode(pinEncoder_Z, INPUT_PULLUP);
  B_state = digitalRead(pinEncoder_B);
  attachInterrupt(pinEncoder_Z, DO_encoder_Z, RISING);
  attachInterrupt(pinEncoder_A, DO_encoder_A, RISING);
  attachInterrupt(pinEncoder_B, DO_encoder_B_CHANGE, CHANGE);
  for (;;) 
  {

    //////////////////////////////////
    //encoder isr code run outside the isr
    if(ItrippedOut)
    {
      //!!!!!!!!!!!!!!!!!!!!!!!!!! - the section which doesn't work well !!!!!!!!!!!!!!!!!!!!!!!!
      //this is the section which presumably keeps the speed of the stepper motor the same with
      //the speed of the encoder
      if (timer_started == true)
      {
        if (count_update_freq >= 100) //the frequency of pulses sent to stepper driver is updated 10 times in one encoder revolution
        {
          unsigned long encoder_now = micros();
          encoder_speed = (((double)count_update_freq) / (((double)(encoder_now - previous_encoder_Time)) / 1000000.0)) / 1000.0; //rot/sec
          count_update_freq = 0;
          previous_encoder_Time = encoder_now;
          encoder_speed = encoder_speed * microStepping * pulsePerRev; //pulse frequency
          ticks = 20000000.0 / encoder_speed;
          digitalWrite(pinDir, encoder_direction);
          //timerAlarmDisable(timer);
          //timer = timerBegin(0, 1, true);  //prescaler 1
          //timerAttachInterrupt(timer, &onTimer, true);
          //portENTER_CRITICAL(&timerMux);
          timerAlarmWrite(timer, (unsigned long)ticks, true);
          timerAlarmEnable(timer);
          //portEXIT_CRITICAL(&timerMux);
        }
      }
      ItrippedOut=false;
    } //end if(ItrippedOut)
    ///////////////////////////////////////////
    
    vTaskDelay(1);
  }//end loop in core 1
}

//END ENCODER SECTION
/////////////////////////////////////////////

void setup() {
  xTaskCreatePinnedToCore(
    TaskEncoderCode,   /* Task function. */
    "TaskEncoder",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &TaskEncoder,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);

  Serial.begin(115200);

  REG_WRITE(GPIO_ENABLE_REG, pinPulse); //pin 25 as output - pulse pin
  pinMode(pinDir, OUTPUT);
  pinMode(en, OUTPUT);
  REG_WRITE(GPIO_OUT_W1TC_REG, EN_PIN);//STEP_PINX LOW  driver enabled

  SPI.begin();
  driver.begin();
  driver.toff(5);                 // Enables driver in software
  driver.rms_current(1000);        // Set motor RMS current
  driver.microsteps(microStepping);          // Set microsteps to 1/16th
  driver.en_pwm_mode(true);       // Toggle stealthChop on TMC2130/2160/5130/5160
  driver.pwm_autoscale(true);     // Needed for stealthChop


  //start timer and the stepper motor with a very low speed; only for testing!!!
  ticks = 20000000.0;
  digitalWrite(pinDir, HIGH);
  timer = timerBegin(0, 1, true);  //prescaler 1
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, (unsigned long)ticks, true);
  timerAlarmEnable(timer);
  timer_started = true;
}

void loop() {

  if ((millis() - prv) > 1000) {
    prv = millis();
    Serial.print("Z=");
    Serial.println(count_encoder_rotations);
    Serial.print("A=");
    Serial.println(count_A);
    Serial.print("enc. speed");
    Serial.println(encoder_speed);
    Serial.println("_______________________");
  }
}

Re: repetitive timerAlarmWrite problem?

Posted: Fri Dec 18, 2020 3:44 pm
by so99999
I read a bit about freertos timers but it seems that they have ms resolution and not very high priority. I don't think they are feasible for such a project.