Projet Nunmouse

Publié par cpb
Déc 26 2016

Nunchuck mouseUn de mes proches est atteint d’une maladie grave et cruelle qui lui rend peu à peu les mouvements des membres inférieurs puis supérieurs pénibles voire impossibles. J’ai réalisé pour lui un petit hack afin de simplifier l’usage de la souris de son ordinateur.

L’idée m’en est venue il y a déjà plusieurs mois, alors que je rédigeais un article pour le numéro Hors Série 75 de Linux Magazine dans lequel je présentais les communications en i²c avec le Raspberry Pi. En illustration de cet article, j’avais indiqué que l’on peut facilement lire la position du joystick, l’état des boutons et les valeurs renvoyées par l’accéléromètre du Nunchuck de la console Wii. J’avais alors noté l’aspect particulièrement ergonomique de cette poignée, et je me suis dit qu’elle pourrait avantageusement remplacer une souris. À cette époque, la maladie étant moins avancée, je n’ai pas mis en œuvre tout de suite ma réalisation.

Pour transformer un Nunchuck en souris, il faut tout d’abord disposer d’un système capable de se comporter en périphérique USB et apte à simuler une souris. Exit donc le Raspberry Pi qui ne dispose que d’un port USB hôte, et pas de port USB périphérique. J’avais envisagé un moment d’utiliser une BeagleBone Black, mais celle-ci était un peu encombrante pour mon projet.

Je me suis donc tourné vers une petite carte Teensy 3.2. Il s’agit d’une carte à microcontrôleur aux dimensions réduites (1,8 x 3,6 cm) et proposant de nombreuses entrées-sorties : 24 GPIO, 10 entrées analogiques, 10 PWM, des ports série, i²c, CAN, SPI, etc. En outre, elle possède un petit port mini-USB device, qui permet de la programmer et à travers lequel elle peut émuler un clavier, une souris, un joystick, un terminal série, et même des commandes pour simulateur de vol !

Teensy-3.2

La version que j’ai utilisée (Teensy 3.2) est construite autour d’un microcontrôleur Freescale K20 fonctionnant à 72MHz avec 256K de mémoire flash pour accueillir le programme et 64K de mémoire ram pour son exécution. Il existe d’autres variantes plus économiques qui auraient probablement suffi pour ce projet.

Plusieurs fournisseurs proposent ces cartes. Le site de référence est celui de Paul J. Stoffregen & Robin C. Coon : PJRC mais on les trouve chez différents distributeurs comme Sparkfun ou Adafruit. Pour limiter les frais de port et de douanes, je préfère un distributeur situé en France : Snootlab.

La programmation de la Teensy peut se faire de différentes façons, la plus simple à mettre en œuvre utilise directement l’environnement de développement de l’Arduino (avec lequel elle offre une certaine compatibilité). Le programme s’écrit en C avec les mêmes conventions que l’Arduino (fonctions setup() et loop(), lien automatique des bibliothèques de fonctions nécessaires, etc.). Le seul défaut est que le plug-in Teensyduino ne fonctionne qu’avec certaines versions de l’IDE Arduino.cc et qu’il est nécessaire de chercher dans ses anciennes versions pour en trouver une adéquate.

Lors de l’installation du plug-in, de très nombreux exemples sont ajoutés, permettant de prendre en main très facilement l’émulation USB de souris, clavier, etc. et l’utilisation des différentes entrées-sorties (GPIO , série, i²c, SPI…)

Ma première approche consistait à relier la carte Teensy au connecteur i²c à l’extrémité du câble du Nunchuck, et à simuler une souris avec les mouvements lus. Néanmoins, le fonctionnement ne me satisfaisait pas, les mouvements de la souris étaient trop saccadés, et la communication i²c ne semblait pas très fiable, j’avais même été obligé d’intégrer un système de reset automatique lorsque la Teensy ne recevait plus de données en coupant temporairement l’alimentation du Nunchuck pour le réinitialiser.

Je me suis donc orienté vers une seconde approche, consistant à ouvrir le Nunchuck, à couper le câble original et à venir relier directement les broches de la Teensy avec les points milieux des potentiomètres X et Y du joystick, et avec les contacts des boutons. Ainsi, la partie électronique du Nunchuck n’est plus utilisée (je l’ai laissée néanmoins en place pour que le PCB serve de support et alimente les potentiomètres correctement). J’ai sciemment renoncé à utiliser les informations de l’accéléromètre intégré dans le Nunchuck (qui était disponibles sur le port i²c), car elles n’avaient pas d’intérêt pour mon application.

J’ai ensuite inséré la carte Teensy directement à l’intérieur du Nunchuck en sacrifiant un support de vis pour libérer de la place. Après l’avoir refermé soigneusement et revissé l’unique vis restante (dont la tête possède une accroche triangulaire particulièrement agaçante quand on n’a pas le tournevis adapté), j’ai réalisé que j’avais oublié de photographier l’intérieur du montage. Je ne peux donc vous offrir que le schéma ci-dessous pour représenter les connexions que j’ai réalisées.

Connexions Nunchuck Teensy

Sur les connecteurs des boutons et des potentiomètres, j’ai réalisé des soudures par-dessus les points existants, sans couper les fils. Pour le connecteur i²c en revanche, j’ai coupé le câble original et soudé les deux fils d’alimentation à sa place.

Avant de refermer le Nunchuck, j’ai percé un petit trou, visible sur la photo, qui me permet de reprogrammer in-situ la carte Teensy en appuyant avec une pointe de trombone. En effet, une fois lancé l’IDE de l’Arduino en mode “upload”, il suffit d’appuyer sur le petit switch pour programmer le micro-contrôleur. Je peux ainsi l’adapter aux préférences de l’utilisateur en modifiant mon logiciel.

Le programme est disponible ci-dessous mais également sur GitHub à l’adresse https://github.com/cpb-/Nunmouse.

Nunmouse.ino:
/// ------------------------------------------------------------------------------------------
/// \file Nunmouse - Teensy program to use a modified Nunchuck as a mouse.
///
/// \author 2016 Christophe BLAESS <christophe at blaess dot fr>
///
/// \license GPL
/// ------------------------------------------------------------------------------------------

// Remember to Select "Mouse" in "Tools-> USB Type" menu.


// The nunchuck is powered up by the D17 output of the Teensy card.
#define POWER_UP_PIN  17

// The Button 1 (Z) is connected to the D20 input of the Teensy.
#define BUTTON_1_PIN  20

// The Button 2 (C) is connected to the D21 input of the Teensy.
#define BUTTON_2_PIN  21

// The central pin of the lateral potentiometer is connected to the A5 input of the Teensy.
#define POSITION_X_INPUT  5

// The central pin of the axial potentiometer is connected to the A4 input of the Teensy.
#define POSITION_Y_INPUT  4

// The delay between two measures in millisec.
#define LOOP_DELAY    15

// The tolerance around the rest position.
#define REST_POSITION_TOLERANCE  5


/// \brief Power up the nunchuck and configure the digital I/O used to read the buttons.
///
static void nunchuck_init(void)
{
  // Digital output for the Nunchuck Vcc.
  pinMode(POWER_UP_PIN, OUTPUT);
  digitalWrite(POWER_UP_PIN, HIGH);

  // Digital input for the Button 1
  pinMode(BUTTON_1_PIN, INPUT);

  // Digital input  for the Button 2
  pinMode(BUTTON_2_PIN, INPUT);

  delay(100);
}


/// \brief Read the position of the joystick and the status of the buttons.
///
/// \param joystick_x: a pointer that (if not NULL) will be filled the X position of the joystick.
/// \param joystick_y: a pointer that (if not NULL) will be filled the Y position of the joystick.
/// \param button_1: a pointer that (if not NULL) will be filled the status of the upper button.
/// \param button_2: a pointer that (if not NULL) will be filled the status of the lower button.
///
static void nunchuck_read_status(int *joystick_x, int *joystick_y, int * button_1, int *button_2)
{
  if (joystick_x != NULL)
    (*joystick_x) = analogRead(POSITION_X_INPUT);

  if (joystick_y != NULL)
    (*joystick_y) = analogRead(POSITION_Y_INPUT);
  if (button_1 != NULL)
    (*button_1) = (digitalRead(BUTTON_1_PIN) == 0);

  if (button_2 != NULL)
    (*button_2) = (digitalRead(BUTTON_2_PIN) == 0);
}



// With NUNMOUSE_DEBUG defined, The teensy acts no more as a mouse but as a serial
// port, and displays the values.
// Remember to Select "Serial" in "Tools-> USB Type" menu.
//
//#define NUNMOUSE_DEBUG

// The rest position of the joystick (position at power up).
static int rest_x;
static int rest_y;

// The maximal and minimal positions of the joystick.
static int max_x;
static int max_y;

static int min_x;
static int min_y;



/// \brief Initialization of the microcontroler program.
///
void setup()
{
  #ifdef NUNMOUSE_DEBUG
    Serial.begin(115200);
  #endif

  nunchuck_init();
  nunchuck_read_status(&rest_x, &rest_y, NULL, NULL);
  delay(100);
  nunchuck_read_status(&rest_x, &rest_y, NULL, NULL);

  max_x = rest_x + 1;
  min_x = rest_x - 1;
  max_y = rest_y + 1;
  min_y = rest_y - 1;
}



/// \brief Main microcontroler loop.
///
void loop()
{
  static int counter = 0;

  static int x = 0;
  static int y = 0;
  static int button_1 = 0;
  static int button_2 = 0;
  static int prev_button_1 = 0;
  static int prev_button_2 = 0;

  int x_mouse = 0;
  int y_mouse = 0;

  // The counter is used only in debug mode to see lines scrolling.
  counter ++;
  counter %= 1024;

  nunchuck_read_status(&x, &y, &button_1, &button_2);

  if (x < min_x) min_x = x; if (x > max_x)
    max_x = x;
  if (y < min_y) min_y = y; if (y > max_y)
    max_y = y;

  // Small tolerance around the rest position.
  if (abs(x_mouse-rest_x) < REST_POSITION_TOLERANCE)
    x_mouse = rest_x;
  if (abs(y_mouse-rest_y) < REST_POSITION_TOLERANCE)
    y_mouse = rest_y;


  // Let the values be in [-4, 4]:

  x_mouse = (x - rest_x);
  x_mouse = x_mouse * 4;
  if (x_mouse > 0) {
    x_mouse = x_mouse / (max_x - rest_x);
  } else {
    x_mouse = x_mouse / (rest_x - min_x);
  }

  y_mouse = (y - rest_y);
  y_mouse = y_mouse * 4;
  if (y_mouse > 0) {
    y_mouse = y_mouse / (max_y - rest_y);
  } else {
    y_mouse = y_mouse / (rest_y - min_y);
  }

  // x2 transformation to have a kind of acceleration when the stick is fully inclinated.
  x_mouse = x_mouse * abs(x_mouse);
  y_mouse = y_mouse * abs(y_mouse);

  // Vertical screen coordinates are turned downward.
  y_mouse = -y_mouse;

  #ifdef NUNMOUSE_DEBUG
    if ((counter % 4) == 0) {
      Serial.printf("%3d Joystick=(%3d, %3d) Initial=(%3d, %3d) Mouse=(%2d, %2d) BT1=%d BT2=%d\n",
                    counter,
                    x, y,
                    rest_x, rest_y,
                    x_mouse, y_mouse,
                    button_1, button_2);
    }
  #else

   // Send a message only if the joystick has moved.
    if ((abs(x_mouse) >= 1) || (abs(y_mouse) >= 1)) {
      Mouse.move(x_mouse, y_mouse);
    }
    // Send a message button if something changed.
    if ((button_1 != prev_button_1) || (button_2 != prev_button_2)) {
      Mouse.set_buttons(button_1, 0, button_2);
      prev_button_1 = button_1;
      prev_button_2 = button_2;
    }
  #endif

  delay(LOOP_DELAY);
}

On remarquera que l’alimentation du circuit du Nunchuck est contrôlée par une GPIO de la Teensy, c’est un reste de la phase où j’utilisais le protocole i²c et où je souhaitais pouvoir réinitialiser le contrôleur. On pourrait très bien l’alimenter directement sur le connecteur 3.3V de la Teensy.

Ce petit projet m’a permis de découvrir plus en détail la carte Teensy qui s’avère fort sympathique et très simple d’usage. Il n’est pas si courant de pouvoir émuler des périphériques USB aisément, c’est pourtant un atout important pour certains projets d’informatique embarquée.

L’émulation de souris réalisée ici est satisfaisante, la gestion du joystick pourra probablement être améliorée à l’usage en modifiant légèrement le programme. Pour que l’émulation soit parfaite, il manque néanmoins un contrôle : la molette centrale est absente. Si le besoin s’en fait sentir, je réfléchirai à un moyen de la remplacer par un mouvement spécifique du joystick par exemple.

URL de trackback pour cette page