Hi, I'm a relative beginner with arduino. I've been working on a project that will use the PID library by Brett Beauregard. I have a rotary encoder and over the last few days, I've built a menu system that operates through clicking and rotating. However, now that I'm adding the PID library, I find when adding the initial declaration PID myPID(&input, &output, &setpoint, 1.0, 1.0, 1.0, DIRECT ); the rotary encoder stops working. Well at least the switch on the encoder does, I don't know if the rotary part of it works or not because that only functions after clicking the encoder. Has anyone else come across anything like this? My code below:
// Include necessary libraries
#define ENCODER_DO_NOT_USE_INTERRUPTS
#include <LiquidCrystal.h>
#include <Encoder.h>
#include <EEPROM.h>
//#include <Wire.h>
#include <PID_v1.h>
// Define LCD pins
const int rs = 5, en = 4, d4 = 3, d5 = 2, d6 = 1, d7 = 0;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Thermistor constants
#define RT0 200000 // Ω
#define B 4045 // K
#define R 100000 // Ω (resistor used in voltage divider)
// Encoder pins
#define ENCODER_CLK 6
#define ENCODER_DT 7
#define ENCODER_SW 8
Encoder encoder(ENCODER_DT, ENCODER_CLK);
// Relay output pins
#define RELAY_VALVE 9
#define RELAY_HEATER 10
// Analog input pins
#define TEMP_PIN A0
#define PRES_PIN A1
// Control parameters
const uint8_t NUM_VARIABLES = 7;
const uint8_t NUM_VARIABLESI = 3;
const uint8_t NUM_VARIABLESF = 7;
const char* variableNames[NUM_VARIABLESF] = { "Temp-ramp", "Temp-hold", "Time", "Pressure", "P" , "I", "D" };
const float variableminf[NUM_VARIABLESF] = {50.0, 50.0, 10.0, 0.1, 0.0, 0.0, 0.0 };
const float variablemaxf[NUM_VARIABLESF] = {220.0, 220.0, 240.0, 2.0, 50.0, 50.0, 50.0 };
const float variablefdefault[NUM_VARIABLESF] = {130.0, 145.0, 45, 0.5, 5.0, 1.0, 1.0 };
float variablesf[NUM_VARIABLESF]; // temps, time, Pressure, Proportional, Integeral, and Derivative terms
const int EEPROM_START_ADDR = 0; // EEPROM starting address
// encoder variables
enum Mode { IDLE, NAVIGATE, EDIT };
Mode currentMode = IDLE;
long lastEncoderPosNormalized = 0;
int currentMenuItem = 0;
double tempEditValueFloat = 0.0;
bool buttonState = HIGH;
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 25;
unsigned long pressStartTime = 0;
bool buttonPressedFlag = false;
// Program timer variables
unsigned long lastTempUpdate = 0;
unsigned long previousMillis = 0;
unsigned long programtimeseconds = 0;
unsigned long programtimeminutes = 0;
unsigned long programtimehours = 0;
// logic variables
bool started = false; // flag for program started
bool steamvalveopen = false;
// variables for temperature measurement and pressure measurement
float RT, VR, ln, TX, T0, VRT;
float Pvoltage = 0.0;
float pressure = 0.0;
float maxpressure = 0.0;
double output, input, setpoint;
//PID* myPID; // declare a pointer to a PID object
PID myPID(&input, &output, &setpoint, 1.0, 1.0, 1.0, DIRECT );
int WindowSize = 5000;
unsigned long windowStartTime;
void setup() {
// Set pin modes
pinMode(RELAY_HEATER, OUTPUT);
pinMode(RELAY_VALVE, OUTPUT);
pinMode(ENCODER_SW, INPUT_PULLUP);
T0 = 25 + 273.15; // Reference temp in Kelvin
lcd.begin(16, 2);
encoder.write(0);
for (uint8_t i = 0; i < NUM_VARIABLESF; i++) {
EEPROM.get(EEPROM_START_ADDR + (i * sizeof(float)), variablesf[i]);
if (isnan(variablesf[i]) || variablesf[i] < variableminf[i] || variablesf[i] > variablemaxf[i]) {
variablesf[i] = variablefdefault[i];
}
}
showIdle();
windowStartTime = millis();
//myPID = new PID(&input, &output, &setpoint, variablesf[4], variablesf[5], variablesf[6], DIRECT);
//myPID.SetOutputLimits(0, WindowSize);
//turn the PID on
//myPID.SetMode(AUTOMATIC);
}
void loop() {
// Temperature calculation
VRT = (5.00 / 1023.00) * analogRead(A0); // Voltage across thermistor
VR = 5.00 - VRT;
RT = VRT / (VR / R); // Resistance of thermistor
ln = log(RT / RT0); // ln(RT/RT0)
TX = (1 / ((ln / B) + (1 / T0))) - 273.15; // Temperature in Celsius
// Pressure sensor conversion
int sensorValue = analogRead(PRES_PIN);
Pvoltage = sensorValue * (5.0 / 1023.0);
pressure = 1.3953 * Pvoltage - 0.6977; // Linear conversion formula
// Program timer and heater/valve logic
if (started && programtimeminutes < variablesf[2]) { // program started and time running is less than the program length in minutes. set timer going
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= 1000) {
previousMillis = currentMillis;
programtimeseconds++;
if (programtimeseconds > 59) {
programtimeseconds = 0;
programtimeminutes++;
}
}
if (pressure > maxpressure) { // Update maxpressure variable
maxpressure = pressure;
}
if ((maxpressure - pressure) > variablesf[3]) { // turn on steam bleed valve to trap steam in vessel when pressure drops
digitalWrite(RELAY_VALVE, HIGH);
steamvalveopen = true;
}
input = TX;
setpoint = variablesf[0];
//myPID.Compute();
if (millis() - windowStartTime > WindowSize){
//time to shift the Relay Window
windowStartTime += WindowSize;
}
if (output < millis() - windowStartTime) digitalWrite(RELAY_HEATER, HIGH);
else digitalWrite(RELAY_HEATER, LOW);
digitalWrite(RELAY_HEATER, HIGH);
} else { // program was cancelled or end of program was reached, turn heater off, open steam bleed valve, reset timer
started = false;
steamvalveopen = false;
maxpressure = 0.0;
digitalWrite(RELAY_VALVE, LOW);
digitalWrite(RELAY_HEATER, LOW);
programtimeseconds = 0;
programtimeminutes = 0;
programtimehours = 0;
}
readButton();
handleEncoder();
if (currentMode == IDLE && (millis() - lastTempUpdate > 1000)) { //Update display every second
lastTempUpdate = millis();
updateIdleDisplay();
}
}
// Read encoder pushbutton with debounce
void readButton() {
int reading = digitalRead(ENCODER_SW);
if (reading != lastButtonState) lastDebounceTime = millis();
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
pressStartTime = millis();
buttonPressedFlag = false;
} else {
unsigned long pressDuration = millis() - pressStartTime;
if (pressDuration >= 500) handleLongPress();
else if (!buttonPressedFlag) handleShortPress();
}
}
}
lastButtonState = reading;
}
// Handle short press (edit variable or enter menu)
void handleShortPress() {
if (currentMode == IDLE) {
currentMode = NAVIGATE;
currentMenuItem = 0;
encoder.write(currentMenuItem * 4);
lastEncoderPosNormalized = currentMenuItem;
showNavigate();
} else if (currentMode == NAVIGATE) {
currentMode = EDIT;
tempEditValueFloat = variablesf[currentMenuItem];
if (currentMenuItem < NUM_VARIABLESI) {
// First 3 variables: step size = 1
encoder.write((int)(tempEditValueFloat) * 4);
lastEncoderPosNormalized = (int)(tempEditValueFloat);
} else {
// Remaining variables: step size = 0.1
encoder.write((int)(tempEditValueFloat * 10) * 4);
lastEncoderPosNormalized = (int)(tempEditValueFloat * 10);
}
showEdit();
} else if (currentMode == EDIT) {
variablesf[currentMenuItem] = tempEditValueFloat;
EEPROM.put(EEPROM_START_ADDR + currentMenuItem * sizeof(float), tempEditValueFloat);
lcd.clear();
lcd.print("Saved ");
lcd.print(variableNames[currentMenuItem]);
delay(800);
currentMode = NAVIGATE;
encoder.write(currentMenuItem * 4);
lastEncoderPosNormalized = currentMenuItem;
showNavigate();
}
}
// Handle long press (cancel edit or toggle state)
void handleLongPress() {
if (currentMode == EDIT) {
currentMode = NAVIGATE;
encoder.write(currentMenuItem * 4);
lastEncoderPosNormalized = currentMenuItem;
showNavigate();
} else if (currentMode == NAVIGATE) {
currentMode = IDLE;
showIdle();
} else if (currentMode == IDLE) {
started = !started;
}
}
// Handle encoder rotation
void handleEncoder() {
long rawPos = encoder.read();
long expectedRawPos = lastEncoderPosNormalized * 4;
long deltaRaw = rawPos - expectedRawPos;
// Filter large unexpected jumps (e.g., > 8 counts)
if (abs(deltaRaw) > 8) {
// Resynchronize encoder position to last known stable value
encoder.write(expectedRawPos);
return;
}
// Only process when a full step (4 counts) is detected
if (deltaRaw >= 4 || deltaRaw <= -4) {
// Determine direction: positive or negative step(s)
int steps = deltaRaw / 4; // Number of steps moved (can be >1 or < -1)
if (currentMode == NAVIGATE) {
int newMenuItem = currentMenuItem + steps;
// Clamp newMenuItem within valid menu range
if (newMenuItem < 0) newMenuItem = 0;
if (newMenuItem >= NUM_VARIABLES) newMenuItem = NUM_VARIABLES - 1;
if (newMenuItem != currentMenuItem) {
currentMenuItem = newMenuItem;
lastEncoderPosNormalized = currentMenuItem;
encoder.write(currentMenuItem * 4);
showNavigate();
} else {
// If no menu change, just resync encoder write
encoder.write(lastEncoderPosNormalized * 4);
}
}else if (currentMode == EDIT) {
int pos = lastEncoderPosNormalized + steps;
if (currentMenuItem < NUM_VARIABLESI) {
// Whole number steps
int minPos = (int)variableminf[currentMenuItem];
int maxPos = (int)variablemaxf[currentMenuItem];
if (pos < minPos) pos = minPos;
if (pos > maxPos) pos = maxPos;
if (pos != lastEncoderPosNormalized) {
lastEncoderPosNormalized = pos;
tempEditValueFloat = (float)pos;
encoder.write(pos * 4);
showEdit();
} else {
encoder.write(lastEncoderPosNormalized * 4);
}
} else {
// Decimal steps of 0.1
int minPos = (int)(variableminf[currentMenuItem] * 10);
int maxPos = (int)(variablemaxf[currentMenuItem] * 10);
if (pos < minPos) pos = minPos;
if (pos > maxPos) pos = maxPos;
if (pos != lastEncoderPosNormalized) {
lastEncoderPosNormalized = pos;
tempEditValueFloat = pos / 10.0;
encoder.write(pos * 4);
showEdit();
} else {
encoder.write(lastEncoderPosNormalized * 4);
}
}
}
}
}
// Display idle screen with temperature and pressure
void showIdle() {
lcd.clear();
updateIdleDisplay();
}
// Update idle display every second
void updateIdleDisplay() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(TX, 1);
lcd.print(" ");
lcd.write(byte(223)); // Degree symbol
lcd.print("C");
char timeBuffer[6];
sprintf(timeBuffer, "%3lu:%02lu", programtimeminutes, programtimeseconds);
lcd.setCursor(10, 0);
lcd.print(timeBuffer);
lcd.setCursor(0, 1);
lcd.print(pressure, 2);
lcd.print(" Bar ");
lcd.print(maxpressure, 2);
}
// Show variable navigation screen
void showNavigate() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(variableNames[currentMenuItem]);
lcd.print(" = ");
lcd.print(variablesf[currentMenuItem], 1);
lcd.setCursor(0, 1);
lcd.print("Click to edit");
}
// Show variable editing screen
void showEdit() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Edit ");
lcd.print(variableNames[currentMenuItem]);
lcd.setCursor(0, 1);
lcd.print("Value: ");
lcd.print(tempEditValueFloat, 1);
}