Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Would you like help with this project? #1

Open
mgrouch opened this issue Feb 19, 2025 · 6 comments
Open

Would you like help with this project? #1

mgrouch opened this issue Feb 19, 2025 · 6 comments

Comments

@mgrouch
Copy link

mgrouch commented Feb 19, 2025

Hello,

Looks like a great way to start a new autopilot implementation. You have drafted all components. esp32-S3 should be enough to run it.

I have experience with esp32, LVGL, IMUs, NMEA, Kalman filters, etc. I have my own boat related project on GitHub.
You can see my works at:

https://bareboat-necessities.github.io/

Here are the areas I can contribute:

  • Add GitHub action for CI automatic builds of binaries
  • Add a script to load binaries for devices
  • Add IMU for tilt compensated compass, and Kalman fusion to remove noise.
  • Add a tool for better calibration using sphere fit which corrects both offsets and scale biases
  • Add UI client with LVGL on esp32 (m5stack tough)
  • Add GPS and IMU Kalman fusion for better gps speed and location accuracy
  • Add Fractional Order PID controller implementation instead of regular PID controller

I won’t be able to help with PCB designs.
That area also needs some touches. I can only help with the software.

Please let me know if you would accept my PRs.

Thanks,
Fair Winds!

@yarigabbai
Copy link
Owner

yarigabbai commented Feb 20, 2025 via email

@mgrouch
Copy link
Author

mgrouch commented Feb 21, 2025

Hello,

I've added my first pull request. It should simplify builds and safe time on detecting compile errors.

#2

I hope you can merge it.

Thanks,

@mgrouch
Copy link
Author

mgrouch commented Feb 21, 2025

Few next steps. autopilot-core.ino is too big file. I think it should be refactored to keep .ino small
and add separate .h files for compass, gps, AP controller. For further maintainability it's better to keep .ino just with variables
and setup()/loop().

Also I see delay(1000) in loop(). I think steering need to be applied more often than just 1Hz.

Thanks

@mgrouch
Copy link
Author

mgrouch commented Feb 21, 2025

There is also this project: https://github.com/spascual90/Fenix

Autopilot on MCU also. Uses NeoGPS instead of TinyGPS. See: https://github.com/SlashDevin/NeoGPS

@mgrouch
Copy link
Author

mgrouch commented Feb 21, 2025

Also AI is very good for writing small code snippets.

Here is an answer of DeepSeek to the question:

Can you write PID controller in C++ for esp32?

Certainly! Below is an example of a simple PID controller implementation in C++ for the ESP32. This example assumes you have some basic knowledge of how to set up and program the ESP32 using the Arduino IDE or PlatformIO.

PID Controller Implementation in C++ for ESP32

class PIDController {
public:
    // Constructor
    PIDController(float kp, float ki, float kd, float setpoint)
        : kp(kp), ki(ki), kd(kd), setpoint(setpoint), prevError(0), integral(0) {}

    // Compute the PID output
    float compute(float input) {
        // Calculate error
        float error = setpoint - input;

        // Proportional term
        float proportional = kp * error;

        // Integral term
        integral += error;
        float integralTerm = ki * integral;

        // Derivative term
        float derivative = kd * (error - prevError);
        prevError = error;

        // Calculate the output
        float output = proportional + integralTerm + derivative;

        return output;
    }

    // Set the setpoint
    void setSetpoint(float newSetpoint) {
        setpoint = newSetpoint;
    }

    // Set the PID gains
    void setGains(float newKp, float newKi, float newKd) {
        kp = newKp;
        ki = newKi;
        kd = newKd;
    }

private:
    float kp;           // Proportional gain
    float ki;           // Integral gain
    float kd;           // Derivative gain
    float setpoint;     // Desired value
    float prevError;    // Previous error
    float integral;     // Integral of errors
};

// Example usage in an ESP32 sketch
void setup() {
    Serial.begin(115200);

    // Initialize PID controller with gains and setpoint
    PIDController pid(1.0, 0.1, 0.01, 100.0);  // Example gains and setpoint

    float input = 0.0;  // Simulated input (e.g., sensor reading)
    float output = 0.0; // Output to actuator (e.g., motor speed)

    for (int i = 0; i < 100; i++) {
        // Simulate a process (e.g., read sensor value)
        input += output * 0.1;  // Simulate system response

        // Compute PID output
        output = pid.compute(input);

        // Print results
        Serial.print("Input: ");
        Serial.print(input);
        Serial.print(" Output: ");
        Serial.println(output);

        delay(100);  // Simulate time delay
    }
}

void loop() {
    // Empty loop
}

Explanation:

  1. PIDController Class:

    • The PIDController class encapsulates the PID control logic.
    • It has three gains: kp (proportional), ki (integral), and kd (derivative).
    • The compute() method calculates the PID output based on the current input (e.g., sensor reading) and the setpoint.
  2. Example Usage:

    • In the setup() function, a PIDController object is created with example gains and a setpoint.
    • A simple loop simulates a process where the input is updated based on the output, and the PID controller computes the new output.
  3. ESP32 Integration:

    • This code can be uploaded to an ESP32 using the Arduino IDE or PlatformIO.
    • Replace the simulated input with actual sensor readings and the output with control signals to actuators (e.g., motors).

Notes:

  • Tune the PID gains (kp, ki, kd) for your specific application.
  • Ensure proper handling of sensor readings and actuator outputs in a real-world scenario.
  • Consider adding anti-windup mechanisms or output clamping if necessary.

Let me know if you need further assistance!

@mgrouch
Copy link
Author

mgrouch commented Feb 21, 2025

Even cooler. I asked it to implement FoPID controller:

Certainly! A Fractional-Order PID (FoPID) controller is an extension of the traditional PID controller, where the derivative and integral terms can have fractional orders. This allows for more precise control in certain systems.

Below is an implementation of a FoPID controller in C++ for the ESP32. This implementation uses the Grunwald-Letnikov (GL) definition for fractional derivatives and integrals, which is a common approach for discrete-time systems.


FoPID Controller Implementation in C++ for ESP32

#include <cmath>
#include <vector>

class FoPIDController {
public:
    // Constructor
    FoPIDController(float kp, float ki, float kd, float lambda, float mu, float setpoint, int memorySize)
        : kp(kp), ki(ki), kd(kd), lambda(lambda), mu(mu), setpoint(setpoint), memorySize(memorySize) {
        errorHistory.resize(memorySize, 0.0f);  // Initialize error history
    }

    // Compute the FoPID output
    float compute(float input) {
        // Calculate error
        float error = setpoint - input;

        // Update error history
        errorHistory.erase(errorHistory.begin());
        errorHistory.push_back(error);

        // Proportional term
        float proportional = kp * error;

        // Fractional integral term (using Grunwald-Letnikov)
        float integralTerm = 0.0f;
        for (size_t i = 0; i < errorHistory.size(); i++) {
            integralTerm += std::tgammaf(lambda + 1) / (std::tgammaf(i + 1) * std::tgammaf(lambda - i + 1)) *
                            errorHistory[errorHistory.size() - 1 - i];
        }
        integralTerm *= ki;

        // Fractional derivative term (using Grunwald-Letnikov)
        float derivativeTerm = 0.0f;
        for (size_t i = 0; i < errorHistory.size(); i++) {
            derivativeTerm += std::tgammaf(mu + 1) / (std::tgammaf(i + 1) * std::tgammaf(mu - i + 1)) *
                               errorHistory[errorHistory.size() - 1 - i];
        }
        derivativeTerm *= kd;

        // Calculate the output
        float output = proportional + integralTerm + derivativeTerm;

        return output;
    }

    // Set the setpoint
    void setSetpoint(float newSetpoint) {
        setpoint = newSetpoint;
    }

    // Set the FoPID gains
    void setGains(float newKp, float newKi, float newKd, float newLambda, float newMu) {
        kp = newKp;
        ki = newKi;
        kd = newKd;
        lambda = newLambda;
        mu = newMu;
    }

private:
    float kp;           // Proportional gain
    float ki;           // Integral gain
    float kd;           // Derivative gain
    float lambda;       // Fractional order for integral term
    float mu;           // Fractional order for derivative term
    float setpoint;     // Desired value
    int memorySize;     // Size of error history for fractional calculations
    std::vector<float> errorHistory;  // History of errors for fractional calculations
};

// Example usage in an ESP32 sketch
void setup() {
    Serial.begin(115200);

    // Initialize FoPID controller with gains, fractional orders, setpoint, and memory size
    FoPIDController fopid(1.0, 0.1, 0.01, 0.5, 0.5, 100.0, 50);  // Example parameters

    float input = 0.0;  // Simulated input (e.g., sensor reading)
    float output = 0.0; // Output to actuator (e.g., motor speed)

    for (int i = 0; i < 100; i++) {
        // Simulate a process (e.g., read sensor value)
        input += output * 0.1;  // Simulate system response

        // Compute FoPID output
        output = fopid.compute(input);

        // Print results
        Serial.print("Input: ");
        Serial.print(input);
        Serial.print(" Output: ");
        Serial.println(output);

        delay(100);  // Simulate time delay
    }
}

void loop() {
    // Empty loop
}

Key Features of the FoPID Controller:

  1. Fractional Orders:

    • lambda: Fractional order for the integral term.
    • mu: Fractional order for the derivative term.
    • These allow for more flexible control compared to traditional PID.
  2. Grunwald-Letnikov (GL) Definition:

    • The fractional derivative and integral are computed using the GL definition, which is suitable for discrete-time systems.
    • The std::tgammaf function is used to compute the gamma function, which is required for the GL coefficients.
  3. Error History:

    • The controller maintains a history of past errors to compute the fractional terms.
    • The size of the history (memorySize) determines the accuracy of the fractional calculations.
  4. Tunable Parameters:

    • The gains (kp, ki, kd) and fractional orders (lambda, mu) can be tuned for your specific application.

Example Parameters:

  • kp = 1.0, ki = 0.1, kd = 0.01: Traditional PID gains.
  • lambda = 0.5, mu = 0.5: Fractional orders for integral and derivative terms.
  • setpoint = 100.0: Desired value.
  • memorySize = 50: Size of the error history for fractional calculations.

Notes:

  • Performance: Fractional-order controllers can be computationally intensive due to the need to maintain and process a history of errors.
  • Tuning: Tuning fractional-order controllers can be more complex than traditional PID controllers. You may need to experiment with different values of lambda and mu.
  • Real-World Use: Replace the simulated input with actual sensor readings and the output with control signals to actuators.

Let me know if you need further clarification or enhancements!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants