Stm32 + USB on C ++ templates. Continuation. Making CDC

I continue to develop a completely template library for Stm32 microcontrollers, in the last article I talked about the successful (almost) implementation of an HID device. Another popular USB class is Virtual COM Port (VCP) from the CDC class. The popularity is explained by the fact that data exchange is carried out in the same way as the usual and simple serial UART protocol, however, it removes the need to install a separate converter in the device.





Interfaces

A CDC class device must support two interfaces: an interface for managing connection parameters and an interface for data exchange.





The management interface is an extension of the base interface class with the difference that it contains one endpoint (although, as far as I understand, without the need to support all the capabilities, you can do without the endpoint at all) and a set of "functionalities" that determine the capabilities of the device. Within the framework of the developed library, this interface is represented by the following class:





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;
  ...
      
      



In the basic case, the interface should support three setup packages:





  • SET_LINE_CODING: setting line parameters: Baudrate, Stop Bits, Parity, Data bits. Some projects that I was targeting ( this project was the main source of inspiration ) ignore this package, however, in this case, some terminals (for example, Putty ) refuse to work.





  • 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. , .





I tested the written code in the Terminal v1.9b program, the screenshot shows the result of sending messages "0" and "1" to the device.





The complete example code can be viewed in the repository . Example tested on STM32F072B-DISCO. As with HID, the bulky library (especially the endpoint manager) made it much easier to implement CDC support, and it took about a full day to complete. Next, I plan to add another Mass Storage Device class, and I can probably stop there. Questions and comments are welcome.








All Articles