HP Prime + Arduino Leonardo : une calculatrice qui lit un capteur d’humidité en USB-HID brut

Ma HP Prime passe le plus clair de son temps à faire des maths — mais c’est aussi, sous le capot, un petit ordinateur avec un port USB et un vrai langage de programmation (le PPL, HP Prime Programming Language). Et depuis quelques versions de firmware, elle sait causer en USB-HID brut avec n’importe quel périphérique USB, sans passer par un protocole calculatrice propriétaire. L’idée du projet : transformer un Arduino Leonardo en passerelle générique — I/O numériques, entrées analogiques, bus I2C — et piloter le tout directement depuis un programme PPL sur la calculatrice, capteur d’humidité à la clé.

L’idée de départ n’est pas sortie de nulle part : c’est Mark Power qui l’a présentée au HPCC 2023 (HP Calculator Conference), avec sa propre version du pont USB-HID basée sur une puce FT260 (un convertisseur USB vers I2C/UART d’FTDI). Cette présentation m’a donné envie de relever le défi à ma façon, avec le matériel que j’avais sous la main — un Arduino Leonardo plutôt qu’un FT260 — en m’appuyant sur des informations trouvées sur le forum HP Museum pour la partie Arduino.

Pourquoi la HP Prime peut parler USB-HID

Sur la HP Prime G2, depuis la version de firmware 14603 (décembre 2021), trois fonctions PPL donnent un accès direct au port USB : USBOpen(VID, PID), USBSend(trame) et USBRecv(). Rien de spécifique à un protocole calculatrice : on ouvre un périphérique par son identifiant VID:PID comme n’importe quel hôte USB-HID, on lui envoie des octets, on en reçoit. Autant dire que c’est resté assez peu exploité — la documentation officielle sur le sujet est maigre, et la plupart des exemples qui circulent n’ont jamais été confirmés sur du matériel réel.

De l’autre côté, un Arduino Leonardo est un choix presque évident : son microcontrôleur (ATmega32U4) gère l’USB nativement en matériel, ce qui permet de l’énumérer comme un vrai périphérique HID générique — contrairement à un Uno ou un Nano classique, qui ont besoin d’une puce USB-série séparée et ne peuvent pas facilement se faire passer pour autre chose qu’un port COM.

Le protocole : des rapports HID de 64 octets

Le sketch Arduino s’appuie sur la bibliothèque HID-Project (NicoHood) et son mode RawHID, qui déclare des rapports de 64 octets dans les deux sens. Le protocole maison tient sur un octet de commande suivi de quelques paramètres :

  • 0x00 PING — renvoie la trame telle quelle, pour vérifier que la liaison répond.
  • 0x01 READ_ANALOG / 0x02 READ_DIGITAL / 0x03 WRITE_DIGITAL / 0x04 PIN_MODE — l’essentiel des entrées-sorties classiques.
  • 0x05 I2C_READ / 0x06 I2C_WRITE — accès générique au bus I2C (adresse 7 bits, registre, jusqu’à 6 octets de données).
  • 0x07 READ_DHT — lecture directe d’un capteur DHT22 (humidité + température), le protocole 1-fil étant bit-bangé côté Arduino.
  • 0x10 START_SCAN / 0x11 STOP_SCAN — un mode « scan » où l’Arduino pousse de lui-même, sans qu’on lui demande rien, un rapport analogique toutes les 30 ms en boucle sur A0-A5 (marqueur 0xA0), pratique pour un tracé en direct.

Un détail qui a son importance : le sketch déclare des rapports pleins de 64 octets (RawHID.begin(rawhidData, 64)), et il faut vraiment envoyer des trames de cette taille complète — une trame tronquée à 8-9 octets désynchronise le firmware HID-Project côté Arduino, qui attend son compte. Toute la couche PPL côté Prime (fonction buildframe() ci-dessous) existe précisément pour ça : elle complète systématiquement chaque commande à 64 octets avant de l’envoyer.

Le sketch Arduino : ArduinoSensorBridge.ino

Voici le sketch flashé sur le Leonardo, tel qu’il tourne réellement à la maison. Il gère les commandes ci-dessus, plus le bit-banging du DHT22 et le mode scan automatique :

La zone ci-dessous se parcourt avec son propre ascenseur (ou s’agrandit avec la poignée en bas à droite). Pour récupérer le code : cliquez dedans, sélectionnez tout (Ctrl+A ou Cmd+A), puis copiez (Ctrl+C).

Le programme HP Prime : ArduinoBridge.hpprgm

Côté calculatrice, le programme PPL expose une petite API bien pratique (ArduinoOpen(), ArduinoReadAnalog(), ArduinoI2cRead(), ArduinoReadDHT()…) au-dessus du protocole brut. Il inclut aussi un pilote BMP280/BME280 complet (pression + température), avec les formules de compensation flottantes officielles du constructeur Bosch appliquées directement en PPL à partir des coefficients de calibration lus en I2C :

La zone ci-dessous se parcourt avec son propre ascenseur (ou s’agrandit avec la poignée en bas à droite). Pour récupérer le code : cliquez dedans, sélectionnez tout (Ctrl+A ou Cmd+A), puis copiez (Ctrl+C).

Le capteur d’humidité en pratique : ArduinoReadDHT() et le tracé en direct

La fonction ArduinoReadDHT(pin) envoie la commande 0x07, récupère température et humidité (encodées en dixièmes sur 16 bits), et filtre les valeurs hors plage physique (humidité > 100 %, par exemple) — le checksum du protocole DHT, une simple somme sur 8 bits, ne détecte pas toutes les erreurs de timing. En cas d’échec, elle réessaie jusqu’à 5 fois en respectant les 2 secondes de repos minimum exigées par le capteur entre deux lectures.

ArduinoDHTPlot(pin) va plus loin : elle boucle sur ArduinoReadDHT() et trace en direct, à l’écran de la Prime, deux courbes qui défilent — la température en rouge, l’humidité en bleu — jusqu’à l’appui sur la touche Annul. De quoi transformer la calculatrice en petit enregistreur de données climatiques de bureau, sans écrire une seule ligne de code supplémentaire côté Arduino.

Débogage : isoler le problème avant même d’allumer la Prime

Petite leçon apprise en cours de route, et qui vaut pour n’importe quel projet USB-HID maison : avant de soupçonner le protocole PPL, il vaut mieux valider le firmware Arduino tout seul, depuis un PC Linux, avec la bibliothèque Python hidapi. Même taille de rapport déclarée (64 octets), même test d’echo — si ça boucle proprement là, le firmware est hors de cause et le problème ne peut venir que du côté HP Prime. Ce test au banc a d’ailleurs débusqué un bug amusant : une trame envoyée trop courte faisait parfois remonter, à la place des données attendues, la chaîne de description USB du périphérique en ASCII (littéralement le mot « arduino ») — signe que le firmware HID-Project, désynchronisé, se mettait à répondre avec autre chose que le protocole attendu.

Autre piège typiquement PPL : les listes sont indexées à partir de 1, donc l’octet 0 d’un rapport HID (resp[0] côté C) devient r(1) en PPL, l’octet 1 devient r(2), etc. Un classique décalage d’un cran qui ne pardonne pas sur un protocole binaire.

Et pour être tout à fait honnête : le transport brut a été validé de bout en bout sur PC Linux (echo exact, lecture analogique, écriture digitale), mais la syntaxe précise d’USBOpen/USBSend/USBRecv côté HP Prime n’a pas pu être confirmée sur une documentation officielle accessible — ce programme est écrit à partir de recherches, pas d’un exemple garanti par HP. Reste donc un point resté en suspens dans le code lui-même : faut-il préfixer chaque trame envoyée d’un octet factice 0x00 (le fameux « ID de rapport » des périphériques HID non numérotés) ? Le programme garde les deux variantes disponibles via un simple interrupteur (USE_ID_PREFIX), à corriger au besoin une fois la calculatrice vraiment branchée sur l’Arduino — exactement le même esprit « à tester et ajuster ensemble sur le matériel réel » que pour mes précédents programmes PPL maison.