Stm32 + USB on C ++ templates

* Thanks to @ grafalex for the cool tape idea





Nobody likes USB

Progressing in the study of programming microcontrollers, I realized the need to master USB, since it is undoubtedly the main interface for non-in-circuit device connection. However, it turned out that there are not many relevant materials in the open world. After analyzing various forums, I formulated the following reasons for the unpopularity of USB in projects:





  • @jaiprakash reminded that the mandatory VID value for a USB device must be bought for a lot of money.





  • The absence of the need for high-speed data transmission in most projects.





  • The high complexity of the standard itself and the development in comparison with the familiar UART interface. It is cheaper to add a ready-made USB <-> UART adapter to the device.





  • Lack of Windows / Linux driver development skills.





As a result, developers mostly prefer to use UART (through a hardware converter or, at most, by creating a VCP device, the code of which is successfully generated by CubeMX). I decided to try to understand USB at least at a basic level, continuing the line of using C ++ templates. This post describes the applied way of allocating resources (namely buffer memory and registers) between device endpoints.





Duplication problem

The main element of a program that implements a USB device is the Endpoint . The host communicates with a specific endpoint. The device must contain an endpoint with number 0, through which control takes place, requests for various descriptors at the enumeration stage, commands for assigning an address, choosing a configuration, and all other control. More details on the concept of endpoints and, in principle, basic knowledge of USB can be found in the translation of "USB in NutShell" on the microsin resource (many thanks to the guys for the work done, they did a very useful job).





Stm32F0/F1 - Packet Memory Area (PMA), . USB- , , . , K, "" K+1, ... , N. ( N - ). : 100% .





, ( ) , runtime compile-time, :





  • . . (ADDRn_TX, COUNTn_TX, ADDRn_RX, COUNTn_RX), , runtime .





  • , EPnR ( , , ).





:





  1. (0..16).





  2. (Control, Interrupt, Bulk, Isochronous).





  3. (In, Out).





  4. .





, .





:





  1. (EPnR).





  2. .





  3. ( ).





: N . , , :





  1. , , .





  2. , .





  3. "" .





:





template<typename... AllEndpoints,
  typename... BidirectionalAndBulkDoubleBufferedEndpoints,
  typename... RxEndpoints,
  typename... BulkDoubleBufferedTxEndpoints>
class EndpointsManagerBase<TypeList<AllEndpoints...>,
  TypeList<BidirectionalAndBulkDoubleBufferedEndpoints...>,
  TypeList<RxEndpoints...>,
  TypeList<BulkDoubleBufferedTxEndpoints...>>
{
  //   
  using AllEndpointsList = TypeList<AllEndpoints...>;
  ///      
  static const auto BdtSize = 8 * (EndpointEPRn<GetType_t<sizeof...(AllEndpoints) - 1, AllEndpointsList>, AllEndpointsList>::RegisterNumber + 1);
  ///      
  template<typename Endpoint>
  static constexpr uint32_t BufferOffset = BdtSize + OffsetOfBuffer<TypeIndex<Endpoint, AllEndpointsList>::value, AllEndpointsList>::value;
  ///      
  template<typename Endpoint>
  static constexpr uint32_t BdtCellOffset =
    EndpointEPRn<Endpoint, AllEndpointsList>::RegisterNumber * 8
      + (Endpoint::Type == EndpointType::Control
      || Endpoint::Type == EndpointType::ControlStatusOut
      || Endpoint::Type == EndpointType::BulkDoubleBuffered
      || Endpoint::Direction == EndpointDirection::Out
      || Endpoint::Direction == EndpointDirection::Bidirectional
        ? 0
        : 4);
  ///    USB
  static const uint32_t BdtBase = PmaBufferBase;
public:
  /// ""  
  template<typename Endpoint>
  using ExtendEndpoint = 
    typename Select<Endpoint::Type == EndpointType::Control || Endpoint::Type == EndpointType::ControlStatusOut,
    ControlEndpoint<Endpoint,
      typename EndpointEPRn<Endpoint, TypeList<AllEndpoints...>>::type,
      PmaBufferBase + BufferOffset<Endpoint>, // TxBuffer
      PmaBufferBase + BdtCellOffset<Endpoint> + 2, // TxCount
      PmaBufferBase + BufferOffset<Endpoint> + Endpoint::MaxPacketSize, // RxBuffer
      PmaBufferBase + BdtCellOffset<Endpoint> + 6>, //RxCount
    typename Select<Endpoint::Direction == EndpointDirection::Bidirectional,
    BidirectionalEndpoint<Endpoint,
      typename EndpointEPRn<Endpoint, TypeList<AllEndpoints...>>::type,
      PmaBufferBase + BufferOffset<Endpoint>, // TxBuffer
      PmaBufferBase + BdtCellOffset<Endpoint> + 2, // TxCount
      PmaBufferBase + BufferOffset<Endpoint> + Endpoint::MaxPacketSize, // RxBuffer
      PmaBufferBase + BdtCellOffset<Endpoint> + 6>, //RxCount
    ... //       
    void>::value>::value;

  static void Init()
  {
    memset(reinterpret_cast<void*>(BdtBase), 0x00, BdtSize);
    //     
    ((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<AllEndpoints>)) = BufferOffset<AllEndpoints>), ...);
    //           
    ((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BidirectionalAndBulkDoubleBufferedEndpoints> + 4)) = (BufferOffset<BidirectionalAndBulkDoubleBufferedEndpoints> + BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize)), ...);
    //  COUNTn_RX   (Rx, Out) 
    ((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<RxEndpoints> + 2)) = (RxEndpoints::MaxPacketSize <= 62
      ? (RxEndpoints::MaxPacketSize / 2) << 10
      : 0x8000 | (RxEndpoints::MaxPacketSize / 32) << 10)), ...);
    //  COUNTn_RX        
    ((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BidirectionalAndBulkDoubleBufferedEndpoints> + 6)) = (BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize <= 62
      ? (BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize / 2) << 10
      : 0x8000 | (BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize / 32) << 10)), ...);

    //    COUNTn_RX  Tx     (,    ,     )
    ((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BulkDoubleBufferedTxEndpoints> + 2)) = 0), ...);
    ((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BulkDoubleBufferedTxEndpoints> + 6)) = 0), ...);
  }
};

template<typename Endpoints>
using EndpointsManager = EndpointsManagerBase<SortedUniqueEndpoints<Endpoints>,
  typename Sample<IsBidirectionalOrBulkDoubleBufferedEndpoint, SortedUniqueEndpoints<Endpoints>>::type,
  typename Sample<IsOutEndpoint, SortedUniqueEndpoints<Endpoints>>::type,
  typename Sample<IsBulkDoubleBufferedTxEndpoint, SortedUniqueEndpoints<Endpoints>>::type>;

template<typename... Endpoints>
using EndpointsInitializer = EndpointsManagerBase<SortedUniqueEndpoints<TypeList<Endpoints...>>,
  TypeList<>,
  TypeList<>,
  TypeList<>>;
      
      



, :





  1. EndpointEPRn - , EPnR . : . , .





  2. BufferOffset - , . , N 0, ..., N-1.





  3. SortedUniqueEndpoints - , + . USB /, Device.





  4. IsBidirectionalOrBulkDoubleBufferedEndpoint, IsOutEndpoint, IsBulkDoubleBufferedTxEndpoint - .





:





using DefaultEp0 = ZeroEndpointBase<64>;
using LedsControlEpBase = OutEndpointBase<1, EndpointType::Interrupt, 64, 32>;
//         
using EpInitializer = EndpointsInitializer<DefaultEp0, LedsControlEpBase>;

// EpInitializer    .
//  ,   ,          
using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>;
using LedsControlEp = EpInitializer::ExtendEndpoint<LedsControlEpBase>;
//  ,   .
using Hid = HidInterface<0, 0, 0, 0, HidDesc, LedsControlEp>;
using Config = HidConfiguration<0, 250, false, false, Report, Hid>;
using MyDevice = Device<0x0200, DeviceClass::InterfaceSpecified, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>;
      
      



Device , :





template<
  ...
  typename _Ep0,
  typename... _Configurations>
  class DeviceBase : public _Ep0
{
  using This = DeviceBase<_Regs, _IRQNumber, _ClockCtrl, _UsbVersion, _Class, _SubClass, _Protocol, _VendorId, _ProductId, _DeviceReleaseNumber, _Ep0, _Configurations...>;
  using Endpoints = Append_t<typename _Configurations::Endpoints...>;
  using Configurations = TypeList<_Configurations...>;

  using EpBufferManager = EndpointsManager<Append_t<_Ep0, Endpoints>>;
  //  Device     
  using EpHandlers = EndpointHandlers<Append_t<This, Endpoints>>;
public:
  static void Enable()
  {
    _ClockCtrl::Enable();
    //        
    EpBufferManager::Init();
      
      



C++ :





  1. , , , ( HID-, , 2400 ).





  2. , .





  3. , , . "" USB.





  4. * . C++, , .





USB . , - - , USB, , - . , . , USB , , "" , .





This post was devoted not to the part of the library related to USB in general, but to a small but important module for distributing resources between endpoints. I would be glad to have questions and comments.





You can view the entire code (I am testing USB so far only on F072RBT6, because there is a disco with a soldered miniusb) here . I hope to defeat USB by summer at least for MK series F0 and F1. I looked at F4 - everything is cooler there (there is OTG support) and difficult.








All Articles