Initial version, HID and vendor echo working

This commit is contained in:
Justin C. Miller
2025-01-31 01:21:03 -08:00
commit 90c38b731e
20 changed files with 932 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build
compile_commands.json

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "pico-sdk"]
path = pico-sdk
url = https://github.com/raspberrypi/pico-sdk.git

22
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"configurations": [
{
"name": "Pico",
"includePath": [
"${workspaceFolder}/**",
"${userHome}/.pico-sdk/sdk/2.1.0/**"
],
"forcedInclude": [
"${userHome}/.pico-sdk/sdk/2.1.0/src/common/pico_base_headers/include/pico.h",
"${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h"
],
"defines": [],
"compilerPath": "${userHome}/.pico-sdk/toolchain/13_3_Rel1/bin/arm-none-eabi-gcc",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"cStandard": "c17",
"cppStandard": "c++14",
"intelliSenseMode": "linux-gcc-arm"
}
],
"version": 4
}

15
.vscode/cmake-kits.json vendored Normal file
View File

@@ -0,0 +1,15 @@
[
{
"name": "Pico",
"compilers": {
"C": "${command:raspberry-pi-pico.getCompilerPath}",
"CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}"
},
"environmentVariables": {
"PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}"
},
"cmakeSettings": {
"Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}"
}
}
]

9
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"marus25.cortex-debug",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.vscode-serial-monitor",
"raspberry-pi.raspberry-pi-pico"
]
}

70
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,70 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Pico Debug (Cortex-Debug)",
"cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"executable": "${command:raspberry-pi-pico.launchTargetPath}",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"gdbPath": "${command:raspberry-pi-pico.getGDBPath}",
"device": "${command:raspberry-pi-pico.getChipUppercase}",
"configFiles": [
"interface/cmsis-dap.cfg",
"target/${command:raspberry-pi-pico.getTarget}.cfg"
],
"svdFile": "${userHome}/.pico-sdk/sdk/2.1.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd",
"runToEntryPoint": "main",
// Fix for no_flash binaries, where monitor reset halt doesn't do what is expected
// Also works fine for flash binaries
"overrideLaunchCommands": [
"monitor reset init",
"load \"${command:raspberry-pi-pico.launchTargetPath}\""
],
"openOCDLaunchCommands": [
"adapter speed 5000"
]
},
{
"name": "Pico Debug (Cortex-Debug with external OpenOCD)",
"cwd": "${workspaceRoot}",
"executable": "${command:raspberry-pi-pico.launchTargetPath}",
"request": "launch",
"type": "cortex-debug",
"servertype": "external",
"gdbTarget": "localhost:3333",
"gdbPath": "${command:raspberry-pi-pico.getGDBPath}",
"device": "${command:raspberry-pi-pico.getChipUppercase}",
"svdFile": "${userHome}/.pico-sdk/sdk/2.1.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd",
"runToEntryPoint": "main",
// Fix for no_flash binaries, where monitor reset halt doesn't do what is expected
// Also works fine for flash binaries
"overrideLaunchCommands": [
"monitor reset init",
"load \"${command:raspberry-pi-pico.launchTargetPath}\""
]
},
{
"name": "Pico Debug (C++ Debugger)",
"type": "cppdbg",
"request": "launch",
"cwd": "${workspaceRoot}",
"program": "${command:raspberry-pi-pico.launchTargetPath}",
"MIMode": "gdb",
"miDebuggerPath": "${command:raspberry-pi-pico.getGDBPath}",
"miDebuggerServerAddress": "localhost:3333",
"debugServerPath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"debugServerArgs": "-f interface/cmsis-dap.cfg -f target/${command:raspberry-pi-pico.getTarget}.cfg -c \"adapter speed 5000\"",
"serverStarted": "Listening on port .* for gdb connections",
"filterStderr": true,
"hardwareBreakpoints": {
"require": true,
"limit": 4
},
"preLaunchTask": "Flash",
"svdPath": "${userHome}/.pico-sdk/sdk/2.1.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd"
},
]
}

39
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,39 @@
{
"cmake.options.statusBarVisibility": "hidden",
"cmake.options.advanced": {
"build": {
"statusBarVisibility": "hidden"
},
"launch": {
"statusBarVisibility": "hidden"
},
"debug": {
"statusBarVisibility": "hidden"
}
},
"cmake.configureOnEdit": false,
"cmake.automaticReconfigure": false,
"cmake.configureOnOpen": false,
"cmake.generator": "Ninja",
"cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.29.9/bin/cmake",
"C_Cpp.debugShortcut": false,
"terminal.integrated.env.windows": {
"PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.1.0",
"PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/13_3_Rel1",
"Path": "${env:USERPROFILE}/.pico-sdk/toolchain/13_3_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.1.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.29.9/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}"
},
"terminal.integrated.env.osx": {
"PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.1.0",
"PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/13_3_Rel1",
"PATH": "${env:HOME}/.pico-sdk/toolchain/13_3_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.1.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.29.9/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}"
},
"terminal.integrated.env.linux": {
"PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.1.0",
"PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/13_3_Rel1",
"PATH": "${env:HOME}/.pico-sdk/toolchain/13_3_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.1.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.29.9/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}"
},
"raspberry-pi-pico.cmakeAutoConfigure": true,
"raspberry-pi-pico.useCmakeTools": false,
"raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.29.9/bin/cmake",
"raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja"
}

58
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,58 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Compile Project",
"type": "process",
"isBuildCommand": true,
"command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja",
"args": ["-C", "${workspaceFolder}/build"],
"group": "build",
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": "$gcc",
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe"
}
},
{
"label": "Run Project",
"type": "process",
"command": "${env:HOME}/.pico-sdk/picotool/2.1.0/picotool/picotool",
"args": [
"load",
"${command:raspberry-pi-pico.launchTargetPath}",
"-fx"
],
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/picotool/2.1.0/picotool/picotool.exe"
}
},
{
"label": "Flash",
"type": "process",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/${command:raspberry-pi-pico.getTarget}.cfg",
"-c",
"adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit"
],
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
}
}
]
}

52
CMakeLists.txt Normal file
View File

@@ -0,0 +1,52 @@
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.0)
set(toolchainVersion 13_3_Rel1)
set(picotoolVersion 2.1.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico CACHE STRING "Board type")
cmake_minimum_required(VERSION 3.12)
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/pico-sdk)
include(pico_sdk_import.cmake)
project(EDMFD C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS 3000)
pico_sdk_init()
add_compile_options(
-Wall
-Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int
-Wno-unused-function # we have some for the docs that aren't called
)
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
add_compile_options(-Wno-maybe-uninitialized)
endif()
add_executable(edmfd)
target_include_directories(edmfd PUBLIC src)
target_sources(edmfd PUBLIC
src/blink.cc
src/hid.cc
src/main.cc
src/usb_descriptors.cc
src/vendor.cc
)
pico_enable_stdio_usb(edmfd 1)
pico_add_extra_outputs(edmfd)
target_link_libraries(edmfd PUBLIC pico_stdlib pico_unique_id tinyusb_device tinyusb_board)

1
pico-sdk Submodule

Submodule pico-sdk added at 95ea6acad1

84
pico_sdk_import.cmake Normal file
View File

@@ -0,0 +1,84 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

42
src/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/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/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 "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/hid.hh Normal file
View File

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

88
src/main.cc Normal file
View File

@@ -0,0 +1,88 @@
/*
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "tusb_config.h"
#include "bsp/board_api.h"
#include "tusb.h"
#include "blink.hh"
#include "hid.hh"
extern "C" {
// TinyUSB callbacks
void tud_mount_cb();
void tud_unmount_cb();
void tud_suspend_cb(bool);
void tud_resume_cb();
}
extern "C"
int main(void)
{
board_init();
tud_init(BOARD_TUD_RHPORT);
if (board_init_after_tusb)
board_init_after_tusb();
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);
}

110
src/tusb_config.h Normal file
View File

@@ -0,0 +1,110 @@
/*
* 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
//--------------------------------------------------------------------+
// Board Specific Configuration
//--------------------------------------------------------------------+
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_TUD_RHPORT
#define BOARD_TUD_RHPORT 0
#endif
// RHPort max operational speed can defined by board.mk
#ifndef BOARD_TUD_MAX_SPEED
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
// defined by compiler flags for flexibility
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#endif
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_NONE
#endif
#ifndef CFG_TUSB_DEBUG
#define CFG_TUSB_DEBUG 0
#endif
// Enable Device stack
#define CFG_TUD_ENABLED 1
// Default is max speed that hardware controller could support with on-chip PHY
#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
/* 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 1
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 1
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE 16
#define CFG_TUD_VENDOR_TX_BUFSIZE 64
#define CFG_TUD_VENDOR_RX_BUFSIZE 64
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

154
src/usb_descriptors.cc Normal file
View File

@@ -0,0 +1,154 @@
#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 (0x0000 | _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 0x0200
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);
}
//--------------------------------------------------------------------+
// 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
//--------------------------------------------------------------------+
enum class strings : uint8_t {
language_id,
manufacturer,
product,
serial,
interface,
_count
};
// 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"screen control", // 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;
enum class interface : uint8_t { HID, vendor, _count };
enum class endpoint : uint8_t { HID = 0x81, vendor_out = 0x02, vendor_in = 0x82 };
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;
}

6
src/usb_descriptors.hh Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
enum class descriptor {
gamepad = 1,
_count
};

20
src/vendor.cc Normal file
View File

@@ -0,0 +1,20 @@
#include "bsp/board_api.h"
#include "vendor/vendor_device.h"
#include "blink.hh"
extern "C" {
// TinyUSB callbacks
void tud_vendor_rx_cb(uint8_t, uint8_t const*, uint16_t);
void tud_vendor_tx_cb(uint8_t, uint32_t);
}
// Invoked when received new data
void tud_vendor_rx_cb(uint8_t, uint8_t const* buffer, uint16_t bufsize) {
tud_vendor_write(buffer, bufsize);
tud_vendor_read_flush();
}
// Invoked when last rx (tx?) transfer finished
void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes) {
tud_vendor_write_flush();
}