I am attempting to build a Raspberry Pi based quadcopter. So far I have succeeded in interfacing with all the hardware, and I have written a PID controller that is fairly stable at low throttle. The problem is that at higher throttle the quadcopter starts thrashing and jerking. I have not even been able to get it off the ground yet, all my testing has been done on a test bench. I have ruled out bad sensors by testing each sensor individually, and they seem to be giving correct values. Is this a problem with my code, or perhaps a bad motor? Any suggestions are greatly appreciated.
Here is my code so far:
QuadServer.java:
package com.zachary.quadserver; import java.net.*; import java.io.*; import java.util.*; import se.hirt.pi.adafruit.pwm.PWMDevice; import se.hirt.pi.adafruit.pwm.PWMDevice.PWMChannel; public class QuadServer { private static Sensor sensor = new Sensor(); private final static int FREQUENCY = 490; private static double PX = 0; private static double PY = 0; private static double IX = 0; private static double IY = 0; private static double DX = 0; private static double DY = 0; private static double kP = 1.3; private static double kI = 2; private static double kD = 0; private static long time = System.currentTimeMillis(); private static double last_errorX = 0; private static double last_errorY = 0; private static double outputX; private static double outputY; private static int val[] = new int[4]; private static int throttle; static double setpointX = 0; static double setpointY = 0; static long receivedTime = System.currentTimeMillis(); public static void main(String[] args) throws IOException, NullPointerException { PWMDevice device = new PWMDevice(); device.setPWMFreqency(FREQUENCY); PWMChannel BR = device.getChannel(12); PWMChannel TR = device.getChannel(13); PWMChannel TL = device.getChannel(14); PWMChannel BL = device.getChannel(15); DatagramSocket serverSocket = new DatagramSocket(8080); Thread read = new Thread(){ public void run(){ while(true) { try { byte receiveData[] = new byte[1024]; DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); serverSocket.receive(receivePacket); String message = new String(receivePacket.getData()); throttle = (int)(Integer.parseInt((message.split("\\s+")[4]))*12.96)+733; setpointX = Integer.parseInt((message.split("\\s+")[3]))-50; setpointY = Integer.parseInt((message.split("\\s+")[3]))-50; receivedTime = System.currentTimeMillis(); } catch (IOException e) { e.printStackTrace(); } } } }; read.start(); while(true) { Arrays.fill(val, calculatePulseWidth((double)throttle/1000, FREQUENCY)); double errorX = -sensor.readGyro(0)-setpointX; double errorY = sensor.readGyro(1)-setpointY; double dt = (double)(System.currentTimeMillis()-time)/1000; double accelX = sensor.readAccel(0); double accelY = sensor.readAccel(1); double accelZ = sensor.readAccel(2); double hypotX = Math.sqrt(Math.pow(accelX, 2)+Math.pow(accelZ, 2)); double hypotY = Math.sqrt(Math.pow(accelY, 2)+Math.pow(accelZ, 2)); double accelAngleX = Math.toDegrees(Math.asin(accelY/hypotY)); double accelAngleY = Math.toDegrees(Math.asin(accelX/hypotX)); if(dt > 0.01) { PX = errorX; PY = errorY; IX += errorX*dt; IY += errorY*dt; IX = 0.95*IX+0.05*accelAngleX; IY = 0.95*IY+0.05*accelAngleY; DX = (errorX - last_errorX)/dt; DY = (errorY - last_errorY)/dt; outputX = kP*PX+kI*IX+kD*DX; outputY = kP*PY+kI*IY+kD*DY; time = System.currentTimeMillis(); } System.out.println(setpointX); add(-outputX+outputY, 0); add(-outputX-outputY, 1); add(outputX-outputY, 2); add(outputX+outputY, 3); //System.out.println(val[0]+", "+val[1]+", "+val[2]+", "+val[3]); if(System.currentTimeMillis()-receivedTime < 1000) { BR.setPWM(0, val[0]); TR.setPWM(0, val[1]); TL.setPWM(0, val[2]); BL.setPWM(0, val[3]); } else { BR.setPWM(0, 1471); TR.setPWM(0, 1471); TL.setPWM(0, 1471); BL.setPWM(0, 1471); } } } private static void add(double value, int i) { value = calculatePulseWidth(value/1000, FREQUENCY); if(val[i]+value > 1471 && val[i]+value < 4071) { val[i] += value; }else if(val[i]+value < 1471) { //System.out.println("low"); val[i] = 1471; }else if(val[i]+value > 4071) { //System.out.println("low"); val[i] = 4071; } } private static int calculatePulseWidth(double millis, int frequency) { return (int) (Math.round(4096 * millis * frequency/1000)); } } Sensor.java:
package com.zachary.quadserver; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPinDigitalOutput; import com.pi4j.io.gpio.PinState; import com.pi4j.io.gpio.RaspiPin; import com.pi4j.io.i2c.*; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPinDigitalOutput; import com.pi4j.io.gpio.PinState; import com.pi4j.io.gpio.RaspiPin; import com.pi4j.io.i2c.*; import java.net.*; import java.io.*; public class Sensor { static I2CDevice sensor; static I2CBus bus; static byte[] accelData, gyroData; static long accelCalib[] = new long[3]; static long gyroCalib[] = new long[3]; static double gyroX = 0; static double gyroY = 0; static double gyroZ = 0; static double accelX; static double accelY; static double accelZ; static double angleX; static double angleY; static double angleZ; public Sensor() { //System.out.println("Hello, Raspberry Pi!"); try { bus = I2CFactory.getInstance(I2CBus.BUS_1); sensor = bus.getDevice(0x68); sensor.write(0x6B, (byte) 0x0); sensor.write(0x6C, (byte) 0x0); System.out.println("Calibrating..."); calibrate(); Thread sensors = new Thread(){ public void run(){ try { readSensors(); } catch (IOException e) { System.out.println(e.getMessage()); } } }; sensors.start(); } catch (IOException e) { System.out.println(e.getMessage()); } } private static void readSensors() throws IOException { long time = System.currentTimeMillis(); long sendTime = System.currentTimeMillis(); while (true) { accelData = new byte[6]; gyroData = new byte[6]; int r = sensor.read(0x3B, accelData, 0, 6); accelX = (((accelData[0] << 8)+accelData[1]-accelCalib[0])/16384.0)*9.8; accelY = (((accelData[2] << 8)+accelData[3]-accelCalib[1])/16384.0)*9.8; accelZ = ((((accelData[4] << 8)+accelData[5]-accelCalib[2])/16384.0)*9.8)+9.8; accelZ = 9.8-Math.abs(accelZ-9.8); double hypotX = Math.sqrt(Math.pow(accelX, 2)+Math.pow(accelZ, 2)); double hypotY = Math.sqrt(Math.pow(accelY, 2)+Math.pow(accelZ, 2)); double accelAngleX = Math.toDegrees(Math.asin(accelY/hypotY)); double accelAngleY = Math.toDegrees(Math.asin(accelX/hypotX)); //System.out.println((int)gyroX+", "+(int)gyroY); //System.out.println("accelX: " + accelX+" accelY: " + accelY+" accelZ: " + accelZ); r = sensor.read(0x43, gyroData, 0, 6); if(System.currentTimeMillis()-time >= 5) { gyroX = (((gyroData[0] << 8)+gyroData[1]-gyroCalib[0])/131.0); gyroY = (((gyroData[2] << 8)+gyroData[3]-gyroCalib[1])/131.0); gyroZ = (((gyroData[4] << 8)+gyroData[5]-gyroCalib[2])/131.0); angleX += gyroX*(System.currentTimeMillis()-time)/1000; angleY += gyroY*(System.currentTimeMillis()-time)/1000; angleZ += gyroZ; angleX = 0.95*angleX + 0.05*accelAngleX; angleY = 0.95*angleY + 0.05*accelAngleY; time = System.currentTimeMillis(); } //System.out.println((int)angleX+", "+(int)angleY); //System.out.println((int)accelAngleX+", "+(int)accelAngleY); } } public static void calibrate() throws IOException { int i; for(i = 0; i < 3000; i++) { accelData = new byte[6]; gyroData = new byte[6]; int r = sensor.read(0x3B, accelData, 0, 6); accelCalib[0] += (accelData[0] << 8)+accelData[1]; accelCalib[1] += (accelData[2] << 8)+accelData[3]; accelCalib[2] += (accelData[4] << 8)+accelData[5]; r = sensor.read(0x43, gyroData, 0, 6); gyroCalib[0] += (gyroData[0] << 8)+gyroData[1]; gyroCalib[1] += (gyroData[2] << 8)+gyroData[3]; gyroCalib[2] += (gyroData[4] << 8)+gyroData[5]; try { Thread.sleep(1); } catch (Exception e){ e.printStackTrace(); } } gyroCalib[0] /= i; gyroCalib[1] /= i; gyroCalib[2] /= i; accelCalib[0] /= i; accelCalib[1] /= i; accelCalib[2] /= i; System.out.println(gyroCalib[0]+", "+gyroCalib[1]+", "+gyroCalib[2]); } public double readAngle(int axis) { switch (axis) { case 0: return angleX; case 1: return angleY; case 2: return angleZ; } return 0; } public double readGyro(int axis) { switch (axis) { case 0: return gyroX; case 1: return gyroY; case 2: return gyroZ; } return 0; } public double readAccel(int axis) { switch (axis) { case 0: return accelX; case 1: return accelY; case 2: return accelZ; } return 0; } } Edit:
I have re-written my code in C++ to see if it will run faster but it's still running at about the same speed(about 15 ms per cycle or about 66 Hz).
This is my new code in C++:
#include <wiringPi.h> #include <wiringPiI2C.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <string> #include <iostream> #include <unistd.h> #include <boost/thread.hpp> #include <time.h> #include <cmath> #define axisX 0 #define axisY 1 #define axisZ 2 #define kP 20 #define kI 0 #define kD 0 #define FREQUENCY 490 #define MODE1 0x00 #define MODE2 0x01 #define SUBADR1 0x02 #define SUBADR2 0x03 #define SUBADR13 0x04 #define PRESCALE 0xFE #define LED0_ON_L 0x06 #define LED0_ON_H 0x07 #define LED0_OFF_L 0x08 #define LED0_OFF_H 0x09 #define ALL_LED_ON_L 0xFA #define ALL_LED_ON_H 0xFB #define ALL_LED_OFF_L 0xFC #define ALL_LED_OFF_H 0xFD // Bits #define RESTART 0x80 #define SLEEP 0x10 #define ALLCALL 0x01 #define INVRT 0x10 #define OUTDRV 0x04 #define BILLION 1000000000L using namespace std; double accelCalX = 0; double accelCalY = 0; double accelCalZ = 0; double gyroCalX = 0; double gyroCalY = 0; double gyroCalZ = 0; double PX; double PY; double IX = 0; double IY = 0; double DX; double DY; double lastErrorX; double lastErrorY; int throttle = 1471; int sensor = wiringPiI2CSetup(0x68); int pwm = wiringPiI2CSetup(0x40); array<int,4> motorVal; struct timespec now, then; int toSigned(int unsignedVal) { int signedVal = unsignedVal; if(unsignedVal > 32768) { signedVal = -(32768-(unsignedVal-32768)); } return signedVal; } double getAccel(int axis) { double X = (toSigned((wiringPiI2CReadReg8(sensor, 0x3B) << 8)+wiringPiI2CReadReg8(sensor, 0x3C)))/1671.8; double Y = (toSigned((wiringPiI2CReadReg8(sensor, 0x3D) << 8)+wiringPiI2CReadReg8(sensor, 0x3E)))/1671.8; double Z = (toSigned((wiringPiI2CReadReg8(sensor, 0x3F) << 8)+wiringPiI2CReadReg8(sensor, 0x40)))/1671.8; X -= accelCalX; Y -= accelCalY; Z -= accelCalZ; Z = 9.8-abs(Z-9.8); switch(axis) { case axisX: return X; case axisY: return Y; case axisZ: return Z; } } double getGyro(int axis) { double X = (toSigned((wiringPiI2CReadReg8(sensor, 0x43) << 8)+wiringPiI2CReadReg8(sensor, 0x44)))/1671.8; double Y = (toSigned((wiringPiI2CReadReg8(sensor, 0x45) << 8)+wiringPiI2CReadReg8(sensor, 0x46)))/1671.8; double Z = (toSigned((wiringPiI2CReadReg8(sensor, 0x47) << 8)+wiringPiI2CReadReg8(sensor, 0x48)))/1671.8; X -= gyroCalX; Y -= gyroCalY; Z -= gyroCalZ; switch(axis) { case axisX: return X; case axisY: return Y; case axisZ: return Z; } } void calibrate() { int i; for(i = 0; i < 1500; i++) { accelCalX += (toSigned((wiringPiI2CReadReg8(sensor, 0x3B) << 8)+wiringPiI2CReadReg8(sensor, 0x3C)))/1671.8; accelCalY += (toSigned((wiringPiI2CReadReg8(sensor, 0x3D) << 8)+wiringPiI2CReadReg8(sensor, 0x3E)))/1671.8; accelCalZ += (toSigned((wiringPiI2CReadReg8(sensor, 0x3F) << 8)+wiringPiI2CReadReg8(sensor, 0x40)))/1671.8; gyroCalX += (toSigned((wiringPiI2CReadReg8(sensor, 0x43) << 8)+wiringPiI2CReadReg8(sensor, 0x44)))/1671.8; gyroCalX += (toSigned((wiringPiI2CReadReg8(sensor, 0x45) << 8)+wiringPiI2CReadReg8(sensor, 0x46)))/1671.8; gyroCalX += (toSigned((wiringPiI2CReadReg8(sensor, 0x45) << 8)+wiringPiI2CReadReg8(sensor, 0x46)))/1671.8; usleep(1000); } accelCalX /= i; accelCalY /= i; accelCalZ /= i; accelCalZ -= 9.8; gyroCalX /= i; gyroCalY /= i; gyroCalZ /= i; cout << accelCalX << " " << accelCalY << " " << accelCalZ << "\n"; } int calculatePulseWidth(double millis, int frequency) { return (int)(floor(4096 * millis * frequency/1000)); } void add(double value, int i) { value = calculatePulseWidth(value/1000, FREQUENCY); if(motorVal[i]+value > 1471 && motorVal[i]+value < 4071) { motorVal[i] += value; }else if(motorVal[i]+value < 1471) { //System.out.println("low"); motorVal[i] = 1471; }else if(motorVal[i]+value > 4071) { //System.out.println("low"); motorVal[i] = 4071; } } void getThrottle() { int sockfd,n; struct sockaddr_in servaddr,cliaddr; socklen_t len; char mesg[1000]; sockfd=socket(AF_INET,SOCK_DGRAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(8080); bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); while(true) { len = sizeof(cliaddr); n = recvfrom(sockfd,mesg,1000,0,(struct sockaddr *)&cliaddr,&len); mesg[n] = 0; string message(mesg); string values[5]; int valIndex = 0; int lastIndex = 0; for(int i = 0; i < message.length(); i++) { if(message[i] == ' ') { values[valIndex] = message.substr(lastIndex+1, i); lastIndex = i; valIndex++; } } values[valIndex] = message.substr(lastIndex+1, message.length()); throttle = calculatePulseWidth(((stoi(values[4])*12.96)+733)/1000, FREQUENCY); } } void setAllPWM(int on, int off) { wiringPiI2CWriteReg8(pwm, ALL_LED_ON_L, (on & 0xFF)); wiringPiI2CWriteReg8(pwm, ALL_LED_ON_H, (on >> 8)); wiringPiI2CWriteReg8(pwm, ALL_LED_OFF_L, (off & 0xFF)); wiringPiI2CWriteReg8(pwm, ALL_LED_OFF_H, (off >> 8)); } void setPWM(int on, int off, int channel) { wiringPiI2CWriteReg8(pwm, LED0_ON_L + 4 * channel, (on & 0xFF)); wiringPiI2CWriteReg8(pwm, LED0_ON_H + 4 * channel, (on >> 8)); wiringPiI2CWriteReg8(pwm, LED0_OFF_L + 4 * channel, (off & 0xFF)); wiringPiI2CWriteReg8(pwm, LED0_OFF_H + 4 * channel, (off >> 8)); } void setPWMFrequency(double frequency) { double prescaleval = 25000000.0; prescaleval /= 4096.0; prescaleval /= frequency; prescaleval -= 1.0; double prescale = floor(prescaleval + 0.5); int oldmode = wiringPiI2CReadReg8(pwm, MODE1); int newmode = (oldmode & 0x7F) | 0x10; wiringPiI2CWriteReg8(pwm, MODE1, newmode); wiringPiI2CWriteReg8(pwm, PRESCALE, (floor(prescale))); wiringPiI2CWriteReg8(pwm, MODE1, oldmode); usleep(50000); wiringPiI2CWriteReg8(pwm, MODE1, (oldmode | 0x80)); } void initSensor() { wiringPiI2CWriteReg8(sensor, 0x6B, 0x0); wiringPiI2CWriteReg8(sensor, 0x6C, 0x0); } void initPWM() { setAllPWM(0, 0); wiringPiI2CWriteReg8(pwm, MODE2, OUTDRV); wiringPiI2CWriteReg8(pwm, MODE1, ALLCALL); usleep(50000); int mode1 = wiringPiI2CReadReg8(pwm, MODE1); mode1 = mode1 & ~SLEEP; wiringPiI2CWriteReg8(pwm, MODE1, mode1); usleep(50000); setPWMFrequency(FREQUENCY); } double millis(timespec time) { return (time.tv_sec*1000)+(time.tv_nsec/1.0e6); } double intpow( double base, int exponent ) { int i; double out = base; for( i=1 ; i < exponent ; i++ ) { out *= base; } return out; } int main (void) { initSensor(); initPWM(); cout << "Calibrating..." << "\n"; calibrate(); boost::thread server(getThrottle); clock_gettime(CLOCK_MONOTONIC, &then); while(true) { motorVal.fill(throttle); clock_gettime(CLOCK_MONOTONIC, &now); double dt = (millis(now)-millis(then))/1000; then = now; double accelX = getAccel(0); double accelY = getAccel(1); double accelZ = getAccel(2); double hypotX = sqrt(intpow(accelX, 2)+intpow(accelZ, 2)); double hypotY = sqrt(intpow(accelY, 2)+intpow(accelZ, 2)); double accelAngleX = (180/3.14)*(asin(accelY/hypotY)); double accelAngleY = (180/3.14)*(asin(accelX/hypotX)); double errorX = -getGyro(0); double errorY = getGyro(1); PX = errorX; PY = errorY; IX += errorX*dt; IY += errorY*dt; IX = 0.95*IX+0.05*accelAngleX; IY = 0.95*IY+0.05*accelAngleY; DX = (errorX-lastErrorX)*dt; DY = (errorY-lastErrorY)*dt; lastErrorX = errorX; lastErrorY = errorY; double outputX = kP*PX+kI*IX+kD*DX; double outputY = kP*PY+kI*IY+kD*DY; add(outputY, 0);//-outputX+ add(outputY, 1);//-outputX- add(outputY, 2);//outputX- add(outputY, 3);//outputX+ setPWM(0, motorVal[0], 12); setPWM(0, motorVal[1], 13); setPWM(0, motorVal[2], 14); setPWM(0, motorVal[3], 15); } } In addition two of the motors seem like they are lagging when I turn the quadcopter fast in one direction. Also for some strange reason the quadcopter seems less responsive to P gain; I have it at 20 in the C++ version and it is working about the same as when I had it at 1.5 in the java version.
Edit:
After doing some more testing I have determined that reading from the MPU6050 and writing to the PCA9685 board that I am using to control the ESCs is the source of the delay. Does anybody know how to speed this up?
Edit:
I managed to speed up my code to about 200 Hz by changing the i2c baud rate, but the quadcopter is still thrashing. I have spent hours trying to tune the pid controller, but it doesn't seem to help at all.