Stm32 + USB en plantillas C ++. Continuación. Haciendo HID

La última vez mostró una forma de asignar recursos entre puntos finales, a saber, registros EPnR, búferes de memoria para descriptores y para los búferes mismos. Propongo continuar con lo que comenzamos y considerar la biblioteca escrita usando el ejemplo de la creación de un dispositivo HID simple que le permite controlar un LED.





Interrumpir compartir

Además de los recursos indicados anteriormente, los puntos finales también comparten una única interrupción. En consecuencia, el manejador general (principal) debe transferir correctamente el control al manejador de interrupciones del punto final deseado. El número de punto final al que accede el host se registra en los bits EP_ID del registro ISTR. Siguiendo la línea de implementación de una biblioteca con plantillas completas, obtuve la siguiente clase:





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)]]();
  }
};
      
      



El elemento clave de la clase es la matriz _handlersIndexes , que asigna el número de punto final y la dirección a un controlador en particular. Para obtener esta matriz, se implementa una clase especial:





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



Por cierto, esta implementación implica una recomendación para declarar los puntos finales con números en orden, porque el tamaño de la matriz de índices del controlador es igual al doble del número máximo de puntos finales.





Clase de punto final

- : , :





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





Gracias a @RaJa por una excelente publicación sobre HID . Además, menos de una semana antes de que se escribiera esta publicación, hubo otro material HID genial de @COKPOWEHEU . Sin estas publicaciones, no habría dominado nada. Los usuarios del foro de radiokot (COKPOWEHEU y VladislavS) proporcionaron aún más ayuda, me sorprendió gratamente la rapidez de las respuestas y el deseo de ayudar.








All Articles