[Keyboard] ZSA Voyager (#22181)

Co-authored-by: Ryan <fauxpark@gmail.com>
Co-authored-by: László Bácsi <lackac@lackac.hu>
Co-authored-by: Joel Challis <git@zvecr.com>
This commit is contained in:
Drashna Jaelre 2024-04-16 15:55:40 -07:00 committed by GitHub
parent 9baafef968
commit 45d60214f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 991 additions and 0 deletions

View File

@ -0,0 +1,14 @@
// Copyright 2023 ZSA Technology Labs, Inc <@zsa>
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#define IS31FL3731_I2C_ADDRESS_1 IS31FL3731_I2C_ADDRESS_GND
#define IS31FL3731_I2C_ADDRESS_2 IS31FL3731_I2C_ADDRESS_VCC
#define IS31FL3731_I2C_TIMEOUT 5
#define MOUSEKEY_WHEEL_INTERVAL MOUSEKEY_INTERVAL
#define MOUSEKEY_WHEEL_MAX_SPEED MOUSEKEY_MAX_SPEED
#define MOUSEKEY_WHEEL_TIME_TO_MAX MOUSEKEY_TIME_TO_MAX

View File

@ -0,0 +1,20 @@
/* Copyright 2021 QMK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#define HAL_USE_I2C TRUE
#include_next <halconf.h>

View File

@ -0,0 +1,219 @@
{
"manufacturer": "ZSA Technology Labs",
"keyboard_name": "Voyager",
"maintainer": "ZSA Technology Labs",
"url": "zsa.io/voyager",
"processor": "STM32F303",
"bootloader": "custom",
"usb": {
"vid": "0x3297",
"pid": "0x1977",
"device_version": "0.0.1",
"shared_endpoint": {
"mouse": false
}
},
"features": {
"bootmagic": true,
"caps_word": true,
"deferred_exec": true,
"mousekey": true,
"extrakey": true,
"nkro": true,
"swap_hands": true,
"rgb_matrix": true
},
"bootmagic": {
"matrix": [0, 1]
},
"diode_direction": "ROW2COL",
"matrix_size": {
"cols": 7,
"rows": 12
},
"mousekey": {
"delay": 0,
"interval": 20,
"max_speed": 7,
"time_to_max": 60,
"wheel_delay": 400
},
"qmk": {
"locking": {
"enabled": true,
"resync": true
}
},
"rgb_matrix": {
"driver": "is31fl3731",
"led_flush_limit": 26,
"led_process_limit": 5,
"max_brightness": 175,
"sleep": true,
"animations": {
"alphas_mods": true,
"gradient_up_down": true,
"gradient_left_right": true,
"breathing": true,
"band_sat": true,
"band_val": true,
"band_pinwheel_sat": true,
"band_pinwheel_val": true,
"band_spiral_sat": true,
"band_spiral_val": true,
"cycle_all": true,
"cycle_left_right": true,
"cycle_up_down": true,
"cycle_out_in": true,
"cycle_out_in_dual": true,
"rainbow_moving_chevron": true,
"cycle_pinwheel": true,
"cycle_spiral": true,
"dual_beacon": true,
"rainbow_beacon": true,
"rainbow_pinwheels": true,
"flower_blooming": true,
"raindrops": true,
"jellybean_raindrops": true,
"hue_breathing": true,
"hue_pendulum": true,
"hue_wave": true,
"pixel_fractal": true,
"pixel_flow": true,
"pixel_rain": true,
"typing_heatmap": true,
"digital_rain": true,
"solid_reactive_simple": true,
"solid_reactive": true,
"solid_reactive_wide": true,
"solid_reactive_multiwide": true,
"solid_reactive_cross": true,
"solid_reactive_multicross": true,
"solid_reactive_nexus": true,
"solid_reactive_multinexus": true,
"splash": true,
"multisplash": true,
"solid_splash": true,
"solid_multisplash": true,
"starlight": true,
"starlight_dual_sat": true,
"starlight_dual_hue": true,
"riverflow": true
},
"layout": [
{"matrix": [0, 1], "x": 0, "y": 10, "flags": 1},
{"matrix": [0, 2], "x": 17, "y": 10, "flags": 4},
{"matrix": [0, 3], "x": 34, "y": 8, "flags": 4},
{"matrix": [0, 4], "x": 52, "y": 5, "flags": 4},
{"matrix": [0, 5], "x": 69, "y": 8, "flags": 4},
{"matrix": [0, 6], "x": 86, "y": 10, "flags": 4},
{"matrix": [1, 1], "x": 0, "y": 21, "flags": 1},
{"matrix": [1, 2], "x": 17, "y": 21, "flags": 4},
{"matrix": [1, 3], "x": 34, "y": 19, "flags": 4},
{"matrix": [1, 4], "x": 52, "y": 17, "flags": 4},
{"matrix": [1, 5], "x": 69, "y": 19, "flags": 4},
{"matrix": [1, 6], "x": 86, "y": 21, "flags": 4},
{"matrix": [2, 1], "x": 0, "y": 32, "flags": 1},
{"matrix": [2, 2], "x": 17, "y": 32, "flags": 4},
{"matrix": [2, 3], "x": 34, "y": 30, "flags": 4},
{"matrix": [2, 4], "x": 52, "y": 28, "flags": 4},
{"matrix": [2, 5], "x": 69, "y": 30, "flags": 4},
{"matrix": [2, 6], "x": 86, "y": 32, "flags": 4},
{"matrix": [3, 1], "x": 0, "y": 43, "flags": 1},
{"matrix": [3, 2], "x": 17, "y": 43, "flags": 4},
{"matrix": [3, 3], "x": 34, "y": 41, "flags": 4},
{"matrix": [3, 4], "x": 52, "y": 39, "flags": 4},
{"matrix": [3, 5], "x": 69, "y": 41, "flags": 4},
{"matrix": [4, 4], "x": 86, "y": 43, "flags": 4},
{"matrix": [5, 0], "x": 86, "y": 53, "flags": 1},
{"matrix": [5, 1], "x": 96, "y": 58, "flags": 1},
{"matrix": [6, 0], "x": 138, "y": 10, "flags": 4},
{"matrix": [6, 1], "x": 155, "y": 10, "flags": 4},
{"matrix": [6, 2], "x": 172, "y": 8, "flags": 4},
{"matrix": [6, 3], "x": 190, "y": 5, "flags": 4},
{"matrix": [6, 4], "x": 207, "y": 8, "flags": 4},
{"matrix": [6, 5], "x": 224, "y": 10, "flags": 1},
{"matrix": [7, 0], "x": 138, "y": 21, "flags": 4},
{"matrix": [7, 1], "x": 155, "y": 21, "flags": 4},
{"matrix": [7, 2], "x": 172, "y": 19, "flags": 4},
{"matrix": [7, 3], "x": 190, "y": 17, "flags": 4},
{"matrix": [7, 4], "x": 207, "y": 19, "flags": 4},
{"matrix": [7, 5], "x": 224, "y": 21, "flags": 1},
{"matrix": [8, 0], "x": 138, "y": 32, "flags": 4},
{"matrix": [8, 1], "x": 155, "y": 32, "flags": 4},
{"matrix": [8, 2], "x": 172, "y": 30, "flags": 4},
{"matrix": [8, 3], "x": 190, "y": 28, "flags": 4},
{"matrix": [8, 4], "x": 207, "y": 30, "flags": 4},
{"matrix": [8, 5], "x": 224, "y": 32, "flags": 1},
{"matrix": [10, 2], "x": 138, "y": 43, "flags": 4},
{"matrix": [9, 1], "x": 155, "y": 43, "flags": 4},
{"matrix": [9, 2], "x": 172, "y": 41, "flags": 4},
{"matrix": [9, 3], "x": 190, "y": 39, "flags": 4},
{"matrix": [9, 4], "x": 207, "y": 41, "flags": 4},
{"matrix": [9, 5], "x": 224, "y": 43, "flags": 1},
{"matrix": [11, 5], "x": 128, "y": 58, "flags": 1},
{"matrix": [11, 6], "x": 138, "y": 53, "flags": 1}
]
},
"layout_aliases": {
"LAYOUT_voyager": "LAYOUT"
},
"layouts": {
"LAYOUT": {
"layout": [
{"label": "k00", "matrix": [0, 1], "x": 3, "y": 0},
{"label": "k01", "matrix": [0, 2], "x": 12, "y": 0},
{"label": "k02", "matrix": [0, 3], "x": 2, "y": 0.25},
{"label": "k03", "matrix": [0, 4], "x": 4, "y": 0.25},
{"label": "k04", "matrix": [0, 5], "x": 11, "y": 0.25},
{"label": "k05", "matrix": [0, 6], "x": 13, "y": 0.25},
{"label": "k26", "matrix": [6, 0], "x": 0, "y": 0.5},
{"label": "k27", "matrix": [6, 1], "x": 1, "y": 0.5},
{"label": "k28", "matrix": [6, 2], "x": 5, "y": 0.5},
{"label": "k29", "matrix": [6, 3], "x": 10, "y": 0.5},
{"label": "k30", "matrix": [6, 4], "x": 14, "y": 0.5},
{"label": "k31", "matrix": [6, 5], "x": 15, "y": 0.5},
{"label": "k06", "matrix": [1, 1], "x": 3, "y": 1},
{"label": "k07", "matrix": [1, 2], "x": 12, "y": 1},
{"label": "k08", "matrix": [1, 3], "x": 2, "y": 1.25},
{"label": "k09", "matrix": [1, 4], "x": 4, "y": 1.25},
{"label": "k10", "matrix": [1, 5], "x": 11, "y": 1.25},
{"label": "k11", "matrix": [1, 6], "x": 13, "y": 1.25},
{"label": "k32", "matrix": [7, 0], "x": 0, "y": 1.5},
{"label": "k33", "matrix": [7, 1], "x": 1, "y": 1.5},
{"label": "k34", "matrix": [7, 2], "x": 5, "y": 1.5},
{"label": "k35", "matrix": [7, 3], "x": 10, "y": 1.5},
{"label": "k36", "matrix": [7, 4], "x": 14, "y": 1.5},
{"label": "k37", "matrix": [7, 5], "x": 15, "y": 1.5},
{"label": "k12", "matrix": [2, 1], "x": 3, "y": 2},
{"label": "k13", "matrix": [2, 2], "x": 12, "y": 2},
{"label": "k14", "matrix": [2, 3], "x": 2, "y": 2.25},
{"label": "k15", "matrix": [2, 4], "x": 4, "y": 2.25},
{"label": "k16", "matrix": [2, 5], "x": 11, "y": 2.25},
{"label": "k17", "matrix": [2, 6], "x": 13, "y": 2.25},
{"label": "k38", "matrix": [8, 0], "x": 0, "y": 2.5},
{"label": "k39", "matrix": [8, 1], "x": 1, "y": 2.5},
{"label": "k40", "matrix": [8, 2], "x": 5, "y": 2.5},
{"label": "k41", "matrix": [8, 3], "x": 10, "y": 2.5},
{"label": "k42", "matrix": [8, 4], "x": 14, "y": 2.5},
{"label": "k43", "matrix": [8, 5], "x": 15, "y": 2.5},
{"label": "k18", "matrix": [3, 1], "x": 3, "y": 3},
{"label": "k19", "matrix": [3, 2], "x": 12, "y": 3},
{"label": "k20", "matrix": [3, 3], "x": 2, "y": 3.25},
{"label": "k21", "matrix": [3, 4], "x": 4, "y": 3.25},
{"label": "k22", "matrix": [3, 5], "x": 11, "y": 3.25},
{"label": "k23", "matrix": [4, 4], "x": 13, "y": 3.25},
{"label": "k44", "matrix": [10, 2], "x": 0, "y": 3.5},
{"label": "k45", "matrix": [9, 1], "x": 1, "y": 3.5},
{"label": "k46", "matrix": [9, 2], "x": 5, "y": 3.5},
{"label": "k47", "matrix": [9, 3], "x": 10, "y": 3.5},
{"label": "k48", "matrix": [9, 4], "x": 14, "y": 3.5},
{"label": "k49", "matrix": [9, 5], "x": 15, "y": 3.5},
{"label": "k24", "matrix": [5, 0], "x": 5, "y": 4.5},
{"label": "k25", "matrix": [5, 1], "x": 6, "y": 4.75},
{"label": "k50", "matrix": [11, 5], "x": 9, "y": 4.75},
{"label": "k51", "matrix": [11, 6], "x": 10, "y": 4.5}
]
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright 2023 ZSA Technology Labs, Inc <@zsa>
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT(
KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS,
CW_TOGG, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSLS,
SFT_T(KC_BSPC),KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, RSFT_T(KC_QUOT),
KC_LGUI, ALT_T(KC_Z),KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMMA,KC_DOT, RALT_T(KC_SLSH), KC_RCTL,
LT(1,KC_ENT), CTL_T(KC_TAB), SFT_T(KC_BSPC), LT(2,KC_SPC)
),
[1] = LAYOUT(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11,
KC_GRV, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, KC_7, KC_8, KC_9, KC_MINS, KC_SLSH, KC_F12,
_______, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_4, KC_5, KC_6, KC_PLUS, KC_ASTR, KC_BSPC,
_______, _______, KC_LBRC, KC_RBRC, KC_LCBR, KC_RCBR, KC_1, KC_2, KC_3, KC_DOT, KC_EQL, KC_ENT,
_______, _______, _______, KC_0
),
[2] = LAYOUT(
RGB_TOG, QK_KB, RGB_MOD, RGB_M_P, RGB_VAD, RGB_VAI, _______, _______, _______, _______, _______, QK_BOOT,
_______, _______, KC_VOLD, KC_VOLU, KC_MUTE, _______, KC_PGUP, KC_HOME, KC_UP, KC_END, _______, _______,
_______, KC_MPRV, KC_MNXT, KC_MSTP, KC_MPLY, _______, KC_PGDN, KC_LEFT, KC_DOWN, KC_RGHT, _______, _______,
_______, _______, _______, _______, _______, _______, _______, C(S(KC_TAB)), C(KC_TAB), _______, _______, _______,
_______, _______, _______, _______
),
};

View File

@ -0,0 +1,85 @@
/*
ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* STM32F303xC memory setup.
*/
MEMORY
{
flash0 (rx) : org = 0x08002000, len = 256k - 0x2000
flash1 (rx) : org = 0x00000000, len = 0
flash2 (rx) : org = 0x00000000, len = 0
flash3 (rx) : org = 0x00000000, len = 0
flash4 (rx) : org = 0x00000000, len = 0
flash5 (rx) : org = 0x00000000, len = 0
flash6 (rx) : org = 0x00000000, len = 0
flash7 (rx) : org = 0x00000000, len = 0
ram0 (wx) : org = 0x20000000, len = 40k
ram1 (wx) : org = 0x00000000, len = 0
ram2 (wx) : org = 0x00000000, len = 0
ram3 (wx) : org = 0x00000000, len = 0
ram4 (wx) : org = 0x10000000, len = 8k
ram5 (wx) : org = 0x00000000, len = 0
ram6 (wx) : org = 0x00000000, len = 0
ram7 (wx) : org = 0x00000000, len = 0
}
/* For each data/text section two region are defined, a virtual region
and a load region (_LMA suffix).*/
/* Flash region to be used for exception vectors.*/
REGION_ALIAS("VECTORS_FLASH", flash0);
REGION_ALIAS("VECTORS_FLASH_LMA", flash0);
/* Flash region to be used for constructors and destructors.*/
REGION_ALIAS("XTORS_FLASH", flash0);
REGION_ALIAS("XTORS_FLASH_LMA", flash0);
/* Flash region to be used for code text.*/
REGION_ALIAS("TEXT_FLASH", flash0);
REGION_ALIAS("TEXT_FLASH_LMA", flash0);
/* Flash region to be used for read only data.*/
REGION_ALIAS("RODATA_FLASH", flash0);
REGION_ALIAS("RODATA_FLASH_LMA", flash0);
/* Flash region to be used for various.*/
REGION_ALIAS("VARIOUS_FLASH", flash0);
REGION_ALIAS("VARIOUS_FLASH_LMA", flash0);
/* Flash region to be used for RAM(n) initialization data.*/
REGION_ALIAS("RAM_INIT_FLASH_LMA", flash0);
/* RAM region to be used for Main stack. This stack accommodates the processing
of all exceptions and interrupts.*/
REGION_ALIAS("MAIN_STACK_RAM", ram0);
/* RAM region to be used for the process stack. This is the stack used by
the main() function.*/
REGION_ALIAS("PROCESS_STACK_RAM", ram0);
/* RAM region to be used for data segment.*/
REGION_ALIAS("DATA_RAM", ram0);
REGION_ALIAS("DATA_RAM_LMA", flash0);
/* RAM region to be used for BSS segment.*/
REGION_ALIAS("BSS_RAM", ram0);
/* RAM region to be used for the default heap.*/
REGION_ALIAS("HEAP_RAM", ram0);
/* Generic rules inclusion.*/
INCLUDE rules.ld

View File

@ -0,0 +1,204 @@
// Copyright 2023 ZSA Technology Labs, Inc <@zsa>
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdint.h>
#include "voyager.h"
#include "mcp23018.h"
#pragma GCC push_options
#pragma GCC optimize("-O3")
extern matrix_row_t matrix[MATRIX_ROWS]; // debounced values
extern matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values
static matrix_row_t raw_matrix_right[MATRIX_COLS];
#define MCP_ROWS_PER_HAND (MATRIX_ROWS / 2)
#ifndef VOYAGER_I2C_TIMEOUT
# define VOYAGER_I2C_TIMEOUT 100
#endif
// Delay between each i2c io expander ops (in MCU cycles)
#ifndef IO_EXPANDER_OP_DELAY
# define IO_EXPANDER_OP_DELAY 500
#endif
extern bool mcp23018_leds[2];
extern bool is_launching;
static uint16_t mcp23018_reset_loop;
uint8_t mcp23018_errors;
bool io_expander_ready(void) {
uint8_t tx;
return mcp23018_readPins(MCP23018_DEFAULT_ADDRESS, mcp23018_PORTA, &tx);
}
void matrix_init_custom(void) {
// outputs
gpio_set_pin_output(B10);
gpio_set_pin_output(B11);
gpio_set_pin_output(B12);
gpio_set_pin_output(B13);
gpio_set_pin_output(B14);
gpio_set_pin_output(B15);
// inputs
gpio_set_pin_input_low(A0);
gpio_set_pin_input_low(A1);
gpio_set_pin_input_low(A2);
gpio_set_pin_input_low(A3);
gpio_set_pin_input_low(A6);
gpio_set_pin_input_low(A7);
gpio_set_pin_input_low(B0);
mcp23018_init(MCP23018_DEFAULT_ADDRESS);
mcp23018_errors += !mcp23018_set_config(MCP23018_DEFAULT_ADDRESS, mcp23018_PORTA, 0b00000000);
mcp23018_errors += !mcp23018_set_config(MCP23018_DEFAULT_ADDRESS, mcp23018_PORTB, 0b00111111);
if (!mcp23018_errors) {
is_launching = true;
}
}
bool matrix_scan_custom(matrix_row_t current_matrix[]) {
bool changed = false;
// Attempt to reset the mcp23018 if it's not initialized
if (mcp23018_errors) {
if (++mcp23018_reset_loop > 0x1FFF) {
if (io_expander_ready()) {
// If we managed to initialize the mcp23018 - we need to reinitialize the matrix / layer state. During an electric discharge the i2c peripherals might be in a weird state. Giving a delay and resetting the MCU allows to recover from this.
wait_ms(200);
mcu_reset();
}
}
}
// Scanning left and right side of the keyboard for key presses.
// Left side is scanned by reading the gpio pins directly, right side is scanned by reading the mcp23018 registers.
matrix_row_t data = 0;
for (uint8_t row = 0; row <= MCP_ROWS_PER_HAND; row++) {
// strobe row
switch (row) {
case 0:
gpio_write_pin_high(B10);
break;
case 1:
gpio_write_pin_high(B11);
break;
case 2:
gpio_write_pin_high(B12);
break;
case 3:
gpio_write_pin_high(B13);
break;
case 4:
gpio_write_pin_high(B14);
break;
case 5:
gpio_write_pin_high(B15);
break;
case 6:
break; // Left hand has 6 rows
}
// Selecting the row on the right side of the keyboard.
if (!mcp23018_errors) {
// select row
mcp23018_errors += !mcp23018_set_output(MCP23018_DEFAULT_ADDRESS, mcp23018_PORTA, 0b01111111 & ~(1 << (row)));
mcp23018_errors += !mcp23018_set_output(MCP23018_DEFAULT_ADDRESS, mcp23018_PORTB, ((uint8_t)!mcp23018_leds[1] << 6) | ((uint8_t)!mcp23018_leds[0] << 7));
}
// Reading the left side of the keyboard.
if (row < MCP_ROWS_PER_HAND) {
// i2c comm incur enough wait time
if (mcp23018_errors) {
// need wait to settle pin state
matrix_io_delay();
}
// read col data
data = ((readPin(A0) << 0) | (readPin(A1) << 1) | (readPin(A2) << 2) | (readPin(A3) << 3) | (readPin(A6) << 4) | (readPin(A7) << 5) | (readPin(B0) << 6));
// unstrobe row
switch (row) {
case 0:
gpio_write_pin_low(B10);
break;
case 1:
gpio_write_pin_low(B11);
break;
case 2:
gpio_write_pin_low(B12);
break;
case 3:
gpio_write_pin_low(B13);
break;
case 4:
gpio_write_pin_low(B14);
break;
case 5:
gpio_write_pin_low(B15);
break;
case 6:
break;
}
if (current_matrix[row] != data) {
current_matrix[row] = data;
changed = true;
}
}
// Reading the right side of the keyboard.
if (!mcp23018_errors) {
for (uint16_t i = 0; i < IO_EXPANDER_OP_DELAY; i++) {
__asm__("nop");
}
uint8_t rx;
mcp23018_errors += !mcp23018_readPins(MCP23018_DEFAULT_ADDRESS, mcp23018_PORTB, &rx);
data = ~(rx & 0b00111111);
for (uint16_t i = 0; i < IO_EXPANDER_OP_DELAY; i++) {
__asm__("nop");
}
} else {
data = 0;
}
if (raw_matrix_right[row] != data) {
raw_matrix_right[row] = data;
changed = true;
}
}
for (uint8_t row = 0; row < MCP_ROWS_PER_HAND; row++) {
current_matrix[11 - row] = 0;
for (uint8_t col = 0; col < MATRIX_COLS; col++) {
current_matrix[11 - row] |= ((raw_matrix_right[6 - col] & (1 << row) ? 1 : 0) << col);
}
}
return changed;
}
// DO NOT REMOVE
// Needed for proper wake/sleep
void matrix_power_up(void) {
bool temp_launching = is_launching;
matrix_init_custom();
is_launching = temp_launching;
if (!temp_launching) {
STATUS_LED_1(false);
STATUS_LED_2(false);
STATUS_LED_3(false);
STATUS_LED_4(false);
}
// initialize matrix state: all keys off
for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
matrix[i] = 0;
}
}
bool is_transport_connected(void) {
return (bool)(mcp23018_errors == 0);
}
#pragma GCC pop_options

View File

@ -0,0 +1,23 @@
/* Copyright 2021 QMK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include_next <mcuconf.h>
// for i2c expander, and ISSI
#undef STM32_I2C_USE_I2C1
#define STM32_I2C_USE_I2C1 TRUE

View File

@ -0,0 +1,40 @@
# Voyager
A next-gen split, ergonomic keyboard with an active left side, USB type C, and low profile switches.
* Keyboard Maintainer: [drashna](https://github.com/drashna), [ZSA](https://github.com/zsa/)
* Hardware Supported: Voyager (STM32F303xC)
* Hardware Availability: [ZSA Store](https://zsa.io/voyager/)
Make example for this keyboard (after setting up your build environment):
make zsa/voyager:default
Flashing example for this keyboard:
make zsa/voyager:default:flash
See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).
## Voyager Customization
### Indicator LEDs
There are 4 functions for enabling and disabling the LEDs on the top of the boards. The functions are `STATUS_LED_1(bool)` through `STATUS_LED_4(bool)`, with the first LED being the top most LED on the left hand, and the fourth LED being the bottom most LED on the right side.
By default, the Indicator LEDs are used to indicate the layer state for the keyboard. If you wish to change this (and indicate caps/num/scroll lock status instead), then define `VOYAGER_USER_LEDS` in your `config.h` file.
### Detecting split / Gaming mode
To make it extra gaming friendly, you can configure what happens when you disconnect the right half. This is especially useful when using gaming unfriendly layers or layouts (e.g. home row mods, dvorak, colemak).
Example for enabling a specific layer while right side is disconnected:
```c
void housekeeping_task_user(void) {
if (!is_transport_connected()) {
// set layer
}
}
```

View File

@ -0,0 +1,10 @@
MCU_LDSCRIPT = voyager
CUSTOM_MATRIX = lite
PROGRAM_CMD = $(call EXEC_DFU)
DFU_ARGS = -d 3297:0791 -a 0 -s 0x08002000:leave
DFU_SUFFIX_ARGS = -v 3297 -p 0791
VPATH += drivers/gpio
SRC += matrix.c mcp23018.c
I2C_DRIVER_REQUIRED = yes

View File

@ -0,0 +1,312 @@
// Copyright 2023 ZSA Technology Labs, Inc <@zsa>
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "voyager.h"
keyboard_config_t keyboard_config;
bool mcp23018_leds[2] = {0, 0};
bool is_launching = false;
#if defined(DEFERRED_EXEC_ENABLE)
# if defined(DYNAMIC_MACRO_ENABLE)
deferred_token dynamic_macro_token = INVALID_DEFERRED_TOKEN;
static uint32_t dynamic_macro_led(uint32_t trigger_time, void *cb_arg) {
static bool led_state = true;
if (!is_launching) {
led_state = !led_state;
STATUS_LED_3(led_state);
}
return 100;
}
void dynamic_macro_record_start_user(void) {
if (my_token == INVALID_DEFERRED_TOKEN) {
STATUS_LED_3(true);
dynamic_macro_token = defer_exec(100, dynamic_macro_led, NULL);
}
}
void dynamic_macro_record_end_user(int8_t direction) {
if (cancel_deferred_exec(dynamic_macro_token)) {
dynamic_macro_token = INVALID_DEFERRED_TOKEN;
STATUS_LED_3(false);
}
}
# endif
static uint32_t startup_exec(uint32_t trigger_time, void *cb_arg) {
static uint8_t startup_loop = 0;
switch (startup_loop++) {
case 0:
STATUS_LED_1(true);
STATUS_LED_2(false);
STATUS_LED_3(false);
STATUS_LED_4(false);
break;
case 1:
STATUS_LED_2(true);
break;
case 2:
STATUS_LED_3(true);
break;
case 3:
STATUS_LED_4(true);
break;
case 4:
STATUS_LED_1(false);
break;
case 5:
STATUS_LED_2(false);
break;
case 6:
STATUS_LED_3(false);
break;
case 7:
STATUS_LED_4(false);
break;
case 8:
is_launching = false;
layer_state_set_kb(layer_state);
return 0;
}
return 250;
}
#endif
void keyboard_pre_init_kb(void) {
// Initialize Reset pins
gpio_set_pin_input(A8);
gpio_set_pin_output(A9);
gpio_write_pin_low(A9);
gpio_set_pin_output(B5);
gpio_set_pin_output(B4);
gpio_set_pin_output(B3);
gpio_write_pin_low(B5);
gpio_write_pin_low(B4);
gpio_write_pin_low(B3);
keyboard_pre_init_user();
}
#if !defined(VOYAGER_USER_LEDS)
layer_state_t layer_state_set_kb(layer_state_t state) {
state = layer_state_set_user(state);
if (is_launching || !keyboard_config.led_level) return state;
uint8_t layer = get_highest_layer(state);
STATUS_LED_1(layer & (1 << 0));
STATUS_LED_2(layer & (1 << 1));
STATUS_LED_3(layer & (1 << 2));
# if !defined(CAPS_LOCK_STATUS)
STATUS_LED_4(layer & (1 << 3));
# endif
return state;
}
#endif
#ifdef RGB_MATRIX_ENABLE
// clang-format off
const is31_led PROGMEM g_is31_leds[RGB_MATRIX_LED_COUNT] = {
/* Refer to IS31 manual for these locations
* driver
* | R location
* | | G location
* | | | B location
* | | | | */
{0, C2_2, C1_2, C4_3},
{0, C2_3, C1_3, C3_3},
{0, C2_4, C1_4, C3_4},
{0, C2_5, C1_5, C3_5},
{0, C2_6, C1_6, C3_6},
{0, C2_7, C1_7, C3_7},
{0, C2_8, C1_8, C3_8},
{0, C8_1, C7_1, C9_1},
{0, C8_2, C7_2, C9_2},
{0, C8_3, C7_3, C9_3},
{0, C8_4, C7_4, C9_4},
{0, C8_5, C7_5, C9_5},
{0, C8_6, C7_6, C9_6},
{0, C2_10, C1_10, C4_11},
{0, C2_11, C1_11, C3_11},
{0, C2_12, C1_12, C3_12},
{0, C2_13, C1_13, C3_13},
{0, C2_14, C1_14, C3_14},
{0, C2_15, C1_15, C3_15},
{0, C2_16, C1_16, C3_16},
{0, C8_9, C7_9, C9_9},
{0, C8_10, C7_10, C9_10},
{0, C8_11, C7_11, C9_11},
{0, C8_12, C7_12, C9_12},
{0, C8_13, C7_13, C9_13},
{0, C8_14, C7_14, C9_14},
{1, C2_7, C1_7, C3_7},
{1, C2_6, C1_6, C3_6},
{1, C2_5, C1_5, C3_5},
{1, C2_4, C1_4, C3_4},
{1, C2_3, C1_3, C3_3},
{1, C2_2, C1_2, C4_3},
{1, C8_5, C7_5, C9_5},
{1, C8_4, C7_4, C9_4},
{1, C8_3, C7_3, C9_3},
{1, C8_2, C7_2, C9_2},
{1, C8_1, C7_1, C9_1},
{1, C2_8, C1_8, C3_8},
{1, C2_14, C1_14, C3_14},
{1, C2_13, C1_13, C3_13},
{1, C2_12, C1_12, C3_12},
{1, C2_11, C1_11, C3_11},
{1, C2_10, C1_10, C4_11},
{1, C8_6, C7_6, C9_6},
{1, C8_12, C7_12, C9_12},
{1, C8_11, C7_11, C9_11},
{1, C8_10, C7_10, C9_10},
{1, C8_9, C7_9, C9_9},
{1, C2_16, C1_16, C3_16},
{1, C2_15, C1_15, C3_15},
{1, C8_14, C7_14, C9_14},
{1, C8_13, C7_13, C9_13},
};
// clang-format on
#endif
#ifdef SWAP_HANDS_ENABLE
// swap-hands action needs a matrix to define the swap
// clang-format off
const keypos_t PROGMEM hand_swap_config[MATRIX_ROWS][MATRIX_COLS] = {
/* Left hand, matrix positions */
{{6,6}, {5,6}, {4,6}, {3,6}, {2,6}, {1,6},{0,6}},
{{6,7}, {5,7}, {4,7}, {3,7}, {2,7}, {1,7},{0,7}},
{{6,8}, {5,8}, {4,8}, {3,8}, {2,8}, {1,8},{0,8}},
{{6,9}, {5,9}, {4,9}, {3,9}, {2,9}, {1,9},{0,9}},
{{6,10},{5,10},{4,10},{3,10},{2,10},{1,10},{0,10}},
{{6,11},{5,11},{4,11},{3,11},{2,11},{1,11},{0,11}},
/* Right hand, matrix positions */
{{6,0}, {5,0}, {4,0}, {3,0}, {2,0}, {1,0},{0,0}},
{{6,1}, {5,1}, {4,1}, {3,1}, {2,1}, {1,1},{0,1}},
{{6,2}, {5,2}, {4,2}, {3,2}, {2,2}, {1,2},{0,2}},
{{6,3}, {5,3}, {4,3}, {3,3}, {2,3}, {1,3},{0,3}},
{{6,4}, {5,4}, {4,4}, {3,4}, {2,4}, {1,4},{0,4}},
{{6,5}, {5,5}, {4,5}, {3,5}, {2,5}, {1,5},{0,5}},
};
// clang-format on
#endif
#ifdef CAPS_LOCK_STATUS
bool led_update_kb(led_t led_state) {
bool res = led_update_user(led_state);
if (res) {
STATUS_LED_4(led_state.caps_lock);
}
return res;
}
#endif
bool process_record_kb(uint16_t keycode, keyrecord_t *record) {
if (!process_record_user(keycode, record)) {
return false;
}
switch (keycode) {
#if !defined(VOYAGER_USER_LEDS)
case LED_LEVEL:
if (record->event.pressed) {
keyboard_config.led_level ^= 1;
eeconfig_update_kb(keyboard_config.raw);
if (keyboard_config.led_level) {
layer_state_set_kb(layer_state);
} else {
STATUS_LED_1(false);
STATUS_LED_2(false);
STATUS_LED_3(false);
STATUS_LED_4(false);
}
}
break;
#endif
#ifdef RGB_MATRIX_ENABLE
case TOGGLE_LAYER_COLOR:
if (record->event.pressed) {
keyboard_config.disable_layer_led ^= 1;
if (keyboard_config.disable_layer_led) rgb_matrix_set_color_all(0, 0, 0);
}
break;
case RGB_TOG:
if (record->event.pressed) {
switch (rgb_matrix_get_flags()) {
case LED_FLAG_ALL: {
rgb_matrix_set_flags(LED_FLAG_NONE);
rgb_matrix_set_color_all(0, 0, 0);
} break;
default: {
rgb_matrix_set_flags(LED_FLAG_ALL);
} break;
}
}
return false;
#endif
}
return true;
}
void keyboard_post_init_kb(void) {
#ifdef RGB_MATRIX_ENABLE
rgb_matrix_enable_noeeprom();
#endif
keyboard_config.raw = eeconfig_read_kb();
if (!keyboard_config.led_level && !keyboard_config.led_level_res) {
keyboard_config.led_level = true;
keyboard_config.led_level_res = 0b11;
eeconfig_update_kb(keyboard_config.raw);
}
#if defined(DEFERRED_EXEC_ENABLE)
is_launching = true;
defer_exec(500, startup_exec, NULL);
#endif
keyboard_post_init_user();
}
void eeconfig_init_kb(void) { // EEPROM is getting reset!
keyboard_config.raw = 0;
keyboard_config.led_level = true;
keyboard_config.led_level_res = 0b11;
eeconfig_update_kb(keyboard_config.raw);
eeconfig_init_user();
}
__attribute__((weak)) void bootloader_jump(void) {
// The ignition bootloader is checking for a high signal on A8 for 100ms when powering on the board.
// Setting both A8 and A9 high will charge the capacitor quickly.
// Setting A9 low before reset will cause the capacitor to discharge
// thus making the bootloder unlikely to trigger twice between power cycles.
gpio_set_pin_output_push_pull(A9);
gpio_set_pin_output_push_pull(A8);
gpio_write_pin_high(A9);
gpio_write_pin_high(A8);
wait_ms(500);
gpio_write_pin_low(A9);
NVIC_SystemReset();
}
__attribute__((weak)) void mcu_reset(void) {
gpio_set_pin_output_push_pull(A9);
gpio_set_pin_output_push_pull(A8);
gpio_write_pin_low(A8);
gpio_write_pin_low(A9);
NVIC_SystemReset();
}

View File

@ -0,0 +1,35 @@
// Copyright 2023 ZSA Technology Labs, Inc <@zsa>
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "quantum.h"
extern bool mcp23018_leds[];
#define MCP23018_DEFAULT_ADDRESS 0b0100000
#define STATUS_LED_1(status) gpio_write_pin(B5, (bool)(status))
#define STATUS_LED_2(status) gpio_write_pin(B4, (bool)(status))
#define STATUS_LED_3(status) mcp23018_leds[0] = (bool)(status)
#define STATUS_LED_4(status) mcp23018_leds[1] = (bool)(status)
enum voyager_keycodes {
TOGGLE_LAYER_COLOR = QK_KB,
LED_LEVEL,
};
typedef union {
uint32_t raw;
struct {
bool disable_layer_led : 1;
bool placeholder : 1;
bool led_level : 1;
uint8_t led_level_res : 2; // DO NOT REMOVE
};
} keyboard_config_t;
extern keyboard_config_t keyboard_config;
bool is_transport_connected(void);