Bonjour à toute la communauté.
Je voudrais présenter ici un nouveau projet (presque) terminé. Je dis presque, car il manque des finitions, mais pour l’essentiel tout est là et fonctionne.
Il s’agit d’un appareil permettant de faire tourner un plateau d’un angle donné puis de déclencher un appareil photo numérique.
Le montage peut également être utilisé en simple intervallomètre sans plateau tournant, ou pour faire tourner l’appareil photo lui-même afin de faire un panoramique.
L’objet du plateau tournant, c’est de pouvoir prendre une série de photos d’un objet avec un décalage fixe de quelques degrés de manière automatique, dans le but de reconstituer un objet 3D à partir de ces photos. Cette technique s’appelle la photogrammétrie, et elle permet d’obtenir des résultats bluffants. Pour la reconstruction 3D j’utilise soit 3DF ZEFYR (Free avec une limitation à 50 vues), ou alors Meshroom qui est un logiciel libre sans limitation. Il faut néanmoins disposer d’une carte graphique avec un processeur NVidia et beaucoup de mémoire graphique (4 à 8Go).
La partie mécanique consiste en une base Arca Swiss installée sur un trépied équipé d’une rotule, sur cette base est fixée un moteur pas à pas (NEMA17) qui actionne un axe via un jeu de deux poulies crantées, amenant un rapport de démultiplication x3. L’axe est lui-même équipé d’une base Arca Swiss en partie haute afin d’y fixer le plateau ou un appareil photo.
Les commandes de mise au point et déclenchement sont effectuées via un câble de télécommande relié à des optocoupleurs.
Le système supporte les commandes suivantes :
?
Raz :
Focus : [Durée] {secondes}
Rideau : [Durée] {secondes}
TourneD : [Ang] : [Vit] : [AngMax] {degrés} {L/M/R} {degrés}
TourneG : [Ang] : [Vit] : [AngMax] {degrés} {L/M/R} {degrés}
Interval: [M/A] : [Durée] {secondes} : [Nombre intervals]
MA : [M/A]
Rapport :
Reboot :
Les valeurs par défaut sont :
Pas de rotation
Durée commande Focus = 3 secondes
Durée commande Déclenchement = 2 secondes
Vitesse de rotation = Moyenne
Sens de rotation = Droite
Intervallomètre inactif, Durée interval = 10 secondes
Nombre intervals = illimité
Etat marche = Arrêt
Le matériel utilisé :
L’appareil photo que j’utilise est un Nikon D750. Je ne sais pas si cela est identique pour tous les modèles, mais sur le D750 il faut activer la commande de mise au point (Focus) en même temps que la commande de déclenchement (Rideau) pour obtenir le déclenchement. Je donne cette précision car j’avais vu sur un Forum qu’un utilisateur n’arrivait pas à déclencher à partir d’un microcontrôleur, sans doute parce qu’il n’activait pas les deux sorties simultanément.
Le moteur pas à pas est un NEMA17, 4fils, 200 pas par tour,
Le microcontrôleur est un Arduino Nano Every. Attention, le programme ne peut pas être chargé dans un Arduino Uno car la taille de la mémoire dynamique est insuffisante.
Pour piloter l’ensemble j’ai installé un module Bluetooth, ce qui me permet d’envoyer des commandes depuis mon téléphone portable. Du coups, je ne sais pas si j’installerai les bouton-poussoir que j’avais prévu.
Le module de pilotage du moteur pas à pas est DRV 8825.
N’ayant pas trouvé facilement de connecteur Jack stéréo de 2,5mm pour circuit imprimé, j’ai opté pour un connecteur de 3,5mm et un petit câble adaptateur.
J’alimente le tout à partir d’une batterie 12V Li Ion.
Voici des photos de la carte et du montage mécanique. Le câblage de la carte n’est pas terrible car j’ai réutilisé un précédent montage et du coup c’est moins bien que si j’étais reparti de zéro.
Je mets aussi le programme et le schéma. 5si tout ne tien pas dans ce post, j’en ferai un second à la suite.
/* Programme Tete-Photo.ino
syph60 02/2025
Ce programme assure le fonctionnement d’un support pivotant et déclencheur pour appareil photo
*/
#include <Wire.h>
#include <stdio.h>
#include <stdarg.h>
# include <SoftwareSerial.h> // Librairie pour liaison série
// Constantes
const uint8_t CstepPin = 2; // Définition sortie commande STEP
const uint8_t CdirPin = 3; // Définition sortie commande DIR
const uint8_t CEnPin = 4; // Définition sortie Enable
const uint8_t CEstate = 9; // Entrée D9 pour état module Bluetooth
const uint8_t CErx = 11; // Entrée D11 réception Bluetooth coté controleur
const uint8_t CEtx = 10; // Entrée D10 émission Bluetooth coté controleur
const int CBMA = 5; // N° entrée du bouton poussoir Marche/Arrêt
const int CBFoc = 6; // N° entrée du bouton poussoir Focus
const int CDecl = 7; // N° entrée du bouton poussoir Déclenchement
const int COfoc = 12; // N° sortie optocoupleur commande Focus
const int COdecl = 8; // N° sortie optocoupleur commande déclencheur
const int VD13 = 13; // N° entrée builtin diode
const int CEbat = A3 ; // Entrée analogique A3 pour tension batterie
const float CKu = 0.012389666; // 5*(7850 + 4656) / (4656 * 1024), Coefficient pour lecture tension batterie
// Fin section constantes
// Variables
int Vsteps = 19200; // Variable nombre de pas pour 360 degrés
int Vstepdeg = 53; // Nombre de pas pour tourner d'un angle de 1,004464 degrés
int VmsDelay[4] = {0,2048,512,24}; // Variable durée impulsion moteur rotation Lente, Moyenne, Rapide
int VResSerial = 0; // Resultat fonction F_Commande
uint8_t VTfoc = 3; // Durée commande Focus, 0 = pas de focus
uint16_t VTdecl = 2; // Durée commande Déclenchement, 0 = pas de déclenchement
uint16_t VTang = 0; // Angle de rotation plateau, 0 = pas de rotation
uint8_t VVrot = 2; // Vitesse rotation plateau (0 pas de rotation,1 Lente,2 Moyenne,3 Rapide)
uint16_t Vtotrot = 0; // Cumul angle rotation
uint16_t VAngmax = 360; // Angle de rotation maxi
bool VSens = true; // Sens de rotation : true = droite, false = gauche
bool VInt = false; // Fonctionnement en mode intervallomètre false = Non, True = Oui
uint8_t VTint = 10; // Durée interval par défaut
uint16_t VNBint= 0; // Nombre d'intervals à exécuter, 0 indique pas de limites
bool VMA = false; // Etat marche/Arrêt
bool Vresaction = false; // Résultat action de commande
float VUbat = 0.0; // Tension batterie
SoftwareSerial VBt(CErx, CEtx); // Bluetooth Emission, Réception coté controleur
int VEtatBT; // Valeur entrée état module Bluetooth
// Fin section variables
/* Fonctions :
Setup()
Loop()
F_Gole(int Pangle) Tourne à gauche d'un angle donné
F_Gori(int Pangle) Tourne à droite d'un angle donné
int F_Commande() Retourne le code de commande entré sur la liaison série
int F_Trouvestr(String PChn, String PChx, int PDeb) Recherche la chaine PChx dans PChn à partir de la position PDeb
String F_Trimall(String PChn) Supprimes tous les espaces dans PChn
bool F_in(int Vms, int VNbe, ...) Vérifie si la valeur Vms est un élément de la liste VNbe...
bool F_Bouton1() Renvoi l'état filtré du bouton poussoir Marche/Arrêt
bool F_Bouton2() Renvoi l'état filtré du bouton poussoir Focus
bool F_Bouton3() Renvoi l'état filtré du bouton poussoir Déclenchement
bool F_Timer(unsigned long Ptemps) attente d'un délais en ms, sans interruption du programme
float F_Ubat() Lecture de la tension d'alimentation VIN (soit, la batterie)
int F_Seravailable() Remplace la fonction Serial.available et prend soit l'entrée du terminal soit celle de Bluetooth
void F_Serprintln(String PTxt) Remplace la fonction println et oriente la sortie soit vers le terminal soit vers Bluetooth
void F_Serprintln(String PTxt) Remplace la fonction println et oriente la sortie soit vers le terminal soit vers Bluetooth
String F_SerreadString() Remplace la fonction Serial.readString et prend soit l'entrée du terminal soit celle de Bluetooth
void F_reboot() Fonction reboot redémarre le contrôleur
int F_Chrcount(String PChn, char PCar) Permet de compter le nombre d'occurences d'un caractère dans une chaine
String F_Strfield(String PChn, char PCar, int PDeb, int PNbe) Retourne la chaine délimitée par PDeb pour N occurences
*/
void setup() {
Serial.begin(9600);
VBt.begin(9600); // initialisation Bluetooth
VBt.println("");
pinMode(CstepPin, OUTPUT); // Sortie STEP
pinMode(CdirPin, OUTPUT); // Sortie sens de rotation
pinMode(CEnPin, OUTPUT); // Sortie Enable
digitalWrite(CEnPin, LOW); // On éteint le moteur
pinMode(CEstate, INPUT); // Entrée état module Bluetooth
pinMode(COfoc, OUTPUT); // Sortie optocoupleur commande focus
pinMode(COdecl, OUTPUT); // Sortie optocoupleur commande déclencheur
pinMode(CBMA, INPUT_PULLUP); // Entrée bouton poussoir Marche/Arrêt
pinMode(CBFoc, INPUT_PULLUP); // Entrée bouton poussoir Focus
pinMode(CDecl, INPUT_PULLUP); //Entrée bouton poussoir Déclenchement
delay(500);
F_Aide();
} // Fin fonction setup
void loop()
{
VResSerial = F_Commande();
Vresaction = F_Action();
if (VResSerial == 9) // Commande Reboot
{
F_reboot();
}
} // Fin fonction loop
void F_Gole(int P_angle){ // Commande du panneau vers la gauche d'un angle donné en paramètre
digitalWrite(CEnPin, LOW);
digitalWrite(CdirPin, HIGH);
for(int x = 0; x < (P_angle * Vstepdeg); x++)
{
digitalWrite(CstepPin, HIGH);
delayMicroseconds(VmsDelay[VVrot]);
digitalWrite(CstepPin, LOW);
delayMicroseconds(VmsDelay[VVrot]);
}
digitalWrite(CEnPin, HIGH); // On éteint le moteur
} // Fin fonction F_Gole
void F_Gori(int P_angle){ // Commande du panneau vers la droite d'un angle donné en paramètre
digitalWrite(CEnPin, LOW); // activation moteur
digitalWrite(CdirPin, LOW); // sélection sens de rotation
for(int x = 0; x < (P_angle*Vstepdeg); x++)
{
digitalWrite(CstepPin, HIGH);
delayMicroseconds(VmsDelay[VVrot]);
digitalWrite(CstepPin, LOW);
delayMicroseconds(VmsDelay[VVrot]);
}
digitalWrite(CEnPin, HIGH); // On éteint le moteur
} // Fin fonction F_Gori
/* Cette fonction lit les données dispo sur la liaison série devant avoir la forme :
Date : JJ/MM/AAAA - HH:MM:SS
Les espaces n'ont pas d'importance, ni les caratères séparateurs utilisés, par contre les tous les séparateurs doivent
être présent, et tous les champs doivent avoir 2 caratères sauf l'année qui en a 4
Si des champs sont omis, leur valeur est fixée à zéro */
int F_Commande()
{
String VInbuff; // Buffer entrée liaison série
String VStr; // Chaine temporaire de travail
int VIdx; // Utilisé pour déterminerla position des caratères
int VNbcar = 0; // Nombre de caractères à lire sur la liaison série
int VRet = 0; // Variable de retour
VRet = 0;
VIdx = -1;
if (Serial)
{
if ( (VNbcar = F_Seravailable()) > 0) // Caractères disponibles sur liaison série
{
VInbuff = F_Trimall(F_SerreadString()); // Supression des espaces
VInbuff.toUpperCase();
VIdx = F_Trouvestr(VInbuff, "RAZ:", 0); // Vérification syntaxique indiquant une commande d'initialisation
if (VIdx != -1)
{
VTang = 5; // Angle de rotation par défaut
VTfoc = 3; // Durée par défaut commande focus = 3 secondes
VTdecl = 2; // Durée par défaut commande déclenchement = 2 s
VVrot = 2; // Vitesse de rotation moyenne
VSens = true; // Sens de rotation à droite
VInt = false; // Fonctionnement en mode intervallomètre désactivé
VTint = 10; // Durée par défaut intervallomètre = 10 secondes
VNBint = 0; // Nombre d'intervals = illimité
VMA = false; // Etat Marche/Arrêt à l'arrêt
VRet = 1;
F_Serprintln("Fait commande Raz");
}
VIdx = F_Trouvestr(VInbuff, "RAPPORT:", 0); // Vérification syntaxique indiquant une commande de rapport
if (VIdx != -1)
{
F_Serprintln("Réglages");
F_Serprintln("--------");
if (VTang > 0) {F_Serprintln("Angle de rotation = " + String(VTang));F_Serprintln("Angle maxi = " + String(VAngmax));} else {F_Serprintln("Pas de rotation");}
if (VTfoc > 0) {F_Serprintln("Durée commande Focus = " + String(VTfoc));} else {F_Serprintln("Pas de Focus");}
if (VTdecl >0) {F_Serprintln("Durée commande Déclenchement = " + String(VTdecl));} else {F_Serprintln("Pas de Déclenchement");}
switch ((int) VVrot)
{
case 0: {F_Serprintln("Vitesse de rotation = Pas de rotation"); break;}
case 1: {F_Serprintln("Vitesse de rotation = Lente"); break;}
case 2: {F_Serprintln("Vitesse de rotation = Moyenne"); break;}
case 3: {F_Serprintln("Vitesse de rotation = Rapide"); break;}
}
if (VSens) {F_Serprintln("Sens de rotation = Droite");} else {F_Serprintln("Sens de rotation = Gauche");}
if (VInt == false) {F_Serprint("Intervallomètre inactif");} else {F_Serprint("Intervallomètre actif");}
F_Serprintln(", Durée interval = " + String(VTint));
if (VNBint == 0) {F_Serprintln("Nombre intervals = illimité");} else {F_Serprintln("Nombre intervals = " + String(VNBint));}
if (VMA == false) {F_Serprintln("Etat marche = Arrêt");} else {F_Serprintln("Etat marche = Marche");}
VRet = 2;
}
VIdx = F_Trouvestr(VInbuff, "FOCUS:", 0); // Vérification syntaxique indiquant une commande de Focus
if (VIdx != -1)
{
if (F_Chrcount(VInbuff, ':') > 0)
{
VStr = F_Strfield(VInbuff, ':', 2,1);
if(VStr != "") {VTfoc = (uint8_t) VStr.toInt();} else {VTfoc = 3;}
if (VTfoc <1 || VTfoc > 10) VTfoc = 3; // Valeur par défaut si hors limites
}
VRet = 3;
F_Serprintln("Fait commande Focus : " + String(VTfoc));
}
VIdx = F_Trouvestr(VInbuff, "RIDEAU:", 0); // Vérification syntaxique indiquant une commande de Focus
if (VIdx != -1)
{
if (F_Chrcount(VInbuff, ':') > 0)
{
VStr = F_Strfield(VInbuff, ':', 2,1);
if(VStr != "") {VTdecl = (uint16_t) VStr.toInt();} else {VTdecl = 2;}
if (VTdecl <1 || VTdecl > 3600) VTdecl = 2; // Valeur par défaut si hors limites
}
VRet = 4;
F_Serprintln("Fait commande Rideau : " + String(VTdecl));
}
VIdx = F_Trouvestr(VInbuff, "TOURNED:", 0); // Vérification syntaxique indiquant une commande de rotation à droite
if (VIdx != -1)
{
if (F_Chrcount(VInbuff, ':') > 0)
{
VStr = F_Strfield(VInbuff, ':', 2,1);
if(VStr != "") {VTang = (uint16_t) VStr.toInt();} else {VTang = 5;}
}
{
VStr = F_Strfield(VInbuff, ':', 3,1);
switch (F_Toasc(VStr))
{
case 76: { VVrot = 1; break;}
case 77: { VVrot = 2; break;}
case 82: { VVrot = 3; break;}
default: {VVrot = 0; break;}
}
VStr = F_Strfield(VInbuff, ':', 4,1);
if (VStr != "") VAngmax = (uint16_t) VStr.toInt(); // Angle de rotation maximum
}
VRet = 5;
VSens = true;
F_Serprintln("Fait commande TourneD : " + String(VTang) + ", Vitesse : " + String(VVrot) + ", Sens : " + VSens + ", Angle maxi : " + String(VAngmax));
}
VIdx = F_Trouvestr(VInbuff, "TOURNEG:", 0); // Vérification syntaxique indiquant une commande de rotation à droite
if (VIdx != -1)
{
if (F_Chrcount(VInbuff, ':') > 0)
{
VStr = F_Strfield(VInbuff, ':', 2,1);
if(VStr != "") {VTang = (uint16_t) VStr.toInt();} else {VTang = 5;}
}
{
VStr = F_Strfield(VInbuff, ':', 3,1);
switch (F_Toasc(VStr))
{
case 76: { VVrot = 1; break;}
case 77: { VVrot = 2; break;}
case 82: { VVrot = 3; break;}
default: {VVrot = 0; break;}
}
VStr = F_Strfield(VInbuff, ':', 4,1);
if (VStr != "") VAngmax = (uint16_t) VStr.toInt(); // Angle de rotation maximum
}
VRet = 6;
VSens = false;
F_Serprintln("Fait commande TourneG : " + String(VTang) + ", Vitesse : " + String(VVrot) + ", Sens : " + VSens + ", Angle maxi : " + String(VAngmax));
}
VIdx = F_Trouvestr(VInbuff, "MA:", 0); // Vérification syntaxique indiquant une commande Marche/Arrêt
if (VIdx != -1)
{
if (F_Chrcount(VInbuff, ':') > 0)
{
VStr = F_Strfield(VInbuff, ':', 2,1);
if(VStr.startsWith("M"))
{
VMA = true;
F_Serprintln("Fait commande Marche");
if (VTang > 0) {Vtotrot = 0;}
}
if(VStr.startsWith("A"))
{
VMA = false;
F_Serprintln("Fait commande Arrêt");
digitalWrite(COfoc, LOW); // Désactivation sortie Focus
digitalWrite(COdecl, LOW); // Désactivation sortie déclencheur
digitalWrite(CEnPin, HIGH); // On éteint le moteur
}
}
VRet = 7;
}
VIdx = F_Trouvestr(VInbuff, "INTERVAL:", 0); // Vérification syntaxique indiquant le mode interval
if (VIdx != -1)
{
F_Serprint("Fait commande Intervallomètre : ");
if (F_Chrcount(VInbuff, ':') > 0)
{
VStr = F_Strfield(VInbuff, ':', 2,1);
if (VStr.startsWith("M"))
{
VInt = true;
F_Serprint("Marche");
}
if (VStr.startsWith("A"))
{
VInt = false;
F_Serprint("Arrêt");
}
VStr = F_Strfield(VInbuff, ':', 3,1);
if(VStr != "") {VTint = (uint8_t) VStr.toInt();} else {VTint = 10;}
F_Serprint(", Délai = " + String(VTint) + "s");
VStr = F_Strfield(VInbuff, ':', 4, 1);
if(VStr != "") {VNBint = (uint16_t) VStr.toInt();} else {VNBint = 0;}
}
VRet = 8;
F_Serprintln(", Nombre intervals = " + String(VNBint));
}
VIdx = F_Trouvestr(VInbuff, "?", 0); // Vérification syntaxique indiquant une commande Help
if (VIdx != -1)
{
F_Aide();
}
VIdx = F_Trouvestr(VInbuff, "REBOOT:", 0); // Vérification syntaxique indiquant une commande Reboot
if (VIdx != -1)
{
VRet = 9;
}
}
}
return VRet;
} // Fin fonction F_Commande
/* Cette fonction recherche la première occurence d'une chaine A, dans une chaine B
en commençant à la position X */
int F_Trouvestr(String PChn, String PChx, int PDeb)
{
int VPos1 = 0;
int VPos2 = 0;
int VLgn = 0;
int VLgx = 0;
int VResu = 0;
String VSubch;
VResu = -1;
VLgn = PChn.length(); // Longueur chaine origine
VLgx = PChx.length(); // Longueur chaine recherchée
VPos1 = PDeb;
do
{
VPos2 = VPos1 + VLgx;
if (VLgn > 0 && VLgx > 0 && VPos2 < VLgn)
{
VSubch = PChn.substring(VPos1, VPos2);
if (VSubch != PChx)
{
VPos1++;
} else {VResu = VPos1;}
}
} while (VResu == -1 && (VPos1+VLgx) < VLgn);
return VResu;
} // Fin fonction F_Trouvestr
/* Cette fonction retourne la chaine passée en paramètre en supprimant tous les espaces */
String F_Trimall(String PChn)
{
String VNewstr = "";
for (int i = 0; i < PChn.length(); i++)
{
if (PChn[i] != ' ') {VNewstr += PChn[i];}
}
return VNewstr;
} // Fin fonction F_Trimall
/* Fonction F_in, cette fonction teste si l'argument Vms existe
dans la liste d'arguements variable passée à la fonction, VNbe
indique le nombre d'arguments passés. va_list, va_start, va_arg et va_end
sont des macros définies dans stdarg.h
Cette fonction fonctionne uniquement avec le type integer.
La fonction renvoie un booléen si l'occurence est trouvée, mais on aurait pu
aussi bien retourner le numéro de l'élément dans la liste des arguments*/
bool F_in(int Vms, int VNbe, ...)
{
bool trouve = false;
va_list(L_ms); // initialisation de la variable liste
int Velem;
va_start(L_ms, VNbe);
do
{
Velem = va_arg(L_ms, int); // Retourne le prochain élément de la liste
if(Velem == Vms){
trouve = true;
break;
}
VNbe -=1;
} while(VNbe);
va_end(L_ms);
return(trouve);
} // fin F_in
/* Routines utilisées pour gérer les bouton poussoirs, retourne true ou false selon que les BP sont
appuyés ou relâchés
*/
bool F_Bouton1()
{
int VEtatcur = LOW;
bool VRetour = false;
static int VNblec = 10; // Nombre de cycles répétitifs, utilisé pour le filtrage des rebonds
static int Vbout0 = HIGH;
static int Vbout1 = HIGH;
Vbout1 = digitalRead(CBMA); // Acquisition entrée du bouton
if (Vbout1 == Vbout0) {VNblec -=1;} else // Filtrage rebonds
{
VNblec = 10;
Vbout0 = Vbout1;
}
if (VNblec <= 0)
{
VNblec = 0;
VEtatcur = Vbout1; // Validation état bouton après filtrage des rebonds
}
if(VEtatcur == LOW) {VRetour = true;} else {VRetour = false;} // Inversion du sens d'action du bouton (à cause de INPUT_PULLUP)
return(VRetour);
} // Fin fonction F_Bouton1
bool F_Bouton2()
{
int VEtatcur = LOW;
bool VRetour = false;
static int VNblec = 10; // Nombre de cycles répétitifs, utilisé pour le filtrage des rebonds
static int Vbout0 = HIGH;
static int Vbout1 = HIGH;
Vbout1 = digitalRead(CBFoc); // Acquisition entrée du bouton
if (Vbout1 == Vbout0) {VNblec -=1;} else // Filtrage rebonds
{
VNblec = 10;
Vbout0 = Vbout1;
}
if (VNblec <= 0)
{
VNblec = 0;
VEtatcur = Vbout1; // Validation état bouton après filtrage des rebonds
}
if(VEtatcur == LOW) {VRetour = true;} else {VRetour = false;} // Inversion du sens d'action du bouton (à cause de INPUT_PULLUP)
return(VRetour);
} // Fin fonction F_Bouton2
bool F_Bouton3()
{
int VEtatcur = LOW;
bool VRetour = false;
static int VNblec = 10; // Nombre de cycles répétitifs, utilisé pour le filtrage des rebonds
static int Vbout0 = HIGH;
static int Vbout1 = HIGH;
Vbout1 = digitalRead(CDecl); // Acquisition entrée du bouton
if (Vbout1 == Vbout0) {VNblec -=1;} else // Filtrage rebonds
{
VNblec = 10;
Vbout0 = Vbout1;
}
if (VNblec <= 0)
{
VNblec = 0;
VEtatcur = Vbout1; // Validation état bouton après filtrage des rebonds
}
if(VEtatcur == LOW) {VRetour = true;} else {VRetour = false;} // Inversion du sens d'action du bouton (à cause de INPUT_PULLUP)
return(VRetour);
} // Fin fonction F_Bouton2
// Fonction F_Timer attente d'un délais en ms, sans interruption du programme
bool F_Timer(unsigned long Ptemps)
{
bool Vtemps = false;
static unsigned long VTimeP = millis();
static unsigned long VTimeC;
VTimeC = millis();
if (VTimeC - VTimeP > Ptemps)
{
Vtemps = true;
VTimeP = VTimeC;
}
return Vtemps;
} // Fin fonction F_Timer
float F_Ubat() // Lecture de la tension d'alimentation VIN (soit, la batterie)
{
int VA3;
VA3 = analogRead(CEbat);
return float(VA3) * CKu; // La valeur de CKu dépend des valeurs exactes du pont diviseur sur l'entrée CEbat
} // Fin fonction F_Ubat
void F_Serprint(String PTxt) // Remplace la fonction print et oriente la sortie soit ves le terminal soit vers Bluetooth
{
VEtatBT = digitalRead(CEstate); // lecture état module Bluetooth (haut = connecté, bas = non connecté)
if (VEtatBT == HIGH)
{
VBt.print(PTxt);
} else
{
Serial.print(PTxt);
}
} // Fin fonction F_Serprint
void F_Serprintln(String PTxt) // Remplace la fonction println et oriente la sortie soit vers le terminal soit vers Bluetooth
{
VEtatBT = digitalRead(CEstate); // lecture état module Bluetooth (haut = connecté, bas = non connecté)
if (VEtatBT == HIGH)
{
VBt.println(PTxt);
} else
{
Serial.println(PTxt);
}
} // Fin fonction F_Serprintln
int F_Seravailable() // Remplace la fonction Serial.available et prend soit l'entrée du terminal soit celle de Bluetooth
{
int VResu = 0;
VEtatBT = digitalRead(CEstate); // lecture état module Bluetooth (haut = connecté, bas = non connecté)
if (VEtatBT == HIGH)
{
VResu = VBt.available();
} else
{
VResu = Serial.available();
}
return VResu;
} // Fin fonction F_Seravailable
String F_SerreadString() // Remplace la fonction Serial.readString et prend soit l'entrée du terminal soit celle de Bluetooth
{
String VResu = "";
VEtatBT = digitalRead(CEstate); // lecture état module Bluetooth (haut = connecté, bas = non connecté)
if (VEtatBT == HIGH)
{
VResu += VBt.readString();
} else
{
VResu += Serial.readString();
}
return VResu;
} // Fin fonction F_SerreadString
void F_reboot() // Fonction reboot redémarre le contrôleur
{
F_Serprintln("Reboot...");
asm volatile ("jmp 0");
} // Fin fonction F_Reboot
// Permet de compter le nombre d'occurences d'un caractère dans une chaine
int F_Chrcount(String PChn, char PCar)
{
int Vresu = 0;
int VLong = PChn.length();
for (int i =0; i < VLong; i++)
{
if (PChn[i] == PCar) {Vresu++;}
}
return Vresu;
} // Fin F_Chrcount
/* La fonction F_StrField permet d'extraire une partie de chaine en
fonction de caractères de délimitation.
La syntaxe est F_Strfield(Chaine,car,deb,nbr)
Chaine = chaine dans laquelle on effectue la recherche
car = caractère de délimitation des éléments recherchés
deb = Occurence de départ
nbr = nombre d'occurences
Exemples :
F_Strfield("ABC","|",1,1) = ABC
F_Strfield("ABC|DEF|GHI","|",1,1) = ABC
F_Strfield("ABC|DEF|GHI","|",2,1) = DEF
F_Strfield("ABC|DEF|GHI","|",1,2) = ABC|DEF
F_Strfield("ABC|DEF|GHI","|",2,2) = DEF|GHI
F_Strfield("ABC|DEF|GHI","|",2,5) = DEF|GHI
*/
String F_Strfield(String PChn, char PCar, int PDeb, int PNbe)
{
String VResu = "";
int VStart, Vidx, Vposition;
int Vdeb = 0;
int Vfin = 0;
String VChn2 = "";
if (PChn.length() == 0 || PDeb < 1 || PNbe < 1)
{
return VResu;
}
else
{
Vidx = 0;
VStart = 0;
Vposition = -1;
while(Vidx < (PDeb -1))
{
Vdeb = Vposition +1;
Vposition = PChn.indexOf(PCar, VStart);
if (Vposition == -1)
{
return VResu;
}
else
{
Vidx+=1;
VStart = Vposition +1;
}
}
VChn2 =PChn.substring(VStart);
//if (PDeb == 1) {Vdeb = 0;} else {Vdeb = Vposition + 1;}
Vidx = 0;
VStart = 0;
while(Vidx < PNbe)
{
Vposition = VChn2.indexOf(PCar, VStart);
if (Vposition != -1)
{
Vidx +=1;
//VStart = Vposition +1;
//Vfin = Vposition;
if (Vidx == PNbe) {return VChn2.substring(0,Vposition);} else {VStart = Vposition +1;}
}
else
{
return VChn2;
}
}
return PChn.substring(Vdeb, Vfin);
}
} // Fin fonction F_Strfield
int F_Toasc(String PChn) // Retourne l'entier représentant la valeur ASCII du premier caratère de PChn
{
int Vresu = 0;
if (PChn != "") Vresu = (int) PChn[0];
return Vresu;
}
bool F_Action() // Exécution des commandes
{
bool Vresu;
static unsigned long Vtmppre = millis();
static unsigned long Vtmpcur = millis();
static unsigned long Vdelai = 0;
Vresu = false;
Vtmpcur = millis();
if (Vdelai > 0 && (Vtmpcur - Vtmppre) > Vdelai) // Temps écoulé
{
Vdelai = 0;
Vtmppre = Vtmpcur;
}
if (VInt == true && VMA == true && Vdelai == 0) // Mode interval et Marche actif et interval non en cours
{
F_Serprintln("Intervallomètre : Déclenchement");
digitalWrite(COfoc, HIGH); // Activation sortie Focus
Vresu = true;
if(VTfoc > 0) // Temps focus non nul
{
delay(VTfoc * 1000); // Attente du délai pour réglage mise au point
}
if(VTdecl > 0) // On enchaine sur le déclenchement
{
digitalWrite(COdecl, HIGH); // Activation sortie déclencheur
delay(VTdecl * 1000); // Attente du temps d'opération du déclencheur
}
digitalWrite(COfoc, LOW); // Désactivation sortie Focus
digitalWrite(COdecl, LOW); // Désactivation sortie déclencheur
Vdelai = VTint * 1000; // Temps interval passé en millisecondes
Vtmpcur = millis();
}
else if (VInt == false && VMA == true && Vdelai == 0) // Mode interval inactif et Marche et interval non en cours
{
if (VTang > 0) {F_Serprintln("Rotation : Déclenchement/Rotation");} else {F_Serprintln("Déclenchement unique");};
if (VTfoc > 0) // Temps focus non nul
{
digitalWrite(COfoc, HIGH); // Activation sortie Focus
delay(VTfoc * 1000); // Attente du délai pour réglage mise au point
}
if(VTdecl > 0) // On enchaine sur le déclenchement
{
digitalWrite(COdecl, HIGH); // Activation sortie déclencheur
delay(VTdecl * 1000); // Attente du temps d'opération du déclencheur
}
digitalWrite(COfoc, LOW); // Désactivation sortie Focus
digitalWrite(COdecl, LOW); // Désactivation sortie déclencheur
if (VTang > 0 && VVrot > 0) // Rotation active
{
if (VSens == true) {F_Gori(VTang);} else {F_Gole(VTang);} // commande rotation
Vtotrot += VTang;
F_Serprintln("Rotation totale : " + String(Vtotrot)); // Affichage rotation totale
if (Vtotrot >= VAngmax)
{
VMA = false; // Arrêt commande
Vtotrot = 0;
F_Serprintln("Pret");
}
}
Vdelai = 1000; // Attente de 1 seconde pour stabilisation
Vtmpcur = millis();
if (VTang == 0) // Si pas de rotation on arrête l'exécution
{
VMA = false;
F_Serprintln("Pret");
}
}
return Vresu;
} // Fin fonction F_Action
void F_Aide()
{
F_Serprintln(" Commandes disponibles :");
F_Serprintln(" -----------------------");
F_Serprintln("?");
F_Serprintln("Raz :");
F_Serprintln("Focus : [Durée] {secondes}");
F_Serprintln("Rideau : [Durée] {secondes}");
F_Serprintln("TourneD : [Ang] : [Vit] : [AngMax] {degrés} {L/M/R} {degrés}");
F_Serprintln("TourneG : [Ang] : [Vit] : [AngMax] {degrés} {L/M/R} {degrés}");
F_Serprintln("Interval: [M/A] : [Durée] {secondes} : [Nombre intervals]");
F_Serprintln("MA : [M/A]");
F_Serprintln("Rapport :");
F_Serprintln("Reboot :");
VEtatBT = digitalRead(CEstate); // lecture état module Bluetooth (haut = connecté, bas = non connecté)
F_Serprint("Etat Bluetooth = ");
if (VEtatBT == HIGH) {F_Serprintln("connecté");} else {F_Serprintln("non connecté");}
VUbat = F_Ubat();
F_Serprintln("Tension batterie = " + String(VUbat) + "V");
} // Fin fonction F_Aide
Il y aura des améliorations à faire, j'en vois déjà une qui consiste à ajouter une commande pause pour définir le temps nécessaire à la stabilisation après rotation du plateau ou de l'appareil photo...



