如何按需删除和重启 esp32 arduino(步进电机控制器应用程序)的硬件定时器(用于中断)

How to delete and restart hw timer (for interrupts) on demand for esp32 arduino (stepper motor controller application)

我无法弄清楚如何禁用然后重新启用(在触发事件时)来自 esp-arduino 库的 hw (esp32-hal-timer) 定时器,here 用于步进电机我的 esp32 开发板的控制器应用程序。它会倒计时并根据需要多次触发 ISR,但是当我禁用它时(这样 ISR 就不会被不必要地调用),当我再次尝试启动它时它不会启动。奇怪的是它以与第一次相同的方式启动,所以我不确定这是我的代码问题还是特定库处理垃圾收集的方式。这也是我第一次尝试使用中断。我的代码如下。

为了避免费力太多,一般流程是在setup方法中初始化定时器(称为motorTimer),然后连接wifi,在mqtt的回调方法中对任何带有整数负载的消息将触发 motor.h class 中的 'moveTo' 方法,然后在触发 ISR 计时器时将触发同一个 class 中的更新方法。然后定时器将在每次迭代中更改其时间,以进行加速度补偿。这很好用,直到需要终止计时器然后稍后重新启动它 - 然后根本不会调用 ISR,就像计时器没有正确停止一样。这就是我的问题所在。

#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;
      }
  }
}

我认为 motor.h 方法不需要显示,但如果需要可以 post。在此先感谢您的帮助!

Edit1: 刚刚意识到在循环函数内部发生定时器删除之前互斥锁被关闭,但它仍然没有修复它。该部分评论中的内容也是我迄今为止尝试但未成功的所有内容。

Edit2:为清楚起见重新措辞。

'restart'这个词让我以为它会立即再次启动计时器,但事实证明并非如此。如果之前将重新加载设置为 false,则必须在计时器实际执行之前再次设置计时器 - 这非常适合我的用例。下面是我的新代码(我想我会包括 wifi 和 mqtt 的东西来帮助其他人):

#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);
}

我发现这个问题与我遇到的问题非常相似,同样在步进应用程序中,我需要将步进器的引脚设置为 运行,然后在 2 毫秒后,需要设置引脚回到低电平。为此,我在第一个定时器的 ISR 中触发了第二个定时器,但无论我 try/set 是什么,第二个定时器总是在 23us 之后触发。为了说明我制作了下面的准系统示例,因此可以看到两个 ISR 之间的间隔无论如何始终为 22/23us。这个 routine/strategy 是非常流行的 TeensyStep library (ESP32 Fork) 的一部分,而且非常短的脉冲长度并没有被大驱动程序真正欣赏。我做错了什么?

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(){}