[C++, Программирование микроконтроллеров] Stm32 + USB на шаблонах C++. Продолжение. Делаем CDC
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.ИнтерфейсыУстройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.Интерфейс управления представляет собой расширение базового класса интерфейса с тем отличием, что содержит одну конечную точку (хотя, насколько я понял, без необходимости поддержки всех возможностей можно обойтись вообще без конечной точки) и набор "функциональностей", определяющих возможности устройства. В рамках разрабатываемой библиотеки данный интерфейс представлен следующим классом:
template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint, typename... _Functionals>
class CdcCommInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>
{
using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>;
static LineCoding _lineCoding;
...
В базовом случае интерфейс должен поддерживать три управляющих (setup) пакета:
- SET_LINE_CODING: установка параметров линии: Baudrate, Stop Bits, Parity, Data bits. Некоторые проекты, на которые я ориентировался (основным источников вдохновения стал этотпроект), игнорируют данный пакет, однако в этом случае некоторые терминалы (например, Putty), отказываются работать.
- GET_LINE_CODING: обратная операция, в ответ на эту команду устройство должно вернуть текущие параметры.
- SET_CONTROL_LINE_STATE: установка состояния линии (RTS, DTR и т.д.).
Код обработчика setup-пакетов:
switch (static_cast<CdcRequest>(setup->Request))
{
case CdcRequest::SetLineCoding:
if(setup->Length == 7)
{
// Wait line coding
_Ep0::SetOutDataTransferCallback([]{
memcpy(&_lineCoding, reinterpret_cast<const void*>(_Ep0::RxBuffer), 7);
_Ep0::ResetOutDataTransferCallback();
_Ep0::SendZLP();
});
_Ep0::SetRxStatus(EndpointStatus::Valid);
}
break;
case CdcRequest::GetLineCoding:
_Ep0::SendData(&_lineCoding, sizeof(LineCoding));
break;
case CdcRequest::SetControlLineState:
_Ep0::SendZLP();
break;
default:
break;
}
Ключевой момент нумерации, а именно формирование дескрипторов, выполнен по уже привычной схеме раскрытия variadic-ов, что позволяет избавиться от зависимости классов в иерархии:
static uint16_t FillDescriptor(InterfaceDescriptor* descriptor)
{
uint16_t totalLength = sizeof(InterfaceDescriptor);
*descriptor = InterfaceDescriptor {
.Number = _Number,
.AlternateSetting = _AlternateSetting,
.EndpointsCount = Base::EndpointsCount,
.Class = DeviceAndInterfaceClass::Comm,
.SubClass = _SubClass,
.Protocol = _Protocol
};
uint8_t* functionalDescriptors = reinterpret_cast<uint8_t*>(descriptor);
((totalLength += _Functionals::FillDescriptor(&functionalDescriptors[totalLength])), ...);
EndpointDescriptor* endpointDescriptors = reinterpret_cast<EndpointDescriptor*>(&functionalDescriptors[totalLength]);
totalLength += _Endpoint::FillDescriptor(endpointDescriptors);
return totalLength;
}
Второй интерфейс, предназначенный для непосредственно обмена данными, абсолютно примитивный, он не должен поддерживать управляющих сообщений, а является просто контейнером для двух конечный точек (точнее одной двунаправленной). Объявление класса:
template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint>
class CdcDataInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>
{
using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>;
...
Поскольку мои познания в CDC-устройствах весьма небольшие, из просмотренных примеров я сделал вывод, что управляющий интерфейс почти всегда одинаковый и содержит 4 функциональности: Header, CallManagement, ACM, Union, поэтому добавил упрощенный шаблон интерфейса:
template<uint8_t _Number, typename _Ep0, typename _Endpoint>
using DefaultCdcCommInterface = CdcCommInterface<_Number, 0, 0x02, 0x01, _Ep0, _Endpoint, HeaderFunctional, CallManagementFunctional, AcmFunctional, UnionFunctional>;
Применение разработанных классовДля использования разработанных классов достаточно объявить две конечные точки (Interrupt для первого интерфейса и двунаправленную Bulk для второго), объявить оба интерфейса, конфигурацию с ними и, наконец, инстанцировать класс устройства:
using CdcCommEndpointBase = InEndpointBase<1, EndpointType::Interrupt, 8, 0xff>;
using CdcDataEndpointBase = BidirectionalEndpointBase<2, EndpointType::Bulk, 32, 0>;
using EpInitializer = EndpointsInitializer<DefaultEp0, CdcCommEndpointBase, CdcDataEndpointBase>;
using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>;
using CdcCommEndpoint = EpInitializer::ExtendEndpoint<CdcCommEndpointBase>;
using CdcDataEndpoint = EpInitializer::ExtendEndpoint<CdcDataEndpointBase>;
using CdcComm = DefaultCdcCommInterface<0, Ep0, CdcCommEndpoint>;
using CdcData = CdcDataInterface<1, 0, 0, 0, Ep0, CdcDataEndpoint>;
using Config = Configuration<0, 250, false, false, CdcComm, CdcData>;
using MyDevice = Device<0x0200, DeviceAndInterfaceClass::Comm, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>;
Непосредственно логика заключается лишь в обработке входящих пакетов, что умещается в одну функцию (в качестве примера управляю светодиодом и выдаю сообщение):
template<>
void CdcDataEndpoint::HandleRx()
{
uint8_t* data = reinterpret_cast<uint8_t*>(CdcDataEndpoint::RxBuffer);
uint8_t size = CdcDataEndpoint::RxBufferCount::Get();
if(size > 0)
{
if(data[0] == '0')
{
Led::Clear();
CdcDataEndpoint::SendData("LED is turn off\r\n", 17);
}
if(data[0] == '1')
{
Led::Set();
CdcDataEndpoint::SendData("LED is turn on\r\n", 16);
}
}
CdcDataEndpoint::SetRxStatus(EndpointStatus::Valid);
}
Отладка и тестированиеНаписать код правильно с первого раза практически невозможно, поэтому очень полезным оказалось все-таки разобраться с инструментами перехвата USB-пакетов, поэтому кратко опишу особенности и проблемы, с которыми столкнулся лично я.Так и не удалось применить логический анализатор, он просто ничего не показывает. Полагаю, что дело в том, что это самый дешевый клон Seale Logic и если бы был в наличи нормальный аппарат, то все бы получилось. Главное преимущество логического анализатора заключается в том, что он позволяет отслеживать обмен данными еще в процессе нумерации, в то время как программы на стороне хоста показывают пакеты только для тех устройств, которые эту нумерацию успешно прошли.WireShark с установленным UsbPcap оказался весьма удобным, он нормально парсит все данные, так что поиск ошибок значительно упрощается. Главное, что нужно сделать - правильно установить фильтры. Не нашел ничего лучше, кроме выполнить следующие две операции:Сначала отфильтровать по заведомо известному значению. Например, по значению PID, которое присутствует в ответе устройства на запрос GET_DEVICE_DESCRIPTOR. Фильтр: "usb.idProduct == 0x5711". Это позволит быстро определить адрес устройства.
Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: "usb.addr contains "1.19"".
Однако стоит заметить, что UsbPcap может доставить некоторые трудности, под катом опишу ситуацию, в которую недавно попал и потратил кучу времени и нервов.Проблема с usbpcapДля большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.Однажды Windows просто не загрузилась с синим экраном "inaccessible boot device". Потратил целые выходные, восстановить так и не смог, пришлось все переустановить. Через некоторое время та же проблема и снова потраченные на переустановку выходные. Спустя пару дней система опять не грузится, начал вспоминать и анализировать, что я такого делал. Выяснил, что проблема возникала после установки как раз WireShark с usbpcap. На одном из форумов наткнулся на сообщение от пользователя, который жаловался на проблему с мышкой/клавиатурой после установки usbpcap. Снес через LiveCD драйвер и Windows запустилась. Не уверен на 100%, но предположение такое: при запуске компьютера Windows начинается загружаться, подгружает драйвера usbpcap, тот блокирует USB, система дальше грузиться не может и падает в BSOD. Очень неочевидное поведение, жаль потраченного времени.Тестировал написанный код в программе Terminal v1.9b, на скриншоте приведен результат отправки на устройство сообщений "0" и "1".
Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, C++] Как компилятор C++ находит правельную функцию (перевод)
- [Программирование, C++] Худшие места в C++ для написания кода
- [Программирование микроконтроллеров, Производство и разработка электроники] HK32F030C8T6 全功能克隆(полный функциональный клон) STM32F030C8T6
- [Информационная безопасность, Программирование, C++] Грехи C++ (перевод)
- [Open source, Программирование, C++] Развитие проекта arataga: пара рефакторингов по результатам натурных испытаний
- [C++, GTK+] Gtk, OpenGL и все-все-все
- [C++, C] На собеседовании: Почему не пишут ядро ОС на C++? Немного про Fuchsia и Zircon
- [Гаджеты, Компьютерное железо, Ноутбуки, Периферия] В USB Type-C версии 2.1 добавят поддержку мощности до 240 Ватт
- [C++, Визуализация данных, Разработка под Windows] Ещё один модуль рисования графиков
- [Информационная безопасность, C++] XSEC: как изучить Windows Access Control за два часа
Теги для поиска: #_c++, #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_c++, #_metaprogrammirovanie (метапрограммирование), #_stm32, #_usb, #_c++, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:36
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.ИнтерфейсыУстройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.Интерфейс управления представляет собой расширение базового класса интерфейса с тем отличием, что содержит одну конечную точку (хотя, насколько я понял, без необходимости поддержки всех возможностей можно обойтись вообще без конечной точки) и набор "функциональностей", определяющих возможности устройства. В рамках разрабатываемой библиотеки данный интерфейс представлен следующим классом: template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint, typename... _Functionals>
class CdcCommInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint> { using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>; static LineCoding _lineCoding; ...
switch (static_cast<CdcRequest>(setup->Request))
{ case CdcRequest::SetLineCoding: if(setup->Length == 7) { // Wait line coding _Ep0::SetOutDataTransferCallback([]{ memcpy(&_lineCoding, reinterpret_cast<const void*>(_Ep0::RxBuffer), 7); _Ep0::ResetOutDataTransferCallback(); _Ep0::SendZLP(); }); _Ep0::SetRxStatus(EndpointStatus::Valid); } break; case CdcRequest::GetLineCoding: _Ep0::SendData(&_lineCoding, sizeof(LineCoding)); break; case CdcRequest::SetControlLineState: _Ep0::SendZLP(); break; default: break; } static uint16_t FillDescriptor(InterfaceDescriptor* descriptor)
{ uint16_t totalLength = sizeof(InterfaceDescriptor); *descriptor = InterfaceDescriptor { .Number = _Number, .AlternateSetting = _AlternateSetting, .EndpointsCount = Base::EndpointsCount, .Class = DeviceAndInterfaceClass::Comm, .SubClass = _SubClass, .Protocol = _Protocol }; uint8_t* functionalDescriptors = reinterpret_cast<uint8_t*>(descriptor); ((totalLength += _Functionals::FillDescriptor(&functionalDescriptors[totalLength])), ...); EndpointDescriptor* endpointDescriptors = reinterpret_cast<EndpointDescriptor*>(&functionalDescriptors[totalLength]); totalLength += _Endpoint::FillDescriptor(endpointDescriptors); return totalLength; } template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint>
class CdcDataInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint> { using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>; ... template<uint8_t _Number, typename _Ep0, typename _Endpoint>
using DefaultCdcCommInterface = CdcCommInterface<_Number, 0, 0x02, 0x01, _Ep0, _Endpoint, HeaderFunctional, CallManagementFunctional, AcmFunctional, UnionFunctional>; using CdcCommEndpointBase = InEndpointBase<1, EndpointType::Interrupt, 8, 0xff>;
using CdcDataEndpointBase = BidirectionalEndpointBase<2, EndpointType::Bulk, 32, 0>; using EpInitializer = EndpointsInitializer<DefaultEp0, CdcCommEndpointBase, CdcDataEndpointBase>; using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>; using CdcCommEndpoint = EpInitializer::ExtendEndpoint<CdcCommEndpointBase>; using CdcDataEndpoint = EpInitializer::ExtendEndpoint<CdcDataEndpointBase>; using CdcComm = DefaultCdcCommInterface<0, Ep0, CdcCommEndpoint>; using CdcData = CdcDataInterface<1, 0, 0, 0, Ep0, CdcDataEndpoint>; using Config = Configuration<0, 250, false, false, CdcComm, CdcData>; using MyDevice = Device<0x0200, DeviceAndInterfaceClass::Comm, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>; template<>
void CdcDataEndpoint::HandleRx() { uint8_t* data = reinterpret_cast<uint8_t*>(CdcDataEndpoint::RxBuffer); uint8_t size = CdcDataEndpoint::RxBufferCount::Get(); if(size > 0) { if(data[0] == '0') { Led::Clear(); CdcDataEndpoint::SendData("LED is turn off\r\n", 17); } if(data[0] == '1') { Led::Set(); CdcDataEndpoint::SendData("LED is turn on\r\n", 16); } } CdcDataEndpoint::SetRxStatus(EndpointStatus::Valid); } Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: "usb.addr contains "1.19"". Однако стоит заметить, что UsbPcap может доставить некоторые трудности, под катом опишу ситуацию, в которую недавно попал и потратил кучу времени и нервов.Проблема с usbpcapДля большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.Однажды Windows просто не загрузилась с синим экраном "inaccessible boot device". Потратил целые выходные, восстановить так и не смог, пришлось все переустановить. Через некоторое время та же проблема и снова потраченные на переустановку выходные. Спустя пару дней система опять не грузится, начал вспоминать и анализировать, что я такого делал. Выяснил, что проблема возникала после установки как раз WireShark с usbpcap. На одном из форумов наткнулся на сообщение от пользователя, который жаловался на проблему с мышкой/клавиатурой после установки usbpcap. Снес через LiveCD драйвер и Windows запустилась. Не уверен на 100%, но предположение такое: при запуске компьютера Windows начинается загружаться, подгружает драйвера usbpcap, тот блокирует USB, система дальше грузиться не может и падает в BSOD. Очень неочевидное поведение, жаль потраченного времени.Тестировал написанный код в программе Terminal v1.9b, на скриншоте приведен результат отправки на устройство сообщений "0" и "1". Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания. =========== Источник: habr.com =========== Похожие новости:
Программирование микроконтроллеров ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:36
Часовой пояс: UTC + 5