最後時間が記述子およびバッファ自分自身のためのエンドポイント、すなわちEPnRレジスタ、メモリ・バッファとの間でリソースを割り当てる1つの方法を示しました。LEDを制御できる単純なHIDデバイスを作成する例を使用して、私たちが始めたものを継続し、作成されたライブラリを検討することを提案します。
割り込み共有
前に示したリソースに加えて、エンドポイントも単一の割り込みを共有します。したがって、一般(メイン)ハンドラーは、目的のエンドポイントの割り込みハンドラーに制御を正しく転送する必要があります。ホストがアクセスしているエンドポイント番号は、ISTRレジスタのEP_IDビットに記録されます。完全にテンプレート化されたライブラリの実装行に続いて、次のクラスを取得しました。
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)]]();
}
};
クラスの重要な要素は_handlersIndexes配列であり、エンドポイントの番号と方向を特定のハンドラーにマップします。この配列を取得するために、特別なクラスが実装されています。
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<>;
};
ちなみに、この実装では、ハンドラーインデックスの配列のサイズが最大エンドポイント番号の2倍に等しいため、番号を使用してエンドポイントを順番に宣言することをお勧めします。
エンドポイントクラス
- : , :
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 , . , ), .
HIDに関するすばらしい投稿をしてくれた@RaJaに感謝します。また、この投稿が書かれる1週間も経たないうちに、@ COKPOWEHEUから別のクールなHID資料がありました。これらの投稿がなければ、私は何も習得できなかったでしょう。radiokotフォーラム(COKPOWEHEUとVladislavS)のユーザーからさらに多くの支援が提供され、回答の迅速さと支援の意欲にうれしく驚きました。