Hello everyone. I am having trouble writing code for a machine with PET filament. I am using a NEMA17HS4401S stepper motor and a TMC2208 driver. An Arduino Nano is used to create the machine. I have separate code for the stepper motor, encoder and LCD1602 (I2C) display, and it works fine. There is also separate code for the PID controller, which also works fine, but as soon as I combine them into one, the stepper motor starts to jerk, but still rotates. If I try to turn off the display update while the stepper motor is running, the jerks become smaller, but are still audible. If I try to increase the PID update delay, the jerks become smaller but do not disappear. I don't know what to do, but I understand that the display and PID controller affect the operation in this case. I would appreciate your help.
The full code is below:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>
#include <PID_v1.h>
#include <thermistor.h>
// Pins for the stepper motor
#define dirPin 7
#define stepPin 8
#define enablePin 9
#define motorInterfaceType 1
// Pins for the encoder
#define ENCODER_CLK 2
#define ENCODER_DT 3
#define ENCODER_SW 4
// Pin for the thermistor
#define temperaturePin A0
LiquidCrystal_I2C lcd(0x27, 16, 2);
thermistor therm1(temperaturePin, 0);
// Motor settings
const int MICROSTEPS = 2;
const int STEPS_PER_REVOLUTION = 200 * MICROSTEPS;
float maxRPS = 10.0, minRPS = 0.1, targetRPS = 1.0, currentRPS = 1.0;
float rpsStep = 0.1, acceleration = 1.0;
const float GEAR_RATIO = 36.0;
const float WHEEL_CIRCUMFERENCE = 282.74;
// Encoder variables
volatile int encoderPos = 0;
int lastEncoderPos = 0;
bool btnPressed = false;
const int impulsesPerClick = 2;
AccelStepper stepper(motorInterfaceType, stepPin, dirPin);
// PID settings
double setpoint = 220, input, output;
double Kp = 80.0, Ki = 35.0, Kd = 80.0;
PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
// System state
byte inputMode = 0; // 0 - temperature, 1 - speed, 2 - direction
int motDir = 1; // 0 - Reverse, 1 - Off, 2 - Forward
void updateEncoder() {
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = millis();
if (interruptTime - lastInterruptTime > 5) {
if (digitalRead(ENCODER_DT) == digitalRead(ENCODER_CLK))
encoderPos++;
else
encoderPos--;
lastInterruptTime = interruptTime;
}
}
void setup() {
Serial.begin(9600);
// Initialize LCD
lcd.init();
lcd.backlight();
// PID setup
pid.SetMode(AUTOMATIC);
pid.SetOutputLimits(0, 255);
input = therm1.analog2temp();
// Pin setup
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
pinMode(ENCODER_SW, INPUT_PULLUP);
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, LOW); // Enable motor driver
// Motor setup
stepper.setMaxSpeed(maxRPS * STEPS_PER_REVOLUTION);
stepper.setAcceleration(acceleration * STEPS_PER_REVOLUTION);
stepper.setSpeed(currentRPS * STEPS_PER_REVOLUTION);
// Encoder interrupt
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), updateEncoder, CHANGE);
}
void loop() {
static unsigned long lastTempUpdate = 0;
static unsigned long lastDisplayUpdate = 0;
// Update temperature and PID (every 500 ms)
if (millis() - lastTempUpdate > 500) {
input = therm1.analog2temp();
pid.Compute();
analogWrite(5, output);
lastTempUpdate = millis();
}
// Handle controls
handleEncoder();
handleButton();
updateMotorSpeed();
stepper.runSpeed();
// Update display (every 300 ms) only if motor is not moving
if (millis() - lastDisplayUpdate > 300 && !isMotorMoving()) {
updateDisplay();
lastDisplayUpdate = millis();
}
}
// Check if motor is moving
bool isMotorMoving() {
return (motDir != 1 && currentRPS > 0.1); // If not off and speed is above minimum
}
void handleEncoder() {
int delta = encoderPos - lastEncoderPos;
if (abs(delta) >= impulsesPerClick) {
int steps = delta / impulsesPerClick;
switch (inputMode) {
case 0: // Temperature
setpoint += steps;
setpoint = constrain(setpoint, 190, 230);
break;
case 1: // Speed
targetRPS += steps * rpsStep;
targetRPS = constrain(targetRPS, minRPS, maxRPS);
break;
case 2: // Direction
motDir += steps;
motDir = constrain(motDir, 0, 2);
break;
}
lastEncoderPos = encoderPos;
}
}
void updateMotorSpeed() {
// Set direction
if (motDir == 0) digitalWrite(dirPin, LOW);
else if (motDir == 2) digitalWrite(dirPin, HIGH);
if (motDir != 1) {
// Smooth speed change
if (abs(currentRPS - targetRPS) > 0.01) {
currentRPS += (currentRPS < targetRPS) ? acceleration * 0.05 : -acceleration * 0.05;
currentRPS = constrain(currentRPS, minRPS, maxRPS);
}
stepper.setSpeed(currentRPS * STEPS_PER_REVOLUTION);
} else {
stepper.setSpeed(0);
}
}
void handleButton() {
static bool lastState = HIGH;
bool state = digitalRead(ENCODER_SW);
if (state != lastState && state == LOW) {
inputMode = (inputMode + 1) % 3;
delay(200); // Debounce
}
lastState = state;
}
void updateDisplay() {
lcd.clear();
// First line - temperature
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print((int)input);
lcd.print("C S:");
lcd.print((int)setpoint);
lcd.print("C");
// Second line - current mode
lcd.setCursor(0, 1);
switch (inputMode) {
case 0:
lcd.print("Temp: ");
lcd.print((int)setpoint);
lcd.print("C >");
break;
case 1:
lcd.print("Speed: ");
lcd.print(targetRPS, 1);
lcd.print("RPS >");
break;
case 2:
lcd.print("Dir: ");
if (motDir == 0) lcd.print("REV >");
else if (motDir == 2) lcd.print("FWD >");
else lcd.print("OFF >");
break;
}
}