For those who don’t know what is Frequency Hopping see this Frequency Hopping .It is a nice feature for secure communication.FHSS is unhackable (though it also depends on how randomly you change the frequencies )

I have tried Frequency (Channel) Hopping with the Auto Acknowledgement feature of nrf24l01 so that I can change the frequencies on both Tx and Rx devices in synchronism. Sometimes there is a loss of synchronism (possible “different” latencies of the mcus [atmega328p] ).In that case, I manually set them using Serial of Arduino IDE.

In the given example I am sending a 32-byte array of data and hopping linearly between channels 90 and 125 (back and forth with an increment of 2 ) each new array of data is Transmitted on a different channel (frequency).

Transmitter Code

void loop() /****** LOOP: RUNS CONSTANTLY ******/ { if ( myRadio1.write( &tx, sizeof(tx) )) { if (myRadio1.isAckPayloadAvailable()) { myRadio1.read(&ack, sizeof(ack)); Serial.print(" Transmitted & Ack Recevied for data: "); for (int j = 0; j < 32; j++) { Serial.print(ack[j]); Serial.print(" "); } } for (int j = 0; j < 32; j++) tx[j]++; if (flag == 0) { i += 2; } else { i -= 2; } if ((i > 124) && (flag == 0)) { flag = 1; } else if ((i < 90) && (flag == 1)) { flag = 0; } myRadio1.setChannel(i); Serial.print(" New Channel "); Serial.println(i); } if (Serial.available()) { i = Serial.parseInt(); myRadio1.setChannel(i); } }

Receiver Code

void loop() { if (myRadio2.available()) { myRadio2.read( &rx, sizeof(rx)); myRadio2.writeAckPayload(1, &rx, sizeof(rx)); Serial.print(" Data received & Ack sent for data = "); for (int j = 0; j < 32; j++) { Serial.print(rx[j]); Serial.print(" "); } if (flag == 0) { i += 2; } else { i -= 2; } if ((i > 124) && (flag == 0)) { flag = 1; } else if ((i < 90) && (flag == 1)) { flag = 0; } myRadio2.setChannel(i); Serial.print(" New Channel "); Serial.println(i); } if (Serial.available()) { i = Serial.parseInt(); myRadio2.setChannel(i); } }

The codes are still not very good. Just an example of FH with Nrf24l01+. Find the full code (not refined but does work) here https://github.com/shubhampaul/Frequency-Hopping-with-NRF24L01 Here is the demo video: Youtube

]]>Now after connecting all the hardware to the respective pins we start integrating the software for : –

**1.Getting MPU readings** (discussed before)

**2.Getting gps readings** – using the codelines discussed before and using smartDelay() for updation,**I have also attached a switch at pin 46 to change from normal to trajectory mode**:

if (gps.location.isValid()) { Lat = gps.location.lat(); Long = gps.location.lng(); } else { Lat = 12.82; /*my location in case there is no gps readings*/ Long = 80.04; } yy = gps.date.year(); //yy=2016; // Serial.println(yy); mu = gps.date.month(); //mu=5; // Serial.println(mu); dd = gps.date.day(); // dd=4; // Serial.println(dd); if (digitalRead(46) == 1) { if(gps.time.isValid()) { hh = gps.time.hour(); //Serial.println(hh); mm = gps.time.minute(); } else { hh=3; mm=0; } } else if((digitalRead(46) == 0)) { int ss=trajec(); hh = gps.time.hour(); mm = gps.time.minute(); } //smartDelay function static void smartDelay(unsigned long ms) { //Serial.println(“C”); unsigned long start = millis(); do { while (Serial2.available()) gps.encode(Serial2.read()); } while (millis() – start < ms); //Serial.println(“D”); }

**3.calculation of azimuth -altitude** : This has already been discussed , here is the consolidated code snippet (The osculating elements are mentioned seperately below )

double ipart(double xx) { //Serial.println(“IPART”); double sgn; if (xx 0) { sgn = 1.0; } double ret = sgn * ((int)fabs(xx)); return ret; } double FNdegmin(double xx) { //Serial.println(“DEGMIN”); double a = ipart(xx) ; double b = xx – a ; double e = ipart(60 * b) ; // deal with carry on minutes if ( e >= 60 ) { e = 0 ; a = a + 1 ; } return (a + (e / 100) ); } double dayno(int dx, int mx, int yx, double fx) { //Serial.println(“DAY NO”); //dno=(367 * yx) – (int)(7*(yx + (int)((mx + 9) / 12)) / 4) + (int)(275 * mx / 9) + dx – 730531.5 + fx; dno = 987 + dx + (fx / 24); //Serial.print(“\ndays:”); //Serial.println(dno); return dno; } double frange(double x) { //Serial.println(“FRANGE”); x = x / (2 * pi); x = (2 * pi) * (x – ipart(x)); if (x = pow(10, -12)); // //Serial.println(“fkepB”); // double v = 2 * atan(sqrt((1 + ecc) / (1 – ecc)) * tan(e / 2)); double v = m + (2 * e – 0.25 *pow(e,3) + 5/96 * pow(e,5)) * sin(m) + (1.25 * pow(e,2) – 11/24 * pow(e,4)) * sin(2*m) + (13/12 * pow(e,3) – 43/64 * pow(e,5)) * sin(3*m) + 103/96 * pow(e,4) * sin(4*m) + 1097/960 * pow(e,5) * sin(5*m); if (v < 0) v = v + (2 * pi); return v; } double fnatan(double x, double y) { //Serial.println(“ATAN”); double a = atan(y / x); if (x < 0) a = a + pi; if ((y 0)) a = a + (2 * pi); return a; } void AltAzCalculate(double RA, double Dec, double Lat, double Long, double hrs, double minut, double dy) { //Serial.println(“G”); // Day offset and Local Siderial Time dy = dy + 4975.5; double LST = (100.46 + 0.985647 * dy + Long + 15 * (hrs + minut / 60) + 360) – (((int)((100.46 + 0.985647 * dy + Long + 15 * (hrs + minut / 60) + 360) / 360)) * 360); // Hour Angle double HA = (LST – RA + 360) – ((int)((LST – RA + 360) / 360)) * 360 ; // HA, DEC, Lat to Alt, AZ double x = cos(HA * (pi / 180)) * cos(Dec * (pi / 180)); double y = sin(HA * (pi / 180)) * cos(Dec * (pi / 180)); double z = sin(Dec * (pi / 180)); double xhor = x * cos((90 – Lat) * (pi / 180)) – z * sin((90 – Lat) * (pi / 180)); double yhor = y; double zhor = x * sin((90 – Lat) * (pi / 180)) + z * cos((90 – Lat) * (pi / 180)); Azimuth = atan2(yhor, xhor) * (180 / pi) + 180; Elevation = asin(zhor) * (180 / pi); } void earth() { //Serial.println(“B”); M[3] = ((n[3] * rads) * d) + (L[3] – p[3]) * rads; M[3] = frange(M[3]); v[3] = fkep(M[3], e[3]); r[3] = a[3] * ((1 – (pow(e[3], 2))) / (1 + (e[3] * cos(v[3])))); x[3] = r[3] * cos(v[3] + p[3] * rads); y[3] = r[3] * sin(v[3] + p[3] * rads); z[3] = 0; } void mainCalculations() { dfrac = hh + (mm / 60); d = dayno(dd, mu, yy, dfrac); //Serial.println(“E”); earth(); //Serial.println(“F”); //Serial.println(“E”); int j; for (j = 0; j <=9; j++) { if (j == 3) continue; if(j==9); // Serial.println(“A”); M[j] = ((n[j] * rads) * d) + (L[j] – p[j]) * rads; if(j==9); // Serial.println(“B”); M[j] = frange(M[j]); if(j==9); // Serial.println(“C”); v[j] = fkep(M[j], e[j]); if(j==9); // Serial.println(“D”); r[j] = a[j] * ((1 – pow(e[j], 2)) / (1 + (e[j] * cos(v[j])))); x[j] = r[j] * (cos(o[j] * rads) * cos(v[j] + p[j] * rads – o[j] * rads) – sin(o[j] * rads) * sin(v[j] + p[j] * rads – o[j] * rads) * cos(i[j] * rads)); y[j] = r[j] * (sin(o[j] * rads) * cos(v[j] + p[j] * rads – o[j] * rads) + cos(o[j] * rads) * sin(v[j] + p[j] * rads – o[j] * rads) * cos(i[j] * rads)); z[j] = r[j] * (sin(v[j] + p[j] * rads – o[j] * rads)) * sin(i[j] * rads); Xi[j] = x[j] – x[3]; Yi[j] = y[j] – y[3]; Zi[j] = z[j]; Xq[j] = Xi[j]; Yq[j] = (Yi[j] * cos(ec)) – (Zi[j] * sin(ec)); Zq[j] = (Yi[j] * sin(ec)) + (Zi[j] * cos(ec)); ra[j] = fnatan(Xq[j], Yq[j]); dec[j] = atan(Zq[j] / sqrt(pow(Xq[j], 2.0) + pow(Yq[j], 2.0))); // Serial.println(j); } //Serial.println(“H”); double alpha = FNdegmin((ra[pno] * degs) / 15); double delta = FNdegmin(dec[pno] * degs); //Serial.println(“G”); AltAzCalculate((alpha * 15), delta, Lat, Long, hh, mm, d); }

Here are the **Osculating elements of 16th August 2013**

double i[10] = {0.0, 7.0052, 3.3949, 0.0, 1.8496, 1.3033, 2.4869, 0.7728, 1.7692, 17.1695}; double o[10] = {0.0, 48.493, 76.804, 0.0, 49.668, 100.629, 113.732, 73.989, 131.946, 110.469}; double p[10] = {0.0, 77.669, 131.99, 103.147, 336.322, 14.556, 91.500, 169.602, 6.152, 223.486}; double a[10] = {0.0, 0.387098, 0.723327, 1.0000, 1.523762, 5.20245, 9.52450, 19.1882, 29.9987, 39.2766}; double n[10] = {0.0, 4.09235, 1.60215, 0.985611, 0.523998, 0.083099, 0.033551, 0.011733, 0.006002, 0.004006}; double e[10] = {0.0, 0.205645 , 0.006769, 0.016679, 0.093346, 0.048892, 0.055724, 0.047874, 0.009816, 0.246211}; double L[10] = {0.0, 93.8725, 233.5729, 324.5489, 82.9625, 87.9728, 216.6279, 11.9756, 335.0233, 258.8717};

**4.Servo mapping and servo feed** (discussed before)

**5.Planet Selection** – I have used a small potentiometer to select different planets as :

int planetInput(int potval) { if ((potval >= 0) && (potval = 91) && (potval = 192) && (potval = 293) && (potval = 394) && (potval = 495) && (potval = 596) && (potval = 697) && (potval <= 1023)) { Serial.print(“Pluto”); return 9; } } //The Pot takes values from A14 pin ana = analogRead(A14); pno = planetInput(ana);

**6**.**Trajectory prediction** – I have used a switch (connected to **pin 46 **) to change from normal mode to trajectory mode **In this mode the servos move a little fast giving the future locations of a planet for the complete day.**Here is the snippet

int trajec() { hh=0; mm=0; while(hh<24&& (digitalRead(46) == 0)) { while(mm<60&& (digitalRead(46) == 0)) { mainCalculations(); nyaw = 360 – yaw; Azim = Azimuth – nyaw; Azim -= 90; while (Azim < 0) Azim = 360.0 – abs(Azim); Azi = map(Azim, 0, 360, 5, 29); Az = (int)Azi; Elev = map(Elevation, -90, 90, 2, 178); El = (int)Elev; myservoAz.write(Az); myservoEl.write(El);

As you can see I have just fast forwarded time manually using a loop and calculated the trajectory for a day is relayed through the servo in a **minute which makes it move faster showing a trajectory of planets.**

Here is final Rough (non – formated ) working code which includes everything :Github

*RTPT (Real Time Planet Tracking System and Trajectory Prediction) Copyright © 2016 Shubham Paul , Samhita Ganguly ,Rohit Kumar **GNU GPL3+*

Motion Processing is an important concept to know if you want to interact with real time data you should be able to interact with motion parameters such as **Linear acceleration, Angular acceleration, Magnetic North of the planet with a reference point on the object.**

MPU9250 has an accelerometer ,gyroscope and a magnetometer.The things that we can get from a processed MPU9250’s raw readings are:

**Yaw Angle****Pitch Angle****Roll Angle**

I will only deal with **Yaw ** here in this post.

This MPU has 16bit registers for each sensor that is accelerometer ,gyroscope ,magnetometer.They store the data from the sensors and the data is transmitted over I2C Serially.

**1.**We take the data serially 8 bit at a time and then concatenate them to form 16bit again.

Look at the following snippet from kriswiners code:

fifo_count = ((uint16_t)data[0] << 8) | data[1]; packet_count = fifo_count/12;// How many sets of full gyro and accelerometer data for averaging for (ii = 0; ii < packet_count; ii++) { int16_t accel_temp[3] = {0, 0, 0}, gyro_temp[3] = {0, 0, 0}; readBytes(MPU9250_ADDRESS, FIFO_R_W, 12, &data[0]); // read data for averaging accel_temp[0] = (int16_t) (((int16_t)data[0] << 8) | data[1] ) ; // Form signed 16-bit integer for each sample in FIFO accel_temp[1] = (int16_t) (((int16_t)data[2] << 8) | data[3] ) ; accel_temp[2] = (int16_t) (((int16_t)data[4] << 8) | data[5] ) ; gyro_temp[0] = (int16_t) (((int16_t)data[6] << 8) | data[7] ) ; gyro_temp[1] = (int16_t) (((int16_t)data[8] << 8) | data[9] ) ; gyro_temp[2] = (int16_t) (((int16_t)data[10] << 8) | data[11]) ; accel_bias[0] += (int32_t) accel_temp[0]; // Sum individual signed 16-bit biases to get accumulated signed 32-bit biases accel_bias[1] += (int32_t) accel_temp[1]; accel_bias[2] += (int32_t) accel_temp[2]; gyro_bias[0] += (int32_t) gyro_temp[0]; gyro_bias[1] += (int32_t) gyro_temp[1]; gyro_bias[2] += (int32_t) gyro_temp[2]; }

**2.**The data is received is calibrated according to the users environment.The calibration of magnetometer is only required because the **MAGNETIC DECLINATION **changes with location the value of calibrated magbias is according to user environment.There are two variables that has to calibrated : 1. **yaw** 2.**magbias**

The below shows **yaw **calibration for a specific magnetic declination (at Potheri,Chennai ,India).This data can be obtained from different sites :

http://geomag.nrcan.gc.ca/calc/mdcal-en.php http://www.ngdc.noaa.gov/geomag-web/

yaw = atan2(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] – q[2] * q[2] – q[3] * q[3]); pitch = -asin(2.0f * (q[1] * q[3] – q[0] * q[2])); roll = atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] – q[1] * q[1] – q[2] * q[2] + q[3] * q[3]); pitch *= 180.0f / PI; yaw *= 180.0f / PI; yaw += 1.34; /* Declination at Potheri, Chennail ,India Model Used: IGRF12 Help Latitude: 12.823640° N Longitude: 80.043518° E Date Declination 2016-04-09 1.34° W changing by 0.06° E per year(+ve for west )*/

See the below snippet [the given snippet the data for calibration of** magbias** comes from another function (magcalMPU9250(float * dest1, float * dest2)) :

readMagData(magCount); // Read the x/y/z adc values getMres(); // magbias[0] = +470.; // User environmental x-axis correction in milliGauss, should be automatically calculated // magbias[1] = +120.; // User environmental x-axis correction in milliGauss // magbias[2] = +125.; // User environmental x-axis correction in milliGauss // Calculate the magnetometer values in milliGauss // Include factory calibration per data sheet and user environmental corrections mx = (float)magCount[0]*mRes*magCalibration[0] – magBias[0]; // get actual magnetometer value, this depends on scale being set my = (float)magCount[1]*mRes*magCalibration[1] – magBias[1]; mz = (float)magCount[2]*mRes*magCalibration[2] – magBias[2];

This is one of the most simple and important part of the MPU code.The function magcalMPU9250(float * dest1, float * dest2) Reads magnetometer reading [**whe you move it in “8” shape **] it stores the maximum and minimum readings and takes its **average** . This is how **magbias** gets **auto calibrated** .The following is the code snippet :

void magcalMPU9250(float * dest1, float * dest2) { uint16_t ii = 0, sample_count = 0; int32_t mag_bias[3] = {0, 0, 0}, mag_scale[3] = {0, 0, 0}; int16_t mag_max[3] = {0x8000, 0x8000, 0x8000}, mag_min[3] = {0x7FFF, 0x7FFF, 0x7FFF}, mag_temp[3] = {0, 0, 0}; Serial.println(“Mag Calibration: Wave device in a figure eight until done!”); sample_count = 128; for(ii = 0; ii < sample_count; ii++) { readMagData(mag_temp); // Read the mag data for (int jj = 0; jj < 3; jj++) { if(mag_temp[jj] > mag_max[jj]) mag_max[jj] = mag_temp[jj]; if(mag_temp[jj] < mag_min[jj]) mag_min[jj] = mag_temp[jj]; } delay(135); // at 8 Hz ODR, new mag data is available every 125 ms } // Get hard iron correction mag_bias[0] = (mag_max[0] + mag_min[0])/2; // get average x mag bias in counts mag_bias[1] = (mag_max[1] + mag_min[1])/2; // get average y mag bias in counts mag_bias[2] = (mag_max[2] + mag_min[2])/2; // get average z mag bias in counts dest1[0] = (float) mag_bias[0]*mRes*magCalibration[0]; // save mag biases in G for main program dest1[1] = (float) mag_bias[1]*mRes*magCalibration[1]; dest1[2] = (float) mag_bias[2]*mRes*magCalibration[2]; // Get soft iron correction estimate mag_scale[0] = (mag_max[0] – mag_min[0])/2; // get average x axis max chord length in counts mag_scale[1] = (mag_max[1] – mag_min[1])/2; // get average y axis max chord length in counts mag_scale[2] = (mag_max[2] – mag_min[2])/2; // get average z axis max chord length in counts float avg_rad = mag_scale[0] + mag_scale[1] + mag_scale[2]; avg_rad /= 3.0; dest2[0] = avg_rad/((float)mag_scale[0]); dest2[1] = avg_rad/((float)mag_scale[1]); dest2[2] = avg_rad/((float)mag_scale[2]); Serial.println(“Mag Calibration done!”); }

for more details please go to the source: https://github.com/kriswiner/MPU6050/wiki/Simple-and-Effective-Magnetometer-Calibration

If you dont want to waste your time everytime by moving the device in “8” shape then you just have to note the average values of magbias[] after it has been calculated and use this snippet :

readMagData(magCount); // Read the x/y/z adc values getMres(); magbias[0] = +470.; // User environmental x-axis correction in milliGauss, should be automatically calculated magbias[1] = +120.; // User environmental x-axis correction in milliGauss magbias[2] = +125.; // User environmental x-axis correction in milliGauss // Calculate the magnetometer values in milliGauss // Include factory calibration per data sheet and user environmental corrections mx = (float)magCount[0]*mRes*magCalibration[0] – magBias[0]; // get actual magnetometer value, this depends on scale being set my = (float)magCount[1]*mRes*magCalibration[1] – magBias[1]; mz = (float)magCount[2]*mRes*magCalibration[2] – magBias[2];

The values 470,120,125 is fixed for my location So after doing this there is no need of **void magcalMPU9250(float * dest1, float * dest2) function ** so you can either comment it or remove it also dont forget to comment the calling statement as well :see the snippet below-

delay(1000); // Get magnetometer calibration from AK8963 ROM initAK8963(magCalibration); Serial.println(“AK8963 initialized for active data mode….”); // Initialize device for active mode read of magnetometer getMres(); //magcalMPU9250(magBias,magScale); // commented call statement if(SerialDebug) { // Serial.println(“Calibration values: “); Serial.print(“X-Axis sensitivity adjustment value “); Serial.println(magCalibration[0], 2); Serial.print(“Y-Axis sensitivity adjustment value “); Serial.println(magCalibration[1], 2); Serial.print(“Z-Axis sensitivity adjustment value “); Serial.println(magCalibration[2], 2); }

**3.** The calibrated data is usually in raw for resolved in x component ,y component ,z component.

4.As the data consists of a lot of **noise **we use certain Filters as (Madgwick/Mahony/Kalman) to fuse reading from accelerometer ,gyroscope and magnetometer and convert them to Quaternion.See the snippet for filter :

void MadgwickQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz) { float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3]; // short name local variable for readability float norm; float hx, hy, _2bx, _2bz; float s1, s2, s3, s4; float qDot1, qDot2, qDot3, qDot4; // Auxiliary variables to avoid repeated arithmetic float _2q1mx; float _2q1my; float _2q1mz; float _2q2mx; float _4bx; float _4bz; float _2q1 = 2.0f * q1; float _2q2 = 2.0f * q2; float _2q3 = 2.0f * q3; float _2q4 = 2.0f * q4; float _2q1q3 = 2.0f * q1 * q3; float _2q3q4 = 2.0f * q3 * q4; float q1q1 = q1 * q1; float q1q2 = q1 * q2; float q1q3 = q1 * q3; float q1q4 = q1 * q4; float q2q2 = q2 * q2; float q2q3 = q2 * q3; float q2q4 = q2 * q4; float q3q3 = q3 * q3; float q3q4 = q3 * q4; float q4q4 = q4 * q4; // Normalise accelerometer measurement norm = sqrt(ax * ax + ay * ay + az * az); if (norm == 0.0f) return; // handle NaN norm = 1.0f/norm; ax *= norm; ay *= norm; az *= norm; // Normalise magnetometer measurement norm = sqrt(mx * mx + my * my + mz * mz); if (norm == 0.0f) return; // handle NaN norm = 1.0f/norm; mx *= norm; my *= norm; mz *= norm; // Reference direction of Earth’s magnetic field _2q1mx = 2.0f * q1 * mx; _2q1my = 2.0f * q1 * my; _2q1mz = 2.0f * q1 * mz; _2q2mx = 2.0f * q2 * mx; hx = mx * q1q1 – _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 – mx * q3q3 – mx * q4q4; hy = _2q1mx * q4 + my * q1q1 – _2q1mz * q2 + _2q2mx * q3 – my * q2q2 + my * q3q3 + _2q3 * mz * q4 – my * q4q4; _2bx = sqrt(hx * hx + hy * hy); _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 – mz * q2q2 + _2q3 * my * q4 – mz * q3q3 + mz * q4q4; _4bx = 2.0f * _2bx; _4bz = 2.0f * _2bz; // Gradient decent algorithm corrective step s1 = -_2q3 * (2.0f * q2q4 – _2q1q3 – ax) + _2q2 * (2.0f * q1q2 + _2q3q4 – ay) – _2bz * q3 * (_2bx * (0.5f – q3q3 – q4q4) + _2bz * (q2q4 – q1q3) – mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 – q1q4) + _2bz * (q1q2 + q3q4) – my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f – q2q2 – q3q3) – mz); s2 = _2q4 * (2.0f * q2q4 – _2q1q3 – ax) + _2q1 * (2.0f * q1q2 + _2q3q4 – ay) – 4.0f * q2 * (1.0f – 2.0f * q2q2 – 2.0f * q3q3 – az) + _2bz * q4 * (_2bx * (0.5f – q3q3 – q4q4) + _2bz * (q2q4 – q1q3) – mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 – q1q4) + _2bz * (q1q2 + q3q4) – my) + (_2bx * q4 – _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f – q2q2 – q3q3) – mz); s3 = -_2q1 * (2.0f * q2q4 – _2q1q3 – ax) + _2q4 * (2.0f * q1q2 + _2q3q4 – ay) – 4.0f * q3 * (1.0f – 2.0f * q2q2 – 2.0f * q3q3 – az) + (-_4bx * q3 – _2bz * q1) * (_2bx * (0.5f – q3q3 – q4q4) + _2bz * (q2q4 – q1q3) – mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 – q1q4) + _2bz * (q1q2 + q3q4) – my) + (_2bx * q1 – _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f – q2q2 – q3q3) – mz); s4 = _2q2 * (2.0f * q2q4 – _2q1q3 – ax) + _2q3 * (2.0f * q1q2 + _2q3q4 – ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f – q3q3 – q4q4) + _2bz * (q2q4 – q1q3) – mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 – q1q4) + _2bz * (q1q2 + q3q4) – my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f – q2q2 – q3q3) – mz); norm = sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4); // normalise step magnitude norm = 1.0f/norm; s1 *= norm; s2 *= norm; s3 *= norm; s4 *= norm; // Compute rate of change of quaternion qDot1 = 0.5f * (-q2 * gx – q3 * gy – q4 * gz) – beta * s1; qDot2 = 0.5f * (q1 * gx + q3 * gz – q4 * gy) – beta * s2; qDot3 = 0.5f * (q1 * gy – q2 * gz + q4 * gx) – beta * s3; qDot4 = 0.5f * (q1 * gz + q2 * gy – q3 * gx) – beta * s4; // Integrate to yield quaternion q1 += qDot1 * deltat; q2 += qDot2 * deltat; q3 += qDot3 * deltat; q4 += qDot4 * deltat; norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4); // normalise quaternion norm = 1.0f/norm; q[0] = q1 * norm; q[1] = q2 * norm; q[2] = q3 * norm; q[3] = q4 * norm; } // Similar to Madgwick scheme but uses proportional and integral filtering on the error between estimated reference vectors and // measured ones. void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz) { float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3]; // short name local variable for readability float norm; float hx, hy, bx, bz; float vx, vy, vz, wx, wy, wz; float ex, ey, ez; float pa, pb, pc; // Auxiliary variables to avoid repeated arithmetic float q1q1 = q1 * q1; float q1q2 = q1 * q2; float q1q3 = q1 * q3; float q1q4 = q1 * q4; float q2q2 = q2 * q2; float q2q3 = q2 * q3; float q2q4 = q2 * q4; float q3q3 = q3 * q3; float q3q4 = q3 * q4; float q4q4 = q4 * q4; // Normalise accelerometer measurement norm = sqrt(ax * ax + ay * ay + az * az); if (norm == 0.0f) return; // handle NaN norm = 1.0f / norm; // use reciprocal for division ax *= norm; ay *= norm; az *= norm; // Normalise magnetometer measurement norm = sqrt(mx * mx + my * my + mz * mz); if (norm == 0.0f) return; // handle NaN norm = 1.0f / norm; // use reciprocal for division mx *= norm; my *= norm; mz *= norm; // Reference direction of Earth’s magnetic field hx = 2.0f * mx * (0.5f – q3q3 – q4q4) + 2.0f * my * (q2q3 – q1q4) + 2.0f * mz * (q2q4 + q1q3); hy = 2.0f * mx * (q2q3 + q1q4) + 2.0f * my * (0.5f – q2q2 – q4q4) + 2.0f * mz * (q3q4 – q1q2); bx = sqrt((hx * hx) + (hy * hy)); bz = 2.0f * mx * (q2q4 – q1q3) + 2.0f * my * (q3q4 + q1q2) + 2.0f * mz * (0.5f – q2q2 – q3q3); // Estimated direction of gravity and magnetic field vx = 2.0f * (q2q4 – q1q3); vy = 2.0f * (q1q2 + q3q4); vz = q1q1 – q2q2 – q3q3 + q4q4; wx = 2.0f * bx * (0.5f – q3q3 – q4q4) + 2.0f * bz * (q2q4 – q1q3); wy = 2.0f * bx * (q2q3 – q1q4) + 2.0f * bz * (q1q2 + q3q4); wz = 2.0f * bx * (q1q3 + q2q4) + 2.0f * bz * (0.5f – q2q2 – q3q3); // Error is cross product between estimated direction and measured direction of gravity ex = (ay * vz – az * vy) + (my * wz – mz * wy); ey = (az * vx – ax * vz) + (mz * wx – mx * wz); ez = (ax * vy – ay * vx) + (mx * wy – my * wx); if (Ki > 0.0f) { eInt[0] += ex; // accumulate integral error eInt[1] += ey; eInt[2] += ez; } else { eInt[0] = 0.0f; // prevent integral wind up eInt[1] = 0.0f; eInt[2] = 0.0f; } // Apply feedback terms gx = gx + Kp * ex + Ki * eInt[0]; gy = gy + Kp * ey + Ki * eInt[1]; gz = gz + Kp * ez + Ki * eInt[2]; // Integrate rate of change of quaternion pa = q2; pb = q3; pc = q4; q1 = q1 + (-q2 * gx – q3 * gy – q4 * gz) * (0.5f * deltat); q2 = pa + (q1 * gx + pb * gz – pc * gy) * (0.5f * deltat); q3 = pb + (q1 * gy – pa * gz + pc * gx) * (0.5f * deltat); q4 = pc + (q1 * gz + pa * gy – pb * gx) * (0.5f * deltat); // Normalise quaternion norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4); norm = 1.0f / norm; q[0] = q1 * norm; q[1] = q2 * norm; q[2] = q3 * norm; q[3] = q4 * norm; }

**5.**As the data changes very rapidly we take the data for some amout of time and take its average .We do it in following snippet of the code (its 50ms for the below code)

count = millis(); digitalWrite(myLed, !digitalRead(myLed)); // toggle led } } else { // Serial print and/or display at 0.5 s rate independent of data rates delt_t = millis() – count; if (delt_t > 50) { // update once per half-second independent of read rate if(SerialDebug) {

**6.**Finally we get the readings in form of yaw pitch and roll from the quaternions.

yaw = atan2(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] – q[2] * q[2] – q[3] * q[3]); pitch = -asin(2.0f * (q[1] * q[3] – q[0] * q[2])); roll = atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] – q[1] * q[1] – q[2] * q[2] + q[3] * q[3]); pitch *= 180.0f / PI; yaw *= 180.0f / PI; yaw += 1.34; /* Declination at Potheri, Chennail ,India Model Used: IGRF12 Help Latitude: 12.823640° N Longitude: 80.043518° E Date Declination 2016-04-09 1.34° W changing by 0.06° E per year (+ve for west )*/ roll *= 180.0f / PI; Serial.print(“Yaw, Pitch, Roll: “); Serial.print(yaw+180, 2); Serial.print(“, “); Serial.print(pitch, 2); Serial.print(“, “); Serial.println(roll, 2);

We have lots of libraries for MPU9250.One of the popular one is given by kriswiner

MPU-9250 Arduino Library by Kriswiner

Once you save the library to your arduino folder you are ready to go.Open the example ** MPU9250BasicAHRS.ino **Also have this setup ready :

Hardware setup:

MPU9250 Breakout ——— Arduino

VIN ———————- 5V

SDA ———————– SDA (Pin 20)

SCL ———————– SCL (Pin 21)

GND ———————- GND

These wires shouldnt be very long as I2C connections dont work well for long wires.After connecting the wires

Now Clean up the ** MPU9250BasicAHRS** code ,it has a LCD display code in it but we dont need it so keep removing unnecessary lines.

Also I have added a part of auto calibration code.

Here is the modified code without unnecessary code lines and auto calibration part : Github

Now upload the code to your arduino and do the connections stated above. Open the Serial Terminal and change the baud rate to **115200 . **You should see this output.

MPU9250 9-DOF 16-bit motion sensor 60 ug LSB MPU9250 I AM 71 I should be 71 MPU9250 is online… x-axis self test: acceleration trim within : 0.8% of factory value y-axis self test: acceleration trim within : -1.9% of factory value z-axis self test: acceleration trim within : 1.8% of factory value x-axis self test: gyration trim within : -0.2% of factory value y-axis self test: gyration trim within : 0.3% of factory value z-axis self test: gyration trim within : 0.6% of factory value MPU9250 bias x y z 254913-660mg1.1-0.11.2o/s MPU9250 initialized for active data mode…. AK8963 I AM 48 I should be 48 AK8963 initialized for active data mode…. Mag Calibration: Wave device in a figure eight until done!

If you see this .

MPU9250 9-DOF 16-bit motion sensor 60 ug LSB MPU9250 I AM FF I should be 71 Could not connect to MPU9250: 0xFF

**This means there is definitely some loose connection or wiring problem ( or in the worst case mpu/arduino fault ) try to rectify it and proceed …**

If everything goes and you see “MPU is online” and “Mag Calibration: Wave device in a figure eight until done!” then everything is working and you should turn your mpu in “8” shaped motion for some time…..This is the **Auto Calibration part….**After some time you should be able to get readings of YAW,PITCH and ROLL as below…

MPU9250 9-DOF 16-bit motion sensor 60 ug LSB MPU9250 I AM FF I should be 71 Could not connect to MPU9250: 0xFF MPU9250 9-DOF 16-bit motion sensor 60 ug LSB MPU9250 9-DOF 16-bit motion sensor 60 ug LSB MPU9250 I AM 71 I should be 71 MPU9250 is online… x-axis self test: acceleration trim within : -0.5% of factory value y-axis self test: acceleration trim within : 0.3% of factory value z-axis self test: acceleration trim within : 0.2% of factory value x-axis self test: gyration trim within : 0.0% of factory value y-axis self test: gyration trim within : 0.4% of factory value z-axis self test: gyration trim within : 1.0% of factory value MPU9250 bias x y z 183913-616mg1.1-0.21.3o/s MPU9250 initialized for active data mode…. AK8963 I AM 48 I should be 48 AK8963 initialized for active data mode…. Mag Calibration: Wave device in a figure eight until done! Mag Calibration done! X-Axis sensitivity adjustment value 1.19 Y-Axis sensitivity adjustment value 1.19 Z-Axis sensitivity adjustment value 1.15 AK8963 ASAX 1.19 ASAY 1.19 ASAZ 1.15 Yaw, Pitch, Roll: 11.34, 28.62, 50.03 Yaw, Pitch, Roll: 20.47, 25.15, 52.88 Yaw, Pitch, Roll: 26.94, 19.02, 52.70 Yaw, Pitch, Roll: 28.22, 15.02, 50.15 Yaw, Pitch, Roll: 27.10, 13.94, 44.68 Yaw, Pitch, Roll: 23.11, 13.69, 37.51 Yaw, Pitch, Roll: 14.29, 13.22, 27.61 Yaw, Pitch, Roll: 357.03, 8.21, 16.72 Yaw, Pitch, Roll: 342.29, 0.69, 9.19 Yaw, Pitch, Roll: 328.42, -4.80, 3.16 Yaw, Pitch, Roll: 317.19, -10.51, -0.58 Yaw, Pitch, Roll: 311.88, -16.57, -3.64 Yaw, Pitch, Roll: 327.71, -23.45, -16.82 Yaw, Pitch, Roll: 325.74, -22.02, -23.51 Yaw, Pitch, Roll: 325.99, -28.17, -26.95 Yaw, Pitch, Roll: 324.57, -24.96, -23.21 Yaw, Pitch, Roll: 320.01, -26.42, -22.25 Yaw, Pitch, Roll: 322.50, -26.04, -26.62 Yaw, Pitch, Roll: 322.85, -23.43, -29.17 Yaw, Pitch, Roll: 323.46, -19.20, -31.48

Awesome! now you have the data…and you can play with real time motion

We first convert yaw to from (-180 to +180) to (0 to 360) by doing

yaw = yaw + 180;

Then we just find the error in yaw using a simple Proportional controller and then add the error back to yaw and then do the servo mappings with the new yaw. :

nyaw = 360 – yaw; //”yaw” comes from MPU which is “actual” Azim = Azimuth – nyaw; /*”Azimuth” is the absolute azimuth which comes from calculations from RA and DEC which assumes our device is already aligned to North….by doing the subtraction we get the proportional error*/ Azim -= 90; //adding 90° because my titlt servo is mounted at an offset of 90° while (Azim < 0) Azim = 360.0 – abs(Azim); /*we use the error proportionally for our servo to auto adjust */ Azi = map(Azim, 0, 360, 5, 29); Az = (int)Azi; Elev = map(Elevation, -90, 90, 2, 178); El = (int)Elev;

This completes my RTPT project.Hope you have learned new things from it.

The final code is in the main RTPT page.

Thank you

*RTPT (Real Time Planet Tracking System and Trajectory Prediction) Copyright © 2016 Shubham Paul , Samhita Ganguly ,Rohit Kumar **GNU GPL3+*

**Arduino Mega**(or MCU with more than 33Kbyte flash )- This is**important**because my final code was around 33KB and i**couldnt fit it in an UNO.****U-BLOX GPS Receiver**( NEO-6M ) or any GPS Module.**MPU9250**(9 axis gyro-accelero-magneto) for auto Yaw stabilization [**OPTIONAL]****Pan- tilt Mechanism with Servos**.(pan servo should be of 3.5turns and Tilt servo can be a normal 180 degree servo )**A laser pointer –**to be fixed to the tilt servo to show planet location in a closed rooom- Optional
**Power Distribution board**and**Battery Eliminator Circuit (BEC).** - A
**Potentiometer**to be used as**Planet selector**and a**Switch**as**Mode Selector.**

Using the GPS is very easy when we use TinyGPS++ Libraries : http://arduiniana.org/libraries/tinygpsplus/

GPS uses Serial communication so I have connected it to Serial2 of Mega That is pin 16 and pin 17.

After connecting burn a test code from library named “**FullExample**“.The library use Software serial make sure you have converted it to **Serial2 also change the GPSBaud to 9600. **Also, have a look at the code named “**DeviceExample**“. Both of the aformentioned code examples can be found here.The reason why I am asking you to understand these codes because we will be using **SmartDelay **to update GPS values from the GPS buffer

static void smartDelay(unsigned long ms) { unsigned long start = millis(); do { while (ss.available()) gps.encode(ss.read()); } while (millis() - start < ms); }

Also we will be using the following functions call statements to get **Latitude,Longitude,Year,Month,Date,Day,Hour,Minute**

gps.location.lat() gps.location.lng() gps.date.year() gps.date.month() gps.date.day() gps.time.hour() gps.time.minute()

We need to get a **3.5 turns Servo** for **PAN** and a normal **180° Servo** for **TILT. **I wont tell you much about a servo pan-tilt mechanism rather focus on the connections and its mapping.

**The pan-tilt can rotate roughly 360° from side-to-side (pan motor) and can tilt up & downward (tilt motor) around 180°**.- A laser pointer will be mounted on this pan-tilt mechanism
- Calculated azimuth angle could be traced by pan motor. Elevation angle traced by tilt motor.

first the connections..we will be using **Pin 9 for Pan Servo and Pin 10 for Tilt Servo**

I have also added a potentiometer (present in the *featured image *at the top of this page, which is my **final schematic**) as a switch to select planets. This is optional and can be implemented easily.

Just look on the servo library examples …you will understand how to make a servo work.We just need three lines of code from the library for each servo.

We need to define Servo objects and attach the pins and initialize them:

#include <servo.h> Servo myservoAz; // create servo object to control a servo Servo myservoEl; myservoAz.attach(9); //Attach the pins myservoEl.attach(10); myservoAz.write(5); //initialize the servo to go to its zero myservoEl.write(2); //for my servos it was 5 (for pan/azimuth servo) and 2(for tilt servo)

Our pan servo is a 3.5 turns servo and it takes input from 5 to 29 to rotate from 0° to 360°

Then we do our mapping which is quit simple.Lets assume the variable “**Azim” **gives us the value of Azimuth and the variable “**Elevation” **altitude,then we do our mapping as

Azi = map(Azim, 0, 360, 5, 29); //its for my 3.5 turn servo that gives a 360° turn runs on values 5-29 Az = (int)Azi; Elev = map(Elevation, -90, 90, 2, 178); //its for my 180° servo that runs on values 0-180 El = (int)Elev;

**Azi ** and **El **are the right mappings and can be fed to the pan tilt as :

myservoAz.write(Az); myservoEl.write(El);

Now all you need to do is convert the calculation of Azimuth-Altitude from the previous post into an Arduino code and use the data from GPS and get the output and feed it to the servo Link.

Note: You must align your pan-tilt setup at its initial position (both at 0° ) with the 0° Azimuth of your actual location ….that is nothing but North Direction!.

In the next post I’ll integrate MPU9250 so that my Pan Servo Automatically senses the north of my location and Automatically shows me the planet position by adjusting itself even if I dont keep it at Azimuth 0°…again this step is optional.

*RTPT (Real Time Planet Tracking System and Trajectory Prediction) Copyright © 2016 Shubham Paul , Samhita Ganguly ,Rohit Kumar **GNU GPL3+*

Yes! the Real time position of any celestial body can be calculated using some parameters called Orbital Elements (or Osculating Elements or **Keplerian Elements**). These are the parameters that define an orbit at a particular time.

**Inclination (i)**angle between the plane of the Ecliptic and the plane of the orbit.

**Longitude of the Ascending Node (o)**states the position in the orbit where the elliptical path of the planet passes through the plane of the ecliptic, from below the plane to above the plane.**Longitude of Perihelion (p)**states the position in the orbit where the planet is

closest to the Sun.**Mean distance (a)**the value of the semi-major axis of the orbit – measured in Astronomical Units for the major planets.**Daily motion (n)**states how far in degrees the planet moves in one (mean solar) day. This figure can be used to find the mean anomaly of the planet for a given number of days either side of the date of the elements. The figures quoted in the Astronomical Almanac do*not*tally with the period of the planet as calculated by applying Kepler’s 3rd Law to the semi-major axis.**Eccentricity (e)**eccentricity of the ellipse which describes the orbit.**Mean Longitude (L)**Position of the planet in the orbit on the date of the elements.source:2

All the above parameters are change constantly (but very very slowly) due to gravitational perturbations by other objects and the effects of relativity because of that we need to take the latest orbital elements into our calculation for least possible error or highest accuracy.I just googled and got orbital elements for the year 2013 (text) .You can try different methods or go to the following sites to gather data.

- http://asa.usno.navy.mil/SecE/Osculating_Elements.html
- http://ssd.jpl.nasa.gov/
- http://www.imcce.fr/langues/en/
- http://naif.jpl.nasa.gov/naif/
- Research gate forum

Now that you have the latest osculating (orbital) elements for a date lets say **16 August 2013, **we will start our calculations but before that we need to know few terms

- JDN
**–**An Integer(Julian Day No.) that would give the day number count from the beginning of Julian year which will help us locating the planets/celestial bodies in their orbits for a particular date and time relative to a - Equatorial Coordinate System – The coordinates Right Ascension (RA) and Declination (DEC) will be used frequently here.Look at the GIF to know what is RA and DEC.

The positions of objects in the sky as viewed from Earth are referred to a coordinate system whose alignment is changing with time in a complex way. A few of the important motions and effects are summarised below;

- The Earth is rotating on its axis once every siderial day
- The rotation axis is moving in a circle with a period of roughly 26,000 years (precession)
- The axis is ‘nodding’ up and down with a period of roughly 19 years (nutation)
- The finite speed of light (sometimes referred to as ‘aberration’ in some books)

The ‘fixed’ stars provide a reference system which allows us to account for the daily rotation of the Earth on its axis. We use the Equatorial coordinate system to refer positions to a frame in which the stars appear still, and the right ascension (RA) and declination (DEC) are used to give the coordinates of the planet with respect to the fixed stars. The ‘zero’ of RA is refered to the ‘vernal equinox’, in the same way that the ‘zero’ of longitude is taken as the Greenwich Meridian.

The presession of the equinoxes means that the ‘zero’ of RA is changing slowly with time, which means that star coordinates must always be referred to an epoch, or date. By using orbital elements referred to the fundamental epoch J2000, the orbits of the planets are described in a coordinate system which is based on the position the vernal equinox *will have* at J2000. A further advantage of this dodge is that our positions for the planets will correspond exactly to the positions found in most recent star charts. You should be able to plot the path of Mars directly onto a star chart such asThe Cambridge Star Atlas.

Nutation (which is a small effect anyway) can also be spirited away by referring our positions to the ‘mean ecliptic of J2000’. The word ‘mean’ indicates that no allowance for nutation has been made. Our observation platform (the Earth) is nodding, so the stars and planets will appear to nod together. Our J2000 elements will give is positions which match the co-ordinates of the stars found in star maps.

There is a problem with this use of J2000 equinox and mean ecliptic. If I just dial the values for RA and DEC into a computerised telescope, then the planet will *not* appear in the centre of the field of view – as the RA and DEC will not be referred to the ‘equinox and true ecliptic of date’. The effect will be very small for 10 years either side of J2000. (source:2)

So we would increase the accuracy by using elements from 2013.

JD = 2450680.5 Equinox and mean ecliptic of J2000.0 Earth Mars i 0.0 1.8496 o 0.0 49.668 p 103.147 336.322 a 1.0000 1.523762 n 0.985611 0.523998 e 0.016679 0.093346 L 324.5489 82.9625 The values for the other planets can be found in the C program below.

The sections below deal with calculating the **RA and DEC** of a planet from the **osculating elements**. Lets find the position of Mars at UT on the **1st of March 2016**. The main steps in the calculation are;

- Finding the position of the planet in its orbit
- Find the number of days since the date of the elements
- Find the mean anomaly from the Mean Longitude and the daily motion
- Find the true anomaly using the Equation of Centre
- Find the radius vector of the planet

- Refer that position to the Ecliptic – hence find the heliocentric ecliptic coordinates of the planet
- Repeat most of above to find the heliocentric coordinates of the Earth
- Transform the heliocentric coordinates to geocentric coordinates by a change of origin
- Transform the geocentric ecliptic coordinates to geocentric equatorial coordinates by a rotation about the X axis
- Calculate the RA and DEC and Earth – planet distance from the rectangular coordinates

The method used here was adapted from Paul Schlyter’s page ‘How to compute planetary positions’ at source:3

Elements i - inclination o - longitude of ascending node at date of elements p - longitude of perihelion at date of elements a - mean distance (au) n - daily motion e - eccentricity of orbit l - mean longitude at date of elements Calculated quantities M - mean anomaly (degrees) V - True anomaly (degrees) r - radius vector (au) referred to current coordinate origin X - recangular coordinate (au) Y - recangular coordinate (au) Z - recangular coordinate (au) alpha - right ascension (hours or decimal degrees according to context) delta - declination (decimal degrees)

- ‘day number’ (dele) of the elements
- ‘day number’ you want the position for (dpos)

d = dpos - dele

The ‘day number’ can be the

- Julian day, or
- The number of days since the fundamental epoch J2000. I use the second alternative as less precision is needed for the numbers!

The following tables show the days from the beginning of the year to the beginning of each month, and the days from J2000 to the beginning of each year.

Days to beginning of month Month Normal year Leap year Jan 0 0 Feb 31 31 Mar 59 60 Apr 90 91 May 120 121 Jun 151 152 Jul 181 182 Aug 212 213 Sep 243 244 Oct 273 274 Nov 304 305 Dec 334 335 Days since J2000 to beginning of each year Days 1999 -366.5 2000 -1.5 2001 364.5 2002 729.5 2003 1094.5 2004 1459.5 2005 1825.5 Similarly, 2013 4747.5 2016 5842.5

we can find the day number corresponding to the date of the elements (16th August 2013) as follows;

dele = 212 + 16 + 4747.5 = 4975.5

and the day number of the date we want the position for (1st Aprih 2016) is;

dpos = 60 + 1 + 5842.5 = 5903.5

dpos - dele = d 5903.5- 4975.5 = 928 days

For fast moving planets such as Mercury and Mars, you need to include the time of day which you want the position for. Just add the Universal Time in decimal hours divided by 24 to the day number of your position (dele above);

day fraction = (H + M/60)/24 H is hours UT M is minutes UT

M = n * d + L - p n is daily motion d is the number of days since the date of the elements L is the mean longitude p is the longitude of perihelion M should be in range 0 to 360 degrees, add or subtract multiples of 360 to bring M into this range.

For our case of Mars and 928 days since the date of the elements;

n = 0.523998 d = 928 L = 82.9625 p = 336.322 M = 0.523998 * 928 + 82.9625 - 336.322 = 232.910644

Mean Anomaly is calculated considering the orbit to be circular, but True Anomaly gives the actual position of the planet as it considers orbit to be elliptical. We convert Mean Anomaly to True Anomaly using the following formula:

v = M + 180/pi * [ (2 * e - e^3/4) * sin(M) + 5/4 * e^2 * sin(2*M) + 13/12 * e^3 * sin(3*M)........ n terms ] v is true anomaly M is mean anomaly e is eccentricity pi is 3.14159... e^3 means the third power of e. Note how the third power is involved the first term as well as the last. A more expanded series can give a better result so this is what i have used in my code : v = m + (2 * e - 0.25 *pow(e,3) + 5/96 * pow(e,5)) * sin(m) + (1.25 * pow(e,2) - 11/24 * pow(e,4)) * sin(2*m) + (13/12 * pow(e,3) - 43/64 * pow(e,5)) * sin(3*m) + 103/96 * pow(e,4) * sin(4*m) + 1097/960 * pow(e,5) * sin(5*m);

This is manually calculated using the Equation of Centre from “**The Astronomical Almanac (page E4)”.**

For our Mars position, we have

M = 232.910644 degrees or 4.065057601 radians e = 0.093346 Therefore, v = 224.9688989 degrees or 3.926448 radians <strong>Note: In my code I have done all the calculations in Radians</strong>

The distance from the planet to the focus of the ellipse is given by a simple formula based on the geometry of the ellipse;

r = a * (1 - e^2) / [1 + e * cos(v)] a is the semi-major axis e is the eccentricity v is the true anomaly the radius vector r will be in the same units as a a.u. in this case.

a = 1.523762 e = 0.093346 v =224.9688989 r = 1.523762 * (1 - 0.093346^2) / [ 1 + 0.093346 * cos (224.9688989) ] = 1.617293 a.u

Having found the true anomaly and the radius vector of the planet, we can go on to find the position of the planet with respect to the plane of the ecliptic. The formulas below are a combination of ‘resolving’ to find components and rotations around various axes to transform the coordinates to the Ecliptic frame. We might expect the formulas to involve the inclination of the planet’s orbit (i), and various angles within the plane of the orbit, as well as the longitude of the ascending node (o).

X = r * [cos(o) * cos(v + p - o) - sin(o) * sin(v + p - o) * cos(i)] Y = r * [sin(o) * cos(v + p - o) + cos(o) * sin(v + p - o) * cos(i)] Z = r * [sin(v + p - o) * sin(i)] r is radius vector v is true anomaly o is longitude of ascending node p is longitude of perihelion i is inclination of plane of orbit the quantity v + p - o is the angle of the planet measured in the plane of the orbit from the ascending node

r = 1.617293 v = 224.9688989 o = 49.668 p = 336.322 i = 1.8496 v + p - o = 511.6228989 - 360 = 151.6228989 and I get the following rectangular coordinates; X = -1.506606 Y = -0.587503 Z = 0.024809 SQRT(X^2 + Y^2 + Z^2) should be same as r

Similarly find M(mean anomaly),V(true anomaly) and r(radius vector) for earth

We simply the equations for Earth, as the inclination of the Earth’s orbit is very small.so…

Xe = r * cos(v + p) Ye = r * sin(v + p) Ze = 0 r is radius vector of Earth v is true anomaly for Earth p is longitude of perihelion for Earth

For the problem in hand we have;

after calculations we have Xe = -0.935762 Ye = 0.325870 Ze = 0.000000

Just subtract Earth’s coordinates from those of the planet and we get geocentric (earth as a centre ) coordinates.

X' = X - Xe Y' = Y - Ye Z' = Z - Ze

We then have the geocentric ecliptic coordinates of the planet. For the case of Mars (1st March 2016) we have,

X' = -0.570844 Y' = -0.913374 Z' = 0.024809

To change the coordinate system from geocentric ecliptic to geocentric equatorial is just a matter of a rotation around the X axis by an angle equal to the ‘obliquity of the Ecliptic. The X axis points towards the ‘First point of Aries’, which is the direction in space associated with the equinox. As we are using elements referred to the equinox of J2000.0, we use the obliquity for that epoch, which is 23.439292 degrees. the formulas are given below;

Xq = X' Yq = Y' * cos(ec) - Z' * sin(ec) Zq = Y' * sin(ec) + Z' * cos(ec) Xq are the equatorial coordinates X' are the geocentric ecliptic coordinates ec is the obliquity of the ecliptic

For Mars, we have

Xq = -0.570844 Yq = -0.847872 Zq = -0.340557

rectangular coordinates are not much use with star charts, so we calculate the familiar right ascension and declination using the formulas;

alpha = arctan(Yq/Xq) If Xq is negative then add 180 degrees to alpha If Xq is positive and Yq is negative then add 360 degrees to alpha alpha is usually expressed in hours, so divide by 15 delta = arctan( Zq / SQRT(Xq^2 + Yq^2)) distance = SQRT( Xq^2 + Yq^2 + Zq^2)

(right ascension) alpha = 15.440000 hrs (declination) delta = -18.250000 degs distance = 1.077971278 a.u.

With this one of the hardest part of our project is done!

Now that we have RA and DEC it gives us the value from the vernal equinox at 1st March 2016,therefore we neede to give our position in order to see with respect to us (the observer as center ).So we first take the **latitude(lat) and longitude(long)** using a **GPS module **of our current location.

For those who are wondering what is Azimuth and Elevation have a look of this image

Note : We will be connecting a GPS module to our arduino setup to constantly refresh and update our coordinates …Now lets proceed with the calculation.

We need to know the

**Local Sidereal Time**– First we need the time (hour and minute in UT) and the day no of the date**1st march 2016 [i.e. dpos= 5903.5 ] .**Now we use this formula to get LST. LST = (100.46 + 0.985647 * day + Long + 15 * (hour + minute / 60) + 360) – (((int) ((100.46 + 0.985647 * day + Long + 15 * (hour + minute / 60) + 360)/360))*360);

**Hour Angle**HA = (LST – RA + 360)- ((int)((LST – RA + 360)/360))*360 ;

**HA, DEC, Lat to Alt, AZ**

x = cos(HA * (pi / 180)) * cos(Dec * (pi / 180));

y = sin(HA * (pi / 180)) *cos(Dec * (pi / 180));

z = sin(Dec * (pi / 180));

**Horizontal coordinates :**xhor = x * cos((90 – Lat) * (pi / 180)) – z *sin((90 – Lat) * (pi / 180));

yhor = y;

zhor = x * sin((90 – Lat) * (pi / 180)) + z *cos((90 – Lat) * (pi / 180));

az = atan2(yhor, xhor) * (180 / pi) + 180;

alt = asin(zhor) * (180 / pi);

If you want to know more about these formulae then do some research.

So finally we have the Result as

Azimuth = 193.845997 degrees Altitude = 58.017963 degrees

This is my Repo link : github

References :

*GNU GPL3+*

This project aims to make a system that effectively tracks celestial bodies (such as planets ) with a fair amount of accuracy. We will be using some algorithms along with a processing unit for the calculations and a servo mechanism to show the location of the planet physically!. The hardware used in the project is pretty much basic and simple because the** primary focus of this project is the software** that is to make people understand the algorithms and their implementations. So please bear with my “un-formatted” hardware.

Not just planet tracking you will learn some additional important things that you can implement in your other projects:

- Planet tracking using Kepler’s algorithms
- Many coordinate systems and their interconversion
- pan-tilt programming and servo mapping (3.5 turns Servo and 180 degrees Servo )
- MPU9250 auto-calibration programming
- Using Madwicks/Mahony Filter to Stabilise MPU readings.
- Yaw correction using P- controller with MPU9250

The steps are too detailed to fit in a single page so I have split the project into different pages they are:

- Step 1: Calculation of Right Ascension and Declination and its conversion to Azimuth and Altitude [Calculated using Kepler’s Laws] using Osculating Elements Here is the link https://paulsite.com/calculation_of_right_ascension_and_declination/
- Step 2: The Circuitry (connections of GPS Module U-BLOX NEO-6M or similar, pan – tilt servo ) and Servo Mapping. Here is the link https://paulsite.com/hardware_integration/Note: If you
**do not use**an MPU9250, you will have to align your system in its initial state to the North direction. - Step 3: This step uses the MPU9250 for
**automatic north detection and its alignment**and the best part is that it is**Dynamic i.e.**even you keep changing (the yaw angle) or azimuth alignment**it will continuously sense the error and correct it dynamically.**Here is the link https://paulsite.com/mpu_programming/Step 4: Software Integration and Trajectory Prediction. Go to this section for the final Arduino code, Here is the link https://paulsite.com/software_integration/

*RTPT (Real Time Planet Tracking System and Trajectory Prediction) Copyright © 2016 Shubham Paul, Samhita Ganguly, Rohit Kumar **GNU GPL3+*