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("_______________________");
}
}