├── .cargo └── config.toml ├── .github └── workflows │ ├── ci.yaml │ ├── publish.yaml │ └── release.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── stm32f051r8 │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── bin │ │ ├── async.rs │ │ └── sync.rs └── stm32f303vc │ ├── .cargo │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── bin │ └── async.rs ├── nucleo.cfg ├── openocd.gdb └── src ├── lib.rs └── read.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "arm-none-eabi-gdb -x openocd.gdb -q" 3 | 4 | rustflags = [ 5 | # LLD (shipped with the Rust toolchain) is used as the default linker 6 | "-C", "link-arg=-Tlink.x", 7 | ] 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: {} 8 | 9 | jobs: 10 | fmt: 11 | name: Rustfmt 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | components: rustfmt 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: fmt 23 | args: --all -- --check 24 | clippy: 25 | name: Clippy 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | include: 30 | - toolchain: nightly 31 | cargo-args: --all-features 32 | - toolchain: stable 33 | cargo-args: "" 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | profile: minimal 39 | toolchain: ${{ matrix.toolchain }} 40 | components: clippy 41 | - uses: actions-rs/cargo@v1 42 | with: 43 | toolchain: ${{ matrix.toolchain }} 44 | command: clippy 45 | args: ${{ matrix.cargo-args }} -- -D warnings 46 | package: 47 | name: Cargo package 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v2 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | profile: minimal 54 | toolchain: stable 55 | components: rustfmt 56 | - uses: actions-rs/cargo@v1 57 | with: 58 | command: package 59 | build_and_test: 60 | name: Build and test 61 | runs-on: ubuntu-latest 62 | strategy: 63 | matrix: 64 | include: 65 | - toolchain: nightly 66 | cargo-args: --all-features 67 | - toolchain: stable 68 | cargo-args: "" 69 | steps: 70 | - uses: actions/checkout@v2 71 | 72 | - name: Set up Rust toolchain 73 | uses: dtolnay/rust-toolchain@master 74 | with: 75 | toolchain: ${{ matrix.toolchain }} 76 | targets: thumbv6m-none-eabi,thumbv7m-none-eabi 77 | 78 | - name: Build 79 | run: cargo build --release ${{ matrix.cargo-args }} 80 | 81 | - name: Build stm32f051r8 example 82 | run: cargo build --release --manifest-path ./examples/stm32f051r8/Cargo.toml --target thumbv6m-none-eabi 83 | - name: Build stm32f303vc example 84 | run: cargo build --release --manifest-path ./examples/stm32f303vc/Cargo.toml --target thumbv7m-none-eabi 85 | 86 | - name: Test 87 | run: cargo test ${{ matrix.cargo-args }} 88 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | name: Publish to crates.io 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup rust 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | profile: minimal 18 | - name: Login 19 | uses: actions-rs/cargo@v1 20 | with: 21 | command: login 22 | args: ${{ secrets.CRATESIO_TOKEN }} 23 | - name: Publish 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: publish 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: {} 5 | 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | profile: minimal 16 | - name: Get version 17 | id: version 18 | run: echo "::set-output name=version::$(cargo read-manifest | jq .version -r)" 19 | - uses: actions/github-script@v3 20 | name: Create draft release 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | script: | 24 | const fs = require("fs").promises; 25 | 26 | const currentVersionChangesRegex = /## ${{ steps.version.outputs.version }}$(.*?)$\n##[^#]/ms; 27 | 28 | const changelog = await fs.readFile("CHANGELOG.md", "utf8"); 29 | const changes = changelog.match(currentVersionChangesRegex)[1].trim(); 30 | 31 | console.log("Creating draft release for ${{ steps.version.outputs.version }}"); 32 | 33 | await github.repos.createRelease({ 34 | ...context.repo, 35 | draft: true, 36 | name: "dht-sensor v${{ steps.version.outputs.version }}", 37 | target_commitish: "${{ github.sha }}", 38 | tag_name: "v${{ steps.version.outputs.version }}", 39 | body: changes, 40 | }); 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.1 4 | 5 | ### Fixes 6 | 7 | * Fix add with overflow for debug builds (#6) 8 | 9 | ## 0.2.0 10 | 11 | ### Features 12 | 13 | * Add timeouts in read functions for reliability (#2) 14 | * Use `dyn Delay` instead of generic type 15 | 16 | ## 0.1.1 17 | 18 | ### Fixes 19 | 20 | * build: Add metadata to `Cargo.toml` to fix https://docs.rs build 21 | 22 | ## 0.1.0 23 | 24 | ### Features 25 | 26 | * Use one of two functions `dht11::Reading::read` and `dht22::Reading::read` to get a reading 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `dht-sensor` 2 | 3 | Thanks for your help with this project! I welcome any contributions, questions or suggestions of any kind or size, whether advanced or beginner. 4 | 5 | ## Pull requests 6 | 7 | Please keep your PRs as semantically contained as possible. If you want to contribute more than one change, 8 | or one change requires another but they are unrelated, please open more than one PR. 9 | PRs will be squashed _at merge_. Use multiple commits, if the contribution is large enough to warrant them, to group changes within the PR. 10 | Please aggressively rebase commits while the PR is open! 11 | 12 | Also please mention with what MCU you're testing the library. If it's one that's not in `examples`, consider adding an example too! 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dht-sensor" 3 | version = "0.2.1" 4 | description = "Driver for the DHT11/DHT22 sensor based on embedded-hal" 5 | authors = ["Michael Beaumont "] 6 | keywords = ["embedded", "sensor", "humidity", "temperature", "embedded-hal-driver"] 7 | categories = ["embedded", "no-std", "hardware-support"] 8 | edition = "2021" 9 | readme = "README.md" 10 | license = "MIT" 11 | repository = "https://github.com/michaelbeaumont/dht-sensor" 12 | exclude = [ 13 | ".github", 14 | "nucleo.cfg", 15 | "openocd.gdb", 16 | "memory.x", 17 | ".gitignore", 18 | ] 19 | 20 | [features] 21 | async = ["embedded-hal-async"] 22 | 23 | [dependencies] 24 | embedded-hal = "1" 25 | embedded-hal-async = { version = "1", optional = true } 26 | defmt = { version = "0.3", optional = true } 27 | 28 | [package.metadata.docs.rs] 29 | default-target = "x86_64-unknown-linux-gnu" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Beaumont 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DHT11/DHT22 sensor driver 2 | 3 | [![crates.io](https://img.shields.io/crates/v/dht-sensor)](https://crates.io/crates/dht-sensor) 4 | [![Docs](https://docs.rs/dht-sensor/badge.svg)](https://docs.rs/dht-sensor) 5 | 6 | This library provides a platform-agnostic driver for the [DHT11 and DHT22](https://learn.adafruit.com/dht/overview) sensors. 7 | 8 | Use one of two functions `dht11::blocking::read` and `dht22::blocking::read` to get a reading. 9 | 10 | ## Usage 11 | 12 | The only prerequisites are an embedded-hal implementation that provides: 13 | 14 | - `DelayNs`-implementing type, for example Cortex-M microcontrollers typically use the `SysTick`. 15 | - `InputPin` and `OutputPin`-implementing type, for example an `Output` from `stm32f0xx_hal`. 16 | 17 | When initializing the pin as an output, the state of the pin might depend on the specific chip 18 | used. Some might pull the pin low by default causing the sensor to be confused when we actually 19 | read it for the first time. The same thing happens when the sensor is polled too quickly in succession. 20 | In both of those cases you will get a `DhtError::Timeout`. 21 | 22 | To avoid this, you can pull the pin high when initializing it and polling the sensor with an 23 | interval of at least 500ms (determined experimentally). Some sources state a refresh rate of 1 or even 2 seconds. 24 | 25 | ## Example 26 | 27 | See the following examples for how to use the library. 28 | 29 | ### Blocking API 30 | 31 | - [stm32f051r8](examples/stm32f051r8/src/bin/sync.rs) 32 | 33 | ### Async API 34 | 35 | - [stm32f051r8](examples/stm32f051r8/src/bin/async.rs) 36 | - [stm32f303vc](examples/stm32f303vc/src/bin/async.rs) 37 | 38 | ### Release mode may be required 39 | 40 | Compiling in debug mode may disturb the timing-sensitive parts of this crate and ultimately lead to failure. 41 | In this case, you will likely receive a `Timeout` error. Try compiling with `--release` instead. 42 | 43 | ### Tests 44 | 45 | To run the tests, use something like `cargo test --lib --target x86_64-unknown-linux-gnu`. 46 | -------------------------------------------------------------------------------- /examples/stm32f051r8/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32F051R8 --protocol swd" 3 | 4 | [build] 5 | target = "thumbv6m-none-eabi" 6 | -------------------------------------------------------------------------------- /examples/stm32f051r8/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/stm32f051r8/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stm32f051r8" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | dht-sensor = { path = "../..", features = ["async"] } 8 | embassy-stm32 = { version = "0.2", features = ["stm32f051r8", "time-driver-any", "memory-x"] } 9 | embassy-executor = { version = "0.7", features = ["arch-cortex-m", "executor-thread"]} 10 | embassy-time = { version = "0.4" } 11 | cortex-m = { version = "0.7", features = ["critical-section-single-core"] } 12 | cortex-m-rt = "^0.7.3" 13 | cortex-m-semihosting = "^0.5.0" 14 | panic-halt = "^0.2.0" 15 | embedded-hal-async = "1" 16 | embedded-hal = "1" 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | debug = true 21 | lto = true 22 | -------------------------------------------------------------------------------- /examples/stm32f051r8/src/bin/async.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m_semihosting::hprintln; 5 | use embassy_executor::Spawner; 6 | use embassy_stm32::gpio::{Flex, Pull, Speed}; 7 | use embassy_time::Delay; 8 | use embedded_hal_async::delay::DelayNs as _; 9 | use panic_halt as _; 10 | 11 | use dht_sensor::*; 12 | 13 | #[allow(dead_code)] 14 | #[derive(Debug, PartialEq, Eq)] 15 | enum DeviceSelect { 16 | Dht11, 17 | Dht22, 18 | } 19 | 20 | const DEV_SEL: DeviceSelect = DeviceSelect::Dht22; 21 | 22 | #[embassy_executor::main] 23 | async fn main(_spawner: Spawner) -> ! { 24 | let mut rcc_cfg = embassy_stm32::rcc::Config::default(); 25 | rcc_cfg.sys = embassy_stm32::rcc::Sysclk::HSE; 26 | rcc_cfg.hse = Some(embassy_stm32::rcc::Hse { 27 | freq: embassy_stm32::time::Hertz(8_000_000), 28 | mode: embassy_stm32::rcc::HseMode::Oscillator, 29 | }); 30 | let mut cfg = embassy_stm32::Config::default(); 31 | cfg.rcc = rcc_cfg; 32 | let p = embassy_stm32::init(cfg); 33 | 34 | // This is used by `dht-sensor` to wait for signals 35 | let mut delay = Delay; 36 | 37 | let mut one_wire_pin = Flex::new(p.PA1); 38 | one_wire_pin.set_as_input_output_pull(Speed::VeryHigh, Pull::Up); 39 | 40 | // Pulling the pin high to avoid confusing the sensor when initializing 41 | one_wire_pin.set_high(); 42 | 43 | // The DHT11 datasheet suggests 1 second 44 | hprintln!("Waiting on the sensor..."); 45 | delay.delay_ms(1000).await; 46 | 47 | loop { 48 | if DEV_SEL == DeviceSelect::Dht22 { 49 | match dht22::r#async::read(&mut delay, &mut one_wire_pin).await { 50 | Ok(dht22::Reading { 51 | temperature, 52 | relative_humidity, 53 | }) => hprintln!("{}°, {}% RH", temperature, relative_humidity), 54 | Err(e) => hprintln!("Error {:?}", e), 55 | } 56 | // Delay of at least 500ms before polling the sensor again, 1 second or more advised 57 | delay.delay_ms(1000).await; 58 | } else { 59 | match dht11::r#async::read(&mut delay, &mut one_wire_pin).await { 60 | Ok(dht11::Reading { 61 | temperature, 62 | relative_humidity, 63 | }) => hprintln!("{}°, {}% RH", temperature, relative_humidity), 64 | Err(e) => hprintln!("Error {:?}", e), 65 | } 66 | // Delay of at least 500ms before polling the sensor again, 1 second or more advised 67 | delay.delay_ms(1000).await; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/stm32f051r8/src/bin/sync.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | use cortex_m_rt::entry; 4 | use cortex_m_semihosting::hprintln; 5 | use embassy_stm32::gpio::{Flex, Pull, Speed}; 6 | use embassy_time::Delay; 7 | use embedded_hal::delay::DelayNs as _; 8 | use panic_halt as _; 9 | 10 | use dht_sensor::*; 11 | 12 | #[allow(dead_code)] 13 | #[derive(Debug, PartialEq, Eq)] 14 | enum DeviceSelect { 15 | Dht11, 16 | Dht22, 17 | } 18 | 19 | const DEV_SEL: DeviceSelect = DeviceSelect::Dht22; 20 | 21 | #[entry] 22 | fn main() -> ! { 23 | let p = embassy_stm32::init(embassy_stm32::Config::default()); 24 | 25 | // This is used by `dht-sensor` to wait for signals 26 | let mut delay = Delay; 27 | 28 | let mut one_wire_pin = Flex::new(p.PA1); 29 | one_wire_pin.set_as_input_output_pull(Speed::VeryHigh, Pull::Up); 30 | 31 | // Pulling the pin high to avoid confusing the sensor when initializing 32 | one_wire_pin.set_high(); 33 | 34 | // The DHT11 datasheet suggests 1 second 35 | hprintln!("Waiting on the sensor..."); 36 | delay.delay_ms(1000); 37 | 38 | loop { 39 | if DEV_SEL == DeviceSelect::Dht22 { 40 | // DHT22 uses a different read function 41 | match dht22::blocking::read(&mut delay, &mut one_wire_pin) { 42 | Ok(dht22::Reading { 43 | temperature, 44 | relative_humidity, 45 | }) => hprintln!("{}°, {}% RH", temperature, relative_humidity), 46 | Err(e) => hprintln!("Error {:?}", e), 47 | } 48 | // Delay of at least 500ms before polling the sensor again, 1 second or more advised 49 | delay.delay_ms(1000); 50 | } else { 51 | // Blocking read first. 52 | match dht11::blocking::read(&mut delay, &mut one_wire_pin) { 53 | Ok(dht11::Reading { 54 | temperature, 55 | relative_humidity, 56 | }) => hprintln!("{}°, {}% RH", temperature, relative_humidity), 57 | Err(e) => hprintln!("Error {:?}", e), 58 | } 59 | // Delay of at least 500ms before polling the sensor again, 1 second or more advised 60 | delay.delay_ms(1000); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/stm32f303vc/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip STM32F303VC --protocol swd" 3 | 4 | [build] 5 | target = "thumbv7m-none-eabi" 6 | -------------------------------------------------------------------------------- /examples/stm32f303vc/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/stm32f303vc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stm32f303vc" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | dht-sensor = { path = "../..", features = ["async"] } 8 | embassy-stm32 = { version = "0.2", features = ["stm32f303vc", "time-driver-any", "memory-x"] } 9 | embassy-executor = { version = "0.7", features = ["arch-cortex-m", "executor-thread"]} 10 | embassy-time = { version = "0.4" } 11 | cortex-m = { version = "0.7", features = ["critical-section-single-core"] } 12 | cortex-m-rt = "^0.7.3" 13 | cortex-m-semihosting = "^0.5.0" 14 | panic-halt = "^0.2.0" 15 | embedded-hal-async = "1" 16 | 17 | [profile.release] 18 | codegen-units = 1 19 | debug = true 20 | lto = true 21 | -------------------------------------------------------------------------------- /examples/stm32f303vc/src/bin/async.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m_semihosting::hprintln; 5 | use embassy_executor::Spawner; 6 | use embassy_stm32::gpio::{Flex, Pull, Speed}; 7 | use embassy_time::Delay; 8 | use embedded_hal_async::delay::DelayNs as _; 9 | use panic_halt as _; 10 | 11 | use dht_sensor::*; 12 | 13 | #[allow(dead_code)] 14 | #[derive(Debug, PartialEq, Eq)] 15 | enum DeviceSelect { 16 | Dht11, 17 | Dht22, 18 | } 19 | 20 | const DEV_SEL: DeviceSelect = DeviceSelect::Dht22; 21 | 22 | #[embassy_executor::main] 23 | async fn main(_spawner: Spawner) -> ! { 24 | let p = embassy_stm32::init(embassy_stm32::Config::default()); 25 | 26 | // This is used by `dht-sensor` to wait for signals 27 | let mut delay = Delay; 28 | 29 | let mut one_wire_pin = Flex::new(p.PA1); 30 | one_wire_pin.set_as_input_output_pull(Speed::VeryHigh, Pull::Up); 31 | 32 | // Pulling the pin high to avoid confusing the sensor when initializing 33 | one_wire_pin.set_high(); 34 | 35 | // The DHT11 datasheet suggests 1 second 36 | hprintln!("Waiting on the sensor..."); 37 | delay.delay_ms(1000).await; 38 | 39 | loop { 40 | if DEV_SEL == DeviceSelect::Dht22 { 41 | match dht22::r#async::read(&mut delay, &mut one_wire_pin).await { 42 | Ok(dht22::Reading { 43 | temperature, 44 | relative_humidity, 45 | }) => hprintln!("{}°, {}% RH", temperature, relative_humidity), 46 | Err(e) => hprintln!("Error {:?}", e), 47 | } 48 | // Delay of at least 500ms before polling the sensor again, 1 second or more advised 49 | delay.delay_ms(500).await; 50 | } else { 51 | match dht11::r#async::read(&mut delay, &mut one_wire_pin).await { 52 | Ok(dht11::Reading { 53 | temperature, 54 | relative_humidity, 55 | }) => hprintln!("{}°, {}% RH", temperature, relative_humidity), 56 | Err(e) => hprintln!("Error {:?}", e), 57 | } 58 | // Delay of at least 500ms before polling the sensor again, 1 second or more advised 59 | delay.delay_ms(1000).await; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /nucleo.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2-1.cfg] 2 | transport select hla_swd 3 | 4 | set WORKAREASIZE 0x2000 5 | source [find target/stm32f0x.cfg] 6 | 7 | reset_config srst_only 8 | -------------------------------------------------------------------------------- /openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | # # run the next few lines so the panic message is printed immediately 14 | # # the number needs to be adjusted for your panic handler 15 | # commands $bpnum 16 | # next 4 17 | # end 18 | 19 | # *try* to stop at the user entry point (it might be gone due to inlining) 20 | break main 21 | 22 | monitor arm semihosting enable 23 | 24 | # # send captured ITM to the file itm.fifo 25 | # # (the microcontroller SWO pin must be connected to the programmer SWO pin) 26 | # # 8000000 must match the core clock frequency 27 | # monitor tpiu config internal itm.txt uart off 8000000 28 | 29 | # # OR: make the microcontroller SWO pin output compatible with UART (8N1) 30 | # # 8000000 must match the core clock frequency 31 | # # 2000000 is the frequency of the SWO pin 32 | # monitor tpiu config external uart off 8000000 2000000 33 | 34 | # # enable ITM port 0 35 | # monitor itm port 0 on 36 | 37 | load 38 | 39 | # start the process but immediately halt the processor 40 | stepi 41 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # DHT11/DHT22 sensor driver 2 | //! 3 | //! This library provides a platform-agnostic driver for the [DHT11 and DHT22](https://learn.adafruit.com/dht/overview) sensors. 4 | //! 5 | //! Use one of two functions [`dht11::blocking::read`] and [`dht22::blocking::read`] to get a reading. 6 | //! 7 | //! ## Usage 8 | //! 9 | //! The only prerequisites are an embedded-hal implementation that provides: 10 | //! 11 | //! - [`DelayNs`]-implementing type, for example Cortex-M microcontrollers typically use the `SysTick`. 12 | //! - [`InputPin`] and [`OutputPin`]-implementing type, for example an `Output` from `stm32f0xx_hal`. 13 | //! 14 | //! When initializing the pin as an output, the state of the pin might depend on the specific chip 15 | //! used. Some might pull the pin low by default causing the sensor to be confused when we actually 16 | //! read it for the first time. The same thing happens when the sensor is polled too quickly in succession. 17 | //! In both of those cases you will get a `DhtError::Timeout`. 18 | //! 19 | //! To avoid this, you can pull the pin high when initializing it and polling the sensor with an 20 | //! interval of at least 500ms (determined experimentally). Some sources state a refresh rate of 1 or even 2 seconds. 21 | //! 22 | //! ## Example 23 | //! 24 | //! See the following examples for how to use the library. 25 | //! 26 | //! ### Blocking API 27 | //! 28 | //! - [stm32f051r8](https://github.com/michaelbeaumont/dht-sensor/blob/main/examples/stm32f051r8/src/bin/sync.rs) 29 | //! 30 | //! ### Async API 31 | //! 32 | //! - [stm32f051r8](https://github.com/michaelbeaumont/dht-sensor/blob/main/examples/stm32f051r8/src/bin/async.rs) 33 | //! - [stm32f303vc](https://github.com/michaelbeaumont/dht-sensor/blob/main/examples/stm32f303vc/src/bin/async.rs) 34 | #![cfg_attr(not(test), no_std)] 35 | 36 | mod read; 37 | use embedded_hal::delay::DelayNs; 38 | use embedded_hal::digital::{InputPin, OutputPin}; 39 | pub use read::DhtError; 40 | 41 | pub mod dht11 { 42 | use super::*; 43 | 44 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 45 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 46 | pub struct Reading { 47 | pub temperature: i8, 48 | pub relative_humidity: u8, 49 | } 50 | 51 | pub mod blocking { 52 | use super::DelayNs; 53 | use super::{raw_to_reading, InputPin, OutputPin, Reading}; 54 | 55 | pub fn read( 56 | delay: &mut impl DelayNs, 57 | pin: &mut P, 58 | ) -> Result> { 59 | pin.set_low()?; 60 | delay.delay_ms(18); 61 | super::read::read_raw(delay, pin).map(raw_to_reading) 62 | } 63 | } 64 | 65 | #[cfg(feature = "async")] 66 | pub mod r#async { 67 | use super::DelayNs; 68 | use super::{raw_to_reading, InputPin, OutputPin, Reading}; 69 | use embedded_hal_async::delay::DelayNs as AsyncDelayNs; 70 | 71 | /// Only the initial 18ms delay is performed asynchronously. 72 | /// 73 | /// The byte and bit read phase is performed with blocking delays. 74 | pub async fn read( 75 | delay: &mut (impl AsyncDelayNs + DelayNs), 76 | pin: &mut P, 77 | ) -> Result> { 78 | pin.set_low()?; 79 | embedded_hal_async::delay::DelayNs::delay_ms(delay, 18).await; 80 | crate::read::read_raw(delay, pin).map(raw_to_reading) 81 | } 82 | } 83 | 84 | fn raw_to_reading(bytes: [u8; 4]) -> Reading { 85 | let [relative_humidity, _, temp_signed, _] = bytes; 86 | let temperature = { 87 | let (signed, magnitude) = convert_signed(temp_signed); 88 | let temp_sign = if signed { -1 } else { 1 }; 89 | temp_sign * magnitude as i8 90 | }; 91 | Reading { 92 | temperature, 93 | relative_humidity, 94 | } 95 | } 96 | 97 | #[test] 98 | fn test_raw_to_reading() { 99 | assert_eq!( 100 | raw_to_reading([0x32, 0, 0x1B, 0]), 101 | Reading { 102 | temperature: 27, 103 | relative_humidity: 50 104 | } 105 | ); 106 | assert_eq!( 107 | raw_to_reading([0x80, 0, 0x83, 0]), 108 | Reading { 109 | temperature: -3, 110 | relative_humidity: 128 111 | } 112 | ); 113 | } 114 | } 115 | 116 | pub mod dht22 { 117 | use super::*; 118 | 119 | #[derive(Clone, Copy, Debug, PartialEq)] 120 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 121 | pub struct Reading { 122 | pub temperature: f32, 123 | pub relative_humidity: f32, 124 | } 125 | 126 | pub mod blocking { 127 | use super::DelayNs; 128 | use super::{raw_to_reading, InputPin, OutputPin, Reading}; 129 | pub fn read( 130 | delay: &mut impl DelayNs, 131 | pin: &mut P, 132 | ) -> Result> { 133 | pin.set_low()?; 134 | delay.delay_ms(1); 135 | super::read::read_raw(delay, pin).map(raw_to_reading) 136 | } 137 | } 138 | 139 | #[cfg(feature = "async")] 140 | pub mod r#async { 141 | use super::DelayNs; 142 | use super::{raw_to_reading, InputPin, OutputPin, Reading}; 143 | use embedded_hal_async::delay::DelayNs as AsyncDelayNs; 144 | 145 | /// Only the initial 1 ms delay is performed asynchronously. 146 | /// 147 | /// The byte and bit read phase is performed with blocking delays. 148 | pub async fn read( 149 | delay: &mut (impl AsyncDelayNs + DelayNs), 150 | pin: &mut P, 151 | ) -> Result> { 152 | pin.set_low()?; 153 | embedded_hal_async::delay::DelayNs::delay_ms(delay, 1).await; 154 | crate::read::read_raw(delay, pin).map(raw_to_reading) 155 | } 156 | } 157 | 158 | fn raw_to_reading(bytes: [u8; 4]) -> Reading { 159 | let [rh_h, rh_l, temp_h_signed, temp_l] = bytes; 160 | let relative_humidity = ((rh_h as u16) << 8 | (rh_l as u16)) as f32 / 10.0; 161 | let temperature = { 162 | let (signed, magnitude) = convert_signed(temp_h_signed); 163 | let temp_sign = if signed { -1.0 } else { 1.0 }; 164 | let temp_magnitude = ((magnitude as u16) << 8) | temp_l as u16; 165 | temp_sign * temp_magnitude as f32 / 10.0 166 | }; 167 | Reading { 168 | temperature, 169 | relative_humidity, 170 | } 171 | } 172 | 173 | #[test] 174 | fn test_raw_to_reading() { 175 | assert_eq!( 176 | raw_to_reading([0x02, 0x10, 0x01, 0x1B]), 177 | Reading { 178 | temperature: 28.3, 179 | relative_humidity: 52.8 180 | } 181 | ); 182 | assert_eq!( 183 | raw_to_reading([0x02, 0x90, 0x80, 0x1B]), 184 | Reading { 185 | temperature: -2.7, 186 | relative_humidity: 65.6 187 | } 188 | ); 189 | } 190 | } 191 | 192 | fn convert_signed(signed: u8) -> (bool, u8) { 193 | let sign = signed & 0x80 != 0; 194 | let magnitude = signed & 0x7F; 195 | (sign, magnitude) 196 | } 197 | 198 | #[test] 199 | fn test_convert_signed() { 200 | assert_eq!(convert_signed(0x13), (false, 0x13)); 201 | assert_eq!(convert_signed(0x93), (true, 0x13)); 202 | } 203 | -------------------------------------------------------------------------------- /src/read.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::delay::DelayNs; 2 | use embedded_hal::digital::{InputPin, OutputPin}; 3 | 4 | const TIMEOUT_US: u8 = 100; 5 | 6 | #[derive(Debug)] 7 | pub enum DhtError { 8 | PinError(E), 9 | ChecksumMismatch, 10 | Timeout, 11 | } 12 | 13 | impl From for DhtError { 14 | fn from(error: E) -> DhtError { 15 | DhtError::PinError(error) 16 | } 17 | } 18 | 19 | fn read_bit( 20 | delay: &mut impl DelayNs, 21 | pin: &mut P, 22 | ) -> Result> { 23 | wait_until_timeout(delay, || pin.is_high())?; 24 | delay.delay_us(35); 25 | let high = pin.is_high()?; 26 | wait_until_timeout(delay, || pin.is_low())?; 27 | Ok(high) 28 | } 29 | 30 | fn read_byte(delay: &mut impl DelayNs, pin: &mut P) -> Result> { 31 | let mut byte: u8 = 0; 32 | for i in 0..8 { 33 | let bit_mask = 1 << (7 - i); 34 | if read_bit(delay, pin)? { 35 | byte |= bit_mask; 36 | } 37 | } 38 | Ok(byte) 39 | } 40 | 41 | pub fn read_raw( 42 | delay: &mut impl DelayNs, 43 | pin: &mut P, 44 | ) -> Result<[u8; 4], DhtError> { 45 | pin.set_high().ok(); 46 | delay.delay_us(48); 47 | 48 | wait_until_timeout(delay, || pin.is_high())?; 49 | wait_until_timeout(delay, || pin.is_low())?; 50 | 51 | let mut data = [0; 4]; 52 | for b in data.iter_mut() { 53 | *b = read_byte(delay, pin)?; 54 | } 55 | let checksum = read_byte(delay, pin)?; 56 | if data.iter().fold(0u8, |sum, v| sum.wrapping_add(*v)) != checksum { 57 | Err(DhtError::ChecksumMismatch) 58 | } else { 59 | Ok(data) 60 | } 61 | } 62 | 63 | /// Wait until the given function returns true or the timeout is reached. 64 | fn wait_until_timeout(delay: &mut impl DelayNs, mut func: F) -> Result<(), DhtError> 65 | where 66 | F: FnMut() -> Result, 67 | { 68 | for _ in 0..TIMEOUT_US { 69 | if func()? { 70 | return Ok(()); 71 | } 72 | delay.delay_us(1); 73 | } 74 | Err(DhtError::Timeout) 75 | } 76 | --------------------------------------------------------------------------------