From b473b139852a92a030a3405feff9d10f1a771569 Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Tue, 5 Mar 2024 16:26:38 -0500 Subject: [PATCH] [pico-stuff] initial upload --- pico-stuff/.clang-format | 5 + pico-stuff/CMakeLists.txt | 24 ++ pico-stuff/README.md | 9 + pico-stuff/monstrosity.c | 154 +++++++++ pico-stuff/morse.c | 100 ++++++ pico-stuff/pico_extras_import_optional.cmake | 59 ++++ pico-stuff/pico_sdk_import.cmake | 73 +++++ pico-stuff/tablegen.py | 64 ++++ pico-stuff/test.c | 324 +++++++++++++++++++ pico-stuff/tusb_config.h | 118 +++++++ pico-stuff/usb_descriptors.c | 244 ++++++++++++++ pico-stuff/usb_descriptors.h | 37 +++ 12 files changed, 1211 insertions(+) create mode 100644 pico-stuff/.clang-format create mode 100644 pico-stuff/CMakeLists.txt create mode 100644 pico-stuff/README.md create mode 100644 pico-stuff/monstrosity.c create mode 100644 pico-stuff/morse.c create mode 100644 pico-stuff/pico_extras_import_optional.cmake create mode 100644 pico-stuff/pico_sdk_import.cmake create mode 100644 pico-stuff/tablegen.py create mode 100644 pico-stuff/test.c create mode 100644 pico-stuff/tusb_config.h create mode 100644 pico-stuff/usb_descriptors.c create mode 100644 pico-stuff/usb_descriptors.h diff --git a/pico-stuff/.clang-format b/pico-stuff/.clang-format new file mode 100644 index 0000000..f53616d --- /dev/null +++ b/pico-stuff/.clang-format @@ -0,0 +1,5 @@ +--- +BasedOnStyle: LLVM +UseTab: Always +IndentWidth: 4 +TabWidth: 4 diff --git a/pico-stuff/CMakeLists.txt b/pico-stuff/CMakeLists.txt new file mode 100644 index 0000000..eb12cdd --- /dev/null +++ b/pico-stuff/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.13) + +project(sandbox) + +include(pico_sdk_import.cmake) + +add_executable(test) + +target_sources(test PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/test.c + ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c + ) + +# Make sure TinyUSB can find tusb_config.h +target_include_directories(test PUBLIC + ${CMAKE_CURRENT_LIST_DIR}) + +pico_sdk_init() + +pico_enable_stdio_usb(test 1) + +target_link_libraries(test PUBLIC pico_stdlib tinyusb_device pico_unique_id) + +pico_add_extra_outputs(test) diff --git a/pico-stuff/README.md b/pico-stuff/README.md new file mode 100644 index 0000000..adfab78 --- /dev/null +++ b/pico-stuff/README.md @@ -0,0 +1,9 @@ +# raspberry pi pico playground + +- `morse.c`: morse blinker (with a shell over serial to send messages to blink) +- `monstrosity.c`: driver code for random components i had laying around +- `test.c`: morse code HID keyboard (needs two buttons). can use vim with it +- `tablegen.py`: helper to generate keymaps for `test.c` morse + +you will need to put the raspberry pi pico sdk somewhere and export `PICO_SDK_PATH` to point to it. +also, it could be useful to export `CMAKE_EXPORT_COMPILE_COMMANDS=1` to get a `compile_commands.json`. diff --git a/pico-stuff/monstrosity.c b/pico-stuff/monstrosity.c new file mode 100644 index 0000000..f5cc88f --- /dev/null +++ b/pico-stuff/monstrosity.c @@ -0,0 +1,154 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/stdlib.h" +#include "pico/stdio.h" +#include +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/pwm.h" + +const uint BOARD_LED_PIN = PICO_DEFAULT_LED_PIN; +const uint LED_PIN = 15; +const uint LED2_PIN = 17; +const uint BUTTON_PIN = 9; +const uint ROT_CLK = 11; +const uint ROT_DT = 10; + + +const uint DOT_PERIOD_MS = 100; + +const char *morse_letters[] = { + ".-", // A + "-...", // B + "-.-.", // C + "-..", // D + ".", // E + "..-.", // F + "--.", // G + "....", // H + "..", // I + ".---", // J + "-.-", // K + ".-..", // L + "--", // M + "-.", // N + "---", // O + ".--.", // P + "--.-", // Q + ".-.", // R + "...", // S + "-", // T + "..-", // U + "...-", // V + ".--", // W + "-..-", // X + "-.--", // Y + "--.." // Z +}; + +void put_morse_letter(uint led_pin, const char *pattern) { + for (; *pattern; ++pattern) { + gpio_put(led_pin, 1); + if (*pattern == '.') + sleep_ms(DOT_PERIOD_MS); + else + sleep_ms(DOT_PERIOD_MS * 3); + gpio_put(led_pin, 0); + sleep_ms(DOT_PERIOD_MS * 1); + } + sleep_ms(DOT_PERIOD_MS * 2); +} + +void put_morse_str(uint led_pin, char *str) { + for (; *str; ++str) { + if (*str >= 'A' && *str <= 'Z') { + put_morse_letter(led_pin, morse_letters[*str - 'A']); + } else if (*str >= 'a' && *str <= 'z') { + put_morse_letter(led_pin, morse_letters[*str - 'a']); + } else if (*str == ' ') { + sleep_ms(DOT_PERIOD_MS * 4); + } + } +} + +int rotCt = 0; + +int main() { + stdio_init_all(); +#ifndef PICO_DEFAULT_LED_PIN +#warning picoboard/blinky example requires a board with a regular LED +#else + gpio_set_function(LED2_PIN, GPIO_FUNC_PWM); + uint slice_num = pwm_gpio_to_slice_num(LED2_PIN); + pwm_config config = pwm_get_default_config(); + pwm_config_set_clkdiv(&config, 4.f); + pwm_init(slice_num, &config, true); + + + gpio_init(ROT_CLK); + gpio_init(ROT_DT); + gpio_set_dir(ROT_CLK, GPIO_IN); + gpio_set_dir(ROT_DT, GPIO_IN); + + gpio_init(BOARD_LED_PIN); + gpio_set_dir(BOARD_LED_PIN, GPIO_OUT); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + gpio_init(BUTTON_PIN); + gpio_set_dir(BUTTON_PIN, GPIO_IN); + + int rotClkState = gpio_get(ROT_CLK); + int lastClkState = rotClkState; + + const int bufSz = 50; + char buf[bufSz]; + int bufIdx = 0; + + put_morse_str(LED_PIN, "hi"); + put_morse_str(BOARD_LED_PIN, "fox"); + + while (1) { + unsigned int c = getchar_timeout_us(20); + if (c != PICO_ERROR_TIMEOUT) { + printf("%c", c); + if (c == 0x0d) { + buf[bufIdx] = '\0'; + printf("\n"); + put_morse_str(LED_PIN, buf); + printf(">>> "); + bufIdx = 0; + } else if (bufIdx < bufSz-1) { + buf[bufIdx++] = c; + } + } + if (gpio_get(BUTTON_PIN)) { + gpio_put(BOARD_LED_PIN, 1); + } else { + gpio_put(BOARD_LED_PIN, 0); + } + + rotClkState = gpio_get(ROT_CLK); + if (lastClkState != rotClkState && rotClkState == 1) { + if (gpio_get(ROT_DT) != rotClkState) { + rotCt -= 10; + } else { + rotCt += 10; + } + if (rotCt >= 256) { + rotCt = 255; + } else if (rotCt < 0) { + rotCt = 0; + } + printf("%d\n", rotCt); + pwm_set_gpio_level(LED2_PIN, rotCt*rotCt); + } + lastClkState = rotClkState; + } +#endif +} diff --git a/pico-stuff/morse.c b/pico-stuff/morse.c new file mode 100644 index 0000000..8a75e4e --- /dev/null +++ b/pico-stuff/morse.c @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdio.h" +#include "pico/stdlib.h" +#include "hardware/gpio.h" + +const uint DOT_PERIOD_MS = 100; + +const char *morse_letters[] = { + ".-", // A + "-...", // B + "-.-.", // C + "-..", // D + ".", // E + "..-.", // F + "--.", // G + "....", // H + "..", // I + ".---", // J + "-.-", // K + ".-..", // L + "--", // M + "-.", // N + "---", // O + ".--.", // P + "--.-", // Q + ".-.", // R + "...", // S + "-", // T + "..-", // U + "...-", // V + ".--", // W + "-..-", // X + "-.--", // Y + "--.." // Z +}; + +void put_morse_letter(uint led_pin, const char *pattern) { + for (; *pattern; ++pattern) { + gpio_put(led_pin, 1); + if (*pattern == '.') + sleep_ms(DOT_PERIOD_MS); + else + sleep_ms(DOT_PERIOD_MS * 3); + gpio_put(led_pin, 0); + sleep_ms(DOT_PERIOD_MS * 1); + } + sleep_ms(DOT_PERIOD_MS * 2); +} + +void put_morse_str(uint led_pin, char *str) { + for (; *str; ++str) { + if (*str >= 'A' && *str <= 'Z') { + put_morse_letter(led_pin, morse_letters[*str - 'A']); + } else if (*str >= 'a' && *str <= 'z') { + put_morse_letter(led_pin, morse_letters[*str - 'a']); + } else if (*str == ' ') { + sleep_ms(DOT_PERIOD_MS * 4); + } + } +} + +int main() { + stdio_init_all(); +#ifndef PICO_DEFAULT_LED_PIN +#warning picoboard/blinky example requires a board with a regular LED +#else + // const uint LED_PIN = PICO_DEFAULT_LED_PIN; + const uint LED_PIN = 15; + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + const int bufSz = 50; + char buf[bufSz]; + int bufIdx = 0; + + put_morse_str(LED_PIN, "quick brown"); + + while (1) { + unsigned int c = getchar_timeout_us(20); + if (c != PICO_ERROR_TIMEOUT) { + printf("%c", c); + if (c == 0x0d) { + buf[bufIdx] = '\0'; + printf("\n"); + put_morse_str(LED_PIN, buf); + printf(">>> "); + bufIdx = 0; + } else if (bufIdx < bufSz-1) { + buf[bufIdx++] = c; + } + } + } +#endif +} diff --git a/pico-stuff/pico_extras_import_optional.cmake b/pico-stuff/pico_extras_import_optional.cmake new file mode 100644 index 0000000..692e14a --- /dev/null +++ b/pico-stuff/pico_extras_import_optional.cmake @@ -0,0 +1,59 @@ +# This is a copy of /external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_extras + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT pico_extras) + message("Downloading Raspberry Pi Pico Extras") + FetchContent_Populate(pico_extras) + set(PICO_EXTRAS_PATH ${pico_extras_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + endif() + endif () +endif () + +if (PICO_EXTRAS_PATH) + set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") + set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + + get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") + if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") + endif () + + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) +endif() \ No newline at end of file diff --git a/pico-stuff/pico_sdk_import.cmake b/pico-stuff/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/pico-stuff/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# 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 () + +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") + +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 master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + 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/pico-stuff/tablegen.py b/pico-stuff/tablegen.py new file mode 100644 index 0000000..b6ce377 --- /dev/null +++ b/pico-stuff/tablegen.py @@ -0,0 +1,64 @@ +# generate a flat binary tree for morse decoding + +from functools import reduce + +# https://stackoverflow.com/questions/71099952 +encode_table = { + "A": ".-", + "B": "-...", + "C": "-.-.", + "D": "-..", + "E": ".", + "F": "..-.", + "G": "--.", + "H": "....", + "I": "..", + "J": ".---", + "K": "-.-", + "L": ".-..", + "M": "--", + "N": "-.", + "O": "---", + "P": ".--.", + "Q": "--.-", + "R": ".-.", + "S": "...", + "T": "-", + "U": "..-", + "V": "...-", + "W": ".--", + "X": "-..-", + "Y": "-.--", + "Z": "--..", + "0": "-----", + "1": ".----", + "2": "..---", + "3": "...--", + "4": "....-", + "5": ".....", + "6": "-....", + "7": "--...", + "8": "---..", + "9": "----.", + ".": ".-.-.-", + ",": "--..--", + "?": "..--..", + "\\n": "-..-..", + "\\b": ".-.-", + "\\e": "..--", + " ": "----", +} + +maxLen = reduce(lambda x, y: max(x, y), [len(code) for code in encode_table.values()]) + +letters = ['*'] + ['*'] * 2**(maxLen + 1) + +for sym, code in encode_table.items(): + idx = 1 + for lett in code: + idx *= 2 + if lett == '-': + idx += 1; + letters[idx] = sym + +print("".join(letters)) diff --git a/pico-stuff/test.c b/pico-stuff/test.c new file mode 100644 index 0000000..9c203c2 --- /dev/null +++ b/pico-stuff/test.c @@ -0,0 +1,324 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "device/usbd.h" +#include "hardware/gpio.h" +#include "pico/stdio.h" +#include "pico/stdlib.h" +#include "tusb.h" +#include "usb_descriptors.h" +#include +#include + +#define max(X, Y) ((X) > (Y) ? (X) : (Y)) +#define min(X, Y) ((X) < (Y) ? (X) : (Y)) + +const uint DOT_PERIOD_MS = 100; +// wait this time before registering a morse letter +const uint MORSE_WAIT_MS = DOT_PERIOD_MS * 3; + +const char *morse_letters[] = { + ".-", // A + "-...", // B + "-.-.", // C + "-..", // D + ".", // E + "..-.", // F + "--.", // G + "....", // H + "..", // I + ".---", // J + "-.-", // K + ".-..", // L + "--", // M + "-.", // N + "---", // O + ".--.", // P + "--.-", // Q + ".-.", // R + "...", // S + "-", // T + "..-", // U + "...-", // V + ".--", // W + "-..-", // X + "-.--", // Y + "--.." // Z +}; + +void put_morse_letter(uint led_pin, const char *pattern) { + for (; *pattern; ++pattern) { + gpio_put(led_pin, 1); + if (*pattern == '.') + sleep_ms(DOT_PERIOD_MS); + else + sleep_ms(DOT_PERIOD_MS * 3); + gpio_put(led_pin, 0); + sleep_ms(DOT_PERIOD_MS * 1); + } + sleep_ms(DOT_PERIOD_MS * 2); +} + +void put_morse_str(uint led_pin, char *str) { + for (; *str; ++str) { + if (*str >= 'A' && *str <= 'Z') { + put_morse_letter(led_pin, morse_letters[*str - 'A']); + } else if (*str >= 'a' && *str <= 'z') { + put_morse_letter(led_pin, morse_letters[*str - 'a']); + } else if (*str == ' ') { + sleep_ms(DOT_PERIOD_MS * 4); + } + } +} + +// decodes a letter +char morseDecode(char *code) { + // flat binary tree (see gentable.py) + const char morseTable[] = + "**ETIANMSURWDKGOHVF\eL\bPJBXCYZQ* " + "54*3***2*******16*******7***8*90************?********.**************" + "\n**************,*************"; + + int tableIdx = 1; + + for (; *code && tableIdx < sizeof(morseTable) / sizeof(char) - 1; code++) { + tableIdx *= 2; + if (*code == '-') { + tableIdx += 1; + } + } + + return morseTable[tableIdx]; +} + +const uint DEBOUNCE_CHECK_MS = 5; +const uint DEBOUNCE_PRESS_MS = 10; +const uint DEBOUNCE_RELEASE_MS = 50; + +typedef struct DebounceState { + bool pressed; + bool changed; + + // ms timestamp of last change + uint64_t lastChange; + // ms since last change + uint64_t sinceChange; + // ms timestamp of last check + uint64_t lastCheck; + // countdown timer + uint count; +} DebounceState; + +// call this in the main loop +void debounce_get(bool rawState, DebounceState *state) { + uint time_ms = time_us_64() / 1000; + + if (state->lastChange == 0) + state->lastChange = time_ms; + state->sinceChange = time_ms - state->lastChange; + + state->changed = false; + + if (time_ms - state->lastCheck < DEBOUNCE_CHECK_MS) { + return; + } + state->lastCheck = time_ms; + + if (rawState == state->pressed) { + // set timer when state is stable + state->count = state->pressed ? DEBOUNCE_RELEASE_MS / DEBOUNCE_CHECK_MS + : DEBOUNCE_PRESS_MS / DEBOUNCE_CHECK_MS; + } else { + // process change + if (--state->count == 0) { + // timer expires + state->pressed = rawState; + state->changed = true; + state->lastChange = time_ms; + state->count = state->pressed + ? DEBOUNCE_RELEASE_MS / DEBOUNCE_CHECK_MS + : DEBOUNCE_PRESS_MS / DEBOUNCE_CHECK_MS; + } + } + + return; +} + +static void send_hid_report(uint8_t report_id, char key) { + if (!tud_ready()) + return; + + switch (report_id) { + case REPORT_ID_KEYBOARD: { + // counter for how many null reports to send + // idk why sending less makes it register as a continuous keypress + // sometimes + const uint NULL_REPORTS = 15; + static uint nullCounter = NULL_REPORTS; + if (key) { + uint8_t keycode[6] = {0}; + if ('A' <= key && 'Z' >= key) { + keycode[0] = HID_KEY_A + (key - 'A'); + } else if ('1' <= key && key <= '9') { + keycode[0] = HID_KEY_1 + (key - '1'); + } else { + switch (key) { + case '0': + // 0 is after rather than before like in ASCII so special + // treatment here + keycode[0] = HID_KEY_0; + break; + case '\n': + keycode[0] = HID_KEY_ENTER; + break; + case '\b': + keycode[0] = HID_KEY_BACKSPACE; + break; + case '\e': + keycode[0] = HID_KEY_ESCAPE; + break; + case ' ': + keycode[0] = HID_KEY_SPACE; + break; + } + } + + tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode); + nullCounter = NULL_REPORTS; + } else { + if (nullCounter > 0) { + nullCounter--; + // this print statement adds a necessary delay for the null reports + printf("sent null %d\n", nullCounter); + tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL); + } + } + break; + } + default: + break; + } +} + +// 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; + + uint8_t next_report_id = report[0] + 1; + + if (next_report_id < REPORT_ID_COUNT) { + // send_hid_report(next_report_id, board_button_read()); + } +} + +const uint PIN_DASH = 17; +const uint PIN_DOT = 16; + +#define BUF_LEN 50 + +// return letter read +char morse_task(void) { + static DebounceState dotState = {0}; + static DebounceState dashState = {0}; + + static char buf[BUF_LEN + 1]; + buf[BUF_LEN] = '\0'; + static uint bufIdx = 0; + + debounce_get(!gpio_get(PIN_DOT), &dotState); + debounce_get(!gpio_get(PIN_DASH), &dashState); + + uint64_t sincePress = min(dotState.sinceChange, dashState.sinceChange); + + if (bufIdx < BUF_LEN) { + if (dotState.pressed && dotState.changed) { + buf[bufIdx++] = '.'; + } else if (dashState.pressed && dashState.changed) { + buf[bufIdx++] = '-'; + } + } + + char c = '\0'; + + if (!dotState.pressed && !dashState.pressed) { + if (sincePress >= MORSE_WAIT_MS && bufIdx != 0) { + buf[bufIdx] = '\0'; + c = morseDecode(buf); + printf("%c", c); + + if (c == '\b') { + printf(" \b"); + } + + bufIdx = 0; + } + } + + if (dotState.pressed || dashState.pressed) { + if (tud_suspended()) { + tud_remote_wakeup(); + } + } + + return c; +} + +// 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; +} + +// 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; +} + +int main(void) { + stdio_init_all(); +#ifndef PICO_DEFAULT_LED_PIN +#warning picoboard/blinky example requires a board with a regular LED +#else + const uint LED_PIN = PICO_DEFAULT_LED_PIN; + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); +#endif + + tusb_init(); + + put_morse_str(LED_PIN, "a"); + + gpio_init(PIN_DASH); + gpio_init(PIN_DOT); + gpio_set_dir(PIN_DASH, GPIO_IN); + gpio_pull_up(PIN_DASH); + gpio_set_dir(PIN_DOT, GPIO_IN); + gpio_pull_up(PIN_DOT); + + while (1) { + tud_task(); + + char c = morse_task(); + send_hid_report(REPORT_ID_KEYBOARD, c); + } +} diff --git a/pico-stuff/tusb_config.h b/pico-stuff/tusb_config.h new file mode 100644 index 0000000..87f3867 --- /dev/null +++ b/pico-stuff/tusb_config.h @@ -0,0 +1,118 @@ +/* + * 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 Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#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 1 +#define CFG_TUD_CDC 1 +#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 64 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// CDC Endpoint transfer buffer size, more is faster +#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/pico-stuff/usb_descriptors.c b/pico-stuff/usb_descriptors.c new file mode 100644 index 0000000..309a5f5 --- /dev/null +++ b/pico-stuff/usb_descriptors.c @@ -0,0 +1,244 @@ +/* + * 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 "device/usbd.h" +#include "pico/unique_id.h" +#include "tusb.h" +#include "usb_descriptors.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) ) + +#define USB_VID 0xCafe +#define USB_BCD 0x0200 + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .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_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD )), + TUD_HID_REPORT_DESC_MOUSE ( HID_REPORT_ID(REPORT_ID_MOUSE )), + TUD_HID_REPORT_DESC_CONSUMER( HID_REPORT_ID(REPORT_ID_CONSUMER_CONTROL )), + TUD_HID_REPORT_DESC_GAMEPAD ( HID_REPORT_ID(REPORT_ID_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 instance) +{ + (void) instance; + return desc_hid_report; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +enum +{ + ITF_NUM_HID, + ITF_NUM_CDC, + ITF_NUM_CDC_DATA, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) + +#define EPNUM_HID 0x84 + +#define EPNUM_CDC_NOTIF 0x81 +#define EPNUM_CDC_OUT 0x02 +#define EPNUM_CDC_IN 0x82 + +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_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64) +}; + +#if TUD_OPT_HIGH_SPEED +// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration + +// other speed configuration +uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; + +// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed +tusb_desc_device_qualifier_t const desc_device_qualifier = +{ + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = USB_BCD, + + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const* tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const*) &desc_device_qualifier; +} + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + // other speed config is basically configuration with type = OHER_SPEED_CONFIG + memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN); + desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; + + // this example use the same configuration for both high and full speed mode + return desc_other_speed_config; +} + +#endif // highspeed + +// 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; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// buffer to hold flash ID +char serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1]; + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "dogeystamp", // 1: Manufacturer + "Morse-inator 2000", // 2: Product + serial, // 3: Serials, uses the flash ID + "Morse-inator 2000 Serial" // 4: CDC interface +}; + +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 == 3) pico_get_unique_board_id_string(serial, sizeof(serial)); + + 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