├── .gitignore ├── pico_ws2812.pio ├── .github └── workflows │ └── main.yml ├── pico_midi.h ├── pico_bootloader.cc ├── pinnacle_pins.h ├── plover_hid_report_buffer.h ├── encoder_pins.h ├── pico_orthography.cc ├── pico_random.cc ├── pico_crc32.h ├── calculate_mask.h ├── pico_serial_port.h ├── libc_stubs.cc ├── usb_descriptors.h ├── st7789_steno_layout.cc ├── pico_interpolator.h ├── pico_clock.cc ├── plover_hid_report_buffer.cc ├── pico_register.h ├── pico_split_5.pio ├── pico_split_10.pio ├── pico_split_8.pio ├── console_report_buffer.h ├── pico_split_16.pio ├── pico_gpio.cc ├── config ├── pair_flash_layout.h ├── asterisk.h ├── asterisk_pre_kickstarter.h ├── ecosteno_v2.h ├── main_flash_layout.h ├── uni_v2.h ├── uni_v4.h ├── picosteno.h ├── multisteno_v2.h ├── kyria_rev4.cc ├── polyglot.h ├── crkbd_v4_pair.h ├── starboard_rp2350.h ├── crkbd_v3_pair.h ├── starboard_rp2040.h ├── crkbd_v4.h ├── crkbd_v3.h ├── kyria_rev3_pair.h ├── kyria_rev3.h ├── kyria_rev4_pair.h └── kyria_rev4.h ├── pico_split_20.pio ├── pico_thread.cc ├── pico_midi.cc ├── pico_sniff.h ├── pico_console.cc ├── auto_draw.h ├── pico_button_state.h ├── pico_timer.h ├── pico_flash.cc ├── console_report_buffer.cc ├── pico_split.h ├── libc_overrides.cc ├── hid_report_buffer.h ├── pico_crc32.cc ├── main_task.h ├── pico_encoder_state.h ├── pico_spinlock.h ├── README.md ├── split_hid_report_buffer.h ├── pinnacle.h ├── pico_ws2812.h ├── pico_serial_port.cc ├── main_report_builder.h ├── hid_report_buffer.cc ├── ssd1306_paper_tape.cc ├── st7789_paper_tape.cc ├── pico_ws2812.cc ├── tusb_config.h ├── LICENSE.txt ├── split_hid_report_buffer.cc ├── ssd1306.h ├── auto_draw.cc ├── ssd1306_steno_layout.cc ├── CMakeLists.txt ├── st7789.h ├── pico_dma.h └── pico_button_state.cc /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .* 3 | -------------------------------------------------------------------------------- /pico_ws2812.pio: -------------------------------------------------------------------------------- 1 | .program ws2812 2 | .side_set 1 opt 3 | 4 | .wrap_target 5 | out x, 1 side 0 [2] ; Blocks until data is ready on low. 6 | nop side 1 [1] ; Start of signal 7 | mov pins, x [4] 8 | .wrap 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Close Pull Request 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened] 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: superbrothers/close-pull-request@v3 12 | with: 13 | comment: "PRs are not accepted. This project is open source, not open contribution." 14 | -------------------------------------------------------------------------------- /pico_midi.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | 5 | //--------------------------------------------------------------------------- 6 | 7 | class PicoMidi { 8 | public: 9 | static void HandleIncomingData(); 10 | }; 11 | 12 | //--------------------------------------------------------------------------- 13 | -------------------------------------------------------------------------------- /pico_bootloader.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/hal/bootloader.h" 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | void Bootloader::Launch() { rom_reset_usb_boot(0, 0); } 9 | 10 | //--------------------------------------------------------------------------- 11 | -------------------------------------------------------------------------------- /pinnacle_pins.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct PinnaclePins { 9 | uint8_t chipSelectPin; 10 | bool invertX = false; 11 | bool invertY = false; 12 | }; 13 | 14 | //--------------------------------------------------------------------------- 15 | -------------------------------------------------------------------------------- /plover_hid_report_buffer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "hid_report_buffer.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct PloverHidReportBuffer : public HidReportBuffer<8> { 9 | public: 10 | static PloverHidReportBuffer instance; 11 | 12 | private: 13 | PloverHidReportBuffer(); 14 | }; 15 | 16 | //--------------------------------------------------------------------------- 17 | -------------------------------------------------------------------------------- /encoder_pins.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct EncoderPins { 9 | uint8_t a; 10 | uint8_t b; 11 | 12 | #ifdef __cplusplus 13 | void Initialize() const; 14 | int ReadState() const; 15 | 16 | private: 17 | static void InitializePin(int pin); 18 | #endif 19 | }; 20 | 21 | //--------------------------------------------------------------------------- 22 | -------------------------------------------------------------------------------- /pico_orthography.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/orthography.h" 4 | #include "pico_spinlock.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #if USE_ORTHOGRAPHY_CACHE 9 | 10 | void StenoCompiledOrthography::LockCache() { spinlock19->Lock(); } 11 | void StenoCompiledOrthography::UnlockCache() { spinlock19->Unlock(); } 12 | 13 | #endif 14 | 15 | //--------------------------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /pico_random.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/random.h" 4 | 5 | //--------------------------------------------------------------------------- 6 | 7 | volatile uint32_t *const randomBit = (uint32_t *)0x4006001c; 8 | 9 | uint32_t Random::GenerateHardwareUint32() { 10 | uint32_t result = 0; 11 | for (int i = 0; i < 32; ++i) { 12 | result = (result << 1) | (*randomBit & 1); 13 | } 14 | 15 | return result; 16 | } 17 | 18 | //--------------------------------------------------------------------------- 19 | -------------------------------------------------------------------------------- /pico_crc32.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | struct PicoDma; 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | struct PicoCrc32 { 14 | public: 15 | static void Initialize(); 16 | 17 | static uint32_t Hash(const void *data, size_t length, PicoDma *dma); 18 | }; 19 | 20 | //--------------------------------------------------------------------------- 21 | -------------------------------------------------------------------------------- /calculate_mask.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | template constexpr uint32_t CALCULATE_MASK(const uint8_t (&v)[N]) { 10 | uint32_t result = 0; 11 | for (const uint8_t x : v) { 12 | const uint8_t pin = x & 0x7f; 13 | if (pin != 0x7f) { 14 | result |= (1 << pin); 15 | } 16 | } 17 | return result; 18 | } 19 | 20 | //--------------------------------------------------------------------------- 21 | -------------------------------------------------------------------------------- /pico_serial_port.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | class PicoSerialPort { 9 | public: 10 | static void HandleIncomingData(); 11 | 12 | static void SendSerialConsole(const void *data, size_t length); 13 | static void Flush(); 14 | 15 | static bool HasOpenSerialConsole() { return hasOpenSerialConsole; }; 16 | 17 | static void OnSerialPortDisconnected(); 18 | 19 | private: 20 | static bool hasOpenSerialConsole; 21 | }; 22 | 23 | //--------------------------------------------------------------------------- 24 | -------------------------------------------------------------------------------- /libc_stubs.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // Definitions to silence linker warnings 4 | // 5 | //--------------------------------------------------------------------------- 6 | 7 | #include 8 | 9 | extern "C" { 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | int _close(int) { return 0; } 14 | int _fstat(int, struct stat *) { return 0; } 15 | int _isatty(int) { return 0; } 16 | int _lseek(int, off_t, int) { return 0; } 17 | 18 | //--------------------------------------------------------------------------- 19 | } 20 | //--------------------------------------------------------------------------- 21 | -------------------------------------------------------------------------------- /usb_descriptors.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | 5 | //--------------------------------------------------------------------------- 6 | 7 | enum { 8 | ITF_NUM_KEYBOARD, 9 | ITF_NUM_CONSOLE, 10 | ITF_NUM_PLOVER_HID, 11 | ITF_NUM_CDC, 12 | ITF_NUM_CDC_DATA, 13 | ITF_NUM_MIDI, 14 | ITF_NUM_MIDI_STREAMING, 15 | ITF_NUM_TOTAL, 16 | }; 17 | 18 | //--------------------------------------------------------------------------- 19 | 20 | const int KEYBOARD_PAGE_REPORT_ID = 1; 21 | const int CONSUMER_PAGE_REPORT_ID = 2; 22 | const int MOUSE_PAGE_REPORT_ID = 3; 23 | const int PLOVER_HID_REPORT_ID = 0x50; 24 | 25 | //--------------------------------------------------------------------------- 26 | -------------------------------------------------------------------------------- /st7789_steno_layout.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "st7789.h" 4 | 5 | //--------------------------------------------------------------------------- 6 | 7 | struct StenoLayoutData { 8 | uint8_t type; 9 | uint8_t x; 10 | }; 11 | 12 | //--------------------------------------------------------------------------- 13 | 14 | #if JAVELIN_DISPLAY_DRIVER == 7789 15 | 16 | //--------------------------------------------------------------------------- 17 | 18 | void St7789::St7789Data::DrawStenoLayout(StenoStroke stroke) { 19 | // Not supported. 20 | } 21 | 22 | //--------------------------------------------------------------------------- 23 | 24 | #endif // JAVELIN_DISPLAY_DRIVER == 7789 25 | 26 | //--------------------------------------------------------------------------- 27 | -------------------------------------------------------------------------------- /pico_interpolator.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "pico_register.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct PicoInterpolator { 9 | PicoRegister accumulator[2]; 10 | PicoRegister base[3]; 11 | const PicoRegister pop[3]; 12 | const PicoRegister peek[3]; 13 | PicoRegister control[2]; 14 | PicoRegister accumulatorAdd[2]; 15 | PicoRegister bases; // Lower 16 bits to base0, Upper 16 to base1 16 | }; 17 | 18 | static_assert(sizeof(PicoInterpolator) == 0x40); 19 | 20 | PicoInterpolator *const interpolator0 = (PicoInterpolator *)0xd0000080; 21 | PicoInterpolator *const interpolator1 = (PicoInterpolator *)0xd00000c0; 22 | 23 | //--------------------------------------------------------------------------- 24 | -------------------------------------------------------------------------------- /pico_clock.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/clock.h" 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | uint32_t Clock::GetMilliseconds() { return (time_us_64() * 4294968) >> 32; } 10 | 11 | uint32_t Clock::GetMicroseconds() { return time_us_32(); } 12 | 13 | void Clock::Sleep(uint32_t milliseconds) { 14 | const uint32_t startTime = Clock::GetMicroseconds(); 15 | const uint32_t microseconds = 1000 * milliseconds; 16 | while (Clock::GetMicroseconds() - startTime < microseconds) { 17 | sleep_us(100); 18 | tud_task(); 19 | #if JAVELIN_USE_WATCHDOG 20 | watchdog_update(); 21 | #endif 22 | } 23 | } 24 | //--------------------------------------------------------------------------- 25 | -------------------------------------------------------------------------------- /plover_hid_report_buffer.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "plover_hid_report_buffer.h" 4 | #include "javelin/processor/plover_hid.h" 5 | #include "usb_descriptors.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | PloverHidReportBuffer PloverHidReportBuffer::instance; 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | PloverHidReportBuffer::PloverHidReportBuffer() 14 | : HidReportBuffer<8>(ITF_NUM_PLOVER_HID) {} 15 | 16 | void StenoPloverHid::SendPacket(const StenoPloverHidPacket &packet) { 17 | PloverHidReportBuffer::instance.SendReport( 18 | PLOVER_HID_REPORT_ID, (uint8_t *)&packet, sizeof(packet)); 19 | } 20 | 21 | //--------------------------------------------------------------------------- 22 | -------------------------------------------------------------------------------- /pico_register.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct PicoRegister { 9 | volatile uint32_t value; 10 | 11 | void operator=(uint32_t newValue) { value = newValue; } 12 | operator uint32_t() const { return value; } 13 | 14 | void operator^=(uint32_t xorMask) { SetAlias(0x1000, xorMask); } 15 | void operator|=(uint32_t setMask) { SetAlias(0x2000, setMask); } 16 | void operator&=(uint32_t mask) { SetAlias(0x3000, ~mask); } 17 | 18 | void SetAlias(uint32_t aliasOffset, uint32_t mask) { 19 | volatile uint32_t *alias = (uint32_t *)(aliasOffset + uintptr_t(&value)); 20 | *alias = mask; 21 | } 22 | }; 23 | 24 | //--------------------------------------------------------------------------- 25 | -------------------------------------------------------------------------------- /pico_split_5.pio: -------------------------------------------------------------------------------- 1 | .program pico_split 2 | .side_set 1 opt 3 | 4 | public tx_start: 5 | out y, 32 6 | 7 | tx_1: 8 | out x, 1 9 | jmp !x tx_1_1 side 1 [1] 10 | tx_1_0: 11 | jmp y--, tx_1 side 0 [1] 12 | jmp tx_end 13 | tx_1_1: 14 | jmp y--, tx_0 [1] 15 | jmp tx_end 16 | 17 | tx_0: 18 | out x, 1 19 | jmp !x tx_0_0 side 0 [1] 20 | tx_0_1: 21 | jmp y--, tx_0 side 1 [1] 22 | jmp tx_end 23 | tx_0_0: 24 | jmp y--, tx_1 [1] 25 | 26 | tx_end: 27 | irq 0 side 0 28 | 29 | set pindirs, 0 30 | 31 | public rx_start: 32 | set y, 1 33 | 34 | wait 1 pin 0 [1] 35 | rx_1: 36 | jmp pin rx_1_1 37 | rx_1_0: 38 | in y, 1 39 | wait 1 pin 0 40 | jmp rx_1 41 | rx_1_1: 42 | in null, 1 43 | 44 | .wrap_target 45 | rx_0: 46 | wait 0 pin 0 [1] 47 | jmp pin rx_0_1 48 | rx_0_0: 49 | in null, 1 50 | wait 1 pin 0 51 | jmp rx_1 52 | rx_0_1: 53 | in y, 1 54 | .wrap 55 | -------------------------------------------------------------------------------- /pico_split_10.pio: -------------------------------------------------------------------------------- 1 | .program pico_split 2 | .side_set 1 opt 3 | 4 | public tx_start: 5 | out y, 32 6 | 7 | tx_1: 8 | out x, 1 9 | jmp !x tx_1_1 side 1 [4] 10 | tx_1_0: 11 | jmp y--, tx_1 side 0 [3] 12 | jmp tx_end 13 | tx_1_1: 14 | jmp y--, tx_0 [3] 15 | jmp tx_end 16 | 17 | tx_0: 18 | out x, 1 19 | jmp !x tx_0_0 side 0 [4] 20 | tx_0_1: 21 | jmp y--, tx_0 side 1 [3] 22 | jmp tx_end 23 | tx_0_0: 24 | jmp y--, tx_1 [3] 25 | 26 | tx_end: 27 | irq 0 side 0 [7] 28 | set pindirs, 0 29 | 30 | public rx_start: 31 | set y, 1 32 | 33 | rx_1: 34 | wait 1 pin 0 [6] 35 | rx_1_repeat: 36 | jmp pin rx_1_1 37 | rx_1_0: 38 | in y, 1 39 | wait 1 pin 0 [5] 40 | jmp rx_1_repeat 41 | rx_1_1: 42 | in null, 1 43 | 44 | .wrap_target 45 | rx_0: 46 | wait 0 pin 0 [6] 47 | jmp pin rx_0_1 48 | rx_0_0: 49 | in null, 1 50 | wait 1 pin 0 [5] 51 | jmp rx_1_repeat 52 | rx_0_1: 53 | in y, 1 54 | .wrap 55 | -------------------------------------------------------------------------------- /pico_split_8.pio: -------------------------------------------------------------------------------- 1 | .program pico_split 2 | .side_set 1 opt 3 | 4 | public tx_start: 5 | out y, 32 6 | 7 | tx_1: 8 | out x, 1 9 | jmp !x tx_1_1 side 1 [3] 10 | tx_1_0: 11 | jmp y--, tx_1 side 0 [2] 12 | jmp tx_end 13 | tx_1_1: 14 | jmp y--, tx_0 [2] 15 | jmp tx_end 16 | 17 | tx_0: 18 | out x, 1 19 | jmp !x tx_0_0 side 0 [3] 20 | tx_0_1: 21 | jmp y--, tx_0 side 1 [2] 22 | jmp tx_end 23 | tx_0_0: 24 | jmp y--, tx_1 [2] 25 | 26 | tx_end: 27 | irq 0 side 0 [7] 28 | set pindirs, 0 29 | 30 | public rx_start: 31 | set y, 1 32 | 33 | rx_1: 34 | wait 1 pin 0 [4] 35 | rx_1_repeat: 36 | jmp pin rx_1_1 37 | rx_1_0: 38 | in y, 1 39 | wait 1 pin 0 [3] 40 | jmp rx_1_repeat 41 | rx_1_1: 42 | in null, 1 43 | 44 | .wrap_target 45 | rx_0: 46 | wait 0 pin 0 [4] 47 | jmp pin rx_0_1 48 | rx_0_0: 49 | in null, 1 50 | wait 1 pin 0 [3] 51 | jmp rx_1_repeat 52 | rx_0_1: 53 | in y, 1 54 | .wrap 55 | -------------------------------------------------------------------------------- /console_report_buffer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "hid_report_buffer.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct ConsoleReportBuffer { 9 | public: 10 | void SendData(const uint8_t *data, size_t length); 11 | void Flush(); 12 | 13 | void SendNextReport() { reportBuffer.SendNextReport(); } 14 | void Reset() { 15 | bufferSize = 0; 16 | reportBuffer.Reset(); 17 | } 18 | size_t GetAvailableBufferCount() const { 19 | return reportBuffer.GetAvailableBufferCount(); 20 | } 21 | 22 | static ConsoleReportBuffer instance; 23 | 24 | private: 25 | ConsoleReportBuffer(); 26 | 27 | static constexpr size_t MAX_BUFFER_SIZE = 64; 28 | 29 | int bufferSize = 0; 30 | uint8_t buffer[MAX_BUFFER_SIZE]; 31 | 32 | HidReportBuffer<64> reportBuffer; 33 | 34 | friend class SplitHidReportBuffer; 35 | }; 36 | 37 | //--------------------------------------------------------------------------- 38 | -------------------------------------------------------------------------------- /pico_split_16.pio: -------------------------------------------------------------------------------- 1 | .program pico_split 2 | .side_set 1 opt 3 | 4 | public tx_start: 5 | out y, 32 6 | 7 | tx_1: 8 | out x, 1 9 | jmp !x tx_1_1 side 1 [7] 10 | tx_1_0: 11 | jmp y--, tx_1 side 0 [6] 12 | jmp tx_end 13 | tx_1_1: 14 | jmp y--, tx_0 [6] 15 | jmp tx_end 16 | 17 | tx_0: 18 | out x, 1 19 | jmp !x tx_0_0 side 0 [7] 20 | tx_0_1: 21 | jmp y--, tx_0 side 1 [6] 22 | jmp tx_end 23 | tx_0_0: 24 | jmp y--, tx_1 [6] 25 | 26 | tx_end: 27 | irq 0 side 0 [7] 28 | set pindirs, 0 29 | 30 | public rx_start: 31 | set y, 1 32 | 33 | rx_1: 34 | wait 1 pin 0 [5] 35 | rx_1_repeat: 36 | nop [5] 37 | jmp pin rx_1_1 38 | rx_1_0: 39 | in y, 1 40 | wait 1 pin 0 [4] 41 | jmp rx_1_repeat 42 | rx_1_1: 43 | in null, 1 44 | 45 | .wrap_target 46 | rx_0: 47 | wait 0 pin 0 [5] 48 | nop [5] 49 | jmp pin rx_0_1 50 | rx_0_0: 51 | in null, 1 52 | wait 1 pin 0 [4] 53 | jmp rx_1_repeat 54 | rx_0_1: 55 | in y, 1 56 | .wrap 57 | -------------------------------------------------------------------------------- /pico_gpio.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/hal/gpio.h" 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | void Gpio::SetInputPin(int pin, Pull pull) { 10 | gpio_init(pin); 11 | gpio_set_dir(pin, GPIO_IN); 12 | switch (pull) { 13 | case Pull::NONE: 14 | break; 15 | case Pull::DOWN: 16 | gpio_pull_down(pin); 17 | break; 18 | case Pull::UP: 19 | gpio_pull_up(pin); 20 | break; 21 | } 22 | } 23 | 24 | bool Gpio::GetPin(int pin) { return gpio_get(pin) != 0; } 25 | 26 | void Gpio::SetPin(int pin, bool value) { 27 | gpio_init(pin); 28 | gpio_set_dir(pin, GPIO_OUT); 29 | gpio_put(pin, value); 30 | } 31 | 32 | void Gpio::SetPinDutyCycle(int pin, int dutyCycle) { 33 | gpio_set_function(pin, GPIO_FUNC_PWM); 34 | const uint32_t slice_num = pwm_gpio_to_slice_num(pin); 35 | pwm_set_wrap(slice_num, 99); 36 | pwm_set_enabled(slice_num, true); 37 | pwm_set_gpio_level(pin, dutyCycle); 38 | } 39 | 40 | //--------------------------------------------------------------------------- 41 | -------------------------------------------------------------------------------- /config/pair_flash_layout.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | struct ScriptStorageData; 10 | struct StenoConfigBlock; 11 | 12 | //--------------------------------------------------------------------------- 13 | 14 | #define JAVELIN_USE_SCRIPT_STORAGE 1 15 | 16 | //--------------------------------------------------------------------------- 17 | 18 | const StenoConfigBlock *const STENO_CONFIG_BLOCK_ADDRESS = 19 | (const StenoConfigBlock *)0x1004a000; 20 | const uint8_t *const SCRIPT_BYTE_CODE = (const uint8_t *)0x1004a100; 21 | const size_t MAXIMUM_BUTTON_SCRIPT_SIZE = 0x7f00; 22 | 23 | static const struct ScriptStorageData *const SCRIPT_STORAGE_ADDRESS = 24 | (struct ScriptStorageData *)0x10060000; 25 | static const size_t MAXIMUM_SCRIPT_STORAGE_SIZE = 0x20000; 26 | 27 | static const intptr_t ASSET_STORAGE_START_ADDRESS = 0x10080000; 28 | static const intptr_t ASSET_STORAGE_END_ADDRESS = 0x10100000; 29 | 30 | //--------------------------------------------------------------------------- 31 | -------------------------------------------------------------------------------- /pico_split_20.pio: -------------------------------------------------------------------------------- 1 | .program pico_split 2 | .side_set 1 opt 3 | 4 | public tx_start: 5 | out y, 32 6 | 7 | tx_1: 8 | out x, 1 [1] 9 | nop side 1 [1] 10 | jmp !x tx_1_1 [7] 11 | tx_1_0: 12 | jmp y--, tx_1 side 0 [7] 13 | jmp tx_end 14 | tx_1_1: 15 | jmp y--, tx_0 [7] 16 | jmp tx_end 17 | 18 | tx_0: 19 | out x, 1 [1] 20 | nop side 0 [1] 21 | jmp !x tx_0_0 [7] 22 | tx_0_1: 23 | jmp y--, tx_0 side 1 [7] 24 | jmp tx_end 25 | tx_0_0: 26 | jmp y--, tx_1 [7] 27 | 28 | tx_end: 29 | irq 0 side 0 [7] 30 | set pindirs, 0 31 | 32 | public rx_start: 33 | set y, 1 34 | 35 | rx_1: 36 | wait 1 pin 0 [6] 37 | rx_1_repeat: 38 | nop [7] 39 | jmp pin rx_1_1 40 | rx_1_0: 41 | in y, 1 42 | wait 1 pin 0 [5] 43 | jmp rx_1_repeat 44 | rx_1_1: 45 | in null, 1 46 | 47 | .wrap_target 48 | rx_0: 49 | wait 0 pin 0 [6] 50 | nop [7] 51 | jmp pin rx_0_1 52 | rx_0_0: 53 | in null, 1 54 | wait 1 pin 0 [5] 55 | jmp rx_1_repeat 56 | rx_0_1: 57 | in y, 1 58 | .wrap 59 | -------------------------------------------------------------------------------- /pico_thread.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/thread.h" 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | void RunParallel(void (*func1)(void *context), void *context1, 9 | void (*func2)(void *context), void *context2) { 10 | 11 | multicore_fifo_push_blocking((uint32_t)func1); 12 | multicore_fifo_push_blocking((uint32_t)context1); 13 | 14 | (*func2)(context2); 15 | 16 | multicore_fifo_pop_blocking(); 17 | } 18 | 19 | //--------------------------------------------------------------------------- 20 | 21 | static void MultiCoreEntryPoint() { 22 | while (1) { 23 | void (*func)(void *) = (void (*)(void *))multicore_fifo_pop_blocking(); 24 | void *context = (void *)multicore_fifo_pop_blocking(); 25 | 26 | (*func)(context); 27 | 28 | multicore_fifo_push_blocking(0); 29 | } 30 | } 31 | 32 | //--------------------------------------------------------------------------- 33 | 34 | void InitMulticore() { 35 | multicore_reset_core1(); 36 | multicore_launch_core1(MultiCoreEntryPoint); 37 | } 38 | 39 | //--------------------------------------------------------------------------- 40 | -------------------------------------------------------------------------------- /pico_midi.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "pico_midi.h" 4 | #include "javelin/console.h" 5 | #include "javelin/hal/midi.h" 6 | #include "javelin/split/split_midi.h" 7 | #include 8 | 9 | //--------------------------------------------------------------------------- 10 | 11 | #if JAVELIN_SPLIT && !JAVELIN_SPLIT_IS_MASTER 12 | 13 | void SplitMidi::SplitMidiData::OnDataReceived(const void *data, size_t length) { 14 | if (tud_midi_mounted()) { 15 | tud_midi_stream_write(0, (const uint8_t *)data, length); 16 | } 17 | } 18 | 19 | #endif 20 | 21 | //--------------------------------------------------------------------------- 22 | 23 | void Midi::Send(const uint8_t *data, size_t length) { 24 | if (tud_midi_mounted()) { 25 | tud_midi_stream_write(0, data, length); 26 | } else { 27 | #if JAVELIN_SPLIT 28 | if (Split::IsMaster()) { 29 | SplitMidi::Add(data, length); 30 | } 31 | #endif 32 | } 33 | } 34 | 35 | void PicoMidi::HandleIncomingData() { 36 | while (tud_midi_available()) { 37 | uint8_t packet[4]; 38 | tud_midi_packet_read(packet); 39 | 40 | // TODO - Send to script? 41 | } 42 | } 43 | 44 | //--------------------------------------------------------------------------- 45 | -------------------------------------------------------------------------------- /pico_sniff.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | struct PicoDmaSniffControl { 10 | enum class Calculate : uint32_t { 11 | CRC_32 = 0, 12 | BIT_REVERSED_CRC_32 = 1, 13 | CRC_16_CCITT = 2, 14 | BIT_REVERSED_CRC_16_CCITT = 3, 15 | CHECKSUM = 0xf, 16 | }; 17 | 18 | uint32_t enable : 1; 19 | uint32_t dmaChannel : 4; 20 | Calculate calculate : 4; 21 | uint32_t bswap : 1; 22 | uint32_t bitReverseOutput : 1; 23 | uint32_t bitInvertOutput : 1; 24 | uint32_t _reserved12 : 20; 25 | 26 | void operator=(const PicoDmaSniffControl &control) volatile { 27 | *(volatile uint32_t *)this = *(uint32_t *)&control; 28 | } 29 | }; 30 | static_assert(sizeof(PicoDmaSniffControl) == 4, 31 | "Unexpected DmaSniffControl size"); 32 | 33 | struct PicoDmaSniff { 34 | volatile PicoDmaSniffControl control; 35 | volatile uint32_t data; 36 | }; 37 | 38 | #if JAVELIN_PICO_PLATFORM == 2350 39 | static PicoDmaSniff *const sniff = (PicoDmaSniff *)0x50000454; 40 | #elif JAVELIN_PICO_PLATFORM == 2040 41 | static PicoDmaSniff *const sniff = (PicoDmaSniff *)0x50000434; 42 | #else 43 | #error Unsupported platform 44 | #endif 45 | 46 | //--------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /pico_console.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "console_report_buffer.h" 4 | #include "javelin/console.h" 5 | #include "pico_serial_port.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | static ConnectionId consoleConnectionId = ConnectionId::ACTIVE; 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | void ConsoleWriter::Write(const char *data, size_t length) { 14 | if (PicoSerialPort::HasOpenSerialConsole() && 15 | (consoleConnectionId == ConnectionId::ACTIVE || 16 | consoleConnectionId == ConnectionId::SERIAL_CONSOLE)) { 17 | PicoSerialPort::SendSerialConsole(data, length); 18 | if (consoleConnectionId == ConnectionId::ACTIVE) { 19 | PicoSerialPort::Flush(); 20 | } 21 | } else { 22 | ConsoleReportBuffer::instance.SendData((const uint8_t *)data, length); 23 | } 24 | } 25 | 26 | void Console::Flush() { ConsoleReportBuffer::instance.Flush(); } 27 | 28 | //--------------------------------------------------------------------------- 29 | 30 | void ConsoleWriter::SetConnection(ConnectionId connectionId, 31 | uint16_t connectionHandle) { 32 | 33 | if (consoleConnectionId == ConnectionId::SERIAL_CONSOLE) { 34 | PicoSerialPort::Flush(); 35 | } 36 | if (connectionId == ConnectionId::ACTIVE) { 37 | Console::Flush(); 38 | } 39 | consoleConnectionId = connectionId; 40 | } 41 | 42 | //--------------------------------------------------------------------------- 43 | -------------------------------------------------------------------------------- /auto_draw.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include JAVELIN_BOARD_CONFIG 5 | #include "javelin/processor/passthrough.h" 6 | #include "javelin/static_allocate.h" 7 | #include "javelin/timer_manager.h" 8 | 9 | //--------------------------------------------------------------------------- 10 | 11 | #if JAVELIN_DISPLAY_DRIVER 12 | 13 | enum class AutoDraw : uint8_t { 14 | NONE, 15 | PAPER_TAPE, 16 | STENO_LAYOUT, 17 | WPM, 18 | STROKES, 19 | }; 20 | 21 | class StenoStrokeCapture final : public StenoPassthrough, private TimerHandler { 22 | private: 23 | using super = StenoPassthrough; 24 | 25 | public: 26 | StenoStrokeCapture(StenoProcessorElement *next) : super(next) {} 27 | 28 | void Process(const StenoKeyState &value, StenoAction action) override; 29 | 30 | void Update(bool onStrokeInput); 31 | 32 | void SetAutoDraw(int displayId, AutoDraw autoDrawId); 33 | 34 | static JavelinStaticAllocate container; 35 | 36 | private: 37 | static constexpr size_t MAXIMUM_STROKE_COUNT = 32; 38 | size_t strokeCount = 0; 39 | #if JAVELIN_SPLIT 40 | AutoDraw autoDraw[2]; 41 | int lastWpm[2] = {-1, -1}; 42 | #else 43 | AutoDraw autoDraw[1]; 44 | int lastWpm[1] = {-1}; 45 | #endif 46 | StenoStroke strokes[MAXIMUM_STROKE_COUNT]; 47 | 48 | void DrawWpm(int displayId); 49 | void DrawStrokes(int displayId); 50 | void SetWpmTimer(bool enable); 51 | 52 | // Override from TimerHandler 53 | void Run(intptr_t id) final { WpmTimerHandler(); } 54 | void WpmTimerHandler(); 55 | }; 56 | 57 | #endif 58 | 59 | //--------------------------------------------------------------------------- 60 | -------------------------------------------------------------------------------- /config/asterisk.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define BOOTSEL_BUTTON_INDEX 26 13 | 14 | #define JAVELIN_BUTTON_MATRIX 0 15 | #define JAVELIN_BUTTON_TOUCH 1 16 | 17 | // clang-format off 18 | constexpr uint8_t BUTTON_TOUCH_PINS[26] = { 19 | 0, /**/ 25, 20 | 5, 4, 3, 2, 1, /**/ 15, 24, 23, 22, 21, 20, 21 | 9, 8, 7, 6, /**/ 14, 19, 18, 17, 16, 22 | 10, 11, /**/ 12, 13, 23 | }; 24 | constexpr float BUTTON_TOUCH_THRESHOLD = 1.30f; 25 | // clang-format on 26 | 27 | #define JAVELIN_SCRIPT_CONFIGURATION \ 28 | R"({"name":"Asterisk","layout":[{"x":0,"y":-0.5,"w":4,"h":0.5},{"x":8,"y":-0.5,"w":4,"h":0.5},{"x":0,"y":0},{"x":1,"y":0},{"x":2,"y":0},{"x":3,"y":0},{"x":4,"y":0,"w":1,"h":2},{"x":7,"y":0,"w":1,"h":2},{"x":8,"y":0},{"x":9,"y":0},{"x":10,"y":0},{"x":11,"y":0},{"x":12,"y":0},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":2.5,"y":3},{"x":3.5,"y":3},{"x":7.5,"y":3},{"x":8.5,"y":3},{"x":5.75,"y":2.5,"s":0.5}]})" 29 | 30 | const size_t BUTTON_COUNT = 27; 31 | 32 | const char *const MANUFACTURER_NAME = "stenokeyboards"; 33 | const char *const PRODUCT_NAME = "Asterisk (Javelin)"; 34 | const int VENDOR_ID = 0x9000; 35 | 36 | //--------------------------------------------------------------------------- 37 | -------------------------------------------------------------------------------- /config/asterisk_pre_kickstarter.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | #define BOOTSEL_BUTTON_INDEX 26 12 | 13 | #define JAVELIN_BUTTON_MATRIX 0 14 | #define JAVELIN_BUTTON_TOUCH 1 15 | 16 | // clang-format off 17 | constexpr uint8_t BUTTON_TOUCH_PINS[26] = { 18 | 0, /**/ 25, 19 | 8, 6, 2, 3, 1, /**/ 24, 22, 23, 19, 17, 12, 20 | 9, 7, 5, 4, /**/ 21, 20, 18, 16, 13, 21 | 10, 11, /**/ 15, 14, 22 | }; 23 | constexpr float BUTTON_TOUCH_THRESHOLD = 1.20f; 24 | // clang-format on 25 | 26 | #define JAVELIN_SCRIPT_CONFIGURATION \ 27 | R"({"name":"Asterisk","layout":[{"x":0,"y":-0.5,"w":4,"h":0.5},{"x":8,"y":-0.5,"w":4,"h":0.5},{"x":0,"y":0},{"x":1,"y":0},{"x":2,"y":0},{"x":3,"y":0},{"x":4,"y":0,"w":1,"h":2},{"x":7,"y":0,"w":1,"h":2},{"x":8,"y":0},{"x":9,"y":0},{"x":10,"y":0},{"x":11,"y":0},{"x":12,"y":0},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":2.5,"y":3},{"x":3.5,"y":3},{"x":7.5,"y":3},{"x":8.5,"y":3},{"x":5.75,"y":2.5,"s":0.5}]})" 28 | 29 | const size_t BUTTON_COUNT = 27; 30 | 31 | const char *const MANUFACTURER_NAME = "stenokeyboards"; 32 | const char *const PRODUCT_NAME = "Asterisk (Javelin)"; 33 | const int VENDOR_ID = 0x9000; 34 | 35 | //--------------------------------------------------------------------------- 36 | -------------------------------------------------------------------------------- /pico_button_state.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "javelin/button_state.h" 5 | #include "javelin/container/cyclic_queue.h" 6 | #include "javelin/debounce.h" 7 | 8 | #include JAVELIN_BOARD_CONFIG 9 | 10 | //--------------------------------------------------------------------------- 11 | 12 | class PicoButtonState { 13 | public: 14 | static void Initialize(); 15 | static void Update() { instance.UpdateInternal(); } 16 | 17 | static bool HasData() { return instance.queue.IsNotEmpty(); } 18 | static bool IsEmpty() { return instance.queue.IsEmpty(); } 19 | static bool IsNotEmpty() { return instance.queue.IsNotEmpty(); } 20 | static const TimedButtonState &Front() { return instance.queue.Front(); } 21 | static void RemoveFront() { instance.queue.RemoveFront(); } 22 | static size_t GetCount() { return instance.queue.GetCount(); } 23 | 24 | static void SetInInterrupt(bool isInInterrupt) { 25 | #if defined(BOOTSEL_BUTTON_INDEX) 26 | instance.isInInterrupt = isInInterrupt; 27 | #endif 28 | } 29 | 30 | private: 31 | static constexpr size_t QUEUE_COUNT = 64; 32 | 33 | #if defined(BOOTSEL_BUTTON_INDEX) 34 | bool lastBootSelButtonState; 35 | bool isInInterrupt; 36 | #endif 37 | 38 | uint32_t keyPressedTime; 39 | GlobalDeferredDebounce debouncer; 40 | CyclicQueue queue; 41 | 42 | static PicoButtonState instance; 43 | 44 | void UpdateInternal(); 45 | 46 | static ButtonState ReadInternal(); 47 | static void ReadTouchCounters(uint32_t *counters); 48 | static bool IsBootSelButtonPressed(); 49 | }; 50 | 51 | //--------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /pico_timer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "pico_register.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct PicoTimer { 9 | PicoRegister timehw; 10 | PicoRegister timelw; 11 | const PicoRegister timehr; 12 | const PicoRegister timelr; 13 | PicoRegister alarms[4]; 14 | PicoRegister armed; 15 | const PicoRegister timerawh; 16 | const PicoRegister timerawl; 17 | PicoRegister dbgPause; 18 | PicoRegister pause; 19 | #if JAVELIN_PICO_PLATFORM == 2350 20 | PicoRegister locked; 21 | PicoRegister source; 22 | #endif 23 | PicoRegister interrupt; // Raw Status 24 | PicoRegister interruptEnable; 25 | PicoRegister interruptForce; 26 | const PicoRegister interruptStatus; // After masking and forcing 27 | 28 | void EnableAlarmInterrupt(uint32_t alarmIndex) { 29 | interruptEnable |= 1 << alarmIndex; 30 | } 31 | void DisableAlarmInterrupt(uint32_t alarmIndex) { 32 | interruptEnable &= ~(1 << alarmIndex); 33 | } 34 | void AcknowledgeAlarmInterrupt(uint32_t alarmIndex) { 35 | interrupt &= ~(1 << alarmIndex); 36 | } 37 | void SetAlarmAfterDelayInMicroseconds(uint32_t alarmIndex, uint32_t delay) { 38 | alarms[alarmIndex] = timerawl + delay; 39 | } 40 | }; 41 | 42 | #if JAVELIN_PICO_PLATFORM == 2040 43 | static PicoTimer *const timer = (PicoTimer *)0x40054000; 44 | #elif JAVELIN_PICO_PLATFORM == 2350 45 | static PicoTimer *const timer = (PicoTimer *)0x400b0000; 46 | static PicoTimer *const timer0 = (PicoTimer *)0x400b0000; 47 | static PicoTimer *const timer1 = (PicoTimer *)0x400b8000; 48 | #else 49 | #error Unsupported platform 50 | #endif 51 | 52 | //--------------------------------------------------------------------------- 53 | -------------------------------------------------------------------------------- /config/ecosteno_v2.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define BOOTSEL_BUTTON_INDEX 32 13 | 14 | #define JAVELIN_DEBOUNCE_MS 10 15 | #define JAVELIN_BUTTON_MATRIX 1 16 | 17 | constexpr uint8_t COLUMN_PINS[] = {25, 24, 23, 22, 21, 20, 7, 4, 3, 2, 1, 0}; 18 | constexpr uint8_t ROW_PINS[] = {6, 5, 19, 18}; 19 | 20 | // clang-format off 21 | constexpr int8_t KEY_MAP[4][16] = { 22 | { -1, -1, 0, -1, 1, -1, /**/ -1, -1, 2, -1, 3, -1 }, 23 | { 4, 5, 6, 7, 8, 9, /**/ 10, 11, 12, 13, 14, 15 }, 24 | { 16, 17, 18, 19, 20, 21, /**/ 22, 23, 24, 25, 26, 27 }, 25 | { -1, -1, -1, -1, 28, 29, /**/ 30, 31, -1, -1, -1, -1 }, 26 | }; 27 | // clang-format on 28 | 29 | #define JAVELIN_SCRIPT_CONFIGURATION \ 30 | R"({"name":"Ecosteno","layout":[{"x":1,"y":0,"w":2,"h":1},{"x":3,"y":0,"w":2,"h":1},{"x":9,"y":0,"w":2,"h":1},{"x":11,"y":0,"w":2,"h":1},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":4,"y":1},{"x":5,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":13,"y":1},{"x":0,"y":2},{"x":1,"y":2},{"x":2,"y":2},{"x":3,"y":2},{"x":4,"y":2},{"x":5,"y":2},{"x":8,"y":2},{"x":9,"y":2},{"x":10,"y":2},{"x":11,"y":2},{"x":12,"y":2},{"x":13,"y":2},{"x":4,"y":4},{"x":5,"y":4},{"x":8,"y":4},{"x":9,"y":4},{"x":7,"y":-1,"s":0.5}]} 31 | )" 32 | 33 | const size_t BUTTON_COUNT = 33; 34 | 35 | const char *const MANUFACTURER_NAME = "Noll Electronics LLC"; 36 | const char *const PRODUCT_NAME = "Ecosteno (Javelin)"; 37 | const int VENDOR_ID = 0xFEED; 38 | 39 | //--------------------------------------------------------------------------- 40 | -------------------------------------------------------------------------------- /pico_flash.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include JAVELIN_BOARD_CONFIG 4 | 5 | #include "javelin/flash.h" 6 | #include 7 | #include 8 | #include 9 | 10 | //--------------------------------------------------------------------------- 11 | 12 | extern "C" char __flash_binary_end[]; 13 | 14 | static bool IsWritableRange(const void *p) { return p >= __flash_binary_end; } 15 | 16 | void Flash::EraseBlockInternal(const void *target, size_t size) { 17 | if (!IsWritableRange(target)) { 18 | return; 19 | } 20 | 21 | instance.erasedBytes += size; 22 | 23 | const uint32_t interrupts = save_and_disable_interrupts(); 24 | flash_range_erase((intptr_t)target - XIP_BASE, size); 25 | restore_interrupts(interrupts); 26 | } 27 | 28 | void Flash::WriteBlockInternal(const void *target, const void *data, 29 | size_t size) { 30 | if (!IsWritableRange(target)) { 31 | return; 32 | } 33 | 34 | for (int i = 0; i < 3; ++i) { 35 | instance.programmedBytes += size; 36 | 37 | const uint32_t interrupts = save_and_disable_interrupts(); 38 | flash_range_program((intptr_t)target - XIP_BASE, (const uint8_t *)data, 39 | size); 40 | restore_interrupts(interrupts); 41 | 42 | if (memcmp(target, data, size) == 0) { 43 | return; 44 | } 45 | 46 | instance.reprogrammedBytes += size; 47 | } 48 | } 49 | 50 | //--------------------------------------------------------------------------- 51 | 52 | bool Flash::IsScriptMemory(const void *start, const void *end) { 53 | const void *byteCodeStart = SCRIPT_BYTE_CODE; 54 | const void *byteCodeEnd = SCRIPT_BYTE_CODE + MAXIMUM_BUTTON_SCRIPT_SIZE; 55 | 56 | // Intersect if Max(start, byteCodeStart) < Min(end, byteCodeEnd) 57 | return start < byteCodeEnd && byteCodeStart < end; 58 | } 59 | 60 | //--------------------------------------------------------------------------- -------------------------------------------------------------------------------- /config/main_flash_layout.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | struct HostLayouts; 10 | struct ScriptStorageData; 11 | struct StenoConfigBlock; 12 | struct StenoSystem; 13 | struct StenoDictionaryCollection; 14 | 15 | //--------------------------------------------------------------------------- 16 | 17 | #define JAVELIN_USE_SCRIPT_STORAGE 1 18 | 19 | //--------------------------------------------------------------------------- 20 | 21 | const StenoConfigBlock *const STENO_CONFIG_BLOCK_ADDRESS = 22 | (const StenoConfigBlock *)0x1004a000; 23 | const uint8_t *const SCRIPT_BYTE_CODE = (const uint8_t *)0x1004a100; 24 | const StenoSystem *const SYSTEM_ADDRESS = (const StenoSystem *)0x10052000; 25 | const uint8_t *const STENO_WORD_LIST_ADDRESS = (const uint8_t *)0x10054000; 26 | static const struct HostLayouts *const HOST_LAYOUTS_ADDRESS = 27 | (struct HostLayouts *)0x103dd000; 28 | static const size_t MAXIMUM_HOST_LAYOUTS_SIZE = 0x3000; 29 | static const struct ScriptStorageData *const SCRIPT_STORAGE_ADDRESS = 30 | (struct ScriptStorageData *)0x103e0000; 31 | static const size_t MAXIMUM_SCRIPT_STORAGE_SIZE = 0x20000; 32 | const StenoDictionaryCollection *const STENO_MAP_DICTIONARY_COLLECTION_ADDRESS = 33 | (const StenoDictionaryCollection *)0x10400000; 34 | const intptr_t ASSET_STORAGE_END_ADDRESS = 0x10fc0000; 35 | const uint8_t *const STENO_USER_DICTIONARY_ADDRESS = 36 | (const uint8_t *)0x10fc0000; 37 | const size_t STENO_USER_DICTIONARY_SIZE = 0x40000; 38 | 39 | const size_t MAXIMUM_MAP_DICTIONARY_SIZE = 0xbc0000; 40 | const size_t MAXIMUM_BUTTON_SCRIPT_SIZE = 0x7f00; 41 | 42 | // Only used if JAVELIN_USE_EMBEDDED_STENO is 0. 43 | const intptr_t ASSET_STORAGE_START_ADDRESS = 0x10400000; 44 | 45 | //--------------------------------------------------------------------------- 46 | -------------------------------------------------------------------------------- /console_report_buffer.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "console_report_buffer.h" 4 | #include "usb_descriptors.h" 5 | 6 | #include 7 | 8 | //--------------------------------------------------------------------------- 9 | 10 | ConsoleReportBuffer ConsoleReportBuffer::instance; 11 | 12 | //--------------------------------------------------------------------------- 13 | 14 | ConsoleReportBuffer::ConsoleReportBuffer() : reportBuffer(ITF_NUM_CONSOLE) {} 15 | 16 | void ConsoleReportBuffer::SendData(const uint8_t *data, size_t length) { 17 | // Fill up the previous buffer if it's not empty. 18 | if (bufferSize != 0) { 19 | const int remaining = MAX_BUFFER_SIZE - bufferSize; 20 | const int bytesToAdd = length > remaining ? remaining : length; 21 | memcpy(buffer + bufferSize, data, bytesToAdd); 22 | bufferSize += bytesToAdd; 23 | 24 | data += bytesToAdd; 25 | length -= bytesToAdd; 26 | 27 | if (bufferSize == MAX_BUFFER_SIZE) { 28 | reportBuffer.SendReport(0, buffer, MAX_BUFFER_SIZE); 29 | bufferSize = 0; 30 | } 31 | 32 | if (length == 0) { 33 | return; 34 | } 35 | } 36 | 37 | // Send all full length requests. 38 | while (length >= MAX_BUFFER_SIZE) { 39 | reportBuffer.SendReport(0, data, MAX_BUFFER_SIZE); 40 | data += MAX_BUFFER_SIZE; 41 | length -= MAX_BUFFER_SIZE; 42 | } 43 | 44 | if (length > 0) { 45 | memcpy(buffer, data, length); 46 | bufferSize = length; 47 | } 48 | } 49 | 50 | //--------------------------------------------------------------------------- 51 | 52 | void ConsoleReportBuffer::Flush() { 53 | if (bufferSize > 0) { 54 | const int remaining = MAX_BUFFER_SIZE - bufferSize; 55 | memset(buffer + bufferSize, 0, remaining); 56 | reportBuffer.SendReport(0, buffer, MAX_BUFFER_SIZE); 57 | bufferSize = 0; 58 | } 59 | } 60 | 61 | //--------------------------------------------------------------------------- 62 | -------------------------------------------------------------------------------- /config/uni_v2.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | #define BOOTSEL_BUTTON_INDEX 28 12 | 13 | #define JAVELIN_DEBOUNCE_MS 10 14 | #define JAVELIN_BUTTON_MATRIX 1 15 | 16 | constexpr uint8_t COLUMN_PINS[] = {28, 27, 26, 22, 20, 9, 8, 7, 6, 5, 4}; 17 | constexpr uint8_t ROW_PINS[] = {29, 23, 21}; 18 | 19 | // clang-format off 20 | constexpr int8_t KEY_MAP[3][16] = { 21 | { 0, 1, 2, 3, 4, /**/ 5, 6, 7, 8, 9, 10 }, 22 | { 11, 12, 13, 14, 15, /**/ 16, 17, 18, 19, 20, 21 }, 23 | { -1, -1, 22, 23, 24, /**/ 25, 26, 27, -1, -1, -1 }, 24 | }; 25 | // clang-format on 26 | 27 | #define JAVELIN_SCRIPT_CONFIGURATION \ 28 | R"({"n":"Uni v2","s":[{"c":"ff000000","s":"f","p":[{"c":"m","p":[-0.05,1]},{"c":"rl","p":[0,-1.05]},{"c":"ra","p":[13.1,0,0.05]},{"c":"ra","p":[0,2.1,0.05]},{"c":"ra","p":[-3,0,0.05]},{"c":"ra","p":[0,2,0.2]},{"c":"ra","p":[-8.1,0,0.05]},{"c":"ra","p":[0,-2,0.05]},{"c":"ra","p":[-2,0,0.2]},{"c":"ra","p":[0,-1,0.05]},{"c":"c"}]}],"k":[{"x":0,"y":0},{"x":1,"y":0},{"x":2,"y":0},{"x":3,"y":0},{"x":4,"y":0},{"x":7,"y":0},{"x":8,"y":0},{"x":9,"y":0},{"x":10,"y":0},{"x":11,"y":0},{"x":12,"y":0},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":4,"y":1},{"x":7,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":2,"y":3},{"x":3,"y":3},{"x":4,"y":3},{"x":7,"y":3},{"x":8,"y":3},{"x":9,"y":3},{"x":12.5,"y":3.5,"s":0.5}]})" 29 | 30 | const size_t BUTTON_COUNT = 29; 31 | 32 | const char *const MANUFACTURER_NAME = "stenokeyboards"; 33 | const char *const PRODUCT_NAME = "The Uni (Javelin)"; 34 | const int VENDOR_ID = 0x9000; 35 | 36 | //--------------------------------------------------------------------------- 37 | -------------------------------------------------------------------------------- /config/uni_v4.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | #define BOOTSEL_BUTTON_INDEX 28 12 | 13 | #define JAVELIN_DEBOUNCE_MS 10 14 | #define JAVELIN_BUTTON_MATRIX 1 15 | 16 | constexpr uint8_t COLUMN_PINS[] = {24, 23, 21, 20, 19, 6, 5, 4, 3, 2, 1}; 17 | constexpr uint8_t ROW_PINS[] = {25, 18, 17}; 18 | 19 | // clang-format off 20 | constexpr int8_t KEY_MAP[3][16] = { 21 | { 0, 1, 2, 3, 4, /**/ 5, 6, 7, 8, 9, 10 }, 22 | { 11, 12, 13, 14, 15, /**/ 16, 17, 18, 19, 20, 21 }, 23 | { -1, -1, 22, 23, 24, /**/ 25, 26, 27, -1, -1, -1 }, 24 | }; 25 | // clang-format on 26 | 27 | #define JAVELIN_SCRIPT_CONFIGURATION \ 28 | R"({"n":"Uni v4","s":[{"c":"ff000000","s":"f","p":[{"c":"m","p":[-0.05,1]},{"c":"rl","p":[0,-1.05]},{"c":"ra","p":[13.1,0,0.05]},{"c":"ra","p":[0,2.1,0.05]},{"c":"ra","p":[-3,0,0.05]},{"c":"ra","p":[0,2,0.2]},{"c":"ra","p":[-8.1,0,0.05]},{"c":"ra","p":[0,-2,0.05]},{"c":"ra","p":[-2,0,0.2]},{"c":"ra","p":[0,-1,0.05]},{"c":"c"}]}],"k":[{"x":0,"y":0},{"x":1,"y":0},{"x":2,"y":0},{"x":3,"y":0},{"x":4,"y":0},{"x":7,"y":0},{"x":8,"y":0},{"x":9,"y":0},{"x":10,"y":0},{"x":11,"y":0},{"x":12,"y":0},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":4,"y":1},{"x":7,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":2,"y":3},{"x":3,"y":3},{"x":4,"y":3},{"x":7,"y":3},{"x":8,"y":3},{"x":9,"y":3},{"x":12.5,"y":3.5,"s":0.5}]})" 29 | 30 | const size_t BUTTON_COUNT = 29; 31 | 32 | const char *const MANUFACTURER_NAME = "stenokeyboards"; 33 | const char *const PRODUCT_NAME = "The Uni (Javelin)"; 34 | const int VENDOR_ID = 0x9000; 35 | 36 | //--------------------------------------------------------------------------- 37 | -------------------------------------------------------------------------------- /config/picosteno.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define JAVELIN_RGB 1 13 | #define JAVELIN_RGB_COUNT 1 14 | #define JAVELIN_RGB_PIN 23 15 | 16 | #define JAVELIN_BUTTON_MATRIX 0 17 | #define JAVELIN_BUTTON_PINS 1 18 | 19 | // clang-format off 20 | constexpr uint8_t BUTTON_PINS[] = { 21 | 4, 26, 22 | 0, 2, 5, 7, 9, 16, 14, 18, 20, 22, 28, 23 | 1, 3, 6, 8, 15, 17, 19, 21, 27, 24 | 10, 11, 12, 13, 25 | 26 | 24, // usr button on MCU 27 | }; 28 | 29 | #define BOOTSEL_BUTTON_INDEX 27 30 | 31 | // 32 | // Button indexes 33 | // 0 1 34 | // 2 3 4 5 6 | 7 8 9 10 11 12 35 | // 13 14 15 16 6 | 7 17 18 19 20 21 36 | // 22 23 | 24 25 37 | // clang-format on 38 | 39 | #define JAVELIN_SCRIPT_CONFIGURATION \ 40 | R"({"name":"Picosteno","layout":[{"x":0,"y":0,"w":4,"h":1},{"x":8,"y":0,"w":4,"h":1},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":4,"y":1,"w":1,"h":2},{"x":7,"y":1,"w":1,"h":2},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":0,"y":2},{"x":1,"y":2},{"x":2,"y":2},{"x":3,"y":2},{"x":8,"y":2},{"x":9,"y":2},{"x":10,"y":2},{"x":11,"y":2},{"x":12,"y":2},{"x":3,"y":4},{"x":4,"y":4},{"x":7,"y":4},{"x":8,"y":4},{"x":5.75,"y":1.75,"s":0.5},{"x":5.75,"y":0.25,"s":0.5}]})" 41 | 42 | const size_t BUTTON_COUNT = 28; 43 | 44 | const char *const MANUFACTURER_NAME = "Noll Electronics LLC"; 45 | const char *const PRODUCT_NAME = "Picosteno (Javelin)"; 46 | const int VENDOR_ID = 0xFEED; 47 | 48 | //--------------------------------------------------------------------------- 49 | -------------------------------------------------------------------------------- /config/multisteno_v2.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define BOOTSEL_BUTTON_INDEX 42 13 | 14 | #define JAVELIN_DEBOUNCE_MS 10 15 | #define JAVELIN_BUTTON_MATRIX 1 16 | 17 | constexpr uint8_t COLUMN_PINS[] = {25, 24, 23, 22, 21, 20, 7, 4, 3, 2, 1, 0}; 18 | constexpr uint8_t ROW_PINS[] = {6, 5, 19, 18}; 19 | 20 | // clang-format off 21 | constexpr int8_t KEY_MAP[4][16] = { 22 | { 0, 1, 2, 3, 4, 5, /**/ 6, 7, 8, 9, 10, 11 }, 23 | { 12, 13, 14, 15, 16, 17, /**/ 18, 19, 20, 21, 22, 23 }, 24 | { 24, 25, 26, 27, 28, 29, /**/ 30, 31, 32, 33, 34, 35 }, 25 | { -1, -1, -1, 36, 37, 38, /**/ 39, 40, 41, -1, -1, -1 }, 26 | }; 27 | // clang-format on 28 | 29 | #define JAVELIN_SCRIPT_CONFIGURATION \ 30 | R"({"name":"Multisteno","layout":[{"x":0,"y":0},{"x":1,"y":0},{"x":2,"y":0},{"x":3,"y":0},{"x":4,"y":0},{"x":5,"y":0},{"x":8,"y":0},{"x":9,"y":0},{"x":10,"y":0},{"x":11,"y":0},{"x":12,"y":0},{"x":13,"y":0},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":4,"y":1},{"x":5,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":13,"y":1},{"x":0,"y":2},{"x":1,"y":2},{"x":2,"y":2},{"x":3,"y":2},{"x":4,"y":2},{"x":5,"y":2},{"x":8,"y":2},{"x":9,"y":2},{"x":10,"y":2},{"x":11,"y":2},{"x":12,"y":2},{"x":13,"y":2},{"x":3.5,"y":4},{"x":4.5,"y":4},{"x":5.5,"y":4},{"x":7.5,"y":4},{"x":8.5,"y":4},{"x":9.5,"y":4},{"x":7,"y":-1,"s":0.5}]} 31 | )" 32 | 33 | const size_t BUTTON_COUNT = 43; 34 | 35 | const char *const MANUFACTURER_NAME = "Noll Electronics LLC"; 36 | const char *const PRODUCT_NAME = "Multisteno (Javelin)"; 37 | const int VENDOR_ID = 0xFEED; 38 | 39 | //--------------------------------------------------------------------------- 40 | -------------------------------------------------------------------------------- /pico_split.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "javelin/split/split.h" 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #if JAVELIN_SPLIT 10 | 11 | class PicoSplit : public Split { 12 | public: 13 | static void Initialize() { instance.Initialize(); } 14 | static void Update() { instance.Update(); } 15 | static void PrintInfo() { instance.PrintInfo(); } 16 | static bool IsPairConnected() { return instance.isConnected; } 17 | 18 | private: 19 | struct SplitData { 20 | enum class State : uint8_t { 21 | READY_TO_SEND, 22 | SENDING, 23 | RECEIVING, 24 | }; 25 | 26 | SplitData(); 27 | 28 | State state; 29 | bool updateSendData = true; 30 | bool isConnected = false; 31 | uint8_t retryCount; 32 | uint8_t programOffset; 33 | uint16_t txId; 34 | uint16_t lastRxId; 35 | uint32_t receiveStartTime; 36 | uint64_t rxPacketCount; 37 | uint64_t txIrqCount; 38 | uint64_t rxWords; 39 | uint64_t txWords; 40 | 41 | #if JAVELIN_SPLIT_TX_PIN == JAVELIN_SPLIT_RX_PIN 42 | pio_sm_config config; 43 | #else 44 | pio_sm_config txConfig; 45 | pio_sm_config rxConfig; 46 | #endif 47 | 48 | size_t metrics[SplitMetricId::COUNT]; 49 | TxBuffer txBuffer; 50 | RxBuffer rxBuffer; 51 | 52 | void Initialize(); 53 | void StartTx(); 54 | void StartRx(); 55 | 56 | void SendData(); 57 | void SendTxBuffer(); 58 | void Update(); 59 | void ResetRxDma(); 60 | void OnReceiveFailed(); 61 | void OnReceiveTimeout(); 62 | void OnReceiveSucceeded(); 63 | 64 | bool ProcessReceive(); 65 | 66 | static void TxIrqHandler(); 67 | 68 | void PrintInfo(); 69 | }; 70 | 71 | static SplitData instance; 72 | }; 73 | 74 | #else 75 | 76 | class PicoSplit : public Split { 77 | public: 78 | static void Initialize() {} 79 | static void Update() {} 80 | static void PrintInfo() {} 81 | static bool IsPairConnected() { return false; } 82 | }; 83 | 84 | #endif // JAVELIN_SPLIT 85 | 86 | //--------------------------------------------------------------------------- -------------------------------------------------------------------------------- /libc_overrides.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // 3 | // Definitions to improve the performance of standard C functions. 4 | // 5 | //--------------------------------------------------------------------------- 6 | 7 | #include 8 | 9 | //--------------------------------------------------------------------------- 10 | 11 | #if JAVELIN_CPU_CORTEX_M0 12 | 13 | extern "C" [[gnu::naked]] size_t strlen(const char *p) { 14 | // spell-checker: disable 15 | asm volatile(R"( 16 | push {r0, r4} 17 | 18 | lsl r1, r0, #30 19 | beq 2f // p is aligned 20 | 21 | 1: 22 | ldrb r3, [r0] 23 | cmp r3, #0 24 | beq 5f // terminating '\0' found 25 | 26 | add r0, #1 27 | lsl r1, r0, #30 28 | bne 1b // Loop until aligned 29 | 30 | 2: // p is aligned 31 | ldr r2, =#0x01010101 32 | lsl r3, r2, #7 33 | 34 | 3: // Process 4 bytes 35 | ldmia r0!, {r1} 36 | sub r4, r1, r2 37 | bic r4, r1 38 | and r4, r3 39 | beq 3b 40 | 41 | 4: // Trailer of loop. 42 | sub r0, #4 43 | lsr r4, #8 44 | bcs 5f 45 | 46 | add r0, #1 47 | lsr r4, #8 48 | bcs 5f 49 | 50 | add r0, #1 51 | lsr r4, #8 52 | bcs 5f 53 | 54 | add r0, #1 55 | 56 | 5: 57 | pop {r1, r4} 58 | sub r0, r1 59 | bx lr 60 | )"); 61 | // spell-checker: enable 62 | } 63 | 64 | #elif JAVELIN_CPU_CORTEX_M33 65 | 66 | extern "C" [[gnu::naked]] size_t strlen(const char *p) { 67 | // spell-checker: disable 68 | asm volatile(R"( 69 | add.w r3, r0, #4 // Force wide version to align loop below without nop. 70 | ldr r2, =#0xfefefefe 71 | 72 | 1: // Process 4 bytes 73 | ldr r1, [r0], #4 74 | uqadd8 r1, r1, r2 75 | mvns r1, r1 76 | beq 1b 77 | 78 | // Trailer of loop. 79 | rbit r1, r1 80 | subs r0, r3 81 | clz r1, r1 82 | add r0, r0, r1, lsr #3 83 | 84 | bx lr 85 | )"); 86 | // spell-checker: enable 87 | } 88 | 89 | #endif 90 | 91 | //--------------------------------------------------------------------------- 92 | -------------------------------------------------------------------------------- /config/kyria_rev4.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "pico_encoder_state.h" 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | uint8_t BUTTON_PINS[] = {JAVELIN_ENCODER_BUTTON_PIN}; 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | #if defined(JAVELIN_DISPLAY_BACKLIGHT_PIN) 14 | int readBacklightPin(int testOutput) { 15 | gpio_init(JAVELIN_DISPLAY_BACKLIGHT_PIN); 16 | gpio_set_dir(JAVELIN_DISPLAY_BACKLIGHT_PIN, GPIO_OUT); 17 | gpio_put(JAVELIN_DISPLAY_BACKLIGHT_PIN, testOutput); 18 | sleep_us(5); 19 | adc_init(); 20 | gpio_init(JAVELIN_DISPLAY_BACKLIGHT_PIN); 21 | adc_gpio_init(JAVELIN_DISPLAY_BACKLIGHT_PIN); 22 | adc_select_input(JAVELIN_DISPLAY_BACKLIGHT_PIN - 26); 23 | 24 | // Dummy read -- first read gives noisy data. 25 | adc_read(); 26 | 27 | // Actual read 28 | const int value = adc_read(); 29 | gpio_init(JAVELIN_DISPLAY_BACKLIGHT_PIN); 30 | return value; 31 | } 32 | #endif 33 | 34 | bool shouldUseDisplayModule() { 35 | #if !USE_HALCYON_DISPLAY || !defined(JAVELIN_DISPLAY_BACKLIGHT_PIN) 36 | return false; 37 | #else 38 | const int value1 = readBacklightPin(0); 39 | if (value1 < 800 || value1 > 2000) { 40 | return false; 41 | } 42 | const int value2 = readBacklightPin(1); 43 | if (value2 < 800 || value2 > 2000) { 44 | return false; 45 | } 46 | return true; 47 | #endif 48 | } 49 | 50 | static bool IsSpiInitialized(spi_inst_t *instance) { 51 | return (spi_get_hw(instance)->cr1 & SPI_SSPCR1_SSE_BITS) != 0; 52 | } 53 | 54 | bool shouldUseEncoderModule() { 55 | #if !USE_HALCYON_ENCODER 56 | return false; 57 | #elif defined(JAVELIN_DISPLAY_SPI) 58 | return !IsSpiInitialized(JAVELIN_DISPLAY_SPI); 59 | #else 60 | return true; 61 | #endif 62 | } 63 | 64 | void preButtonStateInitialize() { 65 | if (!shouldUseEncoderModule()) { 66 | #if USE_HALCYON_ENCODER 67 | BUTTON_PINS[0] = JAVELIN_ENCODER_UNUSED_PIN; 68 | #endif 69 | PicoEncoderState::SetLocalEncoderCount(1); 70 | } 71 | } 72 | 73 | //--------------------------------------------------------------------------- 74 | -------------------------------------------------------------------------------- /config/polyglot.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define JAVELIN_RGB 1 13 | #define JAVELIN_RGB_COUNT 32 14 | #define JAVELIN_RGB_PIN 4 15 | 16 | #define BOOTSEL_BUTTON_INDEX 42 17 | 18 | #define JAVELIN_DEBOUNCE_MS 10 19 | #define JAVELIN_BUTTON_MATRIX 1 20 | 21 | constexpr uint8_t COLUMN_PINS[] = {24, 23, 21, 20, 19, 18, 11, 10, 9, 8, 6, 5}; 22 | constexpr uint8_t ROW_PINS[] = {26, 25, 17, 16}; 23 | 24 | // clang-format off 25 | constexpr int8_t KEY_MAP[4][16] = { 26 | { 0, 1, 2, 3, 4, 5, /**/ 6, 7, 8, 9, 10, 11 }, 27 | { 12, 13, 14, 15, 16, 17, /**/ 18, 19, 20, 21, 22, 23 }, 28 | { 24, 25, 26, 27, 28, 29, /**/ 30, 31, 32, 33, 34, 35 }, 29 | { -1, -1, -1, 36, 37, 38, /**/ 39, 40, 41, -1, -1, -1 }, 30 | }; 31 | // clang-format on 32 | 33 | #define JAVELIN_SCRIPT_CONFIGURATION \ 34 | R"({"n":"Polyglot","s":[{"c":"ff000000","s":"f","p":[{"c":"m","p":[-0.05,1]},{"c":"rl","p":[0,-1.05]},{"c":"ra","p":[13.1,0,0.05]},{"c":"ra","p":[0,4.1,0.05]},{"c":"ra","p":[-13.1,0,0.05]},{"c":"ra","p":[0,-1,0.05]},{"c":"c"}]}],"k":[{"x":0,"y":0},{"x":1,"y":0},{"x":2,"y":0},{"x":3,"y":0},{"x":4,"y":0},{"x":5,"y":0},{"x":7,"y":0},{"x":8,"y":0},{"x":9,"y":0},{"x":10,"y":0},{"x":11,"y":0},{"x":12,"y":0},{"x":0,"y":1},{"x":1,"y":1},{"x":2,"y":1},{"x":3,"y":1},{"x":4,"y":1},{"x":5,"y":1},{"x":7,"y":1},{"x":8,"y":1},{"x":9,"y":1},{"x":10,"y":1},{"x":11,"y":1},{"x":12,"y":1},{"x":0,"y":2},{"x":1,"y":2},{"x":2,"y":2},{"x":3,"y":2},{"x":4,"y":2},{"x":5,"y":2},{"x":7,"y":2},{"x":8,"y":2},{"x":9,"y":2},{"x":10,"y":2},{"x":11,"y":2},{"x":12,"y":2},{"x":3,"y":3},{"x":4,"y":3},{"x":5,"y":3},{"x":7,"y":3},{"x":8,"y":3},{"x":9,"y":3},{"x":12.5,"y":3.5,"s":0.5}]})" 35 | 36 | const size_t BUTTON_COUNT = 43; 37 | 38 | const char *const MANUFACTURER_NAME = "stenokeyboards"; 39 | const char *const PRODUCT_NAME = "The Polyglot (Javelin)"; 40 | const int VENDOR_ID = 0x9000; 41 | 42 | //--------------------------------------------------------------------------- 43 | -------------------------------------------------------------------------------- /hid_report_buffer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | class HidReportBufferBase { 10 | public: 11 | void SendReport(uint8_t reportId, const uint8_t *data, size_t length); 12 | void SendNextReport(); 13 | 14 | void Print(const char *p); 15 | void Reset() { 16 | startIndex = 0; 17 | endIndex = 0; 18 | } 19 | 20 | bool IsEmpty() const { return startIndex == endIndex; } 21 | bool IsFull() const { return endIndex - startIndex >= NUMBER_OF_ENTRIES; } 22 | size_t GetAvailableBufferCount() const { 23 | size_t used = endIndex - startIndex; 24 | return NUMBER_OF_ENTRIES - used; 25 | } 26 | 27 | static void PrintInfo(); 28 | 29 | static uint32_t reportsSentCount[]; 30 | 31 | protected: 32 | HidReportBufferBase(uint8_t entrySize, uint8_t instanceNumber) 33 | : entrySize(entrySize), instanceNumber(instanceNumber) {} 34 | 35 | static constexpr size_t NUMBER_OF_ENTRIES = 16; 36 | 37 | private: 38 | const uint8_t entrySize; 39 | const uint8_t instanceNumber; 40 | size_t startIndex = 0; 41 | size_t endIndex = 0; 42 | 43 | struct Entry { 44 | uint8_t length; 45 | uint8_t reportId; 46 | uint8_t data[1]; 47 | }; 48 | 49 | Entry *GetEntry(size_t index) { 50 | return (Entry *)(entryData + entrySize * index); 51 | } 52 | 53 | void PumpUntilNotFull(); 54 | 55 | public: 56 | uint8_t entryData[0]; 57 | }; 58 | 59 | //--------------------------------------------------------------------------- 60 | 61 | template 62 | struct HidReportBuffer : public HidReportBufferBase { 63 | public: 64 | HidReportBuffer(uint8_t instanceNumber) 65 | : HidReportBufferBase(DATA_SIZE + 2, instanceNumber) { 66 | #pragma GCC diagnostic push 67 | #pragma GCC diagnostic ignored "-Winvalid-offsetof" 68 | static_assert(offsetof(HidReportBuffer, buffers) == 69 | offsetof(HidReportBufferBase, entryData), 70 | "Data is not positioned where expected"); 71 | #pragma GCC diagnostic pop 72 | } 73 | 74 | private: 75 | // Each entry has one byte prefix which is the length, followed by the data. 76 | uint8_t buffers[(DATA_SIZE + 2) * NUMBER_OF_ENTRIES]; 77 | }; 78 | 79 | //--------------------------------------------------------------------------- 80 | -------------------------------------------------------------------------------- /pico_crc32.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "pico_crc32.h" 4 | #include "javelin/crc32.h" 5 | #include "pico_dma.h" 6 | #include "pico_sniff.h" 7 | #include "pico_spinlock.h" 8 | 9 | //--------------------------------------------------------------------------- 10 | 11 | void PicoCrc32::Initialize() { 12 | // Writing to ROM address 0 seems to work fine as a no-op. 13 | dma0->destination = 0; 14 | 15 | constexpr PicoDmaSniffControl sniffControl = { 16 | .enable = true, 17 | .dmaChannel = 0, 18 | .calculate = PicoDmaSniffControl::Calculate::BIT_REVERSED_CRC_32, 19 | .bitReverseOutput = true, 20 | .bitInvertOutput = true, 21 | }; 22 | sniff->control = sniffControl; 23 | } 24 | 25 | uint32_t PicoCrc32::Hash(const void *data, size_t length, PicoDma *dma) { 26 | #if JAVELIN_THREADS 27 | spinlock18->Lock(); 28 | #endif 29 | 30 | dma->source = data; 31 | 32 | sniff->data = 0xffffffff; 33 | 34 | const bool use32BitTransfer = ((intptr_t(data) | length) & 3) == 0; 35 | PicoDmaControl control; 36 | if (use32BitTransfer) { 37 | length >>= 2; 38 | constexpr PicoDmaControl dmaControl32BitTransfer = { 39 | .enable = true, 40 | .dataSize = PicoDmaControl::DataSize::WORD, 41 | .incrementRead = true, 42 | .incrementWrite = false, 43 | .chainToDma = 0, 44 | .transferRequest = PicoDmaTransferRequest::PERMANENT, 45 | .sniffEnable = true, 46 | }; 47 | dma->control = dmaControl32BitTransfer; 48 | } else { 49 | constexpr PicoDmaControl dmaControl8BitTransfer = { 50 | .enable = true, 51 | .dataSize = PicoDmaControl::DataSize::BYTE, 52 | .incrementRead = true, 53 | .incrementWrite = false, 54 | .chainToDma = 0, 55 | .transferRequest = PicoDmaTransferRequest::PERMANENT, 56 | .sniffEnable = true, 57 | }; 58 | dma->control = dmaControl8BitTransfer; 59 | } 60 | dma->countTrigger = length; 61 | 62 | dma->WaitUntilComplete(); 63 | const uint32_t value = sniff->data; 64 | 65 | #if JAVELIN_THREADS 66 | spinlock18->Unlock(); 67 | #endif 68 | 69 | return value; 70 | } 71 | 72 | //--------------------------------------------------------------------------- 73 | 74 | uint32_t Crc32::Hash(const void *v, size_t count) { 75 | return PicoCrc32::Hash(v, count, dma0); 76 | } 77 | 78 | //--------------------------------------------------------------------------- 79 | -------------------------------------------------------------------------------- /main_task.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include JAVELIN_BOARD_CONFIG 5 | #include "javelin/button_state.h" 6 | #include "javelin/container/cyclic_queue.h" 7 | #include "javelin/debounce.h" 8 | #include "javelin/split/split.h" 9 | #include "javelin/static_allocate.h" 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | #if JAVELIN_SPLIT 14 | class MasterTask final : public SplitTxHandler, public SplitRxHandler { 15 | #else 16 | class MasterTask { 17 | #endif 18 | public: 19 | void Update(); 20 | void UpdateQueue(); 21 | 22 | void PrintInfo() const; 23 | 24 | static JavelinStaticAllocate container; 25 | 26 | private: 27 | static constexpr size_t BUFFER_COUNT = 64; 28 | 29 | bool shouldProcessQueue; 30 | 31 | #if JAVELIN_SPLIT 32 | enum class TimestampState : uint8_t { 33 | DISCONNECTED, 34 | INACTIVE, 35 | REQUEST_TIMESTAMP, 36 | WAITING_FOR_RESPONSE, 37 | RESPONSE_RECEIVED, 38 | }; 39 | 40 | TimestampState timestampState; 41 | uint32_t slaveTimestamp; 42 | ButtonState slaveState; 43 | 44 | CyclicQueue slaveStates; 45 | #endif 46 | ButtonState localState; 47 | 48 | // TODO: Remove. 49 | GlobalDeferredDebounce debouncer; 50 | 51 | void ProcessQueue(); 52 | uint32_t GetScriptTime() const; 53 | 54 | #if JAVELIN_SPLIT 55 | bool IsWaitingForSlaveTimestamp() const; 56 | void RequestSlaveTimestamp(); 57 | 58 | void UpdateBuffer(TxBuffer &buffer) final; 59 | void OnTransmitConnected() final; 60 | void OnReceiveConnectionReset() final; 61 | void OnDataReceived(const void *data, size_t length) final; 62 | #endif 63 | }; 64 | 65 | class SlaveTask final : public SplitTxHandler, public SplitRxHandler { 66 | public: 67 | void Update(); 68 | void UpdateBuffer(TxBuffer &buffer) final; 69 | 70 | static JavelinStaticAllocate container; 71 | 72 | private: 73 | static constexpr size_t TIMESTAMP_HISTORY_COUNT = 64; 74 | 75 | bool needsTransmit; 76 | bool updateMasterTimestampOffset; 77 | uint8_t timestampOffsetIndex; 78 | int32_t masterTimestampOffset; 79 | int32_t timestampOffsets[TIMESTAMP_HISTORY_COUNT]; 80 | 81 | static constexpr size_t BUFFER_COUNT = 32; 82 | CyclicQueue buffers; 83 | 84 | // Temp -- remove. 85 | GlobalDeferredDebounce debouncer; 86 | 87 | void UpdateQueue(); 88 | 89 | void OnTransmitConnected() final; 90 | void OnDataReceived(const void *data, size_t length) final; 91 | }; 92 | 93 | //--------------------------------------------------------------------------- 94 | -------------------------------------------------------------------------------- /config/crkbd_v4_pair.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "encoder_pins.h" 5 | #include "pair_flash_layout.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #define JAVELIN_USE_EMBEDDED_STENO 0 10 | #define JAVELIN_USE_USER_DICTIONARY 0 11 | #define JAVELIN_USB_MILLIAMPS 500 12 | 13 | #define JAVELIN_RGB 1 14 | #define JAVELIN_RGB_COUNT 46 15 | #define JAVELIN_RGB_LEFT_COUNT 23 16 | #define JAVELIN_RGB_PIN 10 17 | #define JAVELIN_USE_RGB_MAP 1 18 | 19 | #define JAVELIN_SPLIT 1 20 | #define JAVELIN_SPLIT_PIO_CYCLES 20 21 | #define JAVELIN_SPLIT_TX_PIN 12 22 | #define JAVELIN_SPLIT_RX_PIN 12 23 | #define JAVELIN_SPLIT_IS_MASTER 0 24 | #define JAVELIN_SPLIT_IS_LEFT 0 25 | // #define JAVELIN_SPLIT_SIDE_PIN xx 26 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 512 27 | 28 | #define JAVELIN_BUTTON_MATRIX 0 29 | #define JAVELIN_BUTTON_PINS 1 30 | 31 | // clang-format off 32 | constexpr uint8_t BUTTON_PINS[] = { 33 | 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, /**/ 25, 27, 1, 2, 3, 9, 8, 34 | 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, /**/ 23, 26, 28, 0, 4, 14, 11, 35 | 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, /**/ 22, 20, 29, 5, 18, 15, 36 | 0x7f, 0x7f, 0x7f, /**/ 19, 17, 16, 37 | }; 38 | 39 | // clang-format off 40 | // 41 | // Button indexes 42 | // 0 1 2 3 4 5 6 | 7 8 9 10 11 12 13 43 | // 14 15 16 17 18 19 20 | 21 22 23 24 25 26 27 44 | // 28 29 30 31 32 33 | 34 35 36 37 38 39 45 | // 40 41 42 | 43 44 45 46 | // 47 | // Front: 48 | // 18 17 12 11 04 03 21 | 44 26 27 34 35 40 41 49 | // 19 16 13 10 05 02 22 | 45 25 28 33 36 39 42 50 | // 20 15 14 09 06 01 | 24 29 32 37 38 43 51 | // 08 07 00 | 23 30 31 52 | 53 | constexpr uint8_t RGB_MAP[JAVELIN_RGB_COUNT] = { 54 | 18, 17, 12, 11, 04, 03, 21, /**/ 44, 26, 27, 34, 35, 40, 41, 55 | 19, 16, 13, 10, 05, 02, 22, /**/ 45, 25, 28, 33, 36, 39, 42, 56 | 20, 15, 14, 9, 06, 01, /**/ 24, 29, 32, 37, 38, 43, 57 | 8, 7, 0, /**/ 23, 30, 31 58 | }; 59 | 60 | // clang-format on 61 | 62 | const size_t BUTTON_COUNT = 46; 63 | 64 | #define JAVELIN_ENCODER 1 65 | #define JAVELIN_ENCODER_COUNT 4 66 | #define JAVELIN_ENCODER_LEFT_COUNT 2 67 | #define JAVELIN_ENCODER_SPEED 2 68 | 69 | constexpr EncoderPins ENCODER_PINS[] = {{5, 7}, {6, 7}, {24, 7}, {6, 7}}; 70 | 71 | const char *const MANUFACTURER_NAME = "foostan"; 72 | const char *const PRODUCT_NAME = "Corne (Javelin)"; 73 | const int VENDOR_ID = 0x4653; 74 | // const int PRODUCT_ID = 0x0002; 75 | 76 | //--------------------------------------------------------------------------- 77 | -------------------------------------------------------------------------------- /pico_encoder_state.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include JAVELIN_BOARD_CONFIG 5 | #include "javelin/debounce.h" 6 | #include "javelin/split/split.h" 7 | 8 | //--------------------------------------------------------------------------- 9 | 10 | #if JAVELIN_ENCODER 11 | 12 | class PicoEncoderState 13 | #if JAVELIN_SPLIT 14 | #if JAVELIN_SPLIT_IS_MASTER 15 | : public SplitRxHandler 16 | #else 17 | : public SplitTxHandler 18 | #endif 19 | #endif 20 | { 21 | public: 22 | static void Initialize() { instance.InitializeInternal(); } 23 | 24 | static size_t GetLocalEncoderCount() { return instance.localEncoderCount; } 25 | static void SetLocalEncoderCount(size_t value) { 26 | instance.localEncoderCount = value; 27 | } 28 | 29 | static void Update() { instance.UpdateInternal(); } 30 | static void UpdateNoScriptCall() { instance.UpdateNoScriptCallInternal(); } 31 | 32 | static void RegisterTxHandler() { 33 | #if JAVELIN_SPLIT && !JAVELIN_SPLIT_IS_MASTER 34 | Split::RegisterTxHandler(&instance); 35 | #endif 36 | } 37 | static void RegisterRxHandler() { 38 | #if JAVELIN_SPLIT && JAVELIN_SPLIT_IS_MASTER 39 | Split::RegisterRxHandler(SplitHandlerId::ENCODER, &instance); 40 | #endif 41 | } 42 | 43 | bool SetConfiguration(size_t encoderIndex, const int8_t *data); 44 | static void SetEncoderConfiguration_Binding(void *context, 45 | const char *commandLine); 46 | 47 | private: 48 | PicoEncoderState(); 49 | 50 | uint8_t localEncoderCount; 51 | #if JAVELIN_SPLIT 52 | uint8_t localEncoderOffset; 53 | #else 54 | static const size_t localEncoderOffset = 0; 55 | #endif 56 | 57 | int8_t deltas[JAVELIN_ENCODER_COUNT]; 58 | int8_t lastLUT[JAVELIN_ENCODER_COUNT]; 59 | #if JAVELIN_SPLIT && !JAVELIN_SPLIT_IS_MASTER 60 | int8_t lastDeltas[JAVELIN_ENCODER_COUNT]; 61 | #endif 62 | GlobalDeferredDebounce lastEncoderStates[JAVELIN_ENCODER_COUNT]; 63 | int8_t encoderLUT[JAVELIN_ENCODER_COUNT][16]; 64 | 65 | static PicoEncoderState instance; 66 | 67 | void InitializeInternal(); 68 | void UpdateInternal(); 69 | void UpdateNoScriptCallInternal(); 70 | 71 | bool HasAnyDelta() const; 72 | 73 | void OnDataReceived(const void *data, size_t length); 74 | void UpdateBuffer(TxBuffer &buffer); 75 | void OnReceiveConnected(); 76 | 77 | void CallScript(size_t encoderIndex, int delta); 78 | 79 | void SendConfigurationInfoToPair(size_t encoderIndex); 80 | }; 81 | 82 | #else 83 | 84 | class PicoEncoderState { 85 | public: 86 | static void Initialize() {} 87 | static void Update() {} 88 | static void UpdateNoScriptCall() {} 89 | static void RegisterTxHandler() {} 90 | static void RegisterRxHandler() {} 91 | }; 92 | 93 | #endif 94 | 95 | //--------------------------------------------------------------------------- 96 | -------------------------------------------------------------------------------- /pico_spinlock.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | struct PicoSpinlock { 9 | public: 10 | void Lock() { 11 | while (spinlock == 0) { 12 | // Busy wait. 13 | } 14 | } 15 | 16 | void Unlock() { spinlock = (intptr_t)(this); } 17 | 18 | private: 19 | volatile uint32_t spinlock; 20 | }; 21 | 22 | //--------------------------------------------------------------------------- 23 | 24 | static PicoSpinlock *const spinlock0 = (PicoSpinlock *)0xd0000100; 25 | static PicoSpinlock *const spinlock1 = (PicoSpinlock *)0xd0000104; 26 | static PicoSpinlock *const spinlock2 = (PicoSpinlock *)0xd0000108; 27 | static PicoSpinlock *const spinlock3 = (PicoSpinlock *)0xd000010c; 28 | static PicoSpinlock *const spinlock4 = (PicoSpinlock *)0xd0000110; 29 | static PicoSpinlock *const spinlock5 = (PicoSpinlock *)0xd0000114; 30 | static PicoSpinlock *const spinlock6 = (PicoSpinlock *)0xd0000118; 31 | static PicoSpinlock *const spinlock7 = (PicoSpinlock *)0xd000011c; 32 | static PicoSpinlock *const spinlock8 = (PicoSpinlock *)0xd0000120; 33 | static PicoSpinlock *const spinlock9 = (PicoSpinlock *)0xd0000124; 34 | static PicoSpinlock *const spinlock10 = (PicoSpinlock *)0xd0000128; 35 | static PicoSpinlock *const spinlock11 = (PicoSpinlock *)0xd000012c; 36 | static PicoSpinlock *const spinlock12 = (PicoSpinlock *)0xd0000130; 37 | static PicoSpinlock *const spinlock13 = (PicoSpinlock *)0xd0000134; 38 | static PicoSpinlock *const spinlock14 = (PicoSpinlock *)0xd0000138; 39 | static PicoSpinlock *const spinlock15 = (PicoSpinlock *)0xd000013c; 40 | static PicoSpinlock *const spinlock16 = (PicoSpinlock *)0xd0000140; 41 | static PicoSpinlock *const spinlock17 = (PicoSpinlock *)0xd0000144; 42 | static PicoSpinlock *const spinlock18 = (PicoSpinlock *)0xd0000148; 43 | static PicoSpinlock *const spinlock19 = (PicoSpinlock *)0xd000014c; 44 | static PicoSpinlock *const spinlock20 = (PicoSpinlock *)0xd0000150; 45 | static PicoSpinlock *const spinlock21 = (PicoSpinlock *)0xd0000154; 46 | static PicoSpinlock *const spinlock22 = (PicoSpinlock *)0xd0000158; 47 | static PicoSpinlock *const spinlock23 = (PicoSpinlock *)0xd000015c; 48 | static PicoSpinlock *const spinlock24 = (PicoSpinlock *)0xd0000160; 49 | static PicoSpinlock *const spinlock25 = (PicoSpinlock *)0xd0000164; 50 | static PicoSpinlock *const spinlock26 = (PicoSpinlock *)0xd0000168; 51 | static PicoSpinlock *const spinlock27 = (PicoSpinlock *)0xd000016c; 52 | static PicoSpinlock *const spinlock28 = (PicoSpinlock *)0xd0000170; 53 | static PicoSpinlock *const spinlock29 = (PicoSpinlock *)0xd0000174; 54 | static PicoSpinlock *const spinlock30 = (PicoSpinlock *)0xd0000178; 55 | static PicoSpinlock *const spinlock31 = (PicoSpinlock *)0xd000017c; 56 | 57 | //--------------------------------------------------------------------------- 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi pico-sdk bindings for javelin-steno. 2 | 3 | ## Build Instructions 4 | 5 | 1. Ensure that you have a recent Arm GNU Toolchain (>=12) and CMake (>=4) installed. 6 | 7 | 2. Install [pico-sdk](https://github.com/raspberrypi/pico-sdk) and ensure that 8 | you can build the examples. 9 | 10 | 3. Clone [javelin-steno](https://github.com/jthlim/javelin-steno) repository. 11 | 12 | 4. Clone this repository. 13 | 14 | 5. From within this repository, link the javelin-steno repository: 15 | 16 | ``` 17 | > ln -s javelin 18 | ``` 19 | 20 | 6. Standard CMake, with '-D JAVELIN_BOARD=xxx' 21 | 22 | ``` 23 | > mkdir build 24 | > cd build 25 | > cmake .. -D JAVELIN_BOARD=uni_v4 26 | > make 27 | ``` 28 | 29 | For rp2350 boards, add `-DPICO_PLATFORM=rp2350-arm-s`, e.g., 30 | ``` 31 | > cmake .. -DJAVELIN_BOARD=starboard_rp2350 -DPICO_PLATFORM=rp2350-arm-s 32 | ``` 33 | 34 | 35 | You should now have a uf2 file that can be copied to the device. 36 | 37 | ## Using javelin-steno-pico with your own hardware. 38 | 39 | This process involves uploading all of the data to the keyboard, then 40 | overwriting the bit of the firmware that is specific to interfacing with 41 | your keyboard's hardware. 42 | 43 | 1. First, use the online firmware builder to build a firmware that matches 44 | your chip. It is recommended to use starboard rp2040 or rp2350. Upload 45 | this to your board and confirm that you can connect to the web tools 46 | and do a lookup. 47 | 48 | 2. Copy one of the `config/*.h` files to create a new configuration. 49 | 50 | Example configurations: 51 | 52 | Type | Direct Wired | Matrix 53 | --------|------------------|--------- 54 | Unibody | starboard_rp2040 | uni_v4 55 | Split | crkbd_v4 | crkbd_v3 56 | 57 | 3. Update your pin/button configuration there. 58 | 59 | 4. `JAVELIN_SCRIPT_CONFIGURATION` inside the config file is a JSON that enables 60 | the online web tools to provide a visual editor configuration. 61 | 62 | 5. Build the uf2 firmware and upload it to your keyboard. 63 | 64 | 6. Use the web Script tool to upload a configuration to your keyboard. This 65 | maps buttons to steno keys/other actions. 66 | 67 | If you use a split keyboard, then you will need to repeat this with a *_pair.h 68 | firmware for the other side. 69 | 70 | # Contributions 71 | 72 | This project is a snapshot of internal repositories and is not accepting any 73 | pull requests. 74 | 75 | # Terms 76 | 77 | This code is distributed under [PolyForm Noncommercial license](LICENSE.txt). 78 | For commercial use, please [contact](mailto:jeff@lim.au) me. 79 | 80 | # Troubleshooting 81 | 82 | When building, if you receive a message like: 83 | 84 | ``` 85 | error: uinitialized const member in 'const char []' 86 | ``` 87 | 88 | This means that you're running an older version of the toolchain that has a 89 | compiler bug. Update to a newer version at 90 | https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads 91 | -------------------------------------------------------------------------------- /config/starboard_rp2350.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define JAVELIN_BUTTON_MATRIX 0 13 | #define JAVELIN_BUTTON_PINS 1 14 | 15 | // clang-format off 16 | constexpr uint8_t BUTTON_PINS[] = { 17 | 8, 7, 2, 1, 0, 5, /**/ 22, 28, 27, 26, 18, 17, // Top row 18 | 9, 10, 3, 4, 6, /**/ 21, 20, 19, 15, 16, // Middle row 19 | 11, 12, /**/ 13, 14, // Thumb row 20 | }; 21 | 22 | #define BOOTSEL_BUTTON_INDEX 26 23 | 24 | // 25 | // Button indexes 26 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 27 | // 12 13 14 15 16 5 | 6 17 18 19 20 21 28 | // 22 23 | 24 25 29 | // clang-format on 30 | 31 | #define JAVELIN_SCRIPT_CONFIGURATION \ 32 | R"({"n":"Starboard","s":[{"p":[{"c":"m","p":[7,2.25]},{"c":"rl","p":[0.9,0]},{"c":"ra","p":[1.33,-0.77,0.1]},{"c":"ra","p":[-0.1,-0.18,0.1]},{"c":"ra","p":[0.97,-0.56,0.1]},{"c":"ra","p":[0.14,0.22,0.1]},{"c":"ra","p":[0.89,-0.51,0.1]},{"c":"ra","p":[0.25,0.44,0.1]},{"c":"ra","p":[1.8,-1.04,0.1]},{"c":"ra","p":[1.07,1.85,0.1]},{"c":"ra","p":[-2.52,1.46,0.1]},{"c":"ra","p":[-2.88,2.88,0.1]},{"c":"ra","p":[-0.2,-0.2,0.1]},{"c":"ra","p":[-3.3,0,0.1]},{"c":"ra","p":[-0.2,0.2,0.1]},{"c":"ra","p":[-2.88,-2.88,0.1]},{"c":"ra","p":[-2.52,-1.46,0.1]},{"c":"ra","p":[1.07,-1.85,0.1]},{"c":"ra","p":[1.8,1.04,0.1]},{"c":"ra","p":[0.25,-0.44,0.1]},{"c":"ra","p":[0.89,0.51,0.1]},{"c":"ra","p":[0.14,-0.22,0.1]},{"c":"ra","p":[0.97,0.56,0.1]},{"c":"ra","p":[-0.1,0.18,0.1]},{"c":"ra","p":[1.33,0.77,0.1]},{"c":"c"}]}],"k":[{"x":0.53,"y":0.1,"r":0.52},{"x":1.43,"y":0.62,"r":0.52},{"x":2.58,"y":0.7,"r":0.52},{"x":3.6,"y":1,"r":0.52},{"x":4.4,"y":1.7,"r":0.52},{"x":5.05,"y":2.18,"w":1,"h":2,"r":0.52},{"x":7.95,"y":2.18,"w":1,"h":2,"r":-0.52},{"x":8.6,"y":1.7,"r":-0.52},{"x":9.4,"y":1,"r":-0.52},{"x":10.42,"y":0.7,"r":-0.52},{"x":11.57,"y":0.62,"r":-0.52},{"x":12.47,"y":0.1,"r":-0.52},{"x":0,"y":1,"r":0.52},{"x":0.9,"y":1.52,"r":0.52},{"x":2.05,"y":1.6,"r":0.52},{"x":3.07,"y":1.9,"r":0.52},{"x":3.87,"y":2.6,"r":0.52},{"x":9.13,"y":2.6,"r":-0.52},{"x":9.93,"y":1.9,"r":-0.52},{"x":10.95,"y":1.6,"r":-0.52},{"x":12.1,"y":1.52,"r":-0.52},{"x":13,"y":1,"r":-0.52},{"x":3.9,"y":4,"r":0.79},{"x":4.64,"y":4.74,"r":0.79},{"x":8.36,"y":4.74,"r":-0.79},{"x":9.1,"y":4,"r":-0.79},{"x":6.75,"y":3,"s":0.5}]})" 33 | 34 | const size_t BUTTON_COUNT = 27; 35 | 36 | const char *const MANUFACTURER_NAME = "Andrew Hess"; 37 | const char *const PRODUCT_NAME = "Starboard (Javelin)"; 38 | const int VENDOR_ID = 0xFEED; 39 | // const int PRODUCT_ID = 0xABCD; 40 | 41 | //--------------------------------------------------------------------------- 42 | -------------------------------------------------------------------------------- /split_hid_report_buffer.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "javelin/container/queue.h" 5 | #include "javelin/split/split.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #if JAVELIN_SPLIT 10 | 11 | class SplitHidReportBuffer { 12 | public: 13 | static void Add(uint8_t interface, uint8_t reportId, const uint8_t *data, 14 | size_t length) { 15 | instance.Add(interface, reportId, data, length); 16 | } 17 | 18 | static void Update() { instance.Update(); } 19 | 20 | static void RegisterMasterHandlers() { 21 | Split::RegisterTxHandler(&instance); 22 | Split::RegisterRxHandler(SplitHandlerId::HID_BUFFER_SIZE, 23 | &instance.bufferSize); 24 | } 25 | 26 | static void RegisterSlaveHandlers() { 27 | Split::RegisterRxHandler(SplitHandlerId::HID_REPORT, &instance); 28 | Split::RegisterTxHandler(&instance.bufferSize); 29 | } 30 | 31 | private: 32 | // To avoid flooding the HID buffers and exhausting memory, the buffer size 33 | // levels are shared and the sender will block until there is available space. 34 | union HidBufferSize { 35 | uint8_t available[4]; 36 | uint32_t value; 37 | }; 38 | 39 | struct SplitHidReportBufferSize : SplitTxHandler, SplitRxHandler { 40 | bool dirty; 41 | HidBufferSize bufferSize; 42 | 43 | virtual void UpdateBuffer(TxBuffer &buffer); 44 | virtual void OnTransmitConnected() { dirty = true; } 45 | virtual void OnDataReceived(const void *data, size_t length); 46 | }; 47 | 48 | struct EntryData { 49 | uint8_t interface; 50 | uint8_t reportId; 51 | uint8_t length; 52 | uint8_t data[0]; 53 | }; 54 | 55 | struct SplitHidReportBufferData final : public Queue, 56 | public SplitTxHandler, 57 | public SplitRxHandler { 58 | SplitHidReportBufferSize bufferSize; 59 | 60 | void Add(uint8_t interface, uint8_t reportId, const uint8_t *data, 61 | size_t length); 62 | void Update(); 63 | 64 | bool ProcessEntry(const QueueEntry *entry); 65 | 66 | static QueueEntry *CreateEntry(uint8_t interface, 67 | uint8_t reportId, 68 | const uint8_t *data, 69 | size_t length); 70 | 71 | virtual void UpdateBuffer(TxBuffer &buffer); 72 | virtual void OnDataReceived(const void *data, size_t length); 73 | }; 74 | 75 | static SplitHidReportBufferData instance; 76 | }; 77 | 78 | #else 79 | 80 | class SplitHidReportBuffer { 81 | public: 82 | static void Add(const uint8_t *data) {} 83 | static void Update() {} 84 | 85 | static void RegisterMasterHandlers() {} 86 | static void RegisterSlaveHandlers() {} 87 | }; 88 | 89 | #endif // JAVELIN_SPLIT 90 | 91 | //--------------------------------------------------------------------------- 92 | -------------------------------------------------------------------------------- /config/crkbd_v3_pair.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "pair_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 0 9 | #define JAVELIN_USE_USER_DICTIONARY 0 10 | #define JAVELIN_USB_MILLIAMPS 500 11 | 12 | #define JAVELIN_RGB 1 13 | #define JAVELIN_RGB_COUNT 54 14 | #define JAVELIN_RGB_LEFT_COUNT 27 15 | #define JAVELIN_RGB_PIN 0 16 | #define JAVELIN_USE_RGB_MAP 1 17 | 18 | #define JAVELIN_SPLIT 1 19 | #define JAVELIN_SPLIT_PIO_CYCLES 20 20 | #define JAVELIN_SPLIT_TX_PIN 1 21 | #define JAVELIN_SPLIT_RX_PIN 1 22 | #define JAVELIN_SPLIT_IS_MASTER 0 23 | #define JAVELIN_SPLIT_IS_LEFT 0 24 | // #define JAVELIN_SPLIT_SIDE_PIN xx 25 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 512 26 | 27 | #define JAVELIN_DISPLAY_DRIVER 1306 28 | #define JAVELIN_OLED_WIDTH 128 29 | #define JAVELIN_OLED_HEIGHT 32 30 | #define JAVELIN_DISPLAY_WIDTH 32 31 | #define JAVELIN_DISPLAY_HEIGHT 128 32 | #define JAVELIN_OLED_I2C i2c1 33 | #define JAVELIN_OLED_TX_DMA I2C1_TX 34 | #define JAVELIN_OLED_SDA_PIN 2 35 | #define JAVELIN_OLED_SCL_PIN 3 36 | #define JAVELIN_OLED_I2C_ADDRESS 0x3c 37 | #define JAVELIN_OLED_ROTATION 90 38 | 39 | #define JAVELIN_BUTTON_MATRIX 1 40 | 41 | constexpr uint8_t RIGHT_COLUMN_PINS[] = {20, 22, 26, 27, 28, 29}; 42 | constexpr uint8_t RIGHT_ROW_PINS[] = {4, 5, 6, 7}; 43 | 44 | // clang-format off 45 | // 46 | // Button indexes 47 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 48 | // 12 13 14 15 16 17 | 18 19 20 21 22 23 49 | // 24 25 26 27 28 29 | 30 31 32 33 34 35 50 | // 36 37 38 | 39 40 41 51 | // 52 | // RGB indices 53 | // Back: 54 | // 2 1 0 | 27 28 29 55 | // | 56 | // 3 4 5 | 32 31 30 57 | // | 58 | // 59 | // Front: 60 | // 24 23 18 17 10 09 | 36 37 44 45 50 51 61 | // 25 22 19 16 11 08 | 35 38 43 46 49 52 62 | // 26 21 20 15 12 07 | 34 39 42 47 48 53 63 | // 14 13 06 | 33 40 41 64 | 65 | constexpr int8_t RIGHT_KEY_MAP[4][6] = { 66 | { 6, 7, 8, 9, 10, 11, }, 67 | { 18, 19, 20, 21, 22, 23, }, 68 | { 30, 31, 32, 33, 34, 35, }, 69 | { 39, 40, 41, -1, -1, -1, }, 70 | }; 71 | 72 | constexpr uint8_t RGB_MAP[54] = { 73 | 24, 23, 18, 17, 10, 9, 36, 37, 44, 45, 50, 51, 74 | 25, 22, 19, 16, 11, 8, 35, 38, 43, 46, 49, 52, 75 | 26, 21, 20, 15, 12, 7, 34, 39, 42, 47, 48, 53, 76 | 14, 13, 6, 33, 40, 41, 77 | 78 | // Underglow 79 | 2, 1, 0, 27, 28, 29, 80 | 3, 4, 5, 32, 31, 30, 81 | }; 82 | 83 | // clang-format on 84 | 85 | const size_t BUTTON_COUNT = 42; 86 | 87 | const char *const MANUFACTURER_NAME = "foostan"; 88 | const char *const PRODUCT_NAME = "Corne (Javelin)"; 89 | const int VENDOR_ID = 0x4653; 90 | // const int PRODUCT_ID = 0x0002; 91 | 92 | //--------------------------------------------------------------------------- 93 | -------------------------------------------------------------------------------- /pinnacle.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | // Driver for Cirque. 3 | //--------------------------------------------------------------------------- 4 | 5 | #pragma once 6 | #include JAVELIN_BOARD_CONFIG 7 | #include "javelin/split/split.h" 8 | #include 9 | #include 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | struct Pointer { 14 | int x; 15 | int y; 16 | int z; 17 | 18 | bool operator==(const Pointer &other) const = default; 19 | }; 20 | 21 | //--------------------------------------------------------------------------- 22 | 23 | // Pinnacle devices return ID 0x73a, so use that as the ID 24 | #if JAVELIN_POINTER == 0x73a 25 | 26 | enum class PinnacleRegister : int; 27 | 28 | // Driver for Cirque touchpads. 29 | class Pinnacle 30 | #if JAVELIN_SPLIT 31 | #if JAVELIN_SPLIT_IS_MASTER 32 | : public SplitRxHandler 33 | #else 34 | : public SplitTxHandler 35 | #endif 36 | #endif 37 | { 38 | public: 39 | static void Initialize() { instance.InitializeInternal(); } 40 | static void Uninitialize() { instance.UninitializeInternal(); } 41 | 42 | static void Update() { instance.UpdateInternal(); } 43 | static void PrintInfo(); 44 | 45 | static void RegisterTxHandler() { 46 | #if JAVELIN_SPLIT && !JAVELIN_SPLIT_IS_MASTER 47 | Split::RegisterTxHandler(&instance); 48 | #endif 49 | } 50 | static void RegisterRxHandler() { 51 | #if JAVELIN_SPLIT && JAVELIN_SPLIT_IS_MASTER 52 | Split::RegisterRxHandler(SplitHandlerId::POINTER, &instance); 53 | #endif 54 | } 55 | 56 | static void WriteRegister(int chipSelectPin, PinnacleRegister reg, int value); 57 | static int ReadRegister(int chipSelectPin, PinnacleRegister reg); 58 | static void ReadRegisters(int chipSelectPin, PinnacleRegister startingReg, 59 | uint8_t *result, size_t length); 60 | 61 | static void ClearFlags(int chipSelectPin); 62 | 63 | private: 64 | uint8_t localPointerCount; 65 | #if JAVELIN_SPLIT 66 | uint8_t localPointerOffset; 67 | #else 68 | static const size_t localPointerOffset = 0; 69 | #endif 70 | 71 | bool available; 72 | struct Data { 73 | bool isDirty; 74 | Pointer pointer; 75 | }; 76 | Data data[JAVELIN_POINTER_COUNT]; 77 | 78 | void InitializeInternal(); 79 | void UninitializeInternal(); 80 | void UpdateInternal(); 81 | 82 | void OnDataReceived(const void *data, size_t length); 83 | bool ShouldTransmitData() const; 84 | void UpdateBuffer(TxBuffer &buffer); 85 | 86 | static void CallScript(size_t pointerIndex, const Pointer &pointer); 87 | 88 | static Pinnacle instance; 89 | }; 90 | 91 | #else 92 | 93 | struct Pinnacle { 94 | public: 95 | static void Initialize() {} 96 | static void RegisterTxHandler() {} 97 | static void RegisterRxHandler() {} 98 | 99 | static void Update() {} 100 | static void PrintInfo() {} 101 | }; 102 | 103 | #endif 104 | 105 | //--------------------------------------------------------------------------- 106 | -------------------------------------------------------------------------------- /config/starboard_rp2040.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 100 11 | 12 | #define JAVELIN_RGB 1 13 | #define JAVELIN_RGB_COUNT 1 14 | #define JAVELIN_RGB_PIN 23 15 | 16 | #define JAVELIN_BUTTON_MATRIX 0 17 | #define JAVELIN_BUTTON_PINS 1 18 | 19 | // clang-format off 20 | constexpr uint8_t BUTTON_PINS[] = { 21 | 8, 7, 2, 1, 0, 5, /**/ 22, 28, 27, 26, 18, 17, // Top row 22 | 9, 10, 3, 4, 6, /**/ 21, 20, 19, 15, 16, // Middle row 23 | 11, 12, /**/ 13, 14, // Thumb row 24 | 25 | 24, // usr button on MCU 26 | }; 27 | 28 | #define BOOTSEL_BUTTON_INDEX 27 29 | 30 | // 31 | // Button indexes 32 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 33 | // 12 13 14 15 16 5 | 6 17 18 19 20 21 34 | // 22 23 | 24 25 35 | // clang-format on 36 | 37 | #define JAVELIN_SCRIPT_CONFIGURATION \ 38 | R"({"n":"Starboard","s":[{"p":[{"c":"m","p":[7,2.25]},{"c":"rl","p":[0.9,0]},{"c":"ra","p":[1.33,-0.77,0.1]},{"c":"ra","p":[-0.1,-0.18,0.1]},{"c":"ra","p":[0.97,-0.56,0.1]},{"c":"ra","p":[0.14,0.22,0.1]},{"c":"ra","p":[0.89,-0.51,0.1]},{"c":"ra","p":[0.25,0.44,0.1]},{"c":"ra","p":[1.8,-1.04,0.1]},{"c":"ra","p":[1.07,1.85,0.1]},{"c":"ra","p":[-2.52,1.46,0.1]},{"c":"ra","p":[-2.88,2.88,0.1]},{"c":"ra","p":[-0.2,-0.2,0.1]},{"c":"ra","p":[-3.3,0,0.1]},{"c":"ra","p":[-0.2,0.2,0.1]},{"c":"ra","p":[-2.88,-2.88,0.1]},{"c":"ra","p":[-2.52,-1.46,0.1]},{"c":"ra","p":[1.07,-1.85,0.1]},{"c":"ra","p":[1.8,1.04,0.1]},{"c":"ra","p":[0.25,-0.44,0.1]},{"c":"ra","p":[0.89,0.51,0.1]},{"c":"ra","p":[0.14,-0.22,0.1]},{"c":"ra","p":[0.97,0.56,0.1]},{"c":"ra","p":[-0.1,0.18,0.1]},{"c":"ra","p":[1.33,0.77,0.1]},{"c":"c"}]}],"k":[{"x":0.53,"y":0.1,"r":0.52},{"x":1.43,"y":0.62,"r":0.52},{"x":2.58,"y":0.7,"r":0.52},{"x":3.6,"y":1,"r":0.52},{"x":4.4,"y":1.7,"r":0.52},{"x":5.05,"y":2.18,"w":1,"h":2,"r":0.52},{"x":7.95,"y":2.18,"w":1,"h":2,"r":-0.52},{"x":8.6,"y":1.7,"r":-0.52},{"x":9.4,"y":1,"r":-0.52},{"x":10.42,"y":0.7,"r":-0.52},{"x":11.57,"y":0.62,"r":-0.52},{"x":12.47,"y":0.1,"r":-0.52},{"x":0,"y":1,"r":0.52},{"x":0.9,"y":1.52,"r":0.52},{"x":2.05,"y":1.6,"r":0.52},{"x":3.07,"y":1.9,"r":0.52},{"x":3.87,"y":2.6,"r":0.52},{"x":9.13,"y":2.6,"r":-0.52},{"x":9.93,"y":1.9,"r":-0.52},{"x":10.95,"y":1.6,"r":-0.52},{"x":12.1,"y":1.52,"r":-0.52},{"x":13,"y":1,"r":-0.52},{"x":3.9,"y":4,"r":0.79},{"x":4.64,"y":4.74,"r":0.79},{"x":8.36,"y":4.74,"r":-0.79},{"x":9.1,"y":4,"r":-0.79},{"x":6.75,"y":5,"s":0.5},{"x":6.75,"y":3,"s":0.5}]})" 39 | 40 | const size_t BUTTON_COUNT = 28; 41 | 42 | const char *const MANUFACTURER_NAME = "Andrew Hess"; 43 | const char *const PRODUCT_NAME = "Starboard (Javelin)"; 44 | const int VENDOR_ID = 0xFEED; 45 | // const int PRODUCT_ID = 0xABCD; 46 | 47 | //--------------------------------------------------------------------------- 48 | -------------------------------------------------------------------------------- /pico_ws2812.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "javelin/split/split.h" 5 | #include JAVELIN_BOARD_CONFIG 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #if JAVELIN_RGB 10 | 11 | #define WS2812_GRB 0 12 | #define WS2812_RGB 1 13 | 14 | class Ws2812 { 15 | public: 16 | static void Initialize(); 17 | static void Update() { instance.Update(); } 18 | static bool IsAvailable() { return true; } 19 | static size_t GetCount() { return JAVELIN_RGB_COUNT; } 20 | 21 | static void SetRgb(size_t pixelId, int r, int g, int b) { 22 | #if JAVELIN_RGB_ORDER == WS2812_RGB 23 | instance.SetRgb(pixelId, (r << 24) | (g << 16) | (b << 8)); 24 | #else 25 | instance.SetRgb(pixelId, (r << 16) | (g << 24) | (b << 8)); 26 | #endif 27 | } 28 | static void SetRgb(size_t pixelId, uint32_t ws2812Color) { 29 | instance.SetRgb(pixelId, ws2812Color); 30 | } 31 | 32 | static void RegisterTxHandler() { Split::RegisterTxHandler(&instance); } 33 | 34 | static void RegisterRxHandler() { 35 | Split::RegisterRxHandler(SplitHandlerId::RGB, &instance); 36 | } 37 | 38 | private: 39 | #if JAVELIN_SPLIT 40 | struct Ws2812Data final : public SplitTxHandler, SplitRxHandler { 41 | #else 42 | struct Ws2812Data { 43 | #endif 44 | bool dirty; 45 | #if JAVELIN_SPLIT 46 | bool slaveDirty; 47 | #endif 48 | 49 | uint32_t lastUpdate; 50 | uint32_t pixelValues[JAVELIN_RGB_COUNT]; 51 | 52 | void Update(); 53 | void SetRgb(size_t pixelId, uint32_t ws2812Color) { 54 | if (pixelId >= JAVELIN_RGB_COUNT) { 55 | return; 56 | } 57 | #if JAVELIN_USE_RGB_MAP 58 | pixelId = RGB_MAP[pixelId]; 59 | #endif 60 | if (pixelValues[pixelId] == ws2812Color) { 61 | return; 62 | } 63 | #if JAVELIN_SPLIT 64 | if (Split::IsLeft()) { 65 | if (pixelId < JAVELIN_RGB_LEFT_COUNT) { 66 | dirty = true; 67 | } else { 68 | slaveDirty = true; 69 | } 70 | 71 | } else { 72 | if (pixelId < JAVELIN_RGB_LEFT_COUNT) { 73 | slaveDirty = true; 74 | } else { 75 | dirty = true; 76 | } 77 | } 78 | #else 79 | dirty = true; 80 | #endif 81 | pixelValues[pixelId] = ws2812Color; 82 | } 83 | 84 | #if JAVELIN_SPLIT 85 | void UpdateBuffer(TxBuffer &buffer) final; 86 | void OnTransmitConnected() final { slaveDirty = true; } 87 | void OnTransmitConnectionReset() final { slaveDirty = true; } 88 | void OnDataReceived(const void *data, size_t length) final; 89 | #endif 90 | }; 91 | 92 | static Ws2812Data instance; 93 | }; 94 | 95 | #else 96 | 97 | // Do nothing instance. 98 | class Ws2812 { 99 | public: 100 | static void Initialize() {} 101 | static void Update() {} 102 | static bool IsAvailable() { return false; } 103 | static size_t GetCount() { return 0; } 104 | 105 | static void SetRgb(size_t pixelId, int r, int g, int b) {} 106 | static void SetRgb(size_t pixelId, uint32_t ws2812Color) {} 107 | 108 | static void RegisterTxHandler() {} 109 | static void RegisterRxHandler() {} 110 | }; 111 | 112 | #endif 113 | 114 | //--------------------------------------------------------------------------- 115 | -------------------------------------------------------------------------------- /pico_serial_port.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "pico_serial_port.h" 4 | #include "javelin/console.h" 5 | #include "javelin/console_input_buffer.h" 6 | #include "javelin/hal/serial_port.h" 7 | #include "javelin/split/split_serial_buffer.h" 8 | #include "javelin/start_console_command_detector.h" 9 | #include 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | #if JAVELIN_SPLIT && !JAVELIN_SPLIT_IS_MASTER 14 | 15 | void SplitSerialBuffer::SplitSerialBufferData::OnDataReceived(const void *data, 16 | size_t length) { 17 | if (tud_cdc_connected()) { 18 | tud_cdc_write(data, length); 19 | tud_cdc_write_flush(); 20 | } 21 | } 22 | 23 | #endif 24 | 25 | //--------------------------------------------------------------------------- 26 | 27 | void SerialPort::SendData(const void *data, size_t length) { 28 | if (PicoSerialPort::HasOpenSerialConsole()) { 29 | return; 30 | } 31 | if (tud_cdc_connected()) { 32 | tud_cdc_write(data, length); 33 | tud_cdc_write_flush(); 34 | } else { 35 | #if JAVELIN_SPLIT 36 | if (Split::IsMaster()) { 37 | SplitSerialBuffer::Add(data, length); 38 | } 39 | #endif 40 | } 41 | } 42 | 43 | //--------------------------------------------------------------------------- 44 | 45 | bool PicoSerialPort::hasOpenSerialConsole = false; 46 | static StartConsoleCommandDetector commandDetector; 47 | 48 | void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { 49 | if (!dtr) { 50 | PicoSerialPort::OnSerialPortDisconnected(); 51 | } 52 | } 53 | 54 | void PicoSerialPort::OnSerialPortDisconnected() { 55 | hasOpenSerialConsole = false; 56 | commandDetector.Reset(); 57 | } 58 | 59 | void PicoSerialPort::SendSerialConsole(const void *data, size_t length) { 60 | if (tud_cdc_connected()) { 61 | size_t written = tud_cdc_write(data, length); 62 | for (;;) { 63 | length -= written; 64 | if (length == 0) { 65 | break; 66 | } 67 | data = (uint8_t *)data + written; 68 | tud_task(); 69 | written = tud_cdc_write(data, length); 70 | } 71 | } 72 | } 73 | 74 | void PicoSerialPort::Flush() { tud_cdc_write_flush(); } 75 | 76 | void PicoSerialPort::HandleIncomingData() { 77 | static uint8_t buffer[1024]; 78 | for (uint8_t itf = 0; itf < CFG_TUD_CDC; itf++) { 79 | while (tud_cdc_n_available(itf)) { 80 | const uint32_t count = tud_cdc_n_read(itf, buffer, sizeof(buffer)); 81 | if (!hasOpenSerialConsole) { 82 | const size_t bufferUsedForStartCommand = 83 | commandDetector.IsStartCommandPresent(buffer, count); 84 | if (bufferUsedForStartCommand != (size_t)-1) { 85 | hasOpenSerialConsole = true; 86 | if (count > bufferUsedForStartCommand) { 87 | ConsoleInputBuffer::Add(buffer + bufferUsedForStartCommand, 88 | count - bufferUsedForStartCommand, 89 | ConnectionId::SERIAL_CONSOLE); 90 | } 91 | } 92 | } else { 93 | ConsoleInputBuffer::Add(buffer, count, ConnectionId::SERIAL_CONSOLE); 94 | } 95 | } 96 | } 97 | } 98 | 99 | //--------------------------------------------------------------------------- 100 | -------------------------------------------------------------------------------- /main_report_builder.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "hid_report_buffer.h" 5 | #include "javelin/mem.h" 6 | #include 7 | #include 8 | 9 | //--------------------------------------------------------------------------- 10 | 11 | class Console; 12 | 13 | //--------------------------------------------------------------------------- 14 | 15 | struct MainReportBuilder { 16 | public: 17 | void Press(uint8_t key); 18 | void Release(uint8_t key); 19 | 20 | void PressMouseButton(size_t buttonIndex); 21 | void ReleaseMouseButton(size_t buttonIndex); 22 | void MoveMouse(int dx, int dy); 23 | void VWheelMouse(int delta); 24 | void HWheelMouse(int delta); 25 | 26 | void FlushAllIfRequired(); 27 | void Flush(); 28 | void FlushMouse(); 29 | void SendNextReport() { reportBuffer.SendNextReport(); } 30 | 31 | void Reset(); 32 | size_t GetAvailableBufferCount() const { 33 | return reportBuffer.GetAvailableBufferCount(); 34 | } 35 | 36 | void SetConfiguration(uint8_t configurationValue) { 37 | if (configurationValue == 0) { 38 | compatibilityMode = false; 39 | repeatReportCount = 0; 40 | } else { 41 | compatibilityMode = true; 42 | repeatReportCount = configurationValue - 1; 43 | } 44 | } 45 | void AddConsoleCommands(Console &console); 46 | static void GetKeyboardProtocol_Binding(); 47 | 48 | void PrintInfo() const; 49 | 50 | static MainReportBuilder instance; 51 | 52 | private: 53 | MainReportBuilder(); 54 | 55 | struct Buffer { 56 | union { 57 | uint8_t data[32]; 58 | uint32_t data32[8]; 59 | }; 60 | 61 | union { 62 | uint8_t presenceFlags[32]; 63 | uint16_t presenceFlags16[16]; 64 | uint32_t presenceFlags32[8]; 65 | }; 66 | }; 67 | 68 | struct MouseBuffer { 69 | // Order is important. First 7 bytes is the report packet. 70 | uint32_t buttonData; 71 | int16_t dx, dy, vWheel, hWheel; 72 | uint8_t movementMask; // Bit 0 = dx,dy, Bit 1 = vWheel, Bit 2 = hWheel 73 | uint32_t buttonPresence; 74 | 75 | void Reset() { Mem::Clear(*this); } 76 | 77 | void SetMove(int x, int y) { 78 | dx = x; 79 | dy = y; 80 | movementMask |= 1; 81 | } 82 | 83 | void SetVWheel(int delta) { 84 | vWheel = delta; 85 | movementMask |= 2; 86 | } 87 | 88 | void SetHWheel(int delta) { 89 | hWheel = delta; 90 | movementMask |= 4; 91 | } 92 | 93 | bool HasMovement() const { return (movementMask & 1) != 0; } 94 | bool HasVWheel() const { return (movementMask & 2) != 0; } 95 | bool HasHWheel() const { return (movementMask & 4) != 0; } 96 | 97 | bool HasData() const { return (movementMask | buttonPresence) != 0; } 98 | }; 99 | 100 | bool compatibilityMode = false; 101 | uint8_t modifiers = 0; 102 | uint8_t maxPressIndex = 0; 103 | uint8_t repeatReportCount = 0; 104 | int wpmTally = 0; 105 | Buffer buffers[2]; 106 | MouseBuffer mouseBuffers[2]; 107 | 108 | static constexpr size_t MAXIMUM_REPORT_DATA_SIZE = 17; 109 | HidReportBuffer reportBuffer; 110 | 111 | bool HasData() const; 112 | bool HasMouseData() const { return mouseBuffers[0].HasData(); } 113 | void SendKeyboardPageReportIfRequired(); 114 | void SendConsumerPageReportIfRequired(); 115 | void SendMousePageReportIfRequired(); 116 | 117 | void SendReport(uint8_t reportId, const uint8_t *data, size_t size); 118 | 119 | static void SetKeyboardProtocol_Binding(void *context, 120 | const char *commandLine); 121 | 122 | friend class SplitHidReportBuffer; 123 | }; 124 | 125 | //--------------------------------------------------------------------------- 126 | -------------------------------------------------------------------------------- /config/crkbd_v4.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "encoder_pins.h" 5 | #include "main_flash_layout.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | struct StenoConfigBlock; 10 | struct StenoOrthography; 11 | struct StenoDictionaryCollection; 12 | 13 | //--------------------------------------------------------------------------- 14 | 15 | #define JAVELIN_USE_EMBEDDED_STENO 1 16 | #define JAVELIN_USE_USER_DICTIONARY 1 17 | #define JAVELIN_USB_MILLIAMPS 500 18 | 19 | #define JAVELIN_RGB 1 20 | #define JAVELIN_RGB_COUNT 46 21 | #define JAVELIN_RGB_LEFT_COUNT 23 22 | #define JAVELIN_RGB_PIN 10 23 | #define JAVELIN_USE_RGB_MAP 1 24 | 25 | #define JAVELIN_SPLIT 1 26 | #define JAVELIN_SPLIT_PIO_CYCLES 20 27 | #define JAVELIN_SPLIT_TX_PIN 12 28 | #define JAVELIN_SPLIT_RX_PIN 12 29 | #define JAVELIN_SPLIT_IS_MASTER 1 30 | #define JAVELIN_SPLIT_IS_LEFT 1 31 | // #define JAVELIN_SPLIT_SIDE_PIN xx 32 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 512 33 | 34 | #define JAVELIN_BUTTON_MATRIX 0 35 | #define JAVELIN_BUTTON_PINS 1 36 | 37 | // clang-format off 38 | constexpr uint8_t BUTTON_PINS[] = { 39 | 22, 20, 23, 26, 29, 0, 4, /**/ 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 40 | 19, 18, 24, 27, 1, 2, 8, /**/ 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 41 | 17, 16, 25, 28, 3, 9, /**/ 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 42 | 14, 15, 11, /**/ 0x7f, 0x7f, 0x7f, 43 | }; 44 | 45 | // clang-format off 46 | // 47 | // Button indexes 48 | // 0 1 2 3 4 5 6 | 7 8 9 10 11 12 13 49 | // 14 15 16 17 18 19 20 | 21 22 23 24 25 26 27 50 | // 28 29 30 31 32 33 | 34 35 36 37 38 39 51 | // 40 41 42 | 43 44 45 52 | // 53 | // Front: 54 | // 18 17 12 11 04 03 21 | 44 26 27 34 35 40 41 55 | // 19 16 13 10 05 02 22 | 45 25 28 33 36 39 42 56 | // 20 15 14 09 06 01 | 24 29 32 37 38 43 57 | // 08 07 00 | 23 30 31 58 | 59 | constexpr uint8_t RGB_MAP[JAVELIN_RGB_COUNT] = { 60 | 18, 17, 12, 11, 04, 03, 21, /**/ 44, 26, 27, 34, 35, 40, 41, 61 | 19, 16, 13, 10, 05, 02, 22, /**/ 45, 25, 28, 33, 36, 39, 42, 62 | 20, 15, 14, 9, 06, 01, /**/ 24, 29, 32, 37, 38, 43, 63 | 8, 7, 0, /**/ 23, 30, 31 64 | }; 65 | 66 | // clang-format on 67 | 68 | #define JAVELIN_SCRIPT_CONFIGURATION \ 69 | R"({"name":"CorneV4","layout":[{"x":0,"y":0.3},{"x":1,"y":0.3},{"x":2,"y":0.1},{"x":3,"y":0},{"x":4,"y":0.1},{"x":5,"y":0.2},{"x":6,"y":0.7},{"x":8,"y":0.7},{"x":9,"y":0.2},{"x":10,"y":0.1},{"x":11,"y":0},{"x":12,"y":0.1},{"x":13,"y":0.3},{"x":14,"y":0.3},{"x":0,"y":1.3},{"x":1,"y":1.3},{"x":2,"y":1.1},{"x":3,"y":1},{"x":4,"y":1.1},{"x":5,"y":1.2},{"x":6,"y":1.7},{"x":8,"y":1.7},{"x":9,"y":1.2},{"x":10,"y":1.1},{"x":11,"y":1},{"x":12,"y":1.1},{"x":13,"y":1.3},{"x":14,"y":1.3},{"x":0,"y":2.3},{"x":1,"y":2.3},{"x":2,"y":2.1},{"x":3,"y":2},{"x":4,"y":2.1},{"x":5,"y":2.2},{"x":9,"y":2.2},{"x":10,"y":2.1},{"x":11,"y":2},{"x":12,"y":2.1},{"x":13,"y":2.3},{"x":14,"y":2.3},{"x":3.5,"y":3.4},{"x":4.7,"y":3.58,"r":0.26},{"x":5.95,"y":3.6,"w":1,"h":1.5,"r":0.52},{"x":8.05,"y":3.6,"w":1,"h":1.5,"r":-0.52},{"x":9.3,"y":3.58,"r":-0.26},{"x":10.5,"y":3.4}]})" 70 | 71 | const size_t BUTTON_COUNT = 46; 72 | 73 | #define JAVELIN_ENCODER 1 74 | #define JAVELIN_ENCODER_COUNT 4 75 | #define JAVELIN_ENCODER_LEFT_COUNT 2 76 | #define JAVELIN_ENCODER_SPEED 2 77 | 78 | constexpr EncoderPins ENCODER_PINS[] = {{5, 7}, {6, 7}, {24, 7}, {6, 7}}; 79 | 80 | const char *const MANUFACTURER_NAME = "foostan"; 81 | const char *const PRODUCT_NAME = "Corne (Javelin)"; 82 | const int VENDOR_ID = 0x4653; 83 | // const int PRODUCT_ID = 0x0001; 84 | 85 | //--------------------------------------------------------------------------- 86 | -------------------------------------------------------------------------------- /config/crkbd_v3.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "main_flash_layout.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | #define JAVELIN_USE_EMBEDDED_STENO 1 9 | #define JAVELIN_USE_USER_DICTIONARY 1 10 | #define JAVELIN_USB_MILLIAMPS 500 11 | 12 | #define JAVELIN_RGB 1 13 | #define JAVELIN_RGB_COUNT 54 14 | #define JAVELIN_RGB_LEFT_COUNT 27 15 | #define JAVELIN_RGB_PIN 0 16 | #define JAVELIN_USE_RGB_MAP 1 17 | 18 | #define JAVELIN_SPLIT 1 19 | #define JAVELIN_SPLIT_PIO_CYCLES 20 20 | #define JAVELIN_SPLIT_TX_PIN 1 21 | #define JAVELIN_SPLIT_RX_PIN 1 22 | #define JAVELIN_SPLIT_IS_MASTER 1 23 | #define JAVELIN_SPLIT_IS_LEFT 1 24 | // #define JAVELIN_SPLIT_SIDE_PIN xx 25 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 512 26 | 27 | #define JAVELIN_DISPLAY_DRIVER 1306 28 | #define JAVELIN_OLED_WIDTH 128 29 | #define JAVELIN_OLED_HEIGHT 32 30 | #define JAVELIN_DISPLAY_WIDTH 32 31 | #define JAVELIN_DISPLAY_HEIGHT 128 32 | #define JAVELIN_OLED_I2C i2c1 33 | #define JAVELIN_OLED_TX_DMA I2C1_TX 34 | #define JAVELIN_OLED_SDA_PIN 2 35 | #define JAVELIN_OLED_SCL_PIN 3 36 | #define JAVELIN_OLED_I2C_ADDRESS 0x3c 37 | #define JAVELIN_OLED_ROTATION 90 38 | 39 | #define JAVELIN_BUTTON_MATRIX 1 40 | 41 | constexpr uint8_t LEFT_COLUMN_PINS[] = {29, 28, 27, 26, 22, 20}; 42 | constexpr uint8_t LEFT_ROW_PINS[] = {4, 5, 6, 7}; 43 | 44 | // clang-format off 45 | // 46 | // Button indexes 47 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 48 | // 12 13 14 15 16 17 | 18 19 20 21 22 23 49 | // 24 25 26 27 28 29 | 30 31 32 33 34 35 50 | // 36 37 38 | 39 40 41 51 | // 52 | // RGB indices 53 | // Back: 54 | // 2 1 0 | 27 28 29 55 | // | 56 | // 3 4 5 | 32 31 30 57 | // | 58 | // 59 | // Front: 60 | // 24 23 18 17 10 09 | 36 37 44 45 50 51 61 | // 25 22 19 16 11 08 | 35 38 43 46 49 52 62 | // 26 21 20 15 12 07 | 34 39 42 47 48 53 63 | // 14 13 06 | 33 40 41 64 | 65 | constexpr int8_t LEFT_KEY_MAP[4][6] = { 66 | { 0, 1, 2, 3, 4, 5, }, 67 | { 12, 13, 14, 15, 16, 17, }, 68 | { 24, 25, 26, 27, 28, 29, }, 69 | { -1, -1, -1, 36, 37, 38, }, 70 | }; 71 | 72 | constexpr uint8_t RGB_MAP[54] = { 73 | 24, 23, 18, 17, 10, 9, 36, 37, 44, 45, 50, 51, 74 | 25, 22, 19, 16, 11, 8, 35, 38, 43, 46, 49, 52, 75 | 26, 21, 20, 15, 12, 7, 34, 39, 42, 47, 48, 53, 76 | 14, 13, 6, 33, 40, 41, 77 | 78 | // Underglow 79 | 2, 1, 0, 27, 28, 29, 80 | 3, 4, 5, 32, 31, 30, 81 | }; 82 | 83 | // clang-format on 84 | 85 | #define JAVELIN_SCRIPT_CONFIGURATION \ 86 | R"({"name":"Corne","layout":[{"x":0,"y":0.3},{"x":1,"y":0.3},{"x":2,"y":0.1},{"x":3,"y":0},{"x":4,"y":0.1},{"x":5,"y":0.2},{"x":9,"y":0.2},{"x":10,"y":0.1},{"x":11,"y":0},{"x":12,"y":0.1},{"x":13,"y":0.3},{"x":14,"y":0.3},{"x":0,"y":1.3},{"x":1,"y":1.3},{"x":2,"y":1.1},{"x":3,"y":1},{"x":4,"y":1.1},{"x":5,"y":1.2},{"x":9,"y":1.2},{"x":10,"y":1.1},{"x":11,"y":1},{"x":12,"y":1.1},{"x":13,"y":1.3},{"x":14,"y":1.3},{"x":0,"y":2.3},{"x":1,"y":2.3},{"x":2,"y":2.1},{"x":3,"y":2},{"x":4,"y":2.1},{"x":5,"y":2.2},{"x":9,"y":2.2},{"x":10,"y":2.1},{"x":11,"y":2},{"x":12,"y":2.1},{"x":13,"y":2.3},{"x":14,"y":2.3},{"x":3.5,"y":3.4},{"x":4.7,"y":3.58,"r":0.26},{"x":5.95,"y":3.6,"w":1,"h":1.5,"r":0.52},{"x":8.05,"y":3.6,"w":1,"h":1.5,"r":-0.52},{"x":9.3,"y":3.58,"r":-0.26},{"x":10.5,"y":3.4}]})" 87 | 88 | const size_t BUTTON_COUNT = 42; 89 | 90 | const char *const MANUFACTURER_NAME = "foostan"; 91 | const char *const PRODUCT_NAME = "Corne (Javelin)"; 92 | const int VENDOR_ID = 0x4653; 93 | // const int PRODUCT_ID = 0x0001; 94 | 95 | //--------------------------------------------------------------------------- 96 | -------------------------------------------------------------------------------- /hid_report_buffer.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "hid_report_buffer.h" 4 | #include "javelin/clock.h" 5 | #include "javelin/console.h" 6 | #include "pico_split.h" 7 | #include "split_hid_report_buffer.h" 8 | #include "usb_descriptors.h" 9 | 10 | #include 11 | #include 12 | 13 | //--------------------------------------------------------------------------- 14 | 15 | constexpr uint32_t FULL_BUFFER_RESET_TIMEOUT = 50; 16 | 17 | //--------------------------------------------------------------------------- 18 | 19 | uint32_t HidReportBufferBase::reportsSentCount[ITF_NUM_TOTAL] = {}; 20 | 21 | //--------------------------------------------------------------------------- 22 | 23 | void HidReportBufferBase::SendReport(uint8_t reportId, const uint8_t *data, 24 | size_t length) { 25 | PumpUntilNotFull(); 26 | 27 | const bool triggerSend = startIndex == endIndex; 28 | if (triggerSend) { 29 | 30 | // Wake up host if we are in suspend mode 31 | if ((instanceNumber == ITF_NUM_KEYBOARD || 32 | instanceNumber == ITF_NUM_CONSOLE) && 33 | tud_suspended()) { 34 | tud_remote_wakeup(); 35 | } 36 | 37 | if (!tud_hid_n_ready(instanceNumber)) { 38 | #if JAVELIN_SPLIT 39 | if (Split::IsMaster() && 40 | Connection::IsPairConnected(PairConnectionId::ACTIVE)) { 41 | reportsSentCount[instanceNumber]++; 42 | SplitHidReportBuffer::Add(instanceNumber, reportId, data, length); 43 | } 44 | #endif 45 | return; 46 | } 47 | 48 | if (tud_hid_n_report(instanceNumber, reportId, data, length)) { 49 | ++endIndex; 50 | reportsSentCount[instanceNumber]++; 51 | } 52 | return; 53 | } 54 | 55 | const size_t entryIndex = endIndex++ & (NUMBER_OF_ENTRIES - 1); 56 | Entry *entry = GetEntry(entryIndex); 57 | entry->length = length; 58 | entry->reportId = reportId; 59 | memcpy(entry->data, data, length); 60 | } 61 | 62 | //--------------------------------------------------------------------------- 63 | 64 | void HidReportBufferBase::SendNextReport() { 65 | // This should never happen! 66 | if (startIndex == endIndex) { 67 | return; 68 | } 69 | 70 | for (;;) { 71 | ++startIndex; 72 | if (startIndex == endIndex) { 73 | return; 74 | } 75 | 76 | const size_t entryIndex = startIndex & (NUMBER_OF_ENTRIES - 1); 77 | const Entry *entry = GetEntry(entryIndex); 78 | 79 | reportsSentCount[instanceNumber]++; 80 | if (tud_hid_n_report(instanceNumber, entry->reportId, entry->data, 81 | entry->length)) { 82 | return; 83 | } 84 | } 85 | } 86 | 87 | //--------------------------------------------------------------------------- 88 | 89 | void HidReportBufferBase::PumpUntilNotFull() { 90 | // Android and Linux do not read 'un-attached' usb endpoints, leading the 91 | // console to end up in an infinite waiting loop to have a free entry. 92 | // 93 | // To protect against this, check if there's no progress for a period of time 94 | // and reset the buffer if so. 95 | const size_t startTime = Clock::GetMilliseconds(); 96 | for (;;) { 97 | tud_task(); 98 | PicoSplit::Update(); 99 | 100 | if (!IsFull()) { 101 | return; 102 | } 103 | 104 | if (Clock::GetMilliseconds() - startTime > FULL_BUFFER_RESET_TIMEOUT) { 105 | Reset(); 106 | return; 107 | } 108 | } 109 | } 110 | 111 | //--------------------------------------------------------------------------- 112 | 113 | void HidReportBufferBase::PrintInfo() { 114 | Console::Printf("HID reports sent\n"); 115 | Console::Printf(" Keyboard: %u\n", reportsSentCount[ITF_NUM_KEYBOARD]); 116 | Console::Printf(" Plover HID: %u\n", reportsSentCount[ITF_NUM_PLOVER_HID]); 117 | Console::Printf(" Console: %u\n", reportsSentCount[ITF_NUM_CONSOLE]); 118 | } 119 | 120 | //--------------------------------------------------------------------------- 121 | -------------------------------------------------------------------------------- /ssd1306_paper_tape.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/font/monochrome/font.h" 4 | #include "ssd1306.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | const uint8_t PAPER_TAPE_FONT_DATA[] = { 9 | 5, 0x14, 0x3e, 0x14, 0x3e, 0x14, // # 10 | 4, 0x24, 0x4a, 0x52, 0x24, // S 11 | 5, 0x02, 0x02, 0x7e, 0x02, 0x02, // T 12 | 4, 0x7e, 0x18, 0x24, 0x42, // K 13 | 4, 0x7e, 0x12, 0x12, 0x0c, // P 14 | 5, 0x3e, 0x40, 0x30, 0x40, 0x3e, // W 15 | 4, 0x7e, 0x10, 0x10, 0x7e, // H 16 | 4, 0x7e, 0x12, 0x12, 0x6c, // R 17 | 4, 0x7c, 0x12, 0x12, 0x7c, // A 18 | 4, 0x3c, 0x42, 0x42, 0x3c, // O 19 | 5, 0x24, 0x18, 0x7e, 0x18, 0x24, // * 20 | 4, 0x7e, 0x52, 0x52, 0x42, // E 21 | 4, 0x3e, 0x40, 0x40, 0x3e, // U 22 | 4, 0x7e, 0x12, 0x12, 0x02, // F 23 | 4, 0x7e, 0x12, 0x12, 0x6c, // R 24 | 4, 0x7e, 0x12, 0x12, 0x0c, // P 25 | 4, 0x7e, 0x52, 0x52, 0x2c, // B 26 | 4, 0x7e, 0x40, 0x40, 0x40, // L 27 | 4, 0x3c, 0x42, 0x52, 0x34, // G 28 | 5, 0x02, 0x02, 0x7e, 0x02, 0x02, // T 29 | 4, 0x24, 0x4a, 0x52, 0x24, // S 30 | 4, 0x7e, 0x42, 0x42, 0x3c, // D 31 | 4, 0x62, 0x52, 0x4a, 0x46, // Z 32 | }; 33 | 34 | //--------------------------------------------------------------------------- 35 | 36 | #if JAVELIN_DISPLAY_DRIVER == 1306 37 | 38 | //--------------------------------------------------------------------------- 39 | 40 | void Ssd1306::Ssd1306Data::DrawPaperTape(const StenoStroke *strokes, 41 | size_t length) { 42 | if (!available) { 43 | return; 44 | } 45 | 46 | Clear(); 47 | 48 | #if JAVELIN_DISPLAY_WIDTH >= 64 49 | constexpr size_t MAXIMUM_STROKES = JAVELIN_DISPLAY_HEIGHT / 8; 50 | size_t startingStroke = 51 | length > MAXIMUM_STROKES ? length - MAXIMUM_STROKES : 0; 52 | size_t lineOffset = length < MAXIMUM_STROKES ? MAXIMUM_STROKES - length : 0; 53 | 54 | if (lineOffset >= 2) { 55 | DrawLine(0, 0, JAVELIN_DISPLAY_WIDTH, 0); 56 | DrawLine(JAVELIN_DISPLAY_WIDTH - 1, 0, JAVELIN_DISPLAY_WIDTH - 1, 15); 57 | DrawLine(0, 15, JAVELIN_DISPLAY_WIDTH, 15); 58 | DrawLine(0, 0, 0, 15); 59 | DrawText(JAVELIN_DISPLAY_WIDTH / 2, 12, &Font::DEFAULT, 60 | TextAlignment::MIDDLE, "Paper Tape"); 61 | } 62 | 63 | for (size_t i = startingStroke; i < length; ++i) { 64 | uint32_t stroke = strokes[i].GetKeyState(); 65 | uint8_t *p = &buffer8[lineOffset++]; 66 | const uint8_t *f = PAPER_TAPE_FONT_DATA; 67 | 68 | for (size_t i = 0; i < 23; ++i) { 69 | int width = *f++; 70 | if (stroke & (1 << i)) { 71 | for (int x = 0; x < width; ++x) { 72 | *p = *f++; 73 | p += JAVELIN_DISPLAY_HEIGHT / 8; 74 | } 75 | } else { 76 | p += width * (JAVELIN_DISPLAY_HEIGHT / 8); 77 | f += width; 78 | } 79 | p += JAVELIN_DISPLAY_HEIGHT / 8; 80 | } 81 | } 82 | #elif JAVELIN_DISPLAY_WIDTH >= 23 83 | constexpr size_t MAXIMUM_STROKES = JAVELIN_DISPLAY_HEIGHT; 84 | size_t startingStroke = 85 | length > MAXIMUM_STROKES ? length - MAXIMUM_STROKES : 0; 86 | size_t y = length < MAXIMUM_STROKES ? MAXIMUM_STROKES - length : 0; 87 | 88 | for (size_t i = startingStroke; i < length; ++i) { 89 | uint32_t stroke = strokes[i].GetKeyState(); 90 | uint8_t *p = &buffer8[y / 8]; 91 | uint8_t mask = 1 << (y & 7); 92 | ++y; 93 | 94 | for (size_t i = 0; i < 23; ++i) { 95 | if (stroke & (1 << i)) { 96 | *p |= mask; 97 | } 98 | p += JAVELIN_DISPLAY_HEIGHT / 8; 99 | } 100 | } 101 | #endif 102 | } 103 | 104 | //--------------------------------------------------------------------------- 105 | 106 | #endif // JAVELIN_DISPLAY_DRIVER == 1306 107 | 108 | //--------------------------------------------------------------------------- 109 | -------------------------------------------------------------------------------- /config/kyria_rev3_pair.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "encoder_pins.h" 5 | #include "pair_flash_layout.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #define JAVELIN_USE_EMBEDDED_STENO 0 10 | #define JAVELIN_USE_USER_DICTIONARY 0 11 | #define JAVELIN_USB_MILLIAMPS 500 12 | 13 | #define JAVELIN_RGB 1 14 | #define JAVELIN_RGB_COUNT 62 15 | #define JAVELIN_RGB_LEFT_COUNT 31 16 | #define JAVELIN_RGB_PIN 0 17 | #define JAVELIN_USE_RGB_MAP 1 18 | 19 | #define JAVELIN_SPLIT 1 20 | #define JAVELIN_SPLIT_PIO_CYCLES 16 21 | #define JAVELIN_SPLIT_TX_PIN 1 22 | #define JAVELIN_SPLIT_RX_PIN 1 23 | #define JAVELIN_SPLIT_IS_MASTER 0 24 | #define JAVELIN_SPLIT_SIDE_PIN 9 25 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 512 26 | 27 | #define JAVELIN_DISPLAY_DRIVER 1306 28 | #define JAVELIN_OLED_WIDTH 128 29 | #define JAVELIN_OLED_HEIGHT 64 30 | #define JAVELIN_DISPLAY_WIDTH 128 31 | #define JAVELIN_DISPLAY_HEIGHT 64 32 | #define JAVELIN_OLED_I2C i2c1 33 | #define JAVELIN_OLED_TX_DMA I2C1_TX 34 | #define JAVELIN_OLED_SDA_PIN 2 35 | #define JAVELIN_OLED_SCL_PIN 3 36 | #define JAVELIN_OLED_I2C_ADDRESS 0x3c 37 | #define JAVELIN_OLED_ROTATION 180 38 | 39 | #define JAVELIN_BUTTON_MATRIX 1 40 | 41 | constexpr uint8_t LEFT_COLUMN_PINS[] = {8, 27, 26, 22, 20, 23, 21}; 42 | constexpr uint8_t LEFT_ROW_PINS[] = {4, 5, 6, 7}; 43 | 44 | constexpr uint8_t RIGHT_COLUMN_PINS[] = {23, 4, 5, 6, 7, 8, 21}; 45 | constexpr uint8_t RIGHT_ROW_PINS[] = {27, 26, 22, 20}; 46 | 47 | // clang-format off 48 | // 49 | // Button indexes 50 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 51 | // 12 13 14 15 16 17 | 18 19 20 21 22 23 52 | // 24 25 26 27 28 29 30 31 | 32 33 34 35 36 37 38 39 53 | // 40 41 42 43 44 | 45 46 47 48 49 54 | // 55 | // Matrix positions 56 | // 06 05 04 03 02 01 | 01 02 03 04 05 06 57 | // 16 15 14 13 12 11 | 11 12 13 14 15 16 58 | // 26 25 24 23 22 21 33 20 | 20 33 21 22 23 24 25 26 59 | // 34 32 31 35 30 | 30 35 31 32 34 60 | // 61 | // RGB indices 62 | // Back: 63 | // 2 1 0 | 31 32 33 64 | // | 65 | // 3 4 5 | 36 35 34 66 | // | 67 | // 68 | // Front: 69 | // 30 29 28 27 26 25 | 56 57 58 59 60 61 70 | // 24 23 22 21 20 19 | 50 51 52 53 54 55 71 | // 18 17 16 15 14 13 12 11 | 42 43 44 45 46 47 48 49 72 | // 10 9 8 7 6 | 37 38 39 40 41 73 | 74 | constexpr int8_t LEFT_KEY_MAP[4][8] = { 75 | { -1, 5, 4, 3, 2, 1, 0, -1 }, 76 | { -1, 17, 16, 15, 14, 13, 12, -1 }, 77 | { 31, 29, 28, 27, 26, 25, 24, -1 }, 78 | { 44, 42, 41, 30, 40, 43, -1, -1 }, 79 | }; 80 | 81 | constexpr int8_t RIGHT_KEY_MAP[4][8] = { 82 | { -1, 6, 7, 8, 9, 10, 11, -1 }, 83 | { -1, 18, 19, 20, 21, 22, 23, -1 }, 84 | { 32, 34, 35, 36, 37, 38, 39, -1 }, 85 | { 45, 47, 48, 33, 49, 46, -1, -1 }, 86 | }; 87 | 88 | constexpr uint8_t RGB_MAP[62] = { 89 | 30, 29, 28, 27, 26, 25, 56, 57, 58, 59, 60, 61, 90 | 24, 23, 22, 21, 20, 19, 50, 51, 52, 53, 54, 55, 91 | 18, 17, 16, 15, 14, 13, 12, 11, 42, 43, 44, 45, 46, 47, 48, 49, 92 | 10, 9, 8, 7, 6, 37, 38, 39, 40, 41, 93 | 94 | // Underglow 95 | 2, 1, 0, 31, 32, 33, 96 | 3, 4, 5, 36, 35, 34, 97 | }; 98 | 99 | // clang-format on 100 | 101 | const size_t BUTTON_COUNT = 50; 102 | 103 | #define JAVELIN_ENCODER 1 104 | #define JAVELIN_ENCODER_COUNT 2 105 | #define JAVELIN_ENCODER_LEFT_COUNT 1 106 | #define JAVELIN_ENCODER_SPEED 2 107 | 108 | constexpr EncoderPins ENCODER_PINS[] = {{29, 28}, {29, 28}}; 109 | 110 | const char *const MANUFACTURER_NAME = "splitkb"; 111 | const char *const PRODUCT_NAME = "Kyria (Javelin)"; 112 | const int VENDOR_ID = 0x8d1d; 113 | // const int PRODUCT_ID = 0xcf44; 114 | 115 | //--------------------------------------------------------------------------- 116 | -------------------------------------------------------------------------------- /st7789_paper_tape.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "javelin/font/monochrome/font.h" 4 | #include "st7789.h" 5 | 6 | //--------------------------------------------------------------------------- 7 | 8 | const uint8_t PAPER_TAPE_FONT_DATA[] = { 9 | 5, 0x14, 0x3e, 0x14, 0x3e, 0x14, // # 10 | 4, 0x24, 0x4a, 0x52, 0x24, // S 11 | 5, 0x02, 0x02, 0x7e, 0x02, 0x02, // T 12 | 4, 0x7e, 0x18, 0x24, 0x42, // K 13 | 4, 0x7e, 0x12, 0x12, 0x0c, // P 14 | 5, 0x3e, 0x40, 0x30, 0x40, 0x3e, // W 15 | 4, 0x7e, 0x10, 0x10, 0x7e, // H 16 | 4, 0x7e, 0x12, 0x12, 0x6c, // R 17 | 4, 0x7c, 0x12, 0x12, 0x7c, // A 18 | 4, 0x3c, 0x42, 0x42, 0x3c, // O 19 | 5, 0x24, 0x18, 0x7e, 0x18, 0x24, // * 20 | 4, 0x7e, 0x52, 0x52, 0x42, // E 21 | 4, 0x3e, 0x40, 0x40, 0x3e, // U 22 | 4, 0x7e, 0x12, 0x12, 0x02, // F 23 | 4, 0x7e, 0x12, 0x12, 0x6c, // R 24 | 4, 0x7e, 0x12, 0x12, 0x0c, // P 25 | 4, 0x7e, 0x52, 0x52, 0x2c, // B 26 | 4, 0x7e, 0x40, 0x40, 0x40, // L 27 | 4, 0x3c, 0x42, 0x52, 0x34, // G 28 | 5, 0x02, 0x02, 0x7e, 0x02, 0x02, // T 29 | 4, 0x24, 0x4a, 0x52, 0x24, // S 30 | 4, 0x7e, 0x42, 0x42, 0x3c, // D 31 | 4, 0x62, 0x52, 0x4a, 0x46, // Z 32 | }; 33 | 34 | //--------------------------------------------------------------------------- 35 | 36 | #if JAVELIN_DISPLAY_DRIVER == 7789 37 | 38 | //--------------------------------------------------------------------------- 39 | 40 | void St7789::St7789Data::DrawPaperTape(const StenoStroke *strokes, 41 | size_t length) { 42 | // if (!available) { 43 | // return; 44 | // } 45 | 46 | Clear(); 47 | 48 | // #if JAVELIN_DISPLAY_WIDTH >= 64 49 | // constexpr size_t MAXIMUM_STROKES = JAVELIN_DISPLAY_HEIGHT / 8; 50 | // size_t startingStroke = 51 | // length > MAXIMUM_STROKES ? length - MAXIMUM_STROKES : 0; 52 | // size_t lineOffset = length < MAXIMUM_STROKES ? MAXIMUM_STROKES - length : 53 | // 0; 54 | 55 | // if (lineOffset >= 2) { 56 | // DrawLine(0, 0, JAVELIN_DISPLAY_WIDTH, 0); 57 | // DrawLine(JAVELIN_DISPLAY_WIDTH - 1, 0, JAVELIN_DISPLAY_WIDTH - 1, 15); 58 | // DrawLine(0, 15, JAVELIN_DISPLAY_WIDTH, 15); 59 | // DrawLine(0, 0, 0, 15); 60 | // DrawText(JAVELIN_DISPLAY_WIDTH / 2, 12, &Font::DEFAULT, 61 | // TextAlignment::MIDDLE, "Paper Tape"); 62 | // } 63 | 64 | // for (size_t i = startingStroke; i < length; ++i) { 65 | // uint32_t stroke = strokes[i].GetKeyState(); 66 | // uint8_t *p = &buffer8[lineOffset++]; 67 | // const uint8_t *f = PAPER_TAPE_FONT_DATA; 68 | 69 | // for (size_t i = 0; i < 23; ++i) { 70 | // int width = *f++; 71 | // if (stroke & (1 << i)) { 72 | // for (int x = 0; x < width; ++x) { 73 | // *p = *f++; 74 | // p += JAVELIN_DISPLAY_HEIGHT / 8; 75 | // } 76 | // } else { 77 | // p += width * (JAVELIN_DISPLAY_HEIGHT / 8); 78 | // f += width; 79 | // } 80 | // p += JAVELIN_DISPLAY_HEIGHT / 8; 81 | // } 82 | // } 83 | // #elif JAVELIN_DISPLAY_WIDTH >= 23 84 | // constexpr size_t MAXIMUM_STROKES = JAVELIN_DISPLAY_HEIGHT; 85 | // size_t startingStroke = 86 | // length > MAXIMUM_STROKES ? length - MAXIMUM_STROKES : 0; 87 | // size_t y = length < MAXIMUM_STROKES ? MAXIMUM_STROKES - length : 0; 88 | 89 | // for (size_t i = startingStroke; i < length; ++i) { 90 | // uint32_t stroke = strokes[i].GetKeyState(); 91 | // uint8_t *p = &buffer8[y / 8]; 92 | // uint8_t mask = 1 << (y & 7); 93 | // ++y; 94 | 95 | // for (size_t i = 0; i < 23; ++i) { 96 | // if (stroke & (1 << i)) { 97 | // *p |= mask; 98 | // } 99 | // p += JAVELIN_DISPLAY_HEIGHT / 8; 100 | // } 101 | // } 102 | // #endif 103 | } 104 | 105 | //--------------------------------------------------------------------------- 106 | 107 | #endif // JAVELIN_DISPLAY_DRIVER == 7789 108 | 109 | //--------------------------------------------------------------------------- 110 | -------------------------------------------------------------------------------- /pico_ws2812.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "pico_ws2812.h" 4 | #include "javelin/hal/rgb.h" 5 | #include "pico_dma.h" 6 | #include "pico_ws2812.pio.h" 7 | #include 8 | #include 9 | #include 10 | 11 | //--------------------------------------------------------------------------- 12 | 13 | #if JAVELIN_RGB 14 | 15 | //--------------------------------------------------------------------------- 16 | 17 | const PIO PIO_INSTANCE = pio1; 18 | const int STATE_MACHINE_INDEX = 0; 19 | 20 | #if JAVELIN_SPLIT 21 | #define JAVELIN_RGB_RIGHT_COUNT (JAVELIN_RGB_COUNT - JAVELIN_RGB_LEFT_COUNT) 22 | #endif 23 | 24 | //--------------------------------------------------------------------------- 25 | 26 | Ws2812::Ws2812Data Ws2812::instance; 27 | 28 | //--------------------------------------------------------------------------- 29 | 30 | void Ws2812::Initialize() { 31 | const int CYCLES_PER_BIT = 10; 32 | const int FREQUENCY = 800000; 33 | 34 | const int offset = pio_add_program(PIO_INSTANCE, &ws2812_program); 35 | 36 | pio_gpio_init(PIO_INSTANCE, JAVELIN_RGB_PIN); 37 | pio_sm_set_consecutive_pindirs(PIO_INSTANCE, STATE_MACHINE_INDEX, 38 | JAVELIN_RGB_PIN, 1, true); 39 | pio_sm_config config = ws2812_program_get_default_config(offset); 40 | sm_config_set_sideset_pins(&config, JAVELIN_RGB_PIN); 41 | sm_config_set_out_pins(&config, JAVELIN_RGB_PIN, 1); 42 | sm_config_set_out_shift(&config, false, true, 24); 43 | sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_TX); 44 | const int div = clock_get_hz(clk_sys) / (FREQUENCY * CYCLES_PER_BIT >> 8); 45 | config.clkdiv = div << 8; 46 | pio_sm_init(PIO_INSTANCE, STATE_MACHINE_INDEX, offset, &config); 47 | pio_sm_set_enabled(PIO_INSTANCE, STATE_MACHINE_INDEX, true); 48 | 49 | dma1->destination = &PIO_INSTANCE->txf[STATE_MACHINE_INDEX]; 50 | 51 | constexpr PicoDmaControl dmaControl = { 52 | .enable = true, 53 | .dataSize = PicoDmaControl::DataSize::WORD, 54 | .incrementRead = true, 55 | .incrementWrite = false, 56 | .chainToDma = 1, 57 | .transferRequest = PicoDmaTransferRequest::PIO1_TX0, 58 | .sniffEnable = false, 59 | }; 60 | dma1->control = dmaControl; 61 | 62 | // clang-format off 63 | #if JAVELIN_SPLIT 64 | #if JAVELIN_LEFT_COUNT == JAVELIN_RIGHT_COUNT 65 | dma1->count = JAVELIN_RGB_LEFT_COUNT; 66 | #else 67 | dma1->count = Split::IsLeft() ? JAVELIN_RGB_LEFT_COUNT 68 | : JAVELIN_RGB_RIGHT_COUNT; 69 | #endif 70 | #else 71 | dma1->count = JAVELIN_RGB_COUNT; 72 | #endif // JAVELIN_SPLIT 73 | // clang-format on 74 | } 75 | 76 | void Ws2812::Ws2812Data::Update() { 77 | if (!dirty) { 78 | return; 79 | } 80 | 81 | // Don't update more than once every 10ms. 82 | const uint32_t now = time_us_32(); 83 | const uint32_t timeSinceLastUpdate = now - lastUpdate; 84 | if (timeSinceLastUpdate < 10000) { 85 | return; 86 | } 87 | 88 | dirty = false; 89 | lastUpdate = now; 90 | 91 | #if JAVELIN_SPLIT 92 | dma1->sourceTrigger = 93 | Split::IsLeft() ? pixelValues : pixelValues + JAVELIN_RGB_LEFT_COUNT; 94 | #else 95 | dma1->sourceTrigger = &pixelValues; 96 | #endif 97 | } 98 | 99 | #if JAVELIN_SPLIT 100 | void Ws2812::Ws2812Data::UpdateBuffer(TxBuffer &buffer) { 101 | if (!slaveDirty) { 102 | return; 103 | } 104 | 105 | slaveDirty = false; 106 | if (Split::IsLeft()) { 107 | buffer.Add(SplitHandlerId::RGB, pixelValues + JAVELIN_RGB_LEFT_COUNT, 108 | sizeof(uint32_t) * JAVELIN_RGB_RIGHT_COUNT); 109 | } else { 110 | buffer.Add(SplitHandlerId::RGB, pixelValues, 111 | sizeof(uint32_t) * JAVELIN_RGB_LEFT_COUNT); 112 | } 113 | } 114 | 115 | void Ws2812::Ws2812Data::OnDataReceived(const void *data, size_t length) { 116 | dirty = true; 117 | if (Split::IsLeft()) { 118 | memcpy(pixelValues, data, sizeof(uint32_t) * JAVELIN_RGB_LEFT_COUNT); 119 | } else { 120 | memcpy(pixelValues + JAVELIN_RGB_LEFT_COUNT, data, 121 | sizeof(uint32_t) * JAVELIN_RGB_RIGHT_COUNT); 122 | } 123 | } 124 | #endif 125 | 126 | //--------------------------------------------------------------------------- 127 | 128 | void Rgb::SetRgb(size_t id, int r, int g, int b) { 129 | Ws2812::SetRgb(id, r, g, b); 130 | } 131 | 132 | size_t Rgb::GetCount() { return JAVELIN_RGB_COUNT; } 133 | 134 | //--------------------------------------------------------------------------- 135 | 136 | #endif // JAVELIN_RGB 137 | 138 | //--------------------------------------------------------------------------- 139 | -------------------------------------------------------------------------------- /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 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | // Not required for RP2040B2 or later. 34 | // #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 1 35 | 36 | //--------------------------------------------------------------------------- 37 | // COMMON CONFIGURATION 38 | //--------------------------------------------------------------------------- 39 | 40 | // defined by board.mk 41 | #ifndef CFG_TUSB_MCU 42 | #error CFG_TUSB_MCU must be defined 43 | #endif 44 | 45 | // RHPort number used for device can be defined by board.mk, default to port 0 46 | #ifndef BOARD_DEVICE_RHPORT_NUM 47 | #define BOARD_DEVICE_RHPORT_NUM 0 48 | #endif 49 | 50 | // RHPort max operational speed can defined by board.mk 51 | // Default to Highspeed for MCU with internal HighSpeed PHY (can be port 52 | // specific), otherwise FullSpeed 53 | #ifndef BOARD_DEVICE_RHPORT_SPEED 54 | #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || \ 55 | CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || CFG_TUSB_MCU == OPT_MCU_NUC505 || \ 56 | CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) 57 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED 58 | #else 59 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED 60 | #endif 61 | #endif 62 | 63 | // Device mode with rhport and speed defined by board.mk 64 | #if BOARD_DEVICE_RHPORT_NUM == 0 65 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 66 | #elif BOARD_DEVICE_RHPORT_NUM == 1 67 | #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 68 | #else 69 | #error "Incorrect RHPort configuration" 70 | #endif 71 | 72 | #ifndef CFG_TUSB_OS 73 | #define CFG_TUSB_OS OPT_OS_NONE 74 | #endif 75 | 76 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 77 | // #define CFG_TUSB_DEBUG 0 78 | 79 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction 80 | * on alignment. Tinyusb use follows macros to declare transferring memory so 81 | * that they can be put into those specific section. 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 // 1 for each keyboard, console & plover hid 103 | #define CFG_TUD_CDC 1 104 | #define CFG_TUD_MSC 0 105 | #define CFG_TUD_MIDI 1 106 | #define CFG_TUD_VENDOR 0 107 | 108 | #define CFG_TUD_HID_EP_BUFSIZE 64 109 | 110 | #define CFG_KEYBOARD_BUFSIZE 17 111 | #define CFG_CONSOLE_BUFSIZE 64 112 | #define CFG_PLOVER_HID_EP_BUFSIZE 8 113 | 114 | #define CFG_TUD_CDC_RX_BUFSIZE 1024 115 | #define CFG_TUD_CDC_TX_BUFSIZE 1024 116 | #define CFG_TUD_CDC_EP_BUFSIZE 1024 117 | 118 | #define CFG_TUD_MIDI_RX_BUFSIZE 64 119 | #define CFG_TUD_MIDI_TX_BUFSIZE 64 120 | 121 | #ifdef __cplusplus 122 | } 123 | #endif 124 | 125 | #endif /* _TUSB_CONFIG_H_ */ 126 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | # PolyForm Noncommercial License 1.0.0 2 | 3 | 4 | 5 | ## Acceptance 6 | 7 | In order to get any license under these terms, you must agree 8 | to them as both strict obligations and conditions to all 9 | your licenses. 10 | 11 | ## Copyright License 12 | 13 | The licensor grants you a copyright license for the 14 | software to do everything you might do with the software 15 | that would otherwise infringe the licensor's copyright 16 | in it for any permitted purpose. However, you may 17 | only distribute the software according to [Distribution 18 | License](#distribution-license) and make changes or new works 19 | based on the software according to [Changes and New Works 20 | License](#changes-and-new-works-license). 21 | 22 | ## Distribution License 23 | 24 | The licensor grants you an additional copyright license 25 | to distribute copies of the software. Your license 26 | to distribute covers distributing the software with 27 | changes and new works permitted by [Changes and New Works 28 | License](#changes-and-new-works-license). 29 | 30 | ## Notices 31 | 32 | You must ensure that anyone who gets a copy of any part of 33 | the software from you also gets a copy of these terms or the 34 | URL for them above, as well as copies of any plain-text lines 35 | beginning with `Required Notice:` that the licensor provided 36 | with the software. For example: 37 | 38 | > Required Notice: Copyright Jeffrey Lim (http://lim.au) 39 | 40 | ## Changes and New Works License 41 | 42 | The licensor grants you an additional copyright license to 43 | make changes and new works based on the software for any 44 | permitted purpose. 45 | 46 | ## Patent License 47 | 48 | The licensor grants you a patent license for the software that 49 | covers patent claims the licensor can license, or becomes able 50 | to license, that you would infringe by using the software. 51 | 52 | ## Noncommercial Purposes 53 | 54 | Any noncommercial purpose is a permitted purpose. 55 | 56 | ## Personal Uses 57 | 58 | Personal use for research, experiment, and testing for 59 | the benefit of public knowledge, personal study, private 60 | entertainment, hobby projects, amateur pursuits, or religious 61 | observance, without any anticipated commercial application, 62 | is use for a permitted purpose. 63 | 64 | ## Noncommercial Organizations 65 | 66 | Use by any charitable organization, educational institution, 67 | public research organization, public safety or health 68 | organization, environmental protection organization, 69 | or government institution is use for a permitted purpose 70 | regardless of the source of funding or obligations resulting 71 | from the funding. 72 | 73 | ## Fair Use 74 | 75 | You may have "fair use" rights for the software under the 76 | law. These terms do not limit them. 77 | 78 | ## No Other Rights 79 | 80 | These terms do not allow you to sublicense or transfer any of 81 | your licenses to anyone else, or prevent the licensor from 82 | granting licenses to anyone else. These terms do not imply 83 | any other licenses. 84 | 85 | ## Patent Defense 86 | 87 | If you make any written claim that the software infringes or 88 | contributes to infringement of any patent, your patent license 89 | for the software granted under these terms ends immediately. If 90 | your company makes such a claim, your patent license ends 91 | immediately for work on behalf of your company. 92 | 93 | ## Violations 94 | 95 | The first time you are notified in writing that you have 96 | violated any of these terms, or done anything with the software 97 | not covered by your licenses, your licenses can nonetheless 98 | continue if you come into full compliance with these terms, 99 | and take practical steps to correct past violations, within 100 | 32 days of receiving notice. Otherwise, all your licenses 101 | end immediately. 102 | 103 | ## No Liability 104 | 105 | ***As far as the law allows, the software comes as is, without 106 | any warranty or condition, and the licensor will not be liable 107 | to you for any damages arising out of these terms or the use 108 | or nature of the software, under any kind of legal claim.*** 109 | 110 | ## Definitions 111 | 112 | The **licensor** is the individual or entity offering these 113 | terms, and the **software** is the software the licensor makes 114 | available under these terms. 115 | 116 | **You** refers to the individual or entity agreeing to these 117 | terms. 118 | 119 | **Your company** is any legal entity, sole proprietorship, 120 | or other kind of organization that you work for, plus all 121 | organizations that have control over, are under the control of, 122 | or are under common control with that organization. **Control** 123 | means ownership of substantially all the assets of an entity, 124 | or the power to direct its management and policies by vote, 125 | contract, or otherwise. Control can be direct or indirect. 126 | 127 | **Your licenses** are all the licenses granted to you for the 128 | software under these terms. 129 | 130 | **Use** means anything you do with the software requiring one 131 | of your licenses. 132 | -------------------------------------------------------------------------------- /config/kyria_rev3.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "encoder_pins.h" 5 | #include "main_flash_layout.h" 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #define JAVELIN_USE_EMBEDDED_STENO 1 10 | #define JAVELIN_USE_USER_DICTIONARY 1 11 | #define JAVELIN_USB_MILLIAMPS 500 12 | 13 | #define JAVELIN_RGB 1 14 | #define JAVELIN_RGB_COUNT 62 15 | #define JAVELIN_RGB_LEFT_COUNT 31 16 | #define JAVELIN_RGB_PIN 0 17 | #define JAVELIN_USE_RGB_MAP 1 18 | 19 | #define JAVELIN_SPLIT 1 20 | #define JAVELIN_SPLIT_PIO_CYCLES 16 21 | #define JAVELIN_SPLIT_TX_PIN 1 22 | #define JAVELIN_SPLIT_RX_PIN 1 23 | #define JAVELIN_SPLIT_IS_MASTER 1 24 | #define JAVELIN_SPLIT_SIDE_PIN 9 25 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 512 26 | 27 | #define JAVELIN_DISPLAY_DRIVER 1306 28 | #define JAVELIN_OLED_WIDTH 128 29 | #define JAVELIN_OLED_HEIGHT 64 30 | #define JAVELIN_DISPLAY_WIDTH 128 31 | #define JAVELIN_DISPLAY_HEIGHT 64 32 | #define JAVELIN_OLED_I2C i2c1 33 | #define JAVELIN_OLED_TX_DMA I2C1_TX 34 | #define JAVELIN_OLED_SDA_PIN 2 35 | #define JAVELIN_OLED_SCL_PIN 3 36 | #define JAVELIN_OLED_I2C_ADDRESS 0x3c 37 | #define JAVELIN_OLED_ROTATION 180 38 | 39 | #define JAVELIN_BUTTON_MATRIX 1 40 | 41 | constexpr uint8_t LEFT_COLUMN_PINS[] = {8, 27, 26, 22, 20, 23, 21}; 42 | constexpr uint8_t LEFT_ROW_PINS[] = {4, 5, 6, 7}; 43 | 44 | constexpr uint8_t RIGHT_COLUMN_PINS[] = {23, 4, 5, 6, 7, 8, 21}; 45 | constexpr uint8_t RIGHT_ROW_PINS[] = {27, 26, 22, 20}; 46 | 47 | // clang-format off 48 | // 49 | // Button indexes 50 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 51 | // 12 13 14 15 16 17 | 18 19 20 21 22 23 52 | // 24 25 26 27 28 29 30 31 | 32 33 34 35 36 37 38 39 53 | // 40 41 42 43 44 | 45 46 47 48 49 54 | // 55 | // Matrix positions 56 | // 06 05 04 03 02 01 | 01 02 03 04 05 06 57 | // 16 15 14 13 12 11 | 11 12 13 14 15 16 58 | // 26 25 24 23 22 21 33 20 | 20 33 21 22 23 24 25 26 59 | // 34 32 31 35 30 | 30 35 31 32 34 60 | // 61 | // RGB indices 62 | // Back: 63 | // 2 1 0 | 31 32 33 64 | // | 65 | // 3 4 5 | 36 35 34 66 | // | 67 | // 68 | // Front: 69 | // 30 29 28 27 26 25 | 56 57 58 59 60 61 70 | // 24 23 22 21 20 19 | 50 51 52 53 54 55 71 | // 18 17 16 15 14 13 12 11 | 42 43 44 45 46 47 48 49 72 | // 10 9 8 7 6 | 37 38 39 40 41 73 | 74 | constexpr int8_t LEFT_KEY_MAP[4][8] = { 75 | { -1, 5, 4, 3, 2, 1, 0, -1 }, 76 | { -1, 17, 16, 15, 14, 13, 12, -1 }, 77 | { 31, 29, 28, 27, 26, 25, 24, -1 }, 78 | { 44, 42, 41, 30, 40, 43, -1, -1 }, 79 | }; 80 | 81 | constexpr int8_t RIGHT_KEY_MAP[4][8] = { 82 | { -1, 6, 7, 8, 9, 10, 11, -1 }, 83 | { -1, 18, 19, 20, 21, 22, 23, -1 }, 84 | { 32, 34, 35, 36, 37, 38, 39, -1 }, 85 | { 45, 47, 48, 33, 49, 46, -1, -1 }, 86 | }; 87 | 88 | constexpr uint8_t RGB_MAP[62] = { 89 | 30, 29, 28, 27, 26, 25, 56, 57, 58, 59, 60, 61, 90 | 24, 23, 22, 21, 20, 19, 50, 51, 52, 53, 54, 55, 91 | 18, 17, 16, 15, 14, 13, 12, 11, 42, 43, 44, 45, 46, 47, 48, 49, 92 | 10, 9, 8, 7, 6, 37, 38, 39, 40, 41, 93 | 94 | // Underglow 95 | 2, 1, 0, 31, 32, 33, 96 | 3, 4, 5, 36, 35, 34, 97 | }; 98 | 99 | // clang-format on 100 | 101 | #define JAVELIN_SCRIPT_CONFIGURATION \ 102 | R"({"name":"Kyria","layout":[{"x":0,"y":0.9},{"x":1,"y":0.9},{"x":2,"y":0.35},{"x":3,"y":0},{"x":4,"y":0.35},{"x":5,"y":0.5},{"x":12,"y":0.5},{"x":13,"y":0.35},{"x":14,"y":0},{"x":15,"y":0.35},{"x":16,"y":0.9},{"x":17,"y":0.9},{"x":0,"y":1.9},{"x":1,"y":1.9},{"x":2,"y":1.35},{"x":3,"y":1},{"x":4,"y":1.35},{"x":5,"y":1.5},{"x":12,"y":1.5},{"x":13,"y":1.35},{"x":14,"y":1},{"x":15,"y":1.35},{"x":16,"y":1.9},{"x":17,"y":1.9},{"x":0,"y":2.9},{"x":1,"y":2.9},{"x":2,"y":2.35},{"x":3,"y":2},{"x":4,"y":2.35},{"x":5,"y":2.5},{"x":6.12,"y":3.13,"r":0.52},{"x":7.21,"y":3.99,"r":0.79},{"x":9.79,"y":3.99,"r":-0.79},{"x":10.88,"y":3.13,"r":-0.52},{"x":12,"y":2.5},{"x":13,"y":2.35},{"x":14,"y":2},{"x":15,"y":2.35},{"x":16,"y":2.9},{"x":17,"y":2.9},{"x":2.5,"y":3.4},{"x":3.5,"y":3.4},{"x":4.6,"y":3.55,"r":0.26},{"x":5.62,"y":4,"r":0.52},{"x":6.5,"y":4.7,"r":0.79},{"x":10.5,"y":4.7,"r":-0.79},{"x":11.38,"y":4,"r":-0.52},{"x":12.4,"y":3.55,"r":-0.26},{"x":13.5,"y":3.4},{"x":14.5,"y":3.4}]})" 103 | 104 | const size_t BUTTON_COUNT = 50; 105 | 106 | #define JAVELIN_ENCODER 1 107 | #define JAVELIN_ENCODER_COUNT 2 108 | #define JAVELIN_ENCODER_LEFT_COUNT 1 109 | #define JAVELIN_ENCODER_SPEED 2 110 | 111 | constexpr EncoderPins ENCODER_PINS[] = {{29, 28}, {29, 28}}; 112 | 113 | const char *const MANUFACTURER_NAME = "splitkb"; 114 | const char *const PRODUCT_NAME = "Kyria (Javelin)"; 115 | const int VENDOR_ID = 0x8d1d; 116 | // const int PRODUCT_ID = 0xcf44; 117 | 118 | //--------------------------------------------------------------------------- 119 | -------------------------------------------------------------------------------- /split_hid_report_buffer.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "split_hid_report_buffer.h" 4 | #include "console_report_buffer.h" 5 | #include "main_report_builder.h" 6 | #include "pico_split.h" 7 | #include "plover_hid_report_buffer.h" 8 | #include "usb_descriptors.h" 9 | #include 10 | #include 11 | #include 12 | 13 | //--------------------------------------------------------------------------- 14 | 15 | #if JAVELIN_SPLIT 16 | 17 | //--------------------------------------------------------------------------- 18 | 19 | SplitHidReportBuffer::SplitHidReportBufferData SplitHidReportBuffer::instance; 20 | 21 | //--------------------------------------------------------------------------- 22 | 23 | void SplitHidReportBuffer::SplitHidReportBufferSize::UpdateBuffer( 24 | TxBuffer &buffer) { 25 | HidBufferSize newBufferSize; 26 | newBufferSize.value = 0; 27 | newBufferSize.available[ITF_NUM_KEYBOARD] = 28 | MainReportBuilder::instance.GetAvailableBufferCount(); 29 | newBufferSize.available[ITF_NUM_CONSOLE] = 30 | ConsoleReportBuffer::instance.GetAvailableBufferCount(); 31 | newBufferSize.available[ITF_NUM_PLOVER_HID] = 32 | PloverHidReportBuffer::instance.GetAvailableBufferCount(); 33 | 34 | if (!dirty && newBufferSize.value == bufferSize.value) { 35 | return; 36 | } 37 | dirty = false; 38 | bufferSize = newBufferSize; 39 | 40 | buffer.Add(SplitHandlerId::HID_BUFFER_SIZE, &bufferSize, sizeof(bufferSize)); 41 | } 42 | 43 | void SplitHidReportBuffer::SplitHidReportBufferSize::OnDataReceived( 44 | const void *data, size_t length) { 45 | bufferSize = *(HidBufferSize *)data; 46 | } 47 | 48 | //--------------------------------------------------------------------------- 49 | 50 | QueueEntry * 51 | SplitHidReportBuffer::SplitHidReportBufferData::CreateEntry(uint8_t interface, 52 | uint8_t reportId, 53 | const uint8_t *data, 54 | size_t length) { 55 | QueueEntry *entry = new (length) QueueEntry; 56 | entry->data.interface = interface; 57 | entry->data.reportId = reportId; 58 | entry->data.length = length; 59 | entry->next = nullptr; 60 | memcpy(entry->data.data, data, length); 61 | return entry; 62 | } 63 | 64 | //--------------------------------------------------------------------------- 65 | 66 | void SplitHidReportBuffer::SplitHidReportBufferData::Add(uint8_t interface, 67 | uint8_t reportId, 68 | const uint8_t *data, 69 | size_t length) { 70 | while (bufferSize.bufferSize.available[interface] == 0) { 71 | PicoSplit::Update(); 72 | #if JAVELIN_USE_WATCHDOG 73 | watchdog_update(); 74 | #endif 75 | sleep_us(100); 76 | } 77 | bufferSize.bufferSize.available[interface]--; 78 | 79 | QueueEntry *entry = CreateEntry(interface, reportId, data, length); 80 | AddEntry(entry); 81 | } 82 | 83 | void SplitHidReportBuffer::SplitHidReportBufferData::Update() { 84 | while (head) { 85 | if (!ProcessEntry(head)) { 86 | return; 87 | } 88 | 89 | RemoveHead(); 90 | } 91 | } 92 | 93 | bool SplitHidReportBuffer::SplitHidReportBufferData::ProcessEntry( 94 | const QueueEntry *entry) { 95 | switch (entry->data.interface) { 96 | case ITF_NUM_KEYBOARD: { 97 | auto &reportBuffer = MainReportBuilder::instance.reportBuffer; 98 | if (reportBuffer.IsFull()) { 99 | return false; 100 | } 101 | 102 | reportBuffer.SendReport(entry->data.reportId, entry->data.data, 103 | entry->data.length); 104 | return true; 105 | } 106 | case ITF_NUM_CONSOLE: { 107 | auto &reportBuffer = ConsoleReportBuffer::instance.reportBuffer; 108 | if (reportBuffer.IsFull()) { 109 | return false; 110 | } 111 | 112 | reportBuffer.SendReport(entry->data.reportId, entry->data.data, 113 | entry->data.length); 114 | return true; 115 | } 116 | case ITF_NUM_PLOVER_HID: { 117 | auto &reportBuffer = PloverHidReportBuffer::instance; 118 | if (reportBuffer.IsFull()) { 119 | return false; 120 | } 121 | 122 | reportBuffer.SendReport(entry->data.reportId, entry->data.data, 123 | entry->data.length); 124 | return true; 125 | } 126 | } 127 | // If the interface is unknown, just return true to remove it from the 128 | // queue. 129 | return true; 130 | } 131 | 132 | //--------------------------------------------------------------------------- 133 | 134 | void SplitHidReportBuffer::SplitHidReportBufferData::UpdateBuffer( 135 | TxBuffer &buffer) { 136 | while (head) { 137 | if (!buffer.Add(SplitHandlerId::HID_REPORT, &head->data, 138 | head->data.length + sizeof(EntryData))) { 139 | return; 140 | } 141 | 142 | RemoveHead(); 143 | } 144 | } 145 | 146 | void SplitHidReportBuffer::SplitHidReportBufferData::OnDataReceived( 147 | const void *data, size_t length) { 148 | const EntryData *entryData = (const EntryData *)data; 149 | 150 | QueueEntry *entry = 151 | CreateEntry(entryData->interface, entryData->reportId, entryData->data, 152 | entryData->length); 153 | AddEntry(entry); 154 | bufferSize.dirty = true; 155 | } 156 | 157 | //--------------------------------------------------------------------------- 158 | 159 | #endif // JAVELIN_SPLIT 160 | 161 | //--------------------------------------------------------------------------- 162 | -------------------------------------------------------------------------------- /config/kyria_rev4_pair.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "encoder_pins.h" 5 | #include "pair_flash_layout.h" 6 | #include "pinnacle_pins.h" 7 | 8 | //--------------------------------------------------------------------------- 9 | 10 | #define CONFIG_EXTRA_SOURCE "config/kyria_rev4.cc" 11 | 12 | //--------------------------------------------------------------------------- 13 | 14 | #define USE_CIRQUE_TRACKPAD 1 15 | #define USE_HALCYON_DISPLAY 1 16 | #define USE_HALCYON_ENCODER 1 17 | 18 | //--------------------------------------------------------------------------- 19 | 20 | #define JAVELIN_USE_EMBEDDED_STENO 0 21 | #define JAVELIN_USE_USER_DICTIONARY 0 22 | #define JAVELIN_USB_MILLIAMPS 500 23 | 24 | #define JAVELIN_RGB 1 25 | #define JAVELIN_RGB_COUNT 62 26 | #define JAVELIN_RGB_LEFT_COUNT 31 27 | #define JAVELIN_RGB_PIN 3 28 | #define JAVELIN_USE_RGB_MAP 1 29 | 30 | #define JAVELIN_SPLIT 1 31 | #define JAVELIN_SPLIT_PIO_CYCLES 5 32 | #define JAVELIN_SPLIT_TX_PIN 28 33 | #define JAVELIN_SPLIT_RX_PIN 29 34 | #define JAVELIN_SPLIT_IS_MASTER 0 35 | #define JAVELIN_SPLIT_SIDE_PIN 24 36 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 2048 37 | 38 | #if USE_HALCYON_DISPLAY 39 | #define JAVELIN_DISPLAY_DRIVER 7789 40 | #define JAVELIN_DISPLAY_MISO_PIN 12 41 | #define JAVELIN_DISPLAY_CS_PIN 13 42 | #define JAVELIN_DISPLAY_SCK_PIN 14 43 | #define JAVELIN_DISPLAY_MOSI_PIN 15 44 | #define JAVELIN_DISPLAY_DC_PIN 16 45 | #define JAVELIN_DISPLAY_RST_PIN 26 46 | #define JAVELIN_DISPLAY_BACKLIGHT_PIN 27 47 | #define JAVELIN_DISPLAY_SPI spi1 48 | #define JAVELIN_DISPLAY_TX_DMA SPI1_TX 49 | #define JAVELIN_DISPLAY_SCREEN_WIDTH 135 50 | #define JAVELIN_DISPLAY_SCREEN_HEIGHT 240 51 | #define JAVELIN_DISPLAY_ROTATION 0 52 | #define JAVELIN_DISPLAY_WIDTH 135 53 | #define JAVELIN_DISPLAY_HEIGHT 240 54 | 55 | bool shouldUseDisplayModule(); 56 | #define JAVELIN_DISPLAY_DETECT shouldUseDisplayModule() 57 | 58 | #endif 59 | 60 | #if USE_CIRQUE_TRACKPAD 61 | #define JAVELIN_POINTER 0x73a 62 | #define JAVELIN_POINTER_SPI spi1 63 | #define JAVELIN_POINTER_MISO_PIN 12 64 | #define JAVELIN_POINTER_SCK_PIN 14 65 | #define JAVELIN_POINTER_MOSI_PIN 15 66 | 67 | #define JAVELIN_POINTER_COUNT 2 68 | #define JAVELIN_POINTER_LEFT_COUNT 1 69 | 70 | const PinnaclePins PINNACLE_PINS[2] = { 71 | {.chipSelectPin = 13, .invertX = true, .invertY = true}, 72 | {.chipSelectPin = 13}, 73 | }; 74 | 75 | #endif 76 | 77 | #define JAVELIN_BUTTON_MATRIX 1 78 | 79 | constexpr uint8_t LEFT_COLUMN_PINS[] = {19, 20, 25, 4, 9, 10, 5}; 80 | constexpr uint8_t LEFT_ROW_PINS[] = {8, 11, 7, 6}; 81 | 82 | constexpr uint8_t RIGHT_COLUMN_PINS[] = {5, 10, 9, 4, 25, 20, 19}; 83 | constexpr uint8_t RIGHT_ROW_PINS[] = {8, 11, 7, 6}; 84 | 85 | // clang-format off 86 | // 87 | // Button indexes 88 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 89 | // 12 13 14 15 16 17 | 18 19 20 21 22 23 90 | // 24 25 26 27 28 29 30 31 | 32 33 34 35 36 37 38 39 91 | // 40 41 42 43 44 | 45 46 47 48 49 92 | // 93 | // Matrix positions 94 | // 06 05 04 03 02 01 | 01 02 03 04 05 06 95 | // 16 15 14 13 12 11 | 11 12 13 14 15 16 96 | // 26 25 24 23 22 21 33 20 | 20 33 21 22 23 24 25 26 97 | // 34 32 31 35 30 | 30 35 31 32 34 98 | // 99 | // RGB indices 100 | // Back: 101 | // 2 1 0 | 31 32 33 102 | // | 103 | // 3 4 5 | 36 35 34 104 | // | 105 | // 106 | // Front: 107 | // 30 29 28 27 26 25 | 56 57 58 59 60 61 108 | // 24 23 22 21 20 19 | 50 51 52 53 54 55 109 | // 18 17 16 15 14 13 12 11 | 42 43 44 45 46 47 48 49 110 | // 10 9 8 7 6 | 37 38 39 40 41 111 | 112 | constexpr int8_t LEFT_KEY_MAP[4][8] = { 113 | { -1, 5, 4, 3, 2, 1, 0, -1 }, 114 | { -1, 17, 16, 15, 14, 13, 12, -1 }, 115 | { 31, 29, 28, 27, 26, 25, 24, -1 }, 116 | { 44, 42, 41, 30, 40, 43, -1, -1 }, 117 | }; 118 | 119 | constexpr int8_t RIGHT_KEY_MAP[4][8] = { 120 | { -1, 6, 7, 8, 9, 10, 11, -1 }, 121 | { -1, 18, 19, 20, 21, 22, 23, -1 }, 122 | { 32, 34, 35, 36, 37, 38, 39, -1 }, 123 | { 45, 47, 48, 33, 49, 46, -1, -1 }, 124 | }; 125 | 126 | constexpr uint8_t RGB_MAP[62] = { 127 | 30, 29, 28, 27, 26, 25, 56, 57, 58, 59, 60, 61, 128 | 24, 23, 22, 21, 20, 19, 50, 51, 52, 53, 54, 55, 129 | 18, 17, 16, 15, 14, 13, 12, 11, 42, 43, 44, 45, 46, 47, 48, 49, 130 | 10, 9, 8, 7, 6, 37, 38, 39, 40, 41, 131 | 132 | // Underglow 133 | 2, 1, 0, 31, 32, 33, 134 | 3, 4, 5, 36, 35, 34, 135 | }; 136 | 137 | // clang-format on 138 | 139 | #if USE_HALCYON_ENCODER 140 | 141 | #define JAVELIN_BUTTON_PINS 1 142 | #define JAVELIN_BUTTON_PINS_OFFSET 51 143 | #define JAVELIN_ENCODER_BUTTON_PIN 16 144 | #define JAVELIN_ENCODER_UNUSED_PIN 17 145 | 146 | extern uint8_t BUTTON_PINS[1]; 147 | 148 | constexpr EncoderPins ENCODER_PINS[] = {{23, 22}, {27, 26}, {23, 22}, {27, 26}}; 149 | 150 | void preButtonStateInitialize(); 151 | #define JAVELIN_PRE_BUTTON_STATE_INITIALIZE preButtonStateInitialize(); 152 | 153 | #else 154 | 155 | constexpr EncoderPins ENCODER_PINS[] = {{23, 22}, {23, 22}}; 156 | 157 | #endif 158 | 159 | #define JAVELIN_ENCODER 1 160 | #define JAVELIN_ENCODER_COUNT 4 161 | #define JAVELIN_ENCODER_LEFT_COUNT 2 162 | #define JAVELIN_ENCODER_SPEED 2 163 | 164 | const size_t BUTTON_COUNT = 52; 165 | 166 | const char *const MANUFACTURER_NAME = "splitkb"; 167 | const char *const PRODUCT_NAME = "Halcyon Kyria (Javelin)"; 168 | const int VENDOR_ID = 0x7fce; 169 | // const int PRODUCT_ID = 0xcf44; 170 | 171 | //--------------------------------------------------------------------------- 172 | -------------------------------------------------------------------------------- /ssd1306.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "javelin/font/text_alignment.h" 5 | #include "javelin/split/split.h" 6 | #include "javelin/stroke.h" 7 | 8 | //--------------------------------------------------------------------------- 9 | 10 | struct Font; 11 | enum class FontId : uint32_t; 12 | 13 | //--------------------------------------------------------------------------- 14 | 15 | #if JAVELIN_DISPLAY_DRIVER == 1306 16 | 17 | class Ssd1306 { 18 | public: 19 | static void Initialize() { GetInstance().Initialize(); } 20 | static void PrintInfo(); 21 | 22 | static void Update() { GetInstance().Update(); } 23 | 24 | static void DrawPaperTape(int displayId, const StenoStroke *strokes, 25 | size_t length) { 26 | instances[displayId].DrawPaperTape(strokes, length); 27 | } 28 | static void DrawStenoLayout(int displayId, StenoStroke stroke) { 29 | instances[displayId].DrawStenoLayout(stroke); 30 | } 31 | static void DrawText(int displayId, int x, int y, const Font *font, 32 | TextAlignment alignment, const char *text) { 33 | instances[displayId].DrawText(x, y, font, alignment, text); 34 | } 35 | 36 | #if JAVELIN_SPLIT 37 | static void RegisterMasterHandlers() { 38 | Split::RegisterRxHandler(SplitHandlerId::DISPLAY_AVAILABLE, 39 | &instances[1].available); 40 | Split::RegisterTxHandler(&instances[1]); 41 | Split::RegisterTxHandler(&instances[1].control); 42 | } 43 | static void RegisterSlaveHandlers() { 44 | Split::RegisterTxHandler(&instances[1].available); 45 | Split::RegisterRxHandler(SplitHandlerId::DISPLAY_DATA, &instances[1]); 46 | Split::RegisterRxHandler(SplitHandlerId::DISPLAY_CONTROL, 47 | &instances[1].control); 48 | } 49 | #else 50 | static void RegisterMasterHandlers() {} 51 | static void RegisterSlaveHandlers() {} 52 | #endif 53 | 54 | private: 55 | class Ssd1306Availability 56 | #if JAVELIN_SPLIT 57 | : public SplitTxHandler, 58 | public SplitRxHandler 59 | #endif 60 | { 61 | public: 62 | void operator=(bool value) { available = value; } 63 | bool operator!() const { return !available; } 64 | operator bool() const { return available; } 65 | 66 | private: 67 | bool available = true; // During startup scripts, the frame buffer may 68 | // need to be written. 69 | // Setting this to available will permit 70 | // the slave OLED to be updated until the first 71 | // availability packet comes in. 72 | bool dirty = true; 73 | 74 | void UpdateBuffer(TxBuffer &buffer); 75 | void OnDataReceived(const void *data, size_t length); 76 | void OnReceiveConnectionReset() { available = false; } 77 | void OnTransmitConnectionReset() { dirty = true; } 78 | }; 79 | 80 | class Ssd1306Control 81 | #if JAVELIN_SPLIT 82 | : public SplitTxHandler, 83 | public SplitRxHandler 84 | #endif 85 | { 86 | public: 87 | void Update(); 88 | void SetScreenOn(bool on) { 89 | if (on != data.screenOn) { 90 | data.screenOn = on; 91 | dirtyFlag |= DIRTY_FLAG_SCREEN_ON; 92 | } 93 | } 94 | void SetContrast(uint8_t value) { 95 | if (value != data.contrast) { 96 | data.contrast = value; 97 | dirtyFlag |= DIRTY_FLAG_CONTRAST; 98 | } 99 | } 100 | 101 | private: 102 | struct Ssd1306ControlTxRxData { 103 | bool screenOn; 104 | uint8_t contrast; 105 | }; 106 | 107 | uint8_t dirtyFlag; 108 | Ssd1306ControlTxRxData data; 109 | 110 | static constexpr int DIRTY_FLAG_SCREEN_ON = 1; 111 | static constexpr int DIRTY_FLAG_CONTRAST = 2; 112 | 113 | void UpdateBuffer(TxBuffer &buffer); 114 | void OnDataReceived(const void *data, size_t length); 115 | void OnTransmitConnectionReset(); 116 | }; 117 | 118 | class Ssd1306Data 119 | #if JAVELIN_SPLIT 120 | : public SplitTxHandler, 121 | public SplitRxHandler 122 | #endif 123 | { 124 | public: 125 | Ssd1306Availability available; 126 | Ssd1306Control control; 127 | bool dirty; 128 | bool drawColor = true; 129 | 130 | void Initialize(); 131 | 132 | // None of these will take effect until Update() is called. 133 | void Clear(); 134 | void DrawLine(int x0, int y0, int x1, int y1); 135 | void DrawRect(int left, int top, int right, int bottom); 136 | void DrawBitmapImage(int x, int y, int width, int height, 137 | const uint8_t *data); 138 | void DrawLuminanceRange(int x, int y, int width, int height, 139 | const uint8_t *data, int min, int max); 140 | void DrawText(int x, int y, const Font *font, TextAlignment alignment, 141 | const char *text); 142 | void SetPixel(uint32_t x, uint32_t y); 143 | 144 | void DrawPaperTape(const StenoStroke *strokes, size_t length); 145 | void DrawStenoLayout(StenoStroke stroke); 146 | 147 | void Update(); 148 | 149 | private: 150 | union { 151 | uint8_t buffer8[JAVELIN_OLED_WIDTH * JAVELIN_OLED_HEIGHT / 8]; 152 | uint32_t buffer32[JAVELIN_OLED_WIDTH * JAVELIN_OLED_HEIGHT / 32]; 153 | }; 154 | 155 | bool InitializeSsd1306(); 156 | 157 | void UpdateBuffer(TxBuffer &buffer); 158 | void OnTransmitConnectionReset() { dirty = true; } 159 | void OnDataReceived(const void *data, size_t length); 160 | }; 161 | 162 | static uint16_t dmaBuffer[JAVELIN_OLED_WIDTH * JAVELIN_OLED_HEIGHT / 8 + 1]; 163 | 164 | static bool IsI2cTxReady(); 165 | static void WaitForI2cTxReady(); 166 | 167 | static void SendCommandListDma(const uint8_t *commands, size_t length); 168 | static bool SendCommandList(const uint8_t *commands, size_t length); 169 | static bool SendCommand(uint8_t command); 170 | static void SendDmaBuffer(size_t count); 171 | 172 | #if JAVELIN_SPLIT 173 | static Ssd1306Data &GetInstance() { 174 | if (Split::IsMaster()) { 175 | return instances[0]; 176 | } else { 177 | return instances[1]; 178 | } 179 | } 180 | static Ssd1306Data instances[2]; 181 | #else 182 | static Ssd1306Data &GetInstance() { return instances[0]; } 183 | static Ssd1306Data instances[1]; 184 | #endif 185 | 186 | friend class Display; 187 | }; 188 | 189 | #else 190 | 191 | class Ssd1306 { 192 | public: 193 | static void Initialize() {} 194 | static void PrintInfo() {} 195 | static void DrawPaperTape() {} 196 | 197 | static void Update() {} 198 | 199 | static void RegisterMasterHandlers() {} 200 | static void RegisterSlaveHandlers() {} 201 | }; 202 | 203 | #endif 204 | 205 | //--------------------------------------------------------------------------- 206 | -------------------------------------------------------------------------------- /auto_draw.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "auto_draw.h" 4 | #include "javelin/clock.h" 5 | #include "javelin/console.h" 6 | #include "javelin/font/monochrome/font.h" 7 | #include "javelin/hal/display.h" 8 | #include "javelin/str.h" 9 | #include "javelin/timer_manager.h" 10 | #include "javelin/wpm_tracker.h" 11 | #include "ssd1306.h" 12 | #include "st7789.h" 13 | 14 | //--------------------------------------------------------------------------- 15 | 16 | #if JAVELIN_DISPLAY_DRIVER 17 | 18 | #if JAVELIN_DISPLAY_DRIVER == 1306 19 | #define DisplayDriver Ssd1306 20 | #elif JAVELIN_DISPLAY_DRIVER == 7789 21 | #define DisplayDriver St7789 22 | #endif 23 | 24 | //--------------------------------------------------------------------------- 25 | 26 | JavelinStaticAllocate StenoStrokeCapture::container; 27 | 28 | //--------------------------------------------------------------------------- 29 | 30 | void StenoStrokeCapture::Process(const StenoKeyState &value, 31 | StenoAction action) { 32 | super::Process(value, action); 33 | if (action == StenoAction::TRIGGER) { 34 | if (strokeCount < MAXIMUM_STROKE_COUNT) { 35 | strokes[strokeCount] = value.ToStroke(); 36 | } else { 37 | memmove(&strokes[0], &strokes[1], 38 | sizeof(StenoStroke) * (MAXIMUM_STROKE_COUNT - 1)); 39 | strokes[MAXIMUM_STROKE_COUNT - 1] = value.ToStroke(); 40 | } 41 | ++strokeCount; 42 | Update(true); 43 | } 44 | } 45 | 46 | void StenoStrokeCapture::Update(bool onStrokeInput) { 47 | #if JAVELIN_SPLIT 48 | for (int displayId = 0; displayId < 2; ++displayId) { 49 | #else 50 | const int displayId = 0; 51 | { 52 | #endif 53 | switch (autoDraw[displayId]) { 54 | case AutoDraw::NONE: 55 | // Do nothing 56 | break; 57 | case AutoDraw::PAPER_TAPE: 58 | DisplayDriver::DrawPaperTape(displayId, strokes, 59 | strokeCount > MAXIMUM_STROKE_COUNT 60 | ? MAXIMUM_STROKE_COUNT 61 | : strokeCount); 62 | break; 63 | case AutoDraw::STENO_LAYOUT: 64 | DisplayDriver::DrawStenoLayout(displayId, 65 | strokeCount == 0 ? StenoStroke(0) 66 | : strokeCount < MAXIMUM_STROKE_COUNT 67 | ? strokes[strokeCount - 1] 68 | : strokes[MAXIMUM_STROKE_COUNT - 1]); 69 | break; 70 | case AutoDraw::WPM: 71 | if (!onStrokeInput) { 72 | DrawWpm(displayId); 73 | } 74 | break; 75 | 76 | case AutoDraw::STROKES: 77 | DrawStrokes(displayId); 78 | break; 79 | } 80 | } 81 | } 82 | 83 | void StenoStrokeCapture::SetAutoDraw(int displayId, AutoDraw autoDrawId) { 84 | #if JAVELIN_SPLIT 85 | if (displayId < 0 || displayId >= 2) { 86 | return; 87 | } 88 | #else 89 | displayId = 0; 90 | #endif 91 | if (autoDrawId != AutoDraw::WPM) { 92 | lastWpm[displayId] = -1; 93 | } 94 | autoDraw[displayId] = autoDrawId; 95 | Update(false); 96 | #if JAVELIN_SPLIT 97 | SetWpmTimer(autoDraw[0] == AutoDraw::WPM || autoDraw[1] == AutoDraw::WPM); 98 | #else 99 | SetWpmTimer(autoDraw[0] == AutoDraw::WPM); 100 | #endif 101 | } 102 | 103 | void StenoStrokeCapture::SetWpmTimer(bool enable) { 104 | const intptr_t timerId = -('A' * 256 + 'D'); 105 | if (enable) { 106 | TimerManager::instance.StartTimer(timerId, 1000, true, this, 107 | Clock::GetMilliseconds()); 108 | } else { 109 | TimerManager::instance.StopTimer(timerId, Clock::GetMilliseconds()); 110 | } 111 | } 112 | 113 | void StenoStrokeCapture::WpmTimerHandler() { 114 | #if JAVELIN_SPLIT 115 | for (int displayId = 0; displayId < 2; ++displayId) { 116 | #else 117 | const int displayId = 0; 118 | { 119 | #endif 120 | switch (autoDraw[displayId]) { 121 | case AutoDraw::NONE: 122 | case AutoDraw::PAPER_TAPE: 123 | case AutoDraw::STENO_LAYOUT: 124 | case AutoDraw::STROKES: 125 | break; 126 | 127 | case AutoDraw::WPM: 128 | DrawWpm(displayId); 129 | break; 130 | } 131 | } 132 | } 133 | 134 | void StenoStrokeCapture::DrawWpm(int displayId) { 135 | const int newWpm = WpmTracker::instance.Get5sWpm(); 136 | if (newWpm == lastWpm[displayId]) { 137 | return; 138 | } 139 | lastWpm[displayId] = newWpm; 140 | 141 | Display::Clear(displayId); 142 | 143 | char buffer[16]; 144 | Str::Sprintf(buffer, "%d", newWpm); 145 | #if JAVELIN_DISPLAY_WIDTH >= 130 && JAVELIN_DISPLAY_HEIGHT >= 128 146 | const Font *font = &Font::HUGE_DIGITS; 147 | #elif JAVELIN_DISPLAY_WIDTH >= 64 148 | const Font *font = &Font::LARGE_DIGITS; 149 | #else 150 | const Font *font = &Font::MEDIUM_DIGITS; 151 | #endif 152 | DisplayDriver::DrawText(displayId, JAVELIN_DISPLAY_WIDTH / 2, 153 | JAVELIN_DISPLAY_HEIGHT / 2 - font->height / 2 + 154 | 3 * font->baseline / 4, 155 | font, TextAlignment::MIDDLE, buffer); 156 | 157 | font = &Font::DEFAULT; 158 | DisplayDriver::DrawText(displayId, JAVELIN_DISPLAY_WIDTH / 2, 159 | JAVELIN_DISPLAY_HEIGHT * 3 / 4 + font->baseline / 2, 160 | font, TextAlignment::MIDDLE, "wpm"); 161 | } 162 | 163 | void StenoStrokeCapture::DrawStrokes(int displayId) { 164 | Display::Clear(displayId); 165 | 166 | char buffer[16]; 167 | Str::Sprintf(buffer, "%zu", strokeCount); 168 | 169 | #if JAVELIN_DISPLAY_WIDTH >= 130 && JAVELIN_DISPLAY_HEIGHT >= 128 170 | const Font *font = 171 | Str::Length(buffer) <= 3 ? &Font::HUGE_DIGITS : &Font::LARGE_DIGITS; 172 | #elif JAVELIN_DISPLAY_WIDTH >= 128 173 | const Font *font = &Font::LARGE_DIGITS; 174 | #elif JAVELIN_DISPLAY_WIDTH >= 64 175 | const Font *font = 176 | Str::Length(buffer) <= 3 ? &Font::LARGE_DIGITS : &Font::MEDIUM_DIGITS; 177 | #else 178 | const Font *font = 179 | Str::Length(buffer) <= 2 ? &Font::MEDIUM_DIGITS : &Font::SMALL_DIGITS; 180 | #endif 181 | DisplayDriver::DrawText(displayId, JAVELIN_DISPLAY_WIDTH / 2, 182 | JAVELIN_DISPLAY_HEIGHT / 2 - font->height / 2 + 183 | 3 * font->baseline / 4, 184 | font, TextAlignment::MIDDLE, buffer); 185 | 186 | font = &Font::DEFAULT; 187 | DisplayDriver::DrawText(displayId, JAVELIN_DISPLAY_WIDTH / 2, 188 | JAVELIN_DISPLAY_HEIGHT * 3 / 4 + font->baseline / 2, 189 | font, TextAlignment::MIDDLE, "strokes"); 190 | } 191 | 192 | #if JAVELIN_USE_EMBEDDED_STENO 193 | void Display::SetAutoDraw(int displayId, int autoDrawId) { 194 | StenoStrokeCapture::container->SetAutoDraw(displayId, (AutoDraw)autoDrawId); 195 | } 196 | #endif 197 | 198 | #endif 199 | 200 | //--------------------------------------------------------------------------- 201 | -------------------------------------------------------------------------------- /ssd1306_steno_layout.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "ssd1306.h" 4 | 5 | //--------------------------------------------------------------------------- 6 | 7 | struct StenoLayoutData { 8 | uint8_t type; 9 | uint8_t x; 10 | }; 11 | 12 | //--------------------------------------------------------------------------- 13 | 14 | #if JAVELIN_DISPLAY_DRIVER == 1306 15 | 16 | //--------------------------------------------------------------------------- 17 | 18 | #if JAVELIN_DISPLAY_WIDTH == 128 && JAVELIN_DISPLAY_HEIGHT == 64 19 | 20 | constexpr StenoLayoutData LAYOUT_DATA[] = { 21 | {0, 5}, // # 22 | {1, 5}, // S 23 | {2, 17}, // T 24 | {3, 17}, // K 25 | {2, 29}, // P 26 | {3, 29}, // W 27 | {2, 41}, // H 28 | {3, 41}, // R 29 | {4, 33}, // A 30 | {4, 45}, // O 31 | {1, 53}, // * 32 | {4, 61}, // E 33 | {4, 73}, // U 34 | {2, 65}, // F 35 | {3, 65}, // R 36 | {2, 77}, // P 37 | {3, 77}, // B 38 | {2, 89}, // L 39 | {3, 89}, // G 40 | {2, 101}, // T 41 | {3, 101}, // S 42 | {2, 113}, // D 43 | {3, 113}, // Z 44 | }; 45 | 46 | constexpr uint32_t type1OffData[] = { 47 | 0xfffffc00, 0x000001ff, // Row 0 48 | 0x00000400, 0x00000200, // Row 1 49 | 0x00000400, 0x00000400, // Row 2 50 | 0x00000400, 0x00000800, // Row 3 51 | 0x00000400, 0x00000800, // Row 4 52 | 0x00000400, 0x00000800, // Row 5 53 | 0x00000400, 0x00000800, // Row 6 54 | 0x00000400, 0x00000400, // Row 7 55 | 0x00000400, 0x00000200, // Row 8 56 | 0xfffffc00, 0x000001ff, // Row 9 57 | }; 58 | 59 | constexpr uint32_t type1OnData[] = { 60 | 0xfffffc00, 0x000001ff, // Row 0 61 | 0xfffffc00, 0x000003ff, // Row 1 62 | 0xfffffc00, 0x000007ff, // Row 2 63 | 0xfffffc00, 0x00000fff, // Row 3 64 | 0xfffffc00, 0x00000fff, // Row 4 65 | 0xfffffc00, 0x00000fff, // Row 5 66 | 0xfffffc00, 0x00000fff, // Row 6 67 | 0xfffffc00, 0x000007ff, // Row 7 68 | 0xfffffc00, 0x000003ff, // Row 8 69 | 0xfffffc00, 0x000001ff, // Row 9 70 | }; 71 | 72 | constexpr uint32_t type3OffData[] = { 73 | 0xf0000000, 0x000001ff, // Row 0 74 | 0x10000000, 0x00000200, // Row 1 75 | 0x10000000, 0x00000400, // Row 2 76 | 0x10000000, 0x00000800, // Row 3 77 | 0x10000000, 0x00000800, // Row 4 78 | 0x10000000, 0x00000800, // Row 5 79 | 0x10000000, 0x00000800, // Row 6 80 | 0x10000000, 0x00000400, // Row 7 81 | 0x10000000, 0x00000200, // Row 8 82 | 0xf0000000, 0x000001ff, // Row 9 83 | }; 84 | 85 | constexpr uint32_t type3OnData[] = { 86 | 0xf0000000, 0x000001ff, // Row 0 87 | 0xf0000000, 0x000003ff, // Row 1 88 | 0xf0000000, 0x000007ff, // Row 2 89 | 0xf0000000, 0x00000fff, // Row 3 90 | 0xf0000000, 0x00000fff, // Row 4 91 | 0xf0000000, 0x00000fff, // Row 5 92 | 0xf0000000, 0x00000fff, // Row 6 93 | 0xf0000000, 0x000007ff, // Row 7 94 | 0xf0000000, 0x000003ff, // Row 8 95 | 0xf0000000, 0x000001ff, // Row 9 96 | }; 97 | 98 | constexpr uint32_t type4OffData[] = { 99 | 0x0fff8000, 0x10008000, 0x20008000, 0x40008000, 0x40008000, 100 | 0x40008000, 0x40008000, 0x20008000, 0x10008000, 0x0fff8000, 101 | }; 102 | 103 | constexpr uint32_t type4OnData[] = { 104 | 0x0fff8000, 0x1fff8000, 0x3fff8000, 0x7fff8000, 0x7fff8000, 105 | 0x7fff8000, 0x7fff8000, 0x3fff8000, 0x1fff8000, 0x0fff8000, 106 | }; 107 | 108 | void Ssd1306::Ssd1306Data::DrawStenoLayout(StenoStroke stroke) { 109 | if (!available) { 110 | return; 111 | } 112 | 113 | Clear(); 114 | 115 | for (size_t i = 0; i < sizeof(LAYOUT_DATA) / sizeof(*LAYOUT_DATA); ++i) { 116 | const int x = LAYOUT_DATA[i].x; 117 | if (stroke.GetKeyState() & (1 << i)) { 118 | // On 119 | switch (LAYOUT_DATA[i].type) { 120 | case 0: { 121 | uint8_t *p = &buffer8[x * (JAVELIN_DISPLAY_HEIGHT / 8)]; 122 | for (int i = 0; i < 118; ++i) { 123 | *p = 0xfe; 124 | p += (JAVELIN_DISPLAY_HEIGHT / 8); 125 | } 126 | break; 127 | } 128 | case 1: { 129 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)]; 130 | for (uint32_t d : type1OnData) { 131 | *p++ |= d; 132 | } 133 | break; 134 | } 135 | case 2: { 136 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)]; 137 | for (int i = 0; i < 10; ++i) { 138 | *p |= 0x03fffc00; 139 | p += (JAVELIN_DISPLAY_HEIGHT / 32); 140 | } 141 | break; 142 | } 143 | case 3: { 144 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)]; 145 | for (uint32_t d : type3OnData) { 146 | *p++ |= d; 147 | } 148 | break; 149 | } 150 | case 4: { 151 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)] + 1; 152 | for (uint32_t d : type4OnData) { 153 | *p |= d; 154 | p += (JAVELIN_DISPLAY_HEIGHT / 32); 155 | } 156 | break; 157 | } 158 | } 159 | } else { 160 | // Off 161 | switch (LAYOUT_DATA[i].type) { 162 | case 0: { 163 | uint8_t *p = &buffer8[x * (JAVELIN_DISPLAY_HEIGHT / 8)]; 164 | *p = 0xfe; 165 | p += (JAVELIN_DISPLAY_HEIGHT / 8); 166 | for (int i = 0; i < 116; ++i) { 167 | *p = 0x82; 168 | p += (JAVELIN_DISPLAY_HEIGHT / 8); 169 | } 170 | *p = 0xfe; 171 | break; 172 | } 173 | case 1: { 174 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)]; 175 | for (uint32_t d : type1OffData) { 176 | *p++ |= d; 177 | } 178 | break; 179 | } 180 | case 2: { 181 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)]; 182 | *p |= 0x03fffc00; 183 | p += (JAVELIN_DISPLAY_HEIGHT / 32); 184 | 185 | for (int i = 0; i < 8; ++i) { 186 | *p |= 0x002000400; 187 | p += (JAVELIN_DISPLAY_HEIGHT / 32); 188 | } 189 | *p |= 0x03fffc00; 190 | break; 191 | } 192 | case 3: { 193 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)]; 194 | for (uint32_t d : type3OffData) { 195 | *p++ |= d; 196 | } 197 | break; 198 | } 199 | case 4: { 200 | uint32_t *p = &buffer32[x * (JAVELIN_DISPLAY_HEIGHT / 32)] + 1; 201 | for (uint32_t d : type4OffData) { 202 | *p |= d; 203 | p += (JAVELIN_DISPLAY_HEIGHT / 32); 204 | } 205 | break; 206 | } 207 | } 208 | } 209 | } 210 | } 211 | 212 | #else 213 | 214 | void Ssd1306::Ssd1306Data::DrawStenoLayout(StenoStroke stroke) { 215 | // Not supported. 216 | } 217 | 218 | #endif 219 | 220 | //--------------------------------------------------------------------------- 221 | 222 | #endif // JAVELIN_DISPLAY_DRIVER == 1306 223 | 224 | //--------------------------------------------------------------------------- 225 | -------------------------------------------------------------------------------- /config/kyria_rev4.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "encoder_pins.h" 5 | #include "main_flash_layout.h" 6 | #include "pinnacle_pins.h" 7 | 8 | //--------------------------------------------------------------------------- 9 | 10 | #define CONFIG_EXTRA_SOURCE "config/kyria_rev4.cc" 11 | 12 | //--------------------------------------------------------------------------- 13 | 14 | #define USE_CIRQUE_TRACKPAD 1 15 | #define USE_HALCYON_DISPLAY 1 16 | #define USE_HALCYON_ENCODER 1 17 | 18 | //--------------------------------------------------------------------------- 19 | 20 | #define JAVELIN_USE_EMBEDDED_STENO 1 21 | #define JAVELIN_USE_USER_DICTIONARY 1 22 | #define JAVELIN_USB_MILLIAMPS 500 23 | 24 | #define JAVELIN_RGB 1 25 | #define JAVELIN_RGB_COUNT 62 26 | #define JAVELIN_RGB_LEFT_COUNT 31 27 | #define JAVELIN_RGB_PIN 3 28 | #define JAVELIN_USE_RGB_MAP 1 29 | 30 | #define JAVELIN_SPLIT 1 31 | #define JAVELIN_SPLIT_PIO_CYCLES 5 32 | #define JAVELIN_SPLIT_TX_PIN 28 33 | #define JAVELIN_SPLIT_RX_PIN 29 34 | #define JAVELIN_SPLIT_IS_MASTER 1 35 | #define JAVELIN_SPLIT_SIDE_PIN 24 36 | #define JAVELIN_SPLIT_TX_RX_BUFFER_SIZE 2048 37 | 38 | #if USE_HALCYON_DISPLAY 39 | #define JAVELIN_DISPLAY_DRIVER 7789 40 | #define JAVELIN_DISPLAY_MISO_PIN 12 41 | #define JAVELIN_DISPLAY_CS_PIN 13 42 | #define JAVELIN_DISPLAY_SCK_PIN 14 43 | #define JAVELIN_DISPLAY_MOSI_PIN 15 44 | #define JAVELIN_DISPLAY_DC_PIN 16 45 | #define JAVELIN_DISPLAY_RST_PIN 26 46 | #define JAVELIN_DISPLAY_BACKLIGHT_PIN 27 47 | #define JAVELIN_DISPLAY_SPI spi1 48 | #define JAVELIN_DISPLAY_TX_DMA SPI1_TX 49 | #define JAVELIN_DISPLAY_SCREEN_WIDTH 135 50 | #define JAVELIN_DISPLAY_SCREEN_HEIGHT 240 51 | #define JAVELIN_DISPLAY_ROTATION 0 52 | #define JAVELIN_DISPLAY_WIDTH 135 53 | #define JAVELIN_DISPLAY_HEIGHT 240 54 | 55 | bool shouldUseDisplayModule(); 56 | #define JAVELIN_DISPLAY_DETECT shouldUseDisplayModule() 57 | 58 | #endif 59 | 60 | #if USE_CIRQUE_TRACKPAD 61 | 62 | #define JAVELIN_POINTER 0x73a 63 | #define JAVELIN_POINTER_SPI spi1 64 | #define JAVELIN_POINTER_MISO_PIN 12 65 | #define JAVELIN_POINTER_SCK_PIN 14 66 | #define JAVELIN_POINTER_MOSI_PIN 15 67 | 68 | #define JAVELIN_POINTER_COUNT 2 69 | #define JAVELIN_POINTER_LEFT_COUNT 1 70 | 71 | const PinnaclePins PINNACLE_PINS[2] = { 72 | {.chipSelectPin = 13, .invertX = true, .invertY = true}, 73 | {.chipSelectPin = 13}, 74 | }; 75 | 76 | #endif 77 | 78 | #define JAVELIN_BUTTON_MATRIX 1 79 | 80 | constexpr uint8_t LEFT_COLUMN_PINS[] = {19, 20, 25, 4, 9, 10, 5}; 81 | constexpr uint8_t LEFT_ROW_PINS[] = {8, 11, 7, 6}; 82 | 83 | constexpr uint8_t RIGHT_COLUMN_PINS[] = {5, 10, 9, 4, 25, 20, 19}; 84 | constexpr uint8_t RIGHT_ROW_PINS[] = {8, 11, 7, 6}; 85 | 86 | // clang-format off 87 | // 88 | // Button indexes 89 | // 0 1 2 3 4 5 | 6 7 8 9 10 11 90 | // 12 13 14 15 16 17 | 18 19 20 21 22 23 91 | // 24 25 26 27 28 29 30 31 | 32 33 34 35 36 37 38 39 92 | // 40 41 42 43 44 | 45 46 47 48 49 93 | // 94 | // Matrix positions 95 | // 06 05 04 03 02 01 | 01 02 03 04 05 06 96 | // 16 15 14 13 12 11 | 11 12 13 14 15 16 97 | // 26 25 24 23 22 21 33 20 | 20 33 21 22 23 24 25 26 98 | // 34 32 31 35 30 | 30 35 31 32 34 99 | // 100 | // RGB indices 101 | // Back: 102 | // 2 1 0 | 31 32 33 103 | // | 104 | // 3 4 5 | 36 35 34 105 | // | 106 | // 107 | // Front: 108 | // 30 29 28 27 26 25 | 56 57 58 59 60 61 109 | // 24 23 22 21 20 19 | 50 51 52 53 54 55 110 | // 18 17 16 15 14 13 12 11 | 42 43 44 45 46 47 48 49 111 | // 10 9 8 7 6 | 37 38 39 40 41 112 | 113 | constexpr int8_t LEFT_KEY_MAP[4][8] = { 114 | { -1, 5, 4, 3, 2, 1, 0, -1 }, 115 | { -1, 17, 16, 15, 14, 13, 12, -1 }, 116 | { 31, 29, 28, 27, 26, 25, 24, -1 }, 117 | { 44, 42, 41, 30, 40, 43, -1, -1 }, 118 | }; 119 | 120 | constexpr int8_t RIGHT_KEY_MAP[4][8] = { 121 | { -1, 6, 7, 8, 9, 10, 11, -1 }, 122 | { -1, 18, 19, 20, 21, 22, 23, -1 }, 123 | { 32, 34, 35, 36, 37, 38, 39, -1 }, 124 | { 45, 47, 48, 33, 49, 46, -1, -1 }, 125 | }; 126 | 127 | constexpr uint8_t RGB_MAP[62] = { 128 | 30, 29, 28, 27, 26, 25, 56, 57, 58, 59, 60, 61, 129 | 24, 23, 22, 21, 20, 19, 50, 51, 52, 53, 54, 55, 130 | 18, 17, 16, 15, 14, 13, 12, 11, 42, 43, 44, 45, 46, 47, 48, 49, 131 | 10, 9, 8, 7, 6, 37, 38, 39, 40, 41, 132 | 133 | // Underglow 134 | 2, 1, 0, 31, 32, 33, 135 | 3, 4, 5, 36, 35, 34, 136 | }; 137 | 138 | // clang-format on 139 | 140 | #if USE_HALCYON_ENCODER 141 | #define JAVELIN_BUTTON_PINS 1 142 | #define JAVELIN_BUTTON_PINS_OFFSET 50 143 | #define JAVELIN_ENCODER_BUTTON_PIN 16 144 | #define JAVELIN_ENCODER_UNUSED_PIN 17 145 | 146 | extern uint8_t BUTTON_PINS[1]; 147 | 148 | constexpr EncoderPins ENCODER_PINS[] = {{23, 22}, {27, 26}, {23, 22}, {27, 26}}; 149 | 150 | void preButtonStateInitialize(); 151 | #define JAVELIN_PRE_BUTTON_STATE_INITIALIZE preButtonStateInitialize(); 152 | 153 | #else 154 | 155 | constexpr EncoderPins ENCODER_PINS[] = {{23, 22}, {23, 22}}; 156 | 157 | #endif 158 | 159 | #define JAVELIN_ENCODER 1 160 | #define JAVELIN_ENCODER_COUNT 4 161 | #define JAVELIN_ENCODER_LEFT_COUNT 2 162 | #define JAVELIN_ENCODER_SPEED 2 163 | 164 | const size_t BUTTON_COUNT = 52; 165 | 166 | #define JAVELIN_SCRIPT_CONFIGURATION \ 167 | R"({"name":"Kyria","layout":[{"x":0,"y":0.9},{"x":1,"y":0.9},{"x":2,"y":0.35},{"x":3,"y":0},{"x":4,"y":0.35},{"x":5,"y":0.5},{"x":12,"y":0.5},{"x":13,"y":0.35},{"x":14,"y":0},{"x":15,"y":0.35},{"x":16,"y":0.9},{"x":17,"y":0.9},{"x":0,"y":1.9},{"x":1,"y":1.9},{"x":2,"y":1.35},{"x":3,"y":1},{"x":4,"y":1.35},{"x":5,"y":1.5},{"x":12,"y":1.5},{"x":13,"y":1.35},{"x":14,"y":1},{"x":15,"y":1.35},{"x":16,"y":1.9},{"x":17,"y":1.9},{"x":0,"y":2.9},{"x":1,"y":2.9},{"x":2,"y":2.35},{"x":3,"y":2},{"x":4,"y":2.35},{"x":5,"y":2.5},{"x":6.12,"y":3.13,"r":0.52},{"x":7.21,"y":3.99,"r":0.79},{"x":9.79,"y":3.99,"r":-0.79},{"x":10.88,"y":3.13,"r":-0.52},{"x":12,"y":2.5},{"x":13,"y":2.35},{"x":14,"y":2},{"x":15,"y":2.35},{"x":16,"y":2.9},{"x":17,"y":2.9},{"x":2.5,"y":3.4},{"x":3.5,"y":3.4},{"x":4.6,"y":3.55,"r":0.26},{"x":5.62,"y":4,"r":0.52},{"x":6.5,"y":4.7,"r":0.79},{"x":10.5,"y":4.7,"r":-0.79},{"x":11.38,"y":4,"r":-0.52},{"x":12.4,"y":3.55,"r":-0.26},{"x":13.5,"y":3.4},{"x":14.5,"y":3.4},{"x":6.2,"y":1.4,"s":0.8},{"x":11,"y":1.4,"s":0.8}]})" 168 | 169 | const char *const MANUFACTURER_NAME = "splitkb"; 170 | const char *const PRODUCT_NAME = "Halcyon Kyria (Javelin)"; 171 | const int VENDOR_ID = 0x7fce; 172 | // const int PRODUCT_ID = 0xcf44; 173 | 174 | //--------------------------------------------------------------------------- 175 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 4.0) 2 | cmake_policy(SET CMP0076 NEW) 3 | 4 | set(NAME javelin-steno-pico) 5 | set(JAVELIN_BOARD "" CACHE STRING "Target board, e.g. \"uni_v4\"") 6 | 7 | if ("${JAVELIN_BOARD}" STREQUAL "") 8 | message(FATAL_ERROR, "Target board (e.g. 'uni_v4') must be specified") 9 | endif() 10 | 11 | add_definitions( 12 | -O2 -g 13 | -ffunction-sections -fdata-sections 14 | -fno-devirtualize-speculatively 15 | -fno-exceptions 16 | -DJAVELIN_BOARD_CONFIG=\"config/${JAVELIN_BOARD}.h\" 17 | -DJAVELIN_PLATFORM_PICO_SDK=1 18 | -DJAVELIN_THREADS=1 19 | -DJAVELIN_SPLIT_VERSION_INCREMENT=3 20 | -DNDEBUG=1 21 | -DPICO_FLASH_SIZE_BYTES=0x1000000 22 | -DPICO_FLASH_SPI_CLKDIV=4 23 | -DPICO_MALLOC_PANIC=0 24 | -DPICO_NO_FPGA_CHECK=1 25 | -DPICO_XOSC_STARTUP_DELAY_MULTIPLIER=64 26 | -DPICO_PANIC_FUNCTION=0 27 | ) 28 | 29 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 30 | 31 | if (PICO_PLATFORM STREQUAL "rp2040") 32 | add_definitions( 33 | -DJAVELIN_CPU_CORTEX_M0=1 34 | -DJAVELIN_USE_CUSTOM_POP_COUNT=1 35 | -DJAVELIN_PICO_PLATFORM=2040 36 | -DSYS_CLK_MHZ=200 37 | ) 38 | elseif (PICO_PLATFORM STREQUAL "rp2350-arm-s") 39 | add_definitions( 40 | -flto=auto 41 | -DJAVELIN_CPU_CORTEX_M33=1 42 | -DJAVELIN_USE_CUSTOM_POP_COUNT=1 43 | -DJAVELIN_PICO_PLATFORM=2350 44 | ) 45 | elseif (PICO_PLATFORM STREQUAL "rp2350-riscv") 46 | add_definitions( 47 | -DJAVELIN_CPU_HAZARD_3=1 48 | -DJAVELIN_PICO_PLATFORM=2350 49 | ) 50 | endif() 51 | 52 | project(${NAME} C CXX ASM) 53 | set(CMAKE_C_STANDARD 11) 54 | set(CMAKE_CXX_STANDARD 23) 55 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--print-memory-usage") 56 | 57 | pico_sdk_init() 58 | 59 | add_executable(${NAME}) 60 | 61 | pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_split_5.pio) 62 | pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_split_8.pio) 63 | pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_split_10.pio) 64 | pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_split_16.pio) 65 | pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_split_20.pio) 66 | pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_ws2812.pio) 67 | 68 | target_sources(${NAME} PUBLIC 69 | main.cc 70 | auto_draw.cc 71 | console_report_buffer.cc 72 | hid_report_buffer.cc 73 | libc_overrides.cc 74 | libc_stubs.cc 75 | main_report_builder.cc 76 | main_task.cc 77 | pico_bindings.cc 78 | pico_bootloader.cc 79 | pico_button_state.cc 80 | pico_clock.cc 81 | pico_console.cc 82 | pico_crc32.cc 83 | pico_encoder_state.cc 84 | pico_flash.cc 85 | pico_gpio.cc 86 | pico_midi.cc 87 | pico_orthography.cc 88 | pico_random.cc 89 | pico_serial_port.cc 90 | pico_split.cc 91 | pico_thread.cc 92 | pico_ws2812.cc 93 | pinnacle.cc 94 | plover_hid_report_buffer.cc 95 | split_hid_report_buffer.cc 96 | ssd1306.cc 97 | ssd1306_paper_tape.cc 98 | ssd1306_steno_layout.cc 99 | st7789.cc 100 | st7789_paper_tape.cc 101 | st7789_steno_layout.cc 102 | usb_descriptors.cc 103 | 104 | javelin/asset_manager.cc 105 | javelin/base64.cc 106 | javelin/bit.cc 107 | javelin/build_date.cc 108 | javelin/button_script.cc 109 | javelin/button_script_manager.cc 110 | javelin/console.cc 111 | javelin/console_input_buffer.cc 112 | javelin/container/list.cc 113 | javelin/crc32.cc 114 | javelin/dictionary/cache_dictionary.cc 115 | javelin/dictionary/compact_map_dictionary.cc 116 | javelin/dictionary/debug_dictionary.cc 117 | javelin/dictionary/dictionary.cc 118 | javelin/dictionary/dictionary_definition.cc 119 | javelin/dictionary/dictionary_list.cc 120 | javelin/dictionary/emily_symbols_dictionary.cc 121 | javelin/dictionary/full_map_dictionary.cc 122 | javelin/dictionary/invalid_dictionary.cc 123 | javelin/dictionary/jeff_numbers_dictionary.cc 124 | javelin/dictionary/jeff_phrasing_dictionary.cc 125 | javelin/dictionary/jeff_phrasing_dictionary_data.cc 126 | javelin/dictionary/jeff_show_stroke_dictionary.cc 127 | javelin/dictionary/orthospelling_data.cc 128 | javelin/dictionary/orthospelling_dictionary.cc 129 | javelin/dictionary/reverse_auto_suffix_dictionary.cc 130 | javelin/dictionary/reverse_map_dictionary.cc 131 | javelin/dictionary/reverse_prefix_dictionary.cc 132 | javelin/dictionary/reverse_suffix_dictionary.cc 133 | javelin/dictionary/unicode_dictionary.cc 134 | javelin/dictionary/user_dictionary.cc 135 | javelin/dictionary/wrapped_dictionary.cc 136 | javelin/engine_add_translation_mode.cc 137 | javelin/engine_binding.cc 138 | javelin/engine_console_mode.cc 139 | javelin/engine_normal_mode.cc 140 | javelin/engine.cc 141 | javelin/flash.cc 142 | javelin/font/monochrome/data/default.cc 143 | javelin/font/monochrome/data/dos.cc 144 | javelin/font/monochrome/data/huge_digits.cc 145 | javelin/font/monochrome/data/large.cc 146 | javelin/font/monochrome/data/large_digits.cc 147 | javelin/font/monochrome/data/medium_digits.cc 148 | javelin/font/monochrome/data/small_digits.cc 149 | javelin/font/monochrome/font.cc 150 | javelin/hal/ble.cc 151 | javelin/hal/bootloader.cc 152 | javelin/hal/connection.cc 153 | javelin/hal/display.cc 154 | javelin/hal/gpio.cc 155 | javelin/hal/infrared.cc 156 | javelin/hal/mouse.cc 157 | javelin/hal/power.cc 158 | javelin/hal/rgb.cc 159 | javelin/hal/sound.cc 160 | javelin/hal/usb_status.cc 161 | javelin/host_layout.cc 162 | javelin/key.cc 163 | javelin/key_code.cc 164 | javelin/key_press_parser.cc 165 | javelin/mem.cc 166 | javelin/orthography.cc 167 | javelin/pattern.cc 168 | javelin/pattern_component.cc 169 | javelin/pool_allocate.cc 170 | javelin/processor/all_up.cc 171 | javelin/processor/first_up.cc 172 | javelin/processor/gemini.cc 173 | javelin/processor/jeff_modifiers.cc 174 | javelin/processor/paper_tape.cc 175 | javelin/processor/passport.cc 176 | javelin/processor/plover_hid.cc 177 | javelin/processor/procat.cc 178 | javelin/processor/processor.cc 179 | javelin/processor/processor_list.cc 180 | javelin/processor/repeat.cc 181 | javelin/processor/tx_bolt.cc 182 | javelin/random.cc 183 | javelin/script.cc 184 | javelin/script_byte_code.cc 185 | javelin/script_storage.cc 186 | javelin/segment.cc 187 | javelin/segment_builder.cc 188 | javelin/split/split.cc 189 | javelin/split/split_console.cc 190 | javelin/split/split_key_state.cc 191 | javelin/split/split_midi.cc 192 | javelin/split/split_power_override.cc 193 | javelin/split/split_serial_buffer.cc 194 | javelin/split/split_usb_status.cc 195 | javelin/split/split_version.cc 196 | javelin/start_console_command_detector.cc 197 | javelin/state.cc 198 | javelin/steno_key_code.cc 199 | javelin/steno_key_code_buffer.cc 200 | javelin/steno_key_code_buffer_functions.cc 201 | javelin/steno_key_code_emitter.cc 202 | javelin/steno_key_state.cc 203 | javelin/str.cc 204 | javelin/stroke.cc 205 | javelin/stroke_history.cc 206 | javelin/stroke_list_parser.cc 207 | javelin/system.cc 208 | javelin/timer_manager.cc 209 | javelin/unicode.cc 210 | javelin/unicode_script.cc 211 | javelin/utf8_pointer.cc 212 | javelin/word_list.cc 213 | javelin/wpm_tracker.cc 214 | javelin/writer.cc 215 | ) 216 | 217 | # Add the local directory so tinyusb can find tusb_config.h 218 | target_include_directories(${NAME} PUBLIC 219 | ${CMAKE_CURRENT_SOURCE_DIR} 220 | ) 221 | 222 | target_link_libraries(${NAME} 223 | hardware_adc 224 | hardware_flash 225 | hardware_gpio 226 | hardware_i2c 227 | hardware_pio 228 | hardware_pwm 229 | hardware_spi 230 | pico_bootrom 231 | pico_multicore 232 | pico_stdlib 233 | tinyusb_device 234 | ) 235 | 236 | # create map/bin/hex file etc. 237 | pico_add_extra_outputs(${NAME}) 238 | -------------------------------------------------------------------------------- /st7789.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include "javelin/color.h" 5 | #include "javelin/font/text_alignment.h" 6 | #include "javelin/split/split.h" 7 | #include "javelin/stroke.h" 8 | 9 | //--------------------------------------------------------------------------- 10 | 11 | struct Font; 12 | enum class FontId : uint32_t; 13 | enum class St7789Command : uint8_t; 14 | 15 | //--------------------------------------------------------------------------- 16 | 17 | #if JAVELIN_DISPLAY_DRIVER == 7789 18 | 19 | class St7789 { 20 | public: 21 | static void Initialize() { GetInstance().Initialize(); } 22 | static void PrintInfo(); 23 | 24 | static void Update() { GetInstance().Update(); } 25 | 26 | static void DrawPaperTape(int displayId, const StenoStroke *strokes, 27 | size_t length) { 28 | instances[displayId].DrawPaperTape(strokes, length); 29 | } 30 | static void DrawStenoLayout(int displayId, StenoStroke stroke) { 31 | instances[displayId].DrawStenoLayout(stroke); 32 | } 33 | static void DrawText(int displayId, int x, int y, const Font *font, 34 | TextAlignment alignment, const char *text) { 35 | instances[displayId].DrawText(x, y, font, alignment, text); 36 | } 37 | 38 | static void RunConwayStep(int displayId) { 39 | instances[displayId].RunConwayStep(); 40 | } 41 | 42 | #if JAVELIN_SPLIT 43 | static void RegisterMasterHandlers() { 44 | Split::RegisterRxHandler(SplitHandlerId::DISPLAY_AVAILABLE, 45 | &instances[1].available); 46 | Split::RegisterTxHandler(&instances[1]); 47 | Split::RegisterTxHandler(&instances[1].control); 48 | } 49 | static void RegisterSlaveHandlers() { 50 | Split::RegisterTxHandler(&instances[1].available); 51 | Split::RegisterRxHandler(SplitHandlerId::DISPLAY_DATA, &instances[1]); 52 | Split::RegisterRxHandler(SplitHandlerId::DISPLAY_CONTROL, 53 | &instances[1].control); 54 | } 55 | #else 56 | static void RegisterMasterHandlers() {} 57 | static void RegisterSlaveHandlers() {} 58 | #endif 59 | 60 | private: 61 | class St7789Availability 62 | #if JAVELIN_SPLIT 63 | : public SplitTxHandler, 64 | public SplitRxHandler 65 | #endif 66 | { 67 | public: 68 | void operator=(bool value) { available = value; } 69 | bool operator!() const { return !available; } 70 | operator bool() const { return available; } 71 | 72 | private: 73 | bool available = true; // During startup scripts, the frame buffer may 74 | // need to be written. 75 | // Setting this to available will permit 76 | // the slave screen to be updated until the first 77 | // availability packet comes in. 78 | bool dirty = true; 79 | 80 | void UpdateBuffer(TxBuffer &buffer); 81 | void OnDataReceived(const void *data, size_t length); 82 | void OnReceiveConnectionReset() { available = false; } 83 | void OnTransmitConnectionReset() { dirty = true; } 84 | }; 85 | 86 | class St7789Control 87 | #if JAVELIN_SPLIT 88 | : public SplitTxHandler, 89 | public SplitRxHandler 90 | #endif 91 | { 92 | public: 93 | void Update(); 94 | void SetScreenOn(bool on) { 95 | if (on != data.screenOn) { 96 | data.screenOn = on; 97 | dirtyFlag |= DIRTY_FLAG_SCREEN_ON; 98 | } 99 | } 100 | void SetContrast(uint8_t value) { 101 | if (value != data.contrast) { 102 | data.contrast = value; 103 | dirtyFlag |= DIRTY_FLAG_CONTRAST; 104 | } 105 | } 106 | 107 | private: 108 | struct St7789ControlTxRxData { 109 | bool screenOn; 110 | uint8_t contrast; 111 | }; 112 | 113 | uint8_t dirtyFlag; 114 | St7789ControlTxRxData data; 115 | 116 | static constexpr int DIRTY_FLAG_SCREEN_ON = 1; 117 | static constexpr int DIRTY_FLAG_CONTRAST = 2; 118 | 119 | void UpdateBuffer(TxBuffer &buffer); 120 | void OnDataReceived(const void *data, size_t length); 121 | void OnTransmitConnectionReset(); 122 | }; 123 | 124 | class St7789Data 125 | #if JAVELIN_SPLIT 126 | : public SplitTxHandler, 127 | public SplitRxHandler 128 | #endif 129 | { 130 | public: 131 | St7789Availability available; 132 | St7789Control control; 133 | bool dirty; 134 | uint16_t drawColor565 = 0xffff; 135 | Color drawColor = Color{0xff, 0xff, 0xff}; 136 | size_t txRxOffset; 137 | 138 | void Initialize(); 139 | 140 | // None of these will take effect until Update() is called. 141 | void Clear(); 142 | void DrawLine(int x0, int y0, int x1, int y1); 143 | void DrawRect(int left, int top, int right, int bottom); 144 | void DrawBitmapImage(int x, int y, int width, int height, 145 | const uint8_t *data); 146 | void DrawLuminanceImage(int x, int y, int width, int height, 147 | const uint8_t *data); 148 | void DrawRgb332Image(int x, int y, int width, int height, 149 | const uint8_t *data); 150 | void DrawRgb565Image(int x, int y, int width, int height, 151 | const uint8_t *data); 152 | void DrawRgb888Image(int x, int y, int width, int height, 153 | const uint8_t *data); 154 | void DrawAlpha8Image(int x, int y, int width, int height, 155 | const uint8_t *data); 156 | void DrawArgb1555Image(int x, int y, int width, int height, 157 | const uint8_t *data); 158 | void DrawRgba8888Image(int x, int y, int width, int height, 159 | const uint8_t *data); 160 | void DrawLuminanceRange(int x, int y, int width, int height, 161 | const uint8_t *data, int min, int max); 162 | void DrawText(int x, int y, const Font *font, TextAlignment alignment, 163 | const char *text); 164 | void SetPixel(uint32_t x, uint32_t y); 165 | 166 | void RunConwayStep(); 167 | 168 | void DrawPaperTape(const StenoStroke *strokes, size_t length); 169 | void DrawStenoLayout(StenoStroke stroke); 170 | 171 | void Update(); 172 | 173 | private: 174 | struct RunConwayStepThreadData; 175 | 176 | union { 177 | uint8_t buffer8[2 * JAVELIN_DISPLAY_SCREEN_WIDTH * 178 | JAVELIN_DISPLAY_SCREEN_HEIGHT]; 179 | uint16_t buffer16[JAVELIN_DISPLAY_SCREEN_WIDTH * 180 | JAVELIN_DISPLAY_SCREEN_HEIGHT]; 181 | uint32_t buffer32[JAVELIN_DISPLAY_SCREEN_WIDTH * 182 | JAVELIN_DISPLAY_SCREEN_HEIGHT / 2]; 183 | }; 184 | 185 | void SendScreenData() const; 186 | 187 | void UpdateBuffer(TxBuffer &buffer); 188 | void OnTransmitConnectionReset() { dirty = true; } 189 | void OnDataReceived(const void *data, size_t length); 190 | }; 191 | 192 | static void SendCommand(St7789Command command, const void *data = nullptr, 193 | size_t length = 0); 194 | static void SetLR(uint32_t left, uint32_t right); 195 | static void SetTB(uint32_t top, uint32_t bottom); 196 | 197 | #if JAVELIN_SPLIT 198 | static St7789Data &GetInstance() { 199 | if (Split::IsMaster()) { 200 | return instances[0]; 201 | } else { 202 | return instances[1]; 203 | } 204 | } 205 | static St7789Data instances[2]; 206 | #else 207 | static St7789Data &GetInstance() { return instances[0]; } 208 | static St7789Data instances[1]; 209 | #endif 210 | 211 | friend class Display; 212 | }; 213 | 214 | #else 215 | 216 | class St7789 { 217 | public: 218 | static void Initialize() {} 219 | static void PrintInfo() {} 220 | static void DrawPaperTape() {} 221 | 222 | static void Update() {} 223 | 224 | static void RegisterMasterHandlers() {} 225 | static void RegisterSlaveHandlers() {} 226 | }; 227 | 228 | #endif 229 | 230 | //--------------------------------------------------------------------------- 231 | -------------------------------------------------------------------------------- /pico_dma.h: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | //--------------------------------------------------------------------------- 8 | 9 | #if JAVELIN_PICO_PLATFORM == 2350 10 | 11 | enum class PicoDmaTransferRequest : uint32_t { 12 | PIO0_TX0 = 0, 13 | PIO0_TX1, 14 | PIO0_TX2, 15 | PIO0_TX3, 16 | PIO0_RX0, 17 | PIO0_RX1, 18 | PIO0_RX2, 19 | PIO0_RX3, 20 | PIO1_TX0, 21 | PIO1_TX1, 22 | PIO1_TX2, 23 | PIO1_TX3, 24 | PIO1_RX0, 25 | PIO1_RX1, 26 | PIO1_RX2, 27 | PIO1_RX3, 28 | PIO2_TX0, 29 | PIO2_TX1, 30 | PIO2_TX2, 31 | PIO2_TX3, 32 | PIO2_RX0, 33 | PIO2_RX1, 34 | PIO2_RX2, 35 | PIO2_RX3, 36 | SPI0_TX, 37 | SPI0_RX, 38 | SPI1_TX, 39 | SPI1_RX, 40 | UART0_TX, 41 | UART0_RX, 42 | UART1_TX, 43 | UART1_RX, 44 | PWM_WRAP0, 45 | PWM_WRAP1, 46 | PWM_WRAP2, 47 | PWM_WRAP3, 48 | PWM_WRAP4, 49 | PWM_WRAP5, 50 | PWM_WRAP6, 51 | PWM_WRAP7, 52 | PWM_WRAP8, 53 | PWM_WRAP9, 54 | PWM_WRAP10, 55 | PWM_WRAP11, 56 | I2C0_TX, 57 | I2C0_RX, 58 | I2C1_TX, 59 | I2C1_RX, 60 | ADC, 61 | XIP_STREAM, 62 | XIP_QMITX, 63 | XIP_QMIRX, 64 | XIP_HSTX, 65 | XIP_CORESIGHT, 66 | XIP_SHA256, 67 | TIMER_0 = 0x3b, 68 | TIMER_1 = 0x3c, 69 | TIMER_2 = 0x3d, 70 | TIMER_3 = 0x3e, 71 | PERMANENT = 0x3f, 72 | }; 73 | 74 | struct PicoDmaControl { 75 | 76 | enum class DataSize : uint32_t { 77 | BYTE = 0, 78 | HALF_WORD = 1, 79 | WORD = 2, 80 | }; 81 | 82 | uint32_t enable : 1; 83 | uint32_t highPriority : 1; 84 | DataSize dataSize : 2; 85 | uint32_t incrementRead : 1; 86 | uint32_t incrementReadReverse : 1; 87 | uint32_t incrementWrite : 1; 88 | uint32_t incrementWriteReverse : 1; 89 | uint32_t ringSizeShift : 4; 90 | uint32_t ringSel : 1; 91 | 92 | // Set to the channel being used to disable chaining. 93 | uint32_t chainToDma : 4; 94 | 95 | PicoDmaTransferRequest transferRequest : 6; 96 | uint32_t quietIrq : 1; 97 | uint32_t bswap : 1; 98 | uint32_t sniffEnable : 1; 99 | uint32_t busy : 1; 100 | uint32_t _reserved27 : 2; 101 | uint32_t writeError : 1; 102 | uint32_t readError : 1; 103 | uint32_t ahbError : 1; 104 | 105 | void operator=(const PicoDmaControl &control) volatile { 106 | *(volatile uint32_t *)this = *(uint32_t *)&control; 107 | } 108 | }; 109 | static_assert(sizeof(PicoDmaControl) == 4, "Unexpected DmaControl size"); 110 | 111 | #elif JAVELIN_PICO_PLATFORM == 2040 112 | 113 | enum class PicoDmaTransferRequest : uint32_t { 114 | PIO0_TX0 = 0, 115 | PIO0_TX1, 116 | PIO0_TX2, 117 | PIO0_TX3, 118 | PIO0_RX0, 119 | PIO0_RX1, 120 | PIO0_RX2, 121 | PIO0_RX3, 122 | PIO1_TX0, 123 | PIO1_TX1, 124 | PIO1_TX2, 125 | PIO1_TX3, 126 | PIO1_RX0, 127 | PIO1_RX1, 128 | PIO1_RX2, 129 | PIO1_RX3, 130 | SPI0_TX, 131 | SPI0_RX, 132 | SPI1_TX, 133 | SPI1_RX, 134 | UART0_TX, 135 | UART0_RX, 136 | UART1_TX, 137 | UART1_RX, 138 | PWM_WRAP0, 139 | PWM_WRAP1, 140 | PWM_WRAP2, 141 | PWM_WRAP3, 142 | PWM_WRAP4, 143 | PWM_WRAP5, 144 | PWM_WRAP6, 145 | PWM_WRAP7, 146 | I2C0_TX, 147 | I2C0_RX, 148 | I2C1_TX, 149 | I2C1_RX, 150 | ADC, 151 | XIP_STREAM, 152 | XIP_SSITX, 153 | XIP_SSIRX, 154 | TIMER_0 = 0x3b, 155 | TIMER_1 = 0x3c, 156 | TIMER_2 = 0x3d, 157 | TIMER_3 = 0x3e, 158 | PERMANENT = 0x3f, 159 | }; 160 | 161 | struct PicoDmaControl { 162 | 163 | enum class DataSize : uint32_t { 164 | BYTE = 0, 165 | HALF_WORD = 1, 166 | WORD = 2, 167 | }; 168 | 169 | uint32_t enable : 1; 170 | uint32_t highPriority : 1; 171 | DataSize dataSize : 2; 172 | uint32_t incrementRead : 1; 173 | uint32_t incrementWrite : 1; 174 | uint32_t ringSizeShift : 4; 175 | uint32_t ringSel : 1; 176 | 177 | // Set to the channel being used to disable chaining. 178 | uint32_t chainToDma : 4; 179 | 180 | PicoDmaTransferRequest transferRequest : 6; 181 | uint32_t quietIrq : 1; 182 | uint32_t bswap : 1; 183 | uint32_t sniffEnable : 1; 184 | uint32_t busy : 1; 185 | uint32_t _reserved25 : 4; 186 | uint32_t writeError : 1; 187 | uint32_t readError : 1; 188 | uint32_t ahbError : 1; 189 | 190 | void operator=(const PicoDmaControl &control) volatile { 191 | *(volatile uint32_t *)this = *(uint32_t *)&control; 192 | } 193 | }; 194 | static_assert(sizeof(PicoDmaControl) == 4, "Unexpected DmaControl size"); 195 | 196 | #else 197 | #error Unsupported platform 198 | #endif 199 | 200 | //--------------------------------------------------------------------------- 201 | 202 | struct PicoDmaIrqControl { 203 | struct Irq { 204 | volatile uint32_t enableMask; 205 | volatile uint32_t forceMask; 206 | volatile uint32_t status; 207 | 208 | uint32_t AckAllIrqs() { 209 | const uint32_t mask = status; 210 | status = mask; 211 | return mask; 212 | } 213 | void AckIrq(int dmaChannel) { status = (1 << dmaChannel); } 214 | void EnableIrq(int dmaChannel) { enableMask |= (1 << dmaChannel); } 215 | }; 216 | 217 | volatile uint32_t status; 218 | Irq irq0; 219 | uint32_t _unused10; 220 | Irq irq1; 221 | }; 222 | 223 | static PicoDmaIrqControl *const dmaIrqControl = (PicoDmaIrqControl *)0x50000400; 224 | 225 | //--------------------------------------------------------------------------- 226 | 227 | struct PicoDmaAbort { 228 | volatile uint32_t value; 229 | 230 | void Abort(int channelIndex) { value = (1 << channelIndex); } 231 | }; 232 | 233 | #if JAVELIN_PICO_PLATFORM == 2350 234 | static PicoDmaAbort *const dmaAbort = (PicoDmaAbort *)0x50000464; 235 | #elif JAVELIN_PICO_PLATFORM == 2040 236 | static PicoDmaAbort *const dmaAbort = (PicoDmaAbort *)0x50000444; 237 | #else 238 | #error Unsupported platform 239 | #endif 240 | 241 | //--------------------------------------------------------------------------- 242 | 243 | struct PicoDma { 244 | const volatile void *volatile source; 245 | const volatile void *volatile destination; 246 | volatile uint32_t count; 247 | volatile PicoDmaControl controlTrigger; 248 | 249 | // Alias 1 250 | union { 251 | volatile PicoDmaControl control; 252 | volatile PicoDmaControl controlAlias1; 253 | }; 254 | const volatile void *volatile sourceAlias1; 255 | const volatile void *volatile destinationAlias1; 256 | volatile uint32_t countTrigger; 257 | 258 | // Alias 2 259 | volatile PicoDmaControl controlAlias2; 260 | volatile uint32_t countAlias2; 261 | const volatile void *volatile sourceAlias2; 262 | const volatile void *volatile destinationTrigger; 263 | 264 | // Alias 3 265 | volatile PicoDmaControl controlAlias3; 266 | const volatile void *volatile destinationAlias3; 267 | volatile uint32_t countAlias3; 268 | const volatile void *volatile sourceTrigger; 269 | 270 | inline bool IsBusy() const { return control.busy; } 271 | 272 | inline void WaitUntilComplete() const { 273 | while (IsBusy()) { 274 | } 275 | } 276 | 277 | void Abort() { 278 | dmaAbort->Abort(this - (PicoDma *)0x50000000); 279 | WaitUntilComplete(); 280 | } 281 | 282 | inline void Copy16(void *dest, const void *source, size_t transferCount); 283 | inline void Copy32(void *dest, const void *source, size_t transferCount); 284 | }; 285 | 286 | static PicoDma *const dma0 = (PicoDma *)0x50000000; 287 | static PicoDma *const dma1 = (PicoDma *)0x50000040; 288 | static PicoDma *const dma2 = (PicoDma *)0x50000080; 289 | static PicoDma *const dma3 = (PicoDma *)0x500000c0; 290 | static PicoDma *const dma4 = (PicoDma *)0x50000100; 291 | static PicoDma *const dma5 = (PicoDma *)0x50000140; 292 | static PicoDma *const dma6 = (PicoDma *)0x50000180; 293 | static PicoDma *const dma7 = (PicoDma *)0x500001c0; 294 | static PicoDma *const dma8 = (PicoDma *)0x50000200; 295 | static PicoDma *const dma9 = (PicoDma *)0x50000240; 296 | static PicoDma *const dma10 = (PicoDma *)0x50000280; 297 | static PicoDma *const dma11 = (PicoDma *)0x500002c0; 298 | 299 | #if JAVELIN_PICO_PLATFORM == 2350 300 | static volatile uint32_t *const dmaTimer0 = (uint32_t *)0x50000440; 301 | static volatile uint32_t *const dmaTimer1 = (uint32_t *)0x50000444; 302 | static volatile uint32_t *const dmaTimer2 = (uint32_t *)0x50000448; 303 | static volatile uint32_t *const dmaTimer3 = (uint32_t *)0x5000044c; 304 | #elif JAVELIN_PICO_PLATFORM == 2040 305 | // Upper 16 bits = numerator. 306 | // Lower 16 bits = denominator. 307 | static volatile uint32_t *const dmaTimer0 = (uint32_t *)0x50000420; 308 | static volatile uint32_t *const dmaTimer1 = (uint32_t *)0x50000424; 309 | static volatile uint32_t *const dmaTimer2 = (uint32_t *)0x50000428; 310 | static volatile uint32_t *const dmaTimer3 = (uint32_t *)0x5000042c; 311 | #else 312 | #error Unsupported Platform 313 | #endif 314 | 315 | //--------------------------------------------------------------------------- 316 | 317 | inline void PicoDma::Copy16(void *d, const void *s, size_t transferCount) { 318 | source = s; 319 | destination = d; 320 | count = transferCount; 321 | const PicoDmaControl dmaControl = { 322 | .enable = true, 323 | .dataSize = PicoDmaControl::DataSize::HALF_WORD, 324 | .incrementRead = true, 325 | .incrementWrite = true, 326 | .chainToDma = uint32_t(this - dma0), 327 | .transferRequest = PicoDmaTransferRequest::PERMANENT, 328 | .sniffEnable = false, 329 | }; 330 | controlTrigger = dmaControl; 331 | } 332 | 333 | inline void PicoDma::Copy32(void *d, const void *s, size_t transferCount) { 334 | source = s; 335 | destination = d; 336 | count = transferCount; 337 | const PicoDmaControl dmaControl = { 338 | .enable = true, 339 | .dataSize = PicoDmaControl::DataSize::WORD, 340 | .incrementRead = true, 341 | .incrementWrite = true, 342 | .chainToDma = uint32_t(this - dma0), 343 | .transferRequest = PicoDmaTransferRequest::PERMANENT, 344 | .sniffEnable = false, 345 | }; 346 | controlTrigger = dmaControl; 347 | } 348 | 349 | //--------------------------------------------------------------------------- 350 | -------------------------------------------------------------------------------- /pico_button_state.cc: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------- 2 | 3 | #include "pico_button_state.h" 4 | #include "calculate_mask.h" 5 | #include "javelin/split/split.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include JAVELIN_BOARD_CONFIG 13 | 14 | //--------------------------------------------------------------------------- 15 | 16 | #if JAVELIN_BUTTON_MATRIX 17 | 18 | #if JAVELIN_SPLIT 19 | #if defined(JAVELIN_SPLIT_IS_LEFT) 20 | #if JAVELIN_SPLIT_IS_LEFT 21 | 22 | #define COLUMN_PINS LEFT_COLUMN_PINS 23 | #define ROW_PINS LEFT_ROW_PINS 24 | #define KEY_MAP LEFT_KEY_MAP 25 | constexpr uint32_t COLUMN_PIN_MASK = CALCULATE_MASK(LEFT_COLUMN_PINS); 26 | constexpr uint32_t ROW_PIN_MASK = CALCULATE_MASK(LEFT_ROW_PINS); 27 | constexpr size_t COLUMN_PIN_COUNT = sizeof(LEFT_COLUMN_PINS); 28 | constexpr size_t ROW_PIN_COUNT = sizeof(LEFT_ROW_PINS); 29 | 30 | #else // JAVELIN_SPLIT_IS_LEFT 31 | 32 | #define COLUMN_PINS RIGHT_COLUMN_PINS 33 | #define ROW_PINS RIGHT_ROW_PINS 34 | #define KEY_MAP RIGHT_KEY_MAP 35 | constexpr uint32_t COLUMN_PIN_MASK = CALCULATE_MASK(RIGHT_COLUMN_PINS); 36 | constexpr uint32_t ROW_PIN_MASK = CALCULATE_MASK(RIGHT_ROW_PINS); 37 | constexpr size_t COLUMN_PIN_COUNT = sizeof(RIGHT_COLUMN_PINS); 38 | constexpr size_t ROW_PIN_COUNT = sizeof(RIGHT_ROW_PINS); 39 | 40 | #endif // JAVELIN_SPLIT_IS_LEFT 41 | #else // defined(JAVELIN_SPLIT_IS_LEFT) 42 | 43 | auto COLUMN_PINS = LEFT_COLUMN_PINS; 44 | auto ROW_PINS = LEFT_ROW_PINS; 45 | auto KEY_MAP = LEFT_KEY_MAP; 46 | uint32_t COLUMN_PIN_MASK = CALCULATE_MASK(LEFT_COLUMN_PINS); 47 | uint32_t ROW_PIN_MASK = CALCULATE_MASK(LEFT_ROW_PINS); 48 | size_t COLUMN_PIN_COUNT = sizeof(LEFT_COLUMN_PINS); 49 | size_t ROW_PIN_COUNT = sizeof(LEFT_ROW_PINS); 50 | 51 | #endif // defined(JAVELIN_SPLIT_IS_LEFT) 52 | #else // JAVELIN_SPLIT 53 | 54 | constexpr uint32_t COLUMN_PIN_MASK = CALCULATE_MASK(COLUMN_PINS); 55 | constexpr uint32_t ROW_PIN_MASK = CALCULATE_MASK(ROW_PINS); 56 | constexpr size_t COLUMN_PIN_COUNT = sizeof(COLUMN_PINS); 57 | constexpr size_t ROW_PIN_COUNT = sizeof(ROW_PINS); 58 | 59 | #endif 60 | 61 | #endif // JAVELIN_BUTTON_MATRIX 62 | 63 | #if JAVELIN_BUTTON_TOUCH 64 | uint32_t touchPadThreshold[sizeof(BUTTON_TOUCH_PINS)]; 65 | #if !defined(JAVELIN_TOUCH_CALIBRATION_COUNT) 66 | #define JAVELIN_TOUCH_CALIBRATION_COUNT 5 67 | #endif 68 | 69 | #endif 70 | 71 | //--------------------------------------------------------------------------- 72 | 73 | PicoButtonState PicoButtonState::instance; 74 | 75 | //--------------------------------------------------------------------------- 76 | 77 | void PicoButtonState::Initialize() { 78 | #if JAVELIN_BUTTON_MATRIX 79 | #if JAVELIN_SPLIT 80 | #if !defined(JAVELIN_SPLIT_IS_LEFT) 81 | if (!Split::IsLeft()) { 82 | COLUMN_PINS = RIGHT_COLUMN_PINS; 83 | ROW_PINS = RIGHT_ROW_PINS; 84 | KEY_MAP = RIGHT_KEY_MAP; 85 | COLUMN_PIN_MASK = CALCULATE_MASK(RIGHT_COLUMN_PINS); 86 | ROW_PIN_MASK = CALCULATE_MASK(RIGHT_ROW_PINS); 87 | COLUMN_PIN_COUNT = sizeof(RIGHT_COLUMN_PINS); 88 | ROW_PIN_COUNT = sizeof(RIGHT_ROW_PINS); 89 | } 90 | #endif 91 | #endif 92 | 93 | gpio_init_mask(COLUMN_PIN_MASK | ROW_PIN_MASK); 94 | gpio_set_dir_masked(COLUMN_PIN_MASK | ROW_PIN_MASK, ROW_PIN_MASK); 95 | 96 | for (size_t i = 0; i < COLUMN_PIN_COUNT; ++i) { 97 | gpio_pull_up(COLUMN_PINS[i]); 98 | } 99 | gpio_put_masked(ROW_PIN_MASK, ROW_PIN_MASK); 100 | 101 | #endif 102 | 103 | #if JAVELIN_BUTTON_PINS 104 | uint32_t BUTTON_PIN_MASK = CALCULATE_MASK(BUTTON_PINS); 105 | gpio_init_mask(BUTTON_PIN_MASK); 106 | gpio_set_dir_masked(BUTTON_PIN_MASK, 0); 107 | 108 | for (const uint8_t pinAndPolarity : BUTTON_PINS) { 109 | const uint8_t pin = pinAndPolarity & 0x7f; 110 | if (pin == 0x7f) { 111 | continue; 112 | } 113 | if (pinAndPolarity >> 7) { 114 | gpio_pull_down(pin); 115 | } else { 116 | gpio_pull_up(pin); 117 | } 118 | } 119 | #endif 120 | 121 | #if JAVELIN_BUTTON_TOUCH 122 | constexpr uint32_t BUTTON_TOUCH_PIN_MASK = CALCULATE_MASK(BUTTON_TOUCH_PINS); 123 | gpio_init_mask(BUTTON_TOUCH_PIN_MASK); 124 | for (const uint8_t pin : BUTTON_TOUCH_PINS) { 125 | gpio_disable_pulls(pin); 126 | gpio_set_drive_strength(pin, GPIO_DRIVE_STRENGTH_12MA); 127 | } 128 | 129 | gpio_set_dir_masked(BUTTON_TOUCH_PIN_MASK, BUTTON_TOUCH_PIN_MASK); 130 | gpio_put_masked(BUTTON_TOUCH_PIN_MASK, BUTTON_TOUCH_PIN_MASK); 131 | #endif 132 | 133 | #if JAVELIN_BUTTON_TOUCH 134 | 135 | for (size_t i = 0; i < 4; ++i) { 136 | uint32_t counters[sizeof(BUTTON_TOUCH_PINS)]; 137 | ReadTouchCounters(counters); 138 | 139 | for (size_t j = 0; j < sizeof(BUTTON_TOUCH_PINS); ++j) { 140 | touchPadThreshold[j] += counters[j]; 141 | } 142 | } 143 | 144 | for (size_t j = 0; j < sizeof(BUTTON_TOUCH_PINS); ++j) { 145 | touchPadThreshold[j] = 146 | touchPadThreshold[j] * (int)(BUTTON_TOUCH_THRESHOLD * 256) >> 10; 147 | } 148 | 149 | #endif 150 | } 151 | 152 | #if defined(BOOTSEL_BUTTON_INDEX) 153 | 154 | bool __no_inline_not_in_flash_func(PicoButtonState::IsBootSelButtonPressed)() { 155 | // Do not read bootsel within an interrupt, as disabling flash can cause 156 | // things to blow up if the second CPU is active. 157 | if (instance.isInInterrupt) { 158 | return instance.lastBootSelButtonState; 159 | } 160 | 161 | const int CS_PIN_INDEX = 1; 162 | 163 | // Flash access is temporarily disabled, so interrupts must be disabled. 164 | const uint32_t flags = save_and_disable_interrupts(); 165 | 166 | // Set chip select to Hi-Z 167 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 168 | GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 169 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 170 | 171 | // Note that no sleep function in flash can be called right now. 172 | // This is a ~10us wait. 173 | for (int i = 0; i < 125; ++i) { 174 | asm volatile("" ::: "memory"); 175 | } 176 | 177 | // The HI GPIO registers in SIO can observe and control the 6 QSPI pins. 178 | // Note the button pulls the pin *low* when pressed. 179 | #if JAVELIN_PICO_PLATFORM == 2350 180 | #define CS_BIT SIO_GPIO_HI_IN_QSPI_CSN_BITS 181 | #elif JAVELIN_PICO_PLATFORM == 2040 182 | #define CS_BIT (1u << 1) 183 | #else 184 | #error Unsupported platform 185 | #endif 186 | 187 | const bool buttonState = !(sio_hw->gpio_hi_in & CS_BIT); 188 | 189 | // Restore the state of chip select 190 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 191 | GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 192 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 193 | 194 | restore_interrupts(flags); 195 | instance.lastBootSelButtonState = buttonState; 196 | return buttonState; 197 | } 198 | #endif 199 | 200 | #if JAVELIN_BUTTON_TOUCH 201 | void PicoButtonState::ReadTouchCounters(uint32_t *counters) { 202 | constexpr uint32_t BUTTON_TOUCH_PIN_MASK = CALCULATE_MASK(BUTTON_TOUCH_PINS); 203 | gpio_set_dir_masked(BUTTON_TOUCH_PIN_MASK, BUTTON_TOUCH_PIN_MASK); 204 | gpio_put_masked(BUTTON_TOUCH_PIN_MASK, BUTTON_TOUCH_PIN_MASK); 205 | 206 | // This is a ~100us wait. 207 | for (int i = 0; i < 1250; ++i) { 208 | asm volatile("" ::: "memory"); 209 | } 210 | 211 | for (size_t i = 0; i < sizeof(BUTTON_TOUCH_PINS); ++i) { 212 | const uint8_t pin = BUTTON_TOUCH_PINS[i]; 213 | gpio_set_dir(pin, false); 214 | 215 | size_t counter = 0; 216 | for (; counter < 100000; ++counter) { 217 | if (!gpio_get(pin)) { 218 | break; 219 | } 220 | } 221 | counters[i] = counter; 222 | } 223 | } 224 | #endif 225 | 226 | void PicoButtonState::UpdateInternal() { 227 | if (queue.IsFull()) { 228 | return; 229 | } 230 | 231 | const Debounced debounced = debouncer.Update(ReadInternal()); 232 | if (!debounced.isUpdated) { 233 | return; 234 | } 235 | 236 | const uint32_t now = Clock::GetMilliseconds(); 237 | instance.keyPressedTime = now; 238 | instance.queue.Add(TimedButtonState{ 239 | .timestamp = now, 240 | .state = debounced.value, 241 | }); 242 | } 243 | 244 | ButtonState PicoButtonState::ReadInternal() { 245 | ButtonState state; 246 | state.ClearAll(); 247 | 248 | #if JAVELIN_BUTTON_MATRIX 249 | for (int r = 0; r < ROW_PIN_COUNT; ++r) { 250 | gpio_put_masked(ROW_PIN_MASK, ROW_PIN_MASK & ~(1 << ROW_PINS[r])); 251 | // Seems to work solidly with 2us wait. Use 10 for safety. 252 | busy_wait_us_32(10); 253 | 254 | const int columnMask = gpio_get_all(); 255 | #pragma GCC unroll 1 256 | for (int c = 0; c < COLUMN_PIN_COUNT; ++c) { 257 | if (((columnMask >> COLUMN_PINS[c]) & 1) == 0) { 258 | const int buttonIndex = KEY_MAP[r][c]; 259 | if (buttonIndex >= 0) { 260 | state.Set(buttonIndex); 261 | } 262 | } 263 | } 264 | } 265 | 266 | gpio_put_masked(ROW_PIN_MASK, ROW_PIN_MASK); 267 | #endif 268 | 269 | #if JAVELIN_BUTTON_PINS 270 | 271 | #if !defined(JAVELIN_BUTTON_PINS_OFFSET) 272 | #define JAVELIN_BUTTON_PINS_OFFSET 0 273 | #endif 274 | 275 | const int buttonMask = gpio_get_all(); 276 | #pragma GCC unroll 1 277 | for (size_t b = 0; b < sizeof(BUTTON_PINS); ++b) { 278 | const uint8_t pinAndPolarity = BUTTON_PINS[b]; 279 | const uint8_t pin = pinAndPolarity & 0x7f; 280 | if (pin == 0x7f) { 281 | continue; 282 | } 283 | if (((buttonMask >> pin) & 1) == pinAndPolarity >> 7) { 284 | state.Set(JAVELIN_BUTTON_PINS_OFFSET + b); 285 | } 286 | } 287 | #endif 288 | 289 | #if JAVELIN_BUTTON_TOUCH 290 | uint32_t counters[sizeof(BUTTON_TOUCH_PINS)]; 291 | ReadTouchCounters(counters); 292 | 293 | for (size_t i = 0; i < sizeof(BUTTON_TOUCH_PINS); ++i) { 294 | const bool isTouched = counters[i] > touchPadThreshold[i]; 295 | if (isTouched) { 296 | state.Set(i); 297 | } 298 | } 299 | #endif 300 | 301 | #if defined(BOOTSEL_BUTTON_INDEX) 302 | if (IsBootSelButtonPressed()) { 303 | state.Set(BOOTSEL_BUTTON_INDEX); 304 | } 305 | #endif 306 | 307 | return state; 308 | } 309 | 310 | //--------------------------------------------------------------------------- 311 | --------------------------------------------------------------------------------