├── linux ├── mouse.c ├── mouse.h ├── keyboard.h ├── Makefile ├── spi1-picomk-overlay.dts ├── keyboard.c └── spi_module.c ├── display_mixins.cc ├── docs ├── ibp.png ├── ibp-spi.png ├── ibp-format.png ├── config_menu.gif ├── small_keylayout.png ├── small_keymatrix.png ├── devices.md ├── design.md ├── config.md ├── ibp.md └── layout_cc.md ├── configs ├── examples │ ├── home_screen │ │ ├── home_after.jpg │ │ ├── home_before.jpg │ │ ├── README.md │ │ ├── config.h │ │ └── layout.cc │ ├── custom_keycode │ │ ├── README.md │ │ ├── config.h │ │ └── layout.cc │ └── bare_minimum │ │ ├── config.h │ │ └── layout.cc ├── ibp_debug │ ├── layout.cc │ └── config.h ├── cyberkeeb_2040 │ └── config.h └── default │ ├── config.h │ └── layout.cc ├── .vscode ├── extensions.json ├── c_cpp_properties.json ├── launch.json └── settings.json ├── littlefs └── CMakeLists.txt ├── runner.h ├── sync.h ├── config.cmake ├── storage.h ├── utils.cc ├── .gitmodules ├── main.cc ├── rotary_encoder.h ├── temperature.h ├── .gitignore ├── layout.h ├── LICENSE ├── pio └── ws2812.pio ├── spi.h ├── utils.h ├── ibp.h ├── rotary_encoder.cc ├── ibp_lib.h ├── ws2812.h ├── ssd1306.h ├── CMakeLists.txt ├── configuration.cc ├── joystick.h ├── pico_sdk_import.cmake ├── keyscan.h ├── builtin_keycode.cc ├── display_mixins.h ├── sync.cc ├── tusb_config.h ├── FreeRTOS_Kernel_import.cmake ├── usb.h ├── configuration.h ├── layout_internal.inc ├── temperature.cc ├── config_modifier.h ├── keyscan.cc ├── FreeRTOSConfig.h ├── layout_helper.h ├── ibp.cc ├── ssd1306.cc └── storage.cc /linux/mouse.c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /display_mixins.cc: -------------------------------------------------------------------------------- 1 | #include "display_mixins.h" 2 | -------------------------------------------------------------------------------- /docs/ibp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/docs/ibp.png -------------------------------------------------------------------------------- /docs/ibp-spi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/docs/ibp-spi.png -------------------------------------------------------------------------------- /docs/ibp-format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/docs/ibp-format.png -------------------------------------------------------------------------------- /linux/mouse.h: -------------------------------------------------------------------------------- 1 | #ifndef MOUSE_H_ 2 | #define MOUSE_H_ 3 | 4 | #endif /*MOUSE_H_*/ 5 | -------------------------------------------------------------------------------- /docs/config_menu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/docs/config_menu.gif -------------------------------------------------------------------------------- /docs/small_keylayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/docs/small_keylayout.png -------------------------------------------------------------------------------- /docs/small_keymatrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/docs/small_keymatrix.png -------------------------------------------------------------------------------- /configs/examples/home_screen/home_after.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/configs/examples/home_screen/home_after.jpg -------------------------------------------------------------------------------- /configs/examples/home_screen/home_before.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zli117/PicoMK/HEAD/configs/examples/home_screen/home_before.jpg -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "marus25.cortex-debug", 4 | "ms-vscode.cmake-tools", 5 | "ms-vscode.cpptools" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /configs/examples/custom_keycode/README.md: -------------------------------------------------------------------------------- 1 | # Custom Keycode Example 2 | 3 | Create custom keycode handlers and register them. 4 | 5 | Derived from the default config. `config.h` unchanged. 6 | -------------------------------------------------------------------------------- /littlefs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(littlefs STATIC 2 | littlefs/lfs.c 3 | littlefs/lfs_util.c) 4 | target_include_directories(littlefs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 5 | target_link_libraries(littlefs hardware_flash) 6 | -------------------------------------------------------------------------------- /configs/examples/home_screen/README.md: -------------------------------------------------------------------------------- 1 | Derived from default config. `config.h` is the same. 2 | 3 | | Before | After | 4 | | -------------------------- | ------------------------ | 5 | | ![Before](home_before.jpg) | ![After](home_after.jpg) | -------------------------------------------------------------------------------- /runner.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_H_ 2 | #define RUNNER_H_ 3 | 4 | #include "utils.h" 5 | 6 | namespace runner { 7 | 8 | Status RunnerInit(); 9 | Status RunnerStart(); 10 | 11 | void SetConfigMode(bool is_config); 12 | void NotifyConfigChange(); 13 | 14 | } // namespace runner 15 | 16 | #endif /* RUNNER_H_ */ -------------------------------------------------------------------------------- /linux/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYBOARD_H_ 2 | #define KEYBOARD_H_ 3 | 4 | #include "../ibp_lib.h" 5 | 6 | int CreateKeyboardDevice(int (*open)(void), void (*close)(void)); 7 | int DestroyKeyboardDevice(void); 8 | int OnNewKeycodes(IBPKeyCodes* keys); 9 | int OnNewConsumerKeycodes(IBPConsumer* keys); 10 | 11 | #endif /*KEYBOARD_H_*/ 12 | -------------------------------------------------------------------------------- /sync.h: -------------------------------------------------------------------------------- 1 | #ifndef SYNC_H_ 2 | #define SYNC_H_ 3 | 4 | #include "utils.h" 5 | 6 | Status StartSyncTasks(); 7 | 8 | class CoreBlockerSection { 9 | public: 10 | CoreBlockerSection(); 11 | virtual ~CoreBlockerSection(); 12 | }; 13 | 14 | // Block the other core so that we can do flash operations 15 | void DisableTheOtherCore(); 16 | void ReenableTheOtherCore(); 17 | 18 | #endif /* SYNC_H_ */ 19 | -------------------------------------------------------------------------------- /config.cmake: -------------------------------------------------------------------------------- 1 | if (DEFINED ENV{BOARD_CONFIG}) 2 | set(BOARD_CONFIG $ENV{BOARD_CONFIG}) 3 | message("Using BOARD_CONFIG from environment ('${BOARD_CONFIG}')") 4 | else() 5 | if (NOT BOARD_CONFIG) 6 | set(BOARD_CONFIG "default") 7 | message("BOARD_CONFIG not specified. Default to \"default\"") 8 | else() 9 | message("BOARD_CONFIG is ${BOARD_CONFIG}.") 10 | endif() 11 | endif() 12 | -------------------------------------------------------------------------------- /storage.h: -------------------------------------------------------------------------------- 1 | #ifndef STORAGE_H_ 2 | #define STORAGE_H_ 3 | 4 | #include 5 | 6 | #include "utils.h" 7 | 8 | Status InitializeStorage(); 9 | 10 | Status WriteStringToFile(const std::string& content, const std::string& name); 11 | Status ReadFileContent(const std::string& name, std::string* output); 12 | Status GetFileSize(const std::string& name, size_t* output); 13 | Status RemoveFile(const std::string& name); 14 | 15 | #endif /* STORAGE_H_ */ -------------------------------------------------------------------------------- /utils.cc: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include "task.h" 4 | 5 | LockSemaphore::LockSemaphore(SemaphoreHandle_t semaphore) 6 | : semaphore_(semaphore) { 7 | xSemaphoreTake(semaphore_, portMAX_DELAY); 8 | } 9 | 10 | LockSemaphore::~LockSemaphore() { xSemaphoreGive(semaphore_); } 11 | 12 | LockSpinlock::LockSpinlock(spin_lock_t* lock) 13 | : lock_(lock), irq_(spin_lock_blocking(lock_)) {} 14 | 15 | LockSpinlock::~LockSpinlock() { spin_unlock(lock_, irq_); } 16 | -------------------------------------------------------------------------------- /linux/Makefile: -------------------------------------------------------------------------------- 1 | ccflags-y := -D IBP_KERNEL_MODULE=1 -std=gnu11 -Wno-declaration-after-statement 2 | 3 | obj-m += spi_picomk.o 4 | spi_picomk-objs+= spi_module.o ../ibp_lib.o keyboard.o mouse.o 5 | 6 | all: 7 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 8 | 9 | clean: 10 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 11 | 12 | install: 13 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install 14 | 15 | device_tree: 16 | dtc -O dtb -o spi1-picomk.dtbo spi1-picomk-overlay.dts 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pico-sdk"] 2 | path = pico-sdk 3 | url = https://github.com/raspberrypi/pico-sdk.git 4 | branch = master 5 | [submodule "FreeRTOS-Kernel"] 6 | path = FreeRTOS-Kernel 7 | url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git 8 | branch = smp 9 | [submodule "pico-ssd1306"] 10 | path = pico-ssd1306 11 | url = https://github.com/Harbys/pico-ssd1306 12 | [submodule "cJSON"] 13 | path = cJSON 14 | url = https://github.com/DaveGamble/cJSON.git 15 | [submodule "littlefs/littlefs"] 16 | path = littlefs/littlefs 17 | url = https://github.com/littlefs-project/littlefs.git 18 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "pico-sdk/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/arm-none-eabi-gcc", 11 | "cStandard": "gnu17", 12 | "cppStandard": "gnu++14", 13 | "intelliSenseMode": "linux-gcc-arm", 14 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 15 | } 16 | ], 17 | "version": 4 18 | } -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "FreeRTOS.h" 4 | #include "pico/stdlib.h" 5 | #include "runner.h" 6 | #include "storage.h" 7 | #include "sync.h" 8 | #include "utils.h" 9 | 10 | extern "C" void vApplicationMallocFailedHook(void) { 11 | LOG_ERROR("Failed malloc. OOM"); 12 | } 13 | extern "C" void vApplicationIdleHook(void) {} 14 | extern "C" void vApplicationStackOverflowHook(TaskHandle_t pxTask, 15 | char *pcTaskName) { 16 | LOG_ERROR("Stack overflow for task %s", pcTaskName); 17 | } 18 | extern "C" void vApplicationTickHook(void) {} 19 | 20 | int main() { 21 | if (InitializeStorage() == OK && // 22 | runner::RunnerInit() == OK && // 23 | runner::RunnerStart() == OK) { 24 | vTaskStartScheduler(); 25 | } 26 | 27 | while (true) 28 | ; 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /rotary_encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef ROTARY_ENCODER_H_ 2 | #define ROTARY_ENCODER_H_ 3 | 4 | #include "FreeRTOS.h" 5 | #include "base.h" 6 | #include "semphr.h" 7 | 8 | class RotaryEncoder : public GenericInputDevice { 9 | public: 10 | RotaryEncoder(); 11 | RotaryEncoder(uint8_t pin_a, uint8_t pin_b, uint8_t resolution); 12 | 13 | void InputLoopStart() override {} 14 | void InputTick() override; 15 | void SetConfigMode(bool is_config_mode) override; 16 | 17 | protected: 18 | virtual void HandleMovement(bool dir); 19 | 20 | const uint8_t pin_a_; 21 | const uint8_t pin_b_; 22 | const uint8_t resolution_; 23 | bool a_state_; 24 | uint8_t pulse_count_; 25 | bool dir_; 26 | bool is_config_; 27 | 28 | // SemaphoreHandle_t semaphore_; 29 | }; 30 | 31 | Status RegisterEncoder(uint8_t tag, uint8_t pin_a, uint8_t pin_b, 32 | uint8_t resolution); 33 | 34 | #endif /* ROTARY_ENCODER_H_ */ -------------------------------------------------------------------------------- /temperature.h: -------------------------------------------------------------------------------- 1 | #ifndef TEMPERATURE_H_ 2 | #define TEMPERATURE_H_ 3 | 4 | #include "base.h" 5 | #include "utils.h" 6 | 7 | class TemperatureInputDeivce : virtual public GenericInputDevice { 8 | public: 9 | TemperatureInputDeivce(); 10 | 11 | void InputLoopStart() override; 12 | void InputTick() override; 13 | 14 | std::pair> CreateDefaultConfig() 15 | override; 16 | void OnUpdateConfig(const Config* config) override; 17 | void SetConfigMode(bool is_config_mode) override; 18 | 19 | protected: 20 | virtual int32_t ConvertTemperature(); 21 | 22 | virtual void WriteTemp(int32_t temp); 23 | 24 | bool is_fahrenheit_; 25 | bool is_config_; 26 | bool enabled_; 27 | std::vector buffer_; 28 | uint32_t sample_every_ticks_; 29 | uint32_t counter_; 30 | uint32_t buffer_idx_; 31 | int32_t sum_; 32 | int32_t prev_temp_; 33 | }; 34 | 35 | Status RegisterTemperatureInput(uint8_t tag); 36 | 37 | #endif /* TEMPERATURE_H_ */ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # cmake 2 | CMakeLists.txt.user 3 | CMakeCache.txt 4 | CMakeFiles 5 | CMakeScripts 6 | Testing 7 | Makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | compile_commands.json 11 | CTestTestfile.cmake 12 | _deps 13 | build/ 14 | 15 | # Prerequisites 16 | *.d 17 | 18 | # Object files 19 | *.o 20 | *.ko 21 | *.obj 22 | *.elf 23 | 24 | # Linker output 25 | *.ilk 26 | *.map 27 | *.exp 28 | 29 | # Precompiled Headers 30 | *.gch 31 | *.pch 32 | 33 | # Libraries 34 | *.lib 35 | *.a 36 | *.la 37 | *.lo 38 | 39 | # Shared objects (inc. Windows DLLs) 40 | *.dll 41 | *.so 42 | *.so.* 43 | *.dylib 44 | 45 | # Executables 46 | *.exe 47 | *.out 48 | *.app 49 | *.i*86 50 | *.x86_64 51 | *.hex 52 | 53 | # Debug files 54 | *.dSYM/ 55 | *.su 56 | *.idb 57 | *.pdb 58 | 59 | # Kernel Module Compile Results 60 | *.mod* 61 | *.cmd 62 | .tmp_versions/ 63 | modules.order 64 | Module.symvers 65 | Mkfile.old 66 | dkms.conf 67 | 68 | # Debugger 69 | *.cortex-debug* 70 | 71 | # Device tree 72 | *.dtbo 73 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Cortex Debug", 9 | "cwd": "${workspaceRoot}", 10 | "executable": "${command:cmake.launchTargetPath}", 11 | "request": "launch", 12 | "type": "cortex-debug", 13 | "servertype": "openocd", 14 | "gdbPath": "arm-none-eabi-gdb", 15 | "device": "RP2040", 16 | "configFiles": [ 17 | "interface/picoprobe.cfg", 18 | "target/rp2040.cfg" 19 | ], 20 | "svdFile": "pico-sdk/src/rp2040/hardware_regs/rp2040.svd", 21 | "runToEntryPoint": "main", 22 | // Give restart the same functionality as runToEntryPoint - main 23 | "postRestartCommands": [ 24 | "break main", 25 | "continue" 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /layout.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYOUT_H_ 2 | #define LAYOUT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct Keycode { 10 | uint8_t keycode; 11 | bool is_custom : 1; 12 | uint8_t custom_info : 7; 13 | }; 14 | 15 | struct GPIO { 16 | uint8_t source; // in 17 | uint8_t sink; // out 18 | }; 19 | 20 | size_t GetKeyboardNumLayers(); 21 | size_t GetTotalNumGPIOs(); 22 | uint8_t GetGPIOPin(size_t gpio_idx); 23 | 24 | size_t GetTotalScans(); 25 | uint8_t GetSourceGPIO(size_t scan_idx); 26 | uint8_t GetSinkGPIO(size_t scan_idx); 27 | bool IsSourceChange(size_t scan_idx); 28 | Keycode GetKeycodeAtLayer(uint8_t layer, size_t scan_idx); 29 | 30 | enum BuiltInCustomKeyCode { 31 | // Do not change the order of the mouse buttons, nor adding new items in 32 | // between mouse buttons. 33 | MSE_L = 0, 34 | MSE_R, 35 | MSE_M, 36 | MSE_BACK, 37 | MSE_FORWARD, 38 | LAYER_SWITCH, 39 | ENTER_CONFIG, 40 | CONFIG_SEL, 41 | BOOTSEL, 42 | REBOOT, 43 | TOTAL_BUILT_IN_KC 44 | }; 45 | 46 | #endif /* LAYOUT_H_ */ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 NoSegfault 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /linux/spi1-picomk-overlay.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | 5 | / { 6 | compatible = "brcm,bcm2835"; 7 | 8 | fragment@0 { 9 | target = <&gpio>; 10 | __overlay__ { 11 | spi1_pins: spi1_pins { 12 | brcm,pins = <19 20 21>; 13 | brcm,function = <3>; /* alt4 */ 14 | }; 15 | 16 | spi1_cs_pins: spi1_cs_pins { 17 | brcm,pins = <16>; 18 | brcm,function = <1>; /* output */ 19 | }; 20 | }; 21 | }; 22 | 23 | fragment@1 { 24 | target = <&spi1>; 25 | frag1: __overlay__ { 26 | /* needed to avoid dtc warning */ 27 | #address-cells = <1>; 28 | #size-cells = <0>; 29 | pinctrl-names = "default"; 30 | pinctrl-0 = <&spi1_pins &spi1_cs_pins>; 31 | cs-gpios = <&gpio 16 1>; 32 | status = "okay"; 33 | 34 | picomk: picomk@0 { 35 | compatible = "picomk,spi_board_v0"; 36 | reg = <0>; /* CE0 */ 37 | #address-cells = <1>; 38 | #size-cells = <0>; 39 | spi-max-frequency = <1000000>; 40 | status = "okay"; 41 | }; 42 | }; 43 | }; 44 | 45 | fragment@2 { 46 | target = <&aux>; 47 | __overlay__ { 48 | status = "okay"; 49 | }; 50 | }; 51 | 52 | fragment@3 { 53 | target = <&spidev1>; 54 | __overlay__ { 55 | status = "disabled"; 56 | }; 57 | }; 58 | 59 | __overrides__ { 60 | speed = <&picomk>,"spi-max-frequency:0"; 61 | }; 62 | }; -------------------------------------------------------------------------------- /pio/ws2812.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Derived from the WS2812 example in pico-example 3 | ; 4 | 5 | .program ws2812 6 | .side_set 1 7 | 8 | .define public T1 4 9 | .define public T2 3 10 | .define public T3 3 11 | 12 | .wrap_target 13 | bitloop: 14 | out x, 1 side 0 [T1 - 1] ; Side-set still takes place when instruction stalls 15 | jmp !x do_zero side 1 [T2 - 1] ; Branch on the bit we shifted out. Positive pulse 16 | do_one: 17 | jmp bitloop side 1 [T3 - 1] ; Continue driving high, for a long pulse 18 | do_zero: 19 | nop side 0 [T3 - 1] ; Or drive low, for a short pulse 20 | .wrap 21 | 22 | % c-sdk { 23 | #include "hardware/clocks.h" 24 | 25 | static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, 26 | float freq, bool rgbw) { 27 | pio_gpio_init(pio, pin); 28 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); 29 | 30 | pio_sm_config c = ws2812_program_get_default_config(offset); 31 | sm_config_set_sideset_pins(&c, pin); 32 | sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); 33 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 34 | 35 | int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; 36 | float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); 37 | sm_config_set_clkdiv(&c, div); 38 | 39 | pio_sm_init(pio, sm, offset, &c); 40 | pio_sm_set_enabled(pio, sm, true); 41 | } 42 | %} 43 | -------------------------------------------------------------------------------- /spi.h: -------------------------------------------------------------------------------- 1 | #ifndef SPI_H_ 2 | #define SPI_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "FreeRTOS.h" 8 | #include "base.h" 9 | #include "hardware/irq.h" 10 | #include "hardware/spi.h" 11 | #include "hardware/timer.h" 12 | #include "ibp.h" 13 | #include "utils.h" 14 | 15 | struct IBPSPIArgs { 16 | spi_inst_t* spi_port; 17 | uint32_t rx_pin; 18 | uint32_t tx_pin; 19 | uint32_t cs_pin; 20 | uint32_t sck_pin; 21 | uint32_t baud_rate; 22 | }; 23 | 24 | struct IBPIRQData { 25 | std::shared_ptr> rx_buffer; 26 | std::shared_ptr> tx_buffer; 27 | uint8_t rx_buf_idx; 28 | int8_t rx_packet_size; 29 | uint8_t tx_buf_idx; 30 | uint8_t tx_packet_size; 31 | SemaphoreHandle_t rx_handle; 32 | SemaphoreHandle_t tx_handle; 33 | spi_inst_t* spi_port; 34 | 35 | void Clear(); 36 | }; 37 | 38 | class IBPSPIBase : public virtual IBPDeviceBase { 39 | protected: 40 | IBPSPIBase(IBPSPIArgs args); 41 | 42 | bool TXEmpty(); 43 | 44 | void InitSPI(bool slave); 45 | void InitIRQData(irq_handler_t irq_handler); 46 | 47 | TaskHandle_t task_handle_; 48 | spi_inst_t* spi_port_; 49 | const uint32_t baud_rate_; 50 | const uint32_t rx_pin_; 51 | const uint32_t tx_pin_; 52 | const uint32_t cs_pin_; 53 | const uint32_t sck_pin_; 54 | IBPIRQData* irq_data_; 55 | }; 56 | 57 | class IBPSPIDevice : public IBPSPIBase { 58 | public: 59 | Status IBPInitialize() override; 60 | 61 | void DeviceTask(); 62 | 63 | protected: 64 | IBPSPIDevice(IBPSPIArgs args); 65 | }; 66 | 67 | class IBPSPIHost : public IBPSPIBase { 68 | public: 69 | Status IBPInitialize() override; 70 | 71 | void HostTask(); 72 | 73 | protected: 74 | IBPSPIHost(IBPSPIArgs args); 75 | }; 76 | 77 | #endif /* SPI_H_ */ -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H_ 2 | #define UTILS_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "FreeRTOS.h" 10 | #include "config.h" 11 | #include "hardware/sync.h" 12 | #include "semphr.h" 13 | 14 | // TODO: rename this 15 | enum status { OK, ERROR }; 16 | using Status = status; 17 | 18 | enum LogLevel { L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 }; 19 | 20 | #define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE) 21 | #define LOG(LEVEL, prefix, format, ...) \ 22 | ({ \ 23 | if (CONFIG_DEBUG_LOG_LEVEL >= LEVEL) { \ 24 | printf("%s %s:%d " format "\n", prefix, __FILENAME__, \ 25 | __LINE__ __VA_OPT__(, ) __VA_ARGS__); \ 26 | } \ 27 | 0; \ 28 | }) 29 | 30 | #define LOG_ERROR(format, ...) \ 31 | LOG(LogLevel::L_ERROR, "E", format __VA_OPT__(, ) __VA_ARGS__) 32 | #define LOG_WARNING(format, ...) \ 33 | LOG(LogLevel::L_WARNING, "W", format __VA_OPT__(, ) __VA_ARGS__) 34 | #define LOG_INFO(format, ...) \ 35 | LOG(LogLevel::L_INFO, "I", format __VA_OPT__(, ) __VA_ARGS__) 36 | #define LOG_DEBUG(format, ...) \ 37 | LOG(LogLevel::L_DEBUG, "D", format __VA_OPT__(, ) __VA_ARGS__) 38 | 39 | class LockSemaphore { 40 | public: 41 | explicit LockSemaphore(SemaphoreHandle_t semaphore); 42 | 43 | virtual ~LockSemaphore(); 44 | 45 | private: 46 | SemaphoreHandle_t semaphore_; 47 | }; 48 | 49 | class LockSpinlock { 50 | public: 51 | explicit LockSpinlock(spin_lock_t* lock); 52 | 53 | virtual ~LockSpinlock(); 54 | 55 | private: 56 | spin_lock_t* lock_; 57 | uint32_t irq_; 58 | }; 59 | 60 | #endif /* UTILS_H_ */ -------------------------------------------------------------------------------- /ibp.h: -------------------------------------------------------------------------------- 1 | #ifndef IBP_H_ 2 | #define IBP_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "FreeRTOS.h" 8 | #include "base.h" 9 | #include "semphr.h" 10 | 11 | extern "C" { 12 | #include "ibp_lib.h" 13 | } 14 | 15 | class IBPDeviceBase : public virtual KeyboardOutputDevice, 16 | public virtual MouseOutputDevice, 17 | public virtual GenericInputDevice, 18 | public virtual IBPDriverBase { 19 | public: 20 | IBPDeviceBase(); 21 | 22 | // No need to run anything on the output task as it's likely subclass will 23 | // have its own task for communication. 24 | void OutputTick() override {} 25 | 26 | // Input task 27 | 28 | void SendKeycode(uint8_t keycode) override; 29 | void SendKeycode(const std::vector& keycode) override; 30 | void SendConsumerKeycode(uint16_t keycode) override; 31 | void ChangeActiveLayers(const std::vector& layers) override; 32 | 33 | void MouseKeycode(uint8_t keycode) override; 34 | void MouseMovement(int8_t x, int8_t y) override; 35 | void Pan(int8_t x, int8_t y) override; 36 | 37 | void StartOfInputTick() override; 38 | void FinalizeInputTickOutput() override; 39 | 40 | void SetConfigMode(bool is_config_mode) { is_config_mode_ = is_config_mode; } 41 | 42 | void InputLoopStart() override {} 43 | void InputTick() override; 44 | 45 | protected: 46 | std::string GetOutPacket(); 47 | 48 | // Full packet with the transaction header. 49 | void SetInPacket(const std::string& packet); 50 | 51 | private: 52 | bool is_config_mode_; 53 | bool has_update_[IBP_TOTAL]; 54 | IBPSegment segments_[IBP_TOTAL]; 55 | std::string inbound_packet_; 56 | std::string outbound_packet_; 57 | 58 | // Protects both the inbound and outbound packets. One lock is enough because 59 | // there are usually only two tasks involved: the input task and the low level 60 | // protocol task. 61 | SemaphoreHandle_t packet_semaphore_; 62 | }; 63 | 64 | #endif /* IBP_H_ */ -------------------------------------------------------------------------------- /rotary_encoder.cc: -------------------------------------------------------------------------------- 1 | #include "rotary_encoder.h" 2 | 3 | #include "hardware/gpio.h" 4 | #include "tusb.h" 5 | #include "utils.h" 6 | 7 | RotaryEncoder::RotaryEncoder() : RotaryEncoder(0, 0, 1) {} 8 | RotaryEncoder::RotaryEncoder(uint8_t pin_a, uint8_t pin_b, uint8_t resolution) 9 | : pin_a_(pin_a), 10 | pin_b_(pin_b), 11 | resolution_(resolution), 12 | a_state_(false), 13 | pulse_count_(0), 14 | dir_(false), 15 | is_config_(false) { 16 | gpio_init(pin_a_); 17 | gpio_init(pin_b_); 18 | gpio_set_dir(pin_a_, GPIO_IN); 19 | gpio_set_dir(pin_b_, GPIO_IN); 20 | gpio_disable_pulls(pin_a_); 21 | gpio_disable_pulls(pin_b_); 22 | a_state_ = gpio_get(pin_a_); 23 | } 24 | 25 | void RotaryEncoder::InputTick() { 26 | const bool a_state = gpio_get(pin_a_); 27 | if (a_state != a_state_) { 28 | const bool b_state = gpio_get(pin_b_); 29 | const bool dir = a_state == b_state; 30 | if (dir == dir_) { 31 | if (++pulse_count_ >= resolution_) { 32 | HandleMovement(dir); 33 | pulse_count_ = 0; 34 | } 35 | } else { 36 | dir_ = dir; 37 | pulse_count_ = 1; 38 | } 39 | a_state_ = a_state; 40 | } 41 | } 42 | 43 | void RotaryEncoder::SetConfigMode(bool is_config_mode) { 44 | is_config_ = is_config_mode; 45 | } 46 | 47 | void RotaryEncoder::HandleMovement(bool dir) { 48 | if (is_config_) { 49 | if (config_modifier_ != NULL) { 50 | if (dir) { 51 | config_modifier_->Up(); 52 | } else { 53 | config_modifier_->Down(); 54 | } 55 | } 56 | } else { 57 | for (auto keyboard_out : *keyboard_output_) { 58 | keyboard_out->SendConsumerKeycode( 59 | dir ? HID_USAGE_CONSUMER_VOLUME_INCREMENT 60 | : HID_USAGE_CONSUMER_VOLUME_DECREMENT); 61 | } 62 | } 63 | } 64 | 65 | Status RegisterEncoder(uint8_t tag, uint8_t pin_a, uint8_t pin_b, 66 | uint8_t resolution) { 67 | return DeviceRegistry::RegisterInputDevice(tag, [=]() { 68 | return std::make_shared(pin_a, pin_b, resolution); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /configs/ibp_debug/layout.cc: -------------------------------------------------------------------------------- 1 | // A very simple setup with only one key for developing the IBP protocol. 2 | #include "layout_helper.h" 3 | 4 | #define CONFIG_NUM_PHY_ROWS 1 5 | #define CONFIG_NUM_PHY_COLS 1 6 | 7 | static constexpr GPIO kGPIOMatrix[CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 8 | {G(1, 2)}, 9 | }; 10 | 11 | static constexpr Keycode kKeyCodes[][CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = 12 | { 13 | [0] = 14 | { 15 | {K(K_2)}, 16 | }, 17 | }; 18 | 19 | // clang-format on 20 | 21 | // Compile time validation and conversion for the key matrix. Must include this. 22 | #include "layout_internal.inc" 23 | 24 | class IBPSPIDeviceDebug : public IBPSPIDevice { 25 | public: 26 | static std::shared_ptr GetIBPSPIDeviceDebug( 27 | IBPSPIArgs args) { 28 | static std::shared_ptr instance_ = NULL; 29 | if (instance_ == NULL) { 30 | instance_ = 31 | std::shared_ptr(new IBPSPIDeviceDebug(args)); 32 | } 33 | return instance_; 34 | } 35 | 36 | private: 37 | IBPSPIDeviceDebug(IBPSPIArgs args) : IBPSPIDevice(args) {} 38 | }; 39 | 40 | Status RegisterSPIOut(uint8_t tag, IBPSPIArgs args) { 41 | auto creator_fn = [=]() { 42 | return IBPSPIDeviceDebug::GetIBPSPIDeviceDebug(args); 43 | }; 44 | if (IBPDriverRegistry::RegisterDriver(tag, creator_fn) == OK && 45 | DeviceRegistry::RegisterKeyboardOutputDevice(tag, false, creator_fn) == 46 | OK) { 47 | return OK; 48 | } 49 | return ERROR; 50 | } 51 | 52 | static Status register1 = RegisterKeyscan(/*tag=*/0); 53 | static Status register2 = RegisterUSBKeyboardOutput(/*tag=*/1); 54 | static Status register3 = 55 | RegisterSPIOut(/*tag=*/2, {.spi_port = spi0, 56 | .rx_pin = PICO_DEFAULT_SPI_RX_PIN, 57 | .tx_pin = PICO_DEFAULT_SPI_TX_PIN, 58 | .cs_pin = PICO_DEFAULT_SPI_CSN_PIN, 59 | .sck_pin = PICO_DEFAULT_SPI_SCK_PIN, 60 | .baud_rate = 100000}); 61 | -------------------------------------------------------------------------------- /ibp_lib.h: -------------------------------------------------------------------------------- 1 | #ifndef IBP_LIB_H_ 2 | #define IBP_LIB_H_ 3 | 4 | #ifdef IBP_KERNEL_MODULE 5 | #include 6 | #else 7 | #include 8 | #include 9 | #endif 10 | 11 | #define IBP_MAX_PACKET_LEN 128 12 | #define IBP_MAX_KEYCODES 8 13 | #define IBP_MAX_ACTIVELAYERS 8 14 | #define IBP_INVALID_PACKET_DELAY_MS 1 15 | 16 | typedef enum { 17 | IBP_KEYCODE = 0, 18 | IBP_CONSUMER, 19 | IBP_MOUSE, 20 | IBP_ACTIVE_LAYERS, 21 | IBP_TOTAL, 22 | } FieldType; 23 | 24 | typedef struct { 25 | uint8_t modifier_bitmask; // Same as the USB HID. 26 | uint8_t num_keycodes : 3; 27 | uint8_t keycodes[IBP_MAX_KEYCODES]; 28 | } IBPKeyCodes; 29 | 30 | typedef struct { 31 | uint16_t consumer_keycode; 32 | } IBPConsumer; 33 | 34 | typedef struct { 35 | // For buttons. 36 | // 7 0 37 | // +------+------+------+------+------+------+------+------+ 38 | // | Reserved |FRWARD| BACK |MSE_M |MSE_R |MSE_L | 39 | // +------+------+------+------+------+------+------+------+ 40 | uint8_t button_bitmask; 41 | int8_t x; 42 | int8_t y; 43 | int8_t vertical; 44 | int8_t horizontal; 45 | } IBPMouse; 46 | 47 | typedef struct { 48 | uint8_t num_activated_layers : 3; 49 | uint8_t active_layers[IBP_MAX_ACTIVELAYERS]; 50 | } IBPLayers; 51 | 52 | typedef union { 53 | IBPKeyCodes keycodes; 54 | IBPConsumer consumer_keycode; 55 | IBPMouse mouse; 56 | IBPLayers layers; 57 | } FieldData; 58 | 59 | typedef struct { 60 | // 7 0 61 | // +------+------+------+------+------+------+------+------+ 62 | // | number of data bytes | field type |parity| 63 | // +------+------+------+------+------+------+------+------+ 64 | FieldType field_type : 3; 65 | FieldData field_data; 66 | } IBPSegment; 67 | 68 | int8_t SerializeSegments(const IBPSegment* segments, uint8_t num_segments, 69 | uint8_t* output, uint8_t buffer_size); 70 | int8_t DeSerializeSegment(const uint8_t* input, uint8_t input_buffer_size, 71 | IBPSegment* segment); 72 | int8_t GetTransactionTotalSize(uint8_t transaction_first_byte); 73 | 74 | #endif /* IBP_LIB_H_ */ -------------------------------------------------------------------------------- /configs/examples/home_screen/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // Required 5 | #include "FreeRTOSConfig.h" 6 | 7 | // Name of the keyboard. It'll be reported to USB host and show up when running 8 | // lsusb 9 | #define CONFIG_KEYBOARD_NAME "Pico Keyboard V0.4" 10 | 11 | // Ticks on key scan frequency and debouncing time. Tick here refers to FreeRTOS 12 | // tick, which is 1ms. 13 | #define CONFIG_SCAN_TICKS 5 14 | #define CONFIG_DEBOUNCE_TICKS 15 15 | 16 | // How frequent should we run the slow output tasks 17 | #define CONFIG_SLOW_TICKS 50 18 | 19 | // USB identifiers 20 | #define CONFIG_USB_VID 0xeceb 21 | #define CONFIG_USB_PID 0x3026 22 | #define CONFIG_USB_VENDER_NAME "PicoMK" 23 | #define CONFIG_USB_SERIAL_NUM "1234" 24 | 25 | // Configure the filesystem size and config file name 26 | #define CONFIG_FLASH_FILESYSTEM_SIZE (32 * 4096) 27 | #define CONFIG_FLASH_JSON_FILE_NAME "config.json" 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Advanced options (usually no need to modify) 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | // How many us should we wait between each GPIO sink row scan, for the wires to 34 | // fully discharge. Usually 1 us is good enough. 35 | #define CONFIG_GPIO_SINK_DELAY_US 1 36 | 37 | // Stack size of the tasks. Usually no need to change 38 | #define CONFIG_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE * 4) 39 | #define CONFIG_TASK_PRIORITY (configMAX_PRIORITIES - 2) 40 | 41 | // Frequency for USB polls. 1 ms is usually good 42 | #define CONFIG_USB_POLL_MS 1 43 | 44 | // Enable or disable USB serial debug 45 | 46 | // Set to 1 to enable USB serial debug. 47 | #define CONFIG_DEBUG_ENABLE_USB_SERIAL 0 48 | 49 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 50 | 51 | // Log level for debugging logs 52 | // L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 53 | // Level <= CONFIG_DEBUG_LOG_LEVEL will be shown 54 | #define CONFIG_DEBUG_LOG_LEVEL 2 55 | 56 | // Don't change these three unless you know what you're doing 57 | #define CONFIG_DEBUG_USB_SERIAL_CDC_CMD_MAX_SIZE 8 58 | #define CONFIG_DEBUG_USB_BUFFER_SIZE 64 59 | #define CONFIG_DEBUG_USB_TIMEOUT_US 500000 60 | 61 | #else 62 | 63 | #define CONFIG_DEBUG_LOG_LEVEL 0 // Disable all logs 64 | 65 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 66 | 67 | #endif /* CONFIG_H_ */ 68 | -------------------------------------------------------------------------------- /configs/cyberkeeb_2040/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // Required 5 | #include "FreeRTOSConfig.h" 6 | 7 | // Name of the keyboard. It'll be reported to USB host and show up when running 8 | // lsusb 9 | #define CONFIG_KEYBOARD_NAME "CyberKeeb 2040" 10 | 11 | // Ticks on key scan frequency and debouncing time. Tick here refers to FreeRTOS 12 | // tick, which is 1ms. 13 | #define CONFIG_SCAN_TICKS 5 14 | #define CONFIG_DEBOUNCE_TICKS 15 15 | 16 | // How frequent should we run the slow output tasks 17 | #define CONFIG_SLOW_TICKS 50 18 | 19 | // USB identifiers. Modify if necessary. 20 | #define CONFIG_USB_VID 0xeceb 21 | #define CONFIG_USB_PID 0x3026 22 | #define CONFIG_USB_VENDER_NAME "PicoMK" 23 | #define CONFIG_USB_SERIAL_NUM "1234" 24 | 25 | // Configure the filesystem size and config file name 26 | #define CONFIG_FLASH_FILESYSTEM_SIZE (32 * 4096) 27 | #define CONFIG_FLASH_JSON_FILE_NAME "config.json" 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Advanced options (usually no need to modify) 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | // How many us should we wait between each GPIO sink row scan, for the wires to 34 | // fully discharge. Usually 1 us is good enough. 35 | #define CONFIG_GPIO_SINK_DELAY_US 1 36 | 37 | // Stack size of the tasks. Usually no need to change 38 | #define CONFIG_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE * 4) 39 | 40 | // Priority of the tasks. Usually no need to change 41 | #define CONFIG_TASK_PRIORITY (configMAX_PRIORITIES - 2) 42 | 43 | // Frequency for USB polls. 1 ms is usually good 44 | #define CONFIG_USB_POLL_MS 1 45 | 46 | // Enable or disable USB serial debug 47 | 48 | // Set to 1 to enable USB serial debug. 49 | #define CONFIG_DEBUG_ENABLE_USB_SERIAL 0 50 | 51 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 52 | 53 | // Log level for debugging logs 54 | // L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 55 | // Level <= CONFIG_DEBUG_LOG_LEVEL will be shown 56 | #define CONFIG_DEBUG_LOG_LEVEL 2 57 | 58 | // Don't change these three unless you know what you're doing 59 | #define CONFIG_DEBUG_USB_SERIAL_CDC_CMD_MAX_SIZE 8 60 | #define CONFIG_DEBUG_USB_BUFFER_SIZE 64 61 | #define CONFIG_DEBUG_USB_TIMEOUT_US 500000 62 | 63 | #else 64 | 65 | #define CONFIG_DEBUG_LOG_LEVEL 0 // Disable all logs 66 | 67 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 68 | 69 | #endif /* CONFIG_H_ */ 70 | -------------------------------------------------------------------------------- /configs/default/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // Required 5 | #include "FreeRTOSConfig.h" 6 | 7 | // Name of the keyboard. It'll be reported to USB host and show up when running 8 | // lsusb 9 | #define CONFIG_KEYBOARD_NAME "Pico Keyboard V0.4" 10 | 11 | // Ticks on key scan frequency and debouncing time. Tick here refers to FreeRTOS 12 | // tick, which is 1ms. 13 | #define CONFIG_SCAN_TICKS 5 14 | #define CONFIG_DEBOUNCE_TICKS 15 15 | 16 | // How frequent should we run the slow output tasks 17 | #define CONFIG_SLOW_TICKS 50 18 | 19 | // USB identifiers. Modify if necessary. 20 | #define CONFIG_USB_VID 0xeceb 21 | #define CONFIG_USB_PID 0x3026 22 | #define CONFIG_USB_VENDER_NAME "PicoMK" 23 | #define CONFIG_USB_SERIAL_NUM "1234" 24 | 25 | // Configure the filesystem size and config file name 26 | #define CONFIG_FLASH_FILESYSTEM_SIZE (32 * 4096) 27 | #define CONFIG_FLASH_JSON_FILE_NAME "config.json" 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Advanced options (usually no need to modify) 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | // How many us should we wait between each GPIO sink row scan, for the wires to 34 | // fully discharge. Usually 1 us is good enough. 35 | #define CONFIG_GPIO_SINK_DELAY_US 1 36 | 37 | // Stack size of the tasks. Usually no need to change 38 | #define CONFIG_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE * 4) 39 | 40 | // Priority of the tasks. Usually no need to change 41 | #define CONFIG_TASK_PRIORITY (configMAX_PRIORITIES - 2) 42 | 43 | // Frequency for USB polls. 1 ms is usually good 44 | #define CONFIG_USB_POLL_MS 1 45 | 46 | // Enable or disable USB serial debug 47 | 48 | // Set to 1 to enable USB serial debug. 49 | #define CONFIG_DEBUG_ENABLE_USB_SERIAL 0 50 | 51 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 52 | 53 | // Log level for debugging logs 54 | // L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 55 | // Level <= CONFIG_DEBUG_LOG_LEVEL will be shown 56 | #define CONFIG_DEBUG_LOG_LEVEL 2 57 | 58 | // Don't change these three unless you know what you're doing 59 | #define CONFIG_DEBUG_USB_SERIAL_CDC_CMD_MAX_SIZE 8 60 | #define CONFIG_DEBUG_USB_BUFFER_SIZE 64 61 | #define CONFIG_DEBUG_USB_TIMEOUT_US 500000 62 | 63 | #else 64 | 65 | #define CONFIG_DEBUG_LOG_LEVEL 0 // Disable all logs 66 | 67 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 68 | 69 | #endif /* CONFIG_H_ */ 70 | -------------------------------------------------------------------------------- /configs/ibp_debug/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // Required 5 | #include "FreeRTOSConfig.h" 6 | 7 | // Name of the keyboard. It'll be reported to USB host and show up when running 8 | // lsusb 9 | #define CONFIG_KEYBOARD_NAME "SPI Debug Build" 10 | 11 | // Ticks on key scan frequency and debouncing time. Tick here refers to FreeRTOS 12 | // tick, which is 1ms. 13 | #define CONFIG_SCAN_TICKS 5 14 | #define CONFIG_DEBOUNCE_TICKS 15 15 | 16 | // How frequent should we run the slow output tasks 17 | #define CONFIG_SLOW_TICKS 50 18 | 19 | // USB identifiers. Modify if necessary. 20 | #define CONFIG_USB_VID 0xeceb 21 | #define CONFIG_USB_PID 0x3026 22 | #define CONFIG_USB_VENDER_NAME "PicoMK" 23 | #define CONFIG_USB_SERIAL_NUM "1234" 24 | 25 | // Configure the filesystem size and config file name 26 | #define CONFIG_FLASH_FILESYSTEM_SIZE (32 * 4096) 27 | #define CONFIG_FLASH_JSON_FILE_NAME "config.json" 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Advanced options (usually no need to modify) 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | // How many us should we wait between each GPIO sink row scan, for the wires to 34 | // fully discharge. Usually 1 us is good enough. 35 | #define CONFIG_GPIO_SINK_DELAY_US 1 36 | 37 | // Stack size of the tasks. Usually no need to change 38 | #define CONFIG_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE * 4) 39 | 40 | // Priority of the tasks. Usually no need to change 41 | #define CONFIG_TASK_PRIORITY (configMAX_PRIORITIES - 2) 42 | 43 | // Frequency for USB polls. 1 ms is usually good 44 | #define CONFIG_USB_POLL_MS 1 45 | 46 | // Enable or disable USB serial debug 47 | 48 | // Set to 1 to enable USB serial debug. 49 | #define CONFIG_DEBUG_ENABLE_USB_SERIAL 1 50 | 51 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 52 | 53 | // Log level for debugging logs 54 | // L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 55 | // Level <= CONFIG_DEBUG_LOG_LEVEL will be shown 56 | #define CONFIG_DEBUG_LOG_LEVEL 3 57 | 58 | // Don't change these three unless you know what you're doing 59 | #define CONFIG_DEBUG_USB_SERIAL_CDC_CMD_MAX_SIZE 8 60 | #define CONFIG_DEBUG_USB_BUFFER_SIZE 64 61 | #define CONFIG_DEBUG_USB_TIMEOUT_US 500000 62 | 63 | #else 64 | 65 | #define CONFIG_DEBUG_LOG_LEVEL 0 // Disable all logs 66 | 67 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 68 | 69 | #endif /* CONFIG_H_ */ 70 | -------------------------------------------------------------------------------- /configs/examples/bare_minimum/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // Required 5 | #include "FreeRTOSConfig.h" 6 | 7 | // Name of the keyboard. It'll be reported to USB host and show up when running 8 | // lsusb 9 | #define CONFIG_KEYBOARD_NAME "Pico Keyboard V0.4" 10 | 11 | // Ticks on key scan frequency and debouncing time. Tick here refers to FreeRTOS 12 | // tick, which is 1ms. 13 | #define CONFIG_SCAN_TICKS 5 14 | #define CONFIG_DEBOUNCE_TICKS 15 15 | 16 | // How frequent should we run the slow output tasks 17 | #define CONFIG_SLOW_TICKS 50 18 | 19 | // USB identifiers. Modify if necessary. 20 | #define CONFIG_USB_VID 0xeceb 21 | #define CONFIG_USB_PID 0x3026 22 | #define CONFIG_USB_VENDER_NAME "PicoMK" 23 | #define CONFIG_USB_SERIAL_NUM "1234" 24 | 25 | // Configure the filesystem size and config file name 26 | #define CONFIG_FLASH_FILESYSTEM_SIZE (32 * 4096) 27 | #define CONFIG_FLASH_JSON_FILE_NAME "config.json" 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Advanced options (usually no need to modify) 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | // How many us should we wait between each GPIO sink row scan, for the wires to 34 | // fully discharge. Usually 1 us is good enough. 35 | #define CONFIG_GPIO_SINK_DELAY_US 1 36 | 37 | // Stack size of the tasks. Usually no need to change 38 | #define CONFIG_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE * 4) 39 | 40 | // Priority of the tasks. Usually no need to change 41 | #define CONFIG_TASK_PRIORITY (configMAX_PRIORITIES - 2) 42 | 43 | // Frequency for USB polls. 1 ms is usually good 44 | #define CONFIG_USB_POLL_MS 1 45 | 46 | // Enable or disable USB serial debug 47 | 48 | // Set to 1 to enable USB serial debug. 49 | #define CONFIG_DEBUG_ENABLE_USB_SERIAL 0 50 | 51 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 52 | 53 | // Log level for debugging logs 54 | // L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 55 | // Level <= CONFIG_DEBUG_LOG_LEVEL will be shown 56 | #define CONFIG_DEBUG_LOG_LEVEL 2 57 | 58 | // Don't change these three unless you know what you're doing 59 | #define CONFIG_DEBUG_USB_SERIAL_CDC_CMD_MAX_SIZE 8 60 | #define CONFIG_DEBUG_USB_BUFFER_SIZE 64 61 | #define CONFIG_DEBUG_USB_TIMEOUT_US 500000 62 | 63 | #else 64 | 65 | #define CONFIG_DEBUG_LOG_LEVEL 0 // Disable all logs 66 | 67 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 68 | 69 | #endif /* CONFIG_H_ */ 70 | -------------------------------------------------------------------------------- /configs/examples/custom_keycode/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // Required 5 | #include "FreeRTOSConfig.h" 6 | 7 | // Name of the keyboard. It'll be reported to USB host and show up when running 8 | // lsusb 9 | #define CONFIG_KEYBOARD_NAME "Pico Keyboard V0.4" 10 | 11 | // Ticks on key scan frequency and debouncing time. Tick here refers to FreeRTOS 12 | // tick, which is 1ms. 13 | #define CONFIG_SCAN_TICKS 5 14 | #define CONFIG_DEBOUNCE_TICKS 15 15 | 16 | // How frequent should we run the slow output tasks 17 | #define CONFIG_SLOW_TICKS 50 18 | 19 | // USB identifiers. Modify if necessary. 20 | #define CONFIG_USB_VID 0xeceb 21 | #define CONFIG_USB_PID 0x3026 22 | #define CONFIG_USB_VENDER_NAME "PicoMK" 23 | #define CONFIG_USB_SERIAL_NUM "1234" 24 | 25 | // Configure the filesystem size and config file name 26 | #define CONFIG_FLASH_FILESYSTEM_SIZE (32 * 4096) 27 | #define CONFIG_FLASH_JSON_FILE_NAME "config.json" 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | // Advanced options (usually no need to modify) 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | // How many us should we wait between each GPIO sink row scan, for the wires to 34 | // fully discharge. Usually 1 us is good enough. 35 | #define CONFIG_GPIO_SINK_DELAY_US 1 36 | 37 | // Stack size of the tasks. Usually no need to change 38 | #define CONFIG_TASK_STACK_SIZE (configMINIMAL_STACK_SIZE * 4) 39 | 40 | // Priority of the tasks. Usually no need to change 41 | #define CONFIG_TASK_PRIORITY (configMAX_PRIORITIES - 2) 42 | 43 | // Frequency for USB polls. 1 ms is usually good 44 | #define CONFIG_USB_POLL_MS 1 45 | 46 | // Enable or disable USB serial debug 47 | 48 | // Set to 1 to enable USB serial debug. 49 | #define CONFIG_DEBUG_ENABLE_USB_SERIAL 0 50 | 51 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 52 | 53 | // Log level for debugging logs 54 | // L_ERROR = 1, L_WARNING = 2, L_INFO = 3, L_DEBUG = 4 55 | // Level <= CONFIG_DEBUG_LOG_LEVEL will be shown 56 | #define CONFIG_DEBUG_LOG_LEVEL 2 57 | 58 | // Don't change these three unless you know what you're doing 59 | #define CONFIG_DEBUG_USB_SERIAL_CDC_CMD_MAX_SIZE 8 60 | #define CONFIG_DEBUG_USB_BUFFER_SIZE 64 61 | #define CONFIG_DEBUG_USB_TIMEOUT_US 500000 62 | 63 | #else 64 | 65 | #define CONFIG_DEBUG_LOG_LEVEL 0 // Disable all logs 66 | 67 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 68 | 69 | #endif /* CONFIG_H_ */ 70 | -------------------------------------------------------------------------------- /ws2812.h: -------------------------------------------------------------------------------- 1 | // Derived from the WS2812 example in pico-example 2 | 3 | #ifndef WH2812_H_ 4 | #define WH2812_H_ 5 | 6 | #include 7 | 8 | #include "FreeRTOS.h" 9 | #include "base.h" 10 | #include "configuration.h" 11 | #include "hardware/pio.h" 12 | #include "semphr.h" 13 | #include "utils.h" 14 | 15 | class WS2812 : public LEDOutputDevice { 16 | public: 17 | enum Mode { SET_PIXEL = 0, BREATH, ROTATE, TOTAL }; 18 | 19 | WS2812(uint8_t pin, uint8_t num_pixels, float max_brightness, PIO pio, 20 | uint8_t state_machine); 21 | 22 | void OutputTick() override; 23 | 24 | void StartOfInputTick() override; 25 | void FinalizeInputTickOutput() override; 26 | 27 | void IncreaseBrightness() override; 28 | void DecreaseBrightness() override; 29 | void IncreaseAnimationSpeed() override; 30 | void DecreaseAnimationSpeed() override; 31 | size_t NumPixels() const override { return double_buffer_[0].size(); } 32 | void SetFixedColor(uint8_t w, uint8_t r, uint8_t g, uint8_t b) override; 33 | void SetPixel(size_t idx, uint8_t w, uint8_t r, uint8_t g, 34 | uint8_t b) override; 35 | 36 | void OnUpdateConfig(const Config* config) override; 37 | void SetConfigMode(bool is_config_mode) override; 38 | std::pair> CreateDefaultConfig() 39 | override; 40 | 41 | void SetLedStatus(LEDIndicators indicators) override {} 42 | 43 | void SuspendEvent(bool is_suspend) override; 44 | 45 | protected: 46 | uint32_t RescaleByBrightness(float brightness, uint32_t pixel); 47 | uint32_t CombineColors(uint8_t r, uint8_t g, uint8_t b); 48 | void SeparateColors(uint32_t pixel, uint8_t* r, uint8_t* g, uint8_t* b); 49 | void PutPixel(uint32_t pixel); 50 | 51 | void BreathAnimation(float brightness); 52 | void RotateAnimation(float brightness); 53 | 54 | const uint8_t pin_; 55 | const PIO pio_; 56 | const uint8_t sm_; 57 | const float max_brightness_; 58 | float brightness_; 59 | Mode mode_; 60 | bool redraw_; 61 | bool enabled_; 62 | bool buffer_changed_; 63 | uint8_t active_buffer_; 64 | uint8_t tick_divider_; 65 | uint8_t counter_; 66 | std::vector> double_buffer_; 67 | std::vector random_buffer_; 68 | uint8_t rotate_idx_; 69 | float breath_scalar_; 70 | float breath_scalar_delta_; 71 | bool suspend_; 72 | 73 | SemaphoreHandle_t semaphore_; 74 | }; 75 | 76 | Status RegisterWS2812(uint8_t tag, uint8_t pin, uint8_t num_pixels, 77 | float max_brightness = 0.5, PIO pio = pio0, 78 | uint8_t state_machine = 0); 79 | 80 | #endif /* WH2812_H_ */ 81 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false, 3 | "cmake.buildBeforeRun": true, 4 | "cmake.statusbar.advanced": { 5 | "debug": { 6 | "visibility": "hidden" 7 | }, 8 | "launch": { 9 | "visibility": "hidden" 10 | }, 11 | "build": { 12 | "visibility": "hidden" 13 | }, 14 | "buildTarget": { 15 | "visibility": "hidden" 16 | }, 17 | }, 18 | "files.associations": { 19 | "config.h": "c", 20 | "task.h": "c", 21 | "stdlib.h": "c", 22 | "pico.h": "c", 23 | "types.h": "c", 24 | "stddef.h": "c", 25 | "tusb.h": "c", 26 | "gpio.h": "c", 27 | "hid.h": "c", 28 | "*.inc": "cpp", 29 | "algorithm": "cpp", 30 | "array": "cpp", 31 | "atomic": "cpp", 32 | "bit": "cpp", 33 | "*.tcc": "cpp", 34 | "cassert": "cpp", 35 | "cctype": "cpp", 36 | "cerrno": "cpp", 37 | "cfloat": "cpp", 38 | "climits": "cpp", 39 | "clocale": "cpp", 40 | "cmath": "cpp", 41 | "compare": "cpp", 42 | "concepts": "cpp", 43 | "cstdarg": "cpp", 44 | "cstddef": "cpp", 45 | "cstdint": "cpp", 46 | "cstdio": "cpp", 47 | "cstdlib": "cpp", 48 | "cstring": "cpp", 49 | "ctime": "cpp", 50 | "cwchar": "cpp", 51 | "cwctype": "cpp", 52 | "deque": "cpp", 53 | "map": "cpp", 54 | "set": "cpp", 55 | "string": "cpp", 56 | "unordered_map": "cpp", 57 | "vector": "cpp", 58 | "exception": "cpp", 59 | "functional": "cpp", 60 | "iterator": "cpp", 61 | "memory": "cpp", 62 | "memory_resource": "cpp", 63 | "numeric": "cpp", 64 | "random": "cpp", 65 | "string_view": "cpp", 66 | "system_error": "cpp", 67 | "tuple": "cpp", 68 | "type_traits": "cpp", 69 | "utility": "cpp", 70 | "fstream": "cpp", 71 | "initializer_list": "cpp", 72 | "iomanip": "cpp", 73 | "ios": "cpp", 74 | "iosfwd": "cpp", 75 | "iostream": "cpp", 76 | "istream": "cpp", 77 | "limits": "cpp", 78 | "locale": "cpp", 79 | "new": "cpp", 80 | "numbers": "cpp", 81 | "ostream": "cpp", 82 | "queue": "cpp", 83 | "sstream": "cpp", 84 | "stdexcept": "cpp", 85 | "streambuf": "cpp", 86 | "cinttypes": "cpp", 87 | "cstdbool": "cpp", 88 | "typeinfo": "cpp", 89 | "optional": "cpp", 90 | "lfs.h": "c", 91 | "netfwd": "cpp", 92 | "ratio": "cpp", 93 | "span": "cpp", 94 | "ibp_lib.h": "c" 95 | }, 96 | "C_Cpp.intelliSenseEngineFallback": "enabled", 97 | "C_Cpp.files.exclude": { 98 | 99 | "**/.vscode": true, 100 | "**/.vs": true 101 | }, 102 | "C_Cpp.default.cppStandard": "c++20", 103 | "C_Cpp.default.cStandard": "c23", 104 | } -------------------------------------------------------------------------------- /ssd1306.h: -------------------------------------------------------------------------------- 1 | #ifndef SSD1306_H_ 2 | #define SSD1306_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "FreeRTOS.h" 8 | #include "base.h" 9 | #include "configuration.h" 10 | #include "hardware/i2c.h" 11 | #include "pico-ssd1306/ssd1306.h" 12 | #include "semphr.h" 13 | #include "utils.h" 14 | 15 | class SSD1306Display : virtual public ScreenMixinBase { 16 | public: 17 | enum NumRows { R_32 = 32, R_64 = 64 }; 18 | 19 | SSD1306Display(i2c_inst_t* i2c, uint8_t sda_pin, uint8_t scl_pin, 20 | uint8_t i2c_addr, NumRows num_rows, bool flip); 21 | 22 | void OnUpdateConfig(const Config* config) override; 23 | std::pair> CreateDefaultConfig() 24 | override; 25 | 26 | void SetConfigMode(bool is_config_mode) override; 27 | void OutputTick() override; 28 | 29 | void StartOfInputTick() override; 30 | void FinalizeInputTickOutput() override; 31 | 32 | size_t GetNumRows() const override { return num_rows_; } 33 | size_t GetNumCols() const override { return num_cols_; } 34 | bool IsConfigMode() const override { return config_mode_; } 35 | void SetPixel(size_t row, size_t col, Mode mode) override; 36 | void DrawLine(size_t start_row, size_t start_col, size_t end_row, 37 | size_t end_col, Mode mode) override; 38 | void DrawRect(size_t start_row, size_t start_col, size_t end_row, 39 | size_t end_col, bool fill, Mode mode) override; 40 | void DrawText(size_t row, size_t col, const std::string& text, Font font, 41 | Mode mode) override; 42 | void DrawText(size_t row, size_t col, const std::string& text, 43 | const CustomFont& font, Mode mode) override; 44 | void DrawBuffer(const std::vector& buffer, size_t start_row, 45 | size_t start_col, size_t end_row, size_t end_col) override {} 46 | void Clear() override { 47 | display_->clear(); 48 | buffer_changed_ = true; 49 | } 50 | 51 | // Don't need to do anything special since we already have the sleep 52 | void SuspendEvent(bool is_suspend) override {} 53 | 54 | static Status Register(uint8_t key, bool slow, 55 | const std::shared_ptr ptr); 56 | 57 | protected: 58 | void CMD(uint8_t cmd); 59 | 60 | i2c_inst_t* const i2c_; 61 | const uint8_t sda_pin_; 62 | const uint8_t scl_pin_; 63 | const uint8_t i2c_addr_; 64 | const size_t num_rows_; 65 | const size_t num_cols_; 66 | uint32_t sleep_s_; 67 | 68 | std::unique_ptr display_; 69 | 70 | std::array buffer_; 71 | std::array out_buffer_; 72 | 73 | bool buffer_changed_; 74 | bool send_buffer_; 75 | uint32_t last_active_s_; 76 | bool sleep_; 77 | bool config_mode_; 78 | 79 | SemaphoreHandle_t semaphore_; 80 | }; 81 | 82 | #endif /* SSD1306_H_ */ 83 | -------------------------------------------------------------------------------- /docs/devices.md: -------------------------------------------------------------------------------- 1 | # Devices and Registration Functions 2 | 3 | ## Config Modifier 4 | 5 | Registration function: 6 | 7 | ```cpp 8 | Status RegisterConfigModifier(uint8_t screen_tag); 9 | ``` 10 | 11 | Unlike others, config modifier doesn't need a tag to identify it, since there can be at most one. It takes the tag of the screen to display the config menu as input. 12 | 13 | 14 | ## USB I/O 15 | 16 | Registration functions: 17 | 18 | ```cpp 19 | Status RegisterUSBKeyboardOutput(uint8_t tag); 20 | Status RegisterUSBMouseOutput(uint8_t tag); 21 | Status RegisterUSBInput(uint8_t tag); 22 | ``` 23 | 24 | `USBKeyboardOutput` is responsible for sending keyboard HID reports to the host, and `USBMouseOutput` is for sending mouse HID reports to the host. `USBInput` is for handling various requests from host, such as suspend, capslock LED etc. 25 | 26 | ## Key Scan 27 | 28 | Registration function: 29 | 30 | ```cpp 31 | Status RegisterKeyscan(uint8_t tag); 32 | ``` 33 | 34 | For scanning the key matrix. 35 | 36 | ## Joystick 37 | 38 | Registration function: 39 | 40 | ```cpp 41 | Status RegisterJoystick(uint8_t tag, uint8_t x_adc_pin, uint8_t y_adc_pin, 42 | size_t buffer_size, bool flip_x_dir, bool flip_y_dir, 43 | bool flip_vertical_scroll, uint8_t alt_layer); 44 | ``` 45 | 46 | The default implementation of a joystick. You can register multiple instances. Normally, it sends joystick input as mouse movement. `buffer_size` is the size of smoothing buffer. The larger the buffer, the less noisy the reading, and longer it takes for movement to show up on screen. `alt_layer` is the layer number that if activated, will send joystick input as horizontal and vertical scrolling. 47 | 48 | ## Rotary Encoder 49 | 50 | Registration function: 51 | 52 | ```cpp 53 | Status RegisterEncoder(uint8_t tag, uint8_t pin_a, uint8_t pin_b, 54 | uint8_t resolution); 55 | ``` 56 | 57 | The default implementation of a rotary encoder. You can register multiple instances. `resolution` is how many readings to count as one movement. 58 | 59 | ## WS2812 60 | 61 | Registration function: 62 | 63 | ```cpp 64 | Status RegisterWS2812(uint8_t tag, uint8_t pin, uint8_t num_pixels, 65 | float max_brightness = 0.5, PIO pio = pio0, 66 | uint8_t state_machine = 0); 67 | ``` 68 | 69 | `pin` is for the pin connecting to DIN. `max_brightness` is a scaler in range of [0, 1] that controls how bright **each** RGB LED can get. If `max_brightness` is 1, then each RGB LED can get max brightness of `255`, and if `max_brightness` is 0.2, then each RGB LED can get max brightness of `51`. Don't modify `pio` and `state_machine` unless you know what they do (i.e. you know how pio works on RP2040). 70 | 71 | ## Temperature Sensor 72 | 73 | Read the RP2040 onboard temperature sensor. 74 | 75 | Registration function: 76 | 77 | ```cpp 78 | Status RegisterTemperatureInput(uint8_t tag); 79 | ``` 80 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generated Cmake Pico project file 2 | 3 | cmake_minimum_required(VERSION 3.13...3.27) 4 | 5 | 6 | # Hack to remove dir path from the log source file path 7 | string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE) 8 | add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}") 9 | 10 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 11 | 12 | set(CMAKE_C_STANDARD 23) 13 | set(CMAKE_CXX_STANDARD 20) 14 | 15 | # Initialise pico_sdk from installed location 16 | # (note this can come from environment, CMake cache etc) 17 | set(PICO_SDK_PATH "${CMAKE_CURRENT_SOURCE_DIR}/pico-sdk") 18 | set(FREERTOS_KERNEL_PATH, "${CMAKE_CURRENT_SOURCE_DIR}/FreeRTOS-Kernel") 19 | 20 | # Pull in Raspberry Pi Pico SDK (must be before project) 21 | include(pico-sdk/pico_sdk_init.cmake) 22 | include(FreeRTOS_Kernel_import.cmake) 23 | include(config.cmake) 24 | 25 | project(firmware C CXX ASM) 26 | 27 | # Initialise the Raspberry Pi Pico SDK 28 | pico_sdk_init() 29 | 30 | # Add executable. Default name is the project name, version 0.1 31 | 32 | add_executable(firmware 33 | main.cc 34 | configs/${BOARD_CONFIG}/layout.cc 35 | keyscan.cc 36 | usb.cc 37 | joystick.cc 38 | runner.cc 39 | base.cc 40 | utils.cc 41 | rotary_encoder.cc 42 | ssd1306.cc 43 | config_modifier.cc 44 | builtin_keycode.cc 45 | configuration.cc 46 | storage.cc 47 | sync.cc 48 | temperature.cc 49 | ws2812.cc 50 | cJSON/cJSON.c 51 | display_mixins.cc 52 | ibp_lib.c 53 | ibp.cc 54 | spi.cc) 55 | 56 | 57 | file(GLOB pio "${CMAKE_CURRENT_LIST_DIR}/pio/*.pio") 58 | pico_generate_pio_header(firmware ${pio}) 59 | 60 | pico_set_program_name(firmware "PicoMK") 61 | pico_set_program_version(firmware "0.1") 62 | 63 | pico_enable_stdio_uart(firmware 0) 64 | pico_enable_stdio_usb(firmware 0) 65 | 66 | pico_set_float_implementation(firmware pico) 67 | 68 | # Add the standard library to the build 69 | target_link_libraries(firmware 70 | pico_stdlib 71 | tinyusb_device 72 | FreeRTOS-Kernel 73 | FreeRTOS-Kernel-Heap4) 74 | 75 | add_compile_definitions( 76 | PICO_HEAP_SIZE=65536) 77 | 78 | add_subdirectory(pico-ssd1306) 79 | add_subdirectory(littlefs) 80 | 81 | # Add any user requested libraries 82 | target_link_libraries(firmware 83 | hardware_adc 84 | hardware_i2c 85 | hardware_clocks 86 | hardware_watchdog 87 | hardware_regs 88 | hardware_pio 89 | pico_ssd1306 90 | hardware_spi 91 | hardware_irq 92 | littlefs 93 | ) 94 | 95 | target_include_directories(firmware PRIVATE 96 | ${CMAKE_CURRENT_LIST_DIR} 97 | ${CMAKE_CURRENT_LIST_DIR}/configs/${BOARD_CONFIG} 98 | ) 99 | 100 | pico_add_extra_outputs(firmware) 101 | 102 | -------------------------------------------------------------------------------- /configuration.cc: -------------------------------------------------------------------------------- 1 | #include "configuration.h" 2 | 3 | #include "cJSON/cJSON.h" 4 | #include "utils.h" 5 | 6 | std::string ConfigObject::ToJSON() const { 7 | cJSON* root = ToCJSON(); 8 | if (root == NULL) { 9 | LOG_ERROR("Failed to create json for ConfigObject"); 10 | return ""; 11 | } 12 | char* string = cJSON_Print(root); 13 | if (string == NULL) { 14 | LOG_ERROR("Failed to print json"); 15 | } 16 | std::string output(string); 17 | free((void*)string); 18 | cJSON_Delete(root); 19 | return output; 20 | } 21 | 22 | cJSON* ConfigObject::ToCJSON() const { 23 | cJSON* root = cJSON_CreateObject(); 24 | if (root == NULL) { 25 | return NULL; 26 | } 27 | for (const auto& [k, v] : members_) { 28 | cJSON* json = v->ToCJSON(); 29 | if (json == NULL) { 30 | LOG_WARNING("%s returned NULL json ptr", k); 31 | } 32 | cJSON_AddItemToObject(root, k.c_str(), json); 33 | } 34 | return root; 35 | } 36 | 37 | Status ConfigObject::FromCJSON(const cJSON* json) { 38 | if (json == NULL || !cJSON_IsObject(json)) { 39 | return ERROR; 40 | } 41 | for (auto& [k, v] : members_) { 42 | const cJSON* child = cJSON_GetObjectItemCaseSensitive(json, k.c_str()); 43 | if (v->FromCJSON(child) != OK) { 44 | return ERROR; 45 | } 46 | } 47 | return OK; 48 | } 49 | 50 | cJSON* ConfigList::ToCJSON() const { 51 | cJSON* root = cJSON_CreateArray(); 52 | if (root == NULL) { 53 | return NULL; 54 | } 55 | for (const auto& v : list_) { 56 | cJSON* json = v->ToCJSON(); 57 | if (json == NULL) { 58 | LOG_WARNING("NULL json ptr"); 59 | } 60 | cJSON_AddItemToArray(root, json); 61 | } 62 | return root; 63 | } 64 | 65 | Status ConfigList::FromCJSON(const cJSON* json) { 66 | if (json == NULL || !cJSON_IsArray(json) || 67 | cJSON_GetArraySize(json) != list_.size()) { 68 | return ERROR; 69 | } 70 | for (size_t i = 0; i < list_.size(); ++i) { 71 | const cJSON* child = cJSON_GetArrayItem(json, i); 72 | if (list_[i]->FromCJSON(child) != OK) { 73 | return ERROR; 74 | } 75 | } 76 | return OK; 77 | } 78 | 79 | cJSON* ConfigInt::ToCJSON() const { return cJSON_CreateNumber(value_); } 80 | 81 | Status ConfigInt::FromCJSON(const cJSON* json) { 82 | if (json == NULL || !cJSON_IsNumber(json)) { 83 | return ERROR; 84 | } 85 | const int value = json->valueint; 86 | if (value < min_ || value > max_) { 87 | return ERROR; 88 | } 89 | value_ = value; 90 | return OK; 91 | } 92 | 93 | cJSON* ConfigFloat::ToCJSON() const { return cJSON_CreateNumber(value_); } 94 | 95 | Status ConfigFloat::FromCJSON(const cJSON* json) { 96 | if (json == NULL || !cJSON_IsNumber(json)) { 97 | return ERROR; 98 | } 99 | const float value = json->valuedouble; 100 | if (value < min_ || value > max_) { 101 | return ERROR; 102 | } 103 | value_ = value; 104 | return OK; 105 | } 106 | 107 | Status ParseJsonConfig(const std::string& json, Config* default_config) { 108 | cJSON* c_json = cJSON_ParseWithLength(json.c_str(), json.size()); 109 | if (c_json == NULL) { 110 | return ERROR; 111 | } 112 | const Status status = default_config->FromCJSON(c_json); 113 | cJSON_Delete(c_json); 114 | return status; 115 | } 116 | -------------------------------------------------------------------------------- /joystick.h: -------------------------------------------------------------------------------- 1 | #ifndef JOYSTICK_H_ 2 | #define JOYSTICK_H_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "FreeRTOS.h" 9 | #include "base.h" 10 | #include "config.h" 11 | #include "configuration.h" 12 | #include "semphr.h" 13 | #include "utils.h" 14 | 15 | class CenteringPotentialMeterDriver { 16 | public: 17 | CenteringPotentialMeterDriver(uint8_t adc_pin, size_t smooth_buffer_size, 18 | bool flip); 19 | 20 | void Initialize(); 21 | 22 | // Raw ADC reading 23 | int16_t GetValue(); 24 | 25 | void SetMappedValue(int16_t mapped); 26 | 27 | void SetCalibrationSamples(uint32_t calibration_samples); 28 | void SetCalibrationThreshold(uint32_t calibration_threshold); 29 | 30 | protected: 31 | uint8_t adc_; 32 | uint16_t origin_; 33 | std::vector buffer_; 34 | uint32_t buffer_idx_; 35 | int32_t sum_; 36 | bool flip_; 37 | 38 | uint16_t last_value_; 39 | uint32_t calibration_samples_; 40 | uint32_t calibration_threshold_; 41 | uint32_t calibration_sum_; 42 | uint32_t calibration_zero_count_; 43 | uint32_t calibration_count_; 44 | }; 45 | 46 | class JoystickInputDeivce : virtual public GenericInputDevice, 47 | virtual public KeyboardOutputDevice { 48 | public: 49 | JoystickInputDeivce(uint8_t x_adc_pin, uint8_t y_adc_pin, size_t buffer_size, 50 | bool flip_x_dir, bool flip_y_dir, 51 | bool flip_vertical_scroll, uint8_t scan_num_ticks, 52 | uint8_t alt_layer); 53 | 54 | void InputLoopStart() override; 55 | void InputTick() override; 56 | void SetConfigMode(bool is_config_mode) override; 57 | std::pair> CreateDefaultConfig() 58 | override; 59 | void OnUpdateConfig(const Config* config) override; 60 | 61 | void OutputTick() override {} 62 | void StartOfInputTick() override {} 63 | void FinalizeInputTickOutput() override {} 64 | 65 | void SendKeycode(uint8_t keycode) override {} 66 | void SendKeycode(const std::vector& keycode) override {} 67 | void SendConsumerKeycode(uint16_t keycode) override {} 68 | void ChangeActiveLayers(const std::vector& layers) override; 69 | 70 | protected: 71 | int16_t GetSpeed(const std::vector>& profile, 72 | int16_t reading); 73 | 74 | Status ParseProfileConfig(const ConfigList& list, 75 | std::vector>* output); 76 | 77 | CenteringPotentialMeterDriver x_; 78 | CenteringPotentialMeterDriver y_; 79 | std::vector> profile_x_; 80 | std::vector> profile_y_; 81 | int16_t mouse_resolution_; 82 | int16_t pan_resolution_; 83 | uint8_t counter_; 84 | int16_t x_move_; 85 | int16_t y_move_; 86 | bool enable_joystick_; 87 | bool is_config_mode_; 88 | const int16_t scan_num_ticks_; 89 | const uint8_t alt_layer_; 90 | bool is_pan_mode_; 91 | bool flip_vertical_scroll_; 92 | }; 93 | 94 | Status RegisterJoystick(uint8_t tag, uint8_t x_adc_pin, uint8_t y_adc_pin, 95 | size_t buffer_size, bool flip_x_dir, bool flip_y_dir, 96 | bool flip_vertical_scroll, uint8_t alt_layer); 97 | 98 | #endif /* JOYSTICK_H_ */ 99 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | 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") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /keyscan.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYSCAN_H_ 2 | #define KEYSCAN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "FreeRTOS.h" 12 | #include "base.h" 13 | #include "layout.h" 14 | #include "semphr.h" 15 | #include "utils.h" 16 | 17 | // Each registration creates at most one instance of the handler. If 18 | // CAN_OVERRIDE is true, then another registration with CAN_OVERRIDE equals 19 | // false will override it. 20 | #define REGISTER_CUSTOM_KEYCODE_HANDLER(KEYCODE, CAN_OVERRIDE, CLS) \ 21 | status register_##KEYCODE = KeyScan::RegisterCustomKeycodeHandler( \ 22 | (KEYCODE), (CAN_OVERRIDE), \ 23 | []() -> CustomKeycodeHandler* { return new CLS(); }); 24 | 25 | class KeyScan; 26 | 27 | class CustomKeycodeHandler { 28 | public: 29 | // Key state is always called regardless whether it's a state transition. 30 | virtual void ProcessKeyState(Keycode kc, bool is_pressed, size_t key_idx) {} 31 | 32 | // Key events are only called when the key goes from pressed to released or 33 | // released to pressed, after debouncing. 34 | virtual void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) {} 35 | 36 | // Getting a reference to the KeyScan instance 37 | virtual void SetKeyScan(KeyScan* keyscan) { key_scan_ = keyscan; } 38 | 39 | // Get the name of this custom key 40 | virtual std::string GetName() const = 0; 41 | 42 | protected: 43 | KeyScan* key_scan_; 44 | }; 45 | 46 | class KeyScan : public GenericInputDevice { 47 | public: 48 | using CustomKeycodeHandlerCreator = std::function; 49 | 50 | KeyScan(); 51 | 52 | void InputLoopStart() override; 53 | void InputTick() override; 54 | void SetConfigMode(bool is_config_mode) override; 55 | 56 | static status RegisterCustomKeycodeHandler( 57 | uint8_t keycode, bool overridable, CustomKeycodeHandlerCreator creator); 58 | 59 | Status SetLayerStatus(uint8_t layer, bool active); 60 | Status ToggleLayerStatus(uint8_t layer); 61 | std::vector GetActiveLayers(); 62 | 63 | void SetMouseButtonState(uint8_t mouse_key, bool is_pressed); 64 | void ConfigUp(); 65 | void ConfigDown(); 66 | void ConfigSelect(); 67 | 68 | protected: 69 | struct DebounceTimer { 70 | uint8_t tick_count : 7; 71 | uint8_t pressed : 1; 72 | 73 | DebounceTimer() : tick_count(0), pressed(0) {} 74 | }; 75 | 76 | class HandlerRegistry { 77 | public: 78 | static status RegisterHandler(uint8_t keycode, bool overridable, 79 | CustomKeycodeHandlerCreator creator); 80 | static CustomKeycodeHandler* RegisteredHandlerFactory(uint8_t keycode, 81 | KeyScan* outer); 82 | 83 | private: 84 | static HandlerRegistry* GetRegistry(); 85 | 86 | std::map> 87 | custom_handlers_; 88 | std::map handler_singletons_; 89 | }; 90 | 91 | virtual void SinkGPIODelay(); 92 | 93 | virtual void NotifyOutput(const std::vector& pressed_keycode); 94 | virtual void LayerChanged(); 95 | 96 | std::vector debounce_timer_; 97 | std::vector active_layers_; 98 | // SemaphoreHandle_t semaphore_; 99 | bool is_config_mode_; 100 | }; 101 | 102 | Status RegisterKeyscan(uint8_t tag); 103 | 104 | #endif /* KEYSCAN_H_ */ -------------------------------------------------------------------------------- /builtin_keycode.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hardware/watchdog.h" 4 | #include "keyscan.h" 5 | #include "layout.h" 6 | #include "pico/bootrom.h" 7 | #include "runner.h" 8 | 9 | class MouseButtonHandler : public CustomKeycodeHandler { 10 | public: 11 | // Using ProcessKeyState callback because we want to keep on sending the mouse 12 | // keycode until the key is released. 13 | void ProcessKeyState(Keycode kc, bool is_pressed, size_t key_idx) override { 14 | (void)key_idx; 15 | key_scan_->SetMouseButtonState(kc.keycode, is_pressed); 16 | } 17 | 18 | std::string GetName() const override { return "Mouse key handler"; } 19 | }; 20 | 21 | REGISTER_CUSTOM_KEYCODE_HANDLER(MSE_L, true, MouseButtonHandler); 22 | REGISTER_CUSTOM_KEYCODE_HANDLER(MSE_R, true, MouseButtonHandler); 23 | REGISTER_CUSTOM_KEYCODE_HANDLER(MSE_M, true, MouseButtonHandler); 24 | REGISTER_CUSTOM_KEYCODE_HANDLER(MSE_BACK, true, MouseButtonHandler); 25 | REGISTER_CUSTOM_KEYCODE_HANDLER(MSE_FORWARD, true, MouseButtonHandler); 26 | 27 | class LayerButtonHandler : public CustomKeycodeHandler { 28 | public: 29 | LayerButtonHandler() {} 30 | 31 | void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) override { 32 | (void)key_idx; 33 | const bool toggle = kc.custom_info & 0x40; 34 | const uint8_t layer = kc.custom_info & 0x3f; 35 | if (toggle) { 36 | if (is_pressed) { 37 | key_scan_->ToggleLayerStatus(layer); 38 | } 39 | } else { 40 | key_scan_->SetLayerStatus(layer, is_pressed); 41 | } 42 | } 43 | 44 | std::string GetName() const override { return "Layer switch key handler"; } 45 | }; 46 | 47 | REGISTER_CUSTOM_KEYCODE_HANDLER(LAYER_SWITCH, true, LayerButtonHandler); 48 | 49 | class EnterConfigHandler : public CustomKeycodeHandler { 50 | public: 51 | void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) override { 52 | (void)key_idx; 53 | if (is_pressed) { 54 | runner::SetConfigMode(true); 55 | } 56 | } 57 | 58 | std::string GetName() const override { return "Enter config mode"; } 59 | }; 60 | 61 | REGISTER_CUSTOM_KEYCODE_HANDLER(ENTER_CONFIG, true, EnterConfigHandler); 62 | 63 | class ConfigSelHandler : public CustomKeycodeHandler { 64 | public: 65 | ConfigSelHandler() : currently_pressed_(false) {} 66 | 67 | void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) override { 68 | (void)key_idx; 69 | if (is_pressed) { 70 | key_scan_->ConfigSelect(); 71 | } 72 | } 73 | 74 | std::string GetName() const override { return "Config sel handler"; } 75 | 76 | private: 77 | bool currently_pressed_; 78 | }; 79 | 80 | REGISTER_CUSTOM_KEYCODE_HANDLER(CONFIG_SEL, true, ConfigSelHandler); 81 | 82 | class BootselHandler : public CustomKeycodeHandler { 83 | public: 84 | BootselHandler() {} 85 | 86 | void ProcessKeyState(Keycode kc, bool is_pressed, size_t key_idx) override { 87 | (void)key_idx; 88 | if (is_pressed) { 89 | reset_usb_boot(0, 0); 90 | } 91 | } 92 | 93 | std::string GetName() const override { return "Enter bootsel handler"; } 94 | }; 95 | 96 | REGISTER_CUSTOM_KEYCODE_HANDLER(BOOTSEL, true, BootselHandler); 97 | 98 | class RebootHandler : public CustomKeycodeHandler { 99 | public: 100 | RebootHandler() {} 101 | 102 | void ProcessKeyState(Keycode kc, bool is_pressed, size_t key_idx) override { 103 | (void)key_idx; 104 | if (is_pressed) { 105 | watchdog_reboot(0, 0, 0); 106 | } 107 | } 108 | 109 | std::string GetName() const override { return "Reboot"; } 110 | }; 111 | 112 | REGISTER_CUSTOM_KEYCODE_HANDLER(REBOOT, true, RebootHandler); 113 | -------------------------------------------------------------------------------- /display_mixins.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_MIXINS_H_ 2 | #define DISPLAY_MIXINS_H_ 3 | 4 | #include 5 | 6 | #include "FreeRTOS.h" 7 | #include "base.h" 8 | 9 | template 10 | class ActiveLayersDisplayMixin 11 | : virtual public ScreenMixinBase>, 12 | virtual public KeyboardOutputDevice { 13 | public: 14 | void SendKeycode(uint8_t) override {} 15 | void SendKeycode(const std::vector&) override {} 16 | void SendConsumerKeycode(uint16_t keycode) override {} 17 | void ChangeActiveLayers(const std::vector& layers) override { 18 | if (this->IsConfigMode()) { 19 | return; 20 | } 21 | this->DrawText(1 + row_offset, 0, "Active Layers", ScreenOutputDevice::F8X8, 22 | ScreenOutputDevice::ADD); 23 | this->DrawRect(9 + row_offset, 0, 17, this->GetNumCols() - 1, 24 | /*fill=*/false, ScreenOutputDevice::ADD); 25 | for (size_t i = 0; i < layers.size() && i < 16; ++i) { 26 | this->DrawRect( 27 | 11 + row_offset, i * 7 + 2, 15, i * 7 + 7, /*fill=*/true, 28 | layers[i] ? ScreenOutputDevice::ADD : ScreenOutputDevice::SUBTRACT); 29 | } 30 | } 31 | 32 | static Status Register( 33 | uint8_t key, bool slow, 34 | const std::shared_ptr> ptr) { 35 | return DeviceRegistry::RegisterKeyboardOutputDevice(key, slow, 36 | [=]() { return ptr; }); 37 | } 38 | }; 39 | 40 | template 41 | class StatusLEDDisplayMixin 42 | : virtual public ScreenMixinBase>, 43 | virtual public LEDOutputDevice { 44 | public: 45 | StatusLEDDisplayMixin() : first_run_(true) {} 46 | 47 | void IncreaseBrightness() override {} 48 | void DecreaseBrightness() override {} 49 | void IncreaseAnimationSpeed() override {} 50 | void DecreaseAnimationSpeed() override {} 51 | size_t NumPixels() const override { return 0; } 52 | void SetFixedColor(uint8_t w, uint8_t r, uint8_t g, uint8_t b) override {} 53 | void SetPixel(size_t idx, uint8_t w, uint8_t r, uint8_t g, 54 | uint8_t b) override {} 55 | 56 | void SetLedStatus(LEDIndicators indicators) override { 57 | if (this->IsConfigMode()) { 58 | return; 59 | } 60 | first_run_ = false; 61 | 62 | this->DrawRect(row_offset, 0, this->GetNumRows() - 1, 63 | this->GetNumCols() - 1, /*fill=*/false, 64 | ScreenOutputDevice::ADD); 65 | uint8_t offset = row_offset + 2; 66 | DrawLEDIndicator("NumLock", offset, indicators.num_lock); 67 | if (this->GetNumRows() == 64) { 68 | DrawLEDIndicator("CapsLock", offset += 8, indicators.caps_lock); 69 | DrawLEDIndicator("ScrollLock", offset += 8, indicators.scroll_lock); 70 | DrawLEDIndicator("Compose", offset += 8, indicators.compose); 71 | DrawLEDIndicator("Kana", offset += 8, indicators.kana); 72 | } 73 | current_indicators_ = indicators; 74 | } 75 | 76 | static Status Register( 77 | uint8_t key, bool slow, 78 | const std::shared_ptr> ptr) { 79 | return DeviceRegistry::RegisterLEDOutputDevice(key, slow, 80 | [=]() { return ptr; }); 81 | } 82 | 83 | protected: 84 | void DrawLEDIndicator(const std::string& name, uint8_t row, bool on) { 85 | this->DrawText(row, 2, name, ScreenOutputDevice::F8X8, 86 | ScreenOutputDevice::ADD); 87 | if (!on) { 88 | // Clean up the rectangle 89 | this->DrawRect(row + 1, this->GetNumCols() - 9, row + 6, 90 | this->GetNumCols() - 4, 91 | /*fill=*/true, ScreenOutputDevice::SUBTRACT); 92 | } 93 | this->DrawRect(row + 1, this->GetNumCols() - 9, row + 6, 94 | this->GetNumCols() - 4, on, ScreenOutputDevice::ADD); 95 | } 96 | 97 | LEDIndicators current_indicators_; 98 | bool first_run_; 99 | }; 100 | 101 | #endif /* DISPLAY_MIXINS_H_ */ 102 | -------------------------------------------------------------------------------- /sync.cc: -------------------------------------------------------------------------------- 1 | #include "sync.h" 2 | 3 | #include 4 | 5 | #include "FreeRTOS.h" 6 | #include "hardware/regs/sio.h" 7 | #include "hardware/sync.h" 8 | #include "pico/multicore.h" 9 | #include "pico/platform.h" 10 | #include "queue.h" 11 | #include "task.h" 12 | 13 | struct TaskInfo { 14 | const uint8_t core_id; 15 | std::atomic entered; 16 | spin_lock_t* sync_wait_lock; 17 | }; 18 | 19 | static spin_lock_t* __not_in_flash("sync") critical_section_lock; 20 | 21 | static TaskHandle_t __not_in_flash("sync") task_handles[2] = {NULL, NULL}; 22 | 23 | static TaskInfo __not_in_flash("sync") core_info[2] = { 24 | {.core_id = 0, .entered = false, .sync_wait_lock = NULL}, 25 | {.core_id = 1, .entered = false, .sync_wait_lock = NULL}}; 26 | 27 | extern "C" void __no_inline_not_in_flash_func(CoreBlockerTask)(void* parameter); 28 | 29 | Status StartSyncTasks() { 30 | if (xTaskCreateAffinitySet(&CoreBlockerTask, "core_0_blocker", 31 | configMINIMAL_STACK_SIZE, (void*)&core_info[0], 32 | // Higher priority to make sure it can preempt 33 | // whatever is current running on this core 34 | CONFIG_TASK_PRIORITY + 1, (1 << 0), 35 | &task_handles[0]) != pdPASS || 36 | task_handles[0] == NULL) { 37 | return ERROR; 38 | } 39 | 40 | if (xTaskCreateAffinitySet(&CoreBlockerTask, "core_1_blocker", 41 | configMINIMAL_STACK_SIZE, (void*)&core_info[1], 42 | CONFIG_TASK_PRIORITY + 1, (1 << 1), 43 | &task_handles[1]) != pdPASS || 44 | &task_handles[1] == NULL) { 45 | return ERROR; 46 | } 47 | 48 | core_info[0].sync_wait_lock = 49 | spin_lock_init(spin_lock_claim_unused(/*required=*/true)); 50 | core_info[1].sync_wait_lock = 51 | spin_lock_init(spin_lock_claim_unused(/*required=*/true)); 52 | critical_section_lock = 53 | spin_lock_init(spin_lock_claim_unused(/*required=*/true)); 54 | if (core_info[0].sync_wait_lock == NULL || // 55 | core_info[1].sync_wait_lock == NULL || // 56 | critical_section_lock == NULL) { 57 | return ERROR; 58 | } 59 | 60 | return OK; 61 | } 62 | 63 | extern "C" void __no_inline_not_in_flash_func(CoreBlockerTask)( 64 | void* parameter) { 65 | TaskInfo& task_info = *(TaskInfo*)parameter; 66 | const uint32_t cpuid = *(uint32_t*)((SIO_BASE) + (SIO_CPUID_OFFSET)); 67 | assert(cpuid == task_info.core_id); 68 | 69 | while (true) { 70 | task_info.entered = false; 71 | xTaskNotifyWait(/*do not clear notification on enter*/ 0, 72 | /*clear notification on exit*/ 0xffffffff, 73 | /*pulNotificationValue=*/NULL, portMAX_DELAY); 74 | task_info.entered = true; 75 | 76 | // Here we want to disable IRQ until the other core tells us to stop (by 77 | // releasing the lock). Disabling IRQ is important because IRQ vectors and 78 | // handlers might be in flash. 79 | uint32_t core_irq = spin_lock_blocking(task_info.sync_wait_lock); 80 | spin_unlock(task_info.sync_wait_lock, core_irq); 81 | } 82 | } 83 | 84 | CoreBlockerSection::CoreBlockerSection() { DisableTheOtherCore(); } 85 | 86 | CoreBlockerSection::~CoreBlockerSection() { ReenableTheOtherCore(); } 87 | 88 | void DisableTheOtherCore() { 89 | // Don't disable IRQ here since flash writes we don't need to disable IRQ 90 | // until we actually write it. 91 | spin_lock_unsafe_blocking(critical_section_lock); 92 | 93 | const uint32_t cpuid = *(uint32_t*)((SIO_BASE) + (SIO_CPUID_OFFSET)); 94 | const uint32_t the_other_core = (cpuid + 1) % 2; 95 | 96 | // Don't hold the IRQ since we don't want to disable it for current core yet. 97 | spin_lock_unsafe_blocking(core_info[the_other_core].sync_wait_lock); 98 | xTaskNotifyGive(task_handles[the_other_core]); // Notify the other core to 99 | // enter blocking as well 100 | 101 | // We need to be sure the other core has entered the blocker task which is 102 | // SRAM mapped before we proceed. 103 | while (!core_info[the_other_core].entered) 104 | ; 105 | } 106 | 107 | void ReenableTheOtherCore() { 108 | const uint32_t cpuid = *(uint32_t*)((SIO_BASE) + (SIO_CPUID_OFFSET)); 109 | const uint32_t the_other_core = (cpuid + 1) % 2; 110 | spin_unlock_unsafe(core_info[the_other_core].sync_wait_lock); 111 | spin_unlock_unsafe(critical_section_lock); 112 | } 113 | -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | # Design and Tradeoffs 2 | 3 | This doc documents the design decisions and tradeoffs involved. At the core of the firmware, are two design patterns Observer and Registry. The observed (`GenericInputDevice`) can produce events to observer (`GenericOutputDevice`). Each class can be both an observer and an observed. This decouples the obligation of each device, and allows many to many communication between classes. For example, there can be multiple devices sending key codes via usb. We can centrolize the usb report construction and sending in one observer class `USBKeyboardOutput`. To achieve conditional compilation, we use the Registry pattern which allows us to have zero, one, or even multiple instances of a device, such as rotary encoder, as long as you assign a different tag to each instance at registration time. 4 | 5 | Now the tradeoffs: 6 | 7 | * C++ vs C vs Python: 8 | * Pros of C++: 9 | * There are many features that's desirable: compile time evaluated `constexpr` functions, class to encapsulate both data and code, inheritance, template metaprogramming, STL (especially smart pointers) etc. 10 | * Still allows fairly low level controls. 11 | * Cons of C++: 12 | * Inheritance usually involves virtual functions and vtable, which incurs extra pointer jump. Could be extra slow if the code (and vtable) is on flash. 13 | * A lot of common embedded libraries (FreeRTOS, tinyusb etc.) are designed for C (takes function pointer) etc. Extra work to make it play well with C code (i.e. `extern "C"`). 14 | * STL (and others) can bloat the binary size, which means we can't fit in SRAM. Extra work for flash writes. 15 | * Pros of C: 16 | * Simpler language constructs. Less time spent on debugging weird language behaviors (like virtual inheritance). More predictable. 17 | * Plays well with FreeRTOS and tinyusb. 18 | * Requires less run-time resources than C++ (i.e. no STL, no vtable stuffs) 19 | * Cons of C: 20 | * Might need extensive use of macros for metaprogramming or conditional compilation. Could be a maintaince nightmare. 21 | * Behavior customization if implemented with weak linage can be quite hard to manage. However this might be mitigated with function pointers. 22 | * No concept of destructor. Need to manually handle scoping for things like memory deallocation and lock release. 23 | * Pros of (Micro)Python: 24 | * Generally requires less code than C/C++ for the same feature 25 | * USB HID and filesystem are already handled 26 | * Easier to get started and can just upload text python code instead of compilation 27 | * Interactive REPL for faster iterations 28 | * Easier to learn for newcomers 29 | * Cons of (Micro)Python: 30 | * Dynamic typing can be hard to maintain once project gets big and complex 31 | * Hard to have direct low level control 32 | * Even more bloated than C++ 33 | * Not sure how to integrate with RTOS 34 | * Decision: C++. Reasons: 35 | * Even though C++ has more startup cost than Python or even C, in the long run it can be easier to maintain than C, since it has more options to modularize things, and template can replace a lot of use of macros. 36 | * Binary size is generally not a huge issue for RP2040 as it usually has over 2MB of flash storage. Can't reside in SRAM can be a bit annoying, but this can still be an issue for C and especially Python 37 | * The destructor based scope management is a huge plus compared to C. Never need to remember to release a lock when returning inside an `if`. 38 | 39 | * [FreeRTOS](https://www.freertos.org/) vs [Zephyr](https://zephyrproject.org/) 40 | * Pros of FreeRTOS: 41 | * Relatively simpler. The code is only in a few files 42 | * More familiar to me, as I came from ESP-IDF ecosystem 43 | * Has descent documentation and active community 44 | * Cons of FreeRTOS: 45 | * SMP (multicore) support seems to be an after thought 46 | * Pros of Zephyr: 47 | * A more modern RTOS, with a more Linux like setup, such as device tree hardware description. 48 | * A lot of utilities. 49 | * Really nice documentation. 50 | * Cons of Zephyr: 51 | * Seems like a massive overkill for this project. 52 | * Too much extra things to learn. 53 | * Decision: FreeRTOS. Reasons: 54 | * Easier to get started. I spent two hours reading [Zephyr docs](https://docs.zephyrproject.org/latest/), and still don't feel comfortable to start coding. 55 | * We need to do our own device configurations, like the `layout.cc` file, rather than using device tree, since this is a very specific case. 56 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # Configuration Menu 2 | 3 | One of the unique features of PicoMK is the configuration menu. Each device can export a set of configs and the firmware will automatically present on the configuration menu. With such menu, it's possible to customize the keyboard on the go with any host OS and no need to memorize the key bindings you set several months ago. 4 | 5 | ## Basic Use 6 | 7 | To enable the config mode, you need to register a `ScreenOutputDevice` in `layout.cc`, such as SSD1306 or other types of screens. Then in the `layout.cc` file, register a `ConfigModifier` device. For the built-in implementation, you can use the following line: 8 | 9 | ```cpp 10 | static Status register_config_modifier = RegisterConfigModifier(ssd1306_tag); 11 | ``` 12 | 13 | ## Define the Config for a Device 14 | 15 | You need to override two methods from the `GenericDevice` in your custom device: 16 | 17 | ```cpp 18 | // Called when the config has been updated. 19 | virtual void OnUpdateConfig(const Config* config); 20 | 21 | // Called during initialization to create the default config. Note that it'll 22 | // be called even when there's a valid config json file in the filesystem. The 23 | // default config specifies the structure of the config sub-tree to later 24 | // parse the json file. 25 | virtual std::pair> CreateDefaultConfig(); 26 | ``` 27 | 28 | Further, if the device behaves different in config mode or in normal mode, you also need to override this method: 29 | 30 | ```cpp 31 | // Override this if the device needs to behave differently when in and out of 32 | // config mode. One example would be USB output devices, where during config 33 | // mode it doesn't report any key code or mouse movement to the host. 34 | virtual void SetConfigMode(bool is_config_mode){}; 35 | ``` 36 | 37 | The config the device is represented by a tree of `Config` objects. Each object can be an "object", "list", "integer", or a "float". They are organized in a similar way as in json format, so that later on the config can be serialized to and deserialized from json. Further more, for numerical values, we define a constraint on their value range for later use by the config modifier. There are a list of helper macros in `configuration.h` to build the config tree: 38 | 39 | ```cpp 40 | // Create an object from a list of key value pairs 41 | #define CONFIG_OBJECT(...) 42 | 43 | // Create one key value pair from a string name to a subconfig. 44 | #define CONFIG_OBJECT_ELEM(name, value) 45 | 46 | // Create a list of subconfigs 47 | #define CONFIG_LIST(...) 48 | 49 | // Convenient macro for creating a special list with length of two 50 | #define CONFIG_PAIR(first, second) 51 | 52 | // Value can only be in range [min, max]. value is the compile time default. 53 | #define CONFIG_INT(value, min, max) 54 | 55 | // Value can only be in range [min, max]. Each up/down on the config modifier 56 | // increases/decreases the value by resolution amount. value is the compile time 57 | // default. 58 | #define CONFIG_FLOAT(value, min, max, resolution) 59 | ``` 60 | 61 | For example, to create a config tree whose default values correspond to the following json: 62 | 63 | ```json 64 | { 65 | "int1":10, 66 | "float1":10.5, 67 | "list1":[ 68 | { 69 | "int":5 70 | }, 71 | { 72 | "float":11.1 73 | }, 74 | 30, 75 | ] 76 | } 77 | ``` 78 | 79 | ```cpp 80 | auto config = CONFIG_OBJECT( 81 | // Valid value in range [0, 100], default is 10 82 | CONFIG_OBJECT_ELEM("int1", CONFIG_INT(10, 0, 100)), 83 | 84 | // Valid value in range [5, 20.0], default is 10.5, and each time it's 85 | // increased/decreased by 0.2 86 | CONFIG_OBJECT_ELEM("float1", CONFIG_FLOAT(10.5, 5, 20.0, 0.2)), 87 | 88 | // A list of 3 elements 89 | CONFIG_OBJECT_ELEM( 90 | "list1", CONFIG_LIST(CONFIG_OBJECT(CONFIG_OBJECT_ELEM( 91 | "int", CONFIG_INT(5, 0, 100))), 92 | CONFIG_OBJECT(CONFIG_OBJECT_ELEM( 93 | "float", CONFIG_FLOAT(11.1, 5, 20.0, 0.1))), 94 | CONFIG_INT(30, 0, 2048)))); 95 | ``` 96 | 97 | Finally, in `CreateDefaultConfig` return the device name as well as the config tree: 98 | 99 | ```cpp 100 | return {"Device Name", config}; 101 | ``` 102 | 103 | To parse the config, you need to check the type tag of the current config by calling `GetType()` and dynamically cast the pointer. 104 | 105 | For more examples, please take a look at the implementation for joystick (`joystick.cc`) and WS2812 (`ws2912.cc`). 106 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "config.h" 27 | 28 | // clang-format off 29 | 30 | #ifndef _TUSB_CONFIG_H_ 31 | #define _TUSB_CONFIG_H_ 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | //-------------------------------------------------------------------- 38 | // COMMON CONFIGURATION 39 | //-------------------------------------------------------------------- 40 | 41 | // defined by board.mk 42 | #ifndef CFG_TUSB_MCU 43 | #error CFG_TUSB_MCU must be defined 44 | #endif 45 | 46 | // RHPort number used for device can be defined by board.mk, default to port 0 47 | #ifndef BOARD_DEVICE_RHPORT_NUM 48 | #define BOARD_DEVICE_RHPORT_NUM 0 49 | #endif 50 | 51 | // RHPort max operational speed can defined by board.mk 52 | // Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed 53 | #ifndef BOARD_DEVICE_RHPORT_SPEED 54 | #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ 55 | CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) 56 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED 57 | #else 58 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED 59 | #endif 60 | #endif 61 | 62 | // Device mode with rhport and speed defined by board.mk 63 | #if BOARD_DEVICE_RHPORT_NUM == 0 64 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 65 | #elif BOARD_DEVICE_RHPORT_NUM == 1 66 | #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 67 | #else 68 | #error "Incorrect RHPort configuration" 69 | #endif 70 | 71 | #ifndef CFG_TUSB_OS 72 | #define CFG_TUSB_OS OPT_OS_NONE 73 | #endif 74 | 75 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 76 | // #define CFG_TUSB_DEBUG 0 77 | 78 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 79 | * Tinyusb use follows macros to declare transferring memory so that they can be put 80 | * into those specific section. 81 | * e.g 82 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 83 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 84 | */ 85 | #ifndef CFG_TUSB_MEM_SECTION 86 | #define CFG_TUSB_MEM_SECTION 87 | #endif 88 | 89 | #ifndef CFG_TUSB_MEM_ALIGN 90 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 91 | #endif 92 | 93 | //-------------------------------------------------------------------- 94 | // DEVICE CONFIGURATION 95 | //-------------------------------------------------------------------- 96 | 97 | #ifndef CFG_TUD_ENDPOINT0_SIZE 98 | #define CFG_TUD_ENDPOINT0_SIZE 64 99 | #endif 100 | 101 | //------------- CLASS -------------// 102 | #define CFG_TUD_HID 3 // We have two HID interface 103 | #define CFG_TUD_CDC 1 104 | #define CFG_TUD_MSC 1 105 | #define CFG_TUD_MIDI 0 106 | #define CFG_TUD_VENDOR 0 107 | 108 | // HID buffer size Should be sufficient to hold ID (if any) + Data 109 | #define CFG_TUD_HID_EP_BUFSIZE 64 110 | 111 | #define CFG_TUD_CDC_RX_BUFSIZE 256 112 | #define CFG_TUD_CDC_TX_BUFSIZE 256 113 | 114 | #define CFG_TUD_MSC_EP_BUFSIZE 512 115 | 116 | #ifdef __cplusplus 117 | } 118 | #endif 119 | 120 | // clang-format on 121 | 122 | #endif /* _TUSB_CONFIG_H_ */ -------------------------------------------------------------------------------- /FreeRTOS_Kernel_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /portable/ThirdParty/GCC/RP2040/FREERTOS_KERNEL_import.cmake 2 | 3 | # This can be dropped into an external project to help locate the FreeRTOS kernel 4 | # It should be include()ed prior to project(). Alternatively this file may 5 | # or the CMakeLists.txt in this directory may be included or added via add_subdirectory 6 | # respectively. 7 | 8 | if (DEFINED ENV{FREERTOS_KERNEL_PATH} AND (NOT FREERTOS_KERNEL_PATH)) 9 | set(FREERTOS_KERNEL_PATH $ENV{FREERTOS_KERNEL_PATH}) 10 | message("Using FREERTOS_KERNEL_PATH from environment ('${FREERTOS_KERNEL_PATH}')") 11 | endif () 12 | 13 | # first pass we look in old tree; second pass we look in new tree 14 | foreach(SEARCH_PASS RANGE 0 1) 15 | if (SEARCH_PASS) 16 | # ports may be moving to submodule in the future 17 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "portable/ThirdParty/Community-Supported-Ports/GCC") 18 | set(FREERTOS_KERNEL_RP2040_BACK_PATH "../../../../..") 19 | else() 20 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "portable/ThirdParty/GCC") 21 | set(FREERTOS_KERNEL_RP2040_BACK_PATH "../../../..") 22 | endif() 23 | 24 | if(PICO_PLATFORM STREQUAL "rp2040") 25 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2040") 26 | else() 27 | if (PICO_PLATFORM STREQUAL "rp2350-riscv") 28 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2350_RISC-V") 29 | else() 30 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2350_ARM_NTZ") 31 | endif() 32 | endif() 33 | 34 | if (NOT FREERTOS_KERNEL_PATH) 35 | # check if we are inside the FreeRTOS kernel tree (i.e. this file has been included directly) 36 | get_filename_component(_ACTUAL_PATH ${CMAKE_CURRENT_LIST_DIR} REALPATH) 37 | get_filename_component(_POSSIBLE_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH} REALPATH) 38 | if (_ACTUAL_PATH STREQUAL _POSSIBLE_PATH) 39 | get_filename_component(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH} REALPATH) 40 | endif() 41 | if (_ACTUAL_PATH STREQUAL _POSSIBLE_PATH) 42 | get_filename_component(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH} REALPATH) 43 | message("Setting FREERTOS_KERNEL_PATH to ${FREERTOS_KERNEL_PATH} based on location of FreeRTOS-Kernel-import.cmake") 44 | break() 45 | elseif (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../FreeRTOS-Kernel") 46 | set(FREERTOS_KERNEL_PATH ${PICO_SDK_PATH}/../FreeRTOS-Kernel) 47 | message("Defaulting FREERTOS_KERNEL_PATH as sibling of PICO_SDK_PATH: ${FREERTOS_KERNEL_PATH}") 48 | break() 49 | endif() 50 | endif () 51 | 52 | if (NOT FREERTOS_KERNEL_PATH) 53 | foreach(POSSIBLE_SUFFIX Source FreeRTOS-Kernel FreeRTOS/Source) 54 | # check if FreeRTOS-Kernel exists under directory that included us 55 | set(SEARCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) 56 | get_filename_component(_POSSIBLE_PATH ${SEARCH_ROOT}/${POSSIBLE_SUFFIX} REALPATH) 57 | if (EXISTS ${_POSSIBLE_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/CMakeLists.txt) 58 | get_filename_component(FREERTOS_KERNEL_PATH ${_POSSIBLE_PATH} REALPATH) 59 | message("Setting FREERTOS_KERNEL_PATH to '${FREERTOS_KERNEL_PATH}' found relative to enclosing project") 60 | break() 61 | endif() 62 | endforeach() 63 | if (FREERTOS_KERNEL_PATH) 64 | break() 65 | endif() 66 | endif() 67 | 68 | # user must have specified 69 | if (FREERTOS_KERNEL_PATH) 70 | if (EXISTS "${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}") 71 | break() 72 | endif() 73 | endif() 74 | endforeach () 75 | 76 | if (NOT FREERTOS_KERNEL_PATH) 77 | message(FATAL_ERROR "FreeRTOS location was not specified. Please set FREERTOS_KERNEL_PATH.") 78 | endif() 79 | 80 | set(FREERTOS_KERNEL_PATH "${FREERTOS_KERNEL_PATH}" CACHE PATH "Path to the FreeRTOS Kernel") 81 | 82 | get_filename_component(FREERTOS_KERNEL_PATH "${FREERTOS_KERNEL_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 83 | if (NOT EXISTS ${FREERTOS_KERNEL_PATH}) 84 | message(FATAL_ERROR "Directory '${FREERTOS_KERNEL_PATH}' not found") 85 | endif() 86 | if (NOT EXISTS ${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/CMakeLists.txt) 87 | message(FATAL_ERROR "Directory '${FREERTOS_KERNEL_PATH}' does not contain a '${PICO_PLATFORM}' port here: ${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}") 88 | endif() 89 | set(FREERTOS_KERNEL_PATH ${FREERTOS_KERNEL_PATH} CACHE PATH "Path to the FreeRTOS_KERNEL" FORCE) 90 | 91 | add_subdirectory(${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH} FREERTOS_KERNEL) -------------------------------------------------------------------------------- /configs/examples/bare_minimum/layout.cc: -------------------------------------------------------------------------------- 1 | // layout_helper.h defines the helper macros as well as short alias to each 2 | // keycode. Please include it at the top of the layout.cc file. 3 | #include "layout_helper.h" 4 | 5 | // Alias for the key matrix GPIO pins. This is for better readability and is 6 | // optional. 7 | 8 | #define C0 0 9 | #define C1 1 10 | #define C2 2 11 | #define C3 3 12 | #define C4 4 13 | #define C5 5 14 | #define C6 6 15 | #define C7 7 16 | #define C8 8 17 | #define C9 9 18 | #define C10 10 19 | #define C11 11 20 | #define C12 13 21 | #define C13 12 22 | #define R0 14 23 | #define R1 15 24 | #define R2 18 25 | #define R3 17 26 | #define R4 16 27 | 28 | // These two are also optional 29 | 30 | #define CONFIG_NUM_PHY_ROWS 6 31 | #define CONFIG_NUM_PHY_COLS 15 32 | 33 | // This is a special layer that rotary encoder might have different behaviors. 34 | // Also optional. 35 | 36 | #define ALT_LY 4 37 | 38 | // For a layout.cc file, the followings are required: kGPIOMatrix, and 39 | // kKeyCodes. They need to have exactly the same shape and type. They also need 40 | // to be constexpr. For array types you don't need to specify the size for all 41 | // the dimenions as long as compiler is happy. See docs/layout_cc.md for an 42 | // example key matrix setup. 43 | 44 | // clang-format off 45 | 46 | // Keyboard switch physical GPIO connection setup. This is a map from the 47 | // physical layout of the keys to their switch matrix. The reason for having 48 | // this mapping is that often times the physical layout of the switches does not 49 | // match up with their wiring matrix. For each switch, it specifies the 50 | // direction of scanning. 51 | static constexpr GPIO kGPIOMatrix[CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 52 | {G(C0, R0), G(C1, R0), G(C2, R0), G(C3, R0), G(C4, R0), G(C5, R0), G(C6, R0), G(C7, R0), G(C8, R0), G(C9, R0), G(C10, R0), G(C11, R0), G(C12, R0), G(C13, R0), G(C13, R1)}, 53 | {G(C0, R1), G(C1, R1), G(C2, R1), G(C3, R1), G(C4, R1), G(C5, R1), G(C6, R1), G(C7, R1), G(C8, R1), G(C9, R1), G(C10, R1), G(C11, R1), G(C12, R1), G(C13, R2), G(C13, R3)}, 54 | {G(C0, R2), G(C1, R2), G(C2, R2), G(C3, R2), G(C4, R2), G(C5, R2), G(C6, R2), G(C7, R2), G(C8, R2), G(C9, R2), G(C10, R2), G(C11, R2), G(C12, R2), G(C13, R4)}, 55 | {G(C0, R3), G(C1, R3), G(C2, R3), G(C3, R3), G(C4, R3), G(C5, R3), G(C6, R3), G(C7, R3), G(C8, R3), G(C9, R3), G(C10, R3), G(C11, R3), G(C12, R3)}, 56 | {G(C0, R4), G(C1, R4), G(C2, R4), G(C5, R4), G(C7, R4), G(C8, R4), G(C9, R4), G(C10, R4), G(C11, R4), G(C12, R4)}, 57 | {G(C3, R4), G(C4, R4), G(C6, R4)} 58 | }; 59 | 60 | static constexpr Keycode kKeyCodes[][CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 61 | [0]={ 62 | {K(K_MUTE), K(K_GRAVE), K(K_1), K(K_2), K(K_3), K(K_4), K(K_5), K(K_6), K(K_7), K(K_8), K(K_9), K(K_0), K(K_MINUS), K(K_EQUAL), K(K_BACKS)}, 63 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 64 | {K(K_DEL), K(K_CTR_L), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 65 | {K(K_INS), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 66 | {MO(ALT_LY), K(K_GUI_L), K(K_ALT_L), K(K_SPACE), MO(1), MO(2), K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 67 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 68 | }, 69 | [1]={ 70 | {K(K_MUTE), K(K_GRAVE), K(K_F1), K(K_F2), K(K_F3), K(K_F4), K(K_F5), K(K_F6), K(K_F7), K(K_F8), K(K_F9), K(K_F10), K(K_F11), K(K_F12), K(K_BACKS)}, 71 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 72 | {K(K_DEL), K(K_CAPS), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 73 | {MO(3), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 74 | {CONFIG, TG(2), K(K_ALT_L), K(K_SPACE), ______, ______, K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 75 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 76 | }, 77 | [2]={ 78 | {CK(CONFIG_SEL)}, 79 | {______}, 80 | {CK(REBOOT)}, 81 | }, 82 | [3]={ 83 | {______}, 84 | {CK(BOOTSEL)}, 85 | }, 86 | [ALT_LY]={}, 87 | }; 88 | 89 | // clang-format on 90 | 91 | // Compile time validation and conversion for the key matrix. Must include this. 92 | #include "layout_internal.inc" 93 | 94 | // Only register the key scanner to save binary size 95 | 96 | static Status register1 = RegisterKeyscan(/*tag=*/0); 97 | static Status register2 = RegisterUSBKeyboardOutput(/*tag=*/1); 98 | -------------------------------------------------------------------------------- /usb.h: -------------------------------------------------------------------------------- 1 | #ifndef USB_H_ 2 | #define USB_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "FreeRTOS.h" 10 | #include "base.h" 11 | #include "config.h" 12 | #include "semphr.h" 13 | #include "tusb.h" 14 | #include "utils.h" 15 | 16 | Status USBInit(); 17 | 18 | Status StartUSBTask(); 19 | 20 | class USBOutputAddIn { 21 | public: 22 | USBOutputAddIn(); 23 | 24 | virtual bool SetIdle(uint8_t idle_rate); 25 | 26 | protected: 27 | SemaphoreHandle_t semaphore_; 28 | uint8_t idle_rate_; 29 | }; 30 | 31 | class USBKeyboardOutput : public KeyboardOutputDevice, public USBOutputAddIn { 32 | public: 33 | static std::shared_ptr GetUSBKeyboardOutput(); 34 | 35 | void OutputTick() override; 36 | void SetConfigMode(bool is_config_mode) override; 37 | 38 | void StartOfInputTick() override; 39 | void FinalizeInputTickOutput() override; 40 | 41 | void SendKeycode(uint8_t keycode) override; 42 | void SendKeycode(const std::vector& keycode) override; 43 | void SendConsumerKeycode(uint16_t keycode) override; 44 | void ChangeActiveLayers(const std::vector&) override {} 45 | 46 | protected: 47 | USBKeyboardOutput(); 48 | 49 | std::array, 2> double_buffer_; 50 | uint8_t active_buffer_; 51 | uint8_t boot_protocol_kc_count_; 52 | uint16_t consumer_keycode_; 53 | bool is_config_mode_; 54 | bool has_key_output_; 55 | }; 56 | 57 | class USBKeyboardOutputDisablable : public USBKeyboardOutput { 58 | public: 59 | static std::shared_ptr GetUSBKeyboardOutput( 60 | uint8_t disable_at_layer); 61 | 62 | void OutputTick() override; 63 | 64 | void ChangeActiveLayers(const std::vector& layers) override; 65 | 66 | protected: 67 | USBKeyboardOutputDisablable(uint8_t disable_at_layer); 68 | 69 | const uint8_t disable_at_layer_; 70 | bool disabled_; 71 | }; 72 | 73 | class USBMouseOutput : public MouseOutputDevice, public USBOutputAddIn { 74 | public: 75 | static std::shared_ptr GetUSBMouseOutput(); 76 | 77 | void OutputTick() override; 78 | void SetConfigMode(bool is_config_mode) override; 79 | 80 | void StartOfInputTick() override; 81 | void FinalizeInputTickOutput() override; 82 | 83 | void MouseKeycode(uint8_t keycode) override; 84 | void MouseMovement(int8_t x, int8_t y) override; 85 | void Pan(int8_t horizontal, int8_t vertical) override; 86 | 87 | protected: 88 | USBMouseOutput(); 89 | 90 | std::array, 2> double_buffer_; 91 | uint8_t active_buffer_; 92 | bool is_config_mode_; 93 | }; 94 | 95 | class USBMouseOutputDisablable : virtual public USBMouseOutput, 96 | virtual public KeyboardOutputDevice { 97 | public: 98 | static std::shared_ptr GetUSBMouseOutput( 99 | uint8_t disable_at_layer); 100 | 101 | void OutputTick() override; 102 | 103 | void SendKeycode(uint8_t) override {} 104 | void SendKeycode(const std::vector&) override {} 105 | void SendConsumerKeycode(uint16_t) override {} 106 | void ChangeActiveLayers(const std::vector& layers) override; 107 | 108 | protected: 109 | USBMouseOutputDisablable(uint8_t disable_at_layer); 110 | 111 | const uint8_t disable_at_layer_; 112 | bool disabled_; 113 | }; 114 | 115 | class USBInput : public GenericInputDevice { 116 | public: 117 | static std::shared_ptr GetUSBInput(); 118 | 119 | // Callbacks will be called by the USB task 120 | 121 | void OnSetReport(hid_report_type_t report_type, uint8_t const* buffer, 122 | uint16_t buffer_size); 123 | 124 | // Don't really handle usb mount events 125 | 126 | void OnMount() { OnResume(); } 127 | void OnUnMount() {} 128 | 129 | void OnSuspend(); 130 | void OnResume(); 131 | 132 | // These function to pass the information from callbacks to the corresponding 133 | // outputs. Since we need to make sure output class functions other than 134 | // OutputTick are called on the input task 135 | 136 | void InputLoopStart() override; 137 | void InputTick() override; 138 | 139 | protected: 140 | USBInput(); 141 | 142 | bool state_changed_; 143 | bool suspended_; 144 | LEDOutputDevice::LEDIndicators leds_; 145 | SemaphoreHandle_t semaphore_; 146 | }; 147 | 148 | enum InterfaceID { 149 | ITF_KEYBOARD = 0, 150 | ITF_MOUSE, 151 | ITF_CONSUMER, 152 | 153 | #if CONFIG_DEBUG_ENABLE_USB_SERIAL 154 | ITF_CDC_CTRL, 155 | ITF_CDC_DATA, 156 | #endif /* CONFIG_DEBUG_ENABLE_USB_SERIAL */ 157 | 158 | ITF_TOTAL, 159 | }; 160 | 161 | Status RegisterUSBKeyboardOutput(uint8_t tag); 162 | Status RegisterUSBMouseOutput(uint8_t tag); 163 | Status RegisterDisablableUSBKeyboardOutput(uint8_t tag, 164 | uint8_t disable_at_layer); 165 | Status RegisterDisablableUSBMouseOutput(uint8_t tag, uint8_t disable_at_layer); 166 | Status RegisterUSBInput(uint8_t tag); 167 | 168 | #endif /* USB_H_ */ -------------------------------------------------------------------------------- /configuration.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIGURATION_H_ 2 | #define CONFIGURATION_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cJSON/cJSON.h" 13 | #include "utils.h" 14 | 15 | // Create an object from a list of key value pairs 16 | #define CONFIG_OBJECT(...) \ 17 | (std::shared_ptr(new ConfigObject({__VA_ARGS__}))) 18 | 19 | // Create one key value pair from a string name to a subconfig. 20 | #define CONFIG_OBJECT_ELEM(name, value) \ 21 | { (name), (value) } 22 | 23 | // Create a list of subconfigs 24 | #define CONFIG_LIST(...) \ 25 | (std::shared_ptr(new ConfigList({__VA_ARGS__}))) 26 | 27 | // Convenient macro for creating a special list with length of two 28 | #define CONFIG_PAIR(first, second) \ 29 | (std::shared_ptr(new ConfigPair((first), (second)))) 30 | 31 | // Value can only be in range [min, max]. value is the compile time default. 32 | #define CONFIG_INT(value, min, max) \ 33 | (std::shared_ptr(new ConfigInt((value), (min), (max)))) 34 | 35 | // Value can only be in range [min, max]. Each up/down on the config modifier 36 | // increases/decreases the value by resolution amount. value is the compile time 37 | // default. 38 | #define CONFIG_FLOAT(value, min, max, resolution) \ 39 | (std::shared_ptr( \ 40 | new ConfigFloat((value), (min), (max), (resolution)))) 41 | 42 | class Config { 43 | public: 44 | enum Type { 45 | INVALID = 0, 46 | OBJECT, 47 | LIST, 48 | INTEGER, 49 | FLOAT, 50 | }; 51 | virtual Type GetType() const { return INVALID; } 52 | virtual cJSON* ToCJSON() const { return NULL; } 53 | virtual Status FromCJSON(const cJSON* json) { return ERROR; } 54 | }; 55 | 56 | class ConfigObject : public Config { 57 | public: 58 | Type GetType() const override final { return OBJECT; } 59 | ConfigObject() = default; 60 | ConfigObject(const std::map>& members) 61 | : members_(members) {} 62 | ConfigObject(std::initializer_list< 63 | std::pair>> 64 | l) 65 | : members_(l) {} 66 | 67 | std::map>* GetMembers() { 68 | return &members_; 69 | } 70 | const std::map>* GetMembers() const { 71 | return &members_; 72 | } 73 | 74 | std::string ToJSON() const; 75 | cJSON* ToCJSON() const override; 76 | Status FromCJSON(const cJSON* json) override; 77 | 78 | private: 79 | std::map> members_; 80 | }; 81 | 82 | class ConfigList : public Config { 83 | public: 84 | Type GetType() const override final { return LIST; } 85 | 86 | ConfigList() = default; 87 | ConfigList(const std::vector>& list) : list_(list) {} 88 | ConfigList(std::initializer_list> l) : list_(l) {} 89 | 90 | std::vector>* GetList() { return &list_; } 91 | const std::vector>* GetList() const { return &list_; } 92 | 93 | cJSON* ToCJSON() const override; 94 | Status FromCJSON(const cJSON* json) override; 95 | 96 | private: 97 | std::vector> list_; 98 | }; 99 | 100 | class ConfigPair : public ConfigList { 101 | public: 102 | ConfigPair(std::shared_ptr first, std::shared_ptr second) 103 | : ConfigList({std::move(first), std::move(second)}) {} 104 | }; 105 | 106 | class ConfigInt : public Config { 107 | public: 108 | Type GetType() const override final { return INTEGER; } 109 | 110 | ConfigInt(int32_t value, int32_t min, int32_t max) 111 | : value_(value), min_(min), max_(max) {} 112 | 113 | std::pair GetMinMax() const { return {min_, max_}; } 114 | int32_t GetValue() const { return value_; } 115 | void SetValue(int32_t value) { value_ = value; } 116 | 117 | cJSON* ToCJSON() const override; 118 | Status FromCJSON(const cJSON* json) override; 119 | 120 | private: 121 | int32_t value_; 122 | const int32_t min_; 123 | const int32_t max_; 124 | }; 125 | 126 | class ConfigFloat : public Config { 127 | public: 128 | Type GetType() const override final { return FLOAT; } 129 | 130 | ConfigFloat(float value, float min, float max, float resolution) 131 | : value_(value), min_(min), max_(max), resolution_(resolution) {} 132 | 133 | std::pair GetMinMax() const { return {min_, max_}; } 134 | float GetResolution() const { return resolution_; } 135 | float GetValue() const { return value_; } 136 | void SetValue(float value) { value_ = value; } 137 | 138 | cJSON* ToCJSON() const override; 139 | Status FromCJSON(const cJSON* json) override; 140 | 141 | private: 142 | float value_; 143 | const float min_; 144 | const float max_; 145 | const float resolution_; 146 | }; 147 | 148 | // If return is ERROR, default_config will be in an invalid state. 149 | // default_config is modified in place. 150 | Status ParseJsonConfig(const std::string& json, Config* default_config); 151 | 152 | #endif /* CONFIGURATION_H_ */ 153 | -------------------------------------------------------------------------------- /docs/ibp.md: -------------------------------------------------------------------------------- 1 | # Inter-Board Protocol 2 | 3 | ## Design Objectives 4 | * Works with various hardware protocols, such as SPI, I2C and Serial. 5 | * Half-duplex transfers as some protocols like I2C are not full-duplex. 6 | * Bandwidth efficient. 7 | * Length delimited packets. Packet size is dependent on how many data to transmit, vs fixed length. 8 | * Robust to errors. 9 | * Error detection with CRC and parity bit. 10 | * Will drop the offending packet. However, the future packets should not be affected by one erroneous packet. 11 | * Easy to parse and encode. 12 | * Extensible. 13 | 14 | ## Layers 15 | 16 | ![ibp.png](ibp.png) 17 | 18 | The protocol consists of three layers: 19 | 1. Hardware layer: This layer implements the hardware specific protocols for sending and receiving the IBP packets. This is implemented in files like `spi.h` and `spi.cc`. 20 | 2. IBP Packet layer: This layer parses and encodes the IBP packet. This is implemented in `ibp_lib.h` and `ibp_lib.c`. 21 | 3. Application layer: This layer interprets the IBP packet and handles it in the framework of the existing devices. One example could be a `KeyboardOutputDevice`, which encodes the keycodes in IBP packet format. The base class is implemented in `ibp.h` and `ibp.cc`. 22 | 23 | ## IBP Packet Format 24 | 25 | ![ibp-format.png](ibp-format.png) 26 | 27 | Each packet contains a packet header, and multiple segments. Each segment contains a segment header, 8-bit CRC for the data bytes, and the data bytes. The whole packet (including the packet header) is padded to a multiple of 4 bytes, largely due to the fact that RP2040 SPI RX interrupt only fires when there are 4 bytes in the RX queue. 28 | 29 | ### Packet Header 30 | ``` 31 | bit 7 6 5 4 3 2 1 0 32 | +------+------+------+------+------+------+------+------+ 33 | | total number of bytes incl. this header |parity| 34 | +------+------+------+------+------+------+------+------+ 35 | ``` 36 | The total number of bytes includes the padding. 37 | 38 | ### Segment Header 39 | ``` 40 | bit 7 6 5 4 3 2 1 0 41 | +------+------+------+------+------+------+------+------+ 42 | | number of data bytes | field type |parity| 43 | +------+------+------+------+------+------+------+------+ 44 | ``` 45 | 46 | ## Hardware Layers 47 | 48 | ### SPI Low Level Protocol 49 | 50 | ![ibp-spi.png](ibp-spi.png) 51 | 52 | Communication via SPI is driven by the host device. It generally involves three steps: 1. host sends the IBP packet to device, 2. host waits for the header of the device packet, so that the host knows how many bytes to read, 3. receive the remaining bytes of the device IBP packet. 53 | 54 | This is how the three steps are carried out from host's perspective: 55 | 56 | 1. Host will start the transmission with the IBP packet it wants to send to the device. As IBP packet is 4-byte aligned, it sends at least 4 bytes, which is enough to trigger SPI RX interrupt on Pi Pico. At this step, host doesn't care about the duplex bytes from the device. 57 | 2. Once host packet is pushed out, host further sends 4 `0x00`s down the MOSI line, and wait for the corresponding 4 bytes from the device. This step creates a time delay for slower device to respond. Per protocol, the first non-zero byte the device sends will be the device packet header, and host looks for this header in the 4 response bytes. If no valid packet header is found, the transmission is treated as an error and terminates here. Otherwise, all the bytes, including and after the header will be saved for parsing. 58 | 3. Host keeps on chucking out `0x00`s to keep the device responding. From the header received in step 2, host knows how many bytes in total to expect from the device. This step will send out as many `0x00`s as the remaining bytes. 59 | 60 | An example host implementation can be found in `linux/spi_module.c`. 61 | 62 | This is how the three steps are carried out from device's perspective: 63 | 64 | 1. To save power, the device relies on the interrupt from SPI module to know when a transmission starts. On RP2040, the SPI RX interupt only happens when there are at least 4 bytes in the RX buffer (see the `SSPIMSC` register in section 4.4.4 of [RP2040 Datasheet](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf)). The interrupt handler maintains a buffer and keeps track of the packet size. If the buffer is empty, it expects the first byte to be a valid header. If so, it adds the data to the buffer, and otherwise wakes up the SPI task to handle the error. 65 | 2. When there are enough data in the RX buffer, as specified by the header, the RX interrupt handler wakes up the SPI task, as well as prefill the TX buffer and enables the TX interrupt. RX interrupt handler also masks out the future RX interrupt to ignore the `0x00`s from the host. 66 | 3. TX interrupt fires when there's less than 4 bytes in the TX queue. TX interrupt handler simply pushes the packet bytes into the queue, or notifies the SPI task when there's no bytes remaining. When this happens TX interrupt handler also disables the TX interrupt. SPI main task then parses the RX packet, creates a new TX packet, and clears the RX and TX buffer if there's any remaining data. The task also unmasks the RX interrupt for the next transmission before going back to sleep. 67 | 68 | See the details of the implementation in `spi.cc`. 69 | 70 | ## I2C Low Level Protocol 71 | TODO 72 | -------------------------------------------------------------------------------- /layout_internal.inc: -------------------------------------------------------------------------------- 1 | namespace { 2 | 3 | template 4 | constexpr size_t ArraySize(T (&)[N]) { 5 | return N; 6 | } 7 | 8 | constexpr size_t kNumLayers = ArraySize(kKeyCodes); 9 | 10 | template 11 | struct Scan { 12 | GPIO gpio; 13 | std::array keycodes; 14 | }; 15 | 16 | constexpr size_t kGPIONumMax = 40; // Pico has at most 40 pins 17 | 18 | template 19 | using KeyScanOrder = std::array, N>; 20 | using AllGPIOs = std::array; 21 | 22 | // Force non-constexpr as error 23 | void failure(const char*) {} 24 | 25 | template 26 | constexpr KeyScanOrder ConvertKeyScan(const GPIO (&gpio)[R][C], 27 | const Keycode (&kc)[L][R][C]) { 28 | size_t end_idx[kGPIONumMax] = {0}; 29 | size_t source_count[kGPIONumMax] = {0}; 30 | 31 | // Counting sort the source GPIOs 32 | 33 | for (size_t r = 0; r < R; ++r) { 34 | for (size_t c = 0; c < C; ++c) { 35 | bool is_none = true; 36 | for (size_t l = 0; l < L; ++l) { 37 | if (kc[l][r][c].is_custom || kc[l][r][c].keycode != HID_KEY_NONE) { 38 | is_none = false; 39 | break; 40 | } 41 | } 42 | if (is_none) { 43 | continue; 44 | } 45 | if (gpio[r][c].source == gpio[r][c].sink) { 46 | failure("Source and sink GPIOs have to be different"); 47 | } 48 | if (gpio[r][c].source >= kGPIONumMax) { 49 | failure("Invalid source GPIO number"); 50 | } 51 | if (gpio[r][c].sink >= kGPIONumMax) { 52 | failure("Invalid sink GPIO number"); 53 | } 54 | ++end_idx[gpio[r][c].source]; 55 | ++source_count[gpio[r][c].source]; 56 | } 57 | } 58 | 59 | for (size_t i = 1; i < kGPIONumMax; ++i) { 60 | end_idx[i] += end_idx[i - 1]; 61 | } 62 | 63 | KeyScanOrder output = {0}; 64 | 65 | for (size_t r = 0; r < R; ++r) { 66 | for (size_t c = 0; c < C; ++c) { 67 | bool is_none = true; 68 | for (size_t l = 0; l < L; ++l) { 69 | if (kc[l][r][c].is_custom || kc[l][r][c].keycode != HID_KEY_NONE) { 70 | is_none = false; 71 | break; 72 | } 73 | } 74 | if (is_none) { 75 | continue; 76 | } 77 | const size_t idx = 78 | end_idx[gpio[r][c].source] - source_count[gpio[r][c].source]; 79 | auto& key_slot = output[idx]; 80 | key_slot.gpio = gpio[r][c]; 81 | for (size_t i = gpio[r][c].source > 0 ? end_idx[gpio[r][c].source - 1] 82 | : 0; 83 | i < idx; ++i) { 84 | if (key_slot.gpio.sink == output[i].gpio.sink) { 85 | failure("Duplicate source sink combination."); 86 | } 87 | } 88 | for (size_t l = 0; l < L; ++l) { 89 | if (!kc[l][r][c].is_custom && kc[l][r][c].keycode == HID_KEY_NONE) { 90 | continue; 91 | } 92 | key_slot.keycodes[l] = kc[l][r][c]; 93 | } 94 | --source_count[gpio[r][c].source]; 95 | } 96 | } 97 | 98 | return output; 99 | } 100 | 101 | template 102 | constexpr size_t CountKeyScans(const KeyScanOrder& key_scans) { 103 | size_t i = 0; 104 | for (; i < key_scans.size(); ++i) { 105 | if (key_scans[i].gpio.source == 0 && key_scans[i].gpio.sink == 0) { 106 | return i; 107 | } 108 | } 109 | 110 | return i; 111 | } 112 | 113 | template 114 | constexpr AllGPIOs CollectAllGPIOs(const KeyScanOrder& scan_order) { 115 | bool exist[kGPIONumMax] = {0}; 116 | 117 | for (size_t i = 0; i < CountKeyScans(scan_order); ++i) { 118 | const auto& scan = scan_order[i]; 119 | exist[scan.gpio.source] = true; 120 | exist[scan.gpio.sink] = true; 121 | } 122 | 123 | AllGPIOs output; 124 | output.fill(255); 125 | size_t idx = 0; 126 | for (size_t i = 0; i < kGPIONumMax; ++i) { 127 | if (exist[i]) { 128 | output[idx++] = i; 129 | } 130 | } 131 | return output; 132 | } 133 | 134 | constexpr size_t CountGPIOs(const AllGPIOs& gpios) { 135 | size_t i = 0; 136 | for (; i < gpios.size(); ++i) { 137 | if (gpios[i] == 255) { 138 | return i; 139 | } 140 | } 141 | 142 | return i; 143 | } 144 | 145 | static constexpr auto __not_in_flash("keyscan") kKeys = 146 | ConvertKeyScan(kGPIOMatrix, kKeyCodes); 147 | static constexpr AllGPIOs __not_in_flash("keyscan") kGPIOPins = 148 | CollectAllGPIOs(kKeys); 149 | 150 | } // namespace 151 | 152 | size_t GetKeyboardNumLayers() { return kNumLayers; } 153 | 154 | size_t GetTotalNumGPIOs() { return CountGPIOs(kGPIOPins); } 155 | 156 | uint8_t GetGPIOPin(size_t gpio_idx) { return kGPIOPins.at(gpio_idx); } 157 | 158 | size_t GetTotalScans() { return CountKeyScans(kKeys); } 159 | 160 | uint8_t GetSourceGPIO(size_t scan_idx) { 161 | return kKeys.at(scan_idx).gpio.source; 162 | } 163 | 164 | uint8_t GetSinkGPIO(size_t scan_idx) { return kKeys.at(scan_idx).gpio.sink; } 165 | 166 | bool IsSourceChange(size_t scan_idx) { 167 | if (scan_idx == 0) { 168 | return true; 169 | } 170 | return GetSourceGPIO(scan_idx - 1) != GetSourceGPIO(scan_idx); 171 | } 172 | 173 | Keycode GetKeycodeAtLayer(uint8_t layer, size_t scan_idx) { 174 | return kKeys.at(scan_idx).keycodes.at(layer); 175 | } 176 | -------------------------------------------------------------------------------- /temperature.cc: -------------------------------------------------------------------------------- 1 | #include "temperature.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "hardware/adc.h" 7 | #include "hardware/timer.h" 8 | 9 | constexpr uint8_t kADC = 4; 10 | constexpr uint8_t kBufferSize = 100; 11 | 12 | TemperatureInputDeivce::TemperatureInputDeivce() 13 | : is_fahrenheit_(true), 14 | is_config_(false), 15 | enabled_(true), 16 | buffer_(kBufferSize), 17 | sample_every_ticks_(1), 18 | counter_(0), 19 | buffer_idx_(0), 20 | sum_(0), 21 | prev_temp_(0) { 22 | adc_init(); 23 | adc_set_temp_sensor_enabled(true); 24 | } 25 | 26 | void TemperatureInputDeivce::InputLoopStart() { 27 | adc_select_input(kADC); 28 | 29 | sum_ = 0; 30 | 31 | // Fill the buffer 32 | for (size_t i = 0; i < buffer_.size(); ++i) { 33 | const uint16_t reading = adc_read(); 34 | buffer_[i] = reading; 35 | sum_ += reading; 36 | busy_wait_us(2); 37 | } 38 | buffer_idx_ = 0; 39 | prev_temp_ = 0; 40 | } 41 | 42 | void TemperatureInputDeivce::InputTick() { 43 | if (!enabled_) { 44 | return; 45 | } 46 | 47 | counter_ = (counter_ + 1) % sample_every_ticks_; 48 | if (counter_ != 0) { 49 | return; 50 | } 51 | 52 | adc_select_input(kADC); 53 | const uint16_t adc_raw = adc_read(); 54 | 55 | sum_ -= buffer_[buffer_idx_]; 56 | sum_ += adc_raw; 57 | buffer_[buffer_idx_] = adc_raw; 58 | buffer_idx_ = (buffer_idx_ + 1) % buffer_.size(); 59 | 60 | int32_t temp = ConvertTemperature(); 61 | if (temp != prev_temp_) { 62 | prev_temp_ = temp; 63 | WriteTemp(temp); 64 | } 65 | } 66 | 67 | std::pair> 68 | TemperatureInputDeivce::CreateDefaultConfig() { 69 | auto config = CONFIG_OBJECT( 70 | CONFIG_OBJECT_ELEM("fahrenheit", CONFIG_INT(1, 0, 1)), 71 | CONFIG_OBJECT_ELEM("enabled", CONFIG_INT(1, 0, 1)), 72 | CONFIG_OBJECT_ELEM("sample_n_ticks", CONFIG_INT(100, 0, 1000))); 73 | return {"Temperature", config}; 74 | } 75 | 76 | void TemperatureInputDeivce::OnUpdateConfig(const Config* config) { 77 | if (config->GetType() != Config::OBJECT) { 78 | LOG_ERROR("Root config has to be an object."); 79 | return; 80 | } 81 | const auto& root_map = *((ConfigObject*)config)->GetMembers(); 82 | auto it = root_map.find("fahrenheit"); 83 | if (it == root_map.end()) { 84 | LOG_ERROR("Can't find `fahrenheit` in config"); 85 | return; 86 | } 87 | if (it->second->GetType() != Config::INTEGER) { 88 | LOG_ERROR("`fahrenheit` invalid type"); 89 | return; 90 | } 91 | is_fahrenheit_ = ((ConfigInt*)it->second.get())->GetValue() == 1; 92 | 93 | it = root_map.find("enabled"); 94 | if (it == root_map.end()) { 95 | LOG_ERROR("Can't find `enabled` in config"); 96 | return; 97 | } 98 | if (it->second->GetType() != Config::INTEGER) { 99 | LOG_ERROR("`enabled` invalid type"); 100 | return; 101 | } 102 | enabled_ = ((ConfigInt*)it->second.get())->GetValue() == 1; 103 | 104 | it = root_map.find("sample_n_ticks"); 105 | if (it == root_map.end()) { 106 | LOG_ERROR("Can't find `sample_n_ticks` in config"); 107 | return; 108 | } 109 | if (it->second->GetType() != Config::INTEGER) { 110 | LOG_ERROR("`sample_n_ticks` invalid type"); 111 | return; 112 | } 113 | sample_every_ticks_ = ((ConfigInt*)it->second.get())->GetValue(); 114 | counter_ = 0; 115 | } 116 | 117 | void TemperatureInputDeivce::SetConfigMode(bool is_config_mode) { 118 | is_config_ = is_config_mode; 119 | } 120 | 121 | void TemperatureInputDeivce::WriteTemp(int32_t temp) { 122 | // Note: This function writes directly to the screen output because it's 123 | // allowed by the framework. However, it's generally a bad idea, since the 124 | // code here has no way of konwing what's being painted on the screen by other 125 | // devices. A better way is to create a mixin. See display_mixins.h for 126 | // examples. 127 | if (is_config_) { 128 | return; 129 | } 130 | 131 | for (auto screen : *screen_output_) { 132 | std::unique_ptr buffer(new char[screen->GetNumCols() / 8]); 133 | const size_t padding = screen->GetNumCols() / 8 - 7; 134 | size_t len = std::snprintf(buffer.get(), 16, "Temp:%*d%s", padding, temp, 135 | is_fahrenheit_ ? "F" : "C"); 136 | 137 | // Clear the row first 138 | screen->DrawRect(screen->GetNumRows() - 8, 0, screen->GetNumRows(), 139 | screen->GetNumCols(), true, ScreenOutputDevice::SUBTRACT); 140 | screen->DrawText(screen->GetNumRows() - 8, 0, 141 | std::string(buffer.get(), len), ScreenOutputDevice::F8X8, 142 | ScreenOutputDevice::ADD); 143 | } 144 | } 145 | 146 | int32_t TemperatureInputDeivce::ConvertTemperature() { 147 | constexpr float kConversionFactor = 3.3f / (1 << 12); 148 | 149 | float adc = (float)sum_ * kConversionFactor / buffer_.size(); 150 | float temp = 27.0f - (adc - 0.706f) / 0.001721f; 151 | 152 | if (is_fahrenheit_) { 153 | return (int32_t)(temp * 9 / 5 + 32 + 0.5); 154 | } else { 155 | return (int32_t)(temp + 0.5); 156 | } 157 | } 158 | 159 | Status RegisterTemperatureInput(uint8_t tag) { 160 | return DeviceRegistry::RegisterInputDevice( 161 | tag, []() { return std::make_shared(); }); 162 | } 163 | -------------------------------------------------------------------------------- /docs/layout_cc.md: -------------------------------------------------------------------------------- 1 | # Anatomy of layout.cc 2 | 3 | This doc shows the structure of a `layout.cc` file, including what are the required elements of the file. For the also required `config.h` file, please take a look at the default config's `config.h` at `configs/default/config.h`. For this tutorial we'll use a toy keyboard layout with the following key matrix and setup: 4 | | Schematic | Layout | 5 | | --------------------------------- | ------------------------------ | 6 | | ![Schematic](small_keymatrix.png) | ![Layout](small_keylayout.png) | 7 | 8 | First, we need to include a helper file at the very top 9 | 10 | ```cpp 11 | #include "layout_helper.h" 12 | ``` 13 | 14 | Then we need to define the required variables. Note that all the variables here should at least be `constexpr`. 15 | 16 | ```cpp 17 | static constexpr GPIO kGPIOMatrix[3][3] = { 18 | {G(0, 11), G(0, 12), G(0, 13)}, 19 | {G(1, 11), G(1, 12), G(1, 13)}, 20 | {G(2, 11), G(2, 12)}, 21 | }; 22 | ``` 23 | 24 | `kGPIOMatrix` translates the **physical layout** of the keyboard to the GPIO wiring of each key. The `G` macro takes two parameters: the row GPIO and column GPIO. The `kGPIOMatrix` array has the shape of the maximum layout size so in our case it's 3x3 even though the bottom row only has 2 keys. The keys are represented in a row major left to right fasion, so for the bottom row even though in the physical layout the gap is in between the left arrow and right arrow, we still put them together next to each other. Each element of the matrix represents the scanning direction for the switch. For example `G(0, 11)` means the current flows from pin 0 to pin 11. Note that a pin can be either the source or sink on the matrix, as long as it's not both for the same switch. In other words, `G(0, 0)` will be invalid, but `G(11, 0)` is fine. This allows us to support arbitrary multiplexing wirings. See `config/cyberkeeb_2040` for an example of Japanese Duplexing. Of course, the hardware design has to ensure no ghosting can happen. 25 | 26 | ```cpp 27 | static constexpr Keycode kKeyCodes[][3][3] = { 28 | [0]={ 29 | {K(K_1), K(K_2), K(K_3)}, 30 | {K(K_4), K(K_5), K(K_6)}, 31 | {K(K_ARR_L), K(K_ARR_R)}, 32 | }, 33 | [1]={ 34 | {______, K(K_B)}, 35 | {K(K_A)}, 36 | {K(K_ARR_U), K(K_ARR_D)}, 37 | }, 38 | }; 39 | ``` 40 | 41 | `kKeyCodes` defines the actual keymap, i.e. which switch does what when it's pressed. The first dimenion of the array is the layer, and the other two dimensions are the layout. The layout arrays should match up exactly with the `kGPIOMatrix`. Similar to `kGPIOMatrix` the order of each row is left to right compact. In this example, if layer 1 is active, the top left key will still be `1` and the one to its right will be `B`. The top right key will still be `3`. This is because that location will be zero initialized, which has the same effect as `______` (nop key). At this point, we have successfully defined the key layout. 42 | 43 | ```cpp 44 | // Compile time validation and conversion for the key matrix. Must include this. 45 | #include "layout_internal.inc" 46 | ``` 47 | `layout_internal.inc` is a required include, and has to be done after the layout definition. It checks and converts the layouts defined above at compile time (that why we need `constexpr` for the above variables), so that we don't need to do it at runtime. 48 | 49 | At this point, if you compile and flash the firmware, the keyboard still won't do anything. It's because there's no device registered to scan the key matrix and handle the USB communication. We need to register those devices. Registration is similar to the traditional conditional compilation with macro setup, that only necessary code will be linked. Registration is better than conditional compilation as we don't need to have a centralized place for all the macros. For this example, we will register the only necessary device, the key scanner. 50 | 51 | ```cpp 52 | static Status register1 = RegisterKeyscan(/*tag=*/0); 53 | ``` 54 | 55 | A few notes here. First, the return value has to be assigned to a static variable otherwise compiler will strip out the call. Second, each device needs a unique tag at registration. Other devices such as screen or joystick might need extra parameters. Please see their corresponding documentations. 56 | 57 | The whole file looks like this: 58 | 59 | ```cpp 60 | #include "layout_helper.h" 61 | 62 | static constexpr GPIO kGPIOMatrix[3][3] = { 63 | {G(0, 11), G(0, 12), G(0, 13)}, 64 | {G(1, 11), G(1, 12), G(1, 13)}, 65 | {G(2, 11), G(2, 12)}, 66 | }; 67 | 68 | static constexpr Keycode kKeyCodes[][3][3] = { 69 | [0]={ 70 | {K(K_1), K(K_2), K(K_3)}, 71 | {K(K_4), K(K_5), K(K_6)}, 72 | {K(K_ARR_L), K(K_ARR_R)}, 73 | }, 74 | [1]={ 75 | {______, K(K_B)}, 76 | {K(K_A)}, 77 | {K(K_ARR_U), K(K_ARR_D)}, 78 | }, 79 | }; 80 | 81 | #include "layout_internal.inc" 82 | 83 | static Status register1 = RegisterKeyscan(/*tag=*/0); 84 | ``` 85 | 86 | For next step you can take a look at the default layout at `configs/default/layout.cc` for [Pico-Keyboard](https://github.com/zli117/Pico-Keyboard). Please also take a look at the `layout_helper.h` for helper macros and keycodes. 87 | 88 | # Migrate from old config 89 | Note that in order to support arbitrary duplexing, `kRowGPIO`, `kColGPIO`, and `kDiodeColToRow` are deprecated. If you're using the old config, all you have to do is to remove these three variables. The new `kGPIOMatrix` supports a superset of what the old `kGPIOMatrix` can express, namely now a pin can be both a source and a sink, as long as not for the same switch. 90 | -------------------------------------------------------------------------------- /linux/keyboard.c: -------------------------------------------------------------------------------- 1 | #include "keyboard.h" 2 | 3 | #include 4 | 5 | // Copied from drivers/hid/usbhid/usbkbd.c 6 | static const uint8_t usb_kbd_keycode[256] = { 7 | 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 8 | 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 9 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 10 | 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 11 | 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 12 | 104, 111, 107, 109, 106, 105, 108, 103, 69, 98, 55, 74, 78, 96, 79, 13 | 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86, 127, 116, 117, 183, 14 | 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 134, 138, 130, 132, 15 | 128, 129, 131, 137, 133, 135, 136, 113, 115, 114, 0, 0, 0, 121, 0, 16 | 89, 93, 124, 92, 94, 95, 0, 0, 0, 122, 123, 90, 91, 85, 0, 17 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 22 | 42, 56, 125, 97, 54, 100, 126, 164, 166, 165, 163, 161, 115, 114, 113, 23 | 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140}; 24 | 25 | struct KeyboardDevice { 26 | struct input_dev* dev; 27 | IBPKeyCodes old_keys; 28 | IBPConsumer old_consumer_keys; 29 | int (*open)(void); 30 | void (*close)(void); 31 | }; 32 | 33 | static struct KeyboardDevice keyboard_device = {0}; 34 | static spinlock_t keyboard_device_lock; 35 | 36 | static int keyboard_open(struct input_dev* dev) { 37 | if (keyboard_device.open != NULL) { 38 | return keyboard_device.open(); 39 | } 40 | return 0; 41 | } 42 | 43 | static void keyboard_close(struct input_dev* dev) { 44 | if (keyboard_device.close) { 45 | keyboard_device.close(); 46 | } 47 | } 48 | 49 | int CreateKeyboardDevice(int (*open)(void), void (*close)(void)) { 50 | int error; 51 | 52 | if (keyboard_device.dev != NULL) { 53 | error = -EEXIST; 54 | goto err; 55 | } 56 | 57 | keyboard_device.dev = input_allocate_device(); 58 | if (!keyboard_device.dev) { 59 | printk(KERN_ERR "%s:%d Not enough memory\n", __FILE__, __LINE__); 60 | error = -ENOMEM; 61 | goto err; 62 | } 63 | 64 | keyboard_device.dev->evbit[0] = BIT_MASK(EV_KEY); 65 | keyboard_device.dev->open = keyboard_open; 66 | keyboard_device.dev->close = keyboard_close; 67 | keyboard_device.dev->name = "PicoMK Keyboard"; 68 | keyboard_device.open = open; 69 | keyboard_device.close = close; 70 | for (int i = 0; i < 255; i++) { 71 | set_bit(usb_kbd_keycode[i], keyboard_device.dev->keybit); 72 | } 73 | clear_bit(0, keyboard_device.dev->keybit); 74 | 75 | error = input_register_device(keyboard_device.dev); 76 | if (error) { 77 | printk(KERN_ERR "%s:%d Failed to register device\n", __FILE__, __LINE__); 78 | goto err_free_dev; 79 | } 80 | 81 | memset(&keyboard_device.old_keys, 0, sizeof(IBPKeyCodes)); 82 | memset(&keyboard_device.old_consumer_keys, 0, sizeof(IBPConsumer)); 83 | 84 | spin_lock_init(&keyboard_device_lock); 85 | 86 | return 0; 87 | 88 | err_free_dev: 89 | input_free_device(keyboard_device.dev); 90 | keyboard_device.dev = NULL; 91 | keyboard_device.open = NULL; 92 | keyboard_device.close = NULL; 93 | err: 94 | return error; 95 | } 96 | 97 | int DestroyKeyboardDevice(void) { 98 | spin_lock(&keyboard_device_lock); 99 | if (keyboard_device.dev) { 100 | input_unregister_device(keyboard_device.dev); 101 | input_free_device(keyboard_device.dev); 102 | keyboard_device.dev = NULL; 103 | } 104 | keyboard_device.open = NULL; 105 | keyboard_device.close = NULL; 106 | spin_unlock(&keyboard_device_lock); 107 | return 0; 108 | } 109 | 110 | int OnNewKeycodes(IBPKeyCodes* keys) { 111 | spin_lock(&keyboard_device_lock); 112 | if (keyboard_device.dev == NULL) { 113 | spin_unlock(&keyboard_device_lock); 114 | return 0; 115 | } 116 | spin_unlock(&keyboard_device_lock); 117 | 118 | for (int i = 0; i < 8; i++) { 119 | input_report_key(keyboard_device.dev, usb_kbd_keycode[i + 224], 120 | (keys->modifier_bitmask >> i) & 1); 121 | } 122 | for (int i = 0; i < keyboard_device.old_keys.num_keycodes; ++i) { 123 | if (memscan(keys->keycodes, keyboard_device.old_keys.keycodes[i], 124 | keys->num_keycodes) == keys->keycodes + keys->num_keycodes) { 125 | const uint8_t keycode = 126 | usb_kbd_keycode[keyboard_device.old_keys.keycodes[i]]; 127 | if (keycode) { 128 | input_report_key(keyboard_device.dev, keycode, 0); 129 | } 130 | } 131 | } 132 | for (int i = 0; i < keys->num_keycodes; ++i) { 133 | if (memscan(keyboard_device.old_keys.keycodes, keys->keycodes[i], 134 | keyboard_device.old_keys.num_keycodes) == 135 | keyboard_device.old_keys.keycodes + 136 | keyboard_device.old_keys.num_keycodes) { 137 | const uint8_t keycode = usb_kbd_keycode[keys->keycodes[i]]; 138 | if (keycode) { 139 | input_report_key(keyboard_device.dev, keycode, 1); 140 | } 141 | } 142 | } 143 | input_sync(keyboard_device.dev); 144 | keyboard_device.old_keys = *keys; 145 | return 0; 146 | } 147 | 148 | int OnNewConsumerKeycodes(IBPConsumer* keys) { return 0; } 149 | -------------------------------------------------------------------------------- /config_modifier.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_MODIFIER_H_ 2 | #define CONFIG_MODIFIER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "base.h" 8 | #include "configuration.h" 9 | 10 | class ConfigModifiersImpl; 11 | 12 | class ConfigUIBase { 13 | public: 14 | ConfigUIBase(ConfigModifiersImpl* config_modifier, ScreenOutputDevice* screen, 15 | uint8_t screen_top_margin) 16 | : config_modifier_(config_modifier), 17 | screen_(screen), 18 | redraw_(true), 19 | screen_top_margin_(screen_top_margin) {} 20 | 21 | virtual void Draw() = 0; 22 | virtual void OnUp() = 0; 23 | virtual void OnDown() = 0; 24 | virtual void OnSelect() = 0; 25 | 26 | protected: 27 | inline uint8_t GetScreenNumRows() { 28 | return (screen_->GetNumRows() - screen_top_margin_) / 8; 29 | } 30 | 31 | ScreenOutputDevice::Font GetFont() { return ScreenOutputDevice::F8X8; } 32 | 33 | ConfigModifiersImpl* config_modifier_; 34 | ScreenOutputDevice* screen_; 35 | bool redraw_; 36 | const uint8_t screen_top_margin_; 37 | }; 38 | 39 | class ListUI : public ConfigUIBase { 40 | public: 41 | ListUI(ConfigModifiersImpl* config_modifier, ScreenOutputDevice* screen, 42 | uint8_t screen_top_margin) 43 | : ConfigUIBase(config_modifier, screen, screen_top_margin), 44 | current_highlight_(0), 45 | draw_start_(0) {} 46 | 47 | void OnUp() override; 48 | void OnDown() override; 49 | 50 | protected: 51 | virtual uint32_t GetListLength() = 0; 52 | 53 | virtual void ListDrawImpl(const std::vector& content); 54 | 55 | uint32_t current_highlight_; 56 | uint32_t draw_start_; 57 | }; 58 | 59 | class HomeScreen : public ListUI { 60 | public: 61 | HomeScreen(ConfigModifiersImpl* config_modifier, ScreenOutputDevice* screen, 62 | ConfigObject* global_config_object, uint8_t screen_top_margin) 63 | : ListUI(config_modifier, screen, screen_top_margin), 64 | global_config_object_(global_config_object), 65 | menu_items_({"Edit Config", "Save Config", "Load Default", "Exit"}) {} 66 | 67 | void Draw() override; 68 | void OnSelect() override; 69 | 70 | protected: 71 | uint32_t GetListLength() override; 72 | 73 | ConfigObject* global_config_object_; 74 | std::vector menu_items_; 75 | }; 76 | 77 | class ConfigObjectScreen : public ListUI { 78 | public: 79 | ConfigObjectScreen(ConfigModifiersImpl* config_modifier, 80 | ScreenOutputDevice* screen, ConfigObject* config_object, 81 | uint8_t screen_top_margin); 82 | 83 | void Draw() override; 84 | void OnSelect() override; 85 | 86 | protected: 87 | uint32_t GetListLength() override; 88 | 89 | ConfigObject* config_object_; 90 | std::vector keys_; 91 | }; 92 | 93 | class ConfigListScreen : public ListUI { 94 | public: 95 | ConfigListScreen(ConfigModifiersImpl* config_modifier, 96 | ScreenOutputDevice* screen, ConfigList* config_list, 97 | uint8_t screen_top_margin); 98 | 99 | void Draw() override; 100 | void OnSelect() override; 101 | 102 | protected: 103 | uint32_t GetListLength() override; 104 | 105 | ConfigList* config_list_; 106 | std::vector indices_; 107 | }; 108 | 109 | class ConfigIntScreen : public ConfigUIBase { 110 | public: 111 | ConfigIntScreen(ConfigModifiersImpl* config_modifier, 112 | ScreenOutputDevice* screen, ConfigInt* config_int, 113 | uint8_t screen_top_margin) 114 | : ConfigUIBase(config_modifier, screen, screen_top_margin), 115 | config_int_(config_int) {} 116 | 117 | void Draw() override; 118 | void OnUp() override; 119 | void OnDown() override; 120 | void OnSelect() override; 121 | 122 | protected: 123 | ConfigInt* config_int_; 124 | }; 125 | 126 | class ConfigFloatScreen : public ConfigUIBase { 127 | public: 128 | ConfigFloatScreen(ConfigModifiersImpl* config_modifier, 129 | ScreenOutputDevice* screen, ConfigFloat* config_float, 130 | uint8_t screen_top_margin) 131 | : ConfigUIBase(config_modifier, screen, screen_top_margin), 132 | config_float_(config_float) {} 133 | 134 | void Draw() override; 135 | void OnUp() override; 136 | void OnDown() override; 137 | void OnSelect() override; 138 | 139 | protected: 140 | ConfigFloat* config_float_; 141 | }; 142 | 143 | class ConfigModifiersImpl : virtual public ConfigModifier { 144 | public: 145 | ConfigModifiersImpl(ConfigObject* global_config, uint8_t screen_tag, 146 | uint8_t screen_top_margin = 0) 147 | : global_config_(global_config), 148 | screen_tag_(screen_tag), 149 | screen_top_margin_(screen_top_margin), 150 | screen_(NULL), 151 | is_config_(false) {} 152 | 153 | void SetScreenOutputs( 154 | const std::vector>* device) override; 155 | 156 | void SetConfigMode(bool is_config_mode) override; 157 | 158 | void InputLoopStart() override {} 159 | void InputTick() override {} 160 | void OutputTick() override {} 161 | 162 | void StartOfInputTick() override {} 163 | void FinalizeInputTickOutput() override; 164 | 165 | void Up() override; 166 | void Down() override; 167 | void Select() override; 168 | 169 | virtual void PushUI(std::shared_ptr ui); 170 | virtual void PopUI(); 171 | virtual void EndConfig(); 172 | 173 | protected: 174 | ConfigObject* const global_config_; 175 | const uint8_t screen_tag_; 176 | const uint8_t screen_top_margin_; 177 | std::shared_ptr screen_; 178 | std::vector> ui_stack_; 179 | bool is_config_; // No need to lock since there's no OutputTick 180 | bool pop_ui_; 181 | }; 182 | 183 | Status RegisterConfigModifier(uint8_t screen_tag); 184 | 185 | #endif /* CONFIG_MODIFIER_H_ */ -------------------------------------------------------------------------------- /configs/examples/home_screen/layout.cc: -------------------------------------------------------------------------------- 1 | #include "layout_helper.h" 2 | 3 | #define C0 0 4 | #define C1 1 5 | #define C2 2 6 | #define C3 3 7 | #define C4 4 8 | #define C5 5 9 | #define C6 6 10 | #define C7 7 11 | #define C8 8 12 | #define C9 9 13 | #define C10 10 14 | #define C11 11 15 | #define C12 13 16 | #define C13 12 17 | #define R0 14 18 | #define R1 15 19 | #define R2 18 20 | #define R3 17 21 | #define R4 16 22 | 23 | #define CONFIG_NUM_PHY_ROWS 6 24 | #define CONFIG_NUM_PHY_COLS 15 25 | 26 | #define ALT_LY 4 27 | 28 | // clang-format off 29 | 30 | // Keyboard switch physical GPIO connection setup. 31 | static constexpr GPIO kGPIOMatrix[CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 32 | {G(C0, R0), G(C1, R0), G(C2, R0), G(C3, R0), G(C4, R0), G(C5, R0), G(C6, R0), G(C7, R0), G(C8, R0), G(C9, R0), G(C10, R0), G(C11, R0), G(C12, R0), G(C13, R0), G(C13, R1)}, 33 | {G(C0, R1), G(C1, R1), G(C2, R1), G(C3, R1), G(C4, R1), G(C5, R1), G(C6, R1), G(C7, R1), G(C8, R1), G(C9, R1), G(C10, R1), G(C11, R1), G(C12, R1), G(C13, R2), G(C13, R3)}, 34 | {G(C0, R2), G(C1, R2), G(C2, R2), G(C3, R2), G(C4, R2), G(C5, R2), G(C6, R2), G(C7, R2), G(C8, R2), G(C9, R2), G(C10, R2), G(C11, R2), G(C12, R2), G(C13, R4)}, 35 | {G(C0, R3), G(C1, R3), G(C2, R3), G(C3, R3), G(C4, R3), G(C5, R3), G(C6, R3), G(C7, R3), G(C8, R3), G(C9, R3), G(C10, R3), G(C11, R3), G(C12, R3)}, 36 | {G(C0, R4), G(C1, R4), G(C2, R4), G(C5, R4), G(C7, R4), G(C8, R4), G(C9, R4), G(C10, R4), G(C11, R4), G(C12, R4)}, 37 | {G(C3, R4), G(C4, R4), G(C6, R4)} 38 | }; 39 | 40 | static constexpr Keycode kKeyCodes[][CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 41 | [0]={ 42 | {K(K_MUTE), K(K_GRAVE), K(K_1), K(K_2), K(K_3), K(K_4), K(K_5), K(K_6), K(K_7), K(K_8), K(K_9), K(K_0), K(K_MINUS), K(K_EQUAL), K(K_BACKS)}, 43 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 44 | {K(K_DEL), K(K_CTR_L), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 45 | {K(K_INS), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 46 | {MO(ALT_LY), K(K_GUI_L), K(K_ALT_L), K(K_SPACE), MO(1), MO(2), K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 47 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 48 | }, 49 | [1]={ 50 | {K(K_MUTE), K(K_GRAVE), K(K_F1), K(K_F2), K(K_F3), K(K_F4), K(K_F5), K(K_F6), K(K_F7), K(K_F8), K(K_F9), K(K_F10), K(K_F11), K(K_F12), K(K_BACKS)}, 51 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 52 | {K(K_DEL), K(K_CAPS), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 53 | {MO(3), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 54 | {CONFIG, TG(2), K(K_ALT_L), K(K_SPACE), ______, ______, K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 55 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 56 | }, 57 | [2]={ 58 | {CK(CONFIG_SEL)}, 59 | {______}, 60 | {CK(REBOOT)}, 61 | }, 62 | [3]={ 63 | {______}, 64 | {CK(BOOTSEL)}, 65 | }, 66 | [ALT_LY]={}, 67 | }; 68 | 69 | // clang-format on 70 | 71 | // Compile time validation and conversion for the key matrix 72 | #include "layout_internal.inc" 73 | 74 | // Create the screen display. The content displayed on the screen is created 75 | // with the help of mixins. See display_mixins.h for the builtin mixins. Each 76 | // mixin displays a specific functionality at a specific region. Here we display 77 | // two items: the current active layer and the LED status (i.e. capslock, 78 | // numlock LEDs you can find on a keyboard). The mixins can be customized with 79 | // the template parameters. Here we are using the default paramters. 80 | 81 | class FancierScreen : public virtual SSD1306Display, 82 | public virtual ActiveLayersDisplayMixin<>, 83 | public virtual StatusLEDDisplayMixin<> { 84 | public: 85 | FancierScreen() 86 | : SSD1306Display(i2c0, 20, 21, 0x3c, SSD1306Display::R_64, true), 87 | ActiveLayersDisplayMixin<>(), 88 | StatusLEDDisplayMixin<>() {} 89 | 90 | static Status RegisterScreen(uint8_t key) { 91 | std::shared_ptr instance = std::make_shared(); 92 | if (ActiveLayersDisplayMixin<>::Register(key, /*slow=*/true, instance) != 93 | OK || 94 | StatusLEDDisplayMixin<>::Register(key, /*slow=*/true, instance) != OK || 95 | SSD1306Display::Register(key, /*slow=*/true, instance) != OK) { 96 | return ERROR; 97 | } 98 | return OK; 99 | } 100 | }; 101 | 102 | // Register all the devices 103 | 104 | enum { 105 | JOYSTICK = 0, 106 | KEYSCAN, 107 | ENCODER, 108 | SSD1306, 109 | USB_KEYBOARD, 110 | USB_MOUSE, 111 | USB_INPUT, 112 | LED, 113 | }; 114 | 115 | static Status register1 = RegisterConfigModifier(SSD1306); 116 | static Status register2 = 117 | RegisterJoystick(JOYSTICK, 28, 27, 5, false, false, true, ALT_LY); 118 | static Status register3 = RegisterKeyscan(KEYSCAN); 119 | static Status register4 = RegisterEncoder(ENCODER, 19, 22, 2); 120 | static Status register5 = FancierScreen::RegisterScreen(SSD1306); 121 | static Status register6 = RegisterUSBKeyboardOutput(USB_KEYBOARD); 122 | static Status register7 = RegisterUSBMouseOutput(USB_MOUSE); 123 | static Status register8 = RegisterUSBInput(USB_INPUT); 124 | static Status register9 = RegisterWS2812(LED, 26, 17); 125 | -------------------------------------------------------------------------------- /keyscan.cc: -------------------------------------------------------------------------------- 1 | #include "keyscan.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "FreeRTOS.h" 11 | #include "hardware/gpio.h" 12 | #include "hardware/timer.h" 13 | #include "layout.h" 14 | #include "pico/stdlib.h" 15 | #include "runner.h" 16 | #include "semphr.h" 17 | #include "tusb.h" 18 | #include "utils.h" 19 | 20 | void KeyScan::SetMouseButtonState(uint8_t mouse_key, bool is_pressed) { 21 | for (auto output : *mouse_output_) { 22 | if (is_pressed) { 23 | output->MouseKeycode(mouse_key); 24 | } 25 | } 26 | } 27 | 28 | void KeyScan::ConfigUp() { config_modifier_->Up(); } 29 | 30 | void KeyScan::ConfigDown() { config_modifier_->Down(); } 31 | 32 | void KeyScan::ConfigSelect() { config_modifier_->Select(); } 33 | 34 | void KeyScan::InputLoopStart() { LayerChanged(); } 35 | 36 | void KeyScan::InputTick() { 37 | const std::vector active_layers = GetActiveLayers(); 38 | std::vector pressed_keycode; 39 | 40 | for (size_t i = 0; i < GetTotalScans(); ++i) { 41 | const uint8_t source_pin = GetSourceGPIO(i); 42 | if (IsSourceChange(i)) { 43 | // Only source is the output. Others are all input with pull down. 44 | for (size_t j = 0; j < GetTotalNumGPIOs(); ++j) { 45 | const uint8_t sink_pin = GetGPIOPin(j); 46 | if (sink_pin == source_pin) { 47 | continue; 48 | } 49 | gpio_set_dir(sink_pin, false); 50 | gpio_pull_down(sink_pin); 51 | } 52 | gpio_set_dir(source_pin, true); 53 | gpio_put(source_pin, true); 54 | SinkGPIODelay(); 55 | } 56 | 57 | DebounceTimer& d_timer = debounce_timer_[i]; 58 | 59 | const bool pressed = gpio_get(GetSinkGPIO(i)); 60 | 61 | bool key_event = false; 62 | if (pressed != d_timer.pressed) { 63 | d_timer.tick_count += CONFIG_SCAN_TICKS; 64 | if (d_timer.tick_count >= CONFIG_DEBOUNCE_TICKS) { 65 | d_timer.pressed = !d_timer.pressed; 66 | d_timer.tick_count = 0; 67 | key_event = true; 68 | } 69 | } 70 | 71 | Keycode kc = {0}; 72 | for (uint8_t l : active_layers) { 73 | Keycode layer_kc = GetKeycodeAtLayer(l, i); 74 | if (layer_kc.is_custom || layer_kc.keycode != HID_KEY_NONE) { 75 | kc = layer_kc; 76 | break; 77 | } 78 | } 79 | 80 | if (kc.is_custom) { 81 | auto* handler = 82 | HandlerRegistry::RegisteredHandlerFactory(kc.keycode, this); 83 | if (handler != NULL) { 84 | handler->ProcessKeyState(kc, d_timer.pressed, i); 85 | if (key_event) { 86 | handler->ProcessKeyEvent(kc, d_timer.pressed, i); 87 | } 88 | } else { 89 | LOG_WARNING("Custom Keycode (%d) missing handler", kc.keycode); 90 | } 91 | } else if (d_timer.pressed) { 92 | pressed_keycode.push_back(kc.keycode); 93 | } 94 | } 95 | 96 | NotifyOutput(pressed_keycode); 97 | } 98 | 99 | void KeyScan::SetConfigMode(bool is_config_mode) { 100 | is_config_mode_ = is_config_mode; 101 | } 102 | 103 | status KeyScan::RegisterCustomKeycodeHandler( 104 | uint8_t keycode, bool overridable, CustomKeycodeHandlerCreator creator) { 105 | return HandlerRegistry::RegisterHandler(keycode, overridable, creator); 106 | } 107 | 108 | KeyScan::KeyScan() : is_config_mode_(false) { 109 | for (size_t i = 0; i < GetTotalNumGPIOs(); ++i) { 110 | gpio_init(GetGPIOPin(i)); 111 | } 112 | 113 | debounce_timer_.resize(GetTotalScans()); 114 | 115 | active_layers_.resize(GetKeyboardNumLayers()); 116 | active_layers_[0] = true; 117 | } 118 | 119 | Status KeyScan::SetLayerStatus(uint8_t layer, bool active) { 120 | if (layer > active_layers_.size()) { 121 | return ERROR; 122 | } 123 | if (layer == 0) { 124 | return OK; 125 | } 126 | if (active_layers_[layer] == active) { 127 | return OK; 128 | } 129 | active_layers_[layer] = active; 130 | LayerChanged(); 131 | return OK; 132 | } 133 | 134 | Status KeyScan::ToggleLayerStatus(uint8_t layer) { 135 | return SetLayerStatus(layer, !active_layers_[layer]); 136 | } 137 | 138 | std::vector KeyScan::GetActiveLayers() { 139 | std::vector output; 140 | for (int16_t i = active_layers_.size() - 1; i >= 0; --i) { 141 | if (active_layers_[i]) { 142 | output.push_back(i); 143 | } 144 | } 145 | return output; 146 | } 147 | 148 | void KeyScan::SinkGPIODelay() { busy_wait_us_32(CONFIG_GPIO_SINK_DELAY_US); } 149 | 150 | KeyScan::HandlerRegistry* KeyScan::HandlerRegistry::GetRegistry() { 151 | static HandlerRegistry instance; 152 | return &instance; 153 | } 154 | 155 | status KeyScan::HandlerRegistry::RegisterHandler( 156 | uint8_t keycode, bool overridable, CustomKeycodeHandlerCreator creator) { 157 | HandlerRegistry* instance = GetRegistry(); 158 | auto it = instance->custom_handlers_.find(keycode); 159 | if (it != instance->custom_handlers_.end()) { 160 | // Not overridable 161 | if (!it->second.first) { 162 | return ERROR; 163 | } 164 | } 165 | instance->custom_handlers_.insert( 166 | {keycode, std::make_pair(overridable, creator)}); 167 | return OK; 168 | } 169 | 170 | CustomKeycodeHandler* KeyScan::HandlerRegistry::RegisteredHandlerFactory( 171 | uint8_t keycode, KeyScan* outer) { 172 | HandlerRegistry* instance = GetRegistry(); 173 | auto it = instance->handler_singletons_.find(keycode); 174 | if (it != instance->handler_singletons_.end()) { 175 | return it->second; 176 | } else { 177 | auto creator_it = instance->custom_handlers_.find(keycode); 178 | if (creator_it == instance->custom_handlers_.end()) { 179 | return NULL; 180 | } 181 | auto* handler = creator_it->second.second(); 182 | handler->SetKeyScan(outer); 183 | instance->handler_singletons_[keycode] = handler; 184 | return handler; 185 | } 186 | } 187 | 188 | void KeyScan::NotifyOutput(const std::vector& pressed_keycode) { 189 | for (auto output : *keyboard_output_) { 190 | output->SendKeycode(pressed_keycode); 191 | } 192 | } 193 | 194 | void KeyScan::LayerChanged() { 195 | for (auto output : *keyboard_output_) { 196 | output->ChangeActiveLayers(active_layers_); 197 | } 198 | } 199 | 200 | Status RegisterKeyscan(uint8_t tag) { 201 | return DeviceRegistry::RegisterInputDevice( 202 | tag, []() { return std::shared_ptr(new KeyScan()); }); 203 | } 204 | -------------------------------------------------------------------------------- /FreeRTOSConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * FreeRTOS V202107.00 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | * the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | * 22 | * http://www.FreeRTOS.org 23 | * http://aws.amazon.com/freertos 24 | * 25 | * 1 tab == 4 spaces! 26 | */ 27 | 28 | #ifndef FREERTOS_CONFIG_H 29 | #define FREERTOS_CONFIG_H 30 | 31 | /*----------------------------------------------------------- 32 | * Application specific definitions. 33 | * 34 | * These definitions should be adjusted for your particular hardware and 35 | * application requirements. 36 | * 37 | * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE 38 | * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. 39 | * 40 | * See http://www.freertos.org/a00110.html 41 | *----------------------------------------------------------*/ 42 | 43 | /* Scheduler Related */ 44 | #define configUSE_PREEMPTION 1 45 | #define configUSE_TICKLESS_IDLE 0 46 | #define configUSE_IDLE_HOOK 0 47 | #define configUSE_TICK_HOOK 1 48 | #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) 49 | #define configMAX_PRIORITIES 32 50 | #define configMINIMAL_STACK_SIZE ( configSTACK_DEPTH_TYPE ) 256 51 | #define configUSE_16_BIT_TICKS 0 52 | 53 | #define configIDLE_SHOULD_YIELD 1 54 | 55 | /* Synchronization Related */ 56 | #define configUSE_MUTEXES 1 57 | #define configUSE_RECURSIVE_MUTEXES 1 58 | #define configUSE_APPLICATION_TASK_TAG 0 59 | #define configUSE_COUNTING_SEMAPHORES 1 60 | #define configQUEUE_REGISTRY_SIZE 8 61 | #define configUSE_QUEUE_SETS 1 62 | #define configUSE_TIME_SLICING 1 63 | #define configUSE_NEWLIB_REENTRANT 0 64 | #define configENABLE_BACKWARD_COMPATIBILITY 0 65 | #define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 66 | 67 | /* System */ 68 | #define configSTACK_DEPTH_TYPE uint32_t 69 | #define configMESSAGE_BUFFER_LENGTH_TYPE size_t 70 | 71 | /* Memory allocation related definitions. */ 72 | #define configSUPPORT_STATIC_ALLOCATION 0 73 | #define configSUPPORT_DYNAMIC_ALLOCATION 1 74 | // #define configTOTAL_HEAP_SIZE (128*1024) 75 | #define configTOTAL_HEAP_SIZE (64*1024) 76 | #define configAPPLICATION_ALLOCATED_HEAP 0 77 | 78 | /* Hook function related definitions. */ 79 | #define configCHECK_FOR_STACK_OVERFLOW 2 80 | #define configUSE_MALLOC_FAILED_HOOK 1 81 | #define configUSE_DAEMON_TASK_STARTUP_HOOK 0 82 | 83 | /* Run time and task stats gathering related definitions. */ 84 | #define configGENERATE_RUN_TIME_STATS 0 85 | #define configUSE_TRACE_FACILITY 1 86 | #define configUSE_STATS_FORMATTING_FUNCTIONS 0 87 | 88 | /* Co-routine related definitions. */ 89 | #define configUSE_CO_ROUTINES 0 90 | #define configMAX_CO_ROUTINE_PRIORITIES 1 91 | 92 | /* Software timer related definitions. */ 93 | #define configUSE_TIMERS 1 94 | #define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) 95 | #define configTIMER_QUEUE_LENGTH 10 96 | #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE) 97 | 98 | /* Interrupt nesting behaviour configuration. */ 99 | /* 100 | #define configKERNEL_INTERRUPT_PRIORITY [dependent of processor] 101 | #define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and application] 102 | #define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on processor and application] 103 | */ 104 | 105 | /* SMP port only */ 106 | #define configNUM_CORES 2 107 | #define configTICK_CORE 0 108 | #define configRUN_MULTIPLE_PRIORITIES 1 109 | #define configUSE_CORE_AFFINITY 1 110 | #define configNUMBER_OF_CORES 2 111 | #define configUSE_PASSIVE_IDLE_HOOK 0 112 | 113 | /* RP2040 specific */ 114 | #define configSUPPORT_PICO_SYNC_INTEROP 1 115 | #define configSUPPORT_PICO_TIME_INTEROP 1 116 | 117 | #include 118 | /* Define to trap errors during development. */ 119 | #define configASSERT(x) assert(x) 120 | 121 | /* Set the following definitions to 1 to include the API function, or zero 122 | to exclude the API function. */ 123 | #define INCLUDE_vTaskPrioritySet 1 124 | #define INCLUDE_uxTaskPriorityGet 1 125 | #define INCLUDE_vTaskDelete 1 126 | #define INCLUDE_vTaskSuspend 1 127 | #define INCLUDE_vTaskDelayUntil 1 128 | #define INCLUDE_vTaskDelay 1 129 | #define INCLUDE_xTaskGetSchedulerState 1 130 | #define INCLUDE_xTaskGetCurrentTaskHandle 1 131 | #define INCLUDE_uxTaskGetStackHighWaterMark 1 132 | #define INCLUDE_xTaskGetIdleTaskHandle 1 133 | #define INCLUDE_eTaskGetState 1 134 | #define INCLUDE_xTimerPendFunctionCall 1 135 | #define INCLUDE_xTaskAbortDelay 1 136 | #define INCLUDE_xTaskGetHandle 1 137 | #define INCLUDE_xTaskResumeFromISR 1 138 | #define INCLUDE_xQueueGetMutexHolder 1 139 | 140 | /* A header file that defines trace macro can be included here. */ 141 | 142 | #endif /* FREERTOS_CONFIG_H */ 143 | 144 | -------------------------------------------------------------------------------- /configs/default/layout.cc: -------------------------------------------------------------------------------- 1 | // layout_helper.h defines the helper macros as well as short alias to each 2 | // keycode. Please include it at the top of the layout.cc file. 3 | #include "layout_helper.h" 4 | 5 | // Alias for the key matrix GPIO pins. This is for better readability and is 6 | // optional. 7 | 8 | #define C0 0 9 | #define C1 1 10 | #define C2 2 11 | #define C3 3 12 | #define C4 4 13 | #define C5 5 14 | #define C6 6 15 | #define C7 7 16 | #define C8 8 17 | #define C9 9 18 | #define C10 10 19 | #define C11 11 20 | #define C12 13 21 | #define C13 12 22 | #define R0 14 23 | #define R1 15 24 | #define R2 18 25 | #define R3 17 26 | #define R4 16 27 | 28 | // These two are also optional 29 | 30 | #define CONFIG_NUM_PHY_ROWS 6 31 | #define CONFIG_NUM_PHY_COLS 15 32 | 33 | // This is a special layer that rotary encoder might have different behaviors. 34 | // Also optional. 35 | 36 | #define ALT_LY 4 37 | 38 | // For a layout.cc file, the followings are required: kGPIOMatrix, and 39 | // kKeyCodes. They need to have exactly the same shape and type. They also need 40 | // to be constexpr. For array types you don't need to specify the size for all 41 | // the dimenions as long as compiler is happy. See docs/layout_cc.md for an 42 | // example key matrix setup. 43 | 44 | // clang-format off 45 | 46 | // Keyboard switch physical GPIO connection setup. This is a map from the 47 | // physical layout of the keys to their switch matrix. The reason for having 48 | // this mapping is that often times the physical layout of the switches does not 49 | // match up with their wiring matrix. For each switch, it specifies the 50 | // direction of scanning. 51 | static constexpr GPIO kGPIOMatrix[CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 52 | {G(C0, R0), G(C1, R0), G(C2, R0), G(C3, R0), G(C4, R0), G(C5, R0), G(C6, R0), G(C7, R0), G(C8, R0), G(C9, R0), G(C10, R0), G(C11, R0), G(C12, R0), G(C13, R0), G(C13, R1)}, 53 | {G(C0, R1), G(C1, R1), G(C2, R1), G(C3, R1), G(C4, R1), G(C5, R1), G(C6, R1), G(C7, R1), G(C8, R1), G(C9, R1), G(C10, R1), G(C11, R1), G(C12, R1), G(C13, R2), G(C13, R3)}, 54 | {G(C0, R2), G(C1, R2), G(C2, R2), G(C3, R2), G(C4, R2), G(C5, R2), G(C6, R2), G(C7, R2), G(C8, R2), G(C9, R2), G(C10, R2), G(C11, R2), G(C12, R2), G(C13, R4)}, 55 | {G(C0, R3), G(C1, R3), G(C2, R3), G(C3, R3), G(C4, R3), G(C5, R3), G(C6, R3), G(C7, R3), G(C8, R3), G(C9, R3), G(C10, R3), G(C11, R3), G(C12, R3)}, 56 | {G(C0, R4), G(C1, R4), G(C2, R4), G(C5, R4), G(C7, R4), G(C8, R4), G(C9, R4), G(C10, R4), G(C11, R4), G(C12, R4)}, 57 | {G(C3, R4), G(C4, R4), G(C6, R4)} 58 | }; 59 | 60 | static constexpr Keycode kKeyCodes[][CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 61 | [0]={ 62 | {K(K_MUTE), K(K_GRAVE), K(K_1), K(K_2), K(K_3), K(K_4), K(K_5), K(K_6), K(K_7), K(K_8), K(K_9), K(K_0), K(K_MINUS), K(K_EQUAL), K(K_BACKS)}, 63 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 64 | {K(K_DEL), K(K_CTR_L), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 65 | {K(K_INS), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 66 | {MO(ALT_LY), K(K_GUI_L), K(K_ALT_L), K(K_SPACE), MO(1), MO(2), K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 67 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 68 | }, 69 | [1]={ 70 | {K(K_MUTE), K(K_GRAVE), K(K_F1), K(K_F2), K(K_F3), K(K_F4), K(K_F5), K(K_F6), K(K_F7), K(K_F8), K(K_F9), K(K_F10), K(K_F11), K(K_F12), K(K_BACKS)}, 71 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 72 | {K(K_DEL), K(K_CAPS), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 73 | {MO(3), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 74 | {CONFIG, TG(2), K(K_ALT_L), K(K_SPACE), ______, ______, K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 75 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 76 | }, 77 | [2]={ 78 | {CK(CONFIG_SEL)}, 79 | {______}, 80 | {CK(REBOOT)}, 81 | }, 82 | [3]={ 83 | {______}, 84 | {CK(BOOTSEL)}, 85 | }, 86 | [ALT_LY]={}, 87 | }; 88 | 89 | // clang-format on 90 | 91 | // Compile time validation and conversion for the key matrix. Must include this. 92 | #include "layout_internal.inc" 93 | 94 | // Create the screen display. The content displayed on the screen is created 95 | // with the help of mixins. See display_mixins.h for the builtin mixins. Each 96 | // mixin displays a specific functionality at a specific region. 97 | 98 | class Screen : public virtual SSD1306Display, 99 | public virtual ActiveLayersDisplayMixin<> { 100 | public: 101 | Screen() : SSD1306Display(i2c0, 20, 21, 0x3c, SSD1306Display::R_64, true) {} 102 | 103 | static Status RegisterScreen(uint8_t key) { 104 | std::shared_ptr instance = std::make_shared(); 105 | if (ActiveLayersDisplayMixin<>::Register(key, /*slow=*/true, instance) != 106 | OK || 107 | SSD1306Display::Register(key, /*slow=*/true, instance) != OK) { 108 | return ERROR; 109 | } 110 | return OK; 111 | } 112 | }; 113 | 114 | // Register all the devices 115 | 116 | // Each device is registered with a unique tag. 117 | enum { 118 | JOYSTICK = 0, 119 | KEYSCAN, 120 | ENCODER, 121 | SSD1306, 122 | USB_KEYBOARD, 123 | USB_MOUSE, 124 | USB_INPUT, 125 | TEMPERATURE, 126 | LED, 127 | }; 128 | 129 | // The return values have to be retained and static for the registration code to 130 | // execute at initialization time. See each device's documentation for what 131 | // values are needed for the registration function. 132 | 133 | static Status register1 = RegisterConfigModifier(SSD1306); 134 | static Status register2 = 135 | RegisterJoystick(JOYSTICK, 28, 27, 5, false, false, true, ALT_LY); 136 | static Status register3 = RegisterKeyscan(KEYSCAN); 137 | static Status register4 = RegisterEncoder(ENCODER, 19, 22, 2); 138 | static Status register5 = Screen::RegisterScreen(SSD1306); 139 | static Status register6 = RegisterUSBKeyboardOutput(USB_KEYBOARD); 140 | static Status register7 = RegisterUSBMouseOutput(USB_MOUSE); 141 | static Status register8 = RegisterUSBInput(USB_INPUT); 142 | static Status register9 = RegisterTemperatureInput(TEMPERATURE); 143 | static Status register10 = RegisterWS2812(LED, 26, 17); 144 | -------------------------------------------------------------------------------- /layout_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYOUT_HELPER_H_ 2 | #define LAYOUT_HELPER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "class/hid/hid.h" 8 | #include "config.h" 9 | #include "config_modifier.h" 10 | #include "display_mixins.h" 11 | #include "ibp.h" 12 | #include "joystick.h" 13 | #include "keyscan.h" 14 | #include "layout.h" 15 | #include "pico/platform.h" 16 | #include "rotary_encoder.h" 17 | #include "spi.h" 18 | #include "ssd1306.h" 19 | #include "temperature.h" 20 | #include "usb.h" 21 | #include "utils.h" 22 | #include "ws2812.h" 23 | 24 | // Macro to define keys that sends the USB HID keycode (aliased below in this 25 | // file) directly 26 | #define K(KEYCODE) \ 27 | { .keycode = (KEYCODE), .is_custom = false, .custom_info = 0 } 28 | 29 | // Macro to define a custom keycode. 30 | #define CK(KEYCODE) \ 31 | { .keycode = (KEYCODE), .is_custom = true, .custom_info = 0 } 32 | 33 | // Macro for key that doesn't have any effect. For layouts with multiple layers, 34 | // the key scan will fall through these empty keys 35 | #define ______ \ 36 | { .keycode = (HID_KEY_NONE), .is_custom = false, .custom_info = 0 } 37 | 38 | // Temporarily activate the layer. Deactive once released. 39 | #define MO(LAYER) \ 40 | { \ 41 | .keycode = (LAYER_SWITCH), .is_custom = true, \ 42 | .custom_info = ((LAYER)&0x3f) \ 43 | } 44 | 45 | // Toggle layer activation. 46 | #define TG(LAYER) \ 47 | { \ 48 | .keycode = (LAYER_SWITCH), .is_custom = true, \ 49 | .custom_info = (((LAYER)&0x3f) | 0x40) \ 50 | } 51 | 52 | // Macro to define GPIO wiring for each key switch 53 | #define G(SOURCE, SINK) \ 54 | { .source = (SOURCE), .sink = (SINK) } 55 | 56 | // A special custom key that enters config menu 57 | #define CONFIG CK(ENTER_CONFIG) 58 | 59 | // clang-format off 60 | 61 | // Alias with shorter names. Each name should be no more than 7 characters long. 62 | 63 | #define K_NONE HID_KEY_NONE 64 | #define K_A HID_KEY_A 65 | #define K_B HID_KEY_B 66 | #define K_C HID_KEY_C 67 | #define K_D HID_KEY_D 68 | #define K_E HID_KEY_E 69 | #define K_F HID_KEY_F 70 | #define K_G HID_KEY_G 71 | #define K_H HID_KEY_H 72 | #define K_I HID_KEY_I 73 | #define K_J HID_KEY_J 74 | #define K_K HID_KEY_K 75 | #define K_L HID_KEY_L 76 | #define K_M HID_KEY_M 77 | #define K_N HID_KEY_N 78 | #define K_O HID_KEY_O 79 | #define K_P HID_KEY_P 80 | #define K_Q HID_KEY_Q 81 | #define K_R HID_KEY_R 82 | #define K_S HID_KEY_S 83 | #define K_T HID_KEY_T 84 | #define K_U HID_KEY_U 85 | #define K_V HID_KEY_V 86 | #define K_W HID_KEY_W 87 | #define K_X HID_KEY_X 88 | #define K_Y HID_KEY_Y 89 | #define K_Z HID_KEY_Z 90 | #define K_1 HID_KEY_1 91 | #define K_2 HID_KEY_2 92 | #define K_3 HID_KEY_3 93 | #define K_4 HID_KEY_4 94 | #define K_5 HID_KEY_5 95 | #define K_6 HID_KEY_6 96 | #define K_7 HID_KEY_7 97 | #define K_8 HID_KEY_8 98 | #define K_9 HID_KEY_9 99 | #define K_0 HID_KEY_0 100 | #define K_ENTER HID_KEY_ENTER 101 | #define K_ESC HID_KEY_ESCAPE 102 | #define K_BACKS HID_KEY_BACKSPACE 103 | #define K_TAB HID_KEY_TAB 104 | #define K_SPACE HID_KEY_SPACE 105 | #define K_MINUS HID_KEY_MINUS 106 | #define K_EQUAL HID_KEY_EQUAL 107 | #define K_BRKTL HID_KEY_BRACKET_LEFT 108 | #define K_BRKTR HID_KEY_BRACKET_RIGHT 109 | #define K_BKSL HID_KEY_BACKSLASH 110 | #define K_EU_1 HID_KEY_EUROPE_1 111 | #define K_SEMIC HID_KEY_SEMICOLON 112 | #define K_APST HID_KEY_APOSTROPHE 113 | #define K_GRAVE HID_KEY_GRAVE 114 | #define K_COMMA HID_KEY_COMMA 115 | #define K_PERID HID_KEY_PERIOD 116 | #define K_SLASH HID_KEY_SLASH 117 | #define K_CAPS HID_KEY_CAPS_LOCK 118 | #define K_F1 HID_KEY_F1 119 | #define K_F2 HID_KEY_F2 120 | #define K_F3 HID_KEY_F3 121 | #define K_F4 HID_KEY_F4 122 | #define K_F5 HID_KEY_F5 123 | #define K_F6 HID_KEY_F6 124 | #define K_F7 HID_KEY_F7 125 | #define K_F8 HID_KEY_F8 126 | #define K_F9 HID_KEY_F9 127 | #define K_F10 HID_KEY_F10 128 | #define K_F11 HID_KEY_F11 129 | #define K_F12 HID_KEY_F12 130 | #define K_PRTSC HID_KEY_PRINT_SCREEN 131 | #define K_SCRLK HID_KEY_SCROLL_LOCK 132 | #define K_PAUSE HID_KEY_PAUSE 133 | #define K_INS HID_KEY_INSERT 134 | #define K_HOME HID_KEY_HOME 135 | #define K_PAGEU HID_KEY_PAGE_UP 136 | #define K_DEL HID_KEY_DELETE 137 | #define K_END HID_KEY_END 138 | #define K_PAGED HID_KEY_PAGE_DOWN 139 | #define K_ARR_R HID_KEY_ARROW_RIGHT 140 | #define K_ARR_L HID_KEY_ARROW_LEFT 141 | #define K_ARR_D HID_KEY_ARROW_DOWN 142 | #define K_ARR_U HID_KEY_ARROW_UP 143 | #define K_NUM_L HID_KEY_NUM_LOCK 144 | #define K_EU_2 HID_KEY_EUROPE_2 145 | #define K_APP HID_KEY_APPLICATION 146 | #define K_POWER HID_KEY_POWER 147 | #define K_F13 HID_KEY_F13 148 | #define K_F14 HID_KEY_F14 149 | #define K_F15 HID_KEY_F15 150 | #define K_F16 HID_KEY_F16 151 | #define K_F17 HID_KEY_F17 152 | #define K_F18 HID_KEY_F18 153 | #define K_F19 HID_KEY_F19 154 | #define K_F20 HID_KEY_F20 155 | #define K_F21 HID_KEY_F21 156 | #define K_F22 HID_KEY_F22 157 | #define K_F23 HID_KEY_F23 158 | #define K_F24 HID_KEY_F24 159 | #define K_EXE HID_KEY_EXECUTE 160 | #define K_HELP HID_KEY_HELP 161 | #define K_MENU HID_KEY_MENU 162 | #define K_SEL HID_KEY_SELECT 163 | #define K_STOP HID_KEY_STOP 164 | #define K_AGAIN HID_KEY_AGAIN 165 | #define K_UNDO HID_KEY_UNDO 166 | #define K_CUT HID_KEY_CUT 167 | #define K_COPY HID_KEY_COPY 168 | #define K_PASTE HID_KEY_PASTE 169 | #define K_FIND HID_KEY_FIND 170 | #define K_MUTE HID_KEY_MUTE 171 | #define K_VOL_U HID_KEY_VOLUME_UP 172 | #define K_VOL_D HID_KEY_VOLUME_DOWN 173 | #define K_CANCL HID_KEY_CANCEL 174 | #define K_CLEAR HID_KEY_CLEAR 175 | #define K_PRIOR HID_KEY_PRIOR 176 | #define K_RE HID_KEY_RETURN 177 | #define K_SEP HID_KEY_SEPARATOR 178 | #define K_OUT HID_KEY_OUT 179 | #define K_OPER HID_KEY_OPER 180 | #define K_CTR_L HID_KEY_CONTROL_LEFT 181 | #define K_SFT_L HID_KEY_SHIFT_LEFT 182 | #define K_ALT_L HID_KEY_ALT_LEFT 183 | #define K_GUI_L HID_KEY_GUI_LEFT 184 | #define K_CTR_R HID_KEY_CONTROL_RIGHT 185 | #define K_SFT_R HID_KEY_SHIFT_RIGHT 186 | #define K_ALT_R HID_KEY_ALT_RIGHT 187 | #define K_GUI_R HID_KEY_GUI_RIGHT 188 | 189 | // clang-format on 190 | 191 | #endif /* LAYOUT_HELPER_H_ */ -------------------------------------------------------------------------------- /ibp.cc: -------------------------------------------------------------------------------- 1 | #include "ibp.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "layout.h" 8 | #include "tusb.h" 9 | #include "utils.h" 10 | 11 | extern "C" { 12 | #include "ibp_lib.h" 13 | } 14 | 15 | IBPDeviceBase::IBPDeviceBase() : is_config_mode_(false), has_update_({0}) { 16 | packet_semaphore_ = xSemaphoreCreateBinary(); 17 | xSemaphoreGive(packet_semaphore_); 18 | } 19 | 20 | void IBPDeviceBase::SendKeycode(uint8_t keycode) { 21 | if (is_config_mode_) { 22 | return; 23 | } 24 | auto& keycodes = segments_[IBP_KEYCODE].field_data.keycodes; 25 | if (keycode >= HID_KEY_CONTROL_LEFT && keycode <= HID_KEY_GUI_RIGHT) { 26 | keycodes.modifier_bitmask |= (1 << (keycode - HID_KEY_CONTROL_LEFT)); 27 | } else if (keycodes.num_keycodes < IBP_MAX_KEYCODES) { 28 | keycodes.keycodes[keycodes.num_keycodes++] = keycode; 29 | } 30 | has_update_[IBP_KEYCODE] = true; 31 | } 32 | 33 | void IBPDeviceBase::SendKeycode(const std::vector& keycodes) { 34 | for (uint8_t keycode : keycodes) { 35 | SendKeycode(keycode); 36 | } 37 | } 38 | void IBPDeviceBase::SendConsumerKeycode(uint16_t keycode) { 39 | if (is_config_mode_) { 40 | return; 41 | } 42 | segments_[IBP_CONSUMER].field_data.consumer_keycode.consumer_keycode = 43 | keycode; 44 | has_update_[IBP_CONSUMER] = true; 45 | } 46 | 47 | void IBPDeviceBase::ChangeActiveLayers(const std::vector& layers) { 48 | if (is_config_mode_) { 49 | return; 50 | } 51 | auto& output_layers = segments_[IBP_ACTIVE_LAYERS].field_data.layers; 52 | for (size_t i = 0; i < layers.size(); ++i) { 53 | if (layers[i]) { 54 | output_layers.active_layers[output_layers.num_activated_layers++] = i; 55 | } 56 | if (output_layers.num_activated_layers >= IBP_MAX_ACTIVELAYERS) { 57 | break; 58 | } 59 | } 60 | has_update_[IBP_ACTIVE_LAYERS] = true; 61 | } 62 | 63 | void IBPDeviceBase::MouseKeycode(uint8_t keycode) { 64 | if (keycode > MSE_FORWARD || is_config_mode_) { 65 | return; 66 | } 67 | segments_[IBP_MOUSE].field_data.mouse.button_bitmask |= (1 << keycode); 68 | has_update_[IBP_MOUSE] = true; 69 | } 70 | 71 | void IBPDeviceBase::MouseMovement(int8_t x, int8_t y) { 72 | if (is_config_mode_) { 73 | return; 74 | } 75 | auto& mouse = segments_[IBP_MOUSE].field_data.mouse; 76 | mouse.x = x; 77 | mouse.y = y; 78 | has_update_[IBP_MOUSE] = true; 79 | } 80 | 81 | void IBPDeviceBase::Pan(int8_t x, int8_t y) { 82 | if (is_config_mode_) { 83 | return; 84 | } 85 | auto& mouse = segments_[IBP_MOUSE].field_data.mouse; 86 | mouse.horizontal = x; 87 | mouse.vertical = y; 88 | has_update_[IBP_MOUSE] = true; 89 | } 90 | 91 | void IBPDeviceBase::StartOfInputTick() { 92 | memset(has_update_, false, sizeof(has_update_)); 93 | memset(segments_, 0, sizeof(segments_)); 94 | } 95 | 96 | constexpr size_t kOutputBufferSize = 128; 97 | 98 | void IBPDeviceBase::FinalizeInputTickOutput() { 99 | IBPSegment segments[IBP_TOTAL]; 100 | size_t num_segments = 0; 101 | for (size_t i = 0; i < IBP_TOTAL; ++i) { 102 | if (has_update_[i]) { 103 | segments[num_segments++] = segments_[i]; 104 | } 105 | } 106 | uint8_t buffer[kOutputBufferSize]; 107 | const int num_bytes = 108 | SerializeSegments(segments, num_segments, buffer, sizeof(buffer)); 109 | if (num_bytes <= 0) { 110 | LOG_ERROR("Failed to serialize segments"); 111 | return; 112 | } 113 | LockSemaphore lock(packet_semaphore_); 114 | outbound_packet_ = std::string(buffer, buffer + num_bytes); 115 | } 116 | 117 | void IBPDeviceBase::InputTick() { 118 | std::string local_copy; 119 | { 120 | LockSemaphore lock(packet_semaphore_); 121 | std::swap(local_copy, inbound_packet_); 122 | } 123 | const size_t total_bytes = local_copy.size(); 124 | const uint8_t* bytes = reinterpret_cast(local_copy.c_str()); 125 | size_t offset = 1; 126 | while (offset < total_bytes) { 127 | IBPSegment segment; 128 | int bytes_consumed = 129 | DeSerializeSegment(bytes + offset, total_bytes - offset, &segment); 130 | if (bytes_consumed <= 0) { 131 | break; 132 | } 133 | switch (segment.field_type) { 134 | case IBP_KEYCODE: { 135 | const auto& ibp_keycodes = segment.field_data.keycodes; 136 | std::vector keycodes( 137 | ibp_keycodes.keycodes, 138 | ibp_keycodes.keycodes + ibp_keycodes.num_keycodes); 139 | uint8_t bitmask = ibp_keycodes.modifier_bitmask; 140 | for (size_t i = 0; i < 8; ++i) { 141 | if (bitmask & 0x01) { 142 | keycodes.push_back(HID_KEY_CONTROL_LEFT + i); 143 | } 144 | bitmask >>= 1; 145 | } 146 | for (auto& keyboard_output : *keyboard_output_) { 147 | if (keyboard_output.get() == this) { 148 | continue; 149 | } 150 | keyboard_output->SendKeycode(keycodes); 151 | } 152 | break; 153 | } 154 | case IBP_CONSUMER: { 155 | const auto& ibp_consumer = segment.field_data.consumer_keycode; 156 | for (auto& keyboard_output : *keyboard_output_) { 157 | if (keyboard_output.get() == this) { 158 | continue; 159 | } 160 | keyboard_output->SendConsumerKeycode(ibp_consumer.consumer_keycode); 161 | } 162 | break; 163 | } 164 | case IBP_MOUSE: { 165 | const auto& ibp_mouse = segment.field_data.mouse; 166 | std::vector mouse_keycodes; 167 | uint8_t bitmask = ibp_mouse.button_bitmask; 168 | for (size_t i = MSE_L; i <= MSE_FORWARD; ++i) { 169 | if (bitmask & 0x01) { 170 | mouse_keycodes.push_back(i); 171 | } 172 | bitmask >>= 1; 173 | } 174 | for (auto& mouse_output : *mouse_output_) { 175 | if (mouse_output.get() == this) { 176 | continue; 177 | } 178 | for (auto k : mouse_keycodes) { 179 | mouse_output->MouseKeycode(k); 180 | } 181 | mouse_output->MouseMovement(ibp_mouse.x, ibp_mouse.y); 182 | mouse_output->Pan(ibp_mouse.horizontal, ibp_mouse.vertical); 183 | } 184 | break; 185 | } 186 | // TODO: pass the layers in the future. 187 | default: 188 | break; 189 | } 190 | } 191 | } 192 | 193 | std::string IBPDeviceBase::GetOutPacket() { 194 | uint8_t buffer[kOutputBufferSize]; 195 | const int num_bytes = 196 | SerializeSegments(segments_, /*num_segments=*/0, buffer, sizeof(buffer)); 197 | if (num_bytes <= 0) { 198 | LOG_ERROR("Create empty packet shouldn't fail."); 199 | return ""; 200 | } 201 | std::string packet_copy(buffer, buffer + num_bytes); 202 | { 203 | LockSemaphore lock(packet_semaphore_); 204 | std::swap(packet_copy, outbound_packet_); 205 | } 206 | return packet_copy; 207 | } 208 | 209 | void IBPDeviceBase::SetInPacket(const std::string& packet) { 210 | LockSemaphore lock(packet_semaphore_); 211 | inbound_packet_ = packet; 212 | } 213 | -------------------------------------------------------------------------------- /ssd1306.cc: -------------------------------------------------------------------------------- 1 | #include "ssd1306.h" 2 | 3 | #include "hardware/gpio.h" 4 | #include "hardware/i2c.h" 5 | #include "hardware/timer.h" 6 | #include "pico-ssd1306/shapeRenderer/ShapeRenderer.h" 7 | #include "pico-ssd1306/textRenderer/12x16_font.h" 8 | #include "pico-ssd1306/textRenderer/16x32_font.h" 9 | #include "pico-ssd1306/textRenderer/5x8_font.h" 10 | #include "pico-ssd1306/textRenderer/8x8_font.h" 11 | #include "pico-ssd1306/textRenderer/TextRenderer.h" 12 | 13 | using pico_ssd1306::Size; 14 | using pico_ssd1306::SSD1306; 15 | using pico_ssd1306::WriteMode; 16 | 17 | SSD1306Display::SSD1306Display(i2c_inst_t* i2c, uint8_t sda_pin, 18 | uint8_t scl_pin, uint8_t i2c_addr, 19 | NumRows num_rows, bool flip) 20 | : i2c_(i2c), 21 | sda_pin_(sda_pin), 22 | scl_pin_(scl_pin), 23 | i2c_addr_(i2c_addr), 24 | num_rows_(num_rows), 25 | num_cols_(128), 26 | sleep_s_(0), 27 | buffer_changed_(false), 28 | send_buffer_(true), 29 | last_active_s_(0), 30 | sleep_(false), 31 | config_mode_(false) { 32 | i2c_init(i2c_, 400 * 1000); 33 | gpio_set_function(sda_pin_, GPIO_FUNC_I2C); 34 | gpio_set_function(scl_pin_, GPIO_FUNC_I2C); 35 | gpio_pull_up(sda_pin_); 36 | gpio_pull_up(scl_pin_); 37 | 38 | busy_wait_ms(250); 39 | 40 | std::fill(buffer_.begin(), buffer_.end(), 0); 41 | std::fill(out_buffer_.begin(), out_buffer_.end(), 0); 42 | display_ = std::make_unique( 43 | i2c_, i2c_addr_, num_rows_ == 64 ? Size::W128xH64 : Size::W128xH32); 44 | 45 | // Use a buffer we can control. 46 | display_->setBuffer(buffer_.data()); 47 | if (flip) { 48 | display_->setOrientation(0); 49 | } 50 | 51 | semaphore_ = xSemaphoreCreateBinary(); 52 | xSemaphoreGive(semaphore_); 53 | 54 | busy_wait_ms(250); 55 | 56 | last_active_s_ = time_us_64() / 1000000; 57 | } 58 | 59 | std::pair> 60 | SSD1306Display::CreateDefaultConfig() { 61 | auto config = CONFIG_OBJECT( 62 | CONFIG_OBJECT_ELEM("sleep_seconds", CONFIG_INT(20, 0, 300))); 63 | return {"SSD1306 Screen", config}; 64 | } 65 | 66 | void SSD1306Display::OnUpdateConfig(const Config* config) { 67 | if (config->GetType() != Config::OBJECT) { 68 | LOG_ERROR("Root config has to be an object."); 69 | return; 70 | } 71 | const auto& root_map = *((ConfigObject*)config)->GetMembers(); 72 | auto it = root_map.find("sleep_seconds"); 73 | if (it == root_map.end()) { 74 | LOG_ERROR("Can't find `sleep_seconds` in config"); 75 | return; 76 | } 77 | if (it->second->GetType() != Config::INTEGER) { 78 | LOG_ERROR("`sleep_seconds` invalid type"); 79 | return; 80 | } 81 | sleep_s_ = ((ConfigInt*)it->second.get())->GetValue(); 82 | last_active_s_ = time_us_64() / 1000000; 83 | } 84 | 85 | void SSD1306Display::SetConfigMode(bool is_config_mode) { 86 | LockSemaphore lock(semaphore_); 87 | config_mode_ = is_config_mode; 88 | } 89 | 90 | void SSD1306Display::OutputTick() { 91 | // Manually send the buffer to avoid blocking other tasks 92 | 93 | const uint32_t curr_s = time_us_64() / 1000000; 94 | 95 | bool send_buffer = false; 96 | std::array local_copy; 97 | local_copy[0] = pico_ssd1306::SSD1306_STARTLINE; 98 | { 99 | LockSemaphore lock(semaphore_); 100 | if (send_buffer_) { 101 | std::copy(out_buffer_.begin(), out_buffer_.end(), local_copy.begin() + 1); 102 | send_buffer = send_buffer_; 103 | last_active_s_ = curr_s; 104 | } 105 | send_buffer_ = false; 106 | } 107 | 108 | if (!sleep_ && sleep_s_ > 0 && curr_s - last_active_s_ >= sleep_s_) { 109 | sleep_ = true; 110 | CMD(pico_ssd1306::SSD1306_DISPLAY_OFF); 111 | return; 112 | } 113 | 114 | if (send_buffer) { 115 | if (sleep_) { 116 | sleep_ = false; 117 | CMD(pico_ssd1306::SSD1306_DISPLAY_ON); 118 | } 119 | CMD(pico_ssd1306::SSD1306_PAGEADDR); 120 | CMD(0x00); 121 | CMD(0x07); 122 | CMD(pico_ssd1306::SSD1306_COLUMNADDR); 123 | CMD(0x00); 124 | CMD(127); 125 | 126 | i2c_write_blocking(i2c_, i2c_addr_, local_copy.data(), local_copy.size(), 127 | false); 128 | } 129 | } 130 | 131 | void SSD1306Display::StartOfInputTick() { buffer_changed_ = false; } 132 | 133 | void SSD1306Display::FinalizeInputTickOutput() { 134 | LockSemaphore lock(semaphore_); 135 | if (buffer_changed_) { 136 | std::copy(buffer_.begin(), buffer_.end(), out_buffer_.begin()); 137 | send_buffer_ = true; 138 | } 139 | } 140 | 141 | void SSD1306Display::SetPixel(size_t row, size_t col, Mode mode) { 142 | display_->setPixel(col, row, (WriteMode)mode); 143 | buffer_changed_ = true; 144 | } 145 | 146 | void SSD1306Display::DrawLine(size_t start_row, size_t start_col, 147 | size_t end_row, size_t end_col, Mode mode) { 148 | pico_ssd1306::drawLine(display_.get(), start_col, start_row, end_col, end_row, 149 | (WriteMode)mode); 150 | buffer_changed_ = true; 151 | } 152 | 153 | void SSD1306Display::DrawRect(size_t start_row, size_t start_col, 154 | size_t end_row, size_t end_col, bool fill, 155 | Mode mode) { 156 | if (fill) { 157 | pico_ssd1306::fillRect(display_.get(), start_col, start_row, end_col, 158 | end_row, (WriteMode)mode); 159 | } else { 160 | pico_ssd1306::drawRect(display_.get(), start_col, start_row, end_col, 161 | end_row, (WriteMode)mode); 162 | } 163 | buffer_changed_ = true; 164 | } 165 | 166 | class BuiltinWrapper : public ScreenOutputDevice::CustomFont { 167 | public: 168 | BuiltinWrapper(const uint8_t* buffer) : buffer_(buffer) {} 169 | const uint8_t* GetFont() const override { return buffer_; } 170 | 171 | private: 172 | const uint8_t* const buffer_; 173 | }; 174 | 175 | void SSD1306Display::DrawText(size_t row, size_t col, const std::string& text, 176 | Font font, Mode mode) { 177 | switch (font) { 178 | case F5X8: { 179 | DrawText(row, col, text, BuiltinWrapper(font_5x8), mode); 180 | break; 181 | } 182 | case F8X8: { 183 | DrawText(row, col, text, BuiltinWrapper(font_8x8), mode); 184 | break; 185 | } 186 | case F12X16: { 187 | DrawText(row, col, text, BuiltinWrapper(font_12x16), mode); 188 | break; 189 | } 190 | case F16X32: { 191 | DrawText(row, col, text, BuiltinWrapper(font_16x32), mode); 192 | break; 193 | } 194 | default: 195 | break; 196 | } 197 | } 198 | 199 | void SSD1306Display::DrawText(size_t row, size_t col, const std::string& text, 200 | const CustomFont& font, Mode mode) { 201 | const uint8_t* font_buf = font.GetFont(); 202 | const uint8_t width = font_buf[0]; 203 | size_t col_offset = 0; 204 | for (char c : text) { 205 | pico_ssd1306::drawChar(display_.get(), font_buf, c, col + col_offset, row, 206 | (WriteMode)mode); 207 | col_offset += width; 208 | } 209 | buffer_changed_ = true; 210 | } 211 | 212 | void SSD1306Display::CMD(uint8_t cmd) { 213 | uint8_t data[2] = {0x00, cmd}; 214 | i2c_write_blocking(i2c_, i2c_addr_, data, 2, false); 215 | } 216 | 217 | Status SSD1306Display::Register(uint8_t key, bool slow, 218 | const std::shared_ptr ptr) { 219 | return DeviceRegistry::RegisterScreenOutputDevice(key, true, 220 | [=]() { return ptr; }); 221 | } 222 | -------------------------------------------------------------------------------- /configs/examples/custom_keycode/layout.cc: -------------------------------------------------------------------------------- 1 | // layout_helper.h defines the helper macros as well as short alias to each 2 | // keycode. Please include it at the top of the layout.cc file. 3 | #include "layout_helper.h" 4 | 5 | // Alias for the key matrix GPIO pins. This is for better readability and is 6 | // optional. 7 | 8 | #define C0 0 9 | #define C1 1 10 | #define C2 2 11 | #define C3 3 12 | #define C4 4 13 | #define C5 5 14 | #define C6 6 15 | #define C7 7 16 | #define C8 8 17 | #define C9 9 18 | #define C10 10 19 | #define C11 11 20 | #define C12 13 21 | #define C13 12 22 | #define R0 14 23 | #define R1 15 24 | #define R2 18 25 | #define R3 17 26 | #define R4 16 27 | 28 | // These two are also optional 29 | 30 | #define CONFIG_NUM_PHY_ROWS 6 31 | #define CONFIG_NUM_PHY_COLS 15 32 | 33 | // This is a special layer that rotary encoder might have different behaviors. 34 | // Also optional. 35 | 36 | #define ALT_LY 4 37 | 38 | // For a layout.cc file, the followings are required: kGPIOMatrix, and 39 | // kKeyCodes. They need to have exactly the same name and type. They also need 40 | // to be constexpr. For array types you don't need to specify the size for all 41 | // the dimenions as long as compiler is happy. See docs/layout_cc.md for an 42 | // example key matrix setup. 43 | 44 | // Custom keycodes 45 | enum { 46 | UP = 47 | TOTAL_BUILT_IN_KC, // Avoid conflicting with the built-in custom keycodes 48 | DOWN, 49 | SELECT 50 | }; 51 | 52 | // clang-format off 53 | 54 | // Keyboard switch physical GPIO connection setup. This is a map from the 55 | // physical layout of the keys to their switch matrix. The reason for having 56 | // this mapping is that often times the physical layout of the switches does not 57 | // match up with their wiring matrix. For each switch, it specifies the 58 | // direction of scanning. 59 | static constexpr GPIO kGPIOMatrix[CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 60 | {G(C0, R0), G(C1, R0), G(C2, R0), G(C3, R0), G(C4, R0), G(C5, R0), G(C6, R0), G(C7, R0), G(C8, R0), G(C9, R0), G(C10, R0), G(C11, R0), G(C12, R0), G(C13, R0), G(C13, R1)}, 61 | {G(C0, R1), G(C1, R1), G(C2, R1), G(C3, R1), G(C4, R1), G(C5, R1), G(C6, R1), G(C7, R1), G(C8, R1), G(C9, R1), G(C10, R1), G(C11, R1), G(C12, R1), G(C13, R2), G(C13, R3)}, 62 | {G(C0, R2), G(C1, R2), G(C2, R2), G(C3, R2), G(C4, R2), G(C5, R2), G(C6, R2), G(C7, R2), G(C8, R2), G(C9, R2), G(C10, R2), G(C11, R2), G(C12, R2), G(C13, R4)}, 63 | {G(C0, R3), G(C1, R3), G(C2, R3), G(C3, R3), G(C4, R3), G(C5, R3), G(C6, R3), G(C7, R3), G(C8, R3), G(C9, R3), G(C10, R3), G(C11, R3), G(C12, R3)}, 64 | {G(C0, R4), G(C1, R4), G(C2, R4), G(C5, R4), G(C7, R4), G(C8, R4), G(C9, R4), G(C10, R4), G(C11, R4), G(C12, R4)}, 65 | {G(C3, R4), G(C4, R4), G(C6, R4)} 66 | }; 67 | 68 | static constexpr Keycode kKeyCodes[][CONFIG_NUM_PHY_ROWS][CONFIG_NUM_PHY_COLS] = { 69 | [0]={ 70 | {K(K_MUTE), K(K_GRAVE), K(K_1), K(K_2), K(K_3), K(K_4), K(K_5), K(K_6), K(K_7), K(K_8), K(K_9), K(K_0), K(K_MINUS), K(K_EQUAL), K(K_BACKS)}, 71 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 72 | {K(K_DEL), K(K_CTR_L), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), K(K_ENTER)}, 73 | {K(K_INS), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 74 | {MO(ALT_LY), K(K_GUI_L), K(K_ALT_L), K(K_SPACE), MO(1), MO(2), K(K_ARR_L), K(K_ARR_D), K(K_ARR_U), K(K_ARR_R)}, 75 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 76 | }, 77 | [1]={ 78 | {K(K_MUTE), K(K_GRAVE), K(K_F1), K(K_F2), K(K_F3), K(K_F4), K(K_F5), K(K_F6), K(K_F7), K(K_F8), K(K_F9), K(K_F10), K(K_F11), K(K_F12), K(K_BACKS)}, 79 | {K(K_ESC), K(K_TAB), K(K_Q), K(K_W), K(K_E), K(K_R), K(K_T), K(K_Y), K(K_U), K(K_I), K(K_O), K(K_P), K(K_BRKTL), K(K_BRKTR), K(K_BKSL)}, 80 | {K(K_DEL), K(K_CAPS), K(K_A), K(K_S), K(K_D), K(K_F), K(K_G), K(K_H), K(K_J), K(K_K), K(K_L), K(K_SEMIC), K(K_APST), CK(SELECT)}, 81 | {MO(3), K(K_SFT_L), K(K_Z), K(K_X), K(K_C), K(K_V), K(K_B), K(K_N), K(K_M), K(K_COMMA), K(K_PERID), K(K_SLASH), K(K_SFT_R)}, 82 | {CONFIG, TG(2), K(K_ALT_L), K(K_SPACE), ______, ______, K(K_ARR_L), CK(DOWN), CK(UP), K(K_ARR_R)}, 83 | {CK(MSE_L), CK(MSE_M), CK(MSE_R)} 84 | }, 85 | [2]={ 86 | {CK(CONFIG_SEL)}, 87 | {______}, 88 | {CK(REBOOT)}, 89 | }, 90 | [3]={ 91 | {______}, 92 | {CK(BOOTSEL)}, 93 | }, 94 | [ALT_LY]={}, 95 | }; 96 | 97 | // clang-format on 98 | 99 | // Compile time validation and conversion for the key matrix. Must include this. 100 | #include "layout_internal.inc" 101 | 102 | // Create custom key code handlers. See CustomKeycodeHandler in keyscan.h 103 | 104 | class ConfigUpHandler : public CustomKeycodeHandler { 105 | public: 106 | void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) { 107 | if (is_pressed) { 108 | key_scan_->ConfigUp(); 109 | } 110 | } 111 | 112 | std::string GetName() const override { return "Config Up"; } 113 | }; 114 | 115 | class ConfigDownHandler : public CustomKeycodeHandler { 116 | public: 117 | void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) { 118 | if (is_pressed) { 119 | key_scan_->ConfigDown(); 120 | } 121 | } 122 | 123 | std::string GetName() const override { return "Config Down"; } 124 | }; 125 | 126 | class ConfigSelectHandler : public CustomKeycodeHandler { 127 | public: 128 | void ProcessKeyEvent(Keycode kc, bool is_pressed, size_t key_idx) { 129 | if (is_pressed) { 130 | key_scan_->ConfigSelect(); 131 | } 132 | } 133 | 134 | std::string GetName() const override { return "Config Select"; } 135 | }; 136 | 137 | REGISTER_CUSTOM_KEYCODE_HANDLER(UP, true, ConfigUpHandler); 138 | REGISTER_CUSTOM_KEYCODE_HANDLER(DOWN, true, ConfigDownHandler); 139 | REGISTER_CUSTOM_KEYCODE_HANDLER(SELECT, true, ConfigSelectHandler); 140 | 141 | // Create the screen display 142 | 143 | class Screen : public virtual SSD1306Display, 144 | public virtual ActiveLayersDisplayMixin<> { 145 | public: 146 | Screen() : SSD1306Display(i2c0, 20, 21, 0x3c, SSD1306Display::R_64, true) {} 147 | 148 | static Status RegisterScreen(uint8_t key) { 149 | std::shared_ptr instance = std::make_shared(); 150 | if (ActiveLayersDisplayMixin<>::Register(key, /*slow=*/true, instance) != 151 | OK || 152 | SSD1306Display::Register(key, /*slow=*/true, instance) != OK) { 153 | return ERROR; 154 | } 155 | return OK; 156 | } 157 | }; 158 | 159 | // Register all the devices 160 | 161 | // Each device is registered with a unique tag. 162 | enum { 163 | JOYSTICK = 0, 164 | KEYSCAN, 165 | ENCODER, 166 | SSD1306, 167 | USB_KEYBOARD, 168 | USB_MOUSE, 169 | USB_INPUT, 170 | TEMPERATURE, 171 | LED, 172 | }; 173 | 174 | static Status register1 = RegisterConfigModifier(SSD1306); 175 | static Status register2 = 176 | RegisterJoystick(JOYSTICK, 28, 27, 5, false, false, true, ALT_LY); 177 | static Status register3 = RegisterKeyscan(KEYSCAN); 178 | static Status register4 = RegisterEncoder(ENCODER, 19, 22, 2); 179 | static Status register5 = Screen::RegisterScreen(SSD1306); 180 | static Status register6 = RegisterUSBKeyboardOutput(USB_KEYBOARD); 181 | static Status register7 = RegisterUSBMouseOutput(USB_MOUSE); 182 | static Status register8 = RegisterUSBInput(USB_INPUT); 183 | static Status register9 = RegisterTemperatureInput(TEMPERATURE); 184 | static Status register10 = RegisterWS2812(LED, 26, 17); 185 | -------------------------------------------------------------------------------- /storage.cc: -------------------------------------------------------------------------------- 1 | #include "storage.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "FreeRTOS.h" 8 | #include "config.h" 9 | #include "hardware/flash.h" 10 | #include "hardware/sync.h" 11 | #include "pico/multicore.h" 12 | #include "pico/platform.h" 13 | #include "semphr.h" 14 | #include "sync.h" 15 | 16 | extern "C" { 17 | #include "littlefs/lfs.h" 18 | 19 | static SemaphoreHandle_t __not_in_flash("storage") semaphore; 20 | static lfs_t __not_in_flash("storage") lfs; 21 | 22 | #define FS_OFFSET (PICO_FLASH_SIZE_BYTES - CONFIG_FLASH_FILESYSTEM_SIZE) 23 | 24 | static int read(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, 25 | void* buffer, lfs_size_t size); 26 | static int prog(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, 27 | const void* buffer, lfs_size_t size); 28 | static int erase(const struct lfs_config* c, lfs_block_t block); 29 | static int sync(const struct lfs_config* c); 30 | static int lock(const struct lfs_config* c); 31 | static int unlock(const struct lfs_config* c); 32 | 33 | void failure(const char*) {} 34 | 35 | // Compile time validation 36 | constexpr struct lfs_config CreateLFSConfig() { 37 | if ((CONFIG_FLASH_FILESYSTEM_SIZE) % (FLASH_SECTOR_SIZE) != 0) { 38 | failure("Filesystem size has to be a multiple of FLASH_SECTOR_SIZE"); 39 | } 40 | if ((CONFIG_FLASH_FILESYSTEM_SIZE) > PICO_FLASH_SIZE_BYTES) { 41 | failure("Filesystem is larger than PICO_FLASH_SIZE_BYTES"); 42 | } 43 | 44 | const struct lfs_config cfg = { 45 | // block device operations 46 | .read = read, 47 | .prog = prog, 48 | .erase = erase, 49 | .sync = sync, 50 | 51 | // block device configuration 52 | .read_size = 64, 53 | .prog_size = FLASH_PAGE_SIZE, 54 | .block_size = FLASH_SECTOR_SIZE, 55 | .block_count = CONFIG_FLASH_FILESYSTEM_SIZE / FLASH_SECTOR_SIZE, 56 | .block_cycles = 500, 57 | .cache_size = FLASH_SECTOR_SIZE / 4, 58 | .lookahead_size = 32, 59 | }; 60 | return cfg; 61 | } 62 | 63 | constexpr struct lfs_config __not_in_flash("storage1") 64 | kLFSConfig = CreateLFSConfig(); 65 | 66 | static int read(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, 67 | void* buffer, lfs_size_t size) { 68 | memcpy(buffer, 69 | (uint8_t*)(XIP_NOCACHE_NOALLOC_BASE) + FS_OFFSET + 70 | (block * c->block_size) + off, 71 | size); 72 | return LFS_ERR_OK; 73 | } 74 | 75 | static int __no_inline_not_in_flash_func(prog)(const struct lfs_config* c, 76 | lfs_block_t block, lfs_off_t off, 77 | const void* buffer, 78 | lfs_size_t size) { 79 | const uint32_t irq = save_and_disable_interrupts(); 80 | volatile uint32_t offset = FS_OFFSET + (block * c->block_size) + off; 81 | flash_range_program(FS_OFFSET + (block * c->block_size) + off, 82 | (const uint8_t*)buffer, size); 83 | restore_interrupts(irq); 84 | return LFS_ERR_OK; 85 | } 86 | 87 | static int __no_inline_not_in_flash_func(erase)(const struct lfs_config* c, 88 | lfs_block_t block) { 89 | const uint32_t irq = save_and_disable_interrupts(); 90 | flash_range_erase(FS_OFFSET + (block * c->block_size), c->block_size); 91 | restore_interrupts(irq); 92 | return LFS_ERR_OK; 93 | } 94 | 95 | static int sync(const struct lfs_config* c) { return LFS_ERR_OK; } 96 | 97 | // TODO implement the USB mass storage class 98 | 99 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 100 | void* buffer, uint32_t bufsize) { 101 | return 0; 102 | } 103 | 104 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 105 | uint8_t* buffer, uint32_t bufsize) { 106 | return 0; 107 | } 108 | 109 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], 110 | uint8_t product_id[16], uint8_t product_rev[4]) {} 111 | 112 | bool tud_msc_test_unit_ready_cb(uint8_t lun) { return true; } 113 | 114 | void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, 115 | uint16_t* block_size) {} 116 | 117 | int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, 118 | uint16_t bufsize) { 119 | return 0; 120 | } 121 | } 122 | 123 | Status InitializeStorage() { 124 | semaphore = xSemaphoreCreateBinary(); 125 | xSemaphoreGive(semaphore); 126 | 127 | // Mount the filesystem or format it 128 | int err = lfs_mount(&lfs, &kLFSConfig); 129 | if (err) { 130 | lfs_format(&lfs, &kLFSConfig); 131 | lfs_mount(&lfs, &kLFSConfig); 132 | } 133 | return StartSyncTasks(); 134 | } 135 | 136 | Status WriteStringToFile(const std::string& content, const std::string& name) { 137 | LockSemaphore lock(semaphore); 138 | 139 | // Block the other core to avoid executing flash code when writing to flash 140 | std::unique_ptr blocker; 141 | if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { 142 | blocker = std::make_unique(); 143 | } 144 | 145 | lfs_file_t file; 146 | if (lfs_file_open(&lfs, &file, name.c_str(), LFS_O_RDWR | LFS_O_CREAT) < 0) { 147 | return ERROR; 148 | } 149 | const lfs_ssize_t written = 150 | lfs_file_write(&lfs, &file, content.c_str(), content.size()); 151 | if (written < 0 || written != content.size()) { 152 | return ERROR; 153 | } 154 | if (lfs_file_close(&lfs, &file) < 0) { 155 | return ERROR; 156 | } 157 | return OK; 158 | } 159 | 160 | Status ReadFileContent(const std::string& name, std::string* output) { 161 | LockSemaphore lock(semaphore); 162 | 163 | // Block the other core to avoid executing flash code when writing to flash 164 | std::unique_ptr blocker; 165 | if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { 166 | blocker = std::make_unique(); 167 | } 168 | 169 | lfs_file_t file; 170 | if (lfs_file_open(&lfs, &file, name.c_str(), LFS_O_RDONLY) < 0) { 171 | return ERROR; 172 | } 173 | 174 | const lfs_soff_t file_size = lfs_file_size(&lfs, &file); 175 | if (file_size < 0) { 176 | return ERROR; 177 | } 178 | 179 | std::unique_ptr read_buffer(new uint8_t[file_size]); 180 | 181 | const lfs_ssize_t read_bytes = 182 | lfs_file_read(&lfs, &file, read_buffer.get(), file_size); 183 | if (read_bytes < 0 || read_bytes != file_size) { 184 | return ERROR; 185 | } 186 | *output = std::string((char*)read_buffer.get(), (uint32_t)file_size); 187 | 188 | if (lfs_file_close(&lfs, &file) < 0) { 189 | return ERROR; 190 | } 191 | return OK; 192 | } 193 | 194 | Status GetFileSize(const std::string& name, size_t* output) { 195 | LockSemaphore lock(semaphore); 196 | 197 | // Block the other core to avoid executing flash code when writing to flash 198 | std::unique_ptr blocker; 199 | if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { 200 | blocker = std::make_unique(); 201 | } 202 | 203 | lfs_file_t file; 204 | if (lfs_file_open(&lfs, &file, name.c_str(), LFS_O_RDONLY) < 0) { 205 | return ERROR; 206 | } 207 | 208 | const lfs_soff_t file_size = lfs_file_size(&lfs, &file); 209 | if (file_size < 0) { 210 | return ERROR; 211 | } 212 | *output = file_size; 213 | 214 | return OK; 215 | } 216 | 217 | Status RemoveFile(const std::string& name) { 218 | LockSemaphore lock(semaphore); 219 | 220 | // Block the other core to avoid executing flash code when writing to flash 221 | std::unique_ptr blocker; 222 | if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { 223 | blocker = std::make_unique(); 224 | } 225 | 226 | if (lfs_remove(&lfs, name.c_str()) < 0) { 227 | return ERROR; 228 | } 229 | return OK; 230 | } 231 | -------------------------------------------------------------------------------- /linux/spi_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../ibp_lib.h" 11 | #include "keyboard.h" 12 | #include "mouse.h" 13 | 14 | #define INTERVAL_MS 10 15 | #define BUF_SIZE 132 16 | 17 | // How many bytes to read when waiting for the response header. 18 | #define READ_BYTES_FOR_HEADER 4 19 | 20 | static struct timer_list timer; 21 | static bool timer_initialized = false; 22 | static spinlock_t timer_initialized_lock; 23 | 24 | static struct spi_device *spi_dev = NULL; 25 | static uint8_t *buffer = NULL; 26 | static struct spi_transfer *xs = NULL; 27 | 28 | static int SPIWriteFromBuffer(size_t num_bytes) { 29 | memset(xs, 0, sizeof(xs[0]) * num_bytes); 30 | for (size_t i = 0; i < num_bytes; ++i) { 31 | xs[i].len = 1; 32 | xs[i].tx_buf = &buffer[i]; 33 | xs[i].rx_buf = &buffer[BUF_SIZE - 1]; 34 | xs[i].cs_change = true; 35 | xs[i].delay.value = 0; 36 | xs[i].delay.unit = SPI_DELAY_UNIT_SCK; 37 | xs[i].cs_change_delay.value = 0; 38 | xs[i].cs_change_delay.unit = SPI_DELAY_UNIT_SCK; 39 | xs[i].word_delay.value = 0; 40 | xs[i].word_delay.unit = SPI_DELAY_UNIT_SCK; 41 | } 42 | xs[num_bytes - 1].cs_change = false; 43 | return spi_sync_transfer(spi_dev, xs, num_bytes); 44 | } 45 | 46 | static int SPIReadToBuffer(size_t num_bytes, size_t start_offset) { 47 | memset(xs, 0, sizeof(xs[0]) * num_bytes); 48 | buffer[BUF_SIZE - 1] = 0; 49 | for (size_t i = 0; i < num_bytes; ++i) { 50 | xs[i].len = 1; 51 | xs[i].tx_buf = &buffer[BUF_SIZE - 1]; 52 | xs[i].rx_buf = &buffer[i + start_offset]; 53 | xs[i].cs_change = true; 54 | xs[i].delay.value = 0; 55 | xs[i].delay.unit = SPI_DELAY_UNIT_SCK; 56 | xs[i].cs_change_delay.value = 0; 57 | xs[i].cs_change_delay.unit = SPI_DELAY_UNIT_SCK; 58 | xs[i].word_delay.value = 0; 59 | xs[i].word_delay.unit = SPI_DELAY_UNIT_SCK; 60 | } 61 | xs[num_bytes - 1].cs_change = false; 62 | return spi_sync_transfer(spi_dev, xs, num_bytes); 63 | } 64 | 65 | static void SPIPullFn(struct work_struct *work) { 66 | static_assert(BUF_SIZE > IBP_MAX_PACKET_LEN); 67 | if (spi_dev == NULL || buffer == NULL || xs == NULL) { 68 | goto label2; 69 | } 70 | 71 | IBPSegment segments[IBP_TOTAL]; 72 | 73 | memset(buffer, 0, BUF_SIZE); 74 | memset(segments, 0, sizeof(IBPSegment) * IBP_TOTAL); 75 | 76 | // Right now there's no auto-type so output buffer always has zero segment. 77 | const int8_t output_bytes = 78 | SerializeSegments(segments, /*num_segments=*/0, buffer, BUF_SIZE); 79 | if (output_bytes < 0) { 80 | printk(KERN_WARNING "Failed create output buffer"); 81 | goto label1; 82 | } 83 | 84 | if (SPIWriteFromBuffer(output_bytes) < 0) { 85 | printk(KERN_WARNING "Failed to write"); 86 | goto label1; 87 | } 88 | 89 | // Read some bytes to hopefully include the header if it's missed in the first 90 | // one. 91 | 92 | static_assert(READ_BYTES_FOR_HEADER >= 1 && 93 | READ_BYTES_FOR_HEADER < IBP_MAX_PACKET_LEN); 94 | memset(buffer, 0, BUF_SIZE); 95 | if (SPIReadToBuffer(READ_BYTES_FOR_HEADER, /*start_offset=*/0) < 0) { 96 | printk(KERN_WARNING "Failed to read"); 97 | goto label1; 98 | } 99 | 100 | size_t write_offset = 0; 101 | bool found_header = false; 102 | for (size_t i = 0; i < READ_BYTES_FOR_HEADER; ++i) { 103 | if (buffer[i] != 0 || found_header) { 104 | buffer[write_offset++] = buffer[i]; 105 | found_header = true; 106 | } 107 | } 108 | 109 | const int8_t response_bytes = GetTransactionTotalSize(buffer[0]); 110 | if (response_bytes < 0) { 111 | // Invalid response header. 112 | goto label1; 113 | } 114 | const int8_t remaining_bytes = response_bytes - write_offset; 115 | if (remaining_bytes > 0) { 116 | if (SPIReadToBuffer(remaining_bytes, write_offset)) { 117 | printk(KERN_WARNING "Failed to receive remaining response."); 118 | goto label1; 119 | } 120 | } 121 | 122 | IBPSegment segment; 123 | uint8_t remaining_parse_bytes = response_bytes - 1; 124 | uint8_t *curr_buf = buffer + 1; 125 | int8_t consumed_bytes = 126 | DeSerializeSegment(curr_buf, remaining_parse_bytes, &segment); 127 | while (consumed_bytes > 0) { 128 | if (segment.field_type < IBP_TOTAL) { 129 | segments[segment.field_type] = segment; 130 | } 131 | remaining_parse_bytes -= consumed_bytes; 132 | memset(&segment, 0, sizeof(IBPSegment)); 133 | consumed_bytes = 134 | DeSerializeSegment(curr_buf, remaining_parse_bytes, &segment); 135 | } 136 | 137 | label1: 138 | // We'll update the inputs even when something bad has happened. The input may 139 | // just be empty. 140 | OnNewKeycodes(&segments[IBP_KEYCODE].field_data.keycodes); 141 | OnNewConsumerKeycodes(&segments[IBP_CONSUMER].field_data.consumer_keycode); 142 | 143 | label2: 144 | mod_timer(&timer, jiffies + msecs_to_jiffies(INTERVAL_MS)); 145 | } 146 | 147 | DECLARE_WORK(workqueue, SPIPullFn); 148 | 149 | static void TimerCallback(struct timer_list *data) { 150 | schedule_work(&workqueue); 151 | } 152 | 153 | static int SetupTimer(void) { 154 | spin_lock(&timer_initialized_lock); 155 | if (timer_initialized) { 156 | goto done; 157 | } 158 | timer_setup(&timer, TimerCallback, 0); 159 | mod_timer(&timer, jiffies + msecs_to_jiffies(INTERVAL_MS)); 160 | timer_initialized = true; 161 | done: 162 | spin_unlock(&timer_initialized_lock); 163 | return 0; 164 | } 165 | 166 | static void DeleteTimer(void) { 167 | spin_lock(&timer_initialized_lock); 168 | if (!timer_initialized) { 169 | goto done; 170 | } 171 | cancel_work_sync(&workqueue); 172 | del_timer_sync(&timer); 173 | timer_initialized = false; 174 | done: 175 | spin_unlock(&timer_initialized_lock); 176 | } 177 | 178 | static const struct of_device_id picomk_of_match[] = { 179 | { 180 | .compatible = "picomk,spi_board_v0", 181 | .data = 0, 182 | }, 183 | {/* sentinel */}}; 184 | MODULE_DEVICE_TABLE(of, picomk_of_match); 185 | 186 | static const struct spi_device_id picomk_spi_table[] = {{"spi_board_v0", 0}, 187 | {/* sentinel */}}; 188 | MODULE_DEVICE_TABLE(spi, picomk_spi_table); 189 | 190 | static void picomk_spi_remove(struct spi_device *spi) { 191 | DeleteTimer(); 192 | DestroyKeyboardDevice(); 193 | if (spi_dev) { 194 | spi_dev = NULL; 195 | } 196 | if (buffer) { 197 | kfree(buffer); 198 | buffer = NULL; 199 | } 200 | if (xs) { 201 | kfree(xs); 202 | xs = NULL; 203 | } 204 | } 205 | 206 | static void picomk_spi_shutdown(struct spi_device *spi) { 207 | picomk_spi_remove(spi); 208 | } 209 | 210 | static int picomk_spi_probe(struct spi_device *spi) { 211 | printk(KERN_INFO "PicoMK SPI IBP driver inserted."); 212 | spi_dev = spi; 213 | spi_dev->bits_per_word = 8; 214 | spi_dev->max_speed_hz = 1000000; 215 | spi_dev->mode = 0; 216 | 217 | if (spi_setup(spi_dev)) { 218 | printk(KERN_ERR "Failed to update word size."); 219 | return -ENODEV; 220 | } 221 | 222 | int error; 223 | buffer = kmalloc(BUF_SIZE, GFP_KERNEL); 224 | xs = kmalloc(sizeof(struct spi_transfer) * BUF_SIZE, GFP_KERNEL); 225 | if (!buffer || !xs) { 226 | printk(KERN_ERR "Failed to allocate buffer"); 227 | error = -ENOMEM; 228 | goto bad; 229 | } 230 | 231 | spin_lock_init(&timer_initialized_lock); 232 | 233 | error = CreateKeyboardDevice(NULL, NULL); 234 | if (error) { 235 | goto bad; 236 | } 237 | 238 | SetupTimer(); 239 | 240 | return 0; 241 | 242 | bad: 243 | if (buffer) { 244 | kfree(buffer); 245 | } 246 | if (xs) { 247 | kfree(xs); 248 | } 249 | buffer = NULL; 250 | xs = NULL; 251 | spi_dev = NULL; 252 | return error; 253 | } 254 | 255 | static struct spi_driver picomk_spi_driver = { 256 | .id_table = picomk_spi_table, 257 | .driver = 258 | { 259 | .name = "PicoMK", 260 | .of_match_table = picomk_of_match, 261 | }, 262 | .probe = picomk_spi_probe, 263 | .remove = picomk_spi_remove, 264 | .shutdown = picomk_spi_shutdown, 265 | }; 266 | module_spi_driver(picomk_spi_driver); 267 | 268 | MODULE_LICENSE("Dual MIT/GPL"); 269 | MODULE_AUTHOR("NoSegfault"); 270 | MODULE_DESCRIPTION("SPI Inter-Board Protocol (IBP) Driver for PicoMK."); 271 | MODULE_VERSION("0.0.1"); 272 | --------------------------------------------------------------------------------