├── .cargo └── config.toml ├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── memory.x ├── rust-toolchain.toml └── src ├── clock.rs ├── demo.rs ├── demo ├── conway.rs └── universe.txt ├── dvi.rs ├── dvi ├── dma.rs ├── serializer.rs ├── timing.rs └── tmds.rs ├── dvi_differential.pio ├── link.rs ├── main.rs ├── pre_init.asm ├── render.asm ├── render.rs ├── render ├── font.rs ├── palette.rs ├── queue.rs ├── renderlist.rs └── swapcell.rs ├── scan.asm └── scanlist.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | # Choose a default "cargo run" tool: 3 | # - probe-run provides flashing and defmt via a hardware debugger, and stack unwind on panic 4 | # - elf2uf2-rs loads firmware over USB when the rp2040 is in boot mode 5 | # - "probe-rs-cli run" is similar to probe-run but it uses the latest probe-rs lib crate 6 | runner = "probe-run --chip RP2040" 7 | # runner = "elf2uf2-rs -d" 8 | # runner = "probe-rs-cli run --chip RP2040 --protocol swd" 9 | 10 | rustflags = [ 11 | "-C", "linker=flip-link", 12 | "-C", "link-arg=--nmagic", 13 | "-C", "link-arg=-Tlink.x", 14 | "-C", "link-arg=-Tdefmt.x", 15 | 16 | "-C", "target-cpu=cortex-m0plus", 17 | 18 | # Code-size optimizations. 19 | # trap unreachable can save a lot of space, but requires nightly compiler. 20 | # uncomment the next line if you wish to enable it 21 | # "-Z", "trap-unreachable=no", 22 | "-C", "inline-threshold=5", 23 | "-C", "no-vectorize-loops", 24 | ] 25 | 26 | [build] 27 | target = "thumbv6m-none-eabi" 28 | 29 | [env] 30 | DEFMT_LOG = "trace" 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pio linguist-language=Assembly 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // The format of this file is specified in https://probe.rs/docs/tools/vscode/#start-a-debug-session-with-minimum-configuration 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "preLaunchTask": "rust: cargo build", 7 | "type": "probe-rs-debug", 8 | "request": "launch", 9 | "name": "probe_rs", 10 | "cwd": "${workspaceFolder}", 11 | "chip": "rp2040", 12 | // RP2040 doesn't support connectUnderReset 13 | "connectUnderReset": false, 14 | "speed": 4000, 15 | "runtimeExecutable": "probe-rs-debugger", 16 | "runtimeArgs": [ 17 | "debug" 18 | ], 19 | "flashingConfig": { 20 | "flashingEnabled": true, 21 | "resetAfterFlashing": true, 22 | "haltAfterReset": true, 23 | }, 24 | "coreConfigs": [ 25 | { 26 | "coreIndex": 0, 27 | "programBinary": "target/thumbv6m-none-eabi/debug/pico-dvi-rs", 28 | "chip": "RP2040", 29 | // Uncomment this if you've downloaded the SVD from 30 | // https://github.com/raspberrypi/pico-sdk/raw/1.3.1/src/rp2040/hardware_regs/rp2040.svd 31 | // and placed it in the .vscode directory 32 | // "svdFile": "./.vscode/rp2040.svd", 33 | "rttEnabled": true, 34 | "options": { 35 | "env": { 36 | "DEFMT_LOG": "debug" 37 | } 38 | }, 39 | } 40 | ], 41 | "consoleLogLevel": "Info", //Error, Warn, Info, Debug, Trace 42 | "wireProtocol": "Swd" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "thumbv6m-none-eabi", 3 | "rust-analyzer.check.allTargets": false, 4 | "cSpell.words": [ 5 | "rosc", 6 | "scanline", 7 | "sysinfo", 8 | "Tmds", 9 | "xosc" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "arrayvec" 16 | version = "0.7.4" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 19 | 20 | [[package]] 21 | name = "ascii-canvas" 22 | version = "3.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 25 | dependencies = [ 26 | "term", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.1.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 34 | 35 | [[package]] 36 | name = "bare-metal" 37 | version = "0.2.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 40 | dependencies = [ 41 | "rustc_version", 42 | ] 43 | 44 | [[package]] 45 | name = "bit-set" 46 | version = "0.5.3" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 49 | dependencies = [ 50 | "bit-vec", 51 | ] 52 | 53 | [[package]] 54 | name = "bit-vec" 55 | version = "0.6.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 58 | 59 | [[package]] 60 | name = "bitfield" 61 | version = "0.13.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 64 | 65 | [[package]] 66 | name = "bitflags" 67 | version = "1.3.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 70 | 71 | [[package]] 72 | name = "bitflags" 73 | version = "2.3.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" 76 | 77 | [[package]] 78 | name = "cc" 79 | version = "1.0.79" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 82 | 83 | [[package]] 84 | name = "cfg-if" 85 | version = "1.0.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 88 | 89 | [[package]] 90 | name = "codespan-reporting" 91 | version = "0.11.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 94 | dependencies = [ 95 | "termcolor", 96 | "unicode-width", 97 | ] 98 | 99 | [[package]] 100 | name = "cortex-m" 101 | version = "0.7.7" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" 104 | dependencies = [ 105 | "bare-metal", 106 | "bitfield", 107 | "embedded-hal", 108 | "volatile-register", 109 | ] 110 | 111 | [[package]] 112 | name = "cortex-m-rt" 113 | version = "0.7.3" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" 116 | dependencies = [ 117 | "cortex-m-rt-macros", 118 | ] 119 | 120 | [[package]] 121 | name = "cortex-m-rt-macros" 122 | version = "0.7.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" 125 | dependencies = [ 126 | "proc-macro2", 127 | "quote", 128 | "syn 1.0.109", 129 | ] 130 | 131 | [[package]] 132 | name = "crc-any" 133 | version = "2.4.3" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "774646b687f63643eb0f4bf13dc263cb581c8c9e57973b6ddf78bda3994d88df" 136 | dependencies = [ 137 | "debug-helper", 138 | ] 139 | 140 | [[package]] 141 | name = "critical-section" 142 | version = "1.1.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" 145 | 146 | [[package]] 147 | name = "crunchy" 148 | version = "0.2.2" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 151 | 152 | [[package]] 153 | name = "debug-helper" 154 | version = "0.3.13" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" 157 | 158 | [[package]] 159 | name = "defmt" 160 | version = "0.3.5" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" 163 | dependencies = [ 164 | "bitflags 1.3.2", 165 | "defmt-macros", 166 | ] 167 | 168 | [[package]] 169 | name = "defmt-macros" 170 | version = "0.3.6" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" 173 | dependencies = [ 174 | "defmt-parser", 175 | "proc-macro-error", 176 | "proc-macro2", 177 | "quote", 178 | "syn 2.0.27", 179 | ] 180 | 181 | [[package]] 182 | name = "defmt-parser" 183 | version = "0.3.3" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" 186 | dependencies = [ 187 | "thiserror", 188 | ] 189 | 190 | [[package]] 191 | name = "defmt-rtt" 192 | version = "0.4.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "609923761264dd99ed9c7d209718cda4631c5fe84668e0f0960124cbb844c49f" 195 | dependencies = [ 196 | "critical-section", 197 | "defmt", 198 | ] 199 | 200 | [[package]] 201 | name = "defmt-test" 202 | version = "0.3.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "4df24f1ca104a0c1bce2047d8a21aa9fa29695e5d662118eb48daedb97edca88" 205 | dependencies = [ 206 | "cortex-m", 207 | "cortex-m-rt", 208 | "defmt", 209 | "defmt-test-macros", 210 | ] 211 | 212 | [[package]] 213 | name = "defmt-test-macros" 214 | version = "0.3.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "94a0dfea4063d72e1ba20494dfbc4667f67420869328cf3670b5824a38a22dc1" 217 | dependencies = [ 218 | "proc-macro2", 219 | "quote", 220 | "syn 1.0.109", 221 | ] 222 | 223 | [[package]] 224 | name = "diff" 225 | version = "0.1.13" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 228 | 229 | [[package]] 230 | name = "dirs-next" 231 | version = "2.0.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 234 | dependencies = [ 235 | "cfg-if", 236 | "dirs-sys-next", 237 | ] 238 | 239 | [[package]] 240 | name = "dirs-sys-next" 241 | version = "0.1.2" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 244 | dependencies = [ 245 | "libc", 246 | "redox_users", 247 | "winapi", 248 | ] 249 | 250 | [[package]] 251 | name = "either" 252 | version = "1.9.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 255 | 256 | [[package]] 257 | name = "embedded-alloc" 258 | version = "0.5.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "8931e47e33c5d3194fbcf9cc82df0919193bd2fa40008f388eb1d28fd9c9ea6b" 261 | dependencies = [ 262 | "critical-section", 263 | "linked_list_allocator", 264 | ] 265 | 266 | [[package]] 267 | name = "embedded-dma" 268 | version = "0.2.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" 271 | dependencies = [ 272 | "stable_deref_trait", 273 | ] 274 | 275 | [[package]] 276 | name = "embedded-hal" 277 | version = "0.2.7" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 280 | dependencies = [ 281 | "nb 0.1.3", 282 | "void", 283 | ] 284 | 285 | [[package]] 286 | name = "ena" 287 | version = "0.14.2" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" 290 | dependencies = [ 291 | "log", 292 | ] 293 | 294 | [[package]] 295 | name = "errno" 296 | version = "0.3.1" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 299 | dependencies = [ 300 | "errno-dragonfly", 301 | "libc", 302 | "windows-sys", 303 | ] 304 | 305 | [[package]] 306 | name = "errno-dragonfly" 307 | version = "0.1.2" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 310 | dependencies = [ 311 | "cc", 312 | "libc", 313 | ] 314 | 315 | [[package]] 316 | name = "fixedbitset" 317 | version = "0.4.2" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 320 | 321 | [[package]] 322 | name = "frunk" 323 | version = "0.4.2" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "11a351b59e12f97b4176ee78497dff72e4276fb1ceb13e19056aca7fa0206287" 326 | dependencies = [ 327 | "frunk_core", 328 | "frunk_derives", 329 | ] 330 | 331 | [[package]] 332 | name = "frunk_core" 333 | version = "0.4.2" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "af2469fab0bd07e64ccf0ad57a1438f63160c69b2e57f04a439653d68eb558d6" 336 | 337 | [[package]] 338 | name = "frunk_derives" 339 | version = "0.4.2" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" 342 | dependencies = [ 343 | "frunk_proc_macro_helpers", 344 | "quote", 345 | "syn 2.0.27", 346 | ] 347 | 348 | [[package]] 349 | name = "frunk_proc_macro_helpers" 350 | version = "0.1.2" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "35b54add839292b743aeda6ebedbd8b11e93404f902c56223e51b9ec18a13d2c" 353 | dependencies = [ 354 | "frunk_core", 355 | "proc-macro2", 356 | "quote", 357 | "syn 2.0.27", 358 | ] 359 | 360 | [[package]] 361 | name = "fugit" 362 | version = "0.3.7" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" 365 | dependencies = [ 366 | "defmt", 367 | "gcd", 368 | ] 369 | 370 | [[package]] 371 | name = "gcd" 372 | version = "2.3.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" 375 | 376 | [[package]] 377 | name = "getrandom" 378 | version = "0.2.10" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 381 | dependencies = [ 382 | "cfg-if", 383 | "libc", 384 | "wasi", 385 | ] 386 | 387 | [[package]] 388 | name = "hashbrown" 389 | version = "0.12.3" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 392 | 393 | [[package]] 394 | name = "hermit-abi" 395 | version = "0.3.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 398 | 399 | [[package]] 400 | name = "indexmap" 401 | version = "1.9.3" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 404 | dependencies = [ 405 | "autocfg", 406 | "hashbrown", 407 | ] 408 | 409 | [[package]] 410 | name = "is-terminal" 411 | version = "0.4.9" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 414 | dependencies = [ 415 | "hermit-abi", 416 | "rustix", 417 | "windows-sys", 418 | ] 419 | 420 | [[package]] 421 | name = "itertools" 422 | version = "0.10.5" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 425 | dependencies = [ 426 | "either", 427 | ] 428 | 429 | [[package]] 430 | name = "lalrpop" 431 | version = "0.19.12" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" 434 | dependencies = [ 435 | "ascii-canvas", 436 | "bit-set", 437 | "diff", 438 | "ena", 439 | "is-terminal", 440 | "itertools", 441 | "lalrpop-util", 442 | "petgraph", 443 | "regex", 444 | "regex-syntax 0.6.29", 445 | "string_cache", 446 | "term", 447 | "tiny-keccak", 448 | "unicode-xid", 449 | ] 450 | 451 | [[package]] 452 | name = "lalrpop-util" 453 | version = "0.19.12" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" 456 | dependencies = [ 457 | "regex", 458 | ] 459 | 460 | [[package]] 461 | name = "libc" 462 | version = "0.2.147" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 465 | 466 | [[package]] 467 | name = "linked_list_allocator" 468 | version = "0.10.5" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" 471 | 472 | [[package]] 473 | name = "linux-raw-sys" 474 | version = "0.4.3" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" 477 | 478 | [[package]] 479 | name = "lock_api" 480 | version = "0.4.10" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 483 | dependencies = [ 484 | "autocfg", 485 | "scopeguard", 486 | ] 487 | 488 | [[package]] 489 | name = "log" 490 | version = "0.4.19" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 493 | 494 | [[package]] 495 | name = "memchr" 496 | version = "2.5.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 499 | 500 | [[package]] 501 | name = "nb" 502 | version = "0.1.3" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 505 | dependencies = [ 506 | "nb 1.1.0", 507 | ] 508 | 509 | [[package]] 510 | name = "nb" 511 | version = "1.1.0" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 514 | 515 | [[package]] 516 | name = "new_debug_unreachable" 517 | version = "1.0.4" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 520 | 521 | [[package]] 522 | name = "num_enum" 523 | version = "0.5.11" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" 526 | dependencies = [ 527 | "num_enum_derive", 528 | ] 529 | 530 | [[package]] 531 | name = "num_enum_derive" 532 | version = "0.5.11" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" 535 | dependencies = [ 536 | "proc-macro2", 537 | "quote", 538 | "syn 1.0.109", 539 | ] 540 | 541 | [[package]] 542 | name = "once_cell" 543 | version = "1.18.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 546 | 547 | [[package]] 548 | name = "panic-probe" 549 | version = "0.3.1" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9" 552 | dependencies = [ 553 | "cortex-m", 554 | "defmt", 555 | ] 556 | 557 | [[package]] 558 | name = "parking_lot" 559 | version = "0.12.1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 562 | dependencies = [ 563 | "lock_api", 564 | "parking_lot_core", 565 | ] 566 | 567 | [[package]] 568 | name = "parking_lot_core" 569 | version = "0.9.8" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 572 | dependencies = [ 573 | "cfg-if", 574 | "libc", 575 | "redox_syscall 0.3.5", 576 | "smallvec", 577 | "windows-targets", 578 | ] 579 | 580 | [[package]] 581 | name = "paste" 582 | version = "1.0.14" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 585 | 586 | [[package]] 587 | name = "petgraph" 588 | version = "0.6.3" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" 591 | dependencies = [ 592 | "fixedbitset", 593 | "indexmap", 594 | ] 595 | 596 | [[package]] 597 | name = "phf_shared" 598 | version = "0.10.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 601 | dependencies = [ 602 | "siphasher", 603 | ] 604 | 605 | [[package]] 606 | name = "pico-dvi-rs" 607 | version = "0.0.0" 608 | dependencies = [ 609 | "cortex-m", 610 | "cortex-m-rt", 611 | "defmt", 612 | "defmt-rtt", 613 | "defmt-test", 614 | "embedded-alloc", 615 | "embedded-hal", 616 | "fugit", 617 | "panic-probe", 618 | "pio", 619 | "pio-proc", 620 | "rp-pico", 621 | ] 622 | 623 | [[package]] 624 | name = "pio" 625 | version = "0.2.1" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" 628 | dependencies = [ 629 | "arrayvec", 630 | "num_enum", 631 | "paste", 632 | ] 633 | 634 | [[package]] 635 | name = "pio-parser" 636 | version = "0.2.2" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "77532c2b8279aef98dfc7207ef15298a5a3d6b6cc76ccc8b65913d69f3a8dd6b" 639 | dependencies = [ 640 | "lalrpop", 641 | "lalrpop-util", 642 | "pio", 643 | "regex-syntax 0.6.29", 644 | ] 645 | 646 | [[package]] 647 | name = "pio-proc" 648 | version = "0.2.2" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "6b04dc870fb3a4fd8b3e4ca8c61b53bc8ac4eb78b66805d2b3c2e5c4829e0d7a" 651 | dependencies = [ 652 | "codespan-reporting", 653 | "lalrpop-util", 654 | "pio", 655 | "pio-parser", 656 | "proc-macro-error", 657 | "proc-macro2", 658 | "quote", 659 | "regex-syntax 0.6.29", 660 | "syn 1.0.109", 661 | ] 662 | 663 | [[package]] 664 | name = "precomputed-hash" 665 | version = "0.1.1" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 668 | 669 | [[package]] 670 | name = "proc-macro-error" 671 | version = "1.0.4" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 674 | dependencies = [ 675 | "proc-macro-error-attr", 676 | "proc-macro2", 677 | "quote", 678 | "syn 1.0.109", 679 | "version_check", 680 | ] 681 | 682 | [[package]] 683 | name = "proc-macro-error-attr" 684 | version = "1.0.4" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 687 | dependencies = [ 688 | "proc-macro2", 689 | "quote", 690 | "version_check", 691 | ] 692 | 693 | [[package]] 694 | name = "proc-macro2" 695 | version = "1.0.66" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 698 | dependencies = [ 699 | "unicode-ident", 700 | ] 701 | 702 | [[package]] 703 | name = "quote" 704 | version = "1.0.32" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 707 | dependencies = [ 708 | "proc-macro2", 709 | ] 710 | 711 | [[package]] 712 | name = "rand_core" 713 | version = "0.6.4" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 716 | 717 | [[package]] 718 | name = "redox_syscall" 719 | version = "0.2.16" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 722 | dependencies = [ 723 | "bitflags 1.3.2", 724 | ] 725 | 726 | [[package]] 727 | name = "redox_syscall" 728 | version = "0.3.5" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 731 | dependencies = [ 732 | "bitflags 1.3.2", 733 | ] 734 | 735 | [[package]] 736 | name = "redox_users" 737 | version = "0.4.3" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 740 | dependencies = [ 741 | "getrandom", 742 | "redox_syscall 0.2.16", 743 | "thiserror", 744 | ] 745 | 746 | [[package]] 747 | name = "regex" 748 | version = "1.9.1" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" 751 | dependencies = [ 752 | "aho-corasick", 753 | "memchr", 754 | "regex-automata", 755 | "regex-syntax 0.7.4", 756 | ] 757 | 758 | [[package]] 759 | name = "regex-automata" 760 | version = "0.3.3" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" 763 | dependencies = [ 764 | "aho-corasick", 765 | "memchr", 766 | "regex-syntax 0.7.4", 767 | ] 768 | 769 | [[package]] 770 | name = "regex-syntax" 771 | version = "0.6.29" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 774 | 775 | [[package]] 776 | name = "regex-syntax" 777 | version = "0.7.4" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 780 | 781 | [[package]] 782 | name = "rp-pico" 783 | version = "0.8.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "6341771e6f8e5d130b2b3cbc23435b7847761adf198af09f4b2a60407d43bd56" 786 | dependencies = [ 787 | "cortex-m-rt", 788 | "fugit", 789 | "rp2040-boot2", 790 | "rp2040-hal", 791 | "usb-device", 792 | ] 793 | 794 | [[package]] 795 | name = "rp2040-boot2" 796 | version = "0.3.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" 799 | dependencies = [ 800 | "crc-any", 801 | ] 802 | 803 | [[package]] 804 | name = "rp2040-hal" 805 | version = "0.9.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "6ec610f738b69100fbe75f3b835501b669d41c889ac9a62ef284f8e6d1f17385" 808 | dependencies = [ 809 | "cortex-m", 810 | "critical-section", 811 | "embedded-dma", 812 | "embedded-hal", 813 | "frunk", 814 | "fugit", 815 | "itertools", 816 | "nb 1.1.0", 817 | "paste", 818 | "pio", 819 | "rand_core", 820 | "rp2040-hal-macros", 821 | "rp2040-pac", 822 | "usb-device", 823 | "vcell", 824 | "void", 825 | ] 826 | 827 | [[package]] 828 | name = "rp2040-hal-macros" 829 | version = "0.1.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" 832 | dependencies = [ 833 | "cortex-m-rt", 834 | "proc-macro2", 835 | "quote", 836 | "syn 1.0.109", 837 | ] 838 | 839 | [[package]] 840 | name = "rp2040-pac" 841 | version = "0.5.0" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "12d9d8375815f543f54835d01160d4e47f9e2cae75f17ff8f1ec19ce1da96e4c" 844 | dependencies = [ 845 | "cortex-m", 846 | "cortex-m-rt", 847 | "critical-section", 848 | "vcell", 849 | ] 850 | 851 | [[package]] 852 | name = "rustc_version" 853 | version = "0.2.3" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 856 | dependencies = [ 857 | "semver", 858 | ] 859 | 860 | [[package]] 861 | name = "rustix" 862 | version = "0.38.4" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" 865 | dependencies = [ 866 | "bitflags 2.3.3", 867 | "errno", 868 | "libc", 869 | "linux-raw-sys", 870 | "windows-sys", 871 | ] 872 | 873 | [[package]] 874 | name = "rustversion" 875 | version = "1.0.14" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 878 | 879 | [[package]] 880 | name = "scopeguard" 881 | version = "1.2.0" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 884 | 885 | [[package]] 886 | name = "semver" 887 | version = "0.9.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 890 | dependencies = [ 891 | "semver-parser", 892 | ] 893 | 894 | [[package]] 895 | name = "semver-parser" 896 | version = "0.7.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 899 | 900 | [[package]] 901 | name = "siphasher" 902 | version = "0.3.10" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 905 | 906 | [[package]] 907 | name = "smallvec" 908 | version = "1.11.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 911 | 912 | [[package]] 913 | name = "stable_deref_trait" 914 | version = "1.2.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 917 | 918 | [[package]] 919 | name = "string_cache" 920 | version = "0.8.7" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 923 | dependencies = [ 924 | "new_debug_unreachable", 925 | "once_cell", 926 | "parking_lot", 927 | "phf_shared", 928 | "precomputed-hash", 929 | ] 930 | 931 | [[package]] 932 | name = "syn" 933 | version = "1.0.109" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 936 | dependencies = [ 937 | "proc-macro2", 938 | "quote", 939 | "unicode-ident", 940 | ] 941 | 942 | [[package]] 943 | name = "syn" 944 | version = "2.0.27" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" 947 | dependencies = [ 948 | "proc-macro2", 949 | "quote", 950 | "unicode-ident", 951 | ] 952 | 953 | [[package]] 954 | name = "term" 955 | version = "0.7.0" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 958 | dependencies = [ 959 | "dirs-next", 960 | "rustversion", 961 | "winapi", 962 | ] 963 | 964 | [[package]] 965 | name = "termcolor" 966 | version = "1.2.0" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 969 | dependencies = [ 970 | "winapi-util", 971 | ] 972 | 973 | [[package]] 974 | name = "thiserror" 975 | version = "1.0.44" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" 978 | dependencies = [ 979 | "thiserror-impl", 980 | ] 981 | 982 | [[package]] 983 | name = "thiserror-impl" 984 | version = "1.0.44" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" 987 | dependencies = [ 988 | "proc-macro2", 989 | "quote", 990 | "syn 2.0.27", 991 | ] 992 | 993 | [[package]] 994 | name = "tiny-keccak" 995 | version = "2.0.2" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 998 | dependencies = [ 999 | "crunchy", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "unicode-ident" 1004 | version = "1.0.11" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 1007 | 1008 | [[package]] 1009 | name = "unicode-width" 1010 | version = "0.1.10" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1013 | 1014 | [[package]] 1015 | name = "unicode-xid" 1016 | version = "0.2.4" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 1019 | 1020 | [[package]] 1021 | name = "usb-device" 1022 | version = "0.2.9" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" 1025 | 1026 | [[package]] 1027 | name = "vcell" 1028 | version = "0.1.3" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 1031 | 1032 | [[package]] 1033 | name = "version_check" 1034 | version = "0.9.4" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1037 | 1038 | [[package]] 1039 | name = "void" 1040 | version = "1.0.2" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1043 | 1044 | [[package]] 1045 | name = "volatile-register" 1046 | version = "0.2.1" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" 1049 | dependencies = [ 1050 | "vcell", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "wasi" 1055 | version = "0.11.0+wasi-snapshot-preview1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1058 | 1059 | [[package]] 1060 | name = "winapi" 1061 | version = "0.3.9" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1064 | dependencies = [ 1065 | "winapi-i686-pc-windows-gnu", 1066 | "winapi-x86_64-pc-windows-gnu", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "winapi-i686-pc-windows-gnu" 1071 | version = "0.4.0" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1074 | 1075 | [[package]] 1076 | name = "winapi-util" 1077 | version = "0.1.5" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1080 | dependencies = [ 1081 | "winapi", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "winapi-x86_64-pc-windows-gnu" 1086 | version = "0.4.0" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1089 | 1090 | [[package]] 1091 | name = "windows-sys" 1092 | version = "0.48.0" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1095 | dependencies = [ 1096 | "windows-targets", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "windows-targets" 1101 | version = "0.48.1" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 1104 | dependencies = [ 1105 | "windows_aarch64_gnullvm", 1106 | "windows_aarch64_msvc", 1107 | "windows_i686_gnu", 1108 | "windows_i686_msvc", 1109 | "windows_x86_64_gnu", 1110 | "windows_x86_64_gnullvm", 1111 | "windows_x86_64_msvc", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "windows_aarch64_gnullvm" 1116 | version = "0.48.0" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1119 | 1120 | [[package]] 1121 | name = "windows_aarch64_msvc" 1122 | version = "0.48.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1125 | 1126 | [[package]] 1127 | name = "windows_i686_gnu" 1128 | version = "0.48.0" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1131 | 1132 | [[package]] 1133 | name = "windows_i686_msvc" 1134 | version = "0.48.0" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1137 | 1138 | [[package]] 1139 | name = "windows_x86_64_gnu" 1140 | version = "0.48.0" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1143 | 1144 | [[package]] 1145 | name = "windows_x86_64_gnullvm" 1146 | version = "0.48.0" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1149 | 1150 | [[package]] 1151 | name = "windows_x86_64_msvc" 1152 | version = "0.48.0" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1155 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pico-dvi-rs" 3 | 4 | authors = ["Raph Levien ", "Zachary Kohnen "] 5 | description = "Bit-banged DVI video output on the Raspberry Pi Pico" 6 | license = "BSD-3-Clause" 7 | repository = "https://github.com/DusterTheFirst/pico-dvi-rs" 8 | 9 | edition = "2021" 10 | resolver = "2" 11 | 12 | publish = false 13 | version = "0.0.0" 14 | 15 | [dependencies] 16 | cortex-m = "0.7.7" 17 | cortex-m-rt = "0.7.3" 18 | embedded-alloc = "0.5" 19 | embedded-hal = "0.2.7" 20 | 21 | defmt = "0.3.2" 22 | defmt-rtt = "0.4.0" 23 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 24 | 25 | fugit = { version = "0.3.6", features = ["defmt"] } 26 | pio = "0.2.1" 27 | pio-proc = "0.2.1" 28 | rp-pico = { version = "0.8.0", features = ["rom-v2-intrinsics"] } 29 | 30 | # TODO: use for bitfield 31 | # bilge = "0.1.5" 32 | 33 | [dev-dependencies] 34 | defmt-test = "0.3.0" 35 | 36 | # cargo build/run 37 | [profile.dev] 38 | codegen-units = 1 39 | debug = 2 40 | debug-assertions = true 41 | incremental = false 42 | opt-level = 3 43 | overflow-checks = true 44 | 45 | # cargo build/run --release 46 | [profile.release] 47 | codegen-units = 1 48 | debug = 2 49 | debug-assertions = false 50 | incremental = false 51 | lto = 'fat' 52 | opt-level = 3 53 | overflow-checks = false 54 | 55 | # do not optimize proc-macro crates = faster builds from scratch 56 | [profile.dev.build-override] 57 | codegen-units = 8 58 | debug = false 59 | debug-assertions = false 60 | inherits = "release" 61 | opt-level = 0 62 | overflow-checks = false 63 | 64 | [profile.release.build-override] 65 | codegen-units = 8 66 | debug = false 67 | debug-assertions = false 68 | opt-level = 0 69 | overflow-checks = false 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Zachary Kohnen 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | BOOT2(rx) : ORIGIN = 0x10000000, LENGTH = 0x100 3 | FLASH(rx) : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 4 | RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256K 5 | SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k 6 | SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k 7 | } 8 | 9 | EXTERN(BOOT2_FIRMWARE) 10 | 11 | SECTIONS { 12 | /* ### Boot loader */ 13 | .boot2 ORIGIN(BOOT2) : { 14 | KEEP(*(.boot2)); 15 | } > BOOT2 16 | } INSERT BEFORE .text; 17 | 18 | SECTIONS { 19 | /* ### Main ram section */ 20 | .ram : { 21 | *(.ram .ram.*) 22 | . = ALIGN(4); 23 | } > RAM AT > FLASH 24 | } INSERT AFTER .data; 25 | 26 | SECTIONS { 27 | /* ### Small 4kb memory sections for high bandwidth code or data per core */ 28 | .scratch_x : { 29 | _scratch_x_start = .; 30 | *(.scratch_x .scratch_x.*) 31 | . = ALIGN(4); 32 | _scratch_x_end = .; 33 | } > SCRATCH_X AT > FLASH 34 | _scratch_x_source = LOADADDR(.scratch_x); 35 | 36 | .scratch_y : { 37 | _scratch_y_start = .; 38 | *(.scratch_y .scratch_y.*) 39 | . = ALIGN(4); 40 | _scratch_y_end = .; 41 | } > SCRATCH_Y AT > FLASH 42 | _scratch_y_source = LOADADDR(.scratch_y); 43 | } INSERT AFTER .rodata; 44 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["clippy", "llvm-tools-preview", "rust-src", "rustfmt"] 4 | targets = ["thumbv6m-none-eabi"] 5 | -------------------------------------------------------------------------------- /src/clock.rs: -------------------------------------------------------------------------------- 1 | use fugit::{KilohertzU32, MegahertzU32, RateExtU32}; 2 | use rp_pico::{ 3 | hal::{ 4 | clocks::{ClockSource, ClocksManager}, 5 | pll::{ 6 | self, 7 | common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, 8 | setup_pll_blocking, PLLConfig, PhaseLockedLoop, 9 | }, 10 | rosc::RingOscillator, 11 | xosc::{self, setup_xosc_blocking, CrystalOscillator}, 12 | Clock, Watchdog, 13 | }, 14 | pac, XOSC_CRYSTAL_FREQ, 15 | }; 16 | 17 | struct ClockCfg { 18 | vco_freq: KilohertzU32, 19 | post_div1: u32, 20 | post_div2: u32, 21 | } 22 | 23 | // Values taken from pico-sdk hardware_pll/include/hardware/pll.h 24 | const PICO_PLL_VCO_MIN_FREQ: MegahertzU32 = MegahertzU32::MHz(750); 25 | const PICO_PLL_VCO_MAX_FREQ: MegahertzU32 = MegahertzU32::MHz(1600); 26 | 27 | /// Determine PLL parameters for target frequency 28 | /// 29 | /// Logic is adapted from check_sys_clock_khz in pico-sdk 30 | #[doc(alias = "check_sys_clock_khz", alias = "vcocalc")] 31 | fn configure_sys_clock(requested_freq: KilohertzU32) -> Option { 32 | let crystal_freq: KilohertzU32 = XOSC_CRYSTAL_FREQ.Hz(); 33 | 34 | // Its called a feedback divider but it really is a clock multiplier 35 | // see 2.18.2 in rp2040-datasheet.pdf 36 | for feedback_divider in (16..=320).rev() { 37 | let vco_freq: KilohertzU32 = feedback_divider * crystal_freq; 38 | 39 | // Stop the loop since all consecutive numbers will also be less than this 40 | if vco_freq < PICO_PLL_VCO_MIN_FREQ { 41 | break; 42 | } 43 | 44 | if vco_freq > PICO_PLL_VCO_MAX_FREQ { 45 | continue; 46 | } 47 | 48 | for post_div1 in (1..=7).rev() { 49 | for post_div2 in (1..=post_div1).rev() { 50 | let divider = post_div1 * post_div2; 51 | 52 | let output_frequency: KilohertzU32 = vco_freq / divider; 53 | 54 | // Doing this instead of % to work around https://github.com/korken89/fugit/issues/41 55 | // Ensure the vco_freq is divisible by the clock dividers 56 | let vco_freq_divisible = output_frequency * divider == vco_freq; 57 | 58 | if output_frequency == requested_freq && vco_freq_divisible { 59 | return Some(ClockCfg { 60 | vco_freq, 61 | post_div1, 62 | post_div2, 63 | }); 64 | } 65 | } 66 | } 67 | } 68 | 69 | None 70 | } 71 | 72 | /// Since we need to overclock the pico, we need to set these clocks up ourselves 73 | pub fn init_clocks( 74 | xosc: pac::XOSC, 75 | rosc: pac::ROSC, 76 | clocks: pac::CLOCKS, 77 | pll_sys: pac::PLL_SYS, 78 | pll_usb: pac::PLL_USB, 79 | resets: &mut pac::RESETS, 80 | watchdog: &mut Watchdog, 81 | freq_khz: KilohertzU32, 82 | ) -> ClocksManager { 83 | // Enable the xosc 84 | let xosc = setup_xosc_blocking(xosc, XOSC_CRYSTAL_FREQ.Hz()) 85 | .expect("crystal oscillator should be configured"); 86 | 87 | let rosc = RingOscillator::new(rosc).initialize(); 88 | 89 | // Start tick in watchdog 90 | watchdog.enable_tick_generation((XOSC_CRYSTAL_FREQ / 1_000_000) as u8); 91 | 92 | let mut clocks = ClocksManager::new(clocks); 93 | 94 | let clk_cfg = configure_sys_clock(freq_khz); 95 | let pll_config = match clk_cfg { 96 | Some(ClockCfg { 97 | vco_freq, 98 | post_div1, 99 | post_div2, 100 | }) => PLLConfig { 101 | vco_freq: vco_freq.convert(), 102 | refdiv: 1, 103 | post_div1: post_div1 as u8, 104 | post_div2: post_div2 as u8, 105 | }, 106 | None => PLL_SYS_125MHZ, 107 | }; 108 | 109 | // INFO: Overclock to 10 * 25.175 MHz ~= 252 MHz for mandatory minimum DVI output resolution: VGA (640x480) @ 60 Hz 110 | // Section following comes from https://docs.rs/rp2040-hal/latest/rp2040_hal/clocks/index.html#usage-extended 111 | 112 | // Configure PLLs 113 | // REF FBDIV VCO POSTDIV 114 | // PLL SYS: 12 / 1 = 12MHz * 125 = 1512MHZ / 6 / 1 = 252MHz 115 | // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz 116 | let pll_sys = setup_pll_blocking( 117 | pll_sys, 118 | xosc.operating_frequency(), 119 | pll_config, 120 | &mut clocks, 121 | resets, 122 | ) 123 | .expect("sys pll should be configured"); 124 | let pll_usb = setup_pll_blocking( 125 | pll_usb, 126 | xosc.operating_frequency(), 127 | PLL_USB_48MHZ, 128 | &mut clocks, 129 | resets, 130 | ) 131 | .expect("sys pll should be configured"); 132 | 133 | let clocks = configure_clocks(clocks, xosc, pll_sys, pll_usb); 134 | 135 | // Disable Ring Oscillator 136 | rosc.disable(); 137 | 138 | clocks 139 | } 140 | 141 | fn configure_clocks( 142 | mut clocks: ClocksManager, 143 | xosc: CrystalOscillator, 144 | pll_sys: PhaseLockedLoop, 145 | pll_usb: PhaseLockedLoop, 146 | ) -> ClocksManager { 147 | // Configure clocks 148 | // CLK_REF = XOSC (12MHz) / 1 = 12MHz 149 | clocks 150 | .reference_clock 151 | .configure_clock(&xosc, xosc.get_freq()) 152 | .unwrap(); 153 | 154 | // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz 155 | clocks 156 | .system_clock 157 | .configure_clock(&pll_sys, pll_sys.get_freq()) 158 | .unwrap(); 159 | 160 | // CLK USB = PLL USB (48MHz) / 1 = 48MHz 161 | clocks 162 | .usb_clock 163 | .configure_clock(&pll_usb, pll_usb.get_freq()) 164 | .unwrap(); 165 | 166 | // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz 167 | clocks 168 | .adc_clock 169 | .configure_clock(&pll_usb, pll_usb.get_freq()) 170 | .unwrap(); 171 | 172 | // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz 173 | clocks 174 | .rtc_clock 175 | .configure_clock(&pll_usb, 46875u32.Hz()) 176 | .unwrap(); 177 | 178 | // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable 179 | // Normally choose clk_sys or clk_usb 180 | clocks 181 | .peripheral_clock 182 | .configure_clock(&clocks.system_clock, 12.MHz()) 183 | .unwrap(); 184 | 185 | clocks 186 | } 187 | -------------------------------------------------------------------------------- /src/demo.rs: -------------------------------------------------------------------------------- 1 | use alloc::format; 2 | use embedded_hal::digital::v2::ToggleableOutputPin; 3 | use rp_pico::hal::gpio::{FunctionSioOutput, Pin, PinId, PullDown}; 4 | 5 | use crate::{ 6 | dvi::VERTICAL_REPEAT, 7 | render::{end_display_list, rgb, start_display_list, BW_PALETTE, FONT_HEIGHT, GLOBAL_PALETTE}, 8 | }; 9 | 10 | use self::conway::GameOfLife; 11 | 12 | mod conway; 13 | 14 | const TILE_DATA: &[u32] = &[ 15 | 0x44444444, 0x44454444, 0x54545444, 0x44455454, 0x55555555, 0x55555555, 0x74754444, 0x74747474, 16 | 0x44454444, 0x44444444, 0x44454644, 0x44444444, 0x44454644, 0x44444444, 0x44454444, 0x44444444, 17 | 0x44454444, 0x44444444, 0x44455454, 0x54444444, 0x55555555, 0x55555555, 0x74747474, 0x74754474, 18 | 0x44444444, 0x44454444, 0x44444444, 0x44454444, 0x66444444, 0x44454444, 0x44444444, 0x44454444, 19 | ]; 20 | 21 | struct Counter { 22 | led_pin: Pin, 23 | count: u32, 24 | } 25 | 26 | impl Counter

{ 27 | // We might just want to move the led pin into the serializer, 28 | // but for the moment we let the app continue to own it. 29 | fn count(&mut self) { 30 | if self.count % 15 == 0 { 31 | self.led_pin.toggle().unwrap(); 32 | } 33 | self.count = self.count.wrapping_add(1); 34 | } 35 | } 36 | 37 | fn colorbars(counter: &Counter

) { 38 | let height = 480 / VERTICAL_REPEAT as u32; 39 | let (mut rb, mut sb) = start_display_list(); 40 | rb.begin_stripe(height - FONT_HEIGHT); 41 | rb.end_stripe(); 42 | sb.begin_stripe(320 / VERTICAL_REPEAT as u32); 43 | sb.solid(92, rgb(0xc0, 0xc0, 0xc0)); 44 | sb.solid(90, rgb(0xc0, 0xc0, 0)); 45 | sb.solid(92, rgb(0, 0xc0, 0xc0)); 46 | sb.solid(92, rgb(0, 0xc0, 0x0)); 47 | sb.solid(92, rgb(0xc0, 0, 0xc0)); 48 | sb.solid(90, rgb(0xc0, 0, 0)); 49 | sb.solid(92, rgb(0, 0, 0xc0)); 50 | sb.end_stripe(); 51 | sb.begin_stripe(40 / VERTICAL_REPEAT as u32); 52 | sb.solid(92, rgb(0, 0, 0xc0)); 53 | sb.solid(90, rgb(0x13, 0x13, 0x13)); 54 | sb.solid(92, rgb(0xc0, 0, 0xc0)); 55 | sb.solid(92, rgb(0x13, 0x13, 0x13)); 56 | sb.solid(92, rgb(0, 0xc0, 0xc0)); 57 | sb.solid(90, rgb(0x13, 0x13, 0x13)); 58 | sb.solid(92, rgb(0xc0, 0xc0, 0xc0)); 59 | sb.end_stripe(); 60 | sb.begin_stripe(120 / VERTICAL_REPEAT as u32 - FONT_HEIGHT); 61 | sb.solid(114, rgb(0, 0x21, 0x4c)); 62 | sb.solid(114, rgb(0xff, 0xff, 0xff)); 63 | sb.solid(114, rgb(0x32, 0, 0x6a)); 64 | sb.solid(116, rgb(0x13, 0x13, 0x13)); 65 | sb.solid(30, rgb(0x09, 0x09, 0x09)); 66 | sb.solid(30, rgb(0x13, 0x13, 0x13)); 67 | sb.solid(30, rgb(0x1d, 0x1d, 0x1d)); 68 | sb.solid(92, rgb(0x13, 0x13, 0x13)); 69 | sb.end_stripe(); 70 | rb.begin_stripe(FONT_HEIGHT); 71 | let text = format!("Hello pico-dvi-rs, frame {}", counter.count); 72 | let width = rb.text(&text); 73 | let width = width + width % 2; 74 | rb.end_stripe(); 75 | sb.begin_stripe(FONT_HEIGHT); 76 | sb.pal_1bpp(width, &BW_PALETTE); 77 | sb.solid(640 - width, rgb(0, 0, 0)); 78 | sb.end_stripe(); 79 | end_display_list(rb, sb); 80 | } 81 | 82 | fn tiles(counter: &Counter

) { 83 | let (mut rb, mut sb) = start_display_list(); 84 | let anim_frame = counter.count % 240; 85 | let (x_off, y_off) = if anim_frame < 60 { 86 | (anim_frame, 0) 87 | } else if anim_frame < 120 { 88 | (60, anim_frame - 60) 89 | } else if anim_frame < 180 { 90 | (180 - anim_frame, 60) 91 | } else { 92 | (0, 240 - anim_frame) 93 | }; 94 | let height = 480 / VERTICAL_REPEAT as u32; 95 | let mut y = 0; 96 | let tiled_height = height - FONT_HEIGHT; 97 | while y < tiled_height { 98 | let ystart = if y == 0 { y_off % 16 } else { 0 }; 99 | let this_height = (tiled_height - y).min(16 - ystart); 100 | rb.begin_stripe(this_height); 101 | let x = x_off % 16; 102 | let tile_top = &TILE_DATA[ystart as usize * 2..]; 103 | rb.tile64(tile_top, x, 16); 104 | let mut x = 16 - x; 105 | while x < 640 { 106 | let width = (640 - x).min(16); 107 | rb.tile64(tile_top, 0, width); 108 | x += width; 109 | } 110 | rb.end_stripe(); 111 | y += this_height; 112 | } 113 | sb.begin_stripe(tiled_height); 114 | unsafe { 115 | sb.pal_4bpp(640, &GLOBAL_PALETTE); 116 | } 117 | sb.end_stripe(); 118 | rb.begin_stripe(FONT_HEIGHT); 119 | let text = format!("Hello pico-dvi-rs, frame {}", counter.count); 120 | let width = rb.text(&text); 121 | let width = width + width % 2; 122 | rb.end_stripe(); 123 | sb.begin_stripe(FONT_HEIGHT); 124 | sb.pal_1bpp(width, &BW_PALETTE); 125 | sb.solid(640 - width, rgb(0, 0, 0)); 126 | sb.end_stripe(); 127 | end_display_list(rb, sb); 128 | } 129 | 130 | pub fn demo(led_pin: Pin) -> ! { 131 | let mut counter = Counter { led_pin, count: 0 }; 132 | let mut game_of_life = GameOfLife::new(include_str!("demo/universe.txt")); 133 | 134 | loop { 135 | for _ in 0..120 { 136 | counter.count(); 137 | colorbars(&counter); 138 | } 139 | for _ in 0..240 { 140 | counter.count(); 141 | tiles(&counter); 142 | } 143 | for i in 0..240 { 144 | counter.count(); 145 | 146 | if i % 5 == 0 { 147 | game_of_life.tick(); 148 | } 149 | game_of_life.render(&counter); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/demo/conway.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Range; 2 | 3 | use alloc::format; 4 | use rp_pico::hal::gpio::PinId; 5 | 6 | use super::Counter; 7 | use crate::{ 8 | dvi::VERTICAL_REPEAT, 9 | render::{ 10 | end_display_list, quantized_1bpp_palette, rgb, start_display_list, xrgb, PaletteEntry, 11 | BW_PALETTE, FONT_HEIGHT, 12 | }, 13 | }; 14 | 15 | // Sadly these can not be generic on GameOfLife struct due to limitations with const-generics 16 | const BOARD_WIDTH: usize = 420; 17 | const BOARD_HEIGHT: usize = 210; 18 | 19 | const fn div_ceil(numerator: usize, denominator: usize) -> usize { 20 | (numerator + denominator - 1) / denominator 21 | } 22 | 23 | const BOARD_WIDTH_WORDS: usize = div_ceil(BOARD_WIDTH, 32); 24 | 25 | pub struct GameOfLife { 26 | age: u32, 27 | universe: [u32; BOARD_WIDTH_WORDS * BOARD_HEIGHT], // TODO: pack? 28 | } 29 | 30 | impl GameOfLife { 31 | pub fn new(universe_seed: &str) -> Self { 32 | let center_x = BOARD_WIDTH / 2; 33 | let center_y = BOARD_HEIGHT / 2; 34 | 35 | let mut universe = [0; BOARD_WIDTH_WORDS * BOARD_HEIGHT]; 36 | 37 | let mut current_x = center_x; 38 | let mut current_y = center_y; 39 | 40 | for line in universe_seed.lines() { 41 | if line.is_empty() { 42 | continue; 43 | } 44 | 45 | if let Some(position) = line.strip_prefix("#P").map(str::trim) { 46 | let (x, y): (i32, i32) = position 47 | .split_once(' ') 48 | .map(|(x, y)| (x.parse().unwrap(), y.parse().unwrap())) 49 | .unwrap(); 50 | 51 | current_x = (center_x as i32 + x) as usize; 52 | current_y = (center_y as i32 + y) as usize; 53 | 54 | continue; 55 | } 56 | 57 | let line = line.as_bytes(); 58 | let current_x_word = current_x / 32; 59 | let current_x_bit = current_x % 32; 60 | let line_words = div_ceil(current_x_bit + line.len(), 32); 61 | 62 | let (unaligned_prefix, line) = 63 | line.split_at(usize::min(32 - current_x_bit, line.len())); 64 | let (aligned, unaligned_suffix) = line.split_at((line.len() / 32) * 32); 65 | 66 | fn chars_to_byte(chars: &[u8]) -> u32 { 67 | chars.iter().rev().fold(0, |word, byte| match byte { 68 | b'.' => word << 1, 69 | b'*' => (word << 1) | 0b1u32, 70 | _ => unimplemented!(), 71 | }) 72 | } 73 | 74 | let universe = &mut universe[current_x_word + current_y * BOARD_WIDTH_WORDS..]; 75 | 76 | let starting_word = if !unaligned_prefix.is_empty() { 77 | let unaligned_prefix = chars_to_byte(unaligned_prefix) << current_x_bit; 78 | universe[0] |= unaligned_prefix; // FIXME: zero these bits out first 79 | 80 | 1 81 | } else { 82 | 0 83 | }; 84 | 85 | let ending_word = if !unaligned_suffix.is_empty() { 86 | let unaligned_suffix = chars_to_byte(unaligned_suffix); 87 | universe[line_words - 1] |= unaligned_suffix; // FIXME: zero these bits out first 88 | 89 | line_words - 1 90 | } else { 91 | line_words 92 | }; 93 | 94 | let mut aligned = aligned.chunks_exact(32).map(chars_to_byte); 95 | universe[starting_word..ending_word].fill_with(|| aligned.next().unwrap()); 96 | 97 | current_y += 1; 98 | } 99 | 100 | let actual_size = core::mem::size_of::(); 101 | let actual_size_words = div_ceil(actual_size, 4); 102 | 103 | let line_waste = BOARD_WIDTH % 32; 104 | let total_waste = line_waste * BOARD_HEIGHT; 105 | let total_waste_words = div_ceil(total_waste, 32); 106 | 107 | let ideal_size = div_ceil(BOARD_WIDTH * BOARD_HEIGHT, 8) + 4; 108 | let ideal_size_words = div_ceil(ideal_size, 4); 109 | 110 | defmt::info!( 111 | "size_of::() = {=usize} words\nline_waste = {=usize} bits\nideal_size = {=usize} words\ntotal_waste = {=usize} words", 112 | actual_size_words, 113 | line_waste, 114 | ideal_size_words, 115 | total_waste_words 116 | ); 117 | 118 | GameOfLife { age: 0, universe } 119 | } 120 | 121 | pub fn tick(&mut self) { 122 | self.age += 1; 123 | 124 | const EMPTY_LINE: [u32; BOARD_WIDTH_WORDS] = [0; BOARD_WIDTH_WORDS]; 125 | 126 | let mut previous_new_line = EMPTY_LINE; 127 | let mut new_line = EMPTY_LINE; 128 | 129 | let mut current_range = None; 130 | let mut next_range = Some(0..BOARD_WIDTH_WORDS); 131 | 132 | for _ in 0..BOARD_HEIGHT { 133 | let previous_range = current_range; 134 | current_range = next_range.clone(); 135 | next_range = next_range.map(|Range { start, end }| Range { 136 | start: start + BOARD_WIDTH_WORDS, 137 | end: end + BOARD_WIDTH_WORDS, 138 | }); 139 | 140 | let previous_line = previous_range 141 | .clone() 142 | .and_then(|range| self.universe.get(range)) 143 | .unwrap_or(&EMPTY_LINE); 144 | let current_line = current_range 145 | .clone() 146 | .and_then(|range| self.universe.get(range)) 147 | .unwrap_or(&EMPTY_LINE); 148 | let next_line = next_range 149 | .clone() 150 | .and_then(|range| self.universe.get(range)) 151 | .unwrap_or(&EMPTY_LINE); 152 | 153 | fn straddle_mask_top(previous: u32, current: u32) -> u32 { 154 | previous & (0b1 << 31) | current & 0b11 155 | } 156 | fn mask(word: u32, bit: usize) -> u32 { 157 | word & (0b111 << (bit - 1)) 158 | } 159 | fn straddle_mask_bottom(current: u32, next: u32) -> u32 { 160 | current & (0b11 << 30) | next & 0b1 161 | } 162 | 163 | /// Rather than calling u32::count_ones() (`popcnt`) 3 times, we can put all 3 of the 164 | /// 3 bit integers into one word, and call count_ones on that 165 | /// 166 | /// This does result in significant assembly savings (https://godbolt.org/z/YKbEoab9d) 167 | /// but it might not be the most optimal 168 | fn neighbors(previous: u32, current: u32, next: u32) -> u32 { 169 | (previous | current.rotate_left(4) | next.rotate_right(4)).count_ones() 170 | } 171 | 172 | for word in 0..BOARD_WIDTH_WORDS { 173 | let previous_word = word.checked_sub(1); 174 | let next_word = if word < (BOARD_WIDTH_WORDS - 1) { 175 | Some(word + 1) 176 | } else { 177 | None 178 | }; 179 | 180 | let mut new_word = 0; 181 | 182 | new_word |= match neighbors( 183 | straddle_mask_top( 184 | previous_word 185 | .map(|word| previous_line[word]) 186 | .unwrap_or_default(), 187 | previous_line[word], 188 | ), 189 | straddle_mask_top( 190 | previous_word 191 | .map(|word| current_line[word]) 192 | .unwrap_or_default(), 193 | current_line[word], 194 | ), 195 | straddle_mask_top( 196 | previous_word 197 | .map(|word| next_line[word]) 198 | .unwrap_or_default(), 199 | next_line[word], 200 | ), 201 | ) { 202 | 3 => 0b1, 203 | 4 => current_line[word] & 0b1, 204 | _ => 0b0, 205 | }; 206 | 207 | { 208 | let previous_word = previous_line[word]; 209 | let current_word = current_line[word]; 210 | let next_word = next_line[word]; 211 | for bit in 1..31 { 212 | new_word |= match neighbors( 213 | mask(previous_word, bit), 214 | mask(current_word, bit), 215 | mask(next_word, bit), 216 | ) { 217 | 3 => 0b1 << bit, 218 | 4 => current_word & (0b1 << bit), 219 | _ => 0b0, 220 | }; 221 | } 222 | } 223 | new_word |= match neighbors( 224 | straddle_mask_bottom( 225 | previous_line[word], 226 | next_word 227 | .map(|word| previous_line[word]) 228 | .unwrap_or_default(), 229 | ), 230 | straddle_mask_bottom( 231 | current_line[word], 232 | next_word.map(|word| current_line[word]).unwrap_or_default(), 233 | ), 234 | straddle_mask_bottom( 235 | next_line[word], 236 | next_word.map(|word| next_line[word]).unwrap_or_default(), 237 | ), 238 | ) { 239 | 3 => 0b1 << 31, 240 | 4 => current_line[word] & (0b1 << 31), 241 | _ => 0b0, 242 | }; 243 | 244 | new_line[word] = new_word; 245 | } 246 | 247 | if let Some(range) = previous_range { 248 | self.universe[range].copy_from_slice(&previous_new_line); 249 | } 250 | previous_new_line = core::mem::take(&mut new_line); 251 | } 252 | 253 | // Apply the last new line 254 | if let Some(range) = current_range { 255 | self.universe[range].copy_from_slice(&previous_new_line); 256 | } 257 | } 258 | } 259 | 260 | const TEXT: u32 = 0xffffff; 261 | const BACKGROUND: u32 = 0x800080; 262 | const ALIVE: u32 = 0x00ffff; 263 | const DEAD: u32 = 0x303030; 264 | 265 | #[link_section = ".scratch_x"] 266 | pub static CONWAY_TEXT_PALETTE: [PaletteEntry; 4] = quantized_1bpp_palette(BACKGROUND, TEXT); 267 | 268 | #[link_section = ".scratch_x"] 269 | pub static CONWAY_PALETTE: [PaletteEntry; 4] = quantized_1bpp_palette(DEAD, ALIVE); 270 | 271 | impl GameOfLife { 272 | pub(super) fn render(&self, counter: &Counter

) { 273 | let height = 480 / VERTICAL_REPEAT as u32; 274 | let width = 640; 275 | let background = xrgb(BACKGROUND); 276 | let (mut rb, mut sb) = start_display_list(); 277 | 278 | let horizontal_padding = width - BOARD_WIDTH as u32; 279 | let padding_left = horizontal_padding / 2; 280 | let padding_right = padding_left + (horizontal_padding & 0b1); // Deal with odd padding 281 | 282 | let vertical_padding = height - BOARD_HEIGHT as u32; 283 | let padding_top = vertical_padding / 2; 284 | let padding_bottom = padding_top + (vertical_padding & 0b1); // Deal with odd padding 285 | 286 | rb.begin_stripe(padding_top); 287 | rb.end_stripe(); 288 | sb.begin_stripe(padding_top); 289 | sb.solid(width, background); 290 | sb.end_stripe(); 291 | 292 | rb.begin_stripe(BOARD_HEIGHT as u32); 293 | rb.blit_1bpp( 294 | &self.universe, 295 | BOARD_WIDTH_WORDS, 296 | BOARD_WIDTH_WORDS as u32 * 4, 297 | ); 298 | rb.end_stripe(); 299 | sb.begin_stripe(BOARD_HEIGHT as u32); 300 | sb.solid(padding_left, background); 301 | sb.pal_1bpp(BOARD_WIDTH as u32, &CONWAY_PALETTE); 302 | sb.solid(padding_right, background); 303 | sb.end_stripe(); 304 | 305 | rb.begin_stripe(padding_bottom - FONT_HEIGHT * 2); 306 | rb.end_stripe(); 307 | sb.begin_stripe(padding_bottom - FONT_HEIGHT * 2); 308 | sb.solid(width, background); 309 | sb.end_stripe(); 310 | 311 | { 312 | rb.begin_stripe(FONT_HEIGHT); 313 | let text = format!("Conway's Game of life, age: {}", self.age); 314 | let text_width = rb.text(&text); 315 | let text_width = text_width + text_width % 2; 316 | rb.end_stripe(); 317 | sb.begin_stripe(FONT_HEIGHT); 318 | sb.pal_1bpp(text_width, &CONWAY_TEXT_PALETTE); 319 | sb.solid(width - text_width, background); 320 | sb.end_stripe(); 321 | rb.begin_stripe(FONT_HEIGHT); 322 | let text = format!("Hello pico-dvi-rs, frame {}", counter.count); 323 | let text_width = rb.text(&text); 324 | let text_width = text_width + text_width % 2; 325 | rb.end_stripe(); 326 | sb.begin_stripe(FONT_HEIGHT); 327 | sb.pal_1bpp(text_width, &BW_PALETTE); 328 | sb.solid(width - text_width, rgb(0x00, 0x00, 0x00)); 329 | sb.end_stripe(); 330 | } 331 | end_display_list(rb, sb); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/demo/universe.txt: -------------------------------------------------------------------------------- 1 | #P -70 -90 2 | ..***...***............***...***..........***...***..........***...***..........***...***..........***...***..........***...***..........***...***.. 3 | .................................................................................................................................................... 4 | *....*.*....*........*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....* 5 | *....*.*....*........*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....* 6 | *....*.*....*........*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....* 7 | ..***...***............***...***..........***...***..........***...***..........***...***..........***...***..........***...***..........***...***.. 8 | .................................................................................................................................................... 9 | ..***...***............***...***..........***...***..........***...***..........***...***..........***...***..........***...***..........***...***.. 10 | *....*.*....*........*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....* 11 | *....*.*....*........*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....* 12 | *....*.*....*........*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....*......*....*.*....* 13 | .................................................................................................................................................... 14 | ..***...***............***...***..........***...***..........***...***..........***...***..........***...***..........***...***..........***...***.. 15 | 16 | 17 | #P 0 0 18 | .**...**....**...**....* 19 | .**..*..*..*..*..*.*..*.* 20 | ......**....*.*...*....* 21 | .............* 22 | . 23 | . 24 | ..*.........** 25 | ..*....***..** 26 | ..*...***.....** 27 | ..............** 28 | ................ 29 | . 30 | . 31 | . 32 | . 33 | . 34 | . 35 | ....*** 36 | ....*.* 37 | ....*** 38 | ....*** 39 | ....*** 40 | ....*** 41 | ....*.* 42 | ....*** 43 | . 44 | . 45 | . 46 | . 47 | . 48 | . 49 | .**...**....**...**....* 50 | .**..*..*..*..*..*.*..*.* 51 | ......**....*.*...*....* 52 | .............* -------------------------------------------------------------------------------- /src/dvi.rs: -------------------------------------------------------------------------------- 1 | pub mod dma; 2 | pub mod serializer; 3 | pub mod timing; 4 | pub mod tmds; 5 | 6 | use alloc::boxed::Box; 7 | use cortex_m::peripheral::NVIC; 8 | use rp_pico::hal::{ 9 | gpio::PinId, 10 | pac::Interrupt, 11 | pio, 12 | pwm::{self, ValidPwmOutputPin}, 13 | }; 14 | 15 | use crate::{ 16 | pac::interrupt, 17 | render::{render_line, ScanRender, CORE1_QUEUE, N_LINE_BUFS}, 18 | DVI_INST, 19 | }; 20 | 21 | use self::{ 22 | dma::{DmaChannelList, DmaChannels}, 23 | serializer::DviSerializer, 24 | timing::{DviScanlineDmaList, DviTiming, DviTimingLineState, DviTimingState}, 25 | tmds::TmdsPair, 26 | }; 27 | 28 | /// Number of channels rendered. 29 | /// 30 | /// This is usually 3 for RGB, but can also be 1 for grayscale, in which case 31 | /// the TMDS buffer is output to all three channels. 32 | pub const N_CHANNELS: usize = 3; 33 | pub const VERTICAL_REPEAT: usize = 1; 34 | 35 | /// The additional time (in scanlines) for the TMDS encoding routine. 36 | /// 37 | /// If TMDS encoding can reliably happen in less than one scanline time, 38 | /// this should be 0. If there is variance that sometimes pushes it over 39 | /// the line, then a value of 1 may eliminate artifacts. 40 | const TMDS_PIPELINE_SLACK: u32 = 0; 41 | 42 | const N_TMDS_BUFFERS: usize = if TMDS_PIPELINE_SLACK > 0 && VERTICAL_REPEAT == 1 { 43 | 3 44 | } else { 45 | 2 46 | }; 47 | 48 | /// Dynamic state for DVI output. 49 | /// 50 | /// This struct corresponds reasonably closely to `struct dvi_inst` in the 51 | /// PicoDVI source, but with the focused role of holding state needing to 52 | /// be accessed by the interrupt handler. 53 | pub struct DviInst { 54 | timing: DviTiming, 55 | timing_state: DviTimingState, 56 | channels: DmaChannels, 57 | 58 | dma_list_vblank_sync: DviScanlineDmaList, 59 | dma_list_vblank_nosync: DviScanlineDmaList, 60 | dma_list_active: DviScanlineDmaList, 61 | dma_list_error: DviScanlineDmaList, 62 | 63 | tmds_buf: Box<[TmdsPair]>, 64 | available: [bool; N_TMDS_BUFFERS], 65 | scan_render: ScanRender, 66 | } 67 | 68 | impl DviInst { 69 | pub fn new(timing: DviTiming, channels: DmaChannels) -> Self { 70 | let buf_size = timing.horizontal_words() as usize * N_CHANNELS * N_TMDS_BUFFERS; 71 | let buf = alloc::vec![TmdsPair::encode_balanced_approx(0); buf_size]; 72 | DviInst { 73 | timing, 74 | timing_state: Default::default(), 75 | channels, 76 | dma_list_vblank_sync: Default::default(), 77 | dma_list_vblank_nosync: Default::default(), 78 | dma_list_active: Default::default(), 79 | dma_list_error: Default::default(), 80 | tmds_buf: buf.into(), 81 | available: [false; N_TMDS_BUFFERS], 82 | scan_render: ScanRender::new(), 83 | } 84 | } 85 | 86 | pub fn setup_dma(&mut self) { 87 | self.dma_list_vblank_sync.setup_scanline( 88 | &self.timing, 89 | &self.channels, 90 | DviTimingLineState::Sync, 91 | false, 92 | ); 93 | self.dma_list_vblank_nosync.setup_scanline( 94 | &self.timing, 95 | &self.channels, 96 | DviTimingLineState::FrontPorch, 97 | false, 98 | ); 99 | self.dma_list_active.setup_scanline( 100 | &self.timing, 101 | &self.channels, 102 | DviTimingLineState::Active, 103 | true, 104 | ); 105 | self.dma_list_error.setup_scanline( 106 | &self.timing, 107 | &self.channels, 108 | DviTimingLineState::Active, 109 | false, 110 | ); 111 | } 112 | 113 | // Note: does not start serializer 114 | pub fn start(&mut self) { 115 | self.channels.load_op(&self.dma_list_vblank_nosync); 116 | self.channels.start(); 117 | } 118 | 119 | /// Determine whether a line is available to scan into TMDS. 120 | /// 121 | /// If a TMDS render is to be scheduled this scanline, return the 122 | /// scanline number and a boolean indicating whether the line buffer 123 | /// is available. 124 | /// 125 | /// If no TMDS render is to be scheduled, the scanline number is 126 | /// `!0`. 127 | /// 128 | /// This method also updates the `available` table internally. 129 | #[link_section = ".data"] 130 | fn line_available(&mut self) -> (u32, bool) { 131 | if let Some(y) = self 132 | .timing_state 133 | .v_scanline_index(&self.timing, TMDS_PIPELINE_SLACK) 134 | { 135 | if y % VERTICAL_REPEAT as u32 == 0 { 136 | let y = y / VERTICAL_REPEAT as u32; 137 | let available = self.scan_render.is_line_available(y); 138 | let buf_ix = y as usize % N_TMDS_BUFFERS; 139 | self.available[buf_ix] = available; 140 | return (y, available); 141 | } 142 | } 143 | (!0, false) 144 | } 145 | 146 | /// Update the DMA read address to point to the TMDS scanline. 147 | /// 148 | /// Returns true if an active scanline is available. 149 | #[link_section = ".data"] 150 | fn update_scanline(&mut self) -> bool { 151 | if let Some(y) = self.timing_state.v_scanline_index(&self.timing, 0) { 152 | let buf_ix = (y as usize / VERTICAL_REPEAT) % N_TMDS_BUFFERS; 153 | if self.available[buf_ix] { 154 | let stride = self.timing.horizontal_words() as usize * N_CHANNELS * buf_ix; 155 | let buf = unsafe { self.tmds_buf.as_ptr().add(stride) }; 156 | let channel_stride = if N_CHANNELS == 1 { 157 | 0 158 | } else { 159 | self.timing.horizontal_words() 160 | }; 161 | self.dma_list_active.update_scanline(buf, channel_stride); 162 | return true; 163 | } 164 | } 165 | false 166 | } 167 | 168 | /// Render a scanline into a TMDS buffer. 169 | /// 170 | /// This function is called even if the corresponding line buffer is not 171 | /// available, so the display list can be advanced. 172 | #[link_section = ".data"] 173 | fn render(&mut self, y: u32, available: bool) { 174 | let buf_ix = y as usize % N_TMDS_BUFFERS; 175 | let line_size = self.timing.horizontal_words() as usize * N_CHANNELS; 176 | let line_start = line_size * buf_ix; 177 | let tmds_slice = &mut self.tmds_buf[line_start..][..line_size]; 178 | self.scan_render.render_scanline(tmds_slice, y, available); 179 | } 180 | 181 | /// Schedule the rendering of a line buffer. 182 | /// 183 | /// The line buffers are rendered outside the main interrupt handler, 184 | /// striped across both cores. 185 | #[link_section = ".data"] 186 | fn schedule_line_render(&mut self) { 187 | let offset = TMDS_PIPELINE_SLACK + (N_LINE_BUFS * VERTICAL_REPEAT) as u32; 188 | if let Some(y) = self.timing_state.v_scanline_index(&self.timing, offset) { 189 | if y % VERTICAL_REPEAT as u32 == 0 { 190 | let y = y / VERTICAL_REPEAT as u32; 191 | self.scan_render.schedule_line_render(y); 192 | } 193 | } 194 | } 195 | } 196 | 197 | /// We dedicate core 1 to running the primary video interrupt and also 198 | /// background rendering tasks. 199 | #[link_section = ".data"] 200 | pub fn core1_main( 201 | mut serializer: DviSerializer< 202 | PIO, 203 | SliceId, 204 | Pos, 205 | Neg, 206 | RedPos, 207 | RedNeg, 208 | GreenPos, 209 | GreenNeg, 210 | BluePos, 211 | BlueNeg, 212 | >, 213 | ) -> ! 214 | where 215 | PIO: pio::PIOExt, 216 | SliceId: pwm::SliceId, 217 | Pos: PinId + ValidPwmOutputPin, 218 | Neg: PinId + ValidPwmOutputPin, 219 | RedPos: PinId, 220 | RedNeg: PinId, 221 | GreenPos: PinId, 222 | GreenNeg: PinId, 223 | BluePos: PinId, 224 | BlueNeg: PinId, 225 | { 226 | unsafe { 227 | NVIC::unmask(Interrupt::DMA_IRQ_0); 228 | } 229 | serializer.wait_fifos_full(); 230 | serializer.enable(); 231 | loop { 232 | let line_ix = CORE1_QUEUE.peek_blocking(); 233 | // Safety: exclusive access to the line buffer is granted 234 | // when the render is scheduled to a core. 235 | unsafe { render_line(line_ix) }; 236 | CORE1_QUEUE.remove(); 237 | } 238 | } 239 | 240 | /// Called on core 1 every scan line by the DMA controller 241 | #[link_section = ".data"] 242 | #[interrupt] 243 | fn DMA_IRQ_0() { 244 | // Safety: interrupts are enabled (and thus the interrupt handler is 245 | // called) only after the instance has been initialized. After 246 | // initialization, the interrupt handles has exclusive access. 247 | let inst = unsafe { (*DVI_INST.0.get()).assume_init_mut() }; 248 | let _ = inst.channels.check_int(); 249 | inst.timing_state.advance(&inst.timing); 250 | let (y, available) = inst.line_available(); 251 | // wait for all three channels to load their last op 252 | inst.channels.wait_for_load(inst.timing.horizontal_words()); 253 | if inst.update_scanline() { 254 | inst.channels.load_op(&inst.dma_list_active); 255 | } else { 256 | match inst.timing_state.v_state(&inst.timing) { 257 | DviTimingLineState::Active => inst.channels.load_op(&inst.dma_list_error), 258 | DviTimingLineState::Sync => inst.channels.load_op(&inst.dma_list_vblank_sync), 259 | _ => inst.channels.load_op(&inst.dma_list_vblank_nosync), 260 | } 261 | } 262 | if y < 0x8000_0000 { 263 | inst.render(y, available); 264 | } 265 | inst.schedule_line_render(); 266 | } 267 | -------------------------------------------------------------------------------- /src/dvi/dma.rs: -------------------------------------------------------------------------------- 1 | //! DMA related functions 2 | //! 3 | //! The PicoDVI source does not have a separate file for DMA; it's mostly 4 | //! split between dvi and dvi_timing. 5 | 6 | use rp_pico::hal::{ 7 | dma::SingleChannel, 8 | pio::{Tx, ValidStateMachine}, 9 | }; 10 | 11 | use super::timing::DviScanlineDmaList; 12 | 13 | pub struct DviLaneDmaCfg 14 | where 15 | Ch0: SingleChannel, 16 | Ch1: SingleChannel, 17 | { 18 | control_channel: Ch0, 19 | data_channel: Ch1, 20 | tx_fifo: u32, 21 | dreq: u8, 22 | } 23 | 24 | impl DviLaneDmaCfg 25 | where 26 | Ch0: SingleChannel, 27 | Ch1: SingleChannel, 28 | { 29 | fn new(ch0: Ch0, ch1: Ch1, tx: &Tx) -> Self { 30 | DviLaneDmaCfg { 31 | control_channel: ch0, 32 | data_channel: ch1, 33 | tx_fifo: tx.fifo_address() as u32, 34 | dreq: tx.dreq_value(), 35 | } 36 | } 37 | 38 | fn load_op(&mut self, cfg: &[DmaControlBlock]) { 39 | let ch = self.control_channel.ch(); 40 | unsafe { 41 | ch.ch_read_addr.write(|w| w.bits(cfg.as_ptr() as u32)); 42 | let write_addr = self.data_channel.ch().ch_read_addr.as_ptr(); 43 | ch.ch_write_addr.write(|w| w.bits(write_addr as u32)); 44 | let cfg = DmaChannelConfig::default() 45 | .chain_to(self.control_channel.id()) 46 | .ring(true, 4) 47 | .read_increment(true) 48 | .write_increment(true); 49 | ch.ch_trans_count.write(|w| w.bits(4)); 50 | ch.ch_al1_ctrl.write(|w| w.bits(cfg.0)); 51 | } 52 | } 53 | 54 | fn wait_for_load(&self, n_words: u32) { 55 | unsafe { 56 | // CH{id}_DBG_TCR register, not exposed by HAL 57 | let tcr = (0x5000_0804 + 0x40 * self.data_channel.id() as u32) as *mut u32; 58 | while tcr.read_volatile() != n_words { 59 | core::hint::spin_loop() 60 | } 61 | } 62 | } 63 | } 64 | 65 | // 6 free DMA channels for use by the DVI system 66 | // 67 | // Implement this marker trait on a zero-sized type 68 | // to simplify passing all 6 DMA channels to DMA types. 69 | pub trait DmaChannelList { 70 | type Ch0: SingleChannel; 71 | type Ch1: SingleChannel; 72 | type Ch2: SingleChannel; 73 | type Ch3: SingleChannel; 74 | type Ch4: SingleChannel; 75 | type Ch5: SingleChannel; 76 | } 77 | 78 | pub struct DmaChannels { 79 | pub lane0: DviLaneDmaCfg, 80 | pub lane1: DviLaneDmaCfg, 81 | pub lane2: DviLaneDmaCfg, 82 | } 83 | 84 | impl DmaChannels { 85 | pub fn new( 86 | channels: ( 87 | Channels::Ch0, 88 | Channels::Ch1, 89 | Channels::Ch2, 90 | Channels::Ch3, 91 | Channels::Ch4, 92 | Channels::Ch5, 93 | ), 94 | serializer_tx: (&Tx, &Tx, &Tx), 95 | ) -> Self { 96 | DmaChannels { 97 | lane0: DviLaneDmaCfg::new(channels.0, channels.1, serializer_tx.0), 98 | lane1: DviLaneDmaCfg::new(channels.2, channels.3, serializer_tx.1), 99 | lane2: DviLaneDmaCfg::new(channels.4, channels.5, serializer_tx.2), 100 | } 101 | } 102 | 103 | pub fn load_op(&mut self, dma_list: &DviScanlineDmaList) { 104 | self.lane0.load_op(dma_list.lane(0)); 105 | self.lane1.load_op(dma_list.lane(1)); 106 | self.lane2.load_op(dma_list.lane(2)); 107 | } 108 | 109 | /// Enable interrupts and start the DMA transfers 110 | pub fn start(&mut self) { 111 | self.lane0.data_channel.enable_irq0(); 112 | let mut mask = 0; 113 | mask |= 1 << self.lane0.control_channel.id(); 114 | mask |= 1 << self.lane1.control_channel.id(); 115 | mask |= 1 << self.lane2.control_channel.id(); 116 | // TODO: bludgeon rp2040-hal, or whichever crate it is that's supposed to 117 | // be in charge of such things, into doing this the "right" way. 118 | unsafe { 119 | let multi_chan_trigger: *mut u32 = 0x5000_0430 as *mut _; 120 | multi_chan_trigger.write_volatile(mask); 121 | } 122 | } 123 | 124 | pub fn wait_for_load(&self, n_words: u32) { 125 | self.lane0.wait_for_load(n_words); 126 | self.lane1.wait_for_load(n_words); 127 | self.lane2.wait_for_load(n_words); 128 | } 129 | 130 | pub fn check_int(&mut self) -> bool { 131 | self.lane0.data_channel.check_irq0() 132 | } 133 | } 134 | 135 | /// DMA control block. 136 | /// 137 | /// This is a small chunk of memory transferred by the control DMA channel 138 | /// into the control registers of the data channel. 139 | #[repr(C)] 140 | #[derive(Default)] 141 | pub struct DmaControlBlock { 142 | read_addr: u32, 143 | write_addr: u32, 144 | transfer_count: u32, 145 | config: DmaChannelConfig, 146 | } 147 | 148 | impl DmaControlBlock { 149 | pub fn set( 150 | &mut self, 151 | read_addr: *const T, 152 | dma_cfg: &DviLaneDmaCfg, 153 | transfer_count: u32, 154 | read_ring: u32, 155 | irq_on_finish: bool, 156 | ) where 157 | Ch0: SingleChannel, 158 | Ch1: SingleChannel, 159 | { 160 | self.read_addr = read_addr as u32; 161 | self.write_addr = dma_cfg.tx_fifo; 162 | self.transfer_count = transfer_count; 163 | self.config = DmaChannelConfig::default() 164 | .ring(false, read_ring) 165 | .data_request(dma_cfg.dreq) 166 | .chain_to(dma_cfg.control_channel.id()) 167 | .irq_quiet(!irq_on_finish); 168 | } 169 | 170 | pub fn update_buf(&mut self, buf: *const T) { 171 | self.read_addr = buf as u32; 172 | } 173 | } 174 | 175 | // We're doing this by hand because it's not provided by rp2040-pac, as it's 176 | // based on svd2rust (which is quite tight-assed), but would be provided by 177 | // the hal if we were using rp_pac, which is chiptool-based. 178 | // 179 | // Another note: the caller *must* set `chain_to`, as the default points to 180 | // channel zero. Setting it to the same channel disables the function. 181 | #[repr(transparent)] 182 | #[derive(Clone, Copy)] 183 | struct DmaChannelConfig(u32); 184 | 185 | impl Default for DmaChannelConfig { 186 | fn default() -> Self { 187 | let mut bits = 0; 188 | bits |= 1 << 0; // enable 189 | bits |= 2 << 2; // data size = 32 bits 190 | Self(bits).read_increment(true).data_request(0x3f) 191 | } 192 | } 193 | 194 | impl DmaChannelConfig { 195 | fn read_increment(self, incr: bool) -> Self { 196 | let mut bits = self.0 & !(1 << 4); 197 | bits |= (incr as u32) << 4; 198 | Self(bits) 199 | } 200 | 201 | fn write_increment(self, incr: bool) -> Self { 202 | let mut bits = self.0 & !(1 << 5); 203 | bits |= (incr as u32) << 5; 204 | Self(bits) 205 | } 206 | 207 | fn ring(self, ring_sel: bool, ring_size: u32) -> Self { 208 | let mut bits = self.0 & !0x7c0; 209 | bits |= (ring_sel as u32) << 10; 210 | bits |= ring_size << 6; 211 | Self(bits) 212 | } 213 | 214 | fn chain_to(self, chan: u8) -> Self { 215 | let mut bits = self.0 & !0x7800; 216 | bits |= (chan as u32) << 11; 217 | Self(bits) 218 | } 219 | 220 | fn data_request(self, dreq: u8) -> Self { 221 | let mut bits = self.0 & !0x1f8000; 222 | bits |= (dreq as u32) << 15; 223 | Self(bits) 224 | } 225 | 226 | fn irq_quiet(self, quiet: bool) -> Self { 227 | let mut bits = self.0 & !(1 << 21); 228 | bits |= (quiet as u32) << 21; 229 | Self(bits) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/dvi/serializer.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::PwmPin; 2 | use rp_pico::{ 3 | hal::{ 4 | gpio::{ 5 | FunctionPio0, FunctionPwm, OutputDriveStrength, OutputOverride, OutputSlewRate, Pin, 6 | PinId, PullDown, 7 | }, 8 | pio::{ 9 | self, InstalledProgram, PIOBuilder, PinDir, Running, StateMachine, StateMachineGroup3, 10 | StateMachineIndex, Stopped, Tx, UninitStateMachine, ValidStateMachine, 11 | }, 12 | pwm::{self, FreeRunning, Slice, ValidPwmOutputPin}, 13 | }, 14 | pac, 15 | }; 16 | 17 | pub struct DviDataPins 18 | where 19 | RedPos: PinId, 20 | RedNeg: PinId, 21 | GreenPos: PinId, 22 | GreenNeg: PinId, 23 | BluePos: PinId, 24 | BlueNeg: PinId, 25 | { 26 | pub red_pos: Pin, 27 | pub red_neg: Pin, 28 | 29 | pub green_pos: Pin, 30 | pub green_neg: Pin, 31 | 32 | pub blue_pos: Pin, 33 | pub blue_neg: Pin, 34 | } 35 | 36 | pub struct DviClockPins 37 | where 38 | SliceId: pwm::SliceId, 39 | Pos: PinId + ValidPwmOutputPin, 40 | Neg: PinId + ValidPwmOutputPin, 41 | { 42 | pub clock_pos: Pin, // TODO: allow different order? 43 | pub clock_neg: Pin, 44 | pub pwm_slice: Slice, 45 | } 46 | 47 | pub struct DviSerializer< 48 | PIO, 49 | SliceId, 50 | Pos, 51 | Neg, 52 | RedPos, 53 | RedNeg, 54 | GreenPos, 55 | GreenNeg, 56 | BluePos, 57 | BlueNeg, 58 | > where 59 | PIO: pio::PIOExt, 60 | SliceId: pwm::SliceId, 61 | Pos: PinId + ValidPwmOutputPin, 62 | Neg: PinId + ValidPwmOutputPin, 63 | RedPos: PinId, 64 | RedNeg: PinId, 65 | GreenPos: PinId, 66 | GreenNeg: PinId, 67 | BluePos: PinId, 68 | BlueNeg: PinId, 69 | { 70 | pio: pio::PIO, // FIXME: 71 | data_pins: DviDataPins, // FIXME: 72 | clock_pins: DviClockPins, 73 | 74 | state_machines: StateMachineState, 75 | tx_fifo: ( 76 | Tx<(PIO, pio::SM0)>, 77 | Tx<(PIO, pio::SM1)>, 78 | Tx<(PIO, pio::SM2)>, 79 | ), 80 | } 81 | 82 | enum StateMachineState { 83 | Stopped(StateMachineGroup3), 84 | Running(StateMachineGroup3), 85 | Taken, 86 | } 87 | 88 | impl 89 | DviSerializer< 90 | PIO, 91 | SliceId, 92 | ClockPos, 93 | ClockNeg, 94 | RedPos, 95 | RedNeg, 96 | GreenPos, 97 | GreenNeg, 98 | BluePos, 99 | BlueNeg, 100 | > 101 | where 102 | PIO: pio::PIOExt, 103 | SliceId: pwm::SliceId, 104 | ClockPos: PinId + ValidPwmOutputPin, 105 | ClockNeg: PinId + ValidPwmOutputPin, 106 | RedPos: PinId, 107 | RedNeg: PinId, 108 | GreenPos: PinId, 109 | GreenNeg: PinId, 110 | BluePos: PinId, 111 | BlueNeg: PinId, 112 | { 113 | fn configure_state_machine( 114 | program: &InstalledProgram, 115 | state_machine: UninitStateMachine<(PIO, SM)>, 116 | pos_pin: &mut Pin, 117 | neg_pin: &mut Pin, 118 | ) -> (StateMachine<(PIO, SM), Stopped>, Tx<(PIO, SM)>) 119 | where 120 | Pos: PinId, 121 | Neg: PinId, 122 | SM: StateMachineIndex, 123 | { 124 | let positive_id = pos_pin.id().num; 125 | let negative_id = neg_pin.id().num; 126 | 127 | defmt::assert_eq!( 128 | negative_id.abs_diff(positive_id), 129 | 1, 130 | "differential pins must be sequential" 131 | ); 132 | 133 | // Invert pin outputs if in other order 134 | let output_override = if positive_id < negative_id { 135 | OutputOverride::DontInvert 136 | } else { 137 | OutputOverride::Invert 138 | }; 139 | 140 | let (mut state_machine, _, tx) = PIOBuilder::from_program(unsafe { program.share() }) 141 | .side_set_pin_base(negative_id.min(positive_id)) 142 | .clock_divisor_fixed_point(1, 0) 143 | .autopull(true) 144 | .buffers(pio::Buffers::OnlyTx) 145 | .pull_threshold(20) 146 | .out_shift_direction(pio::ShiftDirection::Right) 147 | .build(state_machine); 148 | 149 | state_machine.set_pindirs([(negative_id, PinDir::Output), (positive_id, PinDir::Output)]); 150 | 151 | neg_pin.set_drive_strength(OutputDriveStrength::TwoMilliAmps); 152 | neg_pin.set_slew_rate(OutputSlewRate::Slow); 153 | neg_pin.set_output_override(output_override); 154 | 155 | pos_pin.set_drive_strength(OutputDriveStrength::TwoMilliAmps); 156 | pos_pin.set_slew_rate(OutputSlewRate::Slow); 157 | pos_pin.set_output_override(output_override); 158 | 159 | (state_machine, tx) 160 | } 161 | 162 | pub fn new( 163 | pio: PIO, 164 | resets: &mut pac::RESETS, 165 | mut data_pins: DviDataPins, 166 | mut clock_pins: DviClockPins, 167 | ) -> Self { 168 | let (mut pio, state_machine_blue, state_machine_green, state_machine_red, _) = 169 | pio.split(resets); 170 | 171 | // 3 PIO state machines to drive 6 data lines 172 | let dvi_output_program = pio_proc::pio_file!("src/dvi_differential.pio"); 173 | 174 | let installed_program = pio.install(&dvi_output_program.program).unwrap(); 175 | 176 | // TODO: do not consume pins? 177 | let (state_machine_blue, tx_blue) = Self::configure_state_machine::( 178 | &installed_program, 179 | state_machine_blue, 180 | &mut data_pins.blue_pos, 181 | &mut data_pins.blue_neg, 182 | ); 183 | 184 | let (state_machine_green, tx_green) = Self::configure_state_machine::( 185 | &installed_program, 186 | state_machine_green, 187 | &mut data_pins.green_pos, 188 | &mut data_pins.green_neg, 189 | ); 190 | 191 | let (state_machine_red, tx_red) = Self::configure_state_machine::( 192 | &installed_program, 193 | state_machine_red, 194 | &mut data_pins.red_pos, 195 | &mut data_pins.red_neg, 196 | ); 197 | 198 | // DVI clock driven 199 | let clock_pwm = &mut clock_pins.pwm_slice; 200 | clock_pwm.default_config(); 201 | clock_pwm.set_top(9); 202 | 203 | clock_pwm.channel_a.clr_inverted(); 204 | clock_pwm.channel_a.set_duty(5); 205 | let mut clock_pos = clock_pwm.channel_a.output_to(clock_pins.clock_pos); 206 | clock_pos.set_drive_strength(OutputDriveStrength::TwoMilliAmps); 207 | clock_pos.set_slew_rate(OutputSlewRate::Slow); 208 | 209 | clock_pwm.channel_b.set_inverted(); 210 | clock_pwm.channel_b.set_duty(5); 211 | let mut clock_neg = clock_pwm.channel_b.output_to(clock_pins.clock_neg); 212 | clock_neg.set_drive_strength(OutputDriveStrength::TwoMilliAmps); 213 | clock_neg.set_slew_rate(OutputSlewRate::Slow); 214 | clock_pwm.enable(); 215 | 216 | Self { 217 | pio, 218 | data_pins, 219 | clock_pins: DviClockPins { 220 | clock_pos, 221 | clock_neg, 222 | pwm_slice: clock_pins.pwm_slice, 223 | }, 224 | state_machines: StateMachineState::Stopped( 225 | state_machine_blue 226 | .with(state_machine_green) 227 | .with(state_machine_red), 228 | ), 229 | tx_fifo: (tx_blue, tx_green, tx_red), 230 | } 231 | } 232 | 233 | pub fn tx( 234 | &self, 235 | ) -> ( 236 | &Tx<(PIO, pio::SM0)>, 237 | &Tx<(PIO, pio::SM1)>, 238 | &Tx<(PIO, pio::SM2)>, 239 | ) { 240 | (&self.tx_fifo.0, &self.tx_fifo.1, &self.tx_fifo.2) 241 | } 242 | 243 | pub fn wait_fifos_full(&self) { 244 | wait_fifo_full(&self.tx_fifo.0); 245 | wait_fifo_full(&self.tx_fifo.1); 246 | wait_fifo_full(&self.tx_fifo.2); 247 | } 248 | 249 | pub fn enable(&mut self) { 250 | if let StateMachineState::Stopped(state_machines) = 251 | core::mem::replace(&mut self.state_machines, StateMachineState::Taken) 252 | { 253 | let state_machines = state_machines.sync().start(); 254 | self.state_machines = StateMachineState::Running(state_machines); 255 | } 256 | self.clock_pins.pwm_slice.enable(); 257 | } 258 | } 259 | 260 | fn wait_fifo_full(fifo: &Tx) { 261 | while !fifo.is_full() { 262 | core::hint::spin_loop() 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/dvi/timing.rs: -------------------------------------------------------------------------------- 1 | //! timing information yoinked from 2 | //! 3 | 4 | use fugit::KilohertzU32; 5 | use rp_pico::hal::dma::SingleChannel; 6 | 7 | use super::{ 8 | dma::{DmaChannelList, DmaChannels, DmaControlBlock, DviLaneDmaCfg}, 9 | tmds::{TmdsPair, TmdsSymbol}, 10 | }; 11 | 12 | // Perhaps there should be a trait with associated constants for resolution, 13 | // to allow compile-time allocation of scanline buffers etc. 14 | pub struct DviTiming { 15 | h_sync_polarity: bool, 16 | h_front_porch: u32, 17 | h_sync_width: u32, 18 | h_back_porch: u32, 19 | h_active_pixels: u32, 20 | 21 | v_sync_polarity: bool, 22 | v_front_porch: u32, 23 | v_sync_width: u32, 24 | v_back_porch: u32, 25 | v_active_lines: u32, 26 | 27 | pub bit_clk: KilohertzU32, 28 | } 29 | 30 | impl DviTiming { 31 | pub fn horizontal_words(&self) -> u32 { 32 | self.h_active_pixels / 2 33 | } 34 | 35 | fn total_lines(&self) -> u32 { 36 | self.v_front_porch + self.v_sync_width + self.v_back_porch + self.v_active_lines 37 | } 38 | 39 | fn state_for_v_count(&self, v_count: u32) -> DviTimingLineState { 40 | let mut y = v_count; 41 | if y < self.v_front_porch { 42 | return DviTimingLineState::FrontPorch; 43 | } 44 | y -= self.v_front_porch; 45 | if y < self.v_sync_width { 46 | return DviTimingLineState::Sync; 47 | } 48 | y -= self.v_sync_width; 49 | if y < self.v_back_porch { 50 | DviTimingLineState::BackPorch 51 | } else { 52 | DviTimingLineState::Active 53 | } 54 | } 55 | } 56 | 57 | pub const VGA_TIMING: DviTiming = DviTiming { 58 | h_sync_polarity: false, 59 | h_front_porch: 16, 60 | h_sync_width: 96, 61 | h_back_porch: 48, 62 | h_active_pixels: 640, 63 | 64 | v_sync_polarity: false, 65 | v_front_porch: 10, 66 | v_sync_width: 2, 67 | v_back_porch: 33, 68 | v_active_lines: 480, 69 | 70 | bit_clk: KilohertzU32::kHz(252000), 71 | }; 72 | 73 | #[derive(Default)] 74 | pub struct DviTimingState { 75 | v_ctr: u32, 76 | } 77 | 78 | impl DviTimingState { 79 | pub fn advance(&mut self, timing: &DviTiming) { 80 | self.v_ctr += 1; 81 | if self.v_ctr == timing.total_lines() { 82 | self.v_ctr = 0; 83 | } 84 | } 85 | 86 | pub fn v_state(&self, timing: &DviTiming) -> DviTimingLineState { 87 | timing.state_for_v_count(self.v_ctr) 88 | } 89 | 90 | pub fn v_scanline_index(&self, timing: &DviTiming, offset: u32) -> Option { 91 | let inactive = timing.v_front_porch + timing.v_sync_width + timing.v_back_porch; 92 | let y = (self.v_ctr + offset).checked_sub(inactive)?; 93 | if y < timing.v_active_lines { 94 | Some(y) 95 | } else { 96 | None 97 | } 98 | } 99 | } 100 | 101 | #[derive(Clone, Copy, PartialEq, Eq, Default)] 102 | pub enum DviTimingLineState { 103 | #[default] 104 | FrontPorch, 105 | Sync, 106 | BackPorch, 107 | Active, 108 | } 109 | 110 | const DVI_SYNC_LANE_CHUNKS: usize = 4; 111 | const DVI_LANE_CHUNKS: usize = 2; 112 | 113 | #[derive(Default)] 114 | pub struct DviScanlineDmaList { 115 | lane0: [DmaControlBlock; DVI_SYNC_LANE_CHUNKS], 116 | lane1: [DmaControlBlock; DVI_LANE_CHUNKS], 117 | lane2: [DmaControlBlock; DVI_LANE_CHUNKS], 118 | } 119 | 120 | impl DviScanlineDmaList { 121 | pub fn lane(&self, i: usize) -> &[DmaControlBlock] { 122 | match i { 123 | 0 => &self.lane0, 124 | 1 => &self.lane1, 125 | _ => &self.lane2, 126 | } 127 | } 128 | 129 | fn lane_mut(&mut self, i: usize) -> &mut [DmaControlBlock] { 130 | match i { 131 | 0 => &mut self.lane0, 132 | 1 => &mut self.lane1, 133 | _ => &mut self.lane2, 134 | } 135 | } 136 | 137 | fn setup_lane_0( 138 | &mut self, 139 | timing: &DviTiming, 140 | dma_cfg: &DviLaneDmaCfg, 141 | line_state: DviTimingLineState, 142 | has_data: bool, 143 | ) where 144 | Ch0: SingleChannel, 145 | Ch1: SingleChannel, 146 | { 147 | let vsync = (line_state == DviTimingLineState::Sync) == timing.v_sync_polarity; 148 | let symbol_hsync_off = get_ctrl_symbol(vsync, !timing.h_sync_polarity); 149 | let symbol_hsync_on = get_ctrl_symbol(vsync, timing.h_sync_polarity); 150 | let lane = &mut self.lane0; 151 | lane[0].set( 152 | symbol_hsync_off, 153 | dma_cfg, 154 | timing.h_front_porch / 2, 155 | 2, 156 | false, 157 | ); 158 | lane[1].set(symbol_hsync_on, dma_cfg, timing.h_sync_width / 2, 2, false); 159 | lane[2].set(symbol_hsync_off, dma_cfg, timing.h_back_porch / 2, 2, true); 160 | let read_ring = if has_data { 0 } else { 2 }; 161 | let symbol = match line_state { 162 | DviTimingLineState::Active => &EMPTY_SCANLINE_TMDS[0], 163 | _ => symbol_hsync_off, 164 | }; 165 | lane[3].set( 166 | symbol, 167 | dma_cfg, 168 | timing.h_active_pixels / 2, 169 | read_ring, 170 | false, 171 | ); 172 | } 173 | 174 | fn setup_lane_12( 175 | &mut self, 176 | lane_number: usize, 177 | timing: &DviTiming, 178 | dma_cfg: &DviLaneDmaCfg, 179 | line_state: DviTimingLineState, 180 | has_data: bool, 181 | ) where 182 | Ch0: SingleChannel, 183 | Ch1: SingleChannel, 184 | { 185 | let symbol_no_sync = get_ctrl_symbol(false, false); 186 | 187 | let lane = self.lane_mut(lane_number); 188 | let inactive = timing.h_front_porch + timing.h_sync_width + timing.h_back_porch; 189 | lane[0].set(symbol_no_sync, dma_cfg, inactive / 2, 2, false); 190 | let read_ring = if has_data { 0 } else { 2 }; 191 | let sym = match line_state { 192 | DviTimingLineState::Active => &EMPTY_SCANLINE_TMDS[lane_number], 193 | _ => symbol_no_sync, 194 | }; 195 | lane[1].set(sym, dma_cfg, timing.h_active_pixels / 2, read_ring, false); 196 | } 197 | 198 | pub fn setup_scanline( 199 | &mut self, 200 | timing: &DviTiming, 201 | channels: &DmaChannels, 202 | line_state: DviTimingLineState, 203 | has_data: bool, 204 | ) { 205 | self.setup_lane_0(timing, &channels.lane0, line_state, has_data); 206 | self.setup_lane_12(1, timing, &channels.lane1, line_state, has_data); 207 | self.setup_lane_12(2, timing, &channels.lane2, line_state, has_data); 208 | } 209 | 210 | pub fn update_scanline(&mut self, buf: *const TmdsPair, stride: u32) { 211 | unsafe { 212 | self.lane0[3].update_buf(buf); 213 | self.lane1[1].update_buf(buf.add(stride as usize)); 214 | self.lane2[1].update_buf(buf.add(stride as usize * 2)); 215 | } 216 | } 217 | } 218 | 219 | #[link_section = ".data"] 220 | static DVI_CTRL_SYMBOLS: [TmdsPair; 4] = [ 221 | TmdsPair::double(TmdsSymbol::C0), 222 | TmdsPair::double(TmdsSymbol::C1), 223 | TmdsPair::double(TmdsSymbol::C2), 224 | TmdsPair::double(TmdsSymbol::C3), 225 | ]; 226 | 227 | fn get_ctrl_symbol(vsync: bool, hsync: bool) -> &'static TmdsPair { 228 | &DVI_CTRL_SYMBOLS[((vsync as usize) << 1) | (hsync as usize)] 229 | } 230 | 231 | #[link_section = ".data"] 232 | static EMPTY_SCANLINE_TMDS: [TmdsPair; 3] = [ 233 | TmdsPair::encode_balanced_approx(0x00), // Blue 234 | TmdsPair::encode_balanced_approx(0x00), // Green 235 | TmdsPair::encode_balanced_approx(0xff), // Red 236 | ]; 237 | -------------------------------------------------------------------------------- /src/dvi/tmds.rs: -------------------------------------------------------------------------------- 1 | /// [TMDS] encoding for DVI 2 | /// [TMDS]: https://en.wikipedia.org/wiki/Transition-minimized_differential_signaling 3 | 4 | /// A single [TMDS] symbol. 5 | /// 6 | /// The [TMDS] encoding for DVI produces one 10-bit symbol for each 8 7 | /// bit word. 8 | /// 9 | /// [TMDS]: https://en.wikipedia.org/wiki/Transition-minimized_differential_signaling 10 | #[derive(Clone, Copy)] 11 | pub struct TmdsSymbol(u32); 12 | 13 | /// A pair of TMDS symbols. 14 | /// 15 | /// These are packed two to a word, laid out for serialization. 16 | #[repr(transparent)] 17 | #[derive(Clone, Copy)] 18 | pub struct TmdsPair(u32); 19 | 20 | impl TmdsSymbol { 21 | pub const C0: Self = TmdsSymbol(0x354); 22 | pub const C1: Self = TmdsSymbol(0xab); 23 | pub const C2: Self = TmdsSymbol(0x154); 24 | pub const C3: Self = TmdsSymbol(0x2ab); 25 | 26 | pub const fn encode(discrepancy: i32, byte: u8) -> (i32, Self) { 27 | let byte_ones = byte.count_ones(); 28 | 29 | // The first step of encoding TMDS data is to XOR/XNOR each input bit with the previous output bit, one by one 30 | // 31 | // Instead using a for loop over each bit, TMDS encoding can be approached 32 | // as a carry-less multiplication with 255. The decoding step would be a 33 | // carry-less multiplication with 3. 34 | // 35 | // To reduce the amount of steps, the carry-less multiplication with 255 36 | // can be split up into a multiplication with 3 * 5 * 17. The following 37 | // 3 lines respectively can be seen as those smaller multiplications. 38 | let byte_mul = ((byte as u32) << 1) ^ byte as u32; 39 | let byte_mul = (byte_mul << 2) ^ byte_mul; 40 | let byte_mul = (byte_mul << 4) ^ byte_mul; 41 | // We only care about the bottom byte 42 | let mut byte_encoded = byte_mul & 0xff; 43 | 44 | let should_xnor = byte_ones > 4 || (byte_ones == 4 && (byte_encoded & 1) == 0); 45 | 46 | if should_xnor { 47 | // Convert the XOR case to the XNOR case, toggling every other bit 48 | byte_encoded ^= 0xaa 49 | } else { 50 | // Set bit 8 to indicate XOR 51 | byte_encoded ^= 0x100 52 | }; 53 | 54 | let encoded_ones = u8::count_ones(byte_encoded as _); 55 | 56 | let should_invert = if discrepancy == 0 || encoded_ones == 4 { 57 | (byte_encoded >> 8) == 0 58 | } else { 59 | (discrepancy > 0) == (encoded_ones > 4) 60 | }; 61 | 62 | let bit_8 = (byte_encoded >> 8) & 1; 63 | let symbol_ones = if should_invert { 64 | // Invert the lower byte and set bit 9 65 | byte_encoded ^= 0x2ff; 66 | 67 | // Invert the ones count of the lower 8 bits, add bit 8 and bit 9 68 | (8 - encoded_ones) + bit_8 + 1 69 | } else { 70 | encoded_ones + bit_8 71 | }; 72 | 73 | let discrepancy = discrepancy + (symbol_ones as i32 - 5); 74 | 75 | (discrepancy, TmdsSymbol(byte_encoded)) 76 | } 77 | } 78 | 79 | impl TmdsPair { 80 | pub const fn new(sym0: TmdsSymbol, sym1: TmdsSymbol) -> Self { 81 | TmdsPair(sym0.0 | ((sym1.0) << 10)) 82 | } 83 | 84 | pub const fn double(symbol: TmdsSymbol) -> Self { 85 | Self::new(symbol, symbol) 86 | } 87 | 88 | /// Encode a pair of bytes. 89 | /// 90 | /// This is not guaranteed to be DC balanced unless the bytes are 91 | /// carefully chosen. 92 | pub const fn encode_pair(b0: u8, b1: u8) -> Self { 93 | let (pair, discrepancy) = Self::encode_pair_discrepancy(b0, b1); 94 | 95 | // TODO: REMOVE? 96 | // Ensure the pair is balanced in testing 97 | debug_assert!(discrepancy == 0); 98 | 99 | pair 100 | } 101 | 102 | /// Encode a pair of bytes, returning the pair and their discrepancy. 103 | pub const fn encode_pair_discrepancy(b0: u8, b1: u8) -> (Self, i32) { 104 | let (discrepancy, symbol_0) = TmdsSymbol::encode(0, b0); 105 | let (discrepancy, symbol_1) = TmdsSymbol::encode(discrepancy, b1); 106 | 107 | (Self::new(symbol_0, symbol_1), discrepancy) 108 | } 109 | 110 | /// Encode two copies of a byte, approximating to achieve DC balance. 111 | /// 112 | /// This method takes advantage of the fact that two values differing 113 | /// only in the least significant bit add are DC balanced. 114 | pub const fn encode_balanced_approx(byte: u8) -> Self { 115 | Self::encode_pair(byte, byte ^ 1) 116 | } 117 | 118 | pub const fn raw(self) -> u32 { 119 | self.0 120 | } 121 | } 122 | 123 | // TODO: https://lib.rs/crates/defmt-test 124 | // TODO: generate test cases from known working implementation??? 125 | #[cfg(test)] 126 | #[defmt_test::tests] 127 | mod test { 128 | #[test] 129 | fn encode() {} 130 | } 131 | -------------------------------------------------------------------------------- /src/dvi_differential.pio: -------------------------------------------------------------------------------- 1 | .program dvi_differential 2 | .side_set 2 3 | .origin 0 4 | 5 | ; In order to achieve one bit per clock, the bit from the shift register is used 6 | ; to jump to the instruction to side-set the right differential signal. 7 | 8 | .wrap_target 9 | out pc, 1 side 0b10 10 | out pc, 1 side 0b01 11 | .wrap 12 | -------------------------------------------------------------------------------- /src/link.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! link { 3 | (ram, $fn_name:ident) => { 4 | concat!(".ram.", file!(), ".", line!(), ".", stringify!($fn_name)) 5 | }; 6 | (scratch x, $fn_name:ident) => { 7 | concat!( 8 | ".scratch_x.", 9 | file!(), 10 | ".", 11 | line!(), 12 | ".", 13 | stringify!($fn_name) 14 | ) 15 | }; 16 | (scratch y, $fn_name:ident) => { 17 | concat!( 18 | ".scratch_y.", 19 | file!(), 20 | ".", 21 | line!(), 22 | ".", 23 | stringify!($fn_name) 24 | ) 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | 6 | use core::{arch::global_asm, cell::UnsafeCell, mem::MaybeUninit}; 7 | 8 | use defmt_rtt as _; 9 | use panic_probe as _; // TODO: remove if you need 5kb of space, since panicking + formatting machinery is huge 10 | 11 | use cortex_m::peripheral::NVIC; 12 | use defmt::info; 13 | use dvi::dma::DmaChannelList; 14 | use embedded_alloc::Heap; 15 | use rp_pico::{ 16 | hal::{ 17 | dma::{Channel, DMAExt, CH0, CH1, CH2, CH3, CH4, CH5}, 18 | gpio::PinState, 19 | multicore::{Multicore, Stack}, 20 | pwm, 21 | sio::{Sio, SioFifo}, 22 | watchdog::Watchdog, 23 | }, 24 | pac::{self, interrupt}, 25 | Pins, 26 | }; 27 | 28 | use pac::Interrupt; 29 | 30 | use crate::{ 31 | clock::init_clocks, 32 | dvi::{ 33 | core1_main, 34 | dma::DmaChannels, 35 | serializer::{DviClockPins, DviDataPins, DviSerializer}, 36 | timing::VGA_TIMING, 37 | DviInst, 38 | }, 39 | render::{init_4bpp_palette, init_display_swapcell, render_line, GLOBAL_PALETTE}, 40 | }; 41 | 42 | mod clock; 43 | mod demo; 44 | mod dvi; 45 | mod link; 46 | mod render; 47 | mod scanlist; 48 | 49 | #[global_allocator] 50 | static HEAP: Heap = Heap::empty(); 51 | 52 | global_asm! { 53 | include_str!("pre_init.asm"), 54 | options(raw) 55 | } 56 | 57 | struct DviChannels; 58 | impl DmaChannelList for DviChannels { 59 | type Ch0 = Channel; 60 | type Ch1 = Channel; 61 | type Ch2 = Channel; 62 | type Ch3 = Channel; 63 | type Ch4 = Channel; 64 | type Ch5 = Channel; 65 | } 66 | 67 | struct DviInstWrapper(UnsafeCell>>); 68 | 69 | // Safety: access to the instance is indeed shared across threads, 70 | // as it is initialized in the main thread and the interrupt should 71 | // be modeled as another thread (and may be on a different core), 72 | // but only one has access at a time. 73 | // 74 | // Note: this is annoying, `static mut` is more ergonomic (but less 75 | // precise). When `SyncUnsafeCell` is stabilized, use that instead. 76 | unsafe impl Sync for DviInstWrapper {} 77 | 78 | static DVI_INST: DviInstWrapper = DviInstWrapper(UnsafeCell::new(MaybeUninit::uninit())); 79 | 80 | static mut CORE1_STACK: Stack<256> = Stack::new(); 81 | 82 | static mut FIFO: MaybeUninit = MaybeUninit::uninit(); 83 | 84 | // Separate macro annotated function to make rust-analyzer fixes apply better 85 | #[rp_pico::entry] 86 | fn macro_entry() -> ! { 87 | entry(); 88 | } 89 | 90 | const PALETTE: &[u32; 16] = &[ 91 | 0x000000, 0xffffff, 0x9d9d9d, 0xe06f8b, 0xbe2633, 0x493c2b, 0xa46422, 0xeb8931, 0xf7e26b, 92 | 0xa3ce27, 0x44891a, 0x2f484e, 0x1b2632, 0x5784, 0x31a2f2, 0xb2dcef, 93 | ]; 94 | 95 | fn entry() -> ! { 96 | info!("Program start"); 97 | 98 | // Test allocations in different memory regions 99 | rom(); 100 | ram(); 101 | ram_x(); 102 | ram_y(); 103 | defmt::info!("If we have not panicked by now, memory regions probably work well"); 104 | 105 | { 106 | const HEAP_SIZE: usize = 128 * 1024; 107 | static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; 108 | unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } 109 | } 110 | 111 | let mut peripherals = pac::Peripherals::take().unwrap(); 112 | //let core_peripherals = pac::CorePeripherals::take().unwrap(); 113 | 114 | sysinfo(&peripherals.SYSINFO); 115 | 116 | let mut watchdog = Watchdog::new(peripherals.WATCHDOG); 117 | let single_cycle_io = Sio::new(peripherals.SIO); 118 | 119 | let timing = VGA_TIMING; 120 | 121 | // External high-speed crystal on the pico board is 12Mhz 122 | let _clocks = init_clocks( 123 | peripherals.XOSC, 124 | peripherals.ROSC, 125 | peripherals.CLOCKS, 126 | peripherals.PLL_SYS, 127 | peripherals.PLL_USB, 128 | &mut peripherals.RESETS, 129 | &mut watchdog, 130 | timing.bit_clk, 131 | ); 132 | 133 | let pins = Pins::new( 134 | peripherals.IO_BANK0, 135 | peripherals.PADS_BANK0, 136 | single_cycle_io.gpio_bank0, 137 | &mut peripherals.RESETS, 138 | ); 139 | 140 | let led_pin = pins.led.into_push_pull_output_in_state(PinState::Low); 141 | 142 | let pwm_slices = pwm::Slices::new(peripherals.PWM, &mut peripherals.RESETS); 143 | let dma = peripherals.DMA.split(&mut peripherals.RESETS); 144 | 145 | let (data_pins, clock_pins) = { 146 | ( 147 | DviDataPins { 148 | // 0 149 | blue_pos: pins.gpio12.into_function(), 150 | blue_neg: pins.gpio13.into_function(), 151 | // 1 152 | green_pos: pins.gpio10.into_function(), 153 | green_neg: pins.gpio11.into_function(), 154 | // 2 155 | red_pos: pins.gpio16.into_function(), 156 | red_neg: pins.gpio17.into_function(), 157 | }, 158 | DviClockPins { 159 | clock_pos: pins.gpio14.into_function(), 160 | clock_neg: pins.gpio15.into_function(), 161 | pwm_slice: pwm_slices.pwm7, 162 | }, 163 | ) 164 | }; 165 | 166 | let serializer = DviSerializer::new( 167 | peripherals.PIO0, 168 | &mut peripherals.RESETS, 169 | data_pins, 170 | clock_pins, 171 | ); 172 | 173 | let dma_channels = DmaChannels::new( 174 | (dma.ch0, dma.ch1, dma.ch2, dma.ch3, dma.ch4, dma.ch5), 175 | serializer.tx(), 176 | ); 177 | 178 | { 179 | // Safety: the DMA_IRQ_0 handler is not enabled yet. We have exclusive access to this static. 180 | let inst = unsafe { (*DVI_INST.0.get()).write(DviInst::new(timing, dma_channels)) }; 181 | inst.setup_dma(); 182 | inst.start(); 183 | } 184 | let mut fifo = single_cycle_io.fifo; 185 | let mut mc = Multicore::new(&mut peripherals.PSM, &mut peripherals.PPB, &mut fifo); 186 | let cores = mc.cores(); 187 | let core1 = &mut cores[1]; 188 | core1 189 | .spawn(unsafe { &mut CORE1_STACK.mem }, move || { 190 | core1_main(serializer) 191 | }) 192 | .unwrap(); 193 | // Safety: enable interrupt for fifo to receive line render requests. 194 | // Transfer ownership of this end of the fifo to the interrupt handler. 195 | unsafe { 196 | FIFO = MaybeUninit::new(fifo); 197 | NVIC::unmask(Interrupt::SIO_IRQ_PROC0); 198 | } 199 | init_display_swapcell(640); 200 | 201 | unsafe { 202 | init_4bpp_palette(&mut GLOBAL_PALETTE, PALETTE); 203 | } 204 | 205 | demo::demo(led_pin); 206 | } 207 | 208 | fn sysinfo(sysinfo: &pac::SYSINFO) { 209 | let is_fpga = sysinfo.platform.read().fpga().bit(); 210 | let is_asic = sysinfo.platform.read().asic().bit(); 211 | let git_hash = sysinfo.gitref_rp2040.read().bits(); 212 | let manufacturer = sysinfo.chip_id.read().manufacturer().bits(); 213 | let part = sysinfo.chip_id.read().part().bits(); 214 | let revision = sysinfo.chip_id.read().revision().bits(); 215 | 216 | info!( 217 | "SYSINFO 218 | platform: 219 | FPGA: {=bool} 220 | ASIC: {=bool} 221 | gitref_rp2040: {=u32:x} 222 | chip_id: 223 | manufacturer: {=u16:X} 224 | part: {=u16} 225 | revision: {=u8}", 226 | is_fpga, is_asic, git_hash, manufacturer, part, revision 227 | ); 228 | } 229 | 230 | // Functions and statics are placed in rom by default 231 | fn rom() { 232 | let ptr = rom as fn() as *const (); 233 | defmt::assert!( 234 | (0x10000100..0x20000000).contains(&(ptr as u32)), 235 | "rom fn is placed at {} which is not in FLASH", 236 | ptr 237 | ); 238 | } 239 | 240 | // This function will be placed in ram 241 | #[link_section = link!(ram, ram)] 242 | fn ram() { 243 | let ptr = ram as fn() as *const (); 244 | defmt::assert!( 245 | (0x20000000..0x20040000).contains(&(ptr as u32)), 246 | "ram fn is placed at {} which is not in RAM", 247 | ptr 248 | ); 249 | } 250 | 251 | // This function will be placed in ram 252 | #[link_section = link!(scratch x, ram_x)] 253 | fn ram_x() { 254 | let ptr = ram_x as fn() as *const (); 255 | defmt::assert!( 256 | (0x20040000..0x20041000).contains(&(ptr as u32)), 257 | "ram_x fn is placed at {} which is not in SCRATCH_X", 258 | ptr 259 | ); 260 | } 261 | 262 | // This function will be placed in ram 263 | #[link_section = link!(scratch y, ram_y)] 264 | fn ram_y() { 265 | let ptr = ram_y as fn() as *const (); 266 | defmt::assert!( 267 | (0x20041000..0x20042000).contains(&(ptr as u32)), 268 | "ram_y fn is placed at {} which is not in SCRATCH_Y", 269 | ptr 270 | ); 271 | } 272 | 273 | /// Called by the system only when core 1 is overloaded and can't handle all the rendering work, and requests core 0 to render one scan line worth of content. 274 | #[link_section = ".data"] 275 | #[interrupt] 276 | fn SIO_IRQ_PROC0() { 277 | // Safety: this interrupt handler has exclusive access to this 278 | // end of the fifo. 279 | let fifo = unsafe { FIFO.assume_init_mut() }; 280 | while let Some(line_ix) = fifo.read() { 281 | // Safety: exclusive access to the line buffer is granted 282 | // when the render is scheduled to a core. 283 | unsafe { render_line(line_ix) }; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/pre_init.asm: -------------------------------------------------------------------------------- 1 | # Copy data from flash into scratch_x and scratch_y ram banks since this 2 | # is not handled already 3 | # See https://github.com/rp-rs/rp-hal/issues/576 for more discusssion. 4 | 5 | .section .text 6 | .align 4 7 | 8 | data_cpy_table: 9 | .word _scratch_x_source 10 | .word _scratch_x_start 11 | .word _scratch_x_end 12 | .word _scratch_y_source 13 | .word _scratch_y_start 14 | .word _scratch_y_end 15 | .word 0 16 | 17 | .global __pre_init 18 | .type __pre_init,%function 19 | 20 | .thumb_func 21 | __pre_init: 22 | push {r4, lr} 23 | ldr r4, =data_cpy_table 24 | 25 | 1: 26 | ldmia r4!, {r1-r3} 27 | cmp r1, #0 28 | beq 2f 29 | bl data_cpy 30 | b 1b 31 | 2: 32 | pop {r4, pc} 33 | data_cpy_loop: 34 | ldm r1!, {r0} 35 | stm r2!, {r0} 36 | data_cpy: 37 | cmp r2, r3 38 | blo data_cpy_loop 39 | bx lr -------------------------------------------------------------------------------- /src/render.asm: -------------------------------------------------------------------------------- 1 | // These functions are in .data because they may be called from both cores. 2 | .section .data 3 | 4 | // r0: command list in direct threaded format 5 | // r1: output buffer 6 | // r2: y 7 | .global render_engine 8 | .type render_engine,%function 9 | .thumb_func 10 | render_engine: 11 | push {r4, r5, r6, r7} 12 | movs r3, #0 // outw 13 | 14 | ldmia r0!, {r4, r5, r6, r7} 15 | bx r4 16 | 17 | .global render_stop 18 | .type render_stop,%function 19 | .thumb_func 20 | render_stop: 21 | pop {r4, r5, r6, r7} 22 | bx lr 23 | 24 | // args: input, stride, [rs1: u8, ls: u8, rs0: u8] 25 | .global render_blit_simple 26 | .type render_blit_simple,%function 27 | .thumb_func 28 | render_blit_simple: 29 | muls r6, r2 30 | ldr r4, [r5, r6] 31 | lsrs r4, r7 32 | lsrs r7, #8 33 | lsls r4, r7 34 | lsrs r7, #8 35 | lsrs r4, r7 36 | orrs r3, r4 37 | ldmia r0!, {r4, r5, r6, r7} 38 | bx r4 39 | 40 | // args: input, stride, [rs1: u8, ls: u8, rs0: u8] 41 | .global render_blit_out 42 | .type render_blit_out,%function 43 | .thumb_func 44 | render_blit_out: 45 | muls r6, r2 46 | ldr r4, [r5, r6] 47 | lsrs r4, r7 48 | lsrs r7, #8 49 | lsls r4, r7 50 | lsrs r7, #8 51 | lsrs r4, r7 52 | orrs r3, r4 53 | stmia r1!, {r3} 54 | movs r3, #0 55 | ldmia r0!, {r4, r5, r6, r7} 56 | bx r4 57 | 58 | // args: input, stride, [rs1: u8, ls1: u8, ls0: u8, rs0: u8] 59 | .global render_blit_straddle 60 | .type render_blit_straddle,%function 61 | .thumb_func 62 | render_blit_straddle: 63 | muls r6, r2 64 | ldr r4, [r5, r6] 65 | mov r5, r4 66 | lsrs r4, r7 67 | lsrs r7, #8 68 | lsls r4, r7 69 | orrs r3, r4 70 | stmia r1!, {r3} 71 | lsrs r7, #8 72 | lsls r5, r7 73 | lsrs r7, #8 74 | lsrs r5, r7 75 | movs r3, r5 76 | ldmia r0!, {r4, r5, r6, r7} 77 | bx r4 78 | 79 | // args: input, stride, [rs1: u8, ls1: u8, ls0: u8, rs0: u8] 80 | .global render_blit_straddle_out 81 | .type render_blit_straddle_out,%function 82 | .thumb_func 83 | render_blit_straddle_out: 84 | muls r6, r2 85 | ldr r4, [r5, r6] 86 | mov r5, r4 87 | lsrs r4, r7 88 | lsrs r7, #8 89 | lsls r4, r7 90 | orrs r3, r4 91 | stmia r1!, {r3} 92 | lsrs r7, #8 93 | lsls r5, r7 94 | lsrs r7, #8 95 | lsrs r5, r7 96 | stmia r1!, {r5} 97 | movs r3, #0 98 | ldmia r0!, {r4, r5, r6, r7} 99 | bx r4 100 | 101 | // args: input, stride 102 | .global render_blit_64_aligned 103 | .type render_blit_64_aligned,%function 104 | .thumb_func 105 | render_blit_64_aligned: 106 | muls r6, r2 107 | adds r5, r6 108 | ldm r5, {r4, r5} 109 | stmia r1!, {r4, r5} 110 | mov r4, r7 111 | ldmia r0!, {r5, r6, r7} 112 | bx r4 113 | 114 | // args: input, stride, [ls: u8, rs: u8] 115 | .global render_blit_64_straddle 116 | .type render_blit_64_straddle,%function 117 | .thumb_func 118 | render_blit_64_straddle: 119 | muls r6, r2 120 | adds r5, r6 121 | ldm r5, {r4, r5} 122 | movs r6, r4 123 | lsls r4, r7 124 | orrs r4, r3 125 | movs r3, r5 126 | lsls r5, r7 127 | lsrs r7, #8 128 | lsrs r6, r7 129 | orrs r5, r6 130 | stmia r1!, {r4, r5} 131 | lsrs r3, r7 132 | ldmia r0!, {r4, r5, r6, r7} 133 | bx r4 134 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | mod font; 2 | mod palette; 3 | mod queue; 4 | mod renderlist; 5 | mod swapcell; 6 | 7 | pub use font::FONT_HEIGHT; 8 | pub use palette::{ 9 | init_4bpp_palette, quantized_1bpp_palette, PaletteEntry, BW_PALETTE, GLOBAL_PALETTE, 10 | }; 11 | 12 | use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; 13 | 14 | use rp_pico::{ 15 | hal::{sio::SioFifo, Sio}, 16 | pac, 17 | }; 18 | 19 | use crate::{ 20 | dvi::{tmds::TmdsPair, VERTICAL_REPEAT}, 21 | scanlist::{Scanlist, ScanlistBuilder}, 22 | }; 23 | 24 | use self::{ 25 | queue::Queue, 26 | renderlist::{Renderlist, RenderlistBuilder}, 27 | swapcell::SwapCell, 28 | }; 29 | 30 | pub const N_LINE_BUFS: usize = 4; 31 | 32 | pub struct ScanRender { 33 | display_list: DisplayList, 34 | stripe_remaining: u32, 35 | scan_ptr: *const u32, 36 | scan_next: *const u32, 37 | assigned: [bool; N_LINE_BUFS], 38 | fifo: SioFifo, 39 | render_ptr: *const u32, 40 | render_y: u32, 41 | } 42 | 43 | /// Size of a line buffer in u32 units. 44 | const LINE_BUF_SIZE: usize = 256; 45 | 46 | static mut LINE_BUFS: [LineBuf; N_LINE_BUFS] = [LineBuf::zero(); N_LINE_BUFS]; 47 | const ATOMIC_FALSE: AtomicBool = AtomicBool::new(false); 48 | static PENDING: [AtomicBool; N_LINE_BUFS] = [ATOMIC_FALSE; N_LINE_BUFS]; 49 | 50 | /// The maximum number of lines that can be scheduled on core1. 51 | const MAX_CORE1_PENDING: usize = 1; 52 | const LINE_QUEUE_SIZE: usize = (MAX_CORE1_PENDING + 1).next_power_of_two(); 53 | pub static CORE1_QUEUE: Queue = Queue::new(); 54 | 55 | /// A complete display list. 56 | /// 57 | /// This can be seen as an expression that generates the pixels for a frame. 58 | pub struct DisplayList { 59 | render: Renderlist, 60 | scan: Scanlist, 61 | } 62 | 63 | static DISPLAY_LIST_SWAPCELL: SwapCell = SwapCell::new(); 64 | 65 | #[derive(Clone, Copy)] 66 | pub struct LineBuf { 67 | render_ptr: *const u32, 68 | /// Y coordinate relative to top of stripe. 69 | y: u32, 70 | buf: [u32; LINE_BUF_SIZE], 71 | } 72 | 73 | impl LineBuf { 74 | const fn zero() -> Self { 75 | let render_ptr = core::ptr::null(); 76 | let y = 0; 77 | let buf = [0; LINE_BUF_SIZE]; 78 | LineBuf { render_ptr, y, buf } 79 | } 80 | } 81 | 82 | core::arch::global_asm! { 83 | include_str!("scan.asm"), 84 | include_str!("render.asm"), 85 | options(raw) 86 | } 87 | 88 | extern "C" { 89 | fn tmds_scan( 90 | scan_list: *const u32, 91 | input: *const u32, 92 | output: *mut TmdsPair, 93 | stride: u32, 94 | ) -> *const u32; 95 | 96 | fn render_engine(render_list: *const u32, output: *mut u32, y: u32); 97 | } 98 | 99 | pub const fn rgb(r: u8, g: u8, b: u8) -> [TmdsPair; 3] { 100 | [ 101 | TmdsPair::encode_balanced_approx(b), 102 | TmdsPair::encode_balanced_approx(g), 103 | TmdsPair::encode_balanced_approx(r), 104 | ] 105 | } 106 | 107 | /// Creates a TMDS pair per color channel of a 24 bit RGB color. 108 | /// 109 | /// The input color is of the form 0xRRGGBB. 110 | /// 111 | /// If each channel is already separated out, use [`rgb`] instead. 112 | pub const fn xrgb(color: u32) -> [TmdsPair; 3] { 113 | [ 114 | TmdsPair::encode_balanced_approx(color as u8), 115 | TmdsPair::encode_balanced_approx((color >> 8) as u8), 116 | TmdsPair::encode_balanced_approx((color >> 16) as u8), 117 | ] 118 | } 119 | 120 | impl ScanRender { 121 | pub fn new() -> Self { 122 | let stripe_remaining = 0; 123 | let scan_ptr = core::ptr::null(); 124 | let scan_next = core::ptr::null(); 125 | // Safety: it makes sense for two cores to both have access to the 126 | // fifo, as it's designed for that purpose. A better PAC API might 127 | // allow us to express this safely. 128 | let pac = unsafe { pac::Peripherals::steal() }; 129 | let sio = Sio::new(pac.SIO); 130 | let fifo = sio.fifo; 131 | let render_ptr = core::ptr::null(); 132 | let render_y = 0; 133 | let display_list = DisplayList::new(640, 480 / VERTICAL_REPEAT as u32); 134 | ScanRender { 135 | stripe_remaining, 136 | scan_ptr, 137 | scan_next, 138 | assigned: [false; N_LINE_BUFS], 139 | fifo, 140 | render_ptr, 141 | render_y, 142 | display_list, 143 | } 144 | } 145 | 146 | #[link_section = ".data"] 147 | #[inline(never)] 148 | pub fn render_scanline(&mut self, tmds_buf: &mut [TmdsPair], y: u32, available: bool) { 149 | unsafe { 150 | if y == 0 { 151 | self.scan_next = self.display_list.scan.get().as_ptr(); 152 | } 153 | if self.stripe_remaining == 0 { 154 | self.stripe_remaining = self.scan_next.read(); 155 | self.scan_ptr = self.scan_next.add(1); 156 | // TODO: set desperate scan_next 157 | } 158 | if available { 159 | let line_ix = y as usize % N_LINE_BUFS; 160 | let line_buf_ptr = LINE_BUFS[line_ix].buf.as_ptr(); 161 | self.scan_next = 162 | tmds_scan(self.scan_ptr, line_buf_ptr, tmds_buf.as_mut_ptr(), 1280); 163 | } 164 | self.stripe_remaining -= 1; 165 | } 166 | } 167 | 168 | #[link_section = ".data"] 169 | pub fn is_line_available(&self, y: u32) -> bool { 170 | let line_ix = y as usize % N_LINE_BUFS; 171 | self.assigned[line_ix] && !PENDING[line_ix].load(Ordering::Relaxed) 172 | } 173 | 174 | #[link_section = ".data"] 175 | pub fn schedule_line_render(&mut self, y: u32) { 176 | if y == 0 { 177 | // The swap is scheduled on scanline 0, but we'd want to move this 178 | // earlier if we wanted to do more stuff like palettes. 179 | DISPLAY_LIST_SWAPCELL.try_swap_by_system(&mut self.display_list); 180 | 181 | self.render_ptr = self.display_list.render.get().as_ptr(); 182 | self.render_y = 0; 183 | } 184 | let line_ix = y as usize % N_LINE_BUFS; 185 | if PENDING[line_ix].load(Ordering::Relaxed) { 186 | self.assigned[line_ix] = false; 187 | return; 188 | } 189 | let render_ptr = self.render_ptr; 190 | // TODO: set ptr, y in linebuf 191 | // Safety: we currently own access to the line buffer. 192 | let line_buf = unsafe { &mut LINE_BUFS[line_ix as usize] }; 193 | line_buf.render_ptr = unsafe { render_ptr.add(2) }; 194 | line_buf.y = self.render_y; 195 | self.render_y += 1; 196 | let stripe_height = unsafe { render_ptr.read() }; 197 | if self.render_y == stripe_height { 198 | let jump = unsafe { render_ptr.add(1).read() as usize }; 199 | self.render_ptr = unsafe { self.display_list.render.get().as_ptr().add(jump) }; 200 | self.render_y = 0; 201 | } 202 | if CORE1_QUEUE.len() < MAX_CORE1_PENDING { 203 | // schedule on core1 204 | PENDING[line_ix].store(true, Ordering::Relaxed); 205 | CORE1_QUEUE.push_unchecked(line_ix as u32); 206 | self.assigned[line_ix] = true; 207 | } else { 208 | // try to schedule on core0 209 | if self.fifo.is_write_ready() { 210 | PENDING[line_ix].store(true, Ordering::Relaxed); 211 | // Writes to channels are generally considered to be release, 212 | // but the implementation in rp2040-hal lacks such a fence, so 213 | // we include it explicitly. 214 | compiler_fence(Ordering::Release); 215 | self.fifo.write(line_ix as u32); 216 | self.assigned[line_ix] = true; 217 | } else { 218 | self.assigned[line_ix] = false; 219 | } 220 | } 221 | } 222 | } 223 | 224 | /// Entry point for rendering a line. 225 | /// 226 | /// This can be called by either core. 227 | #[link_section = ".data"] 228 | pub unsafe fn render_line(line_ix: u32) { 229 | let line_buf = &mut LINE_BUFS[line_ix as usize]; 230 | render_engine(line_buf.render_ptr, line_buf.buf.as_mut_ptr(), line_buf.y); 231 | PENDING[line_ix as usize].store(false, Ordering::Release); 232 | } 233 | 234 | impl DisplayList { 235 | pub fn new(width: u32, height: u32) -> Self { 236 | let mut rb = RenderlistBuilder::new(width); 237 | let mut sb = ScanlistBuilder::new(width, height); 238 | rb.begin_stripe(height); 239 | rb.end_stripe(); 240 | sb.begin_stripe(height); 241 | sb.solid(width, rgb(0, 0, 0)); 242 | sb.end_stripe(); 243 | let render = rb.build(); 244 | let scan = sb.build(); 245 | DisplayList { render, scan } 246 | } 247 | } 248 | 249 | /// The system assumes that this is called before [`start_display_list`] 250 | pub fn init_display_swapcell(width: u32) { 251 | // The display list doesn't have to be usable. 252 | DISPLAY_LIST_SWAPCELL.set_for_client(DisplayList::new(width, 0)); 253 | } 254 | 255 | /// Start building a display list. This blocks until a free display 256 | /// list is available. 257 | /// 258 | /// The user must have called [`init_display_swapcell`] before calling this. 259 | pub fn start_display_list() -> (RenderlistBuilder, ScanlistBuilder) { 260 | let display_list = DISPLAY_LIST_SWAPCELL.take_blocking(); 261 | let rb = RenderlistBuilder::recycle(display_list.render); 262 | let sb = ScanlistBuilder::recycle(display_list.scan); 263 | (rb, sb) 264 | } 265 | 266 | pub fn end_display_list(rb: RenderlistBuilder, sb: ScanlistBuilder) { 267 | let render = rb.build(); 268 | let scan = sb.build(); 269 | let display_list = DisplayList { render, scan }; 270 | DISPLAY_LIST_SWAPCELL.set_for_system(display_list); 271 | } 272 | -------------------------------------------------------------------------------- /src/render/font.rs: -------------------------------------------------------------------------------- 1 | // Font data translated from X11 font, tool forthcoming. 2 | 3 | pub const FONT_STRIDE: u32 = 92; 4 | pub const FONT_HEIGHT: u32 = 15; 5 | #[link_section = ".data"] 6 | pub static FONT_X_OFFSETS: &[u16] = &[ 7 | 604, 636, 251, 352, 360, 13, 86, 157, 283, 315, 25, 148, 189, 180, 221, 655, 368, 376, 384, 8 | 392, 400, 408, 416, 424, 432, 440, 663, 659, 448, 212, 456, 464, 0, 224, 233, 118, 128, 242, 9 | 472, 64, 138, 672, 57, 256, 480, 45, 160, 75, 265, 96, 170, 274, 288, 192, 297, 32, 306, 320, 10 | 329, 676, 680, 684, 338, 488, 666, 496, 345, 576, 504, 512, 688, 520, 528, 669, 696, 583, 699, 11 | 107, 536, 544, 552, 560, 640, 590, 692, 597, 608, 202, 615, 622, 629, 645, 704, 650, 568, 12 | ]; 13 | #[link_section = ".data"] 14 | pub static FONT_X_WIDTHS: &[u8] = &[ 15 | 4, 4, 5, 8, 8, 12, 10, 3, 5, 5, 7, 9, 3, 9, 3, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 4, 8, 9, 8, 16 | 8, 13, 9, 9, 10, 10, 9, 8, 11, 10, 4, 7, 9, 8, 12, 10, 11, 9, 11, 10, 9, 9, 10, 9, 13, 9, 9, 9, 17 | 4, 4, 4, 7, 8, 3, 8, 7, 7, 8, 8, 4, 8, 8, 3, 3, 7, 3, 11, 8, 8, 8, 8, 5, 7, 4, 7, 7, 10, 7, 7, 18 | 7, 5, 3, 5, 8, 19 | ]; 20 | #[link_section = ".data"] 21 | pub static FONT_BITS: &[u32] = &[ 22 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 23 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x1021c1e0, 0x40803041, 0x380f0, 0x1c000070, 0xc004083e, 0x3f906, 24 | 0x102, 0x53f8fc10, 0x40e0fc82, 0x140603ff, 0x421fd01, 0x103c3e00, 0x7e403c3c, 0x3c3c7e3c, 25 | 0xfe180000, 0x40000004, 0x20000, 0x0, 0x100, 0x40000000, 0x48040f00, 0x140c71e4, 0x2, 26 | 0x54122618, 0x40c07041, 0x60c630c, 0x6300018c, 0x800408c2, 0x6090a, 0x102, 0x50098438, 27 | 0x23198442, 0x220a0210, 0x4510183, 0x1c424928, 0x2604242, 0x42424042, 0x2660000, 0x40000004, 28 | 0x20000, 0x0, 0x100, 0x40000000, 0x44041080, 0x14224124, 0x2, 0x38122804, 0x40c070a1, 29 | 0x9082204, 0x41000104, 0x40040882, 0x4090a, 0x102, 0x50090428, 0x22090422, 0x21110410, 30 | 0x4508082, 0x10424928, 0x2504242, 0x42422002, 0x2420000, 0x40000004, 0x20000, 0x0, 0x100, 31 | 0x40000000, 0xc021080, 0x10224224, 0x2, 0x5409cac4, 0x40a0a8a2, 0x9101002, 0x8cd202, 0x1040902, 32 | 0x40912, 0x44502, 0x90428, 0x10090412, 0x40a10410, 0x74884044, 0x10420928, 0x2484040, 33 | 0x42422002, 0x2420000, 0x5c3c0004, 0x3a3a5c3c, 0x5c3a3c, 0x847113c, 0x47f071c1, 0x4112109a, 34 | 0x14774224, 0x2, 0x10080922, 0x40a0a8a2, 0x6101002, 0x933202, 0x1040902, 0x20912, 0x44502, 35 | 0x88444, 0x1031840a, 0x40418c10, 0xcc886044, 0x10420a7e, 0x3e444020, 0x4242103a, 0x2400660, 36 | 0x66660004, 0x66666666, 0x666666, 0x8488966, 0x4410d141, 0x41121086, 0x14224224, 0x2, 0x40912, 37 | 0x409128a2, 0x21013c2, 0x911202, 0x107f902, 0x1f922, 0x7e44502, 0x1f87c44, 0x10c0fc0e, 38 | 0x40408810, 0x84002028, 0x10421c14, 0x40423810, 0x7c3c1046, 0x7e201818, 0x42400004, 0x42424242, 39 | 0x4c424242, 0x8418502, 0x42088a22, 0x40012042, 0x14224424, 0x2, 0x20912, 0x40912514, 40 | 0x25101202, 0x911222, 0xfe40902, 0xfe20922, 0x24902, 0x88482, 0x13000412, 0x40a08810, 41 | 0x84001038, 0x10422814, 0x40424008, 0x40420842, 0x2106006, 0x427c0004, 0x4242427e, 0x5a424242, 42 | 0x8470302, 0x41088422, 0x40014022, 0x14224424, 0x2, 0x720592, 0x408a2514, 0x28901202, 0x911242, 43 | 0x1040902, 0x40942, 0x7e24902, 0x904fe, 0x12000422, 0x41105010, 0x84001810, 0x1042483f, 44 | 0x40fe4004, 0x40420842, 0x2081818, 0x42460004, 0x42424202, 0x32424242, 0x84c0502, 0x40890422, 45 | 0x40012042, 0x14224424, 0x2, 0x890362, 0x428a2514, 0x10882204, 0x41111184, 0x1040882, 0x40942, 46 | 0x2a902, 0x90482, 0x12080442, 0x42085010, 0x84000810, 0x1042490a, 0x42404202, 0x42420442, 47 | 0x2000660, 0x42420004, 0x42424202, 0x424242, 0x8480942, 0x450a14, 0x40009082, 0x14224824, 0x2, 48 | 0x890004, 0x42842208, 0x288c638c, 0x6311118c, 0x10408c2, 0x40040982, 0x40011084, 0x98501, 49 | 0x13180482, 0x42082010, 0xcc000410, 0x1042490a, 0x42404202, 0x42420442, 0x2080000, 0x66660004, 50 | 0x42426666, 0x666666, 0xcc89166, 0x40261114, 0x41109082, 0x14224824, 0x2, 0x70820c, 0x3c842208, 51 | 0x47038270, 0x1c111270, 0x4083e, 0x40040982, 0x40011078, 0x3f8fd01, 0x10e00502, 0x44042010, 52 | 0x7401fc10, 0x103c3e0a, 0x3c403c7e, 0x3c3c043c, 0x2080000, 0x5cdc00fc, 0x42425c3c, 0x5c3a3c, 53 | 0xb87213c, 0x47e23188, 0x41109082, 0x14c24824, 0x2, 0x1f0, 0x0, 0x0, 0x0, 0x0, 0x40000000, 0x0, 54 | 0x0, 0x20000000, 0x20000000, 0x0, 0x800, 0x0, 0x0, 0x0, 0x0, 0x4000, 0x400200, 0x0, 0x20000, 55 | 0x101080, 0x4004020, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000, 0x0, 0x0, 0x20000000, 56 | 0x20000000, 0x0, 0x800, 0x0, 0x0, 0x0, 0x0, 0x6600, 0x400200, 0x0, 0x30000, 0x81080, 0x4004020, 57 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000, 0x10000000, 0x0, 0x0, 0x0, 0x0, 0x0, 58 | 0xff00, 0x3c00, 0x400200, 0x0, 0x18000, 0xf00, 0x30070e0, 0x2, 59 | ]; 60 | -------------------------------------------------------------------------------- /src/render/palette.rs: -------------------------------------------------------------------------------- 1 | use crate::dvi::tmds::TmdsPair; 2 | 3 | #[repr(C)] 4 | #[derive(Clone, Copy)] 5 | pub struct PaletteEntry { 6 | blue: TmdsPair, 7 | green: TmdsPair, 8 | red: TmdsPair, 9 | padding: u32, 10 | } 11 | 12 | impl PaletteEntry { 13 | /// Create a palette entry for a pair of gray pixels. 14 | /// 15 | /// The values *should* be chosen so the result is DC-balanced, but 16 | /// that isn't enforced. 17 | pub const fn gray_pair(gray0: u8, gray1: u8) -> PaletteEntry { 18 | let pair = TmdsPair::encode_pair(gray0, gray1); 19 | PaletteEntry { 20 | blue: pair, 21 | green: pair, 22 | red: pair, 23 | padding: 0, 24 | } 25 | } 26 | 27 | /// Create a palette entry for a pair of colors. 28 | /// 29 | /// The input colors are of the form 0xRRGGBB. 30 | /// 31 | /// The colors are quantized to 4 bits per component and chosen from a 32 | /// a precomputed palette of DC-balanced pairs. 33 | pub const fn quantized_4bit(color0: u32, color1: u32) -> PaletteEntry { 34 | // Quantize to 4 bits 35 | const fn extract_quantize(rgb: u32, ix: u32) -> usize { 36 | let val = (rgb >> (ix * 8)) & 0xff; 37 | ((val * 3855 + 32768) >> 16) as usize 38 | } 39 | 40 | PaletteEntry { 41 | blue: GRAY_4BPP_CONST[extract_quantize(color0, 0) + extract_quantize(color1, 0) * 16], 42 | green: GRAY_4BPP_CONST[extract_quantize(color0, 1) + extract_quantize(color1, 1) * 16], 43 | red: GRAY_4BPP_CONST[extract_quantize(color0, 2) + extract_quantize(color1, 2) * 16], 44 | padding: 0, 45 | } 46 | } 47 | } 48 | 49 | pub const fn quantized_1bpp_palette(color0: u32, color1: u32) -> [PaletteEntry; 4] { 50 | [ 51 | PaletteEntry::quantized_4bit(color0, color0), 52 | PaletteEntry::quantized_4bit(color1, color0), 53 | PaletteEntry::quantized_4bit(color0, color1), 54 | PaletteEntry::quantized_4bit(color1, color1), 55 | ] 56 | } 57 | 58 | #[link_section = ".scratch_x"] 59 | pub static BW_PALETTE: [PaletteEntry; 4] = [ 60 | PaletteEntry::gray_pair(0, 1), 61 | PaletteEntry::gray_pair(0xff, 1), 62 | PaletteEntry::gray_pair(0, 0xfe), 63 | PaletteEntry::gray_pair(0xff, 0xfe), 64 | ]; 65 | 66 | #[link_section = ".data"] 67 | pub static mut GLOBAL_PALETTE: [PaletteEntry; 256] = [PaletteEntry::gray_pair(0, 1); 256]; 68 | 69 | /// Initialize a palette from a list of 16 RGB colors. 70 | /// 71 | /// The input colors are of the form 0xRRGGBB. 72 | /// 73 | /// Note: this implementation quantizes to 4 bits per component and uses 74 | /// a precomputed palette. A more precise approach is possible, at the cost 75 | /// of more compute time (and code complexity). 76 | pub fn init_4bpp_palette(pal: &mut [PaletteEntry; 256], rgb: &[u32; 16]) { 77 | // Get a component from the 24bpp RGB value, quantizing to 4 bits 78 | fn get(rgb: u32, ix: u32) -> usize { 79 | let val = (rgb >> (ix * 8)) & 0xff; 80 | ((val * 3855 + 32768) >> 16) as usize 81 | } 82 | for i in 0..256 { 83 | let rgb0 = rgb[i % 16]; 84 | let rgb1 = rgb[i / 16]; 85 | pal[i].blue = GRAY_4BPP[get(rgb0, 0) + get(rgb1, 0) * 16]; 86 | pal[i].green = GRAY_4BPP[get(rgb0, 1) + get(rgb1, 1) * 16]; 87 | pal[i].red = GRAY_4BPP[get(rgb0, 2) + get(rgb1, 2) * 16]; 88 | } 89 | } 90 | 91 | // Static for use in non-const code 92 | static GRAY_4BPP: [TmdsPair; 256] = GRAY_4BPP_CONST; 93 | // Const for use in const fn as they can not refer to statics 94 | const GRAY_4BPP_CONST: [TmdsPair; 256] = [ 95 | // This was auto-generated by garden/tmds.py 96 | TmdsPair::encode_pair(0x00, 0x01), // (+0, +1) 97 | TmdsPair::encode_pair(0x0e, 0x02), // (-3, +2) 98 | TmdsPair::encode_pair(0x1f, 0x04), // (-3, +4) 99 | TmdsPair::encode_pair(0x31, 0x01), // (-2, +1) 100 | TmdsPair::encode_pair(0x41, 0x02), // (-3, +2) 101 | TmdsPair::encode_pair(0x52, 0x04), // (-3, +4) 102 | TmdsPair::encode_pair(0x66, 0x02), // (+0, +2) 103 | TmdsPair::encode_pair(0x78, 0x02), // (+1, +2) 104 | TmdsPair::encode_pair(0x87, 0x02), // (-1, +2) 105 | TmdsPair::encode_pair(0x99, 0x02), // (+0, +2) 106 | TmdsPair::encode_pair(0xa6, 0x04), // (-4, +4) 107 | TmdsPair::encode_pair(0xb8, 0x04), // (-3, +4) 108 | TmdsPair::encode_pair(0xce, 0x01), // (+2, +1) 109 | TmdsPair::encode_pair(0xda, 0x04), // (-3, +4) 110 | TmdsPair::encode_pair(0xeb, 0x04), // (-3, +4) 111 | TmdsPair::encode_pair(0xff, 0x01), // (+0, +1) 112 | TmdsPair::encode_pair(0x03, 0x0f), // (+3, -2) 113 | TmdsPair::encode_pair(0x11, 0x11), // (+0, +0) 114 | TmdsPair::encode_pair(0x22, 0x11), // (+0, +0) 115 | TmdsPair::encode_pair(0x33, 0x12), // (+0, +1) 116 | TmdsPair::encode_pair(0x44, 0x11), // (+0, +0) 117 | TmdsPair::encode_pair(0x55, 0x11), // (+0, +0) 118 | TmdsPair::encode_pair(0x68, 0x10), // (+2, -1) 119 | TmdsPair::encode_pair(0x77, 0x11), // (+0, +0) 120 | TmdsPair::encode_pair(0x88, 0x11), // (+0, +0) 121 | TmdsPair::encode_pair(0x98, 0x12), // (-1, +1) 122 | TmdsPair::encode_pair(0xaa, 0x11), // (+0, +0) 123 | TmdsPair::encode_pair(0xbb, 0x11), // (+0, +0) 124 | TmdsPair::encode_pair(0xcc, 0x12), // (+0, +1) 125 | TmdsPair::encode_pair(0xdd, 0x11), // (+0, +0) 126 | TmdsPair::encode_pair(0xee, 0x11), // (+0, +0) 127 | TmdsPair::encode_pair(0xfc, 0x14), // (-3, +3) 128 | TmdsPair::encode_pair(0x04, 0x1f), // (+4, -3) 129 | TmdsPair::encode_pair(0x11, 0x22), // (+0, +0) 130 | TmdsPair::encode_pair(0x22, 0x22), // (+0, +0) 131 | TmdsPair::encode_pair(0x35, 0x21), // (+2, -1) 132 | TmdsPair::encode_pair(0x44, 0x22), // (+0, +0) 133 | TmdsPair::encode_pair(0x55, 0x22), // (+0, +0) 134 | TmdsPair::encode_pair(0x64, 0x24), // (-2, +2) 135 | TmdsPair::encode_pair(0x77, 0x22), // (+0, +0) 136 | TmdsPair::encode_pair(0x88, 0x22), // (+0, +0) 137 | TmdsPair::encode_pair(0x9a, 0x21), // (+1, -1) 138 | TmdsPair::encode_pair(0xaa, 0x22), // (+0, +0) 139 | TmdsPair::encode_pair(0xbb, 0x22), // (+0, +0) 140 | TmdsPair::encode_pair(0xcb, 0x24), // (-1, +2) 141 | TmdsPair::encode_pair(0xdd, 0x22), // (+0, +0) 142 | TmdsPair::encode_pair(0xee, 0x22), // (+0, +0) 143 | TmdsPair::encode_pair(0xfa, 0x24), // (-5, +2) 144 | TmdsPair::encode_pair(0x03, 0x31), // (+3, -2) 145 | TmdsPair::encode_pair(0x0f, 0x35), // (-2, +2) 146 | TmdsPair::encode_pair(0x20, 0x34), // (-2, +1) 147 | TmdsPair::encode_pair(0x33, 0x32), // (+0, -1) 148 | TmdsPair::encode_pair(0x43, 0x34), // (-1, +1) 149 | TmdsPair::encode_pair(0x56, 0x32), // (+1, -1) 150 | TmdsPair::encode_pair(0x66, 0x33), // (+0, +0) 151 | TmdsPair::encode_pair(0x78, 0x33), // (+1, +0) 152 | TmdsPair::encode_pair(0x87, 0x33), // (-1, +0) 153 | TmdsPair::encode_pair(0x99, 0x33), // (+0, +0) 154 | TmdsPair::encode_pair(0xa8, 0x34), // (-2, +1) 155 | TmdsPair::encode_pair(0xbd, 0x32), // (+2, -1) 156 | TmdsPair::encode_pair(0xcc, 0x32), // (+0, -1) 157 | TmdsPair::encode_pair(0xde, 0x32), // (+1, -1) 158 | TmdsPair::encode_pair(0xed, 0x34), // (-1, +1) 159 | TmdsPair::encode_pair(0xfc, 0x33), // (-3, +0) 160 | TmdsPair::encode_pair(0x04, 0x40), // (+4, -4) 161 | TmdsPair::encode_pair(0x11, 0x44), // (+0, +0) 162 | TmdsPair::encode_pair(0x22, 0x44), // (+0, +0) 163 | TmdsPair::encode_pair(0x33, 0x43), // (+0, -1) 164 | TmdsPair::encode_pair(0x44, 0x44), // (+0, +0) 165 | TmdsPair::encode_pair(0x55, 0x44), // (+0, +0) 166 | TmdsPair::encode_pair(0x67, 0x43), // (+1, -1) 167 | TmdsPair::encode_pair(0x77, 0x44), // (+0, +0) 168 | TmdsPair::encode_pair(0x88, 0x44), // (+0, +0) 169 | TmdsPair::encode_pair(0x9b, 0x43), // (+2, -1) 170 | TmdsPair::encode_pair(0xaa, 0x44), // (+0, +0) 171 | TmdsPair::encode_pair(0xbb, 0x44), // (+0, +0) 172 | TmdsPair::encode_pair(0xcc, 0x43), // (+0, -1) 173 | TmdsPair::encode_pair(0xdd, 0x44), // (+0, +0) 174 | TmdsPair::encode_pair(0xee, 0x44), // (+0, +0) 175 | TmdsPair::encode_pair(0xfa, 0x48), // (-5, +4) 176 | TmdsPair::encode_pair(0x05, 0x51), // (+5, -4) 177 | TmdsPair::encode_pair(0x11, 0x55), // (+0, +0) 178 | TmdsPair::encode_pair(0x22, 0x55), // (+0, +0) 179 | TmdsPair::encode_pair(0x32, 0x56), // (-1, +1) 180 | TmdsPair::encode_pair(0x44, 0x55), // (+0, +0) 181 | TmdsPair::encode_pair(0x55, 0x55), // (+0, +0) 182 | TmdsPair::encode_pair(0x66, 0x56), // (+0, +1) 183 | TmdsPair::encode_pair(0x77, 0x55), // (+0, +0) 184 | TmdsPair::encode_pair(0x88, 0x55), // (+0, +0) 185 | TmdsPair::encode_pair(0x99, 0x56), // (+0, +1) 186 | TmdsPair::encode_pair(0xaa, 0x55), // (+0, +0) 187 | TmdsPair::encode_pair(0xbb, 0x55), // (+0, +0) 188 | TmdsPair::encode_pair(0xca, 0x56), // (-2, +1) 189 | TmdsPair::encode_pair(0xdd, 0x55), // (+0, +0) 190 | TmdsPair::encode_pair(0xee, 0x55), // (+0, +0) 191 | TmdsPair::encode_pair(0xfb, 0x56), // (-4, +1) 192 | TmdsPair::encode_pair(0x02, 0x66), // (+2, +0) 193 | TmdsPair::encode_pair(0x13, 0x65), // (+2, -1) 194 | TmdsPair::encode_pair(0x24, 0x64), // (+2, -2) 195 | TmdsPair::encode_pair(0x33, 0x66), // (+0, +0) 196 | TmdsPair::encode_pair(0x43, 0x67), // (-1, +1) 197 | TmdsPair::encode_pair(0x56, 0x65), // (+1, -1) 198 | TmdsPair::encode_pair(0x66, 0x67), // (+0, +1) 199 | TmdsPair::encode_pair(0x79, 0x65), // (+2, -1) 200 | TmdsPair::encode_pair(0x87, 0x67), // (-1, +1) 201 | TmdsPair::encode_pair(0x99, 0x67), // (+0, +1) 202 | TmdsPair::encode_pair(0xac, 0x64), // (+2, -2) 203 | TmdsPair::encode_pair(0xbd, 0x65), // (+2, -1) 204 | TmdsPair::encode_pair(0xcc, 0x66), // (+0, +0) 205 | TmdsPair::encode_pair(0xde, 0x65), // (+1, -1) 206 | TmdsPair::encode_pair(0xed, 0x67), // (-1, +1) 207 | TmdsPair::encode_pair(0xfd, 0x66), // (-2, +0) 208 | TmdsPair::encode_pair(0x02, 0x78), // (+2, +1) 209 | TmdsPair::encode_pair(0x11, 0x77), // (+0, +0) 210 | TmdsPair::encode_pair(0x22, 0x77), // (+0, +0) 211 | TmdsPair::encode_pair(0x33, 0x78), // (+0, +1) 212 | TmdsPair::encode_pair(0x44, 0x77), // (+0, +0) 213 | TmdsPair::encode_pair(0x55, 0x77), // (+0, +0) 214 | TmdsPair::encode_pair(0x68, 0x76), // (+2, -1) 215 | TmdsPair::encode_pair(0x77, 0x77), // (+0, +0) 216 | TmdsPair::encode_pair(0x88, 0x77), // (+0, +0) 217 | TmdsPair::encode_pair(0x98, 0x78), // (-1, +1) 218 | TmdsPair::encode_pair(0xaa, 0x77), // (+0, +0) 219 | TmdsPair::encode_pair(0xbb, 0x77), // (+0, +0) 220 | TmdsPair::encode_pair(0xcc, 0x78), // (+0, +1) 221 | TmdsPair::encode_pair(0xdd, 0x77), // (+0, +0) 222 | TmdsPair::encode_pair(0xee, 0x77), // (+0, +0) 223 | TmdsPair::encode_pair(0xfd, 0x78), // (-2, +1) 224 | TmdsPair::encode_pair(0x03, 0x86), // (+3, -2) 225 | TmdsPair::encode_pair(0x11, 0x88), // (+0, +0) 226 | TmdsPair::encode_pair(0x22, 0x88), // (+0, +0) 227 | TmdsPair::encode_pair(0x33, 0x87), // (+0, -1) 228 | TmdsPair::encode_pair(0x44, 0x88), // (+0, +0) 229 | TmdsPair::encode_pair(0x55, 0x88), // (+0, +0) 230 | TmdsPair::encode_pair(0x67, 0x87), // (+1, -1) 231 | TmdsPair::encode_pair(0x77, 0x88), // (+0, +0) 232 | TmdsPair::encode_pair(0x88, 0x88), // (+0, +0) 233 | TmdsPair::encode_pair(0x9b, 0x87), // (+2, -1) 234 | TmdsPair::encode_pair(0xaa, 0x88), // (+0, +0) 235 | TmdsPair::encode_pair(0xbb, 0x88), // (+0, +0) 236 | TmdsPair::encode_pair(0xcc, 0x87), // (+0, -1) 237 | TmdsPair::encode_pair(0xdd, 0x88), // (+0, +0) 238 | TmdsPair::encode_pair(0xee, 0x88), // (+0, +0) 239 | TmdsPair::encode_pair(0xfb, 0x8c), // (-4, +4) 240 | TmdsPair::encode_pair(0x03, 0x98), // (+3, -1) 241 | TmdsPair::encode_pair(0x13, 0x99), // (+2, +0) 242 | TmdsPair::encode_pair(0x21, 0x99), // (-1, +0) 243 | TmdsPair::encode_pair(0x33, 0x99), // (+0, +0) 244 | TmdsPair::encode_pair(0x42, 0x9a), // (-2, +1) 245 | TmdsPair::encode_pair(0x56, 0x99), // (+1, +0) 246 | TmdsPair::encode_pair(0x66, 0x98), // (+0, -1) 247 | TmdsPair::encode_pair(0x78, 0x98), // (+1, -1) 248 | TmdsPair::encode_pair(0x89, 0x97), // (+1, -2) 249 | TmdsPair::encode_pair(0x99, 0x98), // (+0, -1) 250 | TmdsPair::encode_pair(0xa9, 0x99), // (-1, +0) 251 | TmdsPair::encode_pair(0xb9, 0x9b), // (-2, +2) 252 | TmdsPair::encode_pair(0xcc, 0x99), // (+0, +0) 253 | TmdsPair::encode_pair(0xde, 0x99), // (+1, +0) 254 | TmdsPair::encode_pair(0xec, 0x9a), // (-2, +1) 255 | TmdsPair::encode_pair(0xfc, 0x98), // (-3, -1) 256 | TmdsPair::encode_pair(0x04, 0xa6), // (+4, -4) 257 | TmdsPair::encode_pair(0x11, 0xaa), // (+0, +0) 258 | TmdsPair::encode_pair(0x22, 0xaa), // (+0, +0) 259 | TmdsPair::encode_pair(0x35, 0xa9), // (+2, -1) 260 | TmdsPair::encode_pair(0x44, 0xaa), // (+0, +0) 261 | TmdsPair::encode_pair(0x55, 0xaa), // (+0, +0) 262 | TmdsPair::encode_pair(0x64, 0xac), // (-2, +2) 263 | TmdsPair::encode_pair(0x77, 0xaa), // (+0, +0) 264 | TmdsPair::encode_pair(0x88, 0xaa), // (+0, +0) 265 | TmdsPair::encode_pair(0x9a, 0xa9), // (+1, -1) 266 | TmdsPair::encode_pair(0xaa, 0xaa), // (+0, +0) 267 | TmdsPair::encode_pair(0xbb, 0xaa), // (+0, +0) 268 | TmdsPair::encode_pair(0xcb, 0xac), // (-1, +2) 269 | TmdsPair::encode_pair(0xdd, 0xaa), // (+0, +0) 270 | TmdsPair::encode_pair(0xee, 0xaa), // (+0, +0) 271 | TmdsPair::encode_pair(0xfb, 0xad), // (-4, +3) 272 | TmdsPair::encode_pair(0x04, 0xb8), // (+4, -3) 273 | TmdsPair::encode_pair(0x11, 0xbb), // (+0, +0) 274 | TmdsPair::encode_pair(0x22, 0xbb), // (+0, +0) 275 | TmdsPair::encode_pair(0x34, 0xb9), // (+1, -2) 276 | TmdsPair::encode_pair(0x44, 0xbb), // (+0, +0) 277 | TmdsPair::encode_pair(0x55, 0xbb), // (+0, +0) 278 | TmdsPair::encode_pair(0x68, 0xba), // (+2, -1) 279 | TmdsPair::encode_pair(0x77, 0xbb), // (+0, +0) 280 | TmdsPair::encode_pair(0x88, 0xbb), // (+0, +0) 281 | TmdsPair::encode_pair(0x9b, 0xb9), // (+2, -2) 282 | TmdsPair::encode_pair(0xaa, 0xbb), // (+0, +0) 283 | TmdsPair::encode_pair(0xbb, 0xbb), // (+0, +0) 284 | TmdsPair::encode_pair(0xcb, 0xbc), // (-1, +1) 285 | TmdsPair::encode_pair(0xdd, 0xbb), // (+0, +0) 286 | TmdsPair::encode_pair(0xee, 0xbb), // (+0, +0) 287 | TmdsPair::encode_pair(0xfd, 0xbe), // (-2, +3) 288 | TmdsPair::encode_pair(0x02, 0xcd), // (+2, +1) 289 | TmdsPair::encode_pair(0x12, 0xcc), // (+1, +0) 290 | TmdsPair::encode_pair(0x24, 0xcb), // (+2, -1) 291 | TmdsPair::encode_pair(0x33, 0xcd), // (+0, +1) 292 | TmdsPair::encode_pair(0x43, 0xcc), // (-1, +0) 293 | TmdsPair::encode_pair(0x57, 0xcb), // (+2, -1) 294 | TmdsPair::encode_pair(0x66, 0xcc), // (+0, +0) 295 | TmdsPair::encode_pair(0x78, 0xcc), // (+1, +0) 296 | TmdsPair::encode_pair(0x87, 0xcc), // (-1, +0) 297 | TmdsPair::encode_pair(0x99, 0xcc), // (+0, +0) 298 | TmdsPair::encode_pair(0xac, 0xcb), // (+2, -1) 299 | TmdsPair::encode_pair(0xbc, 0xcc), // (+1, +0) 300 | TmdsPair::encode_pair(0xcc, 0xcd), // (+0, +1) 301 | TmdsPair::encode_pair(0xdf, 0xcb), // (+2, -1) 302 | TmdsPair::encode_pair(0xed, 0xcc), // (-1, +0) 303 | TmdsPair::encode_pair(0xfd, 0xcd), // (-2, +1) 304 | TmdsPair::encode_pair(0x04, 0xda), // (+4, -3) 305 | TmdsPair::encode_pair(0x11, 0xdd), // (+0, +0) 306 | TmdsPair::encode_pair(0x22, 0xdd), // (+0, +0) 307 | TmdsPair::encode_pair(0x32, 0xde), // (-1, +1) 308 | TmdsPair::encode_pair(0x44, 0xdd), // (+0, +0) 309 | TmdsPair::encode_pair(0x55, 0xdd), // (+0, +0) 310 | TmdsPair::encode_pair(0x66, 0xde), // (+0, +1) 311 | TmdsPair::encode_pair(0x77, 0xdd), // (+0, +0) 312 | TmdsPair::encode_pair(0x88, 0xdd), // (+0, +0) 313 | TmdsPair::encode_pair(0x99, 0xde), // (+0, +1) 314 | TmdsPair::encode_pair(0xaa, 0xdd), // (+0, +0) 315 | TmdsPair::encode_pair(0xbb, 0xdd), // (+0, +0) 316 | TmdsPair::encode_pair(0xca, 0xde), // (-2, +1) 317 | TmdsPair::encode_pair(0xdd, 0xdd), // (+0, +0) 318 | TmdsPair::encode_pair(0xee, 0xdd), // (+0, +0) 319 | TmdsPair::encode_pair(0xfc, 0xe0), // (-3, +3) 320 | TmdsPair::encode_pair(0x04, 0xeb), // (+4, -3) 321 | TmdsPair::encode_pair(0x11, 0xee), // (+0, +0) 322 | TmdsPair::encode_pair(0x22, 0xee), // (+0, +0) 323 | TmdsPair::encode_pair(0x34, 0xed), // (+1, -1) 324 | TmdsPair::encode_pair(0x44, 0xee), // (+0, +0) 325 | TmdsPair::encode_pair(0x55, 0xee), // (+0, +0) 326 | TmdsPair::encode_pair(0x65, 0xf0), // (-1, +2) 327 | TmdsPair::encode_pair(0x77, 0xee), // (+0, +0) 328 | TmdsPair::encode_pair(0x88, 0xee), // (+0, +0) 329 | TmdsPair::encode_pair(0x9b, 0xed), // (+2, -1) 330 | TmdsPair::encode_pair(0xaa, 0xee), // (+0, +0) 331 | TmdsPair::encode_pair(0xbb, 0xee), // (+0, +0) 332 | TmdsPair::encode_pair(0xca, 0xf0), // (-2, +2) 333 | TmdsPair::encode_pair(0xdd, 0xee), // (+0, +0) 334 | TmdsPair::encode_pair(0xee, 0xee), // (+0, +0) 335 | TmdsPair::encode_pair(0xfd, 0xf1), // (-2, +3) 336 | TmdsPair::encode_pair(0x00, 0xfe), // (+0, -1) 337 | TmdsPair::encode_pair(0x14, 0xfc), // (+3, -3) 338 | TmdsPair::encode_pair(0x24, 0xfa), // (+2, -5) 339 | TmdsPair::encode_pair(0x30, 0xff), // (-3, +0) 340 | TmdsPair::encode_pair(0x48, 0xfa), // (+4, -5) 341 | TmdsPair::encode_pair(0x58, 0xfa), // (+3, -5) 342 | TmdsPair::encode_pair(0x67, 0xfc), // (+1, -3) 343 | TmdsPair::encode_pair(0x79, 0xfc), // (+2, -3) 344 | TmdsPair::encode_pair(0x8c, 0xfb), // (+4, -4) 345 | TmdsPair::encode_pair(0x9c, 0xfc), // (+3, -3) 346 | TmdsPair::encode_pair(0xaf, 0xfb), // (+5, -4) 347 | TmdsPair::encode_pair(0xbf, 0xfb), // (+4, -4) 348 | TmdsPair::encode_pair(0xcf, 0xfd), // (+3, -2) 349 | TmdsPair::encode_pair(0xe0, 0xfc), // (+3, -3) 350 | TmdsPair::encode_pair(0xf0, 0xfc), // (+2, -3) 351 | TmdsPair::encode_pair(0xff, 0xfe), // (+0, -1) 352 | ]; 353 | -------------------------------------------------------------------------------- /src/render/queue.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::{AtomicU32, Ordering}; 2 | 3 | use cortex_m::asm::wfe; 4 | 5 | /// A simple SPSC queue of u32 values. 6 | /// 7 | /// The implementation is specialized to the use case of scheduling line 8 | /// renders on the same core as the interrupt. 9 | pub struct Queue { 10 | rd_ix: AtomicU32, 11 | wr_ix: AtomicU32, 12 | buf: [AtomicU32; SIZE], 13 | } 14 | 15 | impl Queue { 16 | pub const fn new() -> Self { 17 | const ZERO: AtomicU32 = AtomicU32::new(0); 18 | Queue { 19 | rd_ix: ZERO, 20 | wr_ix: ZERO, 21 | buf: [ZERO; SIZE], 22 | } 23 | } 24 | 25 | /// Push a value, assuming queue is not full. 26 | pub fn push_unchecked(&self, val: u32) { 27 | let wr_ix = self.wr_ix.load(Ordering::Relaxed); 28 | let next = (wr_ix + 1) % SIZE as u32; 29 | self.buf[wr_ix as usize].store(val, Ordering::Relaxed); 30 | self.wr_ix.store(next, Ordering::Release); 31 | } 32 | 33 | /// Return next value to become available. 34 | /// 35 | /// Waits while queue is empty. Does not remove the value. 36 | /// 37 | /// The waiting is implemented with the `wfe` instruction, which depends 38 | /// on an event being signaled. This should be reliable when the queue is 39 | /// pushed from an interrupt handler on the same core. 40 | pub fn peek_blocking(&self) -> u32 { 41 | let rd_ix = self.rd_ix.load(Ordering::Relaxed); 42 | while rd_ix == self.wr_ix.load(Ordering::Acquire) { 43 | wfe(); 44 | } 45 | self.buf[rd_ix as usize].load(Ordering::Relaxed) 46 | } 47 | 48 | /// Remove an item from the queue. 49 | /// 50 | /// Only valid if the queue is not empty, otherwise unexpected 51 | /// results can occur. 52 | pub fn remove(&self) { 53 | let rd_ix = self.rd_ix.load(Ordering::Relaxed); 54 | let next = (rd_ix + 1) % SIZE as u32; 55 | self.rd_ix.store(next, Ordering::Release); 56 | } 57 | 58 | pub fn len(&self) -> usize { 59 | let rd_ix = self.rd_ix.load(Ordering::Acquire); 60 | let wr_ix = self.wr_ix.load(Ordering::Relaxed); 61 | (wr_ix as usize + SIZE - rd_ix as usize) % SIZE 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/render/renderlist.rs: -------------------------------------------------------------------------------- 1 | use core::cmp; 2 | 3 | use alloc::vec::Vec; 4 | 5 | use super::font::{FONT_BITS, FONT_STRIDE, FONT_X_OFFSETS, FONT_X_WIDTHS}; 6 | 7 | extern "C" { 8 | fn render_stop(); 9 | 10 | fn render_blit_simple(); 11 | 12 | fn render_blit_out(); 13 | 14 | fn render_blit_straddle(); 15 | 16 | fn render_blit_straddle_out(); 17 | 18 | fn render_blit_64_aligned(); 19 | 20 | fn render_blit_64_straddle(); 21 | } 22 | 23 | pub struct Renderlist(Vec, u32); 24 | 25 | pub struct RenderlistBuilder { 26 | v: Vec, 27 | width: u32, 28 | x: u32, 29 | stripe_start: usize, 30 | } 31 | 32 | impl RenderlistBuilder { 33 | pub fn new(width: u32) -> Self { 34 | RenderlistBuilder { 35 | v: alloc::vec![], 36 | width, 37 | x: 0, 38 | stripe_start: 0, 39 | } 40 | } 41 | 42 | pub fn recycle(mut renderlist: Renderlist) -> Self { 43 | renderlist.0.clear(); 44 | RenderlistBuilder { 45 | v: renderlist.0, 46 | width: renderlist.1, 47 | x: 0, 48 | stripe_start: 0, 49 | } 50 | } 51 | 52 | pub fn begin_stripe(&mut self, height: u32) { 53 | self.v.extend([height, 0]); 54 | } 55 | 56 | pub fn end_stripe(&mut self) { 57 | self.v.push(render_stop as u32); 58 | let len = self.v.len(); 59 | self.v[self.stripe_start + 1] = len as u32; 60 | self.stripe_start = len; 61 | self.x = 0; 62 | } 63 | 64 | fn tile_slice(&mut self, tile: &[u32], stride: u32, start: u32, end: u32) { 65 | let next = self.x % 8 + 8 - start; 66 | let op = match next.cmp(&8) { 67 | cmp::Ordering::Greater => render_blit_straddle, 68 | cmp::Ordering::Equal => render_blit_out, 69 | cmp::Ordering::Less => render_blit_simple, 70 | }; 71 | let tile_ptr = tile.as_ptr() as u32; 72 | let mut shifts = start * 4; 73 | if next > 8 { 74 | shifts |= (self.x % 8) << 10; 75 | shifts |= (8 - end) << 18; 76 | shifts |= (16 - next) << 26; 77 | } else { 78 | shifts |= (8 - (end - start)) << 10; 79 | shifts |= (8 - next) << 18; 80 | } 81 | self.v.extend([op as u32, tile_ptr, stride, shifts]); 82 | self.x += end - start; 83 | } 84 | 85 | // Note: this is currently set up for 4bpp, but could be adapted 86 | // for other bit widths. 87 | pub fn tile64(&mut self, tile: &[u32], start: u32, mut end: u32) { 88 | if self.x + (end - start) >= self.width { 89 | end = self.width - self.x + start; 90 | } 91 | let stride = 8; // hardcoded for tiles, but maybe should be an argument 92 | if start == 0 && end == 16 { 93 | let offset = self.x % 8; 94 | if offset == 0 { 95 | self.v 96 | .extend([render_blit_64_aligned as u32, tile.as_ptr() as u32, stride]); 97 | } else { 98 | let off4 = offset * 4; 99 | let shift = off4 + ((32 - off4) << 8); 100 | self.v.extend([ 101 | render_blit_64_straddle as u32, 102 | tile.as_ptr() as u32, 103 | stride, 104 | shift, 105 | ]); 106 | } 107 | self.x += start + end; 108 | } else { 109 | if start < 8 { 110 | self.tile_slice(tile, stride, start, end.min(8)); 111 | } 112 | if end > 8 { 113 | self.tile_slice(&tile[1..], stride, start.max(8) - 8, end - 8); 114 | } 115 | } 116 | } 117 | 118 | /// Returns width. 119 | /// 120 | /// Should take a font object, but that's hardcoded for now. 121 | pub fn text(&mut self, text: &str) -> u32 { 122 | let mut x = 0; 123 | for c in text.as_bytes() { 124 | let glyph = c - b' '; 125 | let width = FONT_X_WIDTHS[glyph as usize] as u32; 126 | // TODO: be aware of max width, clamp 127 | let offset = FONT_X_OFFSETS[glyph as usize] as u32; 128 | let next = x % 32 + width; 129 | let op = match next.cmp(&32) { 130 | cmp::Ordering::Greater => render_blit_straddle, 131 | cmp::Ordering::Equal => render_blit_out, 132 | cmp::Ordering::Less => render_blit_simple, 133 | }; 134 | let mut shifts = offset & 31; 135 | if next > 32 { 136 | shifts |= (x % 32) << 8; 137 | shifts |= (32 - (offset % 32 + width)) << 16; 138 | shifts |= (64 - next) << 24; 139 | } else { 140 | shifts |= (32 - width) << 8; 141 | shifts |= (32 - next) << 16; 142 | } 143 | let font_ptr = unsafe { FONT_BITS.as_ptr().add(offset as usize / 32) }; 144 | self.v 145 | .extend([op as u32, font_ptr as u32, FONT_STRIDE, shifts]); 146 | x += width; 147 | } 148 | if !text.is_empty() { 149 | let op_ix = self.v.len() - 4; 150 | if self.v[op_ix] == render_blit_simple as u32 { 151 | self.v[op_ix] = render_blit_out as u32; 152 | } else if self.v[op_ix] == render_blit_straddle as u32 { 153 | self.v[op_ix] = render_blit_straddle_out as u32; 154 | } 155 | } 156 | x 157 | } 158 | 159 | pub fn blit_1bpp(&mut self, array: &[u32], words: usize, stride: u32) { 160 | // FIXME: new renderlist instruction? 161 | self.v.extend( 162 | core::iter::repeat(render_blit_out as u32) 163 | .take(words) 164 | .enumerate() 165 | .flat_map(|(word, op)| [op, array[word..].as_ptr() as u32, stride, 0]), // FIXME: some way to make sure we pass the right amount of arguments to these functions? 166 | ); 167 | } 168 | 169 | pub fn build(self) -> Renderlist { 170 | Renderlist(self.v, self.width) 171 | } 172 | } 173 | 174 | impl Renderlist { 175 | pub fn get(&self) -> &[u32] { 176 | &self.0 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/render/swapcell.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | cell::UnsafeCell, 3 | mem::MaybeUninit, 4 | sync::atomic::{AtomicU32, Ordering}, 5 | }; 6 | 7 | use cortex_m::asm::{sev, wfe}; 8 | 9 | /// A cell of storage useful for atomic swaps. 10 | /// 11 | /// There are a number of safety considerations. Basically, use by the "client" 12 | /// must be in a single thread, and similarly by the "system". 13 | pub struct SwapCell { 14 | state: AtomicU32, 15 | value: UnsafeCell>, 16 | } 17 | 18 | unsafe impl Sync for SwapCell {} 19 | 20 | const STATE_TAKEN: u32 = 0; 21 | const STATE_READY_FOR_CLIENT: u32 = 1; 22 | const STATE_READY_FOR_SYSTEM: u32 = 2; 23 | 24 | impl SwapCell { 25 | pub const fn new() -> Self { 26 | SwapCell { 27 | state: AtomicU32::new(0), 28 | value: UnsafeCell::new(MaybeUninit::uninit()), 29 | } 30 | } 31 | 32 | /// Make a value available for the client. 33 | /// 34 | /// It is assumed that there is a program ordering betwen this and the 35 | /// corresponding `take_blocking()'. 36 | pub fn set_for_client(&self, val: T) { 37 | unsafe { 38 | (*self.value.get()).write(val); 39 | } 40 | self.state.store(STATE_READY_FOR_CLIENT, Ordering::Relaxed); 41 | } 42 | 43 | /// Make a value available for the system. 44 | pub fn set_for_system(&self, val: T) { 45 | unsafe { 46 | (*self.value.get()).write(val); 47 | } 48 | self.state.store(STATE_READY_FOR_SYSTEM, Ordering::Release); 49 | } 50 | 51 | /// Take the value when it's ready for the client. 52 | /// 53 | /// Block until the value is ready. 54 | pub fn take_blocking(&self) -> T { 55 | while self.state.load(Ordering::Acquire) != STATE_READY_FOR_CLIENT { 56 | wfe(); 57 | } 58 | let val = unsafe { self.value.get().read().assume_init() }; 59 | self.state.store(STATE_TAKEN, Ordering::Relaxed); 60 | val 61 | } 62 | 63 | /// Swap the value if it's ready for the system. 64 | /// 65 | /// Returns `true` if the value was swapped, leaving the swapped value 66 | /// available for the client. 67 | pub fn try_swap_by_system(&self, slot: &mut T) -> bool { 68 | if self.state.load(Ordering::Acquire) == STATE_READY_FOR_SYSTEM { 69 | unsafe { 70 | core::mem::swap((*self.value.get()).assume_init_mut(), slot); 71 | } 72 | self.state.store(STATE_READY_FOR_CLIENT, Ordering::Release); 73 | sev(); 74 | true 75 | } else { 76 | false 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/scan.asm: -------------------------------------------------------------------------------- 1 | // This entry point is put in .data so it doesn't generate a thunk. 2 | .section .data 3 | 4 | .global tmds_scan 5 | .type tmds_scan,%function 6 | .thumb_func 7 | // r0: scan list in direct threaded format 8 | // r1: input buffer 9 | // r2: output buffer 10 | // r3: stride (of output buffer) 11 | tmds_scan: 12 | push {r4, r5, r6, r7} 13 | mov r4, r8 14 | mov r5, r9 15 | mov r6, r10 16 | push {r4, r5, r6} 17 | 18 | // operation, 2x args 19 | // should count be single pixels or double? 20 | ldmia r0!, {r4, r5, r6} 21 | bx r4 22 | 23 | // Hot loops are in .scratch_x to reduce RAM contention. 24 | .section .scratch_x 25 | 26 | .global tmds_scan_stop 27 | .type tmds_scan_stop,%function 28 | .thumb_func 29 | tmds_scan_stop: 30 | subs r0, #8 31 | pop {r4, r5, r6} 32 | mov r8, r4 33 | mov r9, r5 34 | mov r10, r6 35 | pop {r4, r5, r6, r7} 36 | bx lr 37 | 38 | // args: count tmds_blue tmds_green tmds_red 39 | // Not sure we'll keep this. 40 | .global tmds_scan_solid_tmds 41 | .type tmds_scan_solid_tmds,%function 42 | .thumb_func 43 | tmds_scan_solid_tmds: 44 | mov r8, r1 45 | lsls r5, #2 46 | adds r4, r2, r5 47 | // ip is actual end of output 48 | mov ip, r4 49 | lsls r5, #28 50 | lsrs r5, #28 51 | adds r4, r2, r5 52 | // r10 is end of fractional part (may be == r2) 53 | mov r10, r4 54 | adds r7, r2, r3 // beginning of green row 55 | cmp r2, r10 56 | beq 2f 57 | 1: 58 | stmia r2!, {r6} 59 | cmp r2, r10 60 | bne 1b 61 | cmp r2, ip 62 | beq 4f 63 | 2: 64 | mov r5, r6 65 | 3: 66 | stmia r2!, {r5, r6} 67 | stmia r2!, {r5, r6} 68 | cmp r2, ip 69 | bne 3b 70 | 4: 71 | 72 | add ip, r3 73 | add r10, r3 74 | ldmia r0!, {r4, r6} 75 | adds r1, r7, r3 // beginning of red row 76 | cmp r7, r10 77 | beq 2f 78 | 1: 79 | stmia r7!, {r4} 80 | cmp r7, r10 81 | bne 1b 82 | cmp r7, ip 83 | beq 4f 84 | 2: 85 | mov r5, r4 86 | 3: 87 | stmia r7!, {r4, r5} 88 | stmia r7!, {r4, r5} 89 | cmp r7, ip 90 | bne 3b 91 | 4: 92 | // write red 93 | add ip, r3 94 | add r10, r3 95 | cmp r1, r10 96 | beq 2f 97 | 1: 98 | stmia r1!, {r6} 99 | cmp r1, r10 100 | bne 1b 101 | cmp r1, ip 102 | beq 4f 103 | 2: 104 | mov r7, r6 105 | 3: 106 | stmia r1!, {r6, r7} 107 | stmia r1!, {r6, r7} 108 | cmp r1, ip 109 | bne 3b 110 | 4: 111 | mov r1, r8 112 | ldmia r0!, {r4, r5, r6} 113 | bx r4 114 | 115 | .macro tmds_scan_1bpp_pal_body shift_instr shamt 116 | \shift_instr r5, r4, #\shamt 117 | ands r5, r0 // r0 = mask, equals 0x30 118 | add r5, r8 // r8 = pal 119 | ldm r5, {r5, r6, r7} 120 | str r6, [r2, r3] // r3 = stride 121 | adds r6, r2, r3 122 | str r7, [r6, r3] 123 | stmia r2!, {r5} 124 | .endm 125 | 126 | 1: 127 | b 4f 128 | // args: count pal 129 | .global tmds_scan_1bpp_pal 130 | .type tmds_scan_1bpp_pal,%function 131 | .thumb_func 132 | tmds_scan_1bpp_pal: 133 | lsrs r4, r5, #4 134 | lsls r5, #2 135 | adds r5, r2 136 | mov ip, r5 // actual end of output 137 | mov r8, r6 138 | mov r9, r0 139 | lsls r4, #6 140 | beq 1b 141 | adds r4, r2 142 | mov r10, r4 // end of whole part 143 | movs r0, #0x30 144 | 2: 145 | ldmia r1!, {r4} 146 | tmds_scan_1bpp_pal_body lsls 4 147 | tmds_scan_1bpp_pal_body lsls 2 148 | tmds_scan_1bpp_pal_body lsls 0 149 | tmds_scan_1bpp_pal_body lsrs 2 150 | tmds_scan_1bpp_pal_body lsrs 4 151 | tmds_scan_1bpp_pal_body lsrs 6 152 | tmds_scan_1bpp_pal_body lsrs 8 153 | tmds_scan_1bpp_pal_body lsrs 10 154 | tmds_scan_1bpp_pal_body lsrs 12 155 | tmds_scan_1bpp_pal_body lsrs 14 156 | tmds_scan_1bpp_pal_body lsrs 16 157 | tmds_scan_1bpp_pal_body lsrs 18 158 | tmds_scan_1bpp_pal_body lsrs 20 159 | tmds_scan_1bpp_pal_body lsrs 22 160 | tmds_scan_1bpp_pal_body lsrs 24 161 | tmds_scan_1bpp_pal_body lsrs 26 162 | cmp r2, r10 163 | beq 3f 164 | b 2b 165 | 3: 166 | cmp r2, ip 167 | beq 6f 168 | 4: 169 | ldmia r1!, {r4} 170 | movs r0, #2 171 | 5: 172 | rors r4, r0 173 | lsrs r5, r4, #30 174 | lsls r5, #4 175 | add r5, r8 // r8 = pal 176 | ldm r5, {r5, r6, r7} 177 | str r6, [r2, r3] // r3 = stride 178 | adds r6, r2, r3 179 | str r7, [r6, r3] 180 | stmia r2!, {r5} 181 | cmp r2, ip 182 | bne 5b 183 | 6: 184 | mov r0, r9 185 | ldmia r0!, {r4, r5, r6} 186 | bx r4 187 | 188 | // args: count pal 189 | .global tmds_scan_4bpp_pal 190 | .type tmds_scan_4bpp_pal,%function 191 | .thumb_func 192 | tmds_scan_4bpp_pal: 193 | push {r0, r3} // save registers, freeing them for use 194 | lsls r4, r5, #2 // number of bytes to output 195 | add r4, r2 // pointer to end of output buffer, blue channel 196 | mov ip, r4 // store in hi register 197 | mov r8, r6 // store palette in hi register 198 | adds r0, r2, r3 // pointer to output buffer, green channel 199 | adds r3, r0 // pointer to output buffer, red channel 200 | lsrs r5, #2 // number of 8-pixel chunks to output 201 | beq 2f // skip 8-pixel chunk section if zero 202 | lsls r5, #4 // number of bytes in 8-pixel chunk section 203 | adds r5, r2 // pointer to end of 8-pixel chunk section 204 | mov r10, r5 // store in hi register (for comparison) 205 | 1: 206 | ldmia r1!, {r4} // load a word of pixels - 8 pixels, 4bpp each 207 | uxtb r5, r4 // extract first byte from input pixels 208 | lsls r5, #4 // palette LUT is 16 bytes per entry 209 | add r5, r8 // pointer to palette LUT entry 210 | ldm r5, {r5, r6, r7} // load blue, green, red TMDS pairs 211 | stmia r2!, {r5} // store blue TMDS pair to output buffer 212 | stmia r0!, {r6} // store green TMDS pair to output buffer 213 | stmia r3!, {r7} // store red TMDS pair to output buffer 214 | lsrs r4, #8 // shift pixels 215 | uxtb r5, r4 // extract second byte from input pixels 216 | lsls r5, #4 // above sequence repeats 3 more times 217 | add r5, r8 218 | ldm r5, {r5, r6, r7} 219 | stmia r2!, {r5} 220 | stmia r0!, {r6} 221 | stmia r3!, {r7} 222 | lsrs r4, #8 223 | uxtb r5, r4 224 | lsls r5, #4 225 | add r5, r8 226 | ldm r5, {r5, r6, r7} 227 | stmia r2!, {r5} 228 | stmia r0!, {r6} 229 | stmia r3!, {r7} 230 | lsrs r4, #8 231 | uxtb r5, r4 232 | lsls r5, #4 233 | add r5, r8 234 | ldm r5, {r5, r6, r7} 235 | stmia r2!, {r5} 236 | stmia r0!, {r6} 237 | stmia r3!, {r7} 238 | cmp r2, r10 // compare output pointer to end of 8-pixel section 239 | bne 1b // loop if there is more to compute 240 | cmp r2, ip // compare output pointer to end 241 | beq 4f // skip (if count is divisible by 8 pixels) 242 | 2: 243 | ldmia r1!, {r4} // load last word of input pixels 244 | 3: 245 | uxtb r5, r4 // extract one byte 246 | lsls r5, #4 // palette LUT is 16 bytes per entry 247 | add r5, r8 // pointer to palette LUT entry 248 | ldm r5, {r5, r6, r7} // load red, green, blue TMDS pairs 249 | stmia r2!, {r5} // store blue channel 250 | stmia r0!, {r6} // store green channel 251 | stmia r3!, {r7} // store red channel 252 | lsrs r4, #8 // shift pixels to line up next byte 253 | cmp r2, ip // compare output pointer to end 254 | bne 3b // loop if there are more pixels 255 | 4: 256 | pop {r0, r3} // restore scratch registers 257 | ldmia r0!, {r4, r5, r6} // load function ptr and 2 args for next op 258 | bx r4 // jump to code for op 259 | -------------------------------------------------------------------------------- /src/scanlist.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use crate::{dvi::tmds::TmdsPair, render::PaletteEntry}; 4 | 5 | extern "C" { 6 | fn tmds_scan_stop(); 7 | 8 | fn tmds_scan_solid_tmds(); 9 | 10 | fn tmds_scan_1bpp_pal(); 11 | 12 | fn tmds_scan_4bpp_pal(); 13 | } 14 | 15 | /// A display list for TMDS scanout. 16 | /// 17 | /// A scanlist contains a description of how to render the scene into 18 | /// TMDS encoded scan lines. The input to this stage is intended to be 19 | /// line buffers, but at present only solid color blocks are implemented. 20 | /// 21 | /// There are a number of safety requirements, as the scanlist is 22 | /// interpreted by an unsafe virtual machine. The width of each scanline 23 | /// must match the actual buffer provided, and the total height must 24 | /// also be the number of scanlines. 25 | /// 26 | /// One potential direction is to make the scanlist builder enforce the 27 | /// safety requirements. This would have a modest runtime cost (none 28 | /// during scanout). 29 | pub struct Scanlist(Vec); 30 | 31 | /// A builder for scanlists. 32 | /// 33 | /// The application builds a scanlist, then hands it to the display 34 | /// system for scanout. Currently it is static, but the intent is for 35 | /// scanlists to be double-buffered. 36 | pub struct ScanlistBuilder { 37 | v: Vec, 38 | x: u32, 39 | } 40 | 41 | impl ScanlistBuilder { 42 | pub fn new(_width: u32, _height: u32) -> Self { 43 | ScanlistBuilder { 44 | v: alloc::vec![], 45 | x: 0, 46 | } 47 | } 48 | 49 | pub fn recycle(mut scanlist: Scanlist) -> Self { 50 | scanlist.0.clear(); 51 | ScanlistBuilder { 52 | v: scanlist.0, 53 | x: 0, 54 | } 55 | } 56 | 57 | pub fn build(self) -> Scanlist { 58 | // TODO: check width, do some kind of error? 59 | Scanlist(self.v) 60 | } 61 | 62 | pub fn begin_stripe(&mut self, height: u32) { 63 | self.v.push(height); 64 | } 65 | 66 | pub fn end_stripe(&mut self) { 67 | self.v.push(tmds_scan_stop as u32); 68 | } 69 | 70 | /// Generate a run of solid color. 71 | /// 72 | /// This method only works when aligned to 2-pixel boundaries. 73 | pub fn solid(&mut self, count: u32, color: [TmdsPair; 3]) { 74 | self.v.extend_from_slice(&[ 75 | tmds_scan_solid_tmds as u32, 76 | count / 2, 77 | color[0].raw(), 78 | color[1].raw(), 79 | color[2].raw(), 80 | ]); 81 | self.x += count; 82 | } 83 | 84 | /// Safety note: we take a reference to the palette, but the 85 | /// lifetime must extend until it is used. 86 | pub fn pal_1bpp(&mut self, count: u32, palette: &[PaletteEntry; 4]) { 87 | self.v.extend_from_slice(&[ 88 | tmds_scan_1bpp_pal as u32, 89 | count / 2, 90 | palette.as_ptr() as u32, 91 | ]); 92 | self.x += count; 93 | } 94 | 95 | /// Safety note: we take a reference to the palette, but the 96 | /// lifetime must extend until it is used. 97 | pub fn pal_4bpp(&mut self, count: u32, palette: &[PaletteEntry; 256]) { 98 | self.v.extend_from_slice(&[ 99 | tmds_scan_4bpp_pal as u32, 100 | count / 2, 101 | palette.as_ptr() as u32, 102 | ]); 103 | self.x += count; 104 | } 105 | } 106 | 107 | impl Scanlist { 108 | pub fn get(&self) -> &[u32] { 109 | &self.0 110 | } 111 | } 112 | --------------------------------------------------------------------------------