Pascal Cotret
Published © GPL3+

Towards an Arduino-Powered Satellite Tracker

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

IntermediateFull instructions provided4 hours7,162
Towards an Arduino-Powered Satellite Tracker

Things used in this project

Hardware components

Arduino UNO & Genuino UNO
Arduino UNO & Genuino UNO
×1
NRF24L01
×2
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
NEMA 23 Stepper Motor
OpenBuilds NEMA 23 Stepper Motor
×1
Servos (Tower Pro MG996R)
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
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Raspberry connections

Arduino connections

Code

arduino_code.ino

Arduino
#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 code

C/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);
		}
	}
}
}

Credits

Thomas Le Goff & Maxime Moury

Posted by Pascal Cotret

Comments