🛠 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

Objectives

Establish the first Android Open Accessory (AOA) bridge between a phone and the Mega ADK: the phone connects to the card's USB-A port — the MAX3421E acts as the USB host, and the phone acts as the USB device, which is the reverse of a classic OTG setup. The Android application detects the accessory, opens two channels in point-to-point mode, and the card echoes back any received line. Protocol reminders (AOA 1.0, source.android.com): in accessory mode, the phone enumerates with Google VID 0x18D1, PID 0x2D00 (AOAv1), 0x2D01 (+adb), 0x2D02 to 0x2D05 (AOAv2 variants); the handshake is performed via vendor control requests on endpoint 0 — req 51 GET_PROTOCOL, req 52 SET_STRING (6 strings: manufacturer, model, description, version, URI, serial number), req 53 START_ACCESSORY. Always send the version string: its absence may cause Android phones ≤ 10 to reboot during enumeration. Accessory mode has existed since Android 3.1, backported to 2.3.4.

Hardware

Complete Code

Card side (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 serie",
        "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);
}

Android side (Java, minimal activity with permission request):

// 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);
    }
}

Expected Verification

When plugging the card's USB-A cable into the phone, the application launches automatically via the USB_ACCESSORY_ATTACHED intent, a popup requests USB permission, then each line typed into the computer's serial console is echoed back in the application, and each line sent by the application appears on the console. The phone must re-enumerate with VID 0x18D1 (visible in USB debugging) after START_ACCESSORY.

Common Errors

  1. Forgetting Usb.Task in loop: enumeration never progresses, adk.isReady remains false indefinitely.
  2. Empty or missing version string: risk of phone reboot (Android ≤ 10); manufacturer strings different from those in accessory_filter prevent the application from launching automatically.
  3. AOA and MTP simultaneously: incompatible — if the phone is in "file transfer" mode, unplug/replug in charge-only mode.
  4. Card powered only by the computer: 500 mA budget shared with phone charging, brownouts and phantom disconnections; use an external 7–12 V ≥ 1.5 A power supply.
  5. Looking for Google's old ADK 2011 package: the official ecosystem is dead (wiki 404, docs redirected); use the maintained USB Host Shield 2.0 library and its ADK class.

Other tutorials for this board

Code examples for this board

← Back to the board 🛠 All tutorials