├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── app ├── CMakeLists.txt ├── app_config.h ├── backlight.c ├── backlight.h ├── debug.c ├── debug.h ├── fifo.c ├── fifo.h ├── gpioexp.c ├── gpioexp.h ├── interrupt.c ├── interrupt.h ├── keyboard.c ├── keyboard.h ├── main.c ├── puppet_i2c.c ├── puppet_i2c.h ├── reg.c ├── reg.h ├── touchpad.c ├── touchpad.h ├── tusb_config.h ├── usb.c ├── usb.h └── usb_descriptors.c ├── boards └── bbq20kbd_breakout.h └── etc ├── 99-i2c_puppet.rules └── i2c_puppet.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Building 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | 12 | steps: 13 | - name: Setup cmake 14 | uses: jwlawson/actions-setup-cmake@v1.12 15 | 16 | - name: Setup arm-none-eabi-gcc 17 | uses: fiam/arm-none-eabi-gcc@v1 18 | with: 19 | release: '10-2020-q4' 20 | 21 | - name: Clone repo 22 | uses: actions/checkout@v2 23 | with: 24 | ref: ${{ github.event.client_payload.branch }} 25 | 26 | - name: Get short sha1 27 | id: short_sha1 28 | run: echo "::set-output name=value::$(git rev-parse --short HEAD)" 29 | 30 | - name: Build 31 | run: | 32 | mkdir build output 33 | git submodule update --init 34 | cd 3rdparty/pico-sdk 35 | git submodule update --init 36 | cd ../../build 37 | cmake -DPICO_BOARD=bbq20kbd_breakout -DCMAKE_BUILD_TYPE=Debug .. 38 | make 39 | cp app/i2c_puppet.{bin,elf,uf2} ../output 40 | 41 | - name: Upload package artifact 42 | uses: actions/upload-artifact@v1 43 | with: 44 | path: output 45 | name: i2c_puppet-bbq20kbd_breakout-${{ steps.short_sha1.outputs.value }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.txt.user 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/pico-sdk"] 2 | path = 3rdparty/pico-sdk 3 | url = https://github.com/raspberrypi/pico-sdk.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(PICO_PLATFORM "rp2040") 4 | set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR}/boards) 5 | 6 | include(3rdparty/pico-sdk/pico_sdk_init.cmake) 7 | 8 | project(i2c_puppet) 9 | 10 | pico_sdk_init() 11 | 12 | add_subdirectory(app) 13 | 14 | # binary info in flash 15 | pico_set_program_name(i2c_puppet "I2C Puppet") 16 | pico_set_program_version(i2c_puppet "0.1") 17 | 18 | # printf targets 19 | #pico_enable_stdio_usb(i2c_puppet 1) 20 | pico_enable_stdio_uart(i2c_puppet 1) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Solder Party AB 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # I2C Puppet 2 | 3 | This is a port of the old [BB Q10 Keyboard-to-I2C Software](https://github.com/solderparty/bbq10kbd_i2c_sw) to the RP2040, expanded with new features, while still staying backwards compatible. 4 | 5 | The target product/keyboard for this software is the BB Q20 keyboard, which adds a trackpad to the mix. 6 | 7 | On the features side, this software adds USB support, the keyboard acts as a USB keyboard, and the trackpad acts as a USB mouse. 8 | 9 | On the I2C side, you can access the key presses, the trackpad state, you can control some of the board GPIOs, as well as the backlight. 10 | 11 | See [Protocol](#protocol) for details of the I2C puppet. 12 | 13 | ## Checkout 14 | 15 | The code depends on the Raspberry Pi Pico SDK, which is added as a submodule. Because the Pico SDK includes TinyUSB as a module, it is not recommended to do a recursive submodule init, and rather follow these steps: 16 | 17 | git clone https://github.com/solderparty/i2c_puppet 18 | cd i2c_puppet 19 | git submodule update --init 20 | cd 3rdparty/pico-sdk 21 | git submodule update --init 22 | 23 | ## Build 24 | 25 | See the `boards` directory for a list of available boards. 26 | 27 | mkdir build 28 | cd build 29 | cmake -DPICO_BOARD=bbq20kbd_breakout -DCMAKE_BUILD_TYPE=Debug .. 30 | make 31 | 32 | ## Vendor USB Class 33 | 34 | You can configure the software over USB in a similar way you would do it over I2C. You can access the same registers (like the backlight register) using the USB Vendor Class. 35 | If you don't want to prefix all config interactions with `sudo`, you can copy the included udev rule: 36 | 37 | sudo cp etc/99-i2c_puppet.rules /lib/udev/rules.d/ 38 | sudo udevadm control --reload 39 | sudo udevadm trigger 40 | 41 | To interact with the internal registers of the keyboard over USB, use the `i2c_puppet.py` script included in the `etc` folder. 42 | just import it, create a `I2C_Puppet` object, and you can interact with the keyboard in the same you would do using the I2C interface and the CircuitPython class linked below. 43 | 44 | ## Implementations 45 | 46 | Here are libraries that allow I2C interaction with the boards running this software. Not all libraries might support all the features. 47 | 48 | - [Arduino](https://github.com/solderparty/arduino_bbq10kbd) 49 | - [CircuitPython](https://github.com/solderparty/arturo182_CircuitPython_BBQ10Keyboard) 50 | - [Rust (Embedded-HAL)](https://crates.io/crates/bbq10kbd) 51 | - [Feature-rich Linux Driver](https://github.com/wallComputer/bbqX0kbd_driver/) 52 | - [Linux Kernel Module](https://github.com/billylindeman/bbq10kbd-kernel-driver) 53 | 54 | ## Protocol 55 | 56 | The device uses I2C slave interface to communicate, the address can be configured in `app/config/conf_app.h`, the default is `0x1F`. 57 | 58 | You can read the values of all the registers, the number of returned bytes depends on the register. 59 | It's also possible to write to the registers, to do that, apply the write mask `0x80` to the register ID (for example, the backlight register `0x05` becomes `0x85`). 60 | 61 | ### The FW Version register (REG_VER = 0x01) 62 | 63 | Data written to this register is discarded. Reading this register returns 1 byte, the first nibble contains the major version and the second nibble contains the minor version of the firmware. 64 | 65 | ### The configuration register (REG_CFG = 0x02) 66 | 67 | This register can be read and written to, it's 1 byte in size. 68 | 69 | This register is a bit map of various settings that can be changed to customize the behaviour of the firmware. 70 | 71 | See `REG_CF2` for additional settings. 72 | 73 | | Bit | Name | Description | 74 | | ------ |:----------------:| ------------------------------------------------------------------:| 75 | | 7 | CFG_USE_MODS | Should Alt, Sym and the Shift keys modify the keys being reported. | 76 | | 6 | CFG_REPORT_MODS | Should Alt, Sym and the Shift keys be reported as well. | 77 | | 5 | CFG_PANIC_INT | Currently not implemented. | 78 | | 4 | CFG_KEY_INT | Should an interrupt be generated when a key is pressed. | 79 | | 3 | CFG_NUMLOCK_INT | Should an interrupt be generated when Num Lock is toggled. | 80 | | 2 | CFG_CAPSLOCK_INT | Should an interrupt be generated when Caps Lock is toggled. | 81 | | 1 | CFG_OVERFLOW_INT | Should an interrupt be generated when a FIFO overflow happens. | 82 | | 0 | CFG_OVERFLOW_ON | When a FIFO overflow happens, should the new entry still be pushed, overwriting the oldest one. If 0 then new entry is lost. | 83 | 84 | Defaut value: 85 | `CFG_OVERFLOW_INT | CFG_KEY_INT | CFG_USE_MODS` 86 | 87 | ### Interrupt status register (REG_INT = 0x03) 88 | 89 | When an interrupt happens, the register can be read to check what caused the interrupt. It's 1 byte in size. 90 | 91 | | Bit | Name | Description | 92 | | ------ |:----------------:| -----------------------------------------------------------:| 93 | | 7 | N/A | Currently not implemented. | 94 | | 6 | INT_TOUCH | The interrupt was generated by a trackpad motion. | 95 | | 5 | INT_GPIO | The interrupt was generated by a input GPIO changing level. | 96 | | 4 | INT_PANIC | Currently not implemented. | 97 | | 3 | INT_KEY | The interrupt was generated by a key press. | 98 | | 2 | INT_NUMLOCK | The interrupt was generated by Num Lock. | 99 | | 1 | INT_CAPSLOCK | The interrupt was generated by Caps Lock. | 100 | | 0 | INT_OVERFLOW | The interrupt was generated by FIFO overflow. | 101 | 102 | After reading the register, it has to manually be reset to `0x00`. 103 | 104 | For `INT_GPIO` check the bits in `REG_GIN` to see which GPIO triggered the interrupt. The GPIO interrupt must first be enabled in `REG_GIC`. 105 | 106 | ### Key status register (REG_KEY = 0x04) 107 | 108 | This register contains information about the state of the fifo as well as modified keys. It is 1 byte in size. 109 | 110 | | Bit | Name | Description | 111 | | ------ |:----------------:| -----------------------------------------------:| 112 | | 7 | N/A | Currently not implemented. | 113 | | 6 | KEY_NUMLOCK | Is Num Lock on at the moment. | 114 | | 5 | KEY_CAPSLOCK | Is Caps Lock on at the moment. | 115 | | 0-4 | KEY_COUNT | Number of items in the FIFO waiting to be read. | 116 | 117 | ### Backlight control register (REG_BKL = 0x05) 118 | 119 | Internally a PWM signal is generated to control the keyboard backlight, this register allows changing the brightness of the backlight. It is 1 byte in size, `0x00` being off and `0xFF` being the brightest. 120 | 121 | Default value: `0xFF`. 122 | 123 | ### Debounce configuration register (REG_DEB = 0x06) 124 | 125 | Currently not implemented. 126 | 127 | Default value: 10 128 | 129 | ### Poll frequency configuration register (REG_FRQ = 0x07) 130 | 131 | Currently not implemented. 132 | 133 | Default value: 5 134 | 135 | ### Chip reset register (REG_RST = 0x08) 136 | 137 | Reading or writing to this register will cause a SW reset of the chip. 138 | 139 | ### FIFO access register (REG_FIF = 0x09) 140 | 141 | This register can be used to read the top of the key FIFO. It returns two bytes, a key state and a key code. 142 | 143 | Possible key states: 144 | 145 | | Value | State | 146 | | ------ |:-----------------------:| 147 | | 1 | Pressed | 148 | | 2 | Pressed and Held | 149 | | 3 | Released | 150 | 151 | ### Secondary backlight control register (REG_BK2 = 0x0A) 152 | 153 | Internally a PWM signal is generated to control a secondary backlight (for example, a screen), this register allows changing the brightness of the backlight. It is 1 byte in size, `0x00` being off and `0xFF` being the brightest. 154 | 155 | Default value: `0xFF`. 156 | 157 | ### GPIO direction register (REG_DIR = 0x0B) 158 | 159 | This register controls the direction of the GPIO expander pins, each bit corresponding to one pin. It is 1 byte in size. 160 | 161 | The actual pin[7..0] to MCU pin assignment depends on the board, see `.h` of the board for the assignments. 162 | 163 | Any bit set to `1` means the GPIO is configured as input, any bit set to `0` means the GPIO is configured as output. 164 | 165 | Default value: `0xFF` (all GPIOs configured as input) 166 | 167 | ### GPIO input pull enable register (REG_PUE = 0x0C) 168 | 169 | If a GPIO is configured as an input (using `REG_DIR`), a optional pull-up/pull-down can be enabled. 170 | 171 | This register controls the pull enable status, each bit corresponding to one pin. It is 1 byte in size. 172 | 173 | The actual pin[7..0] to MCU pin assignment depends on the board, see `.h` of the board for the assignments. 174 | 175 | Any bit set to `1` means the input pull for that pin is enabled, any bit set to `0` means the input pull for that pin is disabled. 176 | 177 | The direction of the pull is done in `REG_PUD`. 178 | 179 | When a pin is configured as output, its bit in this register has no effect. 180 | 181 | Default value: 0 (all pulls disabled) 182 | 183 | ### GPIO input pull direction register (REG_PUD = 0x0D) 184 | 185 | If a GPIO is configured as an input (using `REG_DIR`), a optional pull-up/pull-down can be configured. 186 | 187 | The pull functionality is enabled using `REG_PUE` and the direction of the pull is configured using this register, each bit corresponding to one pin. This register is 1 byte in size. 188 | 189 | The actual pin[7..0] to MCU pin assignment depends on the board, see `.h` of the board for the assignments. 190 | 191 | Any bit set to `1` means the input pull is set to pull-up, any bit set to `0` means the input pul is set to pull-down. 192 | 193 | When a pin is configured as output, its bit in this register has no effect. 194 | 195 | Default value: `0xFF` (all pulls set to pull-up, if enabled in `REG_PUE` and set to input in `REG_DIR`) 196 | 197 | ### GPIO value register (REG_GIO = 0x0E) 198 | 199 | This register contains the values of the GPIO Expander pins, each bit corresponding to one pin. It is 1 byte in size. 200 | 201 | The actual pin[7..0] to MCU pin assignment depends on the board, see `.h` of the board for the assignments. 202 | 203 | If a pin is configured as an output (via `REG_DIR`), writing to this register will change the value of that pin. 204 | 205 | Reading from this register will return the values for both input and output pins. 206 | 207 | Default value: Depends on pin values 208 | 209 | ### GPIO interrupt config register (REG_GIC = 0x0F) 210 | 211 | If a GPIO is configured as an input (using `REG_DIR`), an interrupt can be configured to trigger when the pin's value changes. 212 | 213 | This register controls the interrupt, each bit corresponding to one pin. 214 | 215 | The actual pin[7..0] to MCU pin assignment depends on the board, see `.h` of the board for the assignments. 216 | 217 | Any bit set to `1` means the input pin will trigger and interrupt when changing value, any bit set to `0` means no interrupt for that pin is triggered. 218 | 219 | When an interrupt happens, the GPIO that triggered the interrupt can be determined by reading `REG_GIN`. Additionally, the `INT_GPIO` bit will be set in `REG_INT`. 220 | 221 | Default value: `0x00` 222 | 223 | ### GPIO interrupt status register (REG_GIN = 0x10) 224 | 225 | When an interrupt happens, the register can be read to check which GPIO caused the interrupt, each bit corresponding to one pin. This register is 1 byte in size. 226 | 227 | The actual pin[7..0] to MCU pin assignment depends on the board, see `.h` of the board for the assignments. 228 | 229 | After reading the register, it has to manually be reset to `0x00`. 230 | 231 | Default value: `0x00` 232 | 233 | ### Key hold threshold configuration (REG_HLD = 0x11) 234 | 235 | This register can be read and written to, it is 1 byte in size. 236 | 237 | The value of this register (expressed in units of 10ms) is used to determine if a "press and hold" state should be entered. 238 | 239 | If a key is held down longer than the value, it enters the "press and hold" state. 240 | 241 | Default value: 30 (300ms) 242 | 243 | ### Device I2C address (REG_ADR = 0x12) 244 | 245 | The address that the device shows up on the I2C bus under. This register can be read and written to, it is 1 byte in size. 246 | 247 | The change is applied as soon as a new value is written to the register. The next communication must be performed on the new address. 248 | 249 | The address is not saved after a reset. 250 | 251 | Default value: `0x1F` 252 | 253 | ### Interrupt duration (REG_IND = 0x13) 254 | 255 | The value of this register determines how long the INT/IRQ pin is held LOW after an interrupt event happens.This register can be read and written to, it is 1 byte in size. 256 | 257 | The value of this register is expressed in ms. 258 | 259 | Default value: 1 (1ms) 260 | 261 | ### The configuration register 2 (REG_CF2 = 0x14) 262 | 263 | This register can be read and written to, it's 1 byte in size. 264 | 265 | This register is a bit map of various settings that can be changed to customize the behaviour of the firmware. 266 | 267 | See `REG_CFG` for additional settings. 268 | 269 | | Bit | Name | Description | 270 | | ------ |:----------------:| ------------------------------------------------------------------:| 271 | | 7 | N/A | Currently not implemented. | 272 | | 6 | N/A | Currently not implemented. | 273 | | 5 | N/A | Currently not implemented. | 274 | | 4 | N/A | Currently not implemented. | 275 | | 3 | N/A | Currently not implemented. | 276 | | 2 | CF2_USB_MOUSE_ON | Should trackpad events be sent over USB HID. | 277 | | 1 | CF2_USB_KEYB_ON | Should key events be sent over USB HID. | 278 | | 0 | CF2_TOUCH_INT | Should trackpad events generate interrupts. | 279 | 280 | Default value: `CF2_TOUCH_INT | CF2_USB_KEYB_ON | CF2_USB_MOUSE_ON` 281 | 282 | ### Trackpad X Position(REG_TOX = 0x15) 283 | 284 | This is a read-only register, it is 1 byte in size. 285 | 286 | Trackpad X-axis position delta since the last time this register was read. 287 | 288 | The value reported is signed and can be in the range of (-128 to 127). 289 | 290 | When the value of this register is read, it is afterwards reset back to 0. 291 | 292 | It is recommended to read the value of this register often, or data loss might occur. 293 | 294 | Default value: 0 295 | 296 | ### Trackpad Y position (REG_TOY = 0x16) 297 | 298 | This is a read-only register, it is 1 byte in size. 299 | 300 | Trackpad Y-axis position delta since the last time this register was read. 301 | 302 | The value reported is signed and can be in the range of (-128 to 127). 303 | 304 | When the value of this register is read, it is afterwards reset back to 0. 305 | 306 | It is recommended to read the value of this register often, or data loss might occur. 307 | 308 | Default value: 0 309 | 310 | ## Version history 311 | 312 | v1.0: 313 | - Initial release 314 | 315 | See here for the legacy project's history: https://github.com/solderparty/bbq10kbd_i2c_sw#version-history 316 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(i2c_puppet 2 | backlight.c 3 | debug.c 4 | fifo.c 5 | gpioexp.c 6 | puppet_i2c.c 7 | interrupt.c 8 | keyboard.c 9 | main.c 10 | reg.c 11 | touchpad.c 12 | usb.c 13 | usb_descriptors.c 14 | ) 15 | 16 | add_compile_options(-Wall -Wextra -Wpedantic) 17 | 18 | target_include_directories(i2c_puppet PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 19 | 20 | target_link_libraries(i2c_puppet 21 | cmsis_core 22 | hardware_i2c 23 | hardware_pwm 24 | pico_bootsel_via_double_reset 25 | pico_stdlib 26 | tinyusb_device 27 | ) 28 | 29 | # create map/bin/hex/uf2 file in addition to elf 30 | pico_add_extra_outputs(i2c_puppet) 31 | -------------------------------------------------------------------------------- /app/app_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VERSION_MAJOR 1 4 | #define VERSION_MINOR 1 5 | 6 | #define KEY_FIFO_SIZE 31 // number of keys in the public FIFO 7 | -------------------------------------------------------------------------------- /app/backlight.c: -------------------------------------------------------------------------------- 1 | #include "backlight.h" 2 | #include "reg.h" 3 | 4 | #include 5 | #include 6 | 7 | void backlight_sync(void) 8 | { 9 | pwm_set_gpio_level(PIN_BKL, reg_get_value(REG_ID_BKL) * 0x80); 10 | } 11 | 12 | void backlight_init(void) 13 | { 14 | gpio_set_function(PIN_BKL, GPIO_FUNC_PWM); 15 | 16 | const uint slice_num = pwm_gpio_to_slice_num(PIN_BKL); 17 | 18 | pwm_config config = pwm_get_default_config(); 19 | pwm_init(slice_num, &config, true); 20 | 21 | backlight_sync(); 22 | } 23 | -------------------------------------------------------------------------------- /app/backlight.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void backlight_sync(void); 6 | void backlight_init(void); 7 | -------------------------------------------------------------------------------- /app/debug.c: -------------------------------------------------------------------------------- 1 | #include "debug.h" 2 | 3 | #include "app_config.h" 4 | #include "gpioexp.h" 5 | #include "keyboard.h" 6 | #include "reg.h" 7 | #include "touchpad.h" 8 | #include "usb.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define PICO_STDIO_USB_STDOUT_TIMEOUT_US 500000 17 | 18 | static void key_cb(char key, enum key_state state) 19 | { 20 | printf("key: 0x%02X/%d/%c, state: %d\r\n", key, key, key, state); 21 | } 22 | static struct key_callback key_callback = { .func = key_cb }; 23 | 24 | static void key_lock_cb(bool caps_changed, bool num_changed) 25 | { 26 | printf("lock, caps_c: %d, caps: %d, num_c: %d, num: %d\r\n", 27 | caps_changed, keyboard_get_capslock(), 28 | num_changed, keyboard_get_numlock()); 29 | } 30 | static struct key_lock_callback key_lock_callback ={ .func = key_lock_cb }; 31 | 32 | static void touch_cb(int8_t x, int8_t y) 33 | { 34 | printf("%s: x: %d, y: %d !\r\n", __func__, x, y); 35 | } 36 | static struct touch_callback touch_callback = { .func = touch_cb }; 37 | 38 | static void gpioexp_cb(uint8_t gpio, uint8_t gpio_idx) 39 | { 40 | printf("gpioexp, pin: %d, idx: %d\r\n", gpio, gpio_idx); 41 | } 42 | static struct gpioexp_callback gpioexp_callback = { .func = gpioexp_cb }; 43 | 44 | // copied from pico_stdio_usb in the SDK 45 | static void usb_out_chars(const char *buf, int length) 46 | { 47 | static uint64_t last_avail_time; 48 | uint32_t owner; 49 | 50 | if (!mutex_try_enter(usb_get_mutex(), &owner)) { 51 | if (owner == get_core_num()) 52 | return; 53 | 54 | mutex_enter_blocking(usb_get_mutex()); 55 | } 56 | 57 | if (tud_cdc_connected()) { 58 | for (int i = 0; i < length;) { 59 | int n = length - i; 60 | int avail = tud_cdc_write_available(); 61 | if (n > avail) n = avail; 62 | if (n) { 63 | int n2 = tud_cdc_write(buf + i, n); 64 | tud_task(); 65 | tud_cdc_write_flush(); 66 | i += n2; 67 | last_avail_time = time_us_64(); 68 | } else { 69 | tud_task(); 70 | tud_cdc_write_flush(); 71 | if (!tud_cdc_connected() || 72 | (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) { 73 | break; 74 | } 75 | } 76 | } 77 | } else { 78 | // reset our timeout 79 | last_avail_time = 0; 80 | } 81 | 82 | mutex_exit(usb_get_mutex()); 83 | } 84 | static struct stdio_driver stdio_usb = 85 | { 86 | .out_chars = usb_out_chars, 87 | #if PICO_STDIO_ENABLE_CRLF_SUPPORT 88 | .crlf_enabled = PICO_STDIO_DEFAULT_CRLF 89 | #endif 90 | }; 91 | 92 | void debug_init(void) 93 | { 94 | stdio_init_all(); 95 | 96 | stdio_set_driver_enabled(&stdio_usb, true); 97 | 98 | printf("I2C Puppet SW v%d.%d\r\n", VERSION_MAJOR, VERSION_MINOR); 99 | 100 | keyboard_add_key_callback(&key_callback); 101 | keyboard_add_lock_callback(&key_lock_callback); 102 | 103 | touchpad_add_touch_callback(&touch_callback); 104 | 105 | gpioexp_add_int_callback(&gpioexp_callback); 106 | } 107 | -------------------------------------------------------------------------------- /app/debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void debug_init(void); 4 | -------------------------------------------------------------------------------- /app/fifo.c: -------------------------------------------------------------------------------- 1 | #include "app_config.h" 2 | #include "fifo.h" 3 | 4 | static struct 5 | { 6 | struct fifo_item fifo[KEY_FIFO_SIZE]; 7 | uint8_t count; 8 | uint8_t read_idx; 9 | uint8_t write_idx; 10 | } self; 11 | 12 | uint8_t fifo_count(void) 13 | { 14 | return self.count; 15 | } 16 | 17 | void fifo_flush(void) 18 | { 19 | self.write_idx = 0; 20 | self.read_idx = 0; 21 | self.count = 0; 22 | } 23 | 24 | bool fifo_enqueue(const struct fifo_item item) 25 | { 26 | if (self.count >= KEY_FIFO_SIZE) 27 | return false; 28 | 29 | self.fifo[self.write_idx++] = item; 30 | 31 | self.write_idx %= KEY_FIFO_SIZE; 32 | ++self.count; 33 | 34 | return true; 35 | } 36 | 37 | void fifo_enqueue_force(const struct fifo_item item) 38 | { 39 | if (fifo_enqueue(item)) 40 | return; 41 | 42 | self.fifo[self.write_idx++] = item; 43 | self.write_idx %= KEY_FIFO_SIZE; 44 | 45 | self.read_idx++; 46 | self.read_idx %= KEY_FIFO_SIZE; 47 | } 48 | 49 | struct fifo_item fifo_dequeue(void) 50 | { 51 | struct fifo_item item = { 0 }; 52 | if (self.count == 0) 53 | return item; 54 | 55 | item = self.fifo[self.read_idx++]; 56 | self.read_idx %= KEY_FIFO_SIZE; 57 | --self.count; 58 | 59 | return item; 60 | } 61 | -------------------------------------------------------------------------------- /app/fifo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "keyboard.h" 4 | 5 | struct fifo_item 6 | { 7 | char key; 8 | enum key_state state; 9 | }; 10 | 11 | uint8_t fifo_count(void); 12 | void fifo_flush(void); 13 | bool fifo_enqueue(const struct fifo_item item); 14 | void fifo_enqueue_force(const struct fifo_item item); 15 | struct fifo_item fifo_dequeue(void); 16 | -------------------------------------------------------------------------------- /app/gpioexp.c: -------------------------------------------------------------------------------- 1 | #include "gpioexp.h" 2 | #include "reg.h" 3 | 4 | #include 5 | #include 6 | 7 | static struct 8 | { 9 | struct gpioexp_callback *callbacks; 10 | } self; 11 | 12 | static void set_dir(uint8_t gpio, uint8_t gpio_idx, uint8_t dir) 13 | { 14 | #ifndef NDEBUG 15 | printf("%s: gpio: %d, gpio_idx: %d, dir: %d\r\n", __func__, gpio, gpio_idx, dir); 16 | #endif 17 | 18 | gpio_init(gpio); 19 | 20 | if (dir == DIR_INPUT) { 21 | if (reg_is_bit_set(REG_ID_PUE, (1 << gpio_idx))) { 22 | if (reg_is_bit_set(REG_ID_PUD, (1 << gpio_idx)) == PUD_UP) { 23 | gpio_is_pulled_up(gpio); 24 | } else { 25 | gpio_is_pulled_down(gpio); 26 | } 27 | } else { 28 | gpio_disable_pulls(gpio); 29 | } 30 | 31 | gpio_set_dir(gpio, GPIO_IN); 32 | 33 | gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true); 34 | 35 | reg_set_bit(REG_ID_DIR, (1 << gpio_idx)); 36 | } else { 37 | gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, false); 38 | 39 | gpio_set_dir(gpio, GPIO_OUT); 40 | 41 | reg_clear_bit(REG_ID_DIR, (1 << gpio_idx)); 42 | } 43 | } 44 | 45 | void gpioexp_gpio_irq(uint gpio, uint32_t events) 46 | { 47 | (void)gpio; 48 | (void)events; 49 | 50 | #define CALLBACK(bit) \ 51 | if (gpio == PIN_GPIOEXP ## bit) { \ 52 | struct gpioexp_callback *cb = self.callbacks; \ 53 | while (cb) { \ 54 | cb->func(PIN_GPIOEXP ## bit, bit); \ 55 | cb = cb->next; \ 56 | } \ 57 | return; \ 58 | } 59 | 60 | #ifdef PIN_GPIOEXP0 61 | CALLBACK(0) 62 | #endif 63 | 64 | #ifdef PIN_GPIOEXP1 65 | CALLBACK(1) 66 | #endif 67 | 68 | #ifdef PIN_GPIOEXP2 69 | CALLBACK(2) 70 | #endif 71 | 72 | #ifdef PIN_GPIOEXP3 73 | CALLBACK(3) 74 | #endif 75 | 76 | #ifdef PIN_GPIOEXP4 77 | CALLBACK(4) 78 | #endif 79 | 80 | #ifdef PIN_GPIOEXP5 81 | CALLBACK(5) 82 | #endif 83 | 84 | #ifdef PIN_GPIOEXP6 85 | CALLBACK(6) 86 | #endif 87 | 88 | #ifdef PIN_GPIOEXP7 89 | CALLBACK(7) 90 | #endif 91 | } 92 | 93 | void gpioexp_update_dir(uint8_t new_dir) 94 | { 95 | #ifndef NDEBUG 96 | printf("%s: dir: 0x%02X\r\n", __func__, new_dir); 97 | #endif 98 | 99 | const uint8_t old_dir = reg_get_value(REG_ID_DIR); 100 | 101 | (void)old_dir; // Shut up warning in case no GPIOs configured 102 | 103 | #define UPDATE_DIR(bit) \ 104 | if ((old_dir & (1 << bit)) != (new_dir & (1 << bit))) \ 105 | set_dir(PIN_GPIOEXP ## bit, bit, (new_dir & (1 << bit)) != 0); 106 | 107 | #ifdef PIN_GPIOEXP0 108 | UPDATE_DIR(0) 109 | #endif 110 | #ifdef PIN_GPIOEXP1 111 | UPDATE_DIR(1) 112 | #endif 113 | #ifdef PIN_GPIOEXP2 114 | UPDATE_DIR(2) 115 | #endif 116 | #ifdef PIN_GPIOEXP3 117 | UPDATE_DIR(3) 118 | #endif 119 | #ifdef PIN_GPIOEXP4 120 | UPDATE_DIR(4) 121 | #endif 122 | #ifdef PIN_GPIOEXP5 123 | UPDATE_DIR(5) 124 | #endif 125 | #ifdef PIN_GPIOEXP6 126 | UPDATE_DIR(6) 127 | #endif 128 | #ifdef PIN_GPIOEXP7 129 | UPDATE_DIR(7) 130 | #endif 131 | } 132 | 133 | void gpioexp_update_pue_pud(uint8_t new_pue, uint8_t new_pud) 134 | { 135 | #ifndef NDEBUG 136 | printf("%s: pue: 0x%02X, pud: 0x%02X\r\n", __func__, new_pue, new_pud); 137 | #endif 138 | 139 | const uint8_t old_pue = reg_get_value(REG_ID_PUE); 140 | const uint8_t old_pud = reg_get_value(REG_ID_PUD); 141 | 142 | // Shut up warnings in case no GPIOs configured 143 | (void)old_pue; 144 | (void)old_pud; 145 | 146 | reg_set_value(REG_ID_PUE, new_pue); 147 | reg_set_value(REG_ID_PUD, new_pud); 148 | 149 | #define UPDATE_PULL(bit) \ 150 | if (((old_pue & (1 << bit)) != (new_pue & (1 << bit))) || \ 151 | ((old_pud & (1 << bit)) != (new_pud & (1 << bit)))) { \ 152 | set_dir(PIN_GPIOEXP ## bit, bit, reg_is_bit_set(REG_ID_DIR, (1 << bit))); \ 153 | } 154 | 155 | #ifdef PIN_GPIOEXP0 156 | UPDATE_PULL(0) 157 | #endif 158 | #ifdef PIN_GPIOEXP1 159 | UPDATE_PULL(1) 160 | #endif 161 | #ifdef PIN_GPIOEXP2 162 | UPDATE_PULL(2) 163 | #endif 164 | #ifdef PIN_GPIOEXP3 165 | UPDATE_PULL(3) 166 | #endif 167 | #ifdef PIN_GPIOEXP4 168 | UPDATE_PULL(4) 169 | #endif 170 | #ifdef PIN_GPIOEXP5 171 | UPDATE_PULL(5) 172 | #endif 173 | #ifdef PIN_GPIOEXP6 174 | UPDATE_PULL(6) 175 | #endif 176 | #ifdef PIN_GPIOEXP7 177 | UPDATE_PULL(7) 178 | #endif 179 | } 180 | 181 | void gpioexp_set_value(uint8_t value) 182 | { 183 | #ifndef NDEBUG 184 | printf("%s: value: 0x%02X\r\n", __func__, value); 185 | #endif 186 | 187 | #define SET_VALUE(bit) \ 188 | if (reg_is_bit_set(REG_ID_DIR, (1 << bit)) == DIR_OUTPUT) { \ 189 | gpio_put(PIN_GPIOEXP ## bit, (value & (1 << bit))); \ 190 | } 191 | 192 | #ifdef PIN_GPIOEXP0 193 | SET_VALUE(0) 194 | #endif 195 | #ifdef PIN_GPIOEXP1 196 | SET_VALUE(1) 197 | #endif 198 | #ifdef PIN_GPIOEXP2 199 | SET_VALUE(2) 200 | #endif 201 | #ifdef PIN_GPIOEXP3 202 | SET_VALUE(3) 203 | #endif 204 | #ifdef PIN_GPIOEXP4 205 | SET_VALUE(4) 206 | #endif 207 | #ifdef PIN_GPIOEXP5 208 | SET_VALUE(5) 209 | #endif 210 | #ifdef PIN_GPIOEXP6 211 | SET_VALUE(6) 212 | #endif 213 | #ifdef PIN_GPIOEXP7 214 | SET_VALUE(7) 215 | #endif 216 | } 217 | 218 | uint8_t gpioexp_get_value(void) 219 | { 220 | uint8_t value = 0; 221 | 222 | #define GET_VALUE(bit) \ 223 | value |= (gpio_get(PIN_GPIOEXP ## bit) << bit); 224 | 225 | #ifdef PIN_GPIOEXP0 226 | GET_VALUE(0) 227 | #endif 228 | #ifdef PIN_GPIOEXP1 229 | GET_VALUE(1) 230 | #endif 231 | #ifdef PIN_GPIOEXP2 232 | GET_VALUE(2) 233 | #endif 234 | #ifdef PIN_GPIOEXP3 235 | GET_VALUE(3) 236 | #endif 237 | #ifdef PIN_GPIOEXP4 238 | GET_VALUE(4) 239 | #endif 240 | #ifdef PIN_GPIOEXP5 241 | GET_VALUE(5) 242 | #endif 243 | #ifdef PIN_GPIOEXP6 244 | GET_VALUE(6) 245 | #endif 246 | #ifdef PIN_GPIOEXP7 247 | GET_VALUE(7) 248 | #endif 249 | 250 | return value; 251 | } 252 | 253 | void gpioexp_add_int_callback(struct gpioexp_callback *callback) 254 | { 255 | // first callback 256 | if (!self.callbacks) { 257 | self.callbacks = callback; 258 | return; 259 | } 260 | 261 | // find last and insert after 262 | struct gpioexp_callback *cb = self.callbacks; 263 | while (cb->next) 264 | cb = cb->next; 265 | 266 | cb->next = callback; 267 | } 268 | 269 | void gpioexp_init(void) 270 | { 271 | // Configure all to inputs 272 | gpioexp_update_dir(0xFF); 273 | } 274 | -------------------------------------------------------------------------------- /app/gpioexp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct gpioexp_callback 6 | { 7 | void (*func)(uint8_t gpio, uint8_t gpio_idx); 8 | struct gpioexp_callback *next; 9 | }; 10 | 11 | void gpioexp_gpio_irq(uint gpio, uint32_t events); 12 | 13 | void gpioexp_update_dir(uint8_t dir); 14 | void gpioexp_update_pue_pud(uint8_t pue, uint8_t pud); 15 | 16 | void gpioexp_set_value(uint8_t value); 17 | uint8_t gpioexp_get_value(void); 18 | 19 | void gpioexp_add_int_callback(struct gpioexp_callback *callback); 20 | void gpioexp_init(void); 21 | -------------------------------------------------------------------------------- /app/interrupt.c: -------------------------------------------------------------------------------- 1 | #include "interrupt.h" 2 | 3 | #include "app_config.h" 4 | #include "gpioexp.h" 5 | #include "keyboard.h" 6 | #include "reg.h" 7 | #include "touchpad.h" 8 | 9 | #include 10 | 11 | static void key_cb(char key, enum key_state state) 12 | { 13 | (void)key; 14 | (void)state; 15 | 16 | if (!reg_is_bit_set(REG_ID_CFG, CFG_KEY_INT)) 17 | return; 18 | 19 | reg_set_bit(REG_ID_INT, INT_KEY); 20 | 21 | gpio_put(PIN_INT, 0); 22 | busy_wait_ms(reg_get_value(REG_ID_IND)); 23 | gpio_put(PIN_INT, 1); 24 | } 25 | static struct key_callback key_callback = { .func = key_cb }; 26 | 27 | static void key_lock_cb(bool caps_changed, bool num_changed) 28 | { 29 | bool do_int = false; 30 | 31 | if (caps_changed && reg_is_bit_set(REG_ID_CFG, CFG_CAPSLOCK_INT)) { 32 | reg_set_bit(REG_ID_INT, INT_CAPSLOCK); 33 | do_int = true; 34 | } 35 | 36 | if (num_changed && reg_is_bit_set(REG_ID_CFG, CFG_NUMLOCK_INT)) { 37 | reg_set_bit(REG_ID_INT, INT_NUMLOCK); 38 | do_int = true; 39 | } 40 | 41 | if (do_int) { 42 | gpio_put(PIN_INT, 0); 43 | busy_wait_ms(reg_get_value(REG_ID_IND)); 44 | gpio_put(PIN_INT, 1); 45 | } 46 | } 47 | static struct key_lock_callback key_lock_callback = { .func = key_lock_cb }; 48 | 49 | static void touch_cb(int8_t x, int8_t y) 50 | { 51 | (void)x; 52 | (void)y; 53 | 54 | if (!reg_is_bit_set(REG_ID_CF2, CF2_TOUCH_INT)) 55 | return; 56 | 57 | reg_set_bit(REG_ID_INT, INT_TOUCH); 58 | 59 | gpio_put(PIN_INT, 0); 60 | busy_wait_ms(reg_get_value(REG_ID_IND)); 61 | gpio_put(PIN_INT, 1); 62 | } 63 | static struct touch_callback touch_callback = { .func = touch_cb }; 64 | 65 | static void gpioexp_cb(uint8_t gpio, uint8_t gpio_idx) 66 | { 67 | (void)gpio; 68 | 69 | if (!reg_is_bit_set(REG_ID_GIC, (1 << gpio_idx))) 70 | return; 71 | 72 | reg_set_bit(REG_ID_INT, INT_GPIO); 73 | reg_set_bit(REG_ID_GIN, (1 << gpio_idx)); 74 | 75 | gpio_put(PIN_INT, 0); 76 | busy_wait_ms(reg_get_value(REG_ID_IND)); 77 | gpio_put(PIN_INT, 1); 78 | } 79 | static struct gpioexp_callback gpioexp_callback = { .func = gpioexp_cb }; 80 | 81 | void interrupt_init(void) 82 | { 83 | gpio_init(PIN_INT); 84 | gpio_set_dir(PIN_INT, GPIO_OUT); 85 | gpio_pull_up(PIN_INT); 86 | gpio_put(PIN_INT, true); 87 | 88 | keyboard_add_key_callback(&key_callback); 89 | keyboard_add_lock_callback(&key_lock_callback); 90 | 91 | touchpad_add_touch_callback(&touch_callback); 92 | 93 | gpioexp_add_int_callback(&gpioexp_callback); 94 | } 95 | -------------------------------------------------------------------------------- /app/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void interrupt_init(void); 4 | -------------------------------------------------------------------------------- /app/keyboard.c: -------------------------------------------------------------------------------- 1 | #include "app_config.h" 2 | #include "fifo.h" 3 | #include "keyboard.h" 4 | #include "reg.h" 5 | 6 | #include 7 | 8 | #define LIST_SIZE 10 // size of the list keeping track of all the pressed keys 9 | 10 | struct entry 11 | { 12 | char chr; 13 | char alt; 14 | enum key_mod mod; 15 | }; 16 | 17 | struct list_item 18 | { 19 | const struct entry *p_entry; 20 | uint32_t hold_start_time; 21 | enum key_state state; 22 | bool mods[KEY_MOD_ID_LAST]; 23 | char effective_key; 24 | }; 25 | 26 | static const uint8_t row_pins[NUM_OF_ROWS] = 27 | { 28 | PINS_ROWS 29 | }; 30 | 31 | static const uint8_t col_pins[NUM_OF_COLS] = 32 | { 33 | PINS_COLS 34 | }; 35 | 36 | #pragma GCC diagnostic push 37 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 38 | 39 | static const struct entry kbd_entries[][NUM_OF_COLS] = 40 | { 41 | { { KEY_JOY_CENTER }, { 'W', '1' }, { 'G', '/' }, { 'S', '4' }, { 'L', '"' }, { 'H' , ':' } }, 42 | { { }, { 'Q', '#' }, { 'R', '3' }, { 'E', '2' }, { 'O', '+' }, { 'U', '_' } }, 43 | { { KEY_BTN_LEFT1 }, { '~', '0' }, { 'F', '6' }, { .mod = KEY_MOD_ID_SHL }, { 'K', '\'' }, { 'J', ';' } }, 44 | { { }, { ' ', '\t' }, { 'C', '9' }, { 'Z', '7' }, { 'M', '.' }, { 'N', ',' } }, 45 | { { KEY_BTN_LEFT2 }, { .mod = KEY_MOD_ID_SYM }, { 'T', '(' }, { 'D', '5' }, { 'I', '-' }, { 'Y', ')' } }, 46 | { { KEY_BTN_RIGHT1 }, { .mod = KEY_MOD_ID_ALT }, { 'V', '?' }, { 'X', '8' }, { '$', '`' }, { 'B', '!' } }, 47 | { { }, { 'A', '*' }, { .mod = KEY_MOD_ID_SHR }, { 'P', '@' }, { '\b' }, { '\n', '|' } }, 48 | }; 49 | 50 | #if NUM_OF_BTNS > 0 51 | static const struct entry btn_entries[NUM_OF_BTNS] = 52 | { 53 | BTN_KEYS 54 | }; 55 | 56 | static const uint8_t btn_pins[NUM_OF_BTNS] = 57 | { 58 | PINS_BTNS 59 | }; 60 | #endif 61 | 62 | #pragma GCC diagnostic pop 63 | 64 | static struct 65 | { 66 | struct key_lock_callback *lock_callbacks; 67 | struct key_callback *key_callbacks; 68 | 69 | struct list_item list[LIST_SIZE]; 70 | 71 | bool mods[KEY_MOD_ID_LAST]; 72 | 73 | bool capslock_changed; 74 | bool capslock; 75 | 76 | bool numlock_changed; 77 | bool numlock; 78 | } self; 79 | 80 | static void transition_to(struct list_item * const p_item, const enum key_state next_state) 81 | { 82 | const struct entry * const p_entry = p_item->p_entry; 83 | 84 | p_item->state = next_state; 85 | 86 | if (!p_entry) 87 | return; 88 | 89 | if (p_item->effective_key == '\0') { 90 | char key = p_entry->chr; 91 | switch (p_entry->mod) { 92 | case KEY_MOD_ID_ALT: 93 | if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) 94 | key = KEY_MOD_ALT; 95 | break; 96 | 97 | case KEY_MOD_ID_SHL: 98 | if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) 99 | key = KEY_MOD_SHL; 100 | break; 101 | 102 | case KEY_MOD_ID_SHR: 103 | if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) 104 | key = KEY_MOD_SHR; 105 | break; 106 | 107 | case KEY_MOD_ID_SYM: 108 | if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) 109 | key = KEY_MOD_SYM; 110 | break; 111 | 112 | default: 113 | { 114 | if (reg_is_bit_set(REG_ID_CFG, CFG_USE_MODS)) { 115 | const bool shift = (self.mods[KEY_MOD_ID_SHL] || self.mods[KEY_MOD_ID_SHR]) | self.capslock; 116 | const bool alt = self.mods[KEY_MOD_ID_ALT] | self.numlock; 117 | const bool is_button = (key <= KEY_BTN_RIGHT1) || ((key >= KEY_BTN_LEFT2) && (key <= KEY_BTN_RIGHT2)); 118 | 119 | if (alt && !is_button) { 120 | key = p_entry->alt; 121 | } else if (!shift && (key >= 'A' && key <= 'Z')) { 122 | key = (key + ' '); 123 | } 124 | } 125 | 126 | break; 127 | } 128 | } 129 | 130 | p_item->effective_key = key; 131 | } 132 | 133 | if (p_item->effective_key == '\0') 134 | return; 135 | 136 | keyboard_inject_event(p_item->effective_key, next_state); 137 | } 138 | 139 | static void next_item_state(struct list_item * const p_item, const bool pressed) 140 | { 141 | switch (p_item->state) { 142 | case KEY_STATE_IDLE: 143 | if (pressed) { 144 | if (p_item->p_entry->mod != KEY_MOD_ID_NONE) 145 | self.mods[p_item->p_entry->mod] = true; 146 | 147 | if (!self.capslock_changed && self.mods[KEY_MOD_ID_SHR] && self.mods[KEY_MOD_ID_ALT]) { 148 | self.capslock = true; 149 | self.capslock_changed = true; 150 | } 151 | 152 | if (!self.numlock_changed && self.mods[KEY_MOD_ID_SHL] && self.mods[KEY_MOD_ID_ALT]) { 153 | self.numlock = true; 154 | self.numlock_changed = true; 155 | } 156 | 157 | if (!self.capslock_changed && (self.mods[KEY_MOD_ID_SHL] || self.mods[KEY_MOD_ID_SHR])) { 158 | self.capslock = false; 159 | self.capslock_changed = true; 160 | } 161 | 162 | if (!self.numlock_changed && (self.mods[KEY_MOD_ID_SHL] || self.mods[KEY_MOD_ID_SHR])) { 163 | self.numlock = false; 164 | self.numlock_changed = true; 165 | } 166 | 167 | if (!self.mods[KEY_MOD_ID_ALT]) { 168 | self.capslock_changed = false; 169 | self.numlock_changed = false; 170 | } 171 | 172 | if (self.lock_callbacks && (self.capslock_changed || self.numlock_changed)) { 173 | struct key_lock_callback *cb = self.lock_callbacks; 174 | while (cb) { 175 | cb->func(self.capslock_changed, self.numlock_changed); 176 | 177 | cb = cb->next; 178 | } 179 | } 180 | 181 | transition_to(p_item, KEY_STATE_PRESSED); 182 | 183 | p_item->hold_start_time = to_ms_since_boot(get_absolute_time()); 184 | } 185 | break; 186 | 187 | case KEY_STATE_PRESSED: 188 | if ((to_ms_since_boot(get_absolute_time()) - p_item->hold_start_time) > (reg_get_value(REG_ID_HLD) * 10)) { 189 | transition_to(p_item, KEY_STATE_HOLD); 190 | } else if(!pressed) { 191 | transition_to(p_item, KEY_STATE_RELEASED); 192 | } 193 | break; 194 | 195 | case KEY_STATE_HOLD: 196 | if (!pressed) 197 | transition_to(p_item, KEY_STATE_RELEASED); 198 | break; 199 | 200 | case KEY_STATE_RELEASED: 201 | { 202 | if (p_item->p_entry->mod != KEY_MOD_ID_NONE) 203 | self.mods[p_item->p_entry->mod] = false; 204 | 205 | p_item->p_entry = NULL; 206 | p_item->effective_key = '\0'; 207 | transition_to(p_item, KEY_STATE_IDLE); 208 | break; 209 | } 210 | } 211 | } 212 | 213 | static int64_t timer_task(alarm_id_t id, void *user_data) 214 | { 215 | (void)id; 216 | (void)user_data; 217 | 218 | for (uint32_t c = 0; c < NUM_OF_COLS; ++c) { 219 | gpio_pull_up(col_pins[c]); 220 | gpio_put(col_pins[c], 0); 221 | gpio_set_dir(col_pins[c], GPIO_OUT); 222 | 223 | for (uint32_t r = 0; r < NUM_OF_ROWS; ++r) { 224 | const bool pressed = (gpio_get(row_pins[r]) == 0); 225 | const int32_t key_idx = (int32_t)((r * NUM_OF_COLS) + c); 226 | 227 | int32_t list_idx = -1; 228 | for (int32_t i = 0; i < LIST_SIZE; ++i) { 229 | if (self.list[i].p_entry != &((const struct entry*)kbd_entries)[key_idx]) 230 | continue; 231 | 232 | list_idx = i; 233 | break; 234 | } 235 | 236 | if (list_idx > -1) { 237 | next_item_state(&self.list[list_idx], pressed); 238 | continue; 239 | } 240 | 241 | if (!pressed) 242 | continue; 243 | 244 | for (uint32_t i = 0 ; i < LIST_SIZE; ++i) { 245 | if (self.list[i].p_entry != NULL) 246 | continue; 247 | 248 | self.list[i].p_entry = &((const struct entry*)kbd_entries)[key_idx]; 249 | self.list[i].effective_key = '\0'; 250 | self.list[i].state = KEY_STATE_IDLE; 251 | next_item_state(&self.list[i], pressed); 252 | 253 | break; 254 | } 255 | } 256 | 257 | gpio_put(col_pins[c], 1); 258 | gpio_disable_pulls(col_pins[c]); 259 | gpio_set_dir(col_pins[c], GPIO_IN); 260 | } 261 | 262 | #if NUM_OF_BTNS > 0 263 | for (uint32_t b = 0; b < NUM_OF_BTNS; ++b) { 264 | const bool pressed = (gpio_get(btn_pins[b]) == 0); 265 | 266 | int32_t list_idx = -1; 267 | for (int32_t i = 0; i < LIST_SIZE; ++i) { 268 | if (self.list[i].p_entry != &((const struct entry*)btn_entries)[b]) 269 | continue; 270 | 271 | list_idx = i; 272 | break; 273 | } 274 | 275 | if (list_idx > -1) { 276 | next_item_state(&self.list[list_idx], pressed); 277 | continue; 278 | } 279 | 280 | if (!pressed) 281 | continue; 282 | 283 | for (uint32_t i = 0 ; i < LIST_SIZE; ++i) { 284 | if (self.list[i].p_entry != NULL) 285 | continue; 286 | 287 | self.list[i].p_entry = &((const struct entry*)btn_entries)[b]; 288 | self.list[i].effective_key = '\0'; 289 | self.list[i].state = KEY_STATE_IDLE; 290 | next_item_state(&self.list[i], pressed); 291 | 292 | break; 293 | } 294 | } 295 | #endif 296 | 297 | // negative value means interval since last alarm time 298 | return -(reg_get_value(REG_ID_FRQ) * 1000); 299 | } 300 | 301 | void keyboard_inject_event(char key, enum key_state state) 302 | { 303 | const struct fifo_item item = { key, state }; 304 | if (!fifo_enqueue(item)) { 305 | if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_INT)) 306 | reg_set_bit(REG_ID_INT, INT_OVERFLOW); 307 | 308 | if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_ON)) 309 | fifo_enqueue_force(item); 310 | } 311 | 312 | struct key_callback *cb = self.key_callbacks; 313 | while (cb) { 314 | cb->func(key, state); 315 | 316 | cb = cb->next; 317 | } 318 | } 319 | 320 | bool keyboard_is_key_down(char key) 321 | { 322 | for (int32_t i = 0; i < LIST_SIZE; ++i) { 323 | struct list_item *item = &self.list[i]; 324 | 325 | if (item->p_entry == NULL) 326 | continue; 327 | 328 | if ((item->state != KEY_STATE_PRESSED) && (item->state != KEY_STATE_HOLD)) 329 | continue; 330 | 331 | if (item->effective_key != key) 332 | continue; 333 | 334 | return true; 335 | } 336 | 337 | return false; 338 | } 339 | 340 | bool keyboard_is_mod_on(enum key_mod mod) 341 | { 342 | return self.mods[mod]; 343 | } 344 | 345 | void keyboard_add_key_callback(struct key_callback *callback) 346 | { 347 | // first callback 348 | if (!self.key_callbacks) { 349 | self.key_callbacks = callback; 350 | return; 351 | } 352 | 353 | // find last and insert after 354 | struct key_callback *cb = self.key_callbacks; 355 | while (cb->next) 356 | cb = cb->next; 357 | 358 | cb->next = callback; 359 | } 360 | 361 | void keyboard_add_lock_callback(struct key_lock_callback *callback) 362 | { 363 | // first callback 364 | if (!self.lock_callbacks) { 365 | self.lock_callbacks = callback; 366 | return; 367 | } 368 | 369 | // find last and insert after 370 | struct key_lock_callback *cb = self.lock_callbacks; 371 | while (cb->next) 372 | cb = cb->next; 373 | 374 | cb->next = callback; 375 | } 376 | 377 | bool keyboard_get_capslock(void) 378 | { 379 | return self.capslock; 380 | } 381 | 382 | bool keyboard_get_numlock(void) 383 | { 384 | return self.numlock; 385 | } 386 | 387 | void keyboard_init(void) 388 | { 389 | for (int i = 0; i < KEY_MOD_ID_LAST; ++i) 390 | self.mods[i] = false; 391 | 392 | // rows 393 | for (uint32_t i = 0; i < NUM_OF_ROWS; ++i) { 394 | gpio_init(row_pins[i]); 395 | gpio_pull_up(row_pins[i]); 396 | gpio_set_dir(row_pins[i], GPIO_IN); 397 | } 398 | 399 | // cols 400 | for(uint32_t i = 0; i < NUM_OF_COLS; ++i) { 401 | gpio_init(col_pins[i]); 402 | gpio_set_dir(col_pins[i], GPIO_IN); 403 | } 404 | 405 | // btns 406 | #if NUM_OF_BTNS > 0 407 | for(uint32_t i = 0; i < NUM_OF_BTNS; ++i) { 408 | gpio_init(btn_pins[i]); 409 | gpio_pull_up(btn_pins[i]); 410 | gpio_set_dir(btn_pins[i], GPIO_IN); 411 | } 412 | #endif 413 | 414 | add_alarm_in_ms(reg_get_value(REG_ID_FRQ), timer_task, NULL, true); 415 | } 416 | -------------------------------------------------------------------------------- /app/keyboard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | enum key_state 7 | { 8 | KEY_STATE_IDLE = 0, 9 | KEY_STATE_PRESSED, 10 | KEY_STATE_HOLD, 11 | KEY_STATE_RELEASED, 12 | }; 13 | 14 | enum key_mod 15 | { 16 | KEY_MOD_ID_NONE = 0, 17 | KEY_MOD_ID_SYM, 18 | KEY_MOD_ID_ALT, 19 | KEY_MOD_ID_SHL, 20 | KEY_MOD_ID_SHR, 21 | 22 | KEY_MOD_ID_LAST, 23 | }; 24 | 25 | #define KEY_JOY_UP 0x01 26 | #define KEY_JOY_DOWN 0x02 27 | #define KEY_JOY_LEFT 0x03 28 | #define KEY_JOY_RIGHT 0x04 29 | #define KEY_JOY_CENTER 0x05 30 | #define KEY_BTN_LEFT1 0x06 31 | #define KEY_BTN_RIGHT1 0x07 32 | // 0x08 - BACKSPACE 33 | // 0x09 - TAB 34 | // 0x0A - NEW LINE 35 | // 0x0D - CARRIAGE RETURN 36 | #define KEY_BTN_LEFT2 0x11 37 | #define KEY_BTN_RIGHT2 0x12 38 | 39 | #define KEY_MOD_ALT 0x1A 40 | #define KEY_MOD_SHL 0x1B // Left Shift 41 | #define KEY_MOD_SHR 0x1C // Right Shift 42 | #define KEY_MOD_SYM 0x1D 43 | 44 | struct key_callback 45 | { 46 | void (*func)(char, enum key_state); 47 | struct key_callback *next; 48 | }; 49 | 50 | struct key_lock_callback 51 | { 52 | void (*func)(bool, bool); 53 | struct key_lock_callback *next; 54 | }; 55 | 56 | void keyboard_inject_event(char key, enum key_state state); 57 | 58 | bool keyboard_is_key_down(char key); 59 | bool keyboard_is_mod_on(enum key_mod mod); 60 | 61 | void keyboard_add_key_callback(struct key_callback *callback); 62 | void keyboard_add_lock_callback(struct key_lock_callback *callback); 63 | 64 | bool keyboard_get_capslock(void); 65 | bool keyboard_get_numlock(void); 66 | 67 | void keyboard_init(void); 68 | -------------------------------------------------------------------------------- /app/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "backlight.h" 6 | #include "debug.h" 7 | #include "gpioexp.h" 8 | #include "interrupt.h" 9 | #include "keyboard.h" 10 | #include "puppet_i2c.h" 11 | #include "reg.h" 12 | #include "touchpad.h" 13 | #include "usb.h" 14 | 15 | // since the SDK doesn't support per-GPIO irq, we use this global irq and forward it 16 | static void gpio_irq(uint gpio, uint32_t events) 17 | { 18 | // printf("%s: gpio %d, events 0x%02X\r\n", __func__, gpio, events); 19 | touchpad_gpio_irq(gpio, events); 20 | gpioexp_gpio_irq(gpio, events); 21 | } 22 | 23 | // TODO: Microphone 24 | int main(void) 25 | { 26 | // The here order is important because it determines callback call order 27 | usb_init(); 28 | 29 | #ifndef NDEBUG 30 | debug_init(); 31 | #endif 32 | 33 | reg_init(); 34 | 35 | backlight_init(); 36 | 37 | gpioexp_init(); 38 | 39 | keyboard_init(); 40 | 41 | touchpad_init(); 42 | 43 | interrupt_init(); 44 | 45 | puppet_i2c_init(); 46 | 47 | // For now, the `gpio` param is ignored and all enabled GPIOs generate the irq 48 | gpio_set_irq_enabled_with_callback(0xFF, 0, true, &gpio_irq); 49 | 50 | #ifndef NDEBUG 51 | printf("Starting main loop\r\n"); 52 | #endif 53 | 54 | while (true) { 55 | __wfe(); 56 | } 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /app/puppet_i2c.c: -------------------------------------------------------------------------------- 1 | #include "puppet_i2c.h" 2 | 3 | #include "reg.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define REG_ID_INVALID 0x00 10 | 11 | static i2c_inst_t *i2c_instances[2] = { i2c0, i2c1 }; 12 | 13 | static struct 14 | { 15 | i2c_inst_t *i2c; 16 | 17 | struct 18 | { 19 | uint8_t reg; 20 | uint8_t data; 21 | } read_buffer; 22 | 23 | uint8_t write_buffer[2]; 24 | uint8_t write_len; 25 | } self; 26 | 27 | static void irq_handler(void) 28 | { 29 | // the controller sent data 30 | if (self.i2c->hw->intr_stat & I2C_IC_INTR_MASK_M_RX_FULL_BITS) { 31 | if (self.read_buffer.reg == REG_ID_INVALID) { 32 | self.read_buffer.reg = self.i2c->hw->data_cmd & 0xff; 33 | 34 | if (self.read_buffer.reg & PACKET_WRITE_MASK) { 35 | // it'sq a reg write, we need to wait for the second byte before we process 36 | return; 37 | } 38 | } else { 39 | self.read_buffer.data = self.i2c->hw->data_cmd & 0xff; 40 | } 41 | 42 | reg_process_packet(self.read_buffer.reg, self.read_buffer.data, self.write_buffer, &self.write_len); 43 | 44 | // ready for the next operation 45 | self.read_buffer.reg = REG_ID_INVALID; 46 | 47 | return; 48 | } 49 | 50 | // the controller requested a read 51 | if (self.i2c->hw->intr_stat & I2C_IC_INTR_MASK_M_RD_REQ_BITS) { 52 | i2c_write_raw_blocking(self.i2c, self.write_buffer, self.write_len); 53 | 54 | self.i2c->hw->clr_rd_req; 55 | return; 56 | } 57 | } 58 | 59 | void puppet_i2c_sync_address(void) 60 | { 61 | i2c_set_slave_mode(self.i2c, true, reg_get_value(REG_ID_ADR)); 62 | } 63 | 64 | void puppet_i2c_init(void) 65 | { 66 | // determine the instance based on SCL pin, hope you didn't screw up the SDA pin! 67 | self.i2c = i2c_instances[(PIN_PUPPET_SCL / 2) % 2]; 68 | 69 | i2c_init(self.i2c, 100 * 1000); 70 | puppet_i2c_sync_address(); 71 | 72 | gpio_set_function(PIN_PUPPET_SDA, GPIO_FUNC_I2C); 73 | gpio_pull_up(PIN_PUPPET_SDA); 74 | 75 | gpio_set_function(PIN_PUPPET_SCL, GPIO_FUNC_I2C); 76 | gpio_pull_up(PIN_PUPPET_SCL); 77 | 78 | // irq when the controller sends data, and when it requests a read 79 | self.i2c->hw->intr_mask = I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_INTR_MASK_M_RX_FULL_BITS; 80 | 81 | const int irq = I2C0_IRQ + i2c_hw_index(self.i2c); 82 | irq_set_exclusive_handler(irq, irq_handler); 83 | irq_set_enabled(irq, true); 84 | } 85 | -------------------------------------------------------------------------------- /app/puppet_i2c.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void puppet_i2c_sync_address(void); 4 | 5 | void puppet_i2c_init(void); 6 | -------------------------------------------------------------------------------- /app/reg.c: -------------------------------------------------------------------------------- 1 | #include "reg.h" 2 | 3 | #include "app_config.h" 4 | #include "backlight.h" 5 | #include "fifo.h" 6 | #include "gpioexp.h" 7 | #include "puppet_i2c.h" 8 | #include "keyboard.h" 9 | #include "touchpad.h" 10 | 11 | #include 12 | #include // TODO: When there's more than one RP chip, change this to be more generic 13 | #include 14 | 15 | // We don't enable this by default cause it spams quite a lot 16 | //#define DEBUG_REGS 17 | 18 | static struct 19 | { 20 | uint8_t regs[REG_ID_LAST]; 21 | } self; 22 | 23 | static void touch_cb(int8_t x, int8_t y) 24 | { 25 | const int16_t dx = (int8_t)self.regs[REG_ID_TOX] + x; 26 | const int16_t dy = (int8_t)self.regs[REG_ID_TOY] + y; 27 | 28 | // bind to -128 to 127 29 | self.regs[REG_ID_TOX] = MAX(INT8_MIN, MIN(dx, INT8_MAX)); 30 | self.regs[REG_ID_TOY] = MAX(INT8_MIN, MIN(dy, INT8_MAX)); 31 | } 32 | static struct touch_callback touch_callback = { .func = touch_cb }; 33 | 34 | void reg_process_packet(uint8_t in_reg, uint8_t in_data, uint8_t *out_buffer, uint8_t *out_len) 35 | { 36 | const bool is_write = (in_reg & PACKET_WRITE_MASK); 37 | const uint8_t reg = (in_reg & ~PACKET_WRITE_MASK); 38 | 39 | // printf("read complete, is_write: %d, reg: 0x%02X\r\n", is_write, reg); 40 | 41 | *out_len = 0; 42 | 43 | switch (reg) { 44 | 45 | // common R/W registers 46 | case REG_ID_CFG: 47 | case REG_ID_INT: 48 | case REG_ID_DEB: 49 | case REG_ID_FRQ: 50 | case REG_ID_BKL: 51 | case REG_ID_BK2: 52 | case REG_ID_GIC: 53 | case REG_ID_GIN: 54 | case REG_ID_HLD: 55 | case REG_ID_ADR: 56 | case REG_ID_IND: 57 | case REG_ID_CF2: 58 | { 59 | if (is_write) { 60 | reg_set_value(reg, in_data); 61 | 62 | switch (reg) { 63 | case REG_ID_BKL: 64 | case REG_ID_BK2: 65 | backlight_sync(); 66 | break; 67 | 68 | case REG_ID_ADR: 69 | puppet_i2c_sync_address(); 70 | break; 71 | 72 | default: 73 | break; 74 | } 75 | } else { 76 | out_buffer[0] = reg_get_value(reg); 77 | *out_len = sizeof(uint8_t); 78 | } 79 | break; 80 | } 81 | 82 | // special R/W registers 83 | case REG_ID_DIR: // gpio direction 84 | case REG_ID_PUE: // gpio input pull enable 85 | case REG_ID_PUD: // gpio input pull direction 86 | { 87 | if (is_write) { 88 | switch (reg) { 89 | case REG_ID_DIR: 90 | gpioexp_update_dir(in_data); 91 | break; 92 | case REG_ID_PUE: 93 | gpioexp_update_pue_pud(in_data, reg_get_value(REG_ID_PUD)); 94 | break; 95 | case REG_ID_PUD: 96 | gpioexp_update_pue_pud(reg_get_value(REG_ID_PUE), in_data); 97 | break; 98 | } 99 | } else { 100 | out_buffer[0] = reg_get_value(reg); 101 | *out_len = sizeof(uint8_t); 102 | } 103 | break; 104 | } 105 | 106 | case REG_ID_GIO: // gpio value 107 | { 108 | if (is_write) { 109 | gpioexp_set_value(in_data); 110 | } else { 111 | out_buffer[0] = gpioexp_get_value(); 112 | *out_len = sizeof(uint8_t); 113 | } 114 | break; 115 | } 116 | 117 | // read-only registers 118 | case REG_ID_TOX: 119 | case REG_ID_TOY: 120 | out_buffer[0] = reg_get_value(reg); 121 | *out_len = sizeof(uint8_t); 122 | 123 | reg_set_value(reg, 0); 124 | break; 125 | 126 | case REG_ID_VER: 127 | out_buffer[0] = VER_VAL; 128 | *out_len = sizeof(uint8_t); 129 | break; 130 | 131 | case REG_ID_KEY: 132 | out_buffer[0] = fifo_count(); 133 | out_buffer[0] |= keyboard_get_numlock() ? KEY_NUMLOCK : 0x00; 134 | out_buffer[0] |= keyboard_get_capslock() ? KEY_CAPSLOCK : 0x00; 135 | *out_len = sizeof(uint8_t); 136 | break; 137 | 138 | case REG_ID_FIF: 139 | { 140 | const struct fifo_item item = fifo_dequeue(); 141 | 142 | out_buffer[0] = (uint8_t)item.state; 143 | out_buffer[1] = (uint8_t)item.key; 144 | *out_len = sizeof(uint8_t) * 2; 145 | break; 146 | } 147 | 148 | case REG_ID_RST: 149 | NVIC_SystemReset(); 150 | break; 151 | } 152 | } 153 | 154 | uint8_t reg_get_value(enum reg_id reg) 155 | { 156 | return self.regs[reg]; 157 | } 158 | 159 | void reg_set_value(enum reg_id reg, uint8_t value) 160 | { 161 | #ifdef DEBUG_REGS 162 | printf("%s: reg: 0x%02X, val: 0x%02X (%d)\r\n", __func__, reg, value, value); 163 | #endif 164 | 165 | self.regs[reg] = value; 166 | } 167 | 168 | bool reg_is_bit_set(enum reg_id reg, uint8_t bit) 169 | { 170 | return self.regs[reg] & bit; 171 | } 172 | 173 | void reg_set_bit(enum reg_id reg, uint8_t bit) 174 | { 175 | #ifdef DEBUG_REGS 176 | printf("%s: reg: 0x%02X, bit: %d\r\n", __func__, reg, bit); 177 | #endif 178 | 179 | self.regs[reg] |= bit; 180 | } 181 | 182 | void reg_clear_bit(enum reg_id reg, uint8_t bit) 183 | { 184 | #ifdef DEBUG_REGS 185 | printf("%s: reg: 0x%02X, bit: %d\r\n", __func__, reg, bit); 186 | #endif 187 | 188 | self.regs[reg] &= ~bit; 189 | } 190 | 191 | void reg_init(void) 192 | { 193 | reg_set_value(REG_ID_CFG, CFG_OVERFLOW_INT | CFG_KEY_INT | CFG_USE_MODS); 194 | reg_set_value(REG_ID_BKL, 255); 195 | reg_set_value(REG_ID_DEB, 10); 196 | reg_set_value(REG_ID_FRQ, 10); // ms 197 | reg_set_value(REG_ID_BK2, 255); 198 | reg_set_value(REG_ID_PUD, 0xFF); 199 | reg_set_value(REG_ID_HLD, 30); // 10ms units 200 | reg_set_value(REG_ID_ADR, 0x1F); 201 | reg_set_value(REG_ID_IND, 1); // ms 202 | reg_set_value(REG_ID_CF2, CF2_TOUCH_INT | CF2_USB_KEYB_ON | CF2_USB_MOUSE_ON); 203 | 204 | touchpad_add_touch_callback(&touch_callback); 205 | } 206 | -------------------------------------------------------------------------------- /app/reg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | enum reg_id 7 | { 8 | REG_ID_VER = 0x01, // fw version 9 | REG_ID_CFG = 0x02, // config 10 | REG_ID_INT = 0x03, // interrupt status 11 | REG_ID_KEY = 0x04, // key status 12 | REG_ID_BKL = 0x05, // backlight 13 | REG_ID_DEB = 0x06, // key debounce cfg (not implemented) 14 | REG_ID_FRQ = 0x07, // key poll freq cfg 15 | REG_ID_RST = 0x08, // trigger a reset 16 | REG_ID_FIF = 0x09, // key fifo 17 | REG_ID_BK2 = 0x0A, // backlight 2 18 | REG_ID_DIR = 0x0B, // gpio direction 19 | REG_ID_PUE = 0x0C, // gpio input pull enable 20 | REG_ID_PUD = 0x0D, // gpio input pull direction 21 | REG_ID_GIO = 0x0E, // gpio value 22 | REG_ID_GIC = 0x0F, // gpio interrupt config 23 | REG_ID_GIN = 0x10, // gpio interrupt status 24 | REG_ID_HLD = 0x11, // key hold time cfg (in 10ms units) 25 | REG_ID_ADR = 0x12, // i2c puppet address 26 | REG_ID_IND = 0x13, // interrupt pin assert duration 27 | REG_ID_CF2 = 0x14, // config 2 28 | REG_ID_TOX = 0x15, // touch delta x since last read, at most (-128 to 127) 29 | REG_ID_TOY = 0x16, // touch delta y since last read, at most (-128 to 127) 30 | 31 | REG_ID_LAST, 32 | }; 33 | 34 | #define CFG_OVERFLOW_ON (1 << 0) // Should new FIFO entries overwrite oldest ones if FIFO is full 35 | #define CFG_OVERFLOW_INT (1 << 1) // Should FIFO overflow generate an interrupt 36 | #define CFG_CAPSLOCK_INT (1 << 2) // Should toggling caps lock generate interrupts 37 | #define CFG_NUMLOCK_INT (1 << 3) // Should toggling num lock generate interrupts 38 | #define CFG_KEY_INT (1 << 4) // Should key events generate interrupts 39 | #define CFG_PANIC_INT (1 << 5) // Not implemented 40 | #define CFG_REPORT_MODS (1 << 6) // Should Alt, Sym and Shifts be reported as well 41 | #define CFG_USE_MODS (1 << 7) // Should Alt, Sym and Shifts modify the keys reported 42 | 43 | #define CF2_TOUCH_INT (1 << 0) // Should touch events generate interrupts 44 | #define CF2_USB_KEYB_ON (1 << 1) // Should key events be sent over USB HID 45 | #define CF2_USB_MOUSE_ON (1 << 2) // Should touch events be sent over USB HID 46 | // TODO? CF2_STICKY_MODS // Pressing and releasing a mod affects next key pressed 47 | 48 | #define INT_OVERFLOW (1 << 0) 49 | #define INT_CAPSLOCK (1 << 1) 50 | #define INT_NUMLOCK (1 << 2) 51 | #define INT_KEY (1 << 3) 52 | #define INT_PANIC (1 << 4) 53 | #define INT_GPIO (1 << 5) 54 | #define INT_TOUCH (1 << 6) 55 | // Future me: If we need more INT_*, add a INT2 and use (1 << 7) here as indicator that the info is in INT2 56 | 57 | #define KEY_CAPSLOCK (1 << 5) // Caps lock status 58 | #define KEY_NUMLOCK (1 << 6) // Num lock status 59 | #define KEY_COUNT_MASK 0x1F 60 | 61 | #define DIR_OUTPUT 0 62 | #define DIR_INPUT 1 63 | 64 | #define PUD_DOWN 0 65 | #define PUD_UP 1 66 | 67 | #define VER_VAL ((VERSION_MAJOR << 4) | (VERSION_MINOR << 0)) 68 | 69 | #define PACKET_WRITE_MASK (1 << 7) 70 | 71 | void reg_process_packet(uint8_t in_reg, uint8_t in_data, uint8_t *out_buffer, uint8_t *out_len); 72 | 73 | uint8_t reg_get_value(enum reg_id reg); 74 | void reg_set_value(enum reg_id reg, uint8_t value); 75 | 76 | bool reg_is_bit_set(enum reg_id reg, uint8_t bit); 77 | void reg_set_bit(enum reg_id reg, uint8_t bit); 78 | void reg_clear_bit(enum reg_id reg, uint8_t bit); 79 | 80 | void reg_init(void); 81 | -------------------------------------------------------------------------------- /app/touchpad.c: -------------------------------------------------------------------------------- 1 | #include "touchpad.h" 2 | 3 | #include "keyboard.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define DEV_ADDR 0x3B 11 | 12 | #define REG_PID 0x00 13 | #define REG_REV 0x01 14 | #define REG_MOTION 0x02 15 | #define REG_DELTA_X 0x03 16 | #define REG_DELTA_Y 0x04 17 | #define REG_DELTA_XY_H 0x05 18 | #define REG_CONFIG 0x11 19 | #define REG_OBSERV 0x2E 20 | #define REG_MBURST 0x42 21 | 22 | #define BIT_MOTION_MOT (1 << 7) 23 | #define BIT_MOTION_OVF (1 << 4) 24 | 25 | #define BIT_CONFIG_HIRES (1 << 7) 26 | 27 | #define BIT_OBSERV_RUN (0 << 6) 28 | #define BIT_OBSERV_REST1 (1 << 6) 29 | #define BIT_OBSERV_REST2 (2 << 6) 30 | #define BIT_OBSERV_REST3 (3 << 6) 31 | 32 | #define SWIPE_COOLDOWN_TIME_MS 100 // time to wait before generating a new swipe event 33 | #define SWIPE_RELEASE_DELAY_MS 10 // time to wait before sending key release event 34 | #define MOTION_IS_SWIPE(i, j) (((i >= 15) || (i <= -15)) && ((j >= -5) && (j <= 5))) 35 | 36 | static i2c_inst_t *i2c_instances[2] = { i2c0, i2c1 }; 37 | 38 | static struct 39 | { 40 | struct touch_callback *callbacks; 41 | uint32_t last_swipe_time; 42 | i2c_inst_t *i2c; 43 | } self; 44 | 45 | static uint8_t read_register8(uint8_t reg) 46 | { 47 | uint8_t val; 48 | 49 | i2c_write_blocking(self.i2c, DEV_ADDR, ®, sizeof(reg), true); 50 | i2c_read_blocking(self.i2c, DEV_ADDR, &val, sizeof(val), false); 51 | 52 | return val; 53 | } 54 | 55 | //static void write_register8(uint8_t reg, uint8_t val) 56 | //{ 57 | // uint8_t buffer[2] = { reg, val }; 58 | // i2c_write_blocking(self.i2c, DEV_ADDR, buffer, sizeof(buffer), false); 59 | //} 60 | 61 | int64_t release_key(alarm_id_t id, void *user_data) 62 | { 63 | (void)id; 64 | 65 | const int data = (int)user_data; 66 | 67 | keyboard_inject_event((char)data, KEY_STATE_RELEASED); 68 | 69 | return 0; 70 | } 71 | 72 | void touchpad_gpio_irq(uint gpio, uint32_t events) 73 | { 74 | if (gpio != PIN_TP_MOTION) 75 | return; 76 | 77 | if (!(events & GPIO_IRQ_EDGE_FALL)) 78 | return; 79 | 80 | const uint8_t motion = read_register8(REG_MOTION); 81 | if (motion & BIT_MOTION_MOT) { 82 | int8_t x = read_register8(REG_DELTA_X); 83 | int8_t y = read_register8(REG_DELTA_Y); 84 | 85 | x = ((x < 127) ? x : (x - 256)) * -1; 86 | y = ((y < 127) ? y : (y - 256)); 87 | 88 | if (keyboard_is_mod_on(KEY_MOD_ID_ALT)) { 89 | if (to_ms_since_boot(get_absolute_time()) - self.last_swipe_time > SWIPE_COOLDOWN_TIME_MS) { 90 | char key = '\0'; 91 | if (MOTION_IS_SWIPE(y, x)) { 92 | key = (y < 0) ? KEY_JOY_UP : KEY_JOY_DOWN; 93 | } else if (MOTION_IS_SWIPE(x, y)) { 94 | key = (x < 0) ? KEY_JOY_LEFT : KEY_JOY_RIGHT; 95 | } 96 | 97 | if (key != '\0') { 98 | keyboard_inject_event(key, KEY_STATE_PRESSED); 99 | 100 | // we need to allow the usb a bit of time to send the press, so schedule the release after a bit 101 | add_alarm_in_ms(SWIPE_RELEASE_DELAY_MS, release_key, (void*)(int)key, true); 102 | 103 | self.last_swipe_time = to_ms_since_boot(get_absolute_time()); 104 | } 105 | } 106 | } else { 107 | if (self.callbacks) { 108 | struct touch_callback *cb = self.callbacks; 109 | 110 | while (cb) { 111 | cb->func(x, y); 112 | 113 | cb = cb->next; 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | void touchpad_add_touch_callback(struct touch_callback *callback) 121 | { 122 | // first callback 123 | if (!self.callbacks) { 124 | self.callbacks = callback; 125 | return; 126 | } 127 | 128 | // find last and insert after 129 | struct touch_callback *cb = self.callbacks; 130 | while (cb->next) 131 | cb = cb->next; 132 | 133 | cb->next = callback; 134 | } 135 | 136 | void touchpad_init(void) 137 | { 138 | // determine the instance based on SCL pin, hope you didn't screw up the SDA pin! 139 | self.i2c = i2c_instances[(PIN_SCL / 2) % 2]; 140 | 141 | i2c_init(self.i2c, 100 * 1000); 142 | 143 | gpio_set_function(PIN_SDA, GPIO_FUNC_I2C); 144 | gpio_pull_up(PIN_SDA); 145 | 146 | gpio_set_function(PIN_SCL, GPIO_FUNC_I2C); 147 | gpio_pull_up(PIN_SCL); 148 | 149 | // Make the I2C pins available to picotool 150 | bi_decl(bi_2pins_with_func(PIN_SDA, PIN_SCL, GPIO_FUNC_I2C)); 151 | 152 | gpio_init(PIN_TP_SHUTDOWN); 153 | gpio_set_dir(PIN_TP_SHUTDOWN, GPIO_OUT); 154 | gpio_put(PIN_TP_SHUTDOWN, 0); 155 | 156 | gpio_init(PIN_TP_MOTION); 157 | gpio_set_dir(PIN_TP_MOTION, GPIO_IN); 158 | gpio_set_irq_enabled(PIN_TP_MOTION, GPIO_IRQ_EDGE_FALL, true); 159 | 160 | gpio_init(PIN_TP_RESET); 161 | gpio_set_dir(PIN_TP_RESET, GPIO_OUT); 162 | 163 | gpio_put(PIN_TP_RESET, 0); 164 | sleep_ms(100); 165 | gpio_put(PIN_TP_RESET, 1); 166 | } 167 | -------------------------------------------------------------------------------- /app/touchpad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct touch_callback 7 | { 8 | void (*func)(int8_t, int8_t); 9 | struct touch_callback *next; 10 | }; 11 | 12 | void touchpad_gpio_irq(uint gpio, uint32_t events); 13 | 14 | void touchpad_add_touch_callback(struct touch_callback *callback); 15 | 16 | void touchpad_init(void); 17 | -------------------------------------------------------------------------------- /app/tusb_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum 4 | { 5 | USB_ITF_KEYBOARD = 0, 6 | USB_ITF_MOUSE, 7 | // USB_ITF_HID_GENERIC, 8 | USB_ITF_CDC, 9 | USB_ITF_CDC2, 10 | USB_ITF_VENDOR, 11 | USB_ITF_MAX, 12 | }; 13 | 14 | #define BOARD_DEVICE_RHPORT_NUM 0 15 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED 16 | 17 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 18 | 19 | #define CFG_TUSB_MEM_SECTION 20 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 21 | 22 | #define CFG_TUD_ENDPOINT0_SIZE 64 23 | 24 | #define CFG_TUD_HID 2//3 25 | #define CFG_TUD_CDC 1 26 | #define CFG_TUD_MSC 0 27 | #define CFG_TUD_MIDI 0 28 | #define CFG_TUD_VENDOR 1 29 | 30 | #define CFG_TUD_HID_EP_BUFSIZE 8 31 | 32 | #define CFG_TUD_CDC_RX_BUFSIZE 256 33 | #define CFG_TUD_CDC_TX_BUFSIZE 256 34 | 35 | #define CFG_TUD_VENDOR_RX_BUFSIZE 64 36 | #define CFG_TUD_VENDOR_TX_BUFSIZE 64 37 | -------------------------------------------------------------------------------- /app/usb.c: -------------------------------------------------------------------------------- 1 | #include "usb.h" 2 | 3 | #include "backlight.h" 4 | #include "keyboard.h" 5 | #include "touchpad.h" 6 | #include "reg.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define USB_LOW_PRIORITY_IRQ 31 13 | #define USB_TASK_INTERVAL_US 1000 14 | 15 | static struct 16 | { 17 | mutex_t mutex; 18 | bool mouse_moved; 19 | uint8_t mouse_btn; 20 | 21 | uint8_t write_buffer[2]; 22 | uint8_t write_len; 23 | } self; 24 | 25 | // TODO: What about Ctrl? 26 | // TODO: What should L1, L2, R1, R2 do 27 | // TODO: Should touch send arrow keys as an option? 28 | 29 | static void low_priority_worker_irq(void) 30 | { 31 | if (mutex_try_enter(&self.mutex, NULL)) { 32 | tud_task(); 33 | 34 | mutex_exit(&self.mutex); 35 | } 36 | } 37 | 38 | static int64_t timer_task(alarm_id_t id, void *user_data) 39 | { 40 | (void)id; 41 | (void)user_data; 42 | 43 | irq_set_pending(USB_LOW_PRIORITY_IRQ); 44 | 45 | return USB_TASK_INTERVAL_US; 46 | } 47 | 48 | static void key_cb(char key, enum key_state state) 49 | { 50 | // Don't send mods over USB 51 | if ((key == KEY_MOD_SHL) || 52 | (key == KEY_MOD_SHR) || 53 | (key == KEY_MOD_ALT) || 54 | (key == KEY_MOD_SYM)) 55 | return; 56 | 57 | if (tud_hid_n_ready(USB_ITF_KEYBOARD) && reg_is_bit_set(REG_ID_CF2, CF2_USB_KEYB_ON)) { 58 | uint8_t conv_table[128][2] = { HID_ASCII_TO_KEYCODE }; 59 | conv_table['\n'][1] = HID_KEY_ENTER; // Fixup: Enter instead of Return 60 | conv_table[KEY_JOY_UP][1] = HID_KEY_ARROW_UP; 61 | conv_table[KEY_JOY_DOWN][1] = HID_KEY_ARROW_DOWN; 62 | conv_table[KEY_JOY_LEFT][1] = HID_KEY_ARROW_LEFT; 63 | conv_table[KEY_JOY_RIGHT][1] = HID_KEY_ARROW_RIGHT; 64 | 65 | uint8_t keycode[6] = { 0 }; 66 | uint8_t modifier = 0; 67 | 68 | if (state == KEY_STATE_PRESSED) { 69 | if (conv_table[(int)key][0]) 70 | modifier = KEYBOARD_MODIFIER_LEFTSHIFT; 71 | 72 | keycode[0] = conv_table[(int)key][1]; 73 | } 74 | 75 | if (state != KEY_STATE_HOLD) 76 | tud_hid_n_keyboard_report(USB_ITF_KEYBOARD, 0, modifier, keycode); 77 | } 78 | 79 | if (tud_hid_n_ready(USB_ITF_MOUSE) && reg_is_bit_set(REG_ID_CF2, CF2_USB_MOUSE_ON)) { 80 | if (key == KEY_JOY_CENTER) { 81 | if (state == KEY_STATE_PRESSED) { 82 | self.mouse_btn = MOUSE_BUTTON_LEFT; 83 | self.mouse_moved = false; 84 | tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, MOUSE_BUTTON_LEFT, 0, 0, 0, 0); 85 | } else if ((state == KEY_STATE_HOLD) && !self.mouse_moved) { 86 | self.mouse_btn = MOUSE_BUTTON_RIGHT; 87 | tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, MOUSE_BUTTON_RIGHT, 0, 0, 0, 0); 88 | } else if (state == KEY_STATE_RELEASED) { 89 | self.mouse_btn = 0x00; 90 | tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, 0x00, 0, 0, 0, 0); 91 | } 92 | } 93 | } 94 | } 95 | static struct key_callback key_callback = { .func = key_cb }; 96 | 97 | static void touch_cb(int8_t x, int8_t y) 98 | { 99 | if (!tud_hid_n_ready(USB_ITF_MOUSE) || !reg_is_bit_set(REG_ID_CF2, CF2_USB_MOUSE_ON)) 100 | return; 101 | 102 | self.mouse_moved = true; 103 | 104 | tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, self.mouse_btn, x, y, 0, 0); 105 | } 106 | static struct touch_callback touch_callback = { .func = touch_cb }; 107 | 108 | uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) 109 | { 110 | // TODO not Implemented 111 | (void)itf; 112 | (void)report_id; 113 | (void)report_type; 114 | (void)buffer; 115 | (void)reqlen; 116 | 117 | return 0; 118 | } 119 | 120 | void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t len) 121 | { 122 | // TODO set LED based on CAPLOCK, NUMLOCK etc... 123 | (void)itf; 124 | (void)report_id; 125 | (void)report_type; 126 | (void)buffer; 127 | (void)len; 128 | } 129 | 130 | void tud_vendor_rx_cb(uint8_t itf) 131 | { 132 | // printf("%s: itf: %d, avail: %d\r\n", __func__, itf, tud_vendor_n_available(itf)); 133 | 134 | uint8_t buff[64] = { 0 }; 135 | tud_vendor_n_read(itf, buff, 64); 136 | // printf("%s: %02X %02X %02X\r\n", __func__, buff[0], buff[1], buff[2]); 137 | 138 | reg_process_packet(buff[0], buff[1], self.write_buffer, &self.write_len); 139 | 140 | tud_vendor_n_write(itf, self.write_buffer, self.write_len); 141 | } 142 | 143 | void tud_mount_cb(void) 144 | { 145 | // Send mods over USB by default if USB connected 146 | reg_set_value(REG_ID_CFG, reg_get_value(REG_ID_CFG) | CFG_REPORT_MODS); 147 | } 148 | 149 | mutex_t *usb_get_mutex(void) 150 | { 151 | return &self.mutex; 152 | } 153 | 154 | void usb_init(void) 155 | { 156 | tusb_init(); 157 | 158 | keyboard_add_key_callback(&key_callback); 159 | 160 | touchpad_add_touch_callback(&touch_callback); 161 | 162 | // create a new interrupt that calls tud_task, and trigger that interrupt from a timer 163 | irq_set_exclusive_handler(USB_LOW_PRIORITY_IRQ, low_priority_worker_irq); 164 | irq_set_enabled(USB_LOW_PRIORITY_IRQ, true); 165 | 166 | mutex_init(&self.mutex); 167 | add_alarm_in_us(USB_TASK_INTERVAL_US, timer_task, NULL, true); 168 | } 169 | -------------------------------------------------------------------------------- /app/usb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct mutex mutex_t; 4 | 5 | mutex_t *usb_get_mutex(void); 6 | 7 | void usb_init(void); 8 | -------------------------------------------------------------------------------- /app/usb_descriptors.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_CDC_DESC_LEN) 4 | 5 | #define EPNUM_HID_KEYBOARD 0x81 6 | #define EPNUM_HID_MOUSE 0x82 7 | #define EPNUM_HID_GENERIC 0x83 8 | 9 | #define EPNUM_VENDOR_IN 0x84 10 | #define EPNUM_VENDOR_OUT 0x02 11 | 12 | #define EPNUM_CDC_CMD 0x85 13 | #define EPNUM_CDC_IN 0x86 14 | #define EPNUM_CDC_OUT 0x03 15 | 16 | #define CDC_CMD_MAX_SIZE 8 17 | #define CDC_IN_OUT_MAX_SIZE 64 18 | 19 | static uint16_t temp_string[32]; 20 | 21 | char const *string_descriptors[] = 22 | { 23 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 24 | "Solder Party", // 1: Manufacturer 25 | USB_PRODUCT, // 2: Product 26 | "123456", // 3: Serials, should use chip ID 27 | "Keyboard Interface", // 4: Interface 1 String 28 | "Mouse Interface", // 5: Interface 2 String 29 | "HID Interface", // 6: Interface 3 String 30 | "CDC Interface", // 7: Interface 4 String 31 | }; 32 | 33 | tusb_desc_device_t const device_descriptor = 34 | { 35 | .bLength = sizeof(tusb_desc_device_t), 36 | .bDescriptorType = TUSB_DESC_DEVICE, 37 | .bcdUSB = 0x0200, 38 | .bDeviceClass = 0x00, 39 | .bDeviceSubClass = 0x00, 40 | .bDeviceProtocol = 0x00, 41 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 42 | 43 | .idVendor = USB_VID, 44 | .idProduct = USB_PID, 45 | .bcdDevice = 0x1000, 46 | 47 | .iManufacturer = 0x01, 48 | .iProduct = 0x02, 49 | .iSerialNumber = 0x03, 50 | 51 | .bNumConfigurations = 0x01 52 | }; 53 | 54 | uint8_t const hid_keyboard_descriptor[] = 55 | { 56 | TUD_HID_REPORT_DESC_KEYBOARD() 57 | }; 58 | 59 | uint8_t const hid_mouse_descriptor[] = 60 | { 61 | TUD_HID_REPORT_DESC_MOUSE() 62 | }; 63 | 64 | uint8_t const config_descriptor[] = 65 | { 66 | TUD_CONFIG_DESCRIPTOR(1, USB_ITF_MAX, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), 67 | 68 | TUD_HID_DESCRIPTOR(USB_ITF_KEYBOARD, 4, HID_ITF_PROTOCOL_NONE, sizeof(hid_keyboard_descriptor), EPNUM_HID_KEYBOARD, CFG_TUD_HID_EP_BUFSIZE, 10), 69 | TUD_HID_DESCRIPTOR(USB_ITF_MOUSE, 5, HID_ITF_PROTOCOL_NONE, sizeof(hid_mouse_descriptor), EPNUM_HID_MOUSE, CFG_TUD_HID_EP_BUFSIZE, 10), 70 | 71 | TUD_VENDOR_DESCRIPTOR(USB_ITF_VENDOR, 7, EPNUM_VENDOR_OUT, EPNUM_VENDOR_IN, CFG_TUD_VENDOR_EPSIZE), 72 | 73 | TUD_CDC_DESCRIPTOR(USB_ITF_CDC, 7, EPNUM_CDC_CMD, CDC_CMD_MAX_SIZE, EPNUM_CDC_OUT, EPNUM_CDC_IN, CDC_IN_OUT_MAX_SIZE), 74 | }; 75 | 76 | uint8_t const *tud_descriptor_device_cb(void) 77 | { 78 | return (uint8_t const*)&device_descriptor; 79 | } 80 | 81 | uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) 82 | { 83 | if (itf == USB_ITF_KEYBOARD) 84 | return hid_keyboard_descriptor; 85 | 86 | if (itf == USB_ITF_MOUSE) 87 | return hid_mouse_descriptor; 88 | 89 | return NULL; 90 | } 91 | 92 | uint8_t const *tud_descriptor_configuration_cb(uint8_t index) 93 | { 94 | (void) index; 95 | 96 | return config_descriptor; 97 | } 98 | 99 | uint16_t const *tud_descriptor_string_cb(uint8_t idx, uint16_t langid) 100 | { 101 | (void) langid; 102 | 103 | if (idx == 0) { 104 | temp_string[0] = (TUSB_DESC_STRING << 8 ) | (2 * sizeof(uint16_t)); 105 | memcpy(&temp_string[1], string_descriptors[0], 2); 106 | return temp_string; 107 | } 108 | 109 | if (!(idx < sizeof(string_descriptors) / sizeof(string_descriptors[0]))) 110 | return NULL; 111 | 112 | const char *str = string_descriptors[idx]; 113 | uint8_t size = strlen(str); 114 | if (size > 31) 115 | size = 31; 116 | 117 | // Convert ASCII string into UTF-16 118 | for(uint8_t i = 0; i < size; ++i) 119 | temp_string[1 + i] = str[i]; 120 | 121 | temp_string[0] = (TUSB_DESC_STRING << 8 ) | ((size + 1) * sizeof(uint16_t)); 122 | 123 | return temp_string; 124 | } 125 | -------------------------------------------------------------------------------- /boards/bbq20kbd_breakout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define USB_VID 0x1209 4 | #define USB_PID 0xB182 5 | #define USB_PRODUCT "BBQ20KBD" 6 | 7 | #define PIN_INT 0 8 | #define PIN_BKL 25 9 | 10 | #define PIN_SDA 18 11 | #define PIN_SCL 23 12 | 13 | #define PIN_TP_RESET 16 14 | #define PIN_TP_MOTION 22 15 | #define PIN_TP_SHUTDOWN 24 16 | 17 | #define PIN_PUPPET_SDA 28 18 | #define PIN_PUPPET_SCL 29 19 | 20 | #define NUM_OF_ROWS 7 21 | #define PINS_ROWS \ 22 | 1, \ 23 | 2, \ 24 | 3, \ 25 | 4, \ 26 | 5, \ 27 | 6, \ 28 | 7 29 | 30 | #define NUM_OF_COLS 6 31 | #define PINS_COLS \ 32 | 8, \ 33 | 9, \ 34 | 14, \ 35 | 13, \ 36 | 12, \ 37 | 11 38 | 39 | #define NUM_OF_BTNS 1 40 | #define PINS_BTNS \ 41 | 10, 42 | #define BTN_KEYS \ 43 | { KEY_BTN_RIGHT2 }, 44 | 45 | #define PIN_GPIOEXP0 15 46 | #define PIN_GPIOEXP1 17 47 | #define PIN_GPIOEXP2 19 48 | #define PIN_GPIOEXP3 21 49 | #define PIN_GPIOEXP4 26 50 | 51 | #define PICO_DEFAULT_UART 1 52 | #define PICO_DEFAULT_UART_TX_PIN 20 53 | -------------------------------------------------------------------------------- /etc/99-i2c_puppet.rules: -------------------------------------------------------------------------------- 1 | # I2C Puppet devices 2 | 3 | # BBQ10KBD 4 | SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="b182", MODE:="0660", TAG+="uaccess" 5 | -------------------------------------------------------------------------------- /etc/i2c_puppet.py: -------------------------------------------------------------------------------- 1 | import usb 2 | 3 | 4 | _REG_VER = 0x01 # fw version 5 | _REG_CFG = 0x02 # config 6 | _REG_INT = 0x03 # interrupt status 7 | _REG_KEY = 0x04 # key status 8 | _REG_BKL = 0x05 # backlight 9 | _REG_DEB = 0x06 # debounce cfg 10 | _REG_FRQ = 0x07 # poll freq cfg 11 | _REG_RST = 0x08 # reset 12 | _REG_FIF = 0x09 # fifo 13 | _REG_BK2 = 0x0A # backlight 2 14 | _REG_DIR = 0x0B # gpio direction 15 | _REG_PUE = 0x0C # gpio input pull enable 16 | _REG_PUD = 0x0D # gpio input pull direction 17 | _REG_GIO = 0x0E # gpio value 18 | _REG_GIC = 0x0F # gpio interrupt config 19 | _REG_GIN = 0x10 # gpio interrupt status 20 | _REG_HLD = 0x11 # key hold time cfg (in 10ms units) 21 | _REG_ADR = 0x12 # i2c puppet address 22 | _REG_IND = 0x13 # interrupt pin assert duration 23 | _REG_CF2 = 0x14 # config 2 24 | _REG_TOX = 0x15 # touch delta x since last read, at most (-128 to 127) 25 | _REG_TOY = 0x16 # touch delta y since last read, at most (-128 to 127) 26 | 27 | _WRITE_MASK = 1 << 7 28 | 29 | CFG_OVERFLOW_ON = 1 << 0 30 | CFG_OVERFLOW_INT = 1 << 1 31 | CFG_CAPSLOCK_INT = 1 << 2 32 | CFG_NUMLOCK_INT = 1 << 3 33 | CFG_KEY_INT = 1 << 4 34 | CFG_PANIC_INT = 1 << 5 35 | CFG_REPORT_MODS = 1 << 6 36 | CFG_USE_MODS = 1 << 7 37 | 38 | CF2_TOUCH_INT = 1 << 0 39 | CF2_USB_KEYB_ON = 1 << 1 40 | CF2_USB_MOUSE_ON = 1 << 2 41 | 42 | INT_OVERFLOW = 1 << 0 43 | INT_CAPSLOCK = 1 << 1 44 | INT_NUMLOCK = 1 << 2 45 | INT_KEY = 1 << 3 46 | INT_PANIC = 1 << 4 47 | INT_GPIO = 1 << 5 48 | INT_TOUCH = 1 << 6 49 | 50 | KEY_CAPSLOCK = 1 << 5 51 | KEY_NUMLOCK = 1 << 6 52 | KEY_COUNT_MASK = 0x1F 53 | 54 | DIR_OUTPUT = 0 55 | DIR_INPUT = 1 56 | 57 | PUD_DOWN = 0 58 | PUD_UP = 1 59 | 60 | 61 | class I2CPuppet: 62 | def __init__(self, vid=0x1209, pid=0xB182): 63 | self._buffer = bytearray(2) 64 | self._dev = usb.core.find(idVendor=vid, idProduct=pid) 65 | 66 | if self._dev is None: 67 | raise Exception('Device with vid:pid %04X:%04X not found!' % (vid, pid)) 68 | 69 | conf = self._dev.get_active_configuration() 70 | itf = usb.util.find_descriptor(conf, bInterfaceClass=usb.CLASS_VENDOR_SPEC) 71 | 72 | self._ep_out = usb.util.find_descriptor(itf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT) 73 | self._ep_in = usb.util.find_descriptor(itf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN) 74 | 75 | if (self._ep_out is None) or (self._ep_in is None): 76 | raise Exception('Vendor IN or OUT endpoint not found!') 77 | 78 | @property 79 | def version(self): 80 | ver = self._read_register(_REG_VER) 81 | return (ver >> 4, ver & 0x0F) 82 | 83 | @property 84 | def status(self): 85 | return self._read_register(_REG_KEY) 86 | 87 | @property 88 | def backlight(self): 89 | return self._read_register(_REG_BKL) / 255 90 | 91 | @backlight.setter 92 | def backlight(self, value): 93 | self._write_register(_REG_BKL, int(255 * value)) 94 | 95 | @property 96 | def address(self): 97 | return self._read_register(_REG_ADR) 98 | 99 | @address.setter 100 | def address(self, value): 101 | self._write_register(_REG_ADR, value) 102 | 103 | def _read_register(self, reg): 104 | self._buffer[0] = reg 105 | self._dev.write(self._ep_out, self._buffer[:1]) 106 | 107 | return self._dev.read(self._ep_in, 1)[0] 108 | 109 | def _write_register(self, reg, value): 110 | self._buffer[0] = reg | _WRITE_MASK 111 | self._buffer[1] = value 112 | self._dev.write(self._ep_out, self._buffer) 113 | 114 | def _update_register_bit(self, reg, bit, value): 115 | 116 | reg_val = self._read_register(reg) 117 | old_val = reg_val 118 | 119 | if value: 120 | reg_val |= (1 << bit) 121 | else: 122 | reg_val &= ~(1 << bit) 123 | 124 | if reg_val != old_val: 125 | self._write_register(reg, reg_val) 126 | 127 | def _get_register_bit(self, reg, bit): 128 | return self._read_register(reg) & (1 << bit) != 0 129 | 130 | keyboard_backlight = backlight 131 | --------------------------------------------------------------------------------