├── .cargo └── config ├── .github ├── FUNDING.yml ├── bors.toml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── firmware ├── Cargo.toml ├── Embed.toml ├── build.rs ├── memory.x ├── openocd.cfg ├── openocd.gdb └── src │ ├── app.rs │ ├── dap.rs │ ├── jtag.rs │ ├── main.rs │ ├── swd.rs │ ├── usb │ ├── dap_v1.rs │ ├── dap_v2.rs │ ├── dfu.rs │ ├── mod.rs │ └── winusb.rs │ └── vcp.rs └── hs-probe-bsp ├── Cargo.toml └── src ├── bootload.rs ├── delay.rs ├── dma.rs ├── gpio.rs ├── lib.rs ├── otg_hs.rs ├── rcc.rs ├── spi.rs └── uart.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | runner = "arm-none-eabi-gdb -x openocd.gdb" 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x", 5 | ] 6 | 7 | [build] 8 | target = "thumbv7em-none-eabihf" 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [probe-rs] 2 | -------------------------------------------------------------------------------- /.github/bors.toml: -------------------------------------------------------------------------------- 1 | required_approvals = 1 2 | block_labels = ["wip"] 3 | delete_merged_branches = true 4 | status = ["ci-linux (stable)"] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: cargo 8 | directory: "/firmware" 9 | schedule: 10 | interval: daily 11 | open-pull-requests-limit: 10 12 | - package-ecosystem: cargo 13 | directory: "/hs-probe-bsp" 14 | schedule: 15 | interval: daily 16 | open-pull-requests-limit: 10 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | ci-linux: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | # All generated code should be running on stable now 13 | rust: [stable] 14 | 15 | include: 16 | # Test nightly but don't fail 17 | - rust: nightly 18 | experimental: true 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | with: 24 | submodules: 'recursive' 25 | 26 | - name: Install Rust 27 | uses: actions-rs/toolchain@v1 28 | with: 29 | profile: minimal 30 | toolchain: ${{ matrix.rust }} 31 | target: thumbv7em-none-eabihf 32 | override: true 33 | 34 | - name: Build firmware 35 | working-directory: firmware 36 | run: cargo build --release 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | /.idea 4 | .gdb_history 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aligned" 7 | version = "0.3.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "3a785a543aea40f5e4e2e93bb2655d31bc21bb391fff65697150973e383f16bb" 10 | dependencies = [ 11 | "as-slice", 12 | ] 13 | 14 | [[package]] 15 | name = "as-slice" 16 | version = "0.1.5" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" 19 | dependencies = [ 20 | "generic-array 0.12.4", 21 | "generic-array 0.13.3", 22 | "generic-array 0.14.7", 23 | "stable_deref_trait", 24 | ] 25 | 26 | [[package]] 27 | name = "bare-metal" 28 | version = "0.2.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 31 | dependencies = [ 32 | "rustc_version", 33 | ] 34 | 35 | [[package]] 36 | name = "bitfield" 37 | version = "0.13.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 40 | 41 | [[package]] 42 | name = "cortex-m" 43 | version = "0.6.7" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "9075300b07c6a56263b9b582c214d0ff037b00d45ec9fde1cc711490c56f1bb9" 46 | dependencies = [ 47 | "aligned", 48 | "bare-metal", 49 | "bitfield", 50 | "cortex-m 0.7.7", 51 | "volatile-register", 52 | ] 53 | 54 | [[package]] 55 | name = "cortex-m" 56 | version = "0.7.7" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" 59 | dependencies = [ 60 | "bare-metal", 61 | "bitfield", 62 | "embedded-hal", 63 | "volatile-register", 64 | ] 65 | 66 | [[package]] 67 | name = "cortex-m-rt" 68 | version = "0.6.15" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "454f278bf469e2de0a4d22ea019d169d8944f86957c8207a39e3f66c32be2fc6" 71 | dependencies = [ 72 | "cortex-m-rt-macros", 73 | "r0", 74 | ] 75 | 76 | [[package]] 77 | name = "cortex-m-rt-macros" 78 | version = "0.6.15" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "c8e3aa52243e26f5922fa522b0814019e0c98fc567e2756d715dce7ad7a81f49" 81 | dependencies = [ 82 | "proc-macro2", 83 | "quote", 84 | "syn", 85 | ] 86 | 87 | [[package]] 88 | name = "derivative" 89 | version = "2.2.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 92 | dependencies = [ 93 | "proc-macro2", 94 | "quote", 95 | "syn", 96 | ] 97 | 98 | [[package]] 99 | name = "embedded-hal" 100 | version = "0.2.7" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 103 | dependencies = [ 104 | "nb 0.1.3", 105 | "void", 106 | ] 107 | 108 | [[package]] 109 | name = "generic-array" 110 | version = "0.12.4" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 113 | dependencies = [ 114 | "typenum", 115 | ] 116 | 117 | [[package]] 118 | name = "generic-array" 119 | version = "0.13.3" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" 122 | dependencies = [ 123 | "typenum", 124 | ] 125 | 126 | [[package]] 127 | name = "generic-array" 128 | version = "0.14.7" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 131 | dependencies = [ 132 | "typenum", 133 | "version_check", 134 | ] 135 | 136 | [[package]] 137 | name = "git-version" 138 | version = "0.3.5" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "f6b0decc02f4636b9ccad390dcbe77b722a77efedfa393caf8379a51d5c61899" 141 | dependencies = [ 142 | "git-version-macro", 143 | "proc-macro-hack", 144 | ] 145 | 146 | [[package]] 147 | name = "git-version-macro" 148 | version = "0.3.5" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f" 151 | dependencies = [ 152 | "proc-macro-hack", 153 | "proc-macro2", 154 | "quote", 155 | "syn", 156 | ] 157 | 158 | [[package]] 159 | name = "hs-probe-bsp" 160 | version = "0.1.0" 161 | dependencies = [ 162 | "cortex-m 0.7.7", 163 | "stm32ral", 164 | "synopsys-usb-otg", 165 | ] 166 | 167 | [[package]] 168 | name = "hs-probe-firmware" 169 | version = "0.1.0" 170 | dependencies = [ 171 | "cortex-m-rt", 172 | "git-version", 173 | "hs-probe-bsp", 174 | "num_enum", 175 | "panic-rtt-target", 176 | "rtt-target 0.2.2", 177 | "stm32-device-signature", 178 | "usb-device", 179 | "usbd-serial", 180 | ] 181 | 182 | [[package]] 183 | name = "nb" 184 | version = "0.1.3" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 187 | dependencies = [ 188 | "nb 1.1.0", 189 | ] 190 | 191 | [[package]] 192 | name = "nb" 193 | version = "1.1.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 196 | 197 | [[package]] 198 | name = "num_enum" 199 | version = "0.4.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" 202 | dependencies = [ 203 | "derivative", 204 | "num_enum_derive", 205 | ] 206 | 207 | [[package]] 208 | name = "num_enum_derive" 209 | version = "0.4.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" 212 | dependencies = [ 213 | "proc-macro-crate", 214 | "proc-macro2", 215 | "quote", 216 | "syn", 217 | ] 218 | 219 | [[package]] 220 | name = "panic-rtt-target" 221 | version = "0.1.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "0d6ab67bc881453e4c90f958c657c1303670ea87bc1a16e87fd71a40f656dce9" 224 | dependencies = [ 225 | "cortex-m 0.7.7", 226 | "rtt-target 0.3.1", 227 | ] 228 | 229 | [[package]] 230 | name = "proc-macro-crate" 231 | version = "0.1.5" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 234 | dependencies = [ 235 | "toml", 236 | ] 237 | 238 | [[package]] 239 | name = "proc-macro-hack" 240 | version = "0.5.20+deprecated" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 243 | 244 | [[package]] 245 | name = "proc-macro2" 246 | version = "1.0.66" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 249 | dependencies = [ 250 | "unicode-ident", 251 | ] 252 | 253 | [[package]] 254 | name = "quote" 255 | version = "1.0.32" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 258 | dependencies = [ 259 | "proc-macro2", 260 | ] 261 | 262 | [[package]] 263 | name = "r0" 264 | version = "0.2.2" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 267 | 268 | [[package]] 269 | name = "rtt-target" 270 | version = "0.2.2" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "0869b4c5b6a6d8c5583fc473f9eb3423a170f77626b8c8a7fb18eddcda5770e2" 273 | dependencies = [ 274 | "cortex-m 0.6.7", 275 | "ufmt-write", 276 | ] 277 | 278 | [[package]] 279 | name = "rtt-target" 280 | version = "0.3.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "065d6058bb1204f51a562a67209e1817cf714759d5cf845aa45c75fa7b0b9d9b" 283 | dependencies = [ 284 | "ufmt-write", 285 | ] 286 | 287 | [[package]] 288 | name = "rustc_version" 289 | version = "0.2.3" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 292 | dependencies = [ 293 | "semver", 294 | ] 295 | 296 | [[package]] 297 | name = "semver" 298 | version = "0.9.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 301 | dependencies = [ 302 | "semver-parser", 303 | ] 304 | 305 | [[package]] 306 | name = "semver-parser" 307 | version = "0.7.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 310 | 311 | [[package]] 312 | name = "serde" 313 | version = "1.0.176" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc" 316 | 317 | [[package]] 318 | name = "stable_deref_trait" 319 | version = "1.2.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 322 | 323 | [[package]] 324 | name = "stm32-device-signature" 325 | version = "0.3.3" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "e262c8060b3ea4cc462d5b3869147e300357917bc3255dd9f0fe36ff4195bcf0" 328 | dependencies = [ 329 | "cortex-m 0.6.7", 330 | ] 331 | 332 | [[package]] 333 | name = "stm32ral" 334 | version = "0.8.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "48ae31099177ef1bbdeee6f1b9a38d869aa6c8c3232cf248c39642875d42c532" 337 | dependencies = [ 338 | "cortex-m 0.7.7", 339 | "cortex-m-rt", 340 | ] 341 | 342 | [[package]] 343 | name = "syn" 344 | version = "1.0.109" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 347 | dependencies = [ 348 | "proc-macro2", 349 | "quote", 350 | "unicode-ident", 351 | ] 352 | 353 | [[package]] 354 | name = "synopsys-usb-otg" 355 | version = "0.3.2" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "678f3707a7b1fd4863023292c42f73c6bab0e9b0096f41ae612d1af0ff221b45" 358 | dependencies = [ 359 | "cortex-m 0.7.7", 360 | "embedded-hal", 361 | "usb-device", 362 | "vcell", 363 | ] 364 | 365 | [[package]] 366 | name = "toml" 367 | version = "0.5.11" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 370 | dependencies = [ 371 | "serde", 372 | ] 373 | 374 | [[package]] 375 | name = "typenum" 376 | version = "1.16.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 379 | 380 | [[package]] 381 | name = "ufmt-write" 382 | version = "0.1.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" 385 | 386 | [[package]] 387 | name = "unicode-ident" 388 | version = "1.0.11" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 391 | 392 | [[package]] 393 | name = "usb-device" 394 | version = "0.2.9" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" 397 | 398 | [[package]] 399 | name = "usbd-serial" 400 | version = "0.1.1" 401 | source = "git+https://github.com/Disasm/usbd-serial?rev=827116e5fab66a51ab7d68774419b0f187b24b90#827116e5fab66a51ab7d68774419b0f187b24b90" 402 | dependencies = [ 403 | "embedded-hal", 404 | "nb 0.1.3", 405 | "usb-device", 406 | ] 407 | 408 | [[package]] 409 | name = "vcell" 410 | version = "0.1.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 413 | 414 | [[package]] 415 | name = "version_check" 416 | version = "0.9.4" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 419 | 420 | [[package]] 421 | name = "void" 422 | version = "1.0.2" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 425 | 426 | [[package]] 427 | name = "volatile-register" 428 | version = "0.2.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" 431 | dependencies = [ 432 | "vcell", 433 | ] 434 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "firmware", 4 | "hs-probe-bsp", 5 | ] 6 | 7 | [patch.crates-io] 8 | usbd-serial = { git = "https://github.com/Disasm/usbd-serial", rev = "827116e5fab66a51ab7d68774419b0f187b24b90" } 9 | 10 | [profile.release] 11 | debug = true 12 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Adam Greig 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hs-probe-firmware 2 | 3 | A CMSIS-DAP firmware for [hs-probe](https://github.com/korken89/hs-probe). This includes support 4 | for DAPv1 and DAPv2 over high-speed (480 MBit/s) USB 2.0. 5 | 6 | ## Building the firmware 7 | 8 | Make sure your toolchain has support for the microcontroller archtiecture: 9 | ``` 10 | rustup target install thumbv7em-none-eabihf 11 | ``` 12 | 13 | Then you should be ready to build the firmware: 14 | ``` 15 | cargo build --release 16 | ``` 17 | 18 | ## Loading the firmware 19 | 20 | The HS-Probe supports `dfu-util` and can have its firmware loaded via it. To 21 | generate the bin, install 22 | [cargo-binutils](https://github.com/rust-embedded/cargo-binutils) and run: 23 | 24 | ```console 25 | cargo objcopy --release -- -O binary firmware.bin 26 | ``` 27 | 28 | And load it into the HS-Probe with: 29 | 30 | ```console 31 | dfu-util -a 0 -s 0x08000000:leave -D firmware.bin 32 | ``` 33 | 34 | It will automatically restart into DFU mode and load the firmware. 35 | 36 | ### Windows 37 | 38 | Under Windows, the firmware update does not work out of the box. The first time that `dfu-util` is used, the 39 | probe will reboot into the bootloader, but the driver for the `STM32 BOOTLOADER` will be missing. 40 | 41 | To fix this, [Zadig](https://zadig.akeo.ie/) should be used to install the `WinUSB` driver for the `STM32 BOOTLOADER` device. 42 | Afterwards, `dfu-util` should be able to update the firmware. 43 | 44 | ### Recovery 45 | 46 | If the probe does not appear as a USB device, it is possible to force it to boot into the bootloader. For this, the two pins indicated 47 | by the white line on top of the PCB need to be shorted together, while pluggin in the probe. 48 | 49 | If succesful, a `STM32 BOOTLOADER` device will appear which can be used to update the probe firmware using `dfu-util`. 50 | 51 | 52 | ## Feature flags 53 | 54 | The following feature flags exists: 55 | 56 | * `turbo`, this will the MCU speed to 216 MHz instead of the current default of 72 MHz. 57 | * ... 58 | 59 | To build with features, the following command is used: 60 | 61 | ```console 62 | cargo build --release --features turbo,...,... 63 | ``` 64 | 65 | ## Special thanks 66 | 67 | We would like to give special thanks to: 68 | 69 | - [Vadim Kaushan (@disasm)](https://github.com/disasm) for the USB implementation and helping bring the probe up. 70 | - [Adam Greig (@adamgreig)](https://github.com/adamgreig) for the SWD implementation and helping bring the probe up. 71 | - [Emil Fresk (@korken89)](https://github.com/korken89) for the hardware design. 72 | - [Noah Huesser (@yatekii)](https://github.com/yatekii) for the `probe-rs` initiative and helping bring the probe up. 73 | 74 | ## Licence 75 | 76 | Firmware is licensed under either of 77 | 78 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 79 | http://www.apache.org/licenses/LICENSE-2.0) 80 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 81 | 82 | at your option. 83 | -------------------------------------------------------------------------------- /firmware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hs-probe-firmware" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cortex-m-rt = "0.6.12" 9 | rtt-target = { version = "0.2.0", features = ["cortex-m"] } 10 | panic-rtt-target = { version = "0.1.0", features = ["cortex-m"] } 11 | hs-probe-bsp = { path = "../hs-probe-bsp", features = ["rt"] } 12 | usb-device = { version = "0.2.8", features = ["control-buffer-256"] } 13 | usbd-serial = { version = "0.1.1", features = ["high-speed"] } 14 | stm32-device-signature = { version = "0.3.1", features = ["stm32f72x"] } 15 | num_enum = { version = "0.4.3", default-features = false } 16 | git-version = "0.3.4" 17 | 18 | [features] 19 | turbo = [] 20 | -------------------------------------------------------------------------------- /firmware/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.probe] 2 | # usb_vid = "1209" 3 | # usb_pid = "da42" 4 | protocol = "Swd" 5 | 6 | [default.flashing] 7 | enabled = true 8 | 9 | [default.reset] 10 | enabled = true 11 | halt_afterwards = false 12 | 13 | [default.general] 14 | chip = "STM32F723IEKx" 15 | 16 | [default.rtt] 17 | enabled = true 18 | show_timestamps = true 19 | -------------------------------------------------------------------------------- /firmware/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | fn main() { 6 | // Put the linker script somewhere the linker can find it 7 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 8 | fs::copy("memory.x", out_dir.join("memory.x")).unwrap(); 9 | println!("cargo:rustc-link-search={}", out_dir.display()); 10 | println!("cargo:rerun-if-changed=memory.x"); 11 | } 12 | -------------------------------------------------------------------------------- /firmware/memory.x: -------------------------------------------------------------------------------- 1 | /* STM32F723IEK6 */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 512k 5 | RAM : ORIGIN = 0x20000000, LENGTH = 256k 6 | } 7 | -------------------------------------------------------------------------------- /firmware/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/cmsis-dap.cfg] 2 | source [find target/stm32f7x.cfg] 3 | targets 4 | -------------------------------------------------------------------------------- /firmware/openocd.gdb: -------------------------------------------------------------------------------- 1 | set history save on 2 | set confirm off 3 | target extended-remote :3333 4 | monitor arm semihosting enable 5 | monitor reset halt 6 | 7 | # print demangled symbols 8 | set print asm-demangle on 9 | 10 | # set backtrace limit to not have infinite backtrace loops 11 | set backtrace limit 32 12 | 13 | # detect unhandled exceptions, hard faults and panics 14 | #break DefaultHandler 15 | #break HardFault 16 | #break rust_begin_unwind 17 | 18 | #break main 19 | 20 | load 21 | continue 22 | # quit 23 | -------------------------------------------------------------------------------- /firmware/src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::dap::DAPVersion; 2 | use crate::vcp::VcpConfig; 3 | use crate::{DAP1_PACKET_SIZE, DAP2_PACKET_SIZE, VCP_PACKET_SIZE}; 4 | use hs_probe_bsp as bsp; 5 | use hs_probe_bsp::rcc::CoreFrequency; 6 | 7 | #[allow(clippy::large_enum_variant)] 8 | pub enum Request { 9 | Suspend, 10 | DAP1Command(([u8; DAP1_PACKET_SIZE as usize], usize)), 11 | DAP2Command(([u8; DAP2_PACKET_SIZE as usize], usize)), 12 | VCPPacket(([u8; VCP_PACKET_SIZE as usize], usize)), 13 | } 14 | 15 | pub struct App<'a> { 16 | rcc: &'a bsp::rcc::RCC, 17 | dma: &'a bsp::dma::DMA, 18 | pins: &'a bsp::gpio::Pins<'a>, 19 | swd_spi: &'a bsp::spi::SPI, 20 | jtag_spi: &'a bsp::spi::SPI, 21 | usb: &'a mut crate::usb::USB, 22 | dap: &'a mut crate::dap::DAP<'a>, 23 | vcp: &'a mut crate::vcp::VCP<'a>, 24 | delay: &'a bsp::delay::Delay, 25 | resp_buf: [u8; DAP2_PACKET_SIZE as usize], 26 | vcp_config: VcpConfig, 27 | } 28 | 29 | impl<'a> App<'a> { 30 | #[allow(clippy::too_many_arguments)] 31 | pub fn new( 32 | rcc: &'a bsp::rcc::RCC, 33 | dma: &'a bsp::dma::DMA, 34 | pins: &'a bsp::gpio::Pins<'a>, 35 | swd_spi: &'a bsp::spi::SPI, 36 | jtag_spi: &'a bsp::spi::SPI, 37 | usb: &'a mut crate::usb::USB, 38 | dap: &'a mut crate::dap::DAP<'a>, 39 | vcp: &'a mut crate::vcp::VCP<'a>, 40 | delay: &'a bsp::delay::Delay, 41 | ) -> Self { 42 | App { 43 | rcc, 44 | dma, 45 | pins, 46 | swd_spi, 47 | jtag_spi, 48 | usb, 49 | dap, 50 | vcp, 51 | delay, 52 | resp_buf: [0; DAP2_PACKET_SIZE as usize], 53 | vcp_config: VcpConfig::default(), 54 | } 55 | } 56 | 57 | /// Unsafety: this function should be called from the main context. 58 | /// No other contexts should be active at the same time. 59 | pub unsafe fn setup(&mut self, serial: &'static str) { 60 | // Configure system clock 61 | #[cfg(not(feature = "turbo"))] 62 | let clocks = self.rcc.setup(CoreFrequency::F72MHz); 63 | #[cfg(feature = "turbo")] 64 | let clocks = self.rcc.setup(CoreFrequency::F216MHz); 65 | 66 | self.delay.set_sysclk(&clocks); 67 | 68 | // Configure DMA for SPI1, SPI2, USART1 and USART2 transfers 69 | self.dma.setup(); 70 | 71 | // Configure GPIOs 72 | self.pins.setup(); 73 | self.pins.high_impedance_mode(); 74 | 75 | self.swd_spi.set_base_clock(&clocks); 76 | self.swd_spi.disable(); 77 | 78 | self.jtag_spi.set_base_clock(&clocks); 79 | self.jtag_spi.disable(); 80 | 81 | // Configure DAP timing information 82 | self.dap.setup(&clocks); 83 | 84 | // Configure VCP clocks & pins 85 | self.vcp.setup(&clocks); 86 | 87 | // Configure USB peripheral and connect to host 88 | self.usb.setup(&clocks, serial); 89 | 90 | self.pins.led_red.set_low(); 91 | // self.pins.t5v_en.set_high(); 92 | } 93 | 94 | pub fn poll(&mut self) { 95 | // we need to inform the usb mod if we would be ready to receive 96 | // new acm data would there be some available. 97 | if let Some(req) = self.usb.interrupt(self.vcp.is_tx_idle()) { 98 | self.process_request(req); 99 | } 100 | 101 | if self.dap.is_swo_streaming() && !self.usb.dap2_swo_is_busy() { 102 | // Poll for new UART data when streaming is enabled and 103 | // the SWO endpoint is ready to transmit more data. 104 | let len = self.dap.read_swo(&mut self.resp_buf); 105 | 106 | if len > 0 { 107 | self.usb.dap2_stream_swo(&self.resp_buf[0..len]); 108 | } 109 | } 110 | 111 | // Compare potentially new line encoding for vcp 112 | // There is probably a better way to do it but i could 113 | // not find a way to be informed of a new encoding by the 114 | // acm usb stack. 115 | let new_line_coding = self.usb.serial_line_encoding(); 116 | let config = VcpConfig { 117 | stop_bits: new_line_coding.stop_bits(), 118 | data_bits: new_line_coding.data_bits(), 119 | parity_type: new_line_coding.parity_type(), 120 | data_rate: new_line_coding.data_rate(), 121 | }; 122 | if config != self.vcp_config { 123 | self.vcp_config = config; 124 | self.vcp.stop(); 125 | self.vcp.set_config(self.vcp_config); 126 | self.vcp.start(); 127 | } 128 | 129 | // check if there are bytes available in the uart rx buffer 130 | let vcp_rx_len = self.vcp.rx_bytes_available(); 131 | if vcp_rx_len > 0 { 132 | // read them and get potentially new length of bytes 133 | let len = self.vcp.read(&mut self.resp_buf); 134 | // transfer those bytes to the usb host 135 | self.usb.serial_return(&self.resp_buf[0..len]); 136 | } 137 | } 138 | 139 | fn process_request(&mut self, req: Request) { 140 | match req { 141 | Request::DAP1Command((report, n)) => { 142 | let len = self.dap.process_command( 143 | &report[..n], 144 | &mut self.resp_buf[..DAP1_PACKET_SIZE as usize], 145 | DAPVersion::V1, 146 | ); 147 | 148 | if len > 0 { 149 | self.usb.dap1_reply(&self.resp_buf[..len]); 150 | } 151 | } 152 | Request::DAP2Command((report, n)) => { 153 | let len = 154 | self.dap 155 | .process_command(&report[..n], &mut self.resp_buf, DAPVersion::V2); 156 | 157 | if len > 0 { 158 | self.usb.dap2_reply(&self.resp_buf[..len]); 159 | } 160 | } 161 | Request::VCPPacket((buffer, n)) => { 162 | self.vcp.write(&buffer[0..n], n); 163 | } 164 | Request::Suspend => { 165 | self.pins.high_impedance_mode(); 166 | self.pins.led_red.set_high(); 167 | self.pins.led_blue.set_high(); 168 | self.pins.led_green.set_high(); 169 | self.pins.tvcc_en.set_low(); 170 | self.pins.t5v_en.set_low(); 171 | self.swd_spi.disable(); 172 | self.jtag_spi.disable(); 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /firmware/src/dap.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2020 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use crate::{ 5 | bsp::{cortex_m, gpio::Pins, rcc::Clocks, uart::UART}, 6 | jtag, swd, DAP1_PACKET_SIZE, DAP2_PACKET_SIZE, 7 | }; 8 | use core::convert::{TryFrom, TryInto}; 9 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 10 | 11 | #[derive(Copy, Clone)] 12 | pub enum DAPVersion { 13 | V1, 14 | V2, 15 | } 16 | 17 | #[derive(Copy, Clone, TryFromPrimitive, PartialEq)] 18 | #[allow(non_camel_case_types)] 19 | #[repr(u8)] 20 | enum Command { 21 | // General Commands 22 | DAP_Info = 0x00, 23 | DAP_HostStatus = 0x01, 24 | DAP_Connect = 0x02, 25 | DAP_Disconnect = 0x03, 26 | DAP_WriteABORT = 0x08, 27 | DAP_Delay = 0x09, 28 | DAP_ResetTarget = 0x0A, 29 | 30 | // Common SWD/JTAG Commands 31 | DAP_SWJ_Pins = 0x10, 32 | DAP_SWJ_Clock = 0x11, 33 | DAP_SWJ_Sequence = 0x12, 34 | 35 | // SWD Commands 36 | DAP_SWD_Configure = 0x13, 37 | // DAP_SWD_Sequence = 0x1D, 38 | 39 | // SWO Commands 40 | DAP_SWO_Transport = 0x17, 41 | DAP_SWO_Mode = 0x18, 42 | DAP_SWO_Baudrate = 0x19, 43 | DAP_SWO_Control = 0x1A, 44 | DAP_SWO_Status = 0x1B, 45 | DAP_SWO_ExtendedStatus = 0x1E, 46 | DAP_SWO_Data = 0x1C, 47 | 48 | // JTAG Commands 49 | DAP_JTAG_Sequence = 0x14, 50 | // DAP_JTAG_Configure = 0x15, 51 | // DAP_JTAG_IDCODE = 0x16, 52 | 53 | // Transfer Commands 54 | DAP_TransferConfigure = 0x04, 55 | DAP_Transfer = 0x05, 56 | DAP_TransferBlock = 0x06, 57 | DAP_TransferAbort = 0x07, 58 | 59 | // Atomic Commands 60 | // DAP_ExecuteCommands = 0x7F, 61 | // DAP_QueueCommands = 0x7E, 62 | 63 | // Unimplemented Command Response 64 | Unimplemented = 0xFF, 65 | } 66 | 67 | #[derive(Copy, Clone, IntoPrimitive)] 68 | #[allow(non_camel_case_types)] 69 | #[repr(u8)] 70 | enum ResponseStatus { 71 | DAP_OK = 0x00, 72 | DAP_ERROR = 0xFF, 73 | } 74 | 75 | #[derive(Copy, Clone, TryFromPrimitive)] 76 | #[allow(non_camel_case_types)] 77 | #[repr(u8)] 78 | enum DAPInfoID { 79 | VendorID = 0x01, 80 | ProductID = 0x02, 81 | SerialNumber = 0x03, 82 | FirmwareVersion = 0x04, 83 | TargetVendor = 0x05, 84 | TargetName = 0x06, 85 | Capabilities = 0xF0, 86 | TestDomainTimer = 0xF1, 87 | SWOTraceBufferSize = 0xFD, 88 | MaxPacketCount = 0xFE, 89 | MaxPacketSize = 0xFF, 90 | } 91 | 92 | #[derive(Copy, Clone, TryFromPrimitive)] 93 | #[repr(u8)] 94 | enum HostStatusType { 95 | Connect = 0, 96 | Running = 1, 97 | } 98 | 99 | #[derive(Copy, Clone, TryFromPrimitive)] 100 | #[repr(u8)] 101 | #[allow(clippy::upper_case_acronyms)] 102 | enum ConnectPort { 103 | Default = 0, 104 | SWD = 1, 105 | JTAG = 2, 106 | } 107 | 108 | #[repr(u8)] 109 | #[allow(clippy::upper_case_acronyms)] 110 | enum ConnectPortResponse { 111 | Failed = 0, 112 | SWD = 1, 113 | JTAG = 2, 114 | } 115 | 116 | #[derive(TryFromPrimitive)] 117 | #[repr(u8)] 118 | enum SWOTransport { 119 | None = 0, 120 | DAPCommand = 1, 121 | USBEndpoint = 2, 122 | } 123 | 124 | #[derive(TryFromPrimitive)] 125 | #[repr(u8)] 126 | #[allow(clippy::upper_case_acronyms)] 127 | enum SWOMode { 128 | Off = 0, 129 | UART = 1, 130 | Manchester = 2, 131 | } 132 | 133 | #[derive(TryFromPrimitive)] 134 | #[repr(u8)] 135 | enum SWOControl { 136 | Stop = 0, 137 | Start = 1, 138 | } 139 | 140 | struct Request<'a> { 141 | command: Command, 142 | data: &'a [u8], 143 | } 144 | 145 | impl<'a> Request<'a> { 146 | /// Returns None if the report is empty 147 | pub fn from_report(report: &'a [u8]) -> Option { 148 | let (command, data) = report.split_first()?; 149 | 150 | let command = (*command).try_into().unwrap_or(Command::Unimplemented); 151 | 152 | Some(Request { command, data }) 153 | } 154 | 155 | pub fn next_u8(&mut self) -> u8 { 156 | let value = self.data[0]; 157 | self.data = &self.data[1..]; 158 | value 159 | } 160 | 161 | pub fn next_u16(&mut self) -> u16 { 162 | let value = u16::from_le_bytes(self.data[0..2].try_into().unwrap()); 163 | self.data = &self.data[2..]; 164 | value 165 | } 166 | 167 | pub fn next_u32(&mut self) -> u32 { 168 | let value = u32::from_le_bytes(self.data[0..4].try_into().unwrap()); 169 | self.data = &self.data[4..]; 170 | value 171 | } 172 | 173 | pub fn rest(self) -> &'a [u8] { 174 | self.data 175 | } 176 | } 177 | 178 | struct ResponseWriter<'a> { 179 | buf: &'a mut [u8], 180 | idx: usize, 181 | } 182 | 183 | impl<'a> ResponseWriter<'a> { 184 | pub fn new(command: Command, buf: &'a mut [u8]) -> Self { 185 | buf[0] = command as u8; 186 | ResponseWriter { buf, idx: 1 } 187 | } 188 | 189 | pub fn write_u8(&mut self, value: u8) { 190 | self.buf[self.idx] = value; 191 | self.idx += 1; 192 | } 193 | 194 | pub fn write_u16(&mut self, value: u16) { 195 | let value = value.to_le_bytes(); 196 | self.buf[self.idx..self.idx + 2].copy_from_slice(&value); 197 | self.idx += 2; 198 | } 199 | 200 | pub fn write_u32(&mut self, value: u32) { 201 | let value = value.to_le_bytes(); 202 | self.buf[self.idx..self.idx + 4].copy_from_slice(&value); 203 | self.idx += 4; 204 | } 205 | 206 | pub fn write_slice(&mut self, data: &[u8]) { 207 | self.buf[self.idx..self.idx + data.len()].copy_from_slice(data); 208 | self.idx += data.len(); 209 | } 210 | 211 | pub fn write_ok(&mut self) { 212 | self.write_u8(ResponseStatus::DAP_OK.into()); 213 | } 214 | 215 | pub fn write_err(&mut self) { 216 | self.write_u8(ResponseStatus::DAP_ERROR.into()); 217 | } 218 | 219 | pub fn write_u8_at(&mut self, idx: usize, value: u8) { 220 | self.buf[idx] = value; 221 | } 222 | 223 | pub fn write_u16_at(&mut self, idx: usize, value: u16) { 224 | let value = value.to_le_bytes(); 225 | self.buf[idx..idx + 2].copy_from_slice(&value); 226 | } 227 | 228 | pub fn mut_at(&mut self, idx: usize) -> &mut u8 { 229 | &mut self.buf[idx] 230 | } 231 | 232 | pub fn read_u8_at(&self, idx: usize) -> u8 { 233 | self.buf[idx] 234 | } 235 | 236 | pub fn remaining(&mut self) -> &mut [u8] { 237 | &mut self.buf[self.idx..] 238 | } 239 | 240 | pub fn skip(&mut self, n: usize) { 241 | self.idx += n; 242 | } 243 | } 244 | 245 | #[allow(clippy::upper_case_acronyms)] 246 | enum DAPMode { 247 | SWD, 248 | JTAG, 249 | } 250 | 251 | #[allow(clippy::upper_case_acronyms)] 252 | pub struct DAP<'a> { 253 | swd: swd::SWD<'a>, 254 | jtag: jtag::JTAG<'a>, 255 | uart: &'a mut UART<'a>, 256 | pins: &'a Pins<'a>, 257 | mode: Option, 258 | swo_streaming: bool, 259 | match_retries: usize, 260 | } 261 | 262 | impl<'a> DAP<'a> { 263 | pub fn new( 264 | swd: swd::SWD<'a>, 265 | jtag: jtag::JTAG<'a>, 266 | uart: &'a mut UART<'a>, 267 | pins: &'a Pins, 268 | ) -> Self { 269 | DAP { 270 | swd, 271 | jtag, 272 | uart, 273 | pins, 274 | mode: None, 275 | swo_streaming: false, 276 | match_retries: 5, 277 | } 278 | } 279 | 280 | /// Call with the system clock speeds to configure peripherals that require timing information. 281 | /// 282 | /// Currently this only configures the SWO USART baud rate calculation. 283 | pub fn setup(&mut self, clocks: &Clocks) { 284 | self.uart.setup(clocks); 285 | } 286 | 287 | /// Process a new CMSIS-DAP command from `report`. 288 | /// 289 | /// Returns number of bytes written to response buffer. 290 | pub fn process_command( 291 | &mut self, 292 | report: &[u8], 293 | rbuf: &mut [u8], 294 | version: DAPVersion, 295 | ) -> usize { 296 | let req = match Request::from_report(report) { 297 | Some(req) => req, 298 | None => return 0, 299 | }; 300 | 301 | let resp = &mut ResponseWriter::new(req.command, rbuf); 302 | 303 | match req.command { 304 | Command::DAP_Info => self.process_info(req, resp, version), 305 | Command::DAP_HostStatus => self.process_host_status(req, resp), 306 | Command::DAP_Connect => self.process_connect(req, resp), 307 | Command::DAP_Disconnect => self.process_disconnect(req, resp), 308 | Command::DAP_WriteABORT => self.process_write_abort(req, resp), 309 | Command::DAP_Delay => self.process_delay(req, resp), 310 | Command::DAP_ResetTarget => self.process_reset_target(req, resp), 311 | Command::DAP_SWJ_Pins => self.process_swj_pins(req, resp), 312 | Command::DAP_SWJ_Clock => self.process_swj_clock(req, resp), 313 | Command::DAP_SWJ_Sequence => self.process_swj_sequence(req, resp), 314 | Command::DAP_SWD_Configure => self.process_swd_configure(req, resp), 315 | Command::DAP_SWO_Transport => self.process_swo_transport(req, resp), 316 | Command::DAP_SWO_Mode => self.process_swo_mode(req, resp), 317 | Command::DAP_SWO_Baudrate => self.process_swo_baudrate(req, resp), 318 | Command::DAP_SWO_Control => self.process_swo_control(req, resp), 319 | Command::DAP_SWO_Status => self.process_swo_status(req, resp), 320 | Command::DAP_SWO_ExtendedStatus => self.process_swo_extended_status(req, resp), 321 | Command::DAP_SWO_Data => self.process_swo_data(req, resp), 322 | Command::DAP_JTAG_Sequence => self.process_jtag_sequence(req, resp), 323 | Command::DAP_TransferConfigure => self.process_transfer_configure(req, resp), 324 | Command::DAP_Transfer => self.process_transfer(req, resp), 325 | Command::DAP_TransferBlock => self.process_transfer_block(req, resp), 326 | Command::DAP_TransferAbort => { 327 | self.process_transfer_abort(); 328 | // Do not send a response for transfer abort commands 329 | return 0; 330 | } 331 | Command::Unimplemented => {} 332 | } 333 | 334 | resp.idx 335 | } 336 | 337 | /// Returns true if SWO streaming is currently active. 338 | pub fn is_swo_streaming(&self) -> bool { 339 | self.uart.is_active() && self.swo_streaming 340 | } 341 | 342 | /// Polls the UART buffer for new SWO data, returning 343 | /// number of bytes written to buffer. 344 | pub fn read_swo(&mut self, buf: &mut [u8]) -> usize { 345 | self.uart.read(buf) 346 | } 347 | 348 | fn process_info(&mut self, mut req: Request, resp: &mut ResponseWriter, version: DAPVersion) { 349 | match DAPInfoID::try_from(req.next_u8()) { 350 | // Return 0-length string for VendorID, ProductID, SerialNumber 351 | // to indicate they should be read from USB descriptor instead 352 | Ok(DAPInfoID::VendorID) => resp.write_u8(0), 353 | Ok(DAPInfoID::ProductID) => resp.write_u8(0), 354 | Ok(DAPInfoID::SerialNumber) => resp.write_u8(0), 355 | // Return git version as firmware version 356 | Ok(DAPInfoID::FirmwareVersion) => { 357 | resp.write_u8(crate::GIT_VERSION.len() as u8); 358 | resp.write_slice(crate::GIT_VERSION.as_bytes()); 359 | } 360 | // Return 0-length string for TargetVendor and TargetName to indicate 361 | // unknown target device. 362 | Ok(DAPInfoID::TargetVendor) => resp.write_u8(0), 363 | Ok(DAPInfoID::TargetName) => resp.write_u8(0), 364 | Ok(DAPInfoID::Capabilities) => { 365 | resp.write_u8(1); 366 | // Bit 0: SWD supported 367 | // Bit 1: JTAG supported 368 | // Bit 2: SWO UART supported 369 | // Bit 3: SWO Manchester not supported 370 | // Bit 4: Atomic commands not supported 371 | // Bit 5: Test Domain Timer not supported 372 | // Bit 6: SWO Streaming Trace supported 373 | resp.write_u8(0b0100_0111); 374 | } 375 | Ok(DAPInfoID::SWOTraceBufferSize) => { 376 | resp.write_u8(4); 377 | resp.write_u32(self.uart.buffer_len() as u32); 378 | } 379 | Ok(DAPInfoID::MaxPacketCount) => { 380 | resp.write_u8(1); 381 | // Maximum of one packet at a time 382 | resp.write_u8(1); 383 | } 384 | Ok(DAPInfoID::MaxPacketSize) => { 385 | resp.write_u8(2); 386 | match version { 387 | DAPVersion::V1 => { 388 | // Maximum of 64 bytes per packet 389 | resp.write_u16(DAP1_PACKET_SIZE); 390 | } 391 | DAPVersion::V2 => { 392 | // Maximum of 512 bytes per packet 393 | resp.write_u16(DAP2_PACKET_SIZE); 394 | } 395 | } 396 | } 397 | _ => resp.write_u8(0), 398 | } 399 | } 400 | 401 | fn process_host_status(&mut self, mut req: Request, resp: &mut ResponseWriter) { 402 | let status_type = req.next_u8(); 403 | let status_status = req.next_u8(); 404 | // Use HostStatus to set our LED when host is connected to target 405 | if let Ok(HostStatusType::Connect) = HostStatusType::try_from(status_type) { 406 | match status_status { 407 | 0 => { 408 | self.pins.led_red.set_low(); 409 | self.pins.led_green.set_high(); 410 | } 411 | 1 => { 412 | self.pins.led_red.set_high(); 413 | self.pins.led_green.set_low(); 414 | } 415 | _ => (), 416 | } 417 | } 418 | resp.write_u8(0); 419 | } 420 | 421 | fn process_connect(&mut self, mut req: Request, resp: &mut ResponseWriter) { 422 | let port = req.next_u8(); 423 | match ConnectPort::try_from(port) { 424 | Ok(ConnectPort::Default) | Ok(ConnectPort::SWD) => { 425 | self.pins.swd_mode(); 426 | self.swd.spi_enable(); 427 | self.mode = Some(DAPMode::SWD); 428 | resp.write_u8(ConnectPortResponse::SWD as u8); 429 | } 430 | Ok(ConnectPort::JTAG) => { 431 | self.pins.jtag_mode(); 432 | self.jtag.spi_enable(); 433 | self.mode = Some(DAPMode::JTAG); 434 | resp.write_u8(ConnectPortResponse::JTAG as u8); 435 | } 436 | _ => { 437 | resp.write_u8(ConnectPortResponse::Failed as u8); 438 | } 439 | } 440 | } 441 | 442 | fn process_disconnect(&mut self, _req: Request, resp: &mut ResponseWriter) { 443 | self.pins.high_impedance_mode(); 444 | self.mode = None; 445 | self.swd.spi_disable(); 446 | self.jtag.spi_disable(); 447 | resp.write_ok(); 448 | } 449 | 450 | fn process_write_abort(&mut self, mut req: Request, resp: &mut ResponseWriter) { 451 | if self.mode.is_none() { 452 | resp.write_err(); 453 | return; 454 | } 455 | let _idx = req.next_u8(); 456 | let word = req.next_u32(); 457 | match self.swd.write_dp(0x00, word) { 458 | Ok(_) => resp.write_ok(), 459 | Err(_) => resp.write_err(), 460 | } 461 | } 462 | 463 | fn process_delay(&mut self, mut req: Request, resp: &mut ResponseWriter) { 464 | let delay = req.next_u16() as u32; 465 | cortex_m::asm::delay(48 * delay); 466 | resp.write_ok(); 467 | } 468 | 469 | fn process_reset_target(&mut self, _req: Request, resp: &mut ResponseWriter) { 470 | resp.write_ok(); 471 | // "No device specific reset sequence is implemented" 472 | resp.write_u8(0); 473 | } 474 | 475 | fn process_swj_pins(&mut self, mut req: Request, resp: &mut ResponseWriter) { 476 | let output = req.next_u8(); 477 | let mask = req.next_u8(); 478 | let wait = req.next_u32(); 479 | 480 | const SWCLK_POS: u8 = 0; 481 | const SWDIO_POS: u8 = 1; 482 | const TDI_POS: u8 = 2; 483 | const TDO_POS: u8 = 3; 484 | const NTRST_POS: u8 = 5; 485 | const NRESET_POS: u8 = 7; 486 | 487 | match self.mode { 488 | Some(DAPMode::SWD) => { 489 | // In SWD mode, use SPI1 MOSI and CLK for SWDIO/TMS and SWCLK/TCK. 490 | // Between transfers those pins are in SPI alternate mode, so swap them 491 | // to output to manually set them. They'll be reset to SPI mode by the 492 | // next transfer command. 493 | if mask & (1 << SWDIO_POS) != 0 { 494 | self.pins.spi1_mosi.set_mode_output(); 495 | self.pins.spi1_mosi.set_bool(output & (1 << SWDIO_POS) != 0); 496 | } 497 | if mask & (1 << SWCLK_POS) != 0 { 498 | self.pins.spi1_clk.set_mode_output(); 499 | self.pins.spi1_clk.set_bool(output & (1 << SWCLK_POS) != 0); 500 | } 501 | } 502 | Some(DAPMode::JTAG) => { 503 | // In JTAG mode, use SPI1 MOSI and SPI2 SLK for SWDIO/TMS and SWCLK/TCK, 504 | // and SPI2 MOSI for TDI. Between transfers these pins are already in GPIO 505 | // mode, so we don't need to change them. 506 | // 507 | // TDO is an input pin for JTAG and is ignored to match the DAPLink implementation. 508 | if mask & (1 << SWDIO_POS) != 0 { 509 | self.pins.spi1_mosi.set_bool(output & (1 << SWDIO_POS) != 0); 510 | } 511 | if mask & (1 << SWCLK_POS) != 0 { 512 | self.pins.spi2_clk.set_bool(output & (1 << SWCLK_POS) != 0); 513 | } 514 | if mask & (1 << TDI_POS) != 0 { 515 | self.pins.spi2_mosi.set_bool(output & (1 << TDI_POS) != 0); 516 | } 517 | } 518 | 519 | // When not in any mode, ignore JTAG/SWD pins entirely. 520 | None => (), 521 | }; 522 | 523 | // Always allow setting the nRESET pin, which is always in output open-drain mode. 524 | if mask & (1 << NRESET_POS) != 0 { 525 | self.pins.reset.set_bool(output & (1 << NRESET_POS) != 0); 526 | } 527 | 528 | // Delay required time in µs (approximate delay). 529 | cortex_m::asm::delay(42 * wait); 530 | 531 | // Read and return pin state 532 | let state = ((self.pins.spi1_clk.get_state() as u8) << SWCLK_POS) 533 | | ((self.pins.spi1_miso.get_state() as u8) << SWDIO_POS) 534 | | ((self.pins.spi2_mosi.get_state() as u8) << TDI_POS) 535 | | ((self.pins.spi2_miso.get_state() as u8) << TDO_POS) 536 | | (1 << NTRST_POS) 537 | | ((self.pins.reset.get_state() as u8) << NRESET_POS); 538 | resp.write_u8(state); 539 | } 540 | 541 | fn process_swj_clock(&mut self, mut req: Request, resp: &mut ResponseWriter) { 542 | let clock = req.next_u32(); 543 | 544 | self.jtag.set_clock(clock); 545 | let valid = self.swd.set_clock(clock); 546 | if valid { 547 | resp.write_ok(); 548 | } else { 549 | resp.write_err(); 550 | } 551 | } 552 | 553 | fn process_swj_sequence(&mut self, mut req: Request, resp: &mut ResponseWriter) { 554 | let nbits: usize = match req.next_u8() { 555 | // CMSIS-DAP says 0 means 256 bits 556 | 0 => 256, 557 | // Other integers are normal. 558 | n => n as usize, 559 | }; 560 | 561 | let payload = req.rest(); 562 | let nbytes = (nbits + 7) / 8; 563 | let seq = if nbytes <= payload.len() { 564 | &payload[..nbytes] 565 | } else { 566 | resp.write_err(); 567 | return; 568 | }; 569 | 570 | match self.mode { 571 | Some(DAPMode::SWD) => { 572 | self.swd.tx_sequence(seq, nbits); 573 | } 574 | Some(DAPMode::JTAG) => { 575 | self.jtag.tms_sequence(seq, nbits); 576 | } 577 | None => { 578 | resp.write_err(); 579 | return; 580 | } 581 | } 582 | 583 | resp.write_ok(); 584 | } 585 | 586 | fn process_swd_configure(&mut self, mut req: Request, resp: &mut ResponseWriter) { 587 | let config = req.next_u8(); 588 | let clk_period = config & 0b011; 589 | let always_data = (config & 0b100) != 0; 590 | if clk_period == 0 && !always_data { 591 | resp.write_ok(); 592 | } else { 593 | resp.write_err(); 594 | } 595 | } 596 | 597 | fn process_swo_transport(&mut self, mut req: Request, resp: &mut ResponseWriter) { 598 | let transport = req.next_u8(); 599 | match SWOTransport::try_from(transport) { 600 | Ok(SWOTransport::None) => { 601 | self.swo_streaming = false; 602 | resp.write_ok(); 603 | } 604 | Ok(SWOTransport::DAPCommand) => { 605 | self.swo_streaming = false; 606 | resp.write_ok(); 607 | } 608 | Ok(SWOTransport::USBEndpoint) => { 609 | self.swo_streaming = true; 610 | resp.write_ok(); 611 | } 612 | _ => resp.write_err(), 613 | } 614 | } 615 | 616 | fn process_swo_mode(&mut self, mut req: Request, resp: &mut ResponseWriter) { 617 | let mode = req.next_u8(); 618 | match SWOMode::try_from(mode) { 619 | Ok(SWOMode::Off) => { 620 | resp.write_ok(); 621 | } 622 | Ok(SWOMode::UART) => { 623 | resp.write_ok(); 624 | } 625 | _ => resp.write_err(), 626 | } 627 | } 628 | 629 | fn process_swo_baudrate(&mut self, mut req: Request, resp: &mut ResponseWriter) { 630 | let target = req.next_u32(); 631 | let actual = self.uart.set_baud(target); 632 | resp.write_u32(actual); 633 | } 634 | 635 | fn process_swo_control(&mut self, mut req: Request, resp: &mut ResponseWriter) { 636 | match SWOControl::try_from(req.next_u8()) { 637 | Ok(SWOControl::Stop) => { 638 | self.uart.stop(); 639 | resp.write_ok(); 640 | } 641 | Ok(SWOControl::Start) => { 642 | self.uart.start(); 643 | resp.write_ok(); 644 | } 645 | _ => resp.write_err(), 646 | } 647 | } 648 | 649 | fn process_swo_status(&mut self, _req: Request, resp: &mut ResponseWriter) { 650 | // Trace status: 651 | // Bit 0: trace capture active 652 | // Bit 6: trace stream error (always written as 0) 653 | // Bit 7: trace buffer overflow (always written as 0) 654 | resp.write_u8(self.uart.is_active() as u8); 655 | // Trace count: remaining bytes in buffer 656 | resp.write_u32(self.uart.bytes_available() as u32); 657 | } 658 | 659 | fn process_swo_extended_status(&mut self, _req: Request, resp: &mut ResponseWriter) { 660 | // Trace status: 661 | // Bit 0: trace capture active 662 | // Bit 6: trace stream error (always written as 0) 663 | // Bit 7: trace buffer overflow (always written as 0) 664 | resp.write_u8(self.uart.is_active() as u8); 665 | // Trace count: remaining bytes in buffer. 666 | resp.write_u32(self.uart.bytes_available() as u32); 667 | // Index: sequence number of next trace. Always written as 0. 668 | resp.write_u32(0); 669 | // TD_TimeStamp: test domain timer value for trace sequence 670 | resp.write_u32(0); 671 | } 672 | 673 | fn process_swo_data(&mut self, mut req: Request, resp: &mut ResponseWriter) { 674 | // Write status byte to response 675 | resp.write_u8(self.uart.is_active() as u8); 676 | 677 | // Skip length for now 678 | resp.skip(2); 679 | 680 | let mut buf = resp.remaining(); 681 | 682 | // Limit maximum return size to maximum requested bytes 683 | let n = req.next_u16() as usize; 684 | if buf.len() > n { 685 | buf = &mut buf[..n]; 686 | } 687 | 688 | // Read data from UART 689 | let len = self.uart.read(buf); 690 | resp.skip(len); 691 | 692 | // Go back and write length 693 | resp.write_u16_at(2, len as u16); 694 | } 695 | 696 | fn process_jtag_sequence(&mut self, req: Request, resp: &mut ResponseWriter) { 697 | match self.mode { 698 | Some(DAPMode::JTAG) => {} 699 | _ => { 700 | resp.write_err(); 701 | return; 702 | } 703 | } 704 | 705 | resp.write_ok(); 706 | 707 | // Run requested JTAG sequences. Cannot fail. 708 | let size = self.jtag.sequences(req.rest(), resp.remaining()); 709 | resp.skip(size); 710 | } 711 | 712 | fn process_transfer_configure(&mut self, mut req: Request, resp: &mut ResponseWriter) { 713 | // We don't support variable idle cycles 714 | let _idle_cycles = req.next_u8(); 715 | 716 | // Send number of wait retries through to SWD 717 | self.swd.set_wait_retries(req.next_u16() as usize); 718 | 719 | // Store number of match retries 720 | self.match_retries = req.next_u16() as usize; 721 | 722 | resp.write_ok(); 723 | } 724 | 725 | fn process_transfer(&mut self, mut req: Request, resp: &mut ResponseWriter) { 726 | let _idx = req.next_u8(); 727 | let ntransfers = req.next_u8(); 728 | let mut match_mask = 0xFFFF_FFFFu32; 729 | 730 | // Ensure SWD pins are in the right mode, in case they've been used as outputs 731 | // by the SWJ_Pins command. 732 | self.pins.swd_clk_spi(); 733 | self.pins.swd_tx(); 734 | 735 | // Skip two bytes in resp to reserve space for final status, 736 | // which we update while processing. 737 | resp.write_u16(0); 738 | 739 | for transfer_idx in 0..ntransfers { 740 | // Store how many transfers we execute in the response 741 | resp.write_u8_at(1, transfer_idx + 1); 742 | 743 | // Parse the next transfer request 744 | let transfer_req = req.next_u8(); 745 | let apndp = (transfer_req & (1 << 0)) != 0; 746 | let rnw = (transfer_req & (1 << 1)) != 0; 747 | let a = (transfer_req & (3 << 2)) >> 2; 748 | let vmatch = (transfer_req & (1 << 4)) != 0; 749 | let mmask = (transfer_req & (1 << 5)) != 0; 750 | let _ts = (transfer_req & (1 << 7)) != 0; 751 | 752 | if rnw { 753 | // Issue register read 754 | let mut read_value = if apndp { 755 | // Reads from AP are posted, so we issue the 756 | // read and subsequently read RDBUFF for the data. 757 | // This requires an additional transfer so we'd 758 | // ideally keep track of posted reads and just 759 | // keep issuing new AP reads, but our reads are 760 | // sufficiently fast that for now this is simpler. 761 | let rdbuff = swd::DPRegister::RDBUFF.into(); 762 | if self.swd.read_ap(a).check(resp.mut_at(2)).is_none() { 763 | break; 764 | } 765 | match self.swd.read_dp(rdbuff).check(resp.mut_at(2)) { 766 | Some(v) => v, 767 | None => break, 768 | } 769 | } else { 770 | // Reads from DP are not posted, so directly read the register. 771 | match self.swd.read_dp(a).check(resp.mut_at(2)) { 772 | Some(v) => v, 773 | None => break, 774 | } 775 | }; 776 | 777 | // Handle value match requests by retrying if needed. 778 | // Since we're re-reading the same register the posting 779 | // is less important and we can just use the returned value. 780 | if vmatch { 781 | let target_value = req.next_u32(); 782 | let mut match_tries = 0; 783 | while (read_value & match_mask) != target_value { 784 | match_tries += 1; 785 | if match_tries > self.match_retries { 786 | break; 787 | } 788 | 789 | read_value = match self.swd.read(apndp.into(), a).check(resp.mut_at(2)) { 790 | Some(v) => v, 791 | None => break, 792 | } 793 | } 794 | 795 | // If we didn't read the correct value, set the value mismatch 796 | // flag in the response and quit early. 797 | if (read_value & match_mask) != target_value { 798 | resp.write_u8_at(1, resp.read_u8_at(1) | (1 << 4)); 799 | break; 800 | } 801 | } else { 802 | // Save read register value 803 | resp.write_u32(read_value); 804 | } 805 | } else { 806 | // Write transfer processing 807 | 808 | // Writes with match_mask set just update the match mask 809 | if mmask { 810 | match_mask = req.next_u32(); 811 | continue; 812 | } 813 | 814 | // Otherwise issue register write 815 | let write_value = req.next_u32(); 816 | if self 817 | .swd 818 | .write(apndp.into(), a, write_value) 819 | .check(resp.mut_at(2)) 820 | .is_none() 821 | { 822 | break; 823 | } 824 | } 825 | } 826 | } 827 | 828 | fn process_transfer_block(&mut self, mut req: Request, resp: &mut ResponseWriter) { 829 | let _idx = req.next_u8(); 830 | let ntransfers = req.next_u16(); 831 | let transfer_req = req.next_u8(); 832 | let apndp = (transfer_req & (1 << 0)) != 0; 833 | let rnw = (transfer_req & (1 << 1)) != 0; 834 | let a = (transfer_req & (3 << 2)) >> 2; 835 | 836 | // Ensure SWD pins are in the right mode, in case they've been used as outputs 837 | // by the SWJ_Pins command. 838 | self.pins.swd_clk_spi(); 839 | self.pins.swd_tx(); 840 | 841 | // Skip three bytes in resp to reserve space for final status, 842 | // which we update while processing. 843 | resp.write_u16(0); 844 | resp.write_u8(0); 845 | 846 | // Keep track of how many transfers we executed, 847 | // so if there is an error the host knows where 848 | // it happened. 849 | let mut transfers = 0; 850 | 851 | // If reading an AP register, post first read early. 852 | if rnw && apndp && self.swd.read_ap(a).check(resp.mut_at(3)).is_none() { 853 | // Quit early on error 854 | resp.write_u16_at(1, 1); 855 | return; 856 | } 857 | 858 | for transfer_idx in 0..ntransfers { 859 | transfers = transfer_idx; 860 | if rnw { 861 | // Handle repeated reads 862 | let read_value = if apndp { 863 | // For AP reads, the first read was posted, so on the final 864 | // read we need to read RDBUFF instead of the AP register. 865 | if transfer_idx < ntransfers - 1 { 866 | match self.swd.read_ap(a).check(resp.mut_at(3)) { 867 | Some(v) => v, 868 | None => break, 869 | } 870 | } else { 871 | let rdbuff = swd::DPRegister::RDBUFF.into(); 872 | match self.swd.read_dp(rdbuff).check(resp.mut_at(3)) { 873 | Some(v) => v, 874 | None => break, 875 | } 876 | } 877 | } else { 878 | // For DP reads, no special care required 879 | match self.swd.read_dp(a).check(resp.mut_at(3)) { 880 | Some(v) => v, 881 | None => break, 882 | } 883 | }; 884 | 885 | // Save read register value to response 886 | resp.write_u32(read_value); 887 | } else { 888 | // Handle repeated register writes 889 | let write_value = req.next_u32(); 890 | let result = self.swd.write(apndp.into(), a, write_value); 891 | if result.check(resp.mut_at(3)).is_none() { 892 | break; 893 | } 894 | } 895 | } 896 | 897 | // Write number of transfers to response 898 | resp.write_u16_at(1, transfers + 1); 899 | } 900 | 901 | fn process_transfer_abort(&mut self) { 902 | // We'll only ever receive an abort request when we're not already 903 | // processing anything else, since processing blocks checking for 904 | // new requests. Therefore there's nothing to do here. 905 | } 906 | } 907 | 908 | trait CheckResult { 909 | /// Check result of an SWD transfer, updating the response status byte. 910 | /// 911 | /// Returns Some(T) on successful transfer, None on error. 912 | fn check(self, resp: &mut u8) -> Option; 913 | } 914 | 915 | impl CheckResult for swd::Result { 916 | fn check(self, resp: &mut u8) -> Option { 917 | match self { 918 | Ok(v) => { 919 | *resp = 1; 920 | Some(v) 921 | } 922 | Err(swd::Error::AckWait) => { 923 | *resp = 2; 924 | None 925 | } 926 | Err(swd::Error::AckFault) => { 927 | *resp = 4; 928 | None 929 | } 930 | Err(_) => { 931 | *resp = (1 << 3) | 7; 932 | None 933 | } 934 | } 935 | } 936 | } 937 | -------------------------------------------------------------------------------- /firmware/src/jtag.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use crate::bsp::delay::Delay; 5 | use crate::bsp::dma::DMA; 6 | use crate::bsp::gpio::{Pin, Pins}; 7 | use crate::bsp::spi::SPI; 8 | use crate::DAP2_PACKET_SIZE; 9 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; 10 | 11 | struct JTAGPins<'a> { 12 | tms: &'a Pin<'a>, 13 | tck: &'a Pin<'a>, 14 | tdo: &'a Pin<'a>, 15 | tdi: &'a Pin<'a>, 16 | } 17 | 18 | #[allow(clippy::upper_case_acronyms)] 19 | pub struct JTAG<'a> { 20 | spi: &'a SPI, 21 | dma: &'a DMA, 22 | pins: JTAGPins<'a>, 23 | delay: &'a Delay, 24 | half_period_ticks: AtomicU32, 25 | use_bitbang: AtomicBool, 26 | } 27 | 28 | impl<'a> JTAG<'a> { 29 | /// Create a new JTAG object from the provided Pins struct. 30 | pub fn new(spi: &'a SPI, dma: &'a DMA, pins: &'a Pins, delay: &'a Delay) -> Self { 31 | let jtag_pins = JTAGPins { 32 | tms: &pins.spi1_mosi, 33 | tck: &pins.spi2_clk, 34 | tdo: &pins.spi2_miso, 35 | tdi: &pins.spi2_mosi, 36 | }; 37 | 38 | JTAG { 39 | spi, 40 | dma, 41 | pins: jtag_pins, 42 | delay, 43 | half_period_ticks: AtomicU32::new(10000), 44 | use_bitbang: AtomicBool::new(true), 45 | } 46 | } 47 | 48 | pub fn set_clock(&self, max_frequency: u32) { 49 | let period = self.delay.calc_period_ticks(max_frequency); 50 | self.half_period_ticks.store(period / 2, Ordering::SeqCst); 51 | 52 | if let Some(prescaler) = self.spi.calculate_prescaler(max_frequency) { 53 | self.spi.set_prescaler(prescaler); 54 | self.use_bitbang.store(false, Ordering::SeqCst); 55 | } else { 56 | self.use_bitbang.store(true, Ordering::SeqCst); 57 | } 58 | } 59 | 60 | pub fn spi_enable(&self) { 61 | self.spi.setup_jtag(); 62 | } 63 | 64 | pub fn spi_disable(&self) { 65 | self.spi.disable(); 66 | } 67 | 68 | #[inline(never)] 69 | pub fn tms_sequence(&self, data: &[u8], mut bits: usize) { 70 | self.bitbang_mode(); 71 | 72 | let half_period_ticks = self.half_period_ticks.load(Ordering::SeqCst); 73 | let mut last = self.delay.get_current(); 74 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 75 | 76 | for byte in data { 77 | let mut byte = *byte; 78 | let frame_bits = core::cmp::min(bits, 8); 79 | for _ in 0..frame_bits { 80 | let bit = byte & 1; 81 | byte >>= 1; 82 | 83 | self.pins.tms.set_bool(bit != 0); 84 | self.pins.tck.set_low(); 85 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 86 | self.pins.tck.set_high(); 87 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 88 | } 89 | bits -= frame_bits; 90 | } 91 | } 92 | 93 | /// Handle a sequence request. The request data follows the CMSIS-DAP 94 | /// DAP_JTAG_Sequence command: 95 | /// * First byte contains the number of sequences, then 96 | /// * First byte of each sequence contains: 97 | /// * Bits 5..0: Number of clock cycles, where 0 means 64 cycles 98 | /// * Bit 6: TMS value 99 | /// * Bit 7: TDO capture enable 100 | /// * Subsequent bytes of each sequence contain TDI data, one bit per 101 | /// clock cycle, with the final byte padded. Data is transmitted from 102 | /// successive bytes, least significant bit first. 103 | /// 104 | /// Captured TDO data is written least significant bit first to successive 105 | /// bytes of `rxbuf`, which must be long enough for the requested capture, 106 | /// or conservatively as long as `data`. 107 | /// The final byte of TDO data for each sequence is padded, in other words, 108 | /// as many TDO bytes will be returned as there were TDI bytes in sequences 109 | /// with capture enabled. 110 | /// 111 | /// Returns the number of bytes of rxbuf which were written to. 112 | pub fn sequences(&self, data: &[u8], rxbuf: &mut [u8]) -> usize { 113 | // Read request header containing number of sequences. 114 | if data.is_empty() { 115 | return 0; 116 | }; 117 | let mut nseqs = data[0]; 118 | let mut data = &data[1..]; 119 | let mut rxidx = 0; 120 | 121 | // Sanity check 122 | if nseqs == 0 || data.is_empty() { 123 | return 0; 124 | } 125 | 126 | let half_period_ticks = self.half_period_ticks.load(Ordering::SeqCst); 127 | self.delay.delay_ticks(half_period_ticks); 128 | 129 | // Process alike sequences in one shot 130 | // This 131 | if !self.use_bitbang.load(Ordering::SeqCst) { 132 | let mut buffer = [0u8; DAP2_PACKET_SIZE as usize]; 133 | let mut buffer_idx = 0; 134 | let transfer_type = data[0] & 0b1100_0000; 135 | while nseqs > 0 { 136 | // Read header byte for this sequence. 137 | if data.is_empty() { 138 | break; 139 | }; 140 | let header = data[0]; 141 | if (header & 0b1100_0000) != transfer_type { 142 | // This sequence can't be processed in the same way 143 | break; 144 | } 145 | let nbits = header & 0b0011_1111; 146 | if nbits & 7 != 0 { 147 | // We can handle only 8*N bit sequences here 148 | break; 149 | } 150 | let nbits = if nbits == 0 { 64 } else { nbits as usize }; 151 | let nbytes = Self::bytes_for_bits(nbits); 152 | 153 | if data.len() < (nbytes + 1) { 154 | break; 155 | }; 156 | data = &data[1..]; 157 | 158 | buffer[buffer_idx..buffer_idx + nbytes].copy_from_slice(&data[..nbytes]); 159 | buffer_idx += nbytes; 160 | nseqs -= 1; 161 | data = &data[nbytes..]; 162 | } 163 | if buffer_idx > 0 { 164 | let capture = transfer_type & 0b1000_0000; 165 | let tms = transfer_type & 0b0100_0000; 166 | 167 | // Set TMS for this transfer. 168 | self.pins.tms.set_bool(tms != 0); 169 | 170 | self.spi_mode(); 171 | self.spi 172 | .jtag_exchange(self.dma, &buffer[..buffer_idx], &mut rxbuf[rxidx..]); 173 | if capture != 0 { 174 | rxidx += buffer_idx; 175 | } 176 | // Set TDI GPIO to the last bit the SPI peripheral transmitted, 177 | // to prevent it changing state when we set it to an output. 178 | self.pins.tdi.set_bool((buffer[buffer_idx - 1] >> 7) != 0); 179 | self.bitbang_mode(); 180 | self.spi.disable(); 181 | } 182 | } 183 | 184 | // Process each sequence. 185 | for _ in 0..nseqs { 186 | // Read header byte for this sequence. 187 | if data.is_empty() { 188 | break; 189 | }; 190 | let header = data[0]; 191 | data = &data[1..]; 192 | let capture = header & 0b1000_0000; 193 | let tms = header & 0b0100_0000; 194 | let nbits = header & 0b0011_1111; 195 | let nbits = if nbits == 0 { 64 } else { nbits as usize }; 196 | let nbytes = Self::bytes_for_bits(nbits); 197 | if data.len() < nbytes { 198 | break; 199 | }; 200 | 201 | // Split data into TDI data for this sequence and data for remaining sequences. 202 | let tdi = &data[..nbytes]; 203 | data = &data[nbytes..]; 204 | 205 | // Set TMS for this transfer. 206 | self.pins.tms.set_bool(tms != 0); 207 | 208 | // Run one transfer, either read-write or write-only. 209 | if capture != 0 { 210 | self.transfer_rw(nbits, tdi, &mut rxbuf[rxidx..]); 211 | rxidx += nbytes; 212 | } else { 213 | self.transfer_wo(nbits, tdi); 214 | } 215 | } 216 | 217 | rxidx 218 | } 219 | 220 | /// Write-only JTAG transfer without capturing TDO. 221 | /// 222 | /// Writes `n` bits from successive bytes of `tdi`, LSbit first. 223 | #[inline(never)] 224 | fn transfer_wo(&self, n: usize, tdi: &[u8]) { 225 | let half_period_ticks = self.half_period_ticks.load(Ordering::SeqCst); 226 | let mut last = self.delay.get_current(); 227 | 228 | for (byte_idx, byte) in tdi.iter().enumerate() { 229 | for bit_idx in 0..8 { 230 | // Stop after transmitting `n` bits. 231 | if byte_idx * 8 + bit_idx == n { 232 | return; 233 | } 234 | 235 | // Set TDI and toggle TCK. 236 | self.pins.tdi.set_bool(byte & (1 << bit_idx) != 0); 237 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 238 | self.pins.tck.set_high(); 239 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 240 | self.pins.tck.set_low(); 241 | } 242 | } 243 | } 244 | 245 | /// Read-write JTAG transfer, with TDO capture. 246 | /// 247 | /// Writes `n` bits from successive bytes of `tdi`, LSbit first. 248 | /// Captures `n` bits from TDO and writes into successive bytes of `tdo`, LSbit first. 249 | #[inline(never)] 250 | fn transfer_rw(&self, n: usize, tdi: &[u8], tdo: &mut [u8]) { 251 | let half_period_ticks = self.half_period_ticks.load(Ordering::SeqCst); 252 | let mut last = self.delay.get_current(); 253 | 254 | for (byte_idx, (tdi, tdo)) in tdi.iter().zip(tdo.iter_mut()).enumerate() { 255 | *tdo = 0; 256 | for bit_idx in 0..8 { 257 | // Stop after transmitting `n` bits. 258 | if byte_idx * 8 + bit_idx == n { 259 | return; 260 | } 261 | 262 | // We set TDI half a period before the clock rising edge where it is sampled 263 | // by the target, and we sample TDO immediately before the clock falling edge 264 | // where it is updated by the target. 265 | self.pins.tdi.set_bool(tdi & (1 << bit_idx) != 0); 266 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 267 | self.pins.tck.set_high(); 268 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 269 | if self.pins.tdo.is_high() { 270 | *tdo |= 1 << bit_idx; 271 | } 272 | self.pins.tck.set_low(); 273 | } 274 | } 275 | } 276 | 277 | /// Compute required number of bytes to store a number of bits. 278 | fn bytes_for_bits(bits: usize) -> usize { 279 | (bits + 7) / 8 280 | } 281 | 282 | fn bitbang_mode(&self) { 283 | self.pins.tdo.set_mode_input(); 284 | self.pins.tdi.set_mode_output(); 285 | self.pins.tck.set_low().set_mode_output(); 286 | } 287 | 288 | fn spi_mode(&self) { 289 | self.pins.tdo.set_mode_alternate(); 290 | self.pins.tdi.set_mode_alternate(); 291 | self.pins.tck.set_mode_alternate(); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /firmware/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use bsp::{cortex_m, stm32ral}; 5 | use cortex_m_rt::{entry, pre_init}; 6 | use git_version::git_version; 7 | pub use hs_probe_bsp as bsp; 8 | use panic_rtt_target as _; 9 | use rtt_target::{rprintln, rtt_init_print}; 10 | use stm32_device_signature::device_id_hex; 11 | 12 | const GIT_VERSION: &str = git_version!(); 13 | 14 | const DAP1_PACKET_SIZE: u16 = 64; 15 | const DAP2_PACKET_SIZE: u16 = 512; 16 | const VCP_PACKET_SIZE: u16 = 512; 17 | 18 | mod app; 19 | mod dap; 20 | mod jtag; 21 | mod swd; 22 | mod usb; 23 | mod vcp; 24 | 25 | #[pre_init] 26 | unsafe fn pre_init() { 27 | // Check if we should jump to system bootloader. 28 | // 29 | // When we receive the BOOTLOAD command over USB, 30 | // we write a flag to a static and reset the chip, 31 | // and `bootload::check()` will jump to the system 32 | // memory bootloader if the flag is present. 33 | // 34 | // It must be called from pre_init as otherwise the 35 | // flag is overwritten when statics are initialised. 36 | bsp::bootload::check(); 37 | } 38 | 39 | #[entry] 40 | fn main() -> ! { 41 | rtt_init_print!(); 42 | 43 | // Enable I-cache 44 | let mut cp = cortex_m::Peripherals::take().unwrap(); 45 | cp.SCB.enable_icache(); 46 | 47 | let rcc = bsp::rcc::RCC::new(stm32ral::rcc::RCC::take().unwrap()); 48 | 49 | let usb_phy = stm32ral::usbphyc::USBPHYC::take().unwrap(); 50 | let usb_global = stm32ral::otg_hs_global::OTG_HS_GLOBAL::take().unwrap(); 51 | let usb_device = stm32ral::otg_hs_device::OTG_HS_DEVICE::take().unwrap(); 52 | let usb_pwrclk = stm32ral::otg_hs_pwrclk::OTG_HS_PWRCLK::take().unwrap(); 53 | let mut usb = crate::usb::USB::new(usb_phy, usb_global, usb_device, usb_pwrclk); 54 | 55 | let dma = bsp::dma::DMA::new( 56 | stm32ral::dma::DMA1::take().unwrap(), 57 | stm32ral::dma::DMA2::take().unwrap(), 58 | ); 59 | let spi1 = bsp::spi::SPI::new(stm32ral::spi::SPI1::take().unwrap()); 60 | let spi2 = bsp::spi::SPI::new(stm32ral::spi::SPI2::take().unwrap()); 61 | let mut uart1 = bsp::uart::UART::new(stm32ral::usart::USART1::take().unwrap(), &dma); 62 | let uart2 = stm32ral::usart::USART2::take().unwrap(); 63 | 64 | let _gpioa = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOA::take().unwrap()); 65 | let gpiob = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOB::take().unwrap()); 66 | let gpioc = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOC::take().unwrap()); 67 | let gpiod = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOD::take().unwrap()); 68 | let gpioe = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOE::take().unwrap()); 69 | let gpiog = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOG::take().unwrap()); 70 | let gpioi = bsp::gpio::GPIO::new(stm32ral::gpio::GPIOI::take().unwrap()); 71 | 72 | let pins = bsp::gpio::Pins { 73 | led_red: gpioc.pin(10), 74 | led_green: gpiob.pin(8), 75 | led_blue: gpioe.pin(0), 76 | t5v_en: gpiob.pin(1), 77 | tvcc_en: gpioe.pin(2), 78 | reset: gpiog.pin(13), 79 | gnd_detect: gpiog.pin(14), 80 | usart1_rx: gpiob.pin(7), 81 | usart2_rx: gpiod.pin(6), 82 | usart2_tx: gpiod.pin(5), 83 | spi1_clk: gpiob.pin(3), 84 | spi1_miso: gpiob.pin(4), 85 | spi1_mosi: gpiob.pin(5), 86 | spi2_clk: gpioi.pin(1), 87 | spi2_miso: gpioi.pin(2), 88 | spi2_mosi: gpioi.pin(3), 89 | usb_dm: gpiob.pin(14), 90 | usb_dp: gpiob.pin(15), 91 | usb_sel: gpiob.pin(10), 92 | }; 93 | 94 | let syst = stm32ral::syst::SYST::take().unwrap(); 95 | let delay = bsp::delay::Delay::new(syst); 96 | 97 | let swd = swd::SWD::new(&spi1, &pins, &delay); 98 | let jtag = jtag::JTAG::new(&spi2, &dma, &pins, &delay); 99 | let mut dap = dap::DAP::new(swd, jtag, &mut uart1, &pins); 100 | let mut vcp = vcp::VCP::new(uart2, &pins, &dma); 101 | 102 | // Create App instance with the HAL instances 103 | let mut app = app::App::new( 104 | &rcc, &dma, &pins, &spi1, &spi2, &mut usb, &mut dap, &mut vcp, &delay, 105 | ); 106 | 107 | rprintln!("Starting..."); 108 | 109 | // Initialise application, including system peripherals 110 | unsafe { app.setup(device_id_hex()) }; 111 | 112 | loop { 113 | // Process events 114 | app.poll(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /firmware/src/swd.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2020 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use crate::bsp::{delay::Delay, gpio::Pins, spi::SPI}; 5 | use core::sync::atomic::{AtomicU32, Ordering}; 6 | use num_enum::IntoPrimitive; 7 | 8 | #[derive(Copy, Clone, Debug)] 9 | pub enum Error { 10 | BadParity, 11 | AckWait, 12 | AckFault, 13 | AckProtocol, 14 | AckUnknown(u8), 15 | } 16 | 17 | pub type Result = core::result::Result; 18 | 19 | #[repr(u8)] 20 | #[derive(Copy, Clone, Debug, IntoPrimitive)] 21 | #[allow(clippy::upper_case_acronyms)] 22 | pub enum DPRegister { 23 | DPIDR = 0, 24 | CTRLSTAT = 1, 25 | SELECT = 2, 26 | RDBUFF = 3, 27 | } 28 | 29 | #[allow(clippy::upper_case_acronyms)] 30 | pub struct SWD<'a> { 31 | spi: &'a SPI, 32 | pins: &'a Pins<'a>, 33 | delay: &'a Delay, 34 | half_period_ticks: AtomicU32, 35 | 36 | wait_retries: usize, 37 | } 38 | 39 | #[repr(u8)] 40 | #[derive(Copy, Clone, Debug)] 41 | pub enum APnDP { 42 | DP = 0, 43 | AP = 1, 44 | } 45 | 46 | impl From for APnDP { 47 | fn from(x: bool) -> APnDP { 48 | if x { 49 | APnDP::AP 50 | } else { 51 | APnDP::DP 52 | } 53 | } 54 | } 55 | 56 | #[repr(u8)] 57 | #[derive(Copy, Clone, Debug)] 58 | enum RnW { 59 | W = 0, 60 | R = 1, 61 | } 62 | 63 | #[repr(u8)] 64 | #[derive(Copy, Clone, Debug)] 65 | #[allow(clippy::upper_case_acronyms)] 66 | enum ACK { 67 | OK = 0b001, 68 | WAIT = 0b010, 69 | FAULT = 0b100, 70 | PROTOCOL = 0b111, 71 | } 72 | 73 | impl ACK { 74 | pub fn try_ok(ack: u8) -> Result<()> { 75 | match ack { 76 | v if v == (ACK::OK as u8) => Ok(()), 77 | v if v == (ACK::WAIT as u8) => Err(Error::AckWait), 78 | v if v == (ACK::FAULT as u8) => Err(Error::AckFault), 79 | v if v == (ACK::PROTOCOL as u8) => Err(Error::AckProtocol), 80 | _ => Err(Error::AckUnknown(ack)), 81 | } 82 | } 83 | } 84 | 85 | impl<'a> SWD<'a> { 86 | pub fn new(spi: &'a SPI, pins: &'a Pins, delay: &'a Delay) -> Self { 87 | SWD { 88 | spi, 89 | pins, 90 | delay, 91 | half_period_ticks: AtomicU32::new(10000), 92 | wait_retries: 8, 93 | } 94 | } 95 | 96 | pub fn set_clock(&self, max_frequency: u32) -> bool { 97 | let period = self.delay.calc_period_ticks(max_frequency); 98 | self.half_period_ticks.store(period / 2, Ordering::SeqCst); 99 | 100 | if let Some(prescaler) = self.spi.calculate_prescaler(max_frequency) { 101 | self.spi.set_prescaler(prescaler); 102 | true 103 | } else { 104 | false 105 | } 106 | } 107 | 108 | pub fn spi_enable(&self) { 109 | self.spi.setup_swd(); 110 | } 111 | 112 | pub fn spi_disable(&self) { 113 | self.spi.disable(); 114 | } 115 | 116 | pub fn set_wait_retries(&mut self, wait_retries: usize) { 117 | self.wait_retries = wait_retries; 118 | } 119 | 120 | pub fn tx_sequence(&self, data: &[u8], mut bits: usize) { 121 | self.pins.swd_tx_direct(); 122 | self.pins.swd_clk_direct(); 123 | 124 | let half_period_ticks = self.half_period_ticks.load(Ordering::SeqCst); 125 | let mut last = self.delay.get_current(); 126 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 127 | 128 | for byte in data { 129 | let mut byte = *byte; 130 | let frame_bits = core::cmp::min(bits, 8); 131 | for _ in 0..frame_bits { 132 | let bit = byte & 1; 133 | byte >>= 1; 134 | self.pins.spi1_mosi.set_bool(bit != 0); 135 | self.pins.spi1_clk.set_low(); 136 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 137 | self.pins.spi1_clk.set_high(); 138 | last = self.delay.delay_ticks_from_last(half_period_ticks, last); 139 | } 140 | bits -= frame_bits; 141 | } 142 | self.pins.swd_tx(); 143 | self.pins.swd_clk_spi(); 144 | } 145 | 146 | pub fn idle_low(&self) { 147 | self.spi.tx4(0x0); 148 | } 149 | 150 | pub fn read_dp(&self, a: u8) -> Result { 151 | self.read(APnDP::DP, a) 152 | } 153 | 154 | pub fn write_dp(&self, a: u8, data: u32) -> Result<()> { 155 | self.write(APnDP::DP, a, data) 156 | } 157 | 158 | pub fn read_ap(&self, a: u8) -> Result { 159 | self.read(APnDP::AP, a) 160 | } 161 | 162 | pub fn read(&self, apndp: APnDP, a: u8) -> Result { 163 | for _ in 0..self.wait_retries { 164 | match self.read_inner(apndp, a) { 165 | Err(Error::AckWait) => continue, 166 | x => return x, 167 | } 168 | } 169 | Err(Error::AckWait) 170 | } 171 | 172 | pub fn write(&self, apndp: APnDP, a: u8, data: u32) -> Result<()> { 173 | for _ in 0..self.wait_retries { 174 | match self.write_inner(apndp, a, data) { 175 | Err(Error::AckWait) => continue, 176 | x => return x, 177 | } 178 | } 179 | Err(Error::AckWait) 180 | } 181 | 182 | fn read_inner(&self, apndp: APnDP, a: u8) -> Result { 183 | let req = Self::make_request(apndp, RnW::R, a); 184 | 185 | self.spi.tx8(req); 186 | self.spi.wait_busy(); 187 | self.spi.drain(); 188 | self.pins.swd_rx(); 189 | 190 | // 1 clock for turnaround and 3 for ACK 191 | let ack = self.spi.rx4() >> 1; 192 | match ACK::try_ok(ack as u8) { 193 | Ok(_) => (), 194 | Err(e) => { 195 | // On non-OK ACK, target has released the bus but 196 | // is still expecting a turnaround clock before 197 | // the next request, and we need to take over the bus. 198 | self.pins.swd_tx(); 199 | self.idle_low(); 200 | return Err(e); 201 | } 202 | } 203 | 204 | // Read 8x4=32 bits of data and 8x1=8 bits for parity+turnaround+trailing. 205 | // Doing a batch of 5 8-bit reads is the quickest option as we keep the FIFO 206 | // hot. 207 | let (data, parity) = self.spi.swd_rdata_phase(self.pins); 208 | let parity = (parity & 1) as u32; 209 | 210 | // Back to driving SWDIO to ensure it doesn't float high 211 | self.pins.swd_tx(); 212 | 213 | if parity == (data.count_ones() & 1) { 214 | Ok(data) 215 | } else { 216 | Err(Error::BadParity) 217 | } 218 | } 219 | 220 | fn write_inner(&self, apndp: APnDP, a: u8, data: u32) -> Result<()> { 221 | let req = Self::make_request(apndp, RnW::W, a); 222 | let parity = data.count_ones() & 1; 223 | 224 | self.spi.tx8(req); 225 | self.spi.wait_busy(); 226 | self.spi.drain(); 227 | self.pins.swd_rx(); 228 | 229 | // 1 clock for turnaround and 3 for ACK and 1 for turnaround 230 | let ack = (self.spi.rx5() >> 1) & 0b111; 231 | self.pins.swd_tx(); 232 | match ACK::try_ok(ack as u8) { 233 | Ok(_) => (), 234 | Err(e) => return Err(e), 235 | } 236 | 237 | // Write 8x4=32 bits of data and 8x1=8 bits for parity+trailing idle. 238 | // This way we keep the FIFO full and eliminate delays between words, 239 | // even at the cost of more trailing bits. We can't change DS to 4 bits 240 | // until the FIFO is empty, and waiting for that costs more time overall. 241 | // Additionally, many debug ports require a couple of clock cycles after 242 | // the parity bit of a write transaction to make the write effective. 243 | self.spi.swd_wdata_phase(data, parity as u8); 244 | self.spi.wait_busy(); 245 | 246 | Ok(()) 247 | } 248 | 249 | fn make_request(apndp: APnDP, rnw: RnW, a: u8) -> u8 { 250 | let req = 1 | ((apndp as u8) << 1) | ((rnw as u8) << 2) | (a << 3) | (1 << 7); 251 | let parity = (req.count_ones() & 1) as u8; 252 | req | (parity << 5) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /firmware/src/usb/dap_v1.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Request; 2 | use crate::DAP1_PACKET_SIZE; 3 | use usb_device::control::{Recipient, RequestType}; 4 | use usb_device::Result; 5 | use usb_device::{class_prelude::*, device}; 6 | 7 | const INTERFACE_CLASS_HID: u8 = 0x03; 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | #[repr(u8)] 11 | pub enum DescriptorType { 12 | Hid = 0x21, 13 | Report = 0x22, 14 | } 15 | 16 | const REPORT_DESCRIPTOR: &[u8] = &[ 17 | 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 18 | 0x09, 0x01, // Usage (0x01) 19 | 0xA1, 0x01, // Collection (Application) 20 | 0x15, 0x00, // Logical Minimum (0) 21 | 0x25, 0xFF, // Logical Maximum (255) 22 | 0x75, 0x08, // Report Size (8) 23 | 0x95, 0x40, // Report Count (64) 24 | 0x09, 0x01, // Usage (0x01) 25 | 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 26 | 0x95, 0x40, // Report Count (64) 27 | 0x09, 0x01, // Usage (0x01) 28 | 0x91, 29 | 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 30 | 0x95, 0x01, // Report Count (1) 31 | 0x09, 0x01, // Usage (0x01) 32 | 0xB1, 33 | 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 34 | 0xC0, // End Collection 35 | 36 | // 32 bytes 37 | ]; 38 | 39 | pub struct CmsisDapV1<'a, B: UsbBus> { 40 | interface: InterfaceNumber, 41 | name: StringIndex, 42 | read_ep: EndpointOut<'a, B>, 43 | write_ep: EndpointIn<'a, B>, 44 | } 45 | 46 | impl CmsisDapV1<'_, B> { 47 | pub fn new(alloc: &UsbBusAllocator) -> CmsisDapV1 { 48 | CmsisDapV1 { 49 | interface: alloc.interface(), 50 | name: alloc.string(), 51 | read_ep: alloc.interrupt(DAP1_PACKET_SIZE, 1), 52 | write_ep: alloc.interrupt(DAP1_PACKET_SIZE, 1), 53 | } 54 | } 55 | 56 | pub fn process(&mut self) -> Option { 57 | let mut buf = [0u8; DAP1_PACKET_SIZE as usize]; 58 | match self.read_ep.read(&mut buf) { 59 | Ok(size) if size > 0 => Some(Request::DAP1Command((buf, size))), 60 | _ => None, 61 | } 62 | } 63 | 64 | pub fn write_packet(&mut self, data: &[u8]) -> Result<()> { 65 | if data.len() > self.write_ep.max_packet_size() as usize { 66 | return Err(UsbError::BufferOverflow); 67 | } 68 | self.write_ep.write(data).map(|_| ()) 69 | } 70 | } 71 | 72 | impl UsbClass for CmsisDapV1<'_, B> { 73 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { 74 | writer.interface_alt( 75 | self.interface, 76 | device::DEFAULT_ALTERNATE_SETTING, 77 | INTERFACE_CLASS_HID, 78 | 0, 79 | 0, 80 | Some(self.name), 81 | )?; 82 | 83 | let descriptor_len = REPORT_DESCRIPTOR.len(); 84 | if descriptor_len > u16::max_value() as usize { 85 | return Err(UsbError::InvalidState); 86 | } 87 | let descriptor_len = (descriptor_len as u16).to_le_bytes(); 88 | writer.write( 89 | DescriptorType::Hid as u8, 90 | &[ 91 | 0x11, // bcdHID.lower 92 | 0x01, // bcdHID.upper 93 | 0x00, // bCountryCode: 0 = not supported 94 | 0x01, // bNumDescriptors 95 | DescriptorType::Report as u8, // bDescriptorType 96 | descriptor_len[0], // bDescriptorLength.lower 97 | descriptor_len[1], // bDescriptorLength.upper 98 | ], 99 | )?; 100 | 101 | writer.endpoint(&self.read_ep)?; 102 | writer.endpoint(&self.write_ep)?; 103 | 104 | Ok(()) 105 | } 106 | 107 | fn get_string(&self, index: StringIndex, _lang_id: u16) -> Option<&str> { 108 | if index == self.name { 109 | Some("HS-Probe CMSIS-DAP v1 Interface") 110 | } else { 111 | None 112 | } 113 | } 114 | 115 | fn control_in(&mut self, xfer: ControlIn) { 116 | let req = xfer.request(); 117 | if !(req.request_type == RequestType::Standard 118 | && req.recipient == Recipient::Interface 119 | && req.index == u8::from(self.interface) as u16) 120 | { 121 | return; 122 | } 123 | 124 | if req.request == control::Request::GET_DESCRIPTOR { 125 | let (dtype, index) = req.descriptor_type_index(); 126 | if dtype == DescriptorType::Report as u8 && index == 0 { 127 | xfer.accept_with(REPORT_DESCRIPTOR).ok(); 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /firmware/src/usb/dap_v2.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Request; 2 | use crate::DAP2_PACKET_SIZE; 3 | use usb_device::class_prelude::*; 4 | use usb_device::Result; 5 | 6 | pub struct CmsisDapV2<'a, B: UsbBus> { 7 | interface: InterfaceNumber, 8 | name: StringIndex, 9 | read_ep: EndpointOut<'a, B>, 10 | write_ep: EndpointIn<'a, B>, 11 | trace_ep: EndpointIn<'a, B>, 12 | trace_busy: bool, 13 | } 14 | 15 | impl CmsisDapV2<'_, B> { 16 | pub fn new(alloc: &UsbBusAllocator) -> CmsisDapV2 { 17 | CmsisDapV2 { 18 | interface: alloc.interface(), 19 | name: alloc.string(), 20 | read_ep: alloc.bulk(DAP2_PACKET_SIZE), 21 | write_ep: alloc.bulk(DAP2_PACKET_SIZE), 22 | trace_ep: alloc.bulk(DAP2_PACKET_SIZE), 23 | trace_busy: false, 24 | } 25 | } 26 | 27 | pub fn process(&mut self) -> Option { 28 | let mut buf = [0u8; DAP2_PACKET_SIZE as usize]; 29 | match self.read_ep.read(&mut buf) { 30 | Ok(size) if size > 0 => Some(Request::DAP2Command((buf, size))), 31 | _ => None, 32 | } 33 | } 34 | 35 | pub fn write_packet(&mut self, data: &[u8]) -> Result<()> { 36 | if data.len() > self.write_ep.max_packet_size() as usize { 37 | return Err(UsbError::BufferOverflow); 38 | } 39 | self.write_ep.write(data).map(|_| ()) 40 | } 41 | 42 | pub fn trace_busy(&self) -> bool { 43 | self.trace_busy 44 | } 45 | 46 | pub fn trace_write(&mut self, data: &[u8]) -> Result<()> { 47 | if data.len() > self.trace_ep.max_packet_size() as usize { 48 | return Err(UsbError::BufferOverflow); 49 | } 50 | self.trace_ep.write(data).map(|_| ())?; 51 | self.trace_busy = true; 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl UsbClass for CmsisDapV2<'_, B> { 57 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { 58 | writer.interface_alt(self.interface, 0, 0xff, 0, 0, Some(self.name))?; 59 | 60 | writer.endpoint(&self.read_ep)?; 61 | writer.endpoint(&self.write_ep)?; 62 | writer.endpoint(&self.trace_ep)?; 63 | 64 | Ok(()) 65 | } 66 | 67 | fn get_string(&self, index: StringIndex, _lang_id: u16) -> Option<&str> { 68 | if index == self.name { 69 | Some("HS-probe CMSIS-DAP v2 Interface") 70 | } else { 71 | None 72 | } 73 | } 74 | 75 | fn reset(&mut self) { 76 | self.trace_busy = false; 77 | } 78 | 79 | fn endpoint_in_complete(&mut self, addr: EndpointAddress) { 80 | if addr == self.trace_ep.address() { 81 | self.trace_busy = false; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /firmware/src/usb/dfu.rs: -------------------------------------------------------------------------------- 1 | use usb_device::control::{Recipient, RequestType}; 2 | use usb_device::Result; 3 | use usb_device::{class_prelude::*, device}; 4 | 5 | #[allow(unused)] 6 | mod request { 7 | pub const DFU_DETACH: u8 = 0; 8 | pub const DFU_DNLOAD: u8 = 1; 9 | pub const DFU_UPLOAD: u8 = 2; 10 | pub const DFU_GETSTATUS: u8 = 3; 11 | pub const DFU_CLRSTATUS: u8 = 4; 12 | pub const DFU_GETSTATE: u8 = 5; 13 | pub const DFU_ABORT: u8 = 6; 14 | } 15 | 16 | pub struct DfuRuntime { 17 | interface: InterfaceNumber, 18 | name: StringIndex, 19 | } 20 | 21 | impl DfuRuntime { 22 | pub fn new(alloc: &UsbBusAllocator) -> DfuRuntime { 23 | DfuRuntime { 24 | interface: alloc.interface(), 25 | name: alloc.string(), 26 | } 27 | } 28 | } 29 | 30 | impl UsbClass for DfuRuntime { 31 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { 32 | writer.interface_alt( 33 | self.interface, 34 | device::DEFAULT_ALTERNATE_SETTING, 35 | 0xFE, 36 | 1, 37 | 1, 38 | Some(self.name), 39 | )?; 40 | 41 | // DFU Functional Descriptor 42 | writer.write( 43 | 0x21, // Functional descriptor type 44 | &[ 45 | 0x0F, // bmAttributes 46 | 0xFF, 0x00, // wDetachTimeOut 47 | 0x08, 0x00, // wTransferSize 48 | 0x00, 0x01, // bcdDFUVersion 49 | ], 50 | )?; 51 | 52 | Ok(()) 53 | } 54 | 55 | fn get_string(&self, index: StringIndex, _lang_id: u16) -> Option<&str> { 56 | if index == self.name { 57 | Some("HS-Probe DFU") 58 | } else { 59 | None 60 | } 61 | } 62 | 63 | fn control_in(&mut self, xfer: ControlIn) { 64 | let req = xfer.request(); 65 | if !(req.request_type == RequestType::Class 66 | && req.recipient == Recipient::Interface 67 | && req.index == u8::from(self.interface) as u16) 68 | { 69 | return; 70 | } 71 | 72 | match req.request { 73 | request::DFU_GETSTATUS => { 74 | xfer.accept_with_static(&[0x00; 6]).ok(); 75 | } 76 | _ => { 77 | xfer.reject().ok(); 78 | } 79 | } 80 | } 81 | 82 | fn control_out(&mut self, xfer: ControlOut) { 83 | let req = xfer.request(); 84 | if !(req.request_type == RequestType::Class 85 | && req.recipient == Recipient::Interface 86 | && req.index == u8::from(self.interface) as u16) 87 | { 88 | return; 89 | } 90 | 91 | match req.request { 92 | request::DFU_DETACH => { 93 | hs_probe_bsp::bootload::bootload(); 94 | } 95 | _ => { 96 | xfer.reject().ok(); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /firmware/src/usb/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Request; 2 | use crate::bsp::cortex_m; 3 | use crate::bsp::stm32ral::{otg_hs_device, otg_hs_global, otg_hs_pwrclk, usbphyc}; 4 | use crate::VCP_PACKET_SIZE; 5 | use hs_probe_bsp::otg_hs::{UsbBus, UsbBusType}; 6 | use hs_probe_bsp::rcc::Clocks; 7 | use usb_device::bus::UsbBusAllocator; 8 | use usb_device::prelude::*; 9 | use usbd_serial::{LineCoding, SerialPort}; 10 | 11 | mod dap_v1; 12 | mod dap_v2; 13 | mod dfu; 14 | mod winusb; 15 | 16 | use dap_v1::CmsisDapV1; 17 | use dap_v2::CmsisDapV2; 18 | use dfu::DfuRuntime; 19 | use winusb::MicrosoftDescriptors; 20 | 21 | struct UninitializedUSB { 22 | phy: usbphyc::Instance, 23 | global: otg_hs_global::Instance, 24 | device: otg_hs_device::Instance, 25 | pwrclk: otg_hs_pwrclk::Instance, 26 | } 27 | 28 | struct InitializedUSB { 29 | device: UsbDevice<'static, UsbBusType>, 30 | device_state: UsbDeviceState, 31 | winusb: MicrosoftDescriptors, 32 | dap_v1: CmsisDapV1<'static, UsbBusType>, 33 | dap_v2: CmsisDapV2<'static, UsbBusType>, 34 | serial: SerialPort<'static, UsbBusType>, 35 | dfu: DfuRuntime, 36 | } 37 | 38 | #[allow(clippy::large_enum_variant)] 39 | enum State { 40 | Uninitialized(UninitializedUSB), 41 | Initialized(InitializedUSB), 42 | Initializing, 43 | } 44 | 45 | impl State { 46 | pub fn as_initialized(&self) -> &InitializedUSB { 47 | if let State::Initialized(initialized) = self { 48 | initialized 49 | } else { 50 | panic!("USB is not initialized yet"); 51 | } 52 | } 53 | 54 | pub fn as_initialized_mut(&mut self) -> &mut InitializedUSB { 55 | if let State::Initialized(initialized) = self { 56 | initialized 57 | } else { 58 | panic!("USB is not initialized yet"); 59 | } 60 | } 61 | } 62 | 63 | static mut EP_MEMORY: [u32; 4096] = [0; 4096]; 64 | static mut USB_BUS: Option> = None; 65 | 66 | /// USB stack interface 67 | #[allow(clippy::upper_case_acronyms)] 68 | pub struct USB { 69 | state: State, 70 | } 71 | 72 | impl USB { 73 | /// Create a new USB object from the peripheral instance 74 | pub fn new( 75 | phy: usbphyc::Instance, 76 | global: otg_hs_global::Instance, 77 | device: otg_hs_device::Instance, 78 | pwrclk: otg_hs_pwrclk::Instance, 79 | ) -> Self { 80 | let usb = UninitializedUSB { 81 | phy, 82 | global, 83 | device, 84 | pwrclk, 85 | }; 86 | USB { 87 | state: State::Uninitialized(usb), 88 | } 89 | } 90 | 91 | /// Initialise the USB peripheral ready to start processing packets 92 | pub fn setup(&mut self, clocks: &Clocks, serial_string: &'static str) { 93 | let state = core::mem::replace(&mut self.state, State::Initializing); 94 | if let State::Uninitialized(usb) = state { 95 | cortex_m::interrupt::free(|_| unsafe { 96 | let usb = hs_probe_bsp::otg_hs::USB { 97 | usb_phy: usb.phy, 98 | usb_global: usb.global, 99 | usb_device: usb.device, 100 | usb_pwrclk: usb.pwrclk, 101 | hclk: clocks.hclk(), 102 | }; 103 | 104 | let usb_bus = UsbBus::new(usb, &mut EP_MEMORY); 105 | USB_BUS = Some(usb_bus); 106 | let usb_bus = USB_BUS.as_ref().unwrap(); 107 | 108 | let winusb = MicrosoftDescriptors; 109 | 110 | // Order of these calls is important, if the interface numbers for CmsisDapV2 or DfuRuntime change, 111 | // definitions in winusb.rs (DAP_V2_INTERFACE, DFU_INTERFACE) have to be adapted! 112 | let serial = SerialPort::new(usb_bus); 113 | let dap_v1 = CmsisDapV1::new(usb_bus); 114 | let dap_v2 = CmsisDapV2::new(usb_bus); 115 | let dfu = DfuRuntime::new(usb_bus); 116 | 117 | let device = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x1209, 0x4853)) 118 | .manufacturer("Probe-rs development team") 119 | .product("HS-Probe with CMSIS-DAP Support") 120 | .serial_number(serial_string) 121 | .composite_with_iads() 122 | .max_packet_size_0(64) 123 | .max_power(500) 124 | .device_release(0x11) 125 | .build(); 126 | let device_state = device.state(); 127 | 128 | let usb = InitializedUSB { 129 | device, 130 | device_state, 131 | winusb, 132 | dap_v1, 133 | dap_v2, 134 | serial, 135 | dfu, 136 | }; 137 | self.state = State::Initialized(usb) 138 | }); 139 | } else { 140 | panic!("Invalid state"); 141 | } 142 | } 143 | 144 | /// Process a pending USB interrupt. 145 | /// 146 | /// Call this function when a USB interrupt occurs. 147 | /// 148 | /// Returns Some(Request) if a new request has been received 149 | /// from the host. 150 | /// 151 | /// This function will clear the interrupt bits of all interrupts 152 | /// it processes; if any are unprocessed the USB interrupt keeps 153 | /// triggering until all are processed. 154 | pub fn interrupt(&mut self, vcp_idle: bool) -> Option { 155 | let usb = self.state.as_initialized_mut(); 156 | if usb.device.poll(&mut [ 157 | &mut usb.winusb, 158 | &mut usb.serial, 159 | &mut usb.dap_v1, 160 | &mut usb.dap_v2, 161 | &mut usb.dfu, 162 | ]) { 163 | let old_state = usb.device_state; 164 | let new_state = usb.device.state(); 165 | usb.device_state = new_state; 166 | if (old_state != new_state) && (new_state != UsbDeviceState::Configured) { 167 | return Some(Request::Suspend); 168 | } 169 | 170 | let r = usb.dap_v1.process(); 171 | if r.is_some() { 172 | return r; 173 | } 174 | 175 | let r = usb.dap_v2.process(); 176 | if r.is_some() { 177 | return r; 178 | } 179 | 180 | if vcp_idle { 181 | let mut buf = [0; VCP_PACKET_SIZE as usize]; 182 | let serialdata = usb.serial.read(&mut buf); 183 | match serialdata { 184 | Ok(x) => { 185 | return Some(Request::VCPPacket((buf, x))); 186 | } 187 | // discard error? 188 | Err(_e) => (), 189 | } 190 | } 191 | } 192 | None 193 | } 194 | 195 | /// Transmit a DAP report back over the DAPv1 HID interface 196 | pub fn dap1_reply(&mut self, data: &[u8]) { 197 | let usb = self.state.as_initialized_mut(); 198 | usb.dap_v1 199 | .write_packet(data) 200 | .expect("DAPv1 EP write failed"); 201 | } 202 | 203 | /// Transmit a DAP report back over the DAPv2 bulk interface 204 | pub fn dap2_reply(&mut self, data: &[u8]) { 205 | let usb = self.state.as_initialized_mut(); 206 | usb.dap_v2 207 | .write_packet(data) 208 | .expect("DAPv2 EP write failed"); 209 | } 210 | 211 | /// Check if SWO endpoint is currently busy transmitting data 212 | pub fn dap2_swo_is_busy(&self) -> bool { 213 | let usb = self.state.as_initialized(); 214 | usb.dap_v2.trace_busy() 215 | } 216 | 217 | /// Transmit SWO streaming data back over the DAPv2 bulk interface 218 | pub fn dap2_stream_swo(&mut self, data: &[u8]) { 219 | let usb = self.state.as_initialized_mut(); 220 | usb.dap_v2.trace_write(data).expect("trace EP write failed"); 221 | } 222 | 223 | /// Grab the current LineCoding (UART parameters) from the CDC-ACM stack 224 | pub fn serial_line_encoding(&self) -> &LineCoding { 225 | let usb = self.state.as_initialized(); 226 | usb.serial.line_coding() 227 | } 228 | 229 | /// Return UART data to host trough USB 230 | pub fn serial_return(&mut self, data: &[u8]) { 231 | let usb = self.state.as_initialized_mut(); 232 | usb.serial.write(data).expect("Serial EP write failed"); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /firmware/src/usb/winusb.rs: -------------------------------------------------------------------------------- 1 | use num_enum::TryFromPrimitive; 2 | use usb_device::class_prelude::*; 3 | use usb_device::control::RequestType; 4 | 5 | const fn u16_low(val: u16) -> u8 { 6 | val.to_le_bytes()[0] 7 | } 8 | 9 | const fn u16_high(val: u16) -> u8 { 10 | val.to_le_bytes()[1] 11 | } 12 | 13 | #[allow(non_snake_case)] 14 | #[repr(u16)] 15 | #[derive(TryFromPrimitive)] 16 | pub enum OSFeatureDescriptorType { 17 | CompatibleID = 4, 18 | Properties = 5, 19 | Descriptor = 7, 20 | } 21 | 22 | const LEN: u16 = 330; 23 | 24 | const VENDOR_CODE: u8 = 0x41; 25 | 26 | const DAP_V2_INTERFACE: u8 = 3; 27 | const DFU_INTERFACE: u8 = 4; 28 | 29 | enum MsDescriptorTypes { 30 | Header = 0x0, 31 | HeaderConfiguration = 0x1, 32 | HeaderFunction = 0x2, 33 | CompatibleId = 0x3, 34 | RegistryProperty = 0x4, 35 | } 36 | 37 | /// Microsoft OS 2.0 descriptor, according to https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification 38 | /// 39 | /// For interface ['DAP_V2_INTERFACE'] this configures: 40 | /// - compatible ID 'WinUSB' 41 | /// - registry property DeviceInterfaceGUIDs = ['{CDB3B5AD-293B-4663-AA36-1AAE46463776}'] 42 | /// 43 | /// For interface ['DFU_INTERFACE']: 44 | /// - compatible ID 'WinUSB' 45 | /// - registry property DeviceInterfaceGUIDs = ['{A5DCBF10-6530-11D2-901F-00C04FB951ED}'] 46 | const MS_OS_DESCRIPTOR: [u8; LEN as usize] = [ 47 | 0xa, 48 | 0x00, // Length 10 bytes 49 | MsDescriptorTypes::Header as u8, 50 | 0x00, // HEADER_DESCRIPTOR 51 | 0x00, 52 | 0x00, 53 | 0x03, 54 | 0x06, // Windows version 55 | u16_low(LEN), 56 | u16_high(LEN), // Total descriptor length 57 | // Function header, 58 | 0x8, 59 | 0x0, // Length 8 60 | MsDescriptorTypes::HeaderFunction as u8, 61 | 0x00, 62 | DAP_V2_INTERFACE, // First interface (dap v2) 63 | 0x0, // reserved 64 | 8 + 20 + 132, 65 | 0x00, // Subset length, including header 66 | // compatible ID descriptor 67 | 20, 68 | 0x00, // length 20 69 | MsDescriptorTypes::CompatibleId as u8, 70 | 0x00, 71 | b'W', 72 | b'I', 73 | b'N', 74 | b'U', 75 | b'S', 76 | b'B', 77 | 0x00, 78 | 0x00, // Compatible ID: 8 bytes ASCII 79 | 0x00, 80 | 0x00, 81 | 0x00, 82 | 0x00, 83 | 0x00, 84 | 0x00, 85 | 0x00, 86 | 0x00, // Sub-Compatible ID: 8 bytes ASCII 87 | // Registry property 88 | 80 + 2 + 42 + 2 + 2 + 2 + 2, 89 | 0x00, // length 90 | MsDescriptorTypes::RegistryProperty as u8, 91 | 0x00, 92 | 7, 93 | 0, // Data type: multi sz 94 | 42, 95 | 0x00, // property name length, 96 | b'D', 97 | 0, 98 | b'e', 99 | 0, 100 | b'v', 101 | 0, 102 | b'i', 103 | 0, 104 | b'c', 105 | 0, 106 | b'e', 107 | 0, 108 | b'I', 109 | 0, 110 | b'n', 111 | 0, 112 | b't', 113 | 0, 114 | b'e', 115 | 0, 116 | b'r', 117 | 0, 118 | b'f', 119 | 0, 120 | b'a', 121 | 0, 122 | b'c', 123 | 0, 124 | b'e', 125 | 0, 126 | b'G', 127 | 0, 128 | b'U', 129 | 0, 130 | b'I', 131 | 0, 132 | b'D', 133 | 0, 134 | b's', 135 | 0, 136 | 0, 137 | 0, 138 | 80, 139 | 0x00, // data length 140 | b'{', 141 | 0, 142 | b'C', 143 | 0, 144 | b'D', 145 | 0, 146 | b'B', 147 | 0, 148 | b'3', 149 | 0, 150 | b'B', 151 | 0, 152 | b'5', 153 | 0, 154 | b'A', 155 | 0, 156 | b'D', 157 | 0, 158 | b'-', 159 | 0, 160 | b'2', 161 | 0, 162 | b'9', 163 | 0, 164 | b'3', 165 | 0, 166 | b'B', 167 | 0, 168 | b'-', 169 | 0, 170 | b'4', 171 | 0, 172 | b'6', 173 | 0, 174 | b'6', 175 | 0, 176 | b'3', 177 | 0, 178 | b'-', 179 | 0, 180 | b'A', 181 | 0, 182 | b'A', 183 | 0, 184 | b'3', 185 | 0, 186 | b'6', 187 | 0, 188 | b'-', 189 | 0, 190 | b'1', 191 | 0, 192 | b'A', 193 | 0, 194 | b'A', 195 | 0, 196 | b'E', 197 | 0, 198 | b'4', 199 | 0, 200 | b'6', 201 | 0, 202 | b'4', 203 | 0, 204 | b'6', 205 | 0, 206 | b'3', 207 | 0, 208 | b'7', 209 | 0, 210 | b'7', 211 | 0, 212 | b'6', 213 | 0, 214 | b'}', 215 | 0, 216 | 0, 217 | 0, 218 | 0, 219 | 0, 220 | // Function header, 221 | 0x8, 222 | 0x0, // Length 8 223 | MsDescriptorTypes::HeaderFunction as u8, 224 | 0x00, 225 | DFU_INTERFACE, // First interface (dap v2 -> 1) 226 | 0x0, // reserved 227 | 8 + 20 + 132, // Header + compatible ID 228 | 0x00, // Subset length, including header 229 | // compatible ID descriptor 230 | 20, 231 | 0x00, // length 20 232 | MsDescriptorTypes::CompatibleId as u8, 233 | 0x00, 234 | b'W', 235 | b'I', 236 | b'N', 237 | b'U', 238 | b'S', 239 | b'B', 240 | 0x00, 241 | 0x00, // Compatible ID: 8 bytes ASCII 242 | 0x00, 243 | 0x00, 244 | 0x00, 245 | 0x00, 246 | 0x00, 247 | 0x00, 248 | 0x00, 249 | 0x00, // Sub-Compatible ID: 8 bytes ASCII 250 | // Registry property 251 | 80 + 2 + 42 + 2 + 2 + 2 + 2, 252 | 0x00, // length 253 | MsDescriptorTypes::RegistryProperty as u8, 254 | 0x00, 255 | 7, 256 | 0, // Data type: multi sz 257 | 42, 258 | 0x00, // property name length, 259 | b'D', 260 | 0, 261 | b'e', 262 | 0, 263 | b'v', 264 | 0, 265 | b'i', 266 | 0, 267 | b'c', 268 | 0, 269 | b'e', 270 | 0, 271 | b'I', 272 | 0, 273 | b'n', 274 | 0, 275 | b't', 276 | 0, 277 | b'e', 278 | 0, 279 | b'r', 280 | 0, 281 | b'f', 282 | 0, 283 | b'a', 284 | 0, 285 | b'c', 286 | 0, 287 | b'e', 288 | 0, 289 | b'G', 290 | 0, 291 | b'U', 292 | 0, 293 | b'I', 294 | 0, 295 | b'D', 296 | 0, 297 | b's', 298 | 0, 299 | 0, 300 | 0, 301 | 80, 302 | 0x00, // data length 303 | b'{', 304 | 0, 305 | b'A', 306 | 0, 307 | b'5', 308 | 0, 309 | b'D', 310 | 0, 311 | b'C', 312 | 0, 313 | b'B', 314 | 0, 315 | b'F', 316 | 0, 317 | b'1', 318 | 0, 319 | b'0', 320 | 0, 321 | b'-', 322 | 0, 323 | b'6', 324 | 0, 325 | b'5', 326 | 0, 327 | b'3', 328 | 0, 329 | b'0', 330 | 0, 331 | b'-', 332 | 0, 333 | b'1', 334 | 0, 335 | b'1', 336 | 0, 337 | b'D', 338 | 0, 339 | b'2', 340 | 0, 341 | b'-', 342 | 0, 343 | b'9', 344 | 0, 345 | b'0', 346 | 0, 347 | b'1', 348 | 0, 349 | b'F', 350 | 0, 351 | b'-', 352 | 0, 353 | b'0', 354 | 0, 355 | b'0', 356 | 0, 357 | b'C', 358 | 0, 359 | b'0', 360 | 0, 361 | b'4', 362 | 0, 363 | b'F', 364 | 0, 365 | b'B', 366 | 0, 367 | b'9', 368 | 0, 369 | b'5', 370 | 0, 371 | b'1', 372 | 0, 373 | b'E', 374 | 0, 375 | b'D', 376 | 0, 377 | b'}', 378 | 0, 379 | 0, 380 | 0, 381 | 0, 382 | 0, 383 | ]; 384 | 385 | pub struct MicrosoftDescriptors; 386 | 387 | impl UsbClass for MicrosoftDescriptors { 388 | fn get_bos_descriptors(&self, writer: &mut BosWriter) -> usb_device::Result<()> { 389 | writer.capability( 390 | 5, // Platform capability 391 | &[ 392 | 0, // reserved 393 | 0xdf, 394 | 0x60, 395 | 0xdd, 396 | 0xd8, 397 | 0x89, 398 | 0x45, 399 | 0xc7, 400 | 0x4c, 401 | 0x9c, 402 | 0xd2, 403 | 0x65, 404 | 0x9d, 405 | 0x9e, 406 | 0x64, 407 | 0x8A, 408 | 0x9f, // platform capability UUID , Microsoft OS 2.0 platform compabitility 409 | 0x00, 410 | 0x00, 411 | 0x03, 412 | 0x06, // Minimum compatible Windows version (8.1) 413 | u16_low(LEN), 414 | u16_high(LEN), // desciptor set total len , 415 | VENDOR_CODE, 416 | 0x0, // Device does not support alternate enumeration 417 | ], 418 | ) 419 | } 420 | 421 | fn control_in(&mut self, xfer: ControlIn) { 422 | let req = xfer.request(); 423 | if req.request_type != RequestType::Vendor { 424 | return; 425 | } 426 | 427 | // The Microsoft OS descriptors are requested with the vendor code which 428 | // is returned in the BOS descriptor. 429 | if req.request == VENDOR_CODE { 430 | if req.index == 0x7 { 431 | xfer.accept_with_static(&MS_OS_DESCRIPTOR).ok(); 432 | } else { 433 | xfer.reject().ok(); 434 | } 435 | } 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /firmware/src/vcp.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Alexis Marquet 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use core::cmp::Ordering; 5 | 6 | use crate::{ 7 | bsp::{dma::DMA, gpio::Pins, rcc::Clocks, stm32ral}, 8 | VCP_PACKET_SIZE, 9 | }; 10 | 11 | use stm32ral::usart; 12 | use stm32ral::{modify_reg, write_reg}; 13 | use usbd_serial::{ParityType, StopBits}; 14 | 15 | /// UART configuration struct 16 | #[derive(PartialEq, Eq, Clone, Copy)] 17 | pub struct VcpConfig { 18 | pub stop_bits: StopBits, 19 | pub data_bits: u8, 20 | pub parity_type: ParityType, 21 | pub data_rate: u32, 22 | } 23 | 24 | impl Default for VcpConfig { 25 | fn default() -> Self { 26 | VcpConfig { 27 | stop_bits: StopBits::One, 28 | data_bits: 8, 29 | parity_type: ParityType::None, 30 | data_rate: 8_000, 31 | } 32 | } 33 | } 34 | 35 | #[allow(clippy::upper_case_acronyms)] 36 | pub struct VCP<'a> { 37 | uart: usart::Instance, 38 | pins: &'a Pins<'a>, 39 | dma: &'a DMA, 40 | rx_buffer: [u8; VCP_PACKET_SIZE as usize], 41 | tx_buffer: [u8; VCP_PACKET_SIZE as usize], 42 | last_idx_rx: usize, 43 | last_idx_tx: usize, 44 | fck: u32, 45 | } 46 | 47 | impl<'a> VCP<'a> { 48 | pub fn new(uart: usart::Instance, pins: &'a Pins, dma: &'a DMA) -> Self { 49 | VCP { 50 | uart, 51 | pins, 52 | dma, 53 | rx_buffer: [0; VCP_PACKET_SIZE as usize], 54 | tx_buffer: [0; VCP_PACKET_SIZE as usize], 55 | last_idx_rx: 0, 56 | last_idx_tx: 0, 57 | fck: 72_000_000, 58 | } 59 | } 60 | 61 | /// Call with the system clock speeds to configure peripherals that require timing information. 62 | /// 63 | /// Currently this only configures the pins & DMA RX 64 | pub fn setup(&mut self, clocks: &Clocks) { 65 | self.fck = clocks.pclk1(); 66 | 67 | self.pins.usart2_tx.set_ospeed_veryhigh(); 68 | self.pins.usart2_tx.set_otype_pushpull(); 69 | self.pins.usart2_tx.set_pull_up(); 70 | self.pins.usart2_tx.set_mode_alternate(); 71 | self.pins.usart2_tx.set_af(7); 72 | 73 | self.pins.usart2_rx.set_ospeed_veryhigh(); 74 | self.pins.usart2_rx.set_otype_pushpull(); 75 | self.pins.usart2_rx.set_pull_up(); 76 | self.pins.usart2_rx.set_mode_alternate(); 77 | self.pins.usart2_rx.set_af(7); 78 | 79 | self.dma.usart2_start_rx(&mut self.rx_buffer); 80 | } 81 | 82 | /// Start the VCP function. 83 | /// 84 | /// This enables both TX & RX. 85 | pub fn start(&mut self) { 86 | self.last_idx_rx = 0; 87 | self.last_idx_tx = 0; 88 | write_reg!(usart, self.uart, CR3, DMAR: Enabled, DMAT: Enabled); 89 | 90 | write_reg!( 91 | usart, 92 | self.uart, 93 | CR1, 94 | OVER8: Oversampling8, 95 | RE: Enabled, 96 | TE: Enabled, 97 | UE: Enabled 98 | ); 99 | } 100 | 101 | /// Disable UART. 102 | pub fn stop(&self) { 103 | modify_reg!( 104 | usart, 105 | self.uart, 106 | CR1, 107 | RE: Disabled, 108 | TE: Disabled, 109 | UE: Disabled 110 | ); 111 | } 112 | 113 | /// Fetch current number of bytes available. 114 | /// 115 | /// Subsequent calls to read() may return a different amount of data. 116 | pub fn rx_bytes_available(&self) -> usize { 117 | // length of the buffer minus the remainder of the dma transfer 118 | let dma_idx = self.rx_buffer.len() - self.dma.usart2_rx_ndtr(); 119 | if dma_idx >= self.last_idx_rx { 120 | dma_idx - self.last_idx_rx 121 | } else { 122 | (self.rx_buffer.len() - self.last_idx_rx) + dma_idx 123 | } 124 | } 125 | 126 | /// Read new UART data. 127 | /// 128 | /// Returns number of bytes written to buffer. 129 | /// 130 | /// Reads at most rx.len() new bytes, which may be less than what was received. 131 | /// Remaining data will be read on the next call, so long as the internal buffer 132 | /// doesn't overflow, which is not detected. 133 | pub fn read(&mut self, rx: &mut [u8]) -> usize { 134 | // See what index the DMA is going to write next, and copy out 135 | // all prior data. Even if the DMA writes new data while we're 136 | // processing we won't get out of sync and will handle the new 137 | // data next time read() is called. 138 | let dma_idx = self.rx_buffer.len() - self.dma.usart2_rx_ndtr(); 139 | match dma_idx.cmp(&self.last_idx_rx) { 140 | Ordering::Equal => { 141 | // No action required if no data has been received. 142 | 0 143 | } 144 | Ordering::Less => { 145 | // Wraparound occurred: 146 | // Copy from last_idx to end, and from start to new dma_idx. 147 | let mut n1 = self.rx_buffer.len() - self.last_idx_rx; 148 | let mut n2 = dma_idx; 149 | let mut new_last_idx = dma_idx; 150 | 151 | // Ensure we don't overflow rx buffer 152 | if n1 > rx.len() { 153 | n1 = rx.len(); 154 | n2 = 0; 155 | new_last_idx = self.last_idx_rx + n1; 156 | } else if (n1 + n2) > rx.len() { 157 | n2 = rx.len() - n1; 158 | new_last_idx = n2; 159 | } 160 | 161 | rx[..n1].copy_from_slice(&self.rx_buffer[self.last_idx_rx..self.last_idx_rx + n1]); 162 | rx[n1..(n1 + n2)].copy_from_slice(&self.rx_buffer[..n2]); 163 | 164 | self.last_idx_rx = new_last_idx; 165 | n1 + n2 166 | } 167 | Ordering::Greater => { 168 | // New data, no wraparound: 169 | // Copy from last_idx to new dma_idx. 170 | let mut n = dma_idx - self.last_idx_rx; 171 | 172 | // Ensure we don't overflow rx buffer 173 | if n > rx.len() { 174 | n = rx.len(); 175 | } 176 | 177 | rx[..n].copy_from_slice(&self.rx_buffer[self.last_idx_rx..self.last_idx_rx + n]); 178 | 179 | self.last_idx_rx += n; 180 | n 181 | } 182 | } 183 | } 184 | 185 | /// Setup the USART line config. 186 | /// 187 | /// This should be done between a `stop()` and a `start` call since 188 | /// configuring this requires the UE bit to be `0b0`. 189 | pub fn set_config(&mut self, coding: VcpConfig) { 190 | // Find closest divider which is also an even integer >= 16. 191 | // The baud rate is (2*fck)/BRR. 192 | let mut div = (2 * self.fck) / coding.data_rate; 193 | div &= 0xffff_fffe; 194 | if div < 16 { 195 | div = 16; 196 | } 197 | 198 | // Write BRR value based on div. 199 | // Since we are OVERSAMPLE8, shift bottom 4 bits down by 1. 200 | let brr = (div & 0xffff_fff0) | ((div & 0xf) >> 1); 201 | write_reg!(usart, self.uart, BRR, brr); 202 | 203 | // configure data bits 204 | match coding.data_bits { 205 | 7 => modify_reg!(usart, self.uart, CR1, M1: 1, M0: 0), 206 | 8 => modify_reg!(usart, self.uart, CR1, M1: 0, M0: 0), 207 | 9 => modify_reg!(usart, self.uart, CR1, M1: 0, M0: 1), 208 | _ => panic!(), 209 | } 210 | 211 | // configure stop bits 212 | match coding.stop_bits { 213 | StopBits::One => modify_reg!(usart, self.uart, CR2, STOP: 0b00), 214 | StopBits::OnePointFive => modify_reg!(usart, self.uart, CR2, STOP: 0b11), 215 | StopBits::Two => modify_reg!(usart, self.uart, CR2, STOP: 0b10), 216 | } 217 | 218 | // configure parity type 219 | match coding.parity_type { 220 | ParityType::None => modify_reg!(usart, self.uart, CR1, PCE: 0), 221 | ParityType::Odd => modify_reg!(usart, self.uart, CR1, PCE:1, PS: 1), 222 | ParityType::Event => modify_reg!(usart, self.uart, CR1, PCE:1, PS: 0), 223 | ParityType::Mark => (), // unsupported? 224 | ParityType::Space => (), // unsupported? 225 | } 226 | } 227 | 228 | /// Check state of TX Dma transfer 229 | pub fn is_tx_idle(&self) -> bool { 230 | self.dma.usart2_tx_ndtr() == 0 231 | } 232 | /// Start DMA transfer from buffer to TX Shift register. 233 | pub fn write(&mut self, tx: &[u8], len: usize) { 234 | self.tx_buffer[0..len].copy_from_slice(tx); 235 | self.dma.usart2_start_tx_transfer(&self.tx_buffer, len); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /hs-probe-bsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hs-probe-bsp" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cortex-m = "0.7.7" 9 | stm32ral = { version = "0.8.0", features = ["stm32f7x3"] } 10 | synopsys-usb-otg = { version = "0.3.0", features = ["cortex-m", "hs"] } 11 | 12 | [features] 13 | rt = ["stm32ral/rt"] 14 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/bootload.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use stm32ral::{modify_reg, scb}; 5 | 6 | static mut FLAG: u32 = 0; 7 | const FLAG_VALUE: u32 = 0xB00110AD; 8 | 9 | /// Call this function at boot in pre_init, before statics are initialised. 10 | /// 11 | /// If we reset due to requesting a bootload, this function will jump to 12 | /// the system bootloader. 13 | pub fn check() { 14 | unsafe { 15 | // If flag isn't set we just continue with the boot process 16 | if core::ptr::read_volatile(&FLAG) != FLAG_VALUE { 17 | return; 18 | } 19 | 20 | // Otherwise, clear the flag and jump to system bootloader 21 | core::ptr::write_volatile(&mut FLAG, 0); 22 | 23 | cortex_m::asm::bootload(0x0010_0000 as *const u32); 24 | } 25 | } 26 | 27 | /// Call this function to trigger a reset into the system bootloader 28 | pub fn bootload() -> ! { 29 | unsafe { 30 | // Write flag value to FLAG 31 | core::ptr::write_volatile(&mut FLAG, FLAG_VALUE); 32 | 33 | // Request system reset 34 | modify_reg!(scb, SCB, AIRCR, VECTKEYSTAT: 0x05FA, SYSRESETREQ: 1); 35 | } 36 | 37 | // Wait for reset 38 | loop { 39 | cortex_m::asm::nop(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/delay.rs: -------------------------------------------------------------------------------- 1 | use crate::rcc::Clocks; 2 | use core::sync::atomic::{AtomicU32, Ordering}; 3 | use stm32ral::syst; 4 | use stm32ral::{modify_reg, read_reg, write_reg}; 5 | 6 | const SYST_CSR_ENABLE: u32 = 1 << 0; 7 | const SYST_CSR_CLKSOURCE: u32 = 1 << 2; 8 | 9 | pub struct Delay { 10 | systick: syst::Instance, 11 | base_clock: AtomicU32, 12 | } 13 | 14 | impl Delay { 15 | pub fn new(systick: syst::Instance) -> Self { 16 | // Set clock source to processor clock 17 | modify_reg!(syst, systick, CSR, |r| (r | SYST_CSR_CLKSOURCE)); 18 | 19 | // Set reload and current values 20 | write_reg!(syst, systick, RVR, 0xffffff); 21 | write_reg!(syst, systick, CVR, 0); 22 | 23 | // Enable the counter 24 | modify_reg!(syst, systick, CSR, |r| (r | SYST_CSR_ENABLE)); 25 | 26 | Delay { 27 | systick, 28 | base_clock: AtomicU32::new(0), 29 | } 30 | } 31 | 32 | pub fn set_sysclk(&self, clocks: &Clocks) { 33 | self.base_clock.store(clocks.hclk(), Ordering::SeqCst); 34 | } 35 | 36 | pub fn delay_us(&self, us: u32) { 37 | assert!(us < 10_000); 38 | 39 | let base_clock = self.base_clock.load(Ordering::SeqCst); 40 | assert!(base_clock > 0); 41 | 42 | let ticks = (us as u64) * (base_clock as u64) / 1_000_000; 43 | self.delay_ticks(ticks as u32); 44 | } 45 | 46 | pub fn calc_period_ticks(&self, frequency: u32) -> u32 { 47 | let base_clock = self.base_clock.load(Ordering::SeqCst); 48 | assert!(base_clock > 0); 49 | 50 | base_clock / frequency 51 | } 52 | 53 | pub fn delay_ticks(&self, mut ticks: u32) { 54 | let mut last = self.get_current(); 55 | loop { 56 | let now = self.get_current(); 57 | let delta = last.wrapping_sub(now) & 0xffffff; 58 | 59 | if delta >= ticks { 60 | break; 61 | } else { 62 | ticks -= delta; 63 | last = now; 64 | } 65 | } 66 | } 67 | 68 | pub fn delay_ticks_from_last(&self, mut ticks: u32, mut last: u32) -> u32 { 69 | loop { 70 | let now = self.get_current(); 71 | let delta = last.wrapping_sub(now) & 0xffffff; 72 | 73 | if delta >= ticks { 74 | break now; 75 | } else { 76 | ticks -= delta; 77 | last = now; 78 | } 79 | } 80 | } 81 | 82 | #[inline(always)] 83 | pub fn get_current(&self) -> u32 { 84 | read_reg!(syst, self.systick, CVR) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/dma.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use stm32ral::dma; 5 | use stm32ral::{modify_reg, read_reg, write_reg}; 6 | 7 | /* 8 | SPI1_RX: DMA2, stream 2, channel 3 9 | SPI1_TX: DMA2, stream 3, channel 3 10 | SPI2_RX: DMA1, stream 3, channel 0 11 | SPI2_TX: DMA1, stream 4, channel 0 12 | USART1_RX: DMA2, stream 5, channel 4 13 | USART2_RX: DMA1, stream 5, channel 4 14 | USART2_TX: DMA1, stream 6, channel 4 15 | */ 16 | 17 | const SPI_DR_OFFSET: u32 = 0x0C; 18 | const UART_RDR_OFFSET: u32 = 0x24; 19 | const UART_TDR_OFFSET: u32 = 0x28; 20 | 21 | pub struct DMA { 22 | dma1: dma::Instance, 23 | dma2: dma::Instance, 24 | } 25 | 26 | impl DMA { 27 | pub fn new(dma1: dma::Instance, dma2: dma::Instance) -> Self { 28 | DMA { dma1, dma2 } 29 | } 30 | 31 | pub fn setup(&self) { 32 | // Set up DMA2 stream 2, channel 3 for SPI1_RX 33 | write_reg!( 34 | dma, 35 | self.dma2, 36 | CR2, 37 | CHSEL: 3, 38 | PL: High, 39 | MSIZE: Bits8, 40 | PSIZE: Bits8, 41 | MINC: Incremented, 42 | PINC: Fixed, 43 | CIRC: Disabled, 44 | DIR: PeripheralToMemory, 45 | EN: Disabled 46 | ); 47 | write_reg!( 48 | dma, 49 | self.dma2, 50 | PAR2, 51 | stm32ral::spi::SPI1 as u32 + SPI_DR_OFFSET 52 | ); 53 | 54 | // Set up DMA2 stream 3, channel 3 for SPI1_TX 55 | write_reg!( 56 | dma, 57 | self.dma2, 58 | CR3, 59 | CHSEL: 3, 60 | PL: High, 61 | MSIZE: Bits8, 62 | PSIZE: Bits8, 63 | MINC: Incremented, 64 | PINC: Fixed, 65 | CIRC: Disabled, 66 | DIR: MemoryToPeripheral, 67 | EN: Disabled 68 | ); 69 | write_reg!( 70 | dma, 71 | self.dma2, 72 | PAR3, 73 | stm32ral::spi::SPI1 as u32 + SPI_DR_OFFSET 74 | ); 75 | 76 | // Set up DMA1 stream 3, channel 0 for SPI2_RX 77 | write_reg!( 78 | dma, 79 | self.dma1, 80 | CR3, 81 | CHSEL: 0, 82 | PL: High, 83 | MSIZE: Bits8, 84 | PSIZE: Bits8, 85 | MINC: Incremented, 86 | PINC: Fixed, 87 | CIRC: Disabled, 88 | DIR: PeripheralToMemory, 89 | EN: Disabled 90 | ); 91 | write_reg!( 92 | dma, 93 | self.dma1, 94 | PAR3, 95 | stm32ral::spi::SPI2 as u32 + SPI_DR_OFFSET 96 | ); 97 | 98 | // Set up DMA1 stream 4, channel 0 for SPI2_TX 99 | write_reg!( 100 | dma, 101 | self.dma1, 102 | CR4, 103 | CHSEL: 0, 104 | PL: High, 105 | MSIZE: Bits8, 106 | PSIZE: Bits8, 107 | MINC: Incremented, 108 | PINC: Fixed, 109 | CIRC: Disabled, 110 | DIR: MemoryToPeripheral, 111 | EN: Disabled 112 | ); 113 | write_reg!( 114 | dma, 115 | self.dma1, 116 | PAR4, 117 | stm32ral::spi::SPI2 as u32 + SPI_DR_OFFSET 118 | ); 119 | 120 | // Set up DMA2 stream 5, channel 4 for USART1_RX 121 | write_reg!( 122 | dma, 123 | self.dma2, 124 | CR5, 125 | CHSEL: 4, 126 | PL: High, 127 | MSIZE: Bits8, 128 | PSIZE: Bits8, 129 | MINC: Incremented, 130 | PINC: Fixed, 131 | CIRC: Enabled, 132 | DIR: PeripheralToMemory, 133 | EN: Disabled 134 | ); 135 | write_reg!( 136 | dma, 137 | self.dma2, 138 | PAR5, 139 | stm32ral::usart::USART1 as u32 + UART_RDR_OFFSET 140 | ); 141 | 142 | // Set up DMA1 stream 5, channel 4 for USART2_RX 143 | write_reg!( 144 | dma, 145 | self.dma1, 146 | CR5, 147 | CHSEL: 4, 148 | PL: High, 149 | MSIZE: Bits8, 150 | PSIZE: Bits8, 151 | MINC: Incremented, 152 | PINC: Fixed, 153 | CIRC: Enabled, 154 | DIR: PeripheralToMemory, 155 | EN: Disabled 156 | ); 157 | write_reg!( 158 | dma, 159 | self.dma1, 160 | PAR5, 161 | stm32ral::usart::USART2 as u32 + UART_RDR_OFFSET 162 | ); 163 | 164 | // Set up DMA1 stream 6, channel 4 for USART2_TX 165 | write_reg!( 166 | dma, 167 | self.dma1, 168 | CR6, 169 | CHSEL: 4, 170 | PL: High, 171 | MSIZE: Bits8, 172 | PSIZE: Bits8, 173 | MINC: Incremented, 174 | PINC: Fixed, 175 | CIRC: Disabled, 176 | DIR: MemoryToPeripheral, 177 | EN: Disabled 178 | ); 179 | write_reg!( 180 | dma, 181 | self.dma1, 182 | PAR6, 183 | stm32ral::usart::USART2 as u32 + UART_TDR_OFFSET 184 | ); 185 | } 186 | 187 | /// Sets up and enables a DMA transmit/receive for SPI1 (streams 2 and 3, channel 3) 188 | pub fn spi1_enable(&self, tx: &[u8], rx: &mut [u8]) { 189 | write_reg!( 190 | dma, 191 | self.dma2, 192 | LIFCR, 193 | CTCIF2: Clear, 194 | CHTIF2: Clear, 195 | CTEIF2: Clear, 196 | CDMEIF2: Clear, 197 | CFEIF2: Clear, 198 | CTCIF3: Clear, 199 | CHTIF3: Clear, 200 | CTEIF3: Clear, 201 | CDMEIF3: Clear, 202 | CFEIF3: Clear 203 | ); 204 | write_reg!(dma, self.dma2, NDTR2, rx.len() as u32); 205 | write_reg!(dma, self.dma2, NDTR3, tx.len() as u32); 206 | write_reg!(dma, self.dma2, M0AR2, rx.as_mut_ptr() as u32); 207 | write_reg!(dma, self.dma2, M0AR3, tx.as_ptr() as u32); 208 | modify_reg!(dma, self.dma2, CR2, EN: Enabled); 209 | modify_reg!(dma, self.dma2, CR3, EN: Enabled); 210 | } 211 | 212 | /// Check if SPI1 transaction is still ongoing 213 | pub fn spi1_busy(&self) -> bool { 214 | read_reg!(dma, self.dma2, LISR, TCIF2 == NotComplete) 215 | } 216 | 217 | /// Stop SPI1 DMA 218 | pub fn spi1_disable(&self) { 219 | modify_reg!(dma, self.dma2, CR2, EN: Disabled); 220 | modify_reg!(dma, self.dma2, CR3, EN: Disabled); 221 | } 222 | 223 | /// Sets up and enables a DMA transmit/receive for SPI2 (streams 3 and 4, channel 0) 224 | pub fn spi2_enable(&self, tx: &[u8], rx: &mut [u8]) { 225 | write_reg!( 226 | dma, 227 | self.dma1, 228 | LIFCR, 229 | CTCIF3: Clear, 230 | CHTIF3: Clear, 231 | CTEIF3: Clear, 232 | CDMEIF3: Clear, 233 | CFEIF3: Clear 234 | ); 235 | write_reg!( 236 | dma, 237 | self.dma1, 238 | HIFCR, 239 | CTCIF4: Clear, 240 | CHTIF4: Clear, 241 | CTEIF4: Clear, 242 | CDMEIF4: Clear, 243 | CFEIF4: Clear 244 | ); 245 | write_reg!(dma, self.dma1, NDTR3, rx.len() as u32); 246 | write_reg!(dma, self.dma1, NDTR4, tx.len() as u32); 247 | write_reg!(dma, self.dma1, M0AR3, rx.as_mut_ptr() as u32); 248 | write_reg!(dma, self.dma1, M0AR4, tx.as_ptr() as u32); 249 | modify_reg!(dma, self.dma1, CR3, EN: Enabled); 250 | modify_reg!(dma, self.dma1, CR4, EN: Enabled); 251 | } 252 | 253 | /// Check if SPI2 transaction is still ongoing 254 | pub fn spi2_busy(&self) -> bool { 255 | read_reg!(dma, self.dma1, LISR, TCIF3 == NotComplete) 256 | } 257 | 258 | /// Stop SPI2 DMA 259 | pub fn spi2_disable(&self) { 260 | modify_reg!(dma, self.dma1, CR3, EN: Disabled); 261 | modify_reg!(dma, self.dma1, CR4, EN: Disabled); 262 | } 263 | 264 | /// Start USART1 reception into provided buffer 265 | pub fn usart1_start(&self, rx: &mut [u8]) { 266 | write_reg!( 267 | dma, 268 | self.dma2, 269 | HIFCR, 270 | CTCIF5: Clear, 271 | CHTIF5: Clear, 272 | CTEIF5: Clear, 273 | CDMEIF5: Clear, 274 | CFEIF5: Clear 275 | ); 276 | write_reg!(dma, self.dma2, NDTR5, rx.len() as u32); 277 | write_reg!(dma, self.dma2, M0AR5, rx.as_mut_ptr() as u32); 278 | modify_reg!(dma, self.dma2, CR5, EN: Enabled); 279 | } 280 | 281 | /// Return how many bytes are left to transfer for USART1 282 | pub fn usart1_ndtr(&self) -> usize { 283 | read_reg!(dma, self.dma2, NDTR5) as usize 284 | } 285 | 286 | /// Stop USART1 DMA 287 | pub fn usart1_stop(&self) { 288 | modify_reg!(dma, self.dma2, CR5, EN: Disabled); 289 | } 290 | 291 | /// Start USART2 reception into provided buffer 292 | pub fn usart2_start_rx(&self, rx: &mut [u8]) { 293 | write_reg!( 294 | dma, 295 | self.dma1, 296 | HIFCR, 297 | CTCIF5: Clear, 298 | CHTIF5: Clear, 299 | CTEIF5: Clear, 300 | CDMEIF5: Clear, 301 | CFEIF5: Clear 302 | ); 303 | write_reg!(dma, self.dma1, NDTR5, rx.len() as u32); 304 | write_reg!(dma, self.dma1, M0AR5, rx.as_mut_ptr() as u32); 305 | modify_reg!(dma, self.dma1, CR5, EN: Enabled); 306 | } 307 | 308 | /// Return how many bytes are left to transfer for USART2 RX 309 | pub fn usart2_rx_ndtr(&self) -> usize { 310 | read_reg!(dma, self.dma1, NDTR5) as usize 311 | } 312 | /// Return how many bytes are left to transfer for USART2 TX 313 | pub fn usart2_tx_ndtr(&self) -> usize { 314 | read_reg!(dma, self.dma1, NDTR6) as usize 315 | } 316 | 317 | /// Start a DMA transfer for USART2 TX 318 | pub fn usart2_start_tx_transfer(&self, tx: &[u8], len: usize) { 319 | write_reg!( 320 | dma, 321 | self.dma1, 322 | HIFCR, 323 | CTCIF6: Clear, 324 | CHTIF6: Clear, 325 | CTEIF6: Clear, 326 | CDMEIF6: Clear, 327 | CFEIF6: Clear 328 | ); 329 | 330 | modify_reg!(dma, self.dma1, CR6, EN: Disabled); 331 | write_reg!(dma, self.dma1, NDTR6, len as u32); 332 | write_reg!(dma, self.dma1, M0AR6, tx.as_ptr() as u32); 333 | // This barrier guarantees that when the transfer starts, 334 | // any store done to RAM will be drained from the store 335 | // buffer of the M7. 336 | cortex_m::asm::dsb(); 337 | modify_reg!(dma, self.dma1, CR6, EN: Enabled); 338 | } 339 | 340 | /// Stop USART2 DMA 341 | pub fn usart2_stop(&self) { 342 | modify_reg!(dma, self.dma1, CR5, EN: Disabled); 343 | modify_reg!(dma, self.dma1, CR6, EN: Disabled); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/gpio.rs: -------------------------------------------------------------------------------- 1 | use stm32ral::gpio; 2 | use stm32ral::{modify_reg, read_reg, write_reg}; 3 | 4 | pub struct GPIO { 5 | p: gpio::Instance, 6 | } 7 | 8 | impl<'a> GPIO { 9 | pub fn new(p: gpio::Instance) -> Self { 10 | GPIO { p } 11 | } 12 | 13 | pub fn pin(&'a self, n: u8) -> Pin<'a> { 14 | assert!(n < 16); 15 | let n = unsafe { core::mem::transmute(n) }; 16 | Pin { n, port: self } 17 | } 18 | 19 | #[inline(always)] 20 | pub fn set_high(&'a self, n: PinIndex) -> &Self { 21 | write_reg!(gpio, self.p, BSRR, 1 << (n as u8)); 22 | self 23 | } 24 | 25 | #[inline(always)] 26 | pub fn set_low(&'a self, n: PinIndex) -> &Self { 27 | write_reg!(gpio, self.p, BSRR, 1 << ((n as u8) + 16)); 28 | self 29 | } 30 | 31 | #[inline] 32 | pub fn toggle(&'a self, n: PinIndex) -> &Self { 33 | let pin = (read_reg!(gpio, self.p, IDR) >> (n as u8)) & 1; 34 | if pin == 1 { 35 | self.set_low(n) 36 | } else { 37 | self.set_high(n) 38 | } 39 | } 40 | 41 | #[inline] 42 | pub fn set_mode(&'a self, n: PinIndex, mode: u32) -> &Self { 43 | let offset = (n as u8) * 2; 44 | let mask = 0b11 << offset; 45 | let val = (mode << offset) & mask; 46 | modify_reg!(gpio, self.p, MODER, |r| (r & !mask) | val); 47 | self 48 | } 49 | 50 | pub const fn memoise_mode(n: PinIndex, mode: u32) -> MemoisedMode { 51 | let n = (n as u8) & 0xF; 52 | let offset = n * 2; 53 | let mask = 0b11 << offset; 54 | let value = (mode << offset) & mask; 55 | MemoisedMode { mask: !mask, value } 56 | } 57 | 58 | #[inline] 59 | pub fn apply_memoised_mode(&'a self, mode: MemoisedMode) -> &Self { 60 | modify_reg!(gpio, self.p, MODER, |r| (r & mode.mask) | mode.value); 61 | self 62 | } 63 | 64 | #[inline] 65 | pub fn set_mode_input(&'a self, n: PinIndex) -> &Self { 66 | self.set_mode(n, gpio::MODER::MODER0::RW::Input) 67 | } 68 | 69 | pub const fn memoise_mode_input(n: PinIndex) -> MemoisedMode { 70 | Self::memoise_mode(n, gpio::MODER::MODER0::RW::Input) 71 | } 72 | 73 | #[inline] 74 | pub fn set_mode_output(&'a self, n: PinIndex) -> &Self { 75 | self.set_mode(n, gpio::MODER::MODER0::RW::Output) 76 | } 77 | 78 | pub const fn memoise_mode_output(n: PinIndex) -> MemoisedMode { 79 | Self::memoise_mode(n, gpio::MODER::MODER0::RW::Output) 80 | } 81 | 82 | #[inline] 83 | pub fn set_mode_alternate(&'a self, n: PinIndex) -> &Self { 84 | self.set_mode(n, gpio::MODER::MODER0::RW::Alternate) 85 | } 86 | 87 | pub const fn memoise_mode_alternate(n: PinIndex) -> MemoisedMode { 88 | Self::memoise_mode(n, gpio::MODER::MODER0::RW::Alternate) 89 | } 90 | 91 | #[inline] 92 | pub fn set_mode_analog(&'a self, n: PinIndex) -> &Self { 93 | self.set_mode(n, gpio::MODER::MODER0::RW::Analog) 94 | } 95 | 96 | pub const fn memoise_mode_analog(n: PinIndex) -> MemoisedMode { 97 | Self::memoise_mode(n, gpio::MODER::MODER0::RW::Analog) 98 | } 99 | 100 | #[inline] 101 | pub fn set_otype(&'a self, n: PinIndex, otype: u32) -> &Self { 102 | let offset = n as u8; 103 | let mask = 0b1 << offset; 104 | let val = (otype << offset) & mask; 105 | modify_reg!(gpio, self.p, OTYPER, |r| (r & !mask) | val); 106 | self 107 | } 108 | 109 | #[inline] 110 | pub fn set_otype_opendrain(&'a self, n: PinIndex) -> &Self { 111 | self.set_otype(n, gpio::OTYPER::OT0::RW::OpenDrain) 112 | } 113 | 114 | #[inline] 115 | pub fn set_otype_pushpull(&'a self, n: PinIndex) -> &Self { 116 | self.set_otype(n, gpio::OTYPER::OT0::RW::PushPull) 117 | } 118 | 119 | #[inline] 120 | pub fn set_ospeed(&'a self, n: PinIndex, ospeed: u32) -> &Self { 121 | let offset = (n as u8) * 2; 122 | let mask = 0b11 << offset; 123 | let val = (ospeed << offset) & mask; 124 | modify_reg!(gpio, self.p, OSPEEDR, |r| (r & !mask) | val); 125 | self 126 | } 127 | 128 | #[inline] 129 | pub fn set_ospeed_low(&'a self, n: PinIndex) -> &Self { 130 | self.set_ospeed(n, gpio::OSPEEDR::OSPEEDR0::RW::LowSpeed) 131 | } 132 | 133 | #[inline] 134 | pub fn set_ospeed_medium(&'a self, n: PinIndex) -> &Self { 135 | self.set_ospeed(n, gpio::OSPEEDR::OSPEEDR0::RW::MediumSpeed) 136 | } 137 | 138 | #[inline] 139 | pub fn set_ospeed_high(&'a self, n: PinIndex) -> &Self { 140 | self.set_ospeed(n, gpio::OSPEEDR::OSPEEDR0::RW::HighSpeed) 141 | } 142 | 143 | #[inline] 144 | pub fn set_ospeed_veryhigh(&'a self, n: PinIndex) -> &Self { 145 | self.set_ospeed(n, gpio::OSPEEDR::OSPEEDR0::RW::VeryHighSpeed) 146 | } 147 | 148 | #[inline] 149 | pub fn set_af(&'a self, n: PinIndex, af: u32) -> &Self { 150 | let n = n as u8; 151 | if n < 8 { 152 | let offset = n * 4; 153 | let mask = 0b1111 << offset; 154 | let val = (af << offset) & mask; 155 | modify_reg!(gpio, self.p, AFRL, |r| (r & !mask) | val); 156 | } else { 157 | let offset = (n - 8) * 4; 158 | let mask = 0b1111 << offset; 159 | let val = (af << offset) & mask; 160 | modify_reg!(gpio, self.p, AFRH, |r| (r & !mask) | val); 161 | } 162 | self 163 | } 164 | 165 | #[inline] 166 | pub fn set_pull(&'a self, n: PinIndex, pull: u32) -> &Self { 167 | let offset = (n as u8) * 2; 168 | let mask = 0b11 << offset; 169 | let val = (pull << offset) & mask; 170 | modify_reg!(gpio, self.p, PUPDR, |r| (r & !mask) | val); 171 | self 172 | } 173 | 174 | #[inline] 175 | pub fn set_pull_floating(&'a self, n: PinIndex) -> &Self { 176 | self.set_pull(n, gpio::PUPDR::PUPDR0::RW::Floating) 177 | } 178 | 179 | #[inline] 180 | pub fn set_pull_up(&'a self, n: PinIndex) -> &Self { 181 | self.set_pull(n, gpio::PUPDR::PUPDR0::RW::PullUp) 182 | } 183 | 184 | #[inline] 185 | pub fn set_pull_down(&'a self, n: PinIndex) -> &Self { 186 | self.set_pull(n, gpio::PUPDR::PUPDR0::RW::PullDown) 187 | } 188 | 189 | #[inline] 190 | pub fn get_idr(&'a self) -> u32 { 191 | read_reg!(gpio, self.p, IDR) 192 | } 193 | 194 | #[inline] 195 | pub fn get_pin_idr(&'a self, n: PinIndex) -> u32 { 196 | let n = n as u8; 197 | (self.get_idr() & (1 << n)) >> n 198 | } 199 | } 200 | 201 | /// Stores a pre-computed mask and value for quickly changing pin mode 202 | #[derive(Copy, Clone)] 203 | pub struct MemoisedMode { 204 | mask: u32, 205 | value: u32, 206 | } 207 | 208 | #[repr(u16)] 209 | pub enum PinState { 210 | Low = 0, 211 | High = 1, 212 | } 213 | 214 | #[derive(Copy, Clone)] 215 | #[repr(u8)] 216 | pub enum PinIndex { 217 | Pin0 = 0, 218 | Pin1 = 1, 219 | Pin2 = 2, 220 | Pin3 = 3, 221 | Pin4 = 4, 222 | Pin5 = 5, 223 | Pin6 = 6, 224 | Pin7 = 7, 225 | Pin8 = 8, 226 | Pin9 = 9, 227 | Pin10 = 10, 228 | Pin11 = 11, 229 | Pin12 = 12, 230 | Pin13 = 13, 231 | Pin14 = 14, 232 | Pin15 = 15, 233 | } 234 | 235 | pub struct Pin<'a> { 236 | n: PinIndex, 237 | port: &'a GPIO, 238 | } 239 | 240 | impl<'a> Pin<'a> { 241 | #[inline(always)] 242 | pub fn set_high(&self) -> &Self { 243 | self.port.set_high(self.n); 244 | self 245 | } 246 | 247 | #[inline(always)] 248 | pub fn set_low(&self) -> &Self { 249 | self.port.set_low(self.n); 250 | self 251 | } 252 | 253 | #[inline(always)] 254 | pub fn set_bool(&self, state: bool) { 255 | match state { 256 | false => self.set_low(), 257 | true => self.set_high(), 258 | }; 259 | } 260 | 261 | #[inline(always)] 262 | pub fn set_state(&self, state: PinState) { 263 | match state { 264 | PinState::Low => self.set_low(), 265 | PinState::High => self.set_high(), 266 | }; 267 | } 268 | 269 | #[inline(always)] 270 | pub fn get_state(&self) -> PinState { 271 | match self.port.get_pin_idr(self.n) { 272 | 0 => PinState::Low, 273 | 1 => PinState::High, 274 | _ => unreachable!(), 275 | } 276 | } 277 | 278 | #[inline(always)] 279 | pub fn is_high(&self) -> bool { 280 | match self.get_state() { 281 | PinState::High => true, 282 | PinState::Low => false, 283 | } 284 | } 285 | 286 | #[inline(always)] 287 | pub fn is_low(&self) -> bool { 288 | match self.get_state() { 289 | PinState::Low => true, 290 | PinState::High => false, 291 | } 292 | } 293 | 294 | #[inline(always)] 295 | pub fn toggle(&'a self) -> &Self { 296 | self.port.toggle(self.n); 297 | self 298 | } 299 | 300 | #[inline] 301 | pub fn set_mode_input(&'a self) -> &Self { 302 | self.port.set_mode_input(self.n); 303 | self 304 | } 305 | 306 | #[inline] 307 | pub fn set_mode_output(&'a self) -> &Self { 308 | self.port.set_mode_output(self.n); 309 | self 310 | } 311 | 312 | #[inline] 313 | pub fn set_mode_alternate(&'a self) -> &Self { 314 | self.port.set_mode_alternate(self.n); 315 | self 316 | } 317 | 318 | #[inline] 319 | pub fn set_mode_analog(&'a self) -> &Self { 320 | self.port.set_mode_analog(self.n); 321 | self 322 | } 323 | 324 | pub fn memoise_mode_input(&'a self) -> MemoisedMode { 325 | GPIO::memoise_mode_input(self.n) 326 | } 327 | 328 | pub fn memoise_mode_output(&'a self) -> MemoisedMode { 329 | GPIO::memoise_mode_output(self.n) 330 | } 331 | 332 | pub fn memoise_mode_alternate(&'a self) -> MemoisedMode { 333 | GPIO::memoise_mode_alternate(self.n) 334 | } 335 | 336 | pub fn memoise_mode_analog(&'a self) -> MemoisedMode { 337 | GPIO::memoise_mode_analog(self.n) 338 | } 339 | 340 | #[inline] 341 | pub fn apply_memoised_mode(&'a self, mode: MemoisedMode) -> &Self { 342 | self.port.apply_memoised_mode(mode); 343 | self 344 | } 345 | 346 | #[inline] 347 | pub fn set_otype_opendrain(&'a self) -> &Self { 348 | self.port.set_otype_opendrain(self.n); 349 | self 350 | } 351 | 352 | #[inline] 353 | pub fn set_otype_pushpull(&'a self) -> &Self { 354 | self.port.set_otype_pushpull(self.n); 355 | self 356 | } 357 | 358 | #[inline] 359 | pub fn set_ospeed_low(&'a self) -> &Self { 360 | self.port.set_ospeed_low(self.n); 361 | self 362 | } 363 | 364 | #[inline] 365 | pub fn set_ospeed_medium(&'a self) -> &Self { 366 | self.port.set_ospeed_medium(self.n); 367 | self 368 | } 369 | 370 | #[inline] 371 | pub fn set_ospeed_high(&'a self) -> &Self { 372 | self.port.set_ospeed_high(self.n); 373 | self 374 | } 375 | 376 | #[inline] 377 | pub fn set_ospeed_veryhigh(&'a self) -> &Self { 378 | self.port.set_ospeed_veryhigh(self.n); 379 | self 380 | } 381 | 382 | #[inline] 383 | pub fn set_af(&'a self, af: u32) -> &Self { 384 | self.port.set_af(self.n, af); 385 | self 386 | } 387 | 388 | #[inline] 389 | pub fn set_pull_floating(&'a self) -> &Self { 390 | self.port.set_pull_floating(self.n); 391 | self 392 | } 393 | 394 | #[inline] 395 | pub fn set_pull_up(&'a self) -> &Self { 396 | self.port.set_pull_up(self.n); 397 | self 398 | } 399 | 400 | #[inline] 401 | pub fn set_pull_down(&'a self) -> &Self { 402 | self.port.set_pull_down(self.n); 403 | self 404 | } 405 | } 406 | 407 | pub struct Pins<'a> { 408 | pub led_red: Pin<'a>, 409 | pub led_green: Pin<'a>, 410 | pub led_blue: Pin<'a>, 411 | 412 | pub t5v_en: Pin<'a>, 413 | pub tvcc_en: Pin<'a>, 414 | pub reset: Pin<'a>, 415 | pub gnd_detect: Pin<'a>, 416 | 417 | // Used for SWO in SWD mode 418 | pub usart1_rx: Pin<'a>, 419 | 420 | // Used for external serial interface 421 | pub usart2_rx: Pin<'a>, 422 | pub usart2_tx: Pin<'a>, 423 | 424 | // SPI pins for SWD, SPI1_MOSI is used as TMS in JTAG mode 425 | pub spi1_clk: Pin<'a>, // Physically connected to SPI2_CLK 426 | pub spi1_miso: Pin<'a>, 427 | pub spi1_mosi: Pin<'a>, 428 | 429 | // SPI pins for JTAG, disabled in SWD mode 430 | pub spi2_clk: Pin<'a>, // Physically connected to SPI1_CLK 431 | pub spi2_miso: Pin<'a>, 432 | pub spi2_mosi: Pin<'a>, 433 | 434 | // USB HS 435 | pub usb_dm: Pin<'a>, 436 | pub usb_dp: Pin<'a>, 437 | pub usb_sel: Pin<'a>, 438 | } 439 | 440 | impl<'a> Pins<'a> { 441 | /// Configure I/O pins 442 | pub fn setup(&self) { 443 | // Open-drain output to LED (active low). 444 | self.led_red 445 | .set_high() 446 | .set_otype_opendrain() 447 | .set_ospeed_low() 448 | .set_mode_output(); 449 | 450 | self.led_green 451 | .set_high() 452 | .set_otype_opendrain() 453 | .set_ospeed_low() 454 | .set_mode_output(); 455 | 456 | self.led_blue 457 | .set_high() 458 | .set_otype_opendrain() 459 | .set_ospeed_low() 460 | .set_mode_output(); 461 | 462 | // Push-pull output drives target 5V supply enable. 463 | self.t5v_en 464 | .set_low() 465 | .set_otype_pushpull() 466 | .set_ospeed_low() 467 | .set_mode_output(); 468 | 469 | // Push-pull output drives target supply LDO (active high). 470 | self.tvcc_en 471 | .set_low() 472 | .set_otype_pushpull() 473 | .set_ospeed_low() 474 | .set_mode_output(); 475 | 476 | // Open-drain output to RESET reset line (active low). 477 | self.reset 478 | .set_high() 479 | .set_otype_opendrain() 480 | .set_ospeed_high() 481 | .set_mode_output(); 482 | 483 | // Input for GNDDetect 484 | self.gnd_detect.set_pull_up().set_mode_input(); 485 | 486 | // Used for SWO in SWD mode. Starts high-impedance. 487 | self.usart1_rx.set_af(7).set_mode_input(); 488 | 489 | // VCP pins 490 | self.usart2_rx.set_af(7).set_pull_up().set_mode_alternate(); 491 | self.usart2_tx 492 | .set_high() 493 | .set_ospeed_high() 494 | .set_af(7) 495 | .set_mode_alternate(); 496 | 497 | // Push-pull output to SPI1_CLK. Starts high-impedance. 498 | self.spi1_clk 499 | .set_af(5) 500 | .set_otype_pushpull() 501 | .set_ospeed_veryhigh() 502 | .set_mode_input(); 503 | 504 | // Input to SPI1_MISO 505 | self.spi1_miso.set_af(5).set_mode_input(); 506 | 507 | // Push-pull output to SPI1_MOSI. Starts high-impedance. 508 | self.spi1_mosi 509 | .set_af(5) 510 | .set_otype_pushpull() 511 | .set_ospeed_veryhigh() 512 | .set_mode_input(); 513 | 514 | // Push-pull output to SPI2_CLK. Starts high-impedance. 515 | self.spi2_clk 516 | .set_af(5) 517 | .set_otype_pushpull() 518 | .set_ospeed_veryhigh() 519 | .set_mode_input(); 520 | 521 | // Input to SPI2_MISO 522 | self.spi2_miso.set_af(5).set_mode_input(); 523 | 524 | // Push-pull output to SPI2_MOSI. Starts high-impedance. 525 | self.spi2_mosi 526 | .set_af(5) 527 | .set_otype_pushpull() 528 | .set_ospeed_veryhigh() 529 | .set_mode_input(); 530 | 531 | // USB HighSpeed pins 532 | self.usb_dm 533 | .set_af(12) 534 | .set_otype_pushpull() 535 | .set_ospeed_veryhigh() 536 | .set_mode_alternate(); 537 | self.usb_dp 538 | .set_af(12) 539 | .set_otype_pushpull() 540 | .set_ospeed_veryhigh() 541 | .set_mode_alternate(); 542 | self.usb_sel 543 | .set_high() 544 | .set_otype_pushpull() 545 | .set_ospeed_low() 546 | .set_mode_output(); 547 | } 548 | 549 | /// Place SPI pins into high-impedance mode 550 | #[inline] 551 | pub fn high_impedance_mode(&self) { 552 | self.reset.set_high().set_mode_output(); 553 | self.usart1_rx.set_mode_input(); 554 | self.spi1_clk.set_mode_input(); 555 | self.spi1_miso.set_mode_input(); 556 | self.spi1_mosi.set_mode_input(); 557 | self.spi2_clk.set_mode_input(); 558 | self.spi2_miso.set_mode_input(); 559 | self.spi2_mosi.set_mode_input(); 560 | } 561 | 562 | /// Place SPI pins into JTAG mode 563 | #[inline] 564 | pub fn jtag_mode(&self) { 565 | self.reset.set_mode_output(); 566 | self.usart1_rx.set_mode_input(); 567 | self.spi1_clk.set_mode_input(); 568 | self.spi1_miso.set_mode_input(); 569 | self.spi1_mosi.set_mode_output(); 570 | self.spi2_clk.set_mode_output(); 571 | self.spi2_miso.set_mode_input(); 572 | self.spi2_mosi.set_mode_output(); 573 | } 574 | 575 | /// Place SPI pins into SWD mode 576 | #[inline] 577 | pub fn swd_mode(&self) { 578 | self.reset.set_mode_output(); 579 | self.usart1_rx.set_mode_alternate(); 580 | self.spi2_clk.set_mode_input(); 581 | self.spi2_miso.set_mode_input(); 582 | self.spi2_mosi.set_mode_input(); 583 | self.spi1_clk.set_mode_alternate(); 584 | self.spi1_miso.set_mode_alternate(); 585 | self.spi1_mosi.set_mode_alternate(); 586 | } 587 | 588 | /// Disconnect SPI1_MOSI from SWDIO, target drives the bus 589 | #[inline] 590 | pub fn swd_rx(&self) { 591 | self.spi1_mosi.set_mode_input(); 592 | } 593 | 594 | /// Connect SPI1_MOSI to SWDIO, SPI drives the bus 595 | #[inline] 596 | pub fn swd_tx(&self) { 597 | self.spi1_mosi.set_mode_alternate(); 598 | } 599 | 600 | /// Connect SPI1_MOSI to SWDIO, manual bitbanging 601 | #[inline] 602 | pub fn swd_tx_direct(&self) { 603 | self.spi1_mosi.set_mode_output(); 604 | } 605 | 606 | /// Swap SPI1_CLK pin to direct output mode for manual driving 607 | #[inline] 608 | pub fn swd_clk_direct(&self) { 609 | self.spi1_clk.set_mode_output(); 610 | } 611 | 612 | /// Swap SPI1_CLK pin back to alternate mode for SPI use 613 | #[inline] 614 | pub fn swd_clk_spi(&self) { 615 | self.spi1_clk.set_mode_alternate(); 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub use cortex_m; 4 | pub use stm32ral; 5 | 6 | pub mod bootload; 7 | pub mod delay; 8 | pub mod dma; 9 | pub mod gpio; 10 | pub mod otg_hs; 11 | pub mod rcc; 12 | pub mod spi; 13 | pub mod uart; 14 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/otg_hs.rs: -------------------------------------------------------------------------------- 1 | //! USB OTG high-speed peripheral 2 | 3 | use stm32ral::{modify_reg, read_reg, write_reg}; 4 | use stm32ral::{otg_hs_device, otg_hs_global, otg_hs_pwrclk, rcc, usbphyc}; 5 | pub use synopsys_usb_otg::UsbBus; 6 | use synopsys_usb_otg::{PhyType, UsbPeripheral}; 7 | 8 | pub struct USB { 9 | pub usb_phy: usbphyc::Instance, 10 | pub usb_global: otg_hs_global::Instance, 11 | pub usb_device: otg_hs_device::Instance, 12 | pub usb_pwrclk: otg_hs_pwrclk::Instance, 13 | pub hclk: u32, 14 | } 15 | 16 | // We only store peripheral instances to enforce ownership, 17 | // so it's safe to share the USB object 18 | unsafe impl Send for USB {} 19 | unsafe impl Sync for USB {} 20 | 21 | unsafe impl UsbPeripheral for USB { 22 | const REGISTERS: *const () = otg_hs_global::OTG_HS_GLOBAL as *const (); 23 | 24 | const HIGH_SPEED: bool = true; 25 | const FIFO_DEPTH_WORDS: usize = 1024; 26 | const ENDPOINT_COUNT: usize = 9; 27 | 28 | fn enable() { 29 | cortex_m::interrupt::free(|_| { 30 | let rcc = unsafe { &*rcc::RCC }; 31 | 32 | // Enable and reset USB peripheral 33 | modify_reg!(rcc, rcc, AHB1ENR, OTGHSEN: Enabled); 34 | modify_reg!(rcc, rcc, AHB1RSTR, OTGHSRST: Reset); 35 | modify_reg!(rcc, rcc, AHB1RSTR, OTGHSRST: 0); 36 | 37 | // Enable and reset HS PHY 38 | modify_reg!(rcc, rcc, AHB1ENR, OTGHSULPIEN: Enabled); 39 | modify_reg!(rcc, rcc, APB2ENR, USBPHYCEN: Enabled); 40 | modify_reg!(rcc, rcc, APB2RSTR, USBPHYCRST: Reset); 41 | modify_reg!(rcc, rcc, APB2RSTR, USBPHYCRST: 0); 42 | }); 43 | } 44 | 45 | #[inline(always)] 46 | fn ahb_frequency_hz(&self) -> u32 { 47 | self.hclk 48 | } 49 | 50 | #[inline(always)] 51 | fn phy_type(&self) -> PhyType { 52 | PhyType::InternalHighSpeed 53 | } 54 | 55 | fn setup_internal_hs_phy(&self) { 56 | let phy = unsafe { &*usbphyc::USBPHYC }; 57 | 58 | // Turn on LDO 59 | // For some reason setting the bit enables the LDO 60 | modify_reg!(usbphyc, phy, LDO, LDO_DISABLE: 1); 61 | while read_reg!(usbphyc, phy, LDO, LDO_STATUS) == 0 {} 62 | 63 | // Setup PLL 64 | write_reg!(usbphyc, phy, PLL1, 65 | PLL1SEL: 0b000 // A value for 12MHz HSE 66 | ); 67 | modify_reg!(usbphyc, phy, TUNE, |r| r | 0xF13); 68 | modify_reg!(usbphyc, phy, PLL1, PLL1EN: 1); 69 | 70 | // 2ms Delay required to get internal phy clock stable 71 | cortex_m::asm::delay(432000); 72 | } 73 | } 74 | 75 | pub type UsbBusType = UsbBus; 76 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/rcc.rs: -------------------------------------------------------------------------------- 1 | use stm32ral::{flash, pwr, rcc}; 2 | use stm32ral::{modify_reg, read_reg, reset_reg}; 3 | 4 | pub struct RCC { 5 | rcc: rcc::Instance, 6 | } 7 | 8 | impl RCC { 9 | pub fn new(rcc: rcc::Instance) -> Self { 10 | RCC { rcc } 11 | } 12 | 13 | /// Set up the device, enabling all required clocks 14 | /// 15 | /// Unsafety: this function should be called from the main context. 16 | /// No other contexts should be active at the same time. 17 | #[allow(clippy::missing_safety_doc)] 18 | pub unsafe fn setup(&self, frequency: CoreFrequency) -> Clocks { 19 | // Turn on HSI 20 | modify_reg!(rcc, self.rcc, CR, HSION: On); 21 | // Wait for HSI to be ready 22 | while read_reg!(rcc, self.rcc, CR, HSIRDY == NotReady) {} 23 | // Swap system clock to HSI 24 | modify_reg!(rcc, self.rcc, CFGR, SW: HSI); 25 | // Wait for system clock to be HSI 26 | while read_reg!(rcc, self.rcc, CFGR, SWS != HSI) {} 27 | 28 | // Disable everything 29 | modify_reg!( 30 | rcc, 31 | self.rcc, 32 | CR, 33 | HSEON: Off, 34 | CSSON: Off, 35 | PLLON: Off, 36 | PLLI2SON: Off, 37 | PLLSAION: Off 38 | ); 39 | reset_reg!(rcc, self.rcc, RCC, AHB1ENR); 40 | reset_reg!(rcc, self.rcc, RCC, AHB2ENR); 41 | reset_reg!(rcc, self.rcc, RCC, AHB3ENR); 42 | reset_reg!(rcc, self.rcc, RCC, APB1ENR); 43 | reset_reg!(rcc, self.rcc, RCC, APB2ENR); 44 | 45 | // Configure HSE in bypass mode 46 | modify_reg!(rcc, self.rcc, CR, HSEBYP: Bypassed); 47 | // Start HSE 48 | modify_reg!(rcc, self.rcc, CR, HSEON: On); 49 | // Wait for HSE to be ready 50 | while read_reg!(rcc, self.rcc, CR, HSERDY == NotReady) {} 51 | 52 | // Calculate prescalers 53 | let ppre1; 54 | let ppre2; 55 | match frequency { 56 | CoreFrequency::F48MHz => { 57 | ppre1 = 0b000; // AHB clock not divided 58 | ppre2 = 0b000; // AHB clock not divided 59 | } 60 | CoreFrequency::F72MHz => { 61 | ppre1 = 0b100; // AHB clock divided by 2 62 | ppre2 = 0b000; // AHB clock not divided 63 | } 64 | CoreFrequency::F216MHz => { 65 | ppre1 = 0b101; // AHB clock divided by 4 66 | ppre2 = 0b100; // AHB clock divided by 2 67 | } 68 | } 69 | // Set prescalers 70 | modify_reg!(rcc, self.rcc, CFGR, HPRE: Div1, PPRE1: ppre1, PPRE2: ppre2); 71 | 72 | // Calculate PLL parameters and flash latency 73 | let pllm = 6; 74 | let plln; 75 | let pllp; 76 | let pllq; 77 | let flash_latency; 78 | let sysclk; 79 | match frequency { 80 | CoreFrequency::F48MHz => { 81 | plln = 96; 82 | pllp = 0b01; // /4 83 | pllq = 4; 84 | flash_latency = 0b0001; 85 | sysclk = 48_000_000; 86 | } 87 | CoreFrequency::F72MHz => { 88 | plln = 144; 89 | pllp = 0b01; // /4 90 | pllq = 6; 91 | flash_latency = 0b0010; 92 | sysclk = 72_000_000; 93 | } 94 | CoreFrequency::F216MHz => { 95 | plln = 216; 96 | pllp = 0b00; // /2 97 | pllq = 9; 98 | flash_latency = 0b0111; 99 | sysclk = 216_000_000; 100 | } 101 | } 102 | 103 | // Configure PLL from HSE 104 | modify_reg!( 105 | rcc, 106 | self.rcc, 107 | PLLCFGR, 108 | PLLSRC: HSE, 109 | PLLM: pllm, 110 | PLLN: plln, 111 | PLLP: pllp, 112 | PLLQ: pllq 113 | ); 114 | 115 | // Enable PWR domain and setup voltage scale and overdrive options 116 | modify_reg!(rcc, self.rcc, APB1ENR, PWREN: Enabled); 117 | 118 | let enable_overdrive; 119 | 120 | // The scale can be modified only when the PLL is OFF and the 121 | // HSI or HSE clock source is selected as system clock source. 122 | let pwr = &*pwr::PWR; 123 | if sysclk <= 144_000_000 { 124 | modify_reg!(pwr, pwr, CR1, VOS: SCALE3); 125 | enable_overdrive = false; 126 | } else if sysclk <= 168_000_000 { 127 | modify_reg!(pwr, pwr, CR1, VOS: SCALE2); 128 | enable_overdrive = false; 129 | } else if sysclk <= 180_000_000 { 130 | modify_reg!(pwr, pwr, CR1, VOS: SCALE1); 131 | enable_overdrive = false; 132 | } else { 133 | modify_reg!(pwr, pwr, CR1, VOS: SCALE1); 134 | enable_overdrive = true; 135 | } 136 | 137 | // Turn on PLL 138 | modify_reg!(rcc, self.rcc, CR, PLLON: On); 139 | 140 | if enable_overdrive { 141 | // Enable the over-drive mode 142 | modify_reg!(pwr, pwr, CR1, ODEN: 1); 143 | while read_reg!(pwr, pwr, CSR1, ODRDY) == 0 {} 144 | 145 | // Switch the voltage regulator from normal mode to over-drive mode 146 | modify_reg!(pwr, pwr, CR1, ODSWEN: 1); 147 | while read_reg!(pwr, pwr, CSR1, ODSWRDY) == 0 {} 148 | } 149 | 150 | // Wait for PLL to be ready 151 | while read_reg!(rcc, self.rcc, CR, PLLRDY == NotReady) {} 152 | 153 | // Adjust flash wait states 154 | modify_reg!(flash, &*flash::FLASH, ACR, LATENCY: flash_latency); 155 | 156 | // Swap system clock to PLL 157 | modify_reg!(rcc, self.rcc, CFGR, SW: PLL); 158 | // Wait for system clock to be PLL 159 | while read_reg!(rcc, self.rcc, CFGR, SWS != PLL) {} 160 | 161 | // Enable peripheral clocks 162 | modify_reg!( 163 | rcc, 164 | self.rcc, 165 | AHB1ENR, 166 | GPIOAEN: Enabled, 167 | GPIOBEN: Enabled, 168 | GPIOCEN: Enabled, 169 | GPIODEN: Enabled, 170 | GPIOEEN: Enabled, 171 | GPIOGEN: Enabled, 172 | GPIOIEN: Enabled, 173 | DMA1EN: Enabled, 174 | DMA2EN: Enabled 175 | ); 176 | modify_reg!(rcc, self.rcc, APB1ENR, SPI2EN: Enabled, USART2EN: Enabled); 177 | modify_reg!(rcc, self.rcc, APB2ENR, SPI1EN: Enabled, USART1EN: Enabled); 178 | 179 | Clocks { sysclk } 180 | } 181 | } 182 | 183 | #[derive(Eq, PartialEq)] 184 | pub enum CoreFrequency { 185 | F48MHz, 186 | F72MHz, 187 | F216MHz, 188 | } 189 | 190 | pub struct Clocks { 191 | sysclk: u32, 192 | } 193 | 194 | impl Clocks { 195 | pub fn hclk(&self) -> u32 { 196 | let rcc = unsafe { &*rcc::RCC }; 197 | let hpre = read_reg!(rcc, rcc, CFGR, HPRE); 198 | match hpre { 199 | 0b1000 => self.sysclk / 2, 200 | 0b1001 => self.sysclk / 4, 201 | 0b1010 => self.sysclk / 8, 202 | 0b1011 => self.sysclk / 16, 203 | 0b1100 => self.sysclk / 64, 204 | 0b1101 => self.sysclk / 128, 205 | 0b1110 => self.sysclk / 256, 206 | 0b1111 => self.sysclk / 512, 207 | _ => self.sysclk, 208 | } 209 | } 210 | 211 | pub fn pclk1(&self) -> u32 { 212 | let hclk = self.hclk(); 213 | 214 | let rcc = unsafe { &*rcc::RCC }; 215 | let ppre = read_reg!(rcc, rcc, CFGR, PPRE1); 216 | match ppre { 217 | 0b100 => hclk / 2, 218 | 0b101 => hclk / 4, 219 | 0b110 => hclk / 8, 220 | 0b111 => hclk / 16, 221 | _ => hclk, 222 | } 223 | } 224 | 225 | pub fn pclk2(&self) -> u32 { 226 | let hclk = self.hclk(); 227 | 228 | let rcc = unsafe { &*rcc::RCC }; 229 | let ppre = read_reg!(rcc, rcc, CFGR, PPRE2); 230 | match ppre { 231 | 0b100 => hclk / 2, 232 | 0b101 => hclk / 4, 233 | 0b110 => hclk / 8, 234 | 0b111 => hclk / 16, 235 | _ => hclk, 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/spi.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use core::sync::atomic::{AtomicU32, Ordering}; 5 | use stm32ral::spi; 6 | use stm32ral::{modify_reg, read_reg, write_reg}; 7 | 8 | use super::dma::DMA; 9 | use super::gpio::Pins; 10 | use crate::rcc::Clocks; 11 | use core::ops::Deref; 12 | 13 | pub struct SPI { 14 | spi: spi::Instance, 15 | base_clock: AtomicU32, 16 | } 17 | 18 | #[repr(u32)] 19 | #[derive(Copy, Clone, Debug)] 20 | pub enum SPIPrescaler { 21 | Div2 = 0b000, 22 | Div4 = 0b001, 23 | Div8 = 0b010, 24 | Div16 = 0b011, 25 | Div32 = 0b100, 26 | Div64 = 0b101, 27 | Div128 = 0b110, 28 | Div256 = 0b111, 29 | } 30 | 31 | impl SPI { 32 | pub fn new(spi: spi::Instance) -> Self { 33 | SPI { 34 | spi, 35 | base_clock: AtomicU32::new(0), 36 | } 37 | } 38 | 39 | pub fn set_base_clock(&self, clocks: &Clocks) { 40 | if self.spi.deref() as *const _ == spi::SPI1 { 41 | self.base_clock.store(clocks.pclk2(), Ordering::SeqCst); 42 | } 43 | if self.spi.deref() as *const _ == spi::SPI2 { 44 | self.base_clock.store(clocks.pclk1(), Ordering::SeqCst); 45 | } 46 | } 47 | 48 | /// Set up SPI peripheral for SWD mode. 49 | /// 50 | /// Defaults to 1.5MHz clock which should be slow enough to work on most targets. 51 | pub fn setup_swd(&self) { 52 | write_reg!( 53 | spi, 54 | self.spi, 55 | CR1, 56 | BIDIMODE: Unidirectional, 57 | CRCEN: Disabled, 58 | RXONLY: FullDuplex, 59 | SSM: Enabled, 60 | SSI: SlaveNotSelected, 61 | LSBFIRST: LSBFirst, 62 | BR: Div32, 63 | MSTR: Master, 64 | CPOL: IdleHigh, 65 | CPHA: SecondEdge, 66 | SPE: Enabled 67 | ); 68 | } 69 | 70 | /// Set up SPI peripheral for JTAG mode 71 | pub fn setup_jtag(&self) { 72 | write_reg!( 73 | spi, 74 | self.spi, 75 | CR1, 76 | BIDIMODE: Unidirectional, 77 | CRCEN: Disabled, 78 | RXONLY: FullDuplex, 79 | SSM: Enabled, 80 | SSI: SlaveNotSelected, 81 | LSBFIRST: LSBFirst, 82 | BR: Div256, 83 | MSTR: Master, 84 | CPOL: IdleLow, 85 | CPHA: FirstEdge, 86 | SPE: Disabled 87 | ); 88 | write_reg!( 89 | spi, 90 | self.spi, 91 | CR2, 92 | FRXTH: Quarter, 93 | DS: EightBit, 94 | TXDMAEN: Enabled, 95 | RXDMAEN: Enabled 96 | ); 97 | } 98 | 99 | pub fn calculate_prescaler(&self, max_frequency: u32) -> Option { 100 | let base_clock = self.base_clock.load(Ordering::SeqCst); 101 | if base_clock == 0 { 102 | return None; 103 | } 104 | 105 | if (base_clock / 2) <= max_frequency { 106 | return Some(SPIPrescaler::Div2); 107 | } 108 | if (base_clock / 4) <= max_frequency { 109 | return Some(SPIPrescaler::Div4); 110 | } 111 | if (base_clock / 8) <= max_frequency { 112 | return Some(SPIPrescaler::Div8); 113 | } 114 | if (base_clock / 16) <= max_frequency { 115 | return Some(SPIPrescaler::Div16); 116 | } 117 | if (base_clock / 32) <= max_frequency { 118 | return Some(SPIPrescaler::Div32); 119 | } 120 | if (base_clock / 64) <= max_frequency { 121 | return Some(SPIPrescaler::Div64); 122 | } 123 | if (base_clock / 128) <= max_frequency { 124 | return Some(SPIPrescaler::Div128); 125 | } 126 | if (base_clock / 256) <= max_frequency { 127 | return Some(SPIPrescaler::Div256); 128 | } 129 | None 130 | } 131 | 132 | /// Change SPI clock rate to one of the SPIClock variants 133 | pub fn set_prescaler(&self, prescaler: SPIPrescaler) { 134 | modify_reg!(spi, self.spi, CR1, BR: prescaler as u32); 135 | } 136 | 137 | /// Wait for any pending operation then disable SPI 138 | pub fn disable(&self) { 139 | self.wait_busy(); 140 | modify_reg!(spi, self.spi, CR1, SPE: Disabled); 141 | } 142 | 143 | /// Transmit `txdata` and write the same number of bytes into `rxdata`. 144 | pub fn jtag_exchange(&self, dma: &DMA, txdata: &[u8], rxdata: &mut [u8]) { 145 | debug_assert!(rxdata.len() >= 64); 146 | 147 | // Set up DMA transfer (configures NDTR and MAR and enables streams) 148 | dma.spi2_enable(txdata, &mut rxdata[..txdata.len()]); 149 | 150 | // Start SPI transfer 151 | modify_reg!(spi, self.spi, CR1, SPE: Enabled); 152 | 153 | // Busy wait for RX DMA completion (at most 43µs) 154 | while dma.spi2_busy() {} 155 | 156 | // Disable DMA 157 | dma.spi2_disable(); 158 | } 159 | 160 | /// Transmit 4 bits 161 | pub fn tx4(&self, data: u8) { 162 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: FourBit); 163 | self.write_dr_u8(data); 164 | self.wait_txe(); 165 | } 166 | 167 | /// Transmit 8 bits 168 | pub fn tx8(&self, data: u8) { 169 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: EightBit); 170 | self.write_dr_u8(data); 171 | self.wait_txe(); 172 | } 173 | 174 | /// Transmit 16 bits 175 | pub fn tx16(&self, data: u16) { 176 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: EightBit); 177 | self.write_dr_u16(data); 178 | self.wait_txe(); 179 | } 180 | 181 | /// Transmit an SWD WDATA phase, with 32 bits of data and 1 bit of parity. 182 | /// 183 | /// We transmit an extra 7 trailing idle bits after the parity bit because 184 | /// it's much quicker to do that than reconfigure SPI to a smaller data size. 185 | pub fn swd_wdata_phase(&self, data: u32, parity: u8) { 186 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: EightBit); 187 | // Trigger 4 words, filling the FIFO 188 | self.write_dr_u16((data & 0xFFFF) as u16); 189 | self.write_dr_u16((data >> 16) as u16); 190 | self.wait_txe(); 191 | // Trigger fifth and final word 192 | self.write_dr_u8(parity & 1); 193 | } 194 | 195 | /// Receive 4 bits 196 | pub fn rx4(&self) -> u8 { 197 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: FourBit); 198 | self.write_dr_u8(0); 199 | self.wait_rxne(); 200 | self.read_dr_u8() 201 | } 202 | 203 | /// Receive 5 bits 204 | pub fn rx5(&self) -> u8 { 205 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: FiveBit); 206 | self.write_dr_u8(0); 207 | self.wait_rxne(); 208 | self.read_dr_u8() 209 | } 210 | 211 | /// Receive an SWD RDATA phase, with 32 bits of data and 1 bit of parity. 212 | /// 213 | /// This method requires `Pins` be passed in so it can directly control 214 | /// the SWD lines at the end of RDATA in order to correctly sample PARITY 215 | /// and then resume driving SWDIO. 216 | pub fn swd_rdata_phase(&self, pins: &Pins) -> (u32, u8) { 217 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: EightBit); 218 | // Trigger 4 words, filling the FIFO 219 | self.write_dr_u16(0); 220 | self.write_dr_u16(0); 221 | self.wait_rxne(); 222 | let mut data = self.read_dr_u8() as u32; 223 | self.wait_rxne(); 224 | data |= (self.read_dr_u8() as u32) << 8; 225 | self.wait_rxne(); 226 | data |= (self.read_dr_u8() as u32) << 16; 227 | 228 | // While we wait for the final word to be available in the RXFIFO, 229 | // handle the parity bit. First wait for current transaction to complete. 230 | self.wait_rxne(); 231 | 232 | // The parity bit is currently being driven onto the bus by the target. 233 | // On the next rising edge, the target will release the bus, and we need 234 | // to then start driving it before sending any more clocks to avoid a false START. 235 | let parity = pins.spi1_miso.is_high() as u8; 236 | // Take direct control of SWCLK 237 | pins.swd_clk_direct(); 238 | // Send one clock pulse. Target releases bus after rising edge. 239 | pins.spi1_clk.set_low(); 240 | pins.spi1_clk.set_high(); 241 | // Drive bus ourselves with 0 (all our SPI read transactions transmitted 0s) 242 | pins.swd_tx(); 243 | // Restore SWCLK to SPI control 244 | pins.swd_clk_spi(); 245 | 246 | // Trigger four dummy idle cycles 247 | write_reg!(spi, self.spi, CR2, FRXTH: Quarter, DS: FourBit); 248 | self.write_dr_u8(0); 249 | 250 | // Now read the final data word that was waiting in RXFIFO 251 | data |= (self.read_dr_u8() as u32) << 24; 252 | 253 | (data, parity) 254 | } 255 | 256 | /// Empty the receive FIFO 257 | pub fn drain(&self) { 258 | // FIFO is 32 bits so ideally we'd make two 16-bit reads, but that screws 259 | // up the SPI's FIFO pointers and wrecks subsequent reads on later operations. 260 | // It's still faster to just do 4 reads instead of looping on FRLVL. 261 | self.read_dr_u8(); 262 | self.read_dr_u8(); 263 | self.read_dr_u8(); 264 | self.read_dr_u8(); 265 | } 266 | 267 | /// Wait for current SPI operation to complete 268 | #[inline(always)] 269 | pub fn wait_busy(&self) { 270 | while read_reg!(spi, self.spi, SR, BSY == Busy) {} 271 | } 272 | 273 | /// Wait for RXNE 274 | #[inline(always)] 275 | fn wait_rxne(&self) { 276 | while read_reg!(spi, self.spi, SR, RXNE == Empty) {} 277 | } 278 | 279 | /// Wait for TXE 280 | #[inline(always)] 281 | fn wait_txe(&self) { 282 | while read_reg!(spi, self.spi, SR, TXE != Empty) {} 283 | } 284 | 285 | /// Perform an 8-bit read from DR 286 | #[inline(always)] 287 | fn read_dr_u8(&self) -> u8 { 288 | unsafe { core::ptr::read_volatile(&self.spi.DR as *const _ as *const u8) } 289 | } 290 | 291 | /// Perform an 8-bit write to DR 292 | #[inline(always)] 293 | fn write_dr_u8(&self, data: u8) { 294 | let ptr = &self.spi.DR as *const _; 295 | unsafe { core::ptr::write_volatile(ptr as *mut u8, data) }; 296 | } 297 | 298 | /// Perform a 16-bit write to DR 299 | /// 300 | /// Note that in 8-bit or smaller data mode, this enqueues two transmissions. 301 | #[inline(always)] 302 | fn write_dr_u16(&self, data: u16) { 303 | let ptr = &self.spi.DR as *const _; 304 | unsafe { core::ptr::write_volatile(ptr as *mut u16, data) }; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /hs-probe-bsp/src/uart.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Adam Greig 2 | // Dual licensed under the Apache 2.0 and MIT licenses. 3 | 4 | use core::cmp::Ordering; 5 | use stm32ral::usart; 6 | use stm32ral::{modify_reg, read_reg, write_reg}; 7 | 8 | use super::dma::DMA; 9 | use super::rcc::Clocks; 10 | 11 | pub struct UART<'a> { 12 | uart: usart::Instance, 13 | dma: &'a DMA, 14 | buffer: [u8; 256], 15 | last_idx: usize, 16 | fck: u32, 17 | } 18 | 19 | impl<'a> UART<'a> { 20 | pub fn new(uart: usart::Instance, dma: &'a DMA) -> Self { 21 | UART { 22 | uart, 23 | dma, 24 | buffer: [0; 256], 25 | last_idx: 0, 26 | fck: 72_000_000, 27 | } 28 | } 29 | 30 | /// Set the UART peripheral clock speed, used for baud rate calculation. 31 | pub fn setup(&mut self, clocks: &Clocks) { 32 | self.fck = clocks.pclk2(); 33 | } 34 | 35 | /// Begin UART reception into buffer. 36 | /// 37 | /// UART::poll must be called regularly after starting. 38 | pub fn start(&mut self) { 39 | self.last_idx = 0; 40 | write_reg!(usart, self.uart, CR3, DMAR: Enabled); 41 | write_reg!( 42 | usart, 43 | self.uart, 44 | CR1, 45 | OVER8: Oversampling8, 46 | RE: Enabled, 47 | UE: Enabled 48 | ); 49 | self.dma.usart1_start(&mut self.buffer); 50 | } 51 | 52 | /// End UART reception. 53 | pub fn stop(&self) { 54 | self.dma.usart1_stop(); 55 | modify_reg!(usart, self.uart, CR1, RE: Disabled); 56 | } 57 | 58 | /// Returns true if UART currently enabled 59 | pub fn is_active(&self) -> bool { 60 | read_reg!(usart, self.uart, CR1, RE == Enabled) 61 | } 62 | 63 | /// Return length of internal buffer 64 | pub fn buffer_len(&self) -> usize { 65 | self.buffer.len() 66 | } 67 | 68 | /// Request a target baud rate. Returns actual baud rate set. 69 | pub fn set_baud(&self, baud: u32) -> u32 { 70 | // Find closest divider which is also an even integer >= 16. 71 | // The baud rate is (2*fck)/BRR. 72 | let mut div = (2 * self.fck) / baud; 73 | div &= 0xffff_fffe; 74 | if div < 16 { 75 | div = 16; 76 | } 77 | 78 | // Write BRR value based on div. 79 | // Since we are OVERSAMPLE8, shift bottom 4 bits down by 1. 80 | let brr = (div & 0xffff_fff0) | ((div & 0xf) >> 1); 81 | write_reg!(usart, self.uart, BRR, brr); 82 | 83 | // Return actual baud rate 84 | (2 * self.fck) / div 85 | } 86 | 87 | /// Fetch current number of bytes available. 88 | /// 89 | /// Subsequent calls to read() may return a different amount of data. 90 | pub fn bytes_available(&self) -> usize { 91 | let dma_idx = self.buffer.len() - self.dma.usart1_ndtr(); 92 | if dma_idx >= self.last_idx { 93 | dma_idx - self.last_idx 94 | } else { 95 | (self.buffer.len() - self.last_idx) + dma_idx 96 | } 97 | } 98 | 99 | /// Read new UART data. 100 | /// 101 | /// Returns number of bytes written to buffer. 102 | /// 103 | /// Reads at most rx.len() new bytes, which may be less than what was received. 104 | /// Remaining data will be read on the next call, so long as the internal buffer 105 | /// doesn't overflow, which is not detected. 106 | pub fn read(&mut self, rx: &mut [u8]) -> usize { 107 | // See what index the DMA is going to write next, and copy out 108 | // all prior data. Even if the DMA writes new data while we're 109 | // processing we won't get out of sync and will handle the new 110 | // data next time read() is called. 111 | let dma_idx = self.buffer.len() - self.dma.usart1_ndtr(); 112 | 113 | match dma_idx.cmp(&self.last_idx) { 114 | Ordering::Equal => { 115 | // No action required if no data has been received. 116 | 0 117 | } 118 | Ordering::Less => { 119 | // Wraparound occurred: 120 | // Copy from last_idx to end, and from start to new dma_idx. 121 | let mut n1 = self.buffer.len() - self.last_idx; 122 | let mut n2 = dma_idx; 123 | let mut new_last_idx = dma_idx; 124 | 125 | // Ensure we don't overflow rx buffer 126 | if n1 > rx.len() { 127 | n1 = rx.len(); 128 | n2 = 0; 129 | new_last_idx = self.last_idx + n1; 130 | } else if (n1 + n2) > rx.len() { 131 | n2 = rx.len() - n1; 132 | new_last_idx = n2; 133 | } 134 | 135 | rx[..n1].copy_from_slice(&self.buffer[self.last_idx..self.last_idx + n1]); 136 | rx[n1..(n1 + n2)].copy_from_slice(&self.buffer[..n2]); 137 | 138 | self.last_idx = new_last_idx; 139 | n1 + n2 140 | } 141 | Ordering::Greater => { 142 | // New data, no wraparound: 143 | // Copy from last_idx to new dma_idx. 144 | let mut n = dma_idx - self.last_idx; 145 | 146 | // Ensure we don't overflow rx buffer 147 | if n > rx.len() { 148 | n = rx.len(); 149 | } 150 | 151 | rx[..n].copy_from_slice(&self.buffer[self.last_idx..self.last_idx + n]); 152 | 153 | self.last_idx += n; 154 | n 155 | } 156 | } 157 | } 158 | } 159 | --------------------------------------------------------------------------------