Support feather board, move to broken out source

This commit is contained in:
Justin C. Miller
2025-02-15 14:27:26 -08:00
parent 7a0cfebf52
commit 3a8fa7d08f
18 changed files with 411 additions and 105 deletions

42
src/edmfd/blink.cc Normal file
View File

@@ -0,0 +1,42 @@
#include "bsp/board_api.h"
#include "blink.hh"
namespace blink {
namespace {
// Durations unit are multiples of 25ms
static constexpr uint16_t unit = 25;
const uint8_t pattern_defs[size_t(pattern::_count)-2][2] = {
// off, on
{ 100, 20}, // suspended
{ 20, 20}, // not_mounted
{ 40, 1}, // mounted
{ 1, 1}, // transmit
};
pattern current_pattern = pattern::off;
uint32_t next_change = 0;
bool led_state = false;
inline const uint16_t get_duration(pattern p, bool on) {
if (p <= pattern::on) return 0;
return unit * pattern_defs[static_cast<uint8_t>(p)-2][on];
}
}
void set_pattern(pattern pat) {
if (pat == current_pattern) return;
led_state = (pat == pattern::on);
next_change = board_millis() + get_duration(pat, led_state);
current_pattern = pat;
board_led_write(led_state);
}
void task(uint32_t time) {
if (current_pattern == pattern::off || time < next_change)
return;
led_state = !led_state;
next_change = time + get_duration(current_pattern, led_state);
board_led_write(led_state);
}
} // namespace blink

19
src/edmfd/blink.hh Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
namespace blink {
enum class pattern : uint8_t {
off,
on,
suspended,
not_mounted,
mounted,
transmit,
_count,
};
void set_pattern(pattern pat);
void task(uint32_t time);
} // namespace blink

131
src/edmfd/hid.cc Normal file
View File

@@ -0,0 +1,131 @@
#include "bsp/board_api.h"
#include "blink.hh"
#include "hid.hh"
#include "usb_descriptors.hh"
#include "class/hid/hid_device.h"
extern "C" {
// TinyUSB Callbacks
void tud_hid_report_complete_cb(uint8_t, uint8_t const*, uint16_t);
uint16_t tud_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t*, uint16_t);
void tud_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const*, uint16_t);
}
namespace hid {
void send_report(descriptor desc, uint32_t btn)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) return;
switch(desc)
{
case descriptor::gamepad: {
// use to avoid send multiple consecutive zero report for keyboard
static bool has_gamepad_key = false;
hid_gamepad_report_t report = {
.x = 0, .y = 0, .z = 0, .rz = 0, .rx = 0, .ry = 0,
.hat = 0, .buttons = 0
};
if ( btn ) {
report.hat = GAMEPAD_HAT_UP;
report.buttons = GAMEPAD_BUTTON_A;
tud_hid_report(static_cast<uint8_t>(desc), &report, sizeof(report));
has_gamepad_key = true;
} else {
report.hat = GAMEPAD_HAT_CENTERED;
report.buttons = 0;
if (has_gamepad_key) tud_hid_report(static_cast<uint8_t>(desc), &report, sizeof(report));
has_gamepad_key = false;
}
}
break;
default: break;
}
}
// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
void task(uint32_t time)
{
// Poll every 10ms
static constexpr uint32_t interval_ms = 10;
static uint32_t next_update = 0;
if (time < next_update) return; // not enough time
next_update = time + interval_ms;
uint32_t const btn = board_button_read();
// Remote wakeup
if ( tud_suspended() && btn )
{
// Wake up host if we are in suspend mode
// and REMOTE_WAKEUP feature is enabled by host
tud_remote_wakeup();
}else
{
// Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
send_report(descriptor::gamepad, btn);
}
}
} // namespace hid
// Invoked when sent REPORT successfully to host
// Application can use this to send the next report
// Note: For composite reports, report[0] is report ID
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len)
{
(void) instance;
(void) len;
auto next_report_id = descriptor(report[0] + 1u);
if (next_report_id < descriptor::_count) {
hid::send_report(next_report_id, board_button_read());
}
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
// TODO not Implemented
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
(void) instance;
const auto desc = descriptor(report_id);
if (report_type == HID_REPORT_TYPE_OUTPUT) {
// Set button LEDs
if (desc == descriptor::gamepad) {
// bufsize should be (at least) 1
if ( bufsize < 1 ) return;
uint8_t const btn_leds = buffer[0];
if (btn_leds & 1) {
// Capslock On: disable blink, turn led on
blink::set_pattern(blink::pattern::on);
} else {
// Caplocks Off: back to normal blink
blink::set_pattern(blink::pattern::mounted);
}
}
}
}

7
src/edmfd/hid.hh Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
namespace hid {
void task(uint32_t time);
} // namespace hid

163
src/edmfd/main.cc Normal file
View File

@@ -0,0 +1,163 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "tusb_config.h"
#include "bsp/board_api.h"
#include "tusb.h"
#include "logging/log.hh"
#include "blink.hh"
#include "hid.hh"
#include "mcp23017.hh"
extern "C" {
// TinyUSB callbacks
void tud_mount_cb();
void tud_unmount_cb();
void tud_suspend_cb(bool);
void tud_resume_cb();
}
// I2C reserves some addresses for special purposes. We exclude these from the scan.
// These are any addresses of the form 000 0xxx or 111 1xxx
bool reserved_addr(uint8_t addr) {
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
}
void setup_i2c()
{
i2c_init(i2c_default, 100 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
}
int write_mcp(uint8_t addr, uint8_t reg, uint8_t value, const char *regname)
{
uint8_t txdata[2] = { reg, value };
int ret = i2c_write_blocking(i2c_default, addr, txdata, 2, false);
if (ret)
printf("Error %02x writing %02x to %02x:%s", ret, value, addr, regname);
else
printf("Wrote %02x to %02x:%s", value, addr, regname);
return ret;
}
void setup_mcp()
{
// A7 is button, B0 is LED
static constexpr uint8_t addr = 0x20;
if (write_mcp(addr, 0x00, 0xff, "IODIRA")) return;
if (write_mcp(addr, 0x10, 0x00, "IODIRB")) return;
if (write_mcp(addr, 0x02, 0xff, "GPINTENA")) return;
if (write_mcp(addr, 0x06, 0xff, "GPPUA")) return;
}
void i2c_scan()
{
printf("\nI2C Bus Scan\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for (int addr = 0; addr < (1 << 7); ++addr) {
if (addr % 16 == 0) {
printf("%02x ", addr);
}
// Perform a 1-byte dummy read from the probe address. If a slave
// acknowledges this address, the function returns the number of bytes
// transferred. If the address byte is ignored, the function returns
// -1.
// Skip over any reserved addresses.
int ret;
uint8_t rxdata;
if (reserved_addr(addr))
ret = PICO_ERROR_GENERIC;
else
ret = i2c_read_blocking(i2c_default, addr, &rxdata, 1, false);
printf(ret < 0 ? "." : "@");
printf(addr % 16 == 15 ? "\n" : " ");
}
printf("Done.\n");
}
int main(void)
{
setup_default_uart();
stdio_init_all();
puts("\n");
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
board_init();
log::info("board initialized");
tud_init(BOARD_TUD_RHPORT);
if (board_init_after_tusb)
board_init_after_tusb();
log::info("tinyusb initialized");
setup_i2c();
log::info("i2c initialized");
using mcp23017::reg;
mcp23017::gpios gpios {0x20};
uint8_t iocon = 0xba;
gpios.read(reg::iocona, &iocon);
gpios.read(reg::iodira, &iocon);
/*
gpios.write(reg::iodira, 0xff);
gpios.write(reg::gpintena, 0xff);
gpios.write(reg::gppua, 0xff);
*/
while (1) {
uint32_t time = board_millis();
tud_task(); // tinyusb device task
blink::task(time);
hid::task(time);
}
}
//--------------------------------------------------------------------+
// Device callbacks
//--------------------------------------------------------------------+
// Invoked when device is mounted
void tud_mount_cb() {
blink::set_pattern(blink::pattern::mounted);
}
// Invoked when device is unmounted
void tud_umount_cb() {
blink::set_pattern(blink::pattern::not_mounted);
}
// Invoked when usb bus is suspended
// argumen : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool) {
blink::set_pattern(blink::pattern::suspended);
}
// Invoked when usb bus is resumed
void tud_resume_cb() {
blink::pattern pat = tud_mounted() ? blink::pattern::mounted : blink::pattern::not_mounted;
blink::set_pattern(pat);
}

60
src/edmfd/mcp23017.cc Normal file
View File

@@ -0,0 +1,60 @@
#include <stdio.h>
#include "hardware/i2c.h"
#include "pico/stdlib.h"
#include "logging/log.hh"
#include "mcp23017.hh"
namespace mcp23017 {
static const char * regnames[] = {
"iodira", "iodirb",
"ipola", "ipolb",
"gpintena", "gpintenb",
"defvala", "defvalb",
"intcona", "intconb",
"iocona", "ioconb",
"gppua", "gppub",
"intfa", "intfb",
"intcapa", "intcapb",
"gpioa", "gpiob",
"olata", "olatb",
};
gpios::gpios(uint8_t addr) :
m_addr {addr}
{}
int
gpios::read(reg r, uint8_t *value)
{
uint8_t txdata = uint8_t(r);
int ret = i2c_write_burst_blocking(i2c_default, m_addr, &txdata, 1);
if (ret < 0) {
log::error("writing read address {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
return ret;
}
ret = i2c_read_blocking(i2c_default, m_addr, value, 1, false);
if (ret < 0) {
log::error("reading {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
} else {
log::info("read {%02x:%s}: 0x%02x", m_addr, regnames[uint8_t(r)], *value);
}
return ret;
}
int
gpios::write(reg r, uint8_t value)
{
uint8_t txdata[2] = { uint8_t(r), value };
int ret = i2c_write_blocking(i2c_default, m_addr, txdata, sizeof(txdata), false);
if (ret != PICO_OK) {
log::error("writing {%02x:%s}: %d", m_addr, regnames[uint8_t(r)], ret);
} else {
log::info("wrote {%02x:%s}: 0x%02x", m_addr, regnames[uint8_t(r)], value);
}
return ret;
}
} // namespace mcp23017

31
src/edmfd/mcp23017.hh Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
namespace mcp23017 {
enum class reg : uint8_t {
iodira, iodirb,
ipola, ipolb,
gpintena, gpintenb,
defvala, defvalb,
intcona, intconb,
iocona, ioconb,
gppua, gppub,
intfa, intfb,
intcapa, intcapb,
gpioa, gpiob,
olata, olatb,
};
class gpios
{
public:
gpios(uint8_t addr);
int read(reg r, uint8_t *value);
int write(reg r, uint8_t value);
private:
uint8_t m_addr;
};
} // namespace mcp23017

9
src/edmfd/proto.cc Normal file
View File

@@ -0,0 +1,9 @@
#include "proto.hh"
namespace proto {
void parse(const uint8_t *buffer, unsigned len) {
}
} // namespace proto

8
src/edmfd/proto.hh Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <stdint.h>
namespace proto {
void parse(const uint8_t *buffer, unsigned len);
} // namespace proto

14
src/edmfd/screen.cc Normal file
View File

@@ -0,0 +1,14 @@
#include "screen.hh"
namespace screen {
palette g_palette = {0};
void set_color(unsigned index, color_t color) {
if (index >= num_colors) return;
g_palette[index] = color;
}
const palette & get_colors() { return g_palette; }
} // namespace screen

15
src/edmfd/screen.hh Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <array>
#include <stdint.h>
namespace screen {
inline constexpr unsigned color_bits = 4;
inline constexpr unsigned num_colors = 1 << color_bits;
using color_t = uint16_t;
using palette = std::array<color_t, num_colors>;
void set_color(unsigned index, color_t color);
const palette & get_colors();
} // namepsace screen

View File

@@ -0,0 +1,208 @@
#include "bsp/board_api.h"
#include "tusb.h"
#include "usb_descriptors.hh"
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
#define USB_PID (0x0200 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
#define USB_VID 0x4a33 // J3 in ascii
#define USB_BCD 0x0201
extern "C" {
// TinyUSB callbacks
uint8_t const * tud_descriptor_device_cb();
uint8_t const * tud_hid_descriptor_report_cb(uint8_t);
uint8_t const * tud_descriptor_configuration_cb(uint8_t);
uint16_t const * tud_descriptor_string_cb(uint8_t, uint16_t);
uint8_t const * tud_descriptor_bos_cb();
}
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = USB_BCD,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_VID,
.idProduct = USB_PID,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const * tud_descriptor_device_cb(void) {
return (uint8_t const *) &desc_device;
}
//--------------------------------------------------------------------+
// HID Report Descriptor
//--------------------------------------------------------------------+
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_GAMEPAD ( HID_REPORT_ID(static_cast<uint8_t>(descriptor::gamepad)) )
};
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const * tud_hid_descriptor_report_cb(uint8_t) {
return desc_hid_report;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// array of pointer to string descriptors
char16_t const *string_desc_arr[size_t(strings::_count)] = {
u"\u0409", // 0: is supported language is English (0x0409)
u"j3gaming", // 1: Manufacturer
u"EDMFD", // 2: Product
nullptr, // 3: Serials will use unique ID if possible
u"EDMFD Screen", // 4: screen interface
};
static constexpr size_t desc_len = 32;
static uint16_t desc_str[desc_len + 1];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
(void) langid;
size_t chr_count = 0;
strings string_id = static_cast<strings>(index);
switch (string_id) {
case strings::serial:
chr_count = board_usb_get_serial(desc_str + 1, desc_len);
break;
default:
if (string_id >= strings::_count) return nullptr;
uint16_t const *src = reinterpret_cast<uint16_t const*>(string_desc_arr[index]);
uint16_t *dest = desc_str + 1;
while (*src) {
*dest++ = *src++;
++chr_count;
}
break;
}
// first byte is length (including header), second byte is string type
desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
return desc_str;
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
static constexpr size_t config_total_len = TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_VENDOR_DESC_LEN;
uint8_t const desc_configuration[config_total_len] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, uint8_t(interface::_count), 0, config_total_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(uint8_t(interface::HID), 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), uint8_t(endpoint::HID), CFG_TUD_HID_EP_BUFSIZE, 5),
// Interface number, string index, EP Out & IN address, EP size
TUD_VENDOR_DESCRIPTOR(uint8_t(interface::vendor), uint8_t(strings::interface), uint8_t(endpoint::vendor_out), uint8_t(endpoint::vendor_in), 64),
// CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size.
//TUD_CDC_DESCRIPTOR(uint8_t(interface::CDC), 4, uint8_t(endpoint::CDCnotify), 8, uint8_t(endpoint::CDCout), uint8_t(endpoint::CDCin), 64)
};
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
(void) index; // for multiple configurations
// This example use the same configuration for both high and full speed mode
return desc_configuration;
}
//--------------------------------------------------------------------+
// BOS Descriptor
//--------------------------------------------------------------------+
static constexpr uint8_t bos_total_len = (TUD_BOS_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN);
uint8_t const desc_bos[] = {
// total length, number of device caps
TUD_BOS_DESCRIPTOR(bos_total_len, 1),
// Microsoft OS 2.0 descriptor
TUD_BOS_MS_OS_20_DESCRIPTOR(ms_os_20_desc_len, uint8_t(vendor_req::microsoft)),
};
static_assert(sizeof(desc_bos) == bos_total_len, "Incorrect BOS length");
uint8_t const * tud_descriptor_bos_cb() { return desc_bos; }
uint8_t const desc_ms_os_20[ms_os_20_desc_len] = {
// Set header
U16_TO_U8S_LE(0x000A), // length
U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), // type
U32_TO_U8S_LE(0x06030000), // windows version
U16_TO_U8S_LE(ms_os_20_desc_len), // total length
// Configuration subset header
U16_TO_U8S_LE(0x0008), // length
U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), // type
0, // configuration index
0, // reserved
U16_TO_U8S_LE(ms_os_20_desc_len-0x0A), // configuration total length
// Function Subset header: length, type, first interface, reserved, subset length
U16_TO_U8S_LE(0x0008), // length
U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), // type
uint8_t(interface::vendor), // first interface
0, // reserved
U16_TO_U8S_LE(ms_os_20_desc_len-0x0A-0x08), // subset total length
// MS OS 2.0 Compatible ID descriptor
U16_TO_U8S_LE(0x0014), // length
U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), // type
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatible ID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible
// MS OS 2.0 Registry property descriptor: length, type
U16_TO_U8S_LE(ms_os_20_desc_len-0x0A-0x08-0x08-0x14), // length
U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), // type
U16_TO_U8S_LE(0x0007), // wPropertyDataType
U16_TO_U8S_LE(0x002A), // wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16
'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00,
'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00,
U16_TO_U8S_LE(0x0050), // wPropertyDataLength and bPropertyData: “{4b028455-9b3c-4cf4-8992-3c4c40b76d96}\0”.
'{', 0x00, '4', 0x00, 'B', 0x00, '0', 0x00, '2', 0x00, '8', 0x00, '4', 0x00, '5', 0x00, '5', 0x00, '-', 0x00,
'9', 0x00, 'B', 0x00, '3', 0x00, 'C', 0x00, '-', 0x00, '4', 0x00, 'C', 0x00, 'F', 0x00, '4', 0x00, '-', 0x00,
'8', 0x00, '9', 0x00, '9', 0x00, '2', 0x00, '-', 0x00, '3', 0x00, 'C', 0x00, '4', 0x00, 'C', 0x00, '4', 0x00,
'0', 0x00, 'B', 0x00, '7', 0x00, '6', 0x00, 'D', 0x00, '9', 0x00, '6', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00,
};
static_assert(sizeof(desc_ms_os_20) == ms_os_20_desc_len, "Incorrect MS OS Desc size");

View File

@@ -0,0 +1,26 @@
#pragma once
enum class descriptor {
gamepad = 1,
_count
};
enum class strings : uint8_t {
language_id,
manufacturer,
product,
serial,
interface,
_count
};
enum class interface : uint8_t { HID, vendor, _count };
enum class endpoint : uint8_t { HID = 0x81, vendor_out = 0x02, vendor_in = 0x83 };
enum class vendor_req : uint8_t {
microsoft = 1,
edmfd
};
extern uint8_t const desc_ms_os_20[];
static constexpr size_t ms_os_20_desc_len = 0xb2;

64
src/edmfd/vendor.cc Normal file
View File

@@ -0,0 +1,64 @@
#include "bsp/board_api.h"
#include "class/vendor/vendor_device.h"
#include "blink.hh"
#include "screen.hh"
#include "usb_descriptors.hh"
extern "C" {
// TinyUSB callbacks
bool tud_Ivendor_control_xfer_cb(uint8_t, uint8_t, tusb_control_request_t const*);
void tud_vendor_rx_cb(uint8_t, uint8_t const*, uint16_t);
void tud_vendor_tx_cb(uint8_t, uint32_t);
}
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) {
// nothing to with DATA & ACK stage
if (stage != CONTROL_STAGE_SETUP) return true;
if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR &&
vendor_req(request->bRequest) == vendor_req::microsoft &&
request->wIndex == 7) {
// Get Microsoft OS 2.0 compatible descriptor
return tud_control_xfer(rhport, request, const_cast<void*>(static_cast<const void*>(desc_ms_os_20)), ms_os_20_desc_len);
}
// stall unknown request
return false;
}
// Invoked when received new data
void tud_vendor_rx_cb(uint8_t, uint8_t const* buffer, uint16_t bufsize) {
if (!bufsize) return;
switch (buffer[0]) {
case 0x02: { // set colors
for (unsigned i = 1; i < bufsize-3u; i += 3u) {
unsigned index = buffer[i];
screen::color_t color =
*reinterpret_cast<const screen::color_t*>(&buffer[i+1]);
screen::set_color(index, color);
}
break;
}
case 0x03: {// get colors
const screen::palette &pal = screen::get_colors();
tud_vendor_write(&pal, sizeof(pal));
tud_vendor_write_flush();
break;
}
default:
break;
}
tud_vendor_read_flush();
}
// Invoked when last rx (tx?) transfer finished
void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes) {
}