Project tutorial

Towards an Arduino-Powered Satellite Tracker © GPL3+

We wanted to do a satellite tracked based on Arduino. This project shows how to create a wireless control for a basic stepper motor :)

  • 2,243 views
  • 0 comments
  • 1 respect

Components and supplies

A000066 iso both
Arduino UNO & Genuino UNO
×1
NRF24L01
×2
Pi 3 02
Raspberry Pi 3 Model B
×1
Nema%2023 2
OpenBuilds NEMA 23 Stepper Motor
×1
Servo (generic)
https://www.amazon.fr/Tower-Micro-Servo-moteur-h%C3%A9licopt%C3%A8re/dp/B00I8D0E0C/ref=sr_1_1?ie=UTF8&qid=1471346887&sr=8-1&keywords=servomoteur
×1
Arduino motor shield
×1
11026 02
Jumper wires (generic)
×1

Apps and online services

About this project

As part of our academic degree at CentraleSupélec, we have to do several projects. We were involved in Supsat, the student association aiming to work on a nanosatellite (also known as Cubesats).

Our goal there was to work on an automatic satellite tracker. Before our project, students used a QFH antenna:

  • Pro: this antenna is omni-directional (no need for a tracker).
  • Con: the gain is poor :(

We decided to work for a directive antenna where the gain is higher but where a tracker is necessary to make good measurements. We found some related on Instructables, but we wanted to do one ourselves.

  • Interested to work on a real application such as space technologies.
  • Electronics + kind of wireless transmission: this is an IoT device.
  • Furthermore, as we worked with widespread wireless chips, we will be able to use the driver for other projects with the same chip for Nordic Semiconductors.

Requirements

  • Automatic tracker (azimuth/elevation values obtained from another software such as Gpredict or Stellarium).
  • No wires between the computing unit and the motor itself.

Therefore, our project is based on the following schematic:

  • A RaspberryPi 3 is used as the computing unit: a bit powerful, but in the final version, we should be able to run everything (OS, GUI, Gpredict) on the Raspberry.
  • NRF24L01 for the wireless transmission: these are 2.4GHz chips with a built-in antenna and an acceptable range (up to 30 meters indoor !).
  • An Arduino with the official motor shield.
  • Servo and a stepper motor.
  • In this schematic, we forget the mechanical issues: pliers to control the antenna, etc... Furthermore, we consider that motors will be powerful enough to drive the directional antenna.

Connections of NRF24L01 chips follows:

In the attached files, you will find the sender code (C code to be compiled with default GCC settings on the Raspberry) and the receiver code (Arduino stuff).

Schematics

Raspberry connections
Rpi nrf24l01
Arduino connections
Nano nrf24l01

Code

arduino_code.inoArduino
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
#include <string.h>
#include <Servo.h>
#include <Stepper.h>

// Definition des Pins CE, CSN pour le module NRF
#define RF_CE    9
#define RF_CSN   10

// Definition des constantes du stepper
#define STEPS 200  // Max steps for one revolution
#define RPM 60     // Max RPM

#define DELAY 20

int SVRO = 2;    // PIN de controle du servo

int commandeServo = 0; // commande du servo
String respMsg;
String msg;
int steps = 0; // commande du moteur

Stepper stepper(STEPS, 4, 5, 6, 7); // PIN du moteur

Servo myservo;

RF24 radio(RF_CE,RF_CSN);

byte myID = 0x01; // Definition de l'id de l'Arduino 

byte pipes[][7] = {"master","slave","idle"};

struct payload_request_t
{
  uint8_t number;
  uint8_t destination;
  char message[14];
};

struct payload_general_t
{
  uint8_t number;
  char message[15];
};

payload_request_t incoming;
payload_general_t outgoing;

void sendReply() // fonction d'envoi de la reponse au raspberry
{
  // affichage du message dans le moniteur
  Serial.print("Message de retour de la commande \""); 
  Serial.print(outgoing.message);
  Serial.println("\"");
  
  radio.stopListening(); // envoi du message
  radio.openWritingPipe(pipes[1]);
  delay(10);
  radio.write(&outgoing,sizeof(payload_general_t)+1);
  delay(10);
  radio.startListening();
}

void traitement(char *commande, int *servo, int *pas) 
// fonction qui separe la commande moteur de la commande du servo
{
  // modele commande set=com_servo;com_mot
  String comServ = "";
  String comMot = "";
  int i=4;
  int n = strlen(commande);
 
  while(commande[i] != ';'){ // recuperation de la commande du servo
    comServ += commande[i];
    i++;    
  }

  i++;
  while(i<n){ // recuperation de la commande du moteur
    comMot += commande[i];
    i++;    
  }

  *servo = comServ.toInt();
  *pas = comMot.toInt();
  
}

void setup() {
  
  Serial.begin(9600); // Initialisation de la communication Serie
  
  stepper.setSpeed(RPM); // Parametre la vitesse du moteur
  
  myservo.attach(SVRO);
  myservo.write(0); // servo a l'angle 0

  printf_begin();
  radio.begin();   // initialisation de la communication NRF
  radio.setAutoAck(1); 
  radio.setRetries(1,3);
  radio.enableDynamicPayloads();
  radio.openWritingPipe(pipes[1]);
  radio.openReadingPipe(1, pipes[0]);
  radio.startListening();
  radio.printDetails();
}

void loop() {
 
  if(radio.available()) {
    
    radio.read(&incoming, sizeof(payload_request_t));
    // Affichage de la commande demandee dans la console
    Serial.print("Commande recu \"");
    Serial.println(incoming.message); 
    
  // Aduino verifie que c'est bien a son ID que la demande s'adresse
  if (incoming.destination==myID) {
      
   
    if (strncmp(incoming.message, "get", 3) == 0) {
   // Si le message est "get", on retourne la position du servo et la derniere commande du moteur
   
      msg = "S" + String(commandeServo) + "M" + String(steps);
  
    
    } else if (strncmp(incoming.message, "set", 3) == 0) {
      // Sinon si la commande est de type "set"
      traitement(incoming.message,&commandeServo,&steps); 
      // On extrait les deux commandes
      
       if ((commandeServo < 0) || (commandeServo > 90)) {
          msg = "OORS "; // out of range servo
          } else {      
            myservo.write(commandeServo); // on commande le servo
             // on prepare le message de retour
            msg = "S = " + String(commandeServo) + " ";
            }
          Serial.println(respMsg);
       if ((steps == 0) || (steps < 0 - STEPS) || ( steps > STEPS )) {
          msg += "OORM"; // out of range moteur
          } else {  
            // on prepare le message de retour
              msg += "M = " + String(steps); 
              delay(DELAY); 
              if ( steps > 0) { // Sens trigo
                  for (int i=0;i<steps;i++) { 
                     stepper.step(1);
                     delay(DELAY);   
                  }
              } else { // Sens inverse
                  for (int i=0;i>steps;i--) {
                    stepper.step(-1);
                    delay(DELAY); 
                  }  
               }
          }
          // copie du message dans le message de retour
          msg.toCharArray(outgoing.message, 16); 
          Serial.println(outgoing.message); 
     
    } else { // Si la commande est de type inconnu
      strcpy(outgoing.message, "?");
    }

    outgoing.number = incoming.number;
    sendReply(); // On envoie la reponse
    }
  }
  delay(100);
}
Raspberry codeC/C++
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <RF24/RF24.h>
#include <fstream>
#include <math.h>

#include <json/json.h>
using namespace std;

//Initialisation du module radio
RF24 radio(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ);

const uint8_t pipes[][7] = {"master","slave","idle"};

struct payload_request_t
{
  uint8_t number;
  uint8_t destination;
  char message[14];
};

struct payload_general_t
{
  uint8_t number;
  char message[15];
};

uint16_t timeout_ttl = 1000;

payload_request_t request;
payload_general_t answer;

//Cette fonction permet d arreter l execution du code et d afficher l etat de la transmission
void finish(bool sucess, int error, char * message)
{
	char status[6];
	if (sucess)
		strcpy(status, "true");
	else
		strcpy(status, "false");
	printf("{\"status\":\"%s\",\"code\":\"%d\",\"message\":\"%s\"}\n", status, error, message);
	
	/*if (sucess)
		exit(1);
	else
		exit(1);
	*/
}

std::string azimut_std_old = ""; //Variable contenant l'azimut "ancienne" du satellite
std::string elevation_std_old = ""; //Variable contenant l'elevation "ancienne" du satellite
float azimutf_old = 0; //Variable permettant de stocker la valeur précedente de l'azimut
int compteur = 0;

int main(int argc, char** argv)
{
while(1){
   srand(time(NULL));

   /*
    *   Initialisation du module radio
    */
   
   radio.begin();
   radio.setAutoAck(1); // Activation de l'AutoAck
   radio.setRetries(15,15);
   radio.enableDynamicPayloads();
   radio.openWritingPipe(pipes[0]);
   radio.openReadingPipe(1,pipes[1]);

   //Fonctions de tests

   /*// Definition du parametre de destination, on l'attache a l'appel de la fonction pour des tests
   json_object * key = json_object_object_get(jobj, "to");
   if (key == 0) 
	finish(false, 3, (char *) "Missing destination");

   request.destination = atoi( (char *) json_object_get_string(key) );

   // Definition du parametre 'message', on l'attache a l'appel de la fonction pour realiser des tests de la fonction
   key = json_object_object_get(jobj, "message");
   if (key == 0) // 'message' nao existe
	finish(false, 4, (char *) "Missing message content");

   strncpy(request.message, (char *) json_object_get_string(key), 14);
   */	 

   //Definition de la destination de la requete
   request.destination = 1;

   //Ouverture du fichier texte contenant les coordonnees du satellite
   std::ifstream fichier("Coordonnees.txt");

   if (fichier) //On test la bonne lecture du fichier
   {
	std::string azimut_std_new; //Variable contenant l'azimut du satellite
	std::string elevation_std_new; //Variable contenant l'elevation du satellite

	int commande_servo;
	int commande_moteur;
	int azimut;
	int i;

	char set[6];
 	char separateur[6];
	//char commande[10];

	strcpy(set,"set=");
	strcpy(separateur,";");

	for(i=0;i<=compteur;i++){
	std::getline(fichier, elevation_std_new, ';'); //On recupere l'elevation du satellite
	std::getline(fichier, azimut_std_new, '\n'); //On recupere l'azimut du satellite
	}

	compteur++;

	//On regarde si les valeurs ont changé, puis si oui, on modifie les commandes
	if ((elevation_std_old.compare(elevation_std_new))  || (azimut_std_old.compare(azimut_std_new))){
	
		commande_servo = atoi(elevation_std_new.c_str());
		azimut = atoi(azimut_std_new.c_str());

		float azimutf = azimut;

		commande_moteur = (((azimutf - azimutf_old) * 200.0f) / 360.0f) + 0.5f; //Calcul de la commande du moteur en fonction de l'azimut
 
		char commande_moteur_std[10];

		sprintf(commande_moteur_std,  "%d", commande_moteur); //Conversion de la commande moteur de int en string

			
		//Affichage des valeurs recuperees et calculees
		printf("Commande servo : %d\n",commande_servo);
		printf("Azimut : %d \n", azimut);
		printf("Commande moteur : %d\n",commande_moteur);

		//Creation du message
		char message[16] = {""};
	
		strcat(message,set);
		strcat(message,elevation_std_new.c_str());
		strcat(message,separateur);
		strcat(message,commande_moteur_std);	

		printf("message : %s\n",message);

		strcpy(request.message,message);

		// On met à jour les valeurs
		azimut_std_old = azimut_std_new;
		elevation_std_old = elevation_std_new;
		azimutf_old = azimutf;
	
      		printf("test\n");
 
   		request.number = (uint8_t) rand();
  
   		printf("nombre : %d \n",request.number);

   		radio.write(&request,sizeof(payload_request_t));

   		radio.startListening();
	
   		unsigned long started_waiting_at = millis();
   		bool timeout = false;

   		while ( !radio.available() && !timeout )
		if (millis() - started_waiting_at > 10000)//timeout_ttl )
	    		timeout = true;
    
		//   printf("test2\n");
                 
   		if ( timeout )
			finish(false, 5, (char *) "Timed-out");

   		radio.read( &answer, sizeof(payload_general_t));
   		
		if (answer.number != request.number)
			finish(false, 6, (char *) "Wrong ACK");

   		radio.stopListening();
   
		//Commande de test

		//   request.number = (uint8_t) rand();
		//   printf("message bis : %s",request.message);
		//   printf("nombre bis : %d",request.number);
		//   radio.write(&request,sizeof(payload_request_t));
		//   radio.printDetails();

   		finish(true, 0, answer.message);
		}
	}
}
}

Comments

Created by

Thomas Le Goff & Maxime Moury

Are you Thomas Le Goff & Maxime Moury? Claim this project and add it to your profile.

This is mine

Posted by

Default
Pascal Cotret
  • 0 projects
  • 0 followers

Published on

August 16, 2016

Members who respect this project

Openbuildslogo bluebox
See similar projects
you might like

Similar projects you might like

USBcycle: Ride Through Your Virtual World!

Project showcase by Tazling

  • 2,269 views
  • 0 comments
  • 7 respects

Arduino UNO High Precision Counting Scale

Project showcase by Fedeasche

  • 3,863 views
  • 5 comments
  • 19 respects

Mini CNC: A Handmade Masterpiece

Project showcase by Mr-M

  • 1,705 views
  • 2 comments
  • 12 respects

Arduino Controlled Piano Robot: PiBot

Project tutorial by Jeremy Yoo

  • 1,989 views
  • 0 comments
  • 7 respects

DIY Thrust Station

by Vincenzo G.

  • 232 views
  • 0 comments
  • 3 respects

Automation of Railway Models Part 3 - Bidirectional Line

Project showcase by Steve_Massikker

  • 947 views
  • 2 comments
  • 10 respects
Add projectSign up / Login