├── .gitignore ├── .gitmodules ├── README.mkdn ├── fp-lib-table ├── fw ├── .cargo │ └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.mkdn ├── USB.mkdn ├── asm │ └── farjmp.S ├── build.rs ├── memory.x ├── openocd.cfg ├── openocd.gdb └── src │ ├── debounce.rs │ ├── hid.rs │ ├── kbd.rs │ ├── main.rs │ └── scan.rs ├── srch ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.mkdn └── src │ └── main.rs ├── sym-lib-table ├── v1.jpg ├── wasd-code-upgrade-cache.lib ├── wasd-code-upgrade.kicad_pcb ├── wasd-code-upgrade.kicad_pro └── wasd-code-upgrade.kicad_sch /.gitignore: -------------------------------------------------------------------------------- 1 | # For PCBs designed using KiCad: http://www.kicad-pcb.org/ 2 | # Format documentation: http://kicad-pcb.org/help/file-formats/ 3 | 4 | # Temporary files 5 | *.000 6 | *.bak 7 | *.bck 8 | *.kicad_pcb-bak 9 | *.kicad_sch-bak 10 | *.kicad_prl 11 | *.sch-bak 12 | *~ 13 | _autosave-* 14 | *.tmp 15 | *-save.pro 16 | *-save.kicad_pcb 17 | fp-info-cache 18 | 19 | # Netlist files (exported from Eeschema) 20 | *.net 21 | 22 | # Autorouter files (exported from Pcbnew) 23 | *.dsn 24 | *.ses 25 | 26 | # Exported BOM files 27 | *.xml 28 | *.csv 29 | wasd-code-upgrade-backups/ 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib"] 2 | path = lib 3 | url = ../kicad-pcb-libs.git 4 | -------------------------------------------------------------------------------- /README.mkdn: -------------------------------------------------------------------------------- 1 | # WASD CODE brain replacement 2 | 3 | I have a CODE v2B keyboard from WASD (tenkeyless -- the kind without the 4 | numpad). It's a nice keyboard, but has really limited programmability. WASD has 5 | released a newer CODE keyboard (the v3) with some of the features I want, but... 6 | this is a perfectly good keyboard still. Why throw it away? 7 | 8 | So I took it apart. It turns out that it contains a removable controller module, 9 | nestled above the arrow keys. By replacing that controller module, I can fix the 10 | things about the keyboard that bug me, and make it do _whatever I want._ 11 | 12 | (This also means that WASD _could have_ released an upgrade module for their 13 | existing keyboards, but decided to sell you a whole new keyboard instead, 14 | because recycling is bad, I guess? Poor form, WASD.) 15 | 16 | Getting this to work has required a fair amount of reverse engineering work, so 17 | I'm publishing the schematics and mechanicals so nobody has to go through it 18 | again. I'm not prepared to provide any support for this design, particularly for 19 | free -- good luck! 20 | 21 | # Status 22 | 23 | **Hardware:** schematic and layout at the `fab-v1` tag have been built and work. 24 | I've made a series of changes, hopefully for the better, since that tag; these 25 | have not been tested. 26 | 27 | **Firmware:** mostly parity with the original. 28 | 29 | Before you ask: This board _might_ be compatible with QMK? I don't know, I don't 30 | use QMK. 31 | 32 | ![Picture of the v1 board installed in my keyboard](v1.jpg) 33 | 34 | # Configuration 35 | 36 | Configuration uses the DIP switches on the back of the keyboard, but their 37 | meanings have changed from the stock controller. 38 | 39 | - DIP1: swap Left Ctrl and Caps Lock. 40 | - DIP2: swap Alt and Super (Mac mode). 41 | - DIP3: replace Menu key with Fn key. 42 | - DIP4: replace Right Alt key with Menu key (which is the same keycode as the 43 | Compose Character key on Unix keyboards, so this effectively switches right 44 | Alt to Compose -- though you will need to adjust the X layout options to make 45 | this effective). 46 | 47 | When Fn is enabled (DIP3), a subset of the standard media keys are available 48 | (rest are TODO): 49 | 50 | - Fn+PgUp: Volume Up 51 | - Fn+PgDn: Volume Down 52 | 53 | In addition, pressing Fn+Esc enters DFU mode (see below). 54 | 55 | # Note on uploading firmware 56 | 57 | I mostly program/debug the firmware using SWD. However, with the SWD connector 58 | soldered on, the keyboard case won't close. To replace the firmware with the 59 | case closed, you can use DFU mode. 60 | 61 | 1. Ensure the Fn key is enabled (DIP3 set). 62 | 2. Plug in the keyboard and press Fn+Esc. 63 | 3. Run: `dfu-util -d 0483:df11 --alt 0 -s 0x08000000 -D path_to_file.bin -R` 64 | 65 | To get the binary file required by `dfu-util`, you can do: 66 | 67 | ```shell 68 | $ cd fw 69 | $ cargo build --release 70 | $ arm-none-eabi-objcopy -O binary target/thumbv7em-none-eabihf/release/fourk fourk.bin 71 | ``` 72 | -------------------------------------------------------------------------------- /fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (lib (name vssop)(type KiCad)(uri ${KIPRJMOD}/lib/vssop.pretty)(options "")(descr "")) 3 | ) 4 | -------------------------------------------------------------------------------- /fw/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | rustflags = ["-C", "link-arg=-Tlink.x"] 6 | runner = "arm-none-eabi-gdb -q -x openocd.gdb" 7 | -------------------------------------------------------------------------------- /fw/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | .gdb_history 4 | target/ 5 | .swp 6 | .*.swp 7 | -------------------------------------------------------------------------------- /fw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aligned" 5 | version = "0.3.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" 8 | dependencies = [ 9 | "as-slice", 10 | ] 11 | 12 | [[package]] 13 | name = "as-slice" 14 | version = "0.1.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" 17 | dependencies = [ 18 | "generic-array 0.12.3", 19 | "generic-array 0.13.2", 20 | "stable_deref_trait", 21 | ] 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 28 | 29 | [[package]] 30 | name = "bare-metal" 31 | version = "0.2.5" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 34 | dependencies = [ 35 | "rustc_version", 36 | ] 37 | 38 | [[package]] 39 | name = "byteorder" 40 | version = "1.3.4" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 43 | 44 | [[package]] 45 | name = "cc" 46 | version = "1.0.59" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "0.1.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 55 | 56 | [[package]] 57 | name = "cortex-m" 58 | version = "0.6.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" 61 | dependencies = [ 62 | "aligned", 63 | "bare-metal", 64 | "volatile-register", 65 | ] 66 | 67 | [[package]] 68 | name = "cortex-m-rt" 69 | version = "0.6.12" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" 72 | dependencies = [ 73 | "cortex-m-rt-macros", 74 | "r0", 75 | ] 76 | 77 | [[package]] 78 | name = "cortex-m-rt-macros" 79 | version = "0.1.8" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 82 | dependencies = [ 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "fourk" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "byteorder", 93 | "cc", 94 | "cfg-if", 95 | "cortex-m", 96 | "cortex-m-rt", 97 | "num-derive", 98 | "num-traits", 99 | "panic-halt", 100 | "smart-default", 101 | "stm32l4", 102 | "zerocopy", 103 | ] 104 | 105 | [[package]] 106 | name = "generic-array" 107 | version = "0.12.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 110 | dependencies = [ 111 | "typenum", 112 | ] 113 | 114 | [[package]] 115 | name = "generic-array" 116 | version = "0.13.2" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" 119 | dependencies = [ 120 | "typenum", 121 | ] 122 | 123 | [[package]] 124 | name = "num-derive" 125 | version = "0.3.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e0396233fb2d5b0ae3f05ff6aba9a09185f7f6e70f87fb01147d545f85364665" 128 | dependencies = [ 129 | "proc-macro2", 130 | "quote", 131 | "syn", 132 | ] 133 | 134 | [[package]] 135 | name = "num-traits" 136 | version = "0.2.12" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 139 | dependencies = [ 140 | "autocfg", 141 | ] 142 | 143 | [[package]] 144 | name = "panic-halt" 145 | version = "0.2.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" 148 | 149 | [[package]] 150 | name = "proc-macro2" 151 | version = "1.0.18" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 154 | dependencies = [ 155 | "unicode-xid", 156 | ] 157 | 158 | [[package]] 159 | name = "quote" 160 | version = "1.0.7" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 163 | dependencies = [ 164 | "proc-macro2", 165 | ] 166 | 167 | [[package]] 168 | name = "r0" 169 | version = "0.2.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 172 | 173 | [[package]] 174 | name = "rustc_version" 175 | version = "0.2.3" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 178 | dependencies = [ 179 | "semver", 180 | ] 181 | 182 | [[package]] 183 | name = "semver" 184 | version = "0.9.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 187 | dependencies = [ 188 | "semver-parser", 189 | ] 190 | 191 | [[package]] 192 | name = "semver-parser" 193 | version = "0.7.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 196 | 197 | [[package]] 198 | name = "smart-default" 199 | version = "0.6.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" 202 | dependencies = [ 203 | "proc-macro2", 204 | "quote", 205 | "syn", 206 | ] 207 | 208 | [[package]] 209 | name = "stable_deref_trait" 210 | version = "1.2.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 213 | 214 | [[package]] 215 | name = "stm32l4" 216 | version = "0.11.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "3f58fc9ed772b4d742ed566cc14e224497c179d15c594d6108bebddd8d6a9452" 219 | dependencies = [ 220 | "bare-metal", 221 | "cortex-m", 222 | "cortex-m-rt", 223 | "vcell", 224 | ] 225 | 226 | [[package]] 227 | name = "syn" 228 | version = "1.0.34" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" 231 | dependencies = [ 232 | "proc-macro2", 233 | "quote", 234 | "unicode-xid", 235 | ] 236 | 237 | [[package]] 238 | name = "synstructure" 239 | version = "0.12.4" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 242 | dependencies = [ 243 | "proc-macro2", 244 | "quote", 245 | "syn", 246 | "unicode-xid", 247 | ] 248 | 249 | [[package]] 250 | name = "typenum" 251 | version = "1.12.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 254 | 255 | [[package]] 256 | name = "unicode-xid" 257 | version = "0.2.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 260 | 261 | [[package]] 262 | name = "vcell" 263 | version = "0.1.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" 266 | 267 | [[package]] 268 | name = "volatile-register" 269 | version = "0.2.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 272 | dependencies = [ 273 | "vcell", 274 | ] 275 | 276 | [[package]] 277 | name = "zerocopy" 278 | version = "0.3.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" 281 | dependencies = [ 282 | "byteorder", 283 | "zerocopy-derive", 284 | ] 285 | 286 | [[package]] 287 | name = "zerocopy-derive" 288 | version = "0.2.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" 291 | dependencies = [ 292 | "proc-macro2", 293 | "syn", 294 | "synstructure", 295 | ] 296 | -------------------------------------------------------------------------------- /fw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fourk" 3 | version = "0.1.0" 4 | authors = ["Cliff L. Biffle "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | panic-halt = "0.2.0" 11 | cortex-m = "0.6.2" 12 | cortex-m-rt = "0.6.12" 13 | stm32l4 = {version = "0.11", features = ["stm32l4x2", "rt"]} 14 | zerocopy = "0.3" 15 | byteorder = {version = "1.3.4", default-features = false} 16 | num-derive = "0.3.1" 17 | num-traits = {version = "0.2", default-features = false} 18 | smart-default = "0.6.0" 19 | cfg-if = "0.1.10" 20 | 21 | [build-dependencies] 22 | cc = "1.0" 23 | 24 | [features] 25 | default = ["v1"] 26 | v1 = [] 27 | 28 | [[bin]] 29 | name = "fourk" 30 | test = false 31 | bench = false 32 | 33 | [profile.release] 34 | opt-level = 3 35 | lto = true 36 | debug = 2 37 | -------------------------------------------------------------------------------- /fw/README.mkdn: -------------------------------------------------------------------------------- 1 | # Keyboard firmware 2 | 3 | This implements a basic HID keyboard using the STM32L4, in (as of this writing) 4 | just a smidge over 1200 lines of code and just under 5kiB of Flash. 5 | 6 | Features: 7 | 8 | - Implements a USB FS (i.e. 12mbps) keyboard. (USB LS, i.e. 1.5mbps, not 9 | supported.) 10 | - Enlists a timer and the DMA controller to do matrix scanning entirely in 11 | hardware -- software just reads a buffer to see the latest matrix state. 12 | - Currently implemented as one polling loop, i.e. no interrupts to reason about. 13 | - WASD CODE DIP switches implemented, though the implementation is different 14 | (see top-level project readme). 15 | - All in Rust, and mostly safe Rust. 16 | 17 | Limitations: 18 | 19 | - Currently boot protocol only (so, limited to 6 non-modifier keys down at a 20 | time). 21 | - Does not yet support suspend/resume. 22 | - Media keys other than volume control not yet supported. 23 | - Backlight brightness cannot be changed (on/off only). 24 | - Written in a hurry, not well factored. 25 | -------------------------------------------------------------------------------- /fw/USB.mkdn: -------------------------------------------------------------------------------- 1 | # USB notes 2 | 3 | ## Control EP0 4 | 5 | Control endpoint 0 is used as a multiplexed channel for SETUP operations. A 6 | field in the SETUP packet selects between messages to the device (e.g. 7 | `SET_ADDRESS`), a numbered interface (e.g. HID `GET_DESCRIPTOR`), or a numbered 8 | endpoint. 9 | 10 | 11 | 12 | ## Boot protocol 13 | 14 | HID is ridiculously ornate, and defines a subset for BIOSes and such. This 15 | subset appears to be all that most of my keyboards implement. 16 | 17 | The host receives keyboard updates using 8-byte Interrupt IN operations. These 18 | consist of: 19 | 20 | - A byte bitmask for the modifier keys. 21 | - A reserved byte. 22 | - Six bytes of keycodes. 23 | 24 | At any given time the packet contains the codes for six non-modifier keys that 25 | are currently depressed, plus the state of all modifier keys (up to 8 anyway). 26 | The host has to diff this against the previous state if it wants press/release 27 | events. 28 | 29 | The keyboard in this case does _not_ do any kind of key repeat. Keys are down or 30 | up. 31 | 32 | In the initial state, the keyboard will report its state whenever the host asks. 33 | The host can use `SET_IDLE` to change this. `SET_IDLE` asks the device to NAK 34 | polls until something changes or a deadline expires. The deadline part of this 35 | seems to have been designed to implement "keymatic repeat?" 36 | 37 | Anyhoo, the deadline is specified in units of 4ms, which is fine since the host 38 | is giving us a 1ms heartbeat. 39 | 40 | - Slots in the boot packet that are not used are zeroed. 41 | - Key codes are filled in from left to right. 42 | - If keys are released, the others shift left to fill the space. 43 | - If too many keys are pressed, the keyboard is expected to fill all slots with 44 | a "roll over" code 0x01. When the condition clears it goes back to reporting 45 | the depressed keys. Modifiers are still reported during rollover condition. 46 | 47 | 48 | --- 49 | 50 | States. Figure 9-1 in the USB 2 standard has a nice diagram. I'll use their 51 | terms. 52 | 53 | Powered. 54 | - Waiting for a USB reset. 55 | 56 | Reset. 57 | - Responds only on control pipe 0 58 | - Responds only to limited command set. 59 | - Can accept `SET_ADDRESS` and apply it at status packet completion. 60 | 61 | Address. 62 | - Responds to broader command set, but still only on control pipe 0. 63 | - Can share config descriptors. 64 | - Will accept `SET_CONFIGURATION` 65 | 66 | Configured. 67 | - Now complies with one of its interface descriptions, probably the sole one. 68 | - Other endpoints are live now 69 | - Further HID-level configuration can occur. 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /fw/asm/farjmp.S: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | 3 | .text 4 | .global farjmp 5 | .thumb_func 6 | farjmp: 7 | msr MSP, r0 8 | bx r1 9 | -------------------------------------------------------------------------------- /fw/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | linker_script_plumbing(); 8 | build_assembly_sources(); 9 | } 10 | 11 | fn linker_script_plumbing() { 12 | // Put the linker script somewhere the linker can find it 13 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 14 | File::create(out.join("memory.x")) 15 | .unwrap() 16 | .write_all(include_bytes!("memory.x")) 17 | .unwrap(); 18 | println!("cargo:rustc-link-search={}", out.display()); 19 | 20 | println!("cargo:rerun-if-changed=memory.x"); 21 | } 22 | 23 | fn build_assembly_sources() { 24 | cc::Build::new() 25 | .file("asm/farjmp.S") 26 | .compile("libunrusted.a"); 27 | println!("cargo:rerun-if-changed=asm/farjmp.S"); 28 | } 29 | -------------------------------------------------------------------------------- /fw/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K 3 | RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 40K 4 | USBRAM (rw) : ORIGIN = 0x40006c00, LENGTH = 1K 5 | } 6 | 7 | SECTIONS { 8 | .usbram (NOLOAD) : { 9 | *(.usbram .usbram.*) 10 | } >USBRAM 11 | } INSERT AFTER .bss; 12 | -------------------------------------------------------------------------------- /fw/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink.cfg] 2 | source [find target/stm32l4x.cfg] 3 | 4 | -------------------------------------------------------------------------------- /fw/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote localhost:3333 2 | set print asm-demangle on 3 | monitor arm semihosting enable 4 | 5 | load 6 | -------------------------------------------------------------------------------- /fw/src/debounce.rs: -------------------------------------------------------------------------------- 1 | //! Debouncing algorithm. 2 | //! 3 | //! We model the bounce of each key switch separately, because that's how 4 | //! physics works. 5 | 6 | const DELAY: u8 = 10; 7 | 8 | /// Describes the state of a single key being debounced. 9 | #[derive(Copy, Clone, Debug)] 10 | pub enum KeyState { 11 | /// The debouncing interval has elapsed since the last change, and the key 12 | /// is considered to be stably in the given state. 13 | Stable(LogicalState), 14 | /// The key has changed recently and is transitioning into the given state, 15 | /// after the given number of periods. 16 | Unstable(LogicalState, u8), 17 | } 18 | 19 | impl KeyState { 20 | /// Advances the debouncing state machine, given the current instantaneous 21 | /// state of the switch, `current_state`. 22 | pub fn step(&mut self, current_state: LogicalState) { 23 | match self { 24 | // When in an apparently stable state, transition away as soon as 25 | // current_state is observed to change. 26 | Self::Stable(state) => { 27 | if *state != current_state { 28 | *self = Self::Unstable(state.complement(), DELAY); 29 | } 30 | } 31 | 32 | // This case terminates the debouncing interval. Note that, in what 33 | // is almost certainly overengineering, we will short-circuit back 34 | // to the opposing Unstable state if current_state is still 35 | // changing. 36 | Self::Unstable(tgt, 0) => { 37 | if *tgt == current_state { 38 | *self = KeyState::Stable(*tgt); 39 | } else { 40 | *self = KeyState::Unstable(tgt.complement(), DELAY); 41 | } 42 | } 43 | 44 | // This line is the key to the debouncing method: until the counter 45 | // in the Unstable state has ticked down to zero, we *do not* look 46 | // at current_state. 47 | Self::Unstable(tgt, n) => *self = Self::Unstable(*tgt, *n - 1), 48 | 49 | } 50 | } 51 | 52 | /// Checks whether this key should be treated as open or closed, for keycode 53 | /// generation purposes. 54 | pub fn state(&self) -> LogicalState { 55 | // A key is considered closed from the beginning of its debouncing 56 | // interval, after a closing edge has been observed, and for the 57 | // duration of its stable closed state. As soon as an opening edge is 58 | // observed, the key is treated as open. 59 | match self { 60 | Self::Unstable(s, _) | Self::Stable(s) => *s, 61 | } 62 | } 63 | 64 | /// Shorthand for asking if the key is down. 65 | pub fn is_closed(&self) -> bool { 66 | self.state() == LogicalState::Closed 67 | } 68 | } 69 | 70 | impl Default for KeyState { 71 | fn default() -> Self { 72 | Self::Stable(LogicalState::Open) 73 | } 74 | } 75 | 76 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 77 | pub enum LogicalState { Open, Closed } 78 | 79 | impl LogicalState { 80 | pub fn complement(self) -> Self { 81 | match self { 82 | Self::Open => Self::Closed, 83 | Self::Closed => Self::Open, 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /fw/src/hid.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Copy, Clone, Debug, FromPrimitive, AsBytes, Unaligned)] 4 | #[repr(u8)] 5 | pub enum HidClassDescriptorType { 6 | Hid = 0x21, 7 | Report = 0x22, 8 | Physical = 0x33, 9 | } 10 | 11 | #[derive(Copy, Clone, Debug, FromPrimitive)] 12 | #[repr(u8)] 13 | enum HidRequestCode { 14 | GetReport = 1, 15 | GetIdle = 2, 16 | GetProtocol = 3, 17 | SetReport = 9, 18 | SetIdle = 0xA, 19 | SetProtocol = 0xB, 20 | } 21 | 22 | #[derive(Clone, Debug, AsBytes, Unaligned, SmartDefault)] 23 | #[repr(C)] 24 | pub struct HidDescriptor { 25 | #[default = 9] 26 | pub length: u8, 27 | #[default(HidClassDescriptorType::Hid)] 28 | pub type_: HidClassDescriptorType, 29 | pub hid_version: U16, 30 | pub country_code: u8, 31 | #[default = 1] 32 | pub num_descriptors: u8, 33 | pub descriptor_type: u8, 34 | pub descriptor_length: U16, 35 | } 36 | 37 | 38 | #[derive(Debug, Default, Clone)] 39 | pub struct Hid { 40 | expected_out: Option, 41 | } 42 | 43 | #[derive(Copy, Clone, Debug, SmartDefault)] 44 | enum OutKind { 45 | #[default] 46 | SetReport 47 | } 48 | 49 | static BOOT_KBD_DESC: [u8; 62] = [ 50 | 0x05, 0x01, // Usage Page (Desktop), 51 | 0x09, 0x06, // Usage (Keyboard), 52 | 0xA1, 0x01, // Collection (Application), 53 | 0x05, 0x07, // Usage Page (Keyboard), 54 | 0x19, 0xE0, // Usage Minimum (KB Leftcontrol), 55 | 0x29, 0xE7, // Usage Maximum (KB Right GUI), 56 | 0x15, 0x00, // Logical Minimum (0), 57 | 0x25, 0x01, // Logical Maximum (1), 58 | 0x75, 0x01, // Report Size (1), 59 | 0x95, 0x08, // Report Count (8), 60 | 0x81, 0x02, // Input (Variable), 61 | 0x95, 0x01, // Report Count (1), 62 | 0x75, 0x08, // Report Size (8), 63 | 0x81, 0x01, // Input (Constant), 64 | 0x95, 0x03, // Report Count (3), 65 | 0x75, 0x01, // Report Size (1), 66 | 0x05, 0x08, // Usage Page (LED), 67 | 0x19, 0x01, // Usage Minimum (01h), 68 | 0x29, 0x03, // Usage Maximum (03h), 69 | 0x91, 0x02, // Output (Variable), 70 | 0x95, 0x05, // Report Count (5), 71 | 0x75, 0x01, // Report Size (1), 72 | 0x91, 0x01, // Output (Constant), 73 | 0x95, 0x06, // Report Count (6), 74 | 0x75, 0x08, // Report Size (8), 75 | 0x26, 0xFF, 0x00, // Logical Maximum (255), 76 | 0x05, 0x07, // Usage Page (Keyboard), 77 | 0x19, 0x00, // Usage Minimum (None), 78 | 0x29, 0x91, // Usage Maximum (KB LANG2), 79 | 0x81, 0x00, // Input, 80 | 0xC0 // End Collection 81 | ]; 82 | 83 | impl Hid { 84 | pub fn on_set_config(&mut self, usb: &device::USB) { 85 | // Prepare empty report for EP1. 86 | let txoff = get_ep_tx_offset(usb, 1); 87 | write_usb_sram_16(txoff, 0); 88 | write_usb_sram_16(txoff + 2, 0); 89 | write_usb_sram_16(txoff + 4, 0); 90 | write_usb_sram_16(txoff + 6, 0); 91 | set_ep_tx_count(usb, 1, 8); 92 | 93 | // Set up EP1 for HID 94 | usb.epr[1].modify(|r, w| { 95 | w.ea().bits(1) 96 | .ep_type().bits(0b11) // INTERRUPT 97 | .ep_kind().clear_bit() // not used 98 | 99 | // Note: these bits are toggled by writing 1 for some goddamn 100 | // reason, so we set them as follows. I'd love to extract a 101 | // utility function for this but svd2rust has ensured that this 102 | // is impossible. 103 | .dtog_tx().bit(r.dtog_tx().bit()) // clear bit by toggle 104 | .stat_tx().bits(r.stat_tx().bits() ^ 0b11) // VALID 105 | 106 | .dtog_rx().bit(r.dtog_rx().bit()) // clear bit by toggle 107 | .stat_rx().bits(r.stat_rx().bits() ^ 0b01) // STALL (can't receive) 108 | }); 109 | } 110 | 111 | pub fn on_setup_iface_std(&mut self, setup: &SetupPacket, usb: &device::USB) { 112 | match (setup.request_type.data_phase_direction(), StdRequestCode::from_u8(setup.request)) { 113 | (Dir::DeviceToHost, Some(StdRequestCode::GetDescriptor)) => { 114 | match HidClassDescriptorType::from_u16(setup.value.get() >> 8) { 115 | Some(HidClassDescriptorType::Report) => { 116 | // HID Report Descriptor 117 | let desc = &BOOT_KBD_DESC; 118 | write_usb_sram_bytes(get_ep_tx_offset(usb, 0), desc); 119 | // Update transmittable count. 120 | set_ep_tx_count(usb, 0, setup.length.get().min(desc.len() as u16)); 121 | } 122 | _ => { 123 | // Unknown kind of descriptor. 124 | // TODO stall 125 | set_ep_tx_count(usb, 0, 0); 126 | } 127 | } 128 | // Enable transmission. 129 | configure_response(usb, 0, Status::Valid, Status::Valid); 130 | } 131 | _ => { 132 | // Unsupported 133 | // Update transmittable count. 134 | set_ep_tx_count(usb, 0, 0); 135 | // Set a stall condition. 136 | configure_response(usb, 0, Status::Stall, Status::Stall); 137 | } 138 | } 139 | } 140 | 141 | pub fn on_setup_iface_class(&mut self, setup: &SetupPacket, usb: &device::USB) { 142 | match (setup.request_type.data_phase_direction(), HidRequestCode::from_u8(setup.request)) { 143 | (Dir::HostToDevice, Some(HidRequestCode::SetIdle)) => { 144 | set_ep_tx_count(usb, 0, 0); 145 | configure_response(usb, 0, Status::Valid, Status::Valid); 146 | } 147 | (Dir::HostToDevice, Some(HidRequestCode::SetReport)) => { 148 | match setup.value.get() { 149 | 0x02_00 => { 150 | self.expected_out = Some(OutKind::SetReport); 151 | set_ep_tx_count(usb, 0, 0); 152 | configure_response(usb, 0, Status::Valid, Status::Valid); 153 | } 154 | _ => { 155 | set_ep_tx_count(usb, 0, 0); 156 | configure_response(usb, 0, Status::Stall, Status::Stall); 157 | } 158 | } 159 | } 160 | (Dir::HostToDevice, Some(HidRequestCode::SetProtocol)) => { 161 | // whatever - our report protocol matches the boot protocol so 162 | // it's all the same to us. 163 | set_ep_tx_count(usb, 0, 0); 164 | configure_response(usb, 0, Status::Valid, Status::Valid); 165 | } 166 | _ => { 167 | // Unsupported 168 | // Update transmittable count. 169 | set_ep_tx_count(usb, 0, 0); 170 | // Set a stall condition. 171 | configure_response(usb, 0, Status::Stall, Status::Stall); 172 | } 173 | } 174 | } 175 | 176 | pub fn on_in(&mut self, ep: usize, usb: &device::USB, debounce: &[[debounce::KeyState; COLS]; ROW_COUNT], kbd: &kbd::Kbd) { 177 | // The host has just read a HID report. Prepare the next one. 178 | // TODO this introduces one stage of queueing delay; the reports 179 | // should be generated asynchronously. 180 | 181 | let cfg = kbd.read_configuration(debounce); 182 | 183 | // We have a key status matrix. We want a packed list of keycodes. 184 | // Scan the matrix to convert. 185 | let mut keys_written = 0; 186 | let mut packet = BootKbdPacket::default(); 187 | 188 | let map = kbd.map_for_config(cfg); 189 | 190 | for (scan_row, code_row) in debounce.iter().zip(map) { 191 | // Note that this formulation will ignore any high-order bits if 192 | // code_row is narrower than 16, and any extra entries in code_row 193 | // beyond 16. 194 | for (deb, code) in scan_row.iter().zip(code_row) { 195 | if deb.is_closed() { 196 | let value = kbd.rewrite_key(cfg, *code); 197 | 198 | if value >= K::LC { 199 | // Handle as modifier. 200 | let bit_number = value as u8 - K::LC as u8; 201 | packet.modifiers |= 1 << bit_number; 202 | } else if value > K::__ { 203 | // Handle as key (0 means no key or dead key). 204 | match keys_written { 205 | 0..=5 => { 206 | packet.keys_down[keys_written] = value as u8; 207 | keys_written += 1; 208 | } 209 | 6 => { 210 | // We have key overflow. Scribble over the 211 | // packet once and then advance keys_written to 212 | // 7 so we don't do it for further keys. 213 | // (Continue scanning to pick up modifiers.) 214 | for byte in &mut packet.keys_down { 215 | *byte = 0x01; 216 | } 217 | keys_written += 1; 218 | } 219 | _ => (), 220 | } 221 | } 222 | } 223 | } 224 | } 225 | let txoff = get_ep_tx_offset(usb, 1); 226 | write_usb_sram_bytes(txoff, packet.as_bytes()); 227 | 228 | configure_response(usb, ep, Status::Valid, Status::Valid); 229 | } 230 | 231 | pub fn on_out(&mut self, ep: usize, usb: &device::USB, kbd: &kbd::Kbd) { 232 | if let Some(ok) = self.expected_out.take() { 233 | match ok { 234 | OutKind::SetReport => { 235 | let off = get_ep_rx_offset(usb, ep); 236 | let leds = read_usb_sram_16(off) as u8; 237 | kbd.set_hid_leds(leds); 238 | } 239 | } 240 | } 241 | } 242 | } 243 | 244 | #[derive(Debug, Default, AsBytes)] 245 | #[repr(C)] 246 | struct BootKbdPacket { 247 | modifiers: u8, 248 | reserved: u8, 249 | keys_down: [u8; 6], 250 | } 251 | 252 | /// HID Keyboard Usage codes. 253 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] 254 | #[repr(u8)] 255 | pub enum K { 256 | __ = 0, 257 | 258 | A = 0x04, 259 | B = 0x05, 260 | C = 0x06, 261 | D = 0x07, 262 | E = 0x08, 263 | F = 0x09, 264 | G = 0x0A, 265 | H = 0x0B, 266 | I = 0x0C, 267 | J = 0x0D, 268 | #[allow(clippy::enum_variant_names)] 269 | K = 0x0E, 270 | L = 0x0F, 271 | M = 0x10, 272 | N = 0x11, 273 | O = 0x12, 274 | P = 0x13, 275 | Q = 0x14, 276 | R = 0x15, 277 | S = 0x16, 278 | T = 0x17, 279 | U = 0x18, 280 | V = 0x19, 281 | W = 0x1A, 282 | X = 0x1B, 283 | Y = 0x1C, 284 | Z = 0x1D, 285 | 286 | _1 = 0x1E, 287 | _2 = 0x1F, 288 | _3 = 0x20, 289 | _4 = 0x21, 290 | _5 = 0x22, 291 | _6 = 0x23, 292 | _7 = 0x24, 293 | _8 = 0x25, 294 | _9 = 0x26, 295 | _0 = 0x27, 296 | 297 | En = 0x28, 298 | Es = 0x29, 299 | Bs = 0x2A, 300 | Tb = 0x2B, 301 | Sp = 0x2C, 302 | 303 | Mn = 0x2D, 304 | Eq = 0x2E, 305 | Lb = 0x2F, 306 | Rb = 0x30, 307 | Bh = 0x31, 308 | Sc = 0x33, 309 | Ap = 0x34, 310 | Gv = 0x35, 311 | Cm = 0x36, 312 | Pd = 0x37, 313 | Sl = 0x38, 314 | CL = 0x39, 315 | 316 | F1 = 0x3A, 317 | F2 = 0x3B, 318 | F3 = 0x3C, 319 | F4 = 0x3D, 320 | F5 = 0x3E, 321 | F6 = 0x3F, 322 | F7 = 0x40, 323 | F8 = 0x41, 324 | F9 = 0x42, 325 | F10 = 0x43, 326 | F11 = 0x44, 327 | F12 = 0x45, 328 | 329 | PS = 0x46, 330 | SL = 0x47, 331 | Pa = 0x48, 332 | In = 0x49, 333 | Hm = 0x4A, 334 | PU = 0x4B, 335 | Dl = 0x4C, 336 | Ed = 0x4D, 337 | PD = 0x4E, 338 | 339 | Rt = 0x4F, 340 | Lt = 0x50, 341 | Dn = 0x51, 342 | Up = 0x52, 343 | 344 | Cp = 0x65, 345 | 346 | VM = 0x7F, 347 | VU = 0x80, 348 | VD = 0x81, 349 | 350 | LC = 0xE0, 351 | LS = 0xE1, 352 | LA = 0xE2, 353 | LU = 0xE3, 354 | RC = 0xE4, 355 | RS = 0xE5, 356 | RA = 0xE6, 357 | RU = 0xE7, 358 | } 359 | -------------------------------------------------------------------------------- /fw/src/kbd.rs: -------------------------------------------------------------------------------- 1 | //! Keyboard hardware interface. 2 | 3 | use core::sync::atomic::{AtomicU32, Ordering}; 4 | use core::cell::Cell; 5 | 6 | use super::device; 7 | use super::scan; 8 | use super::hid; 9 | use super::debounce; 10 | 11 | pub const COLS: usize = 16; 12 | pub const ROW_COUNT: usize = 8; 13 | 14 | #[cfg(feature = "v1")] 15 | static ROW_PATTERNS: [u32; ROW_COUNT] = [ 16 | (1 << 0) | (0xFF ^ (1 << 0)) << 16, 17 | (1 << 1) | (0xFF ^ (1 << 1)) << 16, 18 | (1 << 2) | (0xFF ^ (1 << 2)) << 16, 19 | (1 << 3) | (0xFF ^ (1 << 3)) << 16, 20 | (1 << 4) | (0xFF ^ (1 << 4)) << 16, 21 | (1 << 5) | (0xFF ^ (1 << 5)) << 16, 22 | (1 << 6) | (0xFF ^ (1 << 6)) << 16, 23 | (1 << 7) | (0xFF ^ (1 << 7)) << 16, 24 | ]; 25 | 26 | #[cfg(not(feature = "v1"))] 27 | static ROW_PATTERNS: [u32; ROW_COUNT] = [ 28 | // Bit-reversed 3-bit counter starting at pin 4. 29 | (0b000 << 4) | (!0b000 << 4) << 16, 30 | (0b100 << 4) | (!0b100 << 4) << 16, 31 | (0b010 << 4) | (!0b010 << 4) << 16, 32 | (0b110 << 4) | (!0b110 << 4) << 16, 33 | (0b001 << 4) | (!0b001 << 4) << 16, 34 | (0b101 << 4) | (!0b101 << 4) << 16, 35 | (0b011 << 4) | (!0b011 << 4) << 16, 36 | (0b111 << 4) | (!0b111 << 4) << 16, 37 | ]; 38 | 39 | // Matrix in logical order: 40 | // 41 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 42 | // PD __ __ _7 F10 _0 _9 _8 Ed _4 F5 PS D1 _3 _2 _1 H0 43 | // __ Rt Dn N F12 Sl __ __ Lt B __ RA D3 __ __ __ H1 44 | // __ __ __ M En __ Pd Cm __ V RC __ D4 C X Z H2 45 | // __ __ Sp H F11 Ap __ F6 Up G __ LA D5 F4 88 Es H3 46 | // __ RS __ J Bh Sc L K __ F RU __ D6 D S A H4 47 | // __ LS __ Y Bs Lb F7 Rb __ T LU Cp __ F3 CL Tb H5 48 | // __ __ __ U __ P O I __ R Pa SL __ E W Q H6 49 | // PU In Dl _6 F9 Mn F8 Eq Hm _5 LC __ D2 F2 F1 Gv H7 50 | 51 | static KEYS: [[hid::K; COLS]; ROW_COUNT] = { 52 | use hid::K::*; 53 | [ 54 | // 6 8 5 10 9 13 14 11 15 12 7 4 3 1 2 0 55 | [__, Up, Ap, __, G, F4, __, LA, Es, __, F6, F11, H, __, Sp, __], // H3 56 | [L, __, Sc, RU, F, D, S, __, A, __, K, Bh, J, RS, __, __], // H4 57 | [F7, __, Lb, LU, T, F3, CL, Cp, Tb, __, Rb, Bs, Y, LS, __, __], // H5 58 | [O, __, P , Pa, R, E, W, SL, Q, __, I, __, U, __, __, __], // H6 59 | [_9, Ed, _0, F5, _4, _3, _2, PS, _1, __, _8, F10, _7, __, __, PD], // H0 60 | [F8, Hm, Mn, LC, _5, F2, F1, __, Gv, __, Eq, F9, _6, In, Dl, PU], // H7 61 | [__, Lt, Sl, __, B, __, __, RA, __, __, __, F12, N, Rt, Dn, __], // H1 62 | [Pd, __, __, RC, V, C, X, __, Z, __, Cm, En, M, __, __, __], // H2 63 | ] 64 | }; 65 | 66 | static FNKEYS: [[hid::K; COLS]; ROW_COUNT] = { 67 | use hid::K::*; 68 | [ 69 | // 6 8 5 10 9 13 14 11 15 12 7 4 3 1 2 0 70 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __], // H3 71 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __], // H4 72 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __], // H5 73 | [__, __, __, VM, __, __, __, __, __, __, __, __, __, __, __, __], // H6 74 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, VD], // H0 75 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, VU], // H7 76 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __], // H1 77 | [__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __], // H2 78 | ] 79 | }; 80 | 81 | pub struct Kbd<'a> { 82 | gpioa: &'a device::GPIOA, 83 | gpioc: &'a device::GPIOC, 84 | scan_results: &'static [AtomicU32], 85 | backlight_on: Cell, 86 | f12_down: Cell, 87 | } 88 | 89 | impl<'a> Kbd<'a> { 90 | #[allow(clippy::too_many_arguments)] 91 | pub fn configure( 92 | gpioa: &'a device::GPIOA, 93 | gpiob: device::GPIOB, 94 | gpioc: &'a device::GPIOC, 95 | scan_buffer: &'static mut [AtomicU32], 96 | rcc: &device::RCC, 97 | dma1: device::DMA1, 98 | tim2: device::TIM2, 99 | timer_mhz: u32, 100 | ) -> Self { 101 | // Turn on GPIO ports for scanout and readback. 102 | rcc.ahb2enr.modify(|_, w| w.gpioaen().set_bit().gpioben().set_bit()); 103 | cortex_m::asm::dsb(); 104 | 105 | // Configure port A. 106 | cfg_if::cfg_if! { 107 | if #[cfg(feature = "v1")] { 108 | // One output line per column, backlight on A8. Backlight is 109 | // active high; ensure that we don't raise it. 110 | gpioa.bsrr.write(|w| { 111 | w.br8().set_bit() 112 | }); 113 | gpioa.moder.write(|w| { 114 | w.moder0().output() 115 | .moder1().output() 116 | .moder2().output() 117 | .moder3().output() 118 | .moder4().output() 119 | .moder5().output() 120 | .moder6().output() 121 | .moder7().output() 122 | .moder8().output() 123 | }); 124 | } else { 125 | // Three scan output lines, plus status LEDs PA0:1 and backlight 126 | // on A7. 127 | 128 | // Backlight is active high; ensure that we don't raise it. 129 | // Status LEDs are active low; don't blink them either. 130 | p.GPIOA.bsrr.write(|w| { 131 | w.br7().set_bit() 132 | .bs0().set_bit() 133 | .bs1().set_bit() 134 | }); 135 | p.GPIOA.moder.write(|w| { 136 | w.moder0().output() 137 | .moder1().output() 138 | .moder4().output() 139 | .moder5().output() 140 | .moder6().output() 141 | .moder7().output() 142 | }); 143 | } 144 | } 145 | 146 | // Apply pull-downs to make floating low-side inputs predictable. 147 | gpiob.pupdr.write(|w| { 148 | w.pupdr0().pull_down() 149 | .pupdr1().pull_down() 150 | .pupdr2().pull_down() 151 | .pupdr3().pull_down() 152 | .pupdr4().pull_down() 153 | .pupdr5().pull_down() 154 | .pupdr6().pull_down() 155 | .pupdr7().pull_down() 156 | .pupdr8().pull_down() 157 | .pupdr9().pull_down() 158 | .pupdr10().pull_down() 159 | .pupdr11().pull_down() 160 | .pupdr12().pull_down() 161 | .pupdr13().pull_down() 162 | .pupdr14().pull_down() 163 | .pupdr15().pull_down() 164 | }); 165 | // And make them inputs. 166 | gpiob.moder.write(|w| { 167 | w.moder0().input() 168 | .moder1().input() 169 | .moder2().input() 170 | .moder3().input() 171 | .moder4().input() 172 | .moder5().input() 173 | .moder6().input() 174 | .moder7().input() 175 | .moder8().input() 176 | .moder9().input() 177 | .moder10().input() 178 | .moder11().input() 179 | .moder12().input() 180 | .moder13().input() 181 | .moder14().input() 182 | .moder15().input() 183 | }); 184 | 185 | // Also turn on and configure port C if we're using it. 186 | #[cfg(feature = "v1")] 187 | { 188 | rcc.ahb2enr.modify(|_, w| w.gpiocen().set_bit()); 189 | 190 | cortex_m::asm::dsb(); 191 | 192 | // LEDs are active low; avoid blinking them. 193 | gpioc.bsrr.write(|w| { 194 | w.bs13().set_bit() 195 | .bs14().set_bit() 196 | }); 197 | // Make them outputs. 198 | gpioc.moder.write(|w| { 199 | w.moder13().output() 200 | .moder14().output() 201 | }); 202 | } 203 | 204 | let scan_results = scan::begin_dma_scan( 205 | gpioa, 206 | &ROW_PATTERNS, 207 | &gpiob, 208 | scan_buffer, 209 | rcc, 210 | dma1, 211 | tim2, 212 | timer_mhz, 213 | ); 214 | 215 | Kbd { 216 | gpioa, 217 | gpioc, 218 | scan_results, 219 | backlight_on: Cell::new(false), 220 | f12_down: Cell::new(false), 221 | } 222 | } 223 | 224 | pub fn set_hid_leds(&self, mask: u8) { 225 | // CAPS 226 | self.set_left_status_led(mask & 0b10 != 0); 227 | // SCROLL 228 | self.set_right_status_led(mask & 0b100 != 0); 229 | } 230 | 231 | pub fn set_left_status_led(&self, state: bool) { 232 | cfg_if::cfg_if! { 233 | if #[cfg(feature = "v1")] { 234 | // PC13, active low 235 | if state { 236 | self.gpioc.bsrr.write(|w| w.br13().set_bit()); 237 | } else { 238 | self.gpioc.bsrr.write(|w| w.bs13().set_bit()); 239 | } 240 | } else { 241 | // PA0, active low 242 | if state { 243 | self.gpioa.bsrr.write(|w| w.br0().set_bit()); 244 | } else { 245 | self.gpioa.bsrr.write(|w| w.bs0().set_bit()); 246 | } 247 | } 248 | } 249 | } 250 | 251 | pub fn set_right_status_led(&self, state: bool) { 252 | cfg_if::cfg_if! { 253 | if #[cfg(feature = "v1")] { 254 | // PC14, active low 255 | if state { 256 | self.gpioc.bsrr.write(|w| w.br14().set_bit()); 257 | } else { 258 | self.gpioc.bsrr.write(|w| w.bs14().set_bit()); 259 | } 260 | } else { 261 | // PA1, active low 262 | if state { 263 | self.gpioa.bsrr.write(|w| w.br1().set_bit()); 264 | } else { 265 | self.gpioa.bsrr.write(|w| w.bs1().set_bit()); 266 | } 267 | } 268 | } 269 | } 270 | 271 | pub fn set_global_backlight(&self, level: u8) { 272 | cfg_if::cfg_if! { 273 | if #[cfg(feature = "v1")] { 274 | // PA8 / TIM1_CH1, active high 275 | // PWM not currently implemented, use simple thresholding. 276 | if level > 127 { 277 | self.gpioa.bsrr.write(|w| w.bs8().set_bit()); 278 | } else { 279 | self.gpioa.bsrr.write(|w| w.br8().set_bit()); 280 | } 281 | } else { 282 | // PA7 / TIM1_CH1N, active high 283 | // PWM not currently implemented, use simple thresholding. 284 | if level > 127 { 285 | self.gpioa.bsrr.write(|w| w.bs7().set_bit()); 286 | } else { 287 | self.gpioa.bsrr.write(|w| w.br7().set_bit()); 288 | } 289 | } 290 | } 291 | } 292 | 293 | pub fn scan_results(&self) -> &[AtomicU32] { 294 | self.scan_results 295 | } 296 | 297 | pub fn read_configuration(&self, debounce: &[[debounce::KeyState; COLS]; ROW_COUNT]) -> u32 { 298 | let f12 = debounce[6][11].is_closed(); 299 | let esc = debounce[0][8].is_closed(); 300 | let fn_enabled = debounce[6][9].is_closed(); 301 | let fn_ = debounce[2][7].is_closed(); 302 | 303 | if esc && fn_enabled && fn_ { 304 | // DFU! 305 | super::DFU_SIGNAL.store(super::ENTER_DFU, Ordering::SeqCst); 306 | cortex_m::peripheral::SCB::sys_reset(); 307 | } 308 | 309 | if f12 && fn_enabled && fn_ { 310 | if self.f12_down.get() { 311 | // already handled this 312 | } else { 313 | let bl = !self.backlight_on.get(); 314 | self.backlight_on.set(bl); 315 | self.f12_down.set(true); 316 | self.set_global_backlight(if bl { 255 } else { 0 }); 317 | } 318 | } else { 319 | self.f12_down.set(false); 320 | } 321 | 322 | u32::from(debounce[4][9].is_closed()) // DIP1 323 | | u32::from(debounce[5][9].is_closed()) << 1 // DIP2 324 | | u32::from(fn_enabled) << 2 // DIP3 325 | | u32::from(debounce[7][9].is_closed()) << 3 // DIP4 326 | | u32::from(debounce[0][9].is_closed()) << 4 // DIP5 327 | | u32::from(debounce[1][9].is_closed()) << 5 // DIP6 328 | | u32::from(fn_) << 6 // fn 329 | } 330 | 331 | pub fn map_for_config(&self, config: u32) -> &[[hid::K; COLS]; ROW_COUNT] { 332 | const DIP3_AND_FN: u32 = (1 << 2) | (1 << 6); 333 | if config & DIP3_AND_FN == DIP3_AND_FN { 334 | &FNKEYS 335 | } else { 336 | &KEYS 337 | } 338 | } 339 | 340 | pub fn rewrite_key(&self, config: u32, mut key: hid::K) -> hid::K { 341 | use hid::K; 342 | 343 | const DIP1: u32 = 1 << 0; 344 | const DIP2: u32 = 1 << 1; 345 | const DIP3: u32 = 1 << 2; 346 | const DIP4: u32 = 1 << 3; 347 | 348 | if config & DIP1 != 0 { 349 | // Swap left ctrl and caps lock 350 | match key { 351 | K::LC => key = K::CL, 352 | K::CL => key = K::LC, 353 | _ => (), 354 | } 355 | } 356 | if config & DIP2 != 0 { 357 | // Swap alt and super 358 | match key { 359 | K::LA => key = K::LU, 360 | K::LU => key = K::LA, 361 | K::RA => key = K::RU, 362 | K::RU => key = K::RA, 363 | _ => (), 364 | } 365 | } 366 | if config & DIP3 != 0 { 367 | // Fn becomes dead from USB's perspective 368 | if key == K::Cp { 369 | key = K::__ 370 | } 371 | } 372 | if config & DIP4 != 0 { 373 | // Remap RA onto Cp. NOTE: it is important that this comes _after_ 374 | // DIP3 processing above, since it would just smash this. 375 | if key == K::RA { 376 | key = K::Cp; 377 | } 378 | } 379 | key 380 | } 381 | 382 | } 383 | -------------------------------------------------------------------------------- /fw/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | //extern crate panic_semihosting; 5 | extern crate panic_halt; 6 | extern crate stm32l4; 7 | 8 | use core::mem::MaybeUninit; 9 | use core::ptr::addr_of_mut; 10 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; 11 | use core::convert::TryInto; 12 | 13 | use byteorder::LittleEndian; 14 | use zerocopy::{U16, FromBytes, AsBytes, Unaligned}; 15 | use cortex_m_rt::{entry, pre_init}; 16 | use stm32l4::stm32l4x2 as device; 17 | 18 | use num_derive::FromPrimitive; 19 | use num_traits::FromPrimitive; 20 | use smart_default::SmartDefault; 21 | 22 | use kbd::{ROW_COUNT, COLS}; 23 | 24 | static DFU_SIGNAL: AtomicU32 = AtomicU32::new(0); 25 | const ENTER_DFU: u32 = 0xb004d00d; 26 | 27 | #[pre_init] 28 | unsafe fn pre_init() { 29 | extern "C" { 30 | fn farjmp(sp: u32, pc: u32) -> !; 31 | } 32 | 33 | if DFU_SIGNAL.load(Ordering::Relaxed) == ENTER_DFU { 34 | DFU_SIGNAL.store(0, Ordering::SeqCst); 35 | // Haven't turned anything on yet, so we can use a fairly short path to 36 | // the bootloader: 37 | let p = device::Peripherals::steal(); 38 | // Enable clock to the SYSCFG block. 39 | p.RCC.apb2enr.modify(|_, w| w.syscfgen().set_bit()); 40 | cortex_m::asm::dsb(); 41 | // Remap System Flash to appear at low addresses. 42 | p.SYSCFG.memrmp.write(|w| w.mem_mode().bits(0b001)); 43 | cortex_m::asm::dsb(); 44 | // Turn SYSCFG back off. 45 | p.RCC.apb2enr.modify(|_, w| w.syscfgen().clear_bit()); 46 | cortex_m::asm::dsb(); 47 | // Read system flash entry point and initial stack pointer. 48 | let sp = *(0x1fff_0000 as *const u32); 49 | let entry = *(0x1fff_0004 as *const u32); 50 | // Go! 51 | farjmp(sp, entry) 52 | } 53 | } 54 | 55 | #[entry] 56 | fn main() -> ! { 57 | let p = unsafe { device::Peripherals::steal() }; 58 | 59 | // Scale the CPU up to our max frequency of 80MHz. 60 | // 61 | // We come out of reset at 4MHz on MSI and the board appears stable at that 62 | // speed. We'd like to move over to HSI16 for greater accuracy, and then 63 | // raise that to 80 with the PLL. 64 | // 65 | // HSI16 is already in a suitable range for the PLL, but could be further 66 | // divided if it enables us to improve (reduce) VCO frequency. However, the 67 | // CPU runs on the PLL's R-tap, and the R-tap has very few divisor options. 68 | // This means our VCO frequency is the constrained part. The easiest choice 69 | // without using fractional mode is a div-by-4 from a VCO frequency of 70 | // 320MHz -- div-by-2 and div-by-6 would each put the frequency out of 71 | // range. (This part does not have an adjustable VCO input frequency range.) 72 | // 73 | // 16MHz input * 20 = 320MHz VCO 74 | // 320MHz VCO / 4 = 80MHz clock 75 | // 76 | // That gives us DIVN1=20, DIVP1=3. 77 | // 78 | // We'll turn off the Q and R taps for now. 79 | 80 | const MHZ: u32 = 80; 81 | 82 | // Getting to 80MHz requires the CPU to be volted at its highest setting of 83 | // VOS1. It _appears_ to come out of reset at VOS1. So that was easy. 84 | 85 | // Turn on HSI16. 86 | p.RCC.cr.modify(|_, w| w.hsion().set_bit()); 87 | while !p.RCC.cr.read().hsirdy().bit() {} 88 | 89 | // Configure PLL1. 90 | p.RCC.pllcfgr.write(|w| unsafe { 91 | w.pllpdiv().bits(0) 92 | .pllr().bits(0b01) 93 | .pllren().set_bit() 94 | .pllq().bits(0b11) 95 | .pllqen().clear_bit() 96 | .pllp().clear_bit() 97 | .pllpen().clear_bit() 98 | .plln().bits(20) 99 | .pllm().bits(0) 100 | .pllsrc().bits(0b10) 101 | }); 102 | // Switch on PLL1. 103 | p.RCC.cr.modify(|_, w| w.pllon().set_bit()); 104 | while !p.RCC.cr.read().pllrdy().bit() {} 105 | 106 | // Adjust Flash wait states for target frequency. 107 | p.FLASH.acr.modify(|_, w| unsafe { w.latency().bits(0b100) }); 108 | while p.FLASH.acr.read().latency() != 0b100 {} 109 | 110 | // Adjust bus clock dividers for target frequency - no changes should be 111 | // needed. 112 | 113 | // Switch CPU frequency. 114 | p.RCC.cfgr.modify(|_, w| unsafe { w.sw().bits(0b11) }); 115 | while p.RCC.cfgr.read().sws() != 0b11 {} 116 | 117 | // k cool 118 | 119 | // Turn on HSI48. 120 | p.RCC.crrcr.modify(|_, w| { 121 | w.hsi48on().set_bit() 122 | }); 123 | while !p.RCC.crrcr.read().hsi48rdy().bit() { 124 | // spin 125 | } 126 | // Use HSI48 as the 48MHz reference. 127 | p.RCC.ccipr.modify(|_, w| unsafe { 128 | w.clk48sel().bits(0b00) 129 | }); 130 | 131 | // Power up the CRS and PWR 132 | p.RCC.apb1enr1.modify(|_, w| w.crsen().set_bit().pwren().set_bit()); 133 | cortex_m::asm::dsb(); 134 | // Turn on the USB peripheral 135 | p.PWR.cr2.modify(|_, w| w.usv().set_bit()); 136 | 137 | // Turn on the USB peripheral. 138 | p.RCC.apb1enr1.modify(|_, w| w.usbfsen().set_bit()); 139 | cortex_m::asm::dsb(); 140 | 141 | // Turn on port A. 142 | p.RCC.ahb2enr.modify(|_, w| w.gpioaen().set_bit()); 143 | cortex_m::asm::dsb(); 144 | 145 | // Mux the USB pins to the proper role. 146 | p.GPIOA.moder.modify(|_, w| { 147 | w.moder11().alternate() 148 | .moder12().alternate() 149 | }); 150 | p.GPIOA.afrh.modify(|_, w| { 151 | w.afrh11().af10() 152 | .afrh12().af10() 153 | }); 154 | 155 | // Do any configuration needed to get the sync signal generated (TBD?) 156 | p.USB.cntr.modify(|_,w| w.pdwn().clear_bit()); 157 | cortex_m::asm::delay(80); 158 | p.USB.cntr.modify(|_,w| w.fres().clear_bit()); 159 | p.USB.istr.write(|w| unsafe { w.bits(0) }); 160 | p.USB.bcdr.modify(|_, w| { 161 | w.dppu().set_bit() 162 | }); 163 | 164 | // Enable the CRS 165 | p.CRS.cr.modify(|_, w| { 166 | w.autotrimen().set_bit() 167 | .cen().set_bit() 168 | }); 169 | 170 | let kbd = kbd::Kbd::configure( 171 | &p.GPIOA, 172 | p.GPIOB, 173 | &p.GPIOC, 174 | get_scan_buffer(), 175 | &p.RCC, 176 | p.DMA1, 177 | p.TIM2, 178 | MHZ, 179 | ); 180 | 181 | let btable = get_btable(); 182 | let buffers = get_buffers(); 183 | let debounce = get_debounce_buffer(); 184 | 185 | setup_usb_buffers(&p.USB, btable, buffers); 186 | 187 | let mut device = Device::default(); 188 | 189 | loop { 190 | let istr = p.USB.istr.read(); 191 | 192 | if istr.reset().bit() { 193 | device.reset(&p.USB); 194 | continue; 195 | } 196 | 197 | //TODO: I cannot figure out how to test suspend/resume on Linux. 198 | #[cfg(feature = "suspend-resume")] 199 | { 200 | if istr.wkup().bit() { 201 | device.resume(&p.USB); 202 | p.USB.istr.write(|w| unsafe { 203 | w.bits(!0) 204 | .wkup().clear_bit() 205 | }); 206 | } 207 | if istr.susp().bit() { 208 | device.suspend(&p.USB); 209 | p.USB.istr.write(|w| unsafe { 210 | w.bits(!0) 211 | .susp().clear_bit() 212 | }); 213 | } 214 | } 215 | if istr.sof().bit() { 216 | // Start of Frame - 1ms synchronization message. 217 | // Use this to advance the debouncing state machines. 218 | // But first, clear the bit so we don't do this again until the next 219 | // one arrives. This register is all write-zero-to-clear, 220 | // write-one-ignored, so we want to set all bits *but* SOF -- 221 | // something svd2rust doesn't really grok. 222 | p.USB.istr.write(|w| unsafe { 223 | w.bits(!0) 224 | .sof().clear_bit() 225 | }); 226 | 227 | for (scan_row, deb_row) in kbd.scan_results().iter().zip(&mut debounce[..]) { 228 | let scan_row = scan_row.load(Ordering::Relaxed); 229 | for (bit, deb) in (0..16).zip(deb_row) { 230 | let state = if scan_row & (1 << bit) != 0 { 231 | debounce::LogicalState::Closed 232 | } else { 233 | debounce::LogicalState::Open 234 | }; 235 | deb.step(state); 236 | } 237 | } 238 | } 239 | if istr.ctr().bit() { 240 | // Correct Transfer 241 | let ep = istr.ep_id().bits() as usize & 0x7; 242 | if istr.dir().bit() { 243 | // OUT/SETUP 244 | let epr = p.USB.epr[ep].read(); 245 | // Clear CTR_RX by writing zero. Preserve other bits, 246 | // including toggles. This is a real bitch with svd2rust. 247 | p.USB.epr[ep].modify(|_, w| { 248 | zero_toggles(w).ctr_rx().bit(false) 249 | }); 250 | // Distinguish types 251 | if epr.setup().bit() { 252 | device.on_setup(ep, &p.USB); 253 | } else { 254 | device.on_out(ep, &p.USB, &kbd); 255 | } 256 | } else { 257 | // Clear CTR_TX by writing 0, preserving toggles. 258 | p.USB.epr[ep].modify(|_, w| { 259 | zero_toggles(w) 260 | .ctr_tx().bit(false) 261 | }); 262 | device.on_in(ep, &p.USB, &*debounce, &kbd); 263 | } 264 | } 265 | } 266 | } 267 | 268 | /// Zeroes the toggle bits in the value being written so they aren't 269 | /// inadvertantly changed. 270 | fn zero_toggles(w: &mut device::usb::epr::W) -> &mut device::usb::epr::W { 271 | w.dtog_rx().bit(false) 272 | .stat_rx().bits(0) 273 | .dtog_tx().bit(false) 274 | .stat_tx().bits(0) 275 | } 276 | 277 | fn setup_usb_buffers( 278 | usb: &device::USB, 279 | btable: &'static mut [BtableSlot; 8], 280 | buffers: [&'static mut [MaybeUninit; 32]; 4], 281 | ) { 282 | let [b0, b1, b2, b3] = buffers; 283 | 284 | btable[0].configure(&mut b0[..], &mut b1[..]); 285 | btable[1].configure(&mut b2[..], &mut b3[..]); 286 | 287 | // Load base of descriptor table. 288 | let btable_off = (btable.as_mut_ptr() as usize).wrapping_sub(USB_SRAM_BASE); 289 | debug_assert!(btable_off < USB_SRAM_SIZE); 290 | usb.btable.write(|w| unsafe { w.bits(btable_off as u32) }); 291 | } 292 | 293 | const USB_SRAM_BASE: usize = 0x4000_6C00; 294 | const USB_SRAM_SIZE: usize = 1024; 295 | 296 | fn read_usb_sram_16(addr: u16) -> u16 { 297 | debug_assert!(addr < 0x3FF); 298 | 299 | unsafe { 300 | core::ptr::read_volatile( 301 | (USB_SRAM_BASE + usize::from(addr)) as *mut u16, 302 | ) 303 | } 304 | } 305 | 306 | fn write_usb_sram_16(addr: u16, data: u16) { 307 | debug_assert!(addr < 0x3FF); 308 | 309 | unsafe { 310 | core::ptr::write_volatile( 311 | (USB_SRAM_BASE + usize::from(addr)) as *mut u16, 312 | data, 313 | ) 314 | } 315 | } 316 | 317 | fn write_usb_sram_8(addr: u16, data: u8) { 318 | let word = read_usb_sram_16(addr & !1); 319 | let word = if addr & 1 != 0 { 320 | (word & 0xFF) | u16::from(data) << 8 321 | } else { 322 | (word & 0xFF00) | u16::from(data) 323 | }; 324 | write_usb_sram_16(addr & !1, word); 325 | } 326 | 327 | fn write_usb_sram_bytes(mut addr: u16, mut data: &[u8]) { 328 | debug_assert!(addr < 0x400); 329 | debug_assert!(addr as usize + data.len() <= 0x400); 330 | 331 | if addr & 1 != 0 && !data.is_empty() { 332 | write_usb_sram_8(addr, data[0]); 333 | addr += 1; 334 | data = &data[1..]; 335 | } 336 | 337 | for (i, d) in data.chunks(2).enumerate() { 338 | if d.len() == 2 { 339 | unsafe { 340 | core::ptr::write_volatile( 341 | (USB_SRAM_BASE + usize::from(addr) + 2 * i) as *mut u16, 342 | u16::from_le_bytes(d.try_into().unwrap()), 343 | ) 344 | } 345 | } else { 346 | unsafe { 347 | core::ptr::write_volatile( 348 | (USB_SRAM_BASE + usize::from(addr) + 2 * i) as *mut u16, 349 | u16::from(d[0]), 350 | ) 351 | } 352 | } 353 | } 354 | } 355 | 356 | fn read_usb_sram(addr: u16) -> T 357 | where T: FromBytes 358 | { 359 | debug_assert!(addr < 0x400); 360 | debug_assert!(addr + (core::mem::size_of::() as u16) <= 0x400); 361 | 362 | let mut buffer: MaybeUninit = MaybeUninit::uninit(); 363 | let buffer_bytes = buffer.as_mut_ptr() as *mut u8; 364 | let src_addr = (USB_SRAM_BASE + usize::from(addr)) as *const u8; 365 | 366 | for i in 0..core::mem::size_of::() { 367 | unsafe { 368 | buffer_bytes.add(i).write(src_addr.add(i).read()); 369 | } 370 | } 371 | unsafe { 372 | buffer.assume_init() 373 | } 374 | } 375 | 376 | #[derive(Clone, Debug, Default, FromBytes, AsBytes, Unaligned)] 377 | #[repr(C)] 378 | pub struct SetupPacket { 379 | pub request_type: RequestType, 380 | pub request: u8, 381 | pub value: U16, 382 | pub index: U16, 383 | pub length: U16, 384 | } 385 | 386 | #[derive(Copy, Clone, Debug, Default, FromBytes, AsBytes, Unaligned)] 387 | #[repr(transparent)] 388 | pub struct RequestType(u8); 389 | 390 | impl RequestType { 391 | pub fn data_phase_direction(self) -> Dir { 392 | if self.0 & 0x80 == 0 { 393 | Dir::HostToDevice 394 | } else { 395 | Dir::DeviceToHost 396 | } 397 | } 398 | 399 | pub fn type_(self) -> RequestTypeType { 400 | match (self.0 >> 5) & 0b11 { 401 | 0 => RequestTypeType::Standard, 402 | 1 => RequestTypeType::Class, 403 | 2 => RequestTypeType::Vendor, 404 | _ => RequestTypeType::Reserved, 405 | } 406 | } 407 | 408 | pub fn recipient(self) -> Recipient { 409 | match self.0 & 0x1F { 410 | 0 => Recipient::Device, 411 | 1 => Recipient::Interface, 412 | 2 => Recipient::Endpoint, 413 | 3 => Recipient::Other, 414 | x => Recipient::Reserved(x), 415 | } 416 | } 417 | } 418 | 419 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 420 | pub enum Dir { 421 | HostToDevice, 422 | DeviceToHost, 423 | } 424 | 425 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 426 | pub enum RequestTypeType { 427 | Standard = 0, 428 | Class = 1, 429 | Vendor = 2, 430 | Reserved = 3, 431 | } 432 | 433 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 434 | pub enum Recipient { 435 | Device, 436 | Interface, 437 | Endpoint, 438 | Other, 439 | Reserved(u8), 440 | } 441 | 442 | fn get_ep_rx_offset(usb: &device::USB, ep: usize) -> u16 { 443 | debug_assert!(ep < 8); 444 | 445 | let table = usb.btable.read().bits() as u16; 446 | read_usb_sram_16(table + ep as u16 * 8 + 4) 447 | } 448 | 449 | fn get_ep_tx_offset(usb: &device::USB, ep: usize) -> u16 { 450 | debug_assert!(ep < 8); 451 | 452 | let table = usb.btable.read().bits() as u16; 453 | read_usb_sram_16(table + ep as u16 * 8) & 0x3FF 454 | } 455 | 456 | fn set_ep_tx_count(usb: &device::USB, ep: usize, count: u16) { 457 | debug_assert!(ep < 8); 458 | 459 | let table = usb.btable.read().bits() as u16; 460 | write_usb_sram_16(table + ep as u16 * 8 + 2, count) 461 | } 462 | 463 | #[derive(Copy, Clone, Debug, FromPrimitive, AsBytes, Unaligned)] 464 | #[repr(u8)] 465 | enum DescriptorType { 466 | Device = 1, 467 | Configuration = 2, 468 | String = 3, 469 | Interface = 4, 470 | Endpoint = 5, 471 | } 472 | 473 | #[derive(Copy, Clone, Debug, FromPrimitive)] 474 | #[repr(u8)] 475 | enum StdRequestCode { 476 | GetStatus = 0, 477 | ClearFeature = 1, 478 | SetFeature = 3, 479 | SetAddress = 5, 480 | GetDescriptor = 6, 481 | SetDescriptor = 7, 482 | GetConfiguration = 8, 483 | SetConfiguration = 9, 484 | GetInterface = 10, 485 | SetInterface = 11, 486 | SynchFrame = 12, 487 | } 488 | 489 | #[derive(Clone, Debug, AsBytes, Unaligned, SmartDefault)] 490 | #[repr(C)] 491 | struct DeviceDescriptor { 492 | #[default = 18] 493 | length: u8, 494 | #[default(DescriptorType::Device)] 495 | type_: DescriptorType, 496 | #[default(U16::new(0x0101))] 497 | usb_version: U16, 498 | device_class: u8, 499 | device_subclass: u8, 500 | device_protocol: u8, 501 | max_packet_size0: u8, 502 | vendor: U16, 503 | product: U16, 504 | device_version: U16, 505 | manufacturer_string: u8, 506 | product_string: u8, 507 | serial_string: u8, 508 | num_configurations: u8, 509 | } 510 | 511 | #[derive(Clone, Debug, AsBytes, Unaligned, SmartDefault)] 512 | #[repr(C)] 513 | struct ConfigDescriptor { 514 | #[default = 9] 515 | length: u8, 516 | #[default(DescriptorType::Configuration)] 517 | type_: DescriptorType, 518 | total_length: U16, 519 | num_interfaces: u8, 520 | configuration_value: u8, 521 | configuration_string: u8, 522 | attributes: u8, 523 | max_power: u8, 524 | } 525 | 526 | #[derive(Clone, Debug, AsBytes, Unaligned, SmartDefault)] 527 | #[repr(C)] 528 | struct InterfaceDescriptor { 529 | #[default = 9] 530 | length: u8, 531 | #[default(DescriptorType::Interface)] 532 | type_: DescriptorType, 533 | interface_number: u8, 534 | alternate_setting: u8, 535 | num_endpoints: u8, 536 | interface_class: u8, 537 | interface_subclass: u8, 538 | interface_protocol: u8, 539 | interface_string: u8, 540 | } 541 | 542 | #[derive(Clone, Debug, AsBytes, Unaligned, SmartDefault)] 543 | #[repr(C)] 544 | struct EndpointDescriptor { 545 | #[default = 7] 546 | length: u8, 547 | #[default(DescriptorType::Endpoint)] 548 | type_: DescriptorType, 549 | endpoint_address: u8, 550 | attributes: u8, 551 | max_packet_size: U16, 552 | interval: u8, 553 | } 554 | 555 | #[derive(Clone, Debug, AsBytes, Unaligned, SmartDefault)] 556 | #[repr(C)] 557 | struct CompoundConfig { 558 | config: ConfigDescriptor, 559 | iface: InterfaceDescriptor, 560 | hid: hid::HidDescriptor, 561 | ep: EndpointDescriptor, 562 | } 563 | 564 | fn device_get_descriptor( 565 | setup: &SetupPacket, 566 | offset: u16, 567 | ) -> Result { 568 | let dtype = DescriptorType::from_u16(setup.value.get() >> 8) 569 | .ok_or(())?; 570 | let idx = setup.value.get() as u8; 571 | 572 | let write = |bytes| { 573 | write_usb_sram_bytes(offset, bytes); 574 | Ok(bytes.len()) 575 | }; 576 | 577 | match (dtype, idx) { 578 | (DescriptorType::Device, 0) => write(DeviceDescriptor { 579 | usb_version: U16::new(0x01_01), 580 | max_packet_size0: 64, 581 | vendor: U16::new(0xdead), 582 | product: U16::new(0xbeef), 583 | device_version: U16::new(0x03_14), 584 | manufacturer_string: 1, 585 | product_string: 1, 586 | serial_string: 1, 587 | num_configurations: 1, 588 | ..DeviceDescriptor::default() 589 | }.as_bytes()), 590 | (DescriptorType::Configuration, 0) => { 591 | let desc = CompoundConfig { 592 | config: ConfigDescriptor { 593 | total_length: U16::new(core::mem::size_of::() as u16), 594 | num_interfaces: 1, 595 | configuration_value: 1, 596 | configuration_string: 1, 597 | attributes: 0x80, 598 | max_power: 50, 599 | ..ConfigDescriptor::default() 600 | }, 601 | iface: InterfaceDescriptor { 602 | interface_number: 0, 603 | alternate_setting: 0, 604 | num_endpoints: 1, 605 | interface_class: 3, 606 | interface_subclass: 1, 607 | interface_protocol: 1, 608 | interface_string: 1, 609 | ..InterfaceDescriptor::default() 610 | }, 611 | hid: hid::HidDescriptor { 612 | hid_version: U16::new(0x0101), 613 | country_code: 0, 614 | descriptor_type: 0x22, 615 | descriptor_length: U16::new(62), 616 | ..hid::HidDescriptor::default() 617 | }, 618 | ep: EndpointDescriptor { 619 | endpoint_address: 0x81, 620 | attributes: 3, 621 | max_packet_size: U16::new(8), 622 | interval: 1, 623 | ..EndpointDescriptor::default() 624 | }, 625 | }; 626 | write(desc.as_bytes()) 627 | }, 628 | (DescriptorType::String, _) => { 629 | // String 630 | match idx { 631 | 0 => { 632 | // LangID set 633 | write(&[ 634 | 4, // bLength 635 | DescriptorType::String as u8, // bDescriptorType 636 | 0x09, 0x04 // en_US 637 | ]) 638 | } 639 | 1 => { 640 | // The one bogus string 641 | write(&[ 642 | 16, // bLength 643 | DescriptorType::String as u8, // bDescriptorType 644 | 0x59, 0x00, 645 | 0x4f, 0x00, 646 | 0x55, 0x00, 647 | 0x52, 0x00, 648 | 0x4d, 0x00, 649 | 0x4f, 0x00, 650 | 0x4d, 0x00, 651 | ]) 652 | } 653 | _ => { 654 | Err(()) 655 | } 656 | } 657 | } 658 | _ => { 659 | // Huh? 660 | Err(()) 661 | } 662 | } 663 | } 664 | 665 | #[derive(Debug, Default)] 666 | #[repr(C)] 667 | pub struct BtableSlot { 668 | addr_tx: u16, 669 | count_tx: u16, 670 | addr_rx: u16, 671 | count_rx: u16, 672 | } 673 | 674 | impl BtableSlot { 675 | /// Configures this slot for a given `tx` (host IN) and `rx` (host OUT) 676 | /// buffer. This is intended to be used once, during table setup. 677 | /// 678 | /// Requirements: 679 | /// - Both references must be valid, i.e. not aliasing, aligned, etc. 680 | /// - Both buffers must reside entirely within USBSRAM. 681 | /// - The `rx` buffer must be at least 2 bytes long. 682 | pub fn configure( 683 | &mut self, 684 | tx: &'static mut [MaybeUninit], 685 | rx: &'static mut [MaybeUninit], 686 | ) { 687 | // Record the offset of `tx` in USBSRAM; its length is immaterial at 688 | // this point. 689 | let tx_offset = (tx.as_ptr() as usize).wrapping_sub(USB_SRAM_BASE); 690 | debug_assert!(tx_offset < USB_SRAM_SIZE); 691 | self.addr_tx = tx_offset as u16; 692 | 693 | // Record both the base and length of `rx`. Only certain lengths can be 694 | // recorded; `rx.len()` will be rounded _down._ 695 | let rx_offset = (rx.as_ptr() as usize).wrapping_sub(USB_SRAM_BASE); 696 | debug_assert!(rx_offset < USB_SRAM_SIZE); 697 | self.addr_rx = rx_offset as u16; 698 | let (bl_size, num_block) = if rx.len() <= 62 { 699 | debug_assert!(rx.len() >= 2); 700 | (0, rx.len() as u16 >> 1) 701 | } else { 702 | debug_assert!(rx.len() <= USB_SRAM_SIZE); 703 | (1, (rx.len() as u16 / 32) - 1) 704 | }; 705 | self.count_rx = (num_block << 10) | (bl_size << 15); 706 | } 707 | 708 | /// Adjusts the `COUNT_TX` field to determine the size of packet that will 709 | /// be served up in response to the next IN request to this endpoint. 710 | pub fn set_tx_available(&mut self, n: usize) { 711 | debug_assert!(n <= usize::from(core::u16::MAX)); 712 | self.count_tx = n as u16; 713 | } 714 | 715 | /// Reads the `COUNT_RX` field, giving the length of the last OUT/SETUP 716 | /// packet received at this endpoint. 717 | pub fn rx_available(&self) -> usize { 718 | usize::from(self.count_rx & 0x3FF) 719 | } 720 | } 721 | 722 | /// Produces a reference to a statically allocated buffer table located in 723 | /// USBSRAM, the first time it is called. If you call it again, it will panic. 724 | fn get_btable() -> &'static mut [BtableSlot; 8] { 725 | static TAKEN: AtomicBool = AtomicBool::new(false); 726 | 727 | if TAKEN.swap(true, Ordering::SeqCst) { panic!() } 728 | 729 | #[link_section = ".usbram"] 730 | static mut BTABLE: MaybeUninit<[BtableSlot; 8]> = MaybeUninit::uninit(); 731 | 732 | let array: &mut [MaybeUninit; 8] = unsafe { 733 | core::mem::transmute(&mut *addr_of_mut!(BTABLE)) 734 | }; 735 | 736 | for slot in array.iter_mut() { 737 | unsafe { 738 | core::ptr::write(slot.as_mut_ptr(), BtableSlot::default()); 739 | } 740 | } 741 | 742 | let initialized: &mut [BtableSlot; 8] = unsafe { 743 | core::mem::transmute(array) 744 | }; 745 | initialized 746 | } 747 | 748 | fn get_buffers() -> [&'static mut [MaybeUninit; 32]; 4] { 749 | static TAKEN: AtomicBool = AtomicBool::new(false); 750 | 751 | if TAKEN.swap(true, Ordering::SeqCst) { panic!() } 752 | 753 | #[link_section = ".usbram"] 754 | static mut BUFFERS: MaybeUninit<[[u16; 32]; 4]> = MaybeUninit::uninit(); 755 | 756 | let array: &mut [MaybeUninit<[u16; 32]>; 8] = unsafe { 757 | core::mem::transmute(&mut *addr_of_mut!(BUFFERS)) 758 | }; 759 | 760 | // Even though we're *acting* like this is uninitialized, let's go ahead and 761 | // initialize it. 762 | for slot in array.iter_mut() { 763 | unsafe { 764 | core::ptr::write(slot.as_mut_ptr(), [0; 32]); 765 | } 766 | } 767 | 768 | unsafe { 769 | [ 770 | core::mem::transmute(&mut array[0]), 771 | core::mem::transmute(&mut array[1]), 772 | core::mem::transmute(&mut array[2]), 773 | core::mem::transmute(&mut array[3]), 774 | ] 775 | } 776 | } 777 | 778 | /// Protocol states for the outermost (Device) layer. 779 | /// 780 | /// These follow the names used in Figure 9-1 in the USB 2.0 specification, even 781 | /// though I think they are bad names. 782 | #[derive(Copy, Clone, Debug, SmartDefault, Eq, PartialEq)] 783 | enum DeviceState { 784 | /// Because we're a bus-powered device, we start out in the "powered" state 785 | /// -- since before we're powered, we don't have any state at all! 786 | /// 787 | /// In this state we are expecting a bus reset, but we may also be 788 | /// suspended. 789 | #[default] 790 | Powered, 791 | 792 | /// In the Default state, we are attached to the bus and can accept packets, 793 | /// but only to set up further states. At this point the device responds to 794 | /// address 0 and only address 0. 795 | Default, 796 | 797 | /// In the Address state, we have been assigned an address, but no 798 | /// configuration has been selected. 799 | Address, 800 | 801 | /// In the Configured state, a configuration has been selected, and the 802 | /// interface/endpoints other than EP0 are serving requests. 803 | Configured, 804 | 805 | // Suspended is orthogonal and represented separately. 806 | } 807 | 808 | #[derive(Clone, Debug, Default)] 809 | struct Device { 810 | state: DeviceState, 811 | suspended: bool, 812 | pending_address: Option, 813 | 814 | iface: hid::Hid, 815 | } 816 | 817 | impl Device { 818 | pub fn reset(&mut self, usb: &device::USB) { 819 | usb.istr.write(|w| unsafe { 820 | w.bits(!0) 821 | .reset().clear_bit() 822 | .wkup().clear_bit() 823 | .susp().clear_bit() 824 | .sof().clear_bit() 825 | .esof().clear_bit() 826 | }); 827 | 828 | // Set up control EP 0 for enumeration. 829 | usb.epr[0].modify(|r, w| { 830 | w.ea().bits(0) 831 | .ep_type().bits(0b01) // CONTROL 832 | .ep_kind().clear_bit() // not STATUS_OUT 833 | 834 | // Note: these bits are toggled by writing 1 for some goddamn 835 | // reason, so we set them as follows. I'd love to extract a 836 | // utility function for this but svd2rust has ensured that this 837 | // is impossible. 838 | .dtog_tx().bit(r.dtog_tx().bit()) // clear bit by toggle 839 | .stat_tx().bits(r.stat_tx().bits() ^ 0b11) // VALID 840 | 841 | .dtog_rx().bit(r.dtog_rx().bit()) // clear bit by toggle 842 | .stat_rx().bits(r.stat_rx().bits() ^ 0b11) // VALID (can receive) 843 | }); 844 | 845 | // Configure to respond to address 0. 846 | usb.daddr.write(|w| w.ef().set_bit()); 847 | self.state = DeviceState::Default; 848 | self.suspended = false; 849 | self.pending_address = None; 850 | 851 | self.iface = hid::Hid::default(); 852 | } 853 | 854 | #[cfg(feature = "suspend-resume")] 855 | pub fn suspend(&mut self, usb: &device::USB) { 856 | if self.state != DeviceState::Powered { 857 | // Set FSUSP bit to stop further checks on SOF reception. 858 | usb.cntr.modify(|_, w| w.fsusp().set_bit()); 859 | // Reduce power consumption (TODO) 860 | // Set LP_MODE to switch analog transceiver to low power. 861 | usb.cntr.modify(|_, w| w.lpmode().set_bit()); 862 | self.suspended = true; 863 | } 864 | } 865 | 866 | #[cfg(feature = "suspend-resume")] 867 | pub fn resume(&mut self, usb: &device::USB) { 868 | // Clear FSUSP bit. 869 | usb.cntr.modify(|_, w| w.fsusp().clear_bit()); 870 | // See what happened by reading state of bus lines. 871 | /* 872 | let dp_dm = (usb.fnr.read().bits() >> 14) & 0b11; 873 | match dp_dm { 874 | 0b00 => { 875 | // We are being reset. The reset event should handle this. 876 | self.suspended = false; 877 | } 878 | 0b01 => { 879 | // We are being resumed without a reset. 880 | self.suspended = false; 881 | } 882 | _ => { 883 | // This is a spurious wake due to noise that made it past the 884 | // analog filter. 885 | self.suspend(usb); 886 | return; 887 | } 888 | } 889 | */ 890 | } 891 | 892 | pub fn on_out(&mut self, ep: usize, usb: &device::USB, kbd: &kbd::Kbd) { 893 | self.iface.on_out(ep, usb, kbd); 894 | } 895 | 896 | pub fn on_setup(&mut self, ep: usize, usb: &device::USB) { 897 | if ep == 0 { 898 | // Reset any previously received address, if the host didn't take us 899 | // through to the status phase. 900 | self.pending_address = None; 901 | 902 | // Collect the setup packet from USBSRAM. 903 | let rxbuf = get_ep_rx_offset(usb, ep); 904 | let setup = read_usb_sram::(rxbuf); 905 | 906 | // Dispatch the different protocol layers. 907 | match (setup.request_type.type_(), setup.request_type.recipient()) { 908 | // Messages to us, the device layer: 909 | (RequestTypeType::Standard, Recipient::Device) => match (setup.request_type.data_phase_direction(), StdRequestCode::from_u8(setup.request)) { 910 | (Dir::HostToDevice, Some(StdRequestCode::SetAddress)) => { 911 | // Record pending address to be set in the status phase. 912 | self.pending_address = Some(setup.value.get() as u8 & 0x7F); 913 | // The host will send an IN for the status phase; make 914 | // sure we don't send anything in response. 915 | set_ep_tx_count(usb, ep, 0); 916 | // Configure the endpoint to handle either just an IN, 917 | // or an OUT followed by an IN, because it appears that 918 | // the host is technically permitted to send a 919 | // zero-length data phase before status. 920 | configure_response(usb, ep, Status::Valid, Status::Valid); 921 | } 922 | 923 | (Dir::DeviceToHost, Some(StdRequestCode::GetDescriptor)) => { 924 | let txoff = get_ep_tx_offset(usb, ep); 925 | match device_get_descriptor(&setup, txoff) { 926 | Ok(len) => { 927 | // A descriptor has been deposited at txoff in 928 | // USBSRAM; configure DMA to send as much of it 929 | // as the host requested. 930 | set_ep_tx_count(usb, ep, setup.length.get().min(len as u16)); 931 | // ACK the next thing. 932 | configure_response(usb, ep, Status::Valid, Status::Valid); 933 | } 934 | Err(_) => { 935 | // We don't have a descriptor matching this 936 | // request. 937 | // Do not transmit any data. 938 | set_ep_tx_count(usb, ep, 0); 939 | // In fact, stall. 940 | configure_response(usb, ep, Status::Stall, Status::Stall); 941 | } 942 | } 943 | } 944 | (Dir::HostToDevice, Some(StdRequestCode::SetConfiguration)) => { 945 | set_ep_tx_count(usb, ep, 0); 946 | self.iface.on_set_config(usb); 947 | configure_response(usb, 0, Status::Valid, Status::Valid); 948 | self.state = DeviceState::Configured; 949 | } 950 | _ => { 951 | // Unsupported 952 | // Update transmittable count. 953 | set_ep_tx_count(usb, ep, 0); 954 | // Set a stall condition. 955 | configure_response(usb, 0, Status::Stall, Status::Stall); 956 | } 957 | }, 958 | (RequestTypeType::Standard, Recipient::Interface) => { 959 | self.iface.on_setup_iface_std(&setup, usb); 960 | } 961 | (RequestTypeType::Class, Recipient::Interface) => { 962 | self.iface.on_setup_iface_class(&setup, usb); 963 | } 964 | _ => { 965 | // Unsupported 966 | // Update transmittable count. 967 | set_ep_tx_count(usb, ep, 0); 968 | // Set a stall condition. 969 | configure_response(usb, ep, Status::Stall, Status::Stall); 970 | }, 971 | } 972 | } else { 973 | // TODO SETUP to other endpoints 974 | configure_response(usb, ep, Status::Stall, Status::Stall); 975 | } 976 | } 977 | 978 | pub fn on_in(&mut self, ep: usize, usb: &device::USB, scan_results: &[[debounce::KeyState; COLS]; ROW_COUNT], kbd: &kbd::Kbd) { 979 | if ep == 0 { 980 | // This indicates completion of e.g. a descriptor transfer or an 981 | // empty status phase. 982 | 983 | if let Some(addr) = self.pending_address.take() { 984 | usb.daddr.write(|w| w.ef().set_bit().add().bits(addr)); 985 | if addr == 0 { 986 | self.state = DeviceState::Default; 987 | } else { 988 | self.state = DeviceState::Address; 989 | } 990 | } 991 | 992 | configure_response(usb, ep, Status::Valid, Status::Valid); 993 | } else { 994 | self.iface.on_in(ep, usb, scan_results, kbd); 995 | } 996 | } 997 | } 998 | 999 | #[allow(dead_code)] // document variants even if we don't construct them 1000 | enum Status { 1001 | Disabled = 0b00, 1002 | Stall = 0b01, 1003 | Nak = 0b10, 1004 | Valid = 0b11, 1005 | } 1006 | 1007 | fn configure_response(usb: &device::USB, ep: usize, tx: Status, rx: Status) { 1008 | usb.epr[ep].modify(|r, w| { 1009 | zero_toggles(w) 1010 | .stat_tx().bits(r.stat_tx().bits() ^ tx as u8) 1011 | .stat_rx().bits(r.stat_rx().bits() ^ rx as u8) 1012 | }); 1013 | } 1014 | 1015 | fn get_scan_buffer() -> &'static mut [AtomicU32; ROW_COUNT] { 1016 | static TAKEN: AtomicBool = AtomicBool::new(false); 1017 | 1018 | if TAKEN.swap(true, Ordering::SeqCst) { panic!() } 1019 | 1020 | static mut BUFFER: MaybeUninit<[AtomicU32; ROW_COUNT]> = MaybeUninit::uninit(); 1021 | 1022 | let array: &mut [MaybeUninit; ROW_COUNT] = unsafe { 1023 | core::mem::transmute(&mut addr_of_mut!(BUFFER)) 1024 | }; 1025 | 1026 | for slot in array.iter_mut() { 1027 | unsafe { 1028 | core::ptr::write(slot.as_mut_ptr(), AtomicU32::new(0)); 1029 | } 1030 | } 1031 | 1032 | unsafe { 1033 | core::mem::transmute(array) 1034 | } 1035 | 1036 | } 1037 | 1038 | fn get_debounce_buffer() -> &'static mut [[debounce::KeyState; COLS]; ROW_COUNT] { 1039 | static TAKEN: AtomicBool = AtomicBool::new(false); 1040 | 1041 | if TAKEN.swap(true, Ordering::SeqCst) { panic!() } 1042 | 1043 | static mut BUFFER: MaybeUninit<[[debounce::KeyState; COLS]; ROW_COUNT]> = MaybeUninit::uninit(); 1044 | 1045 | let array: &mut [[MaybeUninit; COLS]; ROW_COUNT] = unsafe { 1046 | core::mem::transmute(&mut addr_of_mut!(BUFFER)) 1047 | }; 1048 | 1049 | for row in array.iter_mut() { 1050 | for slot in row { 1051 | unsafe { 1052 | core::ptr::write(slot.as_mut_ptr(), debounce::KeyState::default()); 1053 | } 1054 | } 1055 | } 1056 | 1057 | unsafe { 1058 | core::mem::transmute(array) 1059 | } 1060 | 1061 | } 1062 | 1063 | mod debounce; 1064 | mod hid; 1065 | mod scan; 1066 | mod kbd; 1067 | -------------------------------------------------------------------------------- /fw/src/scan.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::AtomicU32; 2 | 3 | use stm32l4::stm32l4x2 as device; 4 | 5 | /// Sets up DMA and timer to automatically scan the keyboard matrix. 6 | /// 7 | /// `drive_port`'s BSRR will be driven regularly with successive entries from 8 | /// `drive_pattern`. At each step, the low 16 bits in each word determine which 9 | /// GPIOs should be driven high, and the high 16 bits which should be driven 10 | /// low. (Any GPIOs whose bits are not set in either half of the word are 11 | /// unchanged, allowing them to be used for something else.) 12 | /// 13 | /// After each step is written to `drive_port`, there is a delay for lines to 14 | /// settle, and then `read_port`'s IDR is copied into the element of `output` 15 | /// that corresponds to the `drive_pattern` entry. 16 | /// 17 | /// Before calling this function, you need to have powered up both GPIO ports 18 | /// and configured driven pins as outputs, and read pins as inputs with 19 | /// pulldowns (if external pulldowns are not provided). 20 | /// 21 | /// Corner cases: 22 | /// 23 | /// - `drive_port` and `read_port` can be the same port (though you'll need to 24 | /// alter the types to achieve this, because svd2rust doesn't let us treat 25 | /// GPIO ports generically). 26 | /// - `drive_pattern` and `output` should be the same length, but if they 27 | /// aren't, the minimum of the two lengths will be used. 28 | #[allow(clippy::too_many_arguments)] 29 | pub fn begin_dma_scan( 30 | drive_port: &device::GPIOA, 31 | drive_pattern: &'static [u32], 32 | read_port: &device::GPIOB, 33 | output: &'static mut [AtomicU32], 34 | rcc: &device::RCC, 35 | dma1: device::DMA1, 36 | tim2: device::TIM2, 37 | timer_mhz: u32, 38 | ) -> &'static [AtomicU32] { 39 | let row_count = drive_pattern.len().min(output.len()); 40 | 41 | // Turn on DMA1. 42 | rcc.ahb1enr.modify(|_, w| w.dma1en().set_bit()); 43 | cortex_m::asm::dsb(); 44 | 45 | // Configure CH2 and CH5 to take DRQs from TIM2. 46 | dma1.cselr.write(|w| { 47 | w.c2s().bits(0b100) // TIM2_UP 48 | .c5s().bits(0b100) // TIM2_CH1 49 | }); 50 | // Arrange CH2 (update) to transfer from the patterns array into BSRR. 51 | dma1.cndtr2.write(|w| unsafe { w.bits(row_count as u32) }); 52 | dma1.cmar2.write(|w| unsafe { w.bits(drive_pattern.as_ptr() as u32) }); 53 | dma1.cpar2.write(|w| unsafe { w.bits(&drive_port.bsrr as *const _ as u32) }); 54 | dma1.ccr2.write(|w| unsafe { 55 | // Circular transfer forever. 56 | w.circ().set_bit() 57 | // Memory to peripheral. 58 | .dir().set_bit() 59 | // Read words from memory. 60 | .msize().bits(0b10) 61 | // And increment after reads. 62 | .minc().set_bit() 63 | // Write words to peripheral. 64 | .psize().bits(0b10) 65 | // Don't increment that side. 66 | .pinc().clear_bit() 67 | // On! 68 | .en().set_bit() 69 | }); 70 | // Arrange CH5 to transfer in response to TIM2_CH1. We'll use this to write 71 | // to the scan outputs. 72 | dma1.cndtr5.write(|w| unsafe { w.bits(row_count as u32) }); 73 | dma1.cpar5.write(|w| unsafe { w.bits(&read_port.idr as *const _ as u32) }); 74 | dma1.cmar5.write(|w| unsafe { w.bits(output.as_mut_ptr() as u32) }); 75 | dma1.ccr5.write(|w| unsafe { 76 | // Circular transfer forever. 77 | w.circ().set_bit() 78 | // Peripheral to memory. 79 | .dir().clear_bit() 80 | // Read words from peripheral. 81 | .psize().bits(0b10) 82 | // Don't increment that side. 83 | .pinc().clear_bit() 84 | // Write words to memory. 85 | .msize().bits(0b10) 86 | // And increment after writes. 87 | .minc().set_bit() 88 | // On! 89 | .en().set_bit() 90 | }); 91 | 92 | // Turn on TIM2. 93 | rcc.apb1enr1.modify(|_, w| w.tim2en().set_bit()); 94 | cortex_m::asm::dsb(); 95 | 96 | // Configure TIM2 to the requested scan rate. 97 | // Use prescaler to lower timer input clock to 1MHz. 98 | tim2.psc.write(|w| unsafe { w.bits(timer_mhz - 1) }); 99 | // Aim for a 1kHz overall matrix scan rate. 100 | let timer_period = 1000 / row_count as u32; 101 | tim2.arr.write(|w| unsafe { w.bits(timer_period - 1) }); 102 | // CH1 event happens halfway through because why not. 103 | tim2.ccr1.write(|w| unsafe { w.bits(timer_period / 2) }); 104 | // We want DRQs on both CC1 and UP. 105 | tim2.dier.write(|w| w.ude().set_bit().cc1de().set_bit()); 106 | // Generate an update to get all the registers loaded. 107 | tim2.egr.write(|w| w.ug().set_bit()); 108 | // Let's roll. 109 | tim2.cr1.write(|w| w.cen().set_bit()); 110 | 111 | output 112 | } 113 | -------------------------------------------------------------------------------- /srch/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | .gdb_history 4 | target/ 5 | .swp 6 | .*.swp 7 | -------------------------------------------------------------------------------- /srch/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "srch" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /srch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "srch" 3 | version = "0.1.0" 4 | authors = ["Cliff L. Biffle "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /srch/README.mkdn: -------------------------------------------------------------------------------- 1 | # code srch 2 | 3 | This is a very simple recursive exhaustive search algorithm for packing keyboard 4 | matrix columns together. I used it to reduce the I/O requirements for the CODE 5 | v2B's matrix from 18 (which ran me out of pins) to 16 (which was ok). 6 | -------------------------------------------------------------------------------- /srch/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | static COLS: [u8; 19] = [ 4 | 0b1100_0000, // 0 5 | 0b1000_0001, 6 | 0b1000_0101, 7 | 0b0001_1000, 8 | 0b1111_1111, 9 | 0b1101_1111, // 5 10 | 0b1111_1101, 11 | 0b1111_1010, 12 | 0b1111_1110, 13 | 0b1100_0101, 14 | 0b1111_1111, // 10 15 | 0b1110_0010, 16 | 0b0111_0101, 17 | 0b1100_1111, 18 | 0b0000_1000, 19 | 0b0001_0000, // 15 20 | 0b1111_1110, 21 | 0b1111_1110, 22 | 0b1111_1110, 23 | ]; 24 | 25 | fn main() { 26 | println!("commencing search"); 27 | let mut best = (COLS.len(), COLS.to_vec(), None); 28 | search(&COLS, COLS.len(), None, &mut best); 29 | 30 | println!("best score: {}", best.0); 31 | println!("minimum column loading: {}", best.1.iter().map(|x| x.count_ones()).fold(8, |x, y| x.min(y))); 32 | 33 | let mut trail = best.2; 34 | while let Some(step) = &trail { 35 | println!("- connect {} and {} ({:08b})", step.i, step.j, step.result); 36 | trail = step.trail.clone(); 37 | } 38 | } 39 | 40 | #[derive(Debug)] 41 | struct Move { 42 | i: usize, 43 | j: usize, 44 | result: u8, 45 | trail: Option>, 46 | } 47 | 48 | fn search(state: &[u8], score: usize, trail: Option>, best: &mut (usize, Vec, Option>)) { 49 | if score <= best.0 { 50 | println!("{}\t{:?}", score, trail); 51 | *best = (score, state.to_vec(), trail.clone()); 52 | } 53 | for (i, &ci) in state.iter().enumerate() { 54 | for (ij, &cj) in state[i+1..].iter().enumerate() { 55 | let j = ij + i + 1; 56 | if nonoverlapping(ci, cj) { 57 | let mut new_state = state.to_vec(); 58 | new_state[i] |= cj; 59 | new_state[j] = 0xFF; 60 | let new_trail = Rc::new(Move { 61 | i, 62 | j, 63 | result: new_state[i], 64 | trail: trail.clone(), 65 | }); 66 | search(&new_state, score - 1, Some(new_trail), best); 67 | } 68 | } 69 | } 70 | } 71 | 72 | fn nonoverlapping(a: u8, b: u8) -> bool { 73 | (a & b) == 0 74 | } 75 | -------------------------------------------------------------------------------- /sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (version 7) 3 | ) 4 | -------------------------------------------------------------------------------- /v1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbiffle/keybrain/fea3de35b7a6cbe252bb583b2e1f823fbb7e90d2/v1.jpg -------------------------------------------------------------------------------- /wasd-code-upgrade-cache.lib: -------------------------------------------------------------------------------- 1 | EESchema-LIBRARY Version 2.4 2 | #encoding utf-8 3 | # 4 | # 74xx_IEEE_74HC238 5 | # 6 | DEF 74xx_IEEE_74HC238 U 0 30 Y Y 1 F N 7 | F0 "U" 150 350 50 H V C CNN 8 | F1 "74xx_IEEE_74HC238" 350 -650 50 H V C CNN 9 | F2 "" 0 0 50 H I C CNN 10 | F3 "" 0 0 50 H I C CNN 11 | DRAW 12 | T 0 -150 140 60 0 0 0 & Normal 0 C C 13 | T 0 20 80 60 0 0 0 EN Normal 0 C C 14 | S -250 300 250 -600 0 1 0 N 15 | P 4 0 1 0 -50 300 -50 -150 -250 -150 -250 -150 N 16 | X VCC 16 0 300 0 D 50 50 0 0 W N 17 | X A0 1 -550 -350 300 R 50 50 1 1 I 18 | X Y5 10 550 -300 300 L 50 50 1 1 O 19 | X Y4 11 550 -200 300 L 50 50 1 1 O 20 | X Y3 12 550 -100 300 L 50 50 1 1 O 21 | X Y2 13 550 0 300 L 50 50 1 1 O 22 | X Y1 14 550 100 300 L 50 50 1 1 O 23 | X Y0 15 550 200 300 L 50 50 1 1 O 24 | X A1 2 -550 -450 300 R 50 50 1 1 I 25 | X A2 3 -550 -550 300 R 50 50 1 1 I 26 | X ~G2A 4 -550 200 300 R 50 50 1 1 I I 27 | X ~G2B 5 -550 50 300 R 50 50 1 1 I I 28 | X G1 6 -550 -100 300 R 50 50 1 1 I 29 | X Y7 7 550 -500 300 L 50 50 1 1 O 30 | X GND 8 0 -600 0 U 50 50 1 1 W N 31 | X Y6 9 550 -400 300 L 50 50 1 1 O 32 | ENDDRAW 33 | ENDDEF 34 | # 35 | # Connector_Conn_ARM_JTAG_SWD_10 36 | # 37 | DEF Connector_Conn_ARM_JTAG_SWD_10 J 0 40 Y Y 1 F N 38 | F0 "J" -100 650 50 H V R CNN 39 | F1 "Connector_Conn_ARM_JTAG_SWD_10" -100 550 50 H V R BNN 40 | F2 "" 0 0 50 H I C CNN 41 | F3 "" -350 -1250 50 V I C CNN 42 | $FPLIST 43 | PinHeader?2x05?P1.27mm* 44 | $ENDFPLIST 45 | DRAW 46 | S -400 500 400 -500 0 1 10 f 47 | S -110 -500 -90 -460 0 1 0 N 48 | S -10 -500 10 -460 0 1 0 N 49 | S -10 500 10 460 0 1 0 N 50 | S 360 90 400 110 0 1 0 N 51 | S 400 -110 360 -90 0 1 0 N 52 | S 400 -10 360 10 0 1 0 N 53 | S 400 310 360 290 0 1 0 N 54 | S 360 -210 400 -190 1 1 0 N 55 | X VTref 1 0 600 100 D 50 50 1 1 W 56 | X ~RESET~ 10 500 300 100 L 50 50 1 1 C 57 | X SWDIO/TMS 2 500 0 100 L 50 50 1 1 B 58 | X GND 3 0 -600 100 U 50 50 1 1 W 59 | X SWDCLK/TCK 4 500 100 100 L 50 50 1 1 O 60 | X GND 5 0 -600 100 U 50 50 1 1 P N 61 | X SWO/TDO 6 500 -100 100 L 50 50 1 1 I 62 | X KEY 7 -400 0 100 R 50 50 1 1 N N 63 | X NC/TDI 8 500 -200 100 L 50 50 1 1 O 64 | X GNDDetect 9 -100 -600 100 U 50 50 1 1 P 65 | ENDDRAW 66 | ENDDEF 67 | # 68 | # Connector_Generic_Conn_01x04 69 | # 70 | DEF Connector_Generic_Conn_01x04 J 0 40 Y N 1 F N 71 | F0 "J" 0 200 50 H V C CNN 72 | F1 "Connector_Generic_Conn_01x04" 0 -300 50 H V C CNN 73 | F2 "" 0 0 50 H I C CNN 74 | F3 "" 0 0 50 H I C CNN 75 | $FPLIST 76 | Connector*:*_1x??_* 77 | $ENDFPLIST 78 | DRAW 79 | S -50 -195 0 -205 1 1 6 N 80 | S -50 -95 0 -105 1 1 6 N 81 | S -50 5 0 -5 1 1 6 N 82 | S -50 105 0 95 1 1 6 N 83 | S -50 150 50 -250 1 1 10 f 84 | X Pin_1 1 -200 100 150 R 50 50 1 1 P 85 | X Pin_2 2 -200 0 150 R 50 50 1 1 P 86 | X Pin_3 3 -200 -100 150 R 50 50 1 1 P 87 | X Pin_4 4 -200 -200 150 R 50 50 1 1 P 88 | ENDDRAW 89 | ENDDEF 90 | # 91 | # Connector_Generic_Conn_02x03_Odd_Even 92 | # 93 | DEF Connector_Generic_Conn_02x03_Odd_Even J 0 40 Y N 1 F N 94 | F0 "J" 50 200 50 H V C CNN 95 | F1 "Connector_Generic_Conn_02x03_Odd_Even" 50 -200 50 H V C CNN 96 | F2 "" 0 0 50 H I C CNN 97 | F3 "" 0 0 50 H I C CNN 98 | $FPLIST 99 | Connector*:*_2x??_* 100 | $ENDFPLIST 101 | DRAW 102 | S -50 -95 0 -105 1 1 6 N 103 | S -50 5 0 -5 1 1 6 N 104 | S -50 105 0 95 1 1 6 N 105 | S -50 150 150 -150 1 1 10 f 106 | S 150 -95 100 -105 1 1 6 N 107 | S 150 5 100 -5 1 1 6 N 108 | S 150 105 100 95 1 1 6 N 109 | X Pin_1 1 -200 100 150 R 50 50 1 1 P 110 | X Pin_2 2 300 100 150 L 50 50 1 1 P 111 | X Pin_3 3 -200 0 150 R 50 50 1 1 P 112 | X Pin_4 4 300 0 150 L 50 50 1 1 P 113 | X Pin_5 5 -200 -100 150 R 50 50 1 1 P 114 | X Pin_6 6 300 -100 150 L 50 50 1 1 P 115 | ENDDRAW 116 | ENDDEF 117 | # 118 | # Connector_Generic_Conn_02x12_Odd_Even 119 | # 120 | DEF Connector_Generic_Conn_02x12_Odd_Even J 0 40 Y N 1 F N 121 | F0 "J" 50 600 50 H V C CNN 122 | F1 "Connector_Generic_Conn_02x12_Odd_Even" 50 -700 50 H V C CNN 123 | F2 "" 0 0 50 H I C CNN 124 | F3 "" 0 0 50 H I C CNN 125 | $FPLIST 126 | Connector*:*_2x??_* 127 | $ENDFPLIST 128 | DRAW 129 | S -50 -595 0 -605 1 1 6 N 130 | S -50 -495 0 -505 1 1 6 N 131 | S -50 -395 0 -405 1 1 6 N 132 | S -50 -295 0 -305 1 1 6 N 133 | S -50 -195 0 -205 1 1 6 N 134 | S -50 -95 0 -105 1 1 6 N 135 | S -50 5 0 -5 1 1 6 N 136 | S -50 105 0 95 1 1 6 N 137 | S -50 205 0 195 1 1 6 N 138 | S -50 305 0 295 1 1 6 N 139 | S -50 405 0 395 1 1 6 N 140 | S -50 505 0 495 1 1 6 N 141 | S -50 550 150 -650 1 1 10 f 142 | S 150 -595 100 -605 1 1 6 N 143 | S 150 -495 100 -505 1 1 6 N 144 | S 150 -395 100 -405 1 1 6 N 145 | S 150 -295 100 -305 1 1 6 N 146 | S 150 -195 100 -205 1 1 6 N 147 | S 150 -95 100 -105 1 1 6 N 148 | S 150 5 100 -5 1 1 6 N 149 | S 150 105 100 95 1 1 6 N 150 | S 150 205 100 195 1 1 6 N 151 | S 150 305 100 295 1 1 6 N 152 | S 150 405 100 395 1 1 6 N 153 | S 150 505 100 495 1 1 6 N 154 | X Pin_1 1 -200 500 150 R 50 50 1 1 P 155 | X Pin_10 10 300 100 150 L 50 50 1 1 P 156 | X Pin_11 11 -200 0 150 R 50 50 1 1 P 157 | X Pin_12 12 300 0 150 L 50 50 1 1 P 158 | X Pin_13 13 -200 -100 150 R 50 50 1 1 P 159 | X Pin_14 14 300 -100 150 L 50 50 1 1 P 160 | X Pin_15 15 -200 -200 150 R 50 50 1 1 P 161 | X Pin_16 16 300 -200 150 L 50 50 1 1 P 162 | X Pin_17 17 -200 -300 150 R 50 50 1 1 P 163 | X Pin_18 18 300 -300 150 L 50 50 1 1 P 164 | X Pin_19 19 -200 -400 150 R 50 50 1 1 P 165 | X Pin_2 2 300 500 150 L 50 50 1 1 P 166 | X Pin_20 20 300 -400 150 L 50 50 1 1 P 167 | X Pin_21 21 -200 -500 150 R 50 50 1 1 P 168 | X Pin_22 22 300 -500 150 L 50 50 1 1 P 169 | X Pin_23 23 -200 -600 150 R 50 50 1 1 P 170 | X Pin_24 24 300 -600 150 L 50 50 1 1 P 171 | X Pin_3 3 -200 400 150 R 50 50 1 1 P 172 | X Pin_4 4 300 400 150 L 50 50 1 1 P 173 | X Pin_5 5 -200 300 150 R 50 50 1 1 P 174 | X Pin_6 6 300 300 150 L 50 50 1 1 P 175 | X Pin_7 7 -200 200 150 R 50 50 1 1 P 176 | X Pin_8 8 300 200 150 L 50 50 1 1 P 177 | X Pin_9 9 -200 100 150 R 50 50 1 1 P 178 | ENDDRAW 179 | ENDDEF 180 | # 181 | # Device_CP_Small 182 | # 183 | DEF Device_CP_Small C 0 10 N N 1 F N 184 | F0 "C" 10 70 50 H V L CNN 185 | F1 "Device_CP_Small" 10 -80 50 H V L CNN 186 | F2 "" 0 0 50 H I C CNN 187 | F3 "" 0 0 50 H I C CNN 188 | $FPLIST 189 | CP_* 190 | $ENDFPLIST 191 | DRAW 192 | S -60 -12 60 -27 0 1 0 F 193 | S -60 27 60 12 0 1 0 N 194 | P 2 0 1 0 -50 60 -30 60 N 195 | P 2 0 1 0 -40 50 -40 70 N 196 | X ~ 1 0 100 73 D 50 50 1 1 P 197 | X ~ 2 0 -100 73 U 50 50 1 1 P 198 | ENDDRAW 199 | ENDDEF 200 | # 201 | # Device_C_Small 202 | # 203 | DEF Device_C_Small C 0 10 N N 1 F N 204 | F0 "C" 10 70 50 H V L CNN 205 | F1 "Device_C_Small" 10 -80 50 H V L CNN 206 | F2 "" 0 0 50 H I C CNN 207 | F3 "" 0 0 50 H I C CNN 208 | $FPLIST 209 | C_* 210 | $ENDFPLIST 211 | DRAW 212 | P 2 0 1 13 -60 -20 60 -20 N 213 | P 2 0 1 12 -60 20 60 20 N 214 | X ~ 1 0 100 80 D 50 50 1 1 P 215 | X ~ 2 0 -100 80 U 50 50 1 1 P 216 | ENDDRAW 217 | ENDDEF 218 | # 219 | # Device_Q_NPN_BEC 220 | # 221 | DEF Device_Q_NPN_BEC Q 0 0 Y N 1 F N 222 | F0 "Q" 200 50 50 H V L CNN 223 | F1 "Device_Q_NPN_BEC" 200 -50 50 H V L CNN 224 | F2 "" 200 100 50 H I C CNN 225 | F3 "" 0 0 50 H I C CNN 226 | DRAW 227 | C 50 0 111 0 1 10 N 228 | P 2 0 1 0 25 25 100 100 N 229 | P 3 0 1 0 25 -25 100 -100 100 -100 N 230 | P 3 0 1 20 25 75 25 -75 25 -75 N 231 | P 5 0 1 0 50 -70 70 -50 90 -90 50 -70 50 -70 F 232 | X B 1 -200 0 225 R 50 50 1 1 I 233 | X E 2 100 -200 100 U 50 50 1 1 P 234 | X C 3 100 200 100 D 50 50 1 1 P 235 | ENDDRAW 236 | ENDDEF 237 | # 238 | # Device_Q_PNP_BEC 239 | # 240 | DEF Device_Q_PNP_BEC Q 0 0 Y N 1 F N 241 | F0 "Q" 200 50 50 H V L CNN 242 | F1 "Device_Q_PNP_BEC" 200 -50 50 H V L CNN 243 | F2 "" 200 100 50 H I C CNN 244 | F3 "" 0 0 50 H I C CNN 245 | DRAW 246 | C 50 0 111 0 1 10 N 247 | P 2 0 1 0 25 25 100 100 N 248 | P 3 0 1 0 25 -25 100 -100 100 -100 N 249 | P 3 0 1 20 25 75 25 -75 25 -75 N 250 | P 5 0 1 0 90 -70 70 -90 50 -50 90 -70 90 -70 F 251 | X B 1 -200 0 225 R 50 50 1 1 I 252 | X E 2 100 -200 100 U 50 50 1 1 P 253 | X C 3 100 200 100 D 50 50 1 1 P 254 | ENDDRAW 255 | ENDDEF 256 | # 257 | # Device_R 258 | # 259 | DEF Device_R R 0 0 N Y 1 F N 260 | F0 "R" 80 0 50 V V C CNN 261 | F1 "Device_R" 0 0 50 V V C CNN 262 | F2 "" -70 0 50 V I C CNN 263 | F3 "" 0 0 50 H I C CNN 264 | $FPLIST 265 | R_* 266 | $ENDFPLIST 267 | DRAW 268 | S -40 -100 40 100 0 1 10 N 269 | X ~ 1 0 150 50 D 50 50 1 1 P 270 | X ~ 2 0 -150 50 U 50 50 1 1 P 271 | ENDDRAW 272 | ENDDEF 273 | # 274 | # Jumper_SolderJumper_2_Open 275 | # 276 | DEF Jumper_SolderJumper_2_Open JP 0 0 Y N 1 F N 277 | F0 "JP" 0 80 50 H V C CNN 278 | F1 "Jumper_SolderJumper_2_Open" 0 -100 50 H V C CNN 279 | F2 "" 0 0 50 H I C CNN 280 | F3 "" 0 0 50 H I C CNN 281 | $FPLIST 282 | SolderJumper*Open* 283 | $ENDFPLIST 284 | DRAW 285 | A -10 0 40 901 -901 0 1 0 N -10 40 -10 -40 286 | A -10 0 40 901 -901 0 1 0 F -10 40 -10 -40 287 | A 10 0 40 -899 899 0 1 0 N 10 -40 10 40 288 | A 10 0 40 -899 899 0 1 0 F 10 -40 10 40 289 | P 2 0 1 0 -10 40 -10 -40 N 290 | P 2 0 1 0 10 40 10 -40 N 291 | X A 1 -150 0 100 R 50 50 1 1 P 292 | X B 2 150 0 100 L 50 50 1 1 P 293 | ENDDRAW 294 | ENDDEF 295 | # 296 | # MCU_ST_STM32L4_STM32L433CBTx 297 | # 298 | DEF MCU_ST_STM32L4_STM32L433CBTx U 0 20 Y Y 1 F N 299 | F0 "U" -500 1350 50 H V L CNN 300 | F1 "MCU_ST_STM32L4_STM32L433CBTx" 300 1350 50 H V L CNN 301 | F2 "Package_QFP:LQFP-48_7x7mm_P0.5mm" -500 -1400 50 H I R CNN 302 | F3 "" 0 0 50 H I C CNN 303 | ALIAS STM32L433CCTx 304 | $FPLIST 305 | LQFP*7x7mm*P0.5mm* 306 | $ENDFPLIST 307 | DRAW 308 | S -500 -1400 500 1300 0 1 10 f 309 | X VBAT 1 -200 1400 100 D 50 50 1 1 W 310 | X PA0 10 600 200 100 L 50 50 1 1 B 311 | X PA1 11 600 100 100 L 50 50 1 1 B 312 | X PA2 12 600 0 100 L 50 50 1 1 B 313 | X PA3 13 600 -100 100 L 50 50 1 1 B 314 | X PA4 14 600 -200 100 L 50 50 1 1 B 315 | X PA5 15 600 -300 100 L 50 50 1 1 B 316 | X PA6 16 600 -400 100 L 50 50 1 1 B 317 | X PA7 17 600 -500 100 L 50 50 1 1 B 318 | X PB0 18 -600 200 100 R 50 50 1 1 B 319 | X PB1 19 -600 100 100 R 50 50 1 1 B 320 | X PC13 2 -600 600 100 R 50 50 1 1 B 321 | X PB2 20 -600 0 100 R 50 50 1 1 B 322 | X PB10 21 -600 -800 100 R 50 50 1 1 B 323 | X PB11 22 -600 -900 100 R 50 50 1 1 B 324 | X VSS 23 -200 -1500 100 U 50 50 1 1 W 325 | X VDD 24 -100 1400 100 D 50 50 1 1 W 326 | X PB12 25 -600 -1000 100 R 50 50 1 1 B 327 | X PB13 26 -600 -1100 100 R 50 50 1 1 B 328 | X PB14 27 -600 -1200 100 R 50 50 1 1 B 329 | X PB15 28 -600 -1300 100 R 50 50 1 1 B 330 | X PA8 29 600 -600 100 L 50 50 1 1 B 331 | X PC14 3 -600 500 100 R 50 50 1 1 B 332 | X PA9 30 600 -700 100 L 50 50 1 1 B 333 | X PA10 31 600 -800 100 L 50 50 1 1 B 334 | X PA11 32 600 -900 100 L 50 50 1 1 B 335 | X PA12 33 600 -1000 100 L 50 50 1 1 B 336 | X PA13 34 600 -1100 100 L 50 50 1 1 B 337 | X VSS 35 -100 -1500 100 U 50 50 1 1 W 338 | X VDDUSB 36 200 1400 100 D 50 50 1 1 W 339 | X PA14 37 600 -1200 100 L 50 50 1 1 B 340 | X PA15 38 600 -1300 100 L 50 50 1 1 B 341 | X PB3 39 -600 -100 100 R 50 50 1 1 B 342 | X PC15 4 -600 400 100 R 50 50 1 1 B 343 | X PB4 40 -600 -200 100 R 50 50 1 1 B 344 | X PB5 41 -600 -300 100 R 50 50 1 1 B 345 | X PB6 42 -600 -400 100 R 50 50 1 1 B 346 | X PB7 43 -600 -500 100 R 50 50 1 1 B 347 | X PH3 44 -600 800 100 R 50 50 1 1 B 348 | X PB8 45 -600 -600 100 R 50 50 1 1 B 349 | X PB9 46 -600 -700 100 R 50 50 1 1 B 350 | X VSS 47 0 -1500 100 U 50 50 1 1 W 351 | X VDD 48 0 1400 100 D 50 50 1 1 W 352 | X PH0 5 -600 1000 100 R 50 50 1 1 I 353 | X PH1 6 -600 900 100 R 50 50 1 1 I 354 | X NRST 7 -600 1200 100 R 50 50 1 1 I 355 | X VSSA 8 100 -1500 100 U 50 50 1 1 W 356 | X VDDA 9 100 1400 100 D 50 50 1 1 W 357 | ENDDRAW 358 | ENDDEF 359 | # 360 | # Regulator_Linear_MIC5205-3.3YM5 361 | # 362 | DEF Regulator_Linear_MIC5205-3.3YM5 U 0 10 Y Y 1 F N 363 | F0 "U" -150 225 50 H V C CNN 364 | F1 "Regulator_Linear_MIC5205-3.3YM5" 0 225 50 H V L CNN 365 | F2 "Package_TO_SOT_SMD:SOT-23-5" 0 325 50 H I C CNN 366 | F3 "" 0 0 50 H I C CNN 367 | ALIAS AP131-18 AP131-20 AP131-25 AP131-28 AP131-29 AP131-30 AP131-33 AP131-35 MIC5205-2.5YM5 MIC5205-2.7YM5 MIC5205-2.8YM5 MIC5205-2.85YM5 MIC5205-2.9YM5 MIC5205-3.0YM5 MIC5205-3.1YM5 MIC5205-3.2YM5 MIC5205-3.3YM5 MIC5205-3.6YM5 MIC5205-3.8YM5 MIC5205-4.0YM5 MIC5205-5.0YM5 MIC5219-2.5YM5 MIC5219-2.6YM5 MIC5219-2.7YM5 MIC5219-2.8YM5 MIC5219-2.85YM5 MIC5219-2.9YM5 MIC5219-3.0YM5 MIC5219-3.1YM5 MIC5219-3.3YM5 MIC5219-3.6YM5 MIC5219-5.0YM5 SPX3819M5-L-1-2 SPX3819M5-L-1-5 SPX3819M5-L-1-8 SPX3819M5-L-2-5 SPX3819M5-L-3-0 SPX3819M5-L-3-3 SPX3819M5-L-5-0 368 | $FPLIST 369 | SOT?23* 370 | $ENDFPLIST 371 | DRAW 372 | S -200 175 200 -200 0 1 10 f 373 | X IN 1 -300 100 100 R 50 50 1 1 W 374 | X GND 2 0 -300 100 U 50 50 1 1 W 375 | X EN 3 -300 0 100 R 50 50 1 1 I 376 | X BP 4 300 0 100 L 50 50 1 1 I 377 | X OUT 5 300 100 100 L 50 50 1 1 w 378 | ENDDRAW 379 | ENDDEF 380 | # 381 | # power_+3V3 382 | # 383 | DEF power_+3V3 #PWR 0 0 Y Y 1 F P 384 | F0 "#PWR" 0 -150 50 H I C CNN 385 | F1 "power_+3V3" 0 140 50 H V C CNN 386 | F2 "" 0 0 50 H I C CNN 387 | F3 "" 0 0 50 H I C CNN 388 | ALIAS +3.3V 389 | DRAW 390 | P 2 0 1 0 -30 50 0 100 N 391 | P 2 0 1 0 0 0 0 100 N 392 | P 2 0 1 0 0 100 30 50 N 393 | X +3V3 1 0 0 0 U 50 50 1 1 W N 394 | ENDDRAW 395 | ENDDEF 396 | # 397 | # power_+5V 398 | # 399 | DEF power_+5V #PWR 0 0 Y Y 1 F P 400 | F0 "#PWR" 0 -150 50 H I C CNN 401 | F1 "power_+5V" 0 140 50 H V C CNN 402 | F2 "" 0 0 50 H I C CNN 403 | F3 "" 0 0 50 H I C CNN 404 | DRAW 405 | P 2 0 1 0 -30 50 0 100 N 406 | P 2 0 1 0 0 0 0 100 N 407 | P 2 0 1 0 0 100 30 50 N 408 | X +5V 1 0 0 0 U 50 50 1 1 W N 409 | ENDDRAW 410 | ENDDEF 411 | # 412 | # power_GND 413 | # 414 | DEF power_GND #PWR 0 0 Y Y 1 F P 415 | F0 "#PWR" 0 -250 50 H I C CNN 416 | F1 "power_GND" 0 -150 50 H V C CNN 417 | F2 "" 0 0 50 H I C CNN 418 | F3 "" 0 0 50 H I C CNN 419 | DRAW 420 | P 6 0 1 0 0 0 0 -50 50 -50 0 -100 -50 -50 0 -50 N 421 | X GND 1 0 0 0 D 50 50 1 1 W N 422 | ENDDRAW 423 | ENDDEF 424 | # 425 | # power_PWR_FLAG 426 | # 427 | DEF power_PWR_FLAG #FLG 0 0 N N 1 F P 428 | F0 "#FLG" 0 75 50 H I C CNN 429 | F1 "power_PWR_FLAG" 0 150 50 H V C CNN 430 | F2 "" 0 0 50 H I C CNN 431 | F3 "" 0 0 50 H I C CNN 432 | DRAW 433 | P 6 0 1 0 0 0 0 50 -40 75 0 100 40 75 0 50 N 434 | X pwr 1 0 0 0 U 50 50 0 0 w 435 | ENDDRAW 436 | ENDDEF 437 | # 438 | #End Library 439 | -------------------------------------------------------------------------------- /wasd-code-upgrade.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "board_outline_line_width": 0.05, 7 | "copper_line_width": 0.2, 8 | "copper_text_italic": false, 9 | "copper_text_size_h": 1.5, 10 | "copper_text_size_v": 1.5, 11 | "copper_text_thickness": 0.3, 12 | "copper_text_upright": true, 13 | "courtyard_line_width": 0.05, 14 | "other_line_width": 0.15, 15 | "other_text_italic": false, 16 | "other_text_size_h": 1.0, 17 | "other_text_size_v": 1.0, 18 | "other_text_thickness": 0.15, 19 | "other_text_upright": true, 20 | "silk_line_width": 0.12, 21 | "silk_text_italic": false, 22 | "silk_text_size_h": 1.0, 23 | "silk_text_size_v": 1.0, 24 | "silk_text_thickness": 0.15, 25 | "silk_text_upright": true 26 | }, 27 | "diff_pair_dimensions": [ 28 | { 29 | "gap": 0.25, 30 | "via_gap": 0.25, 31 | "width": 0.2 32 | } 33 | ], 34 | "drc_exclusions": [], 35 | "rule_severitieslegacy_courtyards_overlap": true, 36 | "rule_severitieslegacy_no_courtyard_defined": false, 37 | "rules": { 38 | "allow_blind_buried_vias": false, 39 | "allow_microvias": false, 40 | "min_hole_to_hole": 0.25, 41 | "min_microvia_diameter": 0.2, 42 | "min_microvia_drill": 0.09999999999999999, 43 | "min_through_hole_diameter": 0.3, 44 | "min_track_width": 0.09999999999999999, 45 | "min_via_diameter": 0.4, 46 | "solder_mask_clearance": 0.05, 47 | "solder_mask_min_width": 0.0, 48 | "solder_paste_clearance": 0.0, 49 | "solder_paste_margin_ratio": -0.0 50 | }, 51 | "track_widths": [ 52 | 0.2, 53 | 0.3, 54 | 0.5 55 | ], 56 | "via_dimensions": [ 57 | { 58 | "diameter": 0.8, 59 | "drill": 0.4 60 | } 61 | ] 62 | }, 63 | "ipc2581": { 64 | "dist": "", 65 | "distpn": "", 66 | "internal_id": "", 67 | "mfg": "", 68 | "mpn": "" 69 | }, 70 | "layer_presets": [], 71 | "viewports": [] 72 | }, 73 | "boards": [], 74 | "cvpcb": { 75 | "equivalence_files": [] 76 | }, 77 | "erc": { 78 | "erc_exclusions": [], 79 | "meta": { 80 | "version": 0 81 | }, 82 | "pin_map": [ 83 | [ 84 | 0, 85 | 0, 86 | 0, 87 | 0, 88 | 0, 89 | 0, 90 | 1, 91 | 0, 92 | 0, 93 | 0, 94 | 0, 95 | 2 96 | ], 97 | [ 98 | 0, 99 | 2, 100 | 0, 101 | 1, 102 | 0, 103 | 0, 104 | 1, 105 | 0, 106 | 2, 107 | 2, 108 | 2, 109 | 2 110 | ], 111 | [ 112 | 0, 113 | 0, 114 | 0, 115 | 0, 116 | 0, 117 | 0, 118 | 1, 119 | 0, 120 | 1, 121 | 0, 122 | 1, 123 | 2 124 | ], 125 | [ 126 | 0, 127 | 1, 128 | 0, 129 | 0, 130 | 0, 131 | 0, 132 | 1, 133 | 1, 134 | 2, 135 | 1, 136 | 1, 137 | 2 138 | ], 139 | [ 140 | 0, 141 | 0, 142 | 0, 143 | 0, 144 | 0, 145 | 0, 146 | 1, 147 | 0, 148 | 0, 149 | 0, 150 | 0, 151 | 2 152 | ], 153 | [ 154 | 0, 155 | 0, 156 | 0, 157 | 0, 158 | 0, 159 | 0, 160 | 0, 161 | 0, 162 | 0, 163 | 0, 164 | 0, 165 | 2 166 | ], 167 | [ 168 | 1, 169 | 1, 170 | 1, 171 | 1, 172 | 1, 173 | 0, 174 | 1, 175 | 1, 176 | 1, 177 | 1, 178 | 1, 179 | 2 180 | ], 181 | [ 182 | 0, 183 | 0, 184 | 0, 185 | 1, 186 | 0, 187 | 0, 188 | 1, 189 | 0, 190 | 0, 191 | 0, 192 | 0, 193 | 2 194 | ], 195 | [ 196 | 0, 197 | 2, 198 | 1, 199 | 2, 200 | 0, 201 | 0, 202 | 1, 203 | 0, 204 | 2, 205 | 2, 206 | 2, 207 | 2 208 | ], 209 | [ 210 | 0, 211 | 2, 212 | 0, 213 | 1, 214 | 0, 215 | 0, 216 | 1, 217 | 0, 218 | 2, 219 | 0, 220 | 0, 221 | 2 222 | ], 223 | [ 224 | 0, 225 | 2, 226 | 1, 227 | 1, 228 | 0, 229 | 0, 230 | 1, 231 | 0, 232 | 2, 233 | 0, 234 | 0, 235 | 2 236 | ], 237 | [ 238 | 2, 239 | 2, 240 | 2, 241 | 2, 242 | 2, 243 | 2, 244 | 2, 245 | 2, 246 | 2, 247 | 2, 248 | 2, 249 | 2 250 | ] 251 | ], 252 | "rule_severities": { 253 | "bus_definition_conflict": "error", 254 | "bus_entry_needed": "error", 255 | "bus_to_bus_conflict": "error", 256 | "bus_to_net_conflict": "error", 257 | "conflicting_netclasses": "error", 258 | "different_unit_footprint": "error", 259 | "different_unit_net": "error", 260 | "duplicate_reference": "error", 261 | "duplicate_sheet_names": "error", 262 | "endpoint_off_grid": "warning", 263 | "extra_units": "error", 264 | "global_label_dangling": "warning", 265 | "hier_label_mismatch": "error", 266 | "label_dangling": "error", 267 | "lib_symbol_issues": "warning", 268 | "missing_bidi_pin": "warning", 269 | "missing_input_pin": "warning", 270 | "missing_power_pin": "error", 271 | "missing_unit": "warning", 272 | "multiple_net_names": "warning", 273 | "net_not_bus_member": "warning", 274 | "no_connect_connected": "warning", 275 | "no_connect_dangling": "warning", 276 | "pin_not_connected": "error", 277 | "pin_not_driven": "error", 278 | "pin_to_pin": "warning", 279 | "power_pin_not_driven": "error", 280 | "similar_labels": "warning", 281 | "simulation_model_issue": "ignore", 282 | "unannotated": "error", 283 | "unit_value_mismatch": "error", 284 | "unresolved_variable": "error", 285 | "wire_dangling": "error" 286 | } 287 | }, 288 | "libraries": { 289 | "pinned_footprint_libs": [], 290 | "pinned_symbol_libs": [] 291 | }, 292 | "meta": { 293 | "filename": "wasd-code-upgrade.kicad_pro", 294 | "version": 1 295 | }, 296 | "net_settings": { 297 | "classes": [ 298 | { 299 | "bus_width": 12, 300 | "clearance": 0.2, 301 | "diff_pair_gap": 0.25, 302 | "diff_pair_via_gap": 0.25, 303 | "diff_pair_width": 0.2, 304 | "line_style": 0, 305 | "microvia_diameter": 0.3, 306 | "microvia_drill": 0.1, 307 | "name": "Default", 308 | "pcb_color": "rgba(0, 0, 0, 0.000)", 309 | "schematic_color": "rgba(0, 0, 0, 0.000)", 310 | "track_width": 0.2, 311 | "via_diameter": 0.6, 312 | "via_drill": 0.3, 313 | "wire_width": 6 314 | } 315 | ], 316 | "meta": { 317 | "version": 3 318 | }, 319 | "net_colors": null, 320 | "netclass_assignments": null, 321 | "netclass_patterns": [] 322 | }, 323 | "pcbnew": { 324 | "last_paths": { 325 | "gencad": "", 326 | "idf": "", 327 | "netlist": "", 328 | "plot": "", 329 | "pos_files": "", 330 | "specctra_dsn": "", 331 | "step": "", 332 | "svg": "", 333 | "vrml": "" 334 | }, 335 | "page_layout_descr_file": "" 336 | }, 337 | "schematic": { 338 | "annotate_start_num": 0, 339 | "bom_fmt_presets": [], 340 | "bom_fmt_settings": { 341 | "field_delimiter": ",", 342 | "keep_line_breaks": false, 343 | "keep_tabs": false, 344 | "name": "CSV", 345 | "ref_delimiter": ",", 346 | "ref_range_delimiter": "", 347 | "string_delimiter": "\"" 348 | }, 349 | "bom_presets": [], 350 | "bom_settings": { 351 | "exclude_dnp": false, 352 | "fields_ordered": [ 353 | { 354 | "group_by": false, 355 | "label": "Reference", 356 | "name": "Reference", 357 | "show": true 358 | }, 359 | { 360 | "group_by": true, 361 | "label": "Value", 362 | "name": "Value", 363 | "show": true 364 | }, 365 | { 366 | "group_by": false, 367 | "label": "Datasheet", 368 | "name": "Datasheet", 369 | "show": true 370 | }, 371 | { 372 | "group_by": false, 373 | "label": "Footprint", 374 | "name": "Footprint", 375 | "show": true 376 | }, 377 | { 378 | "group_by": false, 379 | "label": "Qty", 380 | "name": "${QUANTITY}", 381 | "show": true 382 | }, 383 | { 384 | "group_by": true, 385 | "label": "DNP", 386 | "name": "${DNP}", 387 | "show": true 388 | } 389 | ], 390 | "filter_string": "", 391 | "group_symbols": true, 392 | "name": "Grouped By Value", 393 | "sort_asc": true, 394 | "sort_field": "Reference" 395 | }, 396 | "connection_grid_size": 50.0, 397 | "drawing": { 398 | "dashed_lines_dash_length_ratio": 12.0, 399 | "dashed_lines_gap_length_ratio": 3.0, 400 | "default_line_thickness": 6.0, 401 | "default_text_size": 50.0, 402 | "field_names": [], 403 | "intersheets_ref_own_page": false, 404 | "intersheets_ref_prefix": "", 405 | "intersheets_ref_short": false, 406 | "intersheets_ref_show": false, 407 | "intersheets_ref_suffix": "", 408 | "junction_size_choice": 3, 409 | "label_size_ratio": 0.25, 410 | "operating_point_overlay_i_precision": 3, 411 | "operating_point_overlay_i_range": "~A", 412 | "operating_point_overlay_v_precision": 3, 413 | "operating_point_overlay_v_range": "~V", 414 | "overbar_offset_ratio": 1.23, 415 | "pin_symbol_size": 0.0, 416 | "text_offset_ratio": 0.08 417 | }, 418 | "legacy_lib_dir": "", 419 | "legacy_lib_list": [], 420 | "meta": { 421 | "version": 1 422 | }, 423 | "net_format_name": "", 424 | "page_layout_descr_file": "", 425 | "plot_directory": "", 426 | "spice_current_sheet_as_root": false, 427 | "spice_external_command": "spice \"%I\"", 428 | "spice_model_current_sheet_as_root": true, 429 | "spice_save_all_currents": false, 430 | "spice_save_all_dissipations": false, 431 | "spice_save_all_voltages": false, 432 | "subpart_first_id": 65, 433 | "subpart_id_separator": 0 434 | }, 435 | "sheets": [ 436 | [ 437 | "963c6e31-b807-46b7-8836-6e5bc4a4d809", 438 | "Root" 439 | ] 440 | ], 441 | "text_variables": {} 442 | } 443 | --------------------------------------------------------------------------------