3

I am having trouble figuring out how to disable then re-enable (upon a triggering event) the hw (esp32-hal-timer) timer from the esp-arduino library, here for a stepper motor controller application with my esp32 development board. It will count down and trigger the ISR as desired as many times as I want, but when I disable it (so that the ISR is not unnecessarily called), it will not start when I try to start it again. The weird thing is that it is started the same way it was the first time, so I'm not sure if it is an issue with my code or the way that that particular library handles garbage collection. This is also my first time trying to work with interrupts at all. My code is below.

To avoid having to wade through too much, the general process is to initialize the timer (called motorTimer) in the setup method, then connect to wifi, within the callback method for mqtt any message with the payload of an integer will trigger the 'moveTo' method in the motor.h class, and then the update method in that same class will be triggered when the ISR timer is triggered. The timer will then change its time with each iteration, for acceleration compensation. This works great, until it comes time to kill the timer and then restart it later - then the ISR is not called at all, like the timer wasn't stopped properly. And that is where my issue lies.

#include <SPI.h>
#include <Adafruit_MAX31855.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include "VCDevices.h"

// Replace the next variables with your SSID/Password combination
// //WiFi info:
const char* ssid = "SSID";
const char* password = "PASSWORD_HERE";

// Add your MQTT Broker IP address, example:
const char* mqtt_server = "home.IOT.lan";

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char topArr[50];
char msgArr[100];
// get size of array first, to feed to for loop
int numDevices = sizeof(devices)/sizeof(device);
int value = 0;
unsigned long heartbeat_previousMillis = 0;
unsigned long motorCheck_previousMillis = 0;
const long timeOut = 60000;
const long motorCheckTime = 600;

hw_timer_t * motorTimer = NULL;
bool state = 0;
int count = 0;

int d = 0;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
bool finished = false;

enum LogLevel
{
    Debug,  //Sends message only to the serial port
    Error,  //Sends message over MQTT to 'Errors' topic, and to serial port with "Error: " pre-appended
    Message //Sends message over serial and MQTT to 'StatusMessage' topic
};

///****** TIMER LOGIC HERE ******
//TODO: timer needs to just figure out what time the next pulse needs to be fired at - needs to be calculated on the fly

//This is calculated inside the Motor class.
 
void IRAM_ATTR motorInterrupt(void) 
{
  Serial.println("B");
  portENTER_CRITICAL(&timerMux);
  noInterrupts();
  //check if the motor is in motion still
  if (!linMotor.getMotorStatus())
  {
    d = linMotor.Update();
    timerAlarmWrite(motorTimer, d, true);
    timerAlarmEnable(motorTimer);
  }
  else 
  {
    // timerAlarmWrite(motorTimer, 1, true);
    timerAlarmDisable(motorTimer);
    finished = true;
  }
  //kill the timer and interrupt if not
  interrupts();
  portEXIT_CRITICAL(&timerMux);
}

//****** END TIMER HERE *****

void log(LogLevel level, String message)
{
    switch(level)
    {
        case LogLevel::Debug:
            Serial.println(message);
            break;
        case LogLevel::Error:
            print(ErrorTopic, message);
            break;
        case LogLevel::Message:
            Serial.print("Message: ");
            Serial.println(message);
            print(StatusTopic, message);
            break;
    }
}


void print(char topic[], String message)
{
    Serial.print(topic);
    Serial.print(" : ");
    Serial.println(message);
    //topic.toCharArray(topArr, sizeof(topic)+2);
    message.toCharArray(msgArr, sizeof(msgArr));
    client.publish(topic, msgArr, message.length());
}
// WiFi methods are located below:

void setup_wifi() 
{
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* message, unsigned int length)
{
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;
  String response;

  //check if heartbeat signal was sent
  if (String(topic) == HeartbeatTopic)
  {
    heartbeat_previousMillis = millis();
  }
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }

  Serial.println();
  // Iterate through each device and update states accordingly
  for (int i = 0; i < numDevices; i = i + 1) 
  {
    // the char arrays need to be cast as strings to compare to each other
    if (String(topic) == String(devices[i].controlTopic))
    {
      if (messageTemp == "on")
      {
        digitalWrite(devices[i].pinNumber, devices[i].shouldInvert?LOW:HIGH);
        response = "on";
      }
      else
      {
        digitalWrite(devices[i].pinNumber, devices[i].shouldInvert?HIGH:LOW);
        response = "off";
      }
      log(LogLevel::Message, response);
      print(devices[i].stateTopic, response);
      break;
    }
    if (String(topic) == String(motorControlTopic) || String(topic) == String(motorSetTopic))
    {
      if (String(topic) == String(motorSetTopic))
      {
        //sets speed for now, other params later
        linMotor.SetSpeed(messageTemp.toInt());
        response = "Parameters set";
      }
      if (String(topic) == String(motorControlTopic)) 
      {
        if (messageTemp.toInt() > 0)
        {
          //check if motor is available to run
          if (linMotor.getMotorStatus())
          {
            linMotor.MoveTo(messageTemp.toInt());
            //TODO: Setup timer stuff here
            // motorTimer = NULL;
            // motorTimer = timerBegin(1, 80, true);
            
            // timerAttachInterrupt(motorTimer, &motorInterrupt, true);
            timerSetAutoReload(motorTimer, true);
            timerAlarmWrite(motorTimer, 1, true);
            timerAlarmEnable(motorTimer);
            
            response = "moving to " + String(messageTemp.toInt()) + " mm position";
          }
          else
          {
            response = "motor is busy - wait for movement to end!";
          }
        }
        else if (messageTemp == "home")
        {
          linMotor.SetZero();
          response = "setting motor to zero";
        }
        else if (messageTemp == "stop")
        {
          linMotor.EStop();    
          if (motorTimer != NULL)
          {
            timerDetachInterrupt(motorTimer); 
          }
          response = "motor stopped";
          //TODO: detach timer here!
        }
      }
      
      //TODO: put in GUI call for position updates
      //print(motorStateTopic, "position is: " + String(linMotor.getPosition()));
      log(LogLevel::Message, response);
      print(motorStateTopic, response);
      break;
    }
  }
}  

void setup()
{ 
  //Testing code here: 
  motorTimer = timerBegin(1, 80, true);
  timerAttachInterrupt(motorTimer, &motorInterrupt, true);
  //end testing code
  //Start serial connection
    Serial.begin(115200);
    for (int i = 0; i < numDevices; i = i + 1)
  { 
    pinMode(devices[i].pinNumber, OUTPUT);
    digitalWrite(devices[i].pinNumber, devices[i].shouldInvert?HIGH:LOW);
  }
  pinMode(pulsePin, OUTPUT);
  pinMode(directionPin, OUTPUT);
    delay(500);
  linMotor.SetSpeed(250);
  linMotor.SetAcceleration(20);

    log(LogLevel::Debug, "Connecting to mqtt");
    Serial.println(mqtt_server);
    setup_wifi();
    client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
    
    reconnect();
    log(LogLevel::Message, "Connected");

    log(LogLevel::Message, "System Started!");

  // Initialize heartbeat timer
  heartbeat_previousMillis = millis();
  motorCheck_previousMillis = millis();
}

void reconnect() 
{
  // Loop until we're reconnected
  while (!client.connected()) {
  Serial.print("Attempting MQTT connection...");
    Serial.println(mqtt_server);
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");

      // Subscribe to all relevant messages
      for (int i = 0; i < numDevices; i = i + 1)
      {
        client.subscribe(devices[i].controlTopic);
      } 
      // subscribe to the heartbeat topic as well
      client.subscribe(HeartbeatTopic);
      client.subscribe(motorControlTopic);
      client.subscribe(motorSetTopic);
      } 
      else 
      {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void loop()
{
    if (!client.connected()) reconnect();
  client.loop();  
  unsigned long currentMillis = millis();
  if (currentMillis - motorCheck_previousMillis >= motorCheckTime)
  {
      // print(motorStateTopic, "position is: " + String(linMotor.GetPosition()));
      portENTER_CRITICAL(&timerMux);
      Serial.println(String(linMotor.GetPosition()));
      portEXIT_CRITICAL(&timerMux);
      motorCheck_previousMillis = currentMillis;      
      if (finished) 
      {
        // timerAlarmWrite(motorTimer, 0, false);
        // Serial.println("wrote 0 alarm");
        // timerAlarmDisable(motorTimer);       // stop alarm
        // Serial.println("disabled alarm");
        // timerEnd(motorTimer);
        // Serial.println("timerEnd");
        // motorTimer = NULL;
        // Serial.println("NULLED timer");
        // motorTimer = timerBegin(1, 80, true);
        // Serial.println("timer stated again!");

      //   timerRestart(motorTimer);
      //   timerDetachInterrupt(motorTimer);    // detach interrupt
      //   timerEnd(motorTimer);    
        finished = false;
      }
  }
}

I don't think the motor.h methods need to be shown, but can post if need be. Thanks in advance for any help!

Edit1: just realized the mutex was closed up before the timer deletion was occurring inside the loop function, but it still didn't fix it. Also the stuff in comments in that section is all the stuff I have tried thus far without success.

Edit2: rewording things for clarity.

1
  • So I sorted out the issue myself, and will post the code below for anyone else curious. You have to set the timer reload to false on its last run, and use the timerRestart method somewhere else (you may be able to use it within the ISR as well, but I opted to do it this way instead). Code will come later, after I eliminate some fluff :) Commented Nov 4, 2020 at 2:13

2 Answers 2

1

The word 'restart' had me thinking that it would just immediately start the timer again, but it turns out that is not the case. If the reload was set false previously, the timer has to be set again before it will actually execute - which works perfectly for my use case. Below is my new code (figured I would include the wifi and mqtt stuff to help anyone else as well):

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include "VCDevices.h"

hw_timer_t * motorTimer = NULL;
Motor linMotor2 = Motor(pulsePin, directionPin);
int d = 0;
bool nextRun = false;

const char* ssid = "SSID";
const char* password = "PASSWORD_HERE";
const char* mqtt_server = "MQTT_SERVER_HERE";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char topArr[50];
char msgArr[100];

portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

//Timer ISR
void IRAM_ATTR motorInterrupt(void) 
{
  portENTER_CRITICAL(&timerMux);
  noInterrupts();
  //check if the motor is in motion still
  if (!linMotor2.getMotorStatus())
  {
    d = linMotor2.Update();
    //give timer different delay, dependent on its current speed
    timerAlarmWrite(motorTimer, d, true);
    timerAlarmEnable(motorTimer);
  }
  //kill the timer and interrupt if not
  else
  {
    nextRun = true;
    //set the 'reload' boolean to false, to get it to only trigger one more time
    timerAlarmWrite(motorTimer, 10, false);
    // Serial.println("POSITION REACHED!");
  }

  interrupts();
  portEXIT_CRITICAL(&timerMux); 
}

void reconnect() 
{
  // Loop until we're reconnected
  while (!client.connected()) 
  {
    Serial.print("Attempting MQTT connection...");
    Serial.println(mqtt_server);
    // Attempt to connect
    if (client.connect("ESP8266Client"))
    {
      client.subscribe(motorControlTopic);
      Serial.println("connected");
    } 
    else 
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}


void setup_wifi() 
{
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* message, unsigned int length)
{
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;
  String response;
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }

  Serial.println();
  if (String(topic) == String(motorControlTopic))
  {
    if (messageTemp.toInt() > 0)
    {
      //check if motor is available to run
      if (linMotor2.getMotorStatus())
      {
        
        linMotor2.MoveTo(messageTemp.toInt());

        //set the motor timer and enable it
        timerAlarmWrite(motorTimer, 1, true);
        timerAlarmEnable(motorTimer);
        
        response = "moving to " + String(messageTemp.toInt()) + " mm position";
      }
      else
      {
        response = "motor is busy - wait for movement to end!";
      }
      Serial.println(response);
    }
  } 
}

void setup()
{ 
  //Start serial connection
  Serial.begin(115200);

  //Setup wifi and mqtt stuff
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  //Fix up motor settings
  pinMode(pulsePin, OUTPUT);
  linMotor2.SetSpeed(200);
  linMotor2.SetAcceleration(10);

  //Initialize timer here for later use!
  motorTimer = timerBegin(1, 80, true);
  timerAttachInterrupt(motorTimer, &motorInterrupt, true);
  Serial.println("TIMER SET!!");
  digitalWrite(pulsePin, LOW);

}

void loop()
{
  if (!client.connected()) reconnect();
  client.loop();  
  portENTER_CRITICAL(&timerMux);
  vTaskDelay(500);
  count = linMotor2.GetPosition();
  Serial.println("POSITION: " + String(count));
  if (nextRun)
  {
    noInterrupts();
    timerRestart(motorTimer);
    Serial.println("*********TIMER RESTARTED!******");
    nextRun = false;
    interrupts();
  }
  portEXIT_CRITICAL(&timerMux);
}
Sign up to request clarification or add additional context in comments.

Comments

1

I found this question to be very similar to an issue I am experiencing, also in a stepper application I need to set a pin HIGH fo the stepper to run and then, after 2ms, need to set the pin back to LOW. To do that I am firing a second timer form inside the ISR of the first timer, but no matter what I try/set, the second timer always fires 23us later. To illustrate that I made the below barebone example so one can see that the interval between to two ISR is always 22/23us no matter what. This routine/strategy is part of the very popular TeensyStep library (ESP32 Fork) and the very short pulse length is not really appreciated by the large DRIVERS. What am I doing wrong?

hw_timer_t *timerA = NULL;
hw_timer_t *timerB = NULL;

void IRAM_ATTR onTimerA()
{
  digitalWrite(13, 1);  
  Serial.print("HI ");
  Serial.println(micros());
  timerAlarmEnable(timerB);
}

void IRAM_ATTR onTimerB()
{
  digitalWrite(13, 0);  
  Serial.print("LO ");
  Serial.println(micros());    
}

void setup()
{
  Serial.begin(115200);
  while (!Serial);

  timerA = timerBegin(0, 80, true);
  timerAttachInterrupt(timerA, &onTimerA, true);
  timerAlarmWrite(timerA, 1000000, true);  
  
  timerB = timerBegin(1, 80, true);
  timerAttachInterrupt(timerB, &onTimerB, true);
  timerAlarmWrite(timerB, 200000, false);

  timerAlarmEnable(timerA);
}

void loop(){}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.