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.