├── .envrc ├── .github └── workflows │ └── check-flake.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── TODO.md ├── contrib └── callendar-van-dusen-approximator │ ├── .envrc │ ├── flake.lock │ ├── flake.nix │ └── main.py ├── embedded-devices-derive ├── Cargo.toml └── src │ ├── device.rs │ ├── device_impl.rs │ ├── device_register.rs │ └── lib.rs ├── embedded-devices ├── Cargo.toml ├── README.md └── src │ ├── devices │ ├── analog_devices │ │ ├── max31865 │ │ │ ├── mod.rs │ │ │ └── registers.rs │ │ └── mod.rs │ ├── bosch │ │ ├── bme280 │ │ │ ├── address.rs │ │ │ ├── mod.rs │ │ │ └── registers.rs │ │ ├── bmp280.rs │ │ ├── bmp390 │ │ │ ├── address.rs │ │ │ ├── mod.rs │ │ │ └── registers.rs │ │ └── mod.rs │ ├── microchip │ │ ├── mcp3204.rs │ │ ├── mcp3208.rs │ │ ├── mcp9808 │ │ │ ├── address.rs │ │ │ ├── mod.rs │ │ │ └── registers.rs │ │ └── mod.rs │ ├── mod.rs │ ├── sensirion │ │ ├── mod.rs │ │ └── scd4x │ │ │ ├── address.rs │ │ │ ├── mod.rs │ │ │ └── registers.rs │ └── texas_instruments │ │ ├── ina219 │ │ ├── address.rs │ │ ├── mod.rs │ │ └── registers.rs │ │ ├── ina228 │ │ ├── address.rs │ │ ├── mod.rs │ │ └── registers.rs │ │ ├── mod.rs │ │ ├── tmp102 │ │ ├── address.rs │ │ ├── mod.rs │ │ └── registers.rs │ │ └── tmp117 │ │ ├── address.rs │ │ ├── mod.rs │ │ └── registers.rs │ ├── lib.rs │ └── utils │ ├── callendar_van_dusen.rs │ └── mod.rs ├── embedded-registers-derive ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── basic.rs ├── embedded-registers ├── Cargo.toml └── src │ ├── i2c │ ├── codecs │ │ ├── crc_codec.rs │ │ ├── mod.rs │ │ ├── no_codec.rs │ │ └── simple_codec.rs │ └── mod.rs │ ├── lib.rs │ └── spi │ ├── codecs │ ├── mod.rs │ ├── no_codec.rs │ └── simple_codec.rs │ └── mod.rs ├── flake.lock ├── flake.nix ├── release.toml ├── rust-toolchain.toml └── rustfmt.toml /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/check-flake.yml: -------------------------------------------------------------------------------- 1 | name: Check Nix Flake 2 | on: 3 | push: 4 | pull_request: 5 | permissions: 6 | contents: read 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | jobs: 11 | flake-check: 12 | name: Check Nix Flake 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Install Nix 18 | uses: DeterminateSystems/nix-installer-action@main 19 | - name: Setup Nix Cache 20 | uses: DeterminateSystems/magic-nix-cache-action@main 21 | - name: Check Nix Flake 22 | shell: bash 23 | run: nix flake check 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | result* 3 | .pre-commit-config.yaml 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "approx" 7 | version = "0.5.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 10 | dependencies = [ 11 | "num-traits", 12 | ] 13 | 14 | [[package]] 15 | name = "arrayvec" 16 | version = "0.7.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.4.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 25 | 26 | [[package]] 27 | name = "bitflags" 28 | version = "1.3.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "2.9.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 37 | 38 | [[package]] 39 | name = "bondrewd" 40 | version = "0.1.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "6d1660fac8d3acced44dac64453fafedf5aab2de196b932c727e63e4ae42d1cc" 43 | dependencies = [ 44 | "bondrewd-derive", 45 | ] 46 | 47 | [[package]] 48 | name = "bondrewd-derive" 49 | version = "0.3.18" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "723da0dee1eef38edc021b0793f892bdc024500c6a5b0727a2efe16f0e0a6977" 52 | dependencies = [ 53 | "proc-macro2", 54 | "quote", 55 | "syn 1.0.109", 56 | ] 57 | 58 | [[package]] 59 | name = "bytemuck" 60 | version = "1.22.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" 63 | dependencies = [ 64 | "bytemuck_derive", 65 | ] 66 | 67 | [[package]] 68 | name = "bytemuck_derive" 69 | version = "1.9.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" 72 | dependencies = [ 73 | "proc-macro2", 74 | "quote", 75 | "syn 2.0.100", 76 | ] 77 | 78 | [[package]] 79 | name = "convert_case" 80 | version = "0.8.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" 83 | dependencies = [ 84 | "unicode-segmentation", 85 | ] 86 | 87 | [[package]] 88 | name = "crc" 89 | version = "3.2.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 92 | dependencies = [ 93 | "crc-catalog", 94 | ] 95 | 96 | [[package]] 97 | name = "crc-catalog" 98 | version = "2.4.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 101 | 102 | [[package]] 103 | name = "darling" 104 | version = "0.20.11" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 107 | dependencies = [ 108 | "darling_core", 109 | "darling_macro", 110 | ] 111 | 112 | [[package]] 113 | name = "darling_core" 114 | version = "0.20.11" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 117 | dependencies = [ 118 | "fnv", 119 | "ident_case", 120 | "proc-macro2", 121 | "quote", 122 | "strsim", 123 | "syn 2.0.100", 124 | ] 125 | 126 | [[package]] 127 | name = "darling_macro" 128 | version = "0.20.11" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 131 | dependencies = [ 132 | "darling_core", 133 | "quote", 134 | "syn 2.0.100", 135 | ] 136 | 137 | [[package]] 138 | name = "defmt" 139 | version = "1.0.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" 142 | dependencies = [ 143 | "bitflags 1.3.2", 144 | "defmt-macros", 145 | ] 146 | 147 | [[package]] 148 | name = "defmt-macros" 149 | version = "1.0.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" 152 | dependencies = [ 153 | "defmt-parser", 154 | "proc-macro-error2", 155 | "proc-macro2", 156 | "quote", 157 | "syn 2.0.100", 158 | ] 159 | 160 | [[package]] 161 | name = "defmt-parser" 162 | version = "1.0.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" 165 | dependencies = [ 166 | "thiserror", 167 | ] 168 | 169 | [[package]] 170 | name = "embedded-devices" 171 | version = "0.9.13" 172 | dependencies = [ 173 | "approx", 174 | "bondrewd", 175 | "bytemuck", 176 | "crc", 177 | "defmt", 178 | "embedded-devices-derive", 179 | "embedded-hal", 180 | "embedded-hal-async", 181 | "embedded-registers", 182 | "maybe-async-cfg", 183 | "paste", 184 | "uom", 185 | ] 186 | 187 | [[package]] 188 | name = "embedded-devices-derive" 189 | version = "0.9.13" 190 | dependencies = [ 191 | "convert_case", 192 | "darling", 193 | "proc-macro2", 194 | "quote", 195 | "syn 2.0.100", 196 | ] 197 | 198 | [[package]] 199 | name = "embedded-hal" 200 | version = "1.0.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" 203 | 204 | [[package]] 205 | name = "embedded-hal-async" 206 | version = "1.0.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 209 | dependencies = [ 210 | "embedded-hal", 211 | ] 212 | 213 | [[package]] 214 | name = "embedded-registers" 215 | version = "0.9.13" 216 | dependencies = [ 217 | "arrayvec", 218 | "bondrewd", 219 | "bytemuck", 220 | "crc", 221 | "defmt", 222 | "embedded-hal", 223 | "embedded-hal-async", 224 | "embedded-registers-derive", 225 | "maybe-async-cfg", 226 | ] 227 | 228 | [[package]] 229 | name = "embedded-registers-derive" 230 | version = "0.9.13" 231 | dependencies = [ 232 | "bondrewd", 233 | "bytemuck", 234 | "darling", 235 | "defmt", 236 | "embedded-hal", 237 | "embedded-hal-async", 238 | "embedded-registers", 239 | "proc-macro2", 240 | "quote", 241 | "syn 2.0.100", 242 | ] 243 | 244 | [[package]] 245 | name = "fnv" 246 | version = "1.0.7" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 249 | 250 | [[package]] 251 | name = "ident_case" 252 | version = "1.0.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 255 | 256 | [[package]] 257 | name = "manyhow" 258 | version = "0.11.4" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" 261 | dependencies = [ 262 | "manyhow-macros", 263 | "proc-macro2", 264 | "quote", 265 | "syn 1.0.109", 266 | "syn 2.0.100", 267 | ] 268 | 269 | [[package]] 270 | name = "manyhow-macros" 271 | version = "0.11.4" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" 274 | dependencies = [ 275 | "proc-macro-utils", 276 | "proc-macro2", 277 | "quote", 278 | ] 279 | 280 | [[package]] 281 | name = "maybe-async-cfg" 282 | version = "0.2.5" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "8dbfaa67a76e2623580df07d6bb5e7956c0a4bae4b418314083a9c619bd66627" 285 | dependencies = [ 286 | "manyhow", 287 | "proc-macro2", 288 | "pulldown-cmark", 289 | "quote", 290 | "syn 1.0.109", 291 | ] 292 | 293 | [[package]] 294 | name = "memchr" 295 | version = "2.7.4" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 298 | 299 | [[package]] 300 | name = "num-integer" 301 | version = "0.1.46" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 304 | dependencies = [ 305 | "num-traits", 306 | ] 307 | 308 | [[package]] 309 | name = "num-rational" 310 | version = "0.4.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 313 | dependencies = [ 314 | "num-integer", 315 | "num-traits", 316 | ] 317 | 318 | [[package]] 319 | name = "num-traits" 320 | version = "0.2.19" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 323 | dependencies = [ 324 | "autocfg", 325 | ] 326 | 327 | [[package]] 328 | name = "paste" 329 | version = "1.0.15" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 332 | 333 | [[package]] 334 | name = "proc-macro-error-attr2" 335 | version = "2.0.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 338 | dependencies = [ 339 | "proc-macro2", 340 | "quote", 341 | ] 342 | 343 | [[package]] 344 | name = "proc-macro-error2" 345 | version = "2.0.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 348 | dependencies = [ 349 | "proc-macro-error-attr2", 350 | "proc-macro2", 351 | "quote", 352 | "syn 2.0.100", 353 | ] 354 | 355 | [[package]] 356 | name = "proc-macro-utils" 357 | version = "0.10.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" 360 | dependencies = [ 361 | "proc-macro2", 362 | "quote", 363 | "smallvec", 364 | ] 365 | 366 | [[package]] 367 | name = "proc-macro2" 368 | version = "1.0.94" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 371 | dependencies = [ 372 | "unicode-ident", 373 | ] 374 | 375 | [[package]] 376 | name = "pulldown-cmark" 377 | version = "0.11.3" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" 380 | dependencies = [ 381 | "bitflags 2.9.0", 382 | "memchr", 383 | "unicase", 384 | ] 385 | 386 | [[package]] 387 | name = "quote" 388 | version = "1.0.40" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 391 | dependencies = [ 392 | "proc-macro2", 393 | ] 394 | 395 | [[package]] 396 | name = "smallvec" 397 | version = "1.15.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 400 | 401 | [[package]] 402 | name = "strsim" 403 | version = "0.11.1" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 406 | 407 | [[package]] 408 | name = "syn" 409 | version = "1.0.109" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 412 | dependencies = [ 413 | "proc-macro2", 414 | "quote", 415 | "unicode-ident", 416 | ] 417 | 418 | [[package]] 419 | name = "syn" 420 | version = "2.0.100" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 423 | dependencies = [ 424 | "proc-macro2", 425 | "quote", 426 | "unicode-ident", 427 | ] 428 | 429 | [[package]] 430 | name = "thiserror" 431 | version = "2.0.12" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 434 | dependencies = [ 435 | "thiserror-impl", 436 | ] 437 | 438 | [[package]] 439 | name = "thiserror-impl" 440 | version = "2.0.12" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 443 | dependencies = [ 444 | "proc-macro2", 445 | "quote", 446 | "syn 2.0.100", 447 | ] 448 | 449 | [[package]] 450 | name = "typenum" 451 | version = "1.18.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 454 | 455 | [[package]] 456 | name = "unicase" 457 | version = "2.8.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 460 | 461 | [[package]] 462 | name = "unicode-ident" 463 | version = "1.0.18" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 466 | 467 | [[package]] 468 | name = "unicode-segmentation" 469 | version = "1.12.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 472 | 473 | [[package]] 474 | name = "uom" 475 | version = "0.36.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ffd36e5350a65d112584053ee91843955826bf9e56ec0d1351214e01f6d7cd9c" 478 | dependencies = [ 479 | "num-rational", 480 | "num-traits", 481 | "typenum", 482 | ] 483 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "embedded-devices", 5 | "embedded-devices-derive", 6 | "embedded-registers", 7 | "embedded-registers-derive", 8 | ] 9 | 10 | [workspace.package] 11 | version = "0.9.13" 12 | authors = ["oddlama "] 13 | homepage = "https://github.com/oddlama/embedded-devices" 14 | repository = "https://github.com/oddlama/embedded-devices" 15 | categories = ["embedded", "no-std", "hardware-support", "asynchronous"] 16 | license = "MIT OR Apache-2.0" 17 | -------------------------------------------------------------------------------- /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 2023 oddlama 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 oddlama 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | embedded-devices/README.md -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - uom is nice and all, but compile times are heavy and it is hard to capture true 2 | register resolution with rational32 or rational64, which is also expensive. 3 | You need at least storage w.r.t. different magnitudes to avoid rationals. 4 | Ideally I'd like to use a fixed point library and some form of simple physical quantity enforcement. 5 | Last time I checked the most popular fixed point library was not available for no_std. 6 | - reexport uom 7 | - scd41 panics on crc error, pls fix 8 | - (minor) dot (.) at the end of docstrings? I'm being very inconsistent here 9 | - use thiserr 10 | - all defmt derives only if defmt feature is enabled 11 | - workspace dependencies for stuff that is needed all the time, bondrewd, embedded-hal, maybe-async-cfg, ... 12 | - make #[register(default = )] add a #[doc] annotation 13 | -------------------------------------------------------------------------------- /contrib/callendar-van-dusen-approximator/.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /contrib/callendar-van-dusen-approximator/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1722113426, 11 | "narHash": "sha256-Yo/3loq572A8Su6aY5GP56knpuKYRvM2a1meP9oJZCw=", 12 | "owner": "numtide", 13 | "repo": "devshell", 14 | "rev": "67cce7359e4cd3c45296fb4aaf6a19e2a9c757ae", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "numtide", 19 | "repo": "devshell", 20 | "type": "github" 21 | } 22 | }, 23 | "flake-parts": { 24 | "inputs": { 25 | "nixpkgs-lib": "nixpkgs-lib" 26 | }, 27 | "locked": { 28 | "lastModified": 1722555600, 29 | "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", 30 | "owner": "hercules-ci", 31 | "repo": "flake-parts", 32 | "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "hercules-ci", 37 | "repo": "flake-parts", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1723991338, 44 | "narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=", 45 | "owner": "NixOS", 46 | "repo": "nixpkgs", 47 | "rev": "8a3354191c0d7144db9756a74755672387b702ba", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "NixOS", 52 | "ref": "nixos-unstable", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs-lib": { 58 | "locked": { 59 | "lastModified": 1722555339, 60 | "narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=", 61 | "type": "tarball", 62 | "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" 63 | }, 64 | "original": { 65 | "type": "tarball", 66 | "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" 67 | } 68 | }, 69 | "root": { 70 | "inputs": { 71 | "devshell": "devshell", 72 | "flake-parts": "flake-parts", 73 | "nixpkgs": "nixpkgs" 74 | } 75 | } 76 | }, 77 | "root": "root", 78 | "version": 7 79 | } 80 | -------------------------------------------------------------------------------- /contrib/callendar-van-dusen-approximator/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | devshell = { 4 | url = "github:numtide/devshell"; 5 | inputs.nixpkgs.follows = "nixpkgs"; 6 | }; 7 | flake-parts.url = "github:hercules-ci/flake-parts"; 8 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 9 | }; 10 | 11 | outputs = 12 | inputs: 13 | inputs.flake-parts.lib.mkFlake { inherit inputs; } { 14 | imports = [ 15 | inputs.devshell.flakeModule 16 | ]; 17 | 18 | systems = [ 19 | "x86_64-linux" 20 | "aarch64-linux" 21 | ]; 22 | 23 | perSystem = 24 | { 25 | pkgs, 26 | system, 27 | ... 28 | }: 29 | { 30 | _module.args.pkgs = import inputs.nixpkgs { 31 | inherit system; 32 | config.allowUnfree = true; 33 | config.enableCuda = true; 34 | }; 35 | 36 | devshells.default = { 37 | packages = [ 38 | (pkgs.python3.withPackages ( 39 | p: with p; [ 40 | torchWithCuda 41 | scipy 42 | numpy 43 | matplotlib 44 | ] 45 | )) 46 | ]; 47 | }; 48 | 49 | formatter = pkgs.nixfmt-rfc-style; # `nix fmt` 50 | }; 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /contrib/callendar-van-dusen-approximator/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # analytical solution: 4 | # t = -1/2 sqrt((2.25689e-20 (14123424280463317916104064499712 y - 15409551594649655757462803841024 K))/(477244327538622144 K^3 - 42810999909758488 K^2 y + 1.69526e-30 K^(3/2) sqrt(93888020965527858323701368714067466232986277692359736916684766077860651474130183296704764182528 K^3 - 54462502494335639718685321165686266165384797958199188415987855307271839840242508360456431730688 K^2 y + 37522856856107613087317656212462989703632371986859477818291109746388036214224728942788644700160 K y^2 - 11268860665316324488520037218937802013667426589820931462978601746317432642597497101502847647744 y^3))^(1/3) + (477244327538622144 K^3 - 42810999909758488 K^2 y + 1.69526e-30 K^(3/2) sqrt(93888020965527858323701368714067466232986277692359736916684766077860651474130183296704764182528 K^3 - 54462502494335639718685321165686266165384797958199188415987855307271839840242508360456431730688 K^2 y + 37522856856107613087317656212462989703632371986859477818291109746388036214224728942788644700160 K y^2 - 11268860665316324488520037218937802013667426589820931462978601746317432642597497101502847647744 y^3))^(1/3)/K - 89539.2) - 1/2 sqrt(-(2.25689e-20 (14123424280463317916104064499712 y - 15409551594649655757462803841024 K))/(477244327538622144 K^3 - 42810999909758488 K^2 y + 1.69526e-30 K^(3/2) sqrt(93888020965527858323701368714067466232986277692359736916684766077860651474130183296704764182528 K^3 - 54462502494335639718685321165686266165384797958199188415987855307271839840242508360456431730688 K^2 y + 37522856856107613087317656212462989703632371986859477818291109746388036214224728942788644700160 K y^2 - 11268860665316324488520037218937802013667426589820931462978601746317432642597497101502847647744 y^3))^(1/3) - (477244327538622144 K^3 - 42810999909758488 K^2 y + 1.69526e-30 K^(3/2) sqrt(93888020965527858323701368714067466232986277692359736916684766077860651474130183296704764182528 K^3 - 54462502494335639718685321165686266165384797958199188415987855307271839840242508360456431730688 K^2 y + 37522856856107613087317656212462989703632371986859477818291109746388036214224728942788644700160 K y^2 - 11268860665316324488520037218937802013667426589820931462978601746317432642597497101502847647744 y^3))^(1/3)/K - (1.8551e9)/sqrt((2.25689e-20 (14123424280463317916104064499712 y - 15409551594649655757462803841024 K))/(477244327538622144 K^3 - 42810999909758488 K^2 y + 1.69526e-30 K^(3/2) sqrt(93888020965527858323701368714067466232986277692359736916684766077860651474130183296704764182528 K^3 - 54462502494335639718685321165686266165384797958199188415987855307271839840242508360456431730688 K^2 y + 37522856856107613087317656212462989703632371986859477818291109746388036214224728942788644700160 K y^2 - 11268860665316324488520037218937802013667426589820931462978601746317432642597497101502847647744 y^3))^(1/3) + (477244327538622144 K^3 - 42810999909758488 K^2 y + 1.69526e-30 K^(3/2) sqrt(93888020965527858323701368714067466232986277692359736916684766077860651474130183296704764182528 K^3 - 54462502494335639718685321165686266165384797958199188415987855307271839840242508360456431730688 K^2 y + 37522856856107613087317656212462989703632371986859477818291109746388036214224728942788644700160 K y^2 - 11268860665316324488520037218937802013667426589820931462978601746317432642597497101502847647744 y^3))^(1/3)/K - 89539.2) - 179078.) + 25 5 | 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | import scipy 9 | 10 | n_samples = 500_000 11 | 12 | # Define parameters for the lookup table generation 13 | min_temp = -200 14 | max_temp = 900 15 | max_err = 0.0001 # Desired maximum error in °C 16 | 17 | # Constants for Callendar-Van Dusen equation (Platinum RTD, typical values) 18 | R0 = 100 # Resistance at 0°C 19 | A = 3.9083e-3 20 | B = -5.775e-7 21 | C = -4.183e-12 # Only valid for temperatures below 0°C 22 | 23 | # Function to calculate resistance based on temperature 24 | def resistance(t): 25 | if t < 0: 26 | return R0 * (1 + A * t + B * t**2 + C * (t - 100) * t**3) 27 | else: 28 | return R0 * (1 + A * t + B * t**2) 29 | 30 | def approx_order_1(r, r1, t1, r2, t2): 31 | return np.interp(r, [r1, r2], [t1, t2]) 32 | 33 | def parabola(r, a, b, c): 34 | return a * r**2 + b * r + c 35 | 36 | def cubic(r, a, b, c, d): 37 | return a * r**3 + b * r**2 + c * r + d 38 | 39 | def o4(r, a, b, c, d, e): 40 | return a * r**4 + b * r**3 + c * r**2 + d * r + e 41 | 42 | def o5(r, a, b, c, d, e, f): 43 | return e * r**5 + a * r**4 + b * r**3 + c * r**2 + d * r + e 44 | 45 | def o6(r, a, b, c, d, e, f, g): 46 | return f * r**6 + e * r**5 + a * r**4 + b * r**3 + c * r**2 + d * r + e 47 | Fs = [parabola, cubic, o4, o5, o6] 48 | Frange = list(range(2, 2 + len(Fs))) 49 | 50 | approx_order = 4 51 | 52 | print("preparing values") 53 | # Generate equidistant temperature positions 54 | temperatures = np.linspace(min_temp, max_temp, n_samples, dtype=np.float32) 55 | 56 | # Calculate resistances for the generated temperatures 57 | vresistance = np.vectorize(resistance) 58 | resistances = vresistance(temperatures) 59 | resistances = np.float32(resistances) 60 | print("done preparing values") 61 | 62 | # Function to generate lookup table 63 | def generate_lookup_table(min_temp, max_temp, desired_max_err): 64 | lookup_table = [(0, resistances[0], temperatures[0], None)] # Start with first entry 65 | first_index = 1 66 | 67 | while first_index < n_samples - 1: 68 | # print(f"Lookup table construction at {100.0 * first_index / n_samples}% ({first_index=})") 69 | current_end_index = n_samples - 1 70 | next_change = (current_end_index - first_index) // 2 71 | 72 | # Binary search to find the best position 73 | while True: 74 | fit_params = None 75 | if approx_order in Frange: 76 | F = Fs[approx_order - 2] 77 | fit_params, pcov = scipy.optimize.curve_fit(F, resistances[first_index:current_end_index+1], temperatures[first_index:current_end_index+1]) 78 | f_approx = np.vectorize(lambda r: F(r, *fit_params)) 79 | else: 80 | f_approx = np.vectorize(lambda r: approx_order_1(r, resistances[first_index], temperatures[first_index], resistances[current_end_index], temperatures[current_end_index])) 81 | # print(f"Check range [{first_index},{current_end_index}]") 82 | 83 | approx_temps = f_approx(resistances[first_index:current_end_index+1]) 84 | max_error = np.max(np.abs(approx_temps - temperatures[first_index:current_end_index+1])) 85 | 86 | if next_change == 0: 87 | # print(f"Found optimum at index={current_end_index} with max_error={max_error:.4f}") 88 | print(f"{100.0 * current_end_index / (n_samples - 1)}% - creating segment at index={current_end_index:6}, max_error={max_error:.4f}") 89 | break 90 | 91 | if max_error > desired_max_err: 92 | # print(f" -> Max error {max_error:.4f} °C is too high, reducing range") 93 | current_end_index -= next_change 94 | else: 95 | # print(f" -> Max error {max_error:.4f} °C may be too low, increasing range") 96 | current_end_index += next_change 97 | 98 | if current_end_index >= n_samples - 1: 99 | # print(f"Encountered last piece. Finishing table.") 100 | current_end_index = n_samples - 1 101 | print(f"{100.0 * current_end_index / (n_samples - 1)}% - creating segment at index={current_end_index:6}, max_error={max_error:.4f}") 102 | break 103 | 104 | next_change = next_change // 2 105 | 106 | # Add the best position to the lookup table 107 | lookup_table.append((current_end_index+1, resistances[current_end_index], temperatures[current_end_index], fit_params)) 108 | first_index = current_end_index + 1 109 | 110 | return lookup_table 111 | 112 | # Generate the lookup table 113 | lookup_table = generate_lookup_table(min_temp, max_temp, max_err) 114 | 115 | # t_low = np.linspace(min_temp - 5, min_temp, 500, dtype=np.float32) 116 | # t_high = np.linspace(max_temp, max_temp + 5, 500, dtype=np.float32) 117 | # r_low = vresistance(t_low) 118 | # r_high = vresistance(t_high) 119 | # Ts = np.concatenate([t_low, temperatures, t_high], axis=0) 120 | # Rs = np.concatenate([r_low, resistances, r_high], axis=0) 121 | Ts = temperatures 122 | Rs = resistances 123 | 124 | if approx_order in Frange: 125 | F = Fs[approx_order - 2] 126 | approx_temps = [] 127 | # low extend 128 | # _,_,_,params = lookup_table[1] 129 | # f_approx = np.vectorize(lambda r: F(r, *params)) 130 | # approx_temps.append(f_approx(r_low)) 131 | # valid range 132 | for k,(li,lr,lt,_) in enumerate(lookup_table[0:-1]): 133 | ri,rr,rt,params = lookup_table[k+1] 134 | f_approx = np.vectorize(lambda r: F(r, *params)) 135 | approx_temps.append(f_approx(resistances[li:ri])) 136 | # high extend 137 | # _,_,_,params = lookup_table[-1] 138 | # f_approx = np.vectorize(lambda r: F(r, *params)) 139 | # approx_temps.append(f_approx(r_high)) 140 | # concat 141 | approx_temps = np.concatenate(approx_temps, axis=0) 142 | elif approx_order == 1: 143 | approx_temps = np.interp(Rs, [x[1] for x in lookup_table], [x[2] for x in lookup_table]) 144 | 145 | error = approx_temps - Ts 146 | max_error = np.max(np.abs(error)) 147 | print(f"Table calculation done!") 148 | print(f"Entries: ", len(lookup_table)) 149 | print(f"Maximum Error: {max_error:.4f}", ) 150 | print(f"Mean Error: {error.mean():.4f}") 151 | for i,r,t,params in lookup_table: 152 | print(f"{i:20},{r:20},{t:20},{params}") 153 | 154 | print(f""" 155 | // 4th order piecewise approximation table, requires {len(lookup_table) - 1}*6*4 = {(len(lookup_table) - 1)*6*4} bytes. 156 | // - Values for R0={R0}Ω 157 | // - maximum error: +- {max_err}°C 158 | // - valid range: [{min_temp}°C, {max_temp}°C] 159 | // - no need for binary search 160 | #[rustfmt::skip] 161 | const LOOKUP_TABLE_R{R0}_RESISTANCES: [f32; {len(lookup_table) - 1}] = [ 162 | // maximum valid resistance for the params at the same index 163 | {"\n".join(f" {r}," for _,r,_,_ in lookup_table[1:])} 164 | ]; 165 | 166 | #[rustfmt::skip] 167 | const LOOKUP_TABLE_R{R0}_PARAMS: [[f32; 5]; {len(lookup_table) - 1}] = [ 168 | // polynomial params [a, b, c, d, e]) 169 | {"\n".join(" [" + ", ".join(f"{str(x):25}" for x in params) + " ]," for _,_,_,params in lookup_table[1:])} 170 | ]; 171 | """ 172 | ) 173 | 174 | print(f""" 175 | #[cfg(test)] 176 | mod test {{ 177 | #[test] 178 | fn test_lookup_table_r{R0}() {{""") 179 | N = 100 180 | for i in range(N): 181 | idx = len(resistances) * i // (N + 1) 182 | r = resistances[idx] 183 | t = temperatures[idx] 184 | print(f" assert_abs_diff_eq!(resistance_to_temperature_r{R0}({r}), {t}, epsilon = {max_err});") 185 | print(""" } 186 | } 187 | """) 188 | 189 | # Create the plot 190 | fig, ax1 = plt.subplots(figsize=(10, 6)) 191 | 192 | # Plot predicted and real temperatures 193 | ax1.plot(Rs, approx_temps, label="Predicted Temperature", color='tab:blue') 194 | ax1.plot(Rs, Ts, label="Real Temperature", linestyle="--", color='tab:orange') 195 | ax1.set_xlabel("Resistance (Ω)") 196 | ax1.set_ylabel("Temperature (°C)") 197 | ax1.set_title("Piecewise Linear Approximation of Callendar-Van Dusen Equation") 198 | ax1.grid(True) 199 | ax1.legend(loc='upper left') 200 | 201 | # Create a second y-axis for the error 202 | ax2 = ax1.twinx() 203 | ax2.plot(Rs, error, label="Error", color='tab:red') 204 | ax2.set_ylabel("Error") 205 | 206 | # Combine legends from both y-axes 207 | lines, labels = ax1.get_legend_handles_labels() 208 | lines2, labels2 = ax2.get_legend_handles_labels() 209 | ax1.legend(lines + lines2, labels + labels2, loc='upper left') 210 | 211 | plt.show() 212 | -------------------------------------------------------------------------------- /embedded-devices-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-devices-derive" 3 | edition = "2024" 4 | description = "Macros required to define devices in embedded-devices" 5 | documentation = "https://docs.rs/embedded-devices-derive" 6 | keywords = ["sensor", "embedded", "device", "driver", "peripheral"] 7 | version.workspace = true 8 | authors.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | categories.workspace = true 12 | license.workspace = true 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | convert_case = "0.8.0" 19 | darling = "0.20.11" 20 | proc-macro2 = "1.0" 21 | quote = "1.0" 22 | syn = { version = "2.0", features = ["full"] } 23 | -------------------------------------------------------------------------------- /embedded-devices-derive/src/device.rs: -------------------------------------------------------------------------------- 1 | use darling::ast::NestedMeta; 2 | use darling::FromMeta; 3 | use proc_macro2::TokenStream; 4 | use quote::{format_ident, quote}; 5 | 6 | #[derive(Debug, FromMeta)] 7 | struct DeviceArgs {} 8 | 9 | /// This adds accessor functions for the given register and 10 | /// proxies the embedded_registers::register attribute. 11 | pub(crate) fn device(args: TokenStream, orig_input: TokenStream) -> syn::Result { 12 | let _args = DeviceArgs::from_list(&NestedMeta::parse_meta_list(args)?)?; 13 | 14 | let item = syn::parse2::(orig_input.clone())?; 15 | let ident = item.ident; 16 | let register_marker = format_ident!("{}Register", ident); 17 | 18 | let output = quote! { 19 | #orig_input 20 | 21 | /// A marker trait to distinguish related registers from unrelated ones 22 | pub trait #register_marker {} 23 | }; 24 | 25 | Ok(output) 26 | } 27 | -------------------------------------------------------------------------------- /embedded-devices-derive/src/device_impl.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides procedural helper macro for embedded-devices 2 | 3 | use darling::ast::NestedMeta; 4 | use darling::FromMeta; 5 | use proc_macro2::TokenStream; 6 | use quote::{format_ident, quote, ToTokens}; 7 | use syn::spanned::Spanned; 8 | 9 | #[derive(Debug, FromMeta)] 10 | struct DeviceImplArgs {} 11 | 12 | /// This adds accessor functions for the given register and 13 | /// proxies the embedded_registers::register attribute. 14 | pub(crate) fn device_impl(args: TokenStream, orig_input: TokenStream) -> syn::Result { 15 | let args_span = args.span(); 16 | let _args = DeviceImplArgs::from_list(&NestedMeta::parse_meta_list(args)?)?; 17 | let mut item_impl = syn::parse2::(orig_input.clone())?; 18 | 19 | let syn::Type::Path(self_ty_path) = item_impl.self_ty.as_ref() else { 20 | return Err(syn::Error::new( 21 | args_span, 22 | "Could not parse device identifier as path. This should not happen, please report this as a bug.", 23 | )); 24 | }; 25 | let mut register_marker = self_ty_path.clone(); 26 | let Some(last_segment) = register_marker.path.segments.last_mut() else { 27 | return Err(syn::Error::new( 28 | args_span, 29 | "Could not parse device identifier as path. This should not happen, please report this as a bug.", 30 | )); 31 | }; 32 | last_segment.ident = format_ident!("{}Register", last_segment.ident); 33 | last_segment.arguments = syn::PathArguments::None; 34 | 35 | let read_register_doc = format!( 36 | "Reads from the given register. For a list of all available registers, refer to implentors of [`{}`].", 37 | register_marker.to_token_stream() 38 | ); 39 | let write_register_doc = format!( 40 | "Writes to the given register. For a list of all available registers, refer to implentors of [`{}`].", 41 | register_marker.to_token_stream() 42 | ); 43 | 44 | let additional_items = vec![ 45 | quote! { 46 | #[doc = #read_register_doc] 47 | #[inline] 48 | pub async fn read_register(&mut self) -> Result 49 | where 50 | R: embedded_registers::ReadableRegister + #register_marker 51 | { 52 | self.interface.read_register::().await 53 | } 54 | }, 55 | quote! { 56 | #[doc = #write_register_doc] 57 | #[inline] 58 | pub async fn write_register(&mut self, register: impl AsRef) -> Result<(), I::Error> 59 | where 60 | R: embedded_registers::WritableRegister + #register_marker 61 | { 62 | self.interface.write_register::(register).await 63 | } 64 | }, 65 | ]; 66 | 67 | for i in additional_items { 68 | item_impl.items.push(syn::parse2::(i)?); 69 | } 70 | 71 | let output = quote! { 72 | #item_impl 73 | }; 74 | 75 | Ok(output) 76 | } 77 | -------------------------------------------------------------------------------- /embedded-devices-derive/src/device_register.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides procedural helper macro for embedded-devices 2 | 3 | use darling::ast::NestedMeta; 4 | use proc_macro2::TokenStream; 5 | use quote::{format_ident, quote}; 6 | use syn::spanned::Spanned; 7 | 8 | pub(crate) fn device_register(args: TokenStream, orig_input: TokenStream) -> syn::Result { 9 | let args_span = args.span(); 10 | let args = NestedMeta::parse_meta_list(args)?; 11 | let input = syn::parse2::(orig_input.clone())?; 12 | let ident = input.ident; 13 | 14 | let mut output = quote! { 15 | #orig_input 16 | }; 17 | 18 | for device in args { 19 | let NestedMeta::Meta(device_meta) = device else { 20 | return Err(syn::Error::new( 21 | args_span, 22 | "Could not parse device identifier as path. This should not happen, please report this as a bug.", 23 | )); 24 | }; 25 | 26 | let mut register_marker = device_meta.path().clone(); 27 | let Some(last_segment) = register_marker.segments.last_mut() else { 28 | return Err(syn::Error::new( 29 | args_span, 30 | "Could not parse device identifier as path. This should not happen, please report this as a bug.", 31 | )); 32 | }; 33 | last_segment.ident = format_ident!("{}Register", last_segment.ident); 34 | last_segment.arguments = syn::PathArguments::None; 35 | 36 | output = quote! { 37 | #output 38 | impl #register_marker for #ident {} 39 | }; 40 | } 41 | 42 | Ok(output) 43 | } 44 | -------------------------------------------------------------------------------- /embedded-devices-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides procedural helper macro for embedded-devices 2 | 3 | use proc_macro as pc; 4 | 5 | mod device; 6 | mod device_impl; 7 | mod device_register; 8 | 9 | #[proc_macro_attribute] 10 | pub fn device(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { 11 | match device::device(args.into(), input.into()) { 12 | Ok(result) => result.into(), 13 | Err(e) => e.into_compile_error().into(), 14 | } 15 | } 16 | 17 | #[proc_macro_attribute] 18 | pub fn device_impl(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { 19 | match device_impl::device_impl(args.into(), input.into()) { 20 | Ok(result) => result.into(), 21 | Err(e) => e.into_compile_error().into(), 22 | } 23 | } 24 | 25 | #[proc_macro_attribute] 26 | pub fn device_register(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { 27 | match device_register::device_register(args.into(), input.into()) { 28 | Ok(result) => result.into(), 29 | Err(e) => e.into_compile_error().into(), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /embedded-devices/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-devices" 3 | edition = "2024" 4 | description = "Device driver implementations for many embedded sensors and devices" 5 | documentation = "https://docs.rs/embedded-devices" 6 | keywords = ["sensor", "embedded", "device", "driver", "peripheral"] 7 | version.workspace = true 8 | authors.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | categories.workspace = true 12 | license.workspace = true 13 | 14 | [dependencies] 15 | approx = { version = "0.5.1", default-features = false } 16 | bondrewd = { version = "0.1.14", default-features = false, features = ["derive"] } 17 | bytemuck = { version = "1.22.0", features = ["derive", "min_const_generics"] } 18 | defmt = "1.0.1" 19 | embedded-hal = "1.0.0" 20 | embedded-hal-async = { version = "1.0.0", optional = true } 21 | embedded-devices-derive = { version = "0.9.13", path = "../embedded-devices-derive" } 22 | embedded-registers = { version = "0.9.13", path = "../embedded-registers", default-features = false, features = ["derive"] } 23 | maybe-async-cfg = "0.2.5" 24 | paste = "1.0.15" 25 | uom = { version = "0.36.0", features = ["f32", "f64", "si", "u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "rational32", "rational64"], default-features = false } 26 | crc = "3.2.1" 27 | 28 | [features] 29 | default = ["sync", "async"] 30 | 31 | sync = ["embedded-registers/sync"] 32 | async = ["dep:embedded-hal-async", "embedded-registers/async"] 33 | std = [] 34 | 35 | # Feature flags to enable supported devices 36 | 37 | # Analog Devices 38 | analog_devices-max31865 = [] 39 | 40 | # Bosch 41 | bosch-bme280 = [] 42 | bosch-bmp280 = ["bosch-bme280"] 43 | bosch-bmp390 = [] 44 | 45 | # Microchip 46 | microchip-mcp9808 = [] 47 | microchip-mcp3204 = [] 48 | microchip-mcp3208 = [] 49 | 50 | # Texas Instrument 51 | texas_instruments-ina219 = [] 52 | texas_instruments-ina228 = [] 53 | texas_instruments-tmp102 = [] 54 | texas_instruments-tmp117 = [] 55 | 56 | # Sensirion 57 | sensirion-scd4x = [] 58 | 59 | [package.metadata.docs.rs] 60 | all-features = true 61 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/analog_devices/max31865/registers.rs: -------------------------------------------------------------------------------- 1 | use bondrewd::BitfieldEnum; 2 | use embedded_devices_derive::device_register; 3 | use embedded_registers::register; 4 | 5 | /// Conversion mode. 6 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 7 | #[bondrewd_enum(u8)] 8 | pub enum ConversionMode { 9 | /// No automatic conversions, 1-shot conversions may be initiated from this mode. 10 | NormallyOff = 0b0, 11 | /// Automatic conversion mode, conversions occur continuously at a 50/60Hz rate. 12 | Automatic = 0b1, 13 | } 14 | 15 | /// Wiring mode. 16 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 17 | #[bondrewd_enum(u8)] 18 | pub enum WiringMode { 19 | /// 2- or 4-wire RTD. 20 | TwoOrFourWire = 0b0, 21 | /// 3-wire RTD connected to `FORCE+`, `RTDIN+`, `RTDIN-`. 22 | ThreeWire = 0b1, 23 | } 24 | 25 | /// Fault detection cycle. 26 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 27 | #[bondrewd_enum(u8)] 28 | pub enum FaultDetectionCycle { 29 | /// Fault detection has finished. 30 | Finished = 0b00, 31 | /// Run fault detection with automatic delay. 32 | Automatic = 0b01, 33 | /// Run fault detection with manual delay (cycle 1). 34 | RunManual = 0b10, 35 | /// Finish fault detection with manual delay (cycle 2). 36 | FinishManual = 0b11, 37 | } 38 | 39 | /// Filter mode. 40 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 41 | #[bondrewd_enum(u8)] 42 | #[allow(non_camel_case_types)] 43 | pub enum FilterMode { 44 | /// 60Hz filter 45 | F_60Hz = 0b0, 46 | /// 50Hz filter 47 | F_50Hz = 0b1, 48 | } 49 | 50 | /// The device configuration register. 51 | #[device_register(super::MAX31865)] 52 | #[register(address = 0b0000, mode = "rw")] 53 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 1)] 54 | pub struct Configuration { 55 | /// When no conversions are being performed, VBIAS may be disabled to reduce power 56 | /// dissipation. Set this bit to `true` to enable VBIAS before beginning a single (1-Shot) 57 | /// conversion. When automatic (continuous) conversion mode is selected, 58 | /// VBIAS remains on continuously. 59 | #[register(default = false)] 60 | pub enable_bias_voltage: bool, 61 | /// Conversion mode (operating mode). 62 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 63 | #[register(default = ConversionMode::NormallyOff)] 64 | pub conversion_mode: ConversionMode, 65 | /// When the conversion mode is set to [`ConversionMode::NormallyOff`], 66 | /// set this flag to start a conversion. You must enable VBIAS before 67 | /// starting a conversion. Note any filter capacitors at the RTDIN inputs 68 | /// need to charge before an accurate conversion can be performed. 69 | /// Therefore, enable VBIAS and wait at least 10.5 time constants of the input RC 70 | /// network plus an additional 1ms before initiating the conversion. Note that a single 71 | /// conversion requires approximately 52ms in 60Hz filter mode or 62.5ms in 50Hz 72 | /// filter mode to complete. This flag will auto-clear after the conversion. 73 | #[register(default = false)] 74 | pub oneshot: bool, 75 | /// The wiring mode of the RTD. 76 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 77 | #[register(default = WiringMode::TwoOrFourWire)] 78 | pub wiring_mode: WiringMode, 79 | /// Used to initiate fault detection. This is reset to [`FaultDetectionCycle::Finished`] 80 | /// when fault detection is done. When writing this register to initiate fault detection, 81 | /// the bias voltage must be enabled, conversion mode must be [`ConversionMode::NormallyOff`] 82 | /// and the `clear_fault_status` flag must be off. 83 | /// 84 | /// If the external RTD interface circuitry includes an input filter with a time constant 85 | /// greater than 100µs, the fault detection cycle timing should be controlled in the manual 86 | /// mode operation. For more information, refer to the datasheet. 87 | #[bondrewd(enum_primitive = "u8", bit_length = 2)] 88 | #[register(default = FaultDetectionCycle::Finished)] 89 | pub fault_detection_cycle: FaultDetectionCycle, 90 | /// Set this flag while `oneshot` is off and writing [`FaultDetectionCycle::Finished`] to 91 | /// `fault_detection_cycle` to clear the fault status register. This bit will auto-clear. 92 | #[register(default = false)] 93 | pub clear_fault_status: bool, 94 | /// The wiring mode of the RTD. 95 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 96 | #[register(default = FilterMode::F_60Hz)] 97 | pub filter_mode: FilterMode, 98 | } 99 | 100 | /// The RTD resistance data. 101 | #[device_register(super::MAX31865)] 102 | #[register(address = 0b0001, mode = "r")] 103 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 104 | pub struct Resistance { 105 | /// The ratio of RTD resistance to reference resistance. 106 | /// The decimal value is obtained by dividing this value by `2^15`. 107 | #[bondrewd(bit_length = 15)] 108 | #[register(default = 0)] 109 | pub resistance_ratio: u16, 110 | /// Whether a fault was detected. 111 | #[register(default = false)] 112 | pub fault: bool, 113 | } 114 | 115 | macro_rules! define_fault_threshold_register { 116 | ($name:ident, $address:expr, $value_default:expr, $doc:expr) => { 117 | #[doc = $doc] 118 | #[device_register(super::MAX31865)] 119 | #[register(address = $address, mode = "rw")] 120 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 121 | pub struct $name { 122 | /// The ratio of RTD resistance to reference resistance. 123 | /// The decimal value is obtained by dividing this value by `2^15`. 124 | #[bondrewd(bit_length = 15)] 125 | #[register(default = $default_value)] 126 | pub resistance_ratio: u16, 127 | #[bondrewd(bit_length = 1, reserve)] 128 | #[allow(dead_code)] 129 | pub reserved: u8, 130 | } 131 | }; 132 | } 133 | 134 | define_fault_threshold_register!( 135 | FaultThresholdHigh, 136 | 0b0011, 137 | 0x7fff, 138 | r#" 139 | The High Fault Threshold register selects the high trip threshold for RTD fault detection. 140 | 141 | The RTD High bit in the Fault Status Register is set if the 142 | RTD resistance register value is greater than or equal to 143 | the value in the High Fault Threshold register. 144 | The POR value of the High Fault Threshold register is FFFFh. 145 | "# 146 | ); 147 | 148 | define_fault_threshold_register!( 149 | FaultThresholdLow, 150 | 0b0101, 151 | 0x0000, 152 | r#" 153 | The Low Fault Threshold register selects the low trip threshold for RTD fault detection. 154 | 155 | The RTD Low bit in the Fault Status Register is set if the 156 | RTD resistance value is less than or equal to the value in 157 | the Low Fault Threshold register. The POR value of the 158 | Low Fault Threshold register is 0000h. 159 | "# 160 | ); 161 | 162 | /// The fault status register. 163 | /// 164 | /// Depending on whether 2-, 3- or 4-wire mode is used, 165 | /// these status bits can have different possible causes. 166 | /// Refer to the datasheet for a table of possible causes. 167 | #[device_register(super::MAX31865)] 168 | #[register(address = 0b0111, mode = "r")] 169 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 1)] 170 | pub struct FaultStatus { 171 | /// Measured resistance greater than High Fault Threshold value. 172 | #[register(default = false)] 173 | pub high_threshold: bool, 174 | /// Measured resistance less than Low Fault Threshold value 175 | #[register(default = false)] 176 | pub low_threshold: bool, 177 | /// `V_REFIN-` > 0.85 * `V_BIAS`. 178 | /// Detected on demand by initiating fault detection. 179 | #[register(default = false)] 180 | pub v_refin_minus_exceeds_85_percent_of_v_bias: bool, 181 | /// `V_REFIN-` < 0.85 * `V_BIAS`, FORCE- open. 182 | /// Detected on demand by initiating fault detection. 183 | #[register(default = false)] 184 | pub v_refin_minus_below_85_percent_of_v_bias_with_force_minus_open: bool, 185 | /// `V_RTDIN-` < 0.85 * `V_BIAS`, FORCE- open. 186 | /// Detected on demand by initiating fault detection. 187 | #[register(default = false)] 188 | pub v_rtdin_minus_below_85_percent_of_v_bias_with_force_minus_open: bool, 189 | /// Over/undervoltage, some protected input voltage exceeds VDD or below GND1 190 | #[register(default = false)] 191 | pub over_or_undervoltage: bool, 192 | #[bondrewd(bit_length = 2, reserve)] 193 | #[allow(dead_code)] 194 | pub reserved: u8, 195 | } 196 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/analog_devices/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "analog_devices-max31865")] 2 | pub mod max31865; 3 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/bosch/bme280/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const PRIMARY_ADDRESS: u8 = 0b1110110; 4 | const SECONDARY_ADDRESS: u8 = 0b1110111; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 7 | pub enum Address { 8 | /// Primary device address 0b1110110 (SDO connected to GND). 9 | Primary, 10 | /// Secondary device address 0b1110111 (SDO connected to V_DDIO). 11 | Secondary, 12 | /// Custom address not directly supported by the device, but may be useful 13 | /// when using address translators. 14 | Custom(u8), 15 | } 16 | 17 | impl From
for u8 { 18 | fn from(address: Address) -> Self { 19 | match address { 20 | Address::Primary => PRIMARY_ADDRESS, 21 | Address::Secondary => SECONDARY_ADDRESS, 22 | Address::Custom(x) => x, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/bosch/bmp280.rs: -------------------------------------------------------------------------------- 1 | //! # BMP280 2 | //! 3 | //! The BMP280 is a combined digital pressure and temperature sensor based on proven 4 | //! sensing principles. The sensor module is housed in an extremely compact metal-lid LGA package with 5 | //! a footprint of only 2.5 × 2.5 mm² with a height of 0.93 mm. Its small dimensions and its low power 6 | //! consumption allow the implementation in battery driven devices such as handsets, GPS modules or 7 | //! watches. The BMP280 is register and performance compatible to the Bosch Sensortec BMP280 digital 8 | //! pressure sensor. 9 | //! 10 | //! The BMP280 achieves high performance in all applications requiring temperature and pressure 11 | //! measurement. These emerging applications of home automation control, in-door navigation, fitness as 12 | //! well as GPS refinement require a high accuracy and a low TCO at the same time. 13 | //! 14 | //! - The pressure sensor is an absolute barometric pressure sensor with extremely high accuracy and 15 | //! resolution and drastically lower noise than the Bosch Sensortec BMP180. 16 | //! - The integrated temperature sensor has been optimized for lowest noise and highest resolution. Its 17 | //! output is used for temperature compensation of the pressure sensor and can also be 18 | //! used for estimation of the ambient temperature. 19 | //! 20 | //! The sensor provides both SPI and I²C interfaces and can be supplied using 1.71 to 3.6 V for the 21 | //! sensor supply V DD and 1.2 to 3.6 V for the interface supply V DDIO. Measurements can be triggered by 22 | //! the host or performed in regular intervals. When the sensor is disabled, current consumption drops to 23 | //! 0.1 μA. 24 | //! 25 | //! BMP280 can be operated in three power modes: 26 | //! - sleep mode 27 | //! - normal mode 28 | //! - forced mode 29 | //! 30 | //! In order to tailor data rate, noise, response time and current consumption to the needs of the user, a 31 | //! variety of oversampling modes, filter modes and data rates can be selected. 32 | //! 33 | //! ## Usage (sync) 34 | //! 35 | //! ```rust, only_if(sync) 36 | //! # fn test(mut i2c: I, mut Delay: D) -> Result<(), embedded_devices::devices::bosch::bme280::Error> 37 | //! # where 38 | //! # I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType, 39 | //! # D: embedded_hal::delay::DelayNs 40 | //! # { 41 | //! use embedded_devices::devices::bosch::bmp280::BMP280Sync; 42 | //! use embedded_devices::devices::bosch::bme280::address::Address; 43 | //! use uom::si::thermodynamic_temperature::degree_celsius; 44 | //! use uom::num_traits::ToPrimitive; 45 | //! 46 | //! // Create and initialize the device 47 | //! let mut bmp280 = BMP280Sync::new_i2c(i2c, Address::Primary); 48 | //! bmp280.init(&mut Delay).unwrap(); 49 | //! 50 | //! // Read the current temperature in °C and convert it to a float 51 | //! let measurements = bmp280.measure(&mut Delay)?; 52 | //! let temp = measurements.temperature.get::().to_f32(); 53 | //! println!("Current temperature: {:?}°C", temp); 54 | //! # Ok(()) 55 | //! # } 56 | //! ``` 57 | //! 58 | //! ## Usage (async) 59 | //! 60 | //! ```rust, only_if(async) 61 | //! # async fn test(mut i2c: I, mut Delay: D) -> Result<(), embedded_devices::devices::bosch::bme280::Error> 62 | //! # where 63 | //! # I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType, 64 | //! # D: embedded_hal_async::delay::DelayNs 65 | //! # { 66 | //! use embedded_devices::devices::bosch::bmp280::BMP280Async; 67 | //! use embedded_devices::devices::bosch::bme280::address::Address; 68 | //! use uom::si::thermodynamic_temperature::degree_celsius; 69 | //! use uom::num_traits::ToPrimitive; 70 | //! 71 | //! // Create and initialize the device 72 | //! let mut bmp280 = BMP280Async::new_i2c(i2c, Address::Primary); 73 | //! bmp280.init(&mut Delay).await.unwrap(); 74 | //! 75 | //! // Read the current temperature in °C and convert it to a float 76 | //! let measurements = bmp280.measure(&mut Delay).await?; 77 | //! let temp = measurements.temperature.get::().to_f32(); 78 | //! println!("Current temperature: {:?}°C", temp); 79 | //! # Ok(()) 80 | //! # } 81 | //! ``` 82 | 83 | use uom::si::rational32::{Pressure, ThermodynamicTemperature}; 84 | 85 | use super::bme280::{ 86 | registers::{BurstMeasurementsPT, Config, ControlMeasurement, IIRFilter, Oversampling, SensorMode}, 87 | BME280CommonAsync, BME280CommonSync, Error, 88 | }; 89 | 90 | /// Measurement data 91 | #[derive(Debug)] 92 | pub struct Measurements { 93 | /// Current temperature 94 | pub temperature: ThermodynamicTemperature, 95 | /// Current pressure or None if the sensor reported and invalid value 96 | pub pressure: Option, 97 | } 98 | 99 | /// The BMP280 is a combined digital pressure and temperature sensor based on proven 100 | /// sensing principles. The sensor module is housed in an extremely compact metal-lid LGA package. 101 | /// a footprint of only 2.5 × 2.5 mm² with a height of 0.93 mm. Its small dimensions and its low power 102 | /// consumption allow the implementation in battery driven devices such as handsets, GPS modules or 103 | /// watches. 104 | #[cfg(feature = "sync")] 105 | pub type BMP280Sync = BME280CommonSync; 106 | 107 | /// The BMP280 is a combined digital pressure and temperature sensor based on proven 108 | /// sensing principles. The sensor module is housed in an extremely compact metal-lid LGA package. 109 | /// a footprint of only 2.5 × 2.5 mm² with a height of 0.93 mm. Its small dimensions and its low power 110 | /// consumption allow the implementation in battery driven devices such as handsets, GPS modules or 111 | /// watches. 112 | #[cfg(feature = "async")] 113 | pub type BMP280Async = BME280CommonAsync; 114 | 115 | /// Common configuration values for the BMP280 sensor. 116 | /// The power-on-reset default is to set all oversampling settings to 1X 117 | /// and use no IIR filter. 118 | #[derive(Debug, Clone)] 119 | pub struct Configuration { 120 | /// The oversampling rate for temperature mesurements 121 | temperature_oversampling: Oversampling, 122 | /// The oversampling rate for pressure mesurements 123 | pressure_oversampling: Oversampling, 124 | /// The iir filter to use 125 | iir_filter: IIRFilter, 126 | } 127 | 128 | impl Default for Configuration { 129 | fn default() -> Self { 130 | Self { 131 | temperature_oversampling: Oversampling::X_1, 132 | pressure_oversampling: Oversampling::X_1, 133 | iir_filter: IIRFilter::Disabled, 134 | } 135 | } 136 | } 137 | 138 | #[maybe_async_cfg::maybe( 139 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 140 | sync(feature = "sync"), 141 | async(feature = "async") 142 | )] 143 | impl BME280Common { 144 | /// Configures common sensor settings. Sensor must be in sleep mode for this to work. 145 | /// Check sensor mode beforehand and call [`Self::reset`] if necessary. To configure 146 | /// advanced settings, please directly update the respective registers. 147 | pub async fn configure(&mut self, config: &Configuration) -> Result<(), Error> { 148 | self.write_register( 149 | ControlMeasurement::default() 150 | .with_temperature_oversampling(config.temperature_oversampling) 151 | .with_pressure_oversampling(config.pressure_oversampling), 152 | ) 153 | .await 154 | .map_err(Error::Bus)?; 155 | 156 | let mut reg_config = self.read_register::().await.map_err(Error::Bus)?; 157 | reg_config.write_filter(config.iir_filter); 158 | self.write_register(reg_config).await.map_err(Error::Bus)?; 159 | 160 | Ok(()) 161 | } 162 | 163 | /// Performs a one-shot measurement. This will transition the device into forced mode, 164 | /// which will cause it to take a measurement and return to sleep mode afterwards. 165 | /// 166 | /// This function will wait until the data is acquired, perform a burst read 167 | /// and compensate the returned raw data using the calibration data. 168 | pub async fn measure(&mut self, delay: &mut D) -> Result> { 169 | self.write_register(ControlMeasurement::default().with_sensor_mode(SensorMode::Forced)) 170 | .await 171 | .map_err(Error::Bus)?; 172 | 173 | // Read current oversampling config to determine required measurement delay 174 | let reg_ctrl_m = self.read_register::().await.map_err(Error::Bus)?; 175 | let o_t = reg_ctrl_m.read_temperature_oversampling(); 176 | let o_p = reg_ctrl_m.read_pressure_oversampling(); 177 | 178 | // Maximum time required to perform the measurement. 179 | // See chapter 9 of the datasheet for more information. 180 | let mut max_measurement_delay_us = 1250 + 2300 * o_t.factor(); 181 | if o_p.factor() > 0 { 182 | max_measurement_delay_us += 575 + 2300 * o_p.factor(); 183 | } 184 | 185 | delay.delay_us(max_measurement_delay_us).await; 186 | 187 | let raw_data = self 188 | .read_register::() 189 | .await 190 | .map_err(Error::Bus)? 191 | .read_all(); 192 | let Some(ref cal) = self.calibration_data else { 193 | return Err(Error::NotCalibrated); 194 | }; 195 | 196 | let (temperature, t_fine) = cal.compensate_temperature(raw_data.temperature.temperature); 197 | let pressure = cal.compensate_pressure(raw_data.pressure.pressure, t_fine); 198 | 199 | Ok(Measurements { temperature, pressure }) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/bosch/bmp390/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const PRIMARY_ADDRESS: u8 = 0b1110110; 4 | const SECONDARY_ADDRESS: u8 = 0b1110111; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 7 | pub enum Address { 8 | /// Primary device address 0b1110110 (SDO connected to GND). 9 | Primary, 10 | /// Secondary device address 0b1110111 (SDO connected to V_DDIO). 11 | Secondary, 12 | /// Custom address not directly supported by the device, but may be useful 13 | /// when using address translators. 14 | Custom(u8), 15 | } 16 | 17 | impl From
for u8 { 18 | fn from(address: Address) -> Self { 19 | match address { 20 | Address::Primary => PRIMARY_ADDRESS, 21 | Address::Secondary => SECONDARY_ADDRESS, 22 | Address::Custom(x) => x, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/bosch/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "bosch-bme280")] 2 | pub mod bme280; 3 | #[cfg(feature = "bosch-bmp280")] 4 | pub mod bmp280; 5 | #[cfg(feature = "bosch-bmp390")] 6 | pub mod bmp390; 7 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/microchip/mcp3204.rs: -------------------------------------------------------------------------------- 1 | //! # MCP3204 2 | //! 3 | //! The Microchip Technology Inc. MCP3204 device is a successive approximation 12-bit Analog- 4 | //! to-Digital (A/D) Converter with on-board sample and hold circuitry. The MCP3204 is programmable 5 | //! to provide two pseudo-differential input pairs or four single-ended inputs. 6 | //! 7 | //! Differential Nonlinearity (DNL) is specified at ±1 LSB, while Integral Nonlinearity (INL) is 8 | //! offered in ±1 LSB (MCP3204-B) and ±2 LSB (MCP3204-C) versions. Communication with the devices 9 | //! is accomplished using a simple serial interface compatible with the SPI protocol. The devices 10 | //! are capable of conversion rates of up to 100 ksps. The MCP3204 devices operate over a 11 | //! broad voltage range (2.7V - 5.5V). Low current design permits operation with typical standby 12 | //! and active currents of only 500 nA and 320 μA, respectively. 13 | //! 14 | //! ## Usage (sync) 15 | //! 16 | //! ```rust, only_if(sync) 17 | //! # fn test(mut spi: I) -> Result<(), I::Error> 18 | //! # where 19 | //! # I: embedded_hal::spi::SpiDevice, 20 | //! # { 21 | //! use embedded_devices::devices::microchip::mcp3204::{MCP3204Sync, InputChannel}; 22 | //! use uom::num_rational::Rational32; 23 | //! use uom::num_traits::ToPrimitive; 24 | //! use uom::si::electric_potential::{volt, millivolt}; 25 | //! use uom::si::rational32::ElectricPotential; 26 | //! 27 | //! let mut mcp3204 = MCP3204Sync::new_spi( 28 | //! spi, 29 | //! // 2.5V reference 30 | //! ElectricPotential::new::(Rational32::new(5, 2)), 31 | //! ); 32 | //! 33 | //! let value = mcp3204.convert(InputChannel::Single0)?; 34 | //! let voltage = value.get::().to_f32(); 35 | //! println!("V_in at channel 0: {:?}mV", value); 36 | //! # Ok(()) 37 | //! # } 38 | //! ``` 39 | //! 40 | //! ## Usage (async) 41 | //! 42 | //! ```rust, only_if(async) 43 | //! # async fn test(mut spi: I) -> Result<(), I::Error> 44 | //! # where 45 | //! # I: embedded_hal_async::spi::SpiDevice, 46 | //! # { 47 | //! use embedded_devices::devices::microchip::mcp3204::{MCP3204Async, InputChannel}; 48 | //! use uom::num_rational::Rational32; 49 | //! use uom::num_traits::ToPrimitive; 50 | //! use uom::si::electric_potential::{volt, millivolt}; 51 | //! use uom::si::rational32::ElectricPotential; 52 | //! 53 | //! let mut mcp3204 = MCP3204Async::new_spi( 54 | //! spi, 55 | //! // 2.5V reference 56 | //! ElectricPotential::new::(Rational32::new(5, 2)), 57 | //! ); 58 | //! 59 | //! let value = mcp3204.convert(InputChannel::Single0).await?; 60 | //! let voltage = value.get::().to_f32(); 61 | //! println!("V_in at channel 0: {:?}mV", value); 62 | //! # Ok(()) 63 | //! # } 64 | //! ``` 65 | 66 | use uom::num_rational::Rational32; 67 | use uom::si::{electric_potential::volt, rational32::ElectricPotential}; 68 | 69 | /// The ADC input channel 70 | #[derive(Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 71 | pub enum InputChannel { 72 | /// Single channel 0 73 | Single0 = 0b1000, 74 | /// Single channel 1 75 | Single1 = 0b1001, 76 | /// Single channel 2 77 | Single2 = 0b1010, 78 | /// Single channel 3 79 | Single3 = 0b1011, 80 | /// Pseudo-differential channel (IN+ = CH0, IN- = CH1) 81 | Diff01 = 0b0000, 82 | /// Pseudo-differential channel (IN+ = CH1, IN- = CH0) 83 | Diff10 = 0b0001, 84 | /// Pseudo-differential channel (IN+ = CH2, IN- = CH3) 85 | Diff23 = 0b0010, 86 | /// Pseudo-differential channel (IN+ = CH3, IN- = CH2) 87 | Diff32 = 0b0011, 88 | } 89 | 90 | /// The MCP3204 is a 12-bit ADC with on-board sample and hold circuitry. It is programmable 91 | /// to provide two pseudo-differential input pairs or four single-ended inputs. 92 | /// 93 | /// For a full description and usage examples, refer to the [module documentation](self). 94 | #[maybe_async_cfg::maybe(sync(feature = "sync"), async(feature = "async"))] 95 | pub struct MCP3204 { 96 | /// The interface to communicate with the device 97 | interface: I, 98 | /// The reference voltage 99 | reference_voltage: ElectricPotential, 100 | } 101 | 102 | #[maybe_async_cfg::maybe( 103 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async")), 104 | sync(feature = "sync"), 105 | async(feature = "async") 106 | )] 107 | impl MCP3204 108 | where 109 | I: hal::spi::r#SpiDevice, 110 | { 111 | /// Initializes a new device from the specified SPI device. 112 | /// This consumes the SPI device `I`. 113 | /// 114 | /// The device supports SPI modes 0 and 3. 115 | #[inline] 116 | pub fn new_spi(interface: I, reference_voltage: ElectricPotential) -> Self { 117 | Self { 118 | interface, 119 | reference_voltage, 120 | } 121 | } 122 | } 123 | 124 | #[maybe_async_cfg::maybe( 125 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async")), 126 | sync(feature = "sync"), 127 | async(feature = "async") 128 | )] 129 | impl MCP3204 { 130 | /// Performs a conversion of the given channel and returns the raw value 131 | pub async fn convert_raw(&mut self, channel: InputChannel) -> Result { 132 | // Do bitwise-or with the required start bit. We also pad one leading zero so the readout 133 | // has better alignment. 134 | let command: u8 = 0b01000000 | (channel as u8) << 2; 135 | let mut data = [command, 0, 0]; 136 | self.interface.transfer_in_place(&mut data).await?; 137 | Ok((data[1] as u16) << 4 | (data[2] as u16) >> 4) 138 | } 139 | 140 | /// Performs a conversion of the given channel and returns the value in volts 141 | pub async fn convert(&mut self, channel: InputChannel) -> Result { 142 | let raw_value = self.convert_raw(channel).await?; 143 | let v_ref = self.reference_voltage.get::(); 144 | let value = 145 | ElectricPotential::new::(Rational32::new(raw_value as i32 * v_ref.numer(), 4096 * v_ref.denom())); 146 | Ok(value) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/microchip/mcp3208.rs: -------------------------------------------------------------------------------- 1 | //! # MCP3208 2 | //! 3 | //! The Microchip Technology Inc. MCP3208 device is a successive approximation 12-bit Analog- 4 | //! to-Digital (A/D) Converter with on-board sample and hold circuitry. The MCP3208 is programmable 5 | //! to provide four pseudo-differential input pairs or eight single-ended inputs. 6 | //! 7 | //! Differential Nonlinearity (DNL) is specified at ±1 LSB, while Integral Nonlinearity (INL) is 8 | //! offered in ±1 LSB (MCP3208-B) and ±2 LSB (MCP3208-C) versions. Communication with the devices 9 | //! is accomplished using a simple serial interface compatible with the SPI protocol. The devices 10 | //! are capable of conversion rates of up to 100 ksps. The MCP3208 devices operate over a 11 | //! broad voltage range (2.7V - 5.5V). Low current design permits operation with typical standby 12 | //! and active currents of only 500 nA and 320 μA, respectively. 13 | //! 14 | //! ## Usage (sync) 15 | //! 16 | //! ```rust, only_if(sync) 17 | //! # fn test(mut spi: I) -> Result<(), I::Error> 18 | //! # where 19 | //! # I: embedded_hal::spi::SpiDevice, 20 | //! # { 21 | //! use embedded_devices::devices::microchip::mcp3208::{MCP3208Sync, InputChannel}; 22 | //! use uom::num_rational::Rational32; 23 | //! use uom::num_traits::ToPrimitive; 24 | //! use uom::si::electric_potential::{volt, millivolt}; 25 | //! use uom::si::rational32::ElectricPotential; 26 | //! 27 | //! let mut mcp3208 = MCP3208Sync::new_spi( 28 | //! spi, 29 | //! // 2.5V reference 30 | //! ElectricPotential::new::(Rational32::new(5, 2)), 31 | //! ); 32 | //! 33 | //! let value = mcp3208.convert(InputChannel::Single0)?; 34 | //! let voltage = value.get::().to_f32(); 35 | //! println!("V_in at channel 0: {:?}mV", value); 36 | //! # Ok(()) 37 | //! # } 38 | //! ``` 39 | //! 40 | //! ## Usage (async) 41 | //! 42 | //! ```rust, only_if(async) 43 | //! # async fn test(mut spi: I) -> Result<(), I::Error> 44 | //! # where 45 | //! # I: embedded_hal_async::spi::SpiDevice, 46 | //! # { 47 | //! use embedded_devices::devices::microchip::mcp3208::{MCP3208Async, InputChannel}; 48 | //! use uom::num_rational::Rational32; 49 | //! use uom::num_traits::ToPrimitive; 50 | //! use uom::si::electric_potential::{volt, millivolt}; 51 | //! use uom::si::rational32::ElectricPotential; 52 | //! 53 | //! let mut mcp3208 = MCP3208Async::new_spi( 54 | //! spi, 55 | //! // 2.5V reference 56 | //! ElectricPotential::new::(Rational32::new(5, 2)), 57 | //! ); 58 | //! 59 | //! let value = mcp3208.convert(InputChannel::Single0).await?; 60 | //! let voltage = value.get::().to_f32(); 61 | //! println!("V_in at channel 0: {:?}mV", value); 62 | //! # Ok(()) 63 | //! # } 64 | //! ``` 65 | 66 | use uom::num_rational::Rational32; 67 | use uom::si::{electric_potential::volt, rational32::ElectricPotential}; 68 | 69 | /// The ADC input channel 70 | #[derive(Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 71 | pub enum InputChannel { 72 | /// Single channel 0 73 | Single0 = 0b1000, 74 | /// Single channel 1 75 | Single1 = 0b1001, 76 | /// Single channel 2 77 | Single2 = 0b1010, 78 | /// Single channel 3 79 | Single3 = 0b1011, 80 | /// Single channel 4 81 | Single4 = 0b1100, 82 | /// Single channel 5 83 | Single5 = 0b1101, 84 | /// Single channel 6 85 | Single6 = 0b1110, 86 | /// Single channel 7 87 | Single7 = 0b1111, 88 | /// Pseudo-differential channel (IN+ = CH0, IN- = CH1) 89 | Diff01 = 0b0000, 90 | /// Pseudo-differential channel (IN+ = CH1, IN- = CH0) 91 | Diff10 = 0b0001, 92 | /// Pseudo-differential channel (IN+ = CH2, IN- = CH3) 93 | Diff23 = 0b0010, 94 | /// Pseudo-differential channel (IN+ = CH3, IN- = CH2) 95 | Diff32 = 0b0011, 96 | /// Pseudo-differential channel (IN+ = CH4, IN- = CH5) 97 | Diff45 = 0b0100, 98 | /// Pseudo-differential channel (IN+ = CH5, IN- = CH4) 99 | Diff54 = 0b0101, 100 | /// Pseudo-differential channel (IN+ = CH6, IN- = CH7) 101 | Diff67 = 0b0110, 102 | /// Pseudo-differential channel (IN+ = CH7, IN- = CH6) 103 | Diff76 = 0b0111, 104 | } 105 | 106 | /// The MCP3208 is a 12-bit ADC with on-board sample and hold circuitry. It is programmable 107 | /// to provide four pseudo-differential input pairs or eight single-ended inputs. 108 | /// 109 | /// For a full description and usage examples, refer to the [module documentation](self). 110 | #[maybe_async_cfg::maybe(sync(feature = "sync"), async(feature = "async"))] 111 | pub struct MCP3208 { 112 | /// The interface to communicate with the device 113 | interface: I, 114 | /// The reference voltage 115 | reference_voltage: ElectricPotential, 116 | } 117 | 118 | #[maybe_async_cfg::maybe( 119 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async")), 120 | sync(feature = "sync"), 121 | async(feature = "async") 122 | )] 123 | impl MCP3208 124 | where 125 | I: hal::spi::r#SpiDevice, 126 | { 127 | /// Initializes a new device from the specified SPI device. 128 | /// This consumes the SPI device `I`. 129 | /// 130 | /// The device supports SPI modes 0 and 3. 131 | #[inline] 132 | pub fn new_spi(interface: I, reference_voltage: ElectricPotential) -> Self { 133 | Self { 134 | interface, 135 | reference_voltage, 136 | } 137 | } 138 | } 139 | 140 | #[maybe_async_cfg::maybe( 141 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async")), 142 | sync(feature = "sync"), 143 | async(feature = "async") 144 | )] 145 | impl MCP3208 { 146 | /// Performs a conversion of the given channel and returns the raw value 147 | pub async fn convert_raw(&mut self, channel: InputChannel) -> Result { 148 | // Do bitwise-or with the required start bit. We also pad one leading zero so the readout 149 | // has better alignment. 150 | let command: u8 = 0b01000000 | (channel as u8) << 2; 151 | let mut data = [command, 0, 0]; 152 | self.interface.transfer_in_place(&mut data).await?; 153 | Ok((data[1] as u16) << 4 | (data[2] as u16) >> 4) 154 | } 155 | 156 | /// Performs a conversion of the given channel and returns the value in volts 157 | pub async fn convert(&mut self, channel: InputChannel) -> Result { 158 | let raw_value = self.convert_raw(channel).await?; 159 | let v_ref = self.reference_voltage.get::(); 160 | let value = 161 | ElectricPotential::new::(Rational32::new(raw_value as i32 * v_ref.numer(), 4096 * v_ref.denom())); 162 | Ok(value) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/microchip/mcp9808/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const DEFAULT_ADDRESS: u8 = 0b11000; 4 | 5 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 6 | pub enum Address { 7 | /// Default device address 0b11000, all address selection pins connected to GND. 8 | Default, 9 | /// Alternative device address corresponding to the given pin configuration A2, A1, A0. 10 | /// The address pins directly correspond to the least significant bits of the address, 11 | /// so the resulting address is `0b11{a2}{a1}{a0}`. 12 | Alternative { a2: bool, a1: bool, a0: bool }, 13 | /// Custom address not directly supported by the device, but may be useful 14 | /// when using I2C address translators. 15 | Custom(u8), 16 | } 17 | 18 | impl From
for u8 { 19 | fn from(address: Address) -> Self { 20 | match address { 21 | Address::Default => DEFAULT_ADDRESS, 22 | Address::Alternative { a2, a1, a0 } => DEFAULT_ADDRESS | (a2 as u8) << 2 | (a1 as u8) << 1 | (a0 as u8), 23 | Address::Custom(x) => x, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/microchip/mcp9808/mod.rs: -------------------------------------------------------------------------------- 1 | //! # MCP9808 2 | //! 3 | //! Microchip Technology Inc.'s MCP9808 digital temperature sensor converts temperatures between 4 | //! -20°C and +100°C to a digital word with ±0.25°C/±0.5°C (typical/maximum) accuracy. 5 | //! 6 | //! The MCP9808 comes with user-programmable registers that provide flexibility for temperature sensing 7 | //! applications. The registers allow user-selectable settings such as Shutdown or Low-Power modes and 8 | //! the specification of temperature Alert window limits and critical output limits. 9 | //! 10 | //! When the temperature changes beyond the specified boundary limits, the MCP9808 outputs an Alert signal. 11 | //! The user has the option of setting the Alert output signal polarity as an active-low or active-high 12 | //! comparator output for thermostat operation, or as a temperature Alert interrupt output for 13 | //! microprocessor-based systems. The Alert output can also be configured as a critical temperature output only. 14 | //! 15 | //! This sensor has an industry standard 400 kHz, 2-wire, SMBus/I2C compatible serial interface, 16 | //! allowing up to eight or sixteen sensors to be controlled with a single serial bus. 17 | //! These features make the MCP9808 ideal for sophisticated, multi-zone, temperature-monitoring applications. 18 | //! 19 | //! ## Usage (sync) 20 | //! 21 | //! ```rust, only_if(sync) 22 | //! # fn test(mut i2c: I) -> Result<(), I::Error> 23 | //! # where 24 | //! # I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType 25 | //! # { 26 | //! use embedded_devices::devices::microchip::mcp9808::{MCP9808Sync, address::Address, registers::AmbientTemperature}; 27 | //! use uom::si::thermodynamic_temperature::degree_celsius; 28 | //! use uom::num_traits::ToPrimitive; 29 | //! 30 | //! // Create and initialize the device 31 | //! let mut mcp9808 = MCP9808Sync::new_i2c(i2c, Address::Default); 32 | //! mcp9808.init().unwrap(); 33 | //! 34 | //! // Read the current temperature in °C and convert it to a float 35 | //! let temp = mcp9808 36 | //! .read_register::()? 37 | //! .read_temperature() 38 | //! .get::() 39 | //! .to_f32(); 40 | //! println!("Current temperature: {:?}°C", temp); 41 | //! # Ok(()) 42 | //! # } 43 | //! ``` 44 | //! 45 | //! ## Usage (async) 46 | //! 47 | //! ```rust, only_if(async) 48 | //! # async fn test(mut i2c: I) -> Result<(), I::Error> 49 | //! # where 50 | //! # I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType 51 | //! # { 52 | //! use embedded_devices::devices::microchip::mcp9808::{MCP9808Async, address::Address, registers::AmbientTemperature}; 53 | //! use uom::si::thermodynamic_temperature::degree_celsius; 54 | //! use uom::num_traits::ToPrimitive; 55 | //! 56 | //! // Create and initialize the device 57 | //! let mut mcp9808 = MCP9808Async::new_i2c(i2c, Address::Default); 58 | //! mcp9808.init().await.unwrap(); 59 | //! 60 | //! // Read the current temperature in °C and convert it to a float 61 | //! let temp = mcp9808 62 | //! .read_register::() 63 | //! .await? 64 | //! .read_temperature() 65 | //! .get::() 66 | //! .to_f32(); 67 | //! println!("Current temperature: {:?}°C", temp); 68 | //! # Ok(()) 69 | //! # } 70 | //! ``` 71 | 72 | use embedded_devices_derive::{device, device_impl}; 73 | 74 | pub mod address; 75 | pub mod registers; 76 | 77 | type MCP9808I2cCodec = embedded_registers::i2c::codecs::OneByteRegAddrCodec; 78 | 79 | /// All possible errors that may occur in device initialization 80 | #[derive(Debug, defmt::Format)] 81 | pub enum InitError { 82 | /// Bus error 83 | Bus(BusError), 84 | /// Invalid Device Id was encountered 85 | InvalidDeviceId, 86 | /// Invalid Manufacturer Id was encountered 87 | InvalidManufacturerId, 88 | } 89 | 90 | /// Microchip Technology Inc.'s MCP9808 digital temperature sensor converts temperatures between 91 | /// -20°C and +100°C to a digital word with ±0.25°C/±0.5°C (typical/maximum) accuracy. 92 | /// 93 | /// For a full description and usage examples, refer to the [module documentation](self). 94 | #[device] 95 | #[maybe_async_cfg::maybe( 96 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 97 | sync(feature = "sync"), 98 | async(feature = "async") 99 | )] 100 | pub struct MCP9808 { 101 | /// The interface to communicate with the device 102 | interface: I, 103 | } 104 | 105 | #[maybe_async_cfg::maybe( 106 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice), 107 | sync(feature = "sync"), 108 | async(feature = "async") 109 | )] 110 | impl MCP9808> 111 | where 112 | I: hal::i2c::I2c + hal::i2c::ErrorType, 113 | { 114 | /// Initializes a new device with the given address on the specified bus. 115 | /// This consumes the I2C bus `I`. 116 | /// 117 | /// Before using this device, you should call the [`Self::init`] method which 118 | /// initializes the device and ensures that it is working correctly. 119 | #[inline] 120 | pub fn new_i2c(interface: I, address: self::address::Address) -> Self { 121 | Self { 122 | interface: embedded_registers::i2c::I2cDevice::new(interface, address.into()), 123 | } 124 | } 125 | } 126 | 127 | #[device_impl] 128 | #[maybe_async_cfg::maybe( 129 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 130 | sync(feature = "sync"), 131 | async(feature = "async") 132 | )] 133 | impl MCP9808 { 134 | /// Initializes the sensor by verifying its device id and manufacturer id. 135 | /// Not mandatory, but recommended. 136 | pub async fn init(&mut self) -> Result<(), InitError> { 137 | use self::registers::DeviceIdRevision; 138 | use self::registers::ManufacturerId; 139 | 140 | let device_id = self.read_register::().await.map_err(InitError::Bus)?; 141 | if device_id.read_device_id() != self::registers::DEVICE_ID_VALID { 142 | return Err(InitError::InvalidDeviceId); 143 | } 144 | 145 | let manufacturer_id = self.read_register::().await.map_err(InitError::Bus)?; 146 | if manufacturer_id.read_manufacturer_id() != self::registers::MANUFACTURER_ID_VALID { 147 | return Err(InitError::InvalidManufacturerId); 148 | } 149 | 150 | Ok(()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/microchip/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "microchip-mcp3204")] 2 | pub mod mcp3204; 3 | #[cfg(feature = "microchip-mcp3208")] 4 | pub mod mcp3208; 5 | #[cfg(feature = "microchip-mcp9808")] 6 | pub mod mcp9808; 7 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod analog_devices; 2 | pub mod bosch; 3 | pub mod microchip; 4 | pub mod sensirion; 5 | pub mod texas_instruments; 6 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/sensirion/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "sensirion-scd4x")] 2 | pub mod scd4x; 3 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/sensirion/scd4x/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const ADDRESS: u8 = 0x62; 4 | 5 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 6 | pub enum Address { 7 | /// Default address 8 | Default, 9 | /// Custom address not directly supported by the device, but may be useful 10 | /// when using I2C address translators. 11 | Custom(u8), 12 | } 13 | 14 | impl From
for u8 { 15 | fn from(address: Address) -> Self { 16 | match address { 17 | Address::Default => ADDRESS, 18 | Address::Custom(x) => x, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/ina219/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const ADDRESS_A0_GND_A1_GND: u8 = 0b1000000; 4 | const ADDRESS_A0_VCC_A1_GND: u8 = 0b1000001; 5 | const ADDRESS_A0_SDA_A1_GND: u8 = 0b1000010; 6 | const ADDRESS_A0_SCL_A1_GND: u8 = 0b1000011; 7 | const ADDRESS_A0_GND_A1_VCC: u8 = 0b1000100; 8 | const ADDRESS_A0_VCC_A1_VCC: u8 = 0b1000101; 9 | const ADDRESS_A0_SDA_A1_VCC: u8 = 0b1000110; 10 | const ADDRESS_A0_SCL_A1_VCC: u8 = 0b1000111; 11 | const ADDRESS_A0_GND_A1_SDA: u8 = 0b1001000; 12 | const ADDRESS_A0_VCC_A1_SDA: u8 = 0b1001001; 13 | const ADDRESS_A0_SDA_A1_SDA: u8 = 0b1001010; 14 | const ADDRESS_A0_SCL_A1_SDA: u8 = 0b1001011; 15 | const ADDRESS_A0_GND_A1_SCL: u8 = 0b1001100; 16 | const ADDRESS_A0_VCC_A1_SCL: u8 = 0b1001101; 17 | const ADDRESS_A0_SDA_A1_SCL: u8 = 0b1001110; 18 | const ADDRESS_A0_SCL_A1_SCL: u8 = 0b1001111; 19 | 20 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 21 | pub enum Pin { 22 | /// Address pin connected to GND 23 | Gnd, 24 | /// Address pin connected to VCC 25 | Vcc, 26 | /// Address pin connected to SDA 27 | Sda, 28 | /// Address pin connected to SCL 29 | Scl, 30 | } 31 | 32 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 33 | pub enum Address { 34 | /// Address selection pins A0 and A1 tied to specific pins 35 | A0A1(Pin, Pin), 36 | /// Custom address not directly supported by the device, but may be useful 37 | /// when using I2C address translators. 38 | Custom(u8), 39 | } 40 | 41 | impl From
for u8 { 42 | fn from(address: Address) -> Self { 43 | match address { 44 | Address::A0A1(Pin::Gnd, Pin::Gnd) => ADDRESS_A0_GND_A1_GND, 45 | Address::A0A1(Pin::Vcc, Pin::Gnd) => ADDRESS_A0_VCC_A1_GND, 46 | Address::A0A1(Pin::Sda, Pin::Gnd) => ADDRESS_A0_SDA_A1_GND, 47 | Address::A0A1(Pin::Scl, Pin::Gnd) => ADDRESS_A0_SCL_A1_GND, 48 | Address::A0A1(Pin::Gnd, Pin::Vcc) => ADDRESS_A0_GND_A1_VCC, 49 | Address::A0A1(Pin::Vcc, Pin::Vcc) => ADDRESS_A0_VCC_A1_VCC, 50 | Address::A0A1(Pin::Sda, Pin::Vcc) => ADDRESS_A0_SDA_A1_VCC, 51 | Address::A0A1(Pin::Scl, Pin::Vcc) => ADDRESS_A0_SCL_A1_VCC, 52 | Address::A0A1(Pin::Gnd, Pin::Sda) => ADDRESS_A0_GND_A1_SDA, 53 | Address::A0A1(Pin::Vcc, Pin::Sda) => ADDRESS_A0_VCC_A1_SDA, 54 | Address::A0A1(Pin::Sda, Pin::Sda) => ADDRESS_A0_SDA_A1_SDA, 55 | Address::A0A1(Pin::Scl, Pin::Sda) => ADDRESS_A0_SCL_A1_SDA, 56 | Address::A0A1(Pin::Gnd, Pin::Scl) => ADDRESS_A0_GND_A1_SCL, 57 | Address::A0A1(Pin::Vcc, Pin::Scl) => ADDRESS_A0_VCC_A1_SCL, 58 | Address::A0A1(Pin::Sda, Pin::Scl) => ADDRESS_A0_SDA_A1_SCL, 59 | Address::A0A1(Pin::Scl, Pin::Scl) => ADDRESS_A0_SCL_A1_SCL, 60 | Address::Custom(x) => x, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/ina219/registers.rs: -------------------------------------------------------------------------------- 1 | use bondrewd::BitfieldEnum; 2 | use embedded_devices_derive::device_register; 3 | use embedded_registers::register; 4 | use uom::num_rational::Rational32; 5 | use uom::si::electric_current::ampere; 6 | use uom::si::power::watt; 7 | use uom::si::rational32::ElectricCurrent; 8 | use uom::si::{electric_potential::volt, rational32::ElectricPotential}; 9 | 10 | /// Valid bus voltage ranges. 11 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 12 | #[bondrewd_enum(u8)] 13 | #[allow(non_camel_case_types)] 14 | pub enum BusVoltageRange { 15 | U_16V = 0, 16 | U_32V = 1, 17 | } 18 | 19 | /// PGA settings 20 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 21 | #[bondrewd_enum(u8)] 22 | #[allow(non_camel_case_types)] 23 | pub enum PgaGain { 24 | /// Gain 1, range ±40mV 25 | DIV_1 = 0b00, 26 | /// Gain 1/2, range ±80mV 27 | DIV_2 = 0b01, 28 | /// Gain 1/4, range ±160mV 29 | DIV_4 = 0b10, 30 | /// Gain 1/8, range ±320mV 31 | DIV_8 = 0b11, 32 | } 33 | 34 | /// Bus/Shunt ADC settings (resolution and averaging) 35 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 36 | #[bondrewd_enum(u8)] 37 | #[allow(non_camel_case_types)] 38 | pub enum AdcResolution { 39 | /// 9bit resolution, 1 sample, 84 µs conversion time 40 | B_9 = 0b0000, 41 | /// Secondary representation for [`Self::B_9`] 42 | B_9_a = 0b0100, 43 | /// 10 bit resolution, 1 sample, 148 μs conversion time 44 | B_10 = 0b0001, 45 | /// Secondary representation for [`Self::B_10`] 46 | B_10_a = 0b0101, 47 | /// 11 bit resolution, 1 sample, 276 μs conversion time 48 | B_11 = 0b0010, 49 | /// Secondary representation for [`Self::B_11`] 50 | B_11_a = 0b0110, 51 | /// 12 bit resolution, 1 sample, 532 μs conversion time 52 | B_12 = 0b0011, 53 | /// Secondary representation for [`Self::B_12`] 54 | B_12_a = 0b0111, 55 | /// Tertiary representation for [`Self::B_12`] 56 | B_12_b = 0b1000, 57 | /// 12 bit resolution, 2 sample, 1.06 ms conversion time 58 | B_12_X_2 = 0b1001, 59 | /// 12 bit resolution, 4 sample, 2.13 ms conversion time 60 | B_12_X_4 = 0b1010, 61 | /// 12 bit resolution, 8 sample, 4.26 ms conversion time 62 | B_12_X_8 = 0b1011, 63 | /// 12 bit resolution, 16 sample, 8.51 ms conversion time 64 | B_12_X_16 = 0b1100, 65 | /// 12 bit resolution, 32 sample, 17.02 ms conversion time 66 | B_12_X_32 = 0b1101, 67 | /// 12 bit resolution, 64 sample, 34.05 ms conversion time 68 | B_12_X_64 = 0b1110, 69 | /// 12 bit resolution, 128 sample, 68.10 ms conversion time 70 | B_12_X_128 = 0b1111, 71 | } 72 | 73 | impl AdcResolution { 74 | /// Returns the associated conversion time 75 | pub fn conversion_time_us(&self) -> u32 { 76 | match self { 77 | AdcResolution::B_9 => 84, 78 | AdcResolution::B_9_a => 84, 79 | AdcResolution::B_10 => 148, 80 | AdcResolution::B_10_a => 148, 81 | AdcResolution::B_11 => 276, 82 | AdcResolution::B_11_a => 276, 83 | AdcResolution::B_12 => 532, 84 | AdcResolution::B_12_a => 532, 85 | AdcResolution::B_12_b => 532, 86 | AdcResolution::B_12_X_2 => 1_060, 87 | AdcResolution::B_12_X_4 => 2_130, 88 | AdcResolution::B_12_X_8 => 4_260, 89 | AdcResolution::B_12_X_16 => 8_510, 90 | AdcResolution::B_12_X_32 => 17_020, 91 | AdcResolution::B_12_X_64 => 34_050, 92 | AdcResolution::B_12_X_128 => 68_100, 93 | } 94 | } 95 | } 96 | 97 | /// Operating mode. 98 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 99 | #[bondrewd_enum(u8)] 100 | pub enum OperatingMode { 101 | PowerDown = 0b000, 102 | ShuntTriggered = 0b001, 103 | BusTriggered = 0b010, 104 | ShuntAndBusTriggered = 0b011, 105 | AdcDisable = 0b100, 106 | ShuntContinuous = 0b101, 107 | BusContinuous = 0b110, 108 | ShuntAndBusContinuous = 0b111, 109 | } 110 | 111 | /// All-register reset, settings for bus voltage range, PGA Gain, ADC resolution/averaging 112 | #[device_register(super::INA219)] 113 | #[register(address = 0b000, mode = "rw")] 114 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 115 | pub struct Configuration { 116 | /// Setting this flag generates a system reset that is the same 117 | /// as power-on reset. Resets all registers to default values. 118 | /// This bit self-clears. 119 | #[register(default = false)] 120 | pub reset: bool, 121 | #[bondrewd(bit_length = 1, reserve)] 122 | #[allow(dead_code)] 123 | pub reserved: u8, 124 | /// The range for the bus voltage measurement 125 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 126 | #[register(default = BusVoltageRange::U_32V)] 127 | pub bus_voltage_range: BusVoltageRange, 128 | /// Sets PGA gain and range. Note that the PGA defaults to ÷8 (320mV range). 129 | #[bondrewd(enum_primitive = "u8", bit_length = 2)] 130 | #[register(default = PgaGain::DIV_8)] 131 | pub pga_gain: PgaGain, 132 | /// Bus ADC Resolution/Averaging setting 133 | #[bondrewd(enum_primitive = "u8", bit_length = 4)] 134 | #[register(default = AdcResolution::B_12)] 135 | pub bus_adc_resolution: AdcResolution, 136 | /// Shunt ADC Resolution/Averaging setting 137 | #[bondrewd(enum_primitive = "u8", bit_length = 4)] 138 | #[register(default = AdcResolution::B_12)] 139 | pub shunt_adc_resolution: AdcResolution, 140 | /// Selects continuous, triggered, or power-down mode of operation. 141 | #[bondrewd(enum_primitive = "u8", bit_length = 3)] 142 | #[register(default = OperatingMode::ShuntAndBusContinuous)] 143 | pub operating_mode: OperatingMode, 144 | } 145 | 146 | /// Shunt voltage measurement data 147 | #[device_register(super::INA219)] 148 | #[register(address = 0b001, mode = "r")] 149 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 150 | pub struct ShuntVoltage { 151 | /// The raw voltage measurement with 10µV/LSB resolution 152 | #[register(default = 0)] 153 | pub raw_value: i16, 154 | } 155 | 156 | impl ShuntVoltage { 157 | /// Read the shunt voltage 158 | pub fn read_voltage(&self) -> ElectricPotential { 159 | ElectricPotential::new::(Rational32::new(self.read_raw_value() as i32, 100_000)) 160 | } 161 | } 162 | 163 | /// Bus voltage measurement data 164 | #[device_register(super::INA219)] 165 | #[register(address = 0b010, mode = "r")] 166 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 167 | pub struct BusVoltage { 168 | /// The raw voltage measurement with 4mV/LSB resolution 169 | #[bondrewd(bit_length = 13)] 170 | #[register(default = 0)] 171 | pub raw_value: u16, 172 | #[bondrewd(bit_length = 1, reserve)] 173 | #[allow(dead_code)] 174 | pub reserved: u8, 175 | /// Although the data from the last conversion can be read at any time, 176 | /// this flag indicates when data from a conversion is available in the 177 | /// data output registers. This flag bit is set after all conversions, 178 | /// averaging, and multiplications are complete. 179 | /// 180 | /// It will auto-clear when reading the [`Power`] register or 181 | /// when writing a new mode into the Operating Mode bits in the 182 | /// [`Configuration`] register (except for PowerDown or Disable). 183 | #[register(default = false)] 184 | pub conversion_ready: bool, 185 | /// This flag is set when the Power or Current calculations are 186 | /// out of range. It indicates that current and power data may be meaningless. 187 | #[register(default = false)] 188 | pub overflow: bool, 189 | } 190 | 191 | impl BusVoltage { 192 | /// Read the bus voltage 193 | pub fn read_voltage(&self) -> ElectricPotential { 194 | ElectricPotential::new::(Rational32::new(self.read_raw_value() as i32, 250)) 195 | } 196 | } 197 | 198 | /// Power measurement data 199 | #[device_register(super::INA219)] 200 | #[register(address = 0b011, mode = "r")] 201 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 202 | pub struct Power { 203 | /// The raw power measurement, W/LSB is determined by calibration register 204 | #[register(default = 0)] 205 | pub raw_value: i16, 206 | } 207 | 208 | impl Power { 209 | /// Read the power, current_lsb_na is the calibrated amount of nA/LSB 210 | /// for the current register which is used to derive the nW/LSB for the power register 211 | pub fn read_power(&self, current_lsb_na: u32) -> uom::si::rational32::Power { 212 | // nW/LSB = 20 * nA/LSB, we divide by 50 instead to have the result in µW 213 | // which fits better in a u32. 214 | uom::si::rational32::Power::new::(Rational32::new( 215 | self.read_raw_value() as i32 * current_lsb_na as i32 / 50, 216 | 1_000_000, 217 | )) 218 | } 219 | } 220 | 221 | /// Contains the amount of current flowing through the shunt resistor 222 | #[device_register(super::INA219)] 223 | #[register(address = 0b100, mode = "r")] 224 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 225 | pub struct Current { 226 | /// The raw current measurement, A/LSB is determined by calibration register 227 | #[register(default = 0)] 228 | pub raw_value: i16, 229 | } 230 | 231 | impl Current { 232 | /// Read the current, current_lsb_na is the calibrated amount of nA/LSB 233 | /// for the current register. 234 | pub fn read_current(&self, current_lsb_na: u32) -> ElectricCurrent { 235 | // We pre-divide by 1000 to have the result in µA which fits better in a u32. 236 | ElectricCurrent::new::(Rational32::new( 237 | self.read_raw_value() as i32 * current_lsb_na as i32 / 1000, 238 | 1_000_000, 239 | )) 240 | } 241 | } 242 | 243 | /// Sets full-scale range and LSB of current and power measurements 244 | /// This register sets the current that corresponds to a full-scale drop across 245 | /// the shunt. Full-scale range and the LSB of the current and power measurement 246 | /// depend on the value entered in this register. 247 | #[device_register(super::INA219)] 248 | #[register(address = 0b101, mode = "rw")] 249 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 250 | pub struct Calibration { 251 | /// The raw calibration value 252 | #[bondrewd(bit_length = 15)] 253 | #[register(default = 0)] 254 | pub raw_value: u16, 255 | #[bondrewd(bit_length = 1, reserve)] 256 | #[allow(dead_code)] 257 | pub reserved: u8, 258 | } 259 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/ina228/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const ADDRESS_A0_GND_A1_GND: u8 = 0b1000000; 4 | const ADDRESS_A0_VCC_A1_GND: u8 = 0b1000001; 5 | const ADDRESS_A0_SDA_A1_GND: u8 = 0b1000010; 6 | const ADDRESS_A0_SCL_A1_GND: u8 = 0b1000011; 7 | const ADDRESS_A0_GND_A1_VCC: u8 = 0b1000100; 8 | const ADDRESS_A0_VCC_A1_VCC: u8 = 0b1000101; 9 | const ADDRESS_A0_SDA_A1_VCC: u8 = 0b1000110; 10 | const ADDRESS_A0_SCL_A1_VCC: u8 = 0b1000111; 11 | const ADDRESS_A0_GND_A1_SDA: u8 = 0b1001000; 12 | const ADDRESS_A0_VCC_A1_SDA: u8 = 0b1001001; 13 | const ADDRESS_A0_SDA_A1_SDA: u8 = 0b1001010; 14 | const ADDRESS_A0_SCL_A1_SDA: u8 = 0b1001011; 15 | const ADDRESS_A0_GND_A1_SCL: u8 = 0b1001100; 16 | const ADDRESS_A0_VCC_A1_SCL: u8 = 0b1001101; 17 | const ADDRESS_A0_SDA_A1_SCL: u8 = 0b1001110; 18 | const ADDRESS_A0_SCL_A1_SCL: u8 = 0b1001111; 19 | 20 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 21 | pub enum Pin { 22 | /// Address pin connected to GND 23 | Gnd, 24 | /// Address pin connected to VCC 25 | Vcc, 26 | /// Address pin connected to SDA 27 | Sda, 28 | /// Address pin connected to SCL 29 | Scl, 30 | } 31 | 32 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 33 | pub enum Address { 34 | /// Address selection pins A0 and A1 tied to specific pins 35 | A0A1(Pin, Pin), 36 | /// Custom address not directly supported by the device, but may be useful 37 | /// when using I2C address translators. 38 | Custom(u8), 39 | } 40 | 41 | impl From
for u8 { 42 | fn from(address: Address) -> Self { 43 | match address { 44 | Address::A0A1(Pin::Gnd, Pin::Gnd) => ADDRESS_A0_GND_A1_GND, 45 | Address::A0A1(Pin::Vcc, Pin::Gnd) => ADDRESS_A0_VCC_A1_GND, 46 | Address::A0A1(Pin::Sda, Pin::Gnd) => ADDRESS_A0_SDA_A1_GND, 47 | Address::A0A1(Pin::Scl, Pin::Gnd) => ADDRESS_A0_SCL_A1_GND, 48 | Address::A0A1(Pin::Gnd, Pin::Vcc) => ADDRESS_A0_GND_A1_VCC, 49 | Address::A0A1(Pin::Vcc, Pin::Vcc) => ADDRESS_A0_VCC_A1_VCC, 50 | Address::A0A1(Pin::Sda, Pin::Vcc) => ADDRESS_A0_SDA_A1_VCC, 51 | Address::A0A1(Pin::Scl, Pin::Vcc) => ADDRESS_A0_SCL_A1_VCC, 52 | Address::A0A1(Pin::Gnd, Pin::Sda) => ADDRESS_A0_GND_A1_SDA, 53 | Address::A0A1(Pin::Vcc, Pin::Sda) => ADDRESS_A0_VCC_A1_SDA, 54 | Address::A0A1(Pin::Sda, Pin::Sda) => ADDRESS_A0_SDA_A1_SDA, 55 | Address::A0A1(Pin::Scl, Pin::Sda) => ADDRESS_A0_SCL_A1_SDA, 56 | Address::A0A1(Pin::Gnd, Pin::Scl) => ADDRESS_A0_GND_A1_SCL, 57 | Address::A0A1(Pin::Vcc, Pin::Scl) => ADDRESS_A0_VCC_A1_SCL, 58 | Address::A0A1(Pin::Sda, Pin::Scl) => ADDRESS_A0_SDA_A1_SCL, 59 | Address::A0A1(Pin::Scl, Pin::Scl) => ADDRESS_A0_SCL_A1_SCL, 60 | Address::Custom(x) => x, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "texas_instruments-ina219")] 2 | pub mod ina219; 3 | #[cfg(feature = "texas_instruments-ina228")] 4 | pub mod ina228; 5 | #[cfg(feature = "texas_instruments-tmp102")] 6 | pub mod tmp102; 7 | #[cfg(feature = "texas_instruments-tmp117")] 8 | pub mod tmp117; 9 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/tmp102/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const ADDRESS_GND: u8 = 0b1001000; // 0x48 4 | const ADDRESS_VCC: u8 = 0b1001001; // 0x49 5 | const ADDRESS_SDA: u8 = 0b1001010; // 0x4A 6 | const ADDRESS_SCL: u8 = 0b1001011; // 0x4B 7 | 8 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 9 | pub enum Address { 10 | /// Address selection pin connected to GND 11 | Gnd, 12 | /// Address selection pin connected to VCC 13 | Vcc, 14 | /// Address selection pin connected to SDA 15 | Sda, 16 | /// Address selection pin connected to SCL 17 | Scl, 18 | /// Custom address not directly supported by the device, but may be useful 19 | /// when using I2C address translators. 20 | Custom(u8), 21 | } 22 | 23 | impl From
for u8 { 24 | fn from(address: Address) -> Self { 25 | match address { 26 | Address::Gnd => ADDRESS_GND, 27 | Address::Vcc => ADDRESS_VCC, 28 | Address::Sda => ADDRESS_SDA, 29 | Address::Scl => ADDRESS_SCL, 30 | Address::Custom(x) => x, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/tmp102/mod.rs: -------------------------------------------------------------------------------- 1 | //! # TMP102 2 | //! 3 | //! The TMP102 device is a digital temperature sensor designed for NTC/PTC 4 | //! thermistor replacement where high accuracy is required. The device offers an 5 | //! accuracy of ±0.5°C without requiring calibration or external component 6 | //! signal conditioning. Device temperature sensors are highly linear and do not 7 | //! require complex calculations or lookup tables to derive the temperature. The 8 | //! on-chip 12-bit ADC offers resolutions down to 0.0625°C. 9 | //! 10 | //! ## Usage (sync) 11 | //! 12 | //! ```rust, no_run, only_if(sync) 13 | //! # fn test(mut i2c: I, mut Delay: D) -> Result<(), I::Error> 14 | //! # where 15 | //! # I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType, 16 | //! # D: embedded_hal::delay::DelayNs 17 | //! # { 18 | //! use embedded_devices::devices::texas_instruments::tmp102::{TMP102Sync, address::Address, registers::Temperature}; 19 | //! use uom::si::thermodynamic_temperature::degree_celsius; 20 | //! use uom::num_traits::ToPrimitive; 21 | //! 22 | //! // Create and initialize the device. Default conversion mode is continuous. 23 | //! let mut tmp102 = TMP102Sync::new_i2c(i2c, Address::Gnd); 24 | //! 25 | //! // Read the latest temperature conversion in °C and convert it to a float 26 | //! let temp = tmp102 27 | //! .read_temperature()? 28 | //! .get::() 29 | //! .to_f32(); 30 | //! println!("Current temperature: {:?}°C", temp); 31 | //! 32 | //! // Perform a one-shot measurement now and return to sleep afterwards. 33 | //! let temp = tmp102.oneshot(&mut Delay)?.get::().to_f32(); 34 | //! println!("Oneshot temperature: {:?}°C", temp); 35 | //! # Ok(()) 36 | //! # } 37 | //! ``` 38 | //! 39 | //! ## Usage (async) 40 | //! 41 | //! ```rust, no_run, only_if(async) 42 | //! # async fn test(mut i2c: I, mut Delay: D) -> Result<(), I::Error> 43 | //! # where 44 | //! # I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType, 45 | //! # D: embedded_hal_async::delay::DelayNs 46 | //! # { 47 | //! use embedded_devices::devices::texas_instruments::tmp102::{TMP102Async, address::Address, registers::Temperature}; 48 | //! use uom::si::thermodynamic_temperature::degree_celsius; 49 | //! use uom::num_traits::ToPrimitive; 50 | //! 51 | //! // Create and initialize the device. Default conversion mode is continuous. 52 | //! let mut tmp102 = TMP102Async::new_i2c(i2c, Address::Gnd); 53 | //! 54 | //! // Read the latest temperature conversion in °C and convert it to a float 55 | //! let temp = tmp102 56 | //! .read_temperature().await? 57 | //! .get::() 58 | //! .to_f32(); 59 | //! println!("Current temperature: {:?}°C", temp); 60 | //! 61 | //! // Perform a one-shot measurement now and return to sleep afterwards. 62 | //! let temp = tmp102.oneshot(&mut Delay).await?.get::().to_f32(); 63 | //! println!("Oneshot temperature: {:?}°C", temp); 64 | //! # Ok(()) 65 | //! # } 66 | //! ``` 67 | 68 | use embedded_devices_derive::{device, device_impl}; 69 | use uom::num_rational::Rational32; 70 | use uom::si::rational32::ThermodynamicTemperature; 71 | use uom::si::thermodynamic_temperature::degree_celsius; 72 | 73 | pub mod address; 74 | pub mod registers; 75 | 76 | type TMP102I2cCodec = embedded_registers::i2c::codecs::OneByteRegAddrCodec; 77 | 78 | /// The TMP102 is a general purpose digital temperature sensor. It provides a 13-bit 79 | /// temperature result with a resolution of 0.0625 °C and an accuracy of up to ±3 °C across the 80 | /// temperature range of –40 °C to 125 °C with no calibration. 81 | /// 82 | /// For a full description and usage examples, refer to the [module documentation](self). 83 | #[device] 84 | #[maybe_async_cfg::maybe( 85 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 86 | sync(feature = "sync"), 87 | async(feature = "async") 88 | )] 89 | pub struct TMP102 { 90 | /// The interface to communicate with the device 91 | interface: I, 92 | } 93 | 94 | #[maybe_async_cfg::maybe( 95 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice), 96 | sync(feature = "sync"), 97 | async(feature = "async") 98 | )] 99 | impl TMP102> 100 | where 101 | I: hal::i2c::I2c + hal::i2c::ErrorType, 102 | { 103 | /// Initializes a new device with the given address on the specified bus. 104 | /// This consumes the I2C bus `I`. 105 | #[inline] 106 | pub fn new_i2c(interface: I, address: self::address::Address) -> Self { 107 | Self { 108 | interface: embedded_registers::i2c::I2cDevice::new(interface, address.into()), 109 | } 110 | } 111 | } 112 | 113 | #[device_impl] 114 | #[maybe_async_cfg::maybe( 115 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 116 | sync(feature = "sync"), 117 | async(feature = "async") 118 | )] 119 | impl TMP102 { 120 | /// Performs a one-shot measurement. This will set `shutdown` in [`self::registers::Configuration´]. 121 | /// which will cause the device to perform a single conversion a return to sleep mode afterwards. 122 | /// 123 | /// This function will initialize the measurement, wait until the data is acquired and return 124 | /// the temperature. 125 | pub async fn oneshot( 126 | &mut self, 127 | delay: &mut D, 128 | ) -> Result { 129 | use self::registers::{Configuration, Temperature}; 130 | 131 | // Read current configuration to determine conversion ratio and delay 132 | let mut reg_conf = self.read_register::().await?; 133 | reg_conf.write_oneshot(true); 134 | 135 | // Initiate measurement 136 | self.write_register(reg_conf).await?; 137 | 138 | // Wait for the duration of the conversion 139 | let active_conversion_time = reg_conf.read_conversion_cycle_time().conversion_time_ms() + 10; 140 | delay.delay_ms(active_conversion_time).await; 141 | 142 | let raw_temp = self.read_register::().await?.read_raw_temperature() as i32; 143 | if reg_conf.read_extended() { 144 | Ok(ThermodynamicTemperature::new::(Rational32::new_raw( 145 | raw_temp, 128, 146 | ))) 147 | } else { 148 | Ok(ThermodynamicTemperature::new::(Rational32::new_raw( 149 | raw_temp, 256, 150 | ))) 151 | } 152 | } 153 | 154 | /// Read the last temperature measured 155 | pub async fn read_temperature(&mut self) -> Result { 156 | use self::registers::{Configuration, Temperature}; 157 | 158 | // Read current configuration to determine conversion ratio 159 | let reg_conf = self.read_register::().await?; 160 | 161 | // Read and return the temperature 162 | let raw_temp = self.read_register::().await?.read_raw_temperature() as i32; 163 | if reg_conf.read_extended() { 164 | Ok(ThermodynamicTemperature::new::(Rational32::new_raw( 165 | raw_temp, 128, 166 | ))) 167 | } else { 168 | Ok(ThermodynamicTemperature::new::(Rational32::new_raw( 169 | raw_temp, 256, 170 | ))) 171 | } 172 | } 173 | 174 | pub async fn set_continuous(&mut self) -> Result<(), I::Error> { 175 | use self::registers::Configuration; 176 | 177 | // Read current configuration and update it for continuous mode 178 | let mut reg_conf = self.read_register::().await?; 179 | reg_conf.write_oneshot(false); 180 | reg_conf.write_extended(true); 181 | reg_conf.write_shutdown(false); 182 | 183 | // Initiate measurement 184 | self.write_register(reg_conf).await?; 185 | 186 | Ok(()) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/tmp102/registers.rs: -------------------------------------------------------------------------------- 1 | use bondrewd::BitfieldEnum; 2 | use embedded_devices_derive::device_register; 3 | use embedded_registers::register; 4 | use uom::num_rational::Rational32; 5 | use uom::si::rational32::ThermodynamicTemperature; 6 | use uom::si::thermodynamic_temperature::degree_celsius; 7 | 8 | /// At the end of every conversion, the device updates the temperature 9 | /// register with the conversion result. 10 | #[device_register(super::TMP102)] 11 | #[register(address = 0b0000, mode = "r")] 12 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 13 | pub struct Temperature { 14 | /// The temperature in °C with a resolution of 7.8125m°C/LSB. 15 | pub raw_temperature: i16, 16 | } 17 | 18 | /// Temperature alert flag. 19 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 20 | #[bondrewd_enum(u8)] 21 | pub enum AlertFlag { 22 | /// The alert is unset. 23 | Cleared = 0, 24 | /// The associated alert was triggered. 25 | Set = 1, 26 | } 27 | 28 | /// Conversion cycle time. 29 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 30 | #[bondrewd_enum(u8)] 31 | #[allow(non_camel_case_types)] 32 | pub enum ConversionCycleTime { 33 | /// 4s 34 | T_4000 = 0b00, 35 | /// 1s 36 | T_1000 = 0b01, 37 | /// 250ms 38 | T_0250 = 0b010, 39 | /// 125ms 40 | T_0125 = 0b011, 41 | } 42 | impl ConversionCycleTime { 43 | /// Returns the averaging factor 44 | pub fn conversion_time_ms(&self) -> u32 { 45 | match self { 46 | ConversionCycleTime::T_4000 => 4000, 47 | ConversionCycleTime::T_1000 => 1000, 48 | ConversionCycleTime::T_0250 => 250, 49 | ConversionCycleTime::T_0125 => 125, 50 | } 51 | } 52 | } 53 | 54 | /// Therm/Alert mode. 55 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 56 | #[bondrewd_enum(u8)] 57 | pub enum InterruptThermMode { 58 | /// In this mode, the device compares the conversion result at the end of every conversion 59 | /// with the values in the low limit register and high limit register and sets the high alert 60 | /// status flag in the configuration register if the temperature exceeds the value in the 61 | /// high limit register. When set, the device clears the high alert status flag if the conversion 62 | /// result goes below the value in the low limit register. 63 | /// This is the power-on default. 64 | /// 65 | /// Thus, the difference between the high and low limits effectively acts like a hysteresis. 66 | /// In this mode, the low alert status flag is disabled and always reads 0. Unlike the alert 67 | /// mode, I2C reads of the configuration register do not affect the status bits. The high alert 68 | /// status flag is only set or cleared at the end of conversions based on the value of the 69 | /// temperature result compared to the high and low limits. 70 | Therm = 0, 71 | /// In this mode, the device compares the conversion result at the end of every 72 | /// conversion with the values in the low limit register and high limit register. 73 | /// If the temperature result exceeds the value in the high limit register, 74 | /// the high alert status flag in the configuration register is set. On the other hand, 75 | /// if the temperature result is lower than the value in the low limit register, 76 | /// the alert status flag in the configuration register is set. It is cleared on temperature 77 | /// register read. 78 | /// 79 | /// This mode effectively makes the device behave like a window limit detector. 80 | /// Thus this mode can be used in applications where detecting if the temperature 81 | /// goes outside of the specified range is necessary. 82 | Interrupt = 1, 83 | } 84 | 85 | /// Alert pin polarity. 86 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 87 | #[bondrewd_enum(u8)] 88 | pub enum AlertPinPolarity { 89 | /// Power-on default. 90 | ActiveLow = 0, 91 | ActiveHigh = 1, 92 | } 93 | 94 | /// Fault Queue 95 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 96 | #[bondrewd_enum(u8)] 97 | #[allow(non_camel_case_types)] 98 | pub enum FaultQueue { 99 | /// Alert is triggered at the first event 100 | F_1 = 0b00, 101 | /// Two consecutive events are required to set the Alert 102 | F_2 = 0b01, 103 | /// Four consecutive events are required to set the Alert 104 | F_4 = 0b10, 105 | /// Six consecutive events are required to set the Alert 106 | F_6 = 0b11, 107 | } 108 | 109 | /// Resolution. The TMP102 only supports 12 bits resolution. Read-only. 110 | #[derive(BitfieldEnum, Copy, Clone, PartialEq, Eq, Debug, defmt::Format)] 111 | #[bondrewd_enum(u8)] 112 | #[allow(non_camel_case_types)] 113 | pub enum Resolution { 114 | /// 12 bits resolution 115 | R_12 = 0b11, 116 | } 117 | 118 | /// The device configuration register. 119 | #[device_register(super::TMP102)] 120 | #[register(address = 0b0001, mode = "rw")] 121 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 122 | pub struct Configuration { 123 | #[register(default = false)] 124 | pub oneshot: bool, 125 | #[bondrewd(enum_primitive = "u8", bit_length = 2)] 126 | #[register(default = Resolution::R_12)] 127 | pub resolution: Resolution, 128 | #[bondrewd(enum_primitive = "u8", bit_length = 2)] 129 | #[register(default = FaultQueue::F_1)] 130 | pub fault_queue: FaultQueue, 131 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 132 | #[register(default = AlertPinPolarity::ActiveLow)] 133 | pub alert_polarity: AlertPinPolarity, 134 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 135 | #[register(default = InterruptThermMode::Therm)] 136 | pub alert_mode: InterruptThermMode, 137 | #[register(default = false)] 138 | pub shutdown: bool, 139 | /// Amount of time to wait between conversions. 140 | #[bondrewd(enum_primitive = "u8", bit_length = 2, endianness = "little")] 141 | #[register(default = ConversionCycleTime::T_0250)] 142 | pub conversion_cycle_time: ConversionCycleTime, 143 | /// Set when the conversion result is higher than the high limit. 144 | /// This flag is cleared on read except in Therm mode, where it is 145 | /// cleared when the conversion result is lower than the hysteresis. 146 | #[bondrewd(enum_primitive = "u8", bit_length = 1)] 147 | #[register(default = AlertFlag::Cleared)] 148 | pub alert: AlertFlag, 149 | #[register(default = false)] 150 | pub extended: bool, 151 | #[bondrewd(bit_length = 4, reserve)] 152 | #[allow(dead_code)] 153 | #[register(default = 0)] 154 | pub reserved: u8, 155 | } 156 | 157 | macro_rules! define_temp_limit_register { 158 | ($name:ident, $address:expr, $doc:expr) => { 159 | #[doc = $doc] 160 | #[device_register(super::TMP102)] 161 | #[register(address = $address, mode = "rw")] 162 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 163 | pub struct $name { 164 | /// The temperature limit in °C with a resolution of 7.8125m°C/LSB. 165 | pub raw_temperature_limit: i16, 166 | } 167 | 168 | impl $name { 169 | /// Reads the temperature limit in °C with a resolution of 7.8125m°C/LSB. 170 | pub fn read_temperature_limit(&self) -> ThermodynamicTemperature { 171 | ThermodynamicTemperature::new::( 172 | Rational32::new_raw(self.read_raw_temperature_limit().into(), 128).reduced(), 173 | ) 174 | } 175 | 176 | /// Writes the temperature limit in °C with a resolution of 7.8125m°C/LSB. 177 | /// The passed temperature will be truncated (rounded down). 178 | pub fn write_temperature_limit( 179 | &mut self, 180 | temperature_limit: ThermodynamicTemperature, 181 | ) -> Result<(), core::num::TryFromIntError> { 182 | let temp = temperature_limit.get::(); 183 | let temp: i16 = (temp * Rational32::from_integer(128)).to_integer().try_into()?; 184 | self.write_raw_temperature_limit(temp); 185 | Ok(()) 186 | } 187 | 188 | /// Writes the temperature limit in °C with a resolution of 7.8125m°C/LSB. 189 | /// The passed temperature will be truncated (rounded down). 190 | pub fn with_temperature_limit( 191 | mut self, 192 | temperature_limit: ThermodynamicTemperature, 193 | ) -> Result { 194 | self.write_temperature_limit(temperature_limit)?; 195 | Ok(self) 196 | } 197 | } 198 | }; 199 | } 200 | 201 | define_temp_limit_register!( 202 | TemperatureLimitHigh, 203 | 0b0010, 204 | r#" 205 | This register stores the high limit for comparison with the temperature result 206 | with a resolution is 7.8125m°C/LSB (but the ). The range of the register is ±256°C. 207 | Following power-up or a general-call reset, the high-limit register is loaded with the 208 | stored value from the EEPROM. The factory default reset value is 6000h (192°C). 209 | "# 210 | ); 211 | 212 | define_temp_limit_register!( 213 | TemperatureLimitLow, 214 | 0b0011, 215 | r#" 216 | This register stores the low limit for comparison with the temperature result 217 | with a resolution is 7.8125m°C/LSB. The range of the register is ±256°C. 218 | Following power-up or a general-call reset, the low-limit register is loaded with the 219 | stored value from the EEPROM. The factory default reset value is 8000h (-256°C). 220 | "# 221 | ); 222 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/tmp117/address.rs: -------------------------------------------------------------------------------- 1 | use defmt::Format; 2 | 3 | const ADDRESS_GND: u8 = 0b1001000; // 0x48 4 | const ADDRESS_VCC: u8 = 0b1001001; // 0x49 5 | const ADDRESS_SDA: u8 = 0b1001010; // 0x4A 6 | const ADDRESS_SCL: u8 = 0b1001011; // 0x4B 7 | 8 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Format)] 9 | pub enum Address { 10 | /// Address selection pin connected to GND 11 | Gnd, 12 | /// Address selection pin connected to VCC 13 | Vcc, 14 | /// Address selection pin connected to SDA 15 | Sda, 16 | /// Address selection pin connected to SCL 17 | Scl, 18 | /// Custom address not directly supported by the device, but may be useful 19 | /// when using I2C address translators. 20 | Custom(u8), 21 | } 22 | 23 | impl From
for u8 { 24 | fn from(address: Address) -> Self { 25 | match address { 26 | Address::Gnd => ADDRESS_GND, 27 | Address::Vcc => ADDRESS_VCC, 28 | Address::Sda => ADDRESS_SDA, 29 | Address::Scl => ADDRESS_SCL, 30 | Address::Custom(x) => x, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /embedded-devices/src/devices/texas_instruments/tmp117/mod.rs: -------------------------------------------------------------------------------- 1 | //! # TMP117 2 | //! 3 | //! The TMP117 is a high-precision digital temperature sensor. It is designed to meet ASTM E1112 4 | //! and ISO 80601 requirements for electronic patient thermometers. The TMP117 provides a 16-bit 5 | //! temperature result with a resolution of 0.0078 °C and an accuracy of up to ±0.1 °C across the 6 | //! temperature range of –20 °C to 50 °C with no calibration. The TMP117 has in interface that is 7 | //! I2C- and SMBus™-compatible, programmable alert functionality, and the device can support up to four 8 | //! devices on a single bus. Integrated EEPROM is included for device programming with an additional 9 | //! 48-bits memory available for general use. 10 | //! 11 | //! The low power consumption of the TMP117 minimizes the impact of self-heating on measurement accuracy. 12 | //! The TMP117 operates from 1.7 V to 5.5 V and typically consumes 3.5 μA. 13 | //! 14 | //! For non-medical applications, the TMP117 can serve as a single chip digital alternative to a Platinum RTD. 15 | //! The TMP117 has an accuracy comparable to a Class AA RTD, while only using a fraction of the power of the 16 | //! power typically needed for a PT100 RTD. The TMP117 simplifies the design effort by removing many of the 17 | //! complexities of RTDs such as precision references, matched traces, complicated algorithms, and calibration. 18 | //! 19 | //! The TMP117 units are 100% tested on a production setup that is NIST traceable and verified with 20 | //! equipment that is calibrated to ISO/IEC 17025 accredited standards. 21 | //! 22 | //! ## Usage (sync) 23 | //! 24 | //! ``` 25 | //! # fn test(mut i2c: I, mut Delay: D) -> Result<(), I::Error> 26 | //! # where 27 | //! # I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType, 28 | //! # D: embedded_hal::delay::DelayNs 29 | //! # { 30 | //! use embedded_devices::devices::texas_instruments::tmp117::{TMP117Sync, address::Address, registers::Temperature}; 31 | //! use uom::si::thermodynamic_temperature::degree_celsius; 32 | //! use uom::num_traits::ToPrimitive; 33 | //! 34 | //! // Create and initialize the device. Default conversion mode is continuous. 35 | //! let mut tmp117 = TMP117Sync::new_i2c(i2c, Address::Gnd); 36 | //! tmp117.init(&mut Delay).unwrap(); 37 | //! 38 | //! // Read the latest temperature conversion in °C and convert it to a float 39 | //! let temp = tmp117 40 | //! .read_register::()? 41 | //! .read_temperature() 42 | //! .get::() 43 | //! .to_f32(); 44 | //! println!("Current temperature: {:?}°C", temp); 45 | //! 46 | //! // Perform a one-shot measurement now and return to sleep afterwards. 47 | //! let temp = tmp117.oneshot(&mut Delay)?.get::().to_f32(); 48 | //! println!("Oneshot temperature: {:?}°C", temp); 49 | //! # Ok(()) 50 | //! # } 51 | //! ``` 52 | //! 53 | //! ## Usage (async) 54 | //! 55 | //! ``` 56 | //! # async fn test(mut i2c: I, mut Delay: D) -> Result<(), I::Error> 57 | //! # where 58 | //! # I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType, 59 | //! # D: embedded_hal_async::delay::DelayNs 60 | //! # { 61 | //! use embedded_devices::devices::texas_instruments::tmp117::{TMP117Async, address::Address, registers::Temperature}; 62 | //! use uom::si::thermodynamic_temperature::degree_celsius; 63 | //! use uom::num_traits::ToPrimitive; 64 | //! 65 | //! // Create and initialize the device. Default conversion mode is continuous. 66 | //! let mut tmp117 = TMP117Async::new_i2c(i2c, Address::Gnd); 67 | //! tmp117.init(&mut Delay).await.unwrap(); 68 | //! 69 | //! // Read the latest temperature conversion in °C and convert it to a float 70 | //! let temp = tmp117 71 | //! .read_register::() 72 | //! .await? 73 | //! .read_temperature() 74 | //! .get::() 75 | //! .to_f32(); 76 | //! println!("Current temperature: {:?}°C", temp); 77 | //! 78 | //! // Perform a one-shot measurement now and return to sleep afterwards. 79 | //! let temp = tmp117.oneshot(&mut Delay).await?.get::().to_f32(); 80 | //! println!("Oneshot temperature: {:?}°C", temp); 81 | //! # Ok(()) 82 | //! # } 83 | //! ``` 84 | 85 | use embedded_devices_derive::{device, device_impl}; 86 | use embedded_registers::WritableRegister; 87 | use uom::si::rational32::ThermodynamicTemperature; 88 | 89 | pub mod address; 90 | pub mod registers; 91 | 92 | type TMP117I2cCodec = embedded_registers::i2c::codecs::OneByteRegAddrCodec; 93 | 94 | /// All possible errors that may occur in device initialization 95 | #[derive(Debug, defmt::Format)] 96 | pub enum InitError { 97 | /// Bus error 98 | Bus(BusError), 99 | /// Invalid Device Id was encountered 100 | InvalidDeviceId, 101 | } 102 | 103 | /// All possible errors that may occur in device initialization 104 | #[derive(Debug, defmt::Format)] 105 | pub enum EepromError { 106 | /// Bus error 107 | Bus(BusError), 108 | /// EEPROM is still busy after 13ms 109 | EepromStillBusy, 110 | } 111 | 112 | /// The TMP117 is a high-precision digital temperature sensor. It provides a 16-bit 113 | /// temperature result with a resolution of 0.0078 °C and an accuracy of up to ±0.1 °C across the 114 | /// temperature range of –20 °C to 50 °C with no calibration. 115 | /// 116 | /// For a full description and usage examples, refer to the [module documentation](self). 117 | #[device] 118 | #[maybe_async_cfg::maybe( 119 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 120 | sync(feature = "sync"), 121 | async(feature = "async") 122 | )] 123 | pub struct TMP117 { 124 | /// The interface to communicate with the device 125 | interface: I, 126 | } 127 | 128 | #[maybe_async_cfg::maybe( 129 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice), 130 | sync(feature = "sync"), 131 | async(feature = "async") 132 | )] 133 | impl TMP117> 134 | where 135 | I: hal::i2c::I2c + hal::i2c::ErrorType, 136 | { 137 | /// Initializes a new device with the given address on the specified bus. 138 | /// This consumes the I2C bus `I`. 139 | /// 140 | /// Before using this device, you should call the [`Self::init`] method which 141 | /// initializes the device and ensures that it is working correctly. 142 | #[inline] 143 | pub fn new_i2c(interface: I, address: self::address::Address) -> Self { 144 | Self { 145 | interface: embedded_registers::i2c::I2cDevice::new(interface, address.into()), 146 | } 147 | } 148 | } 149 | 150 | #[device_impl] 151 | #[maybe_async_cfg::maybe( 152 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface), 153 | sync(feature = "sync"), 154 | async(feature = "async") 155 | )] 156 | impl TMP117 { 157 | /// Initialize the sensor by waiting for the boot-up period and verifying its device id. 158 | /// The datasheet specifies a power-on-reset time of 1.5ms. 159 | /// Calling this function is not mandatory, but recommended to ensure proper operation. 160 | pub async fn init(&mut self, delay: &mut D) -> Result<(), InitError> { 161 | use self::registers::DeviceIdRevision; 162 | 163 | // FIXME: issue reset first? 164 | delay.delay_us(1500).await; 165 | 166 | let device_id = self.read_register::().await.map_err(InitError::Bus)?; 167 | if device_id.read_device_id() != self::registers::DEVICE_ID_VALID { 168 | return Err(InitError::InvalidDeviceId); 169 | } 170 | 171 | Ok(()) 172 | } 173 | 174 | /// Performs a soft-reset of the device. 175 | /// The datasheet specifies a time to reset of 2ms which is 176 | /// automatically awaited before allowing further communication. 177 | pub async fn reset(&mut self, delay: &mut D) -> Result<(), I::Error> { 178 | self.write_register(self::registers::Configuration::default().with_soft_reset(true)) 179 | .await?; 180 | delay.delay_ms(2).await; 181 | Ok(()) 182 | } 183 | 184 | /// Write a register value into EEPROM. Usually this will persist the register 185 | /// value as the new power-on default. Not all registers / register-bits support this. 186 | pub async fn write_eeprom(&mut self, delay: &mut D) -> Result<(), EepromError> 187 | where 188 | R: WritableRegister, 189 | D: hal::delay::DelayNs, 190 | { 191 | use self::registers::{EepromLockMode, EepromUnlock}; 192 | 193 | // Unlock EEPROM 194 | self.write_register(EepromUnlock::default().with_lock_mode(EepromLockMode::Unlocked)) 195 | .await 196 | .map_err(EepromError::Bus)?; 197 | 198 | // Wait 7ms for EEPROM write to complete 199 | delay.delay_ms(7).await; 200 | 201 | // Wait up to 5ms for eeprom busy flag to be reset 202 | const TRIES: u8 = 5; 203 | for _ in 0..TRIES { 204 | if !self 205 | .read_register::() 206 | .await 207 | .map_err(EepromError::Bus)? 208 | .read_busy() 209 | { 210 | // EEPROM write complete, lock eeprom again 211 | self.write_register(EepromUnlock::default().with_lock_mode(EepromLockMode::Locked)) 212 | .await 213 | .map_err(EepromError::Bus)?; 214 | 215 | return Ok(()); 216 | } 217 | 218 | // Wait another 1ms for EEPROM write to complete 219 | delay.delay_ms(1).await; 220 | } 221 | 222 | Err(EepromError::EepromStillBusy) 223 | } 224 | 225 | /// Performs a one-shot measurement. This will set the conversion mode to [`self::registers::ConversionMode::Oneshot´]. 226 | /// which will cause the device to perform a single conversion a return to sleep mode afterwards. 227 | /// 228 | /// This function will initialize the measurement, wait until the data is acquired and return 229 | /// the temperature. 230 | pub async fn oneshot( 231 | &mut self, 232 | delay: &mut D, 233 | ) -> Result { 234 | use self::registers::{Configuration, ConversionMode, Temperature}; 235 | 236 | // Read current averaging mode to determine required measurement delay 237 | let mut reg_conf = self.read_register::().await?; 238 | reg_conf.write_conversion_mode(ConversionMode::Oneshot); 239 | 240 | // Initiate measurement 241 | self.write_register(reg_conf).await?; 242 | 243 | // Active conversion time is only linearly influenced by the averaging factor. 244 | // A single-conversion takes 15.5ms. 245 | let active_conversion_time = reg_conf.read_averaging_mode().factor() as u32 * 15500; 246 | delay.delay_us(active_conversion_time).await; 247 | 248 | // Read and return the temperature 249 | Ok(self.read_register::().await?.read_temperature()) 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /embedded-devices/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Device driver implementations for many embedded sensors and devices 2 | //! 3 | #![cfg_attr(not(doctest), doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")))] 4 | #![cfg_attr(not(feature = "std"), no_std)] 5 | #![feature(generic_arg_infer)] 6 | 7 | pub mod devices; 8 | pub mod utils; 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | #[cfg(feature = "bosch-bme280")] 13 | #[test] 14 | fn register_bme280_reset() { 15 | use crate::devices::bosch::bme280::registers::{Reset, ResetBitfield, ResetMagic}; 16 | let value = ResetBitfield { 17 | magic: ResetMagic::Reset, 18 | }; 19 | 20 | let reg = Reset::new(value.clone()); 21 | assert_eq!(reg.data, [0xb6]); 22 | assert_eq!(reg.read_all(), value); 23 | } 24 | 25 | #[cfg(feature = "bosch-bmp390")] 26 | #[test] 27 | fn register_bmp390_error() { 28 | use crate::devices::bosch::bmp390::registers::{Error, ErrorBitfield}; 29 | let value = ErrorBitfield { 30 | fatal_err: true, 31 | cmd_err: false, 32 | conf_err: true, 33 | reserved: 0, 34 | }; 35 | 36 | let reg = Error::new(value.clone()); 37 | assert_eq!(reg.data, [0b00000101]); 38 | assert_eq!(reg.read_all(), value); 39 | } 40 | 41 | #[cfg(feature = "bosch-bmp390")] 42 | #[test] 43 | fn register_bmp390_pressure() { 44 | use crate::devices::bosch::bmp390::registers::{Pressure, PressureBitfield}; 45 | let value = PressureBitfield { pressure: 0x123456 }; 46 | 47 | let reg = Pressure::new(value.clone()); 48 | assert_eq!(reg.data, [0x56, 0x34, 0x12]); 49 | assert_eq!(reg.read_all(), value); 50 | } 51 | 52 | #[cfg(feature = "texas_instruments-ina228")] 53 | #[test] 54 | fn register_defaults() { 55 | use crate::devices::texas_instruments::ina228::registers::AdcConfiguration; 56 | let reg = AdcConfiguration::default(); 57 | assert!(reg.read_enable_temperature()); 58 | } 59 | 60 | // FIXME: once bondrewd #[test] 61 | // FIXME: once bondrewd fn register_bmp390_fifo_length() { 62 | // FIXME: once bondrewd let value = FifoLengthBitfield { 63 | // FIXME: once bondrewd reserved: 0b0100010, 64 | // FIXME: once bondrewd length: 0b110000101, 65 | // FIXME: once bondrewd }; 66 | 67 | // FIXME: once bondrewd let reg = FifoLength::new(value.clone()); 68 | // FIXME: once bondrewd assert_eq!(reg.data, [0b10000101, 0b01000101]); 69 | // FIXME: once bondrewd assert_eq!(reg.read_all(), value); 70 | // FIXME: once bondrewd } 71 | } 72 | -------------------------------------------------------------------------------- /embedded-devices/src/utils/callendar_van_dusen.rs: -------------------------------------------------------------------------------- 1 | // 4th order piecewise approximation table, requires 6*6*4 = 144 bytes. 2 | // - Values for R0=100Ω 3 | // - maximum error: +- 0.0001°C 4 | // - valid range: [-200°C, 900°C] 5 | // - no need for binary search 6 | #[rustfmt::skip] 7 | const LOOKUP_TABLE_R100_RESISTANCES: [f32; 6] = [ 8 | // maximum valid resistance for the params at the same index 9 | 61.735_252, 10 | 103.288_26, 11 | 215.154_63, 12 | 303.148_65, 13 | 377.041_17, 14 | 404.969_5, 15 | ]; 16 | 17 | #[rustfmt::skip] 18 | const LOOKUP_TABLE_R100_PARAMS: [[f32; 5]; 6] = [ 19 | // polynomial params [a, b, c, d, e]) 20 | [1.905_779e-9, -7.117_465e-6, 0.002_669_763, 2.221_340_2, -242.010_03 ], 21 | [3.361_247_3e-8, -1.478_834_1e-5, 0.003_384_182_7, 2.191_016, -241.516_31 ], 22 | [9.669_08e-10, 3.129_280_5e-7, 0.000_816_996_27, 2.381_973_3, -246.776_75 ], 23 | [1.823_480_8e-9, -4.288_475_6e-7, 0.001_060_640_2, 2.346_023_8, -244.768_16 ], 24 | [3.374_456_5e-9, -2.322_593_4e-6, 0.001_931_025_4, 2.167_576_8, -231.000_35 ], 25 | [5.344_515e-9, -5.238_631_3e-6, 0.003_551_305_5, 1.767_021_8, -193.827_36 ], 26 | ]; 27 | 28 | /// Inverse Callendar-Van Dusen equation for R0=100Ω, 29 | /// based on piecewise reconstruction with 4th order polynomials. 30 | /// The maximum error is ±1e-4°C over the whole temperature range [-200°C, 900°C]. 31 | /// but degrades rapidly beyond that range. Inputs that correspond to values outside 32 | /// of this range will yield unpredictable outcomes. 33 | pub fn resistance_to_temperature_r100(r: f32) -> f32 { 34 | // Find the valid parameter index 35 | let param_index = LOOKUP_TABLE_R100_RESISTANCES 36 | .iter() 37 | .position(|&max_valid_resistance| r < max_valid_resistance) 38 | .unwrap_or(LOOKUP_TABLE_R100_RESISTANCES.len() - 1); 39 | let [a, b, c, d, e] = LOOKUP_TABLE_R100_PARAMS[param_index]; 40 | let r2 = r * r; 41 | let r3 = r2 * r; 42 | let r4 = r2 * r2; 43 | a * r4 + b * r3 + c * r2 + d * r + e 44 | } 45 | 46 | /// Callendar-Van Dusen equation for R0=100Ω 47 | pub fn temperature_to_resistance_r100(t: f32) -> f32 { 48 | const R0: f32 = 100.0; 49 | const A: f32 = 3.9083e-3; 50 | const B: f32 = -5.775e-7; 51 | const C: f32 = -4.183e-12; 52 | if t < 0.0 { 53 | let t2 = t * t; 54 | let t3 = t2 * t; 55 | R0 * (1.0 + A * t + B * t2 + C * (t - 100.0) * t3) 56 | } else { 57 | let t2 = t * t; 58 | R0 * (1.0 + A * t + B * t2) 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod test { 64 | use super::resistance_to_temperature_r100; 65 | use approx::assert_abs_diff_eq; 66 | 67 | #[test] 68 | #[rustfmt::skip] 69 | fn test_lookup_table_r100() { 70 | assert_abs_diff_eq!(resistance_to_temperature_r100(18.520_08), -200.0, epsilon = 0.0001); 71 | assert_abs_diff_eq!(resistance_to_temperature_r100(23.206_966), -189.109_99, epsilon = 0.0001); 72 | assert_abs_diff_eq!(resistance_to_temperature_r100(27.853_231), -178.219_96, epsilon = 0.0001); 73 | assert_abs_diff_eq!(resistance_to_temperature_r100(32.462_5), -167.327_73, epsilon = 0.0001); 74 | assert_abs_diff_eq!(resistance_to_temperature_r100(37.035_477), -156.437_71, epsilon = 0.0001); 75 | assert_abs_diff_eq!(resistance_to_temperature_r100(41.576_435), -145.545_49, epsilon = 0.0001); 76 | assert_abs_diff_eq!(resistance_to_temperature_r100(46.085_815), -134.655_47, epsilon = 0.0001); 77 | assert_abs_diff_eq!(resistance_to_temperature_r100(50.567_596), -123.763_245, epsilon = 0.0001); 78 | assert_abs_diff_eq!(resistance_to_temperature_r100(55.021_954), -112.873_22, epsilon = 0.0001); 79 | assert_abs_diff_eq!(resistance_to_temperature_r100(59.452_55), -101.981, epsilon = 0.0001); 80 | assert_abs_diff_eq!(resistance_to_temperature_r100(63.859_31), -91.090_98, epsilon = 0.0001); 81 | assert_abs_diff_eq!(resistance_to_temperature_r100(68.245_6), -80.198_76, epsilon = 0.0001); 82 | assert_abs_diff_eq!(resistance_to_temperature_r100(72.611_08), -69.308_74, epsilon = 0.0001); 83 | assert_abs_diff_eq!(resistance_to_temperature_r100(76.958_79), -58.416_515, epsilon = 0.0001); 84 | assert_abs_diff_eq!(resistance_to_temperature_r100(81.288_15), -47.526_497, epsilon = 0.0001); 85 | assert_abs_diff_eq!(resistance_to_temperature_r100(85.601_91), -36.634_274, epsilon = 0.0001); 86 | assert_abs_diff_eq!(resistance_to_temperature_r100(89.899_2), -25.744_251, epsilon = 0.0001); 87 | assert_abs_diff_eq!(resistance_to_temperature_r100(94.182_49), -14.852_03, epsilon = 0.0001); 88 | assert_abs_diff_eq!(resistance_to_temperature_r100(98.450_62), -3.962_008, epsilon = 0.0001); 89 | assert_abs_diff_eq!(resistance_to_temperature_r100(102.705_765), 6.930_214, epsilon = 0.0001); 90 | assert_abs_diff_eq!(resistance_to_temperature_r100(106.946_34), 17.820_236, epsilon = 0.0001); 91 | assert_abs_diff_eq!(resistance_to_temperature_r100(111.174_08), 28.712_458, epsilon = 0.0001); 92 | assert_abs_diff_eq!(resistance_to_temperature_r100(115.387_26), 39.602_478, epsilon = 0.0001); 93 | assert_abs_diff_eq!(resistance_to_temperature_r100(119.587_6), 50.494_7, epsilon = 0.0001); 94 | assert_abs_diff_eq!(resistance_to_temperature_r100(123.773_384), 61.384_724, epsilon = 0.0001); 95 | assert_abs_diff_eq!(resistance_to_temperature_r100(127.946_31), 72.276_95, epsilon = 0.0001); 96 | assert_abs_diff_eq!(resistance_to_temperature_r100(132.104_7), 83.166_97, epsilon = 0.0001); 97 | assert_abs_diff_eq!(resistance_to_temperature_r100(136.250_23), 94.059_19, epsilon = 0.0001); 98 | assert_abs_diff_eq!(resistance_to_temperature_r100(140.381_23), 104.949_21, epsilon = 0.0001); 99 | assert_abs_diff_eq!(resistance_to_temperature_r100(144.499_34), 115.841_43, epsilon = 0.0001); 100 | assert_abs_diff_eq!(resistance_to_temperature_r100(148.602_94), 126.731_45, epsilon = 0.0001); 101 | assert_abs_diff_eq!(resistance_to_temperature_r100(152.693_66), 137.623_67, epsilon = 0.0001); 102 | assert_abs_diff_eq!(resistance_to_temperature_r100(156.769_85), 148.513_7, epsilon = 0.0001); 103 | assert_abs_diff_eq!(resistance_to_temperature_r100(160.833_18), 159.405_91, epsilon = 0.0001); 104 | assert_abs_diff_eq!(resistance_to_temperature_r100(164.881_97), 170.295_94, epsilon = 0.0001); 105 | assert_abs_diff_eq!(resistance_to_temperature_r100(168.917_88), 181.188_16, epsilon = 0.0001); 106 | assert_abs_diff_eq!(resistance_to_temperature_r100(172.939_29), 192.078_19, epsilon = 0.0001); 107 | assert_abs_diff_eq!(resistance_to_temperature_r100(176.947_8), 202.970_41, epsilon = 0.0001); 108 | assert_abs_diff_eq!(resistance_to_temperature_r100(180.941_8), 213.860_43, epsilon = 0.0001); 109 | assert_abs_diff_eq!(resistance_to_temperature_r100(184.922_91), 224.752_66, epsilon = 0.0001); 110 | assert_abs_diff_eq!(resistance_to_temperature_r100(188.889_51), 235.642_67, epsilon = 0.0001); 111 | assert_abs_diff_eq!(resistance_to_temperature_r100(192.843_22), 246.534_9, epsilon = 0.0001); 112 | assert_abs_diff_eq!(resistance_to_temperature_r100(196.782_42), 257.424_93, epsilon = 0.0001); 113 | assert_abs_diff_eq!(resistance_to_temperature_r100(200.708_72), 268.317_14, epsilon = 0.0001); 114 | assert_abs_diff_eq!(resistance_to_temperature_r100(204.620_53), 279.207_15, epsilon = 0.0001); 115 | assert_abs_diff_eq!(resistance_to_temperature_r100(208.519_42), 290.099_37, epsilon = 0.0001); 116 | assert_abs_diff_eq!(resistance_to_temperature_r100(212.403_85), 300.989_4, epsilon = 0.0001); 117 | assert_abs_diff_eq!(resistance_to_temperature_r100(216.275_34), 311.881_62, epsilon = 0.0001); 118 | assert_abs_diff_eq!(resistance_to_temperature_r100(220.132_35), 322.771_64, epsilon = 0.0001); 119 | assert_abs_diff_eq!(resistance_to_temperature_r100(223.976_46), 333.663_88, epsilon = 0.0001); 120 | assert_abs_diff_eq!(resistance_to_temperature_r100(227.806_08), 344.553_9, epsilon = 0.0001); 121 | assert_abs_diff_eq!(resistance_to_temperature_r100(231.622_76), 355.446_1, epsilon = 0.0001); 122 | assert_abs_diff_eq!(resistance_to_temperature_r100(235.424_97), 366.336_12, epsilon = 0.0001); 123 | assert_abs_diff_eq!(resistance_to_temperature_r100(239.214_26), 377.228_36, epsilon = 0.0001); 124 | assert_abs_diff_eq!(resistance_to_temperature_r100(242.989_09), 388.118_38, epsilon = 0.0001); 125 | assert_abs_diff_eq!(resistance_to_temperature_r100(246.750_96), 399.010_6, epsilon = 0.0001); 126 | assert_abs_diff_eq!(resistance_to_temperature_r100(250.498_4), 409.900_63, epsilon = 0.0001); 127 | assert_abs_diff_eq!(resistance_to_temperature_r100(254.232_86), 420.792_85, epsilon = 0.0001); 128 | assert_abs_diff_eq!(resistance_to_temperature_r100(257.952_88), 431.682_86, epsilon = 0.0001); 129 | assert_abs_diff_eq!(resistance_to_temperature_r100(261.659_97), 442.575_07, epsilon = 0.0001); 130 | assert_abs_diff_eq!(resistance_to_temperature_r100(265.352_6), 453.465_12, epsilon = 0.0001); 131 | assert_abs_diff_eq!(resistance_to_temperature_r100(269.032_3), 464.357_33, epsilon = 0.0001); 132 | assert_abs_diff_eq!(resistance_to_temperature_r100(272.697_5), 475.247_34, epsilon = 0.0001); 133 | assert_abs_diff_eq!(resistance_to_temperature_r100(276.349_8), 486.139_6, epsilon = 0.0001); 134 | assert_abs_diff_eq!(resistance_to_temperature_r100(279.987_6), 497.029_6, epsilon = 0.0001); 135 | assert_abs_diff_eq!(resistance_to_temperature_r100(283.612_5), 507.921_8, epsilon = 0.0001); 136 | assert_abs_diff_eq!(resistance_to_temperature_r100(287.222_9), 518.811_8, epsilon = 0.0001); 137 | assert_abs_diff_eq!(resistance_to_temperature_r100(290.820_37), 529.704_04, epsilon = 0.0001); 138 | assert_abs_diff_eq!(resistance_to_temperature_r100(294.403_4), 540.594_06, epsilon = 0.0001); 139 | assert_abs_diff_eq!(resistance_to_temperature_r100(297.973_48), 551.486_3, epsilon = 0.0001); 140 | assert_abs_diff_eq!(resistance_to_temperature_r100(301.529_1), 562.376_34, epsilon = 0.0001); 141 | assert_abs_diff_eq!(resistance_to_temperature_r100(305.071_78), 573.268_55, epsilon = 0.0001); 142 | assert_abs_diff_eq!(resistance_to_temperature_r100(308.6), 584.158_57, epsilon = 0.0001); 143 | assert_abs_diff_eq!(resistance_to_temperature_r100(312.115_26), 595.050_8, epsilon = 0.0001); 144 | assert_abs_diff_eq!(resistance_to_temperature_r100(315.616_12), 605.940_8, epsilon = 0.0001); 145 | assert_abs_diff_eq!(resistance_to_temperature_r100(319.103_94), 616.833, epsilon = 0.0001); 146 | assert_abs_diff_eq!(resistance_to_temperature_r100(322.577_42), 627.723_1, epsilon = 0.0001); 147 | assert_abs_diff_eq!(resistance_to_temperature_r100(326.037_87), 638.615_3, epsilon = 0.0001); 148 | assert_abs_diff_eq!(resistance_to_temperature_r100(329.483_92), 649.505_3, epsilon = 0.0001); 149 | assert_abs_diff_eq!(resistance_to_temperature_r100(332.916_96), 660.397_5, epsilon = 0.0001); 150 | assert_abs_diff_eq!(resistance_to_temperature_r100(336.335_6), 671.287_54, epsilon = 0.0001); 151 | assert_abs_diff_eq!(resistance_to_temperature_r100(339.741_24), 682.179_75, epsilon = 0.0001); 152 | assert_abs_diff_eq!(resistance_to_temperature_r100(343.132_48), 693.069_76, epsilon = 0.0001); 153 | assert_abs_diff_eq!(resistance_to_temperature_r100(346.510_74), 703.962_04, epsilon = 0.0001); 154 | assert_abs_diff_eq!(resistance_to_temperature_r100(349.874_6), 714.852_05, epsilon = 0.0001); 155 | assert_abs_diff_eq!(resistance_to_temperature_r100(353.225_43), 725.744_26, epsilon = 0.0001); 156 | assert_abs_diff_eq!(resistance_to_temperature_r100(356.561_9), 736.634_3, epsilon = 0.0001); 157 | assert_abs_diff_eq!(resistance_to_temperature_r100(359.885_3), 747.526_5, epsilon = 0.0001); 158 | assert_abs_diff_eq!(resistance_to_temperature_r100(363.194_37), 758.416_5, epsilon = 0.0001); 159 | assert_abs_diff_eq!(resistance_to_temperature_r100(366.490_4), 769.308_7, epsilon = 0.0001); 160 | assert_abs_diff_eq!(resistance_to_temperature_r100(369.772_06), 780.198_7, epsilon = 0.0001); 161 | assert_abs_diff_eq!(resistance_to_temperature_r100(373.040_7), 791.091, epsilon = 0.0001); 162 | assert_abs_diff_eq!(resistance_to_temperature_r100(376.294_98), 801.981, epsilon = 0.0001); 163 | assert_abs_diff_eq!(resistance_to_temperature_r100(379.536_2), 812.873_2, epsilon = 0.0001); 164 | assert_abs_diff_eq!(resistance_to_temperature_r100(382.763_06), 823.763_24, epsilon = 0.0001); 165 | assert_abs_diff_eq!(resistance_to_temperature_r100(385.976_87), 834.655_46, epsilon = 0.0001); 166 | assert_abs_diff_eq!(resistance_to_temperature_r100(389.176_33), 845.545_5, epsilon = 0.0001); 167 | assert_abs_diff_eq!(resistance_to_temperature_r100(392.362_76), 856.437_7, epsilon = 0.0001); 168 | assert_abs_diff_eq!(resistance_to_temperature_r100(395.534_85), 867.327_76, epsilon = 0.0001); 169 | assert_abs_diff_eq!(resistance_to_temperature_r100(398.693_85), 878.22, epsilon = 0.0001); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /embedded-devices/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod callendar_van_dusen; 2 | -------------------------------------------------------------------------------- /embedded-registers-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-registers-derive" 3 | edition = "2024" 4 | description = "Procedural macro for effortless definitions of registers in embedded device drivers" 5 | documentation = "https://docs.rs/embedded-registers-derive" 6 | keywords = ["register", "registers", "bitfield", "derive", "sensor"] 7 | version.workspace = true 8 | authors.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | categories.workspace = true 12 | license.workspace = true 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | bondrewd = { version = "0.1.14", default-features = false, features = ["derive"] } 19 | bytemuck = { version = "1.22.0", features = ["derive", "min_const_generics"] } 20 | darling = "0.20.11" 21 | defmt = "1.0.1" 22 | proc-macro2 = "1.0" 23 | quote = "1.0" 24 | syn = { version = "2.0", features = ["full"] } 25 | 26 | [dev-dependencies] 27 | embedded-hal = "1.0.0" 28 | embedded-hal-async = "1.0.0" 29 | embedded-registers = { path = "../embedded-registers" } 30 | -------------------------------------------------------------------------------- /embedded-registers-derive/tests/basic.rs: -------------------------------------------------------------------------------- 1 | #![feature(generic_arg_infer)] 2 | 3 | use bondrewd::Bitfields; 4 | use embedded_registers::register; 5 | 6 | #[derive(Bitfields, Debug, Clone, Default, PartialEq, Eq, defmt::Format)] 7 | #[bondrewd(default_endianess = "msb", read_from = "lsb0", enforce_bytes = "1")] 8 | pub struct StatusMagnetometer { 9 | mtm1: bool, 10 | mtm2: bool, 11 | mtm3: bool, 12 | #[bondrewd(bit_length = 5, reserve)] 13 | #[allow(dead_code)] 14 | reserved: u8, 15 | } 16 | 17 | #[register(address = 0x04, mode = "r")] 18 | #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 19 | pub struct Configuration { 20 | #[bondrewd(struct_size = 1)] 21 | pub status: StatusMagnetometer, 22 | //#[bondrewd(bit_length = 5)] 23 | something1: u8, 24 | } 25 | 26 | #[test] 27 | fn example_register() { 28 | let mut reg: Configuration = Default::default(); 29 | reg.write_status(StatusMagnetometer { 30 | mtm1: true, 31 | mtm2: true, 32 | mtm3: true, 33 | reserved: 0, 34 | }); 35 | 36 | let bitfield = reg.read_all(); 37 | let mut reg2 = reg; 38 | reg2.write_all(bitfield); 39 | assert_eq!(reg, reg2); 40 | } 41 | -------------------------------------------------------------------------------- /embedded-registers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-registers" 3 | edition = "2024" 4 | description = "Procedural macro for effortless definitions of registers in embedded device drivers" 5 | documentation = "https://docs.rs/embedded-registers" 6 | keywords = ["register", "registers", "bitfield", "derive", "sensor"] 7 | version.workspace = true 8 | authors.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | categories.workspace = true 12 | license.workspace = true 13 | 14 | [dependencies] 15 | arrayvec = { version = "0.7.6", default-features = false } 16 | bondrewd = { version = "0.1.14", default-features = false, features = ["derive"] } 17 | bytemuck = { version = "1.22.0", features = ["derive", "min_const_generics"] } 18 | crc = "3.2.1" 19 | defmt = "1.0.1" 20 | embedded-hal = "1.0.0" 21 | embedded-hal-async = { version = "1.0.0", optional = true } 22 | embedded-registers-derive = { version = "0.9.13", path = "../embedded-registers-derive", optional = true } 23 | maybe-async-cfg = "0.2.5" 24 | 25 | [features] 26 | default = ["derive", "sync", "async"] 27 | 28 | sync = [] 29 | async = ["dep:embedded-hal-async"] 30 | derive = ["dep:embedded-registers-derive"] 31 | std = [] 32 | -------------------------------------------------------------------------------- /embedded-registers/src/i2c/codecs/crc_codec.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use crate::{ReadableRegister, WritableRegister}; 4 | use arrayvec::ArrayVec; 5 | use bytemuck::Zeroable; 6 | use crc::Algorithm; 7 | 8 | /// This codec implements an I2C codec utilizing crc checksums for data 9 | /// The main variables are: 10 | /// - the size of register addresses in bytes. 11 | /// - the crc algorithm in use 12 | /// - set chunk size that get checksummed 13 | /// 14 | /// This implements the codec only for crc outputs of single byte size. 15 | /// If your device has larger crc sums, you cannot use this implementation as is. 16 | /// 17 | /// This codec has no information over register sizes or contents. 18 | /// It will always send one header and then receive/send the associated register data, 19 | /// interspersed with crc sums each CHUNK_SIZE bytes. 20 | /// This makes the codec unsuited for cases where the device has 21 | /// registers that require interspersing the crc between fields of differing sizes. 22 | /// 23 | /// The following generic parameters are available: 24 | /// 25 | /// | Parameter | Type | Description | 26 | /// |---|---|---| 27 | /// | `HEADER_SIZE` | `usize` | The size of the command header (register address) in bytes | 28 | /// | `CHUNK_SIZE` | `usize` | The size of a chunk that has a singular crc sum attached in bytes | 29 | /// | `C` | `Crc8Algorithm` | A static reference to the crc algorithm to be used | 30 | /// Example implemenation for a basic CRC Algorithm: 31 | /// 32 | /// ``` 33 | /// #[derive(Default)] 34 | /// struct MyCrc {} 35 | /// 36 | /// impl Crc8Algorithm for MyCrc { 37 | /// fn instance() -> &'static Algorithm { 38 | /// const CUSTOM_ALG: crc::Algorithm = CRC_8_NRSC_5; 39 | /// &CUSTOM_ALG 40 | /// } 41 | /// } 42 | /// ``` 43 | #[derive(Default)] 44 | pub struct Crc8Codec { 45 | _algo: PhantomData, 46 | } 47 | 48 | pub trait Crc8Algorithm: Default { 49 | /// Return reference to global static CRC Algorithm 50 | fn instance() -> &'static Algorithm; 51 | } 52 | 53 | #[maybe_async_cfg::maybe( 54 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, I2cBoundBus), 55 | sync(feature = "sync"), 56 | async(feature = "async"), 57 | keep_self 58 | )] 59 | impl crate::i2c::Codec 60 | for Crc8Codec 61 | { 62 | #[inline] 63 | async fn read_register(bound_bus: &mut crate::i2c::I2cBoundBus) -> Result 64 | where 65 | R: ReadableRegister, 66 | I: hal::i2c::I2c + hal::i2c::ErrorType, 67 | A: hal::i2c::AddressMode + Copy, 68 | { 69 | let crc = crc::Crc::::new(C::instance()); 70 | let header = &R::ADDRESS.to_be_bytes()[core::mem::size_of_val(&R::ADDRESS) - HEADER_SIZE..]; 71 | 72 | let mut array = ArrayVec::<_, 64>::new(); 73 | unsafe { 74 | array.set_len(R::REGISTER_SIZE + R::REGISTER_SIZE / CHUNK_SIZE); 75 | } 76 | 77 | bound_bus 78 | .interface 79 | .write_read(bound_bus.address, header, &mut array) 80 | .await?; 81 | 82 | let mut register = R::zeroed(); 83 | let data = bytemuck::bytes_of_mut(&mut register); 84 | for (i, x) in array.chunks(CHUNK_SIZE + 1).enumerate() { 85 | let value = &x[0..CHUNK_SIZE]; 86 | data[i..i + CHUNK_SIZE].copy_from_slice(value); 87 | let crc_val = crc.checksum(value); 88 | let crc_real = x[CHUNK_SIZE]; 89 | if crc_real != crc_val { 90 | panic!("crc failed") 91 | } 92 | } 93 | 94 | Ok(register) 95 | } 96 | 97 | #[inline] 98 | async fn write_register( 99 | bound_bus: &mut crate::i2c::I2cBoundBus, 100 | register: impl AsRef, 101 | ) -> Result<(), I::Error> 102 | where 103 | R: WritableRegister, 104 | I: hal::i2c::I2c + hal::i2c::ErrorType, 105 | A: hal::i2c::AddressMode + Copy, 106 | { 107 | #[repr(C, packed(1))] 108 | #[derive(Copy, Clone, bytemuck::Pod, Zeroable)] 109 | struct Buffer { 110 | header: [u8; HEADER_SIZE], 111 | register: R, 112 | } 113 | let crc = crc::Crc::::new(C::instance()); 114 | 115 | let mut buffer = Buffer::<{ HEADER_SIZE }, R> { 116 | header: R::ADDRESS.to_be_bytes()[core::mem::size_of_val(&R::ADDRESS) - HEADER_SIZE..] 117 | .try_into() 118 | .expect("Unexpected compile-time header address size. This is a bug in the chosen Codec or embedded-registers."), 119 | register: *register.as_ref(), 120 | }; 121 | let data = bytemuck::bytes_of_mut(&mut buffer); 122 | 123 | let mut array = ArrayVec::<_, 64>::new(); 124 | for x in data.chunks(CHUNK_SIZE) { 125 | array.try_extend_from_slice(x).unwrap(); 126 | let crc_val = crc.checksum(x); 127 | array.push(crc_val); 128 | } 129 | 130 | bound_bus.interface.write(bound_bus.address, &array).await 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /embedded-registers/src/i2c/codecs/mod.rs: -------------------------------------------------------------------------------- 1 | mod crc_codec; 2 | mod no_codec; 3 | mod simple_codec; 4 | 5 | pub use crc_codec::Crc8Algorithm; 6 | pub use crc_codec::Crc8Codec; 7 | 8 | pub use no_codec::NoCodec; 9 | pub use simple_codec::SimpleCodec; 10 | 11 | pub type OneByteRegAddrCodec = SimpleCodec<1>; 12 | pub type TwoByteRegAddrCodec = SimpleCodec<2>; 13 | -------------------------------------------------------------------------------- /embedded-registers/src/i2c/codecs/no_codec.rs: -------------------------------------------------------------------------------- 1 | use crate::{ReadableRegister, WritableRegister}; 2 | 3 | /// A codec that represents absense of a codec. This has two main usecases: 4 | /// 5 | /// Firstly, if this is used as the default codec for a device, it essentially 6 | /// requires any associated register to explicitly specify a codec. Otherwise 7 | /// accessing that register via the [`RegisterInterfaceSync`](crate::RegisterInterfaceSync) 8 | /// or [`RegisterInterfaceAsync`](crate::RegisterInterfaceAsync) trait will cause a panic. 9 | /// 10 | /// Secondly, specifying this codec as the default for a register will cause 11 | /// any reads or writes to that register via the [`RegisterInterfaceSync`](crate::RegisterInterfaceSync) 12 | /// or [`RegisterInterfaceAsync`](crate::RegisterInterfaceAsync) traits to be performed 13 | /// through the default codec of the device. 14 | #[derive(Default)] 15 | pub struct NoCodec {} 16 | 17 | #[maybe_async_cfg::maybe( 18 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, I2cBoundBus), 19 | sync(feature = "sync"), 20 | async(feature = "async"), 21 | keep_self 22 | )] 23 | impl crate::i2c::Codec for NoCodec { 24 | #[inline] 25 | async fn read_register(_bound_bus: &mut crate::i2c::I2cBoundBus) -> Result 26 | where 27 | R: ReadableRegister, 28 | I: hal::i2c::I2c + hal::i2c::ErrorType, 29 | A: hal::i2c::AddressMode + Copy, 30 | { 31 | panic!("i2c::codecs::NoCodec cannot be used at runtime! Please specify a real codec to access this register."); 32 | } 33 | 34 | #[inline] 35 | async fn write_register( 36 | _bound_bus: &mut crate::i2c::I2cBoundBus, 37 | _register: impl AsRef, 38 | ) -> Result<(), I::Error> 39 | where 40 | R: WritableRegister, 41 | I: hal::i2c::I2c + hal::i2c::ErrorType, 42 | A: hal::i2c::AddressMode + Copy, 43 | { 44 | panic!("i2c::codecs::NoCodec cannot be used at runtime! Please specify a real codec to access this register."); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /embedded-registers/src/i2c/codecs/simple_codec.rs: -------------------------------------------------------------------------------- 1 | use crate::{ReadableRegister, WritableRegister}; 2 | use bytemuck::Zeroable; 3 | 4 | /// This codec represents the most commonly found codecs for I2C devices. 5 | /// The main variable is the size of register addresses in bytes. 6 | /// 7 | /// This codec has no information over register sizes or the existence of 8 | /// read/write address-auto-increment which some devices support. 9 | /// It will always send one header and then receive/send the associated register data, 10 | /// so it's compatible with auto-increment usage, but cannot be used to read or write 11 | /// registers that require interspersing the address between bytes. 12 | /// 13 | /// Devices often use a mixed mode, where some registers allow auto-increment while others 14 | /// don't, or where the address is directly associated with a specific, but varying, register size. 15 | /// Therefore, it is up to the user to make sure that accessing a register via this codec 16 | /// is supported by the hardware. 17 | /// 18 | /// The following generic parameters are available: 19 | /// 20 | /// | Parameter | Type | Description | 21 | /// |---|---|---| 22 | /// | `HEADER_SIZE` | `usize` | The size of the command header (register address) in bytes | 23 | #[derive(Default)] 24 | pub struct SimpleCodec {} 25 | 26 | #[maybe_async_cfg::maybe( 27 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, I2cBoundBus), 28 | sync(feature = "sync"), 29 | async(feature = "async"), 30 | keep_self 31 | )] 32 | impl crate::i2c::Codec for SimpleCodec { 33 | #[inline] 34 | async fn read_register(bound_bus: &mut crate::i2c::I2cBoundBus) -> Result 35 | where 36 | R: ReadableRegister, 37 | I: hal::i2c::I2c + hal::i2c::ErrorType, 38 | A: hal::i2c::AddressMode + Copy, 39 | { 40 | let header = &R::ADDRESS.to_be_bytes()[core::mem::size_of_val(&R::ADDRESS) - HEADER_SIZE..]; 41 | let mut register = R::zeroed(); 42 | 43 | bound_bus 44 | .interface 45 | .write_read(bound_bus.address, header, register.data_mut()) 46 | .await?; 47 | Ok(register) 48 | } 49 | 50 | #[inline] 51 | async fn write_register( 52 | bound_bus: &mut crate::i2c::I2cBoundBus, 53 | register: impl AsRef, 54 | ) -> Result<(), I::Error> 55 | where 56 | R: WritableRegister, 57 | I: hal::i2c::I2c + hal::i2c::ErrorType, 58 | A: hal::i2c::AddressMode + Copy, 59 | { 60 | #[repr(C, packed(1))] 61 | #[derive(Copy, Clone, bytemuck::Pod, Zeroable)] 62 | struct Buffer { 63 | header: [u8; HEADER_SIZE], 64 | register: R, 65 | } 66 | 67 | let mut buffer = Buffer::<{ HEADER_SIZE }, R> { 68 | header: R::ADDRESS.to_be_bytes()[core::mem::size_of_val(&R::ADDRESS) - HEADER_SIZE..] 69 | .try_into() 70 | .expect("Unexpected compile-time header address size. This is a bug in the chosen Codec or embedded-registers."), 71 | register: *register.as_ref(), 72 | }; 73 | 74 | let data = bytemuck::bytes_of_mut(&mut buffer); 75 | bound_bus.interface.write(bound_bus.address, data).await 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /embedded-registers/src/i2c/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod codecs; 2 | 3 | use core::{any::TypeId, marker::PhantomData}; 4 | 5 | use codecs::NoCodec; 6 | 7 | use crate::{ReadableRegister, WritableRegister}; 8 | 9 | /// Represents a trait for I2C codecs. These are responsible to perform 10 | /// writes and reads to registers, given the register address and 11 | /// the raw data. Different devices can have different ways to encode 12 | /// the desired address, address size, continuous-read mode and more. 13 | #[maybe_async_cfg::maybe( 14 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cBoundBus), 15 | sync(feature = "sync"), 16 | async(feature = "async") 17 | )] 18 | #[allow(async_fn_in_trait)] 19 | pub trait Codec: Default + 'static { 20 | /// Read this register from the given I2C interface/device. 21 | async fn read_register(bound_bus: &mut I2cBoundBus) -> Result 22 | where 23 | R: ReadableRegister, 24 | I: hal::i2c::I2c + hal::i2c::ErrorType, 25 | A: hal::i2c::AddressMode + Copy; 26 | 27 | /// Write this register to the given I2C interface/device. 28 | async fn write_register( 29 | bound_bus: &mut I2cBoundBus, 30 | register: impl AsRef, 31 | ) -> Result<(), I::Error> 32 | where 33 | R: WritableRegister, 34 | I: hal::i2c::I2c + hal::i2c::ErrorType, 35 | A: hal::i2c::AddressMode + Copy; 36 | } 37 | 38 | #[maybe_async_cfg::maybe( 39 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async")), 40 | sync(feature = "sync"), 41 | async(feature = "async") 42 | )] 43 | /// This represents a specific device bound to an I2C bus. 44 | pub struct I2cBoundBus 45 | where 46 | I: hal::i2c::I2c + hal::i2c::ErrorType, 47 | A: hal::i2c::AddressMode + Copy, 48 | { 49 | /// I2c interface 50 | pub interface: I, 51 | /// Device address 52 | pub address: A, 53 | } 54 | 55 | #[maybe_async_cfg::maybe( 56 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, I2cBoundBus), 57 | sync(feature = "sync"), 58 | async(feature = "async") 59 | )] 60 | /// This represents an I2C device on an I2C bus, including 61 | /// a default codec. 62 | pub struct I2cDevice 63 | where 64 | I: hal::i2c::I2c + hal::i2c::ErrorType, 65 | A: hal::i2c::AddressMode + Copy, 66 | C: Codec, 67 | { 68 | /// I2c interface and device address 69 | pub bound_bus: I2cBoundBus, 70 | /// The default codec used to interface with registers 71 | /// that don't explicitly specify a codec themselves. 72 | /// Usually this is a simple codec specifying address size and some metadata. 73 | /// See implementors of the Codec trait for more information on available codecs. 74 | pub default_codec: PhantomData, 75 | } 76 | 77 | #[maybe_async_cfg::maybe( 78 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, I2cBoundBus), 79 | sync(feature = "sync"), 80 | async(feature = "async") 81 | )] 82 | impl I2cDevice 83 | where 84 | I: hal::i2c::I2c + hal::i2c::ErrorType, 85 | A: hal::i2c::AddressMode + Copy, 86 | C: Codec, 87 | { 88 | /// Create a new I2cDevice from an interface and device address while specifying the default codec. 89 | pub fn new(interface: I, address: A) -> Self { 90 | Self { 91 | bound_bus: I2cBoundBus { interface, address }, 92 | default_codec: Default::default(), 93 | } 94 | } 95 | } 96 | 97 | #[maybe_async_cfg::maybe( 98 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, RegisterInterface), 99 | sync(feature = "sync"), 100 | async(feature = "async") 101 | )] 102 | impl crate::RegisterInterface for I2cDevice 103 | where 104 | I: hal::i2c::I2c + hal::i2c::ErrorType, 105 | A: hal::i2c::AddressMode + Copy, 106 | C: Codec, 107 | { 108 | type Error = I::Error; 109 | 110 | /// Read this register from this spi device using the codec 111 | /// specified by the register (if any) or otherwise the 112 | /// default codec of the device. 113 | #[inline] 114 | async fn read_register(&mut self) -> Result 115 | where 116 | R: ReadableRegister, 117 | { 118 | if TypeId::of::() == TypeId::of::() { 119 | C::read_register::(&mut self.bound_bus).await 120 | } else { 121 | ::read_register::(&mut self.bound_bus).await 122 | } 123 | } 124 | 125 | /// Write this register to this i2c device using the codec 126 | /// specified by the register (if any) or otherwise the 127 | /// default codec of the device. 128 | #[inline] 129 | async fn write_register(&mut self, register: impl AsRef) -> Result<(), I::Error> 130 | where 131 | R: WritableRegister, 132 | { 133 | if TypeId::of::() == TypeId::of::() { 134 | C::write_register(&mut self.bound_bus, register).await 135 | } else { 136 | ::write_register(&mut self.bound_bus, register).await 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /embedded-registers/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Defines traits for embedded-registers-derive. 2 | //! For Derive Docs see [embedded-registers-derive](https://docs.rs/embedded-registers-derive/latest/embedded_registers_derive/). 3 | //! 4 | //! This crate provides a procedural macro for effortless definitions of registers 5 | //! in embedded device drivers. This is automatically generates functions to read/write 6 | //! the register over I2C and SPI, although it isn't limited to those buses. The 7 | //! resulting struct may be trivially extended to work with any other similar communication bus. 8 | //! 9 | //! - Allows defintion of read-only, write-only and read-write registers 10 | //! - Generates I2C and SPI read/write functions 11 | //! - Registers are defined as bitfields via [bondrewd](https://github.com/Devlyn-Nelson/Bondrewd). 12 | //! - Only the accessed bitfield members are decoded, conserving memory and saving on CPU time. 13 | //! - Supports both async and blocking operation modes simultaneously by generating two versions of 14 | //! each driver using [maybe_async_cfg](https://docs.rs/maybe-async-cfg/latest/maybe_async_cfg) 15 | //! 16 | //! This crate was made primarily for [embedded-devices](https://github.com/oddlama/embedded-devices), 17 | //! which is a collection of drivers for a variety of different embedded sensors and devices. 18 | //! 19 | //! ## Defining a register 20 | //! 21 | //! Registers are defined simply by annotating a bondrewd struct with `#[register(address = 0x42, mode = "rw")]`. 22 | //! The necessary derive attribute for Bondrewd is added automatically. 23 | //! Take for example this 2-byte read-write register at device addresses `0x42,0x43`, which contains two `u8` values: 24 | //! 25 | //! ``` 26 | //! #![feature(generic_arg_infer)] 27 | //! use embedded_registers::register; 28 | //! 29 | //! #[register(address = 0x42, mode = "rw")] 30 | //! #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 31 | //! pub struct ValueRegister { 32 | //! pub width: u8, 33 | //! pub height: u8, 34 | //! } 35 | //! ``` 36 | //! 37 | //! This will create two structs called `ValueRegister` and `ValueRegisterBitfield`. 38 | //! The first will only contain a byte array `[u8; 2]` to store the packed register contents, 39 | //! and the latter will contain the unpacked actual members as defined above. 40 | //! You will always interface with a device using the packed data, which can be transferred over the bus as-is. 41 | //! 42 | //! The packed data contains methods to directly read/write the underlying storage array, which 43 | //! means you can only unpack what you need, saving resources. 44 | //! 45 | //! > I find it a bit misleading that the members written in `ValueRegister` end up in `ValueRegisterBitfield`. 46 | //! > So this might change in the future, but I currently cannot think of another design that is as simple 47 | //! > to use as this one right now. The issue is that we need a struct for the packed data and one for 48 | //! > the unpacked data. Since we usually deal with the packed data, and want to allow direct read/write 49 | //! > operations on the packed data for performance, the naming gets confusing quite quickly. 50 | //! 51 | //! ### Accessing a register (async) 52 | //! 53 | //! To access such a register, you need to obtain an interface that implements the `RegisterInterfaceAsync` trait. 54 | //! This crate already comes with an implementation of that trait for I2C and SPI devices called [`i2c::I2cDeviceAsync`] and [`spi::SpiDeviceAsync`] respectively. 55 | //! You may then read the register simply by calling [`read_register`](RegisterInterfaceAsync::read_register) or [`write_register`](RegisterInterfaceAsync::write_register) on that interface. 56 | //! 57 | //! ```rust, only_if(async) 58 | //! # #![feature(generic_arg_infer)] 59 | //! # use embedded_registers::register; 60 | //! # #[register(address = 0x42, mode = "rw")] 61 | //! # #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)] 62 | //! # pub struct ValueRegister { 63 | //! # pub width: u8, 64 | //! # pub height: u8, 65 | //! # } 66 | //! use embedded_registers::{i2c::{I2cDeviceAsync, codecs::OneByteRegAddrCodec}, RegisterInterfaceAsync}; 67 | //! 68 | //! async fn init(mut i2c_bus: I) -> Result<(), I::Error> 69 | //! where 70 | //! I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType, 71 | //! { 72 | //! // Imagine we already have constructed a device using 73 | //! // the i2c bus from your controller, a device address and default codec: 74 | //! let mut dev = I2cDeviceAsync::<_, _, OneByteRegAddrCodec>::new(i2c_bus, 0x12); 75 | //! // We can now retrieve the register 76 | //! let mut reg = dev.read_register::().await?; 77 | //! 78 | //! // Unpack a specific field from the register and print it 79 | //! println!("{}", reg.read_width()); 80 | //! // If you need all fields (or are not bound to tight resource constraints), 81 | //! // you can also unpack all fields and access them more conveniently 82 | //! let data = reg.read_all(); 83 | //! // All bitfields implement Debug and defmt::Format, so you can conveniently 84 | //! // print the contents 85 | //! println!("{:?}", data); 86 | //! 87 | //! // We can also change a single value 88 | //! reg.write_height(190); 89 | //! // Or pack a bitfield and replace everything 90 | //! reg.write_all(data); // same as reg = ValueRegister::new(data); 91 | //! 92 | //! // Which we can now write back to the device, given that the register is writable. 93 | //! dev.write_register(reg).await?; 94 | //! Ok(()) 95 | //! } 96 | //! ``` 97 | 98 | #![cfg_attr(not(feature = "std"), no_std)] 99 | #[cfg(not(any(feature = "sync", feature = "async")))] 100 | compile_error!("You must enable at least one of the create features `sync` or `async`"); 101 | 102 | pub mod i2c; 103 | pub mod spi; 104 | 105 | // Re-exports for derive macro 106 | pub use bondrewd; 107 | pub use bytemuck; 108 | 109 | /// The basis trait for all registers. A register is a type 110 | /// that maps to a specific register on an embedded device and 111 | /// should own the raw data required for this register. 112 | /// 113 | /// Additionally, a register knows the virtual address ossociated 114 | /// to the embedded device, and a bitfield representation of the 115 | /// data content. 116 | pub trait Register: Default + Clone + bytemuck::Pod { 117 | /// The size of the register in bytes 118 | const REGISTER_SIZE: usize; 119 | /// The virtual address of this register 120 | const ADDRESS: u64; 121 | 122 | /// The associated bondrewd bitfield type 123 | type Bitfield; 124 | /// The SPI codec that should be used for this register when no 125 | /// codec is specified by the user. Setting this to `spi::codecs::NoCodec` 126 | /// will automatically cause accesses to use the device's default codec. 127 | /// If the device doesn't support SPI communication, this can be ignored. 128 | #[cfg(all(feature = "sync", not(feature = "async")))] 129 | type SpiCodec: spi::CodecSync; 130 | #[cfg(all(not(feature = "sync"), feature = "async"))] 131 | type SpiCodec: spi::CodecAsync; 132 | #[cfg(all(feature = "sync", feature = "async"))] 133 | type SpiCodec: spi::CodecSync + spi::CodecAsync; 134 | /// The I2C codec that should be used for this register when no 135 | /// codec is specified by the user. Setting this to `i2c::codecs::NoCodec` 136 | /// will automatically cause accesses to use the device's default codec. 137 | /// If the device doesn't support I2C communication, this can be ignored. 138 | #[cfg(all(feature = "sync", not(feature = "async")))] 139 | type I2cCodec: i2c::CodecSync; 140 | #[cfg(all(not(feature = "sync"), feature = "async"))] 141 | type I2cCodec: i2c::CodecAsync; 142 | #[cfg(all(feature = "sync", feature = "async"))] 143 | type I2cCodec: i2c::CodecSync + i2c::CodecAsync; 144 | 145 | /// Provides immutable access to the raw data. 146 | fn data(&self) -> &[u8]; 147 | /// Provides mutable access to the raw data. 148 | fn data_mut(&mut self) -> &mut [u8]; 149 | } 150 | 151 | /// This trait is a marker trait implemented by any register that can be read via a specific bus interface. 152 | pub trait ReadableRegister: Register {} 153 | 154 | /// This trait is a marker trait implemented by any register that can be written via a specific bus interface. 155 | pub trait WritableRegister: Register {} 156 | 157 | /// A trait that is implemented by any bus interface and allows 158 | /// devices with registers to share register read/write implementations 159 | /// independent of the actual interface in use. 160 | #[allow(async_fn_in_trait)] 161 | #[maybe_async_cfg::maybe(sync(feature = "sync"), async(feature = "async"))] 162 | pub trait RegisterInterface { 163 | type Error; 164 | 165 | /// Reads the given register via this interface 166 | async fn read_register(&mut self) -> Result 167 | where 168 | R: ReadableRegister; 169 | 170 | /// Writes the given register via this interface 171 | async fn write_register(&mut self, register: impl AsRef) -> Result<(), Self::Error> 172 | where 173 | R: WritableRegister; 174 | } 175 | 176 | // re-export the derive stuff 177 | #[cfg(feature = "derive")] 178 | #[doc(hidden)] 179 | pub use embedded_registers_derive::*; 180 | -------------------------------------------------------------------------------- /embedded-registers/src/spi/codecs/mod.rs: -------------------------------------------------------------------------------- 1 | mod no_codec; 2 | mod simple_codec; 3 | 4 | pub use no_codec::NoCodec; 5 | pub use simple_codec::SimpleCodec; 6 | -------------------------------------------------------------------------------- /embedded-registers/src/spi/codecs/no_codec.rs: -------------------------------------------------------------------------------- 1 | use crate::{ReadableRegister, WritableRegister}; 2 | 3 | /// A codec that represents absense of a codec. This has two main usecases: 4 | /// 5 | /// Firstly, if this is used as the default codec for a device, it essentially 6 | /// requires any associated register to explicitly specify a codec. Otherwise 7 | /// accessing that register via the [`RegisterInterfaceSync`](crate::RegisterInterfaceSync) 8 | /// or [`RegisterInterfaceAsync`](crate::RegisterInterfaceAsync) trait will cause a panic. 9 | /// 10 | /// Secondly, specifying this codec as the default for a register will cause 11 | /// any reads or writes to that register via the [`RegisterInterfaceSync`](crate::RegisterInterfaceSync) 12 | /// or [`RegisterInterfaceAsync`](crate::RegisterInterfaceAsync) traits to be performed 13 | /// to be performed through the default codec of the device. 14 | pub struct NoCodec {} 15 | 16 | #[maybe_async_cfg::maybe( 17 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec), 18 | sync(feature = "sync"), 19 | async(feature = "async"), 20 | keep_self 21 | )] 22 | impl crate::spi::Codec for NoCodec { 23 | #[inline] 24 | async fn read_register(_interface: &mut I) -> Result 25 | where 26 | R: ReadableRegister, 27 | I: hal::spi::r#SpiDevice, 28 | { 29 | panic!("spi::codecs::NoCodec cannot be used at runtime! Please specify a real codec to access this register."); 30 | } 31 | 32 | #[inline] 33 | async fn write_register(_interface: &mut I, _register: impl AsRef) -> Result<(), I::Error> 34 | where 35 | R: WritableRegister, 36 | I: hal::spi::r#SpiDevice, 37 | { 38 | panic!("spi::codecs::NoCodec cannot be used at runtime! Please specify a real codec to access this register."); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /embedded-registers/src/spi/codecs/simple_codec.rs: -------------------------------------------------------------------------------- 1 | use crate::{ReadableRegister, Register, WritableRegister}; 2 | use bytemuck::Zeroable; 3 | 4 | /// This codec represents the most commonly found codecs for SPI devices. 5 | /// It consists of an N-bit big-endian register address, a 1-bit R/W indicator 6 | /// and uses zero initializion for reserved bits. The header is always a multiple of 8-bit 7 | /// in width. 8 | /// 9 | /// This codec has no information over register sizes or the existence of 10 | /// read/write address-auto-increment which some devices support. 11 | /// It will always send one header and then receive/send the associated register data, 12 | /// so it's compatible with auto-increment usage, but cannot be used to read or write 13 | /// registers that require interspersing the address between bytes. 14 | /// 15 | /// Devices often use a mixed mode, where some registers allow auto-increment while others 16 | /// don't, or where the address is directly associated with a specific, but varying, register size. 17 | /// Therefore, it is up to the user to make sure that accessing a register via this codec 18 | /// is supported by the hardware. 19 | /// 20 | /// The following generic parameters are available: 21 | /// 22 | /// | Parameter | Type | Description | 23 | /// |---|---|---| 24 | /// | `HEADER_SIZE` | `usize` | The size of the command header in bytes | 25 | /// | `ADDR_MSB` | `u8` | The bit index of the MSB of the register-address (inclusive) | 26 | /// | `ADDR_LSB` | `u8` | The bit index of the LSB of the register-address (inclusive) | 27 | /// | `RW_BIT` | `u8` | The bit index of the RW bit when interpreting the struct in big-endian | 28 | /// | `RW_1_IS_READ` | `bool` | whether the setting the RW bit signals read-mode (true) or write-mode (false) | 29 | /// | `READ_DELAY` | `usize` | Number of bytes that we have to wait (send additional zeros) after sending the header until data arrives | 30 | pub struct SimpleCodec< 31 | const HEADER_SIZE: usize, 32 | const ADDR_MSB: u8, 33 | const ADDR_LSB: u8, 34 | const RW_BIT: u8, 35 | const RW_1_IS_READ: bool, 36 | const READ_DELAY: usize, 37 | > {} 38 | 39 | impl< 40 | const HEADER_SIZE: usize, 41 | const ADDR_MSB: u8, 42 | const ADDR_LSB: u8, 43 | const RW_BIT: u8, 44 | const RW_1_IS_READ: bool, 45 | const READ_DELAY: usize, 46 | > SimpleCodec 47 | { 48 | #[inline] 49 | pub fn fill_addr_header(header: &mut [u8]) 50 | where 51 | R: Register, 52 | { 53 | // create a mask with ADDR_MSB + 1 ones (it is inclusive). 54 | // It doesn't matter if ADDR_LSB is > 0, since the shift below already guarantees 55 | // that there will only ever be zeros. 56 | let addr_mask = u64::checked_shl(1, ADDR_MSB as u32 + 1).unwrap_or(0).wrapping_sub(1); 57 | // Shift the address to the correct place 58 | let addr_shifted = (R::ADDRESS << ADDR_LSB) & addr_mask; 59 | // incorporate addess 60 | let addr_bytes = addr_shifted.to_le_bytes(); 61 | let affected_bytes = ((ADDR_MSB - ADDR_LSB) / 8) as usize; 62 | for i in 0..=affected_bytes { 63 | header[HEADER_SIZE - 1 - i] |= addr_bytes[i]; 64 | } 65 | } 66 | } 67 | 68 | #[maybe_async_cfg::maybe( 69 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec), 70 | sync(feature = "sync"), 71 | async(feature = "async"), 72 | keep_self 73 | )] 74 | impl< 75 | const HEADER_SIZE: usize, 76 | const ADDR_MSB: u8, 77 | const ADDR_LSB: u8, 78 | const RW_BIT: u8, 79 | const RW_1_IS_READ: bool, 80 | const READ_DELAY: usize, 81 | > crate::spi::Codec for SimpleCodec 82 | { 83 | #[inline] 84 | async fn read_register(interface: &mut I) -> Result 85 | where 86 | R: ReadableRegister, 87 | I: hal::spi::r#SpiDevice, 88 | { 89 | #[repr(C, packed(1))] 90 | #[derive(Copy, Clone, bytemuck::Pod, Zeroable)] 91 | struct Buffer { 92 | header: [u8; HEADER_SIZE], 93 | delay: [u8; READ_DELAY], 94 | register: R, 95 | } 96 | 97 | let mut buffer = Buffer::<{ HEADER_SIZE }, { READ_DELAY }, R>::zeroed(); 98 | let data = bytemuck::bytes_of_mut(&mut buffer); 99 | // Set RW_BIT if necessary 100 | data[HEADER_SIZE - 1 - (RW_BIT as usize) / 8] |= (RW_1_IS_READ as u8) << (RW_BIT % 8); 101 | Self::fill_addr_header::(data); 102 | interface.transfer_in_place(data).await?; 103 | Ok(buffer.register) 104 | } 105 | 106 | #[inline] 107 | async fn write_register(interface: &mut I, register: impl AsRef) -> Result<(), I::Error> 108 | where 109 | R: WritableRegister, 110 | I: hal::spi::r#SpiDevice, 111 | { 112 | #[repr(C, packed(1))] 113 | #[derive(Copy, Clone, bytemuck::Pod, Zeroable)] 114 | struct Buffer { 115 | header: [u8; HEADER_SIZE], 116 | register: R, 117 | } 118 | 119 | let mut buffer = Buffer::<{ HEADER_SIZE }, R> { 120 | header: [0u8; HEADER_SIZE], 121 | register: *register.as_ref(), 122 | }; 123 | 124 | let data = bytemuck::bytes_of_mut(&mut buffer); 125 | // Set RW_BIT if necessary 126 | data[HEADER_SIZE - 1 - (RW_BIT as usize) / 8] |= ((!RW_1_IS_READ) as u8) << (RW_BIT % 8); 127 | Self::fill_addr_header::(data); 128 | interface.write(data).await 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /embedded-registers/src/spi/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod codecs; 2 | 3 | use core::{any::TypeId, marker::PhantomData}; 4 | 5 | use codecs::NoCodec; 6 | 7 | use crate::{ReadableRegister, WritableRegister}; 8 | 9 | /// Represents a trait for SPI codecs. These are responsible to perform 10 | /// writes and reads to registers, given the register address and 11 | /// the raw data. Different devices can have different ways to encode 12 | /// the desired address, R/W bit location, continuous-read mode and more. 13 | #[maybe_async_cfg::maybe( 14 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async")), 15 | sync(feature = "sync"), 16 | async(feature = "async") 17 | )] 18 | #[allow(async_fn_in_trait)] 19 | pub trait Codec: 'static { 20 | /// Read this register from the given SPI interface/device. 21 | async fn read_register(interface: &mut I) -> Result 22 | where 23 | R: ReadableRegister, 24 | I: hal::spi::r#SpiDevice; 25 | 26 | /// Write this register to the given SPI interface/device. 27 | async fn write_register(interface: &mut I, register: impl AsRef) -> Result<(), I::Error> 28 | where 29 | R: WritableRegister, 30 | I: hal::spi::r#SpiDevice; 31 | } 32 | 33 | #[maybe_async_cfg::maybe( 34 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec), 35 | sync(feature = "sync"), 36 | async(feature = "async") 37 | )] 38 | /// This represents an SPI device on an SPI bus. 39 | pub struct SpiDevice 40 | where 41 | I: hal::spi::r#SpiDevice, 42 | C: Codec, 43 | { 44 | /// Spi interface 45 | pub interface: I, 46 | /// The default codec used to interface with registers that don't explicitly specify a codec 47 | /// themselves. Usually this is a simple codec designating a bit for R/W and a bit-range 48 | /// for the register address. See implementors of the Codec trait for more information on 49 | /// available codecs. 50 | default_codec: PhantomData, 51 | } 52 | 53 | #[maybe_async_cfg::maybe( 54 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec), 55 | sync(feature = "sync"), 56 | async(feature = "async") 57 | )] 58 | impl SpiDevice 59 | where 60 | I: hal::spi::r#SpiDevice, 61 | C: Codec, 62 | { 63 | /// Create a new I2cDevice from an interface while specifying the default codec. 64 | pub fn new(interface: I) -> Self { 65 | Self { 66 | interface, 67 | default_codec: Default::default(), 68 | } 69 | } 70 | } 71 | 72 | #[maybe_async_cfg::maybe( 73 | idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, RegisterInterface), 74 | sync(feature = "sync"), 75 | async(feature = "async") 76 | )] 77 | impl crate::RegisterInterface for SpiDevice 78 | where 79 | I: hal::spi::r#SpiDevice, 80 | C: Codec, 81 | { 82 | type Error = I::Error; 83 | 84 | /// Read this register from this spi device using the codec 85 | /// specified by the register (if any) or otherwise the 86 | /// default codec of the device. 87 | #[inline] 88 | async fn read_register(&mut self) -> Result 89 | where 90 | R: ReadableRegister, 91 | { 92 | if TypeId::of::() == TypeId::of::() { 93 | C::read_register::(&mut self.interface).await 94 | } else { 95 | ::read_register::(&mut self.interface).await 96 | } 97 | } 98 | 99 | /// Write this register to this spi device using the codec 100 | /// specified by the register (if any) or otherwise the 101 | /// default codec of the device. 102 | #[inline] 103 | async fn write_register(&mut self, register: impl AsRef) -> Result<(), I::Error> 104 | where 105 | R: WritableRegister, 106 | { 107 | if TypeId::of::() == TypeId::of::() { 108 | C::write_register(&mut self.interface, register).await 109 | } else { 110 | ::write_register(&mut self.interface, register).await 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1727316705, 7 | "narHash": "sha256-/mumx8AQ5xFuCJqxCIOFCHTVlxHkMT21idpbgbm/TIE=", 8 | "owner": "ipetkov", 9 | "repo": "crane", 10 | "rev": "5b03654ce046b5167e7b0bccbd8244cb56c16f0e", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "ipetkov", 15 | "ref": "v0.19.0", 16 | "repo": "crane", 17 | "type": "github" 18 | } 19 | }, 20 | "devshell": { 21 | "inputs": { 22 | "nixpkgs": [ 23 | "nixpkgs" 24 | ] 25 | }, 26 | "locked": { 27 | "lastModified": 1741473158, 28 | "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", 29 | "owner": "numtide", 30 | "repo": "devshell", 31 | "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "numtide", 36 | "repo": "devshell", 37 | "type": "github" 38 | } 39 | }, 40 | "dream2nix": { 41 | "inputs": { 42 | "nixpkgs": [ 43 | "nci", 44 | "nixpkgs" 45 | ], 46 | "purescript-overlay": "purescript-overlay", 47 | "pyproject-nix": "pyproject-nix" 48 | }, 49 | "locked": { 50 | "lastModified": 1735160684, 51 | "narHash": "sha256-n5CwhmqKxifuD4Sq4WuRP/h5LO6f23cGnSAuJemnd/4=", 52 | "owner": "nix-community", 53 | "repo": "dream2nix", 54 | "rev": "8ce6284ff58208ed8961681276f82c2f8f978ef4", 55 | "type": "github" 56 | }, 57 | "original": { 58 | "owner": "nix-community", 59 | "repo": "dream2nix", 60 | "type": "github" 61 | } 62 | }, 63 | "flake-compat": { 64 | "flake": false, 65 | "locked": { 66 | "lastModified": 1696426674, 67 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 68 | "owner": "edolstra", 69 | "repo": "flake-compat", 70 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "edolstra", 75 | "repo": "flake-compat", 76 | "type": "github" 77 | } 78 | }, 79 | "flake-compat_2": { 80 | "flake": false, 81 | "locked": { 82 | "lastModified": 1696426674, 83 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 84 | "owner": "edolstra", 85 | "repo": "flake-compat", 86 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 87 | "type": "github" 88 | }, 89 | "original": { 90 | "owner": "edolstra", 91 | "repo": "flake-compat", 92 | "type": "github" 93 | } 94 | }, 95 | "flake-parts": { 96 | "inputs": { 97 | "nixpkgs-lib": "nixpkgs-lib" 98 | }, 99 | "locked": { 100 | "lastModified": 1743550720, 101 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 102 | "owner": "hercules-ci", 103 | "repo": "flake-parts", 104 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 105 | "type": "github" 106 | }, 107 | "original": { 108 | "owner": "hercules-ci", 109 | "repo": "flake-parts", 110 | "type": "github" 111 | } 112 | }, 113 | "gitignore": { 114 | "inputs": { 115 | "nixpkgs": [ 116 | "pre-commit-hooks", 117 | "nixpkgs" 118 | ] 119 | }, 120 | "locked": { 121 | "lastModified": 1709087332, 122 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 123 | "owner": "hercules-ci", 124 | "repo": "gitignore.nix", 125 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 126 | "type": "github" 127 | }, 128 | "original": { 129 | "owner": "hercules-ci", 130 | "repo": "gitignore.nix", 131 | "type": "github" 132 | } 133 | }, 134 | "mk-naked-shell": { 135 | "flake": false, 136 | "locked": { 137 | "lastModified": 1681286841, 138 | "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=", 139 | "owner": "yusdacra", 140 | "repo": "mk-naked-shell", 141 | "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd", 142 | "type": "github" 143 | }, 144 | "original": { 145 | "owner": "yusdacra", 146 | "repo": "mk-naked-shell", 147 | "type": "github" 148 | } 149 | }, 150 | "nci": { 151 | "inputs": { 152 | "crane": "crane", 153 | "dream2nix": "dream2nix", 154 | "mk-naked-shell": "mk-naked-shell", 155 | "nixpkgs": [ 156 | "nixpkgs" 157 | ], 158 | "parts": "parts", 159 | "rust-overlay": "rust-overlay", 160 | "treefmt": "treefmt" 161 | }, 162 | "locked": { 163 | "lastModified": 1743833727, 164 | "narHash": "sha256-mtpvT+bvzd2HIxa4BYkiRZaArRzLMbYk3JwFKC6Xpkw=", 165 | "owner": "yusdacra", 166 | "repo": "nix-cargo-integration", 167 | "rev": "e21b0a9f7196b37a45e5d6ed42bc2e3b9d1084a8", 168 | "type": "github" 169 | }, 170 | "original": { 171 | "owner": "yusdacra", 172 | "repo": "nix-cargo-integration", 173 | "type": "github" 174 | } 175 | }, 176 | "nixpkgs": { 177 | "locked": { 178 | "lastModified": 1743583204, 179 | "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", 180 | "owner": "NixOS", 181 | "repo": "nixpkgs", 182 | "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", 183 | "type": "github" 184 | }, 185 | "original": { 186 | "owner": "NixOS", 187 | "ref": "nixos-unstable", 188 | "repo": "nixpkgs", 189 | "type": "github" 190 | } 191 | }, 192 | "nixpkgs-lib": { 193 | "locked": { 194 | "lastModified": 1743296961, 195 | "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", 196 | "owner": "nix-community", 197 | "repo": "nixpkgs.lib", 198 | "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", 199 | "type": "github" 200 | }, 201 | "original": { 202 | "owner": "nix-community", 203 | "repo": "nixpkgs.lib", 204 | "type": "github" 205 | } 206 | }, 207 | "parts": { 208 | "inputs": { 209 | "nixpkgs-lib": [ 210 | "nci", 211 | "nixpkgs" 212 | ] 213 | }, 214 | "locked": { 215 | "lastModified": 1743550720, 216 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 217 | "owner": "hercules-ci", 218 | "repo": "flake-parts", 219 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 220 | "type": "github" 221 | }, 222 | "original": { 223 | "owner": "hercules-ci", 224 | "repo": "flake-parts", 225 | "type": "github" 226 | } 227 | }, 228 | "pre-commit-hooks": { 229 | "inputs": { 230 | "flake-compat": "flake-compat_2", 231 | "gitignore": "gitignore", 232 | "nixpkgs": [ 233 | "nixpkgs" 234 | ] 235 | }, 236 | "locked": { 237 | "lastModified": 1742649964, 238 | "narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=", 239 | "owner": "cachix", 240 | "repo": "pre-commit-hooks.nix", 241 | "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82", 242 | "type": "github" 243 | }, 244 | "original": { 245 | "owner": "cachix", 246 | "repo": "pre-commit-hooks.nix", 247 | "type": "github" 248 | } 249 | }, 250 | "purescript-overlay": { 251 | "inputs": { 252 | "flake-compat": "flake-compat", 253 | "nixpkgs": [ 254 | "nci", 255 | "dream2nix", 256 | "nixpkgs" 257 | ], 258 | "slimlock": "slimlock" 259 | }, 260 | "locked": { 261 | "lastModified": 1728546539, 262 | "narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=", 263 | "owner": "thomashoneyman", 264 | "repo": "purescript-overlay", 265 | "rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4", 266 | "type": "github" 267 | }, 268 | "original": { 269 | "owner": "thomashoneyman", 270 | "repo": "purescript-overlay", 271 | "type": "github" 272 | } 273 | }, 274 | "pyproject-nix": { 275 | "flake": false, 276 | "locked": { 277 | "lastModified": 1702448246, 278 | "narHash": "sha256-hFg5s/hoJFv7tDpiGvEvXP0UfFvFEDgTdyHIjDVHu1I=", 279 | "owner": "davhau", 280 | "repo": "pyproject.nix", 281 | "rev": "5a06a2697b228c04dd2f35659b4b659ca74f7aeb", 282 | "type": "github" 283 | }, 284 | "original": { 285 | "owner": "davhau", 286 | "ref": "dream2nix", 287 | "repo": "pyproject.nix", 288 | "type": "github" 289 | } 290 | }, 291 | "root": { 292 | "inputs": { 293 | "devshell": "devshell", 294 | "flake-parts": "flake-parts", 295 | "nci": "nci", 296 | "nixpkgs": "nixpkgs", 297 | "pre-commit-hooks": "pre-commit-hooks", 298 | "treefmt-nix": "treefmt-nix" 299 | } 300 | }, 301 | "rust-overlay": { 302 | "inputs": { 303 | "nixpkgs": [ 304 | "nci", 305 | "nixpkgs" 306 | ] 307 | }, 308 | "locked": { 309 | "lastModified": 1743820323, 310 | "narHash": "sha256-UXxJogXhPhBFaX4uxmMudcD/x3sEGFtoSc4busTcftY=", 311 | "owner": "oxalica", 312 | "repo": "rust-overlay", 313 | "rev": "b4734ce867252f92cdc7d25f8cc3b7cef153e703", 314 | "type": "github" 315 | }, 316 | "original": { 317 | "owner": "oxalica", 318 | "repo": "rust-overlay", 319 | "type": "github" 320 | } 321 | }, 322 | "slimlock": { 323 | "inputs": { 324 | "nixpkgs": [ 325 | "nci", 326 | "dream2nix", 327 | "purescript-overlay", 328 | "nixpkgs" 329 | ] 330 | }, 331 | "locked": { 332 | "lastModified": 1688756706, 333 | "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=", 334 | "owner": "thomashoneyman", 335 | "repo": "slimlock", 336 | "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c", 337 | "type": "github" 338 | }, 339 | "original": { 340 | "owner": "thomashoneyman", 341 | "repo": "slimlock", 342 | "type": "github" 343 | } 344 | }, 345 | "treefmt": { 346 | "inputs": { 347 | "nixpkgs": [ 348 | "nci", 349 | "nixpkgs" 350 | ] 351 | }, 352 | "locked": { 353 | "lastModified": 1743748085, 354 | "narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=", 355 | "owner": "numtide", 356 | "repo": "treefmt-nix", 357 | "rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d", 358 | "type": "github" 359 | }, 360 | "original": { 361 | "owner": "numtide", 362 | "repo": "treefmt-nix", 363 | "type": "github" 364 | } 365 | }, 366 | "treefmt-nix": { 367 | "inputs": { 368 | "nixpkgs": [ 369 | "nixpkgs" 370 | ] 371 | }, 372 | "locked": { 373 | "lastModified": 1743748085, 374 | "narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=", 375 | "owner": "numtide", 376 | "repo": "treefmt-nix", 377 | "rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d", 378 | "type": "github" 379 | }, 380 | "original": { 381 | "owner": "numtide", 382 | "repo": "treefmt-nix", 383 | "type": "github" 384 | } 385 | } 386 | }, 387 | "root": "root", 388 | "version": 7 389 | } 390 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | devshell = { 4 | url = "github:numtide/devshell"; 5 | inputs.nixpkgs.follows = "nixpkgs"; 6 | }; 7 | flake-parts.url = "github:hercules-ci/flake-parts"; 8 | nci = { 9 | url = "github:yusdacra/nix-cargo-integration"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 13 | pre-commit-hooks = { 14 | url = "github:cachix/pre-commit-hooks.nix"; 15 | inputs.nixpkgs.follows = "nixpkgs"; 16 | }; 17 | treefmt-nix = { 18 | url = "github:numtide/treefmt-nix"; 19 | inputs.nixpkgs.follows = "nixpkgs"; 20 | }; 21 | }; 22 | 23 | outputs = 24 | inputs: 25 | inputs.flake-parts.lib.mkFlake { inherit inputs; } { 26 | imports = [ 27 | inputs.devshell.flakeModule 28 | inputs.nci.flakeModule 29 | inputs.pre-commit-hooks.flakeModule 30 | inputs.treefmt-nix.flakeModule 31 | ]; 32 | 33 | systems = [ 34 | "x86_64-linux" 35 | "aarch64-linux" 36 | ]; 37 | 38 | perSystem = 39 | { 40 | config, 41 | pkgs, 42 | ... 43 | }: 44 | let 45 | projectName = "embedded-devices"; 46 | in 47 | { 48 | devshells.default = { 49 | packages = [ 50 | config.treefmt.build.wrapper 51 | pkgs.cargo-release 52 | ]; 53 | devshell.startup.pre-commit.text = config.pre-commit.installationScript; 54 | }; 55 | 56 | pre-commit.settings.hooks.treefmt.enable = true; 57 | treefmt = { 58 | projectRootFile = "flake.nix"; 59 | programs = { 60 | deadnix.enable = true; 61 | statix.enable = true; 62 | nixfmt.enable = true; 63 | rustfmt.enable = true; 64 | }; 65 | }; 66 | 67 | nci.projects.${projectName} = { 68 | path = ./.; 69 | numtideDevshell = "default"; 70 | }; 71 | nci.crates.${projectName} = { }; 72 | 73 | packages.default = config.nci.outputs.${projectName}.packages.release; 74 | }; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-commit = true 2 | sign-tag = true 3 | pre-release-commit-message = "chore: release version {{version}}" 4 | tag-message = "chore: release {{crate_name}} version {{version}}" 5 | tag-prefix = "" 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | --------------------------------------------------------------------------------