Skip to content

Instantly share code, notes, and snippets.

@byteit101
Created January 10, 2023 06:10
Show Gist options
  • Save byteit101/3ccfc07a7fdc355614a38095c70dddd0 to your computer and use it in GitHub Desktop.
Save byteit101/3ccfc07a7fdc355614a38095c70dddd0 to your computer and use it in GitHub Desktop.
TinyUSB C++20 compile-time USB descriptor builder (Pico/RP2040 edition)
#include "tusb.h"
//#include <vector>
#include <initializer_list>
#include <algorithm>
#include <array>
//#include <tuple>
#include <cstddef>
#include <type_traits>
namespace gtu {
// todo: endpoint in/out may be different types?
struct endpoint_addr {
bool isIn;
uint8_t num;
constexpr uint8_t pack(){ // could be a bitfield, probably
return num | (isIn? 0x80 : 0x00);
}
};
template<uint8_t channel>
constexpr endpoint_addr ep_in(){
static_assert(channel < 16);
return {
.isIn = true,
.num = channel
};
}
template<uint8_t channel>
constexpr endpoint_addr ep_out(){
static_assert(channel < 16);
return {
.isIn = false,
.num = channel
};
}
struct interface_ref {
uint8_t n;
constexpr uint8_t pack(){
return n;
}
};
// from https://web.archive.org/web/20161215110435/http://b.atch.se/posts/constexpr-counter/
// and https://mc-deltat.github.io/articles/stateful-metaprogramming-cpp20
namespace interface_counter {
template<unsigned N>
struct reader {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
friend auto counted_flag(reader<N>); // E1
#pragma GCC diagnostic pop
};
template<unsigned N>
struct setter {
friend auto counted_flag(reader<N>) {} // E2
static constexpr unsigned n = N;
};
// E3
template<
auto Tag, // E3.1
unsigned NextVal = 0
>
[[nodiscard]]
consteval interface_ref counter_impl() {
constexpr bool counted_past_value = requires(reader<NextVal> r) {
counted_flag(r);
};
if constexpr (counted_past_value) {
return counter_impl<Tag, NextVal + 1>(); // E3.2
}
else {
// E3.3
setter<NextVal> s;
return {.n = (uint8_t)s.n};
}
}
template<
auto Tag = []{}, // E4
auto Val = counter_impl<Tag>()
>
constexpr auto alloc = Val;
// [[nodiscard]] stored_tuple<interface_ref, interface_ref> constexpr alloc2()
// {
// return store_tuple(alloc<>, alloc<>);
// }
}
// static constexpr auto fsec = interface_counter::alloc2<>;
// static constexpr auto fir = interface_counter::alloc<>;
// static constexpr auto sec = interface_counter::alloc<>;
// static_assert(fir.n == 0, "not 0");
// static_assert(sec.n == 1, "not 0");
constexpr tusb_desc_endpoint_t build_endpoint(tusb_desc_endpoint_t endpoint){
endpoint.bLength = sizeof(tusb_desc_endpoint_t);
endpoint.bDescriptorType = TUSB_DESC_ENDPOINT;
return endpoint;
}
constexpr tusb_desc_endpoint_t build_endpoint(endpoint_addr bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0){
return build_endpoint({
.bEndpointAddress = bEndpointAddress.pack(),
.bmAttributes= bmAttributes,
.wMaxPacketSize = wMaxPacketSize,
.bInterval= bInterval
});
}
constexpr tusb_desc_endpoint_t build_endpoint(uint8_t bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0){
return build_endpoint({
.bEndpointAddress = bEndpointAddress,
.bmAttributes= bmAttributes,
.wMaxPacketSize = wMaxPacketSize,
.bInterval= bInterval
});
}
#pragma pack(1)
// packed, and well-ordered tuples
// std::tuple isn't, so we define our own
template <typename... T>
struct stored_tuple { };
// sometimes, rest is > 0 when empty, so explicity delete it
template<typename T>
struct stored_tuple<T>
{
T first;
constexpr stored_tuple(const T& f)
: first(f) {}
constexpr stored_tuple(T&& f)
: first(std::move(f)) {}
// copy and move ctors?
// move arg ctors?
};
template<typename T, typename U, typename... Rest>
struct stored_tuple<T, U, Rest...>
{
T first;
[[no_unique_address]] stored_tuple<U, Rest...> rest;
constexpr stored_tuple(const T& f, const U& u, const Rest&... r)
: first(f), rest(u, r...) {}
constexpr stored_tuple(T&& f, U&& u, Rest&&... r)
: first(std::move(f)), rest(u, r...) {}
// copy and move ctors?
// move arg ctors?
};
static_assert(sizeof(stored_tuple<int>) == sizeof(int));
static_assert(sizeof(stored_tuple<char, int>) == sizeof(char) + sizeof(int));
// forward declaration
template<size_t i,typename T>
struct sthelper;
// mutable version
template<size_t i, template <typename...> class stored_tuple, typename... args>
constexpr auto& stored_get(stored_tuple<args...> &t)
{
return sthelper<i, stored_tuple<args...>>::get(t);
}
template<typename T,typename... Rest>
struct sthelper<0, stored_tuple<T, Rest...>> {
constexpr static T& get(stored_tuple<T, Rest...> &data) {
return data.first;
}
constexpr static const T& get(const stored_tuple<T, Rest...> &data) {
return data.first;
}
};
template<size_t i, typename T, typename... Rest>
struct sthelper<i, stored_tuple<T, Rest...>> {
constexpr static auto& get(stored_tuple<T, Rest...> &data) {
return sthelper<i - 1, stored_tuple<Rest...>>::get(data.rest);
}
constexpr static const auto& get(const stored_tuple<T, Rest...> &data) {
return sthelper<i - 1, stored_tuple<Rest...>>::get(data.rest);
}
};
// const versions
// template<size_t i, typename... args>
// constexpr const auto& stored_get(const stored_tuple<args...> &t)
// {
// return sthelper<i, stored_tuple<args...>>::get(t);
// }
template<size_t i, typename base, typename... args>
constexpr const uint8_t* stored_extract(const stored_tuple<args...> &t)
{
const base& value = sthelper<i, stored_tuple<args...>>::get(t);
return (const uint8_t*)&value;
}
// helpers
template<typename... T>
constexpr stored_tuple<std::unwrap_ref_decay_t<T>...> store_tuple(T&&... args)
{
return stored_tuple<std::unwrap_ref_decay_t<T>...>(std::forward<T>(args)...);
}
template<typename T>
struct stored_tuple_size;
template<typename... T>
struct stored_tuple_size<stored_tuple<T...>> : std::integral_constant<std::size_t, sizeof...(T)> { };
template<typename T>
inline constexpr std::size_t stored_tuple_size_v = stored_tuple_size<T>::value;
namespace audio {
/// USB Endpoint Descriptor
struct TU_ATTR_PACKED tusb_desc_endpoint_audio_t
{
uint8_t bLength ; ///< Size of this descriptor in bytes
uint8_t bDescriptorType ; ///< ENDPOINT Descriptor Type
uint8_t bEndpointAddress ;
struct TU_ATTR_PACKED {
uint8_t xfer : 2;
uint8_t sync : 2;
uint8_t usage : 2;
uint8_t : 2;
} bmAttributes ;
uint16_t wMaxPacketSize;
uint8_t bInterval;
uint8_t bRefresh;
uint8_t bSynchAddress;
} ;
constexpr tusb_desc_endpoint_audio_t build_endpoint(tusb_desc_endpoint_audio_t endpoint){
endpoint.bLength = sizeof(tusb_desc_endpoint_audio_t);
endpoint.bDescriptorType = TUSB_DESC_ENDPOINT;
return endpoint;
}
constexpr tusb_desc_endpoint_audio_t build_endpoint(endpoint_addr bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0, uint8_t bRefresh=0, uint8_t bSynchAddress=0){
return build_endpoint({
.bEndpointAddress = bEndpointAddress.pack(),
.bmAttributes= bmAttributes,
.wMaxPacketSize = wMaxPacketSize,
.bInterval= bInterval,
.bRefresh=bRefresh,
.bSynchAddress = bSynchAddress,
});
}
constexpr tusb_desc_endpoint_audio_t build_endpoint(uint8_t bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0){
return build_endpoint({
.bEndpointAddress = bEndpointAddress,
.bmAttributes= bmAttributes,
.wMaxPacketSize = wMaxPacketSize,
.bInterval= bInterval,
});
}
}
template<typename tuples=stored_tuple<>>
// TODO: requires?
struct busb_interface_assoc {
tusb_desc_interface_assoc_t core;
// TODO: fold expressions + https://stackoverflow.com/questions/72297845/how-to-check-if-an-object-is-an-instance-of-template-class-of-multiple-template should allow validating
[[no_unique_address]] tuples interfaces;
constexpr busb_interface_assoc(tusb_desc_interface_assoc_t interface_assoc, tuples children)
: core(interface_assoc), interfaces(children)
{
core.bLength = sizeof(interface_assoc);
core.bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION;
// TODO: std::get
core.bFirstInterface = stored_get<0>(children).base.bInterfaceNumber;
core.bInterfaceCount = numInterfaces();
}
constexpr size_t numInterfaces() noexcept { return stored_tuple_size_v<tuples>; }
};
template<typename... parts>
// TODO: requires?
struct busb_interface_tuple {
using tuples = stored_tuple<parts...>;
tuples interfaces;
constexpr busb_interface_tuple(parts... children)
: interfaces(store_tuple(children...))
{
}
constexpr size_t numInterfaces() noexcept { return sizeof...(parts); }
};
template<typename size, typename first, typename... rest>
// TODO: requires?
struct busb_length_computer {
using tuples = stored_tuple<first, rest...>;
tuples core;
constexpr busb_length_computer(first frst, size first::*setter, rest... children)
: core(store_tuple(frst, children...))
{
stored_get<0>(core).*setter = sizeof(tuples);
}
};
template<typename... config>
constexpr stored_tuple<tusb_desc_device_t, tusb_desc_configuration_t, stored_tuple<config...>> build_device(tusb_desc_device_t device, stored_tuple<tusb_desc_configuration_t, config...> configurations){
device.bLength = sizeof(device);
device.bDescriptorType = TUSB_DESC_DEVICE;
device.bNumConfigurations = 1;// TODO: count types!
return store_tuple(device,
configurations.first,
configurations.rest);
}
template<typename... config>
constexpr stored_tuple<tusb_desc_configuration_t, config...> build_configuration(uint8_t iConfig, uint16_t maxPower, config... elements)
{
tusb_desc_configuration_t cfg;
cfg.bLength = sizeof(cfg);
cfg.bDescriptorType = TUSB_DESC_CONFIGURATION;
cfg.bNumInterfaces = (elements.numInterfaces() + ...);
cfg.bConfigurationValue = 1;// TODO: expose?
cfg.iConfiguration = iConfig;
cfg.bmAttributes = TU_BIT(7) | 0;// TODO: expose 0?
cfg.bMaxPower = maxPower / 2;
return busb_length_computer(cfg,
&tusb_desc_configuration_t::wTotalLength,
elements...).core;
}
// All of this nonsense is because sizeof(array<T,0>) > 0
// template<typename T, size_t N>
// struct elidable_array {
// std::array<T, N> value;
// constexpr elidable_array(std::array<T, N> value) : value(value) {}
// }
template<typename T>
struct elidable_array_impl {
constexpr elidable_array_impl(std::array<T, 0> value){}
constexpr elidable_array_impl(){}
};
template<typename T, std::size_t N>
using elidable_array = std::conditional_t<N == 0, elidable_array_impl<T>, std::array<T, N>>;
template<size_t child_n=0, typename tuples=stored_tuple<>, typename endpoint_t=tusb_desc_endpoint_t>
struct busb_interface {
// pack away unused fields
tusb_desc_interface_t base;
[[no_unique_address]] tuples class_specific;
[[no_unique_address]] elidable_array<endpoint_t, child_n> children;
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import, tuples class_specific, std::array<endpoint_t, child_n> endpoints)
: base(import), class_specific(class_specific), children(endpoints){
base.bLength = sizeof(base);
base.bDescriptorType = TUSB_DESC_INTERFACE;
base.bInterfaceNumber = intf.pack(); // TODO!
base.bNumEndpoints = endpoints.size();
}
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import, std::array<endpoint_t, child_n> endpoints) : busb_interface(intf, import, store_tuple(), endpoints) {}
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import, tuples class_specific) : busb_interface(intf, import, class_specific, std::array<endpoint_t,0>{}) {}
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import) : busb_interface(intf, import, store_tuple(), std::array<endpoint_t,0>{}) {}
constexpr size_t numInterfaces() noexcept { return 1; }
};
#pragma pack()
static_assert(sizeof(busb_interface<>) == sizeof(tusb_desc_interface_t), "packing failure");
#define GTU_TUSB_Tie(name, struct, toptype)
#define GTU_TUSB_Tie_CS(name, struct, top, subtype)
#define GTU_import(file)
//GTU_import("..TODO!/pico-sdk/lib/tinyusb/src/class/cdc/cdc.h")
GTU_TUSB_Tie(interface_assoc, tusb_desc_interface_assoc_t, TUSB_DESC_INTERFACE_ASSOCIATION)
//\s*(AUDIO_CS_AC_INTERFACE_(\w+))\s+= 0x..,
//GTU_TUSB_Tie_CS(\L$1\E, TUSB_DESC_CS_INTERFACE, $1)
namespace audio {
//GTU_TUSB_Tie_CS(header, audio_desc_cs_ac_interface_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER)
GTU_TUSB_Tie_CS(input_terminal, audio_desc_input_terminal_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL)
GTU_TUSB_Tie_CS(output_terminal, audio_desc_output_terminal_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL)
GTU_TUSB_Tie_CS(feature_unit, audio_desc_feature_unit_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT)
GTU_TUSB_Tie_CS(clock_source, audio_desc_clock_source_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE)
GTU_TUSB_Tie_CS(clock_selector, audio_desc_clock_selector_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR)
GTU_TUSB_Tie_CS(clock_multiplier, audio_desc_clock_multiplier_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER)
namespace midi {
GTU_TUSB_Tie_CS(header, midi_desc_header_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER)
GTU_TUSB_Tie_CS(in_jack, midi_desc_in_jack_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK)
GTU_TUSB_Tie_CS(out_jack, midi_desc_out_jack_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK)
GTU_TUSB_Tie_CS(element, midi_desc_element_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_ELEMENT)
constexpr midi_desc_header_t build_header(midi_desc_header_t header)
{
header.bLength = sizeof(header);
header.bDescriptorType = TUSB_DESC_CS_INTERFACE;
header.bDescriptorSubType = MIDI_CS_INTERFACE_HEADER;
return header;
}
constexpr midi_desc_header_t build_header(uint16_t bcdMSC, uint16_t wTotalLength)
{
return build_header({
.bcdMSC = bcdMSC,
.wTotalLength = wTotalLength,
});
}
template<class... Ts>
constexpr busb_length_computer<uint16_t, midi_desc_header_t, Ts...> build_header(uint16_t bcdMSC, Ts... children)
{
return gtu::busb_length_computer(build_header({
.bcdMSC = bcdMSC,
}), &midi_desc_header_t::wTotalLength,
children...);
}
constexpr midi_desc_in_jack_t build_in_jack(midi_desc_in_jack_t in_jack)
{
in_jack.bLength = sizeof(in_jack);
in_jack.bDescriptorType = TUSB_DESC_CS_INTERFACE;
in_jack.bDescriptorSubType = MIDI_CS_INTERFACE_IN_JACK;
return in_jack;
}
constexpr midi_desc_in_jack_t build_in_jack(uint8_t bJackType, uint8_t bJackID, uint8_t iJack)
{
return build_in_jack({
.bJackType = bJackType,
.bJackID = bJackID,
.iJack = iJack,
});
}
constexpr midi_desc_out_jack_t build_out_jack(midi_desc_out_jack_t out_jack)
{
out_jack.bLength = sizeof(out_jack);
out_jack.bDescriptorType = TUSB_DESC_CS_INTERFACE;
out_jack.bDescriptorSubType = MIDI_CS_INTERFACE_OUT_JACK;
return out_jack;
}
constexpr midi_desc_out_jack_t build_out_jack(uint8_t bJackType, uint8_t bJackID, uint8_t bNrInputPins, uint8_t baSourceID, uint8_t baSourcePin, uint8_t iJack)
{
return build_out_jack({
.bJackType = bJackType,
.bJackID = bJackID,
.bNrInputPins = bNrInputPins,
.baSourceID = baSourceID,
.baSourcePin = baSourcePin,
.iJack = iJack,
});
}
constexpr midi_desc_element_t build_element(midi_desc_element_t element)
{
element.bLength = sizeof(element);
element.bDescriptorType = TUSB_DESC_CS_INTERFACE;
element.bDescriptorSubType = MIDI_CS_INTERFACE_ELEMENT;
return element;
}
constexpr midi_desc_element_t build_element(uint8_t bElementID, uint8_t bNrInputPins, uint8_t baSourceID, uint8_t baSourcePin, uint8_t bNrOutputPins, uint8_t bInTerminalLink, uint8_t bOutTerminalLink, uint8_t bElCapsSize, uint16_t bmElementCaps, uint8_t iElement)
{
return build_element({
.bElementID = bElementID,
.bNrInputPins = bNrInputPins,
.baSourceID = baSourceID,
.baSourcePin = baSourcePin,
.bNrOutputPins = bNrOutputPins,
.bInTerminalLink = bInTerminalLink,
.bOutTerminalLink = bOutTerminalLink,
.bElCapsSize = bElCapsSize,
.bmElementCaps = bmElementCaps,
.iElement = iElement,
});
}
/// MIDI Class-Specific MS Bulk Data Endpoint Descriptor (6.2.2)
template <size_t N>
struct TU_ATTR_PACKED midi_desc_cs_endpoint_t
{
uint8_t bLength ;
uint8_t bDescriptorType ;
uint8_t bDescriptorSubType ;
uint8_t bNumEmbMIDIJack;
[[no_unique_address]] elidable_array<uint8_t, N> baAssocJackID;
};
template <size_t N>
constexpr midi_desc_cs_endpoint_t<N> build_midi_endpoint(std::array<uint8_t, N> assocJackIDs)
{
midi_desc_cs_endpoint_t<N> header;
if constexpr(N != 0)
{
header.baAssocJackID = assocJackIDs;
}
header.bLength = sizeof(header);
header.bDescriptorType = TUSB_DESC_CS_ENDPOINT;
header.bDescriptorSubType = MIDI_CS_ENDPOINT_GENERAL;
header.bNumEmbMIDIJack= static_cast<uint8_t>(N);
return header;
}
} // midi
// TODO: this struct has children!
/// AUDIO Class-Specific AC Interface Header Descriptor (4.3.2)
template <size_t N>
struct TU_ATTR_PACKED audio_desc_cs_ac_header_t
{
uint8_t bLength ; ///< Size of this descriptor in bytes: 9.
uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE.
uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_HEADER.
uint16_t bcdADC ; ///< Audio Device Class Specification Release Number in Binary-Coded Decimal. Value: U16_TO_U8S_LE(0x0100).
uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors.
uint8_t bInCollection;
[[no_unique_address]] elidable_array<uint8_t, N> baInterfaceNr;
};
template <size_t N>
constexpr audio_desc_cs_ac_header_t<N> build_header(uint16_t bcdADC, std::array<uint8_t, N> baInterfaces)
{
audio_desc_cs_ac_header_t<N> header;
if constexpr(N != 0)
{
header.baInterfaceNr = baInterfaces;
}
header.bcdADC = bcdADC;
header.bLength = sizeof(header);
header.bDescriptorType = TUSB_DESC_CS_INTERFACE;
header.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_HEADER;
header.bInCollection= static_cast<uint8_t>(N);
// TODO: this should inclue children!
header.wTotalLength = sizeof(header);
return header;
}
constexpr audio_desc_cs_ac_header_t<1> build_header(uint16_t bcdADC, interface_ref oneInterf)
{
return build_header(bcdADC, std::array{oneInterf.pack()});
}
constexpr audio_desc_input_terminal_t build_input_terminal(audio_desc_input_terminal_t input_terminal)
{
input_terminal.bLength = sizeof(input_terminal);
input_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE;
input_terminal.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL;
return input_terminal;
}
constexpr audio_desc_input_terminal_t build_input_terminal(uint16_t wTerminalType, uint8_t bAssocTerminal, uint8_t bCSourceID, uint8_t bNrChannels, uint32_t bmChannelConfig, uint16_t bmControls, uint8_t iTerminal)
{
return build_input_terminal({
.wTerminalType = wTerminalType,
.bAssocTerminal = bAssocTerminal,
.bCSourceID = bCSourceID,
.bNrChannels = bNrChannels,
.bmChannelConfig = bmChannelConfig,
.bmControls = bmControls,
.iTerminal = iTerminal,
});
}
constexpr audio_desc_output_terminal_t build_output_terminal(audio_desc_output_terminal_t output_terminal)
{
output_terminal.bLength = sizeof(output_terminal);
output_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE;
output_terminal.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL;
return output_terminal;
}
constexpr audio_desc_output_terminal_t build_output_terminal(uint8_t bTerminalID, uint16_t wTerminalType, uint8_t bAssocTerminal, uint8_t bSourceID, uint8_t bCSourceID, uint16_t bmControls, uint8_t iTerminal)
{
return build_output_terminal({
.bTerminalID = bTerminalID,
.wTerminalType = wTerminalType,
.bAssocTerminal = bAssocTerminal,
.bSourceID = bSourceID,
.bCSourceID = bCSourceID,
.bmControls = bmControls,
.iTerminal = iTerminal,
});
}
constexpr audio_desc_feature_unit_t build_feature_unit(audio_desc_feature_unit_t feature_unit)
{
feature_unit.bLength = sizeof(feature_unit);
feature_unit.bDescriptorType = TUSB_DESC_CS_INTERFACE;
feature_unit.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_FEATURE_UNIT;
return feature_unit;
}
constexpr audio_desc_feature_unit_t build_feature_unit(uint8_t bUnitID, uint8_t bSourceID, uint32_t controls, uint8_t iTerminal)
{
return build_feature_unit({
.bUnitID = bUnitID,
.bSourceID = bSourceID,
.controls = controls,
.iTerminal = iTerminal,
});
}
constexpr audio_desc_clock_source_t build_clock_source(audio_desc_clock_source_t clock_source)
{
clock_source.bLength = sizeof(clock_source);
clock_source.bDescriptorType = TUSB_DESC_CS_INTERFACE;
clock_source.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE;
return clock_source;
}
constexpr audio_desc_clock_source_t build_clock_source(uint8_t bClockID, uint8_t bmAttributes, uint8_t bmControls, uint8_t bAssocTerminal, uint8_t iClockSource)
{
return build_clock_source({
.bClockID = bClockID,
.bmAttributes = bmAttributes,
.bmControls = bmControls,
.bAssocTerminal = bAssocTerminal,
.iClockSource = iClockSource,
});
}
constexpr audio_desc_clock_selector_t build_clock_selector(audio_desc_clock_selector_t clock_selector)
{
clock_selector.bLength = sizeof(clock_selector);
clock_selector.bDescriptorType = TUSB_DESC_CS_INTERFACE;
clock_selector.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR;
return clock_selector;
}
constexpr audio_desc_clock_selector_t build_clock_selector(uint8_t bClockID, uint8_t bNrInPins, uint8_t baCSourceID, uint8_t bmControls, uint8_t iClockSource)
{
return build_clock_selector({
.bClockID = bClockID,
.bNrInPins = bNrInPins,
.baCSourceID = baCSourceID,
.bmControls = bmControls,
.iClockSource = iClockSource,
});
}
constexpr audio_desc_clock_multiplier_t build_clock_multiplier(audio_desc_clock_multiplier_t clock_multiplier)
{
clock_multiplier.bLength = sizeof(clock_multiplier);
clock_multiplier.bDescriptorType = TUSB_DESC_CS_INTERFACE;
clock_multiplier.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER;
return clock_multiplier;
}
constexpr audio_desc_clock_multiplier_t build_clock_multiplier(uint8_t bClockID, uint8_t bCSourceID, uint8_t bmControls, uint8_t iClockSource)
{
return build_clock_multiplier({
.bClockID = bClockID,
.bCSourceID = bCSourceID,
.bmControls = bmControls,
.iClockSource = iClockSource,
});
}
}
namespace cdc {
GTU_TUSB_Tie_CS(header, cdc_desc_func_header_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER)
GTU_TUSB_Tie_CS(call_management, cdc_desc_func_call_management_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT)
GTU_TUSB_Tie_CS(acm, cdc_desc_func_acm_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT)
GTU_TUSB_Tie_CS(direct_line_management, cdc_desc_func_direct_line_management_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT)
GTU_TUSB_Tie_CS(telephone_ringer, cdc_desc_func_telephone_ringer_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_TELEPHONE_RINGER)
GTU_TUSB_Tie_CS(telephone_call_state_reporting_capabilities, cdc_desc_func_telephone_call_state_reporting_capabilities_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPACITY)
GTU_TUSB_Tie_CS(_union, cdc_desc_func_union_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION)
GTU_TUSB_Tie_CS(country_selection, cdc_desc_func_country_selection_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_COUNTRY_SELECTION)
GTU_TUSB_Tie_CS(telephone_operational_modes, cdc_desc_func_telephone_operational_modes_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_TELEPHONE_OPERATIONAL_MODES)
constexpr cdc_desc_func_header_t build_header(cdc_desc_func_header_t header)
{
header.bLength = sizeof(header);
header.bDescriptorType = TUSB_DESC_CS_INTERFACE;
header.bDescriptorSubType = CDC_FUNC_DESC_HEADER;
return header;
}
constexpr cdc_desc_func_header_t build_header(uint16_t bcdCDC)
{
return build_header({
.bcdCDC = bcdCDC,
});
}
constexpr cdc_desc_func_call_management_t build_call_management(cdc_desc_func_call_management_t call_management)
{
call_management.bLength = sizeof(call_management);
call_management.bDescriptorType = TUSB_DESC_CS_INTERFACE;
call_management.bDescriptorSubType = CDC_FUNC_DESC_CALL_MANAGEMENT;
return call_management;
}
constexpr cdc_desc_func_call_management_t build_call_management(uint8_t bmCapabilities, uint8_t bDataInterface)
{
return build_call_management({
.bmCapabilities = bmCapabilities,
.bDataInterface = bDataInterface,
});
}
constexpr cdc_desc_func_acm_t build_acm(cdc_desc_func_acm_t acm)
{
acm.bLength = sizeof(acm);
acm.bDescriptorType = TUSB_DESC_CS_INTERFACE;
acm.bDescriptorSubType = CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT;
return acm;
}
constexpr cdc_desc_func_acm_t build_acm(cdc_acm_capability_t bmCapabilities)
{
return build_acm({
.bmCapabilities = bmCapabilities,
});
}
constexpr cdc_desc_func_direct_line_management_t build_direct_line_management(cdc_desc_func_direct_line_management_t direct_line_management)
{
direct_line_management.bLength = sizeof(direct_line_management);
direct_line_management.bDescriptorType = TUSB_DESC_CS_INTERFACE;
direct_line_management.bDescriptorSubType = CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT;
return direct_line_management;
}
constexpr cdc_desc_func_direct_line_management_t build_direct_line_management(uint8_t bmCapabilities)
{
return build_direct_line_management({
.bmCapabilities = bmCapabilities,
});
}
constexpr cdc_desc_func_telephone_ringer_t build_telephone_ringer(cdc_desc_func_telephone_ringer_t telephone_ringer)
{
telephone_ringer.bLength = sizeof(telephone_ringer);
telephone_ringer.bDescriptorType = TUSB_DESC_CS_INTERFACE;
telephone_ringer.bDescriptorSubType = CDC_FUNC_DESC_TELEPHONE_RINGER;
return telephone_ringer;
}
constexpr cdc_desc_func_telephone_ringer_t build_telephone_ringer(uint8_t bRingerVolSteps, uint8_t bNumRingerPatterns)
{
return build_telephone_ringer({
.bRingerVolSteps = bRingerVolSteps,
.bNumRingerPatterns = bNumRingerPatterns,
});
}
constexpr cdc_desc_func_telephone_call_state_reporting_capabilities_t build_telephone_call_state_reporting_capabilities(cdc_desc_func_telephone_call_state_reporting_capabilities_t telephone_call_state_reporting_capabilities)
{
telephone_call_state_reporting_capabilities.bLength = sizeof(telephone_call_state_reporting_capabilities);
telephone_call_state_reporting_capabilities.bDescriptorType = TUSB_DESC_CS_INTERFACE;
telephone_call_state_reporting_capabilities.bDescriptorSubType = CDC_FUNC_DESC_TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPACITY;
return telephone_call_state_reporting_capabilities;
}
constexpr cdc_desc_func_telephone_call_state_reporting_capabilities_t build_telephone_call_state_reporting_capabilities(uint32_t bmCapabilities)
{
return build_telephone_call_state_reporting_capabilities({
.bmCapabilities = bmCapabilities,
});
}
constexpr cdc_desc_func_union_t build_union(cdc_desc_func_union_t _union)
{
_union.bLength = sizeof(_union);
_union.bDescriptorType = TUSB_DESC_CS_INTERFACE;
_union.bDescriptorSubType = CDC_FUNC_DESC_UNION;
return _union;
}
constexpr cdc_desc_func_union_t build_union(uint8_t bControlInterface, uint8_t bSubordinateInterface)
{
return build_union({
.bControlInterface = bControlInterface,
.bSubordinateInterface = bSubordinateInterface,
});
}
constexpr cdc_desc_func_country_selection_t build_country_selection(cdc_desc_func_country_selection_t country_selection)
{
country_selection.bLength = sizeof(country_selection);
country_selection.bDescriptorType = TUSB_DESC_CS_INTERFACE;
country_selection.bDescriptorSubType = CDC_FUNC_DESC_COUNTRY_SELECTION;
return country_selection;
}
constexpr cdc_desc_func_country_selection_t build_country_selection(uint8_t iCountryCodeRelDate, uint16_t wCountryCode)
{
return build_country_selection({
.iCountryCodeRelDate = iCountryCodeRelDate,
.wCountryCode = wCountryCode,
});
}
constexpr cdc_desc_func_telephone_operational_modes_t build_telephone_operational_modes(cdc_desc_func_telephone_operational_modes_t telephone_operational_modes)
{
telephone_operational_modes.bLength = sizeof(telephone_operational_modes);
telephone_operational_modes.bDescriptorType = TUSB_DESC_CS_INTERFACE;
telephone_operational_modes.bDescriptorSubType = CDC_FUNC_DESC_TELEPHONE_OPERATIONAL_MODES;
return telephone_operational_modes;
}
constexpr cdc_desc_func_telephone_operational_modes_t build_telephone_operational_modes(uint8_t bmCapabilities)
{
return build_telephone_operational_modes({
.bmCapabilities = bmCapabilities,
});
}
}
constexpr tusb_desc_interface_assoc_t build_interface_assoc(tusb_desc_interface_assoc_t interface_assoc)
{
interface_assoc.bLength = sizeof(interface_assoc);
interface_assoc.bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION;
return interface_assoc;
}
constexpr tusb_desc_interface_assoc_t build_interface_assoc(uint8_t bInterfaceCount, uint8_t bFunctionClass, uint8_t bFunctionSubClass, uint8_t bFunctionProtocol, uint8_t iFunction)
{
return build_interface_assoc({
.bInterfaceCount = bInterfaceCount,
.bFunctionClass = bFunctionClass,
.bFunctionSubClass = bFunctionSubClass,
.bFunctionProtocol = bFunctionProtocol,
.iFunction = iFunction,
});
}
}
#!/usr/bin/env ruby
file = File.read(ARGV[0]).gsub(/^\s*#define\s+USB_STRING.*$/,"")
known_values = {}
# TODO: Doesn't allow parens
proc1 = file.split(/(USB_STRING\([^\)]+)\)/).map do |seg|
if seg =~ /USB_STRING\((.*)/
if known_values[$1]
known_values[$1]
else
known_values[$1] = known_values.size+1
end.to_s + " /* #{$1} */"
else
seg
end
end.join("")
lmax = known_values.keys.map(&:size).max
proc2 = proc1.sub(/USB_STRING_MAX\(([^;]+)\);/, "(\\1 > #{lmax} ? \\1 : #{lmax}) + 1;").
sub("USB_STRING_TABLE()", "static const char *const usbd_desc_str[] = {\n\tnullptr,\n\t#{known_values.sort_by{|a,b| b}.map{|x,i|"/*[#{i}]=*/#{x}"}.join(",\n\t") }\n};")
if ARGV.size == 2
File.write(ARGV[1], proc2)
else
puts proc2
end
#!/usr/bin/env ruby
require 'cast'
file = ARGF.read
cc = C::Parser.new.parse(File.read('build2/simple.c').gsub(/typedef _([\w\s]+)va_list;/,"").gsub('__asm__ ("" "__xpg_strerror_r")',"").gsub(/__asm[^;]+;/, '').gsub(/\({[^}]+}\);?/,'').tap{|s|File.write("/tmp/mm.c", s)})
files = []
file.scan(/^GTU_import\("(.*)"\)/) do |im_file|
files << im_file
end
structs = cc.entities.select {|n| n.Declaration? and n.type.Struct? or next }
outputs = ""
file.scan(/^GTU_TUSB_Tie\((\w+),\s*(\w+),\s*(\w+)\)/) do |name, struct, tclass|
# [["type", "name"], ...]
ipair = structs.filter{|n| n.declarators.any?{|d|d.name == struct} }.map{|n|
n.type.members.map{|m| [(m.type.Struct? ? m.type.members.first.type.name : m.type.name), m.declarators.first.name]}
}.first
p struct
p ipair
cname = name.sub(/^_/,'')
outputs << <<~EOL
constexpr #{struct} build_#{cname}(#{struct} #{name})
{
#{name}.bLength = sizeof(#{name});
#{name}.bDescriptorType = #{tclass};
return #{name};
}
constexpr #{struct} build_#{cname}(#{ipair[3..-1].map{|x|x.join(" ")}.join(", ")})
{
return build_#{cname}({
#{ipair[3..-1].map {|type, field|
" .#{field} = #{field},"
}.join("\n")}
});
}
EOL
end
file.scan(/^GTU_TUSB_Tie_CS\((\w+),\s*(\w+),\s*(\w+),\s*(\w+)\)/) do |name, struct, tclass, subclass|
# [["type", "name"], ...]
ipair = structs.filter{|n| n.declarators.any?{|d|d.name == struct} }.map{|n|
n.type.members.map{|m| [(m.type.Struct? ? m.type.members.first.type.name : m.type.name), m.declarators.first.name]}
}.first
p struct
p ipair
cname = name.sub(/^_/,'')
outputs << <<~EOL
constexpr #{struct} build_#{cname}(#{struct} #{name})
{
#{name}.bLength = sizeof(#{name});
#{name}.bDescriptorType = #{tclass};
#{name}.bDescriptorSubType = #{subclass};
return #{name};
}
constexpr #{struct} build_#{cname}(#{ipair[3..-1].map{|x|x.join(" ")}.join(", ")})
{
return build_#{cname}({
#{ipair[3..-1].map {|type, field|
" .#{field} = #{field},"
}.join("\n")}
});
}
EOL
end
puts outputs
#include "tusb.h"
#include "pico/usb_reset_interface.h"
#include "pico/unique_id.h"
#include "descriptor_builder.hpp"
static constexpr uint16_t USBD_VID = 0x2E8A; // Raspberry Pi
static constexpr uint16_t USBD_PID = 0x000a; // Raspberry Pi Pico SDK CDC
static constexpr uint8_t USBD_MAX_POWER_MA = 250; //TODO: _mA custom user type?
// mutable metaprogramming with C++20
static constexpr auto serial_control = gtu::interface_counter::alloc<>;
static constexpr auto serial_data = gtu::interface_counter::alloc<>;
static constexpr auto rpi_reset = gtu::interface_counter::alloc<>;
static constexpr auto midi_control = gtu::interface_counter::alloc<>;
static constexpr auto midi_data = gtu::interface_counter::alloc<>;
#define USBD_CDC_CMD_MAX_SIZE (8)
#define USBD_CDC_IN_OUT_MAX_SIZE (64)
#define MIDI_BUFFERSIZE 64
static constexpr uint8_t cdc_epcmd = 1;
static constexpr uint8_t cdc_ep = 2;
static constexpr uint8_t midi_ep = 3;
/*
// Note: descriptors returned from callbacks must exist long enough for transfer to complete
*/
// TODO!
#define USB_STRING(abc) 0
consteval auto build_midi(
gtu::endpoint_addr epout, gtu::endpoint_addr epin, uint16_t epsize,
uint8_t string_index_todo,
std::array<gtu::interface_ref, 2> interfaces)
{
// Audio Control (AC) Interface
auto first = gtu::busb_interface(interfaces[0], {
.bAlternateSetting = 0,
.bInterfaceClass = TUSB_CLASS_AUDIO,
.bInterfaceSubClass = AUDIO_SUBCLASS_CONTROL,
.bInterfaceProtocol = AUDIO_FUNC_PROTOCOL_CODE_UNDEF,
.iInterface = string_index_todo // TODO: string!
}, gtu::store_tuple(
// AC Header
gtu::audio::build_header(0x0100, interfaces[1])
));
uint8_t id_emb_in = 1;
uint8_t id_ext_in = 2;
uint8_t id_emb_out = 3;
uint8_t id_ext_out = 4;
// MIDI Streaming (MS) Interface
auto second = gtu::busb_interface(interfaces[1], {
.bAlternateSetting = 0,
.bInterfaceClass = TUSB_CLASS_AUDIO,
.bInterfaceSubClass = AUDIO_SUBCLASS_MIDI_STREAMING,
.bInterfaceProtocol = AUDIO_FUNC_PROTOCOL_CODE_UNDEF,
.iInterface = 0 // TODO: string!
}, gtu::store_tuple(
// MS Header
gtu::audio::midi::build_header(0x0100,
// TODO: cables!
gtu::audio::midi::build_in_jack(MIDI_JACK_EMBEDDED, id_emb_in /* computed?*/, 0 /*str*/),
gtu::audio::midi::build_in_jack(MIDI_JACK_EXTERNAL, id_ext_in /* computed?*/, 0 /*str*/),
gtu::audio::midi::build_out_jack(MIDI_JACK_EMBEDDED, id_emb_out, 1, id_ext_in, 1, 0 /*str*/),
gtu::audio::midi::build_out_jack(MIDI_JACK_EXTERNAL, id_ext_out, 1, id_emb_in, 1, 0 /*str*/)
)
),std::array // interface endpoints
{
gtu::store_tuple(
gtu::audio::build_endpoint(epout, TUSB_XFER_BULK, epsize, 0),// Endpoint Out
gtu::audio::midi::build_midi_endpoint(std::array{id_emb_in}) // MS Endpoint (connected to embedded jack)
),
gtu::store_tuple(
gtu::audio::build_endpoint(epin, TUSB_XFER_BULK, epsize, 0),// Endpoint In
gtu::audio::midi::build_midi_endpoint(std::array{id_emb_out}) // MS Endpoint (connected to embedded jack)
)
});
return gtu::busb_interface_tuple(first, second);
}
consteval auto build_cdc(
gtu::endpoint_addr epout, gtu::endpoint_addr epin, uint16_t epsize,
gtu::endpoint_addr ep_notif, uint16_t ep_notif_size,
std::array<gtu::interface_ref, 2> interfaces)
{
// Interface Association
return gtu::busb_interface_assoc({
.bFunctionClass = TUSB_CLASS_CDC,
.bFunctionSubClass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL,
.bFunctionProtocol = CDC_COMM_PROTOCOL_NONE,
.iFunction = 0, // TODO: string!
}, gtu::store_tuple(
// CDC Control Interface
gtu::busb_interface(interfaces[0], {
.bAlternateSetting = 0,
.bInterfaceClass = TUSB_CLASS_CDC,
.bInterfaceSubClass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL,
.bInterfaceProtocol = CDC_COMM_PROTOCOL_NONE,
.iInterface = USB_STRING("Board CDC") // TODO: string!
},
// the next things are all ON the interface, ie they have the interface as the parent
gtu::store_tuple(
gtu::cdc::build_header(0x0120), // CDC Header
gtu::cdc::build_call_management(0, interfaces[1].pack()), // CDC Call
gtu::cdc::build_acm({ // CDC ACM: support line request
.support_line_request = true
}),
gtu::cdc::build_union({//CDC Union
.bControlInterface = interfaces[0].pack(),
.bSubordinateInterface = interfaces[1].pack(),
})
),
std::array // interface endpoints
{
// Endpoint Notification
gtu::build_endpoint(ep_notif, TUSB_XFER_INTERRUPT, ep_notif_size, 16),
}
),
//CDC Data Interface
gtu::busb_interface(interfaces[1], {
.bAlternateSetting = 0,
.bInterfaceClass = TUSB_CLASS_CDC_DATA,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0, // TODO: string!
},
std::array // interface endpoints
{
gtu::build_endpoint(epout, TUSB_XFER_BULK, epsize, 0)// Endpoint Out
,gtu::build_endpoint(epin, TUSB_XFER_BULK, epsize, 0) //Endpoint In
}
)
));
}
consteval auto build_reseter(gtu::interface_ref interface)
{
return gtu::busb_interface(interface, {
.bAlternateSetting = 0,
.bInterfaceClass = TUSB_CLASS_VENDOR_SPECIFIC,
.bInterfaceSubClass = RESET_INTERFACE_SUBCLASS,
.bInterfaceProtocol = RESET_INTERFACE_PROTOCOL,
.iInterface = USB_STRING("Reset") // TODO: string!,
});
}
struct unique_board_id_hex_str
{
constexpr static size_t size = PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1;
char c_str[size];
unique_board_id_hex_str()
{
pico_get_unique_board_id_string(c_str, sizeof(c_str));
}
};
const unique_board_id_hex_str usbd_serial_str{};
#define USB_STRING_TABLE() static const char *const usbd_desc_str[] = {"Template Failure"};
#define USB_STRING_MAX(n) n
constexpr uint8_t DESC_STR_MAX = USB_STRING_MAX(unique_board_id_hex_str::size);
USB_STRING_TABLE()
constexpr auto usbs = gtu::build_device({
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USBD_VID,
.idProduct = USBD_PID,
.bcdDevice = 0x0100,
.iManufacturer = USB_STRING("Raspberry Pi"),
.iProduct = USB_STRING("Pico"),
.iSerialNumber = USB_STRING(usbd_serial_str.c_str),
}, gtu::build_configuration(
0, USBD_MAX_POWER_MA,
build_cdc(gtu::ep_out<cdc_ep>(), gtu::ep_in<cdc_ep>(),
USBD_CDC_IN_OUT_MAX_SIZE,
gtu::ep_in<cdc_epcmd>(), USBD_CDC_CMD_MAX_SIZE,
std::array{
serial_control, serial_data
}),
build_reseter(rpi_reset),
build_midi(gtu::ep_out<midi_ep>(), gtu::ep_in<midi_ep>(),
MIDI_BUFFERSIZE,
USB_STRING("Trivial MIDI"),
std::array{
midi_control, midi_data
})
));
constexpr auto tmpz=build_cdc(gtu::ep_out<0>(), gtu::ep_in<0>(),
USBD_CDC_IN_OUT_MAX_SIZE,
gtu::ep_in<0>(), USBD_CDC_CMD_MAX_SIZE,
std::array{
serial_control, serial_data
});
static_assert(sizeof(tmpz) == 66);
static_assert(sizeof(tmpz.core) == 8);
static_assert(sizeof(tmpz.interfaces) == 58);
static_assert(sizeof(tmpz.interfaces.first) == 35);
static_assert(sizeof(tmpz.interfaces.rest) == 23);
static_assert(usbs.first.bNumConfigurations == 1);
// TODO: probably could use a union template
const uint8_t *tud_descriptor_device_cb() {
// TODO: get only by type?
return gtu::stored_extract<0, tusb_desc_device_t>(usbs);
}
const uint8_t *tud_descriptor_configuration_cb(__unused uint8_t index) {
return gtu::stored_extract<1, tusb_desc_configuration_t>(usbs);
}
const uint16_t *tud_descriptor_string_cb(uint8_t index, __unused uint16_t langid) {
static uint16_t desc_str[DESC_STR_MAX];
uint8_t len;
if (index == 0) {
desc_str[1] = 0x0409; // supported language is English
len = 1;
} else {
if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) {
return NULL;
}
const char *str = usbd_desc_str[index];
for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) {
desc_str[1 + len] = str[len];
}
}
// first byte is length (including header), second byte is string type
desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * len + 2));
return desc_str;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment