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

The last time showed one way of allocating resources between endpoints, namely EPnR registers, memory buffers for descriptors and for the buffers themselves. I propose to continue what we started and consider the written library using the example of creating a simple HID device that allows you to control an LED.





Interrupt sharing

In addition to the resources indicated earlier, the endpoints also share a single interrupt. Accordingly, the general (main) handler must correctly transfer control to the interrupt handler of the desired endpoint. The endpoint number that the host is accessing is recorded in the EP_ID bits of the ISTR register. Following the implementation line of a fully templated library, I got the following class:





using EpRequestHandler = std::add_pointer_t<void()>;
template<typename...>
class EndpointHandlersBase;
template<typename... Endpoints, int8_t... Indexes>
class EndpointHandlersBase<TypeList<Endpoints...>, Int8_tArray<Indexes...>>
{
public:
  //    
  static constexpr EpRequestHandler _handlers[] = {Endpoints::Handler...};
  //  
  static constexpr int8_t _handlersIndexes[] = {Indexes...};
public:
  inline static void Handle(uint8_t number, EndpointDirection direction)
  {
    _handlers[_handlersIndexes[2 * number + (direction == EndpointDirection::Out ? 1 : 0)]]();
  }
};
      
      



The key element of the class is the _handlersIndexes array , which maps the endpoint number and direction to a particular handler. To get this array, a special class is implemented:





template<int8_t Index, typename Endpoints>
class EndpointHandlersIndexes
{
  //      .
  using Predicate = Select<Index % 2 == 0, IsTxOrBidirectionalEndpointWithNumber<Index / 2>, IsRxOrBidirectionalEndpointWithNumber<Index / 2>>::value;
  static const int8_t EndpointIndex = Search<Predicate::template type, Endpoints>::value;
public:
  //           -1   .
  using type = typename Int8_tArray_InsertBack<typename EndpointHandlersIndexes<Index - 1, Endpoints>::type, EndpointIndex>::type;
};
template<typename Endpoints>
class EndpointHandlersIndexes<-1, Endpoints>
{
public:
  using type = Int8_tArray<>;
};
      
      



By the way, this implementation implies a recommendation to declare endpoints with numbers in order, because the size of the array of handler indices is equal to twice the maximum endpoint number.





Endpoint class

- : , :





template <uint8_t _Number, EndpointDirection _Direction, EndpointType _Type, uint16_t _MaxPacketSize, uint8_t _Interval>
class EndpointBase
...
      
      



, ( , ). - / (, Interrupt , Bulk - ) :





template <typename _Base, typename _Reg>
class Endpoint : public _Base
...
template<typename _Base, typename _Reg, uint32_t _TxBufferAddress, uint32_t _TxCountRegAddress, uint32_t _RxBufferAddress, uint32_t _RxCountRegAddress>
class BidirectionalEndpoint : public Endpoint<_Base, _Reg>
...
template<typename _Base, typename _Reg, uint32_t _Buffer0Address, uint32_t _Count0RegAddress, uint32_t _Buffer1Address, uint32_t _Count1RegAddress>
class BulkDoubleBufferedEndpoint : public Endpoint<_Base, _Reg>
      
      



: ( EPnR), , ( CTR_TX/RX, TX/RX_STATUS), .





, , ( ) , ( , variadic-, ):





template <uint8_t _Number, uint8_t _AlternateSetting = 0, uint8_t _Class = 0, uint8_t _SubClass = 0, uint8_t _Protocol = 0, typename... _Endpoints>
class Interface
{
public:
  using Endpoints = Zhele::TemplateUtils::TypeList<_Endpoints...>;
  static const uint8_t EndpointsCount = ((_Endpoints::Direction == EndpointDirection::Bidirectional ? 2 : 1) + ...);

  static void Reset()
  {
    (_Endpoints::Reset(), ...);
  }

  static uint16_t FillDescriptor(InterfaceDescriptor* descriptor)
  {
    uint16_t totalLength = sizeof(InterfaceDescriptor);

    *descriptor = InterfaceDescriptor {
      .Number = _Number,
      .AlternateSetting = _AlternateSetting,
      .EndpointsCount = EndpointsCount,
      .Class = _Class,
      .SubClass = _SubClass,
      .Protocol = _Protocol
    };
    
    EndpointDescriptor* endpointsDescriptors = reinterpret_cast<EndpointDescriptor*>(++descriptor);
    totalLength += (_Endpoints::FillDescriptor(endpointsDescriptors++) + ...);

    return totalLength;
  }
};
      
      



, . USB , /, . , - .





template <uint8_t _Number, uint8_t _MaxPower, bool _RemoteWakeup = false, bool _SelfPowered = false, typename... _Interfaces>
class Configuration
{
public:
  using Endpoints = Zhele::TemplateUtils::Append_t<typename _Interfaces::Endpoints...>;
  static void Reset()
  {
    (_Interfaces::Reset(), ...);
  }
...
      
      



, . , - ( , ) .





template<
  typename _Regs,
  IRQn_Type _IRQNumber,
  typename _ClockCtrl, 
  uint16_t _UsbVersion,
  DeviceClass _Class,
  uint8_t _SubClass,
  uint8_t _Protocol,
  uint16_t _VendorId,
  uint16_t _ProductId,
  uint16_t _DeviceReleaseNumber,
  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...>;

  // Replace Ep0 with this for correct handler register.
  using EpBufferManager = EndpointsManager<Append_t<_Ep0, Endpoints>>;
  using EpHandlers = EndpointHandlers<Append_t<This, Endpoints>>;
...
      
      



, :





static void CommonHandler()
{
  if(_Regs()->ISTR & USB_ISTR_RESET)
  {
    Reset();
  }
  if (_Regs()->ISTR & USB_ISTR_CTR)
  {
    uint8_t endpoint = _Regs()->ISTR & USB_ISTR_EP_ID;
    EpHandlers::Handle(endpoint, ((_Regs()->ISTR & USB_ISTR_DIR) != 0 ? EndpointDirection::Out : EndpointDirection::In));
  }
  NVIC_ClearPendingIRQ(_IRQNumber);
}
      
      



, Device , , , :





static void Handler()
{
  if(_Ep0::Reg::Get() & USB_EP_CTR_RX)
  {
    _Ep0::ClearCtrRx();
    if(_Ep0::Reg::Get() & USB_EP_SETUP)
    {
      SetupPacket* setup = reinterpret_cast<SetupPacket*>(_Ep0::RxBuffer);
      switch (setup->Request) {
      case StandartRequestCode::GetStatus: {
        uint16_t status = 0;
        _Ep0::Writer::SendData(&status, sizeof(status));
        break;
      }
      case StandartRequestCode::SetAddress: {
        TempAddressStorage = setup->Value;
        _Ep0::Writer::SendData(0);
        break;
      }
      case StandartRequestCode::GetDescriptor: {
        switch (static_cast<GetDescriptorParameter>(setup->Value)) {
        case GetDescriptorParameter::DeviceDescriptor: {
          DeviceDescriptor tempDeviceDescriptor;
          FillDescriptor(reinterpret_cast<DeviceDescriptor*>(&tempDeviceDescriptor));
          _Ep0::Writer::SendData(&tempDeviceDescriptor, setup->Length < sizeof(DeviceDescriptor) ? setup->Length : sizeof(DeviceDescriptor));
          break;
        }
        case GetDescriptorParameter::ConfigurationDescriptor: {
          uint8_t temp[64];
          uint16_t size = GetType<0, Configurations>::type::FillDescriptor(reinterpret_cast<ConfigurationDescriptor*>(&temp[0]));
          _Ep0::Writer::SendData(reinterpret_cast<ConfigurationDescriptor*>(&temp[0]), setup->Length < size ? setup->Length : size);
          break;
        }
        case GetDescriptorParameter::HidReportDescriptor: {
          uint16_t size = sizeof(GetType_t<0, Configurations>::HidReport::Data);
          _Ep0::Writer::SendData(GetType_t<0, Configurations>::HidReport::Data, setup->Length < size ? setup->Length : size);
          break;
        }
        default:
          _Ep0::SetTxStatus(EndpointStatus::Stall);
          break;
        }
        break;
      }
      case StandartRequestCode::GetConfiguration: {
        uint16_t configuration = 0;
        _Ep0::Writer::SendData(&configuration, 1);
        break;
      }
      case StandartRequestCode::SetConfiguration: {
        _Ep0::Writer::SendData(0);
        break;
      }
      default:
        _Ep0::SetTxStatus(EndpointStatus::Stall);
        break;
      }
    }
    _Ep0::SetRxStatus(EndpointStatus::Valid);
  }
  if(_Ep0::Reg::Get() & USB_EP_CTR_TX)
  {
    _Ep0::ClearCtrTx();
    if(TempAddressStorage != 0)
    {
      _Regs()->DADDR = USB_DADDR_EF | (TempAddressStorage & USB_DADDR_ADD);
      TempAddressStorage = 0;
    }
    _Ep0::SetRxStatus(EndpointStatus::Valid);
  }
}
      
      



HID

HID- - HID, HID - :





hid
template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Hid, typename... _Endpoints>
class HidInterface : public Interface<_Number, _AlternateSetting, 0x03, _SubClass, _Protocol, _Endpoints...>
{
  using Base = Interface<_Number, _AlternateSetting, 0x03, _SubClass, _Protocol, _Endpoints...>;
public:
  using Endpoints = Base::Endpoints;

  static uint16_t FillDescriptor(InterfaceDescriptor* descriptor)
  {
    uint16_t totalLength = sizeof(InterfaceDescriptor);

    *descriptor = InterfaceDescriptor {
      .Number = _Number,
      .AlternateSetting = _AlternateSetting,
      .EndpointsCount = Base::EndpointsCount,
      .Class = 0x03,
      .SubClass = _SubClass,
      .Protocol = _Protocol
    };
    _Hid* hidDescriptor = reinterpret_cast<_Hid*>(++descriptor);
    *hidDescriptor = _Hid {
    };
    uint8_t* reportsPart = reinterpret_cast<uint8_t*>(++hidDescriptor);
    uint16_t bytesWritten = _Hid::FillReports(reportsPart);

    totalLength += sizeof(_Hid) + bytesWritten;

    EndpointDescriptor* endpointsDescriptors = reinterpret_cast<EndpointDescriptor*>(&reportsPart[bytesWritten]);
    totalLength += (_Endpoints::FillDescriptor(endpointsDescriptors++) + ...);

    return totalLength;
  }
private:
};
      
      



, , , , HidInterface , , ().





HID-

, ( , BluePill) ( USB HID Demonstrator).





HID- Report, . :





using Report = HidReport<
  0x06, 0x00, 0xff,    // USAGE_PAGE (Generic Desktop)
  0x09, 0x01,          // USAGE (Vendor Usage 1)
  0xa1, 0x01,          // COLLECTION (Application)
  0x85, 0x01,          //   REPORT_ID (1)
  0x09, 0x01,          //   USAGE (Vendor Usage 1)
  0x15, 0x00,          //   LOGICAL_MINIMUM (0)
  0x25, 0x01,          //   LOGICAL_MAXIMUM (1)
  0x75, 0x08,          //   REPORT_SIZE (8)
  0x95, 0x01,          //   REPORT_COUNT (1)
  0xb1, 0x82,          //   FEATURE (Data,Var,Abs,Vol)
  0x85, 0x01,          //   REPORT_ID (1)
  0x09, 0x01,          //   USAGE (Vendor Usage 1)
  0x91, 0x82,          //   OUTPUT (Data,Var,Abs,Vol)
  0xc0                 // END_COLLECTION
>;
      
      



: , , :





using HidDesc = HidDescriptor<0x1001, Report>;

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

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



- , :





using Led = IO::Pc13Inv; // Inv - .

template<>
void LedsControlEp::Handler()
{
  LedsControlEp::ClearCtrRx();
  uint8_t* buffer = reinterpret_cast<uint8_t*>(LedsControlEp::Buffer);
  bool needSet = buffer[1] != 0;
  //       "STM32  USB-HID —  ".
  //       .
  switch(buffer[0])
  {
  case 1:
    needSet ? Led::Set() : Led::Clear();
    break;
  }
  LedsControlEp::SetRxStatus(EndpointStatus::Valid);
}
      
      



main.c Stm32f103 (-, ):





#include <clock.h>
#include <iopins.h>
#include <usb.h>

using namespace Zhele;
using namespace Zhele::Clock;
using namespace Zhele::IO;
using namespace Zhele::Usb;

using Report = HidReport<
  0x06, 0x00, 0xff,        // USAGE_PAGE (Generic Desktop)
  0x09, 0x01,          // USAGE (Vendor Usage 1)
  0xa1, 0x01,          // COLLECTION (Application)
  0x85, 0x01,          //   REPORT_ID (1)
  0x09, 0x01,          //   USAGE (Vendor Usage 1)
  0x15, 0x00,          //   LOGICAL_MINIMUM (0)
  0x25, 0x01,          //   LOGICAL_MAXIMUM (1)
  0x75, 0x08,          //   REPORT_SIZE (8)
  0x95, 0x01,          //   REPORT_COUNT (1)
  0xb1, 0x82,          //   FEATURE (Data,Var,Abs,Vol)
  0x85, 0x01,          //   REPORT_ID (1)
  0x09, 0x01,          //   USAGE (Vendor Usage 1)
  0x91, 0x82,          //   OUTPUT (Data,Var,Abs,Vol)
  0xc0               // END_COLLECTION
>;

using HidDesc = HidDescriptor<0x1001, Report>;

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

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

using Led = IO::Pc13Inv;

void ConfigureClock();
void ConfigureLeds();

int main()
{
  ConfigureClock();
  ConfigureLeds();

  Zhele::IO::Porta::Enable();
  MyDevice::Enable();

  for(;;)
  {
  }
}

void ConfigureClock()
{
  PllClock::SelectClockSource(PllClock::ClockSource::External);
  PllClock::SetMultiplier(9);
  Apb1Clock::SetPrescaler(Apb1Clock::Div2);
  SysClock::SelectClockSource(SysClock::Pll);
  MyDevice::SelectClockSource(Zhele::Usb::ClockSource::PllDividedOneAndHalf);
}

void ConfigureLeds()
{
  Led::Port::Enable();
  Led::SetConfiguration<Led::Configuration::Out>();
  Led::SetDriverType<Led::DriverType::PushPull>();
  Led::Set();
}

template<>
void LedsControlEp::Handler()
{
  LedsControlEp::ClearCtrRx();
  uint8_t* buffer = reinterpret_cast<uint8_t*>(LedsControlEp::Buffer);
  bool needSet = buffer[1] != 0;

  switch(buffer[0])
  {
  case 1:
    needSet ? Led::Set() : Led::Clear();
    break;
  }

  LedsControlEp::SetRxStatus(EndpointStatus::Valid);
}

extern "C" void USB_LP_IRQHandler()
{
  MyDevice::CommonHandler();
}
      
      



( " ", " " ..) , : . variadic- . , Og 2360 Flash 36 RAM ( Os 1712 , . , ), .





Thanks to @RaJa for a great post about HID . Also, less than a week before this post was written, there was another cool HID material from @COKPOWEHEU . Without these posts, I would not have mastered anything. Even more help was provided by users from the radiokot forum (COKPOWEHEU and VladislavS), I was pleasantly surprised by the promptness of the answers and the desire to help.








All Articles