Ver Fonte

first commit

Satoshi Yoneda há 1 mês atrás
commit
8f7bb5f29e
22 ficheiros alterados com 1574 adições e 0 exclusões
  1. 1 0
      .gitignore
  2. 22 0
      .vscode/c_cpp_properties.json
  3. 15 0
      .vscode/cmake-kits.json
  4. 9 0
      .vscode/extensions.json
  5. 70 0
      .vscode/launch.json
  6. 43 0
      .vscode/settings.json
  7. 58 0
      .vscode/tasks.json
  8. 72 0
      CMakeLists.txt
  9. 79 0
      display.c
  10. 15 0
      display.h
  11. 145 0
      font8x8.h
  12. 30 0
      keymap.h
  13. 80 0
      keypad.c
  14. 22 0
      keypad.h
  15. 63 0
      keypad_scanner.pio
  16. 84 0
      pico_sdk_import.cmake
  17. 196 0
      sd1306.c
  18. 36 0
      sd1306.h
  19. 107 0
      tusb_config.h
  20. 240 0
      usb_descriptors.c
  21. 37 0
      usb_descriptors.h
  22. 150 0
      usb_keypad1.c

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+build

+ 22 - 0
.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.exe",
+            "compileCommands": "${workspaceFolder}/build/compile_commands.json",
+            "cStandard": "c17",
+            "cppStandard": "c++14",
+            "intelliSenseMode": "linux-gcc-arm"
+        }
+    ],
+    "version": 4
+}

+ 15 - 0
.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}"
+        }
+    }
+]

+ 9 - 0
.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"
+    ]
+}

+ 70 - 0
.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"
+        },
+    ]
+}

+ 43 - 0
.vscode/settings.json

@@ -0,0 +1,43 @@
+{
+    "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",
+    "files.associations": {
+        "i2c.h": "c",
+        "multicore.h": "c"
+    }
+}

+ 58 - 0
.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",
+            }
+        }
+    ]
+}

+ 72 - 0
CMakeLists.txt

@@ -0,0 +1,72 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == 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 pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(usb_keypad1 C CXX ASM)
+
+# Initialize the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(usb_keypad1 usb_keypad1.c keypad.c usb_descriptors.c display.c sd1306.c)
+
+pico_set_program_name(usb_keypad1 "usb_keypad1")
+pico_set_program_version(usb_keypad1 "0.1")
+
+# Generate PIO header
+pico_generate_pio_header(usb_keypad1 ${CMAKE_CURRENT_LIST_DIR}/keypad_scanner.pio)
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(usb_keypad1 1)
+pico_enable_stdio_usb(usb_keypad1 0)
+
+# Add the standard library to the build
+target_link_libraries(usb_keypad1
+        pico_stdlib)
+
+# Add the standard include files to the build
+target_include_directories(usb_keypad1 PRIVATE
+  ${CMAKE_CURRENT_LIST_DIR}
+)
+
+# Add any user requested libraries
+target_link_libraries(usb_keypad1 
+        hardware_pio
+        pico_stdlib
+        pico_unique_id
+        tinyusb_device
+        tinyusb_board
+        hardware_i2c
+        pico_multicore
+        
+        )
+
+pico_add_extra_outputs(usb_keypad1)
+

+ 79 - 0
display.c

@@ -0,0 +1,79 @@
+#include "sd1306.h"
+#include "display.h"
+#include <stdlib.h>
+#include "pico/stdlib.h"
+#include "pico/binary_info.h"
+#include "hardware/i2c.h"
+#include "pico/util/queue.h"
+#include "pico/multicore.h"
+
+#include "font8x8.h"
+
+uint8_t display_buffer[OLED_BUF_LEN];
+
+void display_putchar(char c, uint8_t col_x, uint8_t col_y, bool reverse)
+{
+    uint32_t offset = col_x * FONT_WIDTH + OLED_WIDTH * col_y + FONT_WIDTH;
+
+    if( c < 0 ) return;
+    if(col_x > 15 || col_y > 3) return;
+    // フォントの縦横変換
+    for(int i = 0; i < 8 ; i++ ) {
+        volatile uint8_t f;
+        f  = ((font8x8[(int)c][reverse ? 0 : 7] << (reverse ? 7 - i : i)) & 0x80) >> 0;
+        f |= ((font8x8[(int)c][reverse ? 1 : 6] << (reverse ? 7 - i : i)) & 0x80) >> 1;
+        f |= ((font8x8[(int)c][reverse ? 2 : 5] << (reverse ? 7 - i : i)) & 0x80) >> 2;
+        f |= ((font8x8[(int)c][reverse ? 3 : 4] << (reverse ? 7 - i : i)) & 0x80) >> 3;
+        f |= ((font8x8[(int)c][reverse ? 4 : 3] << (reverse ? 7 - i : i)) & 0x80) >> 4;
+        f |= ((font8x8[(int)c][reverse ? 5 : 2] << (reverse ? 7 - i : i)) & 0x80) >> 5;
+        f |= ((font8x8[(int)c][reverse ? 6 : 1] << (reverse ? 7 - i : i)) & 0x80) >> 6;
+        f |= ((font8x8[(int)c][reverse ? 7 : 0] << (reverse ? 7 - i : i)) & 0x80) >> 7;
+        display_buffer[offset - i] = f;
+    }
+}
+
+void display_putstr(char *str, uint8_t col_x, uint8_t col_y, bool reverse)
+{
+    int i = 0;
+    while(str[i] != '\0') {
+        display_putchar(str[i++], col_x++, col_y, reverse);
+    }
+}
+
+void display_clear(void)
+{
+    fill(display_buffer, 0x00);
+}
+
+void display_main(void)
+{
+    // I2C初期化
+    i2c_init(i2c_default, 800 * 1000);
+    // I2Cピン初期化
+    gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
+    gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
+    gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
+    gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
+
+    // OLED初期化
+    oled_init();
+
+    struct render_area frame_area = {start_col: 0, 
+                                    end_col : OLED_WIDTH - 1, 
+                                    start_page : 0, 
+                                    end_page : OLED_NUM_PAGES - 1, 
+                                    buflen : 0 }; 
+    calc_render_area_buflen(&frame_area);
+    // ディスプレイクリア
+    fill(display_buffer, 0x00);
+    while(true) {
+        render(display_buffer, &frame_area);
+        sleep_us(6*1000);
+    }
+}
+
+void display_init(void)
+{
+    multicore_reset_core1();
+    multicore_launch_core1(display_main);
+}

+ 15 - 0
display.h

@@ -0,0 +1,15 @@
+#ifndef _DISPLAY_H_
+#define _DISPLAY_H_
+
+#include <stdlib.h>
+#include "pico/stdlib.h"
+
+// extern uint8_t display_buffer[];
+
+void display_init(void);
+void display_main(void);
+void display_clear(void);
+void display_putchar(char c, uint8_t col_x, uint8_t col_y, bool reverse);
+void display_putstr(char *str, uint8_t col_x, uint8_t col_y, bool reverse);
+
+#endif

+ 145 - 0
font8x8.h

@@ -0,0 +1,145 @@
+#ifndef _FONT8X8_H
+#define _FONT8X8_H
+
+#include "pico/stdlib.h"
+
+#define FONT_HEIGHT _u(8)
+#define FONT_WIDTH  _u(8)
+
+// 8x8 font
+// https://github.com/dhepper/font8x8
+// Author: Daniel Hepper <daniel@hepper.net>
+// License: Public Domain
+static uint8_t font8x8[128][8] = {
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0000 (nul)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0001
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0002
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0003
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0004
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0005
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0006
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0007
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0008
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0009
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000A
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000B
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000C
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000D
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000E
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000F
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0010
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0011
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0012
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0013
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0014
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0015
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0016
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0017
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0018
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0019
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001A
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001B
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001C
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001D
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001E
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001F
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0020 (space)
+    { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00},   // U+0021 (!)
+    { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0022 (")
+    { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00},   // U+0023 (#)
+    { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00},   // U+0024 ($)
+    { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00},   // U+0025 (%)
+    { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00},   // U+0026 (&)
+    { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0027 (')
+    { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00},   // U+0028 (()
+    { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00},   // U+0029 ())
+    { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00},   // U+002A (*)
+    { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00},   // U+002B (+)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06},   // U+002C (,)
+    { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00},   // U+002D (-)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00},   // U+002E (.)
+    { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00},   // U+002F (/)
+    { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00},   // U+0030 (0)
+    { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00},   // U+0031 (1)
+    { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00},   // U+0032 (2)
+    { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00},   // U+0033 (3)
+    { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00},   // U+0034 (4)
+    { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00},   // U+0035 (5)
+    { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00},   // U+0036 (6)
+    { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00},   // U+0037 (7)
+    { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00},   // U+0038 (8)
+    { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00},   // U+0039 (9)
+    { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00},   // U+003A (:)
+    { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06},   // U+003B (;)
+    { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00},   // U+003C (<)
+    { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00},   // U+003D (=)
+    { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00},   // U+003E (>)
+    { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00},   // U+003F (?)
+    { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00},   // U+0040 (@)
+    { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00},   // U+0041 (A)
+    { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00},   // U+0042 (B)
+    { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00},   // U+0043 (C)
+    { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00},   // U+0044 (D)
+    { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00},   // U+0045 (E)
+    { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00},   // U+0046 (F)
+    { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00},   // U+0047 (G)
+    { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00},   // U+0048 (H)
+    { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0049 (I)
+    { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00},   // U+004A (J)
+    { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00},   // U+004B (K)
+    { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00},   // U+004C (L)
+    { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00},   // U+004D (M)
+    { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00},   // U+004E (N)
+    { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00},   // U+004F (O)
+    { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00},   // U+0050 (P)
+    { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00},   // U+0051 (Q)
+    { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00},   // U+0052 (R)
+    { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00},   // U+0053 (S)
+    { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0054 (T)
+    { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00},   // U+0055 (U)
+    { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00},   // U+0056 (V)
+    { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00},   // U+0057 (W)
+    { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00},   // U+0058 (X)
+    { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00},   // U+0059 (Y)
+    { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00},   // U+005A (Z)
+    { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00},   // U+005B ([)
+    { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00},   // U+005C (\)
+    { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00},   // U+005D (])
+    { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00},   // U+005E (^)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF},   // U+005F (_)
+    { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0060 (`)
+    { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00},   // U+0061 (a)
+    { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00},   // U+0062 (b)
+    { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00},   // U+0063 (c)
+    { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00},   // U+0064 (d)
+    { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00},   // U+0065 (e)
+    { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00},   // U+0066 (f)
+    { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F},   // U+0067 (g)
+    { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00},   // U+0068 (h)
+    { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0069 (i)
+    { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E},   // U+006A (j)
+    { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00},   // U+006B (k)
+    { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+006C (l)
+    { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00},   // U+006D (m)
+    { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00},   // U+006E (n)
+    { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00},   // U+006F (o)
+    { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F},   // U+0070 (p)
+    { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78},   // U+0071 (q)
+    { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00},   // U+0072 (r)
+    { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00},   // U+0073 (s)
+    { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00},   // U+0074 (t)
+    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00},   // U+0075 (u)
+    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00},   // U+0076 (v)
+    { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00},   // U+0077 (w)
+    { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00},   // U+0078 (x)
+    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F},   // U+0079 (y)
+    { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00},   // U+007A (z)
+    { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00},   // U+007B ({)
+    { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00},   // U+007C (|)
+    { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00},   // U+007D (})
+    { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+007E (~)
+    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}    // U+007F
+};
+
+
+#endif

+ 30 - 0
keymap.h

@@ -0,0 +1,30 @@
+#ifndef _KEYMAP_H
+
+#define _KEYMAP_H   1
+
+#include "class/hid/hid.h"
+
+const uint8_t keymap[16] = {
+    HID_KEY_7,              // S1
+    HID_KEY_8,              // S2
+    HID_KEY_9,              // S3
+    HID_KEY_KEYPAD_ADD,     // S4
+
+    HID_KEY_4,              // S5
+    HID_KEY_5,              // S6
+    HID_KEY_6,              // S7
+    HID_KEY_KEYPAD_SUBTRACT,// S8
+
+    HID_KEY_1,              // S9
+    HID_KEY_2,              // S10
+    HID_KEY_3,              // S11
+    HID_KEY_KEYPAD_MULTIPLY,// S12
+
+    HID_KEY_0,              // S13
+    HID_KEY_PERIOD,         // S14
+    HID_KEY_KEYPAD_DIVIDE,  // S15
+    HID_KEY_KEYPAD_ENTER,   // S16
+};
+
+
+#endif

+ 80 - 0
keypad.c

@@ -0,0 +1,80 @@
+#include <stdio.h>
+#include "pico/stdlib.h"
+#include "hardware/pio.h"
+#include "hardware/clocks.h"
+#include "pico/multicore.h"
+#include "keypad_scanner.pio.h"
+#include "keypad.h"
+
+volatile uint   write_p = 0;    // バッファ書き込みインデックス
+volatile uint   read_p  = 0;    // バッファ読み出しインデックス
+
+// キーバッファ
+volatile key_t    keystate_buffer[KEYPAD_BUFFER_SIZE];
+// LED
+const uint LED_PIN = PICO_DEFAULT_LED_PIN;
+
+// PIO割り込みハンドラ
+void pio_irq_handler(void)
+{
+    static uint32_t prev_keycode = 0;
+    uint32_t keycode;
+    // IRQクリア
+    pio_interrupt_clear(pio0, 0);
+
+    keycode = prev_keycode;
+    if(! pio_sm_is_rx_fifo_empty(pio0, 0)) {
+        keycode = pio_sm_get(pio0, 0);
+    }
+    uint32_t changed_bit = keycode ^ prev_keycode;
+    prev_keycode = keycode;
+
+    for(uint16_t idx = 0; idx < 16; idx++) {
+        if(changed_bit & 1) {
+            keystate_buffer[write_p & 0xF].code = idx;
+            keystate_buffer[write_p & 0xF].state = keycode & 1;
+            gpio_put(LED_PIN, keystate_buffer[write_p & 0xF].state);
+            write_p++;
+        }
+        changed_bit >>= 1;
+        keycode >>= 1;
+    }
+}
+
+void keypad_init()
+{
+    // オンボードLED
+    gpio_init(LED_PIN);
+    gpio_set_dir(LED_PIN, GPIO_OUT);
+    gpio_put(LED_PIN, 0);
+
+    // PIO
+    PIO pio = pio0;
+    // PIO割り込みの設定
+    irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler);
+    irq_set_enabled(PIO0_IRQ_0, true);
+    pio_set_irq0_source_enabled(pio,pis_interrupt0, pio_irq_handler );
+    uint offset = pio_add_program(pio, &keypad_scanner_program);
+    keypad_scanner_program_init(pio, 0, offset, ROW_BASE, COLUMN_BASE, 16000);
+
+    // SM起動
+    pio_sm_set_enabled(pio, 0, true);
+}
+
+key_t get_key(void)
+{
+    key_t   retval;
+    uint wp;
+
+    wp = write_p;
+    if(wp != read_p) {
+        retval.code = keystate_buffer[read_p & 0x0F].code;
+        retval.state = keystate_buffer[read_p & 0xF].state;
+        read_p++;
+    }
+    else {
+        retval.code = 0;
+        retval.state = KEYPAD_INVALID;
+    }
+    return retval;
+}

+ 22 - 0
keypad.h

@@ -0,0 +1,22 @@
+#ifndef _KEYPAD_H
+
+#define _KEYPAD_H   1
+
+#define COLUMN_BASE 10
+#define ROW_BASE    6
+
+typedef struct _key_state_t {
+    uint16_t    code;
+    int16_t     state;
+} key_t;
+
+#define KEYPAD_BUFFER_SIZE   0x10
+
+#define KEYPAD_PUSH     1
+#define KEYPAD_RELEASE  0
+#define KEYPAD_INVALID  -1
+
+void keypad_init(void);
+key_t get_key(void);
+
+#endif

+ 63 - 0
keypad_scanner.pio

@@ -0,0 +1,63 @@
+
+.program keypad_scanner
+    mov y,null
+    mov isr, null
+    set pins, 0
+
+.wrap_target
+loop_start:
+    set pins, 0b1000    [1]
+    in  pins, 4
+    set pins, 0b0100    [1]
+    in  pins, 4
+    set pins, 0b0010    [1]
+    in  pins, 4
+    set pins, 0b0001    [1]
+    in  pins, 4
+    mov x, isr
+    jmp x!=y state_changes
+    mov isr, null
+    jmp loop_start
+
+state_changes:
+    mov y,x
+    push
+    irq 0
+.wrap
+
+
+% c-sdk {
+
+#include "hardware/clocks.h"
+
+void keypad_scanner_program_init(PIO pio, uint sm, uint offset, uint rowBase, uint columnBase, float freq) {
+    // ピン入出力方向の設定
+    pio_sm_set_consecutive_pindirs(pio, sm, rowBase, 4, true);
+    pio_sm_set_consecutive_pindirs(pio, sm, columnBase, 4, false);
+
+    // 使用するGPIOをPIOに割当
+    pio_gpio_init(pio, rowBase);
+    pio_gpio_init(pio, rowBase+1);
+    pio_gpio_init(pio, rowBase+2);
+    pio_gpio_init(pio, rowBase+3);
+    pio_gpio_init(pio, columnBase);
+    pio_gpio_init(pio, columnBase+1);
+    pio_gpio_init(pio, columnBase+2);
+    pio_gpio_init(pio, columnBase+3);
+
+    pio_sm_config c = keypad_scanner_program_get_default_config(offset);
+    // set命令のポート設定
+    sm_config_set_set_pin_base(&c, rowBase);
+    sm_config_set_set_pin_count(&c, 4);
+    // in命令のポート設定
+    sm_config_set_in_pin_base(&c, columnBase);
+    sm_config_set_in_pin_count(&c, 4);
+    // in命令自動左シフト
+     sm_config_set_in_shift(&c, false, false, 32);
+    // SMのクロック設定
+    float clkdiv = (float)clock_get_hz(clk_sys) / freq;
+    sm_config_set_clkdiv(&c, clkdiv);
+
+    pio_sm_init(pio, sm, offset, &c);
+}
+%}

+ 84 - 0
pico_sdk_import.cmake

@@ -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})

+ 196 - 0
sd1306.c

@@ -0,0 +1,196 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "pico/stdlib.h"
+#include "pico/binary_info.h"
+#include "hardware/i2c.h"
+#include "sd1306.h"
+
+// commands (see datasheet)
+#define OLED_SET_CONTRAST _u(0x81)
+#define OLED_SET_ENTIRE_ON _u(0xA4)
+#define OLED_SET_NORM_INV _u(0xA6)
+#define OLED_SET_DISP _u(0xAE)
+#define OLED_SET_MEM_ADDR _u(0x20)
+#define OLED_SET_COL_ADDR _u(0x21)
+#define OLED_SET_PAGE_ADDR _u(0x22)
+#define OLED_SET_DISP_START_LINE _u(0x40)
+#define OLED_SET_SEG_REMAP _u(0xA0)
+#define OLED_SET_MUX_RATIO _u(0xA8)
+#define OLED_SET_COM_OUT_DIR _u(0xC0)
+#define OLED_SET_DISP_OFFSET _u(0xD3)
+#define OLED_SET_COM_PIN_CFG _u(0xDA)
+#define OLED_SET_DISP_CLK_DIV _u(0xD5)
+#define OLED_SET_PRECHARGE _u(0xD9)
+#define OLED_SET_VCOM_DESEL _u(0xDB)
+#define OLED_SET_CHARGE_PUMP _u(0x8D)
+#define OLED_SET_HORIZ_SCROLL _u(0x26)
+#define OLED_SET_SCROLL _u(0x2E)
+
+#define OLED_ADDR _u(0x3C)
+
+#define OLED_WRITE_MODE _u(0xFE)
+#define OLED_READ_MODE _u(0xFF)
+
+
+
+void fill(uint8_t buf[], uint8_t fill) {
+    // fill entire buffer with the same byte
+    for (int i = 0; i < OLED_BUF_LEN; i++) {
+        buf[i] = fill;
+    }
+};
+
+void fill_page(uint8_t *buf, uint8_t fill, uint8_t page) {
+    // fill entire page with the same byte
+    memset(buf + (page * OLED_WIDTH), fill, OLED_WIDTH);
+};
+
+// convenience methods for printing out a buffer to be rendered
+// mostly useful for debugging images, patterns, etc
+
+void print_buf_page(uint8_t buf[], uint8_t page) {
+    // prints one page of a full length (128x4) buffer
+    for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
+        for (int k = 0; k < OLED_WIDTH; k++) {
+            printf("%u", (buf[page * OLED_WIDTH + k] >> j) & 0x01);
+        }
+        printf("\n");
+    }
+}
+
+void print_buf_pages(uint8_t buf[]) {
+    // prints all pages of a full length buffer
+    for (int i = 0; i < OLED_NUM_PAGES; i++) {
+        printf("--page %d--\n", i);
+        print_buf_page(buf, i);
+    }
+}
+
+void print_buf_area(uint8_t *buf, struct render_area *area) {
+    // print a render area of generic size
+    int area_width = area->end_col - area->start_col + 1;
+    int area_height = area->end_page - area->start_page + 1; // in pages, not pixels
+    for (int i = 0; i < area_height; i++) {
+        for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
+            for (int k = 0; k < area_width; k++) {
+                printf("%u", (buf[i * area_width + k] >> j) & 0x01);
+            }
+            printf("\n");
+        }
+    }
+}
+
+void calc_render_area_buflen(struct render_area *area) {
+    // calculate how long the flattened buffer will be for a render area
+    area->buflen = (area->end_col - area->start_col + 1) * (area->end_page - area->start_page + 1);
+}
+
+#ifdef i2c_default
+
+void oled_send_cmd(uint8_t cmd) {
+    // I2C write process expects a control byte followed by data
+    // this "data" can be a command or data to follow up a command
+
+    // Co = 1, D/C = 0 => the driver expects a command
+    uint8_t buf[2] = {0x80, cmd};
+    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), buf, 2, false);
+}
+
+void oled_send_buf(uint8_t buf[], int buflen) {
+    // in horizontal addressing mode, the column address pointer auto-increments
+    // and then wraps around to the next page, so we can send the entire frame
+    // buffer in one gooooooo!
+
+    // copy our frame buffer into a new buffer because we need to add the control byte
+    // to the beginning
+
+    // TODO find a more memory-efficient way to do this..
+    // maybe break the data transfer into pages?
+    uint8_t *temp_buf = (uint8_t *)malloc(buflen + 1);
+
+    for (int i = 1; i < buflen + 1; i++) {
+        temp_buf[i] = buf[i - 1];
+    }
+    // Co = 0, D/C = 1 => the driver expects data to be written to RAM
+    temp_buf[0] = 0x40;
+    i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), temp_buf, buflen + 1, false);
+
+    free(temp_buf);
+}
+
+void oled_init(void) {
+    // some of these commands are not strictly necessary as the reset
+    // process defaults to some of these but they are shown here
+    // to demonstrate what the initialization sequence looks like
+
+    // some configuration values are recommended by the board manufacturer
+
+    oled_send_cmd(OLED_SET_DISP | 0x00); // set display off
+
+    /* memory mapping */
+    oled_send_cmd(OLED_SET_MEM_ADDR); // set memory address mode
+    oled_send_cmd(0x00); // horizontal addressing mode
+
+    /* resolution and layout */
+    oled_send_cmd(OLED_SET_DISP_START_LINE); // set display start line to 0
+
+    oled_send_cmd(OLED_SET_SEG_REMAP | 0x01); // set segment re-map
+    // column address 127 is mapped to SEG0
+
+    oled_send_cmd(OLED_SET_MUX_RATIO); // set multiplex ratio
+    oled_send_cmd(OLED_HEIGHT - 1); // our display is only 32 pixels high
+
+    oled_send_cmd(OLED_SET_COM_OUT_DIR | 0x08); // set COM (common) output scan direction
+    // scan from bottom up, COM[N-1] to COM0
+
+    oled_send_cmd(OLED_SET_DISP_OFFSET); // set display offset
+    oled_send_cmd(0x00); // no offset
+
+    oled_send_cmd(OLED_SET_COM_PIN_CFG); // set COM (common) pins hardware configuration
+    oled_send_cmd(0x02); // manufacturer magic number
+
+    /* timing and driving scheme */
+    oled_send_cmd(OLED_SET_DISP_CLK_DIV); // set display clock divide ratio
+    oled_send_cmd(0x80); // div ratio of 1, standard freq
+
+    oled_send_cmd(OLED_SET_PRECHARGE); // set pre-charge period
+    oled_send_cmd(0xF1); // Vcc internally generated on our board
+
+    oled_send_cmd(OLED_SET_VCOM_DESEL); // set VCOMH deselect level
+    oled_send_cmd(0x30); // 0.83xVcc
+
+    /* display */
+    oled_send_cmd(OLED_SET_CONTRAST); // set contrast control
+    oled_send_cmd(0xFF);
+
+    oled_send_cmd(OLED_SET_ENTIRE_ON); // set entire display on to follow RAM content
+
+    oled_send_cmd(OLED_SET_NORM_INV); // set normal (not inverted) display
+
+    oled_send_cmd(OLED_SET_CHARGE_PUMP); // set charge pump
+    oled_send_cmd(0x14); // Vcc internally generated on our board
+
+    oled_send_cmd(OLED_SET_SCROLL | 0x00); // deactivate horizontal scrolling if set
+    // this is necessary as memory writes will corrupt if scrolling was enabled
+
+    oled_send_cmd(OLED_SET_DISP | 0x01); // turn display on
+}
+
+void render(uint8_t *buf, struct render_area *area) {
+    // update a portion of the display with a render area
+    oled_send_cmd(OLED_SET_COL_ADDR);
+    oled_send_cmd(area->start_col);
+    oled_send_cmd(area->end_col);
+
+    oled_send_cmd(OLED_SET_PAGE_ADDR);
+    oled_send_cmd(area->start_page);
+    oled_send_cmd(area->end_page);
+
+    oled_send_buf(buf, area->buflen);
+}
+
+#endif
+
+
+

+ 36 - 0
sd1306.h

@@ -0,0 +1,36 @@
+#ifndef _SD1306_H_
+#define _SD1306_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "pico/stdlib.h"
+
+#define OLED_HEIGHT _u(32)
+#define OLED_WIDTH _u(128)
+#define OLED_PAGE_HEIGHT _u(8)
+#define OLED_NUM_PAGES OLED_HEIGHT / OLED_PAGE_HEIGHT
+#define OLED_BUF_LEN (OLED_NUM_PAGES * OLED_WIDTH)
+
+struct render_area {
+    uint8_t start_col;
+    uint8_t end_col;
+    uint8_t start_page;
+    uint8_t end_page;
+
+    int buflen;
+};
+
+void fill(uint8_t buf[], uint8_t fill);
+void fill_page(uint8_t *buf, uint8_t fill, uint8_t page);
+void print_buf_page(uint8_t buf[], uint8_t page);
+void print_buf_pages(uint8_t buf[]);
+void print_buf_area(uint8_t *buf, struct render_area *area);
+void calc_render_area_buflen(struct render_area *area);
+void oled_send_cmd(uint8_t cmd);
+void oled_send_buf(uint8_t buf[], int buflen);
+void oled_init(void);
+void render(uint8_t *buf, struct render_area *area);
+
+
+#endif

+ 107 - 0
tusb_config.h

@@ -0,0 +1,107 @@
+/*
+ * 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            0
+
+// HID buffer size Should be sufficient to hold ID (if any) + Data
+#define CFG_TUD_HID_EP_BUFSIZE    16
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */

+ 240 - 0
usb_descriptors.c

@@ -0,0 +1,240 @@
+/*
+ * 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 "bsp/board_api.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_TOTAL
+};
+
+#define  CONFIG_TOTAL_LEN  (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN)
+
+#define EPNUM_HID   0x81
+
+uint8_t const desc_configuration[] =
+{
+  // Config number, interface count, string index, total length, attribute, power in mA
+  TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+  // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
+  TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5)
+};
+
+#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
+//--------------------------------------------------------------------+
+
+// String Descriptor Index
+enum {
+  STRID_LANGID = 0,
+  STRID_MANUFACTURER,
+  STRID_PRODUCT,
+  STRID_SERIAL,
+};
+
+// array of pointer to string descriptors
+char const *string_desc_arr[] =
+{
+  (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
+  "TinyUSB",                     // 1: Manufacturer
+  "TinyUSB Device",              // 2: Product
+  NULL,                          // 3: Serials will use unique ID if possible
+};
+
+static uint16_t _desc_str[32 + 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;
+
+  switch ( index ) {
+    case STRID_LANGID:
+      memcpy(&_desc_str[1], string_desc_arr[0], 2);
+      chr_count = 1;
+      break;
+
+    case STRID_SERIAL:
+      chr_count = board_usb_get_serial(_desc_str + 1, 32);
+      break;
+
+    default:
+      // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
+      // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
+
+      if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL;
+
+      const char *str = string_desc_arr[index];
+
+      // Cap at max char
+      chr_count = strlen(str);
+      size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
+      if ( chr_count > max_count ) chr_count = max_count;
+
+      // Convert ASCII string into UTF-16
+      for ( size_t i = 0; i < chr_count; i++ ) {
+        _desc_str[1 + i] = str[i];
+      }
+      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;
+}

+ 37 - 0
usb_descriptors.h

@@ -0,0 +1,37 @@
+/*
+ * 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 USB_DESCRIPTORS_H_
+#define USB_DESCRIPTORS_H_
+
+enum
+{
+  REPORT_ID_KEYBOARD = 1,
+  REPORT_ID_MOUSE,
+  REPORT_ID_CONSUMER_CONTROL,
+  REPORT_ID_GAMEPAD,
+  REPORT_ID_COUNT
+};
+
+#endif /* USB_DESCRIPTORS_H_ */

+ 150 - 0
usb_keypad1.c

@@ -0,0 +1,150 @@
+#include <stdio.h>
+#include "pico/stdlib.h"
+
+#include "hardware/pio.h"
+#include "bsp/board_api.h"
+#include "tusb.h"
+#include "usb_descriptors.h"
+
+#include "keypad.h"
+#include "keymap.h"
+#include "display.h"
+
+#define KEYBOARD_REPORT_COUNT    6
+uint8_t key_report[KEYBOARD_REPORT_COUNT] = {0,0,0,0,0,0};
+
+char report_str[16];        // キーボードレポート表示用
+char indicator_str[16];     // LEDインジケーター表示用
+
+
+// レポート配列をクリア
+void clear_key_report(void)
+{
+    for(int i = 0; i < KEYBOARD_REPORT_COUNT; i++){
+        key_report[i] = 0;
+    }
+}
+
+
+// USBデバイスがマウントされた
+void tud_mount_cb(void)
+{
+    clear_key_report();
+}
+
+// USBデバイスがアンマウントされた
+void tud_umount_cb(void)
+{
+    clear_key_report();
+}
+
+// サスペンド状態に移行した
+void tud_suspend_cb(bool remote_wakeup_en)
+{
+    (void) remote_wakeup_en;
+    // 何もしない
+}
+
+// レジュームした
+void tud_resume_cb(void)
+{
+    // 何もしない
+}
+
+// REPORTが完了したら呼び出される
+// 次のREPORTを送るのに使える
+void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len)
+{
+  (void) instance;
+  (void) len;
+  (void) report;
+  // 何もしない
+}
+
+
+// コントロールリクエストGET_REPORT
+// キーボードは何も行わない
+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;
+}
+
+// コントロールリクエストSET_REPORT
+// キーボードではCAPS LOCK等オンボードLEDの制御情報がホストから送られる
+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;
+
+    if( bufsize < 1 ) return;   // バッファゼロなら何もしない
+
+    if(report_type == HID_REPORT_TYPE_OUTPUT) {     // ホスト->デバイス
+        if(report_id == REPORT_ID_KEYBOARD) {     // キーボードレポート
+            uint8_t leds = buffer[0];               // LED制御情報
+
+            sprintf(indicator_str, "%s %s %s",
+                leds & KEYBOARD_LED_CAPSLOCK ? "CAP" : "   ",
+                leds & KEYBOARD_LED_NUMLOCK  ? "NUM" : "   ",
+                leds & KEYBOARD_LED_SCROLLLOCK ? "SCR" : "   "
+                // leds & KEYBOARD_LED_COMPOSE
+                // leds & KEYBOARD_LED_KANA
+                );
+            display_putstr(indicator_str, 0, 0, false);
+        }
+    }
+}
+
+int main()
+{
+    // TinyUSBの初期化
+    board_init();
+    tud_init(BOARD_TUD_RHPORT);
+    if (board_init_after_tusb) {
+        board_init_after_tusb();
+    }
+    // シリアルコンソール
+    stdio_init_all();
+    // OLED表示
+    display_init();
+    // キーパッド
+    keypad_init();
+
+    while (true) {
+        tud_task();     // TinyUSB定期的に呼び出す必要がある
+
+        key_t key = get_key();
+        if(tud_mounted()) {     // USBデバイスとしてマウントされていれば
+            if(key.state != KEYPAD_INVALID) {  // キー状態に変化あり
+                uint8_t scancode = keymap[key.code];
+                if(tud_suspended())     // サスペンド状態なら起こす
+                    tud_remote_wakeup();
+                
+                if(key.state == KEYPAD_PUSH) {    // キーが押された
+                    for(int i = 0; i < KEYBOARD_REPORT_COUNT; i++) {
+                        if(key_report[i] == 0) {
+                            key_report[i] = scancode;
+                            break;
+                        }
+                    }
+                }
+                else {                     // キーオフ
+                    for(int i = 0; i < KEYBOARD_REPORT_COUNT; i++) {
+                        if(key_report[i] == scancode)
+                            key_report[i] = 0;
+                    }
+                }
+                // キーボードレポートをOLEDに表示する
+                sprintf(report_str,"%02x%02x%02x%02x%02x%02x", key_report[0],key_report[1],key_report[2],key_report[3],key_report[4],key_report[5]);
+                display_putstr(report_str, 0, 3, false);
+                if(tud_hid_ready())
+                    tud_hid_keyboard_report(REPORT_ID_KEYBOARD,0, key_report);
+            }
+        }
+    }
+}