После прочтения статьи Программируем и отлаживаем STM32 через USB Type-C порт, не нарушая спецификации USB у меня сразу появилась идея, как сделать правильнее и удобнее (втыкай кабель как хочешь, рабочий USB 2.0 порт). Получился вот такой адаптер для J-Link (JTAG 10 pin):
Разберём подробнее, что за девайс на горстке компонентов и как оно пашет-то?
Общая концепция
Считай, всё объясняет следующая блок-схема:
Основные моменты, которых я придерживался при разработке этого адаптера:
Отладчик J-Link (или любой другой с JTAG 10-pin) должен быть внешним, поэтому никаких отладчиков на плате адаптера не предусмотрено;
Возможность как запитать, так и иметь полностью рабочий USB 2.0 порт от целевой платы - просто прокидываем между двумя USB Type-C Data лейн;
На всякий случай (хоть на плате адаптера и нет USB PD микросхемы) запитывать сам адаптер от LDO, поддерживающего входное напряжение до 20 В;
Автоматически определять подключение кабеля и его ориентацию в зависимости от подключения кабеля и целевой платы, что обязывает использовать полноценные USB кабели со всеми SBU и CC линиями;
Автоматически перебрасывать SWD линии в зависимости от ориентации кабеля, что обязывает устанавливать правильные подтягивающие резисторы на целевой плате.
Примечание: подробнее о последнем пункте - о нём узнал случайно в топике Подтяжка выводов JTAG. Надо ли оно? на форуме electronix. Для меня было небольшим открытием, что я, получается, всегда неверно ("х.х. и в продакшн") подтягивал SWD интерфейс на платах (CLK и IO через 10 кОм к VCC), но это, собственно, и привело к мысли, что таким образом я могу отличать линии CLK и IO в SWD без проблем.
Очевидные минусы, от которых, считай, нереально избавиться без костылей:
Отсутствие линии сброса целевой платы - увы, но без прямого нарушения спецификации USB уже не протянуть ещё один сигнал по USB 3.x кабелю, поэтому если на целевом МК будет залочён отладочный интерфейс, то придётся нервозно жать на кнопку сброса \ разбирать и подпаиваться к нему;
Нет возможности корректно замерять уровень сигналов целевого МК и, соответственно, иметь полноценный отладчик на 1,8\2,5\3,3 В уровни (можно, конечно, исхитриться и перед подключением SWD замерять уровень "1" на линиях SBU и выставлять его на согласователе уровней SWD линий, но это ненадёжно).
Схемотехника
Сделать хотелось из того, что давно валяется в столе и применится фиг знает когда, если и применится, а если что и нужно новое, оно должно быть копеечным и легко доставаемым... поэтому определяем основные компоненты:
STM32F030F4P6 - основной МК, самый простой и мелкий, подойдёт любой из STM32F04xFxP6, STM32F07xFxP6, который есть в наличии.
TS3USB221RSER - мультиплексор USB 2.0, применим его для переключения SWD линий, нам-то какая разница, каким аналоговым ключом переключать их ;)
ME6203A33M3G - мелкий китайский LDO на +3,3 В и входным аж до + 40 В.
Остальное уже совершенно некритично, и можно применять то, что валяется в столе или можно сдуть с плат-доноров.
Получившаяся схема:
Так-то тут опять же никаких изысков или хитростей... ставим мультиплексор на переключение линий:
Да, не забываем подключить 4 канала АЦП на линии CC и SBU:
Остальное всё по привычным, стандартным, проверенным решениям, поэтому...
Прошивка
Написана чисто на LL и, считай, просто на постоянном опросе в бесконечном цикле работает (DMA на ADC по таймеру уже чисто по привычке скорее прикрутил, тут это было совершенно не обязательно).
Main функция выглядит максимально лаконично:
int main(void)
{
SysInit_Clock();
SysTick_Config(SystemCoreClock / 1000U); // 1ms
SysInit_GPIO();
SysInit_DMA();
SysInit_ADC();
SysInit_TIM();
SysView_Init();
SysView_PrintInfo();
swd.state = SWD_STATE_START;
swd_UpdateState(SWD_STATE_IDLE);
LL_TIM_EnableCounter(TIM1);
LL_ADC_REG_StartConversion(ADC1);
SysTick_Delay_ms(100);
for (;;)
{
SysTick_Delay_ms(1);
status_Routine();
swd_Routine();
}
}
Ну а детектирование подключения кабеля и его ориентации происходит по следующему алгоритму:
Детектируется подключение кабеля по заданному порогу на CC линиях
Детектируется ориентация подключения кабеля по заданному порогу на SBU линиях
Решение в лоб по приведённой выше диаграмме работы алгоритма в функции swd_Routine:
void swd_Routine()
{
if (adc_ready)
{
adc_ready = 0;
if (((swd.cc_mv[0] > STATE_CC_CNC_TH_MV) && (swd.cc_mv[1] > STATE_CC_CNC_TH_MV)))
{
swd_UpdateState(SWD_STATE_IDLE);
}
else
{
for (uint8_t i = 0; i < 2; i++)
{
if ((swd.cc_mv[0] < STATE_CC_CC_TH_MV) || (swd.cc_mv[1] < STATE_CC_CC_TH_MV))
{
if ((swd.state != SWD_STATE_SWD_POS1) & (swd.state != SWD_STATE_SWD_POS2))
{
if (swd.state == SWD_STATE_USB_CABLE_CONNECTED)
{
if (swd.sbu_mv[i] < STATE_SBU_CC_TH_MV)
{
if (i)
{
swd_UpdateState(SWD_STATE_SWD_POS1);
break;
}
else
{
swd_UpdateState(SWD_STATE_SWD_POS2);
break;
}
}
}
else
{
swd_UpdateState(SWD_STATE_USB_CABLE_CONNECTED);
}
}
}
}
}
}
}
Остальное можно глянуть в репе, засорять статью быдлокодом не буду.
Кабели USB Type-C бывают разные
Есть полноценные кабели, в которых линии CC и SBU соединены между разъёмами, а есть кабели, в которых только Data лейн и питание прокинуты, а на CC резисторы подтягивающие около разъёмов припаяны.
Для теста я, собственно, сначала собрал свой DIY USB Type-C кабель из двух плат с Али и силиконовых проводов, получилось вот так:
И, собственно, все кабели, что я нашёл дома, а также 2 дополнительных, которые специально для этого заказал с Али:
Самопальный кабель USB 2.0, USB 3.1 с внешнего SSD и короткий USB 3.1 Gen2 c Али показали полную работоспособность, а вот USB 3.1 для передачи видео с монитора Dell, другой USB 3.1 Gen2 с Али уже не работали.
Как работает полноценный кабель:
Как определяется неполноценный кабель (видно, что уже детектировано подключение USB без целевой платы):
Заключение
Ну, собственно, и всё. Больше нечего сказать про такой проект "на коленке из *овна и мк". Скажу только, что было интересно получить новый опыт, и теперь в раздумьях, применять ли такой подход на работе или ещё где (основной минус с отсутствием сброса больше всего напрягает).
Ссылки
USB-Type-C-SWD-Adapter - репозиторий проекта