![Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 1 Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 1](https://www.pvsm.ru/images/2023/03/12/vsyo-pro-USB-C-vzaimodeistvie-cherez-nizkourovnevyi-protokol-PD.png)
В нашей серии статей про USB-C мы проговорили немало аспектов этого стандарта, среди которых были как хорошо известные, так и не очень, в том числе пара таких, которые только обозначились в сети. Мы до определённой степени разобрали почти всё, за исключением USB Power Delivery. Я немного описала этот протокол в статье про обеспечение питания, но тогда речь шла в основном про то, как использовать PD, просто купив правильное решение. Но для электронщика этого недостаточно, так что давайте посмотрим, удастся ли нам собрать собственную триггерную плату с PD.
Прим. пер.: Продолжение серии статей про USB-C, посвящённой всестороннему анализу этой технологии. Остальные части доступны здесь:
- Введение для электронщиков
- Типы кабелей
- Механика разъёмов
- Переходники вне стандарта
- Резисторы и E-Marker
- Обеспечение питания
- Высокоскоростные интерфейсы
- Ноутбук Framework
- Паяльник Pinecil
- Грехи производителей
- Взаимодействие через низкоуровневый протокол PD < — Вы здесь
▍ Реализация триггерной платы с PD в пределах 100 строк Python
Начнём без готового программного стека – возьмём микросхему PD PHY (интерфейс физического уровня), подключим её через I2C, будем сами переключать на ней регистры и реализуем создание пакетов. Для этого я буду использовать MicroPython, поскольку, на мой взгляд, в образовательных целях он подходит лучше всего. К тому же, я искренне люблю писать код Python для экспериментов с оборудованием и надеюсь, что вы также оцените его преимущества.
Наша цель на сегодня? Получение от БП с USB-C напряжения 9 В – коротко и ясно. По сути, мы реализуем то, для чего и создаются все триггерные платы. После достижения этой цели вы сможете создавать собственные триггеры – но более крутые, кастомизируемые и по стоимости сопоставимые с триггерной микросхемой. Ах да, для реализации этого нам потребуется менее 100 строк кода Python.
▍ Минимальные аппаратные требования
Если вы планируете следовать процессу, то вам понадобится микросхема FUSB302 и плата с поддержкой MicroPython. Я использую комбинацию RP2040+FUSB302B на собственной макетке, но ESP8266 тоже подойдёт. FUSB302B – это новая и почти идентичный функционально вариант – не уверена в чём её отличия, но для нас они точно неважны.
![Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 2 Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 2](https://www.pvsm.ru/images/2023/03/12/vsyo-pro-USB-C-vzaimodeistvie-cherez-nizkourovnevyi-protokol-PD-2.png)
Естественно, контакты СС на FUSB302 необходимо подключить к принимающей части USB-C. VCONN здесь нам не нужен, но вы можете замкнуть эту линию на вход 3,3 В. Хотя я ожидаю, что у вас есть кабель USB-C и БП с разъёмом USB-C, либо БП с несъёмным кабелем. И убедитесь, что ваш БП действительно умеет подавать напряжение выше 5 В – если на нём такая возможность не указана, тогда он может не поддерживать PD совсем, опираясь лишь на аналоговые сигналы. Неплохо бы установить между CC и землёй конденсаторы на 100 пФ – 470 пФ, но это не обязательно. Подключите 3,3 В, присоедините SDA и SCL (INT не повредит, но пока не нужен), убедитесь, что подтягивающие резисторы I2C на месте и вперёд!
Что-то ещё? Будет здорово, если вы сможете подключить LED к VBUS. Последовательного подключения резистора на 1 кОм должно быть достаточно даже для 20 В на VBUS. Я допускаю, что некоторые LED могут пострадать при 17 мА, но большинство должны вполне справиться. Если же это окажется не так, замените LED и удвойте сопротивление. Это пригодится позднее во время отладки. Хотя вам не нужно подключать VBUS порта USB-C к VBUS платы FUSB. Это может помочь позже, если вы планируете собрать более продуманное устройство. Обязательно сделайте VBUS доступным, чтобы можно было проверять его мультиметром и определять, выполняется ли код успешно.
▍ Настройка ПО
Для начала скачайте спецификации FUSB302 и USB PD версии 3.0 – нам понадобятся обе. Желательно, чтобы в ходе экспериментирования с PD они были открыты. Тем не менее для текущего процесса нам потребуется только документация на FUSB302.
![Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 3 Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 3](https://www.pvsm.ru/images/2023/03/12/vsyo-pro-USB-C-vzaimodeistvie-cherez-nizkourovnevyi-protokol-PD-3.png)
Установите на плату MicroPython и инициализируйте шину I2C – вот пример кода для RP2040, ESP8266 или ESP32. Очень рекомендую вам сначала протестировать конфигурацию и соединения в MicroPython REPL через терминал. Появится ли при выполнении i2c.scan()
адрес 0x22
? Если да, значит вы всё подключили верно! Если же нет, или инициализация проваливается, убедитесь, что ваши контакты определены правильно, и на линии I2C есть подтягивающие резисторы.
Когда появится адрес I2C, можете поэкспериментировать. Попробуйте прочитать регистр 0x01
, связанный с версией и ревизией. Можете обращаться к карте регистров FUSB302, которая начинается на странице 18 спецификации. Далее по ходу дела вы будете пролистывать эти страницы. Напомню, как читать и записывать регистры устройства I2C в MicroPython:
# Запись 0xAA и 0x55 в адрес 0x22, регистр 0x3e
i2c.writeto_mem(0x22, 0x3e, bytes([0xAA, 0x55]))
# Считывание одного байта из адреса 0x22, регистр 0x43
data = i2c.readfrom_mem(0x22, 0x43, l)
# типом 'data' будут 'bytes', при необходимости конвертируйте либо просто выведите
Готовы продолжать? Далее мы переключимся с REPL – вместо этого я предлагаю вам добавить код в документ main.py, передать его на плату, после чего выполнить при последующем тестировании. Мой рабочий поток здесь заключается в разделении сеанса tmux на отдельные вкладки для редактора кода терминала и для самого терминала/оболочки загрузки кода – для загрузки кода я использую ampy
. Для тех, кто ищет нечто с GUI могу подсказать вариант uPyCraft.
Если вы следуете этому руководству, используя CircuitPython, то, во-первых, это похвально, и я надеюсь, что вам будет достаточно просто адаптировать примеры. Во-вторых, у вас есть преимущество – вам будет проще загружать код, поскольку CircuitPython поддерживает режим устройства хранения на микроконтроллерах вроде RP2040. Однако тут есть и недостаток – вам придётся приложить дополнительные усилия к отладке, поскольку CircuitPython не позволяет изучать код в REPL после падения – мне, например, в прошлый раз не позволил, и это выглядело как фундаментальное ограничение.
▍ Настройка микросхемы
Изначально нам нужно настроить на FUSB302 несколько регистров. Для этих настроек кабель USB-C должен быть подключён – по меньшей мере большинство из них производятся именно в таком режиме. Я бы могла привести вам более изощрённый вариант настройки, но у этой статьи иная задача – нам нужно получить от подключённого БП повышенное напряжение, а для этого много не нужно.
Для начала хорошей практикой будет сбросить FUSB302 – кто знает, вдруг ваш микроконтроллер только что перезагружался. Для этого запишите 0x01
в 0x0c
(RESET). Команда 0x02
приведёт к сбрасыванию логики – это нам потребуется позже. Далее нужно вывести разные части платы из сна, что делается записью 0x0f
в регистр 0x0b
(POWER).
Запишите 0x00
в 0x06
(CONTROL0) для снятия маскировки всех прерываний, а затем 0x07
в CONTROL3 для активации повторов пакетов. Теперь мы готовы к определению полярности CC.
В FUSB302 по умолчанию есть pulldown-резисторы, и хотя их можно снять, они нам помогут, так как сейчас мы работаем с БП, у которого на линии CC присутствует pullup-резистор. Коммуникация по PD происходит только на одной из линий CC, и путём измерения, к какой из них подключён pullup-резистор источника, мы можем определить, какая именно подключена к БП. Конкретный способ сделать это я подсмотрела в коде инициализации FUSB302 из Pinecil, подключив к плате логический анализатор. Но я также видела его использование в других библиотеках для FUSB302.
![Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 4 Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 4](https://www.pvsm.ru/images/2023/03/12/vsyo-pro-USB-C-vzaimodeistvie-cherez-nizkourovnevyi-protokol-PD-4.png)
Это вполне может оказаться методом из разряда «карго-культа», учитывая, что теоретически кто-то может использовать имеющееся на FUSB302 автоматическое переключение роли порта (см. CONTROL 2) – хотя мне не удалось наладить нормальную работу этой функции. Пока что реализация Pinecil, откуда я взяла информацию, работает только в роли получателя и использует ручной метод измерения. Всё просто – подключаемся к CC1, измеряем напряжение, подключаемся к CC2, снова измеряем напряжение, а затем сравниваем. У FUSB302 есть два удобных бита, которые преобразуют напряжение CC в допустимые уровни тока на USB, и мы можем просто сравнить эти два бита между двумя считываниями.
Для начала запишите 0x07
в 0x02
(SWITCH0), так вы подключите внутренний ADC к CC1. Затем считайте 0x40
(STATUS0) и получите из него биты 1-0. Эти биты представляют уровни напряжения, и неподключенный контакт будет нулевым. Далее переключите ADC на CC2, записав 0x0b
(0b1011
) в SWITCH0, и снова считайте STATUS0. Если значение CC1 окажется больше значения CC2, значит БП подключён к CC1, и наоборот. Если и СС1, и CC2 показывают нуль, значит БП не обнаружен – здесь вы добавляете в код особый случай «ничего не обнаружено», а затем, вероятно, ожидаете подключения БП. В качестве альтернативы и домашнего задания можете попробовать заставить работать функционал переключения роли порта.
▍ Запуск передачи и её последствия
Теперь нам известен нужный контакт CC – можно приступать к делу. Предположим, это контакт СС1. Мы активируем на нём и получение, и передачу, а также автоматические ответы GoodCRC. Запишите 0x25
(0b100101
) в регистр 0x03
(SWITCH1) – биты 0-1 изменятся в зависимости от того, к какому CC вы хотите подключить передатчик: бит 2 активирует автоматические ответы GoodCRC, а биты 5-6 указывают, что мы собираемся взаимодействовать через PD 2.0. В действительности мы будем использовать PD 3.0, но спецификация FUSB302 не обновлялась информацией о поддержке этой ревизии, хотя, как мне сказали, функционально они полностью совместимы. Далее запишите 0x07
в регистр SWITCH0 – это подключит блок измерения к контакту CC1.
Момент с GoodCRC в некотором смысле представляет «точку невозврата». Передача сообщений GoodCRC означает, что в независимости от того, взаимодействуете вы с БП или с устройством, встречная сторона, по сути, получает на любые свои отправки ответы «сообщение подтверждено». Если коротко, то отправка ответов GoodCRC подразумевает передачу разумных сигналов на устройство по другую сторону кабеля USB-C. Хотя некоторые сообщения USB-C также требуют после своего получения разумного ответа в рамках определённого временного промежутка – если вы отправляете сообщение GoodCRC автоматически, но затем не присылаете ожидаемый ответ, на другой стороне может возникнуть реакция «что-то пошло не так».
![Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 5 Всё про USB-C: взаимодействие через низкоуровневый протокол PD - 5](https://www.pvsm.ru/images/2023/03/12/vsyo-pro-USB-C-vzaimodeistvie-cherez-nizkourovnevyi-protokol-PD-5.png)
Перевод: Бит, используемый для создания подтверждающих пакетов GoodCRC. Он соответствует биту Port Data Role в заголовке сообщения.
Для SOP:
1: SRS
0: SNK
Не использовать
1: запускает передатчик автоматически при получении сообщения с GoodCRC и автоматически отправляет подтверждающий пакет GoodCRC обратно соответствующему SOP
0: функционал отключён
1: активировать драйвер передачи BMC на контакте CC2
1: активировать драйвер передачи BMC на контакте CC1
Например, блок питания с USB-C будет автоматически отправлять список своих возможностей – профили питания, то есть опции, известные нам как «5 В @ 3 A», «12 В @ 2,25 А» и так далее. Если вы подтверждаете их получение автоматическим ответом GoodCRC, то у вас есть 500 мс на отправку ответа с указанием профиля, который нужно использовать – даже если вы планируете остаться на уровне 5 В. Спецификация USB-C требует, чтобы в случае отсутствия ответа с указанием нужного профиля БП продолжал отключать и включать VBUS – большинство БП этому требованию следуют. Если вы отправите GoodCRC на оповещение о подключении, но не ответите сообщением с предпочтительным профилем, а ваше устройство запитано через USB-C VBUS, то БП отправит его в бесконечный цикл зарядки. Решение здесь простое – отвечать сразу при получении оповещения. Если позже вы захотите запросить другой профиль, то всегда сможете это сделать.
Тем не менее, прежде чем мы начнём получать сообщения, нам следует провести небольшую чистку. Запишите 0x40
в 0x06
(CTRL0) для очистки буфера TX, 0x04
в 0x07
для очистки буфера RX и 0x02
в 0x0c
(RESET) для сброса внутренней логики PD на FUSB302.
Теперь мы готовы – сообщения будут поступать в буфер получения FUSB302, и мы сможем их считывать.
▍ Получение сообщений
БП с PD будет автоматически отправлять сообщение с указанием своих возможностей, и делать он это будет несколько раз после подключения питания – пока мы не подтвердим получение сообщения ответом GoodCRC. Поскольку ваша плата предположительно запитана от другого порта USB-C, вы сможете получить сообщение изнутри REPL и изучить его – однако нужно помнить, что в случае задержки ответа у БП истечёт время ожидания, и ответить интерактивно вы не сможете. Иными словами, вам нужно создавать ответы автоматически, что достаточно просто реализовать.
Как проверить, есть ли в буфере что-либо? Считайте регистр 0x42
(STATUS1) и проверьте биты 5-4: если в буфере что-то есть, бит 5 будет нулевым. Далее вы можете считать байты из буфера – с помощью чтения блока по адресу 0x43
. Буфер может содержать несколько сообщений одновременно, хотя в простом сценарии источника питания USB-C PD можете ожидать, что в нём оно будет одно.
Сейчас вам не нужно внимательно прочитывать байты одного сообщения – для проверки работоспособности можно просто считать весь буфер, длина которого составляет 80 байт. Далее мы так и сделаем.
>>> b = i2c.readfrom_mem(0x22, 0x43, 80)
>>> b
b'xe0xa1a,x91x01x08,xd1x02x00x13xc1x03x00xdcxb0x04x00xa5@x06x00<!xdcxc0Hxc6xe7xc6x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
▍ Контакт установлен!
Мы получили наше первое сообщение. И для этого нам потребовалось последовать относительно простому порядку инициализации – это ненамного сложнее, чем играться с HD44780 LCD. И вы можете определённо прошить этот код даже на ATTiny. Хочу поблагодарить Ralim и Clara Hobbs за то, что заложили основы для всех этих возможностей. Описываемые мной команды созданы ими – я не смогла выяснить всю последовательность сама, поэтому многие из фактически необходимых для работы команд были взяты из их стеков.
В следующей статье мы спарсим это сообщение и создадим на него правильный ответ. Должна извиниться за то, что останавливаюсь на самом интересном месте, но подобающий разбор парсинга сообщений потребует некоторого времени. Тем не менее вам не нужно больше производить какую-либо конфигурацию FUSB302 – вы уже готовы ответить сообщением PD, как только сможете создать подходящий ответ.
Испытываете нетерпение? Хотите проделать это самостоятельно в качестве домашнего задания? Вот мой код для парсинга подобных сообщений, вот код IronOS для того же самого, а вот перехват коммуникации по I2C, в которой Pinecil согласовывает профиль с повышенным напряжением. Для всех остальных мы закончим эту тему через неделю.
Автор: Дмитрий Брайт