Tondeuse téléguidée

Mise en garde

Ce montage n’est pas un produit fini à toute épreuve. J’opère dans un environnement contrôler où les risques sont minimisés. Vous pouvez vous en inspirer, mais la sécurité est sous votre seule responsabilité.

But

Le but est de contrôler le déplacement d’une tondeuse avec une manette de jeux sans fil PS4. Éventuellement, j’aimerais qu’elle tonde une partie de la pelouse de façon autonome.

Démo vidéo

La tondeuse est pilotée par un ESP32. Une communication maître-esclave est établie automatiquement entre le ESP32 et un Arduino UNO surmonté d’un USB Host Shield 2.0. Le HC05 relié sur le ESP32 est configuré en mode esclave [AT+ROLE = 0 ]. Le HC-05 relié sur l’Arduino est configuré en mode maître [AT+ROLE = 1 ]. Le HC-05 maître est paramétré pour se connecter directement à l’adresse Bluetooth du circuit esclave [ AT+CMODE( set address pairing)].

2 pilotes moteur H-Bridge haut courant 43A BTS7960

Arduino + USB Host Shield 2.0 + HC05 + DUALSHOCK™4 wireless controller


Composants et fournitures

USB Host Shield Compatible For Google Android ADK Support U NO MEGA Module

USB Bluetooth V4.0.3. Mini adaptateur Dongle sans fil

350W 24V 2750 RPM ZY1016 Electric Motor E-bike Brushed Scooter Electric Motor

Geekcreit® ESP32 Development Board WiFi+bluetooth Ultra Low Power Consumption Dual Cores ESP-32 ESP-32S Board

Double BTS7960B DC 43A Stepper Motor Driver H-Bridge PWM For Arduino Smart Car

25H 78 TEETH CHAIN SPROCKET FITS 47 49cc BIKE ATV QUAD DIRT MINI POCKET ROCKET

HC-05 Wireless Bluetooth RF Transceiver Module serial RS232 TTL for arduino

Geekcreit® UNO R3 ATmega328P Development Board For Arduino No Cable

Everbilt Roulette pivotante moyenne de 203 mm à roue pneumatique

Plaquette de montage pour le ESP32 DEVKIT V1 DOIT ici


Fonctionnement global

La manette PS4 transmet les commandes au USB HOST.

Le logiciel dans l’ARDUINO UNO fait un prétraitement des commandes et les envoie au ESP32 par l’entremise du circuit Bluetooth HC-05. Ce circuit est configuré en mode Maitre et l’adresse de la carte HC-05 Esclave est programmée dans la carte Maitre. Le circuit HC-05 Esclave est relié sur le ESP32.

La communication Maitre-Esclave s’établit instantanément, lors de la mise sous tension.

La manette est connectée au «USB HOST-ARDUINO» en appuyant sur le bouton PS de la manette. Ils doivent avoir été jumelés préalablement (voir la procédure dans le programme du «USB HOST-ARDUINO»).

Les commandes sont :

  • Avance -> Joystick droit poussé vers l’avant
  • Recule -> Joystick droit poussé vers l’arrière
  • Tourne à droite -> Joystick droit poussé du côté droit
  • Tourne à gauche -> Joystick droit poussé du côté gauche
  • Stop -> Joystick droit libre
  • Augmente la vitesse -> Bouton Triangle
  • Diminue la vitesse -> Bouton Croix
  • Moteur de la roue à l’intérieur du virage inactif : roue libre -> Bouton Cercle
  • Moteur de la roue à l’intérieur du virage actif : Rotation inverse de la roue à l’extérieur du virage et à vitesse moindre -> Bouton Carré

Les roues avant ne sont pas motorisées. Ce sont des roues pivotantes.

La motricité et la direction sont accomplies par les roues arrière.

Sécurité

Si la manette n’est pas en connectée avec l’Arduino, celui-ci envoie la commande «STOP» au ESP32.

Si aucune commande n’est exécutée par l’opérateur depuis 200ms, le programme dans l’Arduino transmet la commande «STOP» au ESP32.

Si le ESP32 n’a rien reçu de l’Arduino depuis 300ms, la commande «STOP» est exécutée

Un «chien de garde» est programmé dans le ESP32. Un réamorçage est forcé automatiquement si le programme ne revient pas au début du «Loop» après un certain délai programmable ( 2 secondes ).

Les moteurs ont une source d’alimentation différente du ESP32. Si les deux avaient la même source d’alimentation, un dysfonctionnement du programme pourrait se produire lors d’une baisse de tension. Ceci pourrait occasionner une perte de contrôle de la tondeuse.

Un interrupteur de sécurité est installé pour couper l’alimentation des moteurs CC au besoin.

La tondeuse est à essence. Une tringle sur le manche doit être tenue par l’opérateur pour garder le moteur actif. Pour remplacer l’opérateur, une corde est bouclée pour tenir la tringle à la bonne position afin de garder le moteur de la tondeuse actif. Un grand bout de la corde pend, il suffit de tirer d’un coup sec pour arrêter le moteur.

Pour éviter les problèmes de communication, je porte à la ceinture l’ensemble «USB-host, Arduino» et je ne suis jamais très loin de la tondeuse.


Détails d’assemblage


L’antidérailleur-tenseur de chaîne est un assemblage de tige fileté , d’écrous, de rondelles de métal, de rondelles de blocage et de roulement à billes.


Extrait d’un échange vers le ESP32 et interprétation

Comme le «USB HOST-ARDUINO» doit pouvoir servir au contrôle de divers systèmes, le code employé est neutre. L’association à des actions particulières se configure dans le système à contrôler.

[ |DATA|———————S| ] <- demande d’arrêt des moteurs

[ |DATA|U———————| ] <- Demande d’avancer

[ |DATA|D———————| ] <- Demande de reculer

[ |DATA|-R——————–| ] <- Demande de tourner à droite

[ |DATA|-L——————–| ] <- Demande de tourner à gauche


Programmation

Le programme du véhicule

Le programme est divisé en deux fichiers. Ils doivent être placés dans le même répertoire nommé «Tondeuse_teleguidee_CMD_PS4_ESP32_V16_B»

Tondeuse_teleguidee_CMD_PS4_ESP32_V16_B.ino

/*
 * 
 *  Création : 2018-03-22
 *  Auteur : Richard Morel 
 * 
 * 
 * Modifications
 *  2019-06-20
 *    - Lors des virages le moteur à l'intérieur du virage est non alimenté
 *    
 *  2019-06-21
 *    - Lors des virages les moteurs tournent dans le sens opposé l'un de l'autre
 *    
 *  2019-06-22  
 *    - Le mode de fonctionnement de la roue intérieur lors d'un virage est optionnelle
 *      Un clic de la touche Cercle  vitesse = 0
 *      Un clic de la touche Carré   vitesse = 100
 *      
 *  2019-07-30
 *    Ajout de validation des échanges
 *    Si il n'y a aucune communication depuis plus de 300ms
 *    l'ordre est envoyé d'arrêter les moteurs
 *  
 *  2019-07-30
 *    Ajout d'un chien de garde en cas de blocage de programme
 *    
 *  2019-08-12
 *    Ajout de la variable boSensAvance pour déterminer le sens de déplacement
 *    Le contrôle des moteurs lors des changements de direction dépend du sens de déplacement
 *    juste avant le changement de direction
 *    
 *  2019-10-04
 *    Adapter à la nouvelle configuration du récepteur de la manette USBHOST-ARDUINO
 *    
 *    
 *  *      ADRESSE BLUETOOTH VOITURETTE : 211345651
 *  *                   HC05 51
 *  
 * *** There are three serial ports on the ESP known as U0UXD, U1UXD and U2UXD.
 * 
 * U0UXD is used to communicate with the ESP32 for programming and during reset/boot.
 * U1UXD is unused and can be used for your projects. Some boards use this port for SPI Flash access though
 * U2UXD is unused and can be used for your projects.
 * 
*/


#include "configuration.h"
#include "esp_system.h"   // Pour le watch Dog

#define RXD2 16
#define TXD2 17

    
//HardwareSerial Serial2(2);


//************************************************************************
//
// FONCTION FONCTION FONCTION FONCTION FONCTION FONCTION FONCTION FONCTION
//
//************************************************************************

//  ledcAttachPin(FwrdSpeedMotRight, pwmChannel_0);
//  ledcAttachPin(RvrsSpeedMotRight, pwmChannel_1); 
//  ledcAttachPin(FwrdSpeedMotLeft,  pwmChannel_2);
//  ledcAttachPin(RvrsSpeedMotLeft,  pwmChannel_3);

/*motor control*/
void go_Advance(void)  //Forward
{
  // Pour faire avancer le véhicule le moteur de gauche
  // doit tourner dans le sens anti-horaire
  // Pour simplifier la programmation, le moteur est relier 
  // en polarité inverse sur le contrôleur BTS7960
  
  digitalWrite(FwrdEnMotRight, HIGH); 
  digitalWrite(RvrsEnMotRight, HIGH); 
  digitalWrite(FwrdEnMotLeft,  HIGH);
  digitalWrite(RvrsEnMotLeft,  HIGH); 
  ledcWrite(pwmChannel_0, vitesse);  // FwrdSpeedMotRight
  ledcWrite(pwmChannel_2, vitesse);  // FwrdSpeedMotLeft    
  ledcWrite(pwmChannel_1, 000);      // RvrsSpeedMotRight 
  ledcWrite(pwmChannel_3, 000);      // RvrsSpeedMotLeft
}

void go_Right(void)  //Turn right
{
  digitalWrite(FwrdEnMotRight, HIGH);
  digitalWrite(RvrsEnMotRight, HIGH);
  digitalWrite(FwrdEnMotLeft,  HIGH);
  digitalWrite(RvrsEnMotLeft,  HIGH); 
  if (boSensAvance){
  ledcWrite(pwmChannel_0, 000);          // FwrdSpeedMotRight
  ledcWrite(pwmChannel_2, 255);          // FwrdSpeedMotLeft
  ledcWrite(pwmChannel_1, vitesseRInt);  // RvrsSpeedMotRight
  ledcWrite(pwmChannel_3, 000);          // RvrsSpeedMotLeft
  } 
  else{
  ledcWrite(pwmChannel_0, 000);          // FwrdSpeedMotRight
  ledcWrite(pwmChannel_2, vitesseRInt);  // FwrdSpeedMotLeft
  ledcWrite(pwmChannel_1, 255);          // RvrsSpeedMotRight
  ledcWrite(pwmChannel_3, 000);          // RvrsSpeedMotLeft 
  }
}

void go_Left(void)  //Turn left
{
  digitalWrite(FwrdEnMotRight, HIGH);
  digitalWrite(RvrsEnMotRight, HIGH); 
  digitalWrite(FwrdEnMotLeft,  HIGH);
  digitalWrite(RvrsEnMotLeft,  HIGH); 
  if (boSensAvance){
    ledcWrite(pwmChannel_0, 255);          // FwrdSpeedMotRight
    ledcWrite(pwmChannel_2, 000);          // FwrdSpeedMotLeft
    ledcWrite(pwmChannel_1, 000);          // RvrsSpeedMotRight
    ledcWrite(pwmChannel_3, vitesseRInt);  // RvrsSpeedMotLeft
  }  
  else{
    ledcWrite(pwmChannel_0, vitesseRInt);  // FwrdSpeedMotRight
    ledcWrite(pwmChannel_2, 000);          // FwrdSpeedMotLeft
    ledcWrite(pwmChannel_1, 000);          // RvrsSpeedMotRight
    ledcWrite(pwmChannel_3, 255);          // RvrsSpeedMotLeft  
  }
}

void go_Back(void)  //Reverse
{
  digitalWrite(FwrdEnMotRight, HIGH);
  digitalWrite(RvrsEnMotRight, HIGH);
  digitalWrite(FwrdEnMotLeft,  HIGH);
  digitalWrite(RvrsEnMotLeft,  HIGH); 
  ledcWrite(pwmChannel_0, 000);      // FwrdSpeedMotRight
  ledcWrite(pwmChannel_2, 000);      // FwrdSpeedMotLeft
  ledcWrite(pwmChannel_1, vitesse);  // RvrsSpeedMotRight
  ledcWrite(pwmChannel_3, vitesse);  // RvrsSpeedMotLeft
}
void stop_Stop()    //Stop
{
  digitalWrite(FwrdEnMotRight, LOW);
  digitalWrite(RvrsEnMotRight, LOW);
  digitalWrite(FwrdEnMotLeft,  LOW);
  digitalWrite(RvrsEnMotLeft,  LOW); 
}



void augmente_Vitesse()
{
  vitesse = vitesse + 10;
  if (vitesse > 255){ vitesse = 255;}
  //Serial.print("Vitesse : ");
  //Serial.println(vitesse);
}


void diminue_Vitesse()
{
  vitesse = vitesse - 10;
  if (vitesse > 255){ vitesse = 0;} // vitesse est unsigned int  
  //Serial.print("Vitesse : ");
  //Serial.println(vitesse);
}

void annuleDataLu()  // annule le dataLu et FORCE UN ARRÊT
{       
  actionRightHatY = 0;
  actionRightHatX = 0;
  actionLeftHatY  = 0;
  actionLeftHatX  = 0;
  actionBoutonR2  = 0; // AnalogButton(R2)
  actionBoutonL2  = 0; // AnalogButton(L2)
  boutonTriangle  = 0; // boutonTriangle
  boutonCircle    = 0; // boutonCircle
  boutonCross     = 0; // boutonCross
  boutonSquare    = 0; // boutonSquare
  boutonUp        = 0; // boutonUp
  boutonRight     = 0; // boutonRight
  boutonDown      = 0; // boutonDown
  boutonLeft      = 0; // boutonLeft
  boutonL1        = 0; // boutonL1
  boutonL3        = 0; // boutonL3
  boutonR1        = 0; // boutonR1
  boutonR3        = 0; // boutonR3
  boutonShare     = 0; // boutonShare
  boutonOptions   = 0; // boutonOptions
  boutonTouchpad  = 0; // boutonTouchpad
  actionJsStop    = 83; // Joystick Right au centre S STOP  
}

void IRAM_ATTR resetModule() {
  ets_printf("reboot\n");
  esp_restart();
}

//************************************************************************
//
// SETUP   SETUP   SETUP   SETUP   SETUP   SETUP   SETUP  SETUP  SETUP
//
//************************************************************************

void setup() {
  startCmptDelaiSansCOMM = millis();
  // Note the format for setting a serial port is as follows: Serial2.begin(baud-rate, protocol, RX pin, TX pin);
  Serial.begin(115200);
  Serial.println("Programme : Tondeuse_teleguidee_CMD_PS4_ESP32_V16_B");
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2);
  Serial.println("Serial Rxd2 is on pin: "+String(RXD2));
  Serial.println("Serial Txd2 is on pin: "+String(TXD2));


  pinMode(FwrdEnMotRight ,   OUTPUT); 
  pinMode(FwrdSpeedMotRight, OUTPUT); 
  pinMode(RvrsSpeedMotRight, OUTPUT);  
  pinMode(RvrsEnMotRight,    OUTPUT);

  pinMode(FwrdEnMotLeft   ,  OUTPUT); 
  pinMode(FwrdSpeedMotLeft,  OUTPUT); 
  pinMode(RvrsSpeedMotLeft,  OUTPUT);  
  pinMode(RvrsEnMotLeft ,    OUTPUT);



  stop_Stop();

  // configure LED PWM functionalitites
  ledcSetup(pwmChannel_0, freq, resolution);
  ledcSetup(pwmChannel_1, freq, resolution);
  ledcSetup(pwmChannel_2, freq, resolution);
  ledcSetup(pwmChannel_3, freq, resolution);
  
  // attach the channel to the GPIO to be controlled
  ledcAttachPin(FwrdSpeedMotRight, pwmChannel_0);
  ledcAttachPin(RvrsSpeedMotRight, pwmChannel_1); 
  ledcAttachPin(FwrdSpeedMotLeft,  pwmChannel_2);
  ledcAttachPin(RvrsSpeedMotLeft,  pwmChannel_3);

  /** Pour le chien de garde **/
  timer = timerBegin(0, 80, true);                  //timer 0, div 80
  timerAttachInterrupt(timer, &resetModule, true);  //attach callback
  timerAlarmWrite(timer, wdtTimeout * 1000, false); //set time in us
  timerAlarmEnable(timer);                          //enable interrupt
}


//************************************************************************
//
// LOOP   LOOP   LOOP   LOOP   LOOP   LOOP   LOOP  LOOP  LOOP
//
//************************************************************************


void loop() { //Choose Serial1 or Serial2 as required
    timerWrite(timer, 0); //reset timer (feed watchdog)

   // Timer pour le KEEP-ALIVE
   diffMillisecondeCOMM = millis() - startCmptDelaiSansCOMM;
   //Serial.println(diffMillisecondeCOMM);
   if (diffMillisecondeCOMM > 300){ // Au moins une réception valide au 300 ms
       stop_Stop();  // ARRÊTE TOUT
       boAnnuleDataLu = true;
       Serial.println("Pas de communication valide avec le contrôleur");
       startCmptDelaiSansCOMM = millis(); 
   }
   
   if(Serial2.available()) {
      //Serial.print(char(Serial2.read()));
      boAnnuleDataLu = false;  
      
      unsigned int dataLu = Serial2.read();
      //Serial.print(dataLu);
      //Serial.print(" ");
      if ((dataLu) == 91){  // 91 = [
        //****** Début de trame *****
         i = 0;
         boTrameValide = true;
      }
      if (boTrameValide){
         CmdPS4[i]= dataLu;

         Serial.print(char(CmdPS4[i])); // POUR SUIVRE LA TRAME REÇUE
         i++;
         if ((dataLu) == 93){  // = ]
            //****** Fin de trame *****
            startCmptDelaiSansCOMM = millis(); // Réinitialise le compteur de délai de non communication
            boTrameValide = false;
            //Serial.println("");
            Serial.println(i);
            i = 0;
         }
       }
  } // fin du if(Serial2.available()) 

  if (boAnnuleDataLu) {  // annule le dataLu et FORCE UN ARRÊT
     annuleDataLu();
  }
  
  actionRightHatY = CmdPS4[8];
  actionRightHatX = CmdPS4[9];
  actionLeftHatY  = CmdPS4[10];
  actionLeftHatX  = CmdPS4[11];
  actionBoutonL2  = CmdPS4[12]; // AnalogButton(L2)
  actionBoutonR2  = CmdPS4[13]; // AnalogButton(R2)
  boutonTriangle  = CmdPS4[14]; // boutonTriangle
  boutonCircle    = CmdPS4[15]; // boutonCircle
  boutonCross     = CmdPS4[16]; // boutonCross
  boutonSquare    = CmdPS4[17]; // boutonSquare
  boutonUp        = CmdPS4[18]; // boutonUp
  boutonRight     = CmdPS4[19]; // boutonRight
  boutonDown      = CmdPS4[20]; // boutonDown
  boutonLeft      = CmdPS4[21]; // boutonLeft
  boutonL1        = CmdPS4[22]; // boutonL1
  boutonL3        = CmdPS4[23]; // boutonL3
  boutonR1        = CmdPS4[24]; // boutonR1
  boutonR3        = CmdPS4[25]; // boutonR3
  boutonShare     = CmdPS4[26]; // boutonShare
  boutonOptions   = CmdPS4[27]; // boutonOptions
  boutonTouchpad  = CmdPS4[28]; // boutonTouchpad 
  actionJsStop    = CmdPS4[29]; // Joystick Right au centre

    
  //Serial.println(char(CmdPS4[29]));

 // vitesse    = 127;
  vitesseL   = vitesse;
  vitesseR   = vitesse;

  if (actionJsStop == 83) {         // S ---> Stop
     //Serial.println("JoyStick Stop");
      stop_Stop();
  }
  
  if (actionRightHatY == 85) {      // U ---> Avance
      //Serial.println("Avance");
      boSensAvance = true;
      go_Advance();
  }

  if (actionRightHatY == 68) {      // D ---> Recule
    // Serial.println("Recule");
      boSensAvance = false;
      go_Back();
  }
  
  if (actionRightHatX == 82) {      // R ---> Tourne à DROITE
    // Serial.println("Tourne à DROITE");
      go_Right();
  }
  
  if (actionRightHatX == 76) {      // L ---> Tourne à GAUCHE
    // Serial.println("Tourne à GAUCHE");
      go_Left();
  }
  
  if (boutonTriangle == 84) {       // T ---> Augmente la vitesse
    //Serial.println("Augmente la vitesse");
    augmente_Vitesse();
    CmdPS4[14] = 0;
  }

  if (boutonCross   == 88) {       // X ---> Diminue la vitesse
    //Serial.println("Diminue la vitesse");
    diminue_Vitesse();
    CmdPS4[16] = 0;
  }
  
  if (boutonCircle  == 67) {      // C ---> vitesse roue
      vitesseRInt = 0;            //   à l'intérieur du virage = 0
  }
  
  if (boutonSquare  == 83) {      // S ---> vitesse roue
      vitesseRInt = 100;          //   à l'intérieur du virage = 100
  }
  
}  // Fin du loop()

configuration.h

/*
              BRANCHEMENT POUR ESP32
*/
//Define BTS7960 (IBT-2) H-Bridge Motor Controller Pins

#define FwrdEnMotRight    33         // (BLEU) - R_EN - FORWARD enable  motor Right
#define FwrdSpeedMotRight 25         // (JAUNE)- RPWM - FORWARD speed   motor Right
#define RvrsSpeedMotRight 26         // (VERT) - LPWM - REVERSE speed   motor Right
#define RvrsEnMotRight    27         // (GRIS) - L_EN - REVERSE enable  motor Right

#define FwrdEnMotLeft     14         // (BLEU) - R_EN - FORWARD enable  motor Left
#define FwrdSpeedMotLeft  04         // (JAUNE)- RPWM - FORWARD speed   motor Left
#define RvrsSpeedMotLeft  15         // (VERT) - LPWM - REVERSE speed   motor Left
#define RvrsEnMotLeft     00         // (GRIS) - L_EN - REVERSE enable  motor Left

// Setting PWM properties
const int freq                = 500;
const int pwmChannel_0        = 0;
const int pwmChannel_1        = 1;
const int pwmChannel_2        = 2;
const int pwmChannel_3        = 3;
const int resolution          = 8; //(8 bits -> 0-255)

unsigned int vitesse          = 255;
unsigned int vitesseL         = 0;
unsigned int vitesseR         = 0;
unsigned int vitesseRInt      = 0;
bool boSensAvance             = true;

unsigned int actionRightHatY  = 0; 
unsigned int actionRightHatX  = 0;
unsigned int actionLeftHatY   = 0; 
unsigned int actionLeftHatX   = 0;

unsigned int leftHatX         = 0;
unsigned int leftHatY         = 0;  
unsigned int rightHatX        = 0;
unsigned int rightHatY        = 0;
unsigned int actionBoutonR2   = 0;
unsigned int actionBoutonL2   = 0;

unsigned int boutonTriangle   = 0;
unsigned int boutonCircle     = 0;
unsigned int boutonCross      = 0;
unsigned int boutonSquare     = 0;
unsigned int boutonUp         = 0;
unsigned int boutonRight      = 0;
unsigned int boutonDown       = 0;
unsigned int boutonLeft       = 0;
unsigned int boutonL1         = 0;
unsigned int boutonL3         = 0;
unsigned int boutonR1         = 0;
unsigned int boutonR3         = 0;
unsigned int boutonShare      = 0;
unsigned int boutonOptions    = 0;
unsigned int boutonTouchpad   = 0;

unsigned int actionJsStop     = 0;

int i                         = 0;
unsigned char CmdPS4[60]      = "";
bool boTrameValide            = false;
bool boAnnuleDataLu           = false;

uint64_t startCmptDelaiSansCOMM = 0;
uint32_t diffMillisecondeCOMM   = 0;

const int wdtTimeout = 2000;  //time in ms to trigger the watchdog
hw_timer_t *timer    = NULL;

Le programme du «USB HOST-ARDUINO». Contrôleur jumelé à la manette.

Le programme est composé d’un fichier. Il doit être placé dans le répertoire nommé «Manette_PS4_CTRL_CAR_BT-HC05_MASTER_SC_2

/*
 *        Contrôleur associé avec une manette PS4
 *    PS4 + USB Host shield 2 + Bluetooth HC-05 MAITRE 
 *    
 *  2019-06-02  
 *  Création: Richard Morel
 *  
 * Adapté de : 
 * Example sketch for the PS4 Bluetooth library - developed by Kristian Lauszus
 * For more information visit my blog: http://blog.tkjelectronics.dk/ or
 * http://blog.tkjelectronics.dk/2014/01/ps4-controller-now-supported-by-the-usb-host-library/#comment-85113
 * or send me an e-mail:  kristianl@tkjelectronics.com
 * 
 * Info boutons manette PS4
 * https://manuals.playstation.net/document/gb/ps4/basic/pn_controller.html
 */

#include <PS4BT.h>
#include <usbhub.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

bool boCmdTransmit    = false;

int actionRightHatY   = 0; 
int actionRightHatX   = 0;
int oldRightHatY      = 0; 
int oldRightHatX      = 0;

int actionLeftHatY    = 0; 
int actionLeftHatX    = 0;
int oldLeftHatY       = 0; 
int oldLeftHatX       = 0;

int actionBoutonL2    = 0; 
int actionBoutonR2    = 0;
int oldBoutonL2       = 0; 
int oldBoutonR2       = 0;

int boutonTriangle    = 0;
int boutonCircle      = 0;
int boutonCross       = 0;
int boutonSquare      = 0;
int boutonUp          = 0;
int boutonRight       = 0;
int boutonDown        = 0;
int boutonLeft        = 0;
int boutonL1          = 0;
int boutonL3          = 0;
int boutonR1          = 0;
int boutonR3          = 0;
int boutonShare       = 0;
int boutonOptions     = 0;
int boutonTouchpad    = 0;

int actionJsStop      = 0;
int oldJsStop         = 0;


uint64_t startCmptDelaiSansCOMM = 0;
uint64_t diffMillisecondeCOMM   = 0;

USB Usb;
//USBHub Hub1(&Usb); // Some dongles have a hub inside
BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so

/* You can create the instance of the PS4BT class in two ways */
// This will start an inquiry and then pair with the PS4 controller - you only have to do this once
// You will need to hold down the PS and Share button at the same time, 
// the PS4 controller will then start to blink rapidly indicating that it is in pairing mode

/* Vous pouvez créer l'instance de la classe PS4BT de deux manières */
// Cela lancera une requête, puis jumelera le USB HOSt avec le contrôleur PS4 - vous ne devez le faire qu'une fois
// Vous devrez maintenir simultanément les boutons PS et Share enfoncés, 
// le contrôleur PS4 se mettra alors à clignoter rapidement pour indiquer qu'il est en mode de couplage.

//PS4BT PS4(&Btd, PAIR);

// PS s'affiche lorsque le jumelage est complété. Mettre (PS4BT PS4(&Btd, PAIR);) en commentaire
// et enlever «//» à (PS4BT PS4(&Btd);) puis retransférer le programme dans l'Arduino

// After that you can simply create the instance like so and then press the PS button on the device
// Après cela, vous pouvez simplement créer l’instance comme suit, puis appuyer sur le bouton PS de l’appareil.
// PS4BT PS4(&Btd);

PS4BT PS4(&Btd);

int state = 0;


//************************************************************************
//
// FONCTION FONCTION FONCTION FONCTION FONCTION FONCTION FONCTION FONCTION
//
//************************************************************************


void transmetCommandes()
{
 int inPauseDeTransmission = 1;  // Si requis, temporisation pour éviter les engorgements   
      Serial.write("[ |");                           delay(inPauseDeTransmission);
      Serial.write("DATA|");                         delay(inPauseDeTransmission);
      Serial.write(actionRightHatY);                 delay(inPauseDeTransmission);
      Serial.write(actionRightHatX);                 delay(inPauseDeTransmission);
      Serial.write(actionLeftHatY);                  delay(inPauseDeTransmission);
      Serial.write(actionLeftHatX);                  delay(inPauseDeTransmission);
      Serial.write(actionBoutonL2);                  delay(inPauseDeTransmission);
      Serial.write(actionBoutonR2);                  delay(inPauseDeTransmission);
      Serial.write(boutonTriangle);                  delay(inPauseDeTransmission);
      Serial.write(boutonCircle);                    delay(inPauseDeTransmission);
      Serial.write(boutonCross);                     delay(inPauseDeTransmission);
      Serial.write(boutonSquare);                    delay(inPauseDeTransmission);
      Serial.write(boutonUp);                        delay(inPauseDeTransmission);
      Serial.write(boutonRight);                     delay(inPauseDeTransmission);
      Serial.write(boutonDown);                      delay(inPauseDeTransmission);
      Serial.write(boutonLeft);                      delay(inPauseDeTransmission);
      Serial.write(boutonL1);                        delay(inPauseDeTransmission);
      Serial.write(boutonL3);                        delay(inPauseDeTransmission);
      Serial.write(boutonR1);                        delay(inPauseDeTransmission);
      Serial.write(boutonR3);                        delay(inPauseDeTransmission);
      Serial.write(boutonShare);                     delay(inPauseDeTransmission);
      Serial.write(boutonOptions);                   delay(inPauseDeTransmission);
      Serial.write(boutonTouchpad);                  delay(inPauseDeTransmission);
      Serial.write(actionJsStop);                    delay(inPauseDeTransmission);      
      Serial.write("| ]");                           delay(inPauseDeTransmission);
      Serial.println(); 
} 

void  cmdStop()
{
      actionJsStop    = 83; // S STOP LA MACHINE
}     

void  initCmd()
{
      actionRightHatY = 45; // code ASCII pour -
      actionRightHatX = 45;
      actionLeftHatY  = 45;
      actionLeftHatX  = 45;
      actionBoutonL2  = 45;
      actionBoutonR2  = 45;
      boutonTriangle  = 45;
      boutonCircle    = 45;
      boutonCross     = 45;
      boutonSquare    = 45;
      boutonUp        = 45;
      boutonRight     = 45;
      boutonDown      = 45;  
      boutonLeft      = 45;
      boutonL1        = 45;
      boutonL3        = 45;
      boutonR1        = 45;
      boutonR3        = 45;
      boutonShare     = 45;
      boutonOptions   = 45;
      boutonTouchpad  = 45;
      actionJsStop    = 45;
}  

      
//************************************************************************
//
// SETUP   SETUP   SETUP   SETUP   SETUP   SETUP   SETUP  SETUP  SETUP
//
//************************************************************************

      
void setup() {
  Serial.begin(115200);
  Serial.println("Programme : Manette_PS4_CTRL_CAR_BT-HC05_MASTER_SC_24"); 
#if !defined(__MIPSEL__)
  while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
  if (Usb.Init() == -1) {
    Serial.print(F("\r\nOSC did not start"));
    while (1); // Halt
  }
  Serial.print(F("\r\nPS4 Bluetooth Library Started"));

  startCmptDelaiSansCOMM = millis();
}


//************************************************************************
//
// LOOP   LOOP   LOOP   LOOP   LOOP   LOOP   LOOP  LOOP  LOOP
//
//************************************************************************

void loop() {
  if (boCmdTransmit){   
     transmetCommandes();
     startCmptDelaiSansCOMM = millis();
     boCmdTransmit = false;
  } // Fin du if ( boCmdTransmit){

  // Timer pour le KEEP-ALIVE
  // Si aucune commande requise par l'opérateur depuis 200ms envoie la commande STOP
  diffMillisecondeCOMM = millis() - startCmptDelaiSansCOMM;
  if (diffMillisecondeCOMM > 200){ // Au moins une transmission au 200 ms  
      initCmd();                   // Remet les commandes à - par défaut
      actionJsStop    = 83;        // S STOP
      boCmdTransmit = true;
      //Serial.print("KA " );  
  }
  
   if(Serial.available() > 0){ // Checks whether data is comming from the serial port
    state = Serial.read();     // Reads the data from the serial port
  }

  Usb.Task();
  if (PS4.connected()) {
       initCmd();

      // --  La manette PS4 envoie en continue les valeurs analogiques
      // --  La manette PS4 envoie l'état du bouton seulement au moment du click

      //*********** JOYSTICK DE DROITE *************
      if (PS4.getAnalogHat(RightHatY) < 25) {
          actionRightHatY   = 85;  // U
          oldJsStop         = 0;
          // Logique pour limiter la transmission sur le changement analogique
          // et indirectement au Keep-Alive si la condition est maintenue
          if (oldRightHatY != actionRightHatY){
             boCmdTransmit = true;
             oldRightHatY = actionRightHatY;
          }    
      }
      
      if (PS4.getAnalogHat(RightHatY) > 200) {
          actionRightHatY   = 68;  // D
          oldJsStop = 0;
          if (oldRightHatY != actionRightHatY){
              boCmdTransmit = true;
              oldRightHatY = actionRightHatY; 
          }               
      }
            
      if (PS4.getAnalogHat(RightHatX) > 200) {
           actionRightHatX   = 82;  // R
           oldJsStop = 0;
           if (oldRightHatX != actionRightHatX){
              boCmdTransmit = true;
              oldRightHatX = actionRightHatX; 
          } 
      }

      if (PS4.getAnalogHat(RightHatX) < 25) {
           actionRightHatX   = 76;  // L
           oldJsStop = 0;
           if (oldRightHatX != actionRightHatX){
              boCmdTransmit = true;
              oldRightHatX = actionRightHatX;   
          } 
      }

      if (PS4.getAnalogHat(RightHatY) >= 25 && PS4.getAnalogHat(RightHatY) <= 200 && PS4.getAnalogHat(RightHatX) >= 25 && PS4.getAnalogHat(RightHatX) <= 200) {
           actionJsStop   = 83;  // S
           oldRightHatY = 0;
           oldRightHatX = 0;  
           if (oldJsStop != actionJsStop){
              boCmdTransmit = true;
              oldJsStop = actionJsStop;
          } 
      }

      //************ JOYSTICK DE GAUCHE *****************
      if (PS4.getAnalogHat(LeftHatY) < 25) {
          actionLeftHatY   = 85;  // U
          if (oldLeftHatY != actionLeftHatY){
             boCmdTransmit = true;
             oldLeftHatY   = actionLeftHatY;
             oldJsStop = 0;
          }    
      }
      
      if (PS4.getAnalogHat(LeftHatY) > 200) {
          actionLeftHatY   = 68;  // D
          if (oldLeftHatY != actionLeftHatY){
              boCmdTransmit = true;
              oldLeftHatY = actionLeftHatY;
              oldJsStop = 0;
          }               
      }
            
      if (PS4.getAnalogHat(LeftHatX) > 200) {
           actionLeftHatX   = 76;  // L
           if (oldLeftHatX != actionLeftHatX){
              boCmdTransmit = true;
              oldLeftHatX = actionLeftHatX;
              oldJsStop = 0; 
          } 
      }

      if (PS4.getAnalogHat(LeftHatX) < 25) {
           actionLeftHatX   = 82;  // R
           if (oldLeftHatX != actionLeftHatX){
             boCmdTransmit = true;
             oldLeftHatX = actionLeftHatX;
             oldJsStop = 0;   
          } 
      }

      if (PS4.getAnalogHat(LeftHatY) >= 25 && PS4.getAnalogHat(LeftHatY) <= 200 && PS4.getAnalogHat(LeftHatX) >= 25 && PS4.getAnalogHat(LeftHatX) <= 200) {
           oldLeftHatY = 0;
           oldLeftHatX = 0;  
      }

     if (PS4.getAnalogButton(L2) || PS4.getAnalogButton(R2)) {
     }

    // VALIDATION DES BOUTONS
     if (PS4.getButtonClick(PS)) {
         Serial.print(F("\r\nPS Disconnect"));
         PS4.disconnect();
       }
       else {
         if (PS4.getButtonClick(TRIANGLE)) {
            boutonTriangle = 84;  // T
            boCmdTransmit  = true;
         }
         if (PS4.getButtonClick(CIRCLE)) {
            boutonCircle   = 67;  // C
            boCmdTransmit  = true;
         }
         if (PS4.getButtonClick(CROSS)) {
            boutonCross    = 88;  // X
            boCmdTransmit  = true;
         }
         if (PS4.getButtonClick(SQUARE)) {
            boutonSquare   = 83;  // S
            boCmdTransmit  = true;
         }
      
         if (PS4.getButtonClick(UP)) {
            boutonUp       = 85;  // U
            boCmdTransmit = true;
         } 
         if (PS4.getButtonClick(RIGHT)) {
            boutonRight    = 82;  // R
            boCmdTransmit  = true;
         } 
         if (PS4.getButtonClick(DOWN)) {
            boutonDown     = 68;  // D
            boCmdTransmit = true;
         }
         if (PS4.getButtonClick(LEFT)) {
            boutonLeft     = 76;  // L
            boCmdTransmit  = true;
         }

         if (PS4.getButtonClick(L1)){
            boutonL1       = 108;  // l
            boCmdTransmit  = true;
         }   
         if (PS4.getButtonClick(L3)){
            boutonL3       = 76; // L
            boCmdTransmit = true;
         }
         if (PS4.getButtonClick(R1)){
            boutonR1       = 114; // r
            boCmdTransmit = true;
         }
         if (PS4.getButtonClick(R3)){
            boutonR3       = 82; // R
            boCmdTransmit  = true;
         }
         if (PS4.getButtonClick(SHARE)){
            boutonShare    = 83; // S
            boCmdTransmit  = true;
         }
         if (PS4.getButtonClick(OPTIONS)) {
            boutonOptions  = 79; // O 
            boCmdTransmit = true; 
         }
         
        if (PS4.getButtonClick(TOUCHPAD)) {
            boutonTouchpad = 84; // T 
            boCmdTransmit  = true; 
        }
      }
        
  }   // Fin du if (PS4.connected()) { 
  
  else { // LA MANETTE PS4 N'EST PAS CONNECTÉE
      initCmd();                   // Remet les commandes à - par défaut
      actionJsStop    = 83;        // S STOP
      boCmdTransmit = true;
      Serial.println("Manette PS4 non connectée"); 
  }
}     // Fin du Loop


Références

Arduino et la manette PS4

Bluetooth et le circuit HC-05

Moteur à courant continu

ESP32 et le module Bluetooth HC-05