Hello All,
I have been working on my project a lot the last couple of weeks. I have a decent speed measurement but when letting the bot drive around I noticed that the speeds of mymotors were not equal so I implemented a PID control. This improved all a bit. But today I'm doing some tests and noticed something weird.
I send a speed setting in m/s to my bot. And than the PID controller should do its work to create the correct output in order to get the same speed for both motors. But when I place some extra friction on one motor I wont see the output of the PID changing? I'm wondering if I made a mistake with coding or I did not understand the way it works.
Below a recording of one motor
You can see the speed drops so I would expect that the output signal tries to increase in order to compensate for the error but it maintains the same ouput.
I have added my code whcih start to get a bit long. In the Loop() the PID is asked to compute the signals. in the function Cmd_DriveForwards() it receives the setpoint.
Again I think when a motor is asked for more power the speed reduces and than the output signal should increase if I understand it correct.
Maybe someone has an idea why this is not happening?
//************************************************************************************************************************************// // // // Project: Rover // // Version: V0.2 // // Date: Feb-2023 // // Current version capabilities: // // - Drive forward option // // - Speed sensing of Left and Right motors // // - Interface with MegunoLink for speed monitoring and speed control // // V0.1: // // - Adjusted speed control hardware with Logic inverter port, so two pins less will be used // // - Added WiFI communication with Megunolink // // - Clean up Loop() by adding new functions // // - Added controls for driving Forward, Backwards, Left, Right and Stop // // V0.2: // // - Changed variable names for more logical names // // - // //************************************************************************************************************************************// // Include SSID and password from a library file. #if defined(ARDUINO_ARCH_ESP32) #include "WiFi.h" #include <ESPmDNS.h> #elif defined(ARDUINO_ARCH_ESP8266) #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #else #error Not compatible with the selected board. #endif #include <MegunoLink.h> #include "CircularBuffer.h" #include "ESPTCPCommandHandler.h" #include "CommandProcessor.h" #include "ArduinoTimer.h" #include "ArduPID.h" #define USEWIFICONFIGFILE #ifdef USEWIFICONFIGFILE // Include SSID and password from a library file. #include "WiFiConfig.h" #else // Option 2 const char *SSID = "Your SSID"; const char *WiFiPassword = "Your Password"; #endif //Variables used for WiFi Server const uint8_t ServerPort = 23; WiFiServer Server(ServerPort); ArduinoTimer SendTimer; uint32_t PlottingPeriod = 200; const int MaxConnections = 2; TcpCommandHandler<MaxConnections> Cmds(Server); CommandProcessor<> SerialCmds(Cmds); String MakeMine(const char *NameTemplate); //Global declarations #define PWM_A 14 //PWM Channel for left motor #define PWM_A_Chan 0 #define AI_1 12 //Enable Channel left motor, logic "0" is forward, inverter IC takes care of inversing signal for AI_2 #define PWM_B 25 //PWM Channel for right motor #define PWM_B_Chan 1 #define BI_1 26 //Enable Channel right motor, logic "0" is forward, inverter IC takes care of inversing signal for BI_2 #define PWM_Res 8 #define PWM_Freq 15000 #define buffer_size 7 //Buffer size for computing average of speed sensing signal #define PI 3.14159265359 const byte slots = 20; // timing variables rightside motor speed sensor //Total slots on motor disk volatile unsigned long usRight; volatile unsigned long prevPulseUsRight; volatile unsigned long pulseUsRight; volatile unsigned long prevPulseUsCopyRight; volatile unsigned long pulseUsCopyRight; unsigned long pulsePeriodRight; unsigned long AvgPulseTimeRight; // timing variables leftside motor speed sensor volatile unsigned long usLeft; volatile unsigned long prevPulseUsLeft; volatile unsigned long pulseUsLeft; volatile unsigned long prevPulseUsCopyLeft; volatile unsigned long pulseUsCopyLeft; unsigned long pulsePeriodLeft; unsigned long AvgPulseTimeLeft; unsigned long prevMs; unsigned long now; // Variables used for calculation of Rotations Per Sec and AVG speed float rpsRight = 0; float rpsLeft = 0; float AvgVelocityRight; float AvgVelocityLeft; float pwm; int PWM_DutyCycle; const unsigned long slotUs = 1000000 / slots; const int RightMotorSpeedSens = 19; //Right motor Interrupt pin18 for speed sensing const int LeftMotorSpeedSens = 18; //Left motor Interrupt pin19 for speed sensing const float WheelDiameter = 0.0664; //Wheel diameter unsigned int RightSpeedCount = 0; unsigned int LeftSpeedCount = 0; unsigned long tempbufferRight[buffer_size]; unsigned long tempbufferLeft[buffer_size]; //Define MegunoLink GUI InterfacePanel MyPanel; typedef CircularBuffer<unsigned long, buffer_size> PulsePeriodBuffer; PulsePeriodBuffer PulseTimesRight; PulsePeriodBuffer PulseTimesLeft; //PID Control variables ArduPID LeftSpeedController; ArduPID RightSpeedController; double LeftInput; double LeftOutput; double RightInput; double RightOutput; double SpeedSetpoint; double LeftKp = 2.2; double LeftKi = 6; double LeftKd = 2; double RightKp = 2.5; double RightKi = 9; double RightKd = 2; //Interrupt for right speed sensor void IRAM_ATTR isrRight() { usRight = micros(); if ((usRight - pulseUsRight) > 7500) // debounce interval, also determines max rpm { prevPulseUsRight = pulseUsRight; pulseUsRight = usRight; } } //Interrupt for left speed sensor void IRAM_ATTR isrLeft() { usLeft = micros(); if ((usLeft - pulseUsLeft) > 7500) // debounce interval, also determines max rpm { prevPulseUsLeft = pulseUsLeft; pulseUsLeft = usLeft; } } bool timeOut(unsigned long ms) { now = millis(); if ((now - prevMs) >= ms) { prevMs = now; return true; } return false; } //WiFi connect function, checks SSID and Password shows if a connection is made and displays the IP address on the serial port void ConnectToWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(SSID, WiFiPassword); Serial.print("Connecting to "); Serial.println(SSID); uint8_t i = 0; while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(500); if ((++i % 16) == 0) { Serial.println(F(" still trying to connect")); } } Serial.print(F("Connected. My IP address is: ")); Serial.println(WiFi.localIP()); } //mDNS function to show device name, printed on the serial port void AdvertiseServices() { String MyName = MakeMine("MyDevice"); if (MDNS.begin(MyName.c_str())) { Serial.println(F("mDNS responder started")); Serial.print(F("My name is: ")); Serial.println(MyName.c_str()); // Add service to MDNS-SD MDNS.addService("n8i-mlp", "tcp", ServerPort); } else { Serial.println(F("Error setting up MDNS responder")); } } /* Returns a semi-unique id for the device. The id is based * on part of a MAC address or chip ID so it won't be * globally unique. */ uint16_t GetDeviceId() { #if defined(ARDUINO_ARCH_ESP32) return ESP.getEfuseMac(); #else return ESP.getChipId(); #endif } /* Append a semi-unique id to the name template */ String MakeMine(const char *NameTemplate) { uint16_t uChipId = GetDeviceId(); String Result = String(NameTemplate) + String(uChipId, HEX); return Result; } void Cmd_ListAll(CommandParameter &Parameters) { Parameters.GetSource().print(F("PlottingPeriod [ms]=")); Parameters.GetSource().println(PlottingPeriod); } void Cmd_SetPlottingPeriod(CommandParameter &Parameters) { PlottingPeriod = Parameters.NextParameterAsInteger(PlottingPeriod); } void Cmd_Unknown() { Serial.println(F("I don't understand")); } void setup() { //Setup Serial connection Serial.begin(115200); Serial.println(F("......Program starts.....")); //Setup WiFI connection ConnectToWiFi(); AdvertiseServices(); // Start the TCP server Server.begin(); Server.setNoDelay(true); // Setup the serial commands to MegunoLink Cmds.AddCommand(F("PlottingPeriod"), Cmd_SetPlottingPeriod); Cmds.AddCommand(F("ListAll"), Cmd_ListAll); Cmds.SetDefaultHandler(Cmd_Unknown); Cmds.AddCommand(F("MotorSpeed"), Cmd_DriveForwards); //Command to communicate with Megunolink Cmds.AddCommand(F("btnDriveForward"), Cmd_DriveForwards); Cmds.AddCommand(F("btnDriveBackwards"), Cmd_DriveBackwards); Cmds.AddCommand(F("btnDriveLeft"), Cmd_DriveLeft); Cmds.AddCommand(F("btnDriveRight"), Cmd_DriveRight); Cmds.AddCommand(F("btnEmergStop"), Cmd_Stop); pinMode(AI_1, OUTPUT); //A motor setup output channel, Left motor pinMode(BI_1, OUTPUT); //B motor setup output channel, Right motor pinMode(RightMotorSpeedSens, INPUT_PULLUP); //Setup input channels for speed sensing pinMode(LeftMotorSpeedSens, INPUT_PULLUP); ledcAttachPin(PWM_A, PWM_A_Chan); //Setup A motor PWM channel ledcAttachPin(PWM_B, PWM_B_Chan); //Setup B motor PWM channel ledcSetup(PWM_A_Chan, PWM_Freq, PWM_Res); ledcSetup(PWM_B_Chan, PWM_Freq, PWM_Res); //Setup interrupt ISR for speedsensing attachInterrupt(digitalPinToInterrupt(RightMotorSpeedSens), isrRight, RISING); attachInterrupt(digitalPinToInterrupt(LeftMotorSpeedSens), isrLeft, RISING); //Setup PID controllers for motor control LeftSpeedController.begin(&LeftInput, &LeftOutput, &SpeedSetpoint, LeftKp, LeftKi, LeftKd); RightSpeedController.begin(&RightInput, &RightOutput, &SpeedSetpoint, RightKp, RightKi, RightKd); LeftSpeedController.setOutputLimits(0,255); //Output limits for controller RightSpeedController.setOutputLimits(0,255); LeftSpeedController.setBias(0); //Create a Bias RightSpeedController.setBias(0); LeftSpeedController.setWindUpLimits(-10,10); //Bounds for the integral term to prevent integral wind-up RightSpeedController.setWindUpLimits(-10,10); LeftSpeedController.start(); RightSpeedController.start(); Serial.println("Setup Ready..."); } void loop() { #if defined(ARDUINO_ARCH_ESP8266) MDNS.update(); #endif SerialCmds.Process(); //Monitor serial commands Cmds.Process(); //Monitor WiFi commands SpeedSensing(); InterfacePanel MyPanel("", Cmds); MyPanel.SetNumber(F("RightSpeedGauge"), AvgVelocityRight); MyPanel.SetNumber(F("LeftSpeedGauge"), AvgVelocityLeft); // Set control value if (SendTimer.TimePassed_Milliseconds(PlottingPeriod)) { Serial.println("~"); TimePlot MyPlot("", Cmds); //Needs to use Cmds to access the connections //Send Data To MegunoLink Pro MyPlot.SendData(F("Left Speed"), AvgVelocityLeft); MyPlot.SendData(F("Left Output"), LeftOutput/100); MyPlot.SendData(F("Right Speed"), AvgVelocityRight); MyPlot.SendData(F("Right Output"), RightOutput/100); MyPlot.SendData("Setpoint", pwm/100); //PWM signal divided by 100 to scale equal with speed } LeftInput = AvgVelocityLeft; RightInput = AvgVelocityRight; LeftSpeedController.compute(); RightSpeedController.compute(); ledcWrite(PWM_A_Chan, LeftOutput); ledcWrite(PWM_B_Chan, RightOutput); } void Cmd_DriveForwards(CommandParameter& p) //Function for driving forward { int SP = p.NextParameterAsInteger(); SpeedSetpoint = (double) SP; digitalWrite(AI_1, HIGH); digitalWrite(BI_1, LOW); pwm = SpeedSetpoint; } void Cmd_DriveBackwards(CommandParameter& p) //Function for driving Backwards { int PWR = p.NextParameterAsInteger(); digitalWrite(AI_1, HIGH); digitalWrite(BI_1, LOW); ledcWrite(PWM_A_Chan, PWR); ledcWrite(PWM_B_Chan, PWR); pwm = PWR; } void Cmd_DriveLeft(CommandParameter& p) //Function for driving Left { unsigned long end = millis() + 200; int PWR = 65; while (millis() < end) { digitalWrite(AI_1, HIGH); digitalWrite(BI_1, HIGH); ledcWrite(PWM_A_Chan, PWR); ledcWrite(PWM_B_Chan, PWR); pwm = PWR; } PWR = 0; ledcWrite(PWM_A_Chan, PWR); ledcWrite(PWM_B_Chan, PWR); } void Cmd_DriveRight(CommandParameter& p) //Function for driving Right { unsigned long end = millis() + 200; int PWR = 65; digitalWrite(AI_1, LOW); digitalWrite(BI_1, LOW); ledcWrite(PWM_A_Chan, PWR); ledcWrite(PWM_B_Chan, PWR); pwm = PWR; } void Cmd_Stop(CommandParameter& p) //Function for stop driving { int PWR = 0; digitalWrite(AI_1, LOW); digitalWrite(BI_1, HIGH); ledcWrite(PWM_A_Chan, PWR); ledcWrite(PWM_B_Chan, PWR); pwm = PWR; } void averagebuffer() { AvgPulseTimeRight = 0; AvgPulseTimeLeft = 0; for(int a=0;a<buffer_size;a++) { AvgPulseTimeRight += tempbufferRight[a]; //Sum up buffer values AvgPulseTimeLeft += tempbufferLeft[a]; } AvgPulseTimeRight = AvgPulseTimeRight/buffer_size; //Compute average of buffer values by dividing by buffer length AvgPulseTimeLeft = AvgPulseTimeLeft/buffer_size; } void MoveAverageSpeed() //This function computes the avverage for Left and Right speed values { for(PulsePeriodBuffer::ForwardIterator Iterator(PulseTimesRight); Iterator.AtEnd() == false; Iterator.Next()) { tempbufferRight[Iterator.ItemNumber()]=Iterator.CurrentValue(); } for(PulsePeriodBuffer::ForwardIterator Iterator(PulseTimesLeft); Iterator.AtEnd() == false; Iterator.Next()) { tempbufferLeft[Iterator.ItemNumber()]=Iterator.CurrentValue(); } averagebuffer(); } void SpeedSensing() //This functions determines the current speed of Left and Right wheels { if (timeOut(250)) { // 4 Hz update frequency cli(); //stop interrupts prevPulseUsCopyRight = prevPulseUsRight; pulseUsCopyRight = pulseUsRight; prevPulseUsCopyLeft = prevPulseUsLeft; pulseUsCopyLeft = pulseUsLeft; sei(); //allow interrupts pulsePeriodRight = pulseUsCopyRight - prevPulseUsCopyRight; //Length of one pulse period pulsePeriodLeft = pulseUsCopyLeft - prevPulseUsCopyLeft; PulseTimesRight.Add(pulsePeriodRight); PulseTimesLeft.Add(pulsePeriodLeft); MoveAverageSpeed(); if(pulsePeriodRight != 0 || pulsePeriodLeft != 0) { rpsRight = (float)slotUs / (float)AvgPulseTimeRight ; //Compute RPS value AvgVelocityRight = (PI * WheelDiameter) * rpsRight; //Compute measured speed in m/s right wheel rpsLeft = (float)slotUs / (float)AvgPulseTimeLeft ; //Compute RPS value AvgVelocityLeft= (PI * WheelDiameter) * rpsLeft; //Compute measured speed in m/s left wheel } } if(pwm == 0) { AvgVelocityLeft = 0; AvgVelocityRight = 0; } }
Grtz,
Ray
Have you been able to figure out the problem?
Unfortunately I can't test your code as my robot base uses a mega and doesn't have WiFi connections and all those other header files. It would be a bit tedious to try and extract the pid code to look for issues. Where did you get the pid code?
I think there is a pid library that you can use.
Thanks for asking. I have partly fixed the issue but found another issue probably related to it. So I will try to give all the info and measurements here to see if someone can point out what I am doing wrong or how to find the issue.
In first post I could not see the PID regulation doing its work there was no reaction on the output signal. Well this came probably because I used a to low Kp. I followed some tutors on the internet and started to increase Kp up to oscillation occurs (this was at a Kp of 1500) than I divided this value by 2 which is advised as a good starting point. Doing this I can see the PID (actualy the P-gain) is doing work. This seems good.
The above image shows that when the speed drops (blue line) the output signal (PWM in magenta) is increasing. But the speed value is never reaching the green line (Setpoint speed).
Yesterday evening I noticed that this offset is proportional. Setting a speed of 0.25m/s the end speed will be 0.18m/s. But when I set a speed of 0.5 m/s the final speed will be 0.36 m/s.
About two or three weeks ago I had a pretty good speed reading. I checked the pulses on th eencoders with my scope and compared them with the software outputs. Only thing I have changed is the filter, first I have a moving average filter and I went to a exponential filter. Maybe this was a ig mistake and today I will try to go back and see if I have a better outcome.
I was also suggested to change my approach, I thought that when I control the speed of the motors to be equal I will be able to drive straight but possibly this is wrong and should base this control on other parameters....I don't yet and will have to experiment a bit (which is the fun part of all this).
Grtz,
Ray
Ok I did some tests to see how the software is behaves and I think I know where the problem is but how to solve is the next question. Below is my SpeedSensing function, which uses interrupts and takes values of pasing interrupt times and places them into a circular buffer called e.g. PulsePeriodLeft at this bufer the average is computed to get a more stable value.
When I remove the moving average filter I have by the way the same issue. So I guess the problem lies somewhere at the interrupts. I think this because also when the motors are at 0 rpm the last value will be vissible offcourse this is not correct as it should be 0. Looking to the code it makes sense cause if no interrupt occurs nothing will change. So at hte end it all has to do with my programming skills and it seems to me there are some bugs here.
void SpeedSensing() //This functions determines the current speed of Left and Right wheels { cli(); //stop interrupts prevPulseUsCopyRight = prevPulseUsRight; pulseUsCopyRight = pulseUsRight; prevPulseUsCopyLeft = prevPulseUsLeft; pulseUsCopyLeft = pulseUsLeft; sei(); //allow interrupts pulsePeriodRight = pulseUsCopyRight - prevPulseUsCopyRight; //Length of one pulse period pulsePeriodLeft = pulseUsCopyLeft - prevPulseUsCopyLeft; PulseTimesRight.Add(pulsePeriodRight); PulseTimesLeft.Add(pulsePeriodLeft); MoveAverageSpeed(); if(pulsePeriodRight != 0 || pulsePeriodLeft != 0) { rpsRight = (float)slotUs / (float)AvgPulseTimeRight ; //Compute RPS value AvgVelocityRight = (PI * WheelDiameter) * rpsRight; //Compute measured speed in m/s right wheel rpsLeft = (float)slotUs / (float)AvgPulseTimeLeft ; //Compute RPS value AvgVelocityLeft= (PI * WheelDiameter) * rpsLeft; //Compute measured speed in m/s left wheel } }
Grtz,
Ray
@electricray1981 When using the PID control are you just using the "Proportional" part of the PID (gain)? With a process control loop if you use just the proportional part of the PID you will never get setpoint and measurement to agree. As the measurement gets closer to the setpoint the feedback error gets smaller. There is a point where the error is so small you would have to have a very small proportional band setting (very large gain) to make the output move any more. It will look like it is always controlling with a small offset. You need the "Integral" function of the PID along with the proportional to to get setpoint and measurement to become equal. Try googling PID functions: ---> Proportional. Integral, and Derivative.
RCC1
The only guy I remember that was controlling a robot base from a computer was @inq with his time of flight experiments. I assume he was using PID control maybe he can chime in...
I would have too work my way up with actual hardware step by step like this,
and then worry about sending a speed request value from a remote computer once I got the PID working correctly.
I haven't tried PID control yet except on a little virtual robot experiment.
@electricray1981, @robotbuilder,
Hay! My ears are burning. 😆
I've barely scanned your thread. Sounds like PID, motors and something about control from computer. I'll read through your thread later. I didn't get to where you asked a specific question yet. So... I'll throw some things out, and see if anything sticks.
PID - This is a project I did to build a ventilator. The pressure as a function of time for a ventilator must have a very special shape. I used pressure sensors in the mask to feedback and PID was used to regulate an intake and exhaust valve setting to track the curve. Here is the project page InqVent. The PID does not control the blower motor as the response time of the blower was way too slow to be useful. It is controlled, but just to achieve peak pressure. All the control is on an ESP8266, however, it also hosts a web interface that is plotting real time data to a laptop, tablet and phone simultaneously.
More recently, I did a robot, but it was using stepper motors as I feel its far easier to control the robot accurately with those instead of motors, encoders and PID. This is not a common opinion on this forum. Most prefer the encoder route. Below is a YouTube on one of the test runs. It also shows using the same ESP8266 and hosting a web interface to control the robot from a phone, while the laptop shows the joy stick position and other real-time telemetry. Here is the full build thread showing the evolution of the build. https://forum.dronebotworkshop.com/user-robot-projects/inqling-junior-robot-mapping-vision-autonomy/
3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, WiFi Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide
Thanks for your comment, I do indeed only use the P because it was a good starting point I read. But seeing your comment what I have now is probably normal behaviour although I have an error of about 28% which I think is pretty high.
If I set 0.25 m/s I reach 0.18 m/s and this difference is proprtional so when I double the setpoint the difference is doubled.
I'll have a look further in PID tunning, your comments makes me think nothing is wrong with the software (although I see some bugs but those have nothing to do with the error I get now)
Grtz,
Ray
Thanks for your help. The link you've posted is basically what I am doing except that I use ArduPID.h library for the PID control. In your link they hardcoded the algorithm by themself instead of using a library.
Grtz,
Ray
Thanks for your comment. You have made a pretty awesome bot (I like it's sound it makes me think of Star Wars bots 😆).
What you have done is basically what I'm trying to accomplish with different hardware I suppose. I'm able to ctroll the motors from my GUI which I made with MegunoLink. And I receive speed feedback and PWM outputs back and later I want to add more sensors but for now the goal is to make it drive in a straight line. I thought I could do this by controlling the speed of the motors and make them equal.
The issue/question I have is that the speed setting is never reached. RCC1 pointed allready something out to me which I did not know (remember anymore from school) is that the setpoint is never reached when using only the P gain, so I will start playing with this first and see if I get improvements.
I have a bug in the code which I'm unable to fix at the moment but that's related to other things nevertheless important to fix but I will come back to that later. I would have to change the code a bit so it becomes smaller and easier for other people to read. The above posted code is too long and has stuff which is not related to any issue so I think I would have take it out in order someone could help me.
All you guys thanks for the feedback I appreciate all the help.
Grtz,
Ray
Here are a couple of bookmarks I found in my browser that I'm sure I must have found helpful at one time. 😉
https://www.crossco.com/resources/technical/how-to-tune-pid-loops/
https://www.pidtuning.net/fivedeadlymistakes.html
Depending on what your end goals are and you're allowed to do with your project, there is an automatic PID tuning algorithm in the Marlin, 3D Printer code base. I had a student interested in creating an automatic PID tuner and I pointed him toward it. That code base is quite large, but searching for "PID", got me to the pertinent code.
Doing some dumpster diving in my old code, here are the pertinent pieces. The tuning documents above basically say you tune them in order P,I,D. It sounds like you have P pretty close and @rcc1 is definitely right about having to have more than just P. Here is my PID function for the ventilator. The constants at the top were the starting points and got the error down below 10%. The full program had these configurable and using the tuning process, I believe I had the error down well below 1%.
_config->Kp = 3200; _config->Ki = 2000; _config->Kd = 0; void InqVent::setValves(s32 time, s32 targetPressure) { // Calculates positions of both valves. // Assumes valves will handle numbers outside of their range. // > 0 - Exhale closed, Inhale open 0 to 100 // < 0 - Inhale closed, Exhale open 0 to 100 static const s32 MAX_INT = 2147470000; static s32 lT = 0; // Last time static s32 lE = 0; // Last error // Deviation from where we want to be. s32 err = targetPressure - _pressure; // Derivative portion - on what it suppose to be, not error - EVALUATE s32 dt = time > lT ? time - lT : time + _config->msPerCycle - lT; if (dt == 0) return; s32 deriv = (err - lE) * 1000 / dt; lT = time; lE = err; // Integral portion _sum = min(MAX_INT, max(-MAX_INT, _sum + err)); // Combine s32 value = _config->Kp * (err + _config->Ki * _sum / 100000 + _config->Kd * deriv / 100000) / 10; _inhale.setMilli(value); _exhale.setMilli(-value); //Debug::log("tP=%d cP=%d err=%d val=%d i=%u e=%u\n", //targetPressure, _pressure, err, value, _inhale.get(), _exhale.get()); }
Good luck with your project.
3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, WiFi Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide
Thanks for the info those links will help me a lot. I think I'm finally back at the position of trying to tune further.
I noticed these bugs I wrote about in previous posts that they had to be fixed first before I could tune them. This is because they relate to the speedsensing of the motors and offcourse if this sensing is not correct how can one tune the speed of the motor.
So I had in the code that I posted some functions to create a circular buffer which I used to compute a moving average. But inside it there were bug that when the speed was 0 the values were not adjusted accordingly so...motor at 0 rpm my PID control still received the old speed value.
You don't need to have a PHD to understand that this is completely wrong. I have been struggling where this problem was but found it.
Than I noticed as well that when the code received a 0 value and computing the average on 0 values I got "inf"values instead of 0 which I wanted.
This made me decide to use a filter lib (I use now an Expenential Filter with a weight of 4). This gives me pretty stable speed readings. Ok still with a error of about 20% (when only using P) but now when the speed get zero the control loop sees 0.
This all took me from this moring up to now but I think I can starting trying to tune the motors FINALLY.
This is really a cool project I'm learning a lot about programming an controls and electronics....all the afford and mistakes worth IMHO
Grtz,
Ray
Besides of all this I would like to give a compliment to all people here on the forum with their positive comments that really help people who are trying to learn something.
Lately I see on e.g. the Arduino forum so many negative, sacastic answers to questions which really doesn't help someone to learn something. I personally never post a question on a forum to get the answer for free I ask it because I get stuck and don't know how to solve it. No one has to write the code for me I really want to do it for myself. But sometimes there are answers on certain forums that makes you think dropping the thing youre trying to do.
But this forum is a bit different I see so a big thumb up for all people that have tried to help me lately here
BR
Ray
Grtz,
Ray
@electricray1981 You are the kind of member the forum needs more of. So many come here and simply say my thing doesn't work, help me. You think I exaggerate but it's true.
First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, 360, fairly knowledge in PC plus numerous MPU's & MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.
Besides of all this I would like to give a compliment to all people here on the forum with their positive comments that really help people who are trying to learn something.
Remember that a lot of people on forums may well also be learning, the reason they are here in the first place, and there may not be that many experts to help.
A question might be left orphaned by itself because time passes and no one is reading an older post which is what I thought might have been happening with your post so I tried to bring it to other's attention.
Had I done some wifi and pid projects I probably could have skimmed through your code to tell you where the issues were. Sadly I haven't been motivated for some time now to do anything. The hardware is collecting dust unused.
Having others take an interest in your project can itself be an impulse to making the extra effort to solve it yourself.
The biggest error I see people make when looking for someone to take an interest in their project is simply not providing all the information such as the code they wrote and the hardware parts and wiring. It also helps if the project goal is provided if only to get a possible interested party involved.