Panomatique
par , le mardi 22 mai 2012 à 12:41

Catégorie : Général
Mots clés : Arduino Photo

Avancement du projet de prise de vue panoramique robotisé à base d'Arduino, avec création d'un shield "fait main" et premier élémentes de mécanique.


Validation du principe

Hé bien voilà, on y est. Le développement du robot de prise de vue panoramique pour caméra standard avance bien.

La partie électronique a été testée sur une breadboard puis implémentée "en dur" sur un protoshileld à souder.

Cette électronique contient une boussole électronique HMC5883L pour connaître l'orientation du robot et gérer l'angle horizontal de prise de vue, une connectique pour 2 servo-moteurs (vertical + horizontal) et un ou deux relais pour déclencher la prise de vue sur l'appareil photo.

Voici le prototype du prototype sur un shield PowerScrewShield de snootlab :

où l'on aperçoit la boussole et le relai-reed.

Détail de la connexion de la boussole électronique au bus I2C :

Ce montage a permit de valider la solution.

Shield Arduino dédié

Du coup j'ai créé une carte un peu plus complète pour ce type de montage, avec 2 relais utilisables en passif et actif (3 broches par relai avec la pin du centre comme "neutre"), 3 déports pour des servo moteurs et un déport pour le bus I2C avec les résistances déjà en place. Je suis parti d'une carte protoype ProtoX-1 de Ciseco achetée chez HobbyTronic :

Mise en boite

Pour les nappes je me suis équipé de la pince nécessaire chez Hobbytronic, des cossess à sertir, des enbases pour les cosses et des câbles en nappe :

Il ne reste donc plus qu'à mettre le tout sous boite avec un shield deux lignes de chez Snootlab pour paramètrer et lancer le panoramique :

La nappe sur le coté droit est connectée au bus I2C d'un coté et à la breadboard de la bousolle de l'autre. On apperçoit la boussole en haut à droite (sur sa tranche) :

Le boitier provient de mon magasin d'électronique préféré : Radio St-Quentin.

Croquis Arduino

Le programme Arduino (partiel) pour gérer le panoramique est le suivant :

#include <stdio.h>

static FILE uartout = {0};

static int uart_putchar (char c, FILE *stream) {
    Serial.write(c) ;
    return 0 ;
}

//----------------------------------------------------------
#include <Wire.h>
#include <HMC5883L.h>
#include <Deuligne.h>
#include <Servo.h> 

Deuligne lcd;
Servo servo_v, servo_h;
HMC5883L compass;

#define PIN_SERVO_V   10
#define PIN_SERVO_H    9
#define START_SERVO_V 90
#define START_SERVO_H 86

#define TOUCH_NONE  -1
#define TOUCH_RIGHT  0
#define TOUCH_UP     1
#define TOUCH_DOWN   2
#define TOUCH_LEFT   3
#define TOUCH_SELECT 4

#define SPEED_V 2
#define SPEED_H 5

#define FOCUS_PIN A1
#define CLIC_PIN  A2

char *menu_main[] = {"Positionner", "Prendre photo", "Prendre panoramique", NULL};
char *menu_panoramique[] = {"Angle/pas horiz.", "Angle/pas vert.", "Demarrer sequence", "(menu principal)", NULL};

#define MENU_MAIN_MOVE    0
#define MENU_MAIN_CLIC    1
#define MENU_MAIN_PANO    2
#define MENU_PANO_HORIZ   0
#define MENU_PANO_VERT    1
#define MENU_PANO_START   2
#define MENU_PANO_QUIT    3

int error;
int position = START_SERVO_V;

int pano_horiz_angle = 180;
int pano_horiz_step  = 30;
int pano_vert_angle  = 60;
int pano_vert_step   = 30;

//----------------------------------------------------------
void setup() {
  Serial.begin(9600) ;
  fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
  stdout = &uartout ;  

  lcd.init();  
  lcd.clear();
  lcd.print("Init LCD...");
  delay(1000);

  lcd.clear();
  lcd.print("Init moteurs...");
  servo_v.attach(PIN_SERVO_V);
  servo_h.attach(PIN_SERVO_H);
  servo_v.write(START_SERVO_V);
  servo_h.write(START_SERVO_H);

  delay(1000);

  lcd.clear();
  lcd.print("Init boussole...");
  compass = HMC5883L();
  if (error = compass.SetScale(1.3)) {
    Serial.println(compass.GetErrorText(error));
    lcd.print(compass.GetErrorText(error));
  }
  if (error = compass.SetMeasurementMode(Measurement_Continuous)) {
    Serial.println(compass.GetErrorText(error));
    lcd.print(compass.GetErrorText(error));
    delay(10000);
  } 
  delay(1000);

  lcd.clear();
  lcd.print("Init telecommande...");
  pinMode(FOCUS_PIN, OUTPUT);      
  pinMode(CLIC_PIN, OUTPUT);      
  delay(1000);

  lcd.clear();
  lcd.print("OK");
  delay(1000);

  Wire.begin();
}

//----------------------------------------------------------
float get_orientation() {
  MagnetometerScaled scaled = compass.ReadScaledAxis();
  float heading = atan2(scaled.YAxis, scaled.XAxis);
  if(heading < 0)
    heading += 2*PI;
  float headingDegrees = heading * 180/M_PI; 

  return headingDegrees;
}

//----------------------------------------------------------
int get_touch(bool wait = true) {
  int key;

  while (true) {
    if ((key = lcd.get_key()) >= 0 || !wait) {
      delay(50);
      if (key == lcd.get_key())
        return key;
    }
  }
}

//----------------------------------------------------------
int menu(char *options[], int selected = 0) {
  int count = 0;
  int old_key = get_touch(false), key;

  while (options[count]) count++;

  lcd.clear();
  lcd.print(options[selected]); 

  while (true) {    
    if ((key = get_touch(false)) != old_key) {
      old_key = key;
      switch(key) {
        case TOUCH_UP:
          selected = (selected - 1) % count;
          if (selected < 0)
             selected  = count - 1;
          break;
        case TOUCH_DOWN:
          selected = (selected + 1) % count;
          break;
        case TOUCH_SELECT:
          return selected;
        default:
          delay(200);
          break;
      }
      if (key >= 0) {
        lcd.clear();
        lcd.print(options[selected]);
      }
    }
  }
}

//----------------------------------------------------------
int input(int value) {
  while (true) {
    lcd.clear();
    lcd.print(value);

    switch(get_touch()) {
      case TOUCH_RIGHT:
        value++;
        break;
      case TOUCH_LEFT:
        value--;
        break;
      case TOUCH_SELECT:
        return value;
     }
  }
}

//----------------------------------------------------------
void input2(int &value1, int &value2) {
  while (true) {
    lcd.clear();
    lcd.print(value1);
    lcd.print(" / ");
    lcd.print(value2);

    switch(get_touch()) {
      case TOUCH_RIGHT:
        value1++;
        break;
      case TOUCH_LEFT:
        value1--;
        break;
      case TOUCH_UP:
        value2++;
        break;
      case TOUCH_DOWN:
        value2--;
        break;
      case TOUCH_SELECT:
        return;
     }
  }
}

//----------------------------------------------------------
void move() {
  servo_v.write(position);

  while (true) {
    lcd.clear();
    lcd.print("H=");
    lcd.print(get_orientation());
    lcd.print(" / V=");
    lcd.print(position - 90);

    switch(get_touch(false)) {
      case TOUCH_RIGHT:
        servo_h.write(START_SERVO_H + SPEED_H);
        break;
      case TOUCH_LEFT:
        servo_h.write(START_SERVO_H - SPEED_H);
        break;
      case TOUCH_UP:<pre class="brush:cpp;">
        position -= SPEED_V;
        servo_v.write(position);
        break;
      case TOUCH_DOWN:
        position += SPEED_V;
        servo_v.write(position);
        break;
      case TOUCH_SELECT:
        servo_h.write(START_SERVO_H);
        return;
      default:
        servo_h.write(START_SERVO_H);
      }

    delay(50);
  }
}

//----------------------------------------------------------
void follow() {
  int key;

  while ((key = get_touch(false)) < 0 || key == TOUCH_SELECT) {
    float headingDegrees = get_orientation();

    lcd.clear();
    lcd.print(headingDegrees);
    lcd.print(" degres");

    servo_v.write(round(180 - headingDegrees/2)); 

    delay(100);
  }
}

//----------------------------------------------------------
void panoramique() {
  int selected = 0;

  while (true) {
    switch ((selected = menu(menu_panoramique, selected))) {
      case MENU_PANO_HORIZ:
        input2(pano_horiz_angle, pano_horiz_step);
        break;    
      case MENU_PANO_VERT:
        input2(pano_vert_angle, pano_vert_step);
        break;    
      case MENU_PANO_START:
        start();
        break;    
      case MENU_PANO_QUIT:
        return;
    }
  }
}

//----------------------------------------------------------
void start() {
  int pano_start = get_orientation();
  int pano_end = pano_start + pano_horiz_angle;
  int pano_pos = pano_start;
  int pos, old_pos = pano_start;
  int pos_looped = 0;

  while (pano_pos < pano_end) {
    lcd.clear();
    lcd.print("-> ");
    lcd.print(pano_pos); 

    for (pos = pano_pos; pos < pano_pos + pano_horiz_step; pos = get_orientation() + pos_looped) {
      servo_h.write(START_SERVO_H + SPEED_H);
      delay(100);

      if (get_orientation() < old_pos - pano_horiz_step / 2) {
        old_pos = get_orientation();
        pos_looped = 360;
      }

      lcd.clear();
      lcd.print(pos);
      lcd.print("-> ");
      lcd.print(get_orientation());
      lcd.setCursor(0, 1);
      lcd.print(old_pos);
      lcd.print(" : ");
      lcd.print(pos_looped);

      printf("%d -> %d %d %d\n", pos, (int)get_orientation(), old_pos, pos_looped);
    }

    servo_h.write(START_SERVO_H);

    if (get_touch(false) >= 0)
      return;

    lcd.print(" ! clic !");
    clic();    

    if (get_touch(false) >= 0)
      return;      

  pano_pos = pos;
  }
}<pre class="brush:cpp;">

//----------------------------------------------------------
void clic() {
  digitalWrite(FOCUS_PIN, HIGH);
  delay(1000);           
  digitalWrite(CLIC_PIN, HIGH);
  delay(3000);           
  digitalWrite(FOCUS_PIN, LOW); 
  digitalWrite(CLIC_PIN, LOW); 
  delay(1000);           
}

//----------------------------------------------------------
void loop() {
  int selected = 0;
<pre class="brush:cpp;">
  while (true) {
    switch ((selected = menu(menu_main, selected))) {
      case MENU_MAIN_MOVE:
        move();
        break;
      case MENU_MAIN_CLIC:
        lcd.clear();
        lcd.print("Clic photo !");
        clic();
        lcd.clear();
        break;
      case MENU_MAIN_PANO:
        panoramique();
        break;    
    }
  }
}

Mécanique

Mais j'y pense il faut monter tout ça sur un bout de mécanique, non ?!

Donc voici le système avec les 2 servo moteurs qui permetent de faire bouger l'ensemble :

Le servo moteur du bas est à rotation continue :

Le servo moteur du haut est un servo standard avec 180° de débattement :

et détail de l'axe opposé :

Bon ben, il reste à faire tourner le tout sur le pied photo; mais ça sera pour une autre fois.

A bientôt

Antoine

Ecrire à l'auteur