A tether is good.
I wish that was the problem. I keep getting some extremely high values and for example the roll and pitch values on the controller (two potentiometers) are as one value in the drone (I moved both pots, but only one number changes). The same situation sets for altitude and yaw. What happens with the camera is that when I press the button on the controller, instead of writing a 1 to the bool in the drone, it writes a nonsensical value to rollInput. Any ideas?
Thank you for your advice. I've already made the tuning stand, so testing is relatively safe.
A programming mistake seems likely. Hard to tell without seeing the code.
This is simplified code:
//TRANSMITTER
#include <SPI.h>
#include <RF24.h>
#include <nRF24L01.h>
int osax1 = A0;
int osay1 = A1;
int osax2 = A2;
int osay2 = A3;
RF24 radio(10, 9); // CE, CSN
const byte address[10] = "sjSEmD3xK";
int data[10];
void setup() {
Serial.begin(9600);
pinMode(osax1, INPUT);
pinMode(osay1, INPUT);
pinMode(osax2, INPUT);
pinMode(osay2, INPUT);
radio.begin();
radio.openWritingPipe(address);
radio.setPALevel(RF24_PA_MIN);
radio.stopListening();
}
void loop() {
data[0] = analogRead(osax1); // yaw
data[1] = analogRead(osay1); // altitude
data[2] = analogRead(osax2) ; // roll
data[3] = analogRead(osay2) ;// pitch
Serial.print("yaw");
Serial.println(data[0]);
Serial.print("altitude");
Serial.println(data[1]);
Serial.print("roll");
Serial.println(data[2]);
Serial.print("pitch");
Serial.println(data[3]);
radio.write(&data, sizeof(data));
}
//RECEIVER
#include <SPI.h>
#include <RF24.h>
#include <nRF24L01.h>
int axis[4];
RF24 radio(5, 4); // CE, CSN
const byte address[10] = "sjSEmD3xK";
void setup() {
Serial.begin(115200);
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_MIN);
radio.startListening();
};
void loop() {
if (radio.available()) {
radio.read(&axis, sizeof(axis));
for (int i = 0; i < 4; i++) {
Serial.println(axis[i]);
}
}
}
Receiver output (all potentiometers have non-zero values at this point):
49086465
2687482
0
0
Looks to me like you are sending 20 (or 40) bytes and reading 8 (or 16). That looks wrong to me. From the output it appears that your receiver is using 32-bit integers and your transmitter is sending 16-bit integers.
49086465 -> 0x02ED0001 -> 0x02ED 0x0001 -> 749, 1
2687482 -> 0x002901FA -> 0x0029 0x01FA -> 41, 506
0
0
The byte order may also be different between the two.
Thank you, I updated my code according to your post and it works! ![]()
Hi, is there a way to speed up my code? On esp32 it now takes about 36 milliseconds, which is really similar to ATmega328 and the speed was the reason I upgraded. I've read on the internet that changing algorithms or libraries can help, but I have a good feeling about my code and would like to increase the speed otherwise than by changing it. My current code is:
#include<Wire.h>
#include <SPI.h>
#include <RF24.h>
#include <nRF24L01.h>
#include <BME280I2C.h>
#include <ESP32Servo.h>
#define esc1 33 // right back
#define esc2 32// right front
#define esc3 25 // left back
#define esc4 26// feft front
// 2
Servo escLF;
Servo escRF;
Servo escRB;
Servo escLB;
BME280I2C bme;
const int MPU_addr = 0x68;
RF24 radio(5, 4);
const uint64_t address[] = {0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL};
double seaLevelPres = 1013.25;
bool camera = false;
// radio input
short data[6];
short altitude;
//sensors
double pitchInput, rollInput, yawInput, altitudeInput;
int counter = 0;
//IMU
double xAcc, yAcc, zAcc, xGyro, yGyro, zGyro;
double currentGyroMillis, previousGyroMillis, deltaGyroTime;
double pitchInputAcc, rollInputAcc, yawInputAcc;
double pitchInputGyro, rollInputGyro, yawInputGyro;
double rollGyroOffset, pitchGyroOffset, yawGyroOffset, rollAccOffset, pitchAccOffset, yawAccOffset;
//kalman's altitude filter
double altitudeInputOffset;
const double QTemp = 0.91; // ProcesnĂ ĹĄum
const float RTemp = 0.125;// MÄĹĂcĂ ĹĄum
float xTemp = 0; // StavovĂĄ promÄnnĂĄ
float pTemp = 1; // Kovariance stavu
float kTemp;// Kalmanovo zesĂlenĂ
const float QPres = 0.043; // ProcesnĂ ĹĄum
const float RPres = 0.007;// MÄĹĂcĂ ĹĄum
float xPres = 0; // StavovĂĄ promÄnnĂĄ
float pPres = 1; // Kovariance stavu
float kPres; // Kalmanovo zesĂlenĂ
//pid
double pitchKp = 1.4, pitchKi = 0.0000002 , pitchKd = 0.05;
double rollKp = 1.5, rollKi = 0.0000002, rollKd = 0.05;
double yawKp = 0.12, yawKi = 0.0000005, yawKd = 0.052;
double altitudeKp = 0.99, altitudeKi = 0.0000001, altitudeKd = 0.053;
double pitchSetpoint = 0, rollSetpoint = 0, yawSetpoint = 0, altitudeSetpoint = 0; //radio transmitter values
double pitchLastError, rollLastError, yawLastError, altitudeLastError;
double pitchIntegral, rollIntegral, yawIntegral, altitudeIntegral;
double pitchOutput, rollOutput, yawOutput, altitudeOutput;
unsigned long previousMillis;
unsigned long currentMillis;
const unsigned long interval = 35;
//motor speeds
double sp1 = 0, sp2 = 0, sp3 = 0, sp4 = 0;
//functions initialization
void InitInput();
void GetRadio(double& yawSetpoint, double& rollSetpoint, double& pitchSetpoint, double& altitudeSetpoint, double& seaLevelPres, bool& camera);
void ReadSensors(double& pitchInput, double& rollInput, double& yawInput, double& altitudeInput);
double CalculatePID(double Input, double Kp, double Ki, double Kd, double Setpoint, double& LastError, double& Integral);
void ControlMotors();
void setup() {
delay(3000);
InitInput();
}
void loop() {
GetRadio(yawSetpoint, rollSetpoint, pitchSetpoint, altitudeSetpoint, seaLevelPres, camera);
ReadSensors(pitchInput, rollInput, yawInput, altitudeInput);
currentMillis = millis();
if ((currentMillis - previousMillis) > interval)
{
Serial.println((currentMillis - previousMillis));
previousMillis = currentMillis;
pitchOutput = CalculatePID(pitchInput, pitchKp, pitchKi, pitchKd, pitchSetpoint, pitchLastError, pitchIntegral);
rollOutput = CalculatePID(rollInput, rollKp, rollKi, rollKd, rollSetpoint, rollLastError, rollIntegral);
yawOutput = CalculatePID(yawInput, yawKp, yawKi, yawKd, yawSetpoint, yawLastError, yawIntegral);
altitudeOutput = CalculatePID(altitudeInput, altitudeKp, altitudeKi, altitudeKd, altitudeSetpoint, altitudeLastError, altitudeIntegral);
}
/*Serial.println(pitchOutput);
Serial.println(rollOutput);
Serial.println(yawOutput);
Serial.println(altitudeOutput);
Serial.print("Altitude sea level pressure");
Serial.println(seaLevelPres);
Serial.print("altitudeSetpoint");
Serial.println(altitudeSetpoint);
Serial.print("pitchSetpoint");
Serial.println(pitchSetpoint);
Serial.print("rollSetpoint");
Serial.println(rollSetpoint);
Serial.print("yawSetpoint");
Serial.println(yawSetpoint);
Serial.print("Pitch");
Serial.println(pitchInput);
Serial.print("Roll");
Serial.println(rollInput);
Serial.print("Yaw");
Serial.println(yawInput);
Serial.print("Altitude");
Serial.println(altitudeInput);
Serial.print("Altitude Offset");
Serial.println(altitudeInputOffset);*/
ControlMotors();
}
double CalculatePID(double Input, double Kp, double Ki, double Kd, double Setpoint, double &LastError, double &Integral) {
double error = Setpoint - Input;
//Serial.println(error);
Integral += error * interval;
double derivative = (error - LastError) / interval;
double Output = Kp * error + Ki * Integral + Kd * derivative;
LastError = error;
return Output;
}
void ControlMotors() {
sp1 = rollOutput + pitchOutput + altitudeOutput + yawOutput;
sp2 = rollOutput + -pitchOutput + altitudeOutput - yawOutput;
sp3 = -rollOutput + pitchOutput + altitudeOutput - yawOutput;
sp4 = -rollOutput - pitchOutput + altitudeOutput + yawOutput;
sp1 = constrain(sp1, 0, 360) / 2;
sp2 = constrain(sp2, 0, 360) / 2;
sp3 = constrain(sp3, 0, 360) / 2;
sp4 = constrain(sp4, 0, 360) / 2;
/*Serial.print("left front");
Serial.println(sp1);
Serial.print("ritght front");
Serial.println(sp2);
Serial.print("right backt");
Serial.println(sp3);
Serial.print("left back");
Serial.println(sp4);*/
escLF.write(sp1);
escRF.write(sp2);
escRB.write(sp3);
escLB.write(sp4);
}
void GetRadio(double& yawSetpoint, double& rollSetpoint, double& pitchSetpoint, double& altitudeSetpoint, double& seaLevelPres, bool& camera) {
if (radio.available()) {
radio.read(&data, sizeof(data));
yawSetpoint = data[0];
altitudeSetpoint = data[1];
rollSetpoint = data[2];
pitchSetpoint = data[3];
seaLevelPres = data[4];
camera = data[5];
}
radio.stopListening();
radio.openWritingPipe(address[1]);
altitude = int(altitudeInput);
radio.write(&altitude, sizeof(altitude));
radio.openReadingPipe(1, address[0]);
radio.startListening();
}
void InitInput() {
Serial.begin(115200);
radio.begin();
Wire.begin();
bme.begin();
escLF.attach(esc1, 1000, 2000);
escRF.attach(esc2, 1000, 2000);
escRB.attach(esc3, 1000, 2000);
escLB.attach(esc4, 1000, 2000);
Wire.beginTransmission(MPU_addr);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission(true);
//gyro config
Wire.beginTransmission(MPU_addr);
Wire.write(0x1B);
Wire.write(0x10);
Wire.endTransmission(true);
//accelometer config
Wire.beginTransmission(MPU_addr);
Wire.write(0x1C);
Wire.write(0x10);
Wire.endTransmission(true);
radio.openWritingPipe(address[1]);
radio.openReadingPipe(1, address[0]);
radio.setPALevel(RF24_PA_MIN);
radio.startListening();
currentGyroMillis = millis();
if (rollAccOffset == 0) {
for (int i = 0; i < 200; i++) {
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr, 6, true);
xAcc = (int16_t(Wire.read() << 8 | Wire.read())) / 4096.0 ;
yAcc = (int16_t(Wire.read() << 8 | Wire.read())) / 4096.0 ;
zAcc = (int16_t(Wire.read() << 8 | Wire.read())) / 4096.0 ;
pitchAccOffset += (atan((yAcc) / sqrt(pow((xAcc), 2) + pow((zAcc), 2))) * RAD_TO_DEG);
rollAccOffset += (atan(-1 * (xAcc) / sqrt(pow((yAcc), 2) + pow((zAcc), 2))) * RAD_TO_DEG);
if (i == 199) {
rollAccOffset = rollAccOffset / 200;
pitchAccOffset = pitchAccOffset / 200;
}
}
}
if (rollGyroOffset == 0) {
for (int i = 0; i < 200; i++) {
Wire.beginTransmission(MPU_addr);
Wire.write(0x43);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr, 6, true);
xGyro = (int16_t(Wire.read() << 8 | Wire.read()));
yGyro = (int16_t(Wire.read() << 8 | Wire.read()));
zGyro = (int16_t(Wire.read() << 8 | Wire.read()));
rollGyroOffset += yGyro / 32.8 ;
pitchGyroOffset += xGyro / 32.8;
yawGyroOffset += zGyro / 32.8;
if (i == 199) {
rollGyroOffset = rollGyroOffset / 200;
pitchGyroOffset = pitchGyroOffset / 200;
yawGyroOffset = yawGyroOffset / 200;
}
}
}
}
void ReadSensors(double& pitchInput, double& rollInput, double& yawInput, double& altitudeInput) {
//IMU
previousGyroMillis = currentGyroMillis;
currentGyroMillis = millis();
deltaGyroTime = (currentGyroMillis - previousGyroMillis) / 1000;
//gyro
Wire.beginTransmission(MPU_addr);
Wire.write(0x43);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr, 6, true);
xGyro = (int16_t(Wire.read() << 8 | Wire.read()));
yGyro = (int16_t(Wire.read() << 8 | Wire.read()));
zGyro = (int16_t(Wire.read() << 8 | Wire.read()));
xGyro = (xGyro / 32.8) - pitchGyroOffset;
yGyro = (yGyro / 32.8) - rollGyroOffset;
zGyro = (zGyro / 32.8) - yawGyroOffset;
pitchInputGyro = xGyro * deltaGyroTime ;
rollInputGyro = yGyro * deltaGyroTime;
yawInputGyro = zGyro * deltaGyroTime;
//accelometer
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr, 6, true);
xAcc = (int16_t(Wire.read() << 8 | Wire.read())) / 4096.0 ;
yAcc = (int16_t(Wire.read() << 8 | Wire.read())) / 4096.0 ;
zAcc = (int16_t(Wire.read() << 8 | Wire.read())) / 4096.0 ;
pitchInputAcc = (atan((yAcc) / sqrt(pow((xAcc), 2) + pow((zAcc), 2))) * RAD_TO_DEG) - pitchAccOffset;
rollInputAcc = (atan(-1 * (xAcc) / sqrt(pow((yAcc), 2) + pow((zAcc), 2))) * RAD_TO_DEG) - rollAccOffset;
//complementary filter
rollInput = 0.98 * (rollInput + rollInputGyro) + 0.02 * (rollInputAcc);
pitchInput = 0.98 * (pitchInput + pitchInputGyro) + 0.02 * (pitchInputAcc);
yawInputAcc = atan2((sin(rollInput) * cos(pitchInput) * xAcc + sin(pitchInput) * yAcc + cos(rollInput) * cos(pitchInput) * zAcc), sqrt(pow(sin(rollInput) * sin(pitchInput) * xAcc - cos(rollInput) * sin(pitchInput) * zAcc, 2) + pow(cos(pitchInput) * xAcc, 2))) - 1;
yawInput = 0.98 * (yawInput + yawInputGyro) + 0.02 * (yawInputAcc);
//altitudemeter
//bmp280
float temp(NAN), hum(NAN), pres(NAN);
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
BME280::PresUnit presUnit(BME280::PresUnit_Pa);
bme.read(pres, temp, hum, tempUnit, presUnit);
pres = pres / 100;
counter += 1;
//kalmans filter
float xPredTemp = xTemp;
float pPredTemp = pTemp + QTemp;
float xPredPres = xPres;
float pPredPres = pPres + QPres;
// VĂ˝poÄet Kalmanova zesĂlenĂ
kTemp = pPredTemp / (pPredTemp + RTemp);
kPres = pPredPres / (pPredPres + RPres);
// Korekce stavu
xTemp = xPredTemp + kTemp * (temp - xPredTemp );
xPres = xPredPres + kPres * (pres - xPredPres );
// Aktualizace kovariance stavu
pTemp = (1 - kTemp) * pPredTemp ;
pPres = (1 - kPres) * pPredPres ;
altitudeInput = (((pow((1013.15 / xPres), 1 / 5.257) - 1) * (xTemp + 273.15)) / 0.0065);
if (altitudeInputOffset == 0 && counter == 100) {
altitudeInputOffset = altitudeInput;
}
altitudeInput -= altitudeInputOffset;
altitudeInput = constrain(altitudeInput, 0, 1000);
}
Well, a simplistic answer looks like reducing interval to less than 35 might work:
A different option might be to split the 4 CalculatePID calls into separate, staggered intervals, or to sequence through them.
Since the 4 PID calculations are all done at one time, once every 36ms, it doesn't make much sense to GetRadio, ReadSensors, and ControlMotors more often than the calculation-- only the last GetRadio and ReadSensors before the calculations matter, and you only need to update the motors once after the calculation. You could split the 36ms (or maybe a shorter time) into 7 separate tasks and do them on different slices.
Do you know how long each task takes?
I'd like to, but that's the lowest value the loop takes.
I checked the times for each function and found that GetRadio takes 32 ms.
Results:
ReadSensors = 5ms
43, 44, 50,10234, 44 micros - 4x CalculatePID < 1ms
108, 100, 99, 10293, 100 micros - ControlMotors < 1ms
GetRadio = 32ms
Thatâs interesting
I thing the way forward would be to poll the radios less often and do the sensor-calc-motor update more frequently. If you read the radios every 100ms, you could move the control loop down to 10ms.
How fast is the physics of your system?
That would be great, but wouldn't that be too much? I read on the internet that the speed shouldn't be that high either. I was thinking about 20ms.
I'm sorry, but I don't understand the question.
The 20ms from the collective wisdom of the internet is a good answer to my physics question. Use 20ms
Okay, thanks. I've also read about the limitation of the integral part in PID. My code after modification looks like this:
double CalculatePID(double Input, double Kp, double Ki, double Kd, double Setpoint, double &LastError, double &Integral, double IntegralLimit) {
double error = Setpoint - Input;
//Serial.println(error);
Integral += error * interval;
//Serial.println(Integral);
Integral = constrain(Integral, -IntegralLimit, IntegralLimit);
double derivative = (error - LastError);
double Output = Kp * error + Ki * Integral + Kd * derivative;
LastError = error;
return Output;
}
I'm not sure how big the IntegralLimit should be. Is there some rule about percentages of integral or something?
Limiting the integral is done to minimize integral windup that ends up being un-wound with oscillations over the setpoint.
One way people do it is to multiply it by Ki to get it into output units before integrating, and limit the result to the output limits, that method makes the limit adaptive to changes in the parameters. The Arduino PID_v1 library uses this and explains it well in
http://brettbeauregard.com/blog/2011/04/improving-the-beginnerâs-pid-reset-windup/
I'm partial to the conditional integration and back-calculation schemes, where the limit is related to the other components-- If the proportional term Kp*error is enough to get you to the output limits, you shouldn't bank integral signal for later.
or
and
So the best/easiest solution for my problem would be using PID_v1 library?
Not necessarily. Youâve written lots of a pid. I was trying to answer your q on how to limit the integral term.
With the right tuning parameters, the system might not have a windup problem.
Okay, thanks for your help.
I would like to add a voltage divider to my drone to measure the battery voltage (fully charged voltage is 12.4 V). This is my first time using a voltage divider, so I would like to make sure that the R1= 1M ohm and R2=330k ohm values I calculated are ok for the ESP32.
Schematic?
The output would be either
12.4*1000/(1000+330)
Or
12.4*330/(1000+330)
âŚDepending on the schematic.