├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── doc ├── datamover_register.txt ├── fifo.txt ├── plot_1ch.py └── transfer_counter_register.txt └── src ├── bin ├── gui │ ├── DejaVuSans-Bold.ttf │ ├── DejaVuSans.ttf │ ├── DejaVuSansMono.ttf │ ├── DejaVuSerif.ttf │ ├── capture.rs │ ├── main.rs │ ├── wave_frag.glsl │ └── wave_vert.glsl └── test.rs ├── buffer.rs ├── config.rs ├── device.rs ├── lib.rs ├── params.rs ├── regs ├── adc.rs ├── axi.rs └── mod.rs ├── sys ├── linux.rs ├── mod.rs └── stub.rs └── trigger.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.data 3 | -------------------------------------------------------------------------------- /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 = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "getrandom", 13 | "once_cell", 14 | "version_check", 15 | "zerocopy", 16 | ] 17 | 18 | [[package]] 19 | name = "aho-corasick" 20 | version = "1.1.3" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 23 | dependencies = [ 24 | "memchr", 25 | ] 26 | 27 | [[package]] 28 | name = "android-activity" 29 | version = "0.5.2" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" 32 | dependencies = [ 33 | "android-properties", 34 | "bitflags 2.6.0", 35 | "cc", 36 | "cesu8", 37 | "jni", 38 | "jni-sys", 39 | "libc", 40 | "log", 41 | "ndk", 42 | "ndk-context", 43 | "ndk-sys", 44 | "num_enum", 45 | "thiserror", 46 | ] 47 | 48 | [[package]] 49 | name = "android-properties" 50 | version = "0.2.2" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" 53 | 54 | [[package]] 55 | name = "anstream" 56 | version = "0.6.14" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 59 | dependencies = [ 60 | "anstyle", 61 | "anstyle-parse", 62 | "anstyle-query", 63 | "anstyle-wincon", 64 | "colorchoice", 65 | "is_terminal_polyfill", 66 | "utf8parse", 67 | ] 68 | 69 | [[package]] 70 | name = "anstyle" 71 | version = "1.0.7" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 74 | 75 | [[package]] 76 | name = "anstyle-parse" 77 | version = "0.2.4" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 80 | dependencies = [ 81 | "utf8parse", 82 | ] 83 | 84 | [[package]] 85 | name = "anstyle-query" 86 | version = "1.1.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 89 | dependencies = [ 90 | "windows-sys 0.52.0", 91 | ] 92 | 93 | [[package]] 94 | name = "anstyle-wincon" 95 | version = "3.0.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 98 | dependencies = [ 99 | "anstyle", 100 | "windows-sys 0.52.0", 101 | ] 102 | 103 | [[package]] 104 | name = "as-raw-xcb-connection" 105 | version = "1.0.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" 108 | 109 | [[package]] 110 | name = "atomic-waker" 111 | version = "1.1.2" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 114 | 115 | [[package]] 116 | name = "autocfg" 117 | version = "1.3.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 120 | 121 | [[package]] 122 | name = "bitflags" 123 | version = "1.3.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 126 | 127 | [[package]] 128 | name = "bitflags" 129 | version = "2.6.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 132 | 133 | [[package]] 134 | name = "block-sys" 135 | version = "0.2.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" 138 | dependencies = [ 139 | "objc-sys", 140 | ] 141 | 142 | [[package]] 143 | name = "block2" 144 | version = "0.3.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" 147 | dependencies = [ 148 | "block-sys", 149 | "objc2", 150 | ] 151 | 152 | [[package]] 153 | name = "bumpalo" 154 | version = "3.16.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 157 | 158 | [[package]] 159 | name = "bytemuck" 160 | version = "1.16.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" 163 | 164 | [[package]] 165 | name = "bytes" 166 | version = "1.6.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 169 | 170 | [[package]] 171 | name = "calloop" 172 | version = "0.12.4" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" 175 | dependencies = [ 176 | "bitflags 2.6.0", 177 | "log", 178 | "polling", 179 | "rustix", 180 | "slab", 181 | "thiserror", 182 | ] 183 | 184 | [[package]] 185 | name = "calloop-wayland-source" 186 | version = "0.2.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" 189 | dependencies = [ 190 | "calloop", 191 | "rustix", 192 | "wayland-backend", 193 | "wayland-client", 194 | ] 195 | 196 | [[package]] 197 | name = "cc" 198 | version = "1.0.104" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" 201 | dependencies = [ 202 | "jobserver", 203 | "libc", 204 | "once_cell", 205 | ] 206 | 207 | [[package]] 208 | name = "cesu8" 209 | version = "1.1.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 212 | 213 | [[package]] 214 | name = "cfg-if" 215 | version = "1.0.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 218 | 219 | [[package]] 220 | name = "cfg_aliases" 221 | version = "0.1.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 224 | 225 | [[package]] 226 | name = "cgl" 227 | version = "0.3.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" 230 | dependencies = [ 231 | "libc", 232 | ] 233 | 234 | [[package]] 235 | name = "chlorine" 236 | version = "1.0.12" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1e10e7569f6ca78ef7664d7d651115172d4875c4410c050306bccde856a99a49" 239 | 240 | [[package]] 241 | name = "colorchoice" 242 | version = "1.0.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 245 | 246 | [[package]] 247 | name = "combine" 248 | version = "4.6.7" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 251 | dependencies = [ 252 | "bytes", 253 | "memchr", 254 | ] 255 | 256 | [[package]] 257 | name = "concurrent-queue" 258 | version = "2.5.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 261 | dependencies = [ 262 | "crossbeam-utils", 263 | ] 264 | 265 | [[package]] 266 | name = "core-foundation" 267 | version = "0.9.4" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 270 | dependencies = [ 271 | "core-foundation-sys", 272 | "libc", 273 | ] 274 | 275 | [[package]] 276 | name = "core-foundation-sys" 277 | version = "0.8.6" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 280 | 281 | [[package]] 282 | name = "core-graphics" 283 | version = "0.23.2" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" 286 | dependencies = [ 287 | "bitflags 1.3.2", 288 | "core-foundation", 289 | "core-graphics-types", 290 | "foreign-types", 291 | "libc", 292 | ] 293 | 294 | [[package]] 295 | name = "core-graphics-types" 296 | version = "0.1.3" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 299 | dependencies = [ 300 | "bitflags 1.3.2", 301 | "core-foundation", 302 | "libc", 303 | ] 304 | 305 | [[package]] 306 | name = "crossbeam-utils" 307 | version = "0.8.20" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 310 | 311 | [[package]] 312 | name = "cursor-icon" 313 | version = "1.1.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" 316 | 317 | [[package]] 318 | name = "dispatch" 319 | version = "0.2.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 322 | 323 | [[package]] 324 | name = "dlib" 325 | version = "0.5.2" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 328 | dependencies = [ 329 | "libloading", 330 | ] 331 | 332 | [[package]] 333 | name = "downcast-rs" 334 | version = "1.2.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 337 | 338 | [[package]] 339 | name = "env_filter" 340 | version = "0.1.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 343 | dependencies = [ 344 | "log", 345 | "regex", 346 | ] 347 | 348 | [[package]] 349 | name = "env_logger" 350 | version = "0.11.3" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 353 | dependencies = [ 354 | "anstream", 355 | "anstyle", 356 | "env_filter", 357 | "humantime", 358 | "log", 359 | ] 360 | 361 | [[package]] 362 | name = "equivalent" 363 | version = "1.0.1" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 366 | 367 | [[package]] 368 | name = "errno" 369 | version = "0.3.9" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 372 | dependencies = [ 373 | "libc", 374 | "windows-sys 0.52.0", 375 | ] 376 | 377 | [[package]] 378 | name = "foreign-types" 379 | version = "0.5.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 382 | dependencies = [ 383 | "foreign-types-macros", 384 | "foreign-types-shared", 385 | ] 386 | 387 | [[package]] 388 | name = "foreign-types-macros" 389 | version = "0.2.3" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "syn", 396 | ] 397 | 398 | [[package]] 399 | name = "foreign-types-shared" 400 | version = "0.3.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 403 | 404 | [[package]] 405 | name = "gethostname" 406 | version = "0.4.3" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" 409 | dependencies = [ 410 | "libc", 411 | "windows-targets 0.48.5", 412 | ] 413 | 414 | [[package]] 415 | name = "getrandom" 416 | version = "0.2.15" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 419 | dependencies = [ 420 | "cfg-if", 421 | "libc", 422 | "wasi", 423 | ] 424 | 425 | [[package]] 426 | name = "gl_generator" 427 | version = "0.14.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 430 | dependencies = [ 431 | "khronos_api", 432 | "log", 433 | "xml-rs", 434 | ] 435 | 436 | [[package]] 437 | name = "glow" 438 | version = "0.13.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" 441 | dependencies = [ 442 | "js-sys", 443 | "slotmap", 444 | "wasm-bindgen", 445 | "web-sys", 446 | ] 447 | 448 | [[package]] 449 | name = "glutin" 450 | version = "0.31.3" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" 453 | dependencies = [ 454 | "bitflags 2.6.0", 455 | "cfg_aliases", 456 | "cgl", 457 | "core-foundation", 458 | "dispatch", 459 | "glutin_egl_sys", 460 | "glutin_glx_sys", 461 | "glutin_wgl_sys", 462 | "icrate", 463 | "libloading", 464 | "objc2", 465 | "once_cell", 466 | "raw-window-handle", 467 | "wayland-sys", 468 | "windows-sys 0.48.0", 469 | "x11-dl", 470 | ] 471 | 472 | [[package]] 473 | name = "glutin-winit" 474 | version = "0.4.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" 477 | dependencies = [ 478 | "cfg_aliases", 479 | "glutin", 480 | "raw-window-handle", 481 | "winit", 482 | ] 483 | 484 | [[package]] 485 | name = "glutin_egl_sys" 486 | version = "0.6.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" 489 | dependencies = [ 490 | "gl_generator", 491 | "windows-sys 0.48.0", 492 | ] 493 | 494 | [[package]] 495 | name = "glutin_glx_sys" 496 | version = "0.5.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" 499 | dependencies = [ 500 | "gl_generator", 501 | "x11-dl", 502 | ] 503 | 504 | [[package]] 505 | name = "glutin_wgl_sys" 506 | version = "0.5.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" 509 | dependencies = [ 510 | "gl_generator", 511 | ] 512 | 513 | [[package]] 514 | name = "hashbrown" 515 | version = "0.14.5" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 518 | 519 | [[package]] 520 | name = "hermit-abi" 521 | version = "0.4.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 524 | 525 | [[package]] 526 | name = "humantime" 527 | version = "2.1.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 530 | 531 | [[package]] 532 | name = "icrate" 533 | version = "0.0.4" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" 536 | dependencies = [ 537 | "block2", 538 | "dispatch", 539 | "objc2", 540 | ] 541 | 542 | [[package]] 543 | name = "imgui" 544 | version = "0.12.0" 545 | source = "git+https://github.com/whitequark/imgui-rs?branch=imgui-1.90.1#0d2b984d9485f821e4e33ec922a0ae09ba985bfd" 546 | dependencies = [ 547 | "bitflags 1.3.2", 548 | "cfg-if", 549 | "imgui-sys", 550 | "mint", 551 | "parking_lot", 552 | ] 553 | 554 | [[package]] 555 | name = "imgui-glow-renderer" 556 | version = "0.12.0" 557 | source = "git+https://github.com/whitequark/imgui-rs?branch=imgui-1.90.1#0d2b984d9485f821e4e33ec922a0ae09ba985bfd" 558 | dependencies = [ 559 | "glow", 560 | "imgui", 561 | "memoffset", 562 | ] 563 | 564 | [[package]] 565 | name = "imgui-sys" 566 | version = "0.12.0" 567 | source = "git+https://github.com/whitequark/imgui-rs?branch=imgui-1.90.1#0d2b984d9485f821e4e33ec922a0ae09ba985bfd" 568 | dependencies = [ 569 | "cc", 570 | "cfg-if", 571 | "chlorine", 572 | "mint", 573 | ] 574 | 575 | [[package]] 576 | name = "imgui-winit-support" 577 | version = "0.12.0" 578 | source = "git+https://github.com/whitequark/imgui-rs?branch=imgui-1.90.1#0d2b984d9485f821e4e33ec922a0ae09ba985bfd" 579 | dependencies = [ 580 | "imgui", 581 | "winit", 582 | ] 583 | 584 | [[package]] 585 | name = "indexmap" 586 | version = "2.2.6" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 589 | dependencies = [ 590 | "equivalent", 591 | "hashbrown", 592 | ] 593 | 594 | [[package]] 595 | name = "is_terminal_polyfill" 596 | version = "1.70.0" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 599 | 600 | [[package]] 601 | name = "jni" 602 | version = "0.21.1" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 605 | dependencies = [ 606 | "cesu8", 607 | "cfg-if", 608 | "combine", 609 | "jni-sys", 610 | "log", 611 | "thiserror", 612 | "walkdir", 613 | "windows-sys 0.45.0", 614 | ] 615 | 616 | [[package]] 617 | name = "jni-sys" 618 | version = "0.3.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 621 | 622 | [[package]] 623 | name = "jobserver" 624 | version = "0.1.31" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 627 | dependencies = [ 628 | "libc", 629 | ] 630 | 631 | [[package]] 632 | name = "js-sys" 633 | version = "0.3.69" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 636 | dependencies = [ 637 | "wasm-bindgen", 638 | ] 639 | 640 | [[package]] 641 | name = "khronos_api" 642 | version = "3.1.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 645 | 646 | [[package]] 647 | name = "libc" 648 | version = "0.2.155" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 651 | 652 | [[package]] 653 | name = "libloading" 654 | version = "0.8.4" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" 657 | dependencies = [ 658 | "cfg-if", 659 | "windows-targets 0.52.6", 660 | ] 661 | 662 | [[package]] 663 | name = "libredox" 664 | version = "0.0.2" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" 667 | dependencies = [ 668 | "bitflags 2.6.0", 669 | "libc", 670 | "redox_syscall 0.4.1", 671 | ] 672 | 673 | [[package]] 674 | name = "linux-raw-sys" 675 | version = "0.4.14" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 678 | 679 | [[package]] 680 | name = "lock_api" 681 | version = "0.4.12" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 684 | dependencies = [ 685 | "autocfg", 686 | "scopeguard", 687 | ] 688 | 689 | [[package]] 690 | name = "log" 691 | version = "0.4.22" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 694 | 695 | [[package]] 696 | name = "memchr" 697 | version = "2.7.4" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 700 | 701 | [[package]] 702 | name = "memmap2" 703 | version = "0.9.4" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" 706 | dependencies = [ 707 | "libc", 708 | ] 709 | 710 | [[package]] 711 | name = "memoffset" 712 | version = "0.9.1" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 715 | dependencies = [ 716 | "autocfg", 717 | ] 718 | 719 | [[package]] 720 | name = "mint" 721 | version = "0.5.9" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" 724 | 725 | [[package]] 726 | name = "ndk" 727 | version = "0.8.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" 730 | dependencies = [ 731 | "bitflags 2.6.0", 732 | "jni-sys", 733 | "log", 734 | "ndk-sys", 735 | "num_enum", 736 | "raw-window-handle", 737 | "thiserror", 738 | ] 739 | 740 | [[package]] 741 | name = "ndk-context" 742 | version = "0.1.1" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 745 | 746 | [[package]] 747 | name = "ndk-sys" 748 | version = "0.5.0+25.2.9519653" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" 751 | dependencies = [ 752 | "jni-sys", 753 | ] 754 | 755 | [[package]] 756 | name = "num_enum" 757 | version = "0.7.2" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" 760 | dependencies = [ 761 | "num_enum_derive", 762 | ] 763 | 764 | [[package]] 765 | name = "num_enum_derive" 766 | version = "0.7.2" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" 769 | dependencies = [ 770 | "proc-macro-crate", 771 | "proc-macro2", 772 | "quote", 773 | "syn", 774 | ] 775 | 776 | [[package]] 777 | name = "objc-sys" 778 | version = "0.3.5" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" 781 | 782 | [[package]] 783 | name = "objc2" 784 | version = "0.4.1" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" 787 | dependencies = [ 788 | "objc-sys", 789 | "objc2-encode", 790 | ] 791 | 792 | [[package]] 793 | name = "objc2-encode" 794 | version = "3.0.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" 797 | 798 | [[package]] 799 | name = "once_cell" 800 | version = "1.19.0" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 803 | 804 | [[package]] 805 | name = "orbclient" 806 | version = "0.3.47" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" 809 | dependencies = [ 810 | "libredox", 811 | ] 812 | 813 | [[package]] 814 | name = "parking_lot" 815 | version = "0.12.3" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 818 | dependencies = [ 819 | "lock_api", 820 | "parking_lot_core", 821 | ] 822 | 823 | [[package]] 824 | name = "parking_lot_core" 825 | version = "0.9.10" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 828 | dependencies = [ 829 | "cfg-if", 830 | "libc", 831 | "redox_syscall 0.5.2", 832 | "smallvec", 833 | "windows-targets 0.52.6", 834 | ] 835 | 836 | [[package]] 837 | name = "percent-encoding" 838 | version = "2.3.1" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 841 | 842 | [[package]] 843 | name = "pin-project-lite" 844 | version = "0.2.14" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 847 | 848 | [[package]] 849 | name = "pkg-config" 850 | version = "0.3.30" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 853 | 854 | [[package]] 855 | name = "polling" 856 | version = "3.7.2" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" 859 | dependencies = [ 860 | "cfg-if", 861 | "concurrent-queue", 862 | "hermit-abi", 863 | "pin-project-lite", 864 | "rustix", 865 | "tracing", 866 | "windows-sys 0.52.0", 867 | ] 868 | 869 | [[package]] 870 | name = "proc-macro-crate" 871 | version = "3.1.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" 874 | dependencies = [ 875 | "toml_edit", 876 | ] 877 | 878 | [[package]] 879 | name = "proc-macro2" 880 | version = "1.0.86" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 883 | dependencies = [ 884 | "unicode-ident", 885 | ] 886 | 887 | [[package]] 888 | name = "quick-xml" 889 | version = "0.34.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" 892 | dependencies = [ 893 | "memchr", 894 | ] 895 | 896 | [[package]] 897 | name = "quote" 898 | version = "1.0.36" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 901 | dependencies = [ 902 | "proc-macro2", 903 | ] 904 | 905 | [[package]] 906 | name = "raw-window-handle" 907 | version = "0.5.2" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" 910 | 911 | [[package]] 912 | name = "redox_syscall" 913 | version = "0.3.5" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 916 | dependencies = [ 917 | "bitflags 1.3.2", 918 | ] 919 | 920 | [[package]] 921 | name = "redox_syscall" 922 | version = "0.4.1" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 925 | dependencies = [ 926 | "bitflags 1.3.2", 927 | ] 928 | 929 | [[package]] 930 | name = "redox_syscall" 931 | version = "0.5.2" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 934 | dependencies = [ 935 | "bitflags 2.6.0", 936 | ] 937 | 938 | [[package]] 939 | name = "regex" 940 | version = "1.10.5" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 943 | dependencies = [ 944 | "aho-corasick", 945 | "memchr", 946 | "regex-automata", 947 | "regex-syntax", 948 | ] 949 | 950 | [[package]] 951 | name = "regex-automata" 952 | version = "0.4.7" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 955 | dependencies = [ 956 | "aho-corasick", 957 | "memchr", 958 | "regex-syntax", 959 | ] 960 | 961 | [[package]] 962 | name = "regex-syntax" 963 | version = "0.8.4" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 966 | 967 | [[package]] 968 | name = "rustix" 969 | version = "0.38.34" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 972 | dependencies = [ 973 | "bitflags 2.6.0", 974 | "errno", 975 | "libc", 976 | "linux-raw-sys", 977 | "windows-sys 0.52.0", 978 | ] 979 | 980 | [[package]] 981 | name = "safe_arch" 982 | version = "0.7.2" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" 985 | dependencies = [ 986 | "bytemuck", 987 | ] 988 | 989 | [[package]] 990 | name = "same-file" 991 | version = "1.0.6" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 994 | dependencies = [ 995 | "winapi-util", 996 | ] 997 | 998 | [[package]] 999 | name = "scoped-tls" 1000 | version = "1.0.1" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1003 | 1004 | [[package]] 1005 | name = "scopeguard" 1006 | version = "1.2.0" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1009 | 1010 | [[package]] 1011 | name = "serde" 1012 | version = "1.0.203" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 1015 | dependencies = [ 1016 | "serde_derive", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "serde_derive" 1021 | version = "1.0.203" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1024 | dependencies = [ 1025 | "proc-macro2", 1026 | "quote", 1027 | "syn", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "slab" 1032 | version = "0.4.9" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1035 | dependencies = [ 1036 | "autocfg", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "slotmap" 1041 | version = "1.0.7" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" 1044 | dependencies = [ 1045 | "version_check", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "smallvec" 1050 | version = "1.13.2" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1053 | 1054 | [[package]] 1055 | name = "smithay-client-toolkit" 1056 | version = "0.18.1" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" 1059 | dependencies = [ 1060 | "bitflags 2.6.0", 1061 | "calloop", 1062 | "calloop-wayland-source", 1063 | "cursor-icon", 1064 | "libc", 1065 | "log", 1066 | "memmap2", 1067 | "rustix", 1068 | "thiserror", 1069 | "wayland-backend", 1070 | "wayland-client", 1071 | "wayland-csd-frame", 1072 | "wayland-cursor", 1073 | "wayland-protocols", 1074 | "wayland-protocols-wlr", 1075 | "wayland-scanner", 1076 | "xkeysym", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "smol_str" 1081 | version = "0.2.2" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" 1084 | dependencies = [ 1085 | "serde", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "syn" 1090 | version = "2.0.68" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" 1093 | dependencies = [ 1094 | "proc-macro2", 1095 | "quote", 1096 | "unicode-ident", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "system_error" 1101 | version = "0.2.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "b52ada790ecca61aa8e561eba96355deb41b9b1b796dd3c965e74a590e9734a3" 1104 | 1105 | [[package]] 1106 | name = "thiserror" 1107 | version = "1.0.61" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1110 | dependencies = [ 1111 | "thiserror-impl", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "thiserror-impl" 1116 | version = "1.0.61" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1119 | dependencies = [ 1120 | "proc-macro2", 1121 | "quote", 1122 | "syn", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "thunderscope" 1127 | version = "0.1.0" 1128 | dependencies = [ 1129 | "bitflags 2.6.0", 1130 | "bytemuck", 1131 | "env_logger", 1132 | "glow", 1133 | "glutin", 1134 | "glutin-winit", 1135 | "imgui", 1136 | "imgui-glow-renderer", 1137 | "imgui-winit-support", 1138 | "libc", 1139 | "log", 1140 | "raw-window-handle", 1141 | "vmap", 1142 | "wide", 1143 | "winit", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "toml_datetime" 1148 | version = "0.6.6" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" 1151 | 1152 | [[package]] 1153 | name = "toml_edit" 1154 | version = "0.21.1" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" 1157 | dependencies = [ 1158 | "indexmap", 1159 | "toml_datetime", 1160 | "winnow", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "tracing" 1165 | version = "0.1.40" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1168 | dependencies = [ 1169 | "pin-project-lite", 1170 | "tracing-core", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "tracing-core" 1175 | version = "0.1.32" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1178 | 1179 | [[package]] 1180 | name = "unicode-ident" 1181 | version = "1.0.12" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1184 | 1185 | [[package]] 1186 | name = "unicode-segmentation" 1187 | version = "1.11.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1190 | 1191 | [[package]] 1192 | name = "utf8parse" 1193 | version = "0.2.2" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1196 | 1197 | [[package]] 1198 | name = "version_check" 1199 | version = "0.9.4" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1202 | 1203 | [[package]] 1204 | name = "vmap" 1205 | version = "0.6.3" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "58dc9bf6fc1f05e86dc98f03e3bf89dacfd5098734ee0aad1d8d6a45b38856a2" 1208 | dependencies = [ 1209 | "libc", 1210 | "system_error", 1211 | "winapi", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "walkdir" 1216 | version = "2.5.0" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1219 | dependencies = [ 1220 | "same-file", 1221 | "winapi-util", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "wasi" 1226 | version = "0.11.0+wasi-snapshot-preview1" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1229 | 1230 | [[package]] 1231 | name = "wasm-bindgen" 1232 | version = "0.2.92" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1235 | dependencies = [ 1236 | "cfg-if", 1237 | "wasm-bindgen-macro", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "wasm-bindgen-backend" 1242 | version = "0.2.92" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1245 | dependencies = [ 1246 | "bumpalo", 1247 | "log", 1248 | "once_cell", 1249 | "proc-macro2", 1250 | "quote", 1251 | "syn", 1252 | "wasm-bindgen-shared", 1253 | ] 1254 | 1255 | [[package]] 1256 | name = "wasm-bindgen-futures" 1257 | version = "0.4.42" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1260 | dependencies = [ 1261 | "cfg-if", 1262 | "js-sys", 1263 | "wasm-bindgen", 1264 | "web-sys", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "wasm-bindgen-macro" 1269 | version = "0.2.92" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1272 | dependencies = [ 1273 | "quote", 1274 | "wasm-bindgen-macro-support", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "wasm-bindgen-macro-support" 1279 | version = "0.2.92" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1282 | dependencies = [ 1283 | "proc-macro2", 1284 | "quote", 1285 | "syn", 1286 | "wasm-bindgen-backend", 1287 | "wasm-bindgen-shared", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "wasm-bindgen-shared" 1292 | version = "0.2.92" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1295 | 1296 | [[package]] 1297 | name = "wayland-backend" 1298 | version = "0.3.5" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "269c04f203640d0da2092d1b8d89a2d081714ae3ac2f1b53e99f205740517198" 1301 | dependencies = [ 1302 | "cc", 1303 | "downcast-rs", 1304 | "rustix", 1305 | "scoped-tls", 1306 | "smallvec", 1307 | "wayland-sys", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "wayland-client" 1312 | version = "0.31.4" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "08bd0f46c069d3382a36c8666c1b9ccef32b8b04f41667ca1fef06a1adcc2982" 1315 | dependencies = [ 1316 | "bitflags 2.6.0", 1317 | "rustix", 1318 | "wayland-backend", 1319 | "wayland-scanner", 1320 | ] 1321 | 1322 | [[package]] 1323 | name = "wayland-csd-frame" 1324 | version = "0.3.0" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" 1327 | dependencies = [ 1328 | "bitflags 2.6.0", 1329 | "cursor-icon", 1330 | "wayland-backend", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "wayland-cursor" 1335 | version = "0.31.4" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "09414bcf0fd8d9577d73e9ac4659ebc45bcc9cff1980a350543ad8e50ee263b2" 1338 | dependencies = [ 1339 | "rustix", 1340 | "wayland-client", 1341 | "xcursor", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "wayland-protocols" 1346 | version = "0.31.2" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" 1349 | dependencies = [ 1350 | "bitflags 2.6.0", 1351 | "wayland-backend", 1352 | "wayland-client", 1353 | "wayland-scanner", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "wayland-protocols-plasma" 1358 | version = "0.2.0" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" 1361 | dependencies = [ 1362 | "bitflags 2.6.0", 1363 | "wayland-backend", 1364 | "wayland-client", 1365 | "wayland-protocols", 1366 | "wayland-scanner", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "wayland-protocols-wlr" 1371 | version = "0.2.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" 1374 | dependencies = [ 1375 | "bitflags 2.6.0", 1376 | "wayland-backend", 1377 | "wayland-client", 1378 | "wayland-protocols", 1379 | "wayland-scanner", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "wayland-scanner" 1384 | version = "0.31.3" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "edf466fc49a4feb65a511ca403fec3601494d0dee85dbf37fff6fa0dd4eec3b6" 1387 | dependencies = [ 1388 | "proc-macro2", 1389 | "quick-xml", 1390 | "quote", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "wayland-sys" 1395 | version = "0.31.3" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "4a6754825230fa5b27bafaa28c30b3c9e72c55530581220cef401fa422c0fae7" 1398 | dependencies = [ 1399 | "dlib", 1400 | "log", 1401 | "once_cell", 1402 | "pkg-config", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "web-sys" 1407 | version = "0.3.69" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1410 | dependencies = [ 1411 | "js-sys", 1412 | "wasm-bindgen", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "web-time" 1417 | version = "0.2.4" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" 1420 | dependencies = [ 1421 | "js-sys", 1422 | "wasm-bindgen", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "wide" 1427 | version = "0.7.25" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "2caba658a80831539b30698ae9862a72db6697dfdd7151e46920f5f2755c3ce2" 1430 | dependencies = [ 1431 | "bytemuck", 1432 | "safe_arch", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "winapi" 1437 | version = "0.3.9" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1440 | dependencies = [ 1441 | "winapi-i686-pc-windows-gnu", 1442 | "winapi-x86_64-pc-windows-gnu", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "winapi-i686-pc-windows-gnu" 1447 | version = "0.4.0" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1450 | 1451 | [[package]] 1452 | name = "winapi-util" 1453 | version = "0.1.8" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 1456 | dependencies = [ 1457 | "windows-sys 0.52.0", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "winapi-x86_64-pc-windows-gnu" 1462 | version = "0.4.0" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1465 | 1466 | [[package]] 1467 | name = "windows-sys" 1468 | version = "0.45.0" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1471 | dependencies = [ 1472 | "windows-targets 0.42.2", 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "windows-sys" 1477 | version = "0.48.0" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1480 | dependencies = [ 1481 | "windows-targets 0.48.5", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "windows-sys" 1486 | version = "0.52.0" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1489 | dependencies = [ 1490 | "windows-targets 0.52.6", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "windows-targets" 1495 | version = "0.42.2" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1498 | dependencies = [ 1499 | "windows_aarch64_gnullvm 0.42.2", 1500 | "windows_aarch64_msvc 0.42.2", 1501 | "windows_i686_gnu 0.42.2", 1502 | "windows_i686_msvc 0.42.2", 1503 | "windows_x86_64_gnu 0.42.2", 1504 | "windows_x86_64_gnullvm 0.42.2", 1505 | "windows_x86_64_msvc 0.42.2", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "windows-targets" 1510 | version = "0.48.5" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1513 | dependencies = [ 1514 | "windows_aarch64_gnullvm 0.48.5", 1515 | "windows_aarch64_msvc 0.48.5", 1516 | "windows_i686_gnu 0.48.5", 1517 | "windows_i686_msvc 0.48.5", 1518 | "windows_x86_64_gnu 0.48.5", 1519 | "windows_x86_64_gnullvm 0.48.5", 1520 | "windows_x86_64_msvc 0.48.5", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "windows-targets" 1525 | version = "0.52.6" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1528 | dependencies = [ 1529 | "windows_aarch64_gnullvm 0.52.6", 1530 | "windows_aarch64_msvc 0.52.6", 1531 | "windows_i686_gnu 0.52.6", 1532 | "windows_i686_gnullvm", 1533 | "windows_i686_msvc 0.52.6", 1534 | "windows_x86_64_gnu 0.52.6", 1535 | "windows_x86_64_gnullvm 0.52.6", 1536 | "windows_x86_64_msvc 0.52.6", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "windows_aarch64_gnullvm" 1541 | version = "0.42.2" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1544 | 1545 | [[package]] 1546 | name = "windows_aarch64_gnullvm" 1547 | version = "0.48.5" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1550 | 1551 | [[package]] 1552 | name = "windows_aarch64_gnullvm" 1553 | version = "0.52.6" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1556 | 1557 | [[package]] 1558 | name = "windows_aarch64_msvc" 1559 | version = "0.42.2" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1562 | 1563 | [[package]] 1564 | name = "windows_aarch64_msvc" 1565 | version = "0.48.5" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1568 | 1569 | [[package]] 1570 | name = "windows_aarch64_msvc" 1571 | version = "0.52.6" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1574 | 1575 | [[package]] 1576 | name = "windows_i686_gnu" 1577 | version = "0.42.2" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1580 | 1581 | [[package]] 1582 | name = "windows_i686_gnu" 1583 | version = "0.48.5" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1586 | 1587 | [[package]] 1588 | name = "windows_i686_gnu" 1589 | version = "0.52.6" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1592 | 1593 | [[package]] 1594 | name = "windows_i686_gnullvm" 1595 | version = "0.52.6" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1598 | 1599 | [[package]] 1600 | name = "windows_i686_msvc" 1601 | version = "0.42.2" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1604 | 1605 | [[package]] 1606 | name = "windows_i686_msvc" 1607 | version = "0.48.5" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1610 | 1611 | [[package]] 1612 | name = "windows_i686_msvc" 1613 | version = "0.52.6" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1616 | 1617 | [[package]] 1618 | name = "windows_x86_64_gnu" 1619 | version = "0.42.2" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1622 | 1623 | [[package]] 1624 | name = "windows_x86_64_gnu" 1625 | version = "0.48.5" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1628 | 1629 | [[package]] 1630 | name = "windows_x86_64_gnu" 1631 | version = "0.52.6" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1634 | 1635 | [[package]] 1636 | name = "windows_x86_64_gnullvm" 1637 | version = "0.42.2" 1638 | source = "registry+https://github.com/rust-lang/crates.io-index" 1639 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1640 | 1641 | [[package]] 1642 | name = "windows_x86_64_gnullvm" 1643 | version = "0.48.5" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1646 | 1647 | [[package]] 1648 | name = "windows_x86_64_gnullvm" 1649 | version = "0.52.6" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1652 | 1653 | [[package]] 1654 | name = "windows_x86_64_msvc" 1655 | version = "0.42.2" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1658 | 1659 | [[package]] 1660 | name = "windows_x86_64_msvc" 1661 | version = "0.48.5" 1662 | source = "registry+https://github.com/rust-lang/crates.io-index" 1663 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1664 | 1665 | [[package]] 1666 | name = "windows_x86_64_msvc" 1667 | version = "0.52.6" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1670 | 1671 | [[package]] 1672 | name = "winit" 1673 | version = "0.29.15" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" 1676 | dependencies = [ 1677 | "ahash", 1678 | "android-activity", 1679 | "atomic-waker", 1680 | "bitflags 2.6.0", 1681 | "bytemuck", 1682 | "calloop", 1683 | "cfg_aliases", 1684 | "core-foundation", 1685 | "core-graphics", 1686 | "cursor-icon", 1687 | "icrate", 1688 | "js-sys", 1689 | "libc", 1690 | "log", 1691 | "memmap2", 1692 | "ndk", 1693 | "ndk-sys", 1694 | "objc2", 1695 | "once_cell", 1696 | "orbclient", 1697 | "percent-encoding", 1698 | "raw-window-handle", 1699 | "redox_syscall 0.3.5", 1700 | "rustix", 1701 | "smithay-client-toolkit", 1702 | "smol_str", 1703 | "unicode-segmentation", 1704 | "wasm-bindgen", 1705 | "wasm-bindgen-futures", 1706 | "wayland-backend", 1707 | "wayland-client", 1708 | "wayland-protocols", 1709 | "wayland-protocols-plasma", 1710 | "web-sys", 1711 | "web-time", 1712 | "windows-sys 0.48.0", 1713 | "x11-dl", 1714 | "x11rb", 1715 | "xkbcommon-dl", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "winnow" 1720 | version = "0.5.40" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 1723 | dependencies = [ 1724 | "memchr", 1725 | ] 1726 | 1727 | [[package]] 1728 | name = "x11-dl" 1729 | version = "2.21.0" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" 1732 | dependencies = [ 1733 | "libc", 1734 | "once_cell", 1735 | "pkg-config", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "x11rb" 1740 | version = "0.13.1" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" 1743 | dependencies = [ 1744 | "as-raw-xcb-connection", 1745 | "gethostname", 1746 | "libc", 1747 | "libloading", 1748 | "once_cell", 1749 | "rustix", 1750 | "x11rb-protocol", 1751 | ] 1752 | 1753 | [[package]] 1754 | name = "x11rb-protocol" 1755 | version = "0.13.1" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" 1758 | 1759 | [[package]] 1760 | name = "xcursor" 1761 | version = "0.3.5" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" 1764 | 1765 | [[package]] 1766 | name = "xkbcommon-dl" 1767 | version = "0.4.2" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" 1770 | dependencies = [ 1771 | "bitflags 2.6.0", 1772 | "dlib", 1773 | "log", 1774 | "once_cell", 1775 | "xkeysym", 1776 | ] 1777 | 1778 | [[package]] 1779 | name = "xkeysym" 1780 | version = "0.2.1" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" 1783 | 1784 | [[package]] 1785 | name = "xml-rs" 1786 | version = "0.8.20" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" 1789 | 1790 | [[package]] 1791 | name = "zerocopy" 1792 | version = "0.7.35" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1795 | dependencies = [ 1796 | "zerocopy-derive", 1797 | ] 1798 | 1799 | [[package]] 1800 | name = "zerocopy-derive" 1801 | version = "0.7.35" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1804 | dependencies = [ 1805 | "proc-macro2", 1806 | "quote", 1807 | "syn", 1808 | ] 1809 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thunderscope" 3 | description = "Driver library and simple graphical interface for the ThunderScope oscilloscope" 4 | version = "0.1.0" 5 | edition = "2021" 6 | default-run = "thunderscope-gui" 7 | 8 | [[bin]] 9 | name = "thunderscope-gui" 10 | path = "src/bin/gui/main.rs" 11 | required-features = ["gui"] 12 | 13 | [[bin]] 14 | name = "thunderscope-test" 15 | path = "src/bin/test.rs" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | env_logger = "0.11" 20 | bitflags = "2.6" 21 | bytemuck = "1.16" 22 | wide = "0.7" 23 | libc = "0.2" 24 | vmap = "0.6" 25 | 26 | raw-window-handle = { version = "0.5", optional = true } 27 | winit = { version = "0.29", optional = true, default-features = false, features = ["rwh_05", "x11"] } 28 | glutin = { version = "0.31", optional = true } 29 | glutin-winit = { version = "0.4.2", optional = true } 30 | glow = { version = "0.13", optional = true } 31 | # `docking` feature, enabled by default, lacks `RasterizerDensity` 32 | imgui = { git = "https://github.com/whitequark/imgui-rs", branch = "imgui-1.90.1", optional = true, default-features = false } 33 | imgui-winit-support = { git = "https://github.com/whitequark/imgui-rs", branch = "imgui-1.90.1", optional = true } 34 | imgui-glow-renderer = { git = "https://github.com/whitequark/imgui-rs", branch = "imgui-1.90.1", optional = true } 35 | 36 | # [patch."https://github.com/whitequark/imgui-rs"] 37 | # imgui = { path = "../imgui-rs/imgui" } 38 | 39 | [features] 40 | default = ["gui", "hardware"] 41 | hardware = [] 42 | gui = [ 43 | "dep:raw-window-handle", 44 | "dep:winit", 45 | "dep:glutin", 46 | "dep:glutin-winit", 47 | "dep:glow", 48 | "dep:imgui", 49 | "dep:imgui-winit-support", 50 | "dep:imgui-glow-renderer", 51 | ] 52 | raw-window-handle = ["dep:raw-window-handle"] 53 | 54 | [profile.dev] 55 | opt-level = 2 56 | -------------------------------------------------------------------------------- /doc/datamover_register.txt: -------------------------------------------------------------------------------- 1 | datamover register 2 | 3 | 32 bits, resides at 0x00000 on the "user" device path (this spits out axi-lite out of the xdma core) 4 | 5 | bit 0 = DatamoverEnabled 6 | bit 1 = FpgaAdcEnabled 7 | bit 2 = not used 8 | bit 3 = not used 9 | 10 | bit 4 = channel_mux[0] 11 | bit 5 = channel_mux[1] 12 | bit 6 = not used 13 | bit 7 = not used 14 | 15 | // channel_mux verilog 16 | always @(*) 17 | begin 18 | case(channel_mux) 19 | 2'b00: adc_data <= {twos_comp[63:0]}; //one channel mode 20 | 2'b01: adc_data <= {twos_comp[63:56],twos_comp[31:24],twos_comp[55:48],twos_comp[23:16],twos_comp[47:40],twos_comp[15:8],twos_comp[39:32],twos_comp[7:0]}; //two channel mode 21 | 2'b10: adc_data <= {twos_comp[63:56],twos_comp[47:40],twos_comp[31:24],twos_comp[15:8],twos_comp[55:48],twos_comp[39:32],twos_comp[23:16],twos_comp[7:0]}; //no three channel, only four 22 | 2'b11: adc_data <= {twos_comp[63:56],twos_comp[47:40],twos_comp[31:24],twos_comp[15:8],twos_comp[55:48],twos_comp[39:32],twos_comp[23:16],twos_comp[7:0]}; //four channel mode 23 | endcase 24 | end 25 | 26 | bit 8 - not used 27 | bit 9 - not used 28 | bit 10 - not used 29 | bit 11 - not used 30 | 31 | bit 12 - channel 1 termination 32 | bit 13 - channel 2 termination 33 | bit 14 - channel 3 termination 34 | bit 15 - channel 4 termination 35 | 36 | bit 16 - channel 1 attenuator 37 | bit 17 - channel 2 attenuator 38 | bit 18 - channel 3 attenuator 39 | bit 19 - channel 4 attenuator 40 | 41 | bit 20 - channel 1 coupling 42 | bit 21 - channel 2 coupling 43 | bit 22 - channel 3 coupling 44 | bit 23 - channel 4 coupling 45 | 46 | bit 24 - 3V3 EN (BoardEnabled) 47 | bit 25 - clock gen reset_n (PllEnabled) 48 | bit 26 - 5V EN (FrontEndEnabled) 49 | bit 27 - not used 50 | 51 | bit 28 - not used 52 | bit 29 - not used 53 | bit 30 - not used 54 | bit 31 - not used 55 | -------------------------------------------------------------------------------- /doc/fifo.txt: -------------------------------------------------------------------------------- 1 | i2c: 0xFF 2 | adc spi: 0xFD 3 | pga spi: 0xFB-channel(0-based) 4 | -------------------------------------------------------------------------------- /doc/plot_1ch.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | plt.figure() 6 | plt.plot(np.fromfile(sys.argv[1], dtype=np.int8)) 7 | plt.xlabel('Sample Index') 8 | plt.ylabel('ADC Code') 9 | plt.grid(True) 10 | plt.show() 11 | -------------------------------------------------------------------------------- /doc/transfer_counter_register.txt: -------------------------------------------------------------------------------- 1 | transfer counter register 2 | 3 | 32 bits, resides at 0x00008 on the "user" device path (this spits out axi-lite out of the xdma core and converts to GPIO) 4 | 5 | private void UpdateBufferHead() 6 | { 7 | // 1 page = 4k 8 | uint transfer_counter = Read32(BarRegister.DATAMOVER_TRANSFER_COUNTER); 9 | uint error_code = transfer_counter >> 30; 10 | if ((error_code & 2) > 0) 11 | throw new Exception("Thunderscope - datamover error"); 12 | 13 | if ((error_code & 1) > 0) 14 | throw new ThunderscopeFifoOverflowException("Thunderscope - FIFO overflow"); 15 | 16 | uint overflow_cycles = transfer_counter >> 16 & 0x3FFF; 17 | if (overflow_cycles > 0) 18 | throw new Exception("Thunderscope - pipeline overflow"); 19 | 20 | uint pages_moved = transfer_counter & 0xFFFF; 21 | ulong buffer_head = hardwareState.BufferHead & ~0xFFFFUL | pages_moved; 22 | if (buffer_head < hardwareState.BufferHead) 23 | buffer_head += 0x10000UL; 24 | 25 | hardwareState.BufferHead = buffer_head; 26 | 27 | ulong pages_available = hardwareState.BufferHead - hardwareState.BufferTail; 28 | if (pages_available >= hardwareState.RamSizePages) 29 | throw new ThunderscopeMemoryOutOfMemoryException("Thunderscope - memory full"); 30 | } 31 | 32 | bit 31 - datamover error 33 | - this is death, very odd error that seems to persist after power off, no idea why - haven't seen it in a long time, may have been fixed in latest gateware 34 | bit 30 - FIFO overflow 35 | - this is the fifo in front of the datamover, if this overflows, its a problem in DRAM (should never happen! - haven't seen since fixed in latest gateware) 36 | 37 | bits 29-16 - overflow cycles 38 | - how many cycles did it overflow for - only for debugging, this is covered by the sticky bit 30 FIFO error 39 | 40 | bits 15-0 - pages_moved - how many 4kB transfers the datamover has written into the ddr3 41 | - if this value - it's value at the last read is over the ddr3 size, that's a "scope out of memory error" 42 | - essentialy a FIFO overflow for the ddr3, which we have to set up software side as a circular buffer 43 | -------------------------------------------------------------------------------- /src/bin/gui/DejaVuSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitequark/thunderscope-rs/4ab64ead7ea3174f4a5ede1d4188768425a231c9/src/bin/gui/DejaVuSans-Bold.ttf -------------------------------------------------------------------------------- /src/bin/gui/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitequark/thunderscope-rs/4ab64ead7ea3174f4a5ede1d4188768425a231c9/src/bin/gui/DejaVuSans.ttf -------------------------------------------------------------------------------- /src/bin/gui/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitequark/thunderscope-rs/4ab64ead7ea3174f4a5ede1d4188768425a231c9/src/bin/gui/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /src/bin/gui/DejaVuSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitequark/thunderscope-rs/4ab64ead7ea3174f4a5ede1d4188768425a231c9/src/bin/gui/DejaVuSerif.ttf -------------------------------------------------------------------------------- /src/bin/gui/capture.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | use std::sync::mpsc::{Receiver, Sender, TryRecvError}; 3 | use std::io::Read; 4 | 5 | use thunderscope::{Result, DeviceCalibration, DeviceConfiguration, DeviceParameters}; 6 | use thunderscope::{RingBuffer, RingCursor}; 7 | use thunderscope::{EdgeFilter, Trigger}; 8 | 9 | const TRIGGER_HYSTERESIS: u8 = 2; 10 | 11 | const SAMPLE_COUNT: usize = 1000; 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct TriggerParameters { 15 | channel: usize, 16 | level: f32, // in volts 17 | edge: EdgeFilter, 18 | } 19 | 20 | #[derive(Debug, Clone, Copy)] 21 | pub enum OperationMode { 22 | Idle, 23 | FreeRunning, 24 | SingleTrigger(TriggerParameters), 25 | RepeatTrigger(TriggerParameters), 26 | } 27 | 28 | #[derive(Debug, Clone, Copy)] 29 | pub struct Parameters { 30 | device: DeviceParameters, 31 | mode: OperationMode, 32 | } 33 | 34 | impl Default for Parameters { 35 | fn default() -> Self { 36 | Self { 37 | device: DeviceParameters::derive( 38 | &DeviceCalibration::default(), 39 | &DeviceConfiguration::default() 40 | ), 41 | mode: OperationMode::Idle 42 | } 43 | } 44 | } 45 | 46 | impl Parameters { 47 | pub fn demo() -> Self { 48 | Self { 49 | device: DeviceParameters::derive( 50 | &DeviceCalibration::default(), 51 | &DeviceConfiguration { channels: [Some(Default::default()), None, None, None] } 52 | ), 53 | mode: OperationMode::RepeatTrigger(TriggerParameters { 54 | channel: 0, 55 | level: 1.0, 56 | edge: EdgeFilter::Rising, 57 | }) 58 | } 59 | } 60 | } 61 | 62 | #[derive(Debug)] 63 | pub struct Waveform { 64 | params: Parameters, 65 | buffer: RingBuffer, 66 | capture: Option<(RingCursor, usize)> 67 | } 68 | 69 | impl Waveform { 70 | pub fn new(size: usize) -> Result { 71 | Ok(Waveform { 72 | params: Parameters::default(), 73 | buffer: RingBuffer::new(size)?, 74 | capture: None 75 | }) 76 | } 77 | 78 | pub fn capture_data(&self) -> Option<&[i8]> { 79 | self.capture.map(|(cursor, length)| self.buffer.read(cursor, length)) 80 | } 81 | } 82 | 83 | struct SineGenerator { 84 | phase: f32, 85 | step: f32, 86 | } 87 | 88 | impl SineGenerator { 89 | fn new(frequency: f32) -> SineGenerator { 90 | SineGenerator { 91 | phase: 0.0, 92 | step: 1e9 * 2.0 * PI / frequency, 93 | } 94 | } 95 | } 96 | 97 | impl std::io::Read for SineGenerator { 98 | fn read(&mut self, data: &mut [u8]) -> std::io::Result { 99 | for sample in data.iter_mut() { 100 | *sample = (self.phase.sin() * 100.0) as i8 as u8; 101 | self.phase = (self.phase + self.step) % (2.0 * PI); 102 | } 103 | // simulate 1 GS/s capture rate 104 | std::thread::sleep(std::time::Duration::from_nanos(1) * (data.len() as u32)); 105 | Ok(data.len()) 106 | } 107 | } 108 | 109 | #[derive(Debug)] 110 | pub enum DataSource { 111 | Hardware(thunderscope::Device), 112 | SineGenerator { frequency: f32 }, // in Hz 113 | } 114 | 115 | pub struct Sampler { 116 | params_recv: Receiver, 117 | // Sampler does not allocate the waveform buffers. It relies on a pair of channels acting like 118 | // a bucket brigade: any received `Waveform` objects are filled in with captures and sent for 119 | // further processing. Eventually the `Waveform` object comes back from the processing engine, 120 | // and the closed cycle continues. 121 | waveform_recv: Receiver, 122 | waveform_send: Sender, 123 | } 124 | 125 | impl Sampler { 126 | pub fn new( 127 | params_recv: Receiver, 128 | waveform_recv: Receiver, 129 | waveform_send: Sender 130 | ) -> Sampler { 131 | Sampler { params_recv, waveform_recv, waveform_send } 132 | } 133 | 134 | pub fn run(mut self, source: DataSource) -> std::thread::JoinHandle> { 135 | std::thread::spawn(move || { 136 | match source { 137 | DataSource::SineGenerator { frequency } => { 138 | let sine_generator = SineGenerator::new(frequency); 139 | self.trigger_and_capture(sine_generator, 140 | |_params| Ok(()))? 141 | } 142 | DataSource::Hardware(instrument) => { 143 | instrument.startup()?; 144 | self.trigger_and_capture(instrument.stream_data(), 145 | |params| instrument.configure(params))?; 146 | instrument.shutdown()?; 147 | } 148 | } 149 | Ok(()) 150 | }) 151 | } 152 | 153 | fn trigger_and_capture(&mut self, mut reader: impl Read, mut reconfigure: F) -> Result<()> 154 | where F: FnMut(&DeviceParameters) -> Result<()> { 155 | let mut wfm_active = self.waveform_recv.recv().expect("failed to receive waveform"); 156 | let mut wfm_standby = None; 157 | let mut params = Parameters::default(); 158 | let mut trigger = None; 159 | loop { 160 | // switch capture parameters, if requested 161 | match self.params_recv.try_recv() { 162 | Ok(new_params) => { 163 | log::info!("sampler: switching parameters to {:#?}", new_params); 164 | params = new_params; 165 | trigger = match new_params.mode { 166 | OperationMode::Idle | 167 | OperationMode::FreeRunning => None, 168 | OperationMode::SingleTrigger(trigger) | 169 | OperationMode::RepeatTrigger(trigger) => 170 | Some((Trigger::new( 171 | new_params.device.volts_to_code(trigger.channel, trigger.level), 172 | TRIGGER_HYSTERESIS 173 | ), trigger.edge)), 174 | }; 175 | reconfigure(&new_params.device)?; 176 | } 177 | Err(_) => {} 178 | } 179 | // try to acquire a standby waveform buffer 180 | // at least one buffer must be available at all times to read samples into, so until 181 | // a standby buffer is available, the active buffer will not be submitted 182 | match self.waveform_recv.try_recv() { 183 | Ok(waveform) => wfm_standby = Some(waveform), 184 | Err(TryRecvError::Empty) => (), 185 | Err(TryRecvError::Disconnected) => { 186 | log::debug!("sampler: done"); 187 | break 188 | } 189 | } 190 | // set up capturing in active buffer 191 | wfm_active.params = params; 192 | wfm_active.capture = None; 193 | let mut cursor = wfm_active.buffer.cursor(); 194 | let mut available = 0; 195 | // refill buffer 196 | let refill_by = wfm_active.buffer.len() - available; 197 | available += wfm_active.buffer.append(refill_by, |slice| reader.read(slice))?; 198 | log::debug!("sampler: refilled buffer by {} bytes ({} available)", 199 | refill_by, available); 200 | if let OperationMode::FreeRunning = params.mode { 201 | // accept capture as-is 202 | wfm_active.capture = Some((cursor, SAMPLE_COUNT)); 203 | log::debug!("sampler: captured waveform free running ({}+{})", 204 | cursor.into_inner(), SAMPLE_COUNT); 205 | } else if let Some((mut trigger, edge_filter)) = trigger { 206 | // find trigger point 207 | let data = wfm_active.buffer.read(cursor, available); 208 | let (processed, edge) = trigger.find(data, edge_filter); 209 | cursor += processed; 210 | available -= processed; 211 | log::debug!("sampler: trigger consumed {} bytes ({} available)", 212 | processed, available); 213 | if let Some(edge) = edge { 214 | // check if we need to capture more 215 | if available < SAMPLE_COUNT { 216 | let refill_by = SAMPLE_COUNT - available; 217 | available += wfm_active.buffer.append(refill_by, 218 | |slice| reader.read(slice))?; 219 | debug_assert!(available >= SAMPLE_COUNT); 220 | log::debug!("sampler: refilled buffer by {} bytes ({} available)", 221 | refill_by, available); 222 | } 223 | // accept capture at trigger point 224 | wfm_active.capture = Some((cursor, SAMPLE_COUNT)); 225 | log::debug!("sampler: captured waveform for {:?} edge ({}+{})", 226 | edge, cursor.into_inner(), SAMPLE_COUNT); 227 | // reset trigger to resynchronize its state 228 | trigger.reset(); 229 | } 230 | } 231 | // if there is a capture, try to submit it for processing 232 | if wfm_active.capture.is_some() { 233 | if let Some(next_waveform) = wfm_standby.take() { 234 | if let OperationMode::SingleTrigger(_) = params.mode { 235 | // if only a single capture was requested, stop capturing 236 | params.mode = OperationMode::Idle; 237 | trigger = None; 238 | } 239 | self.waveform_send.send(wfm_active).expect("failed to send waveform"); 240 | log::debug!("sampler: submitted waveform"); 241 | wfm_active = next_waveform; 242 | } else { 243 | wfm_active.capture = None; 244 | log::debug!("sampler: discarded waveform"); 245 | } 246 | } 247 | } 248 | Ok(()) 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/bin/gui/main.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::num::NonZeroU32; 3 | use std::sync::atomic::{AtomicI8, Ordering}; 4 | use std::time::{Duration, Instant}; 5 | use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; 6 | 7 | use raw_window_handle::HasRawWindowHandle; 8 | use winit::dpi::{LogicalSize, PhysicalSize}; 9 | use winit::event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}; 10 | use winit::event::{Event, StartCause, WindowEvent}; 11 | use winit::window::{Window, WindowBuilder}; 12 | 13 | use glutin_winit::DisplayBuilder; 14 | use glutin::config::ConfigTemplateBuilder; 15 | use glutin::context::{Version, ContextApi, ContextAttributesBuilder}; 16 | use glutin::context::{NotCurrentGlContext, PossiblyCurrentContext}; 17 | use glutin::surface::{GlSurface, Surface, SurfaceAttributesBuilder, WindowSurface}; 18 | use glutin::display::{GetGlDisplay, GlDisplay}; 19 | 20 | use glow::{Context as GlowContext, HasContext}; 21 | 22 | mod capture; 23 | 24 | use thunderscope::EdgeFilter; 25 | use capture::Waveform; 26 | 27 | const TRIGGER_EDGE: EdgeFilter = EdgeFilter::Rising; 28 | static TRIGGER_LEVEL: AtomicI8 = AtomicI8::new(50); 29 | const SAMPLE_COUNT: usize = 128_000; 30 | const RENDER_LINES: bool = true; 31 | 32 | struct WaveformRenderer { 33 | program: ::Program, 34 | vertex_array: ::VertexArray, 35 | sample_array: ::Buffer, 36 | waveform_recv: Receiver, 37 | waveform_send: Sender, 38 | current: Option, 39 | } 40 | 41 | impl WaveformRenderer { 42 | pub fn new( 43 | gl: &glow::Context, 44 | waveform_recv: Receiver, 45 | waveform_send: Sender 46 | ) -> Self { 47 | let shaders = [ 48 | (glow::VERTEX_SHADER, include_str!("wave_vert.glsl")), 49 | (glow::FRAGMENT_SHADER, include_str!("wave_frag.glsl")), 50 | ]; 51 | 52 | unsafe { 53 | let program = gl.create_program().expect("failed to create program"); 54 | let mut native_shaders = Vec::new(); 55 | for (kind, source) in shaders { 56 | let shader = gl.create_shader(kind).expect("failed to create shader"); 57 | gl.shader_source(shader, source); 58 | gl.compile_shader(shader); 59 | if !gl.get_shader_compile_status(shader) { 60 | panic!("could not compile shader: {}", gl.get_shader_info_log(shader)); 61 | } 62 | gl.attach_shader(program, shader); 63 | native_shaders.push(shader); 64 | } 65 | gl.link_program(program); 66 | if !gl.get_program_link_status(program) { 67 | panic!("{}", gl.get_program_info_log(program)); 68 | } 69 | for shader in native_shaders { 70 | gl.detach_shader(program, shader); 71 | gl.delete_shader(shader); 72 | } 73 | 74 | let vertex_array = gl.create_vertex_array().expect("failed to create vertex array"); 75 | let data_array = gl.create_buffer().expect("failed to create buffer"); 76 | Self { 77 | program, 78 | vertex_array, 79 | sample_array: data_array, 80 | waveform_recv, 81 | waveform_send, 82 | current: None 83 | } 84 | } 85 | } 86 | 87 | pub fn poll(&mut self) -> bool { 88 | match self.waveform_recv.try_recv() { 89 | err @ Err(TryRecvError::Disconnected) => 90 | panic!("renderer: failed to receive waveform: {:?}", err), 91 | Err(TryRecvError::Empty) => false, 92 | Ok(new_waveform) => { 93 | log::debug!("renderer: acquired waveform"); 94 | if let Some(old_waveform) = self.current.replace(new_waveform) { 95 | self.waveform_send.send(old_waveform).expect("failed to return waveform"); 96 | } 97 | true 98 | } 99 | } 100 | } 101 | 102 | pub fn resize(&mut self, gl: &glow::Context, width: u32, height: u32) { 103 | unsafe { 104 | gl.viewport(0, 0, width as i32, height as i32); 105 | gl.use_program(Some(self.program)); 106 | gl.uniform_2_f32(gl.get_uniform_location(self.program, "resolution").as_ref(), 107 | width as f32, height as f32); 108 | } 109 | } 110 | 111 | pub fn render(&mut self, gl: &glow::Context) { 112 | unsafe { 113 | gl.clear_color(0.1, 0.0, 0.1, 1.0); 114 | gl.clear(glow::COLOR_BUFFER_BIT); 115 | 116 | let Some(samples) = self.current.as_ref() 117 | .and_then(|waveform| waveform.capture_data()) 118 | .map(|data| bytemuck::cast_slice(data)) else { return }; 119 | 120 | let draw_lines_loc = gl.get_uniform_location(self.program, "draw_lines"); 121 | let channel_color_loc = gl.get_uniform_location(self.program, "channel_color"); 122 | let sample_count_loc = gl.get_uniform_location(self.program, "sample_count"); 123 | let sample_value0_loc = gl.get_attrib_location(self.program, "sample_value0") 124 | .expect("could not retrieve attribute location"); 125 | let sample_value1_loc = gl.get_attrib_location(self.program, "sample_value1") 126 | .expect("could not retrieve attribute location"); 127 | 128 | gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); 129 | gl.enable(glow::BLEND); 130 | 131 | gl.use_program(Some(self.program)); 132 | gl.uniform_1_u32(draw_lines_loc.as_ref(), RENDER_LINES as u32); 133 | gl.uniform_3_f32(channel_color_loc.as_ref(), 1.0, 1.0, 0.0); 134 | gl.uniform_1_i32(sample_count_loc.as_ref(), samples.len() as i32); 135 | gl.bind_vertex_array(Some(self.vertex_array)); 136 | gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.sample_array)); 137 | gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, samples, glow::STREAM_DRAW); 138 | gl.enable_vertex_attrib_array(sample_value0_loc); 139 | gl.vertex_attrib_pointer_f32(sample_value0_loc, 1, glow::BYTE, true, 1, 0); 140 | gl.vertex_attrib_divisor(sample_value0_loc, 1); 141 | gl.enable_vertex_attrib_array(sample_value1_loc); 142 | gl.vertex_attrib_pointer_f32(sample_value1_loc, 1, glow::BYTE, true, 1, 1); 143 | gl.vertex_attrib_divisor(sample_value1_loc, 1); 144 | gl.draw_arrays_instanced(glow::TRIANGLE_STRIP, 0, 4, samples.len() as i32); 145 | gl.disable_vertex_attrib_array(sample_value0_loc); 146 | gl.disable_vertex_attrib_array(sample_value1_loc); 147 | gl.bind_buffer(glow::ARRAY_BUFFER, None); 148 | 149 | gl.disable(glow::BLEND); 150 | } 151 | } 152 | 153 | pub fn destroy(&mut self, gl: &glow::Context) { 154 | unsafe { 155 | gl.delete_program(self.program); 156 | gl.delete_vertex_array(self.vertex_array); 157 | } 158 | } 159 | } 160 | 161 | mod ui_defs { 162 | pub const FONT_DEFAULT_DATA: &[u8] = include_bytes!("DejaVuSansMono.ttf"); 163 | pub const FONT_DEFAULT_SIZE: f32 = 18.0; 164 | 165 | pub const FONT_CONTROLS_DATA: &[u8] = include_bytes!("DejaVuSans-Bold.ttf"); 166 | pub const FONT_CONTROLS_SIZE: f32 = 22.0; 167 | 168 | pub const FONT_LOGO_DATA: &[u8] = include_bytes!("DejaVuSerif.ttf"); 169 | pub const FONT_LOGO_SIZE: f32 = 30.0; 170 | 171 | pub const LOGO_TEXT: &str = "ThunderScope"; 172 | pub const LOGO_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; 173 | 174 | pub const CONTROLS_V_MARGIN: f32 = 12.0; 175 | pub const CONTROLS_H_SPACING: f32 = 14.0; 176 | pub const CONTROLS_TRIGGER_WIDTH: f32 = 120.0; 177 | pub const CONTROLS_RUN_STOP_WIDTH: f32 = 72.0; 178 | 179 | pub const CHANNEL_V_PADDING: f32 = 10.0; 180 | 181 | pub const MARKER_FILL_COLOR: [f32; 4] = [1.0, 0.5, 0.0, 1.0]; 182 | pub const MARKER_LINE_COLOR: [f32; 4] = [0.8, 0.4, 0.0, 1.0]; 183 | pub const MARKER_TEXT_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; 184 | 185 | pub const DEBUG_COLOR: [f32; 4] = [0.8, 0.0, 0.8, 1.0]; 186 | } 187 | 188 | #[derive(Debug, Clone, Copy, Default)] 189 | struct ChannelLayoutMetrics { 190 | inner_height: f32, // in logical px 191 | outer_height: f32, // in logical px; inner_height + padding 192 | zero_offset: f32, // in volts 193 | full_scale: f32, // in volts 194 | } 195 | 196 | impl ChannelLayoutMetrics { 197 | fn volts_to_pixels(&self, volts: f32) -> f32 { 198 | // the nominal full scale is 2 V: -1 V to 1 V 199 | let normalized_volts = (-volts - self.zero_offset) / self.full_scale * 2.0; 200 | (normalized_volts + 1.0) * self.outer_height / 2.0 201 | } 202 | 203 | fn pixels_to_volts(&self, pixels: f32) -> f32 { 204 | let normalized_volts = pixels * 2.0 / self.outer_height - 1.0; 205 | -(normalized_volts / 2.0 * self.full_scale + self.zero_offset) 206 | } 207 | } 208 | 209 | #[derive(Debug, Clone, Copy)] 210 | struct InterfaceLayoutMetrics { 211 | overall_size: [f32; 2], // in logical px 212 | logo_width: f32, // in logical px 213 | control_bar_height: f32, // in logical px 214 | horz_scale_height: f32, // in logical px 215 | vert_scale_width: f32, // in logical px 216 | channels: [ChannelLayoutMetrics; 4], 217 | } 218 | 219 | impl InterfaceLayoutMetrics { 220 | fn new(ui: &imgui::Ui, logo_font: imgui::FontId, 221 | channel_count: usize) -> InterfaceLayoutMetrics { 222 | let [overall_width, overall_height] = ui.window_size(); 223 | let [logo_width, logo_height] = { 224 | let _t = ui.push_font(logo_font); 225 | ui.calc_text_size(ui_defs::LOGO_TEXT) 226 | }; 227 | let control_bar_height = logo_height + ui_defs::CONTROLS_V_MARGIN * 2.0; 228 | let horz_scale_height = 32.0; // FIXME 229 | let vert_scale_width = 100.0; // FIXME 230 | let channel_area_height = overall_height - control_bar_height - horz_scale_height - 231 | ui_defs::CHANNEL_V_PADDING; 232 | let channel_outer_height = channel_area_height / channel_count as f32; 233 | let mut channels = [ChannelLayoutMetrics::default(); 4]; 234 | for index in 0..channel_count { 235 | channels[index].outer_height = channel_outer_height; 236 | channels[index].inner_height = channel_outer_height - ui_defs::CHANNEL_V_PADDING * 2.0; 237 | channels[index].zero_offset = 0.0; // FIXME 238 | channels[index].full_scale = 10.0; // FIXME 239 | } 240 | InterfaceLayoutMetrics { 241 | overall_size: [overall_width, overall_height], 242 | logo_width, 243 | control_bar_height, 244 | horz_scale_height, 245 | vert_scale_width, 246 | channels, 247 | } 248 | } 249 | 250 | fn volts_to_pixels(&self, index: usize, volts: f32) -> f32 { 251 | let mut offset = self.control_bar_height + self.horz_scale_height; 252 | for index_above in 0..index { 253 | offset += self.channels[index_above].outer_height; 254 | } 255 | offset += self.channels[index].volts_to_pixels(volts); 256 | offset 257 | } 258 | 259 | fn pixels_to_volts(&self, index: usize, pixels: f32) -> f32 { 260 | let mut offset = pixels; 261 | offset -= self.control_bar_height + self.horz_scale_height; 262 | for index_above in 0..index { 263 | offset -= self.channels[index_above].outer_height; 264 | } 265 | dbg!(offset); 266 | self.channels[index].pixels_to_volts(offset) 267 | } 268 | 269 | fn channel_rect(&self, index: usize) -> ([f32; 2], [f32; 2]) { 270 | let mut vert_offset = self.control_bar_height + self.horz_scale_height; 271 | for index_above in 0..index { 272 | vert_offset += self.channels[index_above].outer_height; 273 | } 274 | let [overall_width, _] = self.overall_size; 275 | let channel_height = self.channels[index].outer_height; 276 | ([self.vert_scale_width, vert_offset], 277 | [overall_width - ui_defs::CONTROLS_H_SPACING, vert_offset + channel_height]) 278 | } 279 | 280 | fn trace_origin(&self, index: usize) -> [f32; 2] { 281 | let mut vert_offset = 0.0; 282 | for index_above in 0..index { 283 | vert_offset += self.channels[index_above].outer_height; 284 | } 285 | vert_offset += self.channels[index].outer_height / 2.0; 286 | [self.vert_scale_width, vert_offset] 287 | } 288 | } 289 | 290 | #[derive(Debug, PartialEq, Eq, Default)] 291 | struct InterfaceState { 292 | trigger_clicked: bool, 293 | run_stop_clicked: bool, 294 | } 295 | 296 | #[derive(Debug)] 297 | struct InterfaceRenderer { 298 | controls_font: imgui::FontId, 299 | logo_font: imgui::FontId, 300 | 301 | dragging_h_marker: Cell, 302 | h_marker_pos: Cell, 303 | 304 | dragging_v_marker: Cell, 305 | v_marker_pos: Cell, 306 | } 307 | 308 | impl InterfaceRenderer { 309 | fn new(context: &mut imgui::Context, font_config: imgui::FontConfig) -> Self { 310 | use imgui::*; 311 | 312 | let ttf_font = |data, size_pixels| [ 313 | FontSource::TtfData { data, size_pixels, config: Some(font_config.clone()) }, 314 | FontSource::TtfData { data, size_pixels, 315 | config: Some(FontConfig { 316 | glyph_ranges: FontGlyphRanges::from_slice(&[ 317 | '↑' as u32, '↑' as u32, 318 | '↓' as u32, '↓' as u32, 319 | '⇅' as u32, '⇅' as u32, 320 | 0 321 | ]), 322 | ..font_config.clone() 323 | }), 324 | }, 325 | ]; 326 | let _default_font = context.fonts().add_font( 327 | &ttf_font(ui_defs::FONT_DEFAULT_DATA, ui_defs::FONT_DEFAULT_SIZE)); 328 | let controls_font = context.fonts().add_font( 329 | &ttf_font(ui_defs::FONT_CONTROLS_DATA, ui_defs::FONT_CONTROLS_SIZE)); 330 | let logo_font = context.fonts().add_font( 331 | &ttf_font(ui_defs::FONT_LOGO_DATA, ui_defs::FONT_LOGO_SIZE)); 332 | Self { 333 | controls_font, 334 | logo_font, 335 | dragging_h_marker: Cell::new(false), 336 | h_marker_pos: Cell::new(100.0), 337 | dragging_v_marker: Cell::new(false), 338 | v_marker_pos: Cell::new(3.3), 339 | } 340 | } 341 | 342 | fn render_logo(&self, ui: &imgui::Ui) -> [f32; 2] { 343 | let _t = ui.push_font(self.logo_font); 344 | let [w, _] = ui.cursor_pos(); 345 | let [_, mut h] = ui.clone_style().window_padding; 346 | h -= ui.clone_style().frame_padding[1]; 347 | ui.set_cursor_pos([w, h]); 348 | let logo_color = unsafe { 349 | let (mut r, mut g, mut b) = (0.0f32, 0.0f32, 0.0f32); 350 | let h = ui.frame_count() as f32 / 1000.0; 351 | imgui::sys::igColorConvertHSVtoRGB(h, 1.0, 1.0, 352 | &mut r as *mut f32, 353 | &mut g as *mut f32, 354 | &mut b as *mut f32); 355 | [r, g, b, 1.0] 356 | }; 357 | ui.text_colored(logo_color, ui_defs::LOGO_TEXT); 358 | ui.calc_text_size(ui_defs::LOGO_TEXT) 359 | } 360 | 361 | fn render_minimap(&self, ui: &imgui::Ui, width: f32, height: f32) { 362 | let minimap = ui.child_window("##minimap") 363 | .size([width, height]) 364 | .movable(false) 365 | .bring_to_front_on_focus(false); 366 | minimap.build(|| { 367 | let draw_list = ui.get_window_draw_list(); 368 | let ([x, y], [w, h]) = (ui.window_pos(), ui.window_size()); 369 | draw_list 370 | .add_rect([x, y], [x + w, y + h], ui_defs::DEBUG_COLOR) 371 | .build(); 372 | }); 373 | } 374 | 375 | fn with_controls_style R, R>(&self, ui: &imgui::Ui, f: F) -> R { 376 | use imgui::*; 377 | 378 | let _t = ui.push_font(self.controls_font); 379 | let _t = ui.push_style_color(StyleColor::Button, [0.00, 0.00, 0.00, 1.00]); 380 | let _t = ui.push_style_color(StyleColor::ButtonHovered, [0.20, 0.20, 0.20, 1.00]); 381 | let _t = ui.push_style_color(StyleColor::ButtonActive, [0.40, 0.40, 0.40, 1.00]); 382 | let _t = ui.push_style_color(StyleColor::Border, [0.25, 0.25, 0.25, 1.00]); 383 | let _t = ui.push_style_var(StyleVar::FrameRounding(5.0)); 384 | let _t = ui.push_style_var(StyleVar::FrameBorderSize(2.0)); 385 | f() 386 | } 387 | 388 | fn render_trigger_config(&self, ui: &imgui::Ui, width: f32, height: f32) -> bool { 389 | use imgui::*; 390 | 391 | self.with_controls_style(ui, || { 392 | let _t = ui.push_style_color(StyleColor::Text, [1.0, 1.0, 1.0, 1.0]); 393 | ui.button_with_size("T: CH1↑", [width, height]) 394 | }) 395 | } 396 | 397 | fn render_run_stop(&self, ui: &imgui::Ui, width: f32, height: f32) -> bool { 398 | use imgui::*; 399 | 400 | self.with_controls_style(ui, || { 401 | //let _t = ui.push_style_color(StyleColor::Text, [0.0, 1.0, 0.0, 1.0]); 402 | let _t = ui.push_style_color(StyleColor::Text, [1.0, 0.0, 0.0, 1.0]); 403 | ui.button_with_size("STOP", [width, height]) 404 | }) 405 | } 406 | 407 | /* 408 | fn render_trigger_offset_marker(&self, ui: &imgui::Ui) { 409 | let draw_list = ui.get_window_draw_list(); 410 | 411 | let text = "-50ps"; 412 | 413 | let [x, y] = [self.h_marker_pos.get(), 90.0]; 414 | let [wt, ht] = ui.calc_text_size(text); 415 | let [wp, hp] = [wt+5.0, ht+5.0]; 416 | let mut marker_outline = vec![ 417 | [x, y], 418 | [x-wp/2.0, y-5.0], 419 | [x-wp/2.0, y-5.0-hp], 420 | [x+wp/2.0, y-5.0-hp], 421 | [x+wp/2.0, y-5.0], 422 | [x, y], 423 | ]; 424 | let color = ui_style::MARKER_FILL_COLOR; 425 | if self.dragging_h_marker.get() { 426 | if ui.is_mouse_down(imgui::MouseButton::Left) { 427 | let [x, _] = ui.io().mouse_pos; 428 | self.h_marker_pos.set(x); 429 | } else { 430 | self.dragging_h_marker.set(false); 431 | } 432 | } else if ui.is_mouse_hovering_rect([x-wp/2.0,y-5.0-hp], [x+wp/2.0,y]) { 433 | if ui.is_mouse_down(imgui::MouseButton::Left) { 434 | self.dragging_h_marker.set(true) 435 | } 436 | } 437 | draw_list.add_polyline(marker_outline.clone(), color) 438 | .filled(true).build(); 439 | marker_outline.push([x, y+400.0]); 440 | draw_list.add_polyline(marker_outline, ui_style::MARKER_LINE_COLOR) 441 | .thickness(1.0).build(); 442 | draw_list.add_text([x-wt/2.0, y-2.5-ht-5.0], ui_style::MARKER_TEXT_COLOR, text); 443 | } 444 | */ 445 | 446 | fn render_trigger_level_marker(&self, ui: &imgui::Ui, metrics: &InterfaceLayoutMetrics) { 447 | let draw_list = ui.get_window_draw_list(); 448 | 449 | let channel_index = 0; 450 | 451 | let ([l, t], [r, b]) = metrics.channel_rect(channel_index); 452 | draw_list.add_rect([l, t], [r, b], ui_defs::DEBUG_COLOR).build(); 453 | 454 | let volts = self.v_marker_pos.get(); 455 | let text = format!("{:+.2}V", volts); 456 | 457 | let [x, y] = [metrics.vert_scale_width-8.0, metrics.volts_to_pixels(channel_index, volts)]; 458 | let [wt, ht] = ui.calc_text_size(text.as_str()); 459 | let [wp, hp] = [wt+5.0, ht+5.0]; 460 | let mut marker_outline = vec![ 461 | [x, y], 462 | [x-5.0, y-hp/2.0], 463 | [x-5.0-wp, y-hp/2.0], 464 | [x-5.0-wp, y+hp/2.0], 465 | [x-5.0, y+hp/2.0], 466 | [x, y], 467 | ]; 468 | let color = ui_defs::MARKER_FILL_COLOR; 469 | if !self.dragging_h_marker.get() { 470 | if self.dragging_v_marker.get() { 471 | if ui.is_mouse_down(imgui::MouseButton::Left) { 472 | let [_, y] = ui.io().mouse_pos; 473 | self.v_marker_pos.set(metrics.pixels_to_volts(channel_index, y.max(t).min(b))); 474 | } else { 475 | self.dragging_v_marker.set(false); 476 | } 477 | } else if ui.is_mouse_hovering_rect([x-5.0-wp, y-hp/2.0], [x, y+hp/2.0]) { 478 | if ui.is_mouse_down(imgui::MouseButton::Left) { 479 | self.dragging_v_marker.set(true) 480 | } 481 | } 482 | } 483 | draw_list.add_polyline(marker_outline.clone(), color) 484 | .filled(true).build(); 485 | marker_outline.push([r, y]); 486 | draw_list.add_polyline(marker_outline, ui_defs::MARKER_LINE_COLOR) 487 | .thickness(1.0).build(); 488 | draw_list.add_text([x-wt-7.5, y-ht/2.0], ui_defs::MARKER_TEXT_COLOR, text.as_str()); 489 | } 490 | 491 | fn render_controls(&self, ui: &imgui::Ui, state: &mut InterfaceState) { 492 | use imgui::*; 493 | 494 | let _t = ui.push_style_var(StyleVar::WindowPadding( 495 | [ui_defs::CONTROLS_H_SPACING, ui_defs::CONTROLS_V_MARGIN])); 496 | let _t = ui.window("##main") 497 | .size(ui.io().display_size, Condition::Always) 498 | .position([0.0, 0.0], Condition::Always) 499 | .no_decoration() 500 | .draw_background(false) 501 | .bring_to_front_on_focus(false) 502 | .begin(); 503 | let metrics = InterfaceLayoutMetrics::new(ui, self.logo_font, 2); 504 | ui.group(|| { 505 | let _t = ui.push_style_var(StyleVar::ItemSpacing( 506 | [ui_defs::CONTROLS_H_SPACING, 0.0])); 507 | let control_height = metrics.control_bar_height - ui_defs::CONTROLS_V_MARGIN * 2.0; 508 | state.run_stop_clicked = self.render_run_stop(ui, 509 | ui_defs::CONTROLS_RUN_STOP_WIDTH, control_height); 510 | ui.same_line(); 511 | state.trigger_clicked = self.render_trigger_config(ui, 512 | ui_defs::CONTROLS_TRIGGER_WIDTH, control_height); 513 | ui.same_line(); 514 | let logo_width = metrics.logo_width + ui_defs::CONTROLS_H_SPACING; 515 | self.render_minimap(ui, -logo_width, control_height); 516 | ui.same_line(); 517 | self.render_logo(ui); 518 | 519 | // self.render_trigger_offset_marker(ui); 520 | self.render_trigger_level_marker(ui, &metrics); 521 | }); 522 | } 523 | 524 | fn render_trigger_config_popup(&self, ui: &imgui::Ui) { 525 | ui.popup("Trigger", || { 526 | use thunderscope::EdgeFilter; 527 | 528 | for (channel, label) in ["CH1", "CH2", "CH3", "CH4"].iter().enumerate() { 529 | if ui.menu_item_config(label).selected(channel == 0).build() { 530 | // FIXME 531 | } 532 | } 533 | 534 | ui.separator(); 535 | for (edge_filter, label) in [ 536 | (EdgeFilter::Rising, "↑ Rising"), 537 | (EdgeFilter::Falling, "↓ Falling"), 538 | (EdgeFilter::Both, "⇅ Both"), 539 | ] { 540 | if ui.menu_item_config(label).selected(TRIGGER_EDGE == edge_filter).build() { 541 | // FIXME 542 | } 543 | } 544 | 545 | ui.separator(); 546 | ui.align_text_to_frame_padding(); 547 | ui.text("Level"); 548 | ui.same_line(); 549 | ui.set_next_item_width(60.0); 550 | ui.input_float("V##Level", &mut (TRIGGER_LEVEL.load(Ordering::SeqCst) as f32 * 5.0 / 128.0)) 551 | .build(); 552 | }); 553 | } 554 | 555 | fn render(&mut self, ui: &imgui::Ui) { 556 | use imgui::*; 557 | 558 | let mut state = InterfaceState::default(); 559 | self.render_controls(ui, &mut state); 560 | 561 | if state != InterfaceState::default() { 562 | log::info!("{:?}", state) 563 | } 564 | if state.trigger_clicked { 565 | ui.open_popup("Trigger"); 566 | } 567 | self.render_trigger_config_popup(ui); 568 | 569 | if ui.is_key_pressed(Key::Escape) { 570 | std::process::exit(0); 571 | } 572 | 573 | // ui.show_demo_window(&mut true); 574 | } 575 | } 576 | 577 | struct Application { 578 | gl_context: PossiblyCurrentContext, 579 | gl_surface: Surface, 580 | gl_library: GlowContext, 581 | wfm_renderer: WaveformRenderer, 582 | imgui_context: imgui::Context, 583 | imgui_platform: imgui_winit_support::WinitPlatform, 584 | imgui_texture_map: imgui_glow_renderer::SimpleTextureMap, 585 | imgui_renderer: imgui_glow_renderer::Renderer, 586 | ui_state: InterfaceRenderer, 587 | window: Window, 588 | } 589 | 590 | impl Application { 591 | fn process_event(&mut self, event: Event, window_target: &EventLoopWindowTarget) { 592 | match event { 593 | Event::NewEvents(StartCause::ResumeTimeReached { requested_resume, .. }) => { 594 | // handle waveform updates 595 | if self.wfm_renderer.poll() { 596 | self.window.request_redraw(); 597 | } 598 | // handle UI updates 599 | self.imgui_context.io_mut().update_delta_time( 600 | Instant::now().duration_since(requested_resume)); 601 | self.imgui_platform.prepare_frame(self.imgui_context.io_mut(), &self.window) 602 | .expect("failed to prepare UI frame"); 603 | // The `winit` documentation recommends `Poll`, but if no waveforms are acquired, 604 | // this results in a busy loop waiting on `self.renderer.poll()`, pegging a core. 605 | // A 5 ms delay should be enough for even a 200 Hz display. 606 | window_target.set_control_flow( 607 | ControlFlow::wait_duration(Duration::from_millis(5))); 608 | } 609 | Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { 610 | self.window.pre_present_notify(); 611 | // draw waveforms 612 | self.wfm_renderer.render(&self.gl_library); 613 | // draw UI widgets 614 | let ui = self.imgui_context.frame(); 615 | self.ui_state.render(&ui); 616 | self.imgui_platform.prepare_render(ui, &self.window); 617 | self.imgui_renderer.render( 618 | &self.gl_library, &self.imgui_texture_map, self.imgui_context.render()) 619 | .expect("failed to render UI"); 620 | // handle OpenGL 621 | self.gl_surface.swap_buffers(&self.gl_context) 622 | .expect("failed to swap buffers"); 623 | } 624 | Event::WindowEvent { event: WindowEvent::Resized(size), .. } 625 | if size.width != 0 && size.height != 0 => { 626 | self.wfm_renderer.resize(&self.gl_library, size.width, size.height); 627 | self.imgui_platform.handle_event(self.imgui_context.io_mut(), &self.window, &event); 628 | self.gl_surface.resize(&self.gl_context, 629 | NonZeroU32::new(size.width).unwrap(), 630 | NonZeroU32::new(size.height).unwrap(), 631 | ); 632 | } 633 | Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { 634 | window_target.exit(); 635 | } 636 | Event::LoopExiting => { 637 | self.wfm_renderer.destroy(&self.gl_library); 638 | self.imgui_renderer.destroy(&self.gl_library); 639 | } 640 | event => { 641 | self.imgui_platform.handle_event(self.imgui_context.io_mut(), &self.window, &event); 642 | self.window.request_redraw(); 643 | } 644 | } 645 | } 646 | } 647 | 648 | fn main() { 649 | env_logger::Builder::from_default_env() 650 | .format_timestamp_micros() 651 | .filter_level(log::LevelFilter::Info) 652 | .parse_default_env() 653 | .init(); 654 | // create a window 655 | let event_loop = EventLoop::new().expect("failed to create event loop"); 656 | event_loop.set_control_flow(ControlFlow::wait_duration(Duration::ZERO)); 657 | let window_builder = WindowBuilder::new() 658 | .with_title("ThunderScope"); 659 | let config_template_builder = ConfigTemplateBuilder::new() 660 | .prefer_hardware_accelerated(Some(true)); 661 | let (window, gl_config) = DisplayBuilder::new() 662 | .with_window_builder(Some(window_builder)) 663 | .build(&event_loop, config_template_builder, |mut configs| 664 | configs.next().expect("no GL configurations available")) 665 | .expect("failed to create window"); 666 | let window = window.unwrap(); 667 | let (width, height) = window.inner_size().into(); 668 | // create an OpenGL context 669 | let context_attributes = ContextAttributesBuilder::new() 670 | .with_context_api(ContextApi::Gles(Some(Version::new(3, 0)))) 671 | .build(Some(window.raw_window_handle())); 672 | let gl_context = unsafe { 673 | gl_config.display().create_context(&gl_config, &context_attributes) 674 | .expect("failed to create GL context") 675 | }; 676 | let surface_attributes = SurfaceAttributesBuilder::::new() 677 | .build(window.raw_window_handle(), 678 | NonZeroU32::new(width).unwrap(), 679 | NonZeroU32::new(height).unwrap(), 680 | ); 681 | let gl_surface = unsafe { 682 | gl_config.display().create_window_surface(&gl_config, &surface_attributes) 683 | .expect("failed to create GL surface") 684 | }; 685 | let gl_context = gl_context.make_current(&gl_surface) 686 | .expect("failed to make GL context current"); 687 | let gl_library = unsafe { 688 | GlowContext::from_loader_function_cstr(|func| 689 | gl_config.display().get_proc_address(func).cast()) 690 | }; 691 | // determine UI scale 692 | let scale_factor = window.scale_factor(); 693 | log::info!("scaling UI by a factor of {:.2}×", scale_factor); 694 | let window_size = LogicalSize::new(1280.0, 720.0); 695 | let _ = window.request_inner_size( 696 | PhysicalSize::::from_logical(window_size, scale_factor)); 697 | // create ImGui context 698 | let mut imgui_context = imgui::Context::create(); 699 | imgui_context.style_mut().use_light_colors(); 700 | imgui_context.set_ini_filename(None); // disable ini autosaving 701 | // create UI state 702 | let font_config = imgui::FontConfig { 703 | rasterizer_density: scale_factor as f32, 704 | oversample_h: 1, 705 | ..Default::default() 706 | }; 707 | let ui_state = InterfaceRenderer::new(&mut imgui_context, font_config); 708 | // create ImGui renderer 709 | let mut imgui_platform = imgui_winit_support::WinitPlatform::init(&mut imgui_context); 710 | imgui_platform.attach_window(imgui_context.io_mut(), &window, 711 | imgui_winit_support::HiDpiMode::Locked(scale_factor)); 712 | let mut imgui_texture_map = imgui_glow_renderer::SimpleTextureMap::default(); 713 | let imgui_renderer = imgui_glow_renderer::Renderer::initialize(&gl_library, 714 | &mut imgui_context, &mut imgui_texture_map, /*output_srgb=*/true) 715 | .expect("failed to create UI renderer"); 716 | // create communication channels and prime the bucket brigade 717 | let (params_send, params_recv) = channel(); 718 | let (sampler_to_renderer_send, sampler_to_renderer_recv) = channel(); 719 | let (renderer_to_sampler_send, renderer_to_sampler_recv) = channel(); 720 | params_send.send(capture::Parameters::demo()).unwrap(); 721 | for _ in 0..4 { 722 | let waveform = Waveform::new(SAMPLE_COUNT) 723 | .expect("failed to create a ring buffer for acquisition"); 724 | renderer_to_sampler_send.send(waveform).unwrap(); 725 | } 726 | // set up the acquisition and processing pipeline 727 | let sampler = capture::Sampler::new( 728 | params_recv, renderer_to_sampler_recv, sampler_to_renderer_send); 729 | let wfm_renderer = WaveformRenderer::new(&gl_library, 730 | sampler_to_renderer_recv, renderer_to_sampler_send); 731 | // set up acquisition 732 | let data_source = match thunderscope::Device::new() { 733 | Ok(instrument) => capture::DataSource::Hardware(instrument), 734 | Err(_) => capture::DataSource::SineGenerator { frequency: 1e5 }, 735 | }; 736 | let sampler_thread = sampler.run(data_source); 737 | // run the application 738 | { 739 | let mut application = Application { 740 | gl_context, 741 | gl_surface, 742 | gl_library, 743 | wfm_renderer, 744 | imgui_context, 745 | imgui_platform, 746 | imgui_texture_map, 747 | imgui_renderer, 748 | ui_state, 749 | window 750 | }; 751 | event_loop.run(|event, window_target| 752 | application.process_event(event, window_target)) 753 | }.expect("failed to run application"); 754 | // clean up acquisition 755 | sampler_thread.join() 756 | .expect("acquisition thread panicked") 757 | .expect("acquisition failed"); 758 | } 759 | -------------------------------------------------------------------------------- /src/bin/gui/wave_frag.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform bool draw_lines; 5 | uniform vec3 channel_color; 6 | 7 | flat in vec2 prim_size; 8 | in vec2 prim_offset; 9 | 10 | out vec4 frag_color; 11 | 12 | void main() { 13 | float alpha = 1.0f; 14 | if (draw_lines) { 15 | if (prim_offset.x < 0.0f) { // endcap 16 | vec2 norm_offset = vec2( 17 | prim_offset.x / prim_size.y, 18 | prim_offset.y / prim_size.y 19 | ); 20 | alpha = (1.0f - dot(norm_offset, norm_offset)) * 0.6f; 21 | } else if (prim_offset.x > prim_size.x) { // endcap 22 | vec2 norm_offset = vec2( 23 | (prim_offset.x - prim_size.x) / prim_size.y, 24 | prim_offset.y / prim_size.y 25 | ); 26 | alpha = (1.0f - dot(norm_offset, norm_offset)) * 0.6f; 27 | } else { // body 28 | float norm_offset = prim_offset.y / prim_size.y; 29 | alpha = 1.0f - norm_offset * norm_offset; 30 | } 31 | } else { 32 | vec2 norm_offset = prim_offset / prim_size; 33 | alpha = 1.0f - dot(norm_offset, norm_offset); 34 | } 35 | frag_color = vec4(channel_color, alpha); 36 | } 37 | -------------------------------------------------------------------------------- /src/bin/gui/wave_vert.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | const float thickness = 2.0f; 5 | 6 | const vec2 point_quad[] = vec2[]( 7 | vec2(-1.0f, 1.0f), 8 | vec2(-1.0f,-1.0f), 9 | vec2( 1.0f, 1.0f), 10 | vec2( 1.0f,-1.0f) 11 | ); 12 | 13 | const vec2 line_quad[] = vec2[]( 14 | vec2(0.0f, 1.0f), 15 | vec2(0.0f, -1.0f), 16 | vec2(1.0f, 1.0f), 17 | vec2(1.0f, -1.0f) 18 | ); 19 | 20 | mat2 line_rotation(vec2 line_a, vec2 line_b) { 21 | vec2 norm_ab = normalize(line_b - line_a); 22 | return mat2( 23 | norm_ab.x, norm_ab.y, 24 | norm_ab.y, -norm_ab.x 25 | ); 26 | } 27 | 28 | uniform vec2 resolution; 29 | uniform int sample_count; 30 | uniform bool draw_lines; 31 | 32 | in float sample_value0; 33 | in float sample_value1; 34 | 35 | flat out vec2 prim_size; 36 | out vec2 prim_offset; 37 | 38 | vec2 project_sample(int index, float value) { 39 | return vec2( 40 | float(resolution.x) * (float(index) / float(sample_count - 1)), 41 | float(resolution.y) * (0.5f + value / 2.0f) 42 | ); 43 | } 44 | 45 | void main() { 46 | vec2 screen_position; 47 | if (draw_lines) { 48 | if (gl_InstanceID + 1 == sample_count) { 49 | gl_Position = vec4(0.0f, 0.0f, 0.0f, 0.0f); 50 | return; 51 | } 52 | vec2 line_a = project_sample(gl_InstanceID + 0, sample_value0); 53 | vec2 line_b = project_sample(gl_InstanceID + 1, sample_value1); 54 | prim_size = vec2(distance(line_a, line_b), thickness); 55 | prim_offset = line_quad[gl_VertexID] * 56 | mat2(prim_size.x + 2.0f * thickness, 0.0f, 0.0f, thickness) - 57 | vec2(thickness, 0.0f); 58 | screen_position = line_a + prim_offset * line_rotation(line_a, line_b); 59 | } else /* draw points */ { 60 | vec2 point = project_sample(gl_InstanceID, sample_value0); 61 | prim_size = vec2(thickness, thickness); 62 | prim_offset = point_quad[gl_VertexID] * prim_size; 63 | screen_position = point + prim_offset; 64 | } 65 | gl_Position = vec4(screen_position * 2.0f / resolution - vec2(1.0f, 1.0f), 0.0, 1.0); 66 | } 67 | -------------------------------------------------------------------------------- /src/bin/test.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use thunderscope::{ChannelConfiguration, DeviceCalibration, DeviceConfiguration, DeviceParameters}; 4 | 5 | const FILENAME: &str = "test.data"; 6 | 7 | fn main() -> thunderscope::Result<()> { 8 | env_logger::init(); 9 | thunderscope::Device::with(|device| { 10 | let config = DeviceConfiguration { 11 | channels: [Some(ChannelConfiguration::default()), None, None, None] 12 | }; 13 | let params = DeviceParameters::derive(&DeviceCalibration::default(), &config); 14 | device.configure(¶ms)?; 15 | let mut samples = vec![0; 200000]; 16 | device.stream_data().read_exact(samples.as_mut())?; // let the signal path stabilize 17 | device.stream_data().read_exact(samples.as_mut())?; 18 | println!("channel gain: {:.2} dB", params.gain(0)); 19 | let full_scale = params.full_scale(0); 20 | println!("full scale: {:-.3} V to {:+.3} V", -full_scale/2.0, full_scale/2.0); 21 | let count = 64; 22 | println!("first {} codes:\n {:02X?}", count, samples.iter() 23 | .take(count) 24 | .collect::>()); 25 | println!("first {} voltages:\n {:.2?}", count, samples.iter() 26 | .take(count) 27 | .map(|&code| params.code_to_volts(0, code as i8)) 28 | .collect::>()); 29 | std::fs::write(FILENAME, &samples[..]).unwrap(); 30 | println!("saved {} samples, run `python3 ./doc/plot_1ch.py {}`", samples.len(), FILENAME); 31 | Ok(()) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use core::{ops::{Add, Sub}, slice}; 2 | use std::ops::{AddAssign, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo, SubAssign}; 3 | 4 | use crate::Result; 5 | 6 | #[derive(Debug)] 7 | pub struct RingSlice { 8 | ptr: *mut u8, 9 | len: usize, 10 | } 11 | 12 | // SAFETY: Conceptually the same as `Box<[u8]>`. The destructor can run on any thread. 13 | unsafe impl Send for RingSlice {} 14 | 15 | impl RingSlice { 16 | pub fn new(min_size: usize) -> Result { 17 | let len = min_size.next_multiple_of(vmap::allocation_size()); 18 | // `pread()` gets unhappy if you read into the same page twice from both ends. 19 | let len = len.max(vmap::page_size() * 2); 20 | let ptr = vmap::os::map_ring(len)?; 21 | log::trace!("mapped ring slice at {:?}+{:#x?}*2", ptr, len); 22 | Ok(RingSlice { ptr, len }) 23 | } 24 | 25 | pub fn len(&self) -> usize { 26 | self.len 27 | } 28 | 29 | pub fn as_ptr(&self) -> *const u8 { 30 | self.ptr 31 | } 32 | 33 | pub fn as_mut_ptr(&self) -> *mut u8 { 34 | self.ptr 35 | } 36 | } 37 | 38 | impl Drop for RingSlice { 39 | fn drop(&mut self) { 40 | // SAFETY: Mapped with the same parameters in `Self::new`. 41 | let result = unsafe { vmap::os::unmap_ring(self.ptr, self.len) }; 42 | result.expect("failed to unmap ring slice"); 43 | log::trace!("unmapped ring slice at {:?}+{:#x?}*2", self.ptr, self.len); 44 | } 45 | } 46 | 47 | macro_rules! index_range { 48 | { 49 | fn range_to_parts(&any $self:ident, $index:ident: $range_ty:ty) { $( $code:tt )* } 50 | $( $rest:tt )* 51 | } => { 52 | impl Index<$range_ty> for RingSlice { 53 | type Output = [u8]; 54 | 55 | fn index(&$self, $index: $range_ty) -> &Self::Output { 56 | unsafe { 57 | let (ptr, len) = { $( $code )* }; 58 | slice::from_raw_parts(ptr, len) 59 | } 60 | } 61 | } 62 | 63 | impl IndexMut<$range_ty> for RingSlice { 64 | fn index_mut(&mut $self, $index: $range_ty) -> &mut Self::Output { 65 | unsafe { 66 | let (ptr, len) = { $( $code )* }; 67 | slice::from_raw_parts_mut(ptr, len) 68 | } 69 | } 70 | } 71 | 72 | index_range! { $( $rest )* } 73 | }; 74 | {} => {} 75 | } 76 | 77 | index_range! { 78 | fn range_to_parts(&any self, index: Range) { 79 | assert!(index.start < self.len && index.end <= self.len); 80 | if index.end >= index.start { 81 | (self.ptr.offset(index.start as isize), index.end - index.start) 82 | } else { 83 | (self.ptr.offset(index.start as isize), (self.len - index.start) + index.end) 84 | } 85 | } 86 | 87 | fn range_to_parts(&any self, index: RangeFrom) { 88 | assert!(index.start < self.len); 89 | (self.ptr.offset(index.start as isize), self.len) 90 | } 91 | 92 | // Perhaps counterintuitively, the same rotate operation as `Index>`! 93 | fn range_to_parts(&any self, index: RangeTo) { 94 | assert!(index.end <= self.len); 95 | (self.ptr.offset(index.end as isize), self.len) 96 | } 97 | 98 | fn range_to_parts(&any self, _index: RangeFull) { 99 | (self.ptr, self.len) 100 | } 101 | } 102 | 103 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 104 | pub struct RingCursor { 105 | index: usize, 106 | bound: usize, 107 | } 108 | 109 | impl RingCursor { 110 | pub fn new(bound: usize) -> RingCursor { 111 | RingCursor { index: 0, bound } 112 | } 113 | 114 | pub fn into_inner(self) -> usize { 115 | self.index 116 | } 117 | } 118 | 119 | impl Add for RingCursor { 120 | type Output = RingCursor; 121 | 122 | fn add(self, offset: usize) -> Self::Output { 123 | RingCursor { index: self.index.wrapping_add(offset) % self.bound, bound: self.bound } 124 | } 125 | } 126 | 127 | impl AddAssign for RingCursor { 128 | fn add_assign(&mut self, offset: usize) { 129 | *self = *self + offset 130 | } 131 | } 132 | 133 | impl Sub for RingCursor { 134 | type Output = RingCursor; 135 | 136 | fn sub(self, offset: usize) -> Self::Output { 137 | RingCursor { index: self.index.wrapping_sub(offset) % self.bound, bound: self.bound } 138 | } 139 | } 140 | 141 | impl SubAssign for RingCursor { 142 | fn sub_assign(&mut self, offset: usize) { 143 | *self = *self - offset 144 | } 145 | } 146 | 147 | #[derive(Debug)] 148 | pub struct RingBuffer { 149 | buffer: RingSlice, 150 | cursor: RingCursor, 151 | } 152 | 153 | impl RingBuffer { 154 | pub fn new(min_size: usize) -> Result { 155 | let buffer = RingSlice::new(min_size)?; 156 | let cursor = RingCursor::new(buffer.len()); 157 | Ok(RingBuffer { buffer, cursor }) 158 | } 159 | 160 | pub fn len(&self) -> usize { 161 | self.buffer.len() 162 | } 163 | 164 | pub fn cursor(&self) -> RingCursor { 165 | self.cursor 166 | } 167 | 168 | pub fn append(&mut self, max_size: usize, writer: F) -> core::result::Result 169 | where F: FnOnce(&mut [u8]) -> core::result::Result { 170 | assert!(max_size <= self.buffer.len()); 171 | let result = writer(&mut self.buffer[self.cursor.index..][..max_size]); 172 | if let Ok(written) = result { self.cursor += written } 173 | result 174 | } 175 | 176 | pub fn read(&self, cursor: RingCursor, count: usize) -> &[i8] { 177 | assert!(cursor.bound == self.buffer.len()); 178 | assert!(count <= self.buffer.len()); 179 | bytemuck::cast_slice(&self.buffer[cursor.index..][..count]) 180 | } 181 | } 182 | 183 | #[cfg(test)] 184 | mod test { 185 | use super::*; 186 | 187 | #[test] 188 | fn test_ring_buffer_simple() { 189 | let mut buf = RingSlice::new(8).unwrap(); 190 | buf[..][0..8].copy_from_slice([1, 2, 3, 4, 5, 6, 7, 8].as_ref()); 191 | assert_eq!(&buf[0..4], [1, 2, 3, 4].as_ref()); 192 | assert_eq!(&buf[2..6], [3, 4, 5, 6].as_ref()); 193 | assert_eq!(&buf[4..8], [5, 6, 7, 8].as_ref()); 194 | assert_eq!(&buf[5..][..3], [6, 7, 8].as_ref()); 195 | assert_eq!(&buf[..5][buf.len() - 5..], [1, 2, 3, 4, 5].as_ref()); 196 | } 197 | 198 | #[test] 199 | fn test_ring_buffer_overlap() { 200 | let mut buf = RingSlice::new(8192).unwrap(); 201 | assert_eq!(buf.len(), 8192); 202 | buf[8186..8192].copy_from_slice(&[1, 2, 3, 4, 5, 6]); 203 | buf[0..6].copy_from_slice(&[7, 8, 9, 10, 11, 12]); 204 | assert_eq!(&buf[8186..8192], &[1, 2, 3, 4, 5, 6]); 205 | assert_eq!(&buf[0..6], &[7, 8, 9, 10, 11, 12]); 206 | assert_eq!(&buf[8186..6], &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); 207 | } 208 | 209 | #[test] 210 | fn test_ring_cursor() { 211 | let cursor = RingCursor::new(128); 212 | assert_eq!((cursor + 10).index, 10); 213 | assert_eq!((cursor + 10 + 120).index, 2); 214 | assert_eq!((cursor + 130).index, 2); 215 | assert_eq!((cursor - 10).index, 118); 216 | assert_eq!((cursor - 130).index, 126); 217 | assert_eq!((cursor + 0), cursor); 218 | let mut cursor = cursor; 219 | cursor += 10; 220 | assert_eq!(cursor.index, 10); 221 | cursor -= 20; 222 | assert_eq!(cursor.index, 118); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! High-level configuration of the device in terms of physical qualities. 2 | 3 | #![allow(dead_code)] 4 | 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 7 | pub enum Termination { 8 | #[default] 9 | Ohm1M, 10 | Ohm50, 11 | } 12 | 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 14 | pub enum Coupling { 15 | #[default] 16 | DC, 17 | AC 18 | } 19 | 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 21 | pub enum Bandwidth { 22 | #[default] 23 | MHz100, 24 | MHz200, 25 | MHz350, 26 | } 27 | 28 | #[derive(Debug, Clone, Copy, PartialEq)] 29 | pub struct ChannelConfiguration { 30 | /// Probe attenuation in dB. For a 1X probe, `0.0`; for a 10X probe, `20.0`. 31 | pub probe_attenuation: f32, 32 | pub termination: Termination, 33 | pub coupling: Coupling, 34 | pub bandwidth: Bandwidth, 35 | } 36 | 37 | impl Default for ChannelConfiguration { 38 | fn default() -> Self { 39 | Self { 40 | probe_attenuation: 20.0, // 10X probe 41 | termination: Default::default(), 42 | coupling: Default::default(), 43 | bandwidth: Default::default(), 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone, Copy, PartialEq)] 49 | pub struct DeviceConfiguration { 50 | pub channels: [Option; 4] 51 | } 52 | 53 | impl Default for DeviceConfiguration { 54 | fn default() -> Self { 55 | DeviceConfiguration { 56 | channels: [Some(ChannelConfiguration::default()); 4] 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use std::thread; 3 | 4 | use crate::Result; 5 | use crate::sys::Driver; 6 | use crate::regs::axi::{self, Control, FifoIsr, Status}; 7 | use crate::regs::adc; 8 | use crate::config::{Coupling, Termination}; 9 | use crate::params::{ChannelParameters, CoarseAttenuation, DeviceParameters}; 10 | 11 | const SPI_BUS_ADC: u8 = 0; 12 | const SPI_BUS_PGA: [u8; 4] = [2, 3, 4, 5]; 13 | 14 | #[derive(Debug)] 15 | pub struct Device { 16 | driver: Driver, 17 | } 18 | 19 | impl Device { 20 | pub fn new() -> Result { 21 | if cfg!(all(feature = "hardware", target_os = "linux")) { 22 | // FIXME: do this better 23 | Ok(Device { driver: Driver::new("/dev/xdma0")? }) 24 | } else { 25 | log::error!("this platform does not implement a hardware driver"); 26 | Err(crate::Error::Unsupported) 27 | } 28 | } 29 | 30 | pub fn with(f: F) -> Result 31 | where F: FnOnce(&mut Self) -> Result { 32 | let mut device = Self::new()?; 33 | device.startup()?; 34 | let result = f(&mut device); 35 | device.shutdown()?; 36 | result 37 | } 38 | } 39 | 40 | impl Device { 41 | fn read_user_u32(&self, addr: usize) -> Result { 42 | let mut bytes = [0u8; 4]; 43 | self.driver.read_user(addr, &mut bytes[..])?; 44 | let data = u32::from_le_bytes(bytes); 45 | log::trace!("read_user_u32({:#x}) = {:#x}", addr, data); 46 | Ok(data) 47 | } 48 | 49 | fn write_user_u32(&self, addr: usize, data: u32) -> Result<()> { 50 | log::trace!("write_user_u32({:#x}, {:#x})", addr, data); 51 | let bytes = u32::to_le_bytes(data); 52 | self.driver.write_user(addr, &bytes[..])?; 53 | Ok(()) 54 | } 55 | 56 | fn read_control(&self) -> Result { 57 | let value = Control::from_bits_retain(self.read_user_u32(axi::ADDR_CONTROL)?); 58 | log::debug!("read_control() = {:?}", value); 59 | Ok(value) 60 | } 61 | 62 | fn write_control(&self, value: Control) -> Result<()> { 63 | log::debug!("write_control({:?})", value); 64 | Ok(self.write_user_u32(axi::ADDR_CONTROL, value.bits())?) 65 | } 66 | 67 | fn modify_control(&self, f: F) -> Result<()> { 68 | let mut value = self.read_control()?; 69 | f(&mut value); 70 | self.write_control(value) 71 | } 72 | 73 | fn read_status(&self) -> Result { 74 | let value = Status::from_bits_retain(self.read_user_u32(axi::ADDR_STATUS)?); 75 | log::trace!("read_status() = {:?}", value); 76 | Ok(value) 77 | } 78 | 79 | fn write_fifo(&self, data: &[u8]) -> Result<()> { 80 | log::trace!("write_fifo({:02x?})", data); 81 | // enqueue data into the FIFO 82 | for &byte in data { 83 | self.write_user_u32(axi::ADDR_FIFO_TDFD, byte as u32)?; 84 | } 85 | // start transmission; the FIFO is configured for 32-bit datapath length, but the top 86 | // three bytes are ignored by the SPI/I2C gateware connected to it 87 | self.write_user_u32(axi::ADDR_FIFO_TLR, data.len() as u32 * 4)?; 88 | // clear transmit complete flag 89 | self.write_user_u32(axi::ADDR_FIFO_ISR, FifoIsr::TC.bits())?; 90 | // wait for the packet to be transmitted 91 | loop { 92 | let isr = FifoIsr::from_bits_retain(self.read_user_u32(axi::ADDR_FIFO_ISR)?); 93 | assert!(!isr.contains(FifoIsr::TPOE), "Transmit FIFO overflow! ISR = {:?}", isr); 94 | if isr.contains(FifoIsr::TC) { break } // done! 95 | } 96 | Ok(()) 97 | } 98 | 99 | fn write_i2c(&self, i2c_addr: u8, data: &[u8]) -> Result<()> { 100 | log::debug!("write_i2c({:#08b}, {:02x?})", i2c_addr, data); 101 | let mut packet = Vec::::new(); 102 | packet.push(0xff); // select I2C 103 | packet.push(i2c_addr); 104 | packet.extend_from_slice(data); 105 | self.write_fifo(packet.as_ref())?; 106 | // the I2C engine doesn't use TLAST to detect packet boundaries and runs at 400 kHz; 107 | // make sure the engine is done before releasing it. the delay has a 100% safety factor. 108 | thread::sleep(Duration::from_micros((50 * data.len()) as u64)); 109 | Ok(()) 110 | } 111 | 112 | // bus 0 (0xfd): ADC 113 | // bus 2..5 (0xfb..0xf7): PGAn 114 | fn write_spi(&self, spi_bus: u8, data: &[u8]) -> Result<()> { 115 | log::debug!("write_spi({:?}, {:02x?})", spi_bus, data); 116 | let mut packet = Vec::::new(); 117 | packet.push(0xfd - spi_bus); 118 | packet.extend_from_slice(data); 119 | self.write_fifo(packet.as_ref())?; 120 | // the SPI engine doesn't use TLAST either, but it runs at 16 MHz. the delay is enough 121 | // for 160 bytes. 122 | thread::sleep(Duration::from_micros(10)); 123 | Ok(()) 124 | } 125 | 126 | fn write_pll_register(&self, reg_addr: u16, value: u8) -> Result<()> { 127 | log::debug!("write_pll_register({:#06x}, {:#04x})", reg_addr, value); 128 | self.write_i2c(0b11101000, &[ 129 | 0x02, // register write 130 | (reg_addr >> 8) as u8, // register address high 131 | (reg_addr >> 0) as u8, // register address low 132 | value 133 | ]) 134 | } 135 | 136 | fn init_pll_registers(&self, init_words: &[u32]) -> Result<()> { 137 | for &init_word in init_words { 138 | self.write_pll_register((init_word >> 8) as u16, init_word as u8)?; 139 | } 140 | Ok(()) 141 | } 142 | 143 | fn write_adc_register(&self, reg_addr: u8, value: u16) -> Result<()> { 144 | log::debug!("write_adc_register({:#04x}, {:#06x})", reg_addr, value); 145 | self.write_spi(SPI_BUS_ADC, &[ 146 | reg_addr, 147 | (value >> 8) as u8, 148 | (value >> 0) as u8, 149 | ]) 150 | } 151 | 152 | fn init_adc_registers(&self, init_pairs: &[(u8, u16)]) -> Result<()> { 153 | for &(reg_addr, value) in init_pairs { 154 | self.write_adc_register(reg_addr, value)?; 155 | } 156 | Ok(()) 157 | } 158 | 159 | fn enable_adc_channels(&self, enabled: [bool; 4]) -> Result<()> { 160 | log::debug!("enable_adc_channels({:?})", enabled); 161 | // compute number of enabled ADC channels and ADC clock divisor 162 | // channels CH1..CH4 on the faceplate are mapped to IN4..IN1 on the ADC, so this function 163 | // has to perform a really annoying permutation 164 | let clkdiv; // in ADC 165 | let chnum; // in ADC 166 | let chmux; // in FPGA 167 | match enabled.iter().map(|&en| en as u8).sum() { 168 | 1 => { clkdiv = 0; chnum = 1; chmux = Control::empty(); } 169 | 2 => { clkdiv = 1; chnum = 2; chmux = Control::ChannelMux0; } 170 | 3 => { clkdiv = 2; chnum = 4; chmux = Control::ChannelMux1; } // same as 4 171 | 4 => { clkdiv = 2; chnum = 4; chmux = Control::ChannelMux1; } 172 | _ => panic!("unsupported channel configuration"), 173 | }; 174 | // compute ADC input select permutation 175 | let insel = match chnum { 176 | 1 => { 177 | let ch1_index = enabled.iter().rev().position(|&en| en).unwrap(); 178 | [ch1_index, ch1_index, ch1_index, ch1_index] 179 | } 180 | 2 => { 181 | let ch1_index = enabled.iter().rev().position(|&en| en).unwrap(); 182 | let ch2_index = ch1_index + 1 + 183 | enabled.iter().rev().skip(ch1_index + 1).position(|&en| en).unwrap(); 184 | // this is permuted later again 185 | // the (faceplate) channel order in the data is ch1,ch2,ch1,ch2 186 | [ch2_index, ch2_index, ch1_index, ch1_index] 187 | } 188 | 4 => { 189 | // the (faceplate) channel order in the data is ch1,ch2,ch3,ch4 190 | [3, 2, 1, 0] 191 | } 192 | _ => unreachable!() 193 | }; 194 | // reconfigure ADC 195 | self.init_adc_registers(&[ 196 | // power down ADC 197 | (adc::ADDR_HMCAD1520_POWER, 0x0200), 198 | // configure clock divisor and channel count 199 | (adc::ADDR_HMCAD1520_CHNUM_CLKDIV, (clkdiv << 8) | chnum), 200 | // power up ADC 201 | (adc::ADDR_HMCAD1520_POWER, 0x0000), 202 | // configure channel mapping 203 | (adc::ADDR_HMCAD1520_INSEL12, 0x0200 << insel[1] | 0x0002 << insel[0]), 204 | (adc::ADDR_HMCAD1520_INSEL34, 0x0200 << insel[3] | 0x0002 << insel[2]), 205 | ])?; 206 | // reconfigure channel mux in the FPGA 207 | self.modify_control(|val| { 208 | val.remove(Control::ChannelMux0 | Control::ChannelMux1); 209 | val.insert(chmux); 210 | })?; 211 | Ok(()) 212 | } 213 | 214 | fn write_pga_command(&self, pga_bus: u8, command: u16) -> Result<()> { 215 | log::debug!("write_pga_command({:?}, {:#06x})", pga_bus, command); 216 | self.write_spi(pga_bus, &[ 217 | 0x00, // write command word 218 | (command >> 8) as u8, 219 | (command >> 0) as u8, 220 | ]) 221 | } 222 | 223 | fn configure_pga(&self, index: usize, params: &ChannelParameters) -> Result<()> { 224 | self.write_pga_command(SPI_BUS_PGA[index], 225 | (1 << 10) | // always turn off auxiliary output to save power 226 | params.filtering.lmh6518_code() | 227 | params.amplification.lmh6518_code() | 228 | params.fine_attenuation.lmh6518_code() 229 | ) 230 | } 231 | 232 | fn write_digipot_input(&self, addr: u8, input: u16) -> Result<()> { 233 | let command_data = 234 | ((addr as u16) << 12) | // device address 235 | (0b00 << 10) | // write 236 | ((input & 0x3ff) << 0); 237 | self.write_i2c(0b0101100, &[ 238 | (command_data >> 8) as u8, 239 | (command_data >> 0) as u8, 240 | ]) 241 | } 242 | 243 | fn write_trimdac_input(&self, channel: u8, input: u16) -> Result<()> { 244 | log::debug!("write_trimdac_input({:?}, {:#06x})", channel, input); 245 | self.write_i2c(0b1100000, &[ 246 | 0b01011_00_0 | ((channel & 0b11) << 1), 247 | (input >> 8) as u8, 248 | (input >> 0) as u8, 249 | ]) 250 | } 251 | 252 | fn configure_digipot_trimdac(&self, index: usize, params: &ChannelParameters) -> Result<()> { 253 | const WIPER_ADDRESS: [u8; 4] = [0x6, 0x0, 0x1, 0x7]; 254 | self.write_digipot_input(WIPER_ADDRESS[index], 255 | params.offset_magnitude.mcp4432t_503e_code())?; 256 | self.write_trimdac_input(index as u8, 257 | (1 << 15) | // always use Vref as reference 258 | params.offset_value.mcp4728_code() 259 | )?; 260 | Ok(()) 261 | } 262 | 263 | fn enable_datamover(&self) -> Result<()> { 264 | // take the acquisition system out of reset 265 | self.modify_control(|val| val.insert(Control::DatamoverHaltN | Control::FpgaAcqResetN))?; 266 | Ok(()) 267 | } 268 | 269 | fn disable_datamover(&self) -> Result<()> { 270 | // halt the data mover 271 | self.modify_control(|val| val.remove(Control::DatamoverHaltN))?; 272 | // wait for data mover to halt 273 | thread::sleep(Duration::from_millis(5)); 274 | // reset the acquisition subsystem 275 | self.modify_control(|val| val.remove(Control::FpgaAcqResetN))?; 276 | Ok(()) 277 | } 278 | 279 | pub fn configure(&self, params: &DeviceParameters) -> Result<()> { 280 | if *params == Default::default() { 281 | log::info!("configure(DeviceParameters::default())"); 282 | } else { 283 | log::info!("configure({:#?})", params); 284 | } 285 | // configure the PGAs first; this keeps current consumption in check for the initial 286 | // `configure()` call from `startup()` by turning off the PGA aux outputs that (for all 287 | // PGAs together) consume almost 2W 288 | for (index, ch_params) in params.channels.iter().enumerate() { 289 | let ch_params = ch_params.unwrap_or_default(); 290 | self.configure_pga(index, &ch_params)?; 291 | } 292 | // configure termination, coupling, and attenuator 293 | for (index, ch_params) in params.channels.iter().enumerate() { 294 | let ch_params = ch_params.unwrap_or_default(); 295 | self.modify_control(|val| { 296 | match ch_params.termination { 297 | Termination::Ohm1M => val.remove(Control::ch_termination(index)), 298 | Termination::Ohm50 => val.insert(Control::ch_termination(index)), 299 | } 300 | match ch_params.coupling { 301 | Coupling::AC => val.remove(Control::ch_coupling(index)), 302 | Coupling::DC => val.insert(Control::ch_coupling(index)), 303 | } 304 | match ch_params.coarse_attenuation { 305 | CoarseAttenuation::X50 => val.remove(Control::ch_attenuator(index)), 306 | CoarseAttenuation::X1 => val.insert(Control::ch_attenuator(index)), 307 | } 308 | })?; 309 | } 310 | // configure voltage offset 311 | for (index, ch_params) in params.channels.iter().enumerate() { 312 | let ch_params = ch_params.unwrap_or_default(); 313 | self.configure_digipot_trimdac(index, &ch_params)?; 314 | } 315 | // put data mover into reset (it cannot run without ADC clock or tolerate glitches on it) 316 | self.disable_datamover()?; 317 | // configure the ADC input selector, clock divisor, channel mapping, and FPGA data mux 318 | // this disables data mover first and (re-)enables it after 319 | self.enable_adc_channels([ 320 | params.channels[0].is_some(), 321 | params.channels[1].is_some(), 322 | params.channels[2].is_some(), 323 | params.channels[3].is_some(), 324 | ])?; 325 | // take data mover out of reset now that ADC clock is available (again) 326 | self.enable_datamover()?; 327 | Ok(()) 328 | } 329 | 330 | pub fn startup(&self) -> Result<()> { 331 | log::info!("startup()"); 332 | // disable the data mover first and let it stop, in case it was running before 333 | // this prevents device crashes after unclean shutdowns (think ^C) 334 | self.disable_datamover()?; 335 | // enable the 3V3 rail and wait for it to stabilize 336 | self.modify_control(|val| val.insert(Control::ClockGenResetN | Control::Rail3V3Enabled))?; 337 | thread::sleep(Duration::from_millis(10)); 338 | // The RSTN pin must be asserted once after power-up. 339 | // Reset should be asserted for at least 1μs. 340 | self.modify_control(|val| val.remove(Control::ClockGenResetN))?; 341 | thread::sleep(Duration::from_micros(100)); 342 | // System software must wait at least 100μs after RSTN is deasserted 343 | // and wait for GLOBISR.BCDONE=1 before configuring the device. 344 | self.modify_control(|val| val.insert(Control::ClockGenResetN))?; 345 | thread::sleep(Duration::from_millis(1)); 346 | // configure the PLL using the Rev4 blob 347 | self.init_pll_registers(&[ 348 | 0x042308, 0x000301, 0x000402, 0x000521, 349 | 0x000701, 0x010042, 0x010100, 0x010201, 350 | 0x010600, 0x010700, 0x010800, 0x010900, 351 | 0x010A20, 0x010B03, 0x012160, 0x012790, 352 | 0x014100, 0x014200, 0x014300, 0x014400, 353 | 0x0145A0, 0x015300, 0x015450, 0x0155CE, 354 | 0x018000, 0x020080, 0x020105, 0x025080, 355 | 0x025102, 0x04300C, 0x043000 356 | ])?; 357 | thread::sleep(Duration::from_millis(10)); 358 | // align the PLL output phases 359 | self.init_pll_registers(&[ 360 | 0x010002, 0x010042 361 | ])?; 362 | thread::sleep(Duration::from_millis(10)); 363 | // configure the ADC, but leave it powered down or it'll be very unhappy about its clock 364 | self.init_adc_registers(&[ 365 | // reset ADC 366 | (adc::ADDR_HMCAD1520_RESET, 0x0001), 367 | // power down ADC 368 | (adc::ADDR_HMCAD1520_POWER, 0x0200), 369 | // invert channels 370 | (adc::ADDR_HMCAD1520_INVERT, 0x007F), 371 | // adjust full scale value 372 | (adc::ADDR_HMCAD1520_FS_CNTRL, 0x0020), 373 | // enable coarse gain 374 | (adc::ADDR_HMCAD1520_GAIN_CFG, 0x0000), 375 | // set coarse gain for 4 channel mode 376 | (adc::ADDR_HMCAD1520_QUAD_GAIN, 0x9999), 377 | // set coarse gain for 1 and 2 channel modes 378 | (adc::ADDR_HMCAD1520_DUAL_GAIN, 0x0A99), 379 | // select 8-bit output (for HMCAD1520s) 380 | (adc::ADDR_HMCAD1520_RES_SEL, 0x0000), 381 | // set LVDS phase to 0 deg and drive strength to RSDS 382 | (adc::ADDR_HMCAD1520_LVDS_PHASE, 0x0060), 383 | (adc::ADDR_HMCAD1520_LVDS_DRIVE, 0x0222), 384 | // configure output in ramp test mode 385 | // (hmc::ADDR_HMCAD1520_LVDS_PATTERN, 0x0040), 386 | ])?; 387 | // enable the frontend 388 | // this causes a current spike due to PGA aux output being enabled by default, and *must* 389 | // be quickly followed by a call to `configure()` (with any parameters) to disable that 390 | // output as soon as possible, or risk an overcurrent condition 391 | self.modify_control(|val| val.insert(Control::Rail5VEnabled))?; 392 | thread::sleep(Duration::from_millis(5)); 393 | // configure to a known (default) state 394 | // this also enables the data mover 395 | self.configure(&DeviceParameters::default())?; 396 | // done! 397 | Ok(()) 398 | } 399 | 400 | pub fn shutdown(&self) -> Result<()> { 401 | log::info!("shutdown()"); 402 | // disable the data mover first and let it stop, since it runs on ADC clock 403 | self.disable_datamover()?; 404 | // power down the frontend 5V0 and board 3V3 405 | self.write_control(Control::empty())?; 406 | Ok(()) 407 | } 408 | 409 | pub fn stream_data<'a>(&'a self) -> Streamer<'a> { 410 | Streamer { device: self, cursor: None } 411 | } 412 | } 413 | 414 | #[derive(Debug)] 415 | pub struct Streamer<'a> { 416 | device: &'a Device, 417 | cursor: Option, 418 | } 419 | 420 | impl<'a> std::io::Read for Streamer<'a> { 421 | fn read(&mut self, mut buffer: &mut [u8]) -> std::io::Result { 422 | const PAGE_BITS: usize = 12; // 4 Ki 423 | const MEMORY_SIZE: usize = 1 << 16 << PAGE_BITS; // 64 Ki x (1 << PAGE_BITS) = 256 Mi 424 | 425 | let mut written = 0; 426 | while buffer.len() > 0 { 427 | // check if there is an error condition set 428 | // these should never appear so long as the FPGA is functioning correctly 429 | let status = self.device.read_status()?; 430 | if status.intersects(Status::FifoOverflow | Status::DatamoverError) { 431 | log::error!("data mover failure, power cycle the device"); 432 | panic!("data mover failure: {:?} (overflow by {} cycles)", 433 | status, status.overflow_cycles()); 434 | } 435 | // read any newly available data 436 | let next_cursor = status.pages_moved() << PAGE_BITS; 437 | let (prev_cursor, length) = match self.cursor { 438 | None => { // first ever read 439 | self.cursor = Some(next_cursor); 440 | continue 441 | } 442 | Some(prev_cursor) if next_cursor < prev_cursor => // wraparound 443 | (prev_cursor, buffer.len().min(MEMORY_SIZE - prev_cursor)), 444 | Some(prev_cursor) => // no wraparound 445 | (prev_cursor, buffer.len().min(next_cursor - prev_cursor)), 446 | }; 447 | if length > 0 { 448 | let (chunk, rest) = buffer.split_at_mut(length); 449 | log::debug!("streaming {:#010x?}+{:#x?} to {:#x?}+{:#x?}", 450 | prev_cursor, length, chunk.as_ptr(), chunk.len()); 451 | self.device.driver.read_dma(prev_cursor, chunk)?; 452 | self.cursor = Some((prev_cursor + length) % MEMORY_SIZE); 453 | written += length; 454 | buffer = rest; 455 | } else { 456 | break 457 | } 458 | } 459 | Ok(written) 460 | } 461 | 462 | } 463 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(array_chunks)] 2 | 3 | mod sys; 4 | mod regs; 5 | mod config; 6 | mod params; 7 | mod device; 8 | mod buffer; 9 | mod trigger; 10 | 11 | #[derive(Debug)] 12 | pub enum Error { 13 | Unsupported, 14 | NotFound, 15 | Xdma(std::io::Error), 16 | Vmap(vmap::Error), 17 | Other(Box), 18 | } 19 | 20 | impl std::fmt::Display for Error { 21 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 22 | match self { 23 | Self::Unsupported => 24 | write!(f, "platform not supported"), 25 | Self::NotFound => 26 | write!(f, "device not connected"), 27 | Self::Xdma(error) => 28 | write!(f, "XDMA error: {}", error), 29 | Self::Vmap(error) => 30 | write!(f, "virtual memory mapping error: {}", error), 31 | Self::Other(error) => 32 | write!(f, "{}", error), 33 | } 34 | } 35 | } 36 | 37 | impl std::error::Error for Error { 38 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 39 | match self { 40 | &Self::Xdma(ref error) => Some(error), 41 | &Self::Vmap(ref error) => Some(error), 42 | _ => None 43 | } 44 | } 45 | } 46 | 47 | impl From for Error { 48 | fn from(error: vmap::Error) -> Self { 49 | Error::Vmap(error) 50 | } 51 | } 52 | 53 | impl From for Error { 54 | fn from(error: std::io::Error) -> Self { 55 | match error.downcast::() { 56 | Ok(error) => error, 57 | Err(error) => Error::Other(error.into()), 58 | } 59 | } 60 | } 61 | 62 | impl From for std::io::Error { 63 | fn from(error: Error) -> Self { 64 | match error { 65 | Error::Unsupported => 66 | Self::new(std::io::ErrorKind::Unsupported, error), 67 | Error::NotFound => // converted from std::io::Error in first place 68 | Self::new(std::io::ErrorKind::NotFound, error), 69 | Error::Xdma(error) => error, 70 | Error::Vmap(error) => error.into(), 71 | Error::Other(error) => { 72 | match error.downcast::() { 73 | Ok(error) => *error, 74 | Err(error) => std::io::Error::new(std::io::ErrorKind::Other, error) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | pub type Result = 82 | core::result::Result; 83 | 84 | pub use config::{ 85 | Termination, 86 | Coupling, 87 | Bandwidth, 88 | ChannelConfiguration, 89 | DeviceConfiguration, 90 | }; 91 | 92 | pub use params::{ 93 | CoarseAttenuation, 94 | Amplification, 95 | FineAttenuation, 96 | Filtering, 97 | OffsetMagnitude, 98 | OffsetValue, 99 | ChannelParameters, 100 | DeviceParameters, 101 | ChannelCalibration, 102 | DeviceCalibration, 103 | }; 104 | 105 | pub use device::Device; 106 | 107 | pub use trigger::{ 108 | EdgeFilter, 109 | Edge, 110 | Trigger, 111 | }; 112 | 113 | pub use buffer::{ 114 | RingCursor, 115 | RingBuffer, 116 | }; 117 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | //! Low-level parameters of the device that map 1:1 to values written into registers of 2 | //! analog frontend components. 3 | 4 | #![allow(dead_code)] 5 | 6 | use std::fmt; 7 | 8 | use crate::{config::{Bandwidth, Coupling, DeviceConfiguration, Termination}, ChannelConfiguration}; 9 | 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 11 | pub enum CoarseAttenuation { 12 | X1, 13 | #[default] 14 | X50, 15 | } 16 | 17 | impl CoarseAttenuation { 18 | /// Gain in this part of the signal path, in dB. 19 | fn gain(self) -> f32 { 20 | match self { 21 | CoarseAttenuation::X1 => 0.0, 22 | CoarseAttenuation::X50 => -33.9794, 23 | } 24 | } 25 | } 26 | 27 | #[allow(non_camel_case_types)] 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 29 | pub enum Amplification { 30 | dB10, 31 | #[default] 32 | dB30, 33 | } 34 | 35 | impl Amplification { 36 | pub(crate) fn lmh6518_code(self) -> u16 { 37 | (match self { 38 | Self::dB10 => 0b0, // "low gain" 39 | Self::dB30 => 0b1, // "high gain" 40 | }) << 4 41 | } 42 | 43 | /// Gain in this part of the signal path, in dB. 44 | fn gain(self) -> f32 { 45 | match self { 46 | Amplification::dB10 => 10.0, 47 | Amplification::dB30 => 30.0, 48 | } 49 | } 50 | } 51 | 52 | #[allow(non_camel_case_types)] 53 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 54 | pub enum FineAttenuation { 55 | #[default] 56 | dB0, 57 | dB2, 58 | dB4, 59 | dB6, 60 | dB8, 61 | dB10, 62 | dB12, 63 | dB14, 64 | dB16, 65 | dB18, 66 | dB20, 67 | } 68 | 69 | impl FineAttenuation { 70 | pub(crate) fn lmh6518_code(self) -> u16 { 71 | (match self { 72 | Self::dB0 => 0b0000, 73 | Self::dB2 => 0b0001, 74 | Self::dB4 => 0b0010, 75 | Self::dB6 => 0b0011, 76 | Self::dB8 => 0b0100, 77 | Self::dB10 => 0b0101, 78 | Self::dB12 => 0b0110, 79 | Self::dB14 => 0b0111, 80 | Self::dB16 => 0b1000, 81 | Self::dB18 => 0b1001, 82 | Self::dB20 => 0b1010, 83 | }) << 0 84 | } 85 | 86 | /// Gain in this part of the signal path, in dB. 87 | fn gain(self) -> f32 { 88 | match self { 89 | FineAttenuation::dB0 => -0.0, 90 | FineAttenuation::dB2 => -2.0, 91 | FineAttenuation::dB4 => -4.0, 92 | FineAttenuation::dB6 => -6.0, 93 | FineAttenuation::dB8 => -8.0, 94 | FineAttenuation::dB10 => -10.0, 95 | FineAttenuation::dB12 => -12.0, 96 | FineAttenuation::dB14 => -14.0, 97 | FineAttenuation::dB16 => -16.0, 98 | FineAttenuation::dB18 => -18.0, 99 | FineAttenuation::dB20 => -20.0, 100 | } 101 | } 102 | } 103 | 104 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 105 | pub enum Filtering { 106 | MHz20, 107 | #[default] 108 | MHz100, 109 | MHz200, 110 | MHz350, 111 | Off, 112 | } 113 | 114 | impl Filtering { 115 | pub(crate) fn lmh6518_code(self) -> u16 { 116 | (match self { 117 | Self::MHz20 => 0b001, 118 | Self::MHz100 => 0b010, 119 | Self::MHz200 => 0b011, 120 | Self::MHz350 => 0b100, 121 | Self::Off => 0b000, 122 | }) << 6 123 | } 124 | } 125 | 126 | #[derive(Clone, Copy, PartialEq, Eq)] 127 | pub struct OffsetMagnitude { 128 | code: u16, 129 | } 130 | 131 | impl fmt::Debug for OffsetMagnitude { 132 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 133 | write!(f, "OffsetMagnitude::from_ohms({})", self.ohms()) 134 | } 135 | } 136 | 137 | impl Default for OffsetMagnitude { 138 | fn default() -> Self { 139 | OffsetMagnitude { code: 0x3f } // mid-scale 140 | } 141 | } 142 | 143 | impl OffsetMagnitude { 144 | pub(crate) fn mcp4432t_503e_code(self) -> u16 { 145 | self.code 146 | } 147 | 148 | pub(crate) fn from_ohms(ohms: u32) -> Self { 149 | assert!(ohms >= 75 && ohms <= 50000 + 75); 150 | const HALF_LSB: u32 = (50000 / 128) / 2; 151 | let code = (ohms - 75 + /* round to nearest */HALF_LSB) * 128 / 50000; 152 | OffsetMagnitude { code: code as u16 } 153 | } 154 | 155 | pub(crate) fn ohms(self) -> u32 { 156 | self.code as u32 * 50000 / 128 + 75 157 | } 158 | } 159 | 160 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 161 | pub struct OffsetValue { 162 | code: u16, // 12 bit DAC 163 | } 164 | 165 | impl Default for OffsetValue { 166 | fn default() -> Self { 167 | OffsetValue { code: 0x3fff } // mid-scale 168 | } 169 | } 170 | 171 | impl OffsetValue { 172 | pub(crate) fn mcp4728_code(self) -> u16 { 173 | self.code 174 | } 175 | } 176 | 177 | #[derive(Debug, Clone, Copy, PartialEq, Default)] 178 | pub struct ChannelParameters { 179 | pub probe_attenuation: f32, // in dB 180 | pub termination: Termination, 181 | pub coupling: Coupling, 182 | pub coarse_attenuation: CoarseAttenuation, 183 | pub amplification: Amplification, 184 | pub fine_attenuation: FineAttenuation, 185 | pub filtering: Filtering, 186 | pub offset_magnitude: OffsetMagnitude, 187 | pub offset_value: OffsetValue, 188 | } 189 | 190 | impl ChannelParameters { 191 | /// Returns total gain in the instrument signal path, in decibels. 192 | fn gain(&self, adc_coarse_gain: f32) -> f32 { 193 | -self.probe_attenuation 194 | + self.coarse_attenuation.gain() // 1X/50X attenuation switch 195 | + self.amplification.gain() // LMH6518 pre-amplifier 196 | + self.fine_attenuation.gain() // LMH6518 ladder attenuator 197 | + 8.8600 // LMH6518 output amplifier 198 | + adc_coarse_gain // HMCAD1520 coarse gain 199 | - 0.3546 // HMCAD1520 full scale adjustment 200 | } 201 | } 202 | 203 | #[derive(Debug, Clone, Copy, PartialEq)] 204 | pub struct DeviceParameters { 205 | pub channels: [Option; 4], 206 | } 207 | 208 | impl Default for DeviceParameters { 209 | fn default() -> Self { 210 | DeviceParameters { 211 | channels: [Some(ChannelParameters::default()); 4] 212 | } 213 | } 214 | } 215 | 216 | impl DeviceParameters { 217 | /// Returns total gain in the instrument signal path for the given channel, in decibels. 218 | pub fn gain(&self, channel_index: usize) -> f32 { 219 | let channel_count = self.channels.iter().filter(|ch| ch.is_some()).count(); 220 | assert!(channel_count > 0 && self.channels[channel_index].is_some()); 221 | let adc_coarse_gain = match channel_count { 222 | 4 | 223 | 3 | 224 | 2 => 9.0, 225 | 1 => 10.0, 226 | _ => unreachable!() 227 | }; 228 | self.channels[channel_index].unwrap().gain(adc_coarse_gain) 229 | } 230 | 231 | /// Returns the voltage difference (as measured at the probe) between the most negative and 232 | /// most positive ADC code for the given channel, in volts. 233 | pub fn full_scale(&self, channel_index: usize) -> f32 { 234 | 2.0 * 10.0f32.powf(-self.gain(channel_index) / 20.0) 235 | } 236 | 237 | /// Converts a voltage (as measured at the probe) to the ADC code, saturating to the most 238 | /// negative or most positive code for out of range values. 239 | pub fn volts_to_code(&self, channel_index: usize, volts: f32) -> i8 { 240 | let full_scale = self.full_scale(channel_index); 241 | // Since Rust 1.45 this performs a saturating cast. Nice! 242 | (256.0 * (volts / full_scale)) as i8 243 | } 244 | 245 | /// Converts an ADC code to voltage (as measured at the probe). 246 | pub fn code_to_volts(&self, channel_index: usize, code: i8) -> f32 { 247 | let full_scale = self.full_scale(channel_index); 248 | code as f32 / 256.0 * full_scale 249 | } 250 | } 251 | 252 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 253 | pub struct ChannelCalibration { 254 | } 255 | 256 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 257 | pub struct DeviceCalibration { 258 | pub channels: [ChannelCalibration; 4], 259 | } 260 | 261 | impl DeviceParameters { 262 | pub fn derive(calibration: &DeviceCalibration, configuration: &DeviceConfiguration) -> Self { 263 | fn derive_channel(_calibration: &ChannelCalibration, 264 | configuration: &ChannelConfiguration) -> ChannelParameters { 265 | ChannelParameters { 266 | probe_attenuation: configuration.probe_attenuation, 267 | termination: configuration.termination, 268 | coupling: configuration.coupling, 269 | coarse_attenuation: CoarseAttenuation::X1, // FIXME 270 | amplification: Amplification::dB10, // FIXME 271 | fine_attenuation: FineAttenuation::dB20, // FIXME 272 | filtering: match configuration.bandwidth { 273 | Bandwidth::MHz100 => Filtering::MHz100, 274 | Bandwidth::MHz200 => Filtering::MHz200, 275 | Bandwidth::MHz350 => Filtering::MHz350, 276 | }, 277 | offset_magnitude: Default::default(), // FIXME 278 | offset_value: Default::default(), // FIXME 279 | } 280 | } 281 | 282 | DeviceParameters { 283 | channels: std::array::from_fn(|index| 284 | configuration.channels[index].map(|channel| 285 | derive_channel(&calibration.channels[index], &channel))) 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/regs/adc.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | pub const ADDR_HMCAD1520_RESET: u8 = 0x00; 4 | pub const ADDR_HMCAD1520_POWER: u8 = 0x0F; 5 | pub const ADDR_HMCAD1520_INVERT: u8 = 0x24; 6 | pub const ADDR_HMCAD1520_QUAD_GAIN: u8 = 0x2A; 7 | pub const ADDR_HMCAD1520_DUAL_GAIN: u8 = 0x2B; 8 | pub const ADDR_HMCAD1520_CHNUM_CLKDIV: u8 = 0x31; 9 | pub const ADDR_HMCAD1520_GAIN_CFG: u8 = 0x33; 10 | pub const ADDR_HMCAD1520_INSEL12: u8 = 0x3A; 11 | pub const ADDR_HMCAD1520_INSEL34: u8 = 0x3B; 12 | pub const ADDR_HMCAD1520_FS_CNTRL: u8 = 0x55; 13 | pub const ADDR_HMCAD1520_RES_SEL: u8 = 0x53; 14 | pub const ADDR_HMCAD1520_LVDS_PHASE: u8 = 0x42; 15 | pub const ADDR_HMCAD1520_LVDS_DRIVE: u8 = 0x11; 16 | pub const ADDR_HMCAD1520_LVDS_PATTERN: u8 = 0x25; 17 | -------------------------------------------------------------------------------- /src/regs/axi.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use bitflags::bitflags; 4 | 5 | /// Thunderscope Control Register 6 | pub const ADDR_CONTROL: usize = 0x0; 7 | 8 | bitflags! { 9 | // See [doc/datamover_register.txt] for details. 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | pub struct Control: u32 { 12 | const DatamoverHaltN = 1<<0; 13 | const FpgaAcqResetN = 1<<1; 14 | 15 | const ChannelMux0 = 1<<4; 16 | const ChannelMux1 = 1<<5; 17 | 18 | const Ch1Termination = 1<<12; 19 | const Ch2Termination = 1<<13; 20 | const Ch3Termination = 1<<14; 21 | const Ch4Termination = 1<<15; 22 | 23 | const Ch1Attenuator = 1<<16; 24 | const Ch2Attenuator = 1<<17; 25 | const Ch3Attenuator = 1<<18; 26 | const Ch4Attenuator = 1<<19; 27 | 28 | const Ch1Coupling = 1<<20; 29 | const Ch2Coupling = 1<<21; 30 | const Ch3Coupling = 1<<22; 31 | const Ch4Coupling = 1<<23; 32 | 33 | const Rail3V3Enabled = 1<<24; 34 | const ClockGenResetN = 1<<25; 35 | const Rail5VEnabled = 1<<26; 36 | } 37 | } 38 | 39 | impl Control { 40 | pub fn ch_termination(index: usize) -> Self { 41 | match index { 42 | 0 => Control::Ch1Termination, 43 | 1 => Control::Ch2Termination, 44 | 2 => Control::Ch3Termination, 45 | 3 => Control::Ch4Termination, 46 | _ => unreachable!() 47 | } 48 | } 49 | 50 | pub fn ch_coupling(index: usize) -> Self { 51 | match index { 52 | 0 => Control::Ch1Coupling, 53 | 1 => Control::Ch2Coupling, 54 | 2 => Control::Ch3Coupling, 55 | 3 => Control::Ch4Coupling, 56 | _ => unreachable!() 57 | } 58 | } 59 | 60 | pub fn ch_attenuator(index: usize) -> Self { 61 | match index { 62 | 0 => Control::Ch1Attenuator, 63 | 1 => Control::Ch2Attenuator, 64 | 2 => Control::Ch3Attenuator, 65 | 3 => Control::Ch4Attenuator, 66 | _ => unreachable!() 67 | } 68 | } 69 | } 70 | 71 | /// Thunderscope Status Register 72 | pub const ADDR_STATUS: usize = 0x8; 73 | 74 | bitflags! { 75 | // See [doc/transfer_counter_register.txt] for details. 76 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 77 | pub struct Status: u32 { 78 | const FifoOverflow = 1<<30; 79 | const DatamoverError = 1<<31; 80 | } 81 | } 82 | 83 | impl Status { 84 | pub fn overflow_cycles(&self) -> u32 { 85 | (self.bits() >> 16) & 0x3FFF 86 | } 87 | 88 | pub fn pages_moved(self) -> usize { 89 | ((self.bits() >> 0) & 0xFFFF) as usize 90 | } 91 | } 92 | 93 | // For the FIFO registers, see the documentation for the Xilinx AXI4-Stream FIFO v4.1 core. 94 | // /https://confluence.slac.stanford.edu/download/attachments/240276688/pg080-axi-fifo-mm-s.pdf 95 | 96 | /// FIFO Interrupt Status Register 97 | pub const ADDR_FIFO_ISR: usize = 0x00020000; 98 | 99 | bitflags! { 100 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 101 | pub struct FifoIsr: u32 { 102 | /// Receive FIFO Programmable Empty: Generated when the difference between the read and 103 | /// write pointers of the receive FIFO reaches the programmable EMPTY threshold value when 104 | /// the FIFO is being emptied 105 | const RFPE = 1<<19; 106 | /// Receive FIFO Programmable Full: This interrupt is generated when the difference between 107 | /// the read and write pointers of the receive FIFO reaches the programmable FULL threshold 108 | /// value. 109 | const RFPF = 1<<20; 110 | /// Transmit FIFO Programmable Empty: This interrupt is generated when the difference 111 | /// between the read and write pointers of the transmit FIFO reaches the programmable EMPTY 112 | /// threshold value when the FIFO is being emptied. 113 | const TFPE = 1<<21; 114 | /// Transmit FIFO Programmable Full: This interrupt is generated when the difference between 115 | /// the read and write pointers of the transmit FIFO reaches the programmable FULL threshold 116 | /// value. 117 | const TFPF = 1<<22; 118 | /// Receive Reset Complete: This interrupt indicates that a reset of the receive logic has 119 | /// completed. 120 | const RRC = 1<<23; 121 | /// Transmit Reset Complete: This interrupt indicates that a reset of the transmit logic has 122 | /// completed. 123 | const TRC = 1<<24; 124 | /// Transmit Size Error: This interrupt is generated if the number of words (including 125 | /// partial words in the count) written to the transmit data FIFO does not match the value 126 | /// written to the transmit length register (bytes) divided by 4/8 and rounded up to 127 | /// the higher integer value for trailing byte fractions. Interrupts occur only for mismatch 128 | /// of word count (including partial words). Interrupts do not occur due to mismatch of byte 129 | /// count. 130 | const TSE = 1<<25; 131 | /// Receive Complete: Indicates that at least one successful receive has completed and that 132 | /// the receive packet data and packet data length is available. This signal is not set for 133 | /// unsuccessful receives. This interrupt can represent more than one packet received, so it 134 | /// is important to check the receive data FIFO occupancy value to determine if additional 135 | /// receive packets are ready to be processed. 136 | const RC = 1<<26; 137 | /// Transmit Complete: Indicates that at least one transmit has completed. 138 | const TC = 1<<27; 139 | /// Transmit Packet Overrun Error: This interrupt is generated if an attempt is made to 140 | /// write to the transmit data FIFO when it is full. A reset of the transmit logic is 141 | /// required to recover. 142 | const TPOE = 1<<28; 143 | /// Receive Packet Underrun Error: This interrupt occurs when an attempt is made to read 144 | /// the receive FIFO when it is empty. The data read is not valid. A reset of the receive 145 | /// logic is required to recover. 146 | const RPUE = 1<<29; 147 | /// Receive Packet Overrun Read Error: This interrupt occurs when more words are read from 148 | /// the receive data FIFO than are in the packet being processed. Even though the FIFO is 149 | /// not empty, the read has gone beyond the current packet and removed the data from 150 | /// the next packet. A reset of the receive logic is required to recover. 151 | const RPORE = 1<<30; 152 | /// Receive Packet Underrun Read Error: This interrupt occurs when an attempt is made to 153 | /// read the receive length register when it is empty. The data read is not valid. A reset 154 | /// of the receive logic is required to recover. 155 | const RPURE = 1<<31; 156 | } 157 | } 158 | 159 | /// FIFO Interrupt Enable Register 160 | pub const ADDR_FIFO_IER: usize = 0x00020004; 161 | 162 | /// FIFO Transmit Reset Register 163 | pub const ADDR_FIFO_TDFR: usize = 0x00020008; 164 | 165 | // FIFO Transmit Vacancy Register 166 | pub const ADDR_FIFO_TDFV: usize = 0x0002000c; 167 | 168 | /// FIFO Transmit Data Register 169 | pub const ADDR_FIFO_TDFD: usize = 0x00020010; 170 | 171 | /// FIFO Transmit Length Register 172 | pub const ADDR_FIFO_TLR: usize = 0x00020014; 173 | 174 | /// FIFO Transmit Destination Register 175 | pub const ADDR_FIFO_TDR: usize = 0x0002002C; 176 | -------------------------------------------------------------------------------- /src/regs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod axi; 2 | pub mod adc; 3 | -------------------------------------------------------------------------------- /src/sys/linux.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::{fs, io}; 3 | use libc::{c_int, c_void}; 4 | use crate::Result; 5 | 6 | #[derive(Debug)] 7 | struct Fd(c_int); 8 | 9 | impl Fd { 10 | fn open(path: &CStr) -> io::Result { 11 | unsafe { 12 | let fd = libc::open(path.as_ptr(), libc::O_RDWR); 13 | if fd == -1 { 14 | Err(io::Error::last_os_error()) 15 | } else { 16 | Ok(Fd(fd)) 17 | } 18 | } 19 | } 20 | 21 | fn read_at(&self, offset: usize, data: &mut [u8]) -> io::Result<()> { 22 | unsafe { 23 | let bytes_read = libc::pread( 24 | self.0, data.as_mut_ptr() as *mut c_void, data.len(), offset as i64) as usize; 25 | if bytes_read != data.len() { 26 | Err(io::Error::last_os_error()) 27 | } else { 28 | Ok(()) 29 | } 30 | } 31 | } 32 | 33 | fn write_at(&self, offset: usize, data: &[u8]) -> io::Result<()> { 34 | unsafe { 35 | let bytes_written = libc::pwrite( 36 | self.0, data.as_ptr() as *const c_void, data.len(), offset as i64) as usize; 37 | if bytes_written != data.len() { 38 | Err(io::Error::last_os_error()) 39 | } else { 40 | Ok(()) 41 | } 42 | } 43 | } 44 | } 45 | 46 | impl Drop for Fd { 47 | fn drop(&mut self) { 48 | unsafe { 49 | if libc::close(self.0) == -1 { 50 | panic!("error closing fd: {}", io::Error::last_os_error()) 51 | } 52 | } 53 | } 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct DriverData { 58 | user_fd: Fd, 59 | c2h_fd: Fd, 60 | } 61 | 62 | pub fn open(device_path: &str) -> Result { 63 | let control_path = device_path.to_owned() + "_control"; 64 | if fs::metadata(control_path).is_ok() { 65 | let user_path = CString::new(device_path.to_owned() + "_user").unwrap(); 66 | let d2h_path = CString::new(device_path.to_owned() + "_c2h_0").unwrap(); 67 | Ok(DriverData { 68 | user_fd: Fd::open(user_path.as_ref())?, 69 | c2h_fd: Fd::open(d2h_path.as_ref())?, 70 | }) 71 | } else { 72 | Err(crate::Error::NotFound) 73 | } 74 | } 75 | 76 | pub fn read_user(driver_data: &DriverData, addr: usize, data: &mut [u8]) -> Result<()> { 77 | Ok(driver_data.user_fd.read_at(addr, data)?) 78 | } 79 | 80 | pub fn write_user(driver_data: &DriverData, addr: usize, data: &[u8]) -> Result<()> { 81 | Ok(driver_data.user_fd.write_at(addr, data)?) 82 | } 83 | 84 | pub fn read_dma(driver_data: &DriverData, addr: usize, data: &mut [u8]) -> Result<()> { 85 | Ok(driver_data.c2h_fd.read_at(addr, data)?) 86 | } 87 | -------------------------------------------------------------------------------- /src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | #[cfg(all(feature = "hardware", any(target_os = "linux")))] 4 | #[path = "linux.rs"] 5 | mod imp; 6 | 7 | #[cfg(not(all(feature = "hardware", any(target_os = "linux"))))] 8 | #[path = "stub.rs"] 9 | mod imp; 10 | 11 | #[derive(Debug)] 12 | pub struct Driver(imp::DriverData); 13 | 14 | impl Driver { 15 | pub fn new(device_path: &str) -> Result { 16 | Ok(Self(imp::open(device_path)?)) 17 | } 18 | 19 | pub fn read_user(&self, addr: usize, data: &mut [u8]) -> Result<()> { 20 | imp::read_user(&self.0, addr, data) 21 | } 22 | 23 | pub fn write_user(&self, addr: usize, data: &[u8]) -> Result<()> { 24 | imp::write_user(&self.0, addr, data) 25 | } 26 | 27 | pub fn read_dma(&self, addr: usize, data: &mut [u8]) -> Result<()> { 28 | imp::read_dma(&self.0, addr, data) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/sys/stub.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | #[derive(Debug)] 4 | pub struct DriverData; 5 | 6 | pub fn open(_device_path: &str) -> Result { 7 | unimplemented!() 8 | } 9 | 10 | pub fn read_user(_driver_data: &DriverData, _addr: usize, _data: &mut [u8]) -> Result<()> { 11 | unimplemented!() 12 | } 13 | 14 | pub fn write_user(_driver_data: &DriverData, _addr: usize, _data: &[u8]) -> Result<()> { 15 | unimplemented!() 16 | } 17 | 18 | pub fn read_dma(_driver_data: &DriverData, _addr: usize, _data: &mut [u8]) -> Result<()> { 19 | unimplemented!() 20 | } 21 | -------------------------------------------------------------------------------- /src/trigger.rs: -------------------------------------------------------------------------------- 1 | //! Implements rising edge/falling edge/both edges trigger with hysteresis using SIMD operations. 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 4 | pub enum EdgeFilter { 5 | Rising = 0b01, 6 | Falling = 0b10, 7 | Both = 0b11, 8 | } 9 | 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | pub enum Edge { 12 | Rising = 0b01, 13 | Falling = 0b10, 14 | } 15 | 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 17 | enum State { 18 | Fresh, 19 | Below, 20 | Above 21 | } 22 | 23 | #[derive(Debug, Clone, Copy)] 24 | pub struct Trigger { 25 | state: State, 26 | level: i8, // if let Fresh = state { state = if sample < level { Below } else { Above } } 27 | below: i8, // if sample < below { state = Below } 28 | above: i8, // if sample > above { state = Above } 29 | } 30 | 31 | impl Trigger { 32 | /// Create a new trigger mechanism at `level`. 33 | /// 34 | /// The trigger mechanism detects an "above condition" when it processes a sample that is 35 | /// strictly above `level + hysteresis`, and a "below condition" when it processes a sample 36 | /// that is strictly below `level + hysteresis`. A rising edge is detected at the sample where 37 | /// a below condition crosses into an above condition, and a falling edge is detected at 38 | /// the sample where the below condition crosses into an above condition. 39 | /// 40 | /// Since `hysteresis` is applied to each half-scale individually with inequality comparisons, 41 | /// the total amount of hysteresis (the amount of LSBs the input value has to change by to 42 | /// overcome the memory of the trigger mechanism) is `1 + 2 * hysteresis`. 43 | /// 44 | /// For example, if `hysteresis` is 1 and `level` is `50`, when processing a stream of samples 45 | /// `[10, 49, 50, 51, 52, 53, 49, 48, 10]`, a rising edge is detected at sample #4 (value 52), 46 | /// and a falling edge is detected at sample #7 (value 48). 47 | /// 48 | /// The combination of level and hysteresis is clamped to the full scale such that no matter 49 | /// what `level` and `hysteresis` are set to, some sequence of sample values would cause 50 | /// a trigger to be detected. 51 | pub fn new(level: i8, hysteresis: u8) -> Trigger { 52 | Trigger { 53 | state: State::Fresh, 54 | level: level, 55 | below: level.saturating_sub_unsigned(hysteresis).max(-127), 56 | above: level.saturating_add_unsigned(hysteresis).min( 126), 57 | } 58 | } 59 | 60 | /// Reset the trigger 61 | /// 62 | /// After this method is called, the next sample will not cause an edge to be detected, 63 | /// regardless of what the value of the sample is, as if the trigger was re-created with 64 | /// the same parameters. 65 | pub fn reset(&mut self) { 66 | self.state = State::Fresh 67 | } 68 | 69 | /// Scan incoming data for edges. 70 | /// 71 | /// The return value indicates whether processing has ended because an edge has been detected, 72 | /// or because no more samples could been processed. If an edge has been detected, after 73 | /// the function returns, `samples` point to the sample that caused the edge to be detected. 74 | /// 75 | /// This function advances `samples` forward, moving past the samples that have been processed. 76 | /// Trigger processing is done on groups of samples, and any samples not fitting into a group 77 | /// of implementation dependent size (currently 16) are left unprocessed. 78 | pub fn scan(&mut self, samples: &mut &[i8], filter: EdgeFilter) -> Option { 79 | // Dispatch to the most efficient implementation. 80 | // Note that this dynamic dispatch is not quite as efficient as building with for example 81 | // `RUSTFLAGS="-C target-cpu=native"` because the `wide` crate will only use 128-bit 82 | // registers if AVX2 wasn't detected at compile time, but the difference is quite small. 83 | // https://github.com/Lokathor/wide/blob/d94cbeadceacb0d9ebe5f18caedf933e0d4398ad/src/i8x32_.rs#L3-L13 84 | if !cfg!(test) && is_x86_feature_detected!("avx2") { 85 | // SAFETY: The AVX2 function is called only if AVX2 is available, checked above. 86 | unsafe { self.scan_avx2(samples, filter) } 87 | } else if !cfg!(test) && is_x86_feature_detected!("avx") { 88 | // SAFETY: The AVX function is called only if AVX is available, checked above. 89 | unsafe { self.scan_avx(samples, filter) } 90 | } else { 91 | self.scan_generic(samples, filter) 92 | } 93 | } 94 | 95 | /// Like `scan`, but returns the amount of consumed samples. 96 | pub fn find(&mut self, mut samples: &[i8], filter: EdgeFilter) -> (usize, Option) { 97 | let len_before = samples.len(); 98 | let edge_opt = self.scan(&mut samples, filter); 99 | let len_after = samples.len(); 100 | (len_before - len_after, edge_opt) 101 | } 102 | } 103 | 104 | macro_rules! scan_impl { 105 | { < $simd_ty:ident > $( $decl:tt )+ } => { 106 | #[inline(never)] // makes assembly more readable; serves no other purpose 107 | $( $decl )+(&mut self, samples: &mut &[i8], filter: EdgeFilter) -> Option { 108 | // right now it is assumed that this function would be called with a holdoff of 109 | // the sample window size at least, i.e. that processing (00 ff)*8 with high 110 | // performance is not a design goal. if Nth trigger is implemented, this might have 111 | // to be changed. 112 | 113 | use wide::{$simd_ty, CmpGt, CmpLt}; 114 | const LANES: usize = wide::$simd_ty::LANES as usize; 115 | 116 | #[inline] // improves debug builds and makes assembly listings useful 117 | fn scan_for $simd_ty>(samples: &mut &[i8], predicate: P) -> bool { 118 | let mut found = false; 119 | let mut offset = 0; 120 | for &group in samples.array_chunks::() { 121 | let mask = predicate($simd_ty::new(group)); 122 | // rustc generates ctlz even if the increment is within the condition; might 123 | // as well lift it out of the condition 124 | offset += (mask.move_mask().trailing_zeros() as usize).min(LANES); 125 | if mask.any() { 126 | found = true; 127 | break 128 | } 129 | } 130 | *samples = &samples[offset.min(samples.len())..]; 131 | found 132 | } 133 | 134 | match (self.state, *samples) { 135 | (State::Fresh, []) => 136 | return None, 137 | (State::Fresh, [first_sample, next_samples @ ..]) => { 138 | self.state = if *first_sample < self.level { 139 | State::Below 140 | } else { 141 | State::Above 142 | }; 143 | *samples = next_samples; 144 | } 145 | _ => () 146 | } 147 | 148 | let above = $simd_ty::splat(self.above); 149 | let below = $simd_ty::splat(self.below); 150 | loop { 151 | debug_assert!(!matches!(self.state, State::Fresh)); 152 | let found = match self.state { 153 | State::Fresh => unreachable!(), 154 | State::Below => scan_for(samples, |group| group.cmp_gt(above)), 155 | State::Above => scan_for(samples, |group| group.cmp_lt(below)), 156 | }; 157 | if found { 158 | match self.state { 159 | // SAFETY: `self.state == State::Fresh` is handled in the `match` above. 160 | // (LLVM unconditionally elides _that_ arm and misses this one.) 161 | State::Fresh => unsafe { std::hint::unreachable_unchecked() }, 162 | State::Below => self.state = State::Above, // rising edge 163 | State::Above => self.state = State::Below, // falling edge 164 | }; 165 | match (self.state, filter) { 166 | (State::Above, EdgeFilter::Both | EdgeFilter::Rising) => 167 | return Some(Edge::Rising), 168 | (State::Below, EdgeFilter::Both | EdgeFilter::Falling) => 169 | return Some(Edge::Falling), 170 | _ => () 171 | } 172 | } else { 173 | return None 174 | } 175 | } 176 | } 177 | } 178 | } 179 | 180 | impl Trigger { 181 | scan_impl! { fn scan_generic } 182 | scan_impl! { #[target_feature(enable = "avx")] unsafe fn scan_avx } 183 | scan_impl! { #[target_feature(enable = "avx2")] unsafe fn scan_avx2 } 184 | } 185 | 186 | #[cfg(test)] 187 | mod test { 188 | use super::*; 189 | use Edge::*; 190 | use State::*; 191 | 192 | macro_rules! assert_trigger { 193 | ($trig:ident . scan ( $data:expr , $filter:ident ) = $result:expr; +$offset:expr; 194 | $before:pat => $after:pat ) => { 195 | let mut samples = $data.as_ref(); 196 | assert!(matches!($trig.state, $before)); 197 | assert_eq!($trig.find(&mut samples, EdgeFilter::$filter), ($offset, $result)); 198 | assert!(matches!($trig.state, $after)); 199 | }; 200 | } 201 | 202 | #[test] 203 | fn test_fresh_empty() { 204 | let mut trig = Trigger::new(50, 1); 205 | assert_trigger!(trig.scan(&[], Both) = None; +0; Fresh => Fresh); 206 | } 207 | 208 | #[test] 209 | fn test_fresh_above() { 210 | let mut trig = Trigger::new(50, 1); 211 | assert_trigger!(trig.scan(&[80], Both) = None; +1; Fresh => Above); 212 | assert_eq!(trig.above, 51); 213 | assert_eq!(trig.below, 49); 214 | } 215 | 216 | #[test] 217 | fn test_fresh_below() { 218 | let mut trig = Trigger::new(50, 1); 219 | assert_trigger!(trig.scan(&[10], Both) = None; +1; Fresh => Below); 220 | assert_eq!(trig.above, 51); 221 | assert_eq!(trig.below, 49); 222 | } 223 | 224 | fn prime_trigger(state: State) -> Trigger { 225 | let mut trig = Trigger::new(50, 1); 226 | match state { 227 | Fresh => {} 228 | Below => { trig.scan(&mut &[ 0][..], EdgeFilter::Both); } 229 | Above => { trig.scan(&mut &[127][..], EdgeFilter::Both); } 230 | } 231 | trig 232 | } 233 | 234 | #[test] 235 | fn test_short() { 236 | let mut trig = prime_trigger(Below); 237 | let data = &[10, 10, 10, 10]; 238 | assert_trigger!(trig.scan(data, Both) = None; +0; _ => Below); 239 | } 240 | 241 | const RISING_BLOCK: [i8; 16] = 242 | [10, 10, 10, 10, 10, 10, 10, 10, 10, 80, 80, 80, 80, 80, 80, 80]; 243 | 244 | #[test] 245 | fn test_rising_both() { 246 | let mut trig = prime_trigger(Below); 247 | assert_trigger!(trig.scan(RISING_BLOCK, Both) = Some(Rising); +9; _ => Above); 248 | } 249 | 250 | #[test] 251 | fn test_rising_only() { 252 | let mut trig = prime_trigger(Below); 253 | assert_trigger!(trig.scan(RISING_BLOCK, Rising) = Some(Rising); +9; _ => Above); 254 | } 255 | 256 | #[test] 257 | fn test_rising_excluded_short() { 258 | let mut trig = prime_trigger(Below); 259 | assert_trigger!(trig.scan(RISING_BLOCK, Falling) = None; +9; _ => Above); 260 | } 261 | 262 | #[test] 263 | fn test_rising_excluded_long() { 264 | let mut trig = prime_trigger(Below); 265 | let data = &[ 266 | 10, 10, 10, 10, 10, 10, 10, 10, 10, 80, 80, 80, 80, 80, 80, 80, 267 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 268 | ]; 269 | assert_trigger!(trig.scan(data, Falling) = None; +25; _ => Above); 270 | } 271 | 272 | #[test] 273 | fn test_rising_two_blocks() { 274 | let mut trig = prime_trigger(Below); 275 | let data = &[ 276 | 10, 10, 10, 10, 10, 10, 10, 10, 10, 80, 80, 80, 80, 80, 80, 80, 277 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 278 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 279 | ]; 280 | assert_trigger!(trig.scan(data, Falling) = None; +41; _ => Above); 281 | } 282 | 283 | #[test] 284 | fn test_rising_almost_two_blocks() { 285 | let mut trig = prime_trigger(Below); 286 | let data = &[ 287 | 10, 10, 10, 10, 10, 10, 10, 10, 10, 80, 80, 80, 80, 80, 80, 80, 288 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 289 | 80, 80, 80, 80, 80, 80, 80, 80, 290 | ]; 291 | assert_trigger!(trig.scan(data, Falling) = None; +25; _ => Above); 292 | } 293 | 294 | #[test] 295 | fn test_rising_within_dead_zone() { 296 | let mut trig = prime_trigger(Below); 297 | let data = &[ 298 | 10, 10, 10, 10, 10, 10, 10, 10, 10, 49, 49, 49, 49, 49, 49, 49, 299 | 49, 49, 49, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 300 | ]; 301 | assert_trigger!(trig.scan(data, Falling) = None; +32; _ => Below); 302 | } 303 | 304 | const FALLING_BLOCK: [i8; 16] = 305 | [80, 80, 80, 80, 80, 80, 80, 80, 80, 20, 20, 20, 20, 20, 20, 20]; 306 | 307 | #[test] 308 | fn test_falling_both() { 309 | let mut trig = prime_trigger(Above); 310 | assert_trigger!(trig.scan(&FALLING_BLOCK, Both) = Some(Falling); +9; _ => Below); 311 | } 312 | 313 | #[test] 314 | fn test_falling_only() { 315 | let mut trig = prime_trigger(Above); 316 | assert_trigger!(trig.scan(&FALLING_BLOCK, Falling) = Some(Falling); +9; _ => Below); 317 | } 318 | 319 | #[test] 320 | fn test_falling_excluded_short() { 321 | let mut trig = prime_trigger(Above); 322 | assert_trigger!(trig.scan(&FALLING_BLOCK, Rising) = None; +9; _ => Below); 323 | } 324 | 325 | #[test] 326 | fn test_falling_excluded_long() { 327 | let mut trig = prime_trigger(Above); 328 | let data = &[ 329 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 20, 20, 20, 20, 20, 20, 20, 330 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 331 | ]; 332 | assert_trigger!(trig.scan(data, Rising) = None; +25; _ => Below); 333 | } 334 | 335 | #[test] 336 | fn test_falling_two_blocks() { 337 | let mut trig = prime_trigger(Above); 338 | let data = &[ 339 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 20, 20, 20, 20, 20, 20, 20, 340 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 341 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 342 | ]; 343 | assert_trigger!(trig.scan(data, Rising) = None; +41; _ => Below); 344 | } 345 | 346 | #[test] 347 | fn test_falling_almost_two_blocks() { 348 | let mut trig = prime_trigger(Above); 349 | let data = &[ 350 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 20, 20, 20, 20, 20, 20, 20, 351 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 352 | 20, 20, 20, 20, 20, 20, 20, 20, 353 | ]; 354 | assert_trigger!(trig.scan(data, Rising) = None; +25; _ => Below); 355 | } 356 | 357 | #[test] 358 | fn test_falling_dead_zone() { // different from test_rising_dead_zone 359 | let mut trig = prime_trigger(Above); 360 | let data = &[ 361 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 50, 50, 50, 50, 50, 50, 50, 362 | 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 363 | ]; 364 | assert_trigger!(trig.scan(data, Rising) = None; +32; _ => Above); 365 | } 366 | 367 | #[test] 368 | fn test_hysteresis_extreme_high() { 369 | let mut trig = Trigger::new(0x7f, 3); 370 | let data = &[ 371 | 80, 80, 80, 80, 80, 80, 80, 80, 80, 127, 127, 127, 127, 127, 127, 127, 127 372 | ]; 373 | assert_trigger!(trig.scan(data, Rising) = Some(Rising); +9; _ => Above); 374 | } 375 | 376 | #[test] 377 | fn test_hysteresis_extreme_low() { 378 | let mut trig = Trigger::new(-128, 3); 379 | let data = &[ 380 | 80, 80, 80, 80, 80, 80, 80, 80, 80, -128, -128, -128, -128, -128, -128, -128, -128 381 | ]; 382 | assert_trigger!(trig.scan(data, Falling) = Some(Falling); +9; _ => Below); 383 | } 384 | 385 | #[test] 386 | fn test_bug_move_mask_must_be_cast_to_u16() { 387 | let mut trig = prime_trigger(Below); 388 | let data = &[ 389 | 1, 1, -1, -3, -4, -4, -4, -5, -4, -4, -2, -2, -2, -4, -5, -5, 390 | -5, -5, -4, -3, -3, -3, -4, -5, -5, -5, -5, -4, -4, 0, 14, 34, 391 | 53, 68, 77, 80, 80, 81, 83, 84, 82, 82, 82, 82, 82, 85, 88, 89, 392 | ]; 393 | println!("{:?}", trig); 394 | assert_trigger!(trig.scan(data, Rising) = Some(Rising); +32; _ => Above); 395 | } 396 | } 397 | --------------------------------------------------------------------------------