Je continue à développer une bibliothèque de modèles complète pour les microcontrôleurs Stm32, dans le dernier article, j'ai parlé de la mise en œuvre réussie (presque) d'un appareil HID. Une autre classe USB populaire est le port COM virtuel (VCP) de la classe CDC. La popularité s'explique par le fait que l'échange de données est effectué de la même manière que le protocole série UART habituel et simple, cependant, il élimine la nécessité d'installer un convertisseur séparé dans l'appareil.
Interfaces
Un appareil CDC doit prendre en charge deux interfaces: une interface pour la gestion des paramètres de connexion et une interface pour l'échange de données.
L'interface de gestion est une extension de la classe de base de l'interface, à la différence qu'elle contient un point de terminaison (bien que, pour autant que je sache, sans avoir à prendre en charge toutes les capacités, vous pouvez vous passer du point de terminaison du tout) et un ensemble de «fonctionnalités» qui déterminent les capacités de l'appareil. Dans le cadre de la bibliothèque développée, cette interface est représentée par la classe suivante:
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;
...
Dans le cas de base, l'interface doit prendre en charge trois packages d'installation:
SET_LINE_CODING: réglage des paramètres de ligne: Baudrate, Stop Bits, Parity, Data bits. Certains projets que je ciblais ( ce projet était la principale source d'inspiration ) ignorent ce package, cependant, dans ce cas, certains terminaux (par exemple, Putty ) refusent de fonctionner.
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 , , . , - . : : "!(usb.addr == "1.1.1" || usb.addr == "1.2.1" || usb.addr == "1.1.3" || usb.addr == "1.5.1" || usb.addr == "1.5.2" || ..)" ( , ). :
. , 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. , .
J'ai testé le code écrit dans le programme Terminal v1.9b, la capture d'écran montre le résultat de l'envoi des messages "0" et "1" à l'appareil.
L'exemple de code complet peut être consulté dans le référentiel . Exemple testé sur STM32F072B-DISCO. Comme avec HID, la bibliothèque volumineuse (en particulier le gestionnaire de points de terminaison) a rendu beaucoup plus facile la mise en œuvre du support CDC, et cela a pris environ une journée complète. Ensuite, je prévois d'ajouter une autre classe de périphérique de stockage de masse, et je peux probablement m'arrêter là. Les questions et les commentaires sont les bienvenus.