mirror of
https://github.com/justinian/edmfd_firmware.git
synced 2025-12-11 00:54:34 -08:00
Support feather board, move to broken out source
This commit is contained in:
42
src/edmfd/blink.cc
Normal file
42
src/edmfd/blink.cc
Normal 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
19
src/edmfd/blink.hh
Normal 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
131
src/edmfd/hid.cc
Normal 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
7
src/edmfd/hid.hh
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace hid {
|
||||
|
||||
void task(uint32_t time);
|
||||
|
||||
} // namespace hid
|
||||
163
src/edmfd/main.cc
Normal file
163
src/edmfd/main.cc
Normal 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
60
src/edmfd/mcp23017.cc
Normal 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
31
src/edmfd/mcp23017.hh
Normal 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
9
src/edmfd/proto.cc
Normal 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
8
src/edmfd/proto.hh
Normal 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
14
src/edmfd/screen.cc
Normal 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
15
src/edmfd/screen.hh
Normal 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
|
||||
208
src/edmfd/usb_descriptors.cc
Normal file
208
src/edmfd/usb_descriptors.cc
Normal 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");
|
||||
|
||||
26
src/edmfd/usb_descriptors.hh
Normal file
26
src/edmfd/usb_descriptors.hh
Normal 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
64
src/edmfd/vendor.cc
Normal 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) {
|
||||
}
|
||||
Reference in New Issue
Block a user