The terakhir kali menunjukkan salah satu cara mengalokasikan sumber daya antara endpoint, yaitu register EPnR, buffer memori untuk deskriptor dan untuk buffer sendiri. Saya mengusulkan untuk melanjutkan apa yang kami mulai dan mempertimbangkan perpustakaan tertulis menggunakan contoh pembuatan perangkat HID sederhana yang memungkinkan Anda untuk mengontrol LED.
Interupsi pembagian
Selain sumber daya yang ditunjukkan sebelumnya, titik akhir juga berbagi satu interupsi. Oleh karena itu, pengendali umum (utama) harus mentransfer kendali dengan benar ke pengendali interupsi dari titik akhir yang diinginkan. Nomor titik akhir yang diakses host dicatat dalam bit EP_ID dari register ISTR. Mengikuti garis implementasi dari pustaka yang memiliki template penuh, saya mendapatkan kelas berikut:
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)]]();
}
};
Elemen kunci kelas ini adalah larik _handlersIndexes , yang memetakan nomor titik akhir dan arah ke penangan tertentu. Untuk mendapatkan array ini, kelas khusus diimplementasikan:
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<>;
};
Ngomong-ngomong, implementasi ini menyiratkan rekomendasi untuk mendeklarasikan titik akhir dengan angka secara berurutan, karena ukuran larik indeks penangan sama dengan dua kali jumlah titik akhir maksimum.
Kelas titik akhir
- : , :
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 , . , ), .
Terima kasih kepada @RaJa untuk postingan yang bagus tentang HID . Juga, kurang dari seminggu sebelum postingan ini ditulis, ada materi HID keren lainnya dari @COKPOWEHEU . Tanpa posting ini, saya tidak akan menguasai apa pun. Bahkan lebih banyak bantuan diberikan oleh pengguna dari forum radio (COKPOWEHEU dan VladislavS), saya terkejut dengan ketepatan jawaban dan keinginan untuk membantu.