reform

MNT Reform: Open Source Portable Computer
Log (Feed) | Files | Refs (Tags) | README

commit b6369522e4b0502125f11c14a3cc6f1c99e9d68f
parent f0590a615f46d817d876fe3b769c4a507cc54193
Author: Lukas F. Hartmann <lukas@mntre.com>
Date:   Thu,  2 Feb 2023 14:49:08 +0100

reform2-trackball2-fw: add RP2040 version of trackball firmware

Diffstat:
Areform2-trackball2-fw/CMakeLists.txt | 34++++++++++++++++++++++++++++++++++
Areform2-trackball2-fw/README.md | 25+++++++++++++++++++++++++
Areform2-trackball2-fw/build.sh | 10++++++++++
Areform2-trackball2-fw/flash.sh | 4++++
Areform2-trackball2-fw/src/main.c | 312+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Areform2-trackball2-fw/src/tusb_config.h | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Areform2-trackball2-fw/src/usb_descriptors.c | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 734 insertions(+), 0 deletions(-)

diff --git a/reform2-trackball2-fw/CMakeLists.txt b/reform2-trackball2-fw/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.5) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../tinyusb/hw/bsp/family_support.cmake) + +# gets PROJECT name for the example (e.g. <BOARD>-<DIR_NAME>) +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT}) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(${PROJECT}) + +# Example source +target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c + ) + +# Example include +target_include_directories(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +# Configure compilation flags and libraries for the example... see the corresponding function +# in hw/bsp/FAMILY/family.cmake for details. +family_configure_device_example(${PROJECT}) + +target_link_libraries(${PROJECT} PRIVATE pico_stdlib tinyusb_device tinyusb_board hardware_pwm hardware_i2c) + +target_compile_definitions(${PROJECT} PUBLIC + PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64 + ) diff --git a/reform2-trackball2-fw/README.md b/reform2-trackball2-fw/README.md @@ -0,0 +1,25 @@ +# MNT Reform Trackball Firmware (RP2040 version) + +## Build dependencies + +Required: `cmake`, `tinyusb`, `pico-sdk`. + +Two folders up (next to the main `reform` repo), clone the following: + +`git clone https://github.com/hathach/tinyusb` +`git clone https://github.com/raspberrypi/pico-sdk` + +## Building + +Then, just run `build.sh`. + +This yields the firmware in `build`. + +## Flashing + +For flashing, you can use: https://github.com/raspberrypi/picotool + +See the convenience script `flash.sh`. + +If the trackball should not enter flashing mode (i.e. present itself as USB storage), you can try pressing reset twice in quick succession. + diff --git a/reform2-trackball2-fw/build.sh b/reform2-trackball2-fw/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +export PICO_SDK_PATH=$(pwd)/../../pico-sdk + +mkdir -p build +cd build +cmake -DFAMILY=rp2040 .. + +make + diff --git a/reform2-trackball2-fw/flash.sh b/reform2-trackball2-fw/flash.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +picotool load build/reform2-trackball2-fw.uf2 + diff --git a/reform2-trackball2-fw/src/main.c b/reform2-trackball2-fw/src/main.c @@ -0,0 +1,312 @@ +/* + * The MIT License (MIT) + * + * Copyright 2019 Ha Thach (tinyusb.org) + * Copyright 2023 MNT Research GmbH (mntre.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "bsp/board.h" +#include "tusb.h" + +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" + +#define PIN_SDA 0 +#define PIN_SCL 1 + +#define PIN_BTN1 20 +#define PIN_BTN2 21 +#define PIN_BTN3 19 +#define PIN_BTN4 18 +#define PIN_BTN5 17 + +#define PIN_LEDS 24 + +#define ADDR_SENSOR (0x79) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +// Interface index depends on the order in configuration descriptor +enum { + ITF_MOUSE = 0 +}; + +void hid_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + tusb_init(); + + gpio_pull_up(PIN_BTN1); + gpio_pull_up(PIN_BTN2); + gpio_pull_up(PIN_BTN3); + gpio_pull_up(PIN_BTN4); + gpio_pull_up(PIN_BTN5); + + gpio_init(PIN_LEDS); + gpio_set_dir(PIN_LEDS, true); // output + + i2c_init(i2c0, 100 * 1000); + gpio_set_function(PIN_SDA, GPIO_FUNC_I2C); + gpio_set_function(PIN_SCL, GPIO_FUNC_I2C); + + bi_decl(bi_2pins_with_func(PIN_SDA, PIN_SCL, GPIO_FUNC_I2C)); + + char buf[] = {0x7f, 0x00, 0x00, 0x00}; + i2c_write_blocking(i2c0, ADDR_SENSOR, buf, 2, false); + + buf[0] = 0x05; + buf[1] = 0x01; + i2c_write_blocking(i2c0, ADDR_SENSOR, buf, 2, false); + + while (1) + { + tud_task(); // tinyusb device task + + hid_task(); + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : 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 remote_wakeup_en) +{ + (void) remote_wakeup_en; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ +} + +//--------------------------------------------------------------------+ +// USB HID +//--------------------------------------------------------------------+ + +typedef struct TU_ATTR_PACKED +{ + uint8_t buttons; /**< buttons mask for currently pressed buttons in the mouse. */ + int8_t x; /**< Current delta x movement of the mouse. */ + int8_t y; /**< Current delta y movement on the mouse. */ + int8_t wheel; /**< Current delta wheel movement on the mouse. */ + int8_t pan; // using AC Pan +} hid_trackball_report_t; + +bool tud_hid_n_trackball_report(uint8_t instance, uint8_t report_id, + uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) +{ + hid_trackball_report_t report = + { + .buttons = buttons, + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal + }; + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +uint8_t prev_buttons = 0; +uint8_t scroll_toggle = 0; + +int __attribute__((optimize("Os"))) delay300ns() { + // ~300ns + asm volatile( + "mov r0, #10\n" // 1 cycle + "loop1: sub r0, r0, #1\n" // 1 cycle + "bne loop1\n" // 2 cycles if loop taken, 1 if not + ); + return 0; +} + +// TODO to use LEDs, rather implement PIOs like in Pocket Reform's pocket-hid +void led_test(uint32_t rgb) { + for (int y=0; y<5; y++) { + for (int i=23; i>=0; i--) { + uint32_t bit = !!(rgb & (1<<i)); + if (bit) { + // one + gpio_put(PIN_LEDS, 1); + delay300ns(); + delay300ns(); + delay300ns(); + gpio_put(PIN_LEDS, 0); + // ~600ms delay + delay300ns(); + delay300ns(); + } else { + // zero + gpio_put(PIN_LEDS, 1); + delay300ns(); + gpio_put(PIN_LEDS, 0); + // ~1.2ms delay + delay300ns(); + delay300ns(); + delay300ns(); + delay300ns(); + //delay300ns(); + } + } + } +} + +int rgb_channel = 0; +int rgb_val = 0; + +void hid_task(void) +{ + // Poll every 10ms + const uint32_t interval_ms = 10; + static uint32_t start_ms = 0; + + int8_t buf[] = {0x7f, 0x00, 0x00, 0x00}; + + if ( board_millis() - start_ms < interval_ms) return; // not enough time + + start_ms += interval_ms; + + int btn = !gpio_get(PIN_BTN1); + + // 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(); + } + + // LED test + /*led_test(rgb_val<<rgb_channel); + rgb_val++; + if (rgb_val > 0xff) { + rgb_val = 0; + rgb_channel += 8; + if (rgb_channel > 16) { + rgb_channel = 0; + } + }*/ + + /*------------- Mouse -------------*/ + if ( tud_hid_n_ready(ITF_MOUSE) ) + { + buf[0] = 0x02; + i2c_write_blocking(i2c0, ADDR_SENSOR, buf, 1, true); + i2c_read_blocking(i2c0, ADDR_SENSOR, buf, 1, false); + + int btn1 = !gpio_get(PIN_BTN1); + int btn2 = !gpio_get(PIN_BTN2); + int btn3 = !gpio_get(PIN_BTN3); + int btn4 = !gpio_get(PIN_BTN4); + int btn5 = !gpio_get(PIN_BTN5); + + // pressing both wheel buttons together turns on sticky wheel mode + if (btn1 || btn2 || btn4) { + scroll_toggle = 0; + } else if (btn5 && btn3) { + scroll_toggle = 1; + } + + uint8_t buttons = btn1 | (btn2<<1) | (btn4<<2); + + if (buf[0] & 0xf0) { + buf[0] = 0x03; + i2c_write_blocking(i2c0, ADDR_SENSOR, buf, 1, true); + i2c_read_blocking(i2c0, ADDR_SENSOR, buf, 2, false); + + int8_t nx = buf[0]; + int8_t ny = buf[1]; + + // no button, right + down, no scroll pan + if (btn3 || btn5 || scroll_toggle) { + tud_hid_n_trackball_report(ITF_MOUSE, 1, buttons, 0, 0, -2*ny, 2*nx); + } else { + tud_hid_n_trackball_report(ITF_MOUSE, 1, buttons, 2*nx, 2*ny, 0, 0); + } + } else { + if (buttons != prev_buttons) { + tud_hid_n_mouse_report(ITF_MOUSE, 1, buttons, 0, 0, 0, 0); + } + } + + prev_buttons = buttons; + } +} + + +// 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 itf, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) +{ + // TODO not Implemented + (void) itf; + (void) reqlen; + + if (report_type != HID_REPORT_TYPE_FEATURE) return 0; + + if (report_id == 2) { + //buffer[0] = 2; // multiplier + //return 1; + } + + 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 itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) +{ + // TODO set LED based on CAPLOCK, NUMLOCK etc... + (void) itf; + (void) report_id; + (void) report_type; + (void) buffer; + (void) bufsize; +} diff --git a/reform2-trackball2-fw/src/tusb_config.h b/reform2-trackball2-fw/src/tusb_config.h @@ -0,0 +1,106 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to max (auto) speed for MCU with internal HighSpeed PHY +#ifndef BOARD_DEVICE_RHPORT_SPEED + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_HID 2 +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 8 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/reform2-trackball2-fw/src/usb_descriptors.c b/reform2-trackball2-fw/src/usb_descriptors.c @@ -0,0 +1,243 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" + +/* 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 (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .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 +//--------------------------------------------------------------------+ + + +// Mouse Report Descriptor Template +#define TUD_HID_REPORT_DESC_MNTMOUSE() \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + HID_REPORT_ID ( 1 ) \ + HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + /* Left, Right, Middle, Backward, Forward buttons */ \ + HID_REPORT_COUNT( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 3 bit padding */ \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + /* X, Y position [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ) ,\ + HID_REPORT_ID ( 2 ) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RESOLUTION_MULTIPLIER ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + HID_PHYSICAL_MIN ( 1 ), \ + HID_PHYSICAL_MAX ( 12 ), \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Vertical wheel scroll [-127, 127] */ \ + HID_REPORT_ID ( 1 ) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_PHYSICAL_MIN ( 0 ), \ + HID_PHYSICAL_MAX ( 0 ), \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + HID_REPORT_ID ( 1 ) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \ + /* Horizontal wheel scroll [-127, 127] */ \ + HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \ + HID_LOGICAL_MIN ( 0x81 ), \ + HID_LOGICAL_MAX ( 0x7f ), \ + HID_PHYSICAL_MIN ( 0x81 ), \ + HID_PHYSICAL_MAX ( 0x7f ), \ + HID_REPORT_COUNT( 1 ), \ + HID_REPORT_SIZE ( 8 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + +uint8_t const desc_hid_report1[] = +{ + TUD_HID_REPORT_DESC_MNTMOUSE() +}; + +// 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 itf) +{ + if (itf == 0) + { + return desc_hid_report1; + } + + return NULL; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +enum +{ + ITF_NUM_HID1, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN) + +#define EPNUM_HID1 0x81 + +uint8_t const desc_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 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(ITF_NUM_HID1, 4, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report1), EPNUM_HID1, CFG_TUD_HID_EP_BUFSIZE, 10) +}; + +// 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 + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "MNT Research", // 1: Manufacturer + "Reform Trackball (RP2040)", // 2: Product + "MREFATBC20R02", // 3: Serials, should use chip ID + "Mouse Interface", // 4: Interface 1 String +}; + +static uint16_t _desc_str[32]; + +// 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; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i<chr_count; i++) + { + _desc_str[1+i] = str[i]; + } + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2); + + return _desc_str; +}