Tout sur l’USB-C : réponse à la PD de bas niveau

La dernière fois, nous avons configuré le FUSB302 pour recevoir des messages USB PD et avons reçu avec succès un message « annonce de capacité » d’un bloc d’alimentation USB-C. Maintenant, nous ouvrons la spécification PD, analysons le message, puis élaborons une réponse qui fait que le bloc d’alimentation nous donne la tension la plus élevée disponible.

À quoi ressemblait le contenu du tampon, encore ?

>>> b
b'\xe0\xa1a,\x91\x01\x08,\xd1\x02\x00\x13\xc1\x03\x00\xdc\xb0\x04\x00\xa5@\x06\x00<!\xdc\xc0H\xc6\xe7\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Les zéros à la fin peuvent sembler non significatifs, et ils ne le sont en effet pas avec une certitude de 99,99 % – cela dit, ne vous contentez pas de jeter toute la fin ; l’un des octets au début code la longueur du message. Nous allons d’abord lire ces octets, puis lire exactement ce dont nous avons besoin, en nous assurant que nous ne lisons pas deux messages et que nous ne les interprétons pas comme un seul, et que nous ne supprimons pas les zéros qui font partie du message.

Aujourd’hui, nous allons écrire du code qui analyse les messages juste après les avoir lus à partir du tampon FIFO – cependant, gardez toujours ce message à portée de main pour référence ; et si vous n’avez pas le matériel, vous pouvez néanmoins l’utiliser pour vous essayer au décodage. Si vous voulez vous lancer, vous pouvez trouver le code complet d’aujourd’hui ici !

Analyse d’en-tête

Le premier octet du tampon est 0xe0et cela ne fait pas réellement partie d’un message PD que nous devons analyser – c’est un jeton « début de message », et vous pouvez le trouver dans la section « Jetons RX » de la fiche technique FUSB302 page 29. Si vous cherchez là et je ne sais pas ce qu’est SOP – pour nos besoins, SOP (sans ' ou " à la fin) signifie « ce paquet est destiné à un appareil à l’extrémité du câble, et non à l’intérieur du câble » ; nous sommes, en effet, au bout d’un câble et non à l’intérieur. Cependant, les octets supplémentaires constituent une partie significative d’un paquet USB-C, et c’est là que vous souhaitez ouvrir la spécification PD.

L’en-tête est décrit dans la spécification PD 3.0 section 6.2.1.1 – page 109. C’est deux octets : dans notre cas, c’est le \xa1a une partie de la représentation bytearray de Python, 0xa1 0x61 en hexadécimal et 0b10100001 0b1100001 en binaire. Le premier octet contient les bits 7-0 et le deuxième octet contient les bits 15-8 – vous pourriez dire que chaque partie d’un message PD arrive à l’envers. La partie principale qui nous intéresse est les bits 14-12 – prenez le deuxième octet, décalez-le de 4 à droite et masquez-le avec 0b111 pour obtenir la longueur du message. Dans notre cas, (0x61 >> 4) & 0b111 est égal à 6.

Si la longueur du message est égale à zéro, nous avons reçu un message de contrôle – ceux-ci sont décrits dans la section 6.3, à la page 119 de la spécification. Dans l’exemple de message, la longueur est de 6. Il ne s’agit pas d’un nombre d’octets, mais du nombre d’objets de données PD, également appelés PDO (Power Data Object). Chacun d’eux fait quatre octets de long, et dans notre cas, chacun d’eux correspond à un profil PD. De plus, il y a un CRC à la fin du message, qui est de quatre octets. Heureusement, nous n’avons pas besoin de vérifier le CRC – le FUSB302 a vérifié le CRC pour nous ; si le CRC n’était pas correct, il ne mettrait pas le message dans le FIFO pour que nous le lisions en premier lieu.

Combien d’octets de plus devons-nous lire, alors ? Nous avons déjà lu trois octets, déterminant que nous devons lire six objets de données à quatre octets, puis un CRC à quatre octets. Au total, ce message fait 31 octets. Lisons d’abord les objets, puis lisons le CRC et supprimons-le. Le plus simple serait probablement de lire le FIFO quatre octets à la fois – j’ai lu l’intégralité du PDO, puis je l’ai divisé en messages par la suite dans ma propre implémentation.

Obtenir les profils de puissance

pdo_count = 6
pdos = []
for i in range(pdo_count):
pdo = i2c.readfrom_mem(0x22, 0x43, 4)
pdos.append(pdo)
_ = i2c.readfrom_mem(0x22, 0x43, 4) # discarding the CRC

Maintenant, nous avons une liste de profils de puissance pas encore analysés dans pdos – Je les appellerai AOP par souci de brièveté. Ici, vous feriez bien d’écrire une fonction distincte pour analyser un PDO, si ce n’est uniquement pour des raisons de lisibilité.

Le format du message de données est décrit dans la section 6.4 de la spécification, page 129. La première chose que vous vérifiez avec un PDO est le type de données, les bits 30-31 ou les bits 7-6 du dernier octet du PDO tel que nous le recevons. . Il existe quatre types possibles – fixe (le plus populaire), batterie et alimentation variable, et le type PDO augmenté. Nous pouvons nous limiter pour l’instant au traitement des PDO fixes, et ignorer en toute sécurité les autres types.

Si vous commencez déjà à analyser les PDO, vous remarquerez que nous avons cinq PDO fixes et un PDO étendu. Je dirai que cela correspond au marquage sur l’alimentation USB-C avec laquelle j’ai reçu ce message ! Passons en revue le PDO – voir page 132 pour le tableau 6-9 ; c’est une très belle table et elle a tout ce dont vous pourriez avoir besoin. Analysons-le pour le premier PDO.

00101100 10010001 00000001 00001000


Le courant maximum est les bits 0-9 – donc, les deux derniers bits de l’octet 1, puis l’octet 0 entier. La tension est les bits 19-10 – les quatre derniers bits de l’octet 2 et les six premiers bits de l’octet 1. Si cela est pénible à lire, reportez-vous à ce morceau de code qui analyse les PDO en Python. Après avoir obtenu les valeurs de tension et de courant, multipliez la tension par 50 et le courant par 10, pour obtenir respectivement les millivolts et les milliampères.

>>> 0b0100101100 * 10
3000
>>> 0b0001100100 * 50
5000

Oh, regardez ça – nous avons 3000 et 5000, ce qui, comme vous l’avez peut-être deviné, signifie 5 V à 3 A. La fonction d’analyse PDO pour cette partie peut être trouvée ici.

Demander un profil de puissance

Maintenant, nous avons les PDO – de 5 V jusqu’à 20 V. Pour demander l’un d’eux au PSU, nous devons créer un message de demande. Et rappelez-vous – pour que le bloc d’alimentation nous fournisse une tension plus élevée, nous devons envoyer notre réponse rapidement, avant que le bloc d’alimentation n’attende une réponse. Écrivons alors une fonction qui élabore une réponse et peut répondre automatiquement avec celle-ci. C’est un message de quatre octets, avec un en-tête de deux octets – faisons une liste de six zéros, modifions-les sur place, puis envoyons-les. Quelque chose de rapide et sale comme pdo = [0 for i in range(6)] fera des merveilles.

Pour commencer, référons-nous à la spécification de l’en-tête – maintenant nous devons lire les champs dans l’en-tête du message et définir ceux dont nous avons besoin. Encore une fois, section 6.2.1.1, page 109 ! Pour les bits 15-8 (pdo[1]), il suffit de changer le nombre d’objets de données. Dans notre cas, c’est 1 – nous envoyons un message de données avec un seul message de requête PDO à l’intérieur. Pour les bits 7-0 (pdo[0]), nous devons définir la révision de spécification (octets 7-6) sur 0b11. Nous devons également définir le type de message de données dans les octets 4-0 : voir le tableau 6-6 à la page 128 pour cela ; dans notre cas, c’est un message Request, avec le code 0b00010. Oh, et il y a un champ « Message ID » que nous pouvons maintenant laisser à 0, mais que vous voudrez incrémenter pour les messages suivants. C’est tout ce dont nous avons besoin dans l’en-tête – maintenant, rédigeons la demande réelle dans les quatre octets restants.

Les messages de requête sont décrits dans la section 6.4.2, page 141 – vous aurez besoin du tableau 6-21. Pour demander un PDO, nous devons connaître son index – et l’incrémenter de 1 avant de l’envoyer. Ainsi, 5 V @ 3 A est PDO 1, 9 V @ 3 A est PDO 2, et ainsi de suite. Allons-y pour le 9 V AOP et mettons 0b010 dans les bits 31-28. Le bloc d’alimentation USB-C voudra également connaître le courant maximum et moyen que nous prévoyons réellement de consommer. Puisque nous expérimentons, demandons quelque chose comme 1 A, en réglant à la fois le courant maximum (bits 9-0) et le courant de fonctionnement (bits 19-10) sur 0b1100100. Vous ferez également un bon réglage du bit 24 (bit 0 de pdo[5]) pour désactiver la suspension USB – juste au cas où.

Maintenant, nous avons un message ! Cependant, nous ne pouvons pas simplement le mettre dans le FIFO. Nous devons préfixer et ajouter deux séquences d’octets qui permettent au FUSB302 de savoir ce qui se passe, appelées séquences SOP et EOP (début et fin de paquet respectivement) – consultez à nouveau la page 29 de la fiche technique du FUSB302. La séquence SOP est longue de cinq jetons et transmet essentiellement un préambule de message – trois jetons SOP1, un jeton SOP2 et un jeton PACKSYM ; nous devons OU le jeton PACKSYM avec la longueur de notre message en octets, six dans notre cas, ce qui en fait 0x86. La séquence EOP est JAM_CRC, EOP (jeton), TXOFF et TXON. Pourquoi ces séquences exactes, je ne comprends pas très bien, mais je suis assez content d’avoir des piles PD open source à partir desquelles je pourrais copier ce comportement. Donc, 0x12 0x12 0x12 0x13 0x86 avant le paquet, et 0xff 0x14 0xfe 0xa1 après.

Séquence SOP, paquet, séquence EOP – mettez-les tous dans un FIFO, et nous aurons envoyé un message de demande. Le flux de travail global est simple – obtenez des capacités, analysez des capacités, choisissez celle que vous aimez, créez un message de demande, envoyez-le, obtenez votre tension. La récompense ? Vous obtenez la tension de votre choix.

Un brin de débogage

Si nous n’avons rien manqué, sonder VBUS montrera que vous avez réussi à extraire le profil 9 V que nous avons convenu d’essayer. Si vous rencontrez des problèmes, encore une fois, voici le code de référence en Python que vous pouvez utiliser, et voici une référence de transmission I2C pour le Pinecil. Avoir des problèmes? Voici quelques conseils.

Comme d’habitude avec le débogage, print() déclarations vous aideront un peu, jusqu’à un certain point. D’une part, ils sont indispensables, surtout si vous êtes méticuleux dans la conversion des données en représentations binaires ou hexadécimales en fonction de celle qui est la plus utile à tout moment du débogage. Par exemple, vous pouvez imprimer le paquet entier en hexadécimal, puis imprimer les PDO en binaire afin de pouvoir vérifier votre code d’analyse.

Il ne faut pas beaucoup d’impressions en boucle pour retarder considérablement les communications

D’autre part, print() déclarations interfèreront avec les exigences temporelles à un degré surprenant. L’envoi de données via la console prend beaucoup de temps, même s’il s’agit d’une console virtuelle, comme c’est le cas avec l’UART virtuel du RP2040 via USB-CDC. J’ai passé environ deux heures à déboguer ce code sur un RP2040 et à frapper la fenêtre de temporisation tout le temps, seulement pour découvrir que j’avais vingt instructions d’impression, et ils ont à eux seuls fait passer mon code de « vraiment rapide » à « trop ​​lent pour répondre ”. Après avoir commenté le print() déclarations, mon code a commencé à fonctionner sur chaque bloc d’alimentation avec lequel je l’ai essayé, et j’ai ajouté une tonne entière de logique de sélection de tension et de courant personnalisée sans aucun problème.

La vérification du contenu du tampon de réception est également utile. Après avoir envoyé votre demande, vérifiez l’état du tampon de réception, comme à la fin du dernier article. Y a-t-il des données en attente ? Lisez le message et vérifiez l’en-tête – est-ce un message d’acceptation ? Reportez-vous à la page 119 pour le code de celui-ci. Rien dans la mémoire tampon après un message de requête ? Vous avez probablement enfreint les exigences de temps.

D’un autre côté, il est assez difficile d’écrire MicroPython suffisamment lent pour violer les exigences de synchronisation ici. Au fur et à mesure que vous rendez le script plus complexe, il se peut que vous dépensiez trop de choses entre la réception des PDO et le renvoi d’une réponse. Ou peut-être recevez-vous un autre type de message dans votre tampon de réception ? Votre bloc d’alimentation peut envoyer un autre message qui nécessite une réponse rapide – peut-être que vous travaillez avec le port USB-C d’un ordinateur portable et qu’il veut autre chose.

9 volts atteints – Quel est le prochain ?

Ce que nous avons fait ici rivalise avec le prix d’une carte de déclenchement PD, c’est beaucoup plus personnalisable, probablement aussi bon marché sinon moins cher qu’un circuit intégré de déclenchement PD, et indéniablement beaucoup plus cool. Oh, et nous avons appris à lire et à envoyer des messages PD – ce qui peut et vous aidera si jamais vous êtes intéressé à créer quelque chose d’extraordinaire avec USB-C. Tout ce dont vous avez besoin est une puce FUSB302 (environ 50 cents pièce), associée à un microcontrôleur suffisamment dédié à la tâche de parler de PD – vous avez peut-être déjà un tel MCU dans votre projet en train de faire autre chose.

Le code est en MicroPython ; cela dit, c’est suffisamment pseudo-code pour qu’il soit facile de le porter dans une autre langue à partir d’ici. Si vous exécutez C++ ou C, vérifiez la pile IronOS ; il y en a un adapté à STM32 HAL, un adapté à Arduino, et il y a une pile décente de Microchip. Je n’ai vu que le premier en action; Pourtant, si vous ne vous sentez pas comme MicroPython, je parierais que l’un d’entre eux vous conviendra.

Quelque chose que vous avez peut-être remarqué – à aucun moment je n’ai eu à me référer aux diagrammes effrayants de la machine d’état USB-C. Il y a quelques états dans ce code, techniquement, et les machines à états sont suffisamment grandes pour que ce code soit amélioré avec un s’il devenait plus complexe ; cependant, vous n’en avez vraiment pas besoin si tout ce que vous voulez, c’est 9 V à partir d’une alimentation USB-C. Les diagrammes effrayants peuvent cependant vous aider à déboguer des choses comme le délai d’attente de 500 ms entre la publicité et la réponse – en d’autres termes, n’ayez pas peur.

À partir de là, vous pouvez faire beaucoup de choses USB-C. Vous pouvez transformer vos alimentations à prise cylindrique en USB-C avec un peu de circuits supplémentaires, créer une alimentation avec des profils personnalisés farfelus, explorer les capacités cachées des contrôleurs PD, retirer DisplayPort des ports USB-C – enfer, si vous êtes incliné vers le pentesting, vous pouvez même créer des gadgets USB-C malveillants.

Voici mon hack simple personnel – un court algorithme qui sélectionne le meilleur PDO pour une valeur de résistance statique tout en gardant à l’esprit les valeurs de courant maximales ; résoudre exactement le scénario où un tableau de déclenchement nous échoue. Cela correspond parfaitement au code que nous avons écrit jusqu’à présent, et si vous souhaitez développer un périphérique USB-C haute puissance qui fait quelque chose de similaire, cela pourrait vous intéresser.

Vous pouvez et devez approcher USB-C d’une manière hacker, et cet article est un excellent exemple que vous n’avez pas besoin de toute la complexité de la norme USB-C PD si vous voulez faire des choses utiles avec PD – tout ce dont vous avez besoin est dix pages sur huit cents, et cent lignes de code.

François Zipponi
Je suis François Zipponi, éditorialiste pour le site 10-raisons.fr. J'ai commencé ma carrière de journaliste en 2004, et j'ai travaillé pour plusieurs médias français, dont le Monde et Libération. En 2016, j'ai rejoint 10-raisons.fr, un site innovant proposant des articles sous la forme « 10 raisons de... ». En tant qu'éditorialiste, je me suis engagé à fournir un contenu original et pertinent, abordant des sujets variés tels que la politique, l'économie, les sciences, l'histoire, etc. Je m'efforce de toujours traiter les sujets de façon objective et impartiale. Mes articles sont régulièrement partagés sur les réseaux sociaux et j'interviens dans des conférences et des tables rondes autour des thèmes abordés sur 10-raisons.fr.