私はStm32マイクロコントローラー用の完全なテンプレートライブラリーの開発を続けています。前回の記事では、HIDデバイスの(ほぼ)実装の成功について話しました。もう1つの人気のあるUSBクラスは、CDCクラスの仮想COMポート(VCP)です。この人気は、データ交換が通常の単純なシリアルUARTプロトコルと同じ方法で実行されるという事実によって説明されますが、デバイスに別個のコンバーターをインストールする必要がなくなります。
インターフェイス
CDCデバイスは、接続パラメータを管理するためのインターフェイスとデータ交換のためのインターフェイスの2つのインターフェイスをサポートする必要があります。
管理インターフェイスは、インターフェイスの基本クラスの拡張ですが、1つのエンドポイントが含まれている点が異なります(ただし、私が理解している限り、すべての機能をサポートしなくても、エンドポイントがなくても実行できます)。デバイスの機能を決定する「機能」のセット。開発されたライブラリのフレームワーク内で、このインターフェイスは次のクラスで表されます。
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;
...
基本的なケースでは、インターフェイスは3つのセットアップパッケージをサポートする必要があります。
SET_LINE_CODING:ラインパラメータの設定:ボーレート、ストップビット、パリティ、データビット。私がターゲットにしていた一部のプロジェクト(このプロジェクトが主なインスピレーションの源でした)はこのパッケージを無視しますが、この場合、一部の端末(たとえば、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 , , . , - . : : "!(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. , .
ターミナルv1.9bプログラムで記述されたコードをテストしました。スクリーンショットは、メッセージ「0」と「1」をデバイスに送信した結果を示しています。
完全なサンプルコードは、リポジトリで表示できます。STM32F072B-DISCOでテストされた例。HIDと同様に、かさばるライブラリ(特にエンドポイントマネージャー)により、CDCサポートの実装がはるかに簡単になり、完了するまでに約1日かかりました。次に、別のマスストレージデバイスクラスを追加する予定ですが、おそらくそこで停止できます。質問やコメントは大歓迎です。