# USBPD-Stack

> Distribuzione open source dello stack USB Power Delivery NXP MCUXpresso per il TCPC PTN5110: una policy engine PD su ESP32-S3, STM32 e Arduino via adapter HAL.

Pubblicato: 2026-05-20
Aggiornato: 2026-08-25
Ambito: elettronica
Riferimento normativo: USB Power Delivery (PD) su USB Type-C <https://en.wikipedia.org/wiki/USB_Power_Delivery>
Repository: <https://github.com/stefanofante/USBPD-Stack>

Pagina: <https://www.stline.it/wiki/usbpd-stack/>

---

USBPD-Stack è una distribuzione open source dello stack USB Power Delivery NXP MCUXpresso (derivato dal middleware MCUXpresso 25.06.00): mantiene il collaudato driver TCPC per il PTN5110 e aggiunge un build system moderno, un layer HAL portabile e port multi-piattaforma. Il valore aggiunto ST-LINE è la portabilità: la stessa policy engine PD gira su ESP32-S3, STM32 e Arduino cambiando solo gli adapter. Questa pagina sintetizza README, guida d’uso e wiki del repository; il riferimento autoritativo resta GitHub.

## Origine e valore aggiunto

Il cuore PD (policy engine e driver PTN5110) proviene dallo stack USB PD di NXP MCUXpresso, codice già provato in compliance. ST-LINE non riscrive il protocollo: lo riconfeziona in una struttura pulita, con separazione netta fra codice NXP ereditato (`third_party/nxp_pd/`) e aggiunte proprie.

L’apporto originale è il layer di portabilità: adapter HAL header-only per GPIO, I²C e primitive di sistema operativo, port di scheduling per ogni piattaforma, layout PlatformIO-ready e selezione del target a compile-time. Doppia licenza: BSD-3-Clause per i sorgenti NXP, MIT per le parti ST-LINE.

## Cosa fa

Policy engine USB Power Delivery 2.0 / 3.0 con supporto al Type-C Port Controller PTN5110. Gestisce ruoli Source, Sink e DRP, scoperta delle capability, negoziazione dei contratti PDO/RDO e transizioni di alimentazione.

Funzioni opzionali abilitabili da configurazione: alt mode DisplayPort, Programmable Power Supply (PPS) di PD 3.0 e Fast Role Swap (opt-in).

## I concetti USB Power Delivery in breve

USB Power Delivery (PD) è il protocollo che permette a due dispositivi su Type-C di negoziare tensione e corrente ben oltre i 5 V / 3 A del bus USB classico — fino a 240 W con la Extended Power Range. Pochi termini ricorrono in tutta la documentazione:

- **Source / Sink** — chi eroga e chi assorbe potenza. Un **DRP** (Dual-Role Power) può fare entrambi e scambiarsi il ruolo a runtime.
- **PDO / RDO** — la Source pubblicizza le proprie capability come lista di **Power Data Object** (es. “5 V 3 A”, “9 V 3 A”, “20 V 5 A”); il Sink ne sceglie uno e risponde con un **Request Data Object** che fissa il contratto di alimentazione.
- **TCPC** — Type-C Port Controller, il chip (qui il PTN5110) che implementa il PHY PD e l'interfaccia fisica del cavo; la **policy engine** software, che gira sull'MCU, decide ruoli, capability e transizioni dialogando col TCPC via I²C secondo l'interfaccia TCPCI.
- **PPS** — Programmable Power Supply di PD 3.0: tensione regolabile a passi fini (20 mV) per la ricarica diretta delle batterie.

La policy engine di USBPD-Stack è esattamente la macchina a stati che orchestra questo dialogo; il driver PTN5110 è il ponte verso il silicio.

## Target supportati

ESP32-S3 (ESP-IDF) — FreeRTOS + HAL, stato Stable: tick a 1 kHz tramite software timer FreeRTOS condiviso.

STM32 F3/F4/H7 — FreeRTOS + Cube HAL, stato Stable: gestione ALERT via EXTI, timeout I²C configurabile.

Arduino (AVR/ESP/NXP) — loop cooperativo, stato Beta: usa `TwoWire` per l’I²C; `PD_PortArduino_TaskTick()` va chiamata dal loop principale.

Si seleziona esattamente una macro `PD_CONFIG_TARGET_*` prima della compilazione; ogni piattaforma ha la propria struttura `pd_phy_<platform>_config_t` che descrive le risorse MCU richieste.

## Architettura

`include/` — header pubblici del core (policy engine, configurazioni, tipi). `src/` — sorgenti C del core: Policy Engine (`usb_pd_policy.c`, macchina a stati PD), Device Policy Manager (`usb_pd_interface.c`, crea le istanze PD e dispaccia gli eventi), timer di protocollo (`usb_pd_timer.c`) e alt mode.

`third_party/nxp_pd/` — driver PTN5110 e codice NXP ereditato (accesso ai registri TCPC, interrupt ALERT, recovery del bus). `support/` — astrazione HAL (GPIO, I²C, primitive OS) e relative implementazioni per piattaforma. `port/` — glue dello scheduler che invoca periodicamente `PD_TimerIsrFunction`.

Per portare lo stack su una nuova MCU si implementa essenzialmente solo il layer `support/` (più il port di scheduling): il core e il driver PTN5110 restano invariati.

![Diagramma di architettura dello stack USBPD-Stack](/img/openlab/usbpd-stack-arch.webp)

Architettura a layer: core PD + driver PTN5110 (NXP) sotto, adapter HAL e port di piattaforma sopra. Fonte: repository USBPD-Stack.

## Configurazione

Tutte le opzioni vivono in `include/usb_pd_config.h`. Oltre alla selezione del target (`PD_CONFIG_TARGET_ESP32S3` / `_STM32` / `_ARDUINO`; default ESP32-S3 se nessuna definita) i flag principali sono: `PD_CONFIG_MAX_PORT` (numero di porte PD simultanee), `PD_CONFIG_ALT_MODE_SUPPORT` / `_DP_SUPPORT` (alt mode DisplayPort), `PD_CONFIG_PD3_PPS_ENABLE` (PPS), `PD_CONFIG_SOURCE_ROLE_ENABLE` / `_SINK_ROLE_ENABLE` (limita i ruoli annunciati), `PD_CONFIG_PD3_FAST_ROLE_SWAP_ENABLE`.

Le macro si impostano dal build system (`build_flags` in PlatformIO, `add_compile_definitions` in CMake). Include path da aggiungere: `include/`, `port/include`, `support/include`, `third_party/nxp_pd/include`; sorgenti da compilare: `src/`, `port/src`, `support/src`, `third_party/nxp_pd/src`.

## Integrazione

Prerequisiti: un PTN5110 collegato via I²C con il pin ALERT su un GPIO capace di interrupt sul fronte, pull-up corretti su SDA/SCL e un toolchain C per il target (ESP-IDF, STM32Cube, Arduino, PlatformIO).

Flusso tipico: (1) descrivere l’hardware in `pd_phy_<platform>_config_t` (GPIO SDA/SCL e priorità per ESP32-S3; `I2C_HandleTypeDef*`, pin ALERT e IRQ per STM32; `TwoWire*` e pull-up per Arduino); (2) compilare `pd_instance_config_t` con ruoli e tabelle PDO e `phyType = kPD_PhyPTN5110`; (3) implementare i callback `pd_stack_callback_t` e `pd_power_handle_callback_t`; (4) chiamare `PD_InstanceInit`; (5) eseguire `PD_InstanceTask` in un task FreeRTOS o nel loop Arduino, assicurando il tick condiviso da 1 ms.

### Perché il tick è da 1 ms, e non da 10

Il tick condiviso da 1 ms non è una convenzione comoda: è il numero che esce imponendo che i timer di protocollo cadano dentro le loro finestre. Il PD non definisce scadenze puntuali ma **intervalli** — il più stretto è il `tSenderResponse`, fra **24 e 30 ms**: rispondere prima di 24 ms o dopo 30 ms viola il protocollo allo stesso modo.

La finestra utile è quindi larga **6 ms**, e un tick di periodo *T* introduce fino a *T* di errore di quantizzazione sulla scadenza, perché la macchina a stati può accorgersi del tempo trascorso solo al tick successivo:

| Periodo del tick | Errore massimo | % della finestra | Tick nella finestra | Verdetto |
|---|---|---|---|---|
| 0,5 ms | 0,5 ms | 8,3 % | 12 | utilizzabile |
| **1 ms** | 1 ms | 16,7 % | 6 | utilizzabile |
| 2 ms | 2 ms | 33,3 % | 3 | utilizzabile |
| 5 ms | 5 ms | 83,3 % | 1 | al limite |
| 10 ms | 10 ms | 166,7 % | 0 | **fuori specifica** |
| 20 ms | 20 ms | 333,3 % | 0 | **fuori specifica** |

Con **1 ms** l'incertezza vale il 17 % della finestra e ci stanno sei tick dentro: c'è margine sia per l'errore di quantizzazione sia per la latenza di risposta dell'I²C verso il TCPC. Con **5 ms** la quantizzazione da sola mangia l'83 % della finestra e non resta niente per il resto. Con **10 ms** la finestra viene sfondata dal solo tick: non si tratta di prestazioni marginali, la scadenza può essere mancata.

È anche il motivo per cui il tick va **condiviso** e non moltiplicato: due timer software indipendenti che scandiscono lo stesso stack sommano i propri errori di quantizzazione, e su una finestra da 6 ms la somma si nota. Sulle piattaforme FreeRTOS conviene inoltre verificare che `configTICK_RATE_HZ` sia almeno 1000, perché un tick di sistema più lento fa da pavimento a qualunque timer software costruito sopra.

### Cosa serve davvero per un port nuovo

«Si implementa solo il layer `support/`» è vero ma non dice quanto lavoro sia. In concreto il layer deve fornire quattro cose, e sono queste che decidono se una piattaforma è adatta:

| Cosa deve fornire il layer `support/` | Perché lo stack lo chiede |
|---|---|
| I²C: lettura e scrittura di registri a 8 bit, bloccanti, con timeout | è l’unico canale verso il TCPC: ogni transizione della macchina a stati diventa un accesso a registro |
| GPIO: un ingresso con interrupt sul fronte di discesa (ALERT) | il TCPC non è interrogabile a polling senza perdere eventi: segnala e va servito |
| Tick periodico da 1 ms che invochi `PD_TimerIsrFunction` | è la base tempi di tutti i timer di protocollo — vedi il conto sopra |
| Primitive OS: un task o un loop, e una mutua esclusione sull’I²C | il bus è condiviso con il resto dell’applicazione; l’ISR di ALERT e il task non devono accedervi insieme |

Il criterio di ammissibilità di una MCU si riduce quindi a due domande: **ha un GPIO con interrupt su fronte** (non solo su livello) e **può garantire un tick da 1 ms**? Se sì, il port è meccanico. Se la risposta alla seconda è no — tipico dei loop cooperativi molto carichi — il problema non si risolve nel layer `support/`: si risolve alleggerendo il loop, ed è la ragione per cui il target Arduino è dichiarato **Beta** e non Stable.

### Le resistenze sulle CC: perché non compaiono nel software

Una domanda che ricorre nell'integrazione: dove si impostano i 3 A da annunciare? La risposta è che non si impostano nel software dello stack. Prima che il PD entri in gioco, il Type-C dichiara le proprie intenzioni con delle **resistenze sulle linee CC**, e chi le legge è il TCPC:

| Resistore | Valore | Chi lo mette | Cosa dichiara |
|---|---|---|---|
| Rp | 56 kΩ | Source | solo USB di default (500/900 mA) |
| Rp | 22 kΩ | Source | 1,5 A a 5 V |
| Rp | 10 kΩ | Source | 3 A a 5 V |
| Rd | 5,1 kΩ | Sink | «sono un sink» |
| Ra | ≈ 1 kΩ | cavo attivo | «sono un cavo, non un dispositivo» |

Questo livello — chiamato *Type-C* e non *Power Delivery* — funziona anche senza alcun PD: è il motivo per cui un caricatore Type-C senza PD eroga 3 A a un telefono senza scambiare un solo messaggio. Il PD entra **sopra**, per andare oltre i 5 V. Nel PTN5110 le Rp e Rd sono interne e commutate via registro, quindi lo stack le governa indirettamente attraverso il ruolo che dichiara: non c'è nulla da montare sul PCB oltre alla Rd se il dispositivo è solo sink senza TCPC.

## Diagnostica e note di piattaforma

Diagnostica: si abilitano le macro di debug in `usb_pd_config.h` per tracciare lo scambio di messaggi; `PD_Control(..., PD_CONTROL_GET_PD_STATE, ...)` restituisce il nodo corrente della macchina a stati; il driver PTN5110 espone helper per leggere lo stato di fault e regolare la VBUS.

ESP32-S3: l’adapter installa un timer da 1 ms e usa l’istanza driver I²C `i2c_instance` di `pd_phy_esp32s3_config_t`; `PD_InstanceTask` gira in un task dedicato e il callback del timer è registrato non appena è presente un’istanza.

STM32 (F3/F4/H7): richiede handle Cube HAL e FreeRTOS; si forniscono puntatore I²C HAL, info GPIO ALERT e priorità IRQ in `pd_phy_stm32_config_t`, e l’adapter gestisce il dispatch EXTI tramite `PD_PortStm32_DispatchExti`. Arduino: design cooperativo, si chiamano `PD_PortArduino_TaskTick()` e `PD_InstanceTask()` dentro `loop()`, I²C via `TwoWire`.

## Conformità, stato e roadmap

`docs/library_usage.md` contiene la guida d’integrazione passo-passo; `docs/compliance_test_report/` raccoglie catture Ellisys dello stack NXP originale. Release iniziale 0.1.0 (2025-11-06); sviluppo sul branch develop.

Roadmap dichiarata: port di un layer TCPC FUSB302B con helper di gestione ALERT, refresh dei report di compliance Ellisys sulle nuove piattaforme e progetti di esempio pronti al build per ogni target. Riferimenti: scheda prodotto PTN5110 (NXP) e USB Type-C Port Controller Interface Specification (UM11056).
