Stm32 + USB sur les modèles C ++. Continuation. Faire HID

La dernière fois a montré une façon d'allouer des ressources entre les points d'extrémité, à savoir les registres EPnR, les tampons de mémoire pour les descripteurs et pour les tampons eux-mêmes. Je propose de continuer ce que nous avons commencé et de considérer la bibliothèque écrite en utilisant l'exemple de la création d'un simple appareil HID qui vous permet de contrôler une LED.





Partage d'interruption

En plus des ressources indiquées précédemment, les terminaux partagent également une seule interruption. En conséquence, le gestionnaire général (principal) doit transférer correctement le contrôle au gestionnaire d'interruption du point final souhaité. Le numéro de point final auquel l'hôte accède est enregistré dans les bits EP_ID du registre ISTR. En suivant la ligne d'implémentation d'une bibliothèque entièrement basée sur des modèles, j'ai obtenu la classe suivante:





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



L'élément clé de la classe est le tableau _handlersIndexes , qui mappe le numéro et la direction du point de terminaison à un gestionnaire particulier. Pour obtenir ce tableau, une classe spéciale est implémentée:





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



À propos, cette implémentation implique une recommandation de déclarer les points de terminaison avec des nombres dans l'ordre, car la taille du tableau d'indices de gestionnaire est égale à deux fois le nombre maximal de points de terminaison.





Classe de point 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 , . , ), .





Merci à @RaJa pour un excellent article sur HID . De plus, moins d'une semaine avant la rédaction de cet article, il y avait un autre matériel HID sympa de @COKPOWEHEU . Sans ces messages, je n'aurais rien maîtrisé. Encore plus d'aide a été fournie par les utilisateurs du forum radiokot (COKPOWEHEU et VladislavS), j'ai été agréablement surpris par la rapidité des réponses et le désir d'aider.








All Articles