🛠 Premier pont AOA : écho série avec Android

Arduino Mega ADK (Rev3) · intermediaire · Schéma : IMG-mega-adk-intermediaire-1-mega-adk-pont-aoa-serie.svg — Mega ADK alimentée en externe (7-12 V, 1,5 A), port USB-A relié au téléphone Android, tableau de bord écho série

Objectifs

Établir le premier pont Android Open Accessory (AOA) entre un téléphone et la Mega ADK : le téléphone se connecte au port USB-A de la carte — le MAX3421E est l'hôte USB, le téléphone est l'appareil USB, l'inverse d'un montage OTG classique. L'application Android détecte l'accessoire, ouvre deux canaux en mode point à point, et la carte renvoie (écho) toute ligne reçue. Rappels protocolaires (AOA 1.0, source.android.com) : en mode accessoire le téléphone énumère en VID Google 0x18D1, PID 0x2D00 (AOAv1), 0x2D01 (+adb), 0x2D02 à 0x2D05 (variantes AOAv2) ; le handshake se fait par requêtes de contrôle vendor sur l'endpoint 0 — req 51 GET_PROTOCOL, req 52 SET_STRING (6 chaînes : fabricant, modèle, description, version, URI, numéro de série), req 53 START_ACCESSORY. Envoyez toujours la chaîne version : son absence peut redémarrer les téléphones Android ≤ 10 pendant l'énumération. Le mode accessoire existe depuis Android 3.1, rétroporté à 2.3.4.

Matériel

Code complet

Côté carte (Arduino) :

// mega-adk-pont-aoa-serie.ino — Board: "Arduino Mega ADK"
#include <Usb.h>
#include <usbhub.h>
#include <adk.h>

USB Usb;
USBHub hub0(&Usb);
ADK adk(&Usb, "AtelierADK", "PontSerie", "Pont AOA série",
        "1.0", "http://example.org", "SN000001"); // version obligatoire !

void setup {
  Serial.begin(115200);
  if (Usb.Init == -1) {                 // MAX3421E muet
    Serial.println(F("OSCOKIRQ failed to assert"));
    while (1);
  }
  Serial.println(F("Attente du telephone en mode accessoire..."));
}

void loop {
  Usb.Task;                              // piloter l'énumération, souvent !
  if (!adk.isReady) return;

  uint8_t buf[64]; uint16_t len = sizeof(buf);
  if (adk.RcvData(&len, buf) == 0 && len > 0) {
    adk.SndData(len, buf);                 // écho vers l'application
    Serial.write(buf, len);                // miroir sur la console
  }
  while (Serial.available) {             // console -> téléphone
    uint8_t b = (uint8_t)Serial.read;
    adk.SndData(1, &b);
  }
  delay(10);
}

Côté Android (Java, activité minimale avec demande de permission) :

// PontActivity.java — déclare dans le manifeste un intent-filter
// android.hardware.usb.action.USB_ACCESSORY_ATTACHED
public class PontActivity extends Activity {
    private static final String ACTION_PERM = "com.atelier.adk.USB_PERMISSION";
    private FileInputStream in; private FileOutputStream out;
    private TextView flux; private EditText champ;

    @Override protected void onCreate(Bundle b) {
        super.onCreate(b);
        setContentView(R.layout.activity_pont);
        flux = findViewById(R.id.flux); champ = findViewById(R.id.champ);
        registerReceiver(permReceiver, new IntentFilter(ACTION_PERM));
        handleIntent(getIntent);
    }
    private void handleIntent(Intent intent) {
        if (!UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction)) return;
        UsbAccessory acc = UsbManager.getAccessory(intent);
        UsbManager mgr = (UsbManager) getSystemService(Context.USB_SERVICE);
        if (!mgr.hasPermission(acc))
            mgr.requestPermission(acc, PendingIntent.getBroadcast(
                this, 0, new Intent(ACTION_PERM), 0));
        try {
            ParcelFileDescriptor pfd = acc.open(mgr, mgr.hasPermission(acc));
            in  = new FileInputStream(pfd.getFileDescriptor);
            out = new FileOutputStream(pfd.getFileDescriptor);
            new Thread(this::boucleLecture).start;
        } catch (IOException e) { e.printStackTrace; }
    }
    private void boucleLecture {
        byte[] buf = new byte[512]; int n;
        try { while ((n = in.read(buf)) > 0) {
            final String s = new String(buf, 0, n);
            runOnUiThread( -> flux.append("Carte : " + s));
        } } catch (IOException ignored) { }   // débranchement
    }
    public void envoyer(View v) throws IOException {
        out.write((champ.getText.toString + "\n").getBytes);
    }
}

Vérification attendue

En branchant le câble USB-A de la carte au téléphone, l'application se lance automatiquement via l'intent USB_ACCESSORY_ATTACHED, une popup demande l'autorisation USB, puis chaque ligne tapée dans la console série de l'ordinateur revient en écho dans l'application, et chaque ligne envoyée par l'application s'affiche sur la console. Le téléphone doit se re-énumérer en VID 0x18D1 (visible en déverminage USB) après le START_ACCESSORY.

Erreurs fréquentes

  1. Oubli de Usb.Task dans loop : l'énumération n'avance jamais, adk.isReady reste faux indéfiniment.
  2. Chaîne version vide ou absente : risque de redémarrage du téléphone (Android ≤ 10) ; des chaînes constructeur différentes de celles du filtre accessory_filter empêchent le lancement automatique de l'application.
  3. AOA et MTP simultanés : incompatibles — si le téléphone est en mode « transfert de fichiers », débranchez/rebranchez en mode charge seule.
  4. Carte alimentée uniquement par l'ordinateur : budget 500 mA partagé avec la charge du téléphone, brownouts et déconnexions fantômes ; utiliser une alimentation externe 7–12 V ≥ 1,5 A.
  5. Chercher le vieux paquet ADK 2011 de Google : l'écosystème officiel est mort (wiki 404, docs redirigées) ; utilisez la bibliothèque maintenue USB Host Shield 2.0 et sa classe ADK.

Autres tutoriels pour cette carte

Exemples de code pour cette carte

← Retour à la fiche 🛠 Tous les tutoriels