* Merci à @ grafalex pour l' idée géniale de la bande
Personne n'aime l'USB
En progressant dans l'étude de la programmation des microcontrôleurs, je me suis rendu compte de la nécessité de maîtriser l'USB, car c'est sans aucun doute l'interface principale pour la connexion d'appareils non en circuit. Cependant, il s'est avéré qu'il n'y a pas beaucoup de matériaux pertinents dans le monde ouvert. Après avoir analysé divers forums, j'ai formulé les raisons suivantes de l'impopularité de l'USB dans les projets:
@jaiprakash a rappelé que la valeur VID obligatoire pour un périphérique USB doit être achetée pour beaucoup d'argent.
L'absence de nécessité de transmission de données à haut débit dans la plupart des projets.
La grande complexité de la norme elle-même et le développement par rapport à l'interface UART familière. Il est moins coûteux d'ajouter un adaptateur USB <-> UART prêt à l'emploi à l'appareil.
Manque de compétences en développement de pilotes Windows / Linux.
En conséquence, les développeurs préfèrent généralement utiliser UART (via un convertisseur matériel ou, tout au plus, en créant un appareil VCP, dont le code est généré avec succès par CubeMX). J'ai décidé d'essayer de comprendre l'USB au moins à un niveau de base, en continuant d'utiliser les modèles de langage C ++. Cet article décrit la manière appliquée d'allouer des ressources (à savoir la mémoire tampon et les registres) entre les points de terminaison des périphériques.
Problème de duplication
L'élément principal d'un programme qui implémente un périphérique USB est un point de terminaison . L'hôte communique avec un point de terminaison spécifique. Le périphérique doit contenir un point de terminaison avec le numéro 0, à travers lequel le contrôle a lieu, des demandes de divers descripteurs à l'étape de l'énumération, des commandes pour attribuer une adresse, choisir une configuration et tous les autres contrôles. Plus de détails sur le concept de points de terminaison et, en principe, les connaissances de base de l'USB peuvent être trouvés dans la traduction de "USB in NutShell" sur la ressource microsin (merci beaucoup aux gars pour le travail effectué, ils ont fait un travail très utile) .
Stm32F0/F1 - Packet Memory Area (PMA), . USB- , , . , K, "" K+1, ... , N. ( N - ). : 100% .
, ( ) , runtime compile-time, :
. . (ADDRn_TX, COUNTn_TX, ADDRn_RX, COUNTn_RX), , runtime .
, EPnR ( , , ).
:
(0..16).
(Control, Interrupt, Bulk, Isochronous).
(In, Out).
.
, .
:
(EPnR).
.
( ).
: N . , , :
, , .
, .
"" .
:
template<typename... AllEndpoints,
typename... BidirectionalAndBulkDoubleBufferedEndpoints,
typename... RxEndpoints,
typename... BulkDoubleBufferedTxEndpoints>
class EndpointsManagerBase<TypeList<AllEndpoints...>,
TypeList<BidirectionalAndBulkDoubleBufferedEndpoints...>,
TypeList<RxEndpoints...>,
TypeList<BulkDoubleBufferedTxEndpoints...>>
{
//
using AllEndpointsList = TypeList<AllEndpoints...>;
///
static const auto BdtSize = 8 * (EndpointEPRn<GetType_t<sizeof...(AllEndpoints) - 1, AllEndpointsList>, AllEndpointsList>::RegisterNumber + 1);
///
template<typename Endpoint>
static constexpr uint32_t BufferOffset = BdtSize + OffsetOfBuffer<TypeIndex<Endpoint, AllEndpointsList>::value, AllEndpointsList>::value;
///
template<typename Endpoint>
static constexpr uint32_t BdtCellOffset =
EndpointEPRn<Endpoint, AllEndpointsList>::RegisterNumber * 8
+ (Endpoint::Type == EndpointType::Control
|| Endpoint::Type == EndpointType::ControlStatusOut
|| Endpoint::Type == EndpointType::BulkDoubleBuffered
|| Endpoint::Direction == EndpointDirection::Out
|| Endpoint::Direction == EndpointDirection::Bidirectional
? 0
: 4);
/// USB
static const uint32_t BdtBase = PmaBufferBase;
public:
/// ""
template<typename Endpoint>
using ExtendEndpoint =
typename Select<Endpoint::Type == EndpointType::Control || Endpoint::Type == EndpointType::ControlStatusOut,
ControlEndpoint<Endpoint,
typename EndpointEPRn<Endpoint, TypeList<AllEndpoints...>>::type,
PmaBufferBase + BufferOffset<Endpoint>, // TxBuffer
PmaBufferBase + BdtCellOffset<Endpoint> + 2, // TxCount
PmaBufferBase + BufferOffset<Endpoint> + Endpoint::MaxPacketSize, // RxBuffer
PmaBufferBase + BdtCellOffset<Endpoint> + 6>, //RxCount
typename Select<Endpoint::Direction == EndpointDirection::Bidirectional,
BidirectionalEndpoint<Endpoint,
typename EndpointEPRn<Endpoint, TypeList<AllEndpoints...>>::type,
PmaBufferBase + BufferOffset<Endpoint>, // TxBuffer
PmaBufferBase + BdtCellOffset<Endpoint> + 2, // TxCount
PmaBufferBase + BufferOffset<Endpoint> + Endpoint::MaxPacketSize, // RxBuffer
PmaBufferBase + BdtCellOffset<Endpoint> + 6>, //RxCount
... //
void>::value>::value;
static void Init()
{
memset(reinterpret_cast<void*>(BdtBase), 0x00, BdtSize);
//
((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<AllEndpoints>)) = BufferOffset<AllEndpoints>), ...);
//
((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BidirectionalAndBulkDoubleBufferedEndpoints> + 4)) = (BufferOffset<BidirectionalAndBulkDoubleBufferedEndpoints> + BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize)), ...);
// COUNTn_RX (Rx, Out)
((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<RxEndpoints> + 2)) = (RxEndpoints::MaxPacketSize <= 62
? (RxEndpoints::MaxPacketSize / 2) << 10
: 0x8000 | (RxEndpoints::MaxPacketSize / 32) << 10)), ...);
// COUNTn_RX
((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BidirectionalAndBulkDoubleBufferedEndpoints> + 6)) = (BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize <= 62
? (BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize / 2) << 10
: 0x8000 | (BidirectionalAndBulkDoubleBufferedEndpoints::MaxPacketSize / 32) << 10)), ...);
// COUNTn_RX Tx (, , )
((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BulkDoubleBufferedTxEndpoints> + 2)) = 0), ...);
((*(reinterpret_cast<uint16_t*>(BdtBase + BdtCellOffset<BulkDoubleBufferedTxEndpoints> + 6)) = 0), ...);
}
};
template<typename Endpoints>
using EndpointsManager = EndpointsManagerBase<SortedUniqueEndpoints<Endpoints>,
typename Sample<IsBidirectionalOrBulkDoubleBufferedEndpoint, SortedUniqueEndpoints<Endpoints>>::type,
typename Sample<IsOutEndpoint, SortedUniqueEndpoints<Endpoints>>::type,
typename Sample<IsBulkDoubleBufferedTxEndpoint, SortedUniqueEndpoints<Endpoints>>::type>;
template<typename... Endpoints>
using EndpointsInitializer = EndpointsManagerBase<SortedUniqueEndpoints<TypeList<Endpoints...>>,
TypeList<>,
TypeList<>,
TypeList<>>;
, :
EndpointEPRn - , EPnR . : . , .
BufferOffset - , . , N 0, ..., N-1.
SortedUniqueEndpoints - , + . USB /, Device.
IsBidirectionalOrBulkDoubleBufferedEndpoint, IsOutEndpoint, IsBulkDoubleBufferedTxEndpoint - .
:
using DefaultEp0 = ZeroEndpointBase<64>;
using LedsControlEpBase = OutEndpointBase<1, EndpointType::Interrupt, 64, 32>;
//
using EpInitializer = EndpointsInitializer<DefaultEp0, LedsControlEpBase>;
// EpInitializer .
// , ,
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>;
Device , :
template<
...
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...>;
using EpBufferManager = EndpointsManager<Append_t<_Ep0, Endpoints>>;
// Device
using EpHandlers = EndpointHandlers<Append_t<This, Endpoints>>;
public:
static void Enable()
{
_ClockCtrl::Enable();
//
EpBufferManager::Init();
C++ :
, , , ( HID-, , 2400 ).
, .
, , . "" USB.
* . C++, , .
USB . , - - , USB, , - . , . , USB , , "" , .
Cet article n'était pas consacré à la partie de la bibliothèque liée à l'USB en général, mais à un module petit mais important pour la distribution des ressources entre les terminaux. Je serais ravi d'avoir des questions et des commentaires.
Vous pouvez voir le code entier (je teste l'USB jusqu'à présent uniquement sur F072RBT6, car il y a une discothèque avec un miniusb soudé) ici . J'espère vaincre l'USB d'ici l'été au moins pour les séries MK F0 et F1. J'ai regardé F4 - tout y est plus cool (il y a un support OTG) et difficile.