├── .cargo └── config.toml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── cross ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── app │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── board │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── self-tests │ ├── Cargo.toml │ └── tests │ └── scd30.rs ├── host-target-tests ├── Cargo.toml └── tests │ └── serial.rs ├── messages ├── Cargo.toml └── src │ └── lib.rs ├── scd30 ├── Cargo.toml └── src │ └── lib.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = 'run -p xtask --' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "Cargo.toml", 4 | "cross/Cargo.toml", 5 | ] 6 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "CoreFoundation-sys" 5 | version = "0.1.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" 8 | dependencies = [ 9 | "libc", 10 | "mach 0.1.2", 11 | ] 12 | 13 | [[package]] 14 | name = "IOKit-sys" 15 | version = "0.1.5" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" 18 | dependencies = [ 19 | "CoreFoundation-sys", 20 | "libc", 21 | "mach 0.1.2", 22 | ] 23 | 24 | [[package]] 25 | name = "aho-corasick" 26 | version = "0.7.15" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 29 | dependencies = [ 30 | "memchr", 31 | ] 32 | 33 | [[package]] 34 | name = "anyhow" 35 | version = "1.0.38" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" 38 | 39 | [[package]] 40 | name = "as-slice" 41 | version = "0.1.5" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" 44 | dependencies = [ 45 | "generic-array 0.12.4", 46 | "generic-array 0.13.3", 47 | "generic-array 0.14.4", 48 | "stable_deref_trait", 49 | ] 50 | 51 | [[package]] 52 | name = "bitflags" 53 | version = "1.2.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 56 | 57 | [[package]] 58 | name = "byteorder" 59 | version = "1.4.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 62 | 63 | [[package]] 64 | name = "cc" 65 | version = "1.0.67" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" 68 | 69 | [[package]] 70 | name = "cfg-if" 71 | version = "0.1.10" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 74 | 75 | [[package]] 76 | name = "cfg-if" 77 | version = "1.0.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 80 | 81 | [[package]] 82 | name = "crc-any" 83 | version = "2.3.5" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "c3784befdf9469f4d51c69ef0b774f6a99de6bcc655285f746f16e0dd63d9007" 86 | 87 | [[package]] 88 | name = "defmt" 89 | version = "0.2.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "aec6bcd26e64f526aa24eb79c0b30e7d7a3f2f9087acaf8e8650bc4e5fc1b54c" 92 | dependencies = [ 93 | "defmt-macros", 94 | "semver", 95 | ] 96 | 97 | [[package]] 98 | name = "defmt-macros" 99 | version = "0.2.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "d35887971a66a572654b37f2dd6a82e17a9dadf65935402fc910cf422955e994" 102 | dependencies = [ 103 | "defmt-parser", 104 | "proc-macro2", 105 | "quote", 106 | "syn", 107 | ] 108 | 109 | [[package]] 110 | name = "defmt-parser" 111 | version = "0.2.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "c816f778ec2147f95990a606795fe9dcc82781bcd98e222675c4103c2d250ea1" 114 | 115 | [[package]] 116 | name = "embedded-hal" 117 | version = "0.2.4" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "fa998ce59ec9765d15216393af37a58961ddcefb14c753b4816ba2191d865fcb" 120 | dependencies = [ 121 | "nb 0.1.3", 122 | "void", 123 | ] 124 | 125 | [[package]] 126 | name = "embedded-hal-mock" 127 | version = "0.7.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "9eca2fb304dbaea79776ea2a0b1445e0de00b103854dbd956f406edcbf54a262" 130 | dependencies = [ 131 | "embedded-hal", 132 | "nb 0.1.3", 133 | ] 134 | 135 | [[package]] 136 | name = "env_logger" 137 | version = "0.8.3" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" 140 | dependencies = [ 141 | "log", 142 | "regex", 143 | ] 144 | 145 | [[package]] 146 | name = "generic-array" 147 | version = "0.12.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 150 | dependencies = [ 151 | "typenum", 152 | ] 153 | 154 | [[package]] 155 | name = "generic-array" 156 | version = "0.13.3" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" 159 | dependencies = [ 160 | "typenum", 161 | ] 162 | 163 | [[package]] 164 | name = "generic-array" 165 | version = "0.14.4" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 168 | dependencies = [ 169 | "typenum", 170 | "version_check", 171 | ] 172 | 173 | [[package]] 174 | name = "getrandom" 175 | version = "0.2.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 178 | dependencies = [ 179 | "cfg-if 1.0.0", 180 | "libc", 181 | "wasi", 182 | ] 183 | 184 | [[package]] 185 | name = "hash32" 186 | version = "0.1.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" 189 | dependencies = [ 190 | "byteorder", 191 | ] 192 | 193 | [[package]] 194 | name = "heapless" 195 | version = "0.5.6" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" 198 | dependencies = [ 199 | "as-slice", 200 | "generic-array 0.13.3", 201 | "hash32", 202 | "serde", 203 | "stable_deref_trait", 204 | ] 205 | 206 | [[package]] 207 | name = "host-target-tests" 208 | version = "0.1.0" 209 | dependencies = [ 210 | "anyhow", 211 | "messages", 212 | "parking_lot", 213 | "postcard", 214 | "serialport", 215 | ] 216 | 217 | [[package]] 218 | name = "instant" 219 | version = "0.1.9" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 222 | dependencies = [ 223 | "cfg-if 1.0.0", 224 | ] 225 | 226 | [[package]] 227 | name = "libc" 228 | version = "0.2.87" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213" 231 | 232 | [[package]] 233 | name = "libudev" 234 | version = "0.2.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe" 237 | dependencies = [ 238 | "libc", 239 | "libudev-sys", 240 | ] 241 | 242 | [[package]] 243 | name = "libudev-sys" 244 | version = "0.1.4" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" 247 | dependencies = [ 248 | "libc", 249 | "pkg-config", 250 | ] 251 | 252 | [[package]] 253 | name = "lock_api" 254 | version = "0.4.2" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" 257 | dependencies = [ 258 | "scopeguard", 259 | ] 260 | 261 | [[package]] 262 | name = "log" 263 | version = "0.4.14" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 266 | dependencies = [ 267 | "cfg-if 1.0.0", 268 | ] 269 | 270 | [[package]] 271 | name = "mach" 272 | version = "0.1.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" 275 | dependencies = [ 276 | "libc", 277 | ] 278 | 279 | [[package]] 280 | name = "mach" 281 | version = "0.2.3" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" 284 | dependencies = [ 285 | "libc", 286 | ] 287 | 288 | [[package]] 289 | name = "memchr" 290 | version = "2.3.4" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 293 | 294 | [[package]] 295 | name = "messages" 296 | version = "0.1.0" 297 | dependencies = [ 298 | "postcard", 299 | "quickcheck", 300 | "quickcheck_macros", 301 | "serde", 302 | "serde_derive", 303 | ] 304 | 305 | [[package]] 306 | name = "nb" 307 | version = "0.1.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 310 | dependencies = [ 311 | "nb 1.0.0", 312 | ] 313 | 314 | [[package]] 315 | name = "nb" 316 | version = "1.0.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" 319 | 320 | [[package]] 321 | name = "nix" 322 | version = "0.16.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb" 325 | dependencies = [ 326 | "bitflags", 327 | "cc", 328 | "cfg-if 0.1.10", 329 | "libc", 330 | "void", 331 | ] 332 | 333 | [[package]] 334 | name = "once_cell" 335 | version = "1.7.2" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 338 | 339 | [[package]] 340 | name = "parking_lot" 341 | version = "0.11.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 344 | dependencies = [ 345 | "instant", 346 | "lock_api", 347 | "parking_lot_core", 348 | ] 349 | 350 | [[package]] 351 | name = "parking_lot_core" 352 | version = "0.8.3" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 355 | dependencies = [ 356 | "cfg-if 1.0.0", 357 | "instant", 358 | "libc", 359 | "redox_syscall", 360 | "smallvec", 361 | "winapi", 362 | ] 363 | 364 | [[package]] 365 | name = "pest" 366 | version = "2.1.3" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 369 | dependencies = [ 370 | "ucd-trie", 371 | ] 372 | 373 | [[package]] 374 | name = "pkg-config" 375 | version = "0.3.19" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 378 | 379 | [[package]] 380 | name = "postcard" 381 | version = "0.5.2" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "8a9ba0d1b66f31fb374fede892eb4b0818ea819d5e7bc587345ce50a6949e782" 384 | dependencies = [ 385 | "heapless", 386 | "postcard-cobs", 387 | "serde", 388 | ] 389 | 390 | [[package]] 391 | name = "postcard-cobs" 392 | version = "0.1.5-pre" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f" 395 | 396 | [[package]] 397 | name = "proc-macro2" 398 | version = "1.0.24" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 401 | dependencies = [ 402 | "unicode-xid", 403 | ] 404 | 405 | [[package]] 406 | name = "quickcheck" 407 | version = "1.0.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" 410 | dependencies = [ 411 | "env_logger", 412 | "log", 413 | "rand", 414 | ] 415 | 416 | [[package]] 417 | name = "quickcheck_macros" 418 | version = "1.0.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" 421 | dependencies = [ 422 | "proc-macro2", 423 | "quote", 424 | "syn", 425 | ] 426 | 427 | [[package]] 428 | name = "quote" 429 | version = "1.0.9" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 432 | dependencies = [ 433 | "proc-macro2", 434 | ] 435 | 436 | [[package]] 437 | name = "rand" 438 | version = "0.8.3" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 441 | dependencies = [ 442 | "rand_core", 443 | ] 444 | 445 | [[package]] 446 | name = "rand_core" 447 | version = "0.6.2" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 450 | dependencies = [ 451 | "getrandom", 452 | ] 453 | 454 | [[package]] 455 | name = "redox_syscall" 456 | version = "0.2.5" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 459 | dependencies = [ 460 | "bitflags", 461 | ] 462 | 463 | [[package]] 464 | name = "regex" 465 | version = "1.4.3" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" 468 | dependencies = [ 469 | "aho-corasick", 470 | "memchr", 471 | "regex-syntax", 472 | "thread_local", 473 | ] 474 | 475 | [[package]] 476 | name = "regex-syntax" 477 | version = "0.6.22" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 480 | 481 | [[package]] 482 | name = "scd30" 483 | version = "0.1.0" 484 | dependencies = [ 485 | "crc-any", 486 | "defmt", 487 | "embedded-hal", 488 | "embedded-hal-mock", 489 | ] 490 | 491 | [[package]] 492 | name = "scopeguard" 493 | version = "1.1.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 496 | 497 | [[package]] 498 | name = "semver" 499 | version = "0.11.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 502 | dependencies = [ 503 | "semver-parser", 504 | ] 505 | 506 | [[package]] 507 | name = "semver-parser" 508 | version = "0.10.2" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 511 | dependencies = [ 512 | "pest", 513 | ] 514 | 515 | [[package]] 516 | name = "serde" 517 | version = "1.0.123" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 520 | dependencies = [ 521 | "serde_derive", 522 | ] 523 | 524 | [[package]] 525 | name = "serde_derive" 526 | version = "1.0.123" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 529 | dependencies = [ 530 | "proc-macro2", 531 | "quote", 532 | "syn", 533 | ] 534 | 535 | [[package]] 536 | name = "serialport" 537 | version = "4.0.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "22f37409d980045734250d679750bdf11bd875fec5bb5417dd21bb75d04d31a1" 540 | dependencies = [ 541 | "CoreFoundation-sys", 542 | "IOKit-sys", 543 | "bitflags", 544 | "cfg-if 0.1.10", 545 | "libudev", 546 | "mach 0.2.3", 547 | "nix", 548 | "regex", 549 | "winapi", 550 | ] 551 | 552 | [[package]] 553 | name = "smallvec" 554 | version = "1.6.1" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 557 | 558 | [[package]] 559 | name = "stable_deref_trait" 560 | version = "1.2.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 563 | 564 | [[package]] 565 | name = "syn" 566 | version = "1.0.60" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 569 | dependencies = [ 570 | "proc-macro2", 571 | "quote", 572 | "unicode-xid", 573 | ] 574 | 575 | [[package]] 576 | name = "thread_local" 577 | version = "1.1.3" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" 580 | dependencies = [ 581 | "once_cell", 582 | ] 583 | 584 | [[package]] 585 | name = "typenum" 586 | version = "1.12.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 589 | 590 | [[package]] 591 | name = "ucd-trie" 592 | version = "0.1.3" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 595 | 596 | [[package]] 597 | name = "unicode-xid" 598 | version = "0.2.1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 601 | 602 | [[package]] 603 | name = "version_check" 604 | version = "0.9.2" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 607 | 608 | [[package]] 609 | name = "void" 610 | version = "1.0.2" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 613 | 614 | [[package]] 615 | name = "wasi" 616 | version = "0.10.2+wasi-snapshot-preview1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 619 | 620 | [[package]] 621 | name = "winapi" 622 | version = "0.3.9" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 625 | dependencies = [ 626 | "winapi-i686-pc-windows-gnu", 627 | "winapi-x86_64-pc-windows-gnu", 628 | ] 629 | 630 | [[package]] 631 | name = "winapi-i686-pc-windows-gnu" 632 | version = "0.4.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 635 | 636 | [[package]] 637 | name = "winapi-x86_64-pc-windows-gnu" 638 | version = "0.4.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 641 | 642 | [[package]] 643 | name = "xshell" 644 | version = "0.1.9" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "6f18102278453c8f70ea5c514ac78cb4c73a0ef72a8273d17094b52f9584c0c1" 647 | dependencies = [ 648 | "xshell-macros", 649 | ] 650 | 651 | [[package]] 652 | name = "xshell-macros" 653 | version = "0.1.9" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "6093c460064572007f885facc70bb0ca5e40a83ea7ff8b16c1abbee56fd2e767" 656 | 657 | [[package]] 658 | name = "xtask" 659 | version = "0.1.0" 660 | dependencies = [ 661 | "anyhow", 662 | "xshell", 663 | ] 664 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "host-target-tests", 4 | "messages", 5 | "scd30", 6 | "xtask", 7 | ] -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `test-embedded-app-example` 2 | 3 | Example of testing an embedded application with 3 different kinds of tests. 4 | [Check out the associated blog post here!](https://ferrous-systems.com/blog/test-embedded-app/) 5 | 6 | Please refer to https://github.com/knurling-rs/app-template for the installation instuctions. 7 | 8 | ## License 9 | 10 | Licensed under either of 11 | 12 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 13 | http://www.apache.org/licenses/LICENSE-2.0) 14 | 15 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 16 | 17 | at your option. 18 | 19 | ### Contribution 20 | 21 | Unless you explicitly state otherwise, any contribution intentionally submitted 22 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 23 | licensed as above, without any additional terms or conditions. 24 | 25 | [Knurling]: https://knurling.ferrous-systems.com/ 26 | [Ferrous Systems]: https://ferrous-systems.com/ 27 | [GitHub Sponsors]: https://github.com/sponsors/knurling-rs 28 | -------------------------------------------------------------------------------- /cross/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-run --chip nRF52840" 3 | rustflags = [ 4 | "-C", "linker=flip-link", 5 | "-C", "link-arg=-Tlink.x", 6 | "-C", "link-arg=-Tdefmt.x", 7 | "-C", "link-arg=--nmagic", 8 | ] 9 | 10 | [build] 11 | target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) -------------------------------------------------------------------------------- /cross/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aligned" 5 | version = "0.3.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "c19796bd8d477f1a9d4ac2465b464a8b1359474f06a96bb3cda650b4fca309bf" 8 | dependencies = [ 9 | "as-slice", 10 | ] 11 | 12 | [[package]] 13 | name = "app" 14 | version = "0.1.0" 15 | dependencies = [ 16 | "board", 17 | "cortex-m-rtic", 18 | "defmt", 19 | "defmt-rtt", 20 | "heapless 0.6.1", 21 | "messages", 22 | "panic-probe", 23 | "postcard", 24 | ] 25 | 26 | [[package]] 27 | name = "as-slice" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" 31 | dependencies = [ 32 | "generic-array 0.12.4", 33 | "generic-array 0.13.3", 34 | "generic-array 0.14.4", 35 | "stable_deref_trait", 36 | ] 37 | 38 | [[package]] 39 | name = "autocfg" 40 | version = "1.0.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 43 | 44 | [[package]] 45 | name = "bare-metal" 46 | version = "0.2.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 49 | dependencies = [ 50 | "rustc_version", 51 | ] 52 | 53 | [[package]] 54 | name = "bitfield" 55 | version = "0.13.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 58 | 59 | [[package]] 60 | name = "board" 61 | version = "0.1.0" 62 | dependencies = [ 63 | "cortex-m 0.6.7", 64 | "defmt", 65 | "nrf52840-hal", 66 | "scd30", 67 | ] 68 | 69 | [[package]] 70 | name = "byteorder" 71 | version = "1.4.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 74 | 75 | [[package]] 76 | name = "cast" 77 | version = "0.2.3" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" 80 | dependencies = [ 81 | "rustc_version", 82 | ] 83 | 84 | [[package]] 85 | name = "cfg-if" 86 | version = "0.1.10" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 89 | 90 | [[package]] 91 | name = "cortex-m" 92 | version = "0.6.7" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "9075300b07c6a56263b9b582c214d0ff037b00d45ec9fde1cc711490c56f1bb9" 95 | dependencies = [ 96 | "aligned", 97 | "bare-metal", 98 | "bitfield", 99 | "cortex-m 0.7.1", 100 | "volatile-register", 101 | ] 102 | 103 | [[package]] 104 | name = "cortex-m" 105 | version = "0.7.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "a0b756a8bffc56025de45218a48ff9b801180440c0ee49a722b32d49dcebc771" 108 | dependencies = [ 109 | "bare-metal", 110 | "bitfield", 111 | "embedded-hal", 112 | "volatile-register", 113 | ] 114 | 115 | [[package]] 116 | name = "cortex-m-rt" 117 | version = "0.6.13" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "980c9d0233a909f355ed297ef122f257942de5e0a2cb1c39f60684b65bcb90fb" 120 | dependencies = [ 121 | "cortex-m-rt-macros", 122 | "r0", 123 | ] 124 | 125 | [[package]] 126 | name = "cortex-m-rt-macros" 127 | version = "0.1.8" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 130 | dependencies = [ 131 | "proc-macro2", 132 | "quote", 133 | "syn", 134 | ] 135 | 136 | [[package]] 137 | name = "cortex-m-rtic" 138 | version = "0.5.6" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "fa43f63284b363ac64f9ce5221a0f593b54f73258aba8e1a88c1feed8efdb664" 141 | dependencies = [ 142 | "cortex-m 0.6.7", 143 | "cortex-m-rt", 144 | "cortex-m-rtic-macros", 145 | "heapless 0.6.1", 146 | "rtic-core", 147 | "version_check", 148 | ] 149 | 150 | [[package]] 151 | name = "cortex-m-rtic-macros" 152 | version = "0.5.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "9a1a6a4c9550373038c0e21a78d44d529bd697c25bbf6b8004bddc6e63b119c7" 155 | dependencies = [ 156 | "proc-macro2", 157 | "quote", 158 | "rtic-syntax", 159 | "syn", 160 | ] 161 | 162 | [[package]] 163 | name = "crc-any" 164 | version = "2.3.5" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "c3784befdf9469f4d51c69ef0b774f6a99de6bcc655285f746f16e0dd63d9007" 167 | 168 | [[package]] 169 | name = "defmt" 170 | version = "0.2.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "aec6bcd26e64f526aa24eb79c0b30e7d7a3f2f9087acaf8e8650bc4e5fc1b54c" 173 | dependencies = [ 174 | "defmt-macros", 175 | "semver 0.11.0", 176 | ] 177 | 178 | [[package]] 179 | name = "defmt-macros" 180 | version = "0.2.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "d35887971a66a572654b37f2dd6a82e17a9dadf65935402fc910cf422955e994" 183 | dependencies = [ 184 | "defmt-parser", 185 | "proc-macro2", 186 | "quote", 187 | "syn", 188 | ] 189 | 190 | [[package]] 191 | name = "defmt-parser" 192 | version = "0.2.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "c816f778ec2147f95990a606795fe9dcc82781bcd98e222675c4103c2d250ea1" 195 | 196 | [[package]] 197 | name = "defmt-rtt" 198 | version = "0.2.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "a5dfca43cd6b33d2d9f4db9757101ce1044e9672cd7f412dfcc5c5f772243c3b" 201 | dependencies = [ 202 | "cortex-m 0.6.7", 203 | "defmt", 204 | ] 205 | 206 | [[package]] 207 | name = "defmt-test" 208 | version = "0.2.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "3a91dc0b435675bcf541541ef6b86a4b5d761d9a108d03e9304058caddcedb64" 211 | dependencies = [ 212 | "cortex-m 0.6.7", 213 | "cortex-m-rt", 214 | "defmt", 215 | "defmt-test-macros", 216 | ] 217 | 218 | [[package]] 219 | name = "defmt-test-macros" 220 | version = "0.2.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "395afff6ac8939f200b832bb7bbf36cc2716942161415ad8facf76ea972f63e8" 223 | dependencies = [ 224 | "proc-macro2", 225 | "quote", 226 | "syn", 227 | ] 228 | 229 | [[package]] 230 | name = "embedded-dma" 231 | version = "0.1.2" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "46c8c02e4347a0267ca60813c952017f4c5948c232474c6010a381a337f1bda4" 234 | dependencies = [ 235 | "stable_deref_trait", 236 | ] 237 | 238 | [[package]] 239 | name = "embedded-hal" 240 | version = "0.2.4" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "fa998ce59ec9765d15216393af37a58961ddcefb14c753b4816ba2191d865fcb" 243 | dependencies = [ 244 | "nb 0.1.3", 245 | "void", 246 | ] 247 | 248 | [[package]] 249 | name = "fixed" 250 | version = "1.6.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "6ed9c66f2c3a8da3aed1c98c2bd9d345a213fcbeb0f408369a970ffdcdc86a3f" 253 | dependencies = [ 254 | "typenum", 255 | ] 256 | 257 | [[package]] 258 | name = "generic-array" 259 | version = "0.12.4" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 262 | dependencies = [ 263 | "typenum", 264 | ] 265 | 266 | [[package]] 267 | name = "generic-array" 268 | version = "0.13.3" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" 271 | dependencies = [ 272 | "typenum", 273 | ] 274 | 275 | [[package]] 276 | name = "generic-array" 277 | version = "0.14.4" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 280 | dependencies = [ 281 | "typenum", 282 | "version_check", 283 | ] 284 | 285 | [[package]] 286 | name = "hash32" 287 | version = "0.1.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" 290 | dependencies = [ 291 | "byteorder", 292 | ] 293 | 294 | [[package]] 295 | name = "hashbrown" 296 | version = "0.9.1" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 299 | 300 | [[package]] 301 | name = "heapless" 302 | version = "0.5.6" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" 305 | dependencies = [ 306 | "as-slice", 307 | "generic-array 0.13.3", 308 | "hash32", 309 | "serde", 310 | "stable_deref_trait", 311 | ] 312 | 313 | [[package]] 314 | name = "heapless" 315 | version = "0.6.1" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422" 318 | dependencies = [ 319 | "as-slice", 320 | "generic-array 0.14.4", 321 | "hash32", 322 | "stable_deref_trait", 323 | ] 324 | 325 | [[package]] 326 | name = "indexmap" 327 | version = "1.6.1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" 330 | dependencies = [ 331 | "autocfg", 332 | "hashbrown", 333 | ] 334 | 335 | [[package]] 336 | name = "messages" 337 | version = "0.1.0" 338 | dependencies = [ 339 | "serde", 340 | "serde_derive", 341 | ] 342 | 343 | [[package]] 344 | name = "nb" 345 | version = "0.1.3" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 348 | dependencies = [ 349 | "nb 1.0.0", 350 | ] 351 | 352 | [[package]] 353 | name = "nb" 354 | version = "1.0.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" 357 | 358 | [[package]] 359 | name = "nrf-hal-common" 360 | version = "0.12.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "675eee40228591a1991eade1d4adc1a23fd7ff750b5c895ca068b9c2f0878b72" 363 | dependencies = [ 364 | "cast", 365 | "cfg-if", 366 | "cortex-m 0.6.7", 367 | "embedded-dma", 368 | "embedded-hal", 369 | "fixed", 370 | "nb 1.0.0", 371 | "nrf52840-pac", 372 | "rand_core", 373 | "void", 374 | ] 375 | 376 | [[package]] 377 | name = "nrf52840-hal" 378 | version = "0.12.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "c1f9e382ab941f46db0e2f086fa64f54faf3473eddb16a0e350c0a5b37febba2" 381 | dependencies = [ 382 | "embedded-hal", 383 | "nrf-hal-common", 384 | "nrf52840-pac", 385 | ] 386 | 387 | [[package]] 388 | name = "nrf52840-pac" 389 | version = "0.9.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "e1b780a5afd2621774652f28c82837f6aa6d19cf0ad71c734fc1fe53298a2d73" 392 | dependencies = [ 393 | "bare-metal", 394 | "cortex-m 0.6.7", 395 | "cortex-m-rt", 396 | "vcell", 397 | ] 398 | 399 | [[package]] 400 | name = "panic-probe" 401 | version = "0.2.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "45b5c81d6b51f415b3ba40b30dc598dfffb29ad4cf4a8815989754850ce6c0dc" 404 | dependencies = [ 405 | "cortex-m 0.6.7", 406 | "cortex-m-rt", 407 | "defmt", 408 | ] 409 | 410 | [[package]] 411 | name = "pest" 412 | version = "2.1.3" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 415 | dependencies = [ 416 | "ucd-trie", 417 | ] 418 | 419 | [[package]] 420 | name = "postcard" 421 | version = "0.5.2" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "8a9ba0d1b66f31fb374fede892eb4b0818ea819d5e7bc587345ce50a6949e782" 424 | dependencies = [ 425 | "heapless 0.5.6", 426 | "postcard-cobs", 427 | "serde", 428 | ] 429 | 430 | [[package]] 431 | name = "postcard-cobs" 432 | version = "0.1.5-pre" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f" 435 | 436 | [[package]] 437 | name = "proc-macro2" 438 | version = "1.0.24" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 441 | dependencies = [ 442 | "unicode-xid", 443 | ] 444 | 445 | [[package]] 446 | name = "quote" 447 | version = "1.0.9" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 450 | dependencies = [ 451 | "proc-macro2", 452 | ] 453 | 454 | [[package]] 455 | name = "r0" 456 | version = "0.2.2" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 459 | 460 | [[package]] 461 | name = "rand_core" 462 | version = "0.5.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 465 | 466 | [[package]] 467 | name = "rtic-core" 468 | version = "0.3.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "8bd58a6949de8ff797a346a28d9f13f7b8f54fa61bb5e3cb0985a4efb497a5ef" 471 | 472 | [[package]] 473 | name = "rtic-syntax" 474 | version = "0.4.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "8152fcaa845720d61e6cc570548b89144c2c307f18a480bbd97e55e9f6eeff04" 477 | dependencies = [ 478 | "indexmap", 479 | "proc-macro2", 480 | "syn", 481 | ] 482 | 483 | [[package]] 484 | name = "rustc_version" 485 | version = "0.2.3" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 488 | dependencies = [ 489 | "semver 0.9.0", 490 | ] 491 | 492 | [[package]] 493 | name = "scd30" 494 | version = "0.1.0" 495 | dependencies = [ 496 | "crc-any", 497 | "defmt", 498 | "embedded-hal", 499 | ] 500 | 501 | [[package]] 502 | name = "self-tests" 503 | version = "0.1.0" 504 | dependencies = [ 505 | "board", 506 | "cortex-m 0.6.7", 507 | "defmt", 508 | "defmt-rtt", 509 | "defmt-test", 510 | "panic-probe", 511 | ] 512 | 513 | [[package]] 514 | name = "semver" 515 | version = "0.9.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 518 | dependencies = [ 519 | "semver-parser 0.7.0", 520 | ] 521 | 522 | [[package]] 523 | name = "semver" 524 | version = "0.11.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 527 | dependencies = [ 528 | "semver-parser 0.10.2", 529 | ] 530 | 531 | [[package]] 532 | name = "semver-parser" 533 | version = "0.7.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 536 | 537 | [[package]] 538 | name = "semver-parser" 539 | version = "0.10.2" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 542 | dependencies = [ 543 | "pest", 544 | ] 545 | 546 | [[package]] 547 | name = "serde" 548 | version = "1.0.123" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 551 | dependencies = [ 552 | "serde_derive", 553 | ] 554 | 555 | [[package]] 556 | name = "serde_derive" 557 | version = "1.0.123" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 560 | dependencies = [ 561 | "proc-macro2", 562 | "quote", 563 | "syn", 564 | ] 565 | 566 | [[package]] 567 | name = "stable_deref_trait" 568 | version = "1.2.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 571 | 572 | [[package]] 573 | name = "syn" 574 | version = "1.0.60" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 577 | dependencies = [ 578 | "proc-macro2", 579 | "quote", 580 | "unicode-xid", 581 | ] 582 | 583 | [[package]] 584 | name = "typenum" 585 | version = "1.12.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 588 | 589 | [[package]] 590 | name = "ucd-trie" 591 | version = "0.1.3" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 594 | 595 | [[package]] 596 | name = "unicode-xid" 597 | version = "0.2.1" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 600 | 601 | [[package]] 602 | name = "vcell" 603 | version = "0.1.3" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 606 | 607 | [[package]] 608 | name = "version_check" 609 | version = "0.9.2" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 612 | 613 | [[package]] 614 | name = "void" 615 | version = "1.0.2" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 618 | 619 | [[package]] 620 | name = "volatile-register" 621 | version = "0.2.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 624 | dependencies = [ 625 | "vcell", 626 | ] 627 | -------------------------------------------------------------------------------- /cross/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "app", 4 | "board", 5 | "self-tests", 6 | ] 7 | 8 | [profile.dev] 9 | codegen-units = 1 10 | incremental = false 11 | lto = 'fat' 12 | opt-level = 's' 13 | 14 | [profile.test] 15 | codegen-units = 1 16 | incremental = false 17 | lto = 'fat' 18 | opt-level = 's' -------------------------------------------------------------------------------- /cross/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "app" 5 | version = "0.1.0" 6 | 7 | # makes `cargo check --all-targets` work 8 | [[bin]] 9 | name = "app" 10 | bench = false 11 | doctest = false 12 | test = false 13 | 14 | [dependencies] 15 | board = { path = "../board" } 16 | cortex-m-rtic = "0.5.6" 17 | defmt = "0.2.0" 18 | defmt-rtt = "0.2.0" 19 | heapless = "0.6.1" 20 | messages = { path = "../../messages" } 21 | panic-probe = { version = "0.2.0", features = ["print-defmt"] } 22 | postcard = "0.5.2" 23 | 24 | [features] 25 | default = ['defmt-default'] 26 | # these features are required by defmt 27 | defmt-default = [] 28 | defmt-trace = [] 29 | defmt-debug = [] 30 | defmt-info = [] 31 | defmt-warn = [] 32 | defmt-error = [] 33 | -------------------------------------------------------------------------------- /cross/app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::slice; 5 | 6 | use board::{Board, Instant, Scd30, Serial}; 7 | use defmt::unwrap; 8 | use defmt_rtt as _; 9 | use heapless::{consts, Vec}; 10 | use messages::{Host2Target, Measurement, Target2Host}; 11 | use panic_probe as _; 12 | use rtic::cyccnt::U32Ext; 13 | 14 | #[rtic::app(device = board::pac, monotonic = rtic::cyccnt::CYCCNT)] 15 | const APP: () = { 16 | struct Resources { 17 | #[init(0)] 18 | count: u32, 19 | scd30: Scd30, 20 | serial: Serial, 21 | #[init(None)] 22 | measurement: Option, 23 | } 24 | 25 | #[init(spawn = [periodic])] 26 | fn init(cx: init::Context) -> init::LateResources { 27 | let mut board = Board::init(cx.core.DCB, cx.core.DWT); 28 | 29 | board 30 | .scd30 31 | .start_continuous_measurement() 32 | .unwrap(); 33 | unwrap!(cx.spawn.periodic()); 34 | 35 | defmt::info!("DONE"); 36 | init::LateResources { 37 | scd30: board.scd30, 38 | serial: board.serial, 39 | } 40 | } 41 | 42 | #[idle(resources = [serial, measurement])] 43 | fn idle(mut cx: idle::Context) -> ! { 44 | let serial = cx.resources.serial; 45 | let mut rx_buffer = Vec::::new(); 46 | let mut tx_buffer = [0; 64]; 47 | 48 | let mut byte = 0; 49 | loop { 50 | serial.read(slice::from_mut(&mut byte)).unwrap(); 51 | 52 | if byte == 0 { 53 | defmt::info!("RX bytes={}", &*rx_buffer); 54 | 55 | if let Ok(request) = postcard::from_bytes_cobs::(&mut rx_buffer) { 56 | match request { 57 | Host2Target::GetLastMeasurement => { 58 | let resp = cx 59 | .resources 60 | .measurement 61 | .lock(|opt| opt.clone()) 62 | .map(Target2Host::Measurement) 63 | .unwrap_or(Target2Host::NotReady); 64 | 65 | let bytes = postcard::to_slice_cobs(&resp, &mut tx_buffer).unwrap(); 66 | defmt::info!("TX bytes={}", bytes); 67 | serial.write(bytes).unwrap(); 68 | } 69 | } 70 | } else { 71 | defmt::error!("postcard deserialization error") 72 | } 73 | 74 | rx_buffer.clear(); 75 | } else { 76 | rx_buffer.push(byte).unwrap(); 77 | } 78 | } 79 | } 80 | 81 | // NOTE instead of a periodic software task it would be more efficient to use a hardware task 82 | // bound to an "external pin interrupt" that fires when the SCD30's RDY pin goes high 83 | #[task(schedule = [periodic], resources = [count, scd30, measurement])] 84 | fn periodic(cx: periodic::Context) { 85 | // run this again in 20 ms -- this polling period affects the `timestamp` accuracy 86 | unwrap!(cx.schedule.periodic(cx.scheduled + 1_280_000.cycles())); 87 | 88 | let scd30 = cx.resources.scd30; 89 | if let Ok(data_ready) = scd30.data_ready() { 90 | // NOTE likewise this timestamp would be more accurate if a hardware task was used 91 | let timestamp = Instant::now().as_cycles(); 92 | 93 | if data_ready { 94 | if let Ok(sensor_data) = scd30.read_measurement() { 95 | defmt::info!("{}", sensor_data); 96 | 97 | *cx.resources.measurement = Some(Measurement { 98 | id: *cx.resources.count, 99 | timestamp, 100 | co2: sensor_data.co2, 101 | }); 102 | *cx.resources.count += 1; 103 | } else { 104 | defmt::error!("couldn't read sensor data"); 105 | } 106 | } 107 | } else { 108 | defmt::error!("couldn't check sensor's data ready flag"); 109 | } 110 | } 111 | 112 | extern "C" { 113 | fn RTC0(); 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /cross/board/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "board" 5 | version = "0.1.0" 6 | 7 | # makes `cargo check --all-targets` (used by Rust-Analyzer) work 8 | [lib] 9 | bench = false 10 | doctest = false 11 | test = false 12 | 13 | [dependencies] 14 | cortex-m = "0.6.7" 15 | defmt = "0.2.0" 16 | nrf52840-hal = "0.12.0" 17 | scd30 = { path = "../../scd30" } 18 | 19 | [features] 20 | # these features are required by defmt 21 | defmt-default = [] 22 | defmt-trace = [] 23 | defmt-debug = [] 24 | defmt-info = [] 25 | defmt-warn = [] 26 | defmt-error = [] 27 | -------------------------------------------------------------------------------- /cross/board/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::time::Duration; 4 | 5 | use cortex_m::peripheral::{DCB, DWT}; 6 | use defmt::unwrap; 7 | pub use nrf52840_hal::pac; 8 | use nrf52840_hal::{ 9 | gpio::{p0, Level}, 10 | pac::{TWIM0, UARTE0}, 11 | twim, 12 | uarte::{self, Baudrate, Parity}, 13 | Twim, Uarte, 14 | }; 15 | pub use scd30::SensorData; 16 | 17 | const CYCCNT_FREQUENCY_MHZ: u32 = 64; 18 | 19 | pub type Scd30 = scd30::Scd30>; 20 | pub type Serial = Uarte; 21 | 22 | /// Peripherals and on-board sensors 23 | pub struct Board { 24 | pub scd30: Scd30, 25 | pub serial: Serial, 26 | } 27 | 28 | impl Board { 29 | /// Initializes the board's peripherals and sensors 30 | pub fn init(mut dcb: DCB, mut dwt: DWT) -> Self { 31 | dcb.enable_trace(); 32 | unsafe { dwt.cyccnt.write(0) } 33 | dwt.enable_cycle_counter(); 34 | defmt::timestamp!( 35 | "{=u32:µs}", 36 | cortex_m::peripheral::DWT::get_cycle_count() / CYCCNT_FREQUENCY_MHZ 37 | ); 38 | 39 | let dev_periph = unwrap!(nrf52840_hal::pac::Peripherals::take()); 40 | let p0 = p0::Parts::new(dev_periph.P0); 41 | 42 | let scl = p0.p0_30.into_floating_input().degrade(); 43 | let sda = p0.p0_31.into_floating_input().degrade(); 44 | let pins = twim::Pins { scl, sda }; 45 | let twim = Twim::new(dev_periph.TWIM0, pins, twim::Frequency::K100); 46 | 47 | // TXD = 06 48 | // RXD = 08 49 | let txd = p0.p0_06.into_push_pull_output(Level::Low).degrade(); 50 | let rxd = p0.p0_08.into_floating_input().degrade(); 51 | let pins = uarte::Pins { 52 | txd, 53 | rxd, 54 | cts: None, 55 | rts: None, 56 | }; 57 | 58 | let uarte = Uarte::new( 59 | dev_periph.UARTE0, 60 | pins, 61 | Parity::EXCLUDED, 62 | Baudrate::BAUD115200, 63 | ); 64 | 65 | Self { 66 | scd30: Scd30::init(twim), 67 | serial: uarte, 68 | } 69 | } 70 | 71 | pub fn delay(&self, dur: Duration) { 72 | let start = Instant::now(); 73 | while start.elapsed() < dur {} 74 | } 75 | } 76 | 77 | #[derive(Clone, Copy)] 78 | pub struct Instant(u32); 79 | 80 | impl Instant { 81 | pub fn now() -> Self { 82 | Instant(cortex_m::peripheral::DWT::get_cycle_count()) 83 | } 84 | 85 | pub fn elapsed(&self) -> Duration { 86 | let now = Instant::now(); 87 | let elapsed = now.0.wrapping_sub(self.0); 88 | let micros = elapsed / CYCCNT_FREQUENCY_MHZ; 89 | Duration::from_micros(micros as u64) 90 | } 91 | 92 | pub fn as_cycles(self) -> u32 { 93 | self.0 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cross/self-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "self-tests" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [[test]] 9 | name = "scd30" 10 | harness = false 11 | 12 | [dev-dependencies] 13 | board = { path = "../board" } 14 | cortex-m = "0.6.7" 15 | defmt = "0.2.0" 16 | defmt-rtt = "0.2.0" 17 | defmt-test = "0.2.1" 18 | panic-probe = { version = "0.2.0", features = ["print-defmt"] } 19 | 20 | [features] 21 | default = ['defmt-trace'] 22 | # these features are required by defmt 23 | defmt-default = [] 24 | defmt-trace = [] 25 | defmt-debug = [] 26 | defmt-info = [] 27 | defmt-warn = [] 28 | defmt-error = [] 29 | -------------------------------------------------------------------------------- /cross/self-tests/tests/scd30.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use defmt_rtt as _; 5 | use panic_probe as _; 6 | 7 | #[defmt_test::tests] 8 | mod tests { 9 | use core::time::Duration; 10 | 11 | use board::Board; 12 | use defmt::{assert_eq, unwrap}; 13 | 14 | #[init] 15 | fn init() -> Board { 16 | let cm_periph = unwrap!(cortex_m::Peripherals::take()); 17 | Board::init(cm_periph.DCB, cm_periph.DWT) 18 | } 19 | 20 | #[test] 21 | fn confirm_firmware_version(board: &mut Board) { 22 | const EXPECTED: [u8; 2] = [3, 66]; 23 | 24 | assert_eq!(EXPECTED, board.scd30.get_firmware_version().unwrap()) 25 | } 26 | 27 | #[test] 28 | fn data_ready_within_two_seconds(board: &mut Board) { 29 | board 30 | .scd30 31 | .start_continuous_measurement() 32 | .unwrap(); 33 | 34 | // do this twice because there may be a cached measurement 35 | // (the sensor is never power-cycled / reset) 36 | for _ in 0..2 { 37 | board.delay(Duration::from_millis(2_100)); 38 | assert!(board.scd30.data_ready().unwrap()); 39 | 40 | // clear data ready flag 41 | let _ = board.scd30.read_measurement(); 42 | } 43 | } 44 | 45 | #[test] 46 | fn reasonable_co2_value(board: &mut Board) { 47 | // range reported by the sensor when using I2C 48 | const MIN_CO2: f32 = 0.; 49 | const MAX_CO2: f32 = 40_000.; 50 | 51 | // do this twice for good measure 52 | for _ in 0..2 { 53 | while !board.scd30.data_ready().unwrap() {} 54 | 55 | let measurement = board.scd30.read_measurement().unwrap(); 56 | assert!(measurement.co2.is_finite()); 57 | assert!(measurement.co2 >= MIN_CO2); 58 | assert!(measurement.co2 <= MAX_CO2); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /host-target-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "host-target-tests" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [dev-dependencies] 9 | anyhow = "1.0.38" 10 | messages = { path = "../messages" } 11 | parking_lot = "0.11.1" 12 | postcard = { version = "0.5.2", features = ["alloc"] } 13 | serialport = "4.0.0" 14 | -------------------------------------------------------------------------------- /host-target-tests/tests/serial.rs: -------------------------------------------------------------------------------- 1 | use std::{thread, time::Duration}; 2 | 3 | use anyhow::anyhow; 4 | use messages::{Host2Target, Measurement, Target2Host}; 5 | use parking_lot::{Mutex, MutexGuard}; 6 | use serialport::SerialPort; 7 | 8 | const BAUD_RATE: u32 = 115_200; 9 | 10 | #[test] 11 | fn get_measurement_succeeds() -> Result<(), anyhow::Error> { 12 | let mut target = TargetSerialConn::open()?; 13 | dbg!(target.get_measurement()?); 14 | Ok(()) 15 | } 16 | 17 | #[test] 18 | fn new_measurement_every_2_seconds() -> Result<(), anyhow::Error> { 19 | let mut target = TargetSerialConn::open()?; 20 | 21 | let samples = (0..3).map(|_| { 22 | thread::sleep(Duration::from_millis(2_100)); 23 | Ok(dbg!(target.get_measurement()?.unwrap())) 24 | }).collect::, anyhow::Error>>()?; 25 | 26 | for pair in samples.windows(2) { 27 | // all samples should be different 28 | assert_ne!(pair[0], pair[1]); 29 | // new measurements should have contiguous IDs 30 | assert_eq!(pair[0].id.wrapping_add(1), pair[1].id); 31 | // timestamps should be different 32 | // (the timestamp can wrap around so we don't use `>=`) 33 | assert_ne!(pair[0].timestamp, pair[1].timestamp); 34 | } 35 | 36 | Ok(()) 37 | } 38 | 39 | #[test] 40 | fn consecutive_sampling_returns_same_measurement() -> Result<(), anyhow::Error> { 41 | let mut target = TargetSerialConn::open()?; 42 | 43 | // sample faster than the SCD30 update rate 44 | let first = dbg!(target.get_measurement()?); 45 | let second = dbg!(target.get_measurement()?); 46 | let third = dbg!(target.get_measurement()?); 47 | 48 | // at most one new measurement can occur; 2 samples should be the same measurement 49 | if first != second { 50 | assert_eq!(second, third); 51 | } 52 | 53 | if second != third { 54 | assert_eq!(first, second); 55 | } 56 | 57 | Ok(()) 58 | } 59 | 60 | /// A connection between the host and the target over a serial interface 61 | pub struct TargetSerialConn { 62 | port: Box, 63 | rx_bytes: Vec, 64 | _guard: MutexGuard<'static, ()>, 65 | } 66 | 67 | impl TargetSerialConn { 68 | /// Opens a serial connection to the target 69 | // NOTE this operation does NOT use a lock file so a different process is free to operate on 70 | // the serial port (e.g. `[sudo] cat /dev/ttyACM0`). That can make the rest of this 71 | // API misbehave. 72 | pub fn open() -> Result { 73 | const VID: u16 = 0x1366; 74 | const PID: u16 = 0x1015; 75 | 76 | static MUTEX: Mutex<()> = parking_lot::const_mutex(()); 77 | 78 | let _guard = MUTEX.lock(); 79 | 80 | let ports = serialport::available_ports()?; 81 | for port in ports { 82 | if let serialport::SerialPortType::UsbPort(info) = &port.port_type { 83 | if info.vid == VID && info.pid == PID { 84 | let port = serialport::new(port.port_name, BAUD_RATE) 85 | .timeout(Duration::from_millis(100)) 86 | .open()?; 87 | return Ok(Self { 88 | port, 89 | rx_bytes: vec![], 90 | _guard, 91 | }); 92 | } 93 | } 94 | } 95 | 96 | Err(anyhow!("device {:04x}:{:04x} is not connected", VID, PID)) 97 | } 98 | 99 | /// Requests the last measurement 100 | pub fn get_measurement(&mut self) -> Result, anyhow::Error> { 101 | let resp = self.request(&Host2Target::GetLastMeasurement)?; 102 | 103 | Ok(match resp { 104 | Target2Host::NotReady => None, 105 | Target2Host::Measurement(measurement) => Some(measurement), 106 | }) 107 | } 108 | 109 | /// Sends a request to the target and waits for a response. 110 | /// Returns the target response. 111 | fn request(&mut self, request: &Host2Target) -> Result { 112 | let tx_bytes = 113 | postcard::to_allocvec_cobs(&request).map_err(|e| anyhow::Error::msg(e.to_string()))?; 114 | 115 | self.port.write_all(dbg!(&tx_bytes))?; 116 | 117 | let mut buffer = [0; 64]; 118 | 119 | let delimiter_pos = loop { 120 | let bytes_read = self.port.read(&mut buffer)?; 121 | 122 | self.rx_bytes.extend_from_slice(&buffer[..bytes_read]); 123 | 124 | if let Some(pos) = self.rx_bytes.iter().position(|byte| *byte == 0) { 125 | break pos; 126 | } 127 | }; 128 | 129 | dbg!(&self.rx_bytes); 130 | 131 | let endpos = delimiter_pos + 1; 132 | let frame = &mut self.rx_bytes[..dbg!(endpos)]; 133 | let res = postcard::from_bytes_cobs::(dbg!(frame)) 134 | .map_err(|e| anyhow::Error::msg(e.to_string())); 135 | 136 | // pop frame from RX buffer *before* raising any error 137 | let len = self.rx_bytes.len(); 138 | self.rx_bytes.rotate_left(endpos); 139 | self.rx_bytes.truncate(len - endpos); 140 | 141 | dbg!(&self.rx_bytes); 142 | 143 | res 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /messages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "messages" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | serde = { version = "1.0.123", default-features = false } 9 | serde_derive = "1.0.123" 10 | 11 | [dev-dependencies] 12 | postcard = { version = "0.5.2", features = ["alloc"] } 13 | quickcheck = "1" 14 | quickcheck_macros = "1" 15 | -------------------------------------------------------------------------------- /messages/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | 3 | use serde_derive::{Deserialize, Serialize}; 4 | 5 | /// A message sent from the host to the target 6 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 7 | pub enum Host2Target { 8 | GetLastMeasurement, 9 | } 10 | 11 | /// A message sent from the target to the host 12 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 13 | pub enum Target2Host { 14 | NotReady, 15 | Measurement(Measurement), 16 | } 17 | 18 | /// A measurement reported by the target 19 | #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] 20 | pub struct Measurement { 21 | /// The measurement identifier; this is a monotonically increasing counter 22 | pub id: u32, 23 | /// A timestamp in unspecified units; it may wrap around 24 | pub timestamp: u32, 25 | /// The CO2 concentration in parts per million (ppm) 26 | pub co2: f32, 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use quickcheck_macros::quickcheck; 32 | 33 | use super::{Host2Target, Measurement, Target2Host}; 34 | 35 | /// Max payload size for a USB (2.0 Full Size) HID packet 36 | const MAX_SIZE: usize = 64; 37 | 38 | #[test] 39 | fn host2target_message_size() -> postcard::Result<()> { 40 | let msg = Host2Target::GetLastMeasurement; 41 | let bytes = postcard::to_allocvec(&msg)?; 42 | assert!(dbg!(bytes).len() <= MAX_SIZE); 43 | Ok(()) 44 | } 45 | 46 | #[test] 47 | fn target2host_not_ready_message_size() -> postcard::Result<()> { 48 | let msg = Target2Host::NotReady; 49 | let bytes = postcard::to_allocvec(&msg)?; 50 | assert!(dbg!(bytes).len() <= MAX_SIZE); 51 | Ok(()) 52 | } 53 | 54 | #[quickcheck] 55 | fn target2host_measurement_message_size( 56 | id: u32, 57 | timestamp: u32, 58 | co2: f32, 59 | ) -> postcard::Result<()> { 60 | let msg = Target2Host::Measurement(Measurement { id, timestamp, co2 }); 61 | let bytes = postcard::to_allocvec(&msg)?; 62 | assert!(bytes.len() <= MAX_SIZE); 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /scd30/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scd30" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | embedded-hal = "0.2.4" 9 | crc-any = { version = "2.3.5", default-features = false } 10 | defmt = "0.2.0" 11 | 12 | [dev-dependencies] 13 | embedded-hal-mock = "0.7.2" 14 | -------------------------------------------------------------------------------- /scd30/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | 3 | use crc_any::CRCu8; 4 | use defmt::Format; 5 | use embedded_hal::blocking::i2c; 6 | 7 | /// SCD30 I2C address 8 | const ADDRESS: u8 = 0x61; 9 | 10 | #[derive(Clone, Copy, Format)] 11 | pub struct SensorData { 12 | pub co2: f32, 13 | pub temperature: f32, 14 | pub humidity: f32, 15 | } 16 | 17 | /// A SCD30 sensor on the I2C bus `I` 18 | pub struct Scd30(I) 19 | where 20 | I: i2c::Read + i2c::Write; 21 | 22 | /// A driver error 23 | #[derive(Debug, PartialEq)] 24 | pub enum Error { 25 | /// I2C bus error 26 | I2c(E), 27 | /// CRC validation failed 28 | InvalidCrc, 29 | } 30 | 31 | impl Scd30 32 | where 33 | I: i2c::Read + i2c::Write, 34 | { 35 | /// Initializes the SCD30 driver. 36 | /// This consumes the I2C bus `I` 37 | pub fn init(i2c: I) -> Self { 38 | Scd30(i2c) 39 | } 40 | 41 | /// Returns the firmware version reported by the SCD30 sensor 42 | pub fn get_firmware_version(&mut self) -> Result<[u8; 2], Error> { 43 | let command: [u8; 2] = [0xd1, 0x00]; 44 | let mut rd_buffer = [0u8; 3]; 45 | 46 | self.0.write(ADDRESS, &command).map_err(Error::I2c)?; 47 | self.0.read(ADDRESS, &mut rd_buffer).map_err(Error::I2c)?; 48 | 49 | let major = rd_buffer[0]; 50 | let minor = rd_buffer[1]; 51 | let crc = rd_buffer[2]; 52 | 53 | if compute_crc(&rd_buffer[..2]) == crc { 54 | Ok([major, minor]) 55 | } else { 56 | Err(Error::InvalidCrc) 57 | } 58 | } 59 | 60 | pub fn start_continuous_measurement(&mut self) -> Result<(), Error> { 61 | // NOTE should be configurable 62 | const AMBIENT_PRESSURE: u16 = 1_020; 63 | 64 | // command bytes 65 | let mut command: [u8; 5] = [0x00, 0x10, 0x00, 0x00, 0x00]; 66 | let argument_bytes = &AMBIENT_PRESSURE.to_be_bytes(); 67 | 68 | command[2] = argument_bytes[0]; 69 | command[3] = argument_bytes[1]; 70 | command[4] = compute_crc(argument_bytes); 71 | 72 | self.0.write(ADDRESS, &command).map_err(Error::I2c)?; 73 | 74 | Ok(()) 75 | } 76 | 77 | // NOTE testing these 3 methods is left as an exercise for the reader 78 | pub fn data_ready(&mut self) -> Result> { 79 | let command: [u8; 2] = [0x02, 0x02]; 80 | let mut rd_buffer = [0u8; 3]; 81 | 82 | self.0.write(ADDRESS, &command).map_err(Error::I2c)?; 83 | self.0.read(ADDRESS, &mut rd_buffer).map_err(Error::I2c)?; 84 | 85 | Ok(u16::from_be_bytes([rd_buffer[0], rd_buffer[1]]) == 1) 86 | } 87 | 88 | pub fn read_measurement(&mut self) -> Result> { 89 | let command: [u8; 2] = [0x03, 0x00]; 90 | let mut rd_buffer = [0u8; 18]; 91 | 92 | self.0.write(ADDRESS, &command).map_err(Error::I2c)?; 93 | self.0.read(ADDRESS, &mut rd_buffer).map_err(Error::I2c)?; 94 | 95 | let data = SensorData { 96 | co2: f32::from_bits(u32::from_be_bytes([ 97 | rd_buffer[0], 98 | rd_buffer[1], 99 | rd_buffer[3], 100 | rd_buffer[4], 101 | ])), 102 | temperature: f32::from_bits(u32::from_be_bytes([ 103 | rd_buffer[6], 104 | rd_buffer[7], 105 | rd_buffer[9], 106 | rd_buffer[10], 107 | ])), 108 | humidity: f32::from_bits(u32::from_be_bytes([ 109 | rd_buffer[12], 110 | rd_buffer[13], 111 | rd_buffer[15], 112 | rd_buffer[16], 113 | ])), 114 | }; 115 | Ok(data) 116 | } 117 | 118 | /// Destroys this driver and releases the I2C bus `I` 119 | pub fn destroy(self) -> I { 120 | self.0 121 | } 122 | } 123 | 124 | fn compute_crc(bytes: &[u8]) -> u8 { 125 | let mut crc = CRCu8::create_crc(0x31, 8, 0xff, 0x00, false); 126 | crc.digest(bytes); 127 | crc.get_crc() 128 | } 129 | 130 | #[cfg(test)] 131 | mod tests { 132 | use super::{Error, Scd30, ADDRESS}; 133 | use embedded_hal_mock::i2c; 134 | 135 | #[test] 136 | fn firmware_version() { 137 | let expectations = vec![ 138 | i2c::Transaction::write(ADDRESS, vec![0xD1, 0x00]), 139 | i2c::Transaction::read(ADDRESS, vec![0x03, 0x42, 0xF3]), 140 | ]; 141 | let mock = i2c::Mock::new(&expectations); 142 | 143 | let mut scd30 = Scd30::init(mock); 144 | let version = scd30.get_firmware_version().unwrap(); 145 | assert_eq!([3, 66], version); 146 | 147 | let mut mock = scd30.destroy(); 148 | mock.done(); // verify expectations 149 | } 150 | 151 | #[test] 152 | fn firmware_version_bad_crc() { 153 | let expectations = vec![ 154 | i2c::Transaction::write(ADDRESS, vec![0xD1, 0x00]), 155 | // NOTE negated CRC byte in the response! 156 | i2c::Transaction::read(ADDRESS, vec![0x03, 0x42, !0xF3]), 157 | ]; 158 | let mock = i2c::Mock::new(&expectations); 159 | 160 | let mut scd30 = Scd30::init(mock); 161 | let res = scd30.get_firmware_version(); 162 | assert_eq!(Err(Error::InvalidCrc), res); 163 | 164 | scd30.destroy().done(); // verify expectations 165 | } 166 | 167 | #[test] 168 | fn crc() { 169 | // example from the Interface Specification document 170 | assert_eq!(super::compute_crc(&[0xBE, 0xEF]), 0x92); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "xtask" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | anyhow = "1.0.38" 10 | xshell = "0.1.9" 11 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![deny(unused_must_use)] 3 | 4 | use std::{env, path::PathBuf}; 5 | 6 | use xshell::cmd; 7 | 8 | fn main() -> Result<(), anyhow::Error> { 9 | let args = env::args().skip(1).collect::>(); 10 | let args = args.iter().map(|s| &**s).collect::>(); 11 | 12 | match &args[..] { 13 | ["test", "all"] => test_all(), 14 | ["test", "host"] => test_host(), 15 | ["test", "host-target"] => test_host_target(), 16 | ["test", "target"] => test_target(), 17 | _ => { 18 | println!("USAGE cargo xtask test [all|host|host-target|target]"); 19 | Ok(()) 20 | } 21 | } 22 | } 23 | 24 | fn test_all() -> Result<(), anyhow::Error> { 25 | test_host()?; 26 | test_target()?; 27 | test_host_target() 28 | } 29 | 30 | fn test_host() -> Result<(), anyhow::Error> { 31 | let _p = xshell::pushd(root_dir())?; 32 | cmd!("cargo test --workspace --exclude host-target-tests").run()?; 33 | Ok(()) 34 | } 35 | 36 | fn test_host_target() -> Result<(), anyhow::Error> { 37 | flash()?; 38 | 39 | let _p = xshell::pushd(root_dir())?; 40 | cmd!("cargo test -p host-target-tests").run()?; 41 | 42 | Ok(()) 43 | } 44 | 45 | fn test_target() -> Result<(), anyhow::Error> { 46 | let _p = xshell::pushd(root_dir().join("cross"))?; 47 | cmd!("cargo test -p self-tests").run()?; 48 | Ok(()) 49 | } 50 | 51 | fn flash() -> Result<(), anyhow::Error> { 52 | let _p = xshell::pushd(root_dir().join("cross"))?; 53 | cmd!("cargo flash --chip nRF52840_xxAA --release").run()?; 54 | Ok(()) 55 | } 56 | 57 | fn root_dir() -> PathBuf { 58 | let mut xtask_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 59 | xtask_dir.pop(); 60 | xtask_dir 61 | } 62 | --------------------------------------------------------------------------------