// WIKI

USBPD-Stack

Distribuzione cross-platform dello stack USB Power Delivery NXP per PTN5110.

Pubblicato il Aggiornato il ESP32-S3 · STM32 · ArduinoCPD 2.0 / 3.0PTN5110 TCPCBSD-3 + MIT
Codice su GitHub ↗

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
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).

Ultimo aggiornamento: · Trovato un errore o un dato superato? Segnalacelo

← Torna all’indice Wiki

Un progetto simile?

Acustica, embedded, strumenti di calcolo: se hai un caso d’uso vicino, parliamone.