{"id":4747,"date":"2026-10-01T13:57:42","date_gmt":"2026-10-01T11:57:42","guid":{"rendered":"https:\/\/sophie-matthieu.eu\/?p=4747"},"modified":"2026-10-01T14:27:40","modified_gmt":"2026-10-01T12:27:40","slug":"hp-prime-arduino-leonardo-une-calculatrice-qui-lit-un-capteur-dhumidite-en-usb-hid-brut","status":"publish","type":"post","link":"https:\/\/sophie-matthieu.eu\/index.php\/2026\/10\/01\/hp-prime-arduino-leonardo-une-calculatrice-qui-lit-un-capteur-dhumidite-en-usb-hid-brut\/","title":{"rendered":"HP Prime + Arduino Leonardo : une calculatrice qui lit un capteur d&rsquo;humidit\u00e9 en USB-HID brut"},"content":{"rendered":"<p>Ma <strong>HP Prime<\/strong> passe le plus clair de son temps \u00e0 faire des maths \u2014 mais c&rsquo;est aussi, sous le capot, un petit ordinateur avec un port USB et un vrai langage de programmation (le <strong>PPL<\/strong>, HP Prime Programming Language). Et depuis quelques versions de firmware, elle sait causer en <strong>USB-HID brut<\/strong> avec n&rsquo;importe quel p\u00e9riph\u00e9rique USB, sans passer par un protocole calculatrice propri\u00e9taire. L&rsquo;id\u00e9e du projet : transformer un <strong>Arduino Leonardo<\/strong> en passerelle g\u00e9n\u00e9rique \u2014 I\/O num\u00e9riques, entr\u00e9es analogiques, bus I2C \u2014 et piloter le tout directement depuis un programme PPL sur la calculatrice, capteur d&rsquo;humidit\u00e9 \u00e0 la cl\u00e9.<\/p>\n<p>L&rsquo;id\u00e9e de d\u00e9part n&rsquo;est pas sortie de nulle part : c&rsquo;est <strong>Mark Power<\/strong> qui l&rsquo;a pr\u00e9sent\u00e9e au <strong>HPCC 2023<\/strong> (HP Calculator Conference), avec sa propre version du pont USB-HID bas\u00e9e sur une puce <strong>FT260<\/strong> (un convertisseur USB vers I2C\/UART d&rsquo;FTDI). Cette pr\u00e9sentation m&rsquo;a donn\u00e9 envie de relever le d\u00e9fi \u00e0 ma fa\u00e7on, avec le mat\u00e9riel que j&rsquo;avais sous la main \u2014 un Arduino Leonardo plut\u00f4t qu&rsquo;un FT260 \u2014 en m&rsquo;appuyant sur des informations trouv\u00e9es sur le forum <a href=\"https:\/\/www.hpmuseum.org\/forum\/\" target=\"_blank\" rel=\"noopener\">HP Museum<\/a> pour la partie Arduino.<\/p>\n<h2>Pourquoi la HP Prime peut parler USB-HID<\/h2>\n<p>Sur la HP Prime <strong>G2<\/strong>, depuis la version de firmware <strong>14603<\/strong> (d\u00e9cembre 2021), trois fonctions PPL donnent un acc\u00e8s direct au port USB : <code>USBOpen(VID, PID)<\/code>, <code>USBSend(trame)<\/code> et <code>USBRecv()<\/code>. Rien de sp\u00e9cifique \u00e0 un protocole calculatrice : on ouvre un p\u00e9riph\u00e9rique par son identifiant <strong>VID:PID<\/strong> comme n&rsquo;importe quel h\u00f4te USB-HID, on lui envoie des octets, on en re\u00e7oit. Autant dire que c&rsquo;est rest\u00e9 assez peu exploit\u00e9 \u2014 la documentation officielle sur le sujet est maigre, et la plupart des exemples qui circulent n&rsquo;ont jamais \u00e9t\u00e9 confirm\u00e9s sur du mat\u00e9riel r\u00e9el.<\/p>\n<p>De l&rsquo;autre c\u00f4t\u00e9, un <strong>Arduino Leonardo<\/strong> est un choix presque \u00e9vident : son microcontr\u00f4leur (ATmega32U4) g\u00e8re l&rsquo;USB nativement en mat\u00e9riel, ce qui permet de l&rsquo;\u00e9num\u00e9rer comme un vrai p\u00e9riph\u00e9rique HID g\u00e9n\u00e9rique \u2014 contrairement \u00e0 un Uno ou un Nano classique, qui ont besoin d&rsquo;une puce USB-s\u00e9rie s\u00e9par\u00e9e et ne peuvent pas facilement se faire passer pour autre chose qu&rsquo;un port COM.<\/p>\n<h2>Le protocole : des rapports HID de 64 octets<\/h2>\n<p>Le sketch Arduino s&rsquo;appuie sur la biblioth\u00e8que <strong>HID-Project<\/strong> (NicoHood) et son mode <code>RawHID<\/code>, qui d\u00e9clare des rapports de <strong>64 octets<\/strong> dans les deux sens. Le protocole maison tient sur un octet de commande suivi de quelques param\u00e8tres :<\/p>\n<ul>\n<li><code>0x00 PING<\/code> \u2014 renvoie la trame telle quelle, pour v\u00e9rifier que la liaison r\u00e9pond.<\/li>\n<li><code>0x01 READ_ANALOG<\/code> \/ <code>0x02 READ_DIGITAL<\/code> \/ <code>0x03 WRITE_DIGITAL<\/code> \/ <code>0x04 PIN_MODE<\/code> \u2014 l&rsquo;essentiel des entr\u00e9es-sorties classiques.<\/li>\n<li><code>0x05 I2C_READ<\/code> \/ <code>0x06 I2C_WRITE<\/code> \u2014 acc\u00e8s g\u00e9n\u00e9rique au bus I2C (adresse 7 bits, registre, jusqu&rsquo;\u00e0 6 octets de donn\u00e9es).<\/li>\n<li><code>0x07 READ_DHT<\/code> \u2014 lecture directe d&rsquo;un capteur DHT22 (humidit\u00e9 + temp\u00e9rature), le protocole 1-fil \u00e9tant bit-bang\u00e9 c\u00f4t\u00e9 Arduino.<\/li>\n<li><code>0x10 START_SCAN<\/code> \/ <code>0x11 STOP_SCAN<\/code> \u2014 un mode \u00ab scan \u00bb o\u00f9 l&rsquo;Arduino pousse de lui-m\u00eame, sans qu&rsquo;on lui demande rien, un rapport analogique toutes les 30 ms en boucle sur A0-A5 (marqueur <code>0xA0<\/code>), pratique pour un trac\u00e9 en direct.<\/li>\n<\/ul>\n<p>Un d\u00e9tail qui a son importance : le sketch d\u00e9clare des rapports pleins de <strong>64 octets<\/strong> (<code>RawHID.begin(rawhidData, 64)<\/code>), et il faut vraiment envoyer des trames de cette taille compl\u00e8te \u2014 une trame tronqu\u00e9e \u00e0 8-9 octets d\u00e9synchronise le firmware HID-Project c\u00f4t\u00e9 Arduino, qui attend son compte. Toute la couche PPL c\u00f4t\u00e9 Prime (fonction <code>buildframe()<\/code> ci-dessous) existe pr\u00e9cis\u00e9ment pour \u00e7a : elle compl\u00e8te syst\u00e9matiquement chaque commande \u00e0 64 octets avant de l&rsquo;envoyer.<\/p>\n<h2>Le sketch Arduino : ArduinoSensorBridge.ino<\/h2>\n<p>Voici le sketch flash\u00e9 sur le Leonardo, tel qu&rsquo;il tourne r\u00e9ellement \u00e0 la maison. Il g\u00e8re les commandes ci-dessus, plus le bit-banging du DHT22 et le mode scan automatique :<\/p>\n<p><em>La zone ci-dessous se parcourt avec son propre ascenseur (ou s\u2019agrandit avec la poign\u00e9e en bas \u00e0 droite). Pour r\u00e9cup\u00e9rer le code : cliquez dedans, s\u00e9lectionnez tout (Ctrl+A ou Cmd+A), puis copiez (Ctrl+C).<\/em><\/p>\n<p><textarea class=\"code-listing\" readonly=\"readonly\" wrap=\"off\" rows=\"24\" onclick=\"this.select()\">\/*<br \/>\n  ArduinoSensorBridge &#8211; Auteur original : Matthieu Le Moel.<br \/>\n  Licence : GPL (GNU General Public License).<\/p>\n<p>*\/<br \/>\n\/*<br \/>\n  ArduinoSensorBridge \u2014 pont USB-HID brut entre la HP Prime (USBOpen\/USBSend\/<br \/>\n  USBRecv) et un Arduino Leonardo, pour piloter des capteurs generiques<br \/>\n  (analogique, digital, I2C).<\/p>\n<p>  Base sur HID-Project (NicoHood), exemple RawHID officiel verifie :<br \/>\n  https:\/\/github.com\/NicoHood\/HID\/blob\/master\/examples\/RawHID\/RawHID\/RawHID.ino<\/p>\n<p>  Transport brut valide (echo octet-pour-octet) avant d&#x27;ajouter ce protocole.<\/p>\n<p>  Protocole : rapports de 64 octets, [0]=commande, [1..7]=parametres\/donnees<br \/>\n  (le reste du rapport est ignore\/complete a 0). Reponse : meme format.<\/p>\n<p>    CMD_PING          0x00  -&gt; renvoie la trame telle quelle<br \/>\n    CMD_READ_ANALOG   0x01  [1]=broche A0..A5 (0..5) -&gt; [1]=octet bas,[2]=octet haut (ADC 0-1023)<br \/>\n    CMD_READ_DIGITAL  0x02  [1]=broche -&gt; [1]=0\/1<br \/>\n    CMD_WRITE_DIGITAL 0x03  [1]=broche [2]=valeur(0\/1) -&gt; [1]=1 (ack)<br \/>\n    CMD_PIN_MODE      0x04  [1]=broche [2]=mode(0=INPUT,1=OUTPUT,2=INPUT_PULLUP) -&gt; [1]=1 (ack)<br \/>\n    CMD_I2C_READ      0x05  [1]=adresse7bits [2]=registre [3]=nb octets(&lt;=6) -&gt; [1..6]=donnees (ou [0]=0xFF si erreur)<br \/>\n    CMD_I2C_WRITE     0x06  [1]=adresse7bits [2]=registre [3]=nb octets(&lt;=5) [4..]=donnees -&gt; [1]=1 si ok<br \/>\n    CMD_READ_DHT      0x07  [1]=broche -&gt; [1..2]=humidite*10 (u16), [3..4]=temperature*10 (s16)<br \/>\n                             (ou [0]=0xFE si timeout\/checksum invalide &#8212; reessayer)<br \/>\n    CMD_START_SCAN    0x10  -&gt; [1]=1 (ack), demarre le scan automatique (voir plus bas)<br \/>\n    CMD_STOP_SCAN     0x11  -&gt; [1]=1 (ack), arrete le scan automatique<\/p>\n<p>  Mode SCAN (CMD_START_SCAN) : une fois active, l&#x27;Arduino pousse de lui-meme,<br \/>\n  toutes les ~30ms et SANS attendre de demande, un rapport de lecture<br \/>\n  analogique en boucle sur A0..A5 :<br \/>\n      [0]=0xA0 (marqueur &quot;donnee de scan&quot;, different d&#x27;une reponse de commande)<br \/>\n      [1]=index de broche (0=A0 .. 5=A5)<br \/>\n      [2]=octet bas ADC, [3]=octet haut ADC<br \/>\n  Le mode question\/reponse (CMD_PING, CMD_READ_ANALOG, etc.) reste utilisable<br \/>\n  en parallele &#8212; envoyer CMD_STOP_SCAN pour revenir a un mode purement<br \/>\n  question\/reponse si besoin.<\/p>\n<p>  VID:PID Leonardo (inchange par HID-Project) : 2341:8036 (verifie via lsusb).<br \/>\n*\/<\/p>\n<p>#include &quot;HID-Project.h&quot;<br \/>\n#include &lt;Wire.h&gt;<\/p>\n<p>enum : uint8_t {<br \/>\n  CMD_PING          = 0x00,<br \/>\n  CMD_READ_ANALOG   = 0x01,<br \/>\n  CMD_READ_DIGITAL  = 0x02,<br \/>\n  CMD_WRITE_DIGITAL = 0x03,<br \/>\n  CMD_PIN_MODE      = 0x04,<br \/>\n  CMD_I2C_READ      = 0x05,<br \/>\n  CMD_I2C_WRITE     = 0x06,<br \/>\n  CMD_READ_DHT      = 0x07,<br \/>\n  CMD_START_SCAN    = 0x10,<br \/>\n  CMD_STOP_SCAN     = 0x11,<br \/>\n};<\/p>\n<p>const uint8_t SCAN_MARKER = 0xA0;<br \/>\nconst unsigned long SCAN_INTERVAL_MS = 30;<\/p>\n<p>uint8_t rawhidData[64];<br \/>\nconst uint8_t analogPins[] = { A0, A1, A2, A3, A4, A5 };<\/p>\n<p>bool scanning = false;<br \/>\nuint8_t scanPin = 0;<br \/>\nunsigned long lastScanMs = 0;<\/p>\n<p>bool ledState = false;<br \/>\nunsigned long lastBlinkMs = 0;<br \/>\nconst unsigned long BLINK_INTERVAL_MS = 150;<\/p>\n<p>void setup() {<br \/>\n  RawHID.begin(rawhidData, sizeof(rawhidData));<br \/>\n  Wire.begin();<br \/>\n  Serial.begin(115200);<br \/>\n  pinMode(LED_BUILTIN, OUTPUT);<br \/>\n  digitalWrite(LED_BUILTIN, LOW);<br \/>\n}<\/p>\n<p>void loop() {<br \/>\n  handleIncomingCommand();<br \/>\n  handleScan();<br \/>\n}<\/p>\n<p>\/\/ Lecture DHT22 (protocole 1 fil, bit-banging manuel &#8212; pas de bibliotheque<br \/>\n\/\/ externe requise). Renvoie true si les 40 bits + checksum sont valides.<br \/>\nbool readDHT22(uint8_t pin, int16_t &amp;humidity10, int16_t &amp;temp10) {<br \/>\n  uint8_t data[5] = {0, 0, 0, 0, 0};<\/p>\n<p>  \/\/ Signal de depart : broche a LOW &gt;= 1ms puis relachee (pull-up interne)<br \/>\n  pinMode(pin, OUTPUT);<br \/>\n  digitalWrite(pin, LOW);<br \/>\n  delay(2);<br \/>\n  digitalWrite(pin, HIGH);<br \/>\n  pinMode(pin, INPUT_PULLUP);<br \/>\n  delayMicroseconds(30);<\/p>\n<p>  \/\/ Reponse du capteur : LOW ~80us puis HIGH ~80us<br \/>\n  unsigned long t0 = micros();<br \/>\n  while (digitalRead(pin) == HIGH) { if (micros() &#8211; t0 &gt; 200) return false; }<br \/>\n  t0 = micros();<br \/>\n  while (digitalRead(pin) == LOW)  { if (micros() &#8211; t0 &gt; 200) return false; }<br \/>\n  t0 = micros();<br \/>\n  while (digitalRead(pin) == HIGH) { if (micros() &#8211; t0 &gt; 200) return false; }<\/p>\n<p>  \/\/ 40 bits de donnees : chaque bit commence par ~50us LOW, puis HIGH<br \/>\n  \/\/ ~26-28us (bit 0) ou ~70us (bit 1)<br \/>\n  for (uint8_t i = 0; i &lt; 40; i++) {<br \/>\n    t0 = micros();<br \/>\n    while (digitalRead(pin) == LOW) { if (micros() &#8211; t0 &gt; 200) return false; }<br \/>\n    unsigned long highStart = micros();<br \/>\n    t0 = micros();<br \/>\n    while (digitalRead(pin) == HIGH) { if (micros() &#8211; t0 &gt; 200) return false; }<br \/>\n    data[i \/ 8] &lt;&lt;= 1;<br \/>\n    if (micros() &#8211; highStart &gt; 40) data[i \/ 8] |= 1;<br \/>\n  }<\/p>\n<p>  if (((data[0] + data[1] + data[2] + data[3]) &amp; 0xFF) != data[4]) return false;<\/p>\n<p>  humidity10 = (int16_t)((data[0] &lt;&lt; 8) | data[1]);<br \/>\n  int16_t rawTemp = ((data[2] &amp; 0x7F) &lt;&lt; 8) | data[3];<br \/>\n  temp10 = (data[2] &amp; 0x80) ? -rawTemp : rawTemp;<br \/>\n  return true;<br \/>\n}<\/p>\n<p>void handleIncomingCommand() {<br \/>\n  if (!RawHID.available()) return;<\/p>\n<p>  \/\/ Lit tout le rapport recu (meme pattern que le test d&#x27;echo qui a fonctionne)<br \/>\n  uint8_t in[64] = {0};<br \/>\n  uint8_t n = 0;<br \/>\n  while (RawHID.available() &amp;&amp; n &lt; 64) in[n++] = RawHID.read();<\/p>\n<p>  Serial.print(&quot;recu n=&quot;); Serial.print(n); Serial.print(&quot; : &quot;);<br \/>\n  for (uint8_t i = 0; i &lt; 8; i++) { Serial.print(in[i], HEX); Serial.print(&quot; &quot;); }<br \/>\n  Serial.println();<\/p>\n<p>  uint8_t resp[64] = {0};<br \/>\n  resp[0] = in[0];<\/p>\n<p>  switch (in[0]) {<br \/>\n    case CMD_PING:<br \/>\n      memcpy(resp, in, 8);<br \/>\n      break;<\/p>\n<p>    case CMD_READ_ANALOG: {<br \/>\n      uint8_t pin = in[1];<br \/>\n      if (pin &lt; 6) {<br \/>\n        uint16_t v = analogRead(analogPins[pin]);<br \/>\n        resp[1] = v &amp; 0xFF;<br \/>\n        resp[2] = (v &gt;&gt; 8) &amp; 0xFF;<br \/>\n      }<br \/>\n      break;<br \/>\n    }<\/p>\n<p>    case CMD_READ_DIGITAL:<br \/>\n      resp[1] = digitalRead(in[1]) ? 1 : 0;<br \/>\n      break;<\/p>\n<p>    case CMD_WRITE_DIGITAL:<br \/>\n      digitalWrite(in[1], in[2] ? HIGH : LOW);<br \/>\n      resp[1] = 1;<br \/>\n      break;<\/p>\n<p>    case CMD_PIN_MODE: {<br \/>\n      uint8_t mode = in[2];<br \/>\n      pinMode(in[1], mode == 1 ? OUTPUT : mode == 2 ? INPUT_PULLUP : INPUT);<br \/>\n      resp[1] = 1;<br \/>\n      break;<br \/>\n    }<\/p>\n<p>    case CMD_I2C_READ: {<br \/>\n      uint8_t addr = in[1], reg = in[2], cnt = in[3];<br \/>\n      if (cnt &gt; 6) cnt = 6;<br \/>\n      Wire.beginTransmission(addr);<br \/>\n      Wire.write(reg);<br \/>\n      if (Wire.endTransmission(false) == 0) {<br \/>\n        Wire.requestFrom((int)addr, (int)cnt);<br \/>\n        for (uint8_t i = 0; i &lt; cnt &amp;&amp; Wire.available(); i++) resp[1 + i] = Wire.read();<br \/>\n      } else {<br \/>\n        resp[0] = 0xFF;<br \/>\n      }<br \/>\n      break;<br \/>\n    }<\/p>\n<p>    case CMD_I2C_WRITE: {<br \/>\n      uint8_t addr = in[1], reg = in[2], cnt = in[3];<br \/>\n      if (cnt &gt; 5) cnt = 5;<br \/>\n      Wire.beginTransmission(addr);<br \/>\n      Wire.write(reg);<br \/>\n      for (uint8_t i = 0; i &lt; cnt; i++) Wire.write(in[4 + i]);<br \/>\n      resp[1] = (Wire.endTransmission() == 0) ? 1 : 0;<br \/>\n      break;<br \/>\n    }<\/p>\n<p>    case CMD_READ_DHT: {<br \/>\n      int16_t h10, t10;<br \/>\n      if (readDHT22(in[1], h10, t10)) {<br \/>\n        resp[1] = h10 &amp; 0xFF;<br \/>\n        resp[2] = (h10 &gt;&gt; 8) &amp; 0xFF;<br \/>\n        resp[3] = t10 &amp; 0xFF;<br \/>\n        resp[4] = (t10 &gt;&gt; 8) &amp; 0xFF;<br \/>\n      } else {<br \/>\n        resp[0] = 0xFE;<br \/>\n      }<br \/>\n      break;<br \/>\n    }<\/p>\n<p>    case CMD_START_SCAN:<br \/>\n      scanning = true;<br \/>\n      scanPin = 0;<br \/>\n      resp[1] = 1;<br \/>\n      break;<\/p>\n<p>    case CMD_STOP_SCAN:<br \/>\n      scanning = false;<br \/>\n      digitalWrite(LED_BUILTIN, LOW);<br \/>\n      resp[1] = 1;<br \/>\n      break;<\/p>\n<p>    default:<br \/>\n      resp[0] = 0xFF;<br \/>\n      break;<br \/>\n  }<\/p>\n<p>  RawHID.write(resp, 64);<br \/>\n}<\/p>\n<p>void handleScan() {<br \/>\n  if (!scanning) return;<br \/>\n  unsigned long now = millis();<\/p>\n<p>  if (now &#8211; lastBlinkMs &gt;= BLINK_INTERVAL_MS) {<br \/>\n    lastBlinkMs = now;<br \/>\n    ledState = !ledState;<br \/>\n    digitalWrite(LED_BUILTIN, ledState ? HIGH : LOW);<br \/>\n  }<\/p>\n<p>  if (now &#8211; lastScanMs &lt; SCAN_INTERVAL_MS) return;<br \/>\n  lastScanMs = now;<\/p>\n<p>  uint16_t v = analogRead(analogPins[scanPin]);<br \/>\n  uint8_t report[64] = {0};<br \/>\n  report[0] = SCAN_MARKER;<br \/>\n  report[1] = scanPin;<br \/>\n  report[2] = v &amp; 0xFF;<br \/>\n  report[3] = (v &gt;&gt; 8) &amp; 0xFF;<br \/>\n  RawHID.write(report, 64);<\/p>\n<p>  scanPin = (scanPin + 1) % 6;<br \/>\n}<br \/>\n<\/textarea><\/p>\n<h2>Le programme HP Prime : ArduinoBridge.hpprgm<\/h2>\n<p>C\u00f4t\u00e9 calculatrice, le programme PPL expose une petite API bien pratique (<code>ArduinoOpen()<\/code>, <code>ArduinoReadAnalog()<\/code>, <code>ArduinoI2cRead()<\/code>, <code>ArduinoReadDHT()<\/code>&#8230;) au-dessus du protocole brut. Il inclut aussi un pilote <strong>BMP280\/BME280<\/strong> complet (pression + temp\u00e9rature), avec les formules de compensation flottantes officielles du constructeur Bosch appliqu\u00e9es directement en PPL \u00e0 partir des coefficients de calibration lus en I2C :<\/p>\n<p><em>La zone ci-dessous se parcourt avec son propre ascenseur (ou s\u2019agrandit avec la poign\u00e9e en bas \u00e0 droite). Pour r\u00e9cup\u00e9rer le code : cliquez dedans, s\u00e9lectionnez tout (Ctrl+A ou Cmd+A), puis copiez (Ctrl+C).<\/em><\/p>\n<p><textarea class=\"code-listing\" readonly=\"readonly\" wrap=\"off\" rows=\"24\" onclick=\"this.select()\">\/\/ ArduinoBridge &#8211; Auteur original : Matthieu Le Moel.<br \/>\n\/\/ Licence : GPL (GNU General Public License).<br \/>\n\/\/<br \/>\n\/\/ ArduinoBridge : pilote un Arduino Leonardo (sketch ArduinoSensorBridge.ino)<br \/>\n\/\/ en USB-HID brut depuis la HP Prime, via USBOpen\/USBSend\/USBRecv.<br \/>\n\/\/<br \/>\n\/\/ PREREQUIS :<br \/>\n\/\/  &#8211; HP Prime G2, firmware recent (USBOpen\/USBSend\/USBRecv disponibles<br \/>\n\/\/    depuis la version 14603, decembre 2021).<br \/>\n\/\/  &#8211; Arduino Leonardo flashe avec ArduinoSensorBridge.ino (repertoire<br \/>\n\/\/    ArduinoSensorBridge\/ a cote de ce fichier).<br \/>\n\/\/  &#8211; VID:PID verifie sur le vrai materiel (lsusb) : 2341:8036.<br \/>\n\/\/<br \/>\n\/\/ ATTENTION &#8211; NON TESTE SUR LA VRAIE CALCULATRICE :<br \/>\n\/\/  Le protocole USB-HID brut a ete valide de bout en bout sur PC Linux<br \/>\n\/\/  (echo exact, lecture analogique, ecriture digitale &#8212; voir les tests).<br \/>\n\/\/  Cote HP Prime, la syntaxe exacte d&#x27;USBOpen\/USBSend\/USBRecv n&#x27;a pas pu<br \/>\n\/\/  etre verifiee sur une documentation officielle accessible (forum<br \/>\n\/\/  hpmuseum.org bloque les acces automatises) &#8212; ce code est ecrit a partir<br \/>\n\/\/  d&#x27;extraits de recherche, PAS d&#x27;un exemple officiel confirme. A tester et<br \/>\n\/\/  corriger ensemble sur la vraie calculatrice, comme pour KMAP\/IPCALC.<br \/>\n\/\/<br \/>\n\/\/  Point precis a verifier en premier si ca ne marche pas : sur PC Linux,<br \/>\n\/\/  il a fallu prefixer chaque trame envoyee d&#x27;un octet factice (0x00) &#8212;<br \/>\n\/\/  un &quot;ID de rapport&quot; implicite mange par la couche USB-HID du systeme.<br \/>\n\/\/  Il est possible que USBSend() sur la Prime gere deja ca en interne (donc<br \/>\n\/\/  PAS besoin du prefixe), ou au contraire qu&#x27;il faille le meme prefixe.<br \/>\n\/\/  Les deux variantes sont proposees plus bas (USE_ID_PREFIX).<\/p>\n<p>\/\/ Mettre a 1 si les reponses semblent decalees d&#x27;un octet (voir remarque ci-dessus)<br \/>\nUSE_ID_PREFIX;<\/p>\n<p>VID;<br \/>\nPID;<\/p>\n<p>\/\/ Construit une trame COMPLETE de 64 octets [cmd, p1, p2, &#8230;, 0&#8230;] a partir<br \/>\n\/\/ d&#x27;une liste &#8212; le sketch declare RawHID.begin(rawhidData, 64) : un rapport<br \/>\n\/\/ tronque (8-9 octets, comme avant) desynchronise le firmware.<br \/>\nbuildframe(cmd, params)<br \/>\nBEGIN<br \/>\nLOCAL f, i, n;<br \/>\nf:=MAKELIST(0,i,1,64,1);<br \/>\nf(1):=cmd;<br \/>\nn:=SIZE(params);<br \/>\nFOR i FROM 1 TO n DO<br \/>\n  IF i&lt;64 THEN f(i+1):=params(i); END;<br \/>\nEND;<br \/>\nRETURN f;<br \/>\nEND;<\/p>\n<p>\/\/ Envoie une trame de 64 octets, renvoie la reponse (liste, jusqu&#x27;a 64 octets).<br \/>\n\/\/ USBRecv() est non bloquant (peut renvoyer une liste vide ou partielle) &#8212;<br \/>\n\/\/ on interroge donc en boucle jusqu&#x27;a avoir au moins 8 octets utiles ou expirer.<br \/>\n\/\/ Timeout base sur le temps reel ecoule (TICKS(), en ms) et non sur un nombre<br \/>\n\/\/ fixe d&#x27;iterations &#8212; un compteur d&#x27;essais fixe correspond a un temps reel<br \/>\n\/\/ variable selon la vitesse d&#x27;execution du moment, ce qui rendait le timeout<br \/>\n\/\/ parfois trop court (PING instable).<br \/>\nsendframe(f)<br \/>\nBEGIN<br \/>\nLOCAL toSend, resp, i, j, chunk, respLen, t0;<br \/>\nIF USE_ID_PREFIX==1 THEN<br \/>\n  toSend:=MAKELIST(0,i,1,65,1);<br \/>\n  FOR i FROM 1 TO 64 DO toSend(i+1):=f(i); END;<br \/>\nELSE<br \/>\n  toSend:=f;<br \/>\nEND;<br \/>\nUSBSend(toSend);<\/p>\n<p>resp:=MAKELIST(0,i,1,64,1);<br \/>\nrespLen:=0;<br \/>\nt0:=TICKS();<br \/>\nWHILE respLen&lt;8 AND (TICKS()-t0)&lt;2000 DO<br \/>\n  chunk:=USBRecv();<br \/>\n  IF SIZE(chunk)&gt;0 THEN<br \/>\n    FOR j FROM 1 TO SIZE(chunk) DO<br \/>\n      IF respLen&lt;64 THEN<br \/>\n        respLen:=respLen+1;<br \/>\n        resp(respLen):=chunk(j);<br \/>\n      END;<br \/>\n    END;<br \/>\n  END;<br \/>\nEND;<br \/>\nRETURN resp;<br \/>\nEND;<\/p>\n<p>EXPORT ArduinoOpen()<br \/>\nBEGIN<br \/>\nVID:=9025;  \/\/ 0x2341, Arduino SA<br \/>\nPID:=32822; \/\/ 0x8036, Leonardo &#8212; verifie via lsusb sur le vrai materiel<br \/>\nUSE_ID_PREFIX:=0; \/\/ confirme sur le vrai materiel : la Prime n&#x27;a pas besoin du prefixe<br \/>\nUSBOpen(VID,PID);<br \/>\nMSGBOX(&quot;HP Prime &lt;-&gt; Arduino : ouvert (VID &quot;+STRING(VID)+&quot; PID &quot;+STRING(PID)+&quot;).&quot;);<br \/>\nEND;<\/p>\n<p>EXPORT ArduinoPing()<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(0,{17,34,51,68,85,102,119});<br \/>\nr:=sendframe(f);<br \/>\nIF r(1)==0 AND r(2)==17 THEN<br \/>\n  MSGBOX(&quot;PING OK &#8212; Arduino repond correctement.&quot;);<br \/>\nELSE<br \/>\n  MSGBOX(&quot;PING echec &#8212; reponse : &quot;+STRING(r)+<br \/>\n         CHAR(10)+&quot;Essaie de changer USE_ID_PREFIX (0 &lt;-&gt; 1).&quot;);<br \/>\nEND;<br \/>\nEND;<\/p>\n<p>\/\/ Lit une broche analogique A0..A5 (0..5), renvoie 0-1023<br \/>\nEXPORT ArduinoReadAnalog(pin)<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(1,{pin});<br \/>\nr:=sendframe(f);<br \/>\nRETURN r(2)+256*r(3);<br \/>\nEND;<\/p>\n<p>\/\/ Lit une broche digitale, renvoie 0 ou 1<br \/>\nEXPORT ArduinoReadDigital(pin)<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(2,{pin});<br \/>\nr:=sendframe(f);<br \/>\nRETURN r(2);<br \/>\nEND;<\/p>\n<p>\/\/ Ecrit une broche digitale (val=0 ou 1)<br \/>\nEXPORT ArduinoWriteDigital(pin, val)<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(3,{pin,val});<br \/>\nr:=sendframe(f);<br \/>\nRETURN r(2); \/\/ 1 = ack recu<br \/>\nEND;<\/p>\n<p>\/\/ Configure le mode d&#x27;une broche : mode=0 INPUT, 1 OUTPUT, 2 INPUT_PULLUP<br \/>\nEXPORT ArduinoPinMode(pin, mode)<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(4,{pin,mode});<br \/>\nr:=sendframe(f);<br \/>\nRETURN r(2);<br \/>\nEND;<\/p>\n<p>\/\/ Lit &quot;n&quot; octets (&lt;=6) depuis un capteur I2C, a partir du registre &quot;reg&quot;.<br \/>\n\/\/ Cablage par torsades (pas de breadboard) -&gt; memes coupures de contact<br \/>\n\/\/ possibles que pour le DHT22 ; on reessaie donc silencieusement (5 fois,<br \/>\n\/\/ court delai) avant d&#x27;abandonner et de renvoyer {}.<br \/>\nEXPORT ArduinoI2cRead(addr, reg, n)<br \/>\nBEGIN<br \/>\nLOCAL f, r, i, out, tries;<br \/>\nf:=buildframe(5,{addr,reg,n});<br \/>\nFOR tries FROM 1 TO 5 DO<br \/>\n  IF tries&gt;1 THEN WAIT(0.2); END;<br \/>\n  r:=sendframe(f);<br \/>\n  IF r(1)&lt;&gt;255 THEN<br \/>\n    out:=MAKELIST(0,i,1,n,1);<br \/>\n    FOR i FROM 1 TO n DO out(i):=r(i+1); END;<br \/>\n    RETURN out;<br \/>\n  END;<br \/>\nEND;<br \/>\nRETURN {}; \/\/ echec apres 5 essais (pas d&#x27;accuse de l&#x27;esclave 0x&quot;+STRING(addr)+&quot;)<br \/>\nEND;<\/p>\n<p>\/\/ Ecrit une liste &quot;data&quot; (&lt;=5 octets) dans un capteur I2C, registre &quot;reg&quot;.<br \/>\n\/\/ Meme logique de reessai silencieux que ArduinoI2cRead.<br \/>\nEXPORT ArduinoI2cWrite(addr, reg, data)<br \/>\nBEGIN<br \/>\nLOCAL f, params, i, n, r, tries;<br \/>\nn:=SIZE(data);<br \/>\nparams:={0,0,0,0,0,0,0}; \/\/ preallouee (evite de compter sur une croissance dynamique de liste)<br \/>\nparams(1):=addr; params(2):=reg; params(3):=n;<br \/>\nFOR i FROM 1 TO n DO params(3+i):=data(i); END;<br \/>\nf:=buildframe(6,params);<br \/>\nFOR tries FROM 1 TO 5 DO<br \/>\n  IF tries&gt;1 THEN WAIT(0.2); END;<br \/>\n  r:=sendframe(f);<br \/>\n  IF r(2)==1 THEN RETURN 1; END;<br \/>\nEND;<br \/>\nRETURN 0; \/\/ echec apres 5 essais<br \/>\nEND;<\/p>\n<p>\/\/ &#8212; Mode SCAN automatique (CMD_START_SCAN\/CMD_STOP_SCAN, voir .ino) &#8212;&#8212;-<br \/>\n\/\/ Une fois demarre, l&#x27;Arduino pousse tout seul, sans requete, un rapport<br \/>\n\/\/ [0xA0, broche, valLo, valHi] toutes les ~30ms en boucle sur A0..A5.<\/p>\n<p>\/\/ Lit un rapport brut (4 octets utiles) sans envoyer de commande &#8212; pour les<br \/>\n\/\/ rapports non sollicites du mode scan. Renvoie {} si rien recu.<br \/>\nreadRawReport(maxTries)<br \/>\nBEGIN<br \/>\nLOCAL resp, respLen, chunk, j, tries;<br \/>\nresp:=MAKELIST(0,j,1,4,1);<br \/>\nrespLen:=0;<br \/>\ntries:=0;<br \/>\nWHILE respLen&lt;4 AND tries&lt;maxTries DO<br \/>\n  chunk:=USBRecv();<br \/>\n  IF SIZE(chunk)&gt;0 THEN<br \/>\n    FOR j FROM 1 TO SIZE(chunk) DO<br \/>\n      IF respLen&lt;4 THEN<br \/>\n        respLen:=respLen+1;<br \/>\n        resp(respLen):=chunk(j);<br \/>\n      END;<br \/>\n    END;<br \/>\n  END;<br \/>\n  tries:=tries+1;<br \/>\nEND;<br \/>\nIF respLen&lt;4 THEN RETURN {}; END;<br \/>\nRETURN resp;<br \/>\nEND;<\/p>\n<p>EXPORT ArduinoStartScan()<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(16,{}); \/\/ CMD_START_SCAN = 0x10<br \/>\nr:=sendframe(f);<br \/>\nIF r(1)==16 AND r(2)==1 THEN<br \/>\n  MSGBOX(&quot;Scan demarre.&quot;);<br \/>\nELSE<br \/>\n  MSGBOX(&quot;Echec demarrage scan &#8212; reponse : &quot;+STRING(r));<br \/>\nEND;<br \/>\nEND;<\/p>\n<p>EXPORT ArduinoStopScan()<br \/>\nBEGIN<br \/>\nLOCAL f, r;<br \/>\nf:=buildframe(17,{}); \/\/ CMD_STOP_SCAN = 0x11<br \/>\nr:=sendframe(f);<br \/>\nIF r(1)==17 AND r(2)==1 THEN<br \/>\n  MSGBOX(&quot;Scan arrete.&quot;);<br \/>\nELSE<br \/>\n  MSGBOX(&quot;Echec arret scan &#8212; reponse : &quot;+STRING(r));<br \/>\nEND;<br \/>\nEND;<\/p>\n<p>\/\/ Lit un echantillon de scan (mode SCAN deja demarre cote Arduino).<br \/>\n\/\/ Renvoie {broche, valeur} si un rapport de scan a ete recu, {} sinon<br \/>\n\/\/ (rien de nouveau, ou rapport residuel qui n&#x27;est pas un marqueur de scan).<br \/>\nEXPORT ArduinoScanRead()<br \/>\nBEGIN<br \/>\nLOCAL r;<br \/>\nr:=readRawReport(5);<br \/>\nIF SIZE(r)&lt;4 THEN RETURN {}; END;<br \/>\nIF r(1)==160 THEN \/\/ 0xA0<br \/>\n  RETURN {r(2), r(3)+256*r(4)};<br \/>\nEND;<br \/>\nRETURN {};<br \/>\nEND;<\/p>\n<p>\/\/ Demo : demarre le scan, affiche en direct la derniere broche\/valeur lue,<br \/>\n\/\/ jusqu&#x27;a l&#x27;appui sur la touche ANNUL (ISKEYDOWN(4)), puis arrete le scan.<br \/>\nEXPORT ArduinoScanDemo()<br \/>\nBEGIN<br \/>\nLOCAL s;<br \/>\nArduinoStartScan();<br \/>\nREPEAT<br \/>\n  s:=ArduinoScanRead();<br \/>\n  IF SIZE(s)==2 THEN<br \/>\n    RECT_P();<br \/>\n    TEXTOUT_P(&quot;A&quot;+STRING(s(1))+&quot; = &quot;+STRING(s(2)),10,10,2,RGB(0,0,0));<br \/>\n  END;<br \/>\nUNTIL ISKEYDOWN(4);<br \/>\nArduinoStopScan();<br \/>\nEND;<\/p>\n<p>\/\/ &#8212; DHT22 (temperature + humidite, protocole 1 fil, CMD_READ_DHT) &#8212;&#8212;&#8211;<\/p>\n<p>\/\/ Lit un DHT22 branche sur &quot;pin&quot;. Renvoie {temperature en C, humidite en %},<br \/>\n\/\/ ou {} si la lecture a echoue apres plusieurs essais.<br \/>\n\/\/<br \/>\n\/\/ Le checksum du protocole DHT (simple somme sur 8 bits) ne detecte pas<br \/>\n\/\/ toutes les erreurs &#8212; une lecture peut &quot;reussir&quot; avec une valeur<br \/>\n\/\/ impossible (ex. humidite &gt; 100%) si un pull-up trop faible fait deraper<br \/>\n\/\/ le timing d&#x27;un seul bit. On rejette donc aussi les valeurs hors plage<br \/>\n\/\/ physique du capteur, et on reessaie (jusqu&#x27;a 5 fois, 2s d&#x27;ecart minimum<br \/>\n\/\/ entre deux lectures DHT22 &#8212; respecte le repos exige par le capteur).<br \/>\nEXPORT ArduinoReadDHT(pin)<br \/>\nBEGIN<br \/>\nLOCAL f, r, h, t, tries;<br \/>\nFOR tries FROM 1 TO 5 DO<br \/>\n  IF tries&gt;1 THEN WAIT(2); END; \/\/ respecte le repos exige par le DHT22 entre deux lectures<br \/>\n  f:=buildframe(7,{pin});<br \/>\n  r:=sendframe(f);<br \/>\n  IF r(1)&lt;&gt;254 THEN \/\/ 0xFE = echec (timeout ou checksum invalide) -&gt; on reessaie<br \/>\n    h:=(r(2)+256*r(3))\/10;<br \/>\n    t:=r(4)+256*r(5);<br \/>\n    IF t&gt;=32768 THEN t:=t-65536; END;<br \/>\n    t:=t\/10;<br \/>\n    IF h&gt;=0 AND h&lt;=100 AND t&gt;=-40 AND t&lt;=80 THEN \/\/ filtre les valeurs hors plage physique<br \/>\n      RETURN {t, h};<br \/>\n    END;<br \/>\n  END;<br \/>\nEND;<br \/>\nRETURN {};<br \/>\nEND;<\/p>\n<p>\/\/ Trace en direct les courbes temperature (rouge) et humidite (bleue) d&#x27;un<br \/>\n\/\/ DHT22 branche sur &quot;pin&quot;. Un nouvel echantillon = un appel a ArduinoReadDHT<br \/>\n\/\/ (qui gere deja ses propres reessais) ; les echecs ({}) sont ignores sans<br \/>\n\/\/ interrompre le trace. Arret avec la touche ANNUL.<br \/>\n\/\/<br \/>\n\/\/ Echelle fixe : 0-40 C et 0-100 %HR (couvre les conditions interieures<br \/>\n\/\/ habituelles) &#8212; ajuster TPLOT_MIN\/TPLOT_MAX ci-dessous si besoin.<br \/>\nEXPORT ArduinoDHTPlot(pin)<br \/>\nBEGIN<br \/>\nLOCAL serieT, serieH, r, i, n;<br \/>\nLOCAL px0, px1, py0, py1, maxPoints, tmin, tmax, count;<br \/>\nmaxPoints:=140;<br \/>\nserieT:=MAKELIST(0,i,1,maxPoints,1); \/\/ buffer pre-alloue (meme style que buildframe\/ArduinoI2cRead)<br \/>\nserieH:=MAKELIST(0,i,1,maxPoints,1);<br \/>\ncount:=0;<br \/>\ntmin:=0; tmax:=40;<br \/>\npx0:=30; px1:=310; py0:=24; py1:=210; \/\/ zone de trace en pixels (ecran 320&#215;240)<\/p>\n<p>RECT_P();<br \/>\nTEXTOUT_P(&quot;T (rouge, 0-40C)  H (bleu, 0-100%)&quot;,30,4,1,RGB(0,0,0));<\/p>\n<p>REPEAT<br \/>\n  r:=ArduinoReadDHT(pin);<br \/>\n  IF SIZE(r)==2 THEN<br \/>\n    IF count&lt;maxPoints THEN<br \/>\n      count:=count+1;<br \/>\n      serieT(count):=r(1);<br \/>\n      serieH(count):=r(2);<br \/>\n    ELSE<br \/>\n      FOR i FROM 1 TO maxPoints-1 DO \/\/ buffer plein : decale tout d&#x27;un cran (defilement)<br \/>\n        serieT(i):=serieT(i+1);<br \/>\n        serieH(i):=serieH(i+1);<br \/>\n      END;<br \/>\n      serieT(maxPoints):=r(1);<br \/>\n      serieH(maxPoints):=r(2);<br \/>\n    END;<\/p>\n<p>    RECT_P();<br \/>\n    TEXTOUT_P(&quot;T (rouge, 0-40C)  H (bleu, 0-100%)&quot;,30,4,1,RGB(0,0,0));<br \/>\n    LINE_P(px0,py0,px0,py1,RGB(0,0,0));<br \/>\n    LINE_P(px0,py1,px1,py1,RGB(0,0,0));<\/p>\n<p>    n:=count;<br \/>\n    IF n&gt;1 THEN<br \/>\n      FOR i FROM 1 TO n-1 DO<br \/>\n        LINE_P(px0+(i-1)*(px1-px0)\/maxPoints, py1-(serieT(i)-tmin)\/(tmax-tmin)*(py1-py0),<br \/>\n               px0+i*(px1-px0)\/maxPoints,     py1-(serieT(i+1)-tmin)\/(tmax-tmin)*(py1-py0),<br \/>\n               RGB(220,0,0));<br \/>\n        LINE_P(px0+(i-1)*(px1-px0)\/maxPoints, py1-serieH(i)\/100*(py1-py0),<br \/>\n               px0+i*(px1-px0)\/maxPoints,     py1-serieH(i+1)\/100*(py1-py0),<br \/>\n               RGB(0,80,220));<br \/>\n      END;<br \/>\n    END;<\/p>\n<p>    TEXTOUT_P(&quot;T=&quot;+STRING(serieT(n))+&quot;C  H=&quot;+STRING(serieH(n))+&quot;%&quot;,30,py1+6,1,RGB(0,0,0));<br \/>\n  END;<br \/>\nUNTIL ISKEYDOWN(4);<br \/>\nEND;<\/p>\n<p>\/\/ &#8212; BMP280\/BME280 (pression + temperature, I2C generique) &#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\n\/\/ Utilise ArduinoI2cRead\/ArduinoI2cWrite (deja transportes tels quels par le<br \/>\n\/\/ firmware) &#8212; la calibration et la formule de compensation (datasheet<br \/>\n\/\/ Bosch, version flottante officielle) sont faites cote HP Prime.<br \/>\n\/\/ Adresse I2C : 0x76 (118) ou 0x77 (119) selon le module.<\/p>\n<p>BMP_ADDR;<br \/>\nBMP_T1; BMP_T2; BMP_T3;<br \/>\nBMP_P1; BMP_P2; BMP_P3; BMP_P4; BMP_P5; BMP_P6; BMP_P7; BMP_P8; BMP_P9;<br \/>\nBMP_TFINE;<\/p>\n<p>s16(lo, hi)<br \/>\nBEGIN<br \/>\nLOCAL v;<br \/>\nv:=lo+256*hi;<br \/>\nIF v&gt;=32768 THEN v:=v-65536; END;<br \/>\nRETURN v;<br \/>\nEND;<\/p>\n<p>u16(lo, hi)<br \/>\nBEGIN<br \/>\nRETURN lo+256*hi;<br \/>\nEND;<\/p>\n<p>\/\/ Lit les coefficients de calibration et configure le capteur en mode normal.<br \/>\n\/\/ addr : 118 (0x76) ou 119 (0x77).<br \/>\nEXPORT ArduinoBmpInit(addr)<br \/>\nBEGIN<br \/>\nLOCAL c1, c2, c3, c4;<br \/>\nBMP_ADDR:=addr;<br \/>\nc1:=ArduinoI2cRead(addr,136,6); \/\/ 0x88 : dig_T1(u16) dig_T2(s16) dig_T3(s16)<br \/>\nBMP_T1:=u16(c1(1),c1(2));<br \/>\nBMP_T2:=s16(c1(3),c1(4));<br \/>\nBMP_T3:=s16(c1(5),c1(6));<br \/>\nc2:=ArduinoI2cRead(addr,142,6); \/\/ 0x8E : dig_P1(u16) dig_P2(s16) dig_P3(s16)<br \/>\nBMP_P1:=u16(c2(1),c2(2));<br \/>\nBMP_P2:=s16(c2(3),c2(4));<br \/>\nBMP_P3:=s16(c2(5),c2(6));<br \/>\nc3:=ArduinoI2cRead(addr,148,6); \/\/ 0x94 : dig_P4(s16) dig_P5(s16) dig_P6(s16)<br \/>\nBMP_P4:=s16(c3(1),c3(2));<br \/>\nBMP_P5:=s16(c3(3),c3(4));<br \/>\nBMP_P6:=s16(c3(5),c3(6));<br \/>\nc4:=ArduinoI2cRead(addr,154,6); \/\/ 0x9A : dig_P7(s16) dig_P8(s16) dig_P9(s16)<br \/>\nBMP_P7:=s16(c4(1),c4(2));<br \/>\nBMP_P8:=s16(c4(3),c4(4));<br \/>\nBMP_P9:=s16(c4(5),c4(6));<br \/>\nArduinoI2cWrite(addr,244,{39}); \/\/ 0xF4 = 0x27 : temp\/press oversampling x1, mode normal<br \/>\nArduinoI2cWrite(addr,245,{0});  \/\/ 0xF5 = 0x00 : pas de filtre IIR, standby min<br \/>\nMSGBOX(&quot;BMP280 initialise (addr &quot;+STRING(addr)+&quot;).&quot;);<br \/>\nEND;<\/p>\n<p>\/\/ Renvoie {temperature en degres C, pression en Pa}. Appeler ArduinoBmpInit()<br \/>\n\/\/ une fois avant.<br \/>\nEXPORT ArduinoBmpRead()<br \/>\nBEGIN<br \/>\nLOCAL d, rawT, rawP, var1, var2, t, p;<br \/>\nd:=ArduinoI2cRead(BMP_ADDR,247,6); \/\/ 0xF7 : press_msb,lsb,xlsb, temp_msb,lsb,xlsb<br \/>\nrawP:=d(1)*4096+d(2)*16+IP(d(3)\/16);<br \/>\nrawT:=d(4)*4096+d(5)*16+IP(d(6)\/16);<\/p>\n<p>\/\/ Compensation temperature (formule flottante officielle Bosch)<br \/>\nvar1:=(rawT\/16384-BMP_T1\/1024)*BMP_T2;<br \/>\nvar2:=(rawT\/131072-BMP_T1\/8192)*(rawT\/131072-BMP_T1\/8192)*BMP_T3;<br \/>\nBMP_TFINE:=var1+var2;<br \/>\nt:=BMP_TFINE\/5120;<\/p>\n<p>\/\/ Compensation pression<br \/>\nvar1:=BMP_TFINE\/2-64000;<br \/>\nvar2:=var1*var1*BMP_P6\/32768;<br \/>\nvar2:=var2+var1*BMP_P5*2;<br \/>\nvar2:=var2\/4+BMP_P4*65536;<br \/>\nvar1:=(BMP_P3*var1*var1\/524288+BMP_P2*var1)\/524288;<br \/>\nvar1:=(1+var1\/32768)*BMP_P1;<br \/>\nIF var1==0 THEN RETURN {t,0}; END; \/\/ evite une division par zero<br \/>\np:=1048576-rawP;<br \/>\np:=(p-var2\/4096)*6250\/var1;<br \/>\nvar1:=BMP_P9*p*p\/2147483648;<br \/>\nvar2:=p*BMP_P8\/32768;<br \/>\np:=p+(var1+var2+BMP_P7)\/16;<\/p>\n<p>RETURN {t,p};<br \/>\nEND;<\/p>\n<p>EXPORT ArduinoClose()<br \/>\nBEGIN<br \/>\n\/\/ USBClose() n&#x27;existe pas en PPL (seuls USBOpen\/USBSend\/USBRecv sont<br \/>\n\/\/ documentes) &#8212; la connexion USB-HID ne se ferme pas explicitement.<br \/>\nMSGBOX(&quot;Connexion Arduino fermee.&quot;);<br \/>\nEND;<br \/>\n<\/textarea><\/p>\n<h2>Le capteur d&rsquo;humidit\u00e9 en pratique : ArduinoReadDHT() et le trac\u00e9 en direct<\/h2>\n<p>La fonction <code>ArduinoReadDHT(pin)<\/code> envoie la commande <code>0x07<\/code>, r\u00e9cup\u00e8re temp\u00e9rature et humidit\u00e9 (encod\u00e9es en dixi\u00e8mes sur 16 bits), et filtre les valeurs hors plage physique (humidit\u00e9 &gt; 100 %, par exemple) \u2014 le checksum du protocole DHT, une simple somme sur 8 bits, ne d\u00e9tecte pas toutes les erreurs de timing. En cas d&rsquo;\u00e9chec, elle r\u00e9essaie jusqu&rsquo;\u00e0 5 fois en respectant les 2 secondes de repos minimum exig\u00e9es par le capteur entre deux lectures.<\/p>\n<p><code>ArduinoDHTPlot(pin)<\/code> va plus loin : elle boucle sur <code>ArduinoReadDHT()<\/code> et trace en direct, \u00e0 l&rsquo;\u00e9cran de la Prime, deux courbes qui d\u00e9filent \u2014 la temp\u00e9rature en rouge, l&rsquo;humidit\u00e9 en bleu \u2014 jusqu&rsquo;\u00e0 l&rsquo;appui sur la touche Annul. De quoi transformer la calculatrice en petit enregistreur de donn\u00e9es climatiques de bureau, sans \u00e9crire une seule ligne de code suppl\u00e9mentaire c\u00f4t\u00e9 Arduino.<\/p>\n<h2>D\u00e9bogage : isoler le probl\u00e8me avant m\u00eame d&rsquo;allumer la Prime<\/h2>\n<p>Petite le\u00e7on apprise en cours de route, et qui vaut pour n&rsquo;importe quel projet USB-HID maison : avant de soup\u00e7onner le protocole PPL, il vaut mieux valider le firmware Arduino tout seul, depuis un PC Linux, avec la biblioth\u00e8que Python <code>hidapi<\/code>. M\u00eame taille de rapport d\u00e9clar\u00e9e (64 octets), m\u00eame test d&rsquo;echo \u2014 si \u00e7a boucle proprement l\u00e0, le firmware est hors de cause et le probl\u00e8me ne peut venir que du c\u00f4t\u00e9 HP Prime. Ce test au banc a d&rsquo;ailleurs d\u00e9busqu\u00e9 un bug amusant : une trame envoy\u00e9e trop courte faisait parfois remonter, \u00e0 la place des donn\u00e9es attendues, la cha\u00eene de description USB du p\u00e9riph\u00e9rique en ASCII (litt\u00e9ralement le mot \u00ab arduino \u00bb) \u2014 signe que le firmware HID-Project, d\u00e9synchronis\u00e9, se mettait \u00e0 r\u00e9pondre avec autre chose que le protocole attendu.<\/p>\n<blockquote>\n<p>Autre pi\u00e8ge typiquement PPL : les listes sont index\u00e9es <strong>\u00e0 partir de 1<\/strong>, donc l&rsquo;octet 0 d&rsquo;un rapport HID (<code>resp[0]<\/code> c\u00f4t\u00e9 C) devient <code>r(1)<\/code> en PPL, l&rsquo;octet 1 devient <code>r(2)<\/code>, etc. Un classique d\u00e9calage d&rsquo;un cran qui ne pardonne pas sur un protocole binaire.<\/p>\n<\/blockquote>\n<p>Et pour \u00eatre tout \u00e0 fait honn\u00eate : le transport brut a \u00e9t\u00e9 valid\u00e9 de bout en bout sur PC Linux (echo exact, lecture analogique, \u00e9criture digitale), mais la syntaxe pr\u00e9cise d&rsquo;<code>USBOpen<\/code>\/<code>USBSend<\/code>\/<code>USBRecv<\/code> c\u00f4t\u00e9 HP Prime n&rsquo;a pas pu \u00eatre confirm\u00e9e sur une documentation officielle accessible \u2014 ce programme est \u00e9crit \u00e0 partir de recherches, pas d&rsquo;un exemple garanti par HP. Reste donc un point rest\u00e9 en suspens dans le code lui-m\u00eame : faut-il pr\u00e9fixer chaque trame envoy\u00e9e d&rsquo;un octet factice <code>0x00<\/code> (le fameux \u00ab ID de rapport \u00bb des p\u00e9riph\u00e9riques HID non num\u00e9rot\u00e9s) ? Le programme garde les deux variantes disponibles via un simple interrupteur (<code>USE_ID_PREFIX<\/code>), \u00e0 corriger au besoin une fois la calculatrice vraiment branch\u00e9e sur l&rsquo;Arduino \u2014 exactement le m\u00eame esprit \u00ab \u00e0 tester et ajuster ensemble sur le mat\u00e9riel r\u00e9el \u00bb que pour mes pr\u00e9c\u00e9dents programmes PPL maison.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ma HP Prime passe le plus clair de son temps \u00e0 faire des maths \u2014 mais c&rsquo;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&rsquo;importe quel [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-4747","post","type-post","status-publish","format-standard","hentry","category-electronique-maker"],"_links":{"self":[{"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/posts\/4747","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/comments?post=4747"}],"version-history":[{"count":3,"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/posts\/4747\/revisions"}],"predecessor-version":[{"id":4750,"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/posts\/4747\/revisions\/4750"}],"wp:attachment":[{"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/media?parent=4747"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/categories?post=4747"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sophie-matthieu.eu\/index.php\/wp-json\/wp\/v2\/tags?post=4747"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}