Pid controller code help

/********************************************************
 * PID Basic Example
 * Reading analog input 0 to control analog PWM output 3
 ********************************************************/

#include <PID_v1.h>

//#define PIN_INPUT 0
//#define PIN_OUTPUT 3

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp=2, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

void setup()
{
  //initialize the variables we're linked to
  Input = analogRead(PIN_INPUT);
  Setpoint = 100;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  Input = analogRead(PIN_INPUT);
  myPID.Compute();
  analogWrite(PIN_OUTPUT, Output);
}

I am attempting to create a closed loop motor controller to set the speed of the motor in RPM.

I am a bit confused about exactly what the variables need set to or do I need to map the range.

For testing I want my motor that has a max RPM of 37 to have a setpoint of 9 RPMs. I have an encoder that reads RPMs to a variable "actualRPM" and a cyrton motor driver with PWM support.

As I understand it:
Setpoint = 9
Input = actualRPM

Then do I use a motor.setspeed(Output)? Im a bit confused what translates RPM into PWM? Do I need to be mapping the actualRPM (0-37) to PWM (0-255), then the output PWM adjust?

Should I:
Setpoint = map(Setpoint, 0, 37, 0, 255)
Input = map(actualRPM, 0, 37, 0, 255)

Then for an output to my cytron: motor.setspeed(Output)

Show the code that converts encoder input to RPM.

1 Like
// -----
// InterruptRotator.ino - Example for the RotaryEncoder library.
// This class is implemented for use with the Arduino environment.
//
// Copyright (c) by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD 3-Clause License. See http://www.mathertel.de/License.aspx
// More information on: http://www.mathertel.de/Arduino
// -----
// 18.01.2014 created by Matthias Hertel
// 04.02.2021 conditions and settings added for ESP8266
// 03.07.2022 avoid ESP8266 compiler warnings.
// 03.07.2022 encoder instance not static.
// -----

// This example checks the state of the rotary encoder using interrupts and in the loop() function.
// The current position and direction is printed on output when changed.

// Hardware setup:
// Attach a rotary encoder with output pins to
// * 2 and 3 on Arduino UNO. (supported by attachInterrupt)
// * A2 and A3 can be used when directly using the ISR interrupts, see comments below.
// * D5 and D6 on ESP8266 board (e.g. NodeMCU).
// Swap the pins when direction is detected wrong.
// The common contact should be attached to ground.
//
// Hints for using attachinterrupt see https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

#include <Arduino.h>
#include <RotaryEncoder.h>

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY)
// Example for Arduino UNO with input signals on pin 2 and 3
#define PIN_IN1 A2
#define PIN_IN2 A3

#elif defined(ESP8266)
// Example for ESP8266 NodeMCU with input signals on pin D5 and D6
#define PIN_IN1 D5
#define PIN_IN2 D6

#endif

// A pointer to the dynamic created rotary encoder instance.
// This will be done in setup()
RotaryEncoder *encoder = nullptr;

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY)
// This interrupt routine will be called on any change of one of the input signals
void checkPosition()
{
  encoder->tick(); // just call tick() to check the state.
}

#elif defined(ESP8266)
/**
 * @brief The interrupt service routine will be called on any change of one of the input signals.
 */
IRAM_ATTR void checkPosition()
{
  encoder->tick(); // just call tick() to check the state.
}

#endif


void setup()
{
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("InterruptRotator example for the RotaryEncoder library.");

  // setup the rotary encoder functionality

  // use FOUR3 mode when PIN_IN1, PIN_IN2 signals are always HIGH in latch position.
  // encoder = new RotaryEncoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::FOUR3);

  // use FOUR0 mode when PIN_IN1, PIN_IN2 signals are always LOW in latch position.
  // encoder = new RotaryEncoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::FOUR0);

  // use TWO03 mode when PIN_IN1, PIN_IN2 signals are both LOW or HIGH in latch position.
  encoder = new RotaryEncoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::TWO03);

  // register interrupt routine
  attachInterrupt(digitalPinToInterrupt(PIN_IN1), checkPosition, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_IN2), checkPosition, CHANGE);
} // setup()


// Read the current position of the encoder and print out when changed.
void loop()
{
  static int pos = 0;

  encoder->tick(); // just call tick() to check the state.

  int newPos = encoder->getPosition();
  if (pos != newPos) {
    Serial.print("pos:");
    Serial.print(newPos);
    Serial.print(" dir:");
    Serial.println((int)(encoder->getDirection()));
    pos = newPos;
  } // if
} // loop ()


// To use other pins with Arduino UNO you can also use the ISR directly.
// Here is some code for A2 and A3 using ATMega168 ff. specific registers.

// Setup flags to activate the ISR PCINT1.
// You may have to modify the next 2 lines if using other pins than A2 and A3
//   PCICR |= (1 << PCIE1);    // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C.
//   PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  // This enables the interrupt for pin 2 and 3 of Port C.

// The Interrupt Service Routine for Pin Change Interrupt 1
// This routine will only be called on any signal change on A2 and A3.
// ISR(PCINT1_vect) {
//   encoder->tick(); // just call tick() to check the state.
// }

// The End

I am using this library and I set

actualRPM =   encoder->getRPM();

If you want the actual function in the library I can get that also.

Shouldn't input be rpm from encoder and setpoint from pot?

I understand the Setpoint to be the goal. Input being the actual and output being the correction. What gets me a bit confused is when we start changing "labels". Two items are RPM and on is PWM. Thats why I am asking the questions. I assume they all need to have a common label so we would need to map them, but I'm learning PID.

The relationship between the setpoint and pwm values is defined by the K values (Kp, Ki, and Kd). They need to be chosen appropriately and the selection is generally unique to each project. The process of selection is called "PID tuning" and there are many tutorials on line that help to understand the method.

To oversimplify, Kp is the most important, and that can be thought of as a scale factor that relates PWM units and the setpoint units. Sample interval comes into play with Ki and Kd.

Ok. I think I understand a bit better and I can test and play to try and tune both the motor and my understanding better. Sorry for ask all the questions but all the "detailed" information on Brett Beauregard blog comes up with a "Not Found" so the documentation is fairly scarce. I attempted to look at some other PID libraries and would be open to suggestions if one is better or worse.

I have read a bunch of post on this forum and some things a @johnwasser posted about PID in the past answering some other peoples questions.

Use the search phrase "PID tuning" to find tutorials on methodology. Beauregard doesn't discuss it in any detail, if I recall correctly, but his blog is here: Improving the Beginner’s PID – Introduction | Project Blog

I have read his blog. That is what I was refering to above. But all the example links in his blog are dead :frowning: . I will do additonal reserch on tuning. Thank you!

Not all of the links are dead. This one has some info on the effects of different K values:

http://brettbeauregard.com/blog/2017/06/introducing-proportional-on-measurement/

1 Like

That blog is a useful resource. It looks like the links got broken, but the articles are still there, if you search for the "PID" tag PID | Project Blog

1 Like

The very first link in my search for "PID tuning" is this one, which looks OK.

1 Like

Purchased the simulator. Very neat tool. Think I got what I needed atleast on the test bench.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.