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