* Obrigado a @grafalex pela ótima ideia de fita
Ninguém gosta de USB
Avançando no estudo de programação de microcontroladores, percebi a necessidade de masterizar o USB, já que é sem dúvida a principal interface para conexão de dispositivos não em circuito. No entanto, descobriu-se que não existem muitos materiais relevantes no mundo aberto. Depois de analisar vários fóruns, formulei as seguintes razões para a impopularidade do USB em projetos:
@jaiprakash lembrou que o valor VID obrigatório para um dispositivo USB deve ser comprado por muito dinheiro.
A ausência da necessidade de transmissão de dados em alta velocidade na maioria dos projetos.
A alta complexidade do próprio padrão e do desenvolvimento em comparação com a interface UART familiar. É mais barato adicionar um adaptador USB <-> UART pré-fabricado ao dispositivo.
Falta de habilidades de desenvolvimento de driver do Windows / Linux.
Como resultado, os desenvolvedores preferem principalmente usar UART (por meio de um conversor de hardware ou, no máximo, criando um dispositivo VCP, cujo código é gerado com sucesso pelo CubeMX). Decidi tentar entender o USB pelo menos em um nível básico, continuando a linha de usar os modelos de linguagem C ++. Esta postagem descreve a forma aplicada de alocar recursos (ou seja, memória de buffer e registros) entre os terminais do dispositivo.
Problema de duplicação
O principal elemento de um programa que implementa um dispositivo USB é um Endpoint . O host se comunica com um terminal específico. O dispositivo deve conter um ponto de extremidade com o número 0, por meio do qual ocorre o controle, solicitações de vários descritores no estágio de enumeração, comandos para atribuir um endereço, escolher uma configuração e todos os outros controles. Mais detalhes sobre o conceito de endpoints e, a princípio, conhecimento básico de USB podem ser encontrados na tradução de "USB in NutShell" no recurso microsin (muito obrigado aos caras pelo trabalho realizado, eles fizeram um trabalho muito útil) .
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 , , "" , .
Este post foi dedicado não à parte da biblioteca relacionada ao USB em geral, mas a um pequeno, mas importante módulo para distribuição de recursos entre terminais. Eu ficaria feliz em ter perguntas e comentários.
Você pode ver o código inteiro (estou testando o USB até agora apenas no F072RBT6, porque há uma discoteca com um miniusb soldado) aqui . Espero derrotar o USB até o verão, pelo menos para as séries MK F0 e F1. Olhei para F4 - tudo é mais legal lá (há suporte para OTG) e difícil.