Hack IR de jouet

De Wiki LOGre
Aller à la navigation Aller à la recherche

Projet réalisé par Fma38.

Projet abouti.

Présentation

Martÿn a acheté sur une brocante un jeu de construction qu'il pensait pouvoir interfacer avec un petit boîtier à relais que son fils pilote par le regard.

TODO: mettre photos du jeu

Ce jeu dispose de 3 moteurs pilotables en avant et en arrière par une télécommande à 6 boutons. Le hic, c'est que ce sont des boutons tactiles ! Et, bien sûr, tout est intégré dans une seule puce, sous un blob de plastique.

Télécommande originale.jpg

Par chance, il s'agit d'une télécommande infrarouge, facile à hacker.

Étude du protocole

Première étape, essayer l'émetteur sur un testeur de composants qui permet d'analyser les principaux protocoles IR : il ne détecte malheureusement rien :o( Il faut donc analyser les trames à un niveau plus bas.

Pour ça, on utilise une simple led IR en guise de récepteur, connectée directement à un oscillo. Et on obtient les jolis chronogrammes suivants :

Touche verte avancer Touche verte reculer Touche bleue avancer Touche bleue reculer Touche rouge avancer Touche rouge reculer

Protocole ultra-simple, donc ! Voici quelques infos complémentaires :

  • la porteuse pour l'état haut est à 36,8kHz ;
  • la période de répétition des trames est de 101ms ;
  • les états haut et bas courts durent 800µs ;
  • l'état haut long dure 2400µs ;
  • lorsqu'aucune touche n'est appuyée, la télécommande envoie régulièrement une trame stop, composée de 8 pulses courts identiques ;
  • sans réception de trame pendant environ 1s, le moteur est arrêté (la trame stop permet de l'arrêter instantanément).

Note : il est possible de piloter plusieurs moteurs en alternant les trames, mais la réception d'une nouvelle trame agit comme un stop pour le moteur en train de tourner, ce qui rend la rotation des moteurs un peu hachée. La télécommande d'origine n'alterne pas les trames lorsque plusieurs boutons sont utilisés (c'est celle du premier bouton qui reste active).

Nouvelle télécommande

La même led IR connectée à un Arduino, un petit bout de code, et hop ! c'est emballé.

Télécommande hackée.jpg

La librairie infrarouge utilisée est celle-ci : orientée objet, facile à utiliser, et très bien documentée.

Voici un petit code d'exemple simulant le fonctionnement de la télécommande d'origine :

// It requires an IR-Led connected to the sending pin
// (3 on Uno/Nano, 9 on Leonardo/Uno, 9 on Mega2560 etc...)

# include <stdint.h>

#include <IrSenderPwm.h>

static constexpr frequency_t frequency = 36800U;

static constexpr pin_t PIN_IR = 3U;
static constexpr pin_t BUTTONGREEN_UP = 4U;
static constexpr pin_t BUTTONGREEN_DOWN = 5U;
static constexpr pin_t BUTTONBLUE_UP = 6U;
static constexpr pin_t BUTTONBLUE_DOWN = 7U;
static constexpr pin_t BUTTONRED_UP = 8U;
static constexpr pin_t BUTTONRED_DOWN = 9U;

static const uint16_t SHORT = 800;
static const uint16_t LONG = 2400;

static const microseconds_t stopArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT
};
static const microseconds_t greenUpArray[] = {
   SHORT, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT
};
static const microseconds_t greenDownArray[] = {
   SHORT, SHORT, LONG, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT
};
static const microseconds_t blueUpArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT
};
static const microseconds_t blueDownArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, LONG, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT
};
static const microseconds_t redUpArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, SHORT, SHORT, LONG, SHORT
};
static const microseconds_t redDownArray[] = {
   SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, SHORT, LONG, SHORT, LONG, SHORT, SHORT, SHORT
};

static const IrSequence stopSeq(stopArray, sizeof(stopArray)/sizeof(microseconds_t));
static const IrSequence greenUpSeq(greenUpArray, sizeof(greenUpArray)/sizeof(microseconds_t));
static const IrSequence greenDownSeq(greenDownArray, sizeof(greenDownArray)/sizeof(microseconds_t));
static const IrSequence blueUpSeq(blueUpArray, sizeof(blueUpArray)/sizeof(microseconds_t));
static const IrSequence blueDownSeq(blueDownArray, sizeof(blueDownArray)/sizeof(microseconds_t));
static const IrSequence redUpSeq(redUpArray, sizeof(redUpArray)/sizeof(microseconds_t));
static const IrSequence redDownSeq(redDownArray, sizeof(redDownArray)/sizeof(microseconds_t));

static IrSender* irSender;

long lastSeqStartTime = millis();


void setup()
{
    Serial.begin(115200);
    while (!Serial);

    pinMode(BUTTONGREEN_UP, INPUT_PULLUP);
    pinMode(BUTTONGREEN_DOWN, INPUT_PULLUP);
    pinMode(BUTTONBLUE_UP, INPUT_PULLUP);
    pinMode(BUTTONBLUE_DOWN, INPUT_PULLUP);
    pinMode(BUTTONRED_UP, INPUT_PULLUP);
    pinMode(BUTTONRED_DOWN, INPUT_PULLUP);
        
    if (Board::getInstance()->hasHardwarePwm()) {
        Serial.println(F("Hardware PWM available"));
    }
    else {
        Serial.println(F("Hardware PWM NOT available -> using software emulation"));
    }

    irSender = IrSenderPwm::getInstance(true, PIN_IR);
    
    Serial.print(F("Shooting @ pin "));
    Serial.println(irSender->getPin(), DEC);
}


void loop()
{
    if (!digitalRead(BUTTONGREEN_UP)) {
//        Serial.println(F("Green up seq"));
        sendSeq(&greenUpSeq);
    }
    else if (!digitalRead(BUTTONGREEN_DOWN)) {
//        Serial.println(F("Green down seq"));
        sendSeq(&greenDownSeq);
    }
    else if (!digitalRead(BUTTONBLUE_UP)) {
//        Serial.println(F("Red up seq"));
        sendSeq(&blueUpSeq);
    }
    else if (!digitalRead(BUTTONBLUE_DOWN)) {
//        Serial.println(F("Red down seq"));
        sendSeq(&blueDownSeq);
    }
    else if (!digitalRead(BUTTONRED_UP)) {
//        Serial.println(F("Blue up seq"));
        sendSeq(&redUpSeq);
    }
    else if (!digitalRead(BUTTONRED_DOWN)) {
//        Serial.println(F("Blue down seq"));
        sendSeq(&redDownSeq);
    }
    else {
//        Serial.println(F("Stop seq"));
        sendSeq(&stopSeq);
    }
}


void sendSeq(IrSequence *seq)
{
    // Ensure delay between sequences
    while ((millis()-lastSeqStartTime) < 101);
    
    lastSeqStartTime = millis();
    irSender->send(*seq, frequency);
}

Liens