├── sample ├── rules.mk ├── config.h └── keymap.c └── README.md /sample/rules.mk: -------------------------------------------------------------------------------- 1 | # Analog Input 2 | JOYSTICK_ENABLE = yes 3 | -------------------------------------------------------------------------------- /sample/config.h: -------------------------------------------------------------------------------- 1 | // 1000hz Polling Rate 2 | #define USB_POLLING_INTERVAL_MS 1 3 | #define QMK_KEYS_PER_SCAN 12 4 | 5 | // Joystick Button Count 6 | #define JOYSTICK_BUTTON_COUNT 32 7 | 8 | // Joystick Axes Count 9 | #define JOYSTICK_AXIS_COUNT 6 10 | 11 | // Joystick Axes Resolution 12 | #define JOYSTICK_AXIS_RESOLUTION 8 13 | -------------------------------------------------------------------------------- /sample/keymap.c: -------------------------------------------------------------------------------- 1 | // Copyright 2022 @waffle87 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | #include QMK_KEYBOARD_H 4 | 5 | enum layers { 6 | _GAME1, 7 | _FN1, 8 | _GAME2, 9 | _FN2, 10 | _GAME3, 11 | _FN3 12 | }; 13 | 14 | enum custom_keycodes { 15 | GC_LSU = SAFE_RANGE, 16 | GC_LSD, 17 | GC_LSL, 18 | GC_LSR, 19 | GC_RSU, 20 | GC_RSD, 21 | GC_RSL, 22 | GC_RSR, 23 | GC_DPU, 24 | GC_DPD, 25 | GC_DPL, 26 | GC_DPR, 27 | GC_SQU, 28 | GC_CRO, 29 | GC_CIR, 30 | GC_TRI, 31 | GC_L1, 32 | GC_L2, 33 | GC_L3, 34 | GC_R1, 35 | GC_R2, 36 | GC_R3, 37 | GC_STA, 38 | GC_SEL, 39 | GC_HOM 40 | }; 41 | 42 | // Joystick Config 43 | joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = { 44 | JOYSTICK_AXIS_VIRTUAL, 45 | JOYSTICK_AXIS_VIRTUAL, 46 | JOYSTICK_AXIS_VIRTUAL, 47 | JOYSTICK_AXIS_VIRTUAL, 48 | JOYSTICK_AXIS_VIRTUAL, 49 | JOYSTICK_AXIS_VIRTUAL, 50 | }; 51 | 52 | #define GAME2 TG(_GAME2) 53 | #define GAME3 TG(_GAME3) 54 | #define FN1 MO(_FN1) 55 | #define FN2 MO(_FN2) 56 | #define FN3 MO(_FN3) 57 | 58 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { 59 | [_GAME1] = LAYOUT_ortho_5x12( 60 | GC_HOM, GC_SEL, GC_STA, XXXXXXX, XXXXXXX, GC_L3, GC_R3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 61 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, GC_SQU, GC_TRI, GC_R1, GC_L1, XXXXXXX, 62 | XXXXXXX, XXXXXXX, GC_DPL, GC_DPD, GC_DPR, XXXXXXX, XXXXXXX, GC_CRO, GC_CIR, GC_R2, GC_L2, XXXXXXX, 63 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 64 | XXXXXXX, FN1, XXXXXXX, XXXXXXX, XXXXXXX, GC_DPU, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX 65 | ), 66 | [_FN1] = LAYOUT_ortho_5x12( 67 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 68 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 69 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 70 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 71 | _______, _______, _______, _______, GAME2, _______, _______, GAME3, _______, _______, _______, _______ 72 | ), 73 | [_GAME2] = LAYOUT_ortho_5x12( 74 | GC_HOM, GC_SEL, GC_STA, XXXXXXX, XXXXXXX, GC_L3, GC_R3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 75 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, GC_SQU, GC_TRI, GC_R1, GC_L1, XXXXXXX, 76 | XXXXXXX, XXXXXXX, GC_LSL, GC_LSD, GC_LSR, XXXXXXX, XXXXXXX, GC_CRO, GC_CIR, GC_R2, GC_L2, XXXXXXX, 77 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 78 | XXXXXXX, FN2, XXXXXXX, XXXXXXX, XXXXXXX, GC_LSU, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX 79 | ), 80 | [_FN2] = LAYOUT_ortho_5x12( 81 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 82 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 83 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 84 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 85 | _______, _______, _______, _______, GAME2, _______, _______, _______, _______, _______, _______, _______ 86 | ), 87 | [_GAME3] = LAYOUT_ortho_5x12( 88 | GC_HOM, GC_SEL, GC_STA, XXXXXXX, XXXXXXX, GC_L3, GC_R3, GC_RSL, GC_RSD, GC_RSR, XXXXXXX, XXXXXXX, 89 | XXXXXXX, XXXXXXX, XXXXXXX, GC_LSU, XXXXXXX, XXXXXXX, XXXXXXX, GC_SQU, GC_TRI, GC_R1, GC_L1, XXXXXXX, 90 | XXXXXXX, XXXXXXX, GC_LSL, GC_LSD, GC_LSR, XXXXXXX, XXXXXXX, GC_CRO, GC_CIR, GC_R2, GC_L2, XXXXXXX, 91 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 92 | XXXXXXX, FN3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, GC_RSU, XXXXXXX, GC_DPL, GC_DPD, GC_DPU, GC_DPR 93 | ), 94 | [_FN3] = LAYOUT_ortho_5x12( 95 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 96 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 97 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 98 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 99 | _______, _______, _______, _______, _______, _______, _______, GAME3, _______, _______, _______, _______ 100 | ) 101 | }; 102 | 103 | bool LSU_STATE = false; 104 | bool LSD_STATE = false; 105 | bool LSL_STATE = false; 106 | bool LSR_STATE = false; 107 | bool RSU_STATE = false; 108 | bool RSD_STATE = false; 109 | bool RSL_STATE = false; 110 | bool RSR_STATE = false; 111 | bool DPU_STATE = false; 112 | bool DPD_STATE = false; 113 | bool DPL_STATE = false; 114 | bool DPR_STATE = false; 115 | 116 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 117 | switch (keycode) { 118 | case GC_LSU: 119 | if (record->event.pressed) { 120 | LSU_STATE = true; 121 | joystick_set_axis(1, -127); 122 | } else { 123 | LSU_STATE = false; 124 | if (LSD_STATE) { 125 | joystick_set_axis(1, 127); 126 | } else { 127 | joystick_set_axis(1, 0); 128 | } 129 | } 130 | return false; 131 | case GC_LSD: 132 | if (record->event.pressed) { 133 | LSD_STATE = true; 134 | if (!LSU_STATE) { 135 | joystick_set_axis(1, 127); 136 | } 137 | } else { 138 | LSD_STATE = false; 139 | if (!LSU_STATE) { 140 | joystick_set_axis(1, 0); 141 | } 142 | } 143 | return false; 144 | case GC_LSL: 145 | if (record->event.pressed) { 146 | LSL_STATE = true; 147 | joystick_set_axis(0, -127); 148 | } else { 149 | LSL_STATE = false; 150 | if (LSR_STATE) { 151 | joystick_set_axis(0, 127); 152 | } else { 153 | joystick_set_axis(0, 0); 154 | } 155 | } 156 | return false; 157 | case GC_LSR: 158 | if (record->event.pressed) { 159 | LSR_STATE = true; 160 | joystick_set_axis(0, 127); 161 | } else { 162 | LSR_STATE = false; 163 | if (LSL_STATE) { 164 | joystick_set_axis(0, -127); 165 | } else { 166 | joystick_set_axis(0, 0); 167 | } 168 | } 169 | return false; 170 | case GC_DPU: 171 | if (record->event.pressed) { 172 | DPU_STATE = true; 173 | if (DPD_STATE) { 174 | unregister_joystick_button(16); 175 | } 176 | register_joystick_button(18); 177 | } else { 178 | DPU_STATE = false; 179 | unregister_joystick_button(18); 180 | } 181 | return false; 182 | case GC_DPD: 183 | if (record->event.pressed) { 184 | DPD_STATE = true; 185 | if (!DPU_STATE) { 186 | register_joystick_button(16); 187 | } 188 | } else { 189 | DPD_STATE = false; 190 | unregister_joystick_button(16); 191 | } 192 | return false; 193 | case GC_DPL: 194 | if (record->event.pressed) { 195 | DPL_STATE = true; 196 | if (DPR_STATE) { 197 | unregister_joystick_button(17); 198 | } else { 199 | register_joystick_button(15); 200 | } 201 | } else { 202 | DPL_STATE = false; 203 | unregister_joystick_button(15); 204 | if (DPR_STATE) { 205 | register_joystick_button(17); 206 | } 207 | } 208 | return false; 209 | case GC_DPR: 210 | if (record->event.pressed) { 211 | DPR_STATE = true; 212 | if (DPL_STATE) { 213 | unregister_joystick_button(15); 214 | } else { 215 | register_joystick_button(17); 216 | } 217 | } else { 218 | DPR_STATE = false; 219 | unregister_joystick_button(17); 220 | if (DPL_STATE) { 221 | register_joystick_button(15); 222 | } 223 | } 224 | return false; 225 | case GC_RSU: 226 | if (record->event.pressed) { 227 | RSU_STATE = true; 228 | joystick_set_axis(4, -127); 229 | } else { 230 | RSU_STATE = false; 231 | if (RSD_STATE) { 232 | joystick_set_axis(4, 127); 233 | } else { 234 | joystick_set_axis(4, 0); 235 | } 236 | } 237 | return false; 238 | case GC_RSD: 239 | if (record->event.pressed) { 240 | RSD_STATE = true; 241 | if (!RSU_STATE) { 242 | joystick_set_axis(4, 127); 243 | } 244 | } else { 245 | RSD_STATE = false; 246 | if (RSU_STATE) { 247 | joystick_set_axis(4, -127); 248 | } else { 249 | joystick_set_axis(4, 0); 250 | } 251 | } 252 | return false; 253 | case GC_RSL: 254 | if (record->event.pressed) { 255 | RSL_STATE = true; 256 | joystick_set_axis(3, -127); 257 | } else { 258 | RSL_STATE = false; 259 | if (RSR_STATE) { 260 | joystick_set_axis(3, 127); 261 | } else { 262 | joystick_set_axis(3, 0); 263 | } 264 | } 265 | return false; 266 | case GC_RSR: 267 | if (record->event.pressed) { 268 | RSR_STATE = true; 269 | joystick_set_axis(3, 127); 270 | } else { 271 | RSR_STATE = false; 272 | if (RSL_STATE) { 273 | joystick_set_axis(3, -127); 274 | } else { 275 | joystick_set_axis(3, 0); 276 | } 277 | } 278 | return false; 279 | case GC_SQU: 280 | if (record->event.pressed) { 281 | register_joystick_button(0); 282 | } else { 283 | unregister_joystick_button(0); 284 | } 285 | return false; 286 | case GC_CRO: 287 | if (record->event.pressed) { 288 | register_joystick_button(1); 289 | } else { 290 | unregister_joystick_button(1); 291 | } 292 | return false; 293 | case GC_CIR: 294 | if (record->event.pressed) { 295 | register_joystick_button(2); 296 | } else { 297 | unregister_joystick_button(2); 298 | } 299 | return false; 300 | case GC_TRI: 301 | if (record->event.pressed) { 302 | register_joystick_button(3); 303 | } else { 304 | unregister_joystick_button(3); 305 | } 306 | return false; 307 | case GC_L1: 308 | if (record->event.pressed) { 309 | register_joystick_button(4); 310 | } else { 311 | unregister_joystick_button(4); 312 | } 313 | return false; 314 | case GC_R1: 315 | if (record->event.pressed) { 316 | register_joystick_button(5); 317 | } else { 318 | unregister_joystick_button(5); 319 | } 320 | return false; 321 | case GC_L2: 322 | if (record->event.pressed) { 323 | register_joystick_button(6); 324 | } else { 325 | unregister_joystick_button(6); 326 | } 327 | return false; 328 | case GC_R2: 329 | if (record->event.pressed) { 330 | register_joystick_button(7); 331 | } else { 332 | unregister_joystick_button(7); 333 | } 334 | return false; 335 | case GC_SEL: 336 | if (record->event.pressed) { 337 | register_joystick_button(8); 338 | } else { 339 | unregister_joystick_button(8); 340 | } 341 | return false; 342 | case GC_STA: 343 | if (record->event.pressed) { 344 | register_joystick_button(9); 345 | } else { 346 | unregister_joystick_button(9); 347 | } 348 | return false; 349 | case GC_L3: 350 | if (record->event.pressed) { 351 | register_joystick_button(10); 352 | } else { 353 | unregister_joystick_button(10); 354 | } 355 | return false; 356 | case GC_R3: 357 | if (record->event.pressed) { 358 | register_joystick_button(11); 359 | } else { 360 | unregister_joystick_button(11); 361 | } 362 | return false; 363 | case GC_HOM: 364 | if (record->event.pressed) { 365 | register_joystick_button(12); 366 | } else { 367 | unregister_joystick_button(12); 368 | } 369 | return false; 370 | default: 371 | return true; 372 | } 373 | }; 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QMK_HID_Gamepad_with_SOCD 2 | QMK Keymap(s) to emulate a Gamepad/All Button Controller with SOCD (Simultaneous Opposing Cardinal Directions) cleaning. For use with keyboards running QMK firmware. 3 | 4 | # Features 5 | - Effectively emulates an all-button controller. 6 | - SOCD Cleaning on DPAD and both Analog sticks. 7 | - Can customize button layout by editing keymap (matrix). 8 | - Ideal for fighting games and platform fighters. 9 | - Can utilize third party program (i.e. Steam or x360ce) to emulate Xbox, Playstation, Nintendo controllers. 10 | - 1000hz Polling Rate 11 | 12 | # Requirements 13 | - QMK enabled keyboard (V-USB devices not supported). i.e. OLKB planck/preonic, Keychron Q or V series keyboards, or Boardsource 4x12 or 5x12 14 | - Text Editor (Recommend Notepad++) 15 | - QMK MSYS 16 | - QMK Toolbox 17 | 18 | # Layouts 19 | 20 | **Note on Default SOCD behavior for ALL layouts:** 21 | - **DPAD Behavior** 22 | - Up + Down = Up 23 | - Left + Right = Neutral 24 | - **LS and RS behavior** 25 | - Up + Down = Up 26 | - Left + Right = Last Input Wins 27 | --- 28 | 29 | ![hitbox1](https://user-images.githubusercontent.com/99369506/194227453-bf0bbee1-3570-4bce-a103-d2de0db479dd.png) 30 | 31 | **Hitbox Layout 1:** Default Layout, ideal for fighting games and most styles. 32 | 33 | --- 34 | 35 | ![hitbox2](https://user-images.githubusercontent.com/99369506/194227479-c70b9ea1-d058-40e8-aba6-d5c53e39e7d2.png) 36 | 37 | **Hitbox Layout 2:** Same as default Hitbox, but SOCD is Last Input Wins. Ideal for fast as possible charge moves. Refer to TO for tournament legality. 38 | 39 | --- 40 | 41 | ![layout3](https://user-images.githubusercontent.com/99369506/193212578-a2e78a99-dd1c-422f-873a-54da1e7f4b7b.png) 42 | 43 | **Gamepad Layout 3:** Ideal layout for platform fighters (i.e. Multiversus, Nickelodeon ASB, Rivals of Aether). 44 | 45 | --- 46 | 47 | # Guide (WIP) 48 | 49 | ### 1. Setup the QMK MSYS environment: 50 | - Download [QMK MSYS](https://github.com/qmk/qmk_distro_msys/releases/latest). 51 | - Install and run QMK MSYS 52 | 53 | ![Screenshot 2022-09-30 200352](https://user-images.githubusercontent.com/99369506/193384158-07bf864e-7e77-4499-a7d9-8ee47a9849af.png) 54 | - Input `qmk setup` in the terminal window. This will take a few minutes. 55 | 56 | ### 2. Backup your keyboard's original firmware: 57 | - Compile default keymap firmware. 58 | - The default keymap is generated after setting up the environment. So I can compile the default firmware in QMK MSYS as follows: (Boardsource 5x12 keyboard as example) 59 | ``` 60 | qmk compile -kb boardsource/5x12 -km default 61 | ``` 62 | - By default keyboards are located in "C:\Users\\{Username}\qmk_firmware\keyboards\" after you've setup the environment. If your keyboard is not within the environment, please contact manufacturer for the QMK files to import to your environment. 63 | 64 | - After compiling, the firmware is saved in the qmk_firmware directory as a .hex file. i.e. "C:\Users\\{Username}\qmk_firmware\boardsource_5x12_crossup.hex" 65 | 66 | - If you own one keyboard and want to default to it: (Boardsource 5x12 as example) 67 | ``` 68 | qmk config user.keyboard=boardsource/5x12 69 | ``` 70 | ### 3. Create custom keymap: 71 | 72 | - Create a copy of default keymap 73 | ``` 74 | qmk new-keymap 75 | ``` 76 | - It will prompt for keymap name of your choosing. For example, "crossup" will be used. 77 | After inputting keymap name, it will output location of the new keymap. This is the location of the keymap you will be editing. 78 | 79 | ### 4. Enable keyboard joystick 80 | - Navigate to your keyboard's folder i.e. "C:\Users\\{Username}\qmk_firmware\keyboards\boardsource\5x12\" 81 | - Open the rules.mk file with your text editor. 82 | - Add following and save. 83 |
Analog Input 84 | 85 | ```js 86 | # Enable Joystick 87 | JOYSTICK_ENABLE = yes 88 | 89 | JOYSTICK_DRIVER = digital 90 | ``` 91 |
92 | 93 | ### 5. Create config.h 94 | - Navigate to your custom keymap location i.e. "C:\Users\\{Username}\qmk_firmware\keyboards\boardsource\5x12\keymaps\crossup" 95 | - Create a new file "config.h" 96 | - Open config.h in text editor and input the following. (Enables 1000hz polling rate and sets joystick/button parameters.) 97 |
config.h 98 | 99 | ```js 100 | // 1000hz Polling Rate 101 | #define USB_POLLING_INTERVAL_MS 1 102 | #define QMK_KEYS_PER_SCAN 12 103 | 104 | // Joystick Button Count 105 | #define JOYSTICK_BUTTON_COUNT 32 106 | 107 | // Joystick Axes Count 108 | #define JOYSTICK_AXIS_COUNT 6 109 | 110 | // Joystick Axes Resolution 111 | #define JOYSTICK_AXIS_RESOLUTION 8 112 | ``` 113 |
114 | 115 | ### 6. Edit custom keymap 116 | - Open keymap.c in text editor. 117 | - Declare: Create each layer as an entry in an enum. Input the following. 118 |
enum layers 119 | 120 | ```js 121 | enum layers { 122 | _GAME1, 123 | _FN1, 124 | _GAME2, 125 | _FN2, 126 | _GAME3, 127 | _FN3 128 | }; 129 | ``` 130 |
131 | 132 | - Declare: Create each custom keycode as an entry in an enum; add joystick config; and define key switch keys. Input the following directly below/after layer enum. 133 |
Custom Keycodes 134 | 135 | ```js 136 | enum custom_keycodes { 137 | GC_LSU = SAFE_RANGE, 138 | GC_LSD, 139 | GC_LSL, 140 | GC_LSR, 141 | GC_RSU, 142 | GC_RSD, 143 | GC_RSL, 144 | GC_RSR, 145 | GC_DPU, 146 | GC_DPD, 147 | GC_DPL, 148 | GC_DPR, 149 | GC_SQU, 150 | GC_CRO, 151 | GC_CIR, 152 | GC_TRI, 153 | GC_L1, 154 | GC_L2, 155 | GC_L3, 156 | GC_R1, 157 | GC_R2, 158 | GC_R3, 159 | GC_STA, 160 | GC_SEL, 161 | GC_HOM 162 | }; 163 | 164 | // Joystick Config 165 | joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = { 166 | JOYSTICK_AXIS_VIRTUAL, 167 | JOYSTICK_AXIS_VIRTUAL, 168 | JOYSTICK_AXIS_VIRTUAL, 169 | JOYSTICK_AXIS_VIRTUAL, 170 | JOYSTICK_AXIS_VIRTUAL, 171 | JOYSTICK_AXIS_VIRTUAL, 172 | }; 173 | 174 | #define GAME2 TG(_GAME2) 175 | #define GAME3 TG(_GAME3) 176 | #define FN1 MO(_FN1) 177 | #define FN2 MO(_FN2) 178 | #define FN3 MO(_FN3) 179 | ``` 180 |
181 | 182 | - **Define:** Add the keycodes for each layer into the keymaps array(s). 183 | - All keyboard matrices are different. You can customise these layers to your liking. 184 | 185 | Example layer matrices for Boardsource 5x12: 186 |
Layer Matrices 187 | 188 | ```js 189 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { 190 | [_GAME1] = LAYOUT_ortho_5x12( 191 | GC_HOM, GC_SEL, GC_STA, XXXXXXX, XXXXXXX, GC_L3, GC_R3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 192 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, GC_SQU, GC_TRI, GC_R1, GC_L1, XXXXXXX, 193 | XXXXXXX, XXXXXXX, GC_DPL, GC_DPD, GC_DPR, XXXXXXX, XXXXXXX, GC_CRO, GC_CIR, GC_R2, GC_L2, XXXXXXX, 194 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 195 | XXXXXXX, FN1, XXXXXXX, XXXXXXX, XXXXXXX, GC_DPU, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX 196 | ), 197 | [_FN1] = LAYOUT_ortho_5x12( 198 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 199 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 200 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 201 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 202 | _______, _______, _______, _______, GAME2, _______, _______, GAME3, _______, _______, _______, _______ 203 | ), 204 | [_GAME2] = LAYOUT_ortho_5x12( 205 | GC_HOM, GC_SEL, GC_STA, XXXXXXX, XXXXXXX, GC_L3, GC_R3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 206 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, GC_SQU, GC_TRI, GC_R1, GC_L1, XXXXXXX, 207 | XXXXXXX, XXXXXXX, GC_LSL, GC_LSD, GC_LSR, XXXXXXX, XXXXXXX, GC_CRO, GC_CIR, GC_R2, GC_L2, XXXXXXX, 208 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 209 | XXXXXXX, FN2, XXXXXXX, XXXXXXX, XXXXXXX, GC_LSU, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX 210 | ), 211 | [_FN2] = LAYOUT_ortho_5x12( 212 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 213 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 214 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 215 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 216 | _______, _______, _______, _______, GAME2, _______, _______, _______, _______, _______, _______, _______ 217 | ), 218 | [_GAME3] = LAYOUT_ortho_5x12( 219 | GC_HOM, GC_SEL, GC_STA, XXXXXXX, XXXXXXX, GC_L3, GC_R3, GC_RSL, GC_RSD, GC_RSR, XXXXXXX, XXXXXXX, 220 | XXXXXXX, XXXXXXX, XXXXXXX, GC_LSU, XXXXXXX, XXXXXXX, XXXXXXX, GC_SQU, GC_TRI, GC_R1, GC_L1, XXXXXXX, 221 | XXXXXXX, XXXXXXX, GC_LSL, GC_LSD, GC_LSR, XXXXXXX, XXXXXXX, GC_CRO, GC_CIR, GC_R2, GC_L2, XXXXXXX, 222 | XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, 223 | XXXXXXX, FN3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, GC_RSU, XXXXXXX, GC_DPL, GC_DPD, GC_DPU, GC_DPR 224 | ), 225 | [_FN3] = LAYOUT_ortho_5x12( 226 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 227 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 228 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 229 | _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, 230 | _______, _______, _______, _______, _______, _______, _______, GAME3, _______, _______, _______, _______ 231 | ) 232 | }; 233 | ``` 234 |
235 | 236 | - **Add custom keycode and SOCD code:** Add the following below/after the layer matrices. 237 |
SOCD Code 238 | 239 | ```js 240 | bool LSU_STATE = false; 241 | bool LSD_STATE = false; 242 | bool LSL_STATE = false; 243 | bool LSR_STATE = false; 244 | bool RSU_STATE = false; 245 | bool RSD_STATE = false; 246 | bool RSL_STATE = false; 247 | bool RSR_STATE = false; 248 | bool DPU_STATE = false; 249 | bool DPD_STATE = false; 250 | bool DPL_STATE = false; 251 | bool DPR_STATE = false; 252 | 253 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 254 | switch (keycode) { 255 | case GC_LSU: 256 | if (record->event.pressed) { 257 | LSU_STATE = true; 258 | joystick_set_axis(1, -127); 259 | } else { 260 | LSU_STATE = false; 261 | if (LSD_STATE) { 262 | joystick_set_axis(1, 127); 263 | } else { 264 | joystick_set_axis(1, 0); 265 | } 266 | } 267 | return false; 268 | case GC_LSD: 269 | if (record->event.pressed) { 270 | LSD_STATE = true; 271 | if (!LSU_STATE) { 272 | joystick_set_axis(1, 127); 273 | } 274 | } else { 275 | LSD_STATE = false; 276 | if (!LSU_STATE) { 277 | joystick_set_axis(1, 0); 278 | } 279 | } 280 | return false; 281 | case GC_LSL: 282 | if (record->event.pressed) { 283 | LSL_STATE = true; 284 | joystick_set_axis(0, -127); 285 | } else { 286 | LSL_STATE = false; 287 | if (LSR_STATE) { 288 | joystick_set_axis(0, 127); 289 | } else { 290 | joystick_set_axis(0, 0); 291 | } 292 | } 293 | return false; 294 | case GC_LSR: 295 | if (record->event.pressed) { 296 | LSR_STATE = true; 297 | joystick_set_axis(0, 127); 298 | } else { 299 | LSR_STATE = false; 300 | if (LSL_STATE) { 301 | joystick_set_axis(0, -127); 302 | } else { 303 | joystick_set_axis(0, 0); 304 | } 305 | } 306 | return false; 307 | case GC_DPU: 308 | if (record->event.pressed) { 309 | DPU_STATE = true; 310 | if (DPD_STATE) { 311 | unregister_joystick_button(16); 312 | } 313 | register_joystick_button(18); 314 | } else { 315 | DPU_STATE = false; 316 | unregister_joystick_button(18); 317 | } 318 | return false; 319 | case GC_DPD: 320 | if (record->event.pressed) { 321 | DPD_STATE = true; 322 | if (!DPU_STATE) { 323 | register_joystick_button(16); 324 | } 325 | } else { 326 | DPD_STATE = false; 327 | unregister_joystick_button(16); 328 | } 329 | return false; 330 | case GC_DPL: 331 | if (record->event.pressed) { 332 | DPL_STATE = true; 333 | if (DPR_STATE) { 334 | unregister_joystick_button(17); 335 | } else { 336 | register_joystick_button(15); 337 | } 338 | } else { 339 | DPL_STATE = false; 340 | unregister_joystick_button(15); 341 | if (DPR_STATE) { 342 | register_joystick_button(17); 343 | } 344 | } 345 | return false; 346 | case GC_DPR: 347 | if (record->event.pressed) { 348 | DPR_STATE = true; 349 | if (DPL_STATE) { 350 | unregister_joystick_button(15); 351 | } else { 352 | register_joystick_button(17); 353 | } 354 | } else { 355 | DPR_STATE = false; 356 | unregister_joystick_button(17); 357 | if (DPL_STATE) { 358 | register_joystick_button(15); 359 | } 360 | } 361 | return false; 362 | case GC_RSU: 363 | if (record->event.pressed) { 364 | RSU_STATE = true; 365 | joystick_set_axis(4, -127); 366 | } else { 367 | RSU_STATE = false; 368 | if (RSD_STATE) { 369 | joystick_set_axis(4, 127); 370 | } else { 371 | joystick_set_axis(4, 0); 372 | } 373 | } 374 | return false; 375 | case GC_RSD: 376 | if (record->event.pressed) { 377 | RSD_STATE = true; 378 | if (!RSU_STATE) { 379 | joystick_set_axis(4, 127); 380 | } 381 | } else { 382 | RSD_STATE = false; 383 | if (RSU_STATE) { 384 | joystick_set_axis(4, -127); 385 | } else { 386 | joystick_set_axis(4, 0); 387 | } 388 | } 389 | return false; 390 | case GC_RSL: 391 | if (record->event.pressed) { 392 | RSL_STATE = true; 393 | joystick_set_axis(3, -127); 394 | } else { 395 | RSL_STATE = false; 396 | if (RSR_STATE) { 397 | joystick_set_axis(3, 127); 398 | } else { 399 | joystick_set_axis(3, 0); 400 | } 401 | } 402 | return false; 403 | case GC_RSR: 404 | if (record->event.pressed) { 405 | RSR_STATE = true; 406 | joystick_set_axis(3, 127); 407 | } else { 408 | RSR_STATE = false; 409 | if (RSL_STATE) { 410 | joystick_set_axis(3, -127); 411 | } else { 412 | joystick_set_axis(3, 0); 413 | } 414 | } 415 | return false; 416 | case GC_SQU: 417 | if (record->event.pressed) { 418 | register_joystick_button(0); 419 | } else { 420 | unregister_joystick_button(0); 421 | } 422 | return false; 423 | case GC_CRO: 424 | if (record->event.pressed) { 425 | register_joystick_button(1); 426 | } else { 427 | unregister_joystick_button(1); 428 | } 429 | return false; 430 | case GC_CIR: 431 | if (record->event.pressed) { 432 | register_joystick_button(2); 433 | } else { 434 | unregister_joystick_button(2); 435 | } 436 | return false; 437 | case GC_TRI: 438 | if (record->event.pressed) { 439 | register_joystick_button(3); 440 | } else { 441 | unregister_joystick_button(3); 442 | } 443 | return false; 444 | case GC_L1: 445 | if (record->event.pressed) { 446 | register_joystick_button(4); 447 | } else { 448 | unregister_joystick_button(4); 449 | } 450 | return false; 451 | case GC_R1: 452 | if (record->event.pressed) { 453 | register_joystick_button(5); 454 | } else { 455 | unregister_joystick_button(5); 456 | } 457 | return false; 458 | case GC_L2: 459 | if (record->event.pressed) { 460 | register_joystick_button(6); 461 | } else { 462 | unregister_joystick_button(6); 463 | } 464 | return false; 465 | case GC_R2: 466 | if (record->event.pressed) { 467 | register_joystick_button(7); 468 | } else { 469 | unregister_joystick_button(7); 470 | } 471 | return false; 472 | case GC_SEL: 473 | if (record->event.pressed) { 474 | register_joystick_button(8); 475 | } else { 476 | unregister_joystick_button(8); 477 | } 478 | return false; 479 | case GC_STA: 480 | if (record->event.pressed) { 481 | register_joystick_button(9); 482 | } else { 483 | unregister_joystick_button(9); 484 | } 485 | return false; 486 | case GC_L3: 487 | if (record->event.pressed) { 488 | register_joystick_button(10); 489 | } else { 490 | unregister_joystick_button(10); 491 | } 492 | return false; 493 | case GC_R3: 494 | if (record->event.pressed) { 495 | register_joystick_button(11); 496 | } else { 497 | unregister_joystick_button(11); 498 | } 499 | return false; 500 | case GC_HOM: 501 | if (record->event.pressed) { 502 | register_joystick_button(12); 503 | } else { 504 | unregister_joystick_button(12); 505 | } 506 | return false; 507 | default: 508 | return true; 509 | } 510 | }; 511 | ``` 512 |
513 | 514 |
SOCD Code (UP+DOWN=NEUTRAL) 515 | 516 | ```js 517 | bool LSU_STATE = false; 518 | bool LSD_STATE = false; 519 | bool LSL_STATE = false; 520 | bool LSR_STATE = false; 521 | bool RSU_STATE = false; 522 | bool RSD_STATE = false; 523 | bool RSL_STATE = false; 524 | bool RSR_STATE = false; 525 | bool DPU_STATE = false; 526 | bool DPD_STATE = false; 527 | bool DPL_STATE = false; 528 | bool DPR_STATE = false; 529 | 530 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { 531 | switch (keycode) { 532 | case GC_LSU: 533 | if (record->event.pressed) { 534 | LSU_STATE = true; 535 | if (LSD_STATE) { 536 | joystick_set_axis(1, 0); 537 | } else { 538 | joystick_set_axis(1, -127); 539 | } 540 | } else { 541 | LSU_STATE = false; 542 | if (LSD_STATE) { 543 | joystick_set_axis(1, 127); 544 | } else { 545 | joystick_set_axis(1, 0); 546 | } 547 | } 548 | return false; 549 | case GC_LSD: 550 | if (record->event.pressed) { 551 | LSD_STATE = true; 552 | if (LSU_STATE) { 553 | joystick_set_axis(1, 0); 554 | } else { 555 | joystick_set_axis(1, 127); 556 | } 557 | } else { 558 | LSD_STATE = false; 559 | if (LSU_STATE) { 560 | joystick_set_axis(1, -127); 561 | } else { 562 | joystick_set_axis(1, 0); 563 | } 564 | } 565 | return false; 566 | case GC_LSL: 567 | if (record->event.pressed) { 568 | LSL_STATE = true; 569 | if (LSR_STATE) { 570 | joystick_set_axis(0, 0); 571 | } else { 572 | joystick_set_axis(0, -127); 573 | } 574 | } else { 575 | LSL_STATE = false; 576 | if (LSR_STATE) { 577 | joystick_set_axis(0, 127); 578 | } else { 579 | joystick_set_axis(0, 0); 580 | } 581 | } 582 | return false; 583 | case GC_LSR: 584 | if (record->event.pressed) { 585 | LSR_STATE = true; 586 | if (LSL_STATE) { 587 | joystick_set_axis(0, 0); 588 | } else { 589 | joystick_set_axis(0, 127); 590 | } 591 | } else { 592 | LSR_STATE = false; 593 | if (LSL_STATE) { 594 | joystick_set_axis(0, -127); 595 | } else { 596 | joystick_set_axis(0, 0); 597 | } 598 | } 599 | return false; 600 | case GC_DPU: 601 | if (record->event.pressed) { 602 | DPU_STATE = true; 603 | if (DPD_STATE) { 604 | unregister_joystick_button(16); 605 | } else { 606 | register_joystick_button(18); 607 | } 608 | } else { 609 | DPU_STATE = false; 610 | unregister_joystick_button(18); 611 | if (DPD_STATE) { 612 | register_joystick_button(16); 613 | } 614 | } 615 | return false; 616 | case GC_DPD: 617 | if (record->event.pressed) { 618 | DPD_STATE = true; 619 | if (DPU_STATE) { 620 | unregister_joystick_button(18); 621 | } else { 622 | register_joystick_button(16); 623 | } 624 | } else { 625 | DPD_STATE = false; 626 | unregister_joystick_button(16); 627 | if (DPU_STATE) { 628 | register_joystick_button(18); 629 | } 630 | } 631 | return false; 632 | case GC_DPL: 633 | if (record->event.pressed) { 634 | DPL_STATE = true; 635 | if (DPR_STATE) { 636 | unregister_joystick_button(17); 637 | } else { 638 | register_joystick_button(15); 639 | } 640 | } else { 641 | DPL_STATE = false; 642 | unregister_joystick_button(15); 643 | if (DPR_STATE) { 644 | register_joystick_button(17); 645 | } 646 | } 647 | return false; 648 | case GC_DPR: 649 | if (record->event.pressed) { 650 | DPR_STATE = true; 651 | if (DPL_STATE) { 652 | unregister_joystick_button(15); 653 | } else { 654 | register_joystick_button(17); 655 | } 656 | } else { 657 | DPR_STATE = false; 658 | unregister_joystick_button(17); 659 | if (DPL_STATE) { 660 | register_joystick_button(15); 661 | } 662 | } 663 | return false; 664 | case GC_RSU: 665 | if (record->event.pressed) { 666 | RSU_STATE = true; 667 | joystick_set_axis(4, -127); 668 | } else { 669 | RSU_STATE = false; 670 | if (RSD_STATE) { 671 | joystick_set_axis(4, 127); 672 | } else { 673 | joystick_set_axis(4, 0); 674 | } 675 | } 676 | return false; 677 | case GC_RSD: 678 | if (record->event.pressed) { 679 | RSD_STATE = true; 680 | if (!RSU_STATE) { 681 | joystick_set_axis(4, 127); 682 | } 683 | } else { 684 | RSD_STATE = false; 685 | if (RSU_STATE) { 686 | joystick_set_axis(4, -127); 687 | } else { 688 | joystick_set_axis(4, 0); 689 | } 690 | } 691 | return false; 692 | case GC_RSL: 693 | if (record->event.pressed) { 694 | RSL_STATE = true; 695 | joystick_set_axis(3, -127); 696 | } else { 697 | RSL_STATE = false; 698 | if (RSR_STATE) { 699 | joystick_set_axis(3, 127); 700 | } else { 701 | joystick_set_axis(3, 0); 702 | } 703 | } 704 | return false; 705 | case GC_RSR: 706 | if (record->event.pressed) { 707 | RSR_STATE = true; 708 | joystick_set_axis(3, 127); 709 | } else { 710 | RSR_STATE = false; 711 | if (RSL_STATE) { 712 | joystick_set_axis(3, -127); 713 | } else { 714 | joystick_set_axis(3, 0); 715 | } 716 | } 717 | return false; 718 | case GC_SQU: 719 | if (record->event.pressed) { 720 | register_joystick_button(0); 721 | } else { 722 | unregister_joystick_button(0); 723 | } 724 | return false; 725 | case GC_CRO: 726 | if (record->event.pressed) { 727 | register_joystick_button(1); 728 | } else { 729 | unregister_joystick_button(1); 730 | } 731 | return false; 732 | case GC_CIR: 733 | if (record->event.pressed) { 734 | register_joystick_button(2); 735 | } else { 736 | unregister_joystick_button(2); 737 | } 738 | return false; 739 | case GC_TRI: 740 | if (record->event.pressed) { 741 | register_joystick_button(3); 742 | } else { 743 | unregister_joystick_button(3); 744 | } 745 | return false; 746 | case GC_L1: 747 | if (record->event.pressed) { 748 | register_joystick_button(4); 749 | } else { 750 | unregister_joystick_button(4); 751 | } 752 | return false; 753 | case GC_R1: 754 | if (record->event.pressed) { 755 | register_joystick_button(5); 756 | } else { 757 | unregister_joystick_button(5); 758 | } 759 | return false; 760 | case GC_L2: 761 | if (record->event.pressed) { 762 | register_joystick_button(6); 763 | } else { 764 | unregister_joystick_button(6); 765 | } 766 | return false; 767 | case GC_R2: 768 | if (record->event.pressed) { 769 | register_joystick_button(7); 770 | } else { 771 | unregister_joystick_button(7); 772 | } 773 | return false; 774 | case GC_SEL: 775 | if (record->event.pressed) { 776 | register_joystick_button(8); 777 | } else { 778 | unregister_joystick_button(8); 779 | } 780 | return false; 781 | case GC_STA: 782 | if (record->event.pressed) { 783 | register_joystick_button(9); 784 | } else { 785 | unregister_joystick_button(9); 786 | } 787 | return false; 788 | case GC_L3: 789 | if (record->event.pressed) { 790 | register_joystick_button(10); 791 | } else { 792 | unregister_joystick_button(10); 793 | } 794 | return false; 795 | case GC_R3: 796 | if (record->event.pressed) { 797 | register_joystick_button(11); 798 | } else { 799 | unregister_joystick_button(11); 800 | } 801 | return false; 802 | case GC_HOM: 803 | if (record->event.pressed) { 804 | register_joystick_button(12); 805 | } else { 806 | unregister_joystick_button(12); 807 | } 808 | return false; 809 | default: 810 | return true; 811 | } 812 | }; 813 | ``` 814 |
815 | 816 | - Save your keymap.c file. 817 | - See attached sample keymap.c file to cross reference. 818 | 819 | ### 7. Compile custom firmware: 820 | - In QMK MSYS input the following: (Will take a few minutes and will check for errors) 821 | ``` 822 | qmk compile -km crossup 823 | ``` 824 | - Upon successful compile, it should output firmware file *crossup.hex located in your keyboard folder i.e. "C:\Users\\{Username}\qmk_firmware\boardsource_5x12_crossup.hex" 825 | 826 | ### 8. Flash keyboard with custom firmware: 827 | - Download and run [QMK Toolbox](https://github.com/qmk/qmk_toolbox/releases) 828 | - Click "Open" and select your custom firmware i.e. "C:\Users\\{Username}\qmk_firmware\boardsource_5x12_crossup.hex" 829 | - Reset your keyboard. Refer to manufacturer where reset button is located. 830 | - Click Flash. Will see keyboard disconnect from QMK Toolbox upon successful flash. 831 | 832 | # Congratulations 833 | - You've successfully flashed custom firmware onto your QMK keyboard to use as an HID Gamepad with SOCD. 834 | 835 | # Usage 836 | - To switch layers/layouts: 837 | - Hold the FN Key and Press GAME2 or GAME3 key toggle the second or third layout. 838 | - To return to default layer: 839 | - If on second layout: Hold FN Key and Press GAME2 to switch back to default layout. 840 | - If on third layout: Hold FN Key and Press GAME3 to switch back to default layout. 841 | 842 | # Optional 843 | Can use Steam to use keyboard/gamepad on Steam and Steam Deck (setup requires Desktop Mode once). 844 | - Instructions: 845 | - Open Steam (If on Steam Deck, switch to Desktop Mode and open the application) 846 | - Go to View > Settings 847 | - In the Controller Section, click on General Controller settings![settings](https://user-images.githubusercontent.com/99369506/193379186-0214adf6-8431-4932-aa46-32376e7ed754.png) 848 | - On this window, make sure at least General Gamepad Configuration Support and Xbox Configuration Support. On first setup, controller might be named after keyboard i.e. "5x12"![controller](https://user-images.githubusercontent.com/99369506/193379517-24dbeb58-efdc-437a-b196-35d88e5b2d85.png) 849 | - When ready, click on Define Layout and start mapping your keyboard keys to the on screen controller. Click Save when done. 850 | 851 | - That should be it. If done on the Steam deck in Desktop mode, the next time you turn on the Steam Deck in Game Mode, your keyboard should act as an xinput controller. 852 | 853 | # Tournament Legality 854 | - All of this is done hardware side, but still requires setup with Steam to get controller to work. Does not work natively with consoles. 855 | - Depending on game, Last Input Wins is not allowed (Street Fighter bans LIW, while Smash often allows it). Please refer to your TO and tournament rules. 856 | - I'm only providing base code, but ultimately the user controls the firmware. Macros can be programmed into custom firmware. 857 | 858 | # References/Resources 859 | - [QMK Documentation](https://docs.qmk.fm/) 860 | - [QMK Discord](https://discord.gg/fBGYurv) 861 | - [r/OLKB](https://www.reddit.com/r/olkb/) 862 | - [r/fightsticks](https://www.reddit.com/r/fightsticks/) 863 | - [Hitbox Cross|Up](https://www.hitboxarcade.com/products/cross-up) 864 | - [What is SOCD?](https://www.hitboxarcade.com/blogs/support/what-is-socd) 865 | 866 | # To Do List 867 | - Clean up Guide/Formatting 868 | - Add basic "Hitbox" layout 869 | - Add "Frame1/b0xx/Smashbox" layout 870 | - Add sample firmware retaining keyboard functionality 871 | --------------------------------------------------------------------------------