commit 90c38b731ef552319585583101c52f3973e6b34d Author: Justin C. Miller Date: Fri Jan 31 01:21:03 2025 -0800 Initial version, HID and vendor echo working diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6536ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7733770 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pico-sdk"] + path = pico-sdk + url = https://github.com/raspberrypi/pico-sdk.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..b24aa9f --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -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 +} diff --git a/.vscode/cmake-kits.json b/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/.vscode/cmake-kits.json @@ -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}" + } + } +] \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/.vscode/extensions.json @@ -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" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d921c35 --- /dev/null +++ b/.vscode/launch.json @@ -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" + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..992389d --- /dev/null +++ b/.vscode/settings.json @@ -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" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1b6a876 --- /dev/null +++ b/.vscode/tasks.json @@ -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", + } + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1e26983 --- /dev/null +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/pico-sdk b/pico-sdk new file mode 160000 index 0000000..95ea6ac --- /dev/null +++ b/pico-sdk @@ -0,0 +1 @@ +Subproject commit 95ea6acad131124694cda1c162c52cd30e0aece0 diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..a0721d0 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,84 @@ +# This is a copy of /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}) diff --git a/src/blink.cc b/src/blink.cc new file mode 100644 index 0000000..87f6dc7 --- /dev/null +++ b/src/blink.cc @@ -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(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 \ No newline at end of file diff --git a/src/blink.hh b/src/blink.hh new file mode 100644 index 0000000..762cf2a --- /dev/null +++ b/src/blink.hh @@ -0,0 +1,19 @@ +#pragma once +#include + +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 \ No newline at end of file diff --git a/src/hid.cc b/src/hid.cc new file mode 100644 index 0000000..fede1dd --- /dev/null +++ b/src/hid.cc @@ -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(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(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); + } + } + } +} \ No newline at end of file diff --git a/src/hid.hh b/src/hid.hh new file mode 100644 index 0000000..0b1c3a5 --- /dev/null +++ b/src/hid.hh @@ -0,0 +1,7 @@ +#pragma once + +namespace hid { + +void task(uint32_t time); + +} // namespace hid \ No newline at end of file diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..d1ddb1a --- /dev/null +++ b/src/main.cc @@ -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 +#include +#include + +#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); +} \ No newline at end of file diff --git a/src/tusb_config.h b/src/tusb_config.h new file mode 100644 index 0000000..3fc5e1b --- /dev/null +++ b/src/tusb_config.h @@ -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_ */ \ No newline at end of file diff --git a/src/usb_descriptors.cc b/src/usb_descriptors.cc new file mode 100644 index 0000000..71e039d --- /dev/null +++ b/src/usb_descriptors.cc @@ -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(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(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(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; +} \ No newline at end of file diff --git a/src/usb_descriptors.hh b/src/usb_descriptors.hh new file mode 100644 index 0000000..240aa28 --- /dev/null +++ b/src/usb_descriptors.hh @@ -0,0 +1,6 @@ +#pragma once + +enum class descriptor { + gamepad = 1, + _count +}; \ No newline at end of file diff --git a/src/vendor.cc b/src/vendor.cc new file mode 100644 index 0000000..ec87ef3 --- /dev/null +++ b/src/vendor.cc @@ -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(); +}