Repo of no-std crates for my personal embedded projects

SHTC3 crate

+1613 -7
+386 -6
Cargo.lock
··· 3 version = 4 4 5 [[package]] 6 name = "bitflags" 7 version = "1.3.2" 8 source = "registry+https://github.com/rust-lang/crates.io-index" 9 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 11 [[package]] 12 name = "byteorder" 13 version = "1.5.0" 14 source = "registry+https://github.com/rust-lang/crates.io-index" 15 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 16 17 [[package]] 18 name = "defmt" 19 version = "1.0.1" 20 source = "registry+https://github.com/rust-lang/crates.io-index" 21 checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" 22 dependencies = [ 23 - "bitflags", 24 "defmt-macros", 25 ] 26 ··· 47 ] 48 49 [[package]] 50 name = "hash32" 51 version = "0.3.1" 52 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 61 source = "registry+https://github.com/rust-lang/crates.io-index" 62 checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" 63 dependencies = [ 64 - "defmt", 65 "hash32", 66 "stable_deref_trait", 67 ] 68 69 [[package]] 70 name = "proc-macro-error-attr2" 71 version = "2.0.0" 72 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 99 100 [[package]] 101 name = "quote" 102 - version = "1.0.42" 103 source = "registry+https://github.com/rust-lang/crates.io-index" 104 - checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 105 dependencies = [ 106 "proc-macro2", 107 ] ··· 114 name = "sachy-bthome" 115 version = "0.1.0" 116 dependencies = [ 117 - "defmt", 118 "heapless", 119 "sachy-fmt", 120 ] ··· 123 name = "sachy-fmt" 124 version = "0.1.0" 125 dependencies = [ 126 - "defmt", 127 ] 128 129 [[package]] ··· 144 ] 145 146 [[package]] 147 name = "thiserror" 148 version = "2.0.17" 149 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 161 "proc-macro2", 162 "quote", 163 "syn", 164 ] 165 166 [[package]] ··· 168 version = "1.0.22" 169 source = "registry+https://github.com/rust-lang/crates.io-index" 170 checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
··· 3 version = 4 4 5 [[package]] 6 + name = "autocfg" 7 + version = "1.5.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 10 + 11 + [[package]] 12 name = "bitflags" 13 version = "1.3.2" 14 source = "registry+https://github.com/rust-lang/crates.io-index" 15 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 17 [[package]] 18 + name = "bitflags" 19 + version = "2.10.0" 20 + source = "registry+https://github.com/rust-lang/crates.io-index" 21 + checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 22 + 23 + [[package]] 24 name = "byteorder" 25 version = "1.5.0" 26 source = "registry+https://github.com/rust-lang/crates.io-index" 27 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 28 29 [[package]] 30 + name = "cast" 31 + version = "0.3.0" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 34 + 35 + [[package]] 36 + name = "cc" 37 + version = "1.2.48" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" 40 + dependencies = [ 41 + "find-msvc-tools", 42 + "shlex", 43 + ] 44 + 45 + [[package]] 46 + name = "cfg-if" 47 + version = "1.0.4" 48 + source = "registry+https://github.com/rust-lang/crates.io-index" 49 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 50 + 51 + [[package]] 52 + name = "core-foundation" 53 + version = "0.10.0" 54 + source = "registry+https://github.com/rust-lang/crates.io-index" 55 + checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" 56 + dependencies = [ 57 + "core-foundation-sys", 58 + "libc", 59 + ] 60 + 61 + [[package]] 62 + name = "core-foundation-sys" 63 + version = "0.8.7" 64 + source = "registry+https://github.com/rust-lang/crates.io-index" 65 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 66 + 67 + [[package]] 68 + name = "defmt" 69 + version = "0.3.100" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" 72 + dependencies = [ 73 + "defmt 1.0.1", 74 + ] 75 + 76 + [[package]] 77 name = "defmt" 78 version = "1.0.1" 79 source = "registry+https://github.com/rust-lang/crates.io-index" 80 checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" 81 dependencies = [ 82 + "bitflags 1.3.2", 83 "defmt-macros", 84 ] 85 ··· 106 ] 107 108 [[package]] 109 + name = "embedded-hal" 110 + version = "1.0.0" 111 + source = "registry+https://github.com/rust-lang/crates.io-index" 112 + checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" 113 + dependencies = [ 114 + "defmt 0.3.100", 115 + ] 116 + 117 + [[package]] 118 + name = "embedded-hal-async" 119 + version = "1.0.0" 120 + source = "registry+https://github.com/rust-lang/crates.io-index" 121 + checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 122 + dependencies = [ 123 + "embedded-hal", 124 + ] 125 + 126 + [[package]] 127 + name = "embedded-hal-mock" 128 + version = "0.11.1" 129 + source = "registry+https://github.com/rust-lang/crates.io-index" 130 + checksum = "f9a0f04f8886106faf281c47b6a0e4054a369baedaf63591fdb8da9761f3f379" 131 + dependencies = [ 132 + "embedded-hal", 133 + "embedded-hal-nb", 134 + ] 135 + 136 + [[package]] 137 + name = "embedded-hal-nb" 138 + version = "1.0.0" 139 + source = "registry+https://github.com/rust-lang/crates.io-index" 140 + checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" 141 + dependencies = [ 142 + "embedded-hal", 143 + "nb", 144 + ] 145 + 146 + [[package]] 147 + name = "find-msvc-tools" 148 + version = "0.1.5" 149 + source = "registry+https://github.com/rust-lang/crates.io-index" 150 + checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 151 + 152 + [[package]] 153 + name = "gpio-cdev" 154 + version = "0.6.0" 155 + source = "registry+https://github.com/rust-lang/crates.io-index" 156 + checksum = "09831ec59b80be69e75d29cf36e16afbbe5fd1af9c1bf4689ad91c77db5aa6a6" 157 + dependencies = [ 158 + "bitflags 2.10.0", 159 + "libc", 160 + "nix 0.27.1", 161 + ] 162 + 163 + [[package]] 164 name = "hash32" 165 version = "0.3.1" 166 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 175 source = "registry+https://github.com/rust-lang/crates.io-index" 176 checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" 177 dependencies = [ 178 + "defmt 1.0.1", 179 "hash32", 180 "stable_deref_trait", 181 ] 182 183 [[package]] 184 + name = "i2cdev" 185 + version = "0.6.2" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "9b940f7497c4f95b863b21cd34c3737b53a67d80d94cf29055d7f7eeca6ffdb4" 188 + dependencies = [ 189 + "bitflags 2.10.0", 190 + "byteorder", 191 + "libc", 192 + "nix 0.26.4", 193 + ] 194 + 195 + [[package]] 196 + name = "io-kit-sys" 197 + version = "0.4.1" 198 + source = "registry+https://github.com/rust-lang/crates.io-index" 199 + checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" 200 + dependencies = [ 201 + "core-foundation-sys", 202 + "mach2", 203 + ] 204 + 205 + [[package]] 206 + name = "libc" 207 + version = "0.2.178" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 210 + 211 + [[package]] 212 + name = "linux-embedded-hal" 213 + version = "0.4.1" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "2a8a605c95f708c78554738a12153b213f107d3bd5323f7ce32d6deb3faafb40" 216 + dependencies = [ 217 + "cast", 218 + "embedded-hal", 219 + "embedded-hal-nb", 220 + "gpio-cdev", 221 + "i2cdev", 222 + "nb", 223 + "nix 0.27.1", 224 + "serialport", 225 + "spidev", 226 + "sysfs_gpio", 227 + ] 228 + 229 + [[package]] 230 + name = "mach2" 231 + version = "0.4.3" 232 + source = "registry+https://github.com/rust-lang/crates.io-index" 233 + checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" 234 + dependencies = [ 235 + "libc", 236 + ] 237 + 238 + [[package]] 239 + name = "memoffset" 240 + version = "0.6.5" 241 + source = "registry+https://github.com/rust-lang/crates.io-index" 242 + checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 243 + dependencies = [ 244 + "autocfg", 245 + ] 246 + 247 + [[package]] 248 + name = "memoffset" 249 + version = "0.7.1" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 252 + dependencies = [ 253 + "autocfg", 254 + ] 255 + 256 + [[package]] 257 + name = "nb" 258 + version = "1.1.0" 259 + source = "registry+https://github.com/rust-lang/crates.io-index" 260 + checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 261 + 262 + [[package]] 263 + name = "nix" 264 + version = "0.23.2" 265 + source = "registry+https://github.com/rust-lang/crates.io-index" 266 + checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" 267 + dependencies = [ 268 + "bitflags 1.3.2", 269 + "cc", 270 + "cfg-if", 271 + "libc", 272 + "memoffset 0.6.5", 273 + ] 274 + 275 + [[package]] 276 + name = "nix" 277 + version = "0.26.4" 278 + source = "registry+https://github.com/rust-lang/crates.io-index" 279 + checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 280 + dependencies = [ 281 + "bitflags 1.3.2", 282 + "cfg-if", 283 + "libc", 284 + "memoffset 0.7.1", 285 + "pin-utils", 286 + ] 287 + 288 + [[package]] 289 + name = "nix" 290 + version = "0.27.1" 291 + source = "registry+https://github.com/rust-lang/crates.io-index" 292 + checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 293 + dependencies = [ 294 + "bitflags 2.10.0", 295 + "cfg-if", 296 + "libc", 297 + ] 298 + 299 + [[package]] 300 + name = "pin-utils" 301 + version = "0.1.0" 302 + source = "registry+https://github.com/rust-lang/crates.io-index" 303 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 304 + 305 + [[package]] 306 name = "proc-macro-error-attr2" 307 version = "2.0.0" 308 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 335 336 [[package]] 337 name = "quote" 338 + version = "1.0.40" 339 source = "registry+https://github.com/rust-lang/crates.io-index" 340 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 341 dependencies = [ 342 "proc-macro2", 343 ] ··· 350 name = "sachy-bthome" 351 version = "0.1.0" 352 dependencies = [ 353 + "defmt 1.0.1", 354 "heapless", 355 "sachy-fmt", 356 ] ··· 359 name = "sachy-fmt" 360 version = "0.1.0" 361 dependencies = [ 362 + "defmt 1.0.1", 363 + ] 364 + 365 + [[package]] 366 + name = "sachy-shtc3" 367 + version = "0.1.0" 368 + dependencies = [ 369 + "defmt 1.0.1", 370 + "embedded-hal", 371 + "embedded-hal-async", 372 + "embedded-hal-mock", 373 + "linux-embedded-hal", 374 + ] 375 + 376 + [[package]] 377 + name = "scopeguard" 378 + version = "1.2.0" 379 + source = "registry+https://github.com/rust-lang/crates.io-index" 380 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 381 + 382 + [[package]] 383 + name = "serialport" 384 + version = "4.8.1" 385 + source = "registry+https://github.com/rust-lang/crates.io-index" 386 + checksum = "21f60a586160667241d7702c420fc223939fb3c0bb8d3fac84f78768e8970dee" 387 + dependencies = [ 388 + "bitflags 2.10.0", 389 + "cfg-if", 390 + "core-foundation", 391 + "core-foundation-sys", 392 + "io-kit-sys", 393 + "mach2", 394 + "nix 0.26.4", 395 + "quote", 396 + "scopeguard", 397 + "unescaper", 398 + "windows-sys", 399 + ] 400 + 401 + [[package]] 402 + name = "shlex" 403 + version = "1.3.0" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 406 + 407 + [[package]] 408 + name = "spidev" 409 + version = "0.6.1" 410 + source = "registry+https://github.com/rust-lang/crates.io-index" 411 + checksum = "32dadd0a877f0652fa52dbc4d2ed9f4877bea5cd30725507b36e1970a5ef0519" 412 + dependencies = [ 413 + "bitflags 2.10.0", 414 + "libc", 415 + "nix 0.26.4", 416 ] 417 418 [[package]] ··· 433 ] 434 435 [[package]] 436 + name = "sysfs_gpio" 437 + version = "0.6.2" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "e8808c55bc926565c62ef7838bcaa8add51585236803e2bdfa1472e3a3ab5e17" 440 + dependencies = [ 441 + "nix 0.23.2", 442 + ] 443 + 444 + [[package]] 445 name = "thiserror" 446 version = "2.0.17" 447 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 459 "proc-macro2", 460 "quote", 461 "syn", 462 + ] 463 + 464 + [[package]] 465 + name = "unescaper" 466 + version = "0.1.6" 467 + source = "registry+https://github.com/rust-lang/crates.io-index" 468 + checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" 469 + dependencies = [ 470 + "thiserror", 471 ] 472 473 [[package]] ··· 475 version = "1.0.22" 476 source = "registry+https://github.com/rust-lang/crates.io-index" 477 checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 478 + 479 + [[package]] 480 + name = "windows-sys" 481 + version = "0.52.0" 482 + source = "registry+https://github.com/rust-lang/crates.io-index" 483 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 484 + dependencies = [ 485 + "windows-targets", 486 + ] 487 + 488 + [[package]] 489 + name = "windows-targets" 490 + version = "0.52.6" 491 + source = "registry+https://github.com/rust-lang/crates.io-index" 492 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 493 + dependencies = [ 494 + "windows_aarch64_gnullvm", 495 + "windows_aarch64_msvc", 496 + "windows_i686_gnu", 497 + "windows_i686_gnullvm", 498 + "windows_i686_msvc", 499 + "windows_x86_64_gnu", 500 + "windows_x86_64_gnullvm", 501 + "windows_x86_64_msvc", 502 + ] 503 + 504 + [[package]] 505 + name = "windows_aarch64_gnullvm" 506 + version = "0.52.6" 507 + source = "registry+https://github.com/rust-lang/crates.io-index" 508 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 509 + 510 + [[package]] 511 + name = "windows_aarch64_msvc" 512 + version = "0.52.6" 513 + source = "registry+https://github.com/rust-lang/crates.io-index" 514 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 515 + 516 + [[package]] 517 + name = "windows_i686_gnu" 518 + version = "0.52.6" 519 + source = "registry+https://github.com/rust-lang/crates.io-index" 520 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 521 + 522 + [[package]] 523 + name = "windows_i686_gnullvm" 524 + version = "0.52.6" 525 + source = "registry+https://github.com/rust-lang/crates.io-index" 526 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 527 + 528 + [[package]] 529 + name = "windows_i686_msvc" 530 + version = "0.52.6" 531 + source = "registry+https://github.com/rust-lang/crates.io-index" 532 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 533 + 534 + [[package]] 535 + name = "windows_x86_64_gnu" 536 + version = "0.52.6" 537 + source = "registry+https://github.com/rust-lang/crates.io-index" 538 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 539 + 540 + [[package]] 541 + name = "windows_x86_64_gnullvm" 542 + version = "0.52.6" 543 + source = "registry+https://github.com/rust-lang/crates.io-index" 544 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 545 + 546 + [[package]] 547 + name = "windows_x86_64_msvc" 548 + version = "0.52.6" 549 + source = "registry+https://github.com/rust-lang/crates.io-index" 550 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+1 -1
Cargo.toml
··· 1 [workspace] 2 resolver = "3" 3 - members = ["sachy-battery","sachy-bthome", "sachy-fmt"] 4 5 [workspace.package] 6 authors = ["Sachymetsu <sachymetsu@tutamail.com>"]
··· 1 [workspace] 2 resolver = "3" 3 + members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-shtc3"] 4 5 [workspace.package] 6 authors = ["Sachymetsu <sachymetsu@tutamail.com>"]
+22
sachy-shtc3/Cargo.toml
···
··· 1 + [package] 2 + name = "sachy-shtc3" 3 + description = "A SHTC3 driver crate" 4 + version = { workspace = true } 5 + edition = { workspace = true } 6 + authors = { workspace = true } 7 + repository = { workspace = true } 8 + license = { workspace = true } 9 + rust-version = { workspace = true } 10 + 11 + [dependencies] 12 + defmt = { version = "1.0.1", optional = true } 13 + embedded-hal = { version = "1.0.0" } 14 + embedded-hal-async = { version = "1.0.0" } 15 + 16 + [dev-dependencies] 17 + embedded-hal-mock = { version = "0.11.1", features = ["eh1"], default-features = false } 18 + linux-embedded-hal = "0.4.0" 19 + 20 + [features] 21 + defmt = ["dep:defmt", "embedded-hal/defmt-03"] 22 + default = []
+39
sachy-shtc3/src/crc.rs
···
··· 1 + /// Calculate the CRC8 checksum. 2 + /// 3 + /// Implementation based on the reference implementation by Sensirion. 4 + #[inline] 5 + pub(crate) const fn crc8(data: &[u8]) -> u8 { 6 + const CRC8_POLYNOMIAL: u8 = 0x31; 7 + let mut crc: u8 = u8::MAX; 8 + let mut i = 0; 9 + 10 + while i < data.len() { 11 + crc ^= data[i]; 12 + i += 1; 13 + 14 + let mut c = 0; 15 + while c < 8 { 16 + c += 1; 17 + if (crc & 0x80) > 0 { 18 + crc = (crc << 1) ^ CRC8_POLYNOMIAL; 19 + } else { 20 + crc <<= 1; 21 + } 22 + } 23 + } 24 + 25 + crc 26 + } 27 + 28 + #[cfg(test)] 29 + mod tests { 30 + use super::*; 31 + 32 + /// Test the crc8 function against the test value provided in the 33 + /// SHTC3 datasheet (section 5.10). 34 + #[test] 35 + fn crc8_test_value() { 36 + assert_eq!(crc8(&[0x00]), 0xac); 37 + assert_eq!(crc8(&[0xbe, 0xef]), 0x92); 38 + } 39 + }
+966
sachy-shtc3/src/lib.rs
···
··· 1 + //! # Introduction 2 + //! 3 + //! This is a platform agnostic Rust driver for the Sensirion SHTC3 temperature / 4 + //! humidity sensor, based on the 5 + //! [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) traits. 6 + //! 7 + //! ## Supported Devices 8 + //! 9 + //! Tested with the following sensors: 10 + //! - [SHTC3](https://www.sensirion.com/shtc3/) 11 + //! 12 + //! ## Blocking / Non-Blocking Modes 13 + //! 14 + //! This driver provides blocking and non-blocking calls. The blocking calls delay the execution 15 + //! until the measurement is done and return the results. The non-blocking ones just start the 16 + //! measurement and allow the application code to do other stuff and get the results afterwards. 17 + //! 18 + //! ## Clock Stretching 19 + //! 20 + //! While the sensor would provide measurement commands with clock stretching to indicate when the 21 + //! measurement is done, this is not implemented and probably won't be. 22 + //! 23 + //! ## Usage 24 + //! 25 + //! ### Setup 26 + //! 27 + //! Instantiate a new driver instance using a [blocking I²C HAL 28 + //! implementation](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/i2c/index.html) 29 + //! and a [blocking `Delay` 30 + //! instance](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/delay/index.html). 31 + //! For example, using `linux-embedded-hal` and an SHTC3 sensor: 32 + //! 33 + //! ```no_run 34 + //! use linux_embedded_hal::{Delay, I2cdev}; 35 + //! use sachy_shtc3::ShtC3; 36 + //! 37 + //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 38 + //! let mut sht = ShtC3::new(dev); 39 + //! ``` 40 + //! 41 + //! ### Device Info 42 + //! 43 + //! Then, you can query information about the sensor: 44 + //! 45 + //! ```no_run 46 + //! use linux_embedded_hal::{Delay, I2cdev}; 47 + //! use sachy_shtc3::ShtC3; 48 + //! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap()); 49 + //! let device_id = sht.device_identifier().unwrap(); 50 + //! let raw_id = sht.raw_id_register().unwrap(); 51 + //! ``` 52 + //! 53 + //! ### Measurements (Blocking) 54 + //! 55 + //! For measuring your environment, you can either measure just temperature, 56 + //! just humidity, or both: 57 + //! 58 + //! ```no_run 59 + //! use linux_embedded_hal::{Delay, I2cdev}; 60 + //! use sachy_shtc3::{ShtC3, PowerMode}; 61 + //! 62 + //! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap()); 63 + //! let mut delay = Delay; 64 + //! 65 + //! let temperature = sht.measure_temperature(PowerMode::NormalMode, &mut delay).unwrap(); 66 + //! let humidity = sht.measure_humidity(PowerMode::NormalMode, &mut delay).unwrap(); 67 + //! let combined = sht.measure(PowerMode::NormalMode, &mut delay).unwrap(); 68 + //! 69 + //! println!("Temperature: {} °C", temperature.as_degrees_celsius()); 70 + //! println!("Humidity: {} %RH", humidity.as_percent()); 71 + //! println!("Combined: {} °C / {} %RH", 72 + //! combined.temperature.as_degrees_celsius(), 73 + //! combined.humidity.as_percent()); 74 + //! ``` 75 + //! 76 + //! You can also use the low power mode for less power consumption, at the cost 77 + //! of reduced repeatability and accuracy of the sensor signals. For more 78 + //! information, see the ["Low Power Measurement Mode" application note][low-power]. 79 + //! 80 + //! [low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf 81 + //! 82 + //! ```no_run 83 + //! use linux_embedded_hal::{Delay, I2cdev}; 84 + //! use sachy_shtc3::{ShtC3, PowerMode}; 85 + //! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap()); 86 + //! let mut delay = Delay; 87 + //! let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap(); 88 + //! ``` 89 + //! 90 + //! ### Measurements (Non-Blocking) 91 + //! 92 + //! If you want to avoid blocking measurements, you can use the non-blocking 93 + //! commands instead. You are, however, responsible for ensuring the correct 94 + //! timing of the calls. 95 + //! 96 + //! ```no_run 97 + //! use linux_embedded_hal::I2cdev; 98 + //! use sachy_shtc3::{ShtC3, PowerMode}; 99 + //! 100 + //! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap()); 101 + //! 102 + //! sht.start_measurement(PowerMode::NormalMode).unwrap(); 103 + //! // Wait for at least `max_measurement_duration(&sht, PowerMode::NormalMode)` µs 104 + //! let result = sht.get_measurement_result().unwrap(); 105 + //! ``` 106 + //! 107 + //! In non-blocking mode, if desired, you can also read the raw 16-bit 108 + //! measurement results from the sensor by using the following two methods 109 + //! instead: 110 + //! 111 + //! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result()) 112 + //! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result()) 113 + //! 114 + //! The raw values are of type u16. They require a conversion formula for 115 + //! conversion to a temperature / humidity value (see datasheet). 116 + //! 117 + //! Invoking any command other than 118 + //! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in 119 + //! sleep mode will result in an error. 120 + //! 121 + //! ### Soft Reset 122 + //! 123 + //! The SHTC3 provides a soft reset mechanism that forces the system into a 124 + //! well-defined state without removing the power supply. If the system is in 125 + //! its idle state (i.e. if no measurement is in progress) the soft reset 126 + //! command can be sent. This triggers the sensor to reset all internal state 127 + //! machines and reload calibration data from the memory. 128 + //! 129 + //! ```no_run 130 + //! use linux_embedded_hal::{Delay, I2cdev}; 131 + //! use sachy_shtc3::{ShtC3, PowerMode}; 132 + //! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap()); 133 + //! let mut delay = Delay; 134 + //! sht.reset(&mut delay).unwrap(); 135 + //! ``` 136 + #![deny(unsafe_code, missing_docs)] 137 + #![no_std] 138 + 139 + mod crc; 140 + mod types; 141 + 142 + use embedded_hal::{ 143 + delay::DelayNs as BlockingDelayNs, 144 + i2c::{self, I2c, SevenBitAddress}, 145 + }; 146 + 147 + use crc::crc8; 148 + use embedded_hal_async::delay::DelayNs; 149 + pub use types::*; 150 + 151 + /// Whether temperature or humidity is returned first when doing a measurement. 152 + #[derive(Debug, Copy, Clone, PartialEq, Eq)] 153 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 154 + enum MeasurementOrder { 155 + TemperatureFirst, 156 + HumidityFirst, 157 + } 158 + 159 + /// Measurement power mode: Normal mode or low power mode. 160 + /// 161 + /// The sensors provides a low power measurement mode. Using the low power mode 162 + /// significantly shortens the measurement duration and thus minimizes the 163 + /// energy consumption per measurement. The benefit of ultra-low power 164 + /// consumption comes at the cost of reduced repeatability of the sensor 165 + /// signals: while the impact on the relative humidity signal is negligible and 166 + /// does not affect accuracy, it has an effect on temperature accuracy. 167 + /// 168 + /// More details can be found in the ["Low Power Measurement Mode" application 169 + /// note][an-low-power] by Sensirion. 170 + /// 171 + /// [an-low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf 172 + #[derive(Debug, Copy, Clone, PartialEq, Eq)] 173 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 174 + pub enum PowerMode { 175 + /// Normal measurement. 176 + NormalMode, 177 + /// Low power measurement: Less energy consumption, but repeatability and 178 + /// accuracy of measurements are negatively impacted. 179 + LowPower, 180 + } 181 + 182 + /// All possible errors in this crate 183 + #[derive(Debug, PartialEq, Clone)] 184 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 185 + pub enum Error<E> { 186 + /// I²C bus error 187 + I2c(E), 188 + /// CRC checksum validation failed 189 + Crc, 190 + } 191 + 192 + impl<E> From<CrcError> for Error<E> { 193 + fn from(_value: CrcError) -> Self { 194 + Self::Crc 195 + } 196 + } 197 + 198 + #[derive(Debug, PartialEq, Clone)] 199 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 200 + struct CrcError; 201 + 202 + impl<E> From<E> for Error<E> 203 + where 204 + E: i2c::Error, 205 + { 206 + fn from(e: E) -> Self { 207 + Error::I2c(e) 208 + } 209 + } 210 + 211 + /// I²C commands sent to the sensor. 212 + #[derive(Debug, Copy, Clone)] 213 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 214 + enum Command { 215 + /// Go into sleep mode. 216 + Sleep, 217 + /// Wake up from sleep mode. 218 + WakeUp, 219 + /// Measurement commands. 220 + Measure { 221 + power_mode: PowerMode, 222 + order: MeasurementOrder, 223 + }, 224 + /// Software reset. 225 + SoftwareReset, 226 + /// Read ID register. 227 + ReadIdRegister, 228 + } 229 + 230 + impl Command { 231 + fn as_bytes(self) -> [u8; 2] { 232 + match self { 233 + Command::Sleep => [0xB0, 0x98], 234 + Command::WakeUp => [0x35, 0x17], 235 + Command::Measure { 236 + power_mode: PowerMode::NormalMode, 237 + order: MeasurementOrder::TemperatureFirst, 238 + } => [0x78, 0x66], 239 + Command::Measure { 240 + power_mode: PowerMode::NormalMode, 241 + order: MeasurementOrder::HumidityFirst, 242 + } => [0x58, 0xE0], 243 + Command::Measure { 244 + power_mode: PowerMode::LowPower, 245 + order: MeasurementOrder::TemperatureFirst, 246 + } => [0x60, 0x9C], 247 + Command::Measure { 248 + power_mode: PowerMode::LowPower, 249 + order: MeasurementOrder::HumidityFirst, 250 + } => [0x40, 0x1A], 251 + Command::ReadIdRegister => [0xEF, 0xC8], 252 + Command::SoftwareReset => [0x80, 0x5D], 253 + } 254 + } 255 + } 256 + 257 + /// Driver for the SHTC3 sensor. 258 + #[derive(Debug, Default)] 259 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 260 + pub struct ShtC3<I2C> { 261 + /// The concrete I²C device implementation. 262 + i2c: I2C, 263 + /// The I²C device address. 264 + address: u8, 265 + } 266 + 267 + impl<I2C> ShtC3<I2C> { 268 + /// Create a new instance of the driver for the SHTC3. 269 + #[inline] 270 + pub const fn new(i2c: I2C) -> Self { 271 + Self { i2c, address: 0x70 } 272 + } 273 + 274 + /// Get the device's wakeup delay duration in microseconds 275 + #[inline(always)] 276 + pub const fn wakeup_duration(&self) -> u32 { 277 + 240 278 + } 279 + 280 + /// Destroy driver instance, return I²C bus instance. 281 + pub fn destroy(self) -> I2C { 282 + self.i2c 283 + } 284 + 285 + /// Return the maximum measurement duration (depending on the mode) in 286 + /// microseconds. 287 + /// 288 + /// Maximum measurement duration (SHTC3 datasheet 3.1): 289 + /// - Normal mode: 12.1 ms 290 + /// - Low power mode: 0.8 ms 291 + #[inline(always)] 292 + pub const fn max_measurement_duration(&self, mode: PowerMode) -> u32 { 293 + match mode { 294 + PowerMode::NormalMode => 12100, 295 + PowerMode::LowPower => 800, 296 + } 297 + } 298 + 299 + /// Returns the reset duration for the SHTC3 in microseconds 300 + #[inline(always)] 301 + pub const fn reset_duration(&self) -> u32 { 302 + 240_000 303 + } 304 + 305 + /// Iterate over the provided buffer and validate the CRC8 checksum. 306 + /// 307 + /// If the checksum is wrong, return `CrcError`. 308 + /// 309 + /// Note: This method will consider every third byte a checksum byte. If 310 + /// the buffer size is not a multiple of 3, then not all data will be 311 + /// validated. 312 + fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> { 313 + let mut chunks = buf.chunks_exact(3); 314 + 315 + for chunk in chunks.by_ref() { 316 + if crc8(&chunk[..2]) != chunk[2] { 317 + return Err(CrcError); 318 + } 319 + } 320 + 321 + #[cfg(feature = "defmt")] 322 + if !chunks.remainder().is_empty() { 323 + defmt::warn!("Remaining data in buffer was not CRC8 validated"); 324 + } 325 + 326 + Ok(()) 327 + } 328 + } 329 + 330 + impl<I2C> ShtC3<I2C> 331 + where 332 + I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>, 333 + { 334 + /// Write an I²C command to the sensor. 335 + async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> { 336 + self.i2c 337 + .write(self.address, &command.as_bytes()) 338 + .await 339 + .map_err(Error::I2c) 340 + } 341 + 342 + /// Read data into the provided buffer and validate the CRC8 checksum. 343 + /// 344 + /// If the checksum is wrong, return `Error::Crc`. 345 + /// 346 + /// Note: This method will consider every third byte a checksum byte. If 347 + /// the buffer size is not a multiple of 3, then not all data will be 348 + /// validated. 349 + async fn read_with_crc_async(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> { 350 + self.i2c.read(self.address, buf).await?; 351 + self.validate_crc(buf)?; 352 + Ok(()) 353 + } 354 + 355 + /// Return the raw ID register. 356 + pub async fn raw_id_register_async(&mut self) -> Result<u16, Error<I2C::Error>> { 357 + // Request serial number 358 + self.send_command_async(Command::ReadIdRegister).await?; 359 + 360 + // Read id register 361 + let mut buf = [0; 3]; 362 + self.read_with_crc_async(&mut buf).await?; 363 + 364 + Ok(u16::from_be_bytes([buf[0], buf[1]])) 365 + } 366 + 367 + /// Return the 7-bit device identifier. 368 + /// 369 + /// Should be 0x47 (71) for the SHTC3. 370 + pub async fn device_identifier_async(&mut self) -> Result<u8, Error<I2C::Error>> { 371 + let ident = self.raw_id_register_async().await?; 372 + let lsb = (ident & 0b0011_1111) as u8; 373 + let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8; 374 + Ok(lsb | msb) 375 + } 376 + 377 + /// Set sensor to sleep mode. 378 + /// 379 + /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires 380 + /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C 381 + /// communication. 382 + pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> { 383 + self.send_command_async(Command::Sleep).await 384 + } 385 + 386 + /// Trigger a soft reset. (async) 387 + /// 388 + /// The SHTC3 provides a soft reset mechanism that forces the system into a 389 + /// well-defined state without removing the power supply. If the system is 390 + /// in its idle state (i.e. if no measurement is in progress) the soft 391 + /// reset command can be sent. This triggers the sensor to reset all 392 + /// internal state machines and reload calibration data from the memory. 393 + pub async fn reset_async(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> { 394 + self.send_command_async(Command::SoftwareReset).await?; 395 + // Table 5: 180-240 µs 396 + delay.delay_us(self.reset_duration()).await; 397 + Ok(()) 398 + } 399 + 400 + /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async) 401 + pub async fn wakeup_async( 402 + &mut self, 403 + delay: &mut impl DelayNs, 404 + ) -> Result<(), Error<I2C::Error>> { 405 + self.send_command_async(Command::WakeUp).await?; 406 + delay.delay_us(self.wakeup_duration()).await; 407 + Ok(()) 408 + } 409 + 410 + /// Run a temperature/humidity measurement and return the combined result. 411 + /// 412 + /// This is an async function call. 413 + pub async fn measure_async( 414 + &mut self, 415 + mode: PowerMode, 416 + delay: &mut impl DelayNs, 417 + ) -> Result<Measurement, Error<I2C::Error>> { 418 + self.send_command_async(Command::Measure { 419 + power_mode: mode, 420 + order: MeasurementOrder::TemperatureFirst, 421 + }) 422 + .await?; 423 + 424 + delay.delay_us(self.max_measurement_duration(mode)).await; 425 + 426 + let mut buf = [0; 6]; 427 + self.read_with_crc_async(&mut buf).await?; 428 + 429 + Ok(RawMeasurement { 430 + temperature: u16::from_be_bytes([buf[0], buf[1]]), 431 + humidity: u16::from_be_bytes([buf[3], buf[4]]), 432 + } 433 + .into()) 434 + } 435 + } 436 + 437 + /// General blocking functions. 438 + impl<I2C> ShtC3<I2C> 439 + where 440 + I2C: I2c<SevenBitAddress>, 441 + { 442 + /// Write an I²C command to the sensor. 443 + fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> { 444 + self.i2c 445 + .write(self.address, &command.as_bytes()) 446 + .map_err(Error::I2c) 447 + } 448 + 449 + /// Read data into the provided buffer and validate the CRC8 checksum. 450 + /// 451 + /// If the checksum is wrong, return `Error::Crc`. 452 + /// 453 + /// Note: This method will consider every third byte a checksum byte. If 454 + /// the buffer size is not a multiple of 3, then not all data will be 455 + /// validated. 456 + fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> { 457 + self.i2c.read(self.address, buf)?; 458 + self.validate_crc(buf)?; 459 + Ok(()) 460 + } 461 + 462 + /// Return the raw ID register. 463 + pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> { 464 + // Request serial number 465 + self.send_command(Command::ReadIdRegister)?; 466 + 467 + // Read id register 468 + let mut buf = [0; 3]; 469 + self.read_with_crc(&mut buf)?; 470 + 471 + Ok(u16::from_be_bytes([buf[0], buf[1]])) 472 + } 473 + 474 + /// Return the 7-bit device identifier. 475 + /// 476 + /// Should be 0x47 (71) for the SHTC3. 477 + pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> { 478 + let ident = self.raw_id_register()?; 479 + let lsb = (ident & 0b0011_1111) as u8; 480 + let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8; 481 + Ok(lsb | msb) 482 + } 483 + 484 + /// Trigger a soft reset. (blocking) 485 + /// 486 + /// The SHTC3 provides a soft reset mechanism that forces the system into a 487 + /// well-defined state without removing the power supply. If the system is 488 + /// in its idle state (i.e. if no measurement is in progress) the soft 489 + /// reset command can be sent. This triggers the sensor to reset all 490 + /// internal state machines and reload calibration data from the memory. 491 + pub fn reset(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> { 492 + self.send_command(Command::SoftwareReset)?; 493 + // Table 5: 180-240 µs 494 + delay.delay_us(self.reset_duration()); 495 + Ok(()) 496 + } 497 + 498 + /// Trigger a soft reset. 499 + /// 500 + /// The SHTC3 provides a soft reset mechanism that forces the system into a 501 + /// well-defined state without removing the power supply. If the system is 502 + /// in its idle state (i.e. if no measurement is in progress) the soft 503 + /// reset command can be sent. This triggers the sensor to reset all 504 + /// internal state machines and reload calibration data from the memory. 505 + pub fn start_reset(&mut self) -> Result<(), Error<I2C::Error>> { 506 + self.send_command(Command::SoftwareReset) 507 + } 508 + 509 + /// Set sensor to sleep mode. 510 + /// 511 + /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires 512 + /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C 513 + /// communication. 514 + pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> { 515 + self.send_command(Command::Sleep) 516 + } 517 + 518 + /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. 519 + pub fn wakeup(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> { 520 + self.start_wakeup()?; 521 + delay.delay_us(self.wakeup_duration()); 522 + Ok(()) 523 + } 524 + } 525 + 526 + /// Non-blocking functions for starting / reading measurements. 527 + impl<I2C> ShtC3<I2C> 528 + where 529 + I2C: I2c<SevenBitAddress>, 530 + { 531 + /// Start a measurement with the specified measurement order and write the 532 + /// result into the provided buffer. 533 + /// 534 + /// If you just need one of the two measurements, provide a 3-byte buffer 535 + /// instead of a 6-byte buffer. 536 + fn start_measure_partial( 537 + &mut self, 538 + power_mode: PowerMode, 539 + order: MeasurementOrder, 540 + ) -> Result<(), Error<I2C::Error>> { 541 + // Request measurement 542 + self.send_command(Command::Measure { power_mode, order }) 543 + } 544 + 545 + /// Start a combined temperature / humidity measurement. 546 + pub fn start_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> { 547 + self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst) 548 + } 549 + 550 + /// Start a temperature measurement. 551 + pub fn start_temperature_measurement( 552 + &mut self, 553 + mode: PowerMode, 554 + ) -> Result<(), Error<I2C::Error>> { 555 + self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst) 556 + } 557 + 558 + /// Start a humidity measurement. 559 + pub fn start_humidity_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> { 560 + self.start_measure_partial(mode, MeasurementOrder::HumidityFirst) 561 + } 562 + 563 + /// Read the result of a temperature / humidity measurement. 564 + pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> { 565 + let raw = self.get_raw_measurement_result()?; 566 + Ok(raw.into()) 567 + } 568 + 569 + /// Read the result of a temperature measurement. 570 + pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> { 571 + let raw = self.get_raw_partial_measurement_result()?; 572 + Ok(Temperature::from_raw(raw)) 573 + } 574 + 575 + /// Read the result of a humidity measurement. 576 + pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> { 577 + let raw = self.get_raw_partial_measurement_result()?; 578 + Ok(Humidity::from_raw(raw)) 579 + } 580 + 581 + /// Read the raw result of a combined temperature / humidity measurement. 582 + pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> { 583 + let mut buf = [0; 6]; 584 + self.read_with_crc(&mut buf)?; 585 + Ok(RawMeasurement { 586 + temperature: u16::from_be_bytes([buf[0], buf[1]]), 587 + humidity: u16::from_be_bytes([buf[3], buf[4]]), 588 + }) 589 + } 590 + 591 + /// Read the raw result of a partial temperature or humidity measurement. 592 + /// 593 + /// Return the raw 3-byte buffer (after validating CRC). 594 + pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> { 595 + let mut buf = [0; 3]; 596 + self.read_with_crc(&mut buf)?; 597 + Ok(u16::from_be_bytes([buf[0], buf[1]])) 598 + } 599 + 600 + /// Wake up sensor from [sleep mode](#method.sleep). 601 + pub fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> { 602 + self.send_command(Command::WakeUp) 603 + } 604 + } 605 + 606 + /// Blocking functions for doing measurements. 607 + impl<I2C> ShtC3<I2C> 608 + where 609 + I2C: I2c<SevenBitAddress>, 610 + { 611 + /// Wait the maximum time needed for the given measurement mode 612 + pub fn wait_for_measurement(&mut self, mode: PowerMode, delay: &mut impl BlockingDelayNs) { 613 + delay.delay_us(self.max_measurement_duration(mode)); 614 + } 615 + 616 + /// Run a temperature/humidity measurement and return the combined result. 617 + /// 618 + /// This is a blocking function call. 619 + pub fn measure( 620 + &mut self, 621 + mode: PowerMode, 622 + delay: &mut impl BlockingDelayNs, 623 + ) -> Result<Measurement, Error<I2C::Error>> { 624 + self.start_measurement(mode)?; 625 + self.wait_for_measurement(mode, delay); 626 + self.get_measurement_result() 627 + } 628 + 629 + /// Run a temperature measurement and return the result. 630 + /// 631 + /// This is a blocking function call. 632 + /// 633 + /// Internally, it will request a measurement in "temperature first" mode 634 + /// and only read the first half of the measurement response. 635 + pub fn measure_temperature( 636 + &mut self, 637 + mode: PowerMode, 638 + delay: &mut impl BlockingDelayNs, 639 + ) -> Result<Temperature, Error<I2C::Error>> { 640 + self.start_temperature_measurement(mode)?; 641 + self.wait_for_measurement(mode, delay); 642 + self.get_temperature_measurement_result() 643 + } 644 + 645 + /// Run a humidity measurement and return the result. 646 + /// 647 + /// This is a blocking function call. 648 + /// 649 + /// Internally, it will request a measurement in "humidity first" mode 650 + /// and only read the first half of the measurement response. 651 + pub fn measure_humidity( 652 + &mut self, 653 + mode: PowerMode, 654 + delay: &mut impl BlockingDelayNs, 655 + ) -> Result<Humidity, Error<I2C::Error>> { 656 + self.start_humidity_measurement(mode)?; 657 + self.wait_for_measurement(mode, delay); 658 + self.get_humidity_measurement_result() 659 + } 660 + } 661 + 662 + #[cfg(test)] 663 + mod tests { 664 + extern crate alloc; 665 + 666 + use super::*; 667 + 668 + use embedded_hal::i2c::ErrorKind; 669 + use embedded_hal_mock::eh1::{ 670 + delay::NoopDelay, 671 + i2c::{Mock as I2cMock, Transaction}, 672 + }; 673 + 674 + const SHT_ADDR: u8 = 0x70; 675 + 676 + mod core { 677 + use super::*; 678 + 679 + /// Test whether the `send_command` function propagates I²C errors. 680 + #[test] 681 + fn send_command_error() { 682 + let expectations = 683 + [Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]) 684 + .with_error(ErrorKind::Other)]; 685 + let mock = I2cMock::new(&expectations); 686 + let mut sht = ShtC3::new(mock); 687 + let err = sht.send_command(Command::ReadIdRegister).unwrap_err(); 688 + assert_eq!(err, Error::I2c(ErrorKind::Other)); 689 + sht.destroy().done(); 690 + } 691 + 692 + /// Test the `validate_crc` function. 693 + #[test] 694 + fn validate_crc() { 695 + let mock = I2cMock::new(&[]); 696 + let sht = ShtC3::new(mock); 697 + 698 + // Not enough data 699 + sht.validate_crc(&[]).unwrap(); 700 + sht.validate_crc(&[0xbe]).unwrap(); 701 + sht.validate_crc(&[0xbe, 0xef]).unwrap(); 702 + 703 + // Valid CRC 704 + sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap(); 705 + 706 + // Invalid CRC 707 + match sht.validate_crc(&[0xbe, 0xef, 0x91]) { 708 + Err(CrcError) => {} 709 + Ok(_) => panic!("CRC check did not fail"), 710 + } 711 + 712 + // Valid CRC (8 bytes) 713 + sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00]) 714 + .unwrap(); 715 + 716 + // Invalid CRC (8 bytes) 717 + match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) { 718 + Err(CrcError) => {} 719 + Ok(_) => panic!("CRC check did not fail"), 720 + } 721 + 722 + sht.destroy().done(); 723 + } 724 + 725 + /// Test the `read_with_crc` function. 726 + #[test] 727 + fn read_with_crc() { 728 + let mut buf = [0; 3]; 729 + 730 + // Valid CRC 731 + let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x92])]; 732 + let mock = I2cMock::new(&expectations); 733 + let mut sht = ShtC3::new(mock); 734 + sht.read_with_crc(&mut buf).unwrap(); 735 + assert_eq!(buf, [0xbe, 0xef, 0x92]); 736 + sht.destroy().done(); 737 + 738 + // Invalid CRC 739 + let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x00])]; 740 + let mock = I2cMock::new(&expectations); 741 + let mut sht = ShtC3::new(mock); 742 + match sht.read_with_crc(&mut buf) { 743 + Err(Error::Crc) => {} 744 + Err(_) => panic!("Invalid error: Must be Crc"), 745 + Ok(_) => panic!("CRC check did not fail"), 746 + } 747 + assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed 748 + sht.destroy().done(); 749 + } 750 + } 751 + 752 + mod factory_functions { 753 + use super::*; 754 + 755 + #[test] 756 + fn new_shtc3() { 757 + let mock = I2cMock::new(&[]); 758 + let sht = ShtC3::new(mock); 759 + assert_eq!(sht.address, 0x70); 760 + sht.destroy().done(); 761 + } 762 + } 763 + 764 + mod device_info { 765 + use super::*; 766 + 767 + /// Test the `raw_id_register` function. 768 + #[test] 769 + fn raw_id_register() { 770 + let msb = 0b00001000; 771 + let lsb = 0b00000111; 772 + let crc = crc8(&[msb, lsb]); 773 + let expectations = [ 774 + Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]), 775 + Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]), 776 + ]; 777 + let mock = I2cMock::new(&expectations); 778 + let mut sht = ShtC3::new(mock); 779 + let val = sht.raw_id_register().unwrap(); 780 + assert_eq!(val, (msb as u16) << 8 | (lsb as u16)); 781 + sht.destroy().done(); 782 + } 783 + 784 + /// Test the `device_identifier` function. 785 + #[test] 786 + fn device_identifier() { 787 + let msb = 0b00001000; 788 + let lsb = 0b00000111; 789 + let crc = crc8(&[msb, lsb]); 790 + let expectations = [ 791 + Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]), 792 + Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]), 793 + ]; 794 + let mock = I2cMock::new(&expectations); 795 + let mut sht = ShtC3::new(mock); 796 + let ident = sht.device_identifier().unwrap(); 797 + assert_eq!(ident, 0b01000111); 798 + sht.destroy().done(); 799 + } 800 + } 801 + 802 + mod measurements { 803 + use super::*; 804 + 805 + #[test] 806 + fn measure_normal() { 807 + let expectations = [ 808 + // Expect a write command: Normal mode measurement, temperature 809 + // first, no clock stretching. 810 + Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]), 811 + // Return the measurement result (using example values from the 812 + // datasheet, section 5.4 "Measuring and Reading the Signals") 813 + Transaction::read( 814 + SHT_ADDR, 815 + alloc::vec![ 816 + 0b0110_0100, 817 + 0b1000_1011, 818 + 0b1100_0111, 819 + 0b1010_0001, 820 + 0b0011_0011, 821 + 0b0001_1100, 822 + ], 823 + ), 824 + ]; 825 + let mock = I2cMock::new(&expectations); 826 + let mut sht = ShtC3::new(mock); 827 + let mut delay = NoopDelay; 828 + let measurement = sht.measure(PowerMode::NormalMode, &mut delay).unwrap(); 829 + assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C 830 + assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH 831 + sht.destroy().done(); 832 + } 833 + 834 + #[test] 835 + fn measure_low_power() { 836 + let expectations = [ 837 + // Expect a write command: Low power mode measurement, temperature 838 + // first, no clock stretching. 839 + Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]), 840 + // Return the measurement result (using example values from the 841 + // datasheet, section 5.4 "Measuring and Reading the Signals") 842 + Transaction::read( 843 + SHT_ADDR, 844 + alloc::vec![ 845 + 0b0110_0100, 846 + 0b1000_1011, 847 + 0b1100_0111, 848 + 0b1010_0001, 849 + 0b0011_0011, 850 + 0b0001_1100, 851 + ], 852 + ), 853 + ]; 854 + let mock = I2cMock::new(&expectations); 855 + let mut sht = ShtC3::new(mock); 856 + let mut delay = NoopDelay; 857 + let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap(); 858 + assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C 859 + assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH 860 + sht.destroy().done(); 861 + } 862 + 863 + #[test] 864 + fn measure_temperature_only() { 865 + let expectations = [ 866 + // Expect a write command: Normal mode measurement, temperature 867 + // first, no clock stretching. 868 + Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]), 869 + // Return the measurement result (using example values from the 870 + // datasheet, section 5.4 "Measuring and Reading the Signals") 871 + Transaction::read(SHT_ADDR, alloc::vec![0b0110_0100, 0b1000_1011, 0b1100_0111]), 872 + ]; 873 + let mock = I2cMock::new(&expectations); 874 + let mut sht = ShtC3::new(mock); 875 + let mut delay = NoopDelay; 876 + let temperature = sht 877 + .measure_temperature(PowerMode::NormalMode, &mut delay) 878 + .unwrap(); 879 + assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C 880 + sht.destroy().done(); 881 + } 882 + 883 + #[test] 884 + fn measure_humidity_only() { 885 + let expectations = [ 886 + // Expect a write command: Normal mode measurement, humidity 887 + // first, no clock stretching. 888 + Transaction::write(SHT_ADDR, alloc::vec![0x58, 0xE0]), 889 + // Return the measurement result (using example values from the 890 + // datasheet, section 5.4 "Measuring and Reading the Signals") 891 + Transaction::read(SHT_ADDR, alloc::vec![0b1010_0001, 0b0011_0011, 0b0001_1100]), 892 + ]; 893 + let mock = I2cMock::new(&expectations); 894 + let mut sht = ShtC3::new(mock); 895 + let mut delay = NoopDelay; 896 + let humidity = sht 897 + .measure_humidity(PowerMode::NormalMode, &mut delay) 898 + .unwrap(); 899 + assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH 900 + sht.destroy().done(); 901 + } 902 + 903 + /// Ensure that I²C write errors are handled when measuring. 904 + #[test] 905 + fn measure_write_error() { 906 + let expectations = 907 + [Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]) 908 + .with_error(ErrorKind::Other)]; 909 + let mock = I2cMock::new(&expectations); 910 + let mut sht = ShtC3::new(mock); 911 + let err = sht 912 + .measure(PowerMode::LowPower, &mut NoopDelay) 913 + .unwrap_err(); 914 + assert_eq!(err, Error::I2c(ErrorKind::Other)); 915 + sht.destroy().done(); 916 + } 917 + } 918 + 919 + mod power_management { 920 + use super::*; 921 + 922 + /// Test the `sleep` function. 923 + #[test] 924 + fn sleep() { 925 + let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0xB0, 0x98])]; 926 + let mock = I2cMock::new(&expectations); 927 + let mut sht = ShtC3::new(mock); 928 + sht.sleep().unwrap(); 929 + sht.destroy().done(); 930 + } 931 + 932 + /// Test the `wakeup` function. 933 + #[test] 934 + fn wakeup() { 935 + let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x35, 0x17])]; 936 + let mock = I2cMock::new(&expectations); 937 + let mut sht = ShtC3::new(mock); 938 + sht.wakeup(&mut NoopDelay).unwrap(); 939 + sht.destroy().done(); 940 + } 941 + 942 + /// Test the `reset` function. 943 + #[test] 944 + fn reset() { 945 + let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x80, 0x5D])]; 946 + let mock = I2cMock::new(&expectations); 947 + let mut sht = ShtC3::new(mock); 948 + sht.reset(&mut NoopDelay).unwrap(); 949 + sht.destroy().done(); 950 + } 951 + } 952 + 953 + mod max_measurement_duration { 954 + use super::*; 955 + 956 + #[test] 957 + fn shortcut_function() { 958 + let c3 = ShtC3::new(I2cMock::new(&[])); 959 + 960 + assert_eq!(c3.max_measurement_duration(PowerMode::NormalMode), 12100); 961 + assert_eq!(c3.max_measurement_duration(PowerMode::LowPower), 800); 962 + 963 + c3.destroy().done(); 964 + } 965 + } 966 + }
+199
sachy-shtc3/src/types.rs
···
··· 1 + /// A temperature measurement. 2 + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 3 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 4 + pub struct Temperature(i32); 5 + 6 + /// A humidity measurement. 7 + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 8 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 9 + pub struct Humidity(i32); 10 + 11 + /// A combined temperature / humidity measurement. 12 + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 13 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 14 + pub struct Measurement { 15 + /// The measured temperature. 16 + pub temperature: Temperature, 17 + /// The measured humidity. 18 + pub humidity: Humidity, 19 + } 20 + 21 + impl core::ops::AddAssign for Measurement { 22 + fn add_assign(&mut self, rhs: Self) { 23 + self.temperature.0 += rhs.temperature.0; 24 + self.humidity.0 += rhs.humidity.0; 25 + } 26 + } 27 + 28 + impl core::ops::DivAssign<i32> for Measurement { 29 + fn div_assign(&mut self, rhs: i32) { 30 + self.temperature.0 /= rhs; 31 + self.humidity.0 /= rhs; 32 + } 33 + } 34 + 35 + /// A combined raw temperature / humidity measurement. 36 + /// 37 + /// The raw values are of type u16. They require a conversion formula for 38 + /// conversion to a temperature / humidity value (see datasheet). 39 + #[derive(Debug, Copy, Clone, PartialEq, Eq)] 40 + #[cfg_attr(feature = "defmt", derive(defmt::Format))] 41 + pub struct RawMeasurement { 42 + /// The measured temperature (raw value). 43 + pub temperature: u16, 44 + /// The measured humidity (raw value). 45 + pub humidity: u16, 46 + } 47 + 48 + impl From<RawMeasurement> for Measurement { 49 + fn from(other: RawMeasurement) -> Self { 50 + Self { 51 + temperature: Temperature::from_raw(other.temperature), 52 + humidity: Humidity::from_raw(other.humidity), 53 + } 54 + } 55 + } 56 + 57 + impl Temperature { 58 + /// Create a new `Temperature` from a raw measurement result. 59 + pub const fn from_raw(raw: u16) -> Self { 60 + Self(convert_temperature(raw)) 61 + } 62 + 63 + /// Return temperature in milli-degrees celsius. 64 + pub const fn as_millidegrees_celsius(&self) -> i32 { 65 + self.0 66 + } 67 + 68 + /// Return temperature in degrees celcius with 0.01 precision 69 + pub const fn as_10mk_celsius(&self) -> i16 { 70 + (self.0 / 10) as i16 71 + } 72 + 73 + /// Return temperature in degrees celsius. 74 + pub const fn as_degrees_celsius(&self) -> f32 { 75 + self.0 as f32 / 1000.0 76 + } 77 + } 78 + 79 + impl Humidity { 80 + /// Create a new `Humidity` from a raw measurement result. 81 + pub const fn from_raw(raw: u16) -> Self { 82 + Self(convert_humidity(raw)) 83 + } 84 + 85 + /// Return relative humidity in 1/100 %RH 86 + pub const fn as_10mk_percent(&self) -> u16 { 87 + (self.0 / 10).unsigned_abs() as u16 88 + } 89 + 90 + /// Return relative humidity in 1/1000 %RH. 91 + pub const fn as_millipercent(&self) -> i32 { 92 + self.0 93 + } 94 + 95 + /// Return relative humidity in 1 %RH 96 + pub const fn as_1k_percent(&self) -> u8 { 97 + (self.0 / 1000).unsigned_abs() as u8 98 + } 99 + 100 + /// Return relative humidity in %RH. 101 + pub const fn as_percent(&self) -> f32 { 102 + self.0 as f32 / 1000.0 103 + } 104 + } 105 + 106 + /// Convert raw temperature measurement to milli-degrees celsius. 107 + /// 108 + /// Formula (datasheet 5.11): -45 + 175 * (val / 2^16), 109 + /// optimized for fixed point math. 110 + #[inline] 111 + const fn convert_temperature(temp_raw: u16) -> i32 { 112 + (((temp_raw as u32) * 21875) >> 13) as i32 - 45000 113 + } 114 + 115 + /// Convert raw humidity measurement to relative humidity. 116 + /// 117 + /// Formula (datasheet 5.11): 100 * (val / 2^16), 118 + /// optimized for fixed point math. 119 + #[inline] 120 + const fn convert_humidity(humi_raw: u16) -> i32 { 121 + (((humi_raw as u32) * 12500) >> 13) as i32 122 + } 123 + 124 + #[cfg(test)] 125 + mod tests { 126 + use super::*; 127 + 128 + /// Test conversion of raw measurement results into °C. 129 + #[test] 130 + fn test_convert_temperature() { 131 + let test_data = [ 132 + (0x0000, -45000), 133 + // Datasheet setion 5.11 "Conversion of Sensor Output" 134 + ((0b0110_0100_u16 << 8) | 0b1000_1011, 23730), 135 + ]; 136 + for td in &test_data { 137 + assert_eq!(convert_temperature(td.0), td.1); 138 + } 139 + } 140 + 141 + /// Test conversion of raw measurement results into %RH. 142 + #[test] 143 + fn test_convert_humidity() { 144 + let test_data = [ 145 + (0x0000, 0), 146 + // Datasheet setion 5.11 "Conversion of Sensor Output" 147 + ((0b1010_0001_u16 << 8) | 0b0011_0011, 62968), 148 + ]; 149 + for td in &test_data { 150 + assert_eq!(convert_humidity(td.0), td.1); 151 + } 152 + } 153 + 154 + /// Test conversion of raw measurement results into °C and %RH. 155 + #[test] 156 + fn measurement_conversion() { 157 + // Datasheet setion 5.11 "Conversion of Sensor Output" 158 + let temperature = convert_temperature((0b0110_0100_u16 << 8) | 0b1000_1011); 159 + let humidity = convert_humidity((0b1010_0001_u16 << 8) | 0b0011_0011); 160 + assert_eq!(temperature, 23730); 161 + assert_eq!(humidity, 62968); 162 + } 163 + 164 + #[test] 165 + fn temperature() { 166 + let temp = Temperature(24123); 167 + assert_eq!(temp.as_millidegrees_celsius(), 24123); 168 + assert_eq!(temp.as_degrees_celsius(), 24.123); 169 + } 170 + 171 + #[test] 172 + fn humidity() { 173 + let humi = Humidity(65432); 174 + assert_eq!(humi.as_millipercent(), 65432); 175 + assert_eq!(humi.as_percent(), 65.432); 176 + } 177 + 178 + #[test] 179 + fn measurement_from_into() { 180 + // Datasheet setion 5.11 "Conversion of Sensor Output" 181 + let raw = RawMeasurement { 182 + temperature: (0b0110_0100_u16 << 8) | 0b1000_1011, 183 + humidity: (0b1010_0001_u16 << 8) | 0b0011_0011, 184 + }; 185 + 186 + // std::convert::From 187 + let measurement1 = Measurement::from(raw); 188 + assert_eq!(measurement1.temperature.0, 23730); 189 + assert_eq!(measurement1.humidity.0, 62968); 190 + 191 + // std::convert::Into 192 + let measurement2: Measurement = raw.into(); 193 + assert_eq!(measurement2.temperature.0, 23730); 194 + assert_eq!(measurement2.humidity.0, 62968); 195 + 196 + // std::cmp::PartialEq 197 + assert_eq!(measurement1, measurement2); 198 + } 199 + }