Stm32 + USB em modelos C ++

* 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 ( , , ).





:





  1. (0..16).





  2. (Control, Interrupt, Bulk, Isochronous).





  3. (In, Out).





  4. .





, .





:





  1. (EPnR).





  2. .





  3. ( ).





: N . , , :





  1. , , .





  2. , .





  3. "" .





:





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



, :





  1. EndpointEPRn - , EPnR . : . , .





  2. BufferOffset - , . , N 0, ..., N-1.





  3. SortedUniqueEndpoints - , + . USB /, Device.





  4. 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++ :





  1. , , , ( HID-, , 2400 ).





  2. , .





  3. , , . "" USB.





  4. * . 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.








All Articles