├── riscv-ulp ├── riscv_ulp_link_default.x ├── libriscv_ulp_start.a ├── riscv_ulp_start_assemble.ps1 ├── riscv_ulp_start_assemble.sh ├── riscv_ulp_link_base.x └── riscv_ulp_start.S ├── .gitignore ├── .github ├── configs │ └── sdkconfig.defaults └── workflows │ ├── publish-dry-run.yml │ ├── ci-esp-idf-master.yml │ ├── publish.yml │ └── ci.yml ├── src ├── riscv_ulp_hal.rs ├── prelude.rs ├── hall.rs ├── riscv_ulp_hal │ ├── sys.rs │ ├── mutex.rs │ ├── delay.rs │ ├── sys │ │ ├── cpu.rs │ │ └── gpio.rs │ ├── pac.rs │ ├── start.rs │ └── reg.rs ├── cpu.rs ├── lib.rs ├── peripherals.rs ├── delay.rs ├── mutex.rs ├── ulp.rs ├── adc.rs ├── units.rs ├── interrupt.rs ├── ledc.rs ├── can.rs └── i2c.rs ├── .cargo └── config.toml ├── examples ├── blinky.rs ├── ledc-simple.rs ├── button.rs ├── uart_loopback.rs ├── spi_loopback.rs ├── rmt_neopixel.rs ├── ledc-threads.rs ├── i2c_ssd1306.rs ├── rmt_musical_buzzer.rs └── rmt_morse_code.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md └── LICENSE-APACHE /riscv-ulp/riscv_ulp_link_default.x: -------------------------------------------------------------------------------- 1 | PROVIDE(_ram_size = 4K); 2 | INCLUDE riscv_ulp_link_base.x; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /.espressif 3 | /.embuild 4 | /target 5 | /Cargo.lock 6 | **/*.rs.bk 7 | ulp/ulp_start.o 8 | -------------------------------------------------------------------------------- /riscv-ulp/libriscv_ulp_start.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjoernQ/esp-idf-hal/master/riscv-ulp/libriscv_ulp_start.a -------------------------------------------------------------------------------- /.github/configs/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Workaround for https://github.com/espressif/esp-idf/issues/7631 2 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n 3 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n 4 | 5 | # Some examples (ledc-simple) require a larger than the default stack size for 6 | # the main thread. 7 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 8 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "riscv-ulp-hal")] 2 | mod pac; 3 | #[cfg(feature = "riscv-ulp-hal")] 4 | mod reg; 5 | 6 | #[cfg(feature = "riscv-ulp-hal")] 7 | #[macro_use] 8 | pub mod sys; 9 | #[cfg(feature = "riscv-ulp-hal")] 10 | pub mod delay; 11 | #[cfg(feature = "riscv-ulp-hal")] 12 | pub mod mutex; 13 | #[cfg(feature = "riscv-ulp-hal")] 14 | pub mod start; 15 | -------------------------------------------------------------------------------- /riscv-ulp/riscv_ulp_start_assemble.ps1: -------------------------------------------------------------------------------- 1 | # remove existing blob because otherwise this will append object file to the old blob 2 | Remove-Item -Force riscv_ulp_start.a 3 | 4 | riscv32-esp-elf-gcc -Desp_ulp -ggdb3 -fdebug-prefix-map=$(pwd)=/riscv_ulp_start -c -mabi=ilp32 -march=rv32imc riscv_ulp_start.S -o riscv_ulp_start.o 5 | riscv32-esp-elf-ar crs libriscv_ulp_start.a riscv_ulp_start.o 6 | 7 | Remove-Item riscv_ulp_start.o 8 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! The prelude. 2 | //! 3 | //! To use the esp_idf_hal effectively, a lot of traits and types need to be imported. 4 | //! Instead of importing them one by one manually, the prelude contains the most 5 | //! commonly used imports that are used around application runtime management. 6 | //! 7 | //! This can be imported as `use esp_idf_hal::prelude::*`. 8 | 9 | pub use crate::peripherals::*; 10 | pub use crate::units::*; 11 | -------------------------------------------------------------------------------- /riscv-ulp/riscv_ulp_start_assemble.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | # remove existing blob because otherwise this will append object file to the old blob 6 | rm -f riscv_ulp_start.a 7 | 8 | riscv32-esp-elf-gcc -Desp_ulp -ggdb3 -fdebug-prefix-map=$(pwd)=/riscv_ulp_start -c -mabi=ilp32 -march=rv32imc riscv_ulp_start.S -o riscv_ulp_start.o 9 | riscv32-esp-elf-ar crs libriscv_ulp_start.a riscv_ulp_start.o 10 | 11 | rm riscv_ulp_start.o 12 | -------------------------------------------------------------------------------- /src/hall.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use crate::adc; 4 | 5 | pub struct HallSensor(PhantomData<*const ()>); 6 | 7 | impl HallSensor { 8 | /// # Safety 9 | /// 10 | /// Care should be taken not to instnatiate this Hall Sensor instance, if it is already instantiated and used elsewhere 11 | pub unsafe fn new() -> Self { 12 | HallSensor(PhantomData) 13 | } 14 | } 15 | 16 | unsafe impl Send for HallSensor {} 17 | 18 | impl embedded_hal_0_2::adc::Channel for HallSensor { 19 | type ID = (); 20 | 21 | fn channel() -> Self::ID { 22 | () 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.xtensa-esp32-espidf] 2 | linker = "ldproxy" 3 | 4 | [target.xtensa-esp32s2-espidf] 5 | linker = "ldproxy" 6 | 7 | [target.xtensa-esp32s3-espidf] 8 | linker = "ldproxy" 9 | 10 | [target.riscv32imc-esp-espidf] 11 | linker = "ldproxy" 12 | 13 | # Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3 14 | # See also https://github.com/ivmarkov/embuild/issues/16 15 | rustflags = ["-C", "default-linker-libraries"] 16 | 17 | [env] 18 | ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" 19 | 20 | [unstable] 21 | build-std = ["std", "panic_abort"] 22 | build-std-features = ["panic_immediate_abort"] 23 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/sys.rs: -------------------------------------------------------------------------------- 1 | /// A mini "esp-idf-ulp-sys" module exposing stuff on top of which the ULP HAL support is implemented 2 | /// (currently, only GPIO) + some utilities for the riscv ULP processor 3 | pub use self::cpu::*; 4 | pub use self::gpio::*; 5 | 6 | pub mod cpu; 7 | #[allow(clippy::missing_safety_doc)] 8 | pub mod gpio; 9 | 10 | pub type EspError = core::convert::Infallible; 11 | 12 | #[macro_export] 13 | macro_rules! esp_result { 14 | ($err:expr, $value:expr) => {{ 15 | $err; 16 | 17 | Ok($value) 18 | }}; 19 | } 20 | 21 | #[macro_export] 22 | macro_rules! esp { 23 | ($err:expr) => {{ 24 | $err; 25 | 26 | core::result::Result::<(), EspError>::Ok(()) 27 | }}; 28 | } 29 | -------------------------------------------------------------------------------- /examples/blinky.rs: -------------------------------------------------------------------------------- 1 | //! Blinks an LED 2 | //! 3 | //! This assumes that a LED is connected to GPIO4. 4 | //! Depending on your target and the board you are using you should change the pin. 5 | //! If your board doesn't have on-board LEDs don't forget to add an appropriate resistor. 6 | //! 7 | 8 | use std::thread; 9 | use std::time::Duration; 10 | 11 | use embedded_hal::digital::blocking::OutputPin; 12 | 13 | use esp_idf_hal::peripherals::Peripherals; 14 | 15 | fn main() -> anyhow::Result<()> { 16 | esp_idf_sys::link_patches(); 17 | 18 | let peripherals = Peripherals::take().unwrap(); 19 | let mut led = peripherals.pins.gpio4.into_output()?; 20 | 21 | loop { 22 | led.set_high()?; 23 | // we are using thread::sleep here to make sure the watchdog isn't triggered 24 | thread::sleep(Duration::from_millis(1000)); 25 | 26 | led.set_low()?; 27 | thread::sleep(Duration::from_millis(1000)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/publish-dry-run.yml: -------------------------------------------------------------------------------- 1 | name: PublishDryRun 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | env: 7 | rust_toolchain: nightly-2022-04-07 8 | 9 | jobs: 10 | publishdryrun: 11 | name: Publish Dry Run 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup | Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup | Rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: ${{ env.rust_toolchain }} 20 | - name: Setup | Std 21 | run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu 22 | - name: Setup | Default to nightly 23 | run: rustup default ${{ env.rust_toolchain }} 24 | - name: Build | Publish Dry Run 25 | run: export ESP_IDF_TOOLS_INSTALL_DIR=out; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo publish --dry-run --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort 26 | -------------------------------------------------------------------------------- /examples/ledc-simple.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::delay::blocking::DelayUs; 2 | 3 | use esp_idf_hal::delay::FreeRtos; 4 | use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; 5 | use esp_idf_hal::peripherals::Peripherals; 6 | use esp_idf_hal::prelude::*; 7 | 8 | fn main() -> anyhow::Result<()> { 9 | esp_idf_sys::link_patches(); 10 | 11 | println!("Configuring output channel"); 12 | 13 | let peripherals = Peripherals::take().unwrap(); 14 | let config = TimerConfig::default().frequency(25.kHz().into()); 15 | let timer = Timer::new(peripherals.ledc.timer0, &config)?; 16 | let mut channel = Channel::new(peripherals.ledc.channel0, &timer, peripherals.pins.gpio4)?; 17 | 18 | println!("Starting duty-cycle loop"); 19 | 20 | let max_duty = channel.get_max_duty(); 21 | for numerator in [0, 1, 2, 3, 4, 5].iter().cycle() { 22 | println!("Duty {}/5", numerator); 23 | channel.set_duty(max_duty * numerator / 5)?; 24 | FreeRtos.delay_ms(2000)?; 25 | } 26 | 27 | loop { 28 | FreeRtos.delay_ms(1000)?; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2019-2020 Contributors to xtensa-lx6-rt 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. -------------------------------------------------------------------------------- /.github/workflows/ci-esp-idf-master.yml: -------------------------------------------------------------------------------- 1 | name: CIEspIdfMaster 2 | 3 | on: 4 | schedule: 5 | - cron: '50 5 * * *' 6 | 7 | env: 8 | rust_toolchain: nightly-2022-04-07 9 | 10 | jobs: 11 | compile: 12 | name: Compile 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Setup | Checkout 16 | uses: actions/checkout@v2 17 | - name: Setup | Rust 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: ${{ env.rust_toolchain }} 21 | components: rustfmt, clippy 22 | - name: Setup | Std 23 | run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu 24 | - name: Setup | Default to nightly 25 | run: rustup default ${{ env.rust_toolchain }} 26 | - name: Build | Fmt Check 27 | run: cargo fmt -- --check 28 | - name: Build | Compile Native ESP-IDF master no_std 29 | run: export ESP_IDF_VERSION=master; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --features esp-idf-sys/native --no-default-features --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort 30 | -------------------------------------------------------------------------------- /examples/button.rs: -------------------------------------------------------------------------------- 1 | //! Turn an LED on/off depending on the state of a button 2 | //! 3 | //! This assumes that a LED is connected to GPIO4. 4 | //! Additionally this assumes a button connected to GPIO9. 5 | //! On an ESP32C3 development board this is the BOOT button. 6 | //! 7 | //! Depending on your target and the board you are using you should change the pins. 8 | //! If your board doesn't have on-board LEDs don't forget to add an appropriate resistor. 9 | 10 | use std::thread; 11 | use std::time::Duration; 12 | 13 | use embedded_hal::digital::blocking::InputPin; 14 | use embedded_hal::digital::blocking::OutputPin; 15 | 16 | use esp_idf_hal::gpio::Pull; 17 | use esp_idf_hal::peripherals::Peripherals; 18 | 19 | fn main() -> anyhow::Result<()> { 20 | esp_idf_sys::link_patches(); 21 | 22 | let peripherals = Peripherals::take().unwrap(); 23 | let mut led = peripherals.pins.gpio4.into_output()?; 24 | let mut button = peripherals.pins.gpio9.into_input()?; 25 | button.set_pull_down()?; 26 | 27 | loop { 28 | // we are using thread::sleep here to make sure the watchdog isn't triggered 29 | thread::sleep(Duration::from_millis(10)); 30 | 31 | if button.is_high()? { 32 | led.set_low()?; 33 | } else { 34 | led.set_high()?; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(esp32, esp32s3))] 2 | use core::arch::asm; 3 | 4 | use esp_idf_sys::*; 5 | 6 | /// Returns the number of cores supported by the esp32* chip 7 | pub const CORES: u32 = SOC_CPU_CORES_NUM; 8 | 9 | #[repr(C)] 10 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 11 | pub enum Core { 12 | Core0 = 0, // PRO on dual-core systems, the one and only CPU on single-core systems 13 | #[cfg(any(esp32, esp32s3))] 14 | Core1 = 1, // APP on dual-core systems 15 | } 16 | 17 | impl Core { 18 | #[inline(always)] 19 | #[link_section = ".iram1.cpu_core"] 20 | pub fn is_active(&self) -> bool { 21 | *self == core() 22 | } 23 | } 24 | 25 | /// Returns the currently active core ID 26 | /// On single-core systems, like esp32s2 and esp32c3 this function always returns 0 27 | /// 28 | /// On dual-core systems like esp32 and esp32s3 this function returns: 29 | /// 0 - when the active core is the PRO CPU 30 | /// 1 - when the active core is the APP CPU 31 | #[inline(always)] 32 | #[link_section = ".iram1.cpu_core"] 33 | pub fn core() -> Core { 34 | #[cfg(any(esp32c3, esp32s2))] 35 | let core = 0; 36 | 37 | #[allow(unused_assignments)] 38 | #[cfg(any(esp32, esp32s3))] 39 | let mut core = 0; 40 | 41 | #[cfg(any(esp32, esp32s3))] 42 | unsafe { 43 | asm!("rsr.prid {0}", "extui {0},{0},13,1", out(reg) core); 44 | } 45 | 46 | match core { 47 | 0 => Core::Core0, 48 | #[cfg(any(esp32, esp32s3))] 49 | 1 => Core::Core1, 50 | other => panic!("Unknown core: {}", other), 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | env: 7 | rust_toolchain: nightly-2022-04-07 8 | 9 | jobs: 10 | publish: 11 | name: Publish 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup | Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup | Rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: ${{ env.rust_toolchain }} 20 | - name: Setup | Std 21 | run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu 22 | - name: Setup | Default to nightly 23 | run: rustup default ${{ env.rust_toolchain }} 24 | - name: Login 25 | run: cargo login ${{ secrets.crates_io_token }} 26 | - name: Build | Publish 27 | run: export ESP_IDF_TOOLS_INSTALL_DIR=out; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo publish --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort 28 | - name: Build Documentation 29 | run: cargo doc --features esp-idf-sys/native --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort; echo "" > target/riscv32imc-esp-espidf/doc/index.html; mv target/riscv32imc-esp-espidf/doc ./docs 30 | - name: Deploy Documentation 31 | if: ${{ github.ref == 'refs/heads/master' }} 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | force_orphan: true 36 | publish_dir: ./docs -------------------------------------------------------------------------------- /examples/uart_loopback.rs: -------------------------------------------------------------------------------- 1 | //! UART loopback test 2 | //! 3 | //! Folowing pins are used: 4 | //! TX GPIO5 5 | //! RX GPIO6 6 | //! 7 | //! Depending on your target and the board you are using you have to change the pins. 8 | //! 9 | //! This example transfers data via UART. 10 | //! Connect TX and RX pins to see the outgoing data is read as incoming data. 11 | 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | use embedded_hal::serial::nb::{Read, Write}; 16 | 17 | use esp_idf_hal::peripherals::Peripherals; 18 | use esp_idf_hal::prelude::*; 19 | use esp_idf_hal::serial; 20 | 21 | fn main() -> anyhow::Result<()> { 22 | esp_idf_sys::link_patches(); 23 | 24 | let peripherals = Peripherals::take().unwrap(); 25 | let tx = peripherals.pins.gpio5; 26 | let rx = peripherals.pins.gpio6; 27 | 28 | println!("Starting UART loopback test"); 29 | let config = serial::config::Config::default().baudrate(Hertz(115_200)); 30 | let mut serial: serial::Serial = serial::Serial::new( 31 | peripherals.uart1, 32 | serial::Pins { 33 | tx, 34 | rx, 35 | cts: None, 36 | rts: None, 37 | }, 38 | config, 39 | ) 40 | .unwrap(); 41 | 42 | loop { 43 | // we are using thread::sleep here to make sure the watchdog isn't triggered 44 | thread::sleep(Duration::from_millis(500)); 45 | nb::block!(serial.write(0xaa))?; 46 | 47 | // note: this will block - if you don't connect RX and TX you will see the watchdog kick in 48 | let byte = nb::block!(serial.read())?; 49 | println!("Written 0xaa, read 0x{:02x}", byte); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/spi_loopback.rs: -------------------------------------------------------------------------------- 1 | //! SPI loopback test 2 | //! 3 | //! Folowing pins are used: 4 | //! SCLK GPIO6 5 | //! MISO GPIO2 6 | //! MOSI GPIO7 7 | //! CS GPIO10 8 | //! 9 | //! Depending on your target and the board you are using you have to change the pins. 10 | //! 11 | //! This example transfers data via SPI. 12 | //! Connect MISO and MOSI pins to see the outgoing data is read as incoming data. 13 | 14 | use std::thread; 15 | use std::time::Duration; 16 | 17 | use embedded_hal::spi::blocking::SpiDevice; 18 | 19 | use esp_idf_hal::peripherals::Peripherals; 20 | use esp_idf_hal::prelude::*; 21 | use esp_idf_hal::spi; 22 | 23 | fn main() -> anyhow::Result<()> { 24 | esp_idf_sys::link_patches(); 25 | 26 | let peripherals = Peripherals::take().unwrap(); 27 | let spi = peripherals.spi2; 28 | 29 | let sclk = peripherals.pins.gpio6; 30 | let miso = peripherals.pins.gpio2; 31 | let mosi = peripherals.pins.gpio7; 32 | let cs = peripherals.pins.gpio10; 33 | 34 | println!("Starting SPI loopback test"); 35 | let config = ::default().baudrate(26.MHz().into()); 36 | let mut spi = spi::Master::::new( 37 | spi, 38 | spi::Pins { 39 | sclk, 40 | sdo: miso, 41 | sdi: Some(mosi), 42 | cs: Some(cs), 43 | }, 44 | config, 45 | )?; 46 | 47 | let mut read = [0u8; 4]; 48 | let write = [0xde, 0xad, 0xbe, 0xef]; 49 | 50 | loop { 51 | // we are using thread::sleep here to make sure the watchdog isn't triggered 52 | thread::sleep(Duration::from_millis(500)); 53 | spi.transfer(&mut read, &write)?; 54 | println!("Wrote {:x?}, read {:x?}", write, read); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esp-idf-hal" 3 | version = "0.36.0" 4 | authors = ["sapir ", "Ivan Markov "] 5 | edition = "2018" 6 | resolver = "2" 7 | categories = ["embedded", "hardware-support"] 8 | keywords = ["hal", "idf", "esp-idf", "esp32"] 9 | description = "A Hardware abstraction layer for Espressif's ESP family of microcontrollers based on the ESP-IDF framework." 10 | repository = "https://github.com/esp-rs/esp-idf-hal" 11 | license = "MIT OR Apache-2.0" 12 | readme = "README.md" 13 | documentation = "https://esp-rs.github.io/esp-idf-hal/" 14 | 15 | [features] 16 | default = ["std", "alloc", "esp-idf-sys", "embedded-svc"] 17 | 18 | std = ["alloc", "esp-idf-sys/std"] 19 | 20 | alloc = [] 21 | 22 | riscv-ulp-hal = [] 23 | 24 | experimental = [] 25 | 26 | [dependencies] 27 | nb = "0.1.2" 28 | mutex-trait = { version = "0.2", optional = true, default-features = false } 29 | embedded-hal = "=1.0.0-alpha.8" 30 | embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } 31 | embedded-svc = { version = "0.20", optional = true, default-features = false } 32 | esp-idf-sys = { version = "0.31", optional = true, default-features = false, features = ["native"] } 33 | critical-section = { version = "0.2.5", optional = true, features = ["custom-impl"] } 34 | embassy = { version = "0", optional = true } 35 | # The real version is this one and it needs to be patched into the binary crate: 36 | #embassy = { version = "0.1", git = "https://github.com/embassy-rs/embassy", optional = true, features = ["executor-agnostic"] } 37 | 38 | [build-dependencies] 39 | embuild = "0.28" 40 | anyhow = "1" 41 | 42 | [dev-dependencies] 43 | anyhow = "1" 44 | esp-idf-sys = { version = "0.31", features = ["native", "binstart"] } 45 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/mutex.rs: -------------------------------------------------------------------------------- 1 | use core::cell::{RefCell, RefMut}; 2 | use core::ops::{Deref, DerefMut}; 3 | 4 | pub struct Mutex(RefCell); 5 | 6 | impl Mutex { 7 | #[inline(always)] 8 | pub const fn new(data: T) -> Self { 9 | Self(RefCell::new(data)) 10 | } 11 | 12 | #[inline(always)] 13 | pub fn lock(&self) -> MutexGuard<'_, T> { 14 | MutexGuard::new(self) 15 | } 16 | } 17 | 18 | unsafe impl Sync for Mutex where T: Send {} 19 | unsafe impl Send for Mutex where T: Send {} 20 | 21 | pub struct MutexGuard<'a, T>(&'a Mutex, RefMut<'a, T>); 22 | 23 | impl<'a, T> MutexGuard<'a, T> { 24 | #[inline(always)] 25 | fn new(mutex: &'a Mutex) -> Self { 26 | Self(mutex, mutex.0.borrow_mut()) 27 | } 28 | } 29 | 30 | unsafe impl Sync for MutexGuard<'_, T> where T: Sync {} 31 | 32 | impl<'a, T> Deref for MutexGuard<'a, T> { 33 | type Target = T; 34 | 35 | #[inline(always)] 36 | fn deref(&self) -> &Self::Target { 37 | &self.1 38 | } 39 | } 40 | 41 | impl<'a, T> DerefMut for MutexGuard<'a, T> { 42 | #[inline(always)] 43 | fn deref_mut(&mut self) -> &mut Self::Target { 44 | &mut self.1 45 | } 46 | } 47 | 48 | #[cfg(feature = "mutex-trait")] 49 | impl mutex_trait::Mutex for Mutex { 50 | type Data = T; 51 | 52 | #[inline(always)] 53 | fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { 54 | let mut guard = Mutex::lock(self); 55 | 56 | f(&mut *guard) 57 | } 58 | } 59 | 60 | #[cfg(feature = "embedded-svc-mutex")] 61 | impl embedded_svc::mutex::Mutex for Mutex { 62 | type Data = T; 63 | 64 | type Guard<'a> 65 | where 66 | T: 'a, 67 | = MutexGuard<'a, T>; 68 | 69 | #[inline(always)] 70 | fn new(data: Self::Data) -> Self { 71 | Mutex::new(data) 72 | } 73 | 74 | #[inline(always)] 75 | fn lock(&self) -> Self::Guard<'_> { 76 | Mutex::lock(self) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/delay.rs: -------------------------------------------------------------------------------- 1 | use super::sys::*; 2 | 3 | /// Busy-loop based delay for the RiscV ULP coprocessor 4 | pub struct Ulp; 5 | 6 | impl embedded_hal_0_2::blocking::delay::DelayUs for Ulp { 7 | fn delay_us(&mut self, us: u32) { 8 | delay_cycles(us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); 9 | } 10 | } 11 | 12 | impl embedded_hal_0_2::blocking::delay::DelayUs for Ulp { 13 | fn delay_us(&mut self, us: u16) { 14 | delay_cycles(us as u32 * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); 15 | } 16 | } 17 | 18 | impl embedded_hal_0_2::blocking::delay::DelayUs for Ulp { 19 | fn delay_us(&mut self, us: u8) { 20 | delay_cycles(us as u32 * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); 21 | } 22 | } 23 | 24 | impl embedded_hal_0_2::blocking::delay::DelayMs for Ulp { 25 | fn delay_ms(&mut self, ms: u32) { 26 | delay_cycles(ms * ULP_RISCV_CYCLES_PER_MS); 27 | } 28 | } 29 | 30 | impl embedded_hal_0_2::blocking::delay::DelayMs for Ulp { 31 | fn delay_ms(&mut self, ms: u16) { 32 | delay_cycles(ms as u32 * ULP_RISCV_CYCLES_PER_MS); 33 | } 34 | } 35 | 36 | impl embedded_hal_0_2::blocking::delay::DelayMs for Ulp { 37 | fn delay_ms(&mut self, ms: u8) { 38 | delay_cycles(ms as u32 * ULP_RISCV_CYCLES_PER_MS); 39 | } 40 | } 41 | 42 | impl embedded_hal::delay::blocking::DelayUs for Ulp { 43 | type Error = core::convert::Infallible; 44 | 45 | fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { 46 | delay_cycles(us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); 47 | Ok(()) 48 | } 49 | 50 | fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { 51 | delay_cycles(ms * ULP_RISCV_CYCLES_PER_MS); 52 | Ok(()) 53 | } 54 | } 55 | 56 | #[inline(always)] 57 | fn delay_cycles(cycles: u32) { 58 | let start = get_ccount(); 59 | 60 | while get_ccount() - start < cycles { /* Wait */ } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An [embedded-hal](https://github.com/rust-embedded/embedded-hal) implementation for ESP32[-XX] + ESP-IDF 2 | 3 | ![CI](https://github.com/esp-rs/esp-idf-hal/actions/workflows/ci.yml/badge.svg) 4 | 5 | * This crate is intended for usage in ESP32[-XX] embedded projects that utilize and link with the **ESP-IDF SDK**. 6 | * For embedded projects that don't need Rust STD support, WiFi or BLE (and thus don't link with the ESP-IDF SDK), please check [esp-hal](https://github.com/esp-rs/esp-hal). 7 | 8 | For more information, check out: 9 | * The [Rust on ESP Book](https://esp-rs.github.io/book/) 10 | * The [embedded-hal](https://github.com/rust-embedded/embedded-hal) project 11 | * The [esp-hal](https://github.com/esp-rs/esp-hal) project 12 | * The [esp-idf-template](https://github.com/esp-rs/esp-idf-template) project 13 | * The [esp-idf-sys](https://github.com/esp-rs/esp-idf-sys) project 14 | * The [embedded-svc](https://github.com/esp-rs/embedded-svc) project 15 | * The [esp-idf-svc](https://github.com/esp-rs/esp-idf-svc) project 16 | * The [Rust for Xtensa toolchain](https://github.com/esp-rs/rust-build) 17 | * The [Rust-with-STD demo](https://github.com/ivmarkov/rust-esp32-std-demo) project 18 | 19 | ## Hardware Notes 20 | 21 | Each chip has a number of GPIO pins which are generally used by the `SPI0` and `SPI1` peripherals in order to connect external PSRAM and/or SPI Flash memory. The datasheets explicitly state that these are not recommended for use, however this crate includes them anyways for completeness. 22 | 23 | Please refer to the table below to determine the pins which are not recommended for use for your chip. 24 | 25 | | Chip | GPIOs | 26 | | ------------ | :----------------: | 27 | | **ESP32** | 6 - 11, 16 - 17 | 28 | | **ESP32-C3** | 12 - 17 | 29 | | **ESP32-S2** | 26 - 32 | 30 | | **ESP32-S3** | 26 - 32, 33 - 37\* | 31 | 32 | _\* When using Octal Flash and/or Octal PSRAM_ 33 | 34 | ## Examples 35 | 36 | The examples could be built and flashed conveniently with [`cargo-espflash`](https://github.com/esp-rs/espflash/). To run `ledc-simple` on an ESP32-C3: 37 | ``` 38 | $ cargo espflash --release --target riscv32imc-esp-espidf --example ledc-simple --monitor /dev/ttyUSB0 39 | ``` 40 | 41 | In order to run the examples on other chips you will most likely need to adapt at least the used pins. 42 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/sys/cpu.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// This module is a manual translation of the following C file from current ESP-IDF master: 4 | /// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/include/ulp_riscv/ulp_utils.h 5 | /// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/ulp_utils.c 6 | use core::arch::asm; 7 | 8 | use crate::riscv_ulp_hal::pac::*; 9 | use crate::riscv_ulp_hal::reg::*; 10 | 11 | pub const ULP_RISCV_CYCLES_PER_US_NUM: u32 = 85; 12 | pub const ULP_RISCV_CYCLES_PER_US_DENUM: u32 = 10; 13 | pub const ULP_RISCV_CYCLES_PER_MS: u32 = 14 | ULP_RISCV_CYCLES_PER_US_NUM * (1000 / ULP_RISCV_CYCLES_PER_US_DENUM); 15 | 16 | #[inline(always)] 17 | pub fn get_ccount() -> u32 { 18 | #[allow(unused_assignments)] 19 | let mut ccount = 0; 20 | 21 | unsafe { 22 | asm!("rdcycle {}", out(reg) ccount); 23 | } 24 | 25 | ccount 26 | } 27 | 28 | pub fn wakeup_main_processor() { 29 | unsafe { set_peri_reg_mask(RTC_CNTL_STATE0_REG, RTC_CNTL_SW_CPU_INT) }; 30 | } 31 | 32 | pub fn rescue_from_monitor() { 33 | // Rescue RISCV from monitor state 34 | unsafe { 35 | clear_peri_reg_mask( 36 | RTC_CNTL_COCPU_CTRL_REG, 37 | RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN, 38 | ) 39 | }; 40 | } 41 | 42 | pub fn enable_timer(enable: bool) { 43 | unsafe { 44 | if enable { 45 | set_peri_reg_mask(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 46 | } else { 47 | clear_peri_reg_mask(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 48 | } 49 | } 50 | } 51 | 52 | pub fn shutdown() -> ! { 53 | unsafe { 54 | // Setting the delay time after RISCV recv `DONE` signal, Ensure that action `RESET` can be executed in time. 55 | reg_set_field( 56 | RTC_CNTL_COCPU_CTRL_REG, 57 | RTC_CNTL_COCPU_SHUT_2_CLK_DIS_S, 58 | RTC_CNTL_COCPU_SHUT_2_CLK_DIS_V, 59 | 0x3F, 60 | ); 61 | 62 | // Suspends the ulp operation 63 | set_peri_reg_mask(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE); 64 | 65 | // Resets the processor 66 | set_peri_reg_mask(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); 67 | } 68 | 69 | #[allow(clippy::empty_loop)] 70 | loop {} 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | schedule: 9 | - cron: '50 4 * * *' 10 | 11 | env: 12 | rust_toolchain: nightly-2022-04-07 13 | 14 | jobs: 15 | compile: 16 | name: Compile 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Setup | Checkout 20 | uses: actions/checkout@v2 21 | - name: Setup | Rust 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: ${{ env.rust_toolchain }} 25 | components: rustfmt, clippy 26 | - name: Setup | Std 27 | run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu 28 | - name: Setup | Default to nightly 29 | run: rustup default ${{ env.rust_toolchain }} 30 | - name: Build | Fmt Check 31 | run: cargo fmt -- --check 32 | - name: Build | Clippy 33 | run: export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo clippy --no-deps --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings 34 | - name: Build | Compile 35 | run: export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort 36 | - name: Build | RISCV-ULP-HAL feature 37 | run: cargo build --features riscv-ulp-hal --no-default-features --target riscv32imc-unknown-none-elf -Zbuild-std=core,panic_abort -Zbuild-std-features=panic_immediate_abort 38 | - name: Build | Compile Native ESP-IDF V4.4 no_std 39 | run: export ESP_IDF_VERSION=release/v4.4; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --features esp-idf-sys/native --no-default-features --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort 40 | - name: Setup | ldproxy 41 | uses: actions-rs/install@v0.1 42 | with: 43 | crate: ldproxy 44 | version: latest 45 | - name: Build | Examples 46 | run: export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --examples --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort 47 | -------------------------------------------------------------------------------- /examples/rmt_neopixel.rs: -------------------------------------------------------------------------------- 1 | //! A simple example to change colours of a WS2812/NeoPixel compatible LED. 2 | //! 3 | //! It is set to pin 18 which some dev boards have connected to a compatible LED. 4 | //! 5 | //! This example demonstrates the use of [`FixedLengthSignal`][crate::rmt::FixedLengthSignal] which 6 | //! lives on the stack and requires a known length before creating it. 7 | //! 8 | //! There is a similar implementation in the esp-idf project: 9 | //! https://github.com/espressif/esp-idf/tree/20847eeb96/examples/peripherals/rmt/led_strip 10 | //! 11 | //! Datasheet (PDF) for a WS2812, which explains how the pulses are to be sent: 12 | //! https://cdn-shop.adafruit.com/datasheets/WS2812.pdf 13 | 14 | use core::time::Duration; 15 | use embedded_hal::delay::blocking::DelayUs; 16 | use esp_idf_hal::delay::Ets; 17 | use esp_idf_hal::peripherals::Peripherals; 18 | use esp_idf_hal::rmt::config::TransmitConfig; 19 | use esp_idf_hal::rmt::{FixedLengthSignal, PinState, Pulse, Transmit}; 20 | 21 | fn main() -> anyhow::Result<()> { 22 | esp_idf_sys::link_patches(); 23 | 24 | let peripherals = Peripherals::take().unwrap(); 25 | let led = peripherals.pins.gpio18.into_output()?; 26 | let channel = peripherals.rmt.channel0; 27 | let config = TransmitConfig::new().clock_divider(1); 28 | let mut tx = Transmit::new(led, channel, &config)?; 29 | 30 | let rgbs = [0xff0000, 0xffff00, 0x00ffff, 0x00ff00, 0xa000ff]; 31 | loop { 32 | for rgb in rgbs { 33 | let ticks_hz = tx.counter_clock()?; 34 | let t0h = Pulse::new_with_duration(ticks_hz, PinState::High, &ns(350))?; 35 | let t0l = Pulse::new_with_duration(ticks_hz, PinState::Low, &ns(800))?; 36 | let t1h = Pulse::new_with_duration(ticks_hz, PinState::High, &ns(700))?; 37 | let t1l = Pulse::new_with_duration(ticks_hz, PinState::Low, &ns(600))?; 38 | 39 | let mut signal = FixedLengthSignal::<24>::new(); 40 | for i in 0..24 { 41 | let bit = 2_u32.pow(i) & rgb != 0; 42 | let (high_pulse, low_pulse) = if bit { (t1h, t1l) } else { (t0h, t0l) }; 43 | signal.set(i as usize, &(high_pulse, low_pulse))?; 44 | } 45 | tx.start_blocking(&signal)?; 46 | Ets.delay_ms(1000)?; 47 | } 48 | } 49 | } 50 | 51 | fn ns(nanos: u64) -> Duration { 52 | Duration::from_nanos(nanos) 53 | } 54 | -------------------------------------------------------------------------------- /riscv-ulp/riscv_ulp_link_base.x: -------------------------------------------------------------------------------- 1 | PROVIDE(UserSoft = DefaultHandler); 2 | PROVIDE(SupervisorSoft = DefaultHandler); 3 | PROVIDE(MachineSoft = DefaultHandler); 4 | PROVIDE(UserTimer = DefaultHandler); 5 | PROVIDE(SupervisorTimer = DefaultHandler); 6 | PROVIDE(MachineTimer = DefaultHandler); 7 | PROVIDE(UserExternal = DefaultHandler); 8 | PROVIDE(SupervisorExternal = DefaultHandler); 9 | PROVIDE(MachineExternal = DefaultHandler); 10 | 11 | PROVIDE(DefaultHandler = DefaultInterruptHandler); 12 | PROVIDE(ExceptionHandler = DefaultExceptionHandler); 13 | 14 | ENTRY(reset_vector) 15 | 16 | MEMORY 17 | { 18 | ram(RW) : ORIGIN = 0, LENGTH = _ram_size 19 | } 20 | 21 | PROVIDE(_stext = ORIGIN(ram)); 22 | 23 | SECTIONS 24 | { 25 | .text.dummy (NOLOAD) : 26 | { 27 | /* This section is intended to make _stext address work */ 28 | . = ABSOLUTE(_stext); 29 | } > ram 30 | 31 | .text _stext : 32 | { 33 | KEEP(*(.init)); // Default reset vector must link to offset 0x0 34 | KEEP(*(.start.rust)); 35 | KEEP(*(.trap.rust)); 36 | 37 | *(.text .text.*); 38 | } > ram 39 | 40 | .rodata ALIGN(4): 41 | { 42 | *(.srodata .srodata.*); 43 | *(.rodata .rodata.*); 44 | 45 | /* 4-byte align the end (VMA) of this section. 46 | This is required by LLD to ensure the LMA of the following .data 47 | section will have the correct alignment. */ 48 | . = ALIGN(4); 49 | } > ram 50 | 51 | .data ALIGN(4): 52 | { 53 | /* Must be called __global_pointer$ for linker relaxations to work. */ 54 | PROVIDE(__global_pointer$ = . + 0x800); 55 | *(.sdata .sdata.* .sdata2 .sdata2.*); 56 | *(.data .data.*); 57 | . = ALIGN(4); 58 | } > ram 59 | 60 | .bss ALIGN(4) : 61 | { 62 | *(.sbss .sbss.* .bss .bss.*); 63 | . = ALIGN(4); 64 | } > ram 65 | 66 | /* fake output .got section */ 67 | /* Dynamic relocations are unsupported. This section is only used to detect 68 | relocatable code in the input files and raise an error if relocatable code 69 | is found */ 70 | .got (INFO) : 71 | { 72 | KEEP(*(.got .got.*)); 73 | } 74 | 75 | .eh_frame (INFO) : 76 | { 77 | KEEP(*(.eh_frame)) 78 | } 79 | 80 | .eh_frame_hdr (INFO) : 81 | { 82 | *(.eh_frame_hdr) 83 | } 84 | 85 | _stack_top = ORIGIN(ram) + LENGTH(ram); 86 | } -------------------------------------------------------------------------------- /examples/ledc-threads.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Borrow, sync::Arc, time::Duration}; 2 | 3 | use esp_idf_hal::gpio::OutputPin; 4 | use esp_idf_hal::ledc::*; 5 | use esp_idf_hal::peripherals::Peripherals; 6 | use esp_idf_hal::prelude::*; 7 | 8 | const CYCLES: usize = 3; 9 | 10 | fn cycle_duty( 11 | mut pwm: Channel, 12 | times: usize, 13 | log_prefix: &str, 14 | sleep: Duration, 15 | ) -> anyhow::Result<()> 16 | where 17 | C: HwChannel, 18 | H: HwTimer, 19 | T: Borrow>, 20 | P: OutputPin, 21 | { 22 | let max_duty = pwm.get_max_duty(); 23 | 24 | for cycle in 0..times { 25 | println!("{} cycle: {}", log_prefix, cycle); 26 | 27 | for numerator in [0, 1, 2, 3, 4, 5].iter() { 28 | println!("{} duty: {}/5", log_prefix, numerator); 29 | pwm.set_duty(max_duty * numerator / 5)?; 30 | std::thread::sleep(sleep); 31 | } 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | fn main() -> anyhow::Result<()> { 38 | esp_idf_sys::link_patches(); 39 | 40 | println!("Setting up PWM output channels"); 41 | 42 | let mut peripherals = Peripherals::take().unwrap(); 43 | let config = config::TimerConfig::default().frequency(25.kHz().into()); 44 | let timer = Arc::new(Timer::new(peripherals.ledc.timer0, &config)?); 45 | let timer0 = timer.clone(); 46 | let timer1 = timer.clone(); 47 | let channel0 = Channel::new(peripherals.ledc.channel0, timer0, peripherals.pins.gpio4)?; 48 | let channel1 = Channel::new(peripherals.ledc.channel1, timer1, peripherals.pins.gpio5)?; 49 | 50 | println!("Spawning PWM threads"); 51 | 52 | let thread0 = std::thread::Builder::new() 53 | .stack_size(7000) 54 | .spawn(move || cycle_duty(channel0, CYCLES, "PWM 0", Duration::from_millis(1000)))?; 55 | let thread1 = std::thread::Builder::new() 56 | .stack_size(7000) 57 | .spawn(move || cycle_duty(channel1, CYCLES, "PWM 1", Duration::from_millis(1750)))?; 58 | 59 | println!("Waiting for PWM threads"); 60 | 61 | thread0.join().unwrap()?; 62 | thread1.join().unwrap()?; 63 | 64 | println!("Joined PWM threads"); 65 | 66 | if let Ok(timer) = Arc::try_unwrap(timer) { 67 | println!("Unwrapped timer"); 68 | if let Ok(hw_timer) = timer.release() { 69 | println!("Recovered HW timer"); 70 | peripherals.ledc.timer0 = hw_timer; 71 | } 72 | } 73 | 74 | println!("Done"); 75 | 76 | loop { 77 | // Don't let the idle task starve and trigger warnings from the watchdog. 78 | std::thread::sleep(Duration::from_millis(100)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /riscv-ulp/riscv_ulp_start.S: -------------------------------------------------------------------------------- 1 | // NOTE: Adapted from riscv-rt/asm.S 2 | #define REGBYTES (1 << 2) 3 | 4 | .section .init, "ax" 5 | .global reset_vector 6 | .global irq_vector 7 | 8 | // The reset vector, jumps to startup code 9 | reset_vector: 10 | j _start 11 | 12 | // Interrupt handler 13 | .option push 14 | .option norelax // To prevent an unsupported R_RISCV_ALIGN relocation from being generated 15 | .balign 16 16 | irq_vector: 17 | addi sp, sp, -16*REGBYTES 18 | 19 | sw ra, 0*REGBYTES(sp) 20 | sw t0, 1*REGBYTES(sp) 21 | sw t1, 2*REGBYTES(sp) 22 | sw t2, 3*REGBYTES(sp) 23 | sw t3, 4*REGBYTES(sp) 24 | sw t4, 5*REGBYTES(sp) 25 | sw t5, 6*REGBYTES(sp) 26 | sw t6, 7*REGBYTES(sp) 27 | sw a0, 8*REGBYTES(sp) 28 | sw a1, 9*REGBYTES(sp) 29 | sw a2, 10*REGBYTES(sp) 30 | sw a3, 11*REGBYTES(sp) 31 | sw a4, 12*REGBYTES(sp) 32 | sw a5, 13*REGBYTES(sp) 33 | sw a6, 14*REGBYTES(sp) 34 | sw a7, 15*REGBYTES(sp) 35 | 36 | add a0, sp, zero 37 | jal ra, _start_trap_rust 38 | 39 | lw ra, 0*REGBYTES(sp) 40 | lw t0, 1*REGBYTES(sp) 41 | lw t1, 2*REGBYTES(sp) 42 | lw t2, 3*REGBYTES(sp) 43 | lw t3, 4*REGBYTES(sp) 44 | lw t4, 5*REGBYTES(sp) 45 | lw t5, 6*REGBYTES(sp) 46 | lw t6, 7*REGBYTES(sp) 47 | lw a0, 8*REGBYTES(sp) 48 | lw a1, 9*REGBYTES(sp) 49 | lw a2, 10*REGBYTES(sp) 50 | lw a3, 11*REGBYTES(sp) 51 | lw a4, 12*REGBYTES(sp) 52 | lw a5, 13*REGBYTES(sp) 53 | lw a6, 14*REGBYTES(sp) 54 | lw a7, 15*REGBYTES(sp) 55 | 56 | addi sp, sp, 16*REGBYTES 57 | ret 58 | .option pop 59 | 60 | _start: 61 | .cfi_startproc 62 | .cfi_undefined ra 63 | 64 | li x1, 0 65 | li x2, 0 66 | li x3, 0 67 | li x4, 0 68 | li x5, 0 69 | li x6, 0 70 | li x7, 0 71 | li x8, 0 72 | li x9, 0 73 | li x10,0 74 | li x11,0 75 | li x12,0 76 | li x13,0 77 | li x14,0 78 | li x15,0 79 | li x16,0 80 | li x17,0 81 | li x18,0 82 | li x19,0 83 | li x20,0 84 | li x21,0 85 | li x22,0 86 | li x23,0 87 | li x24,0 88 | li x25,0 89 | li x26,0 90 | li x27,0 91 | li x28,0 92 | li x29,0 93 | li x30,0 94 | li x31,0 95 | 96 | .option push 97 | .option norelax // To prevent an unsupported R_RISCV_ALIGN relocation from being generated 98 | la gp, __global_pointer$ 99 | .option pop 100 | 101 | // Allocate stack 102 | la sp, _stack_top 103 | 104 | // Set frame pointer 105 | add s0, sp, zero 106 | 107 | jal zero, _start_rust 108 | 109 | .cfi_endproc 110 | 111 | loop: 112 | j loop 113 | 114 | // Make sure there is an abort when linking 115 | .globl abort 116 | abort: 117 | j abort 118 | -------------------------------------------------------------------------------- /examples/i2c_ssd1306.rs: -------------------------------------------------------------------------------- 1 | //! I2C test with SSD1306 2 | //! 3 | //! Folowing pins are used: 4 | //! SDA GPIO5 5 | //! SCL GPIO6 6 | //! 7 | //! Depending on your target and the board you are using you have to change the pins. 8 | //! 9 | //! For this example you need to hook up an SSD1306 I2C display. 10 | //! The display will flash black and white. 11 | 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | use embedded_hal::i2c::blocking::I2c; 16 | 17 | use esp_idf_hal::i2c; 18 | use esp_idf_hal::peripherals::Peripherals; 19 | use esp_idf_hal::prelude::*; 20 | 21 | const SSD1306_ADDRESS: u8 = 0x3c; 22 | 23 | fn main() -> anyhow::Result<()> { 24 | esp_idf_sys::link_patches(); 25 | 26 | let peripherals = Peripherals::take().unwrap(); 27 | let i2c = peripherals.i2c0; 28 | let sda = peripherals.pins.gpio5; 29 | let scl = peripherals.pins.gpio6; 30 | 31 | println!("Starting I2C SSD1306 test"); 32 | 33 | let config = ::default().baudrate(100.kHz().into()); 34 | let mut i2c = i2c::Master::::new(i2c, i2c::MasterPins { sda, scl }, config)?; 35 | 36 | // initialze the display - don't worry about the meaning of these bytes - it's specific to SSD1306 37 | i2c.write(SSD1306_ADDRESS, &[0, 0xae])?; 38 | i2c.write(SSD1306_ADDRESS, &[0, 0xd4])?; 39 | i2c.write(SSD1306_ADDRESS, &[0, 0x80])?; 40 | i2c.write(SSD1306_ADDRESS, &[0, 0xa8])?; 41 | i2c.write(SSD1306_ADDRESS, &[0, 0x3f])?; 42 | i2c.write(SSD1306_ADDRESS, &[0, 0xd3])?; 43 | i2c.write(SSD1306_ADDRESS, &[0, 0x00])?; 44 | i2c.write(SSD1306_ADDRESS, &[0, 0x40])?; 45 | i2c.write(SSD1306_ADDRESS, &[0, 0x8d])?; 46 | i2c.write(SSD1306_ADDRESS, &[0, 0x14])?; 47 | i2c.write(SSD1306_ADDRESS, &[0, 0xa1])?; 48 | i2c.write(SSD1306_ADDRESS, &[0, 0xc8])?; 49 | i2c.write(SSD1306_ADDRESS, &[0, 0xda])?; 50 | i2c.write(SSD1306_ADDRESS, &[0, 0x12])?; 51 | i2c.write(SSD1306_ADDRESS, &[0, 0x81])?; 52 | i2c.write(SSD1306_ADDRESS, &[0, 0xcf])?; 53 | i2c.write(SSD1306_ADDRESS, &[0, 0xf1])?; 54 | i2c.write(SSD1306_ADDRESS, &[0, 0xdb])?; 55 | i2c.write(SSD1306_ADDRESS, &[0, 0x40])?; 56 | i2c.write(SSD1306_ADDRESS, &[0, 0xa4])?; 57 | i2c.write(SSD1306_ADDRESS, &[0, 0xa6])?; 58 | i2c.write(SSD1306_ADDRESS, &[0, 0xaf])?; 59 | i2c.write(SSD1306_ADDRESS, &[0, 0x20, 0x00])?; 60 | 61 | // fill the display 62 | for _ in 0..64 { 63 | let data: [u8; 17] = [ 64 | 0x40, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 65 | 0xff, 0xff, 0xff, 66 | ]; 67 | i2c.write(SSD1306_ADDRESS, &data)?; 68 | } 69 | 70 | loop { 71 | // we are using thread::sleep here to make sure the watchdog isn't triggered 72 | thread::sleep(Duration::from_millis(500)); 73 | i2c.write(SSD1306_ADDRESS, &[0, 0xa6])?; 74 | thread::sleep(Duration::from_millis(500)); 75 | i2c.write(SSD1306_ADDRESS, &[0, 0xa7])?; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/pac.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// This module is a manual translation of a bunch of C files from current ESP-IDF master (currently ESP32-S2 specific): 4 | /// - https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/soc.h (a subset) 5 | /// - https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/sens_reg.h (a subset) 6 | /// - https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/rtc_io_reg.h (a subset) 7 | use super::reg::bit; 8 | 9 | pub const DR_REG_SENS_BASE: u32 = 0x3f408800; 10 | pub const DR_REG_RTCIO_BASE: u32 = 0x3ff48400; 11 | pub const DR_REG_RTCCNTL_BASE: u32 = 0x3f408000; 12 | 13 | pub const RTC_CNTL_COCPU_CTRL_REG: u32 = DR_REG_RTCCNTL_BASE + 0x0100; 14 | pub const RTC_CNTL_COCPU_DONE: u32 = bit(25); 15 | pub const RTC_CNTL_COCPU_SHUT_RESET_EN: u32 = bit(22); 16 | pub const RTC_CNTL_COCPU_SHUT_2_CLK_DIS: u32 = 0x000000FF; 17 | pub const RTC_CNTL_COCPU_SHUT_2_CLK_DIS_V: u32 = 0xFF; 18 | pub const RTC_CNTL_COCPU_SHUT_2_CLK_DIS_S: u32 = 14; 19 | 20 | pub const RTC_CNTL_STATE0_REG: u32 = DR_REG_RTCCNTL_BASE + 0x0018; 21 | pub const RTC_CNTL_SW_CPU_INT: u32 = bit(0); 22 | pub const RTC_CNTL_ULP_CP_SLP_TIMER_EN: u32 = bit(31); 23 | pub const RTC_CNTL_ULP_CP_SLP_TIMER_EN_V: u32 = 0x1; 24 | pub const RTC_CNTL_ULP_CP_SLP_TIMER_EN_S: u32 = 31; 25 | 26 | pub const SENS_SAR_IO_MUX_CONF_REG: u32 = DR_REG_SENS_BASE + 0x0144; 27 | pub const SENS_IOMUX_CLK_GATE_EN_M: u32 = bit(31); 28 | 29 | pub const RTC_IO_TOUCH_PAD0_REG: u32 = DR_REG_RTCIO_BASE + 0x84; 30 | pub const RTC_IO_TOUCH_PAD0_DRV: u32 = 0x00000003; 31 | pub const RTC_IO_TOUCH_PAD0_DRV_V: u32 = 0x3; 32 | pub const RTC_IO_TOUCH_PAD0_DRV_S: u32 = 29; 33 | pub const RTC_IO_TOUCH_PAD0_MUX_SEL: u32 = bit(19); 34 | pub const RTC_IO_TOUCH_PAD0_FUN_SEL: u32 = 0x00000003; 35 | pub const RTC_IO_TOUCH_PAD0_FUN_SEL_V: u32 = 0x3; 36 | pub const RTC_IO_TOUCH_PAD0_FUN_SEL_S: u32 = 17; 37 | pub const RTC_IO_TOUCH_PAD0_FUN_IE: u32 = bit(13); 38 | pub const RTC_IO_TOUCH_PAD0_FUN_IE_V: u32 = 0x01; 39 | pub const RTC_IO_TOUCH_PAD0_FUN_IE_S: u32 = 13; 40 | pub const RTC_IO_TOUCH_PAD0_RUE: u32 = bit(27); 41 | pub const RTC_IO_TOUCH_PAD0_RDE: u32 = bit(28); 42 | 43 | pub const RTC_GPIO_ENABLE_W1TS_REG: u32 = DR_REG_RTCIO_BASE + 0x10; 44 | pub const RTC_GPIO_ENABLE_W1TS: u32 = 0x0003FFFF; 45 | pub const RTC_GPIO_ENABLE_W1TS_V: u32 = 0x3FFFF; 46 | pub const RTC_GPIO_ENABLE_W1TS_S: u32 = 10; 47 | 48 | pub const RTC_GPIO_ENABLE_W1TC_REG: u32 = DR_REG_RTCIO_BASE + 0x14; 49 | pub const RTC_GPIO_ENABLE_W1TC: u32 = 0x0003FFFF; 50 | pub const RTC_GPIO_ENABLE_W1TC_V: u32 = 0x3FFFF; 51 | pub const RTC_GPIO_ENABLE_W1TC_S: u32 = 10; 52 | 53 | pub const RTC_GPIO_IN_REG: u32 = DR_REG_RTCIO_BASE + 0x24; 54 | pub const RTC_GPIO_IN_NEXT: u32 = 0x0003FFFF; 55 | pub const RTC_GPIO_IN_NEXT_V: u32 = 0x3FFFF; 56 | pub const RTC_GPIO_IN_NEXT_S: u32 = 10; 57 | 58 | pub const RTC_GPIO_OUT_W1TS_REG: u32 = DR_REG_RTCIO_BASE + 0x4; 59 | pub const RTC_GPIO_OUT_DATA_W1TS: u32 = 0x0003FFFF; 60 | pub const RTC_GPIO_OUT_DATA_W1TS_V: u32 = 0x3FFFF; 61 | pub const RTC_GPIO_OUT_DATA_W1TS_S: u32 = 10; 62 | 63 | pub const RTC_GPIO_OUT_W1TC_REG: u32 = DR_REG_RTCIO_BASE + 0x8; 64 | pub const RTC_GPIO_OUT_DATA_W1TC: u32 = 0x0003FFFF; 65 | pub const RTC_GPIO_OUT_DATA_W1TC_V: u32 = 0x3FFFF; 66 | pub const RTC_GPIO_OUT_DATA_W1TC_S: u32 = 10; 67 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![feature(cfg_version)] 3 | #![feature(generic_associated_types)] // For mutex 4 | #![cfg_attr(version("1.61"), allow(deprecated_where_clause_location))] 5 | #![cfg_attr(not(version("1.59")), feature(asm))] 6 | #![cfg_attr( 7 | all(version("1.58"), target_arch = "xtensa"), 8 | feature(asm_experimental_arch) 9 | )] 10 | 11 | #[cfg(all(feature = "std", feature = "riscv-ulp-hal"))] 12 | compile_error!("Feature `std` is not compatible with feature `ulp`"); 13 | 14 | #[cfg(all(feature = "embedded-svc-mutex", feature = "riscv-ulp-hal"))] 15 | compile_error!("Feature `embedded-svc-mutex` is not compatible with feature `ulp`"); 16 | 17 | #[cfg(all(feature = "riscv-ulp-hal", not(esp32s2)))] 18 | compile_error!("Feature `ulp` is currently only supported on esp32s2"); 19 | 20 | #[macro_use] 21 | pub mod riscv_ulp_hal; 22 | 23 | pub mod adc; 24 | #[cfg(not(feature = "riscv-ulp-hal"))] 25 | pub mod can; 26 | #[cfg(not(feature = "riscv-ulp-hal"))] 27 | pub mod cpu; 28 | #[cfg(not(feature = "riscv-ulp-hal"))] 29 | pub mod delay; 30 | pub mod gpio; 31 | #[cfg(esp32)] 32 | pub mod hall; 33 | #[cfg(not(feature = "riscv-ulp-hal"))] 34 | pub mod i2c; 35 | #[cfg(not(feature = "riscv-ulp-hal"))] 36 | pub mod interrupt; 37 | pub mod ledc; 38 | #[cfg(not(feature = "riscv-ulp-hal"))] 39 | pub mod mutex; 40 | pub mod peripherals; 41 | pub mod prelude; 42 | #[cfg(not(feature = "riscv-ulp-hal"))] 43 | pub mod rmt; 44 | #[cfg(not(feature = "riscv-ulp-hal"))] 45 | pub mod serial; 46 | #[cfg(not(feature = "riscv-ulp-hal"))] 47 | pub mod spi; 48 | #[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] 49 | pub mod ulp; 50 | pub mod units; 51 | 52 | #[cfg(feature = "riscv-ulp-hal")] 53 | pub use crate::riscv_ulp_hal::delay; 54 | 55 | // This is used to create `embedded_hal` compatible error structs 56 | // that preserve original `EspError`. 57 | // 58 | // Example: 59 | // embedded_hal_error!(I2cError, embedded_hal::i2c::Error, embedded_hal::i2c::ErrorKind) 60 | #[allow(unused_macros)] 61 | macro_rules! embedded_hal_error { 62 | ($error:ident, $errortrait:ty, $kind:ty) => { 63 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 64 | pub struct $error { 65 | kind: $kind, 66 | cause: esp_idf_sys::EspError, 67 | } 68 | 69 | impl $error { 70 | pub fn new(kind: $kind, cause: esp_idf_sys::EspError) -> Self { 71 | Self { kind, cause } 72 | } 73 | 74 | pub fn other(cause: esp_idf_sys::EspError) -> Self { 75 | Self::new(<$kind>::Other, cause) 76 | } 77 | 78 | pub fn cause(&self) -> esp_idf_sys::EspError { 79 | self.cause 80 | } 81 | } 82 | 83 | impl From for $error { 84 | fn from(e: esp_idf_sys::EspError) -> Self { 85 | Self::other(e) 86 | } 87 | } 88 | 89 | impl $errortrait for $error { 90 | fn kind(&self) -> $kind { 91 | self.kind 92 | } 93 | } 94 | 95 | impl core::fmt::Display for $error { 96 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 97 | write!( 98 | f, 99 | "{} {{ kind: {}, cause: {} }}", 100 | stringify!($error), 101 | self.kind, 102 | self.cause() 103 | ) 104 | } 105 | } 106 | 107 | #[cfg(feature = "std")] 108 | impl std::error::Error for $error {} 109 | }; 110 | } 111 | 112 | #[allow(unused_imports)] 113 | pub(crate) use embedded_hal_error; 114 | -------------------------------------------------------------------------------- /src/peripherals.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "riscv-ulp-hal"))] 2 | use crate::mutex; 3 | 4 | #[cfg(feature = "riscv-ulp-hal")] 5 | use crate::riscv_ulp_hal::mutex; 6 | 7 | use crate::adc; 8 | #[cfg(not(feature = "riscv-ulp-hal"))] 9 | use crate::can; 10 | use crate::gpio; 11 | #[cfg(esp32)] 12 | use crate::hall; 13 | #[cfg(not(feature = "riscv-ulp-hal"))] 14 | use crate::i2c; 15 | #[cfg(not(feature = "riscv-ulp-hal"))] 16 | use crate::ledc; 17 | #[cfg(not(feature = "riscv-ulp-hal"))] 18 | use crate::rmt; 19 | #[cfg(not(feature = "riscv-ulp-hal"))] 20 | use crate::serial; 21 | #[cfg(not(feature = "riscv-ulp-hal"))] 22 | use crate::spi; 23 | #[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] 24 | use crate::ulp; 25 | 26 | pub struct Peripherals { 27 | pub pins: gpio::Pins, 28 | #[cfg(not(feature = "riscv-ulp-hal"))] 29 | pub uart0: serial::UART0, 30 | #[cfg(not(feature = "riscv-ulp-hal"))] 31 | pub uart1: serial::UART1, 32 | #[cfg(all(esp32, not(feature = "riscv-ulp-hal")))] 33 | pub uart2: serial::UART2, 34 | #[cfg(not(feature = "riscv-ulp-hal"))] 35 | pub i2c0: i2c::I2C0, 36 | #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] 37 | pub i2c1: i2c::I2C1, 38 | #[cfg(not(feature = "riscv-ulp-hal"))] 39 | pub spi1: spi::SPI1, 40 | #[cfg(not(feature = "riscv-ulp-hal"))] 41 | pub spi2: spi::SPI2, 42 | #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] 43 | pub spi3: spi::SPI3, 44 | pub adc1: adc::ADC1, 45 | pub adc2: adc::ADC2, 46 | #[cfg(esp32)] 47 | pub hall_sensor: hall::HallSensor, 48 | #[cfg(not(feature = "riscv-ulp-hal"))] 49 | pub can: can::CAN, 50 | #[cfg(not(feature = "riscv-ulp-hal"))] 51 | pub ledc: ledc::Peripheral, 52 | #[cfg(not(feature = "riscv-ulp-hal"))] 53 | pub rmt: rmt::Peripheral, 54 | #[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] 55 | pub ulp: ulp::ULP, 56 | } 57 | 58 | static TAKEN: mutex::Mutex = mutex::Mutex::new(false); 59 | 60 | impl Peripherals { 61 | pub fn take() -> Option { 62 | let mut taken = TAKEN.lock(); 63 | 64 | if *taken { 65 | None 66 | } else { 67 | *taken = true; 68 | Some(unsafe { Peripherals::new() }) 69 | } 70 | } 71 | 72 | /// # Safety 73 | /// 74 | /// Care should be taken not to instantiate the Peripherals structure, if it is already instantiated and used elsewhere 75 | pub unsafe fn new() -> Self { 76 | Self { 77 | pins: gpio::Pins::new(), 78 | #[cfg(not(feature = "riscv-ulp-hal"))] 79 | uart0: serial::UART0::new(), 80 | #[cfg(not(feature = "riscv-ulp-hal"))] 81 | uart1: serial::UART1::new(), 82 | #[cfg(all(esp32, not(feature = "riscv-ulp-hal")))] 83 | uart2: serial::UART2::new(), 84 | #[cfg(not(feature = "riscv-ulp-hal"))] 85 | i2c0: i2c::I2C0::new(), 86 | #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] 87 | i2c1: i2c::I2C1::new(), 88 | #[cfg(not(feature = "riscv-ulp-hal"))] 89 | spi1: spi::SPI1::new(), 90 | #[cfg(not(feature = "riscv-ulp-hal"))] 91 | spi2: spi::SPI2::new(), 92 | #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] 93 | spi3: spi::SPI3::new(), 94 | adc1: adc::ADC1::new(), 95 | adc2: adc::ADC2::new(), 96 | #[cfg(esp32)] 97 | hall_sensor: hall::HallSensor::new(), 98 | #[cfg(not(feature = "riscv-ulp-hal"))] 99 | can: can::CAN::new(), 100 | #[cfg(not(feature = "riscv-ulp-hal"))] 101 | ledc: ledc::Peripheral::new(), 102 | #[cfg(not(feature = "riscv-ulp-hal"))] 103 | rmt: rmt::Peripheral::new(), 104 | #[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] 105 | ulp: ulp::ULP::new(), 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/start.rs: -------------------------------------------------------------------------------- 1 | //! Minimal startup / runtime for ESP32-SXX RISC-V ULPs 2 | //! Adapted from riscv-rt/src/lib.rs 3 | 4 | #![deny(missing_docs)] 5 | 6 | use super::sys::cpu; 7 | 8 | #[export_name = "error: ulp_start appears more than once in the dependency graph"] 9 | #[doc(hidden)] 10 | pub static __ONCE__: () = (); 11 | 12 | /// # Safety 13 | /// 14 | /// Rust entry point (_start_rust) 15 | /// This function is NOT supposed to be called from use code 16 | /// 17 | /// Calls main. This function never returns. 18 | #[link_section = ".start.rust"] 19 | #[export_name = "_start_rust"] 20 | pub unsafe extern "C" fn start_rust() -> ! { 21 | #[rustfmt::skip] 22 | extern "Rust" { 23 | // This symbol will be provided by the user 24 | fn main(); 25 | } 26 | 27 | cpu::rescue_from_monitor(); 28 | 29 | main(); 30 | 31 | cpu::shutdown(); 32 | } 33 | 34 | /// Registers saved in trap handler 35 | #[allow(missing_docs)] 36 | #[repr(C)] 37 | pub struct TrapFrame { 38 | pub ra: usize, 39 | pub t0: usize, 40 | pub t1: usize, 41 | pub t2: usize, 42 | pub t3: usize, 43 | pub t4: usize, 44 | pub t5: usize, 45 | pub t6: usize, 46 | pub a0: usize, 47 | pub a1: usize, 48 | pub a2: usize, 49 | pub a3: usize, 50 | pub a4: usize, 51 | pub a5: usize, 52 | pub a6: usize, 53 | pub a7: usize, 54 | } 55 | 56 | /// # Safety 57 | /// 58 | /// Trap entry point rust (_start_trap_rust) 59 | /// This function is NOT supposed to be called from use code 60 | /// 61 | /// `mcause` is read to determine the cause of the trap. XLEN-1 bit indicates 62 | /// if it's an interrupt or an exception. The result is examined and ExceptionHandler 63 | /// or one of the core interrupt handlers is called. 64 | #[link_section = ".trap.rust"] 65 | #[export_name = "_start_trap_rust"] 66 | pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { 67 | // use riscv::register::mcause; 68 | 69 | extern "C" { 70 | fn ExceptionHandler(trap_frame: &TrapFrame); 71 | #[allow(dead_code)] 72 | fn DefaultHandler(); 73 | } 74 | 75 | // let cause = mcause::read(); 76 | // if cause.is_exception() { 77 | ExceptionHandler(trap_frame.as_ref().unwrap()) 78 | // } else { 79 | // let code = cause.code(); 80 | // if code < __INTERRUPTS.len() { 81 | // let h = &__INTERRUPTS[code]; 82 | // if h.reserved == 0 { 83 | // DefaultHandler(); 84 | // } else { 85 | // (h.handler)(); 86 | // } 87 | // } else { 88 | // DefaultHandler(); 89 | // } 90 | // } 91 | } 92 | 93 | #[doc(hidden)] 94 | #[no_mangle] 95 | #[allow(unused_variables, non_snake_case)] 96 | pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! { 97 | loop { 98 | // Prevent this from turning into a UDF instruction 99 | // see rust-lang/rust#28728 for details 100 | continue; 101 | } 102 | } 103 | 104 | #[doc(hidden)] 105 | #[no_mangle] 106 | #[allow(unused_variables, non_snake_case)] 107 | pub fn DefaultInterruptHandler() { 108 | loop { 109 | // Prevent this from turning into a UDF instruction 110 | // see rust-lang/rust#28728 for details 111 | continue; 112 | } 113 | } 114 | 115 | /* Interrupts */ 116 | #[doc(hidden)] 117 | pub enum Interrupt { 118 | UserSoft, 119 | SupervisorSoft, 120 | MachineSoft, 121 | UserTimer, 122 | SupervisorTimer, 123 | MachineTimer, 124 | UserExternal, 125 | SupervisorExternal, 126 | MachineExternal, 127 | } 128 | 129 | pub use self::Interrupt as interrupt; 130 | 131 | extern "C" { 132 | fn UserSoft(); 133 | fn SupervisorSoft(); 134 | fn MachineSoft(); 135 | fn UserTimer(); 136 | fn SupervisorTimer(); 137 | fn MachineTimer(); 138 | fn UserExternal(); 139 | fn SupervisorExternal(); 140 | fn MachineExternal(); 141 | } 142 | 143 | #[doc(hidden)] 144 | pub union Vector { 145 | handler: unsafe extern "C" fn(), 146 | reserved: usize, 147 | } 148 | 149 | #[doc(hidden)] 150 | #[allow(dead_code)] 151 | #[no_mangle] 152 | pub static __INTERRUPTS: [Vector; 12] = [ 153 | Vector { handler: UserSoft }, 154 | Vector { 155 | handler: SupervisorSoft, 156 | }, 157 | Vector { reserved: 0 }, 158 | Vector { 159 | handler: MachineSoft, 160 | }, 161 | Vector { handler: UserTimer }, 162 | Vector { 163 | handler: SupervisorTimer, 164 | }, 165 | Vector { reserved: 0 }, 166 | Vector { 167 | handler: MachineTimer, 168 | }, 169 | Vector { 170 | handler: UserExternal, 171 | }, 172 | Vector { 173 | handler: SupervisorExternal, 174 | }, 175 | Vector { reserved: 0 }, 176 | Vector { 177 | handler: MachineExternal, 178 | }, 179 | ]; 180 | -------------------------------------------------------------------------------- /examples/rmt_musical_buzzer.rs: -------------------------------------------------------------------------------- 1 | //! Play a song using a piezo buzzer. 2 | //! 3 | //! Should play "Ode to Joy" on pin 17. 4 | //! 5 | //! Based off the ESP-IDF rmt musical buzzer example: 6 | //! https://github.com/espressif/esp-idf/blob/b092fa073047c957545a0ae9504f04972a8c6d74/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c 7 | use core::time::Duration; 8 | use embedded_hal::delay::blocking::DelayUs; 9 | use esp_idf_hal::delay::Ets; 10 | use esp_idf_hal::gpio::OutputPin; 11 | use esp_idf_hal::peripherals::Peripherals; 12 | use esp_idf_hal::rmt::config::{Loop, TransmitConfig}; 13 | use esp_idf_hal::rmt::{FixedLengthSignal, HwChannel, PinState, Pulse, PulseTicks, Transmit}; 14 | use notes::*; 15 | 16 | fn main() -> anyhow::Result<()> { 17 | esp_idf_sys::link_patches(); 18 | 19 | let peripherals = Peripherals::take().unwrap(); 20 | let led = peripherals.pins.gpio17.into_output()?; 21 | let channel = peripherals.rmt.channel0; 22 | let config = TransmitConfig::new().looping(Loop::Count(1024)); 23 | let mut tx = Transmit::new(led, channel, &config)?; 24 | 25 | loop { 26 | play_song(&mut tx, ODE_TO_JOY)?; 27 | Ets.delay_ms(3000)?; 28 | } 29 | } 30 | 31 | pub fn play_song( 32 | tx: &mut Transmit, 33 | song: &[NoteValue], 34 | ) -> anyhow::Result<()> { 35 | for note_value in song { 36 | play_note(tx, note_value.note.0, note_value.duration)?; 37 | } 38 | Ok(()) 39 | } 40 | 41 | pub fn play_note( 42 | tx: &mut Transmit, 43 | pitch: u16, 44 | duration: Duration, 45 | ) -> anyhow::Result<()> { 46 | // Calculate the frequency for a piezo buzzer. 47 | let ticks_hz = tx.counter_clock()?; 48 | let tick_count = (ticks_hz.0 as u128 / pitch as u128 / 2_u128) as u16; 49 | let ticks = PulseTicks::new(tick_count)?; 50 | 51 | // Add high and low pulses for the tick duration. 52 | let on = Pulse::new(PinState::High, ticks); 53 | let off = Pulse::new(PinState::Low, ticks); 54 | let mut signal = FixedLengthSignal::<1>::new(); 55 | signal.set(0, &(on, off))?; 56 | 57 | // Play the note for the 80% of the duration. 58 | tx.start(signal)?; 59 | Ets.delay_ms((80 * duration.as_millis() / 100) as u32)?; 60 | 61 | // Small pause between notes, 20% of the specified duration. 62 | tx.stop()?; 63 | Ets.delay_ms((20 * duration.as_millis() / 100) as u32)?; 64 | 65 | Ok(()) 66 | } 67 | 68 | #[derive(Debug)] 69 | pub struct Note(u16); 70 | 71 | pub mod notes { 72 | use crate::Note; 73 | 74 | #[allow(dead_code)] 75 | pub const A4: Note = Note(440); 76 | pub const AS4: Note = Note(466); 77 | pub const B4: Note = Note(494); 78 | pub const C5: Note = Note(523); 79 | pub const CS5: Note = Note(554); 80 | pub const D5: Note = Note(587); 81 | pub const DS5: Note = Note(622); 82 | pub const E5: Note = Note(659); 83 | pub const F5: Note = Note(698); 84 | pub const FS5: Note = Note(740); 85 | pub const G5: Note = Note(784); 86 | pub const GS5: Note = Note(831); 87 | pub const A5: Note = Note(880); 88 | } 89 | 90 | #[derive(Debug)] 91 | pub struct NoteValue { 92 | note: Note, 93 | duration: Duration, 94 | } 95 | 96 | macro_rules! n { 97 | ($n: expr, $duration: expr) => { 98 | NoteValue { 99 | note: $n, 100 | duration: Duration::from_millis($duration), 101 | } 102 | }; 103 | } 104 | 105 | const ODE_TO_JOY: &[NoteValue] = &[ 106 | n!(FS5, 400), 107 | n!(FS5, 600), 108 | n!(G5, 400), 109 | n!(A5, 400), 110 | n!(A5, 400), 111 | n!(G5, 400), 112 | n!(FS5, 400), 113 | n!(E5, 400), 114 | n!(D5, 400), 115 | n!(D5, 400), 116 | n!(E5, 400), 117 | n!(FS5, 400), 118 | n!(FS5, 400), 119 | n!(FS5, 200), 120 | n!(E5, 200), 121 | n!(E5, 800), 122 | n!(FS5, 400), 123 | n!(FS5, 600), 124 | n!(G5, 400), 125 | n!(A5, 400), 126 | n!(A5, 400), 127 | n!(G5, 400), 128 | n!(FS5, 400), 129 | n!(E5, 400), 130 | n!(D5, 400), 131 | n!(D5, 400), 132 | n!(E5, 400), 133 | n!(FS5, 400), 134 | n!(E5, 400), 135 | n!(E5, 200), 136 | n!(D5, 200), 137 | n!(D5, 800), 138 | n!(E5, 400), 139 | n!(E5, 400), 140 | n!(FS5, 400), 141 | n!(D5, 400), 142 | n!(E5, 400), 143 | n!(FS5, 200), 144 | n!(G5, 200), 145 | n!(FS5, 400), 146 | n!(D5, 400), 147 | n!(E5, 400), 148 | n!(FS5, 200), 149 | n!(G5, 200), 150 | n!(FS5, 400), 151 | n!(E5, 400), 152 | n!(D5, 400), 153 | n!(E5, 400), 154 | n!(A4, 400), 155 | n!(A4, 400), 156 | n!(FS5, 400), 157 | n!(FS5, 600), 158 | n!(G5, 400), 159 | n!(A5, 400), 160 | n!(A5, 400), 161 | n!(G5, 400), 162 | n!(FS5, 400), 163 | n!(E5, 400), 164 | n!(D5, 400), 165 | n!(D5, 400), 166 | n!(E5, 400), 167 | n!(FS5, 400), 168 | n!(E5, 400), 169 | n!(E5, 200), 170 | n!(D5, 200), 171 | n!(D5, 800), 172 | ]; 173 | -------------------------------------------------------------------------------- /src/delay.rs: -------------------------------------------------------------------------------- 1 | use core::convert::Infallible; 2 | use core::time::Duration; 3 | 4 | use esp_idf_sys::*; 5 | 6 | #[allow(non_upper_case_globals)] 7 | pub(crate) const portMAX_DELAY: TickType_t = TickType_t::max_value(); 8 | 9 | #[allow(non_upper_case_globals)] 10 | const portTICK_PERIOD_MS: u32 = 1000 / configTICK_RATE_HZ; 11 | 12 | pub struct TickType(pub TickType_t); 13 | 14 | impl From for TickType { 15 | fn from(duration: Duration) -> Self { 16 | TickType( 17 | ((duration.as_millis() + portTICK_PERIOD_MS as u128 - 1) / portTICK_PERIOD_MS as u128) 18 | as TickType_t, 19 | ) 20 | } 21 | } 22 | 23 | impl From> for TickType { 24 | fn from(duration: Option) -> Self { 25 | if let Some(duration) = duration { 26 | duration.into() 27 | } else { 28 | TickType(portMAX_DELAY) 29 | } 30 | } 31 | } 32 | 33 | impl From for Duration { 34 | fn from(ticks: TickType) -> Self { 35 | Duration::from_millis(ticks.0 as u64 * portTICK_PERIOD_MS as u64) 36 | } 37 | } 38 | 39 | impl From for Option { 40 | fn from(ticks: TickType) -> Self { 41 | if ticks.0 == portMAX_DELAY { 42 | None 43 | } else { 44 | Some(ticks.into()) 45 | } 46 | } 47 | } 48 | 49 | /// Espressif Task Scheduler-based delay provider 50 | pub struct Ets; 51 | 52 | // No longer available in the generated bindings for ESP-IDF 5 53 | #[cfg(esp_idf_version_major = "5")] 54 | extern "C" { 55 | pub fn ets_delay_us(us: u32); 56 | } 57 | 58 | impl Ets { 59 | fn delay_us_internal(&mut self, us: u32) { 60 | unsafe { 61 | ets_delay_us(us); 62 | } 63 | } 64 | 65 | fn delay_ms_internal(&mut self, ms: u32) { 66 | unsafe { 67 | ets_delay_us(ms * 1000); 68 | } 69 | } 70 | } 71 | 72 | impl embedded_hal_0_2::blocking::delay::DelayUs for Ets { 73 | fn delay_us(&mut self, us: u32) { 74 | self.delay_us_internal(us); 75 | } 76 | } 77 | 78 | impl embedded_hal_0_2::blocking::delay::DelayUs for Ets { 79 | fn delay_us(&mut self, us: u16) { 80 | self.delay_us_internal(us as _); 81 | } 82 | } 83 | 84 | impl embedded_hal_0_2::blocking::delay::DelayUs for Ets { 85 | fn delay_us(&mut self, us: u8) { 86 | self.delay_us_internal(us as _); 87 | } 88 | } 89 | 90 | impl embedded_hal_0_2::blocking::delay::DelayMs for Ets { 91 | fn delay_ms(&mut self, ms: u32) { 92 | self.delay_ms_internal(ms); 93 | } 94 | } 95 | 96 | impl embedded_hal_0_2::blocking::delay::DelayMs for Ets { 97 | fn delay_ms(&mut self, ms: u16) { 98 | self.delay_ms_internal(ms as _); 99 | } 100 | } 101 | 102 | impl embedded_hal_0_2::blocking::delay::DelayMs for Ets { 103 | fn delay_ms(&mut self, ms: u8) { 104 | self.delay_ms_internal(ms as _); 105 | } 106 | } 107 | 108 | impl embedded_hal::delay::blocking::DelayUs for Ets { 109 | type Error = Infallible; 110 | 111 | fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { 112 | self.delay_us_internal(us); 113 | 114 | Ok(()) 115 | } 116 | 117 | fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { 118 | self.delay_ms_internal(ms); 119 | 120 | Ok(()) 121 | } 122 | } 123 | 124 | /// FreeRTOS-based delay provider 125 | pub struct FreeRtos; 126 | 127 | impl FreeRtos { 128 | fn delay_us_internal(&mut self, us: u32) { 129 | let ms = us / 1000; 130 | 131 | Self::delay_ms_internal(self, ms); 132 | } 133 | 134 | fn delay_ms_internal(&mut self, ms: u32) { 135 | // divide by tick length, rounding up 136 | let ticks = (ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS; 137 | 138 | unsafe { 139 | vTaskDelay(ticks); 140 | } 141 | } 142 | } 143 | 144 | impl embedded_hal_0_2::blocking::delay::DelayUs for FreeRtos { 145 | fn delay_us(&mut self, us: u32) { 146 | self.delay_us_internal(us); 147 | } 148 | } 149 | 150 | impl embedded_hal_0_2::blocking::delay::DelayUs for FreeRtos { 151 | fn delay_us(&mut self, us: u16) { 152 | self.delay_us_internal(us as _); 153 | } 154 | } 155 | 156 | impl embedded_hal_0_2::blocking::delay::DelayUs for FreeRtos { 157 | fn delay_us(&mut self, us: u8) { 158 | self.delay_us_internal(us as _); 159 | } 160 | } 161 | 162 | impl embedded_hal_0_2::blocking::delay::DelayMs for FreeRtos { 163 | fn delay_ms(&mut self, ms: u32) { 164 | self.delay_ms_internal(ms); 165 | } 166 | } 167 | 168 | impl embedded_hal_0_2::blocking::delay::DelayMs for FreeRtos { 169 | fn delay_ms(&mut self, ms: u16) { 170 | self.delay_ms_internal(ms as _); 171 | } 172 | } 173 | 174 | impl embedded_hal_0_2::blocking::delay::DelayMs for FreeRtos { 175 | fn delay_ms(&mut self, ms: u8) { 176 | self.delay_ms_internal(ms as _); 177 | } 178 | } 179 | 180 | impl embedded_hal::delay::blocking::DelayUs for FreeRtos { 181 | type Error = Infallible; 182 | 183 | fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { 184 | self.delay_us_internal(us); 185 | 186 | Ok(()) 187 | } 188 | 189 | fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { 190 | self.delay_ms_internal(ms); 191 | 192 | Ok(()) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/reg.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// This module is a manual translation of the following C file from current ESP-IDF master: 4 | /// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_register_ops.h 5 | use core::ptr::{read_volatile, write_volatile}; 6 | 7 | /* 8 | * When COCPU accesses the RTC register, it needs to convert the access address. 9 | * When COCPU accesses the RTC memory, dont need to convert the access address. 10 | */ 11 | #[inline(always)] 12 | pub unsafe fn write_rtc_mem(addr: u32, val: i32) { 13 | write_volatile(addr as *mut i32, val); 14 | } 15 | 16 | #[inline(always)] 17 | pub unsafe fn read_rtc_mem(addr: u32) -> i32 { 18 | read_volatile(addr as *const i32) 19 | } 20 | 21 | /* 22 | * When COCPU accesses the RTC register, it needs to convert the access address. 23 | * When COCPU accesses the RTC memory, dont need to convert the access address. 24 | */ 25 | #[inline(always)] 26 | pub const fn riscv_reg_conv(addr: u32) -> u32 { 27 | ((addr & 0xffff) << 3) & 0xe000 | addr & 0x1fff | 0x8000 28 | } 29 | 30 | #[inline(always)] 31 | pub const fn ets_uncached_addr(addr: u32) -> u32 { 32 | riscv_reg_conv(addr) 33 | } 34 | 35 | #[inline(always)] 36 | pub const fn bit(nr: u32) -> u32 { 37 | 1u32 << nr 38 | } 39 | 40 | // Write value to register 41 | #[inline(always)] 42 | pub unsafe fn reg_write(r: u32, v: u32) { 43 | write_volatile(riscv_reg_conv(r) as *mut u32, v); 44 | } 45 | 46 | // Read value from register 47 | #[inline(always)] 48 | pub unsafe fn reg_read(r: u32) -> u32 { 49 | read_volatile(riscv_reg_conv(r) as *const u32) 50 | } 51 | 52 | // Get bit or get bits from register 53 | #[inline(always)] 54 | pub unsafe fn reg_get_bit(r: u32, b: u32) -> u32 { 55 | read_volatile(riscv_reg_conv(r) as *const u32) & b 56 | } 57 | 58 | // Get bit or get bits from register 59 | #[inline(always)] 60 | pub unsafe fn reg_set_bit(r: u32, b: u32) { 61 | let addr = riscv_reg_conv(r) as *mut u32; 62 | write_volatile(addr, read_volatile(addr) | b); 63 | } 64 | 65 | // Clear bit or clear bits of register 66 | #[inline(always)] 67 | pub unsafe fn reg_clr_bit(r: u32, b: u32) { 68 | let addr = riscv_reg_conv(r) as *mut u32; 69 | write_volatile(addr, read_volatile(addr) & !b); 70 | } 71 | 72 | // Set bits of register controlled by mask 73 | #[inline(always)] 74 | pub unsafe fn reg_set_bits(r: u32, b: u32, m: u32) { 75 | let addr = riscv_reg_conv(r) as *mut u32; 76 | write_volatile(addr, read_volatile(addr) & !m | b & m); 77 | } 78 | 79 | // Get field from register, uses field _S & _V to determine mask 80 | #[inline(always)] 81 | pub unsafe fn reg_get_field(r: u32, f_s: u32, f_v: u32) -> u32 { 82 | (reg_read(r) >> f_s) & f_v 83 | } 84 | 85 | // Set field of a register from variable, uses field _S & _V to determine mask 86 | #[inline(always)] 87 | pub unsafe fn reg_set_field(r: u32, f_s: u32, f_v: u32, v: u32) { 88 | reg_write(r, (reg_read(r) & !(f_v << f_s)) | ((v & f_v) << f_s)); 89 | } 90 | 91 | // Get field value from a variable, used when _f is not left shifted by _f##_S 92 | #[inline(always)] 93 | pub const fn value_get_field(r: u32, f: u32, f_s: u32) -> u32 { 94 | (r >> f_s) & f 95 | } 96 | 97 | // Get field value from a variable, used when _f is left shifted by _f##_S 98 | #[inline(always)] 99 | pub const fn value_get_field2(r: u32, f: u32, f_s: u32) -> u32 { 100 | (r & f) >> f_s 101 | } 102 | 103 | // Set field value to a variable, used when _f is not left shifted by _f##_S 104 | #[inline(always)] 105 | pub const fn value_set_field(r: u32, f: u32, f_s: u32, v: u32) -> u32 { 106 | r & !(f << f_s) | (v << f_s) 107 | } 108 | 109 | // Set field value to a variable, used when _f is left shifted by _f##_S 110 | #[inline(always)] 111 | pub const fn value_set_field2(r: u32, f: u32, f_s: u32, v: u32) -> u32 { 112 | r & !f | (v << f_s) 113 | } 114 | 115 | // Generate a value from a field value, used when _f is not left shifted by _f##_S 116 | #[inline(always)] 117 | pub const fn field_to_value(f: u32, f_s: u32, v: u32) -> u32 { 118 | (v & f) << f_s 119 | } 120 | 121 | // Generate a value from a field value, used when _f is left shifted by _f##_S 122 | #[inline(always)] 123 | pub const fn field_to_value2(f: u32, f_s: u32, v: u32) -> u32 { 124 | (v << f_s) & f 125 | } 126 | 127 | // Read value from register 128 | #[inline(always)] 129 | pub unsafe fn read_peri_reg(addr: u32) -> u32 { 130 | read_volatile(ets_uncached_addr(addr) as *const u32) 131 | } 132 | 133 | // Write value to register 134 | #[inline(always)] 135 | pub unsafe fn write_peri_reg(addr: u32, v: u32) { 136 | write_volatile(ets_uncached_addr(addr) as *mut u32, v); 137 | } 138 | 139 | // Clear bits of register controlled by mask 140 | #[inline(always)] 141 | pub unsafe fn clear_peri_reg_mask(addr: u32, mask: u32) { 142 | write_peri_reg(addr, read_peri_reg(addr) & !mask); 143 | } 144 | 145 | #[inline(always)] 146 | pub unsafe fn set_peri_reg_mask(addr: u32, mask: u32) { 147 | write_peri_reg(addr, read_peri_reg(addr) | mask); 148 | } 149 | 150 | // Get bits of register controlled by mask 151 | #[inline(always)] 152 | pub unsafe fn get_peri_reg_mask(addr: u32, mask: u32) -> u32 { 153 | read_peri_reg(addr) & mask 154 | } 155 | 156 | // Get bits of register controlled by highest bit and lowest bit 157 | #[inline(always)] 158 | pub unsafe fn get_peri_reg_bits(addr: u32, bit_map: u32, shift: u8) -> u32 { 159 | (read_peri_reg(addr) & (bit_map << shift)) >> shift 160 | } 161 | 162 | // Set bits of register controlled by mask and shift 163 | pub unsafe fn set_peri_reg_bits(addr: u32, bit_map: u32, value: u32, shift: u8) { 164 | write_peri_reg( 165 | addr, 166 | read_peri_reg(addr) & !(bit_map << shift) | ((value & bit_map) << shift), 167 | ); 168 | } 169 | 170 | // Get field of register 171 | pub unsafe fn get_peri_reg_bits2(addr: u32, mask: u32, shift: u8) -> u32 { 172 | (read_peri_reg(addr) >> shift) & mask 173 | } 174 | -------------------------------------------------------------------------------- /examples/rmt_morse_code.rs: -------------------------------------------------------------------------------- 1 | //! RMT Transmit Example -- Morse Code 2 | //! 3 | //! To try this out, connect a piezo buzzer via a resistor into pin 17. It should repeat "HELLO" 4 | //! in morse code. 5 | //! 6 | //! Set pin 16 to low to stop the signal and make the buzzer sound "GOODBYE" in morse code. 7 | //! 8 | //! Example loosely based off which has a circuit you can follow. 9 | //! https://github.com/espressif/esp-idf/tree/master/examples/peripherals/rmt/morse_code 10 | //! 11 | //! This example demonstrates: 12 | //! * A carrier signal. 13 | //! * Looping. 14 | //! * Background sending. 15 | //! * Stopping/releasing a [`Pin`] and [`Channel`], to be used again. 16 | //! 17 | use embedded_hal::delay::blocking::DelayUs; 18 | use embedded_hal::digital::blocking::InputPin; 19 | use esp_idf_hal::delay::Ets; 20 | use esp_idf_hal::gpio::{Gpio16, Gpio17, Input, Output, Pin}; 21 | use esp_idf_hal::peripherals::Peripherals; 22 | use esp_idf_hal::rmt::config::{CarrierConfig, DutyPercent, Loop, TransmitConfig}; 23 | use esp_idf_hal::rmt::VariableLengthSignal; // This example requires `alloc` feature. 24 | use esp_idf_hal::rmt::{PinState, Pulse, PulseTicks, Transmit, CHANNEL0}; 25 | use esp_idf_hal::units::FromValueType; 26 | 27 | fn main() -> anyhow::Result<()> { 28 | esp_idf_sys::link_patches(); 29 | 30 | let peripherals = Peripherals::take().unwrap(); 31 | let led: Gpio17 = peripherals.pins.gpio17.into_output()?; 32 | let stop: Gpio16 = peripherals.pins.gpio16.into_input()?; 33 | let channel = peripherals.rmt.channel0; 34 | 35 | let carrier = CarrierConfig::new() 36 | .duty_percent(DutyPercent::new(50)?) 37 | .frequency(611.Hz()); 38 | let mut config = TransmitConfig::new() 39 | .carrier(Some(carrier)) 40 | .looping(Loop::Count(100)) 41 | .clock_divider(255); 42 | 43 | let tx = send_morse_code(&config, led, channel, "HELLO ")?; 44 | 45 | println!("Keep sending until pin {} is set low.", stop.pin()); 46 | while stop.is_high()? { 47 | Ets.delay_ms(100)?; 48 | } 49 | println!("Pin {} is set to low--stopping message.", stop.pin()); 50 | 51 | // Release pin and channel so we can use them again. 52 | let (led, channel) = tx.release()?; 53 | 54 | // Wait so the messages don't get garbled. 55 | Ets.delay_ms(3000)?; 56 | 57 | // Now send a single message and stop. 58 | println!("Saying GOODBYE!"); 59 | config.looping = Loop::None; 60 | send_morse_code(&config, led, channel, "GOODBYE")?; 61 | 62 | Ok(()) 63 | } 64 | 65 | fn send_morse_code( 66 | config: &TransmitConfig, 67 | led: Gpio17, 68 | channel: CHANNEL0, 69 | message: &str, 70 | ) -> anyhow::Result, CHANNEL0>> { 71 | println!("Sending morse message '{}' to pin {}.", message, led.pin()); 72 | 73 | let mut signal = VariableLengthSignal::new(); 74 | let pulses = str_pulses(message); 75 | // We've been collecting `Pulse` but `VariableLengthSignal` needs `&Pulse`: 76 | let pulses: Vec<&Pulse> = pulses.iter().collect(); 77 | signal.push(pulses)?; 78 | 79 | let mut tx = Transmit::new(led, channel, config)?; 80 | tx.start(signal)?; 81 | 82 | // Return `tx` so we can release the pin and channel later. 83 | Ok(tx) 84 | } 85 | 86 | fn high() -> Pulse { 87 | Pulse::new(PinState::High, PulseTicks::max()) 88 | } 89 | 90 | fn low() -> Pulse { 91 | Pulse::new(PinState::Low, PulseTicks::max()) 92 | } 93 | 94 | enum Code { 95 | Dot, 96 | Dash, 97 | WordGap, 98 | } 99 | 100 | impl Code { 101 | pub fn push_pulse(&self, pulses: &mut Vec) { 102 | match &self { 103 | Code::Dot => pulses.extend_from_slice(&[high(), low()]), 104 | Code::Dash => pulses.extend_from_slice(&[high(), high(), high(), low()]), 105 | Code::WordGap => pulses.extend_from_slice(&[low(), low(), low(), low(), low(), low()]), 106 | } 107 | } 108 | } 109 | 110 | fn find_codes(c: &char) -> &'static [Code] { 111 | for (found, codes) in CODES.iter() { 112 | if found == c { 113 | return codes; 114 | } 115 | } 116 | &[] 117 | } 118 | 119 | fn str_pulses(s: &str) -> Vec { 120 | let mut pulses = vec![]; 121 | for c in s.chars() { 122 | for code in find_codes(&c) { 123 | code.push_pulse(&mut pulses); 124 | } 125 | 126 | // Create a gap after each symbol. 127 | pulses.push(low()); 128 | pulses.push(low()); 129 | } 130 | pulses 131 | } 132 | 133 | const CODES: &[(char, &[Code])] = &[ 134 | (' ', &[Code::WordGap]), 135 | ('A', &[Code::Dot, Code::Dash]), 136 | ('B', &[Code::Dash, Code::Dot, Code::Dot, Code::Dot]), 137 | ('C', &[Code::Dash, Code::Dot, Code::Dash, Code::Dot]), 138 | ('D', &[Code::Dash, Code::Dot, Code::Dot]), 139 | ('E', &[Code::Dot]), 140 | ('F', &[Code::Dot, Code::Dot, Code::Dash, Code::Dot]), 141 | ('G', &[Code::Dash, Code::Dash, Code::Dot]), 142 | ('H', &[Code::Dot, Code::Dot, Code::Dot, Code::Dot]), 143 | ('I', &[Code::Dot, Code::Dot]), 144 | ('J', &[Code::Dot, Code::Dash, Code::Dash, Code::Dash]), 145 | ('K', &[Code::Dash, Code::Dot, Code::Dash]), 146 | ('L', &[Code::Dot, Code::Dash, Code::Dot, Code::Dot]), 147 | ('M', &[Code::Dash, Code::Dash]), 148 | ('N', &[Code::Dash, Code::Dot]), 149 | ('O', &[Code::Dash, Code::Dash, Code::Dash]), 150 | ('P', &[Code::Dot, Code::Dash, Code::Dash, Code::Dot]), 151 | ('Q', &[Code::Dash, Code::Dash, Code::Dot, Code::Dash]), 152 | ('R', &[Code::Dot, Code::Dash, Code::Dot]), 153 | ('S', &[Code::Dot, Code::Dot, Code::Dot]), 154 | ('T', &[Code::Dash]), 155 | ('U', &[Code::Dot, Code::Dot, Code::Dash]), 156 | ('V', &[Code::Dot, Code::Dot, Code::Dot, Code::Dash]), 157 | ('W', &[Code::Dot, Code::Dash, Code::Dash]), 158 | ('X', &[Code::Dash, Code::Dot, Code::Dot, Code::Dash]), 159 | ('Y', &[Code::Dash, Code::Dot, Code::Dash, Code::Dash]), 160 | ('Z', &[Code::Dash, Code::Dash, Code::Dot, Code::Dot]), 161 | ]; 162 | -------------------------------------------------------------------------------- /src/mutex.rs: -------------------------------------------------------------------------------- 1 | use core::cell::UnsafeCell; 2 | use core::ops::{Deref, DerefMut}; 3 | use core::ptr; 4 | use core::time::Duration; 5 | 6 | use esp_idf_sys::*; 7 | 8 | // NOTE: ESP-IDF-specific 9 | const PTHREAD_MUTEX_INITIALIZER: u32 = 0xFFFFFFFF; 10 | 11 | pub struct Mutex(UnsafeCell, UnsafeCell); 12 | 13 | impl Mutex { 14 | #[inline(always)] 15 | pub const fn new(data: T) -> Self { 16 | Self( 17 | UnsafeCell::new(PTHREAD_MUTEX_INITIALIZER as _), 18 | UnsafeCell::new(data), 19 | ) 20 | } 21 | 22 | #[inline(always)] 23 | pub fn lock(&self) -> MutexGuard<'_, T> { 24 | MutexGuard::new(self) 25 | } 26 | } 27 | 28 | impl Drop for Mutex { 29 | fn drop(&mut self) { 30 | let r = unsafe { pthread_mutex_destroy(self.0.get_mut() as *mut _) }; 31 | debug_assert_eq!(r, 0); 32 | } 33 | } 34 | 35 | unsafe impl Sync for Mutex where T: Send {} 36 | unsafe impl Send for Mutex where T: Send {} 37 | 38 | pub struct MutexGuard<'a, T>(&'a Mutex); 39 | 40 | impl<'a, T> MutexGuard<'a, T> { 41 | #[inline(always)] 42 | fn new(mutex: &'a Mutex) -> Self { 43 | let r = unsafe { pthread_mutex_lock(mutex.0.get()) }; 44 | debug_assert_eq!(r, 0); 45 | 46 | Self(mutex) 47 | } 48 | } 49 | 50 | unsafe impl Sync for MutexGuard<'_, T> where T: Sync {} 51 | 52 | impl<'a, T> Drop for MutexGuard<'a, T> { 53 | #[inline(always)] 54 | fn drop(&mut self) { 55 | let r = unsafe { pthread_mutex_unlock(self.0 .0.get()) }; 56 | debug_assert_eq!(r, 0); 57 | } 58 | } 59 | 60 | impl<'a, T> Deref for MutexGuard<'a, T> { 61 | type Target = T; 62 | 63 | #[inline(always)] 64 | fn deref(&self) -> &Self::Target { 65 | unsafe { self.0 .1.get().as_mut().unwrap() } 66 | } 67 | } 68 | 69 | impl<'a, T> DerefMut for MutexGuard<'a, T> { 70 | #[inline(always)] 71 | fn deref_mut(&mut self) -> &mut Self::Target { 72 | unsafe { self.0 .1.get().as_mut().unwrap() } 73 | } 74 | } 75 | 76 | #[cfg(feature = "mutex-trait")] 77 | impl mutex_trait::Mutex for Mutex { 78 | type Data = T; 79 | 80 | #[inline(always)] 81 | fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { 82 | let mut guard = Mutex::lock(self); 83 | 84 | f(&mut *guard) 85 | } 86 | } 87 | 88 | #[cfg(feature = "embedded-svc")] 89 | impl embedded_svc::mutex::Mutex for Mutex { 90 | type Data = T; 91 | 92 | type Guard<'a> 93 | where 94 | T: 'a, 95 | = MutexGuard<'a, T>; 96 | 97 | #[inline(always)] 98 | fn new(data: Self::Data) -> Self { 99 | Mutex::new(data) 100 | } 101 | 102 | #[inline(always)] 103 | fn lock(&self) -> Self::Guard<'_> { 104 | Mutex::lock(self) 105 | } 106 | } 107 | 108 | pub struct Condvar(UnsafeCell); 109 | 110 | impl Condvar { 111 | pub fn new() -> Self { 112 | let mut cond: pthread_cond_t = Default::default(); 113 | 114 | let r = unsafe { pthread_cond_init(&mut cond as *mut _, ptr::null()) }; 115 | debug_assert_eq!(r, 0); 116 | 117 | Self(UnsafeCell::new(cond)) 118 | } 119 | 120 | pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { 121 | let r = unsafe { pthread_cond_wait(self.0.get(), guard.0 .0.get()) }; 122 | debug_assert_eq!(r, 0); 123 | 124 | guard 125 | } 126 | 127 | pub fn wait_timeout<'a, T>( 128 | &self, 129 | guard: MutexGuard<'a, T>, 130 | duration: Duration, 131 | ) -> (MutexGuard<'a, T>, bool) { 132 | let abstime = timespec { 133 | tv_sec: duration.as_secs() as _, 134 | tv_nsec: duration.subsec_nanos() as _, 135 | }; 136 | 137 | let r = 138 | unsafe { pthread_cond_timedwait(self.0.get(), guard.0 .0.get(), &abstime as *const _) }; 139 | debug_assert!(r == ETIMEDOUT as i32 || r == 0); 140 | 141 | (guard, r == ETIMEDOUT as i32) 142 | } 143 | 144 | pub fn notify_one(&self) { 145 | let r = unsafe { pthread_cond_signal(self.0.get()) }; 146 | debug_assert_eq!(r, 0); 147 | } 148 | 149 | pub fn notify_all(&self) { 150 | let r = unsafe { pthread_cond_broadcast(self.0.get()) }; 151 | debug_assert_eq!(r, 0); 152 | } 153 | } 154 | 155 | unsafe impl Sync for Condvar {} 156 | unsafe impl Send for Condvar {} 157 | 158 | impl Default for Condvar { 159 | fn default() -> Self { 160 | Self::new() 161 | } 162 | } 163 | 164 | impl Drop for Condvar { 165 | fn drop(&mut self) { 166 | let r = unsafe { pthread_cond_destroy(self.0.get()) }; 167 | debug_assert_eq!(r, 0); 168 | } 169 | } 170 | 171 | #[cfg(feature = "embedded-svc")] 172 | impl embedded_svc::mutex::MutexFamily for Condvar { 173 | type Mutex = Mutex; 174 | } 175 | 176 | #[cfg(feature = "embedded-svc")] 177 | impl embedded_svc::mutex::Condvar for Condvar { 178 | #[inline(always)] 179 | fn new() -> Self { 180 | Condvar::new() 181 | } 182 | 183 | fn wait<'a, T>( 184 | &self, 185 | guard: <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, 186 | ) -> <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a> 187 | { 188 | Condvar::wait(self, guard) 189 | } 190 | 191 | fn wait_timeout<'a, T>( 192 | &self, 193 | guard: <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, 194 | duration: Duration, 195 | ) -> ( 196 | <::Mutex as embedded_svc::mutex::Mutex>::Guard< 197 | 'a, 198 | >, 199 | bool, 200 | ) { 201 | Condvar::wait_timeout(self, guard, duration) 202 | } 203 | 204 | fn notify_one(&self) { 205 | Condvar::notify_one(self) 206 | } 207 | 208 | fn notify_all(&self) { 209 | Condvar::notify_all(self) 210 | } 211 | } 212 | 213 | #[cfg(feature = "embassy")] 214 | pub mod embassy { 215 | pub enum EspMutexKind {} 216 | impl embassy::blocking_mutex::kind::MutexKind for EspMutexKind { 217 | type Mutex = super::Mutex; 218 | } 219 | 220 | impl<'a, T> embassy::blocking_mutex::Mutex for super::Mutex { 221 | type Data = T; 222 | 223 | fn new(data: Self::Data) -> Self { 224 | super::Mutex::new(data) 225 | } 226 | 227 | #[inline(always)] 228 | fn lock(&self, f: impl FnOnce(&Self::Data) -> R) -> R { 229 | let mut guard = super::Mutex::lock(self); 230 | 231 | f(&mut guard) 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/riscv_ulp_hal/sys/gpio.rs: -------------------------------------------------------------------------------- 1 | /// A mini "esp-idf-ulp-sys" module exposing stuff on top of which the ULP HAL support is implemented 2 | /// (currently, only GPIO) 3 | /// Implemented as a manual transation of a few C fields from current ESP-IDF S2 master: 4 | /// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_gpio.h 5 | use crate::riscv_ulp_hal::pac::*; 6 | use crate::riscv_ulp_hal::reg::*; 7 | 8 | #[allow(non_camel_case_types)] 9 | pub type adc_unit_t = i32; 10 | #[allow(non_camel_case_types)] 11 | pub type adc_channel_t = i32; 12 | #[allow(non_camel_case_types)] 13 | pub type dac_channel_t = i32; 14 | #[allow(non_camel_case_types)] 15 | pub type touch_pad_t = i32; 16 | #[allow(non_camel_case_types)] 17 | pub type adc_atten_t = i32; 18 | 19 | #[allow(non_upper_case_globals)] 20 | pub const adc_unit_t_ADC_UNIT_1: adc_unit_t = 0; 21 | #[allow(non_upper_case_globals)] 22 | pub const adc_unit_t_ADC_UNIT_2: adc_unit_t = 1; 23 | 24 | #[allow(non_upper_case_globals)] 25 | pub const adc_atten_t_ADC_ATTEN_DB_0: adc_atten_t = 0; 26 | #[allow(non_upper_case_globals)] 27 | pub const adc_atten_t_ADC_ATTEN_DB_2_5: adc_atten_t = 1; 28 | #[allow(non_upper_case_globals)] 29 | pub const adc_atten_t_ADC_ATTEN_DB_6: adc_atten_t = 2; 30 | #[allow(non_upper_case_globals)] 31 | pub const adc_atten_t_ADC_ATTEN_DB_11: adc_atten_t = 3; 32 | 33 | #[allow(non_upper_case_globals)] 34 | pub const gpio_mode_t_GPIO_MODE_DISABLE: u8 = 0; 35 | #[allow(non_upper_case_globals)] 36 | pub const gpio_mode_t_GPIO_MODE_INPUT: u8 = 1; 37 | #[allow(non_upper_case_globals)] 38 | pub const gpio_mode_t_GPIO_MODE_OUTPUT: u8 = 2; 39 | #[allow(non_upper_case_globals)] 40 | pub const gpio_mode_t_GPIO_MODE_INPUT_OUTPUT: u8 = 3; 41 | #[allow(non_upper_case_globals)] 42 | pub const gpio_mode_t_GPIO_MODE_OUTPUT_OD: u8 = 4; 43 | #[allow(non_upper_case_globals)] 44 | pub const gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD: u8 = 5; 45 | 46 | #[allow(non_upper_case_globals)] 47 | pub const gpio_pull_mode_t_GPIO_PULLUP_ONLY: u8 = 0; 48 | #[allow(non_upper_case_globals)] 49 | pub const gpio_pull_mode_t_GPIO_PULLDOWN_ONLY: u8 = 1; 50 | #[allow(non_upper_case_globals)] 51 | pub const gpio_pull_mode_t_GPIO_PULLUP_PULLDOWN: u8 = 2; 52 | #[allow(non_upper_case_globals)] 53 | pub const gpio_pull_mode_t_GPIO_FLOATING: u8 = 3; 54 | 55 | #[inline(always)] 56 | pub unsafe fn gpio_set_direction(gpio_num: i32, direction: u8) { 57 | if direction == gpio_mode_t_GPIO_MODE_DISABLE { 58 | // Deinit 59 | clear_peri_reg_mask( 60 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 61 | RTC_IO_TOUCH_PAD0_MUX_SEL, 62 | ); 63 | return; 64 | } else { 65 | // Init 66 | set_peri_reg_mask(SENS_SAR_IO_MUX_CONF_REG, SENS_IOMUX_CLK_GATE_EN_M); 67 | set_peri_reg_mask( 68 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 69 | RTC_IO_TOUCH_PAD0_MUX_SEL, 70 | ); 71 | reg_set_field( 72 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 73 | RTC_IO_TOUCH_PAD0_FUN_SEL_S, 74 | RTC_IO_TOUCH_PAD0_FUN_SEL_V, 75 | 0, 76 | ); 77 | } 78 | 79 | let input = direction == gpio_mode_t_GPIO_MODE_INPUT 80 | || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT 81 | || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD; 82 | let output = direction == gpio_mode_t_GPIO_MODE_OUTPUT 83 | || direction == gpio_mode_t_GPIO_MODE_OUTPUT_OD 84 | || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT 85 | || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD; 86 | let od = direction == gpio_mode_t_GPIO_MODE_OUTPUT_OD 87 | || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD; 88 | 89 | if input { 90 | set_peri_reg_mask( 91 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 92 | RTC_IO_TOUCH_PAD0_FUN_IE, 93 | ); 94 | } else { 95 | clear_peri_reg_mask( 96 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 97 | RTC_IO_TOUCH_PAD0_FUN_IE, 98 | ); 99 | } 100 | 101 | if output { 102 | reg_set_field( 103 | RTC_GPIO_ENABLE_W1TS_REG, 104 | RTC_GPIO_ENABLE_W1TS_S, 105 | RTC_GPIO_ENABLE_W1TS_V, 106 | bit(gpio_num as u32), 107 | ); 108 | reg_set_field( 109 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 110 | RTC_IO_TOUCH_PAD0_DRV_S, 111 | RTC_IO_TOUCH_PAD0_DRV_V, 112 | if od { 1 } else { 0 }, 113 | ); 114 | } else { 115 | reg_set_field( 116 | RTC_GPIO_ENABLE_W1TC_REG, 117 | RTC_GPIO_ENABLE_W1TC_S, 118 | RTC_GPIO_ENABLE_W1TC_V, 119 | bit(gpio_num as u32), 120 | ); 121 | } 122 | } 123 | 124 | #[inline(always)] 125 | pub unsafe fn gpio_set_pull_mode(gpio_num: i32, mode: u8) { 126 | let pullup = 127 | mode == gpio_pull_mode_t_GPIO_PULLUP_ONLY || mode == gpio_pull_mode_t_GPIO_PULLUP_PULLDOWN; 128 | let pulldown = mode == gpio_pull_mode_t_GPIO_PULLDOWN_ONLY 129 | || mode == gpio_pull_mode_t_GPIO_PULLUP_PULLDOWN; 130 | 131 | if pullup { 132 | set_peri_reg_mask( 133 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 134 | RTC_IO_TOUCH_PAD0_RUE, 135 | ); 136 | } else { 137 | clear_peri_reg_mask( 138 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 139 | RTC_IO_TOUCH_PAD0_RUE, 140 | ); 141 | } 142 | 143 | if pulldown { 144 | set_peri_reg_mask( 145 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 146 | RTC_IO_TOUCH_PAD0_RDE, 147 | ); 148 | } else { 149 | clear_peri_reg_mask( 150 | RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, 151 | RTC_IO_TOUCH_PAD0_RDE, 152 | ); 153 | } 154 | } 155 | 156 | #[inline(always)] 157 | pub unsafe fn gpio_get_level(gpio_num: i32) -> u8 { 158 | if (reg_get_field(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S, RTC_GPIO_IN_NEXT_V) 159 | & bit(gpio_num as u32)) 160 | != 0 161 | { 162 | 1 163 | } else { 164 | 0 165 | } 166 | } 167 | 168 | #[inline(always)] 169 | pub unsafe fn gpio_set_level(gpio_num: i32, level: u8) { 170 | if level != 0 { 171 | reg_set_field( 172 | RTC_GPIO_OUT_W1TS_REG, 173 | RTC_GPIO_OUT_DATA_W1TS_S, 174 | RTC_GPIO_OUT_DATA_W1TS_V, 175 | bit(gpio_num as u32), 176 | ); 177 | } else { 178 | reg_set_field( 179 | RTC_GPIO_OUT_W1TC_REG, 180 | RTC_GPIO_OUT_DATA_W1TC_S, 181 | RTC_GPIO_OUT_DATA_W1TC_V, 182 | bit(gpio_num as u32), 183 | ); 184 | } 185 | } 186 | 187 | #[inline(always)] 188 | pub unsafe fn gpio_get_output_level(gpio_num: i32) -> u8 { 189 | if (reg_get_field( 190 | RTC_GPIO_OUT_W1TS_REG, 191 | RTC_GPIO_OUT_DATA_W1TS_S, 192 | RTC_GPIO_OUT_DATA_W1TS_V, 193 | ) & bit(gpio_num as u32)) 194 | != 0 195 | { 196 | 1 197 | } else { 198 | 0 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/ulp.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 4 | pub enum SleepTimer { 5 | First = 0, 6 | Second = 1, 7 | Third = 2, 8 | Fourth = 3, 9 | Fifth = 4, 10 | #[cfg(esp32s2)] 11 | Sixth = 5, 12 | } 13 | 14 | impl Default for SleepTimer { 15 | fn default() -> Self { 16 | Self::First 17 | } 18 | } 19 | 20 | #[cfg(any( 21 | all(esp32, esp_idf_esp32_ulp_coproc_enabled), 22 | all( 23 | esp32s2, 24 | esp_idf_esp32s2_ulp_coproc_enabled, 25 | not(esp_idf_esp32s2_ulp_coproc_riscv) 26 | ), 27 | all( 28 | esp32s3, 29 | esp_idf_esp32s3_ulp_coproc_enabled, 30 | not(esp_idf_esp32s3_ulp_coproc_riscv) 31 | ) 32 | ))] 33 | #[derive(Copy, Clone, Debug)] 34 | pub struct Word { 35 | pc: u16, 36 | value: u16, 37 | } 38 | 39 | #[cfg(any( 40 | all(esp32, esp_idf_esp32_ulp_coproc_enabled), 41 | all( 42 | esp32s2, 43 | esp_idf_esp32s2_ulp_coproc_enabled, 44 | not(esp_idf_esp32s2_ulp_coproc_riscv) 45 | ), 46 | all( 47 | esp32s3, 48 | esp_idf_esp32s3_ulp_coproc_enabled, 49 | not(esp_idf_esp32s3_ulp_coproc_riscv) 50 | ) 51 | ))] 52 | impl Word { 53 | pub fn pc(&self) -> u16 { 54 | self.pc 55 | } 56 | 57 | pub fn value(&self) -> u16 { 58 | self.value 59 | } 60 | 61 | pub fn is_modified(&self) -> bool { 62 | self.pc != 0 63 | } 64 | } 65 | 66 | pub struct ULP(PhantomData<*const ()>); 67 | 68 | unsafe impl Send for ULP {} 69 | 70 | impl ULP { 71 | /// # Safety 72 | /// 73 | /// Care should be taken not to instantiate the ULP instance, if it is already instantiated and used elsewhere 74 | pub unsafe fn new() -> Self { 75 | Self(PhantomData) 76 | } 77 | } 78 | 79 | #[cfg(any( 80 | all(esp32, esp_idf_esp32_ulp_coproc_enabled), 81 | all(esp32s2, esp_idf_esp32s2_ulp_coproc_enabled), 82 | all(esp32s3, esp_idf_esp32s3_ulp_coproc_enabled) 83 | ))] 84 | impl ULP { 85 | const RTC_SLOW_MEM: u32 = 0x5000_0000_u32; 86 | 87 | pub const MEM_START_ULP: *mut core::ffi::c_void = 0_u32 as _; 88 | 89 | pub const MEM_START: *mut core::ffi::c_void = Self::RTC_SLOW_MEM as _; 90 | 91 | #[cfg(esp32)] 92 | pub const MEM_SIZE: usize = esp_idf_sys::CONFIG_ESP32_ULP_COPROC_RESERVE_MEM as _; 93 | 94 | #[cfg(esp32s2)] 95 | pub const MEM_SIZE: usize = esp_idf_sys::CONFIG_ESP32S2_ULP_COPROC_RESERVE_MEM as _; 96 | 97 | #[cfg(esp32s3)] 98 | pub const MEM_SIZE: usize = esp_idf_sys::CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM as _; 99 | 100 | #[cfg(esp32)] 101 | const TIMER_REG: *mut u32 = esp_idf_sys::RTC_CNTL_STATE0_REG as _; 102 | 103 | #[cfg(any(esp32s2, esp32s3))] 104 | const TIMER_REG: *mut u32 = esp_idf_sys::RTC_CNTL_ULP_CP_TIMER_REG as _; 105 | 106 | #[cfg(any(esp32, esp32s2, esp32s3))] 107 | const TIMER_EN_BIT: u32 = 108 | esp_idf_sys::RTC_CNTL_ULP_CP_SLP_TIMER_EN_V << esp_idf_sys::RTC_CNTL_ULP_CP_SLP_TIMER_EN_S; 109 | 110 | pub fn stop(&mut self) -> Result<(), esp_idf_sys::EspError> { 111 | unsafe { 112 | // disable ULP timer 113 | core::ptr::write_volatile( 114 | Self::TIMER_REG, 115 | core::ptr::read_volatile(Self::TIMER_REG) & !Self::TIMER_EN_BIT, 116 | ); 117 | 118 | // wait for at least 1 RTC_SLOW_CLK cycle 119 | esp_idf_sys::esp_rom_delay_us(10); 120 | } 121 | 122 | Ok(()) 123 | } 124 | 125 | pub fn is_started(&self) -> Result { 126 | unsafe { 127 | let enabled = (core::ptr::read_volatile(Self::TIMER_REG) & Self::TIMER_EN_BIT) != 0; 128 | 129 | Ok(enabled) 130 | } 131 | } 132 | 133 | pub fn set_sleep_period_default( 134 | &mut self, 135 | duration: core::time::Duration, 136 | ) -> Result<(), esp_idf_sys::EspError> { 137 | self.set_sleep_period(Default::default(), duration) 138 | } 139 | 140 | pub fn set_sleep_period( 141 | &mut self, 142 | timer: SleepTimer, 143 | duration: core::time::Duration, 144 | ) -> Result<(), esp_idf_sys::EspError> { 145 | esp_idf_sys::esp!(unsafe { 146 | esp_idf_sys::ulp_set_wakeup_period( 147 | timer as esp_idf_sys::size_t, 148 | duration.as_micros() as u32, 149 | ) 150 | })?; 151 | 152 | Ok(()) 153 | } 154 | 155 | fn check_boundaries(ptr: *const T) -> Result<(), esp_idf_sys::EspError> { 156 | let ptr = ptr as *const u8; 157 | let mem_start = Self::MEM_START as *const u8; 158 | 159 | unsafe { 160 | if ptr < mem_start 161 | || ptr.offset(core::mem::size_of::() as _) 162 | > mem_start.offset(Self::MEM_SIZE as _) 163 | { 164 | esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_SIZE)?; 165 | } 166 | } 167 | 168 | Ok(()) 169 | } 170 | } 171 | 172 | #[cfg(any( 173 | all(esp32, esp_idf_esp32_ulp_coproc_enabled), 174 | all( 175 | esp32s2, 176 | esp_idf_esp32s2_ulp_coproc_enabled, 177 | not(esp_idf_esp32s2_ulp_coproc_riscv) 178 | ), 179 | all( 180 | esp32s3, 181 | esp_idf_esp32s3_ulp_coproc_enabled, 182 | not(esp_idf_esp32s3_ulp_coproc_riscv) 183 | ) 184 | ))] 185 | impl ULP { 186 | pub unsafe fn load_at_ulp_address( 187 | &mut self, 188 | address: *mut core::ffi::c_void, 189 | program: &[u8], 190 | ) -> Result<(), esp_idf_sys::EspError> { 191 | let address: usize = core::mem::transmute(address); 192 | if address % core::mem::size_of::() != 0 { 193 | esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_ARG)?; 194 | } 195 | 196 | if program.len() % core::mem::size_of::() != 0 { 197 | esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_SIZE)?; 198 | } 199 | 200 | esp_idf_sys::esp!(esp_idf_sys::ulp_load_binary( 201 | (address / core::mem::size_of::()) as u32, 202 | program.as_ptr(), 203 | (program.len() / core::mem::size_of::()) as u32 204 | ))?; 205 | 206 | Ok(()) 207 | } 208 | 209 | pub unsafe fn load(&mut self, program: &[u8]) -> Result<(), esp_idf_sys::EspError> { 210 | self.load_at_ulp_address(Self::MEM_START_ULP, program) 211 | } 212 | 213 | pub unsafe fn start(&mut self, address: *const u32) -> Result<(), esp_idf_sys::EspError> { 214 | esp_idf_sys::esp!(esp_idf_sys::ulp_run(address as u32))?; 215 | 216 | Ok(()) 217 | } 218 | 219 | pub unsafe fn read_word(&self, ptr: *const u32) -> Result { 220 | Self::check_boundaries(ptr)?; 221 | Self::check_alignment(ptr)?; 222 | 223 | let value = core::ptr::read_volatile(ptr); 224 | 225 | Ok(Word { 226 | pc: ((value >> 16) & 0xffff_u32) as u16, 227 | value: (value & 0xffff_u32) as u16, 228 | }) 229 | } 230 | 231 | pub unsafe fn write_word( 232 | &self, 233 | ptr: *mut u32, 234 | value: u16, 235 | ) -> Result<(), esp_idf_sys::EspError> { 236 | Self::check_boundaries(ptr)?; 237 | Self::check_alignment(ptr)?; 238 | 239 | core::ptr::write_volatile(ptr as *mut u32, value as u32); 240 | 241 | Ok(()) 242 | } 243 | 244 | pub unsafe fn swap_word( 245 | &mut self, 246 | ptr: *mut u32, 247 | value: u16, 248 | ) -> Result { 249 | let old_value = self.read_word(ptr)?; 250 | 251 | self.write_word(ptr, value)?; 252 | 253 | Ok(old_value) 254 | } 255 | 256 | fn check_alignment(ptr: *const T) -> Result<(), esp_idf_sys::EspError> { 257 | let ptr_usize: usize = unsafe { core::mem::transmute(ptr) }; 258 | 259 | if ptr_usize % core::mem::size_of::() != 0 { 260 | esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_SIZE)?; 261 | } 262 | 263 | Ok(()) 264 | } 265 | } 266 | 267 | #[cfg(any( 268 | all( 269 | esp32s2, 270 | esp_idf_esp32s2_ulp_coproc_enabled, 271 | esp_idf_esp32s2_ulp_coproc_riscv 272 | ), 273 | all( 274 | esp32s3, 275 | esp_idf_esp32s3_ulp_coproc_enabled, 276 | esp_idf_esp32s3_ulp_coproc_riscv 277 | ) 278 | ))] 279 | impl ULP { 280 | pub unsafe fn load(&mut self, program: &[u8]) -> Result<(), esp_idf_sys::EspError> { 281 | esp_idf_sys::esp!(esp_idf_sys::ulp_riscv_load_binary( 282 | program.as_ptr(), 283 | program.len() as _ 284 | ))?; 285 | 286 | Ok(()) 287 | } 288 | 289 | pub unsafe fn start(&mut self) -> Result<(), esp_idf_sys::EspError> { 290 | esp_idf_sys::esp!(esp_idf_sys::ulp_riscv_run())?; 291 | 292 | Ok(()) 293 | } 294 | 295 | pub unsafe fn read_var(&self, src: *const T) -> Result { 296 | Self::check_boundaries(src)?; 297 | 298 | Ok(core::ptr::read_volatile(src)) 299 | } 300 | 301 | pub unsafe fn write_var(&self, dst: *mut T, value: T) -> Result<(), esp_idf_sys::EspError> { 302 | Self::check_boundaries(dst)?; 303 | 304 | core::ptr::write_volatile(dst, value); 305 | 306 | Ok(()) 307 | } 308 | 309 | pub unsafe fn swap_var( 310 | &mut self, 311 | ptr: *mut T, 312 | value: T, 313 | ) -> Result { 314 | let old_value = self.read_var(ptr)?; 315 | 316 | self.write_var(ptr, value)?; 317 | 318 | Ok(old_value) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/adc.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | #[cfg(not(feature = "riscv-ulp-hal"))] 4 | use esp_idf_sys::*; 5 | 6 | #[cfg(feature = "riscv-ulp-hal")] 7 | use crate::riscv_ulp_hal::sys::*; 8 | 9 | #[cfg(all(esp32, not(feature = "riscv-ulp-hal")))] 10 | use crate::hall; 11 | 12 | pub trait Adc: Send { 13 | fn unit() -> adc_unit_t; 14 | } 15 | 16 | pub trait Analog: Send { 17 | fn attenuation() -> adc_atten_t; 18 | } 19 | 20 | pub struct Atten0dB { 21 | _adc: PhantomData, 22 | } 23 | 24 | pub struct Atten2p5dB { 25 | _adc: PhantomData, 26 | } 27 | 28 | pub struct Atten6dB { 29 | _adc: PhantomData, 30 | } 31 | 32 | pub struct Atten11dB { 33 | _adc: PhantomData, 34 | } 35 | 36 | impl Analog for Atten0dB { 37 | fn attenuation() -> adc_atten_t { 38 | adc_atten_t_ADC_ATTEN_DB_0 39 | } 40 | } 41 | 42 | impl Analog for Atten2p5dB { 43 | fn attenuation() -> adc_atten_t { 44 | adc_atten_t_ADC_ATTEN_DB_2_5 45 | } 46 | } 47 | 48 | impl Analog for Atten6dB { 49 | fn attenuation() -> adc_atten_t { 50 | adc_atten_t_ADC_ATTEN_DB_6 51 | } 52 | } 53 | 54 | impl Analog for Atten11dB { 55 | fn attenuation() -> adc_atten_t { 56 | adc_atten_t_ADC_ATTEN_DB_11 57 | } 58 | } 59 | 60 | /// ADC configuration 61 | #[cfg(not(feature = "riscv-ulp-hal"))] 62 | pub mod config { 63 | use esp_idf_sys::*; 64 | 65 | /// The sampling/readout resolution of the ADC 66 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 67 | pub enum Resolution { 68 | #[cfg(esp32)] 69 | Resolution9Bit, 70 | #[cfg(esp32)] 71 | Resolution10Bit, 72 | #[cfg(esp32)] 73 | Resolution11Bit, 74 | #[cfg(any(esp32, esp32c3, esp32s3))] 75 | Resolution12Bit, 76 | #[cfg(esp32s2)] 77 | Resolution13Bit, 78 | } 79 | 80 | impl Default for Resolution { 81 | #[cfg(any(esp32, esp32c3, esp32s3))] 82 | fn default() -> Self { 83 | Self::Resolution12Bit 84 | } 85 | 86 | #[cfg(esp32s2)] 87 | fn default() -> Self { 88 | Self::Resolution13Bit 89 | } 90 | } 91 | 92 | impl From for adc_bits_width_t { 93 | fn from(resolution: Resolution) -> Self { 94 | match resolution { 95 | #[cfg(esp32)] 96 | Resolution::Resolution9Bit => adc_bits_width_t_ADC_WIDTH_BIT_9, 97 | #[cfg(esp32)] 98 | Resolution::Resolution10Bit => adc_bits_width_t_ADC_WIDTH_BIT_10, 99 | #[cfg(esp32)] 100 | Resolution::Resolution11Bit => adc_bits_width_t_ADC_WIDTH_BIT_11, 101 | #[cfg(any(esp32, esp32s3, esp32c3))] 102 | Resolution::Resolution12Bit => adc_bits_width_t_ADC_WIDTH_BIT_12, 103 | #[cfg(esp32s2)] 104 | Resolution::Resolution13Bit => adc_bits_width_t_ADC_WIDTH_BIT_13, 105 | } 106 | } 107 | } 108 | 109 | #[derive(Debug, Copy, Clone, Default)] 110 | pub struct Config { 111 | pub resolution: Resolution, 112 | pub calibration: bool, 113 | } 114 | 115 | impl Config { 116 | pub fn new() -> Self { 117 | Default::default() 118 | } 119 | 120 | #[must_use] 121 | pub fn resolution(mut self, resolution: Resolution) -> Self { 122 | self.resolution = resolution; 123 | self 124 | } 125 | 126 | #[must_use] 127 | pub fn calibration(mut self, calibration: bool) -> Self { 128 | self.calibration = calibration; 129 | self 130 | } 131 | } 132 | } 133 | 134 | #[cfg(not(feature = "riscv-ulp-hal"))] 135 | pub struct PoweredAdc { 136 | adc: ADC, 137 | resolution: config::Resolution, 138 | cal_characteristics: 139 | Option<[Option; adc_atten_t_ADC_ATTEN_MAX as usize + 1]>, 140 | } 141 | 142 | #[cfg(not(feature = "riscv-ulp-hal"))] 143 | unsafe impl Send for PoweredAdc {} 144 | 145 | #[cfg(not(feature = "riscv-ulp-hal"))] 146 | impl PoweredAdc { 147 | #[cfg(esp32)] 148 | const CALIBRATION_SCHEME: esp_adc_cal_value_t = esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_VREF; 149 | 150 | #[cfg(any(esp32c3, esp32s2))] 151 | const CALIBRATION_SCHEME: esp_adc_cal_value_t = esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_TP; 152 | 153 | #[cfg(esp32s3)] 154 | const CALIBRATION_SCHEME: esp_adc_cal_value_t = 155 | esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_TP_FIT; 156 | 157 | #[cfg(not(esp32s2))] 158 | const MAX_READING: u32 = 4095; 159 | 160 | #[cfg(esp32s2)] 161 | const MAX_READING: u32 = 8191; 162 | 163 | pub fn new(adc: ADC, config: config::Config) -> Result { 164 | if config.calibration { 165 | esp!(unsafe { esp_adc_cal_check_efuse(Self::CALIBRATION_SCHEME) })?; 166 | } 167 | 168 | if ADC::unit() == adc_unit_t_ADC_UNIT_1 { 169 | esp!(unsafe { adc1_config_width(config.resolution.into()) })?; 170 | } 171 | 172 | Ok(Self { 173 | adc, 174 | resolution: config.resolution, 175 | cal_characteristics: if config.calibration { 176 | Some(Default::default()) 177 | } else { 178 | None 179 | }, 180 | }) 181 | } 182 | 183 | pub fn release(self) -> ADC { 184 | self.adc 185 | } 186 | 187 | fn raw_to_voltage( 188 | &mut self, 189 | measurement: c_types::c_int, 190 | attenuation: adc_atten_t, 191 | ) -> Result { 192 | let mv = if let Some(cal) = self.get_cal_characteristics(attenuation)? { 193 | unsafe { esp_adc_cal_raw_to_voltage(measurement as u32, &cal as *const _) as u16 } 194 | } else { 195 | (measurement as u32 * Self::get_max_mv(attenuation) / Self::MAX_READING) as u16 196 | }; 197 | 198 | Ok(mv) 199 | } 200 | 201 | #[allow(non_upper_case_globals)] 202 | fn get_max_mv(attenuation: adc_atten_t) -> u32 { 203 | #[cfg(esp32)] 204 | let mv = match attenuation { 205 | adc_atten_t_ADC_ATTEN_DB_0 => 950, 206 | adc_atten_t_ADC_ATTEN_DB_2_5 => 1250, 207 | adc_atten_t_ADC_ATTEN_DB_6 => 1750, 208 | adc_atten_t_ADC_ATTEN_DB_11 => 2450, 209 | other => panic!("Unknown attenuation: {}", other), 210 | }; 211 | 212 | #[cfg(any(esp32c3, esp32s2))] 213 | let mv = match attenuation { 214 | adc_atten_t_ADC_ATTEN_DB_0 => 750, 215 | adc_atten_t_ADC_ATTEN_DB_2_5 => 1050, 216 | adc_atten_t_ADC_ATTEN_DB_6 => 1300, 217 | adc_atten_t_ADC_ATTEN_DB_11 => 2500, 218 | other => panic!("Unknown attenuation: {}", other), 219 | }; 220 | 221 | #[cfg(esp32s3)] 222 | let mv = match attenuation { 223 | adc_atten_t_ADC_ATTEN_DB_0 => 950, 224 | adc_atten_t_ADC_ATTEN_DB_2_5 => 1250, 225 | adc_atten_t_ADC_ATTEN_DB_6 => 1750, 226 | adc_atten_t_ADC_ATTEN_DB_11 => 3100, 227 | other => panic!("Unknown attenuation: {}", other), 228 | }; 229 | 230 | mv 231 | } 232 | 233 | fn get_cal_characteristics( 234 | &mut self, 235 | attenuation: adc_atten_t, 236 | ) -> Result, EspError> { 237 | if let Some(characteristics) = &mut self.cal_characteristics { 238 | if let Some(cal) = characteristics[attenuation as usize] { 239 | Ok(Some(cal)) 240 | } else { 241 | esp!(unsafe { esp_adc_cal_check_efuse(Self::CALIBRATION_SCHEME) })?; 242 | 243 | let mut cal: esp_adc_cal_characteristics_t = Default::default(); 244 | unsafe { 245 | esp_adc_cal_characterize( 246 | ADC::unit(), 247 | attenuation, 248 | self.resolution.into(), 249 | 0, 250 | &mut cal as *mut _, 251 | ) 252 | }; 253 | 254 | characteristics[attenuation as usize] = Some(cal); 255 | 256 | Ok(Some(cal)) 257 | } 258 | } else { 259 | Ok(None) 260 | } 261 | } 262 | 263 | fn read( 264 | &mut self, 265 | unit: adc_unit_t, 266 | channel: adc_channel_t, 267 | atten: adc_atten_t, 268 | ) -> nb::Result { 269 | let mut measurement = 0_i32; 270 | 271 | if unit == adc_unit_t_ADC_UNIT_1 { 272 | measurement = unsafe { adc1_get_raw(channel) }; 273 | } else { 274 | let res = unsafe { 275 | adc2_get_raw(channel, self.resolution.into(), &mut measurement as *mut _) 276 | }; 277 | 278 | if res == ESP_ERR_INVALID_STATE as i32 { 279 | return Err(nb::Error::WouldBlock); 280 | } else if res < 0 { 281 | return Err(nb::Error::Other(EspError::from(res).unwrap())); 282 | } 283 | }; 284 | 285 | Ok(self.raw_to_voltage(measurement, atten)?) 286 | } 287 | 288 | #[cfg(esp32)] 289 | fn read_hall(&mut self) -> nb::Result { 290 | let measurement = unsafe { hall_sensor_read() }; 291 | 292 | Ok(self.raw_to_voltage(measurement, adc_atten_t_ADC_ATTEN_DB_0)?) 293 | } 294 | } 295 | 296 | #[cfg(not(feature = "riscv-ulp-hal"))] 297 | impl embedded_hal_0_2::adc::OneShot for PoweredAdc 298 | where 299 | ADC: Adc, 300 | AN: Analog, 301 | PIN: embedded_hal_0_2::adc::Channel, 302 | { 303 | type Error = EspError; 304 | 305 | fn read(&mut self, _pin: &mut PIN) -> nb::Result { 306 | self.read( 307 | ADC::unit(), 308 | PIN::channel() as adc_channel_t, 309 | AN::attenuation(), 310 | ) 311 | } 312 | } 313 | 314 | #[cfg(all(esp32, not(feature = "riscv-ulp-hal")))] 315 | impl embedded_hal_0_2::adc::OneShot for PoweredAdc { 316 | type Error = EspError; 317 | 318 | fn read(&mut self, _hall_sensor: &mut hall::HallSensor) -> nb::Result { 319 | self.read_hall() 320 | } 321 | } 322 | 323 | macro_rules! impl_adc { 324 | ($adc:ident: $unit:expr) => { 325 | pub struct $adc(::core::marker::PhantomData<*const ()>); 326 | 327 | impl $adc { 328 | /// # Safety 329 | /// 330 | /// Care should be taken not to instnatiate this ADC instance, if it is already instantiated and used elsewhere 331 | pub unsafe fn new() -> Self { 332 | $adc(::core::marker::PhantomData) 333 | } 334 | } 335 | 336 | unsafe impl Send for $adc {} 337 | 338 | impl Adc for $adc { 339 | #[inline(always)] 340 | fn unit() -> adc_unit_t { 341 | $unit 342 | } 343 | } 344 | }; 345 | } 346 | 347 | impl_adc!(ADC1: adc_unit_t_ADC_UNIT_1); 348 | impl_adc!(ADC2: adc_unit_t_ADC_UNIT_2); 349 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /src/units.rs: -------------------------------------------------------------------------------- 1 | //! Units of measurement implementation for times and frequencies. 2 | //! 3 | //! It provides type safety, easy conversion and limited arithmetic support. 4 | //! 5 | //! # Usage 6 | //! 7 | //! ``` 8 | //! let frequency_mhz_1 = MegaHertz(10), 9 | //! let frequency_mhz_2 = 10.MHz(), 10 | //! let frequency_khz_1: KiloHertz = frequency_mhz_1.into(), 11 | //! let frequency_khz_2 = KiloHertz::from(frequency_mhz_2), 12 | //! let frequency_khz_3 = frequency_khz_1 + 10.MHz().into(), 13 | //! let frequency_hz_1 = 1.Hz() + frequency_khz_3.into(), 14 | //! ``` 15 | 16 | use core::convert::TryFrom; 17 | use core::convert::TryInto; 18 | use core::fmt; 19 | 20 | pub type ValueType = u32; 21 | pub type LargeValueType = u64; 22 | 23 | pub trait Quantity: Sized {} 24 | pub trait Time: Quantity + Into {} 25 | pub trait Frequency: Quantity + Into {} 26 | pub trait Count: Quantity + Into {} 27 | 28 | pub trait TimeU64: Quantity + Into {} 29 | pub trait FrequencyU64: Quantity + Into {} 30 | pub trait CountU64: Quantity + Into {} 31 | 32 | /// defines and implements extension traits for quantities with units 33 | macro_rules! define { 34 | ($primitive:ident, $trait:ident, $( ($type: ident, $quantity: ident, $unit: ident, 35 | $print_unit: literal), )+) => { 36 | $( 37 | #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Default)] 38 | pub struct $quantity(pub $primitive); 39 | 40 | impl Quantity for $quantity {} 41 | impl $type for $quantity {} 42 | )* 43 | 44 | pub trait $trait { 45 | $( 46 | #[allow(non_snake_case)] 47 | fn $unit(self) -> $quantity; 48 | )* 49 | } 50 | 51 | impl $trait for $primitive { 52 | $( 53 | fn $unit(self) -> $quantity { 54 | $quantity(self) 55 | } 56 | )* 57 | } 58 | 59 | $( 60 | impl From<$quantity> for $primitive { 61 | fn from(x: $quantity) -> Self { 62 | x.0 63 | } 64 | } 65 | 66 | impl From<$primitive> for $quantity { 67 | fn from(x: $primitive) -> $quantity { 68 | $quantity(x) 69 | } 70 | } 71 | 72 | impl fmt::Debug for $quantity { 73 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 74 | write!(f, "{}{}", self.0, $print_unit) 75 | } 76 | } 77 | 78 | impl fmt::Display for $quantity { 79 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 80 | write!(f, "{}{}", self.0, $print_unit) 81 | } 82 | } 83 | 84 | impl core::ops::Div<$primitive> for $quantity { 85 | type Output = Self; 86 | fn div(self, rhs: $primitive) -> Self::Output { 87 | $quantity(self.0/rhs) 88 | } 89 | } 90 | 91 | impl core::ops::Mul<$primitive> for $quantity { 92 | type Output = Self; 93 | fn mul(self, rhs: $primitive) -> Self::Output { 94 | $quantity(self.0*rhs) 95 | } 96 | } 97 | 98 | impl core::ops::Mul<$quantity> for $primitive { 99 | type Output = $quantity; 100 | fn mul(self, rhs: $quantity) -> Self::Output { 101 | $quantity(self*rhs.0) 102 | } 103 | } 104 | 105 | impl core::ops::Div<$quantity> for $quantity { 106 | type Output = $primitive; 107 | fn div(self, rhs: Self) -> Self::Output { 108 | self.0/rhs.0 109 | } 110 | } 111 | 112 | impl core::ops::Add<$quantity> for $quantity { 113 | type Output = Self; 114 | fn add(self, rhs: Self) -> Self::Output { 115 | Self(self.0+rhs.0) 116 | } 117 | } 118 | 119 | impl core::ops::Sub<$quantity> for $quantity { 120 | type Output = Self; 121 | fn sub(self, rhs: Self) -> Self::Output { 122 | Self(self.0-rhs.0) 123 | } 124 | } 125 | )* 126 | }; 127 | } 128 | 129 | /// Define ValueType and LargeValueType quantities and conversion from ValueType to LargeValueType 130 | macro_rules! define_large { 131 | ($( ($type: ident, $quantity: ident, $unit:ident, $type_large: ident, 132 | $quantity_large: ident, $unit_large:ident, $print_unit: literal) ),+) => { 133 | 134 | define!( 135 | ValueType, 136 | FromValueType, 137 | $(($type, $quantity, $unit, $print_unit),)* 138 | ); 139 | 140 | define!( 141 | LargeValueType, 142 | FromLargeValueType, 143 | $(($type_large, $quantity_large, $unit_large, $print_unit),)* 144 | ); 145 | 146 | $( 147 | impl From<$quantity> for $quantity_large { 148 | fn from(x: $quantity) -> Self { 149 | Self(LargeValueType::from(x.0)) 150 | } 151 | } 152 | impl TryFrom<$quantity_large> for $quantity { 153 | type Error=core::num::TryFromIntError; 154 | fn try_from(x: $quantity_large) -> Result<$quantity, Self::Error> { 155 | Ok(Self(ValueType::try_from(x.0)?)) 156 | } 157 | } 158 | )* 159 | 160 | }; 161 | } 162 | 163 | /// defines From trait for pair or quantities with scaling 164 | macro_rules! convert { 165 | ($( ($from: ty, $from_large: ty, $into: ty, $into_large: ty, $factor: expr) ),+) => { 166 | $( 167 | impl From<$from> for $into { 168 | fn from(x: $from) -> Self { 169 | Self(x.0 * $factor) 170 | } 171 | } 172 | impl From<$from> for $into_large { 173 | fn from(x: $from) -> Self { 174 | Self(LargeValueType::from(x.0) * $factor) 175 | } 176 | } 177 | impl From<$from_large> for $into_large { 178 | fn from(x: $from_large) -> Self { 179 | Self(x.0 * $factor) 180 | } 181 | } 182 | )* 183 | }; 184 | } 185 | 186 | /// defines multiply trait for frequency and time 187 | macro_rules! multiply { 188 | ($( ($time: ty, $time_large: ty, $freq: ty, $freq_large: ty, 189 | $factor: expr, $divider: expr) ),+) => { 190 | $( 191 | impl core::ops::Mul<$freq> for $time { 192 | type Output = Ticks; 193 | fn mul(self, rhs: $freq) -> Self::Output { 194 | TicksU64::from(LargeValueType::from(self.0) * LargeValueType::from(rhs.0) 195 | * $factor / $divider).try_into().unwrap() 196 | } 197 | } 198 | 199 | impl core::ops::Mul<$time> for $freq { 200 | type Output = Ticks; 201 | fn mul(self, rhs: $time) -> Self::Output { 202 | TicksU64::from(LargeValueType::from(self.0) * LargeValueType::from(rhs.0) 203 | * $factor / $divider).try_into().unwrap() 204 | } 205 | } 206 | 207 | impl core::ops::Mul<$freq_large> for $time_large { 208 | type Output = TicksU64; 209 | fn mul(self, rhs: $freq_large) -> Self::Output { 210 | (self.0 * rhs.0 * $factor / $divider).into() 211 | } 212 | } 213 | 214 | impl core::ops::Mul<$time_large> for $freq_large { 215 | type Output = TicksU64; 216 | fn mul(self, rhs: $time_large) -> Self::Output { 217 | (self.0 * rhs.0 * $factor / $divider).into() 218 | } 219 | } 220 | 221 | impl core::ops::Mul<$freq> for $time_large { 222 | type Output = TicksU64; 223 | fn mul(self, rhs: $freq) -> Self::Output { 224 | (self.0 * LargeValueType::from(rhs.0) * $factor / $divider).into() 225 | } 226 | } 227 | 228 | impl core::ops::Mul<$time> for $freq_large { 229 | type Output = TicksU64; 230 | fn mul(self, rhs: $time) -> Self::Output { 231 | (self.0 * LargeValueType::from(rhs.0) * $factor / $divider).into() 232 | } 233 | } 234 | 235 | impl core::ops::Mul<$freq_large> for $time { 236 | type Output = TicksU64; 237 | fn mul(self, rhs: $freq_large) -> Self::Output { 238 | (LargeValueType::from(self.0) * rhs.0 * $factor / $divider).into() 239 | } 240 | } 241 | 242 | impl core::ops::Mul<$time_large> for $freq { 243 | type Output = TicksU64; 244 | fn mul(self, rhs: $time_large) -> Self::Output { 245 | (LargeValueType::from(self.0) * rhs.0 * $factor / $divider).into() 246 | } 247 | } 248 | )* 249 | }; 250 | } 251 | 252 | macro_rules! divide { 253 | ($( ($freq: ty, $freq_large: ty, $time: ty, $time_large: ty, $factor: expr) ),+) => { 254 | $( 255 | impl core::ops::Div<$freq> for Ticks { 256 | type Output = $time; 257 | fn div(self, rhs: $freq) -> Self::Output { 258 | ValueType::try_from( 259 | (LargeValueType::from(self.0) * $factor / LargeValueType::from(rhs.0)) 260 | ).unwrap().into() 261 | } 262 | } 263 | 264 | impl core::ops::Div<$freq> for TicksU64 { 265 | type Output = $time_large; 266 | fn div(self, rhs: $freq) -> Self::Output { 267 | (self.0 * $factor / LargeValueType::from(rhs.0) ).into() 268 | } 269 | } 270 | 271 | impl core::ops::Div<$freq_large> for TicksU64 { 272 | type Output = $time_large; 273 | fn div(self, rhs: $freq_large) -> Self::Output { 274 | (self.0 * $factor / rhs.0 ).into() 275 | } 276 | } 277 | 278 | impl core::ops::Div<$freq_large> for Ticks { 279 | type Output = $time_large; 280 | fn div(self, rhs: $freq_large) -> Self::Output { 281 | (LargeValueType::from(self.0) * $factor / rhs.0).into() 282 | } 283 | } 284 | )* 285 | }; 286 | } 287 | 288 | #[rustfmt::skip::macros(define_large)] 289 | define_large!( 290 | (Frequency, Hertz, Hz, FrequencyU64, HertzU64, Hz_large, "Hz" ), 291 | (Frequency, KiloHertz, kHz, FrequencyU64, KiloHertzU64, kHz_large, "kHz" ), 292 | (Frequency, MegaHertz, MHz, FrequencyU64, MegaHertzU64, MHz_large, "MHz" ), 293 | (Time, NanoSeconds, ns, TimeU64, NanoSecondsU64, ns_large, "ns" ), 294 | (Time, MicroSeconds, us, TimeU64, MicroSecondsU64, us_large, "us" ), 295 | (Time, MilliSeconds, ms, TimeU64, MilliSecondsU64, ms_large, "ms" ), 296 | (Time, Seconds, s, TimeU64, SecondsU64, s_large, "s" ), 297 | (Count, Ticks, ticks, CountU64, TicksU64, ticks_large, "" ) 298 | ); 299 | 300 | #[rustfmt::skip::macros(convert)] 301 | convert!( 302 | (KiloHertz, KiloHertzU64, Hertz, HertzU64, 1_000 ), 303 | (MegaHertz, MegaHertzU64, Hertz, HertzU64, 1_000_000 ), 304 | (MegaHertz, MegaHertzU64, KiloHertz, KiloHertzU64, 1_000 ), 305 | (Seconds, SecondsU64, MilliSeconds, MilliSecondsU64, 1_000 ), 306 | (Seconds, SecondsU64, MicroSeconds, MicroSecondsU64, 1_000_000 ), 307 | (Seconds, SecondsU64, NanoSeconds, NanoSecondsU64, 1_000_000_000 ), 308 | (MilliSeconds, MilliSecondsU64, MicroSeconds, MicroSecondsU64, 1_000 ), 309 | (MilliSeconds, MilliSecondsU64, NanoSeconds, NanoSecondsU64, 1_000_000 ), 310 | (MicroSeconds, MicroSecondsU64, NanoSeconds, NanoSecondsU64, 1_000 ) 311 | ); 312 | 313 | #[rustfmt::skip::macros(multiply)] 314 | multiply!( 315 | (Seconds, SecondsU64, Hertz, HertzU64, 1, 1 ), 316 | (Seconds, SecondsU64, KiloHertz, KiloHertzU64, 1_000, 1 ), 317 | (Seconds, SecondsU64, MegaHertz, MegaHertzU64, 1_000_000, 1 ), 318 | (MilliSeconds, MilliSecondsU64, Hertz, HertzU64, 1, 1_000 ), 319 | (MilliSeconds, MilliSecondsU64, KiloHertz, KiloHertzU64, 1, 1 ), 320 | (MilliSeconds, MilliSecondsU64, MegaHertz, MegaHertzU64, 1_000, 1 ), 321 | (MicroSeconds, MicroSecondsU64, Hertz, HertzU64, 1, 1_000_000 ), 322 | (MicroSeconds, MicroSecondsU64, KiloHertz, KiloHertzU64, 1, 1_000 ), 323 | (MicroSeconds, MicroSecondsU64, MegaHertz, MegaHertzU64, 1, 1 ), 324 | (NanoSeconds, NanoSecondsU64, Hertz, HertzU64, 1, 1_000_000_000 ), 325 | (NanoSeconds, NanoSecondsU64, KiloHertz, KiloHertzU64, 1, 1_000_000 ), 326 | (NanoSeconds, NanoSecondsU64, MegaHertz, MegaHertzU64, 1, 1_000 ) 327 | ); 328 | 329 | #[rustfmt::skip::macros(divide)] 330 | divide!( 331 | (Hertz, HertzU64, NanoSeconds, NanoSecondsU64, 1_000_000_000 ), 332 | (KiloHertz, KiloHertzU64, NanoSeconds, NanoSecondsU64, 1_000_000 ), 333 | (MegaHertz, MegaHertzU64, NanoSeconds, NanoSecondsU64, 1_000 ) 334 | ); 335 | -------------------------------------------------------------------------------- /src/interrupt.rs: -------------------------------------------------------------------------------- 1 | use core::cell::{RefCell, RefMut}; 2 | use core::ops::{Deref, DerefMut}; 3 | use core::ptr; 4 | use core::sync::atomic::{AtomicPtr, Ordering}; 5 | 6 | use esp_idf_sys::*; 7 | 8 | /// Returns true if the currently active core is executing an ISR request 9 | #[inline(always)] 10 | #[link_section = ".iram1.interrupt_active"] 11 | pub fn active() -> bool { 12 | unsafe { xPortInIsrContext() != 0 } 13 | } 14 | 15 | static ISR_YIELDER: AtomicPtr = AtomicPtr::new(ptr::null_mut()); 16 | 17 | #[inline(always)] 18 | #[link_section = ".iram1.interrupt_get_isr_yielder"] 19 | unsafe fn get_isr_yielder() -> Option { 20 | if active() { 21 | let ptr = ISR_YIELDER.load(Ordering::SeqCst); 22 | if ptr.is_null() { 23 | None 24 | } else { 25 | Some(core::mem::transmute(ptr)) 26 | } 27 | } else { 28 | None 29 | } 30 | } 31 | 32 | /// # Safety 33 | /// 34 | /// This function should only be called from within an ISR handler, so as to set 35 | /// a custom ISR yield function (e.g. when using the ESP-IDF timer service). 36 | /// 37 | /// Thus, if some function further down the ISR call chain invokes `do_yield`, 38 | /// the custom yield function set here will be called. 39 | /// 40 | /// Users should not forget to call again `set_isr_yielder` at the end of the 41 | /// ISR handler so as to reastore the yield function which was valid before the 42 | /// ISR handler was invoked. 43 | #[inline(always)] 44 | #[link_section = ".iram1.interrupt_set_isr_yielder"] 45 | pub unsafe fn set_isr_yielder(yielder: Option) -> Option { 46 | if active() { 47 | let ptr = if let Some(yielder) = yielder { 48 | #[allow(clippy::transmutes_expressible_as_ptr_casts)] 49 | core::mem::transmute(yielder) 50 | } else { 51 | ptr::null_mut() 52 | }; 53 | 54 | let ptr = ISR_YIELDER.swap(ptr, Ordering::SeqCst); 55 | 56 | if ptr.is_null() { 57 | None 58 | } else { 59 | Some(core::mem::transmute(ptr)) 60 | } 61 | } else { 62 | None 63 | } 64 | } 65 | 66 | pub mod task { 67 | use core::ptr; 68 | use core::time::Duration; 69 | 70 | use esp_idf_sys::*; 71 | 72 | use crate::delay::TickType; 73 | 74 | #[inline(always)] 75 | #[link_section = ".iram1.interrupt_task_do_yield"] 76 | pub fn do_yield() { 77 | if super::active() { 78 | #[cfg(esp32c3)] 79 | unsafe { 80 | if let Some(yielder) = super::get_isr_yielder() { 81 | yielder(); 82 | } else { 83 | vPortYieldFromISR(); 84 | } 85 | } 86 | 87 | #[cfg(not(esp32c3))] 88 | unsafe { 89 | if let Some(yielder) = super::get_isr_yielder() { 90 | yielder(); 91 | } else { 92 | vPortEvaluateYieldFromISR(0); 93 | } 94 | } 95 | } else { 96 | unsafe { 97 | vPortYield(); 98 | } 99 | } 100 | } 101 | 102 | #[inline(always)] 103 | #[link_section = ".iram1.interrupt_task_current"] 104 | pub fn current() -> Option { 105 | if super::active() { 106 | None 107 | } else { 108 | Some(unsafe { xTaskGetCurrentTaskHandle() }) 109 | } 110 | } 111 | 112 | pub fn wait_any_notification() { 113 | loop { 114 | if let Some(notification) = wait_notification(None) { 115 | if notification != 0 { 116 | break; 117 | } 118 | } 119 | } 120 | } 121 | 122 | pub fn wait_notification(duration: Option) -> Option { 123 | let mut notification = 0_u32; 124 | 125 | #[cfg(esp_idf_version = "4.3")] 126 | let notified = unsafe { 127 | xTaskNotifyWait( 128 | 0, 129 | u32::MAX, 130 | &mut notification as *mut _, 131 | TickType::from(duration).0, 132 | ) 133 | } != 0; 134 | 135 | #[cfg(not(esp_idf_version = "4.3"))] 136 | let notified = unsafe { 137 | xTaskGenericNotifyWait( 138 | 0, 139 | 0, 140 | u32::MAX, 141 | &mut notification as *mut _, 142 | TickType::from(duration).0, 143 | ) 144 | } != 0; 145 | 146 | if notified { 147 | Some(notification) 148 | } else { 149 | None 150 | } 151 | } 152 | 153 | /// # Safety 154 | /// 155 | /// When calling this function care should be taken to pass a valid 156 | /// FreeRTOS task handle. Moreover, the FreeRTOS task should be valid 157 | /// when this function is being called. 158 | pub unsafe fn notify(task: TaskHandle_t, notification: u32) -> bool { 159 | let notified = if super::active() { 160 | let mut higher_prio_task_woken: BaseType_t = Default::default(); 161 | 162 | #[cfg(esp_idf_version = "4.3")] 163 | let notified = xTaskGenericNotifyFromISR( 164 | task, 165 | notification, 166 | eNotifyAction_eSetBits, 167 | ptr::null_mut(), 168 | &mut higher_prio_task_woken as *mut _, 169 | ); 170 | 171 | #[cfg(not(esp_idf_version = "4.3"))] 172 | let notified = xTaskGenericNotifyFromISR( 173 | task, 174 | 0, 175 | notification, 176 | eNotifyAction_eSetBits, 177 | ptr::null_mut(), 178 | &mut higher_prio_task_woken as *mut _, 179 | ); 180 | 181 | if higher_prio_task_woken != 0 { 182 | do_yield(); 183 | } 184 | 185 | notified 186 | } else { 187 | #[cfg(esp_idf_version = "4.3")] 188 | let notified = 189 | xTaskGenericNotify(task, notification, eNotifyAction_eSetBits, ptr::null_mut()); 190 | 191 | #[cfg(not(esp_idf_version = "4.3"))] 192 | let notified = xTaskGenericNotify( 193 | task, 194 | 0, 195 | notification, 196 | eNotifyAction_eSetBits, 197 | ptr::null_mut(), 198 | ); 199 | 200 | notified 201 | }; 202 | 203 | notified != 0 204 | } 205 | } 206 | 207 | /// A critical section allows the user to disable interrupts 208 | #[cfg(not(esp32c3))] 209 | pub struct CriticalSection(core::cell::UnsafeCell); 210 | 211 | #[cfg(esp32c3)] 212 | pub struct CriticalSection(core::marker::PhantomData<*const ()>); 213 | 214 | #[cfg(esp32c3)] 215 | #[inline(always)] 216 | #[link_section = ".iram1.interrupt_enter"] 217 | fn enter(_cs: &CriticalSection) { 218 | unsafe { 219 | vPortEnterCritical(); 220 | } 221 | } 222 | 223 | #[cfg(not(esp32c3))] 224 | #[inline(always)] 225 | #[link_section = ".iram1.interrupt_enter"] 226 | fn enter(cs: &CriticalSection) { 227 | #[cfg(esp_idf_version = "4.3")] 228 | unsafe { 229 | vPortEnterCritical(cs.0.get()); 230 | } 231 | 232 | #[cfg(not(esp_idf_version = "4.3"))] 233 | unsafe { 234 | xPortEnterCriticalTimeout(cs.0.get(), portMUX_NO_TIMEOUT); 235 | } 236 | } 237 | 238 | #[cfg(esp32c3)] 239 | #[inline(always)] 240 | #[link_section = ".iram1.interrupt_exit"] 241 | fn exit(_cs: &CriticalSection) { 242 | unsafe { 243 | vPortExitCritical(); 244 | } 245 | } 246 | 247 | #[cfg(not(esp32c3))] 248 | #[inline(always)] 249 | #[link_section = ".iram1.interrupt_exit"] 250 | fn exit(cs: &CriticalSection) { 251 | unsafe { 252 | vPortExitCritical(cs.0.get()); 253 | } 254 | } 255 | 256 | impl CriticalSection { 257 | /// Constructs a new `CriticalSection` instance 258 | #[inline(always)] 259 | #[link_section = ".iram1.interrupt_cs_new"] 260 | pub const fn new() -> Self { 261 | #[cfg(not(esp32c3))] 262 | let mux = core::cell::UnsafeCell::new(portMUX_TYPE { 263 | owner: portMUX_FREE_VAL, 264 | count: 0, 265 | #[cfg(esp_idf_freertos_portmux_debug)] 266 | lastLockedFn: b"(never locked)", 267 | #[cfg(esp_idf_freertos_portmux_debug)] 268 | lastLockedLine: -1, 269 | }); 270 | 271 | #[cfg(esp32c3)] 272 | let mux = core::marker::PhantomData; 273 | 274 | Self(mux) 275 | } 276 | 277 | /// Disables all interrupts for the lifetime of the returned guard instance. 278 | /// This method supports nesting in that is safe to be called multiple times. 279 | /// This method is also safe to call from ISR routines. 280 | /// 281 | /// NOTE: On dual-core esp32* chips, interrupts will be disabled only on one of 282 | /// the cores (the one where `CriticalSection::enter` is called), while the other 283 | /// core will continue its execution. Moreover, if the same `CriticalSection` instance 284 | /// is shared across multiple threads, where some of these happen to be scheduled on 285 | /// the second core (which has its interrupts enabled), the second core will then spinlock 286 | /// (busy-wait) in `CriticalSection::enter`, until the first CPU releases the critical 287 | /// section and re-enables its interrupts. The second core will then - in turn - disable 288 | /// its interrupts and own the spinlock. 289 | /// 290 | /// For more information, refer to https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/freertos-smp.html#critical-sections 291 | #[inline(always)] 292 | #[link_section = ".iram1.interrupt_cs_enter"] 293 | pub fn enter(&self) -> CriticalSectionGuard { 294 | enter(self); 295 | 296 | CriticalSectionGuard(self) 297 | } 298 | } 299 | 300 | impl Default for CriticalSection { 301 | #[inline(always)] 302 | #[link_section = ".iram1.interrupt_cs_default"] 303 | fn default() -> Self { 304 | Self::new() 305 | } 306 | } 307 | 308 | unsafe impl Send for CriticalSection {} 309 | unsafe impl Sync for CriticalSection {} 310 | 311 | pub struct CriticalSectionGuard<'a>(&'a CriticalSection); 312 | 313 | impl<'a> Drop for CriticalSectionGuard<'a> { 314 | /// Drops the critical section guard thus potentially re-enabling 315 | /// al interrupts for the currently active core. 316 | /// 317 | /// Note that - due to the fact that calling `CriticalSection::enter` 318 | /// multiple times on the same or multiple critical sections is supported - 319 | /// interrupts for the core will be re-enabled only when the last guard that 320 | /// disabled interrupts for the concrete core is dropped. 321 | #[inline(always)] 322 | #[link_section = ".iram1.interrupt_csg_drop"] 323 | fn drop(&mut self) { 324 | exit(self.0); 325 | } 326 | } 327 | 328 | /// Executes closure f in an interrupt-free context 329 | #[inline(always)] 330 | #[link_section = ".iram1.interrupt_free"] 331 | pub fn free(f: impl FnOnce() -> R) -> R { 332 | let cs = CriticalSection::new(); 333 | let _guard = cs.enter(); 334 | 335 | f() 336 | } 337 | 338 | #[cfg(feature = "critical-section")] 339 | mod embassy_cs { 340 | static CS: super::CriticalSection = super::CriticalSection::new(); 341 | 342 | struct EmbassyCriticalSectionImpl {} 343 | critical_section::custom_impl!(EmbassyCriticalSectionImpl); 344 | 345 | unsafe impl critical_section::Impl for EmbassyCriticalSectionImpl { 346 | unsafe fn acquire() -> u8 { 347 | super::enter(&CS); 348 | return 1; 349 | } 350 | 351 | unsafe fn release(token: u8) { 352 | if token != 0 { 353 | super::exit(&CS); 354 | } 355 | } 356 | } 357 | } 358 | 359 | #[cfg(feature = "embassy")] 360 | pub mod embassy { 361 | pub enum CriticalSectionMutexKind {} 362 | impl embassy::blocking_mutex::kind::MutexKind for CriticalSectionMutexKind { 363 | type Mutex = super::Mutex; 364 | } 365 | 366 | impl<'a, T> embassy::blocking_mutex::Mutex for super::Mutex { 367 | type Data = T; 368 | 369 | fn new(data: Self::Data) -> Self { 370 | super::Mutex::new(data) 371 | } 372 | 373 | #[inline(always)] 374 | #[link_section = ".iram1.interrupt_embmutex_lock"] 375 | fn lock(&self, f: impl FnOnce(&Self::Data) -> R) -> R { 376 | let mut guard = super::Mutex::lock(self); 377 | 378 | f(&mut guard) 379 | } 380 | } 381 | } 382 | 383 | /// A mutex based on critical sections 384 | pub struct Mutex { 385 | cs: CriticalSection, 386 | data: RefCell, 387 | } 388 | 389 | impl Mutex { 390 | #[inline(always)] 391 | #[link_section = ".iram1.interrupt_mutex_new"] 392 | pub const fn new(data: T) -> Self { 393 | Self { 394 | cs: CriticalSection::new(), 395 | data: RefCell::new(data), 396 | } 397 | } 398 | 399 | #[inline(always)] 400 | #[link_section = ".iram1.interrupt_mutex_lock"] 401 | pub fn lock(&self) -> MutexGuard<'_, T> { 402 | MutexGuard::new(self) 403 | } 404 | } 405 | 406 | unsafe impl Sync for Mutex where T: Send {} 407 | unsafe impl Send for Mutex where T: Send {} 408 | 409 | pub struct MutexGuard<'a, T: 'a>(CriticalSectionGuard<'a>, RefMut<'a, T>); 410 | 411 | impl<'a, T> MutexGuard<'a, T> { 412 | #[inline(always)] 413 | #[link_section = ".iram1.interrupt_mutexg_new"] 414 | fn new(mutex: &'a Mutex) -> Self { 415 | Self(mutex.cs.enter(), mutex.data.borrow_mut()) 416 | } 417 | } 418 | 419 | unsafe impl Sync for MutexGuard<'_, T> where T: Sync {} 420 | 421 | impl<'a, T> Deref for MutexGuard<'a, T> { 422 | type Target = T; 423 | 424 | #[inline(always)] 425 | #[link_section = ".iram1.interrupt_mutexg_deref"] 426 | fn deref(&self) -> &Self::Target { 427 | &*self.1 428 | } 429 | } 430 | 431 | impl<'a, T> DerefMut for MutexGuard<'a, T> { 432 | #[inline(always)] 433 | #[link_section = ".iram1.interrupt_mutexg_derefmut"] 434 | fn deref_mut(&mut self) -> &mut Self::Target { 435 | &mut *self.1 436 | } 437 | } 438 | 439 | #[cfg(feature = "mutex-trait")] 440 | impl<'a, T> mutex_trait::Mutex for &'a Mutex { 441 | type Data = T; 442 | 443 | #[inline(always)] 444 | #[link_section = ".iram1.interrupt_mutext_lock"] 445 | fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { 446 | let mut guard = Mutex::lock(self); 447 | 448 | f(&mut guard) 449 | } 450 | } 451 | 452 | #[cfg(feature = "embedded-svc")] 453 | pub struct MutexFamily; 454 | 455 | #[cfg(feature = "embedded-svc")] 456 | impl embedded_svc::mutex::MutexFamily for MutexFamily { 457 | type Mutex = Mutex; 458 | } 459 | 460 | #[cfg(feature = "embedded-svc")] 461 | impl embedded_svc::mutex::Mutex for Mutex { 462 | type Data = T; 463 | 464 | type Guard<'a> 465 | where 466 | T: 'a, 467 | = MutexGuard<'a, T>; 468 | 469 | #[inline(always)] 470 | #[link_section = ".iram1.interrupt_mutexe_new"] 471 | fn new(data: Self::Data) -> Self { 472 | Mutex::new(data) 473 | } 474 | 475 | #[inline(always)] 476 | #[link_section = ".iram1.interrupt_mutexe_lock"] 477 | fn lock(&self) -> Self::Guard<'_> { 478 | Mutex::lock(self) 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /src/ledc.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(feature = "riscv-ulp-hal"))] 2 | 3 | //! LED Control peripheral (which also crates PWM signals for other purposes) 4 | //! 5 | //! Interface to the [LED Control (LEDC) 6 | //! peripheral](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html) 7 | //! 8 | //! This is an initial implementation supporting the generation of PWM signals 9 | //! but no chrome and spoilers like fading. 10 | //! 11 | //! # Examples 12 | //! 13 | //! Create a 25 kHz PWM signal with 75 % duty cycle on GPIO 1 14 | //! ``` 15 | //! use embedded_hal::pwm::blocking::PwmPin; 16 | //! use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; 17 | //! use esp_idf_hal::peripherals::Peripherals; 18 | //! use esp_idf_hal::prelude::*; 19 | //! 20 | //! let peripherals = Peripherals::take().unwrap(); 21 | //! let config = TimerConfig::default().frequency(25.kHz().into()); 22 | //! let timer = Timer::new(peripherals.ledc.timer0, &config)?; 23 | //! let channel = Channel::new(peripherals.ledc.channel0, &timer, peripherals.pins.gpio1)?; 24 | //! 25 | //! let max_duty = channel.get_max_duty()?; 26 | //! channel.set_duty(max_duty * 3 / 4); 27 | //! ``` 28 | //! 29 | //! See the `examples/` folder of this repository for more. 30 | 31 | use core::{borrow::Borrow, marker::PhantomData}; 32 | 33 | use esp_idf_sys::*; 34 | 35 | use crate::gpio::OutputPin; 36 | use crate::mutex::Mutex; 37 | 38 | pub use chip::*; 39 | 40 | type Duty = u32; 41 | 42 | const IDLE_LEVEL: u32 = 0; 43 | 44 | static FADE_FUNC_INSTALLED: Mutex = Mutex::new(false); 45 | 46 | /// Types for configuring the LED Control peripheral 47 | pub mod config { 48 | use super::*; 49 | use crate::units::*; 50 | 51 | pub use chip::Resolution; 52 | 53 | pub struct TimerConfig { 54 | pub frequency: Hertz, 55 | pub resolution: Resolution, 56 | pub speed_mode: ledc_mode_t, 57 | } 58 | 59 | impl TimerConfig { 60 | #[must_use] 61 | pub fn frequency(mut self, f: Hertz) -> Self { 62 | self.frequency = f; 63 | self 64 | } 65 | 66 | #[must_use] 67 | pub fn resolution(mut self, r: Resolution) -> Self { 68 | self.resolution = r; 69 | self 70 | } 71 | 72 | #[must_use] 73 | pub fn speed_mode(mut self, mode: ledc_mode_t) -> Self { 74 | self.speed_mode = mode; 75 | self 76 | } 77 | } 78 | 79 | impl Default for TimerConfig { 80 | fn default() -> Self { 81 | TimerConfig { 82 | frequency: 1000.Hz(), 83 | resolution: Resolution::Bits8, 84 | speed_mode: ledc_mode_t_LEDC_LOW_SPEED_MODE, 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// LED Control timer abstraction 91 | pub struct Timer { 92 | instance: T, 93 | speed_mode: ledc_mode_t, 94 | max_duty: Duty, 95 | } 96 | 97 | impl Timer { 98 | /// Creates a new LED Control timer abstraction 99 | pub fn new(instance: T, config: &config::TimerConfig) -> Result { 100 | let timer_config = ledc_timer_config_t { 101 | speed_mode: config.speed_mode, 102 | timer_num: T::timer(), 103 | __bindgen_anon_1: ledc_timer_config_t__bindgen_ty_1 { 104 | duty_resolution: config.resolution.timer_bits(), 105 | }, 106 | freq_hz: config.frequency.into(), 107 | clk_cfg: ledc_clk_cfg_t_LEDC_AUTO_CLK, 108 | }; 109 | 110 | // SAFETY: We own the instance and therefor are safe to configure it. 111 | esp!(unsafe { ledc_timer_config(&timer_config) })?; 112 | 113 | Ok(Timer { 114 | instance, 115 | speed_mode: config.speed_mode, 116 | max_duty: config.resolution.max_duty(), 117 | }) 118 | } 119 | 120 | /// Pauses the timer. Operation can be resumed with 121 | /// [`resume()`](Timer::resume()). 122 | pub fn pause(&mut self) -> Result<(), EspError> { 123 | esp!(unsafe { ledc_timer_pause(self.speed_mode, T::timer()) })?; 124 | Ok(()) 125 | } 126 | 127 | /// Stops the timer and releases its hardware resource 128 | pub fn release(mut self) -> Result { 129 | self.reset()?; 130 | Ok(self.instance) 131 | } 132 | 133 | fn reset(&mut self) -> Result<(), EspError> { 134 | esp!(unsafe { ledc_timer_rst(self.speed_mode, T::timer()) })?; 135 | Ok(()) 136 | } 137 | 138 | /// Resumes the operation of a previously paused timer 139 | pub fn resume(&mut self) -> Result<(), EspError> { 140 | esp!(unsafe { ledc_timer_resume(self.speed_mode, T::timer()) })?; 141 | Ok(()) 142 | } 143 | } 144 | 145 | /// LED Control output channel abstraction 146 | pub struct Channel>, P: OutputPin> { 147 | instance: C, 148 | _hw_timer: PhantomData, 149 | timer: T, 150 | pin: P, 151 | duty: Duty, 152 | } 153 | 154 | // TODO: Stop channel when the instance gets dropped. It seems that we can't 155 | // have both at the same time: a method for releasing its hardware resources 156 | // and implementing Drop. 157 | impl>, P: OutputPin> Channel { 158 | /// Creates a new LED Control output channel abstraction 159 | pub fn new(instance: C, timer: T, pin: P) -> Result { 160 | let duty = 0; 161 | let channel_config = ledc_channel_config_t { 162 | speed_mode: timer.borrow().speed_mode, 163 | channel: C::channel(), 164 | timer_sel: H::timer(), 165 | intr_type: ledc_intr_type_t_LEDC_INTR_DISABLE, 166 | gpio_num: pin.pin(), 167 | duty: duty as u32, 168 | ..Default::default() 169 | }; 170 | 171 | let mut installed = FADE_FUNC_INSTALLED.lock(); 172 | if !*installed { 173 | // It looks like ledc_channel_config requires the face function to 174 | // be installed. I don't see why this is nescessary yet but hey, 175 | // let the Wookie win for now. 176 | // 177 | // TODO: Check whether it's worth to track its install status and 178 | // remove it if no longer needed. 179 | esp!(unsafe { ledc_fade_func_install(0) })?; 180 | *installed = true; 181 | } 182 | drop(installed); 183 | 184 | // SAFETY: As long as we have borrowed the timer, we are safe to use 185 | // it. 186 | esp!(unsafe { ledc_channel_config(&channel_config) })?; 187 | 188 | Ok(Channel { 189 | instance, 190 | _hw_timer: PhantomData, 191 | timer, 192 | pin, 193 | duty, 194 | }) 195 | } 196 | 197 | /// Stops the output channel and releases its hardware resource and GPIO 198 | /// pin 199 | pub fn release(mut self) -> Result<(C, P), EspError> { 200 | self.stop()?; 201 | Ok((self.instance, self.pin)) 202 | } 203 | 204 | pub fn get_duty(&self) -> Duty { 205 | self.duty 206 | } 207 | 208 | pub fn get_max_duty(&self) -> Duty { 209 | self.timer.borrow().max_duty 210 | } 211 | 212 | pub fn disable(&mut self) -> Result<(), EspError> { 213 | self.update_duty(0)?; 214 | Ok(()) 215 | } 216 | 217 | pub fn enable(&mut self) -> Result<(), EspError> { 218 | self.update_duty(self.duty)?; 219 | Ok(()) 220 | } 221 | 222 | pub fn set_duty(&mut self, duty: Duty) -> Result<(), EspError> { 223 | // Clamp the actual duty cycle to the current maximum as done by other 224 | // Pwm/PwmPin implementations. 225 | // 226 | // TODO: Why does calling self.get_max_duty() result in the compiler 227 | // error 'expected `u32`, found enum `Result`' when our method returns 228 | // Duty? 229 | let clamped = duty.min(self.timer.borrow().max_duty); 230 | self.duty = clamped; 231 | self.update_duty(clamped)?; 232 | Ok(()) 233 | } 234 | 235 | fn stop(&mut self) -> Result<(), EspError> { 236 | esp!(unsafe { ledc_stop(self.timer.borrow().speed_mode, C::channel(), IDLE_LEVEL) })?; 237 | Ok(()) 238 | } 239 | 240 | fn update_duty(&mut self, duty: Duty) -> Result<(), EspError> { 241 | esp!(unsafe { 242 | ledc_set_duty_and_update( 243 | self.timer.borrow().speed_mode, 244 | C::channel(), 245 | duty as u32, 246 | Default::default(), 247 | ) 248 | })?; 249 | Ok(()) 250 | } 251 | } 252 | 253 | // PwmPin temporarily removed from embedded-hal-1.0.alpha7 in anticipation of e-hal 1.0 release 254 | // impl>, P: OutputPin> embedded_hal::pwm::blocking::PwmPin for Channel { 255 | // type Duty = Duty; 256 | // type Error = EspError; 257 | 258 | // fn disable(&mut self) -> Result<(), Self::Error> { 259 | // self.disable() 260 | // } 261 | 262 | // fn enable(&mut self) -> Result<(), Self::Error> { 263 | // self.enable() 264 | // } 265 | 266 | // fn get_duty(&self) -> Result { 267 | // Ok(self.get_duty()) 268 | // } 269 | 270 | // fn get_max_duty(&self) -> Result { 271 | // Ok(self.get_max_duty()) 272 | // } 273 | 274 | // fn set_duty(&mut self, duty: Duty) -> Result<(), Self::Error> { 275 | // self.set_duty(duty) 276 | // } 277 | // } 278 | 279 | impl>, P: OutputPin> embedded_hal_0_2::PwmPin 280 | for Channel 281 | { 282 | type Duty = Duty; 283 | 284 | fn disable(&mut self) { 285 | if let Err(e) = self.disable() { 286 | panic!("disabling PWM failed: {}", e); 287 | } 288 | } 289 | 290 | fn enable(&mut self) { 291 | if let Err(e) = self.enable() { 292 | panic!("enabling PWM failed: {}", e); 293 | } 294 | } 295 | 296 | fn get_duty(&self) -> Self::Duty { 297 | self.get_duty() 298 | } 299 | 300 | fn get_max_duty(&self) -> Self::Duty { 301 | self.get_max_duty() 302 | } 303 | 304 | fn set_duty(&mut self, duty: Duty) { 305 | if let Err(e) = self.set_duty(duty) { 306 | panic!("updating duty failed: {}", e); 307 | } 308 | } 309 | } 310 | 311 | mod chip { 312 | use core::marker::PhantomData; 313 | use esp_idf_sys::*; 314 | 315 | pub enum Resolution { 316 | Bits1, 317 | Bits2, 318 | Bits3, 319 | Bits4, 320 | Bits5, 321 | Bits6, 322 | Bits7, 323 | Bits8, 324 | Bits9, 325 | Bits10, 326 | Bits11, 327 | Bits12, 328 | Bits13, 329 | Bits14, 330 | #[cfg(esp32)] 331 | Bits15, 332 | #[cfg(esp32)] 333 | Bits16, 334 | #[cfg(esp32)] 335 | Bits17, 336 | #[cfg(esp32)] 337 | Bits18, 338 | #[cfg(esp32)] 339 | Bits19, 340 | #[cfg(esp32)] 341 | Bits20, 342 | } 343 | 344 | impl Resolution { 345 | pub const fn bits(&self) -> usize { 346 | match self { 347 | Resolution::Bits1 => 1, 348 | Resolution::Bits2 => 2, 349 | Resolution::Bits3 => 3, 350 | Resolution::Bits4 => 4, 351 | Resolution::Bits5 => 5, 352 | Resolution::Bits6 => 6, 353 | Resolution::Bits7 => 7, 354 | Resolution::Bits8 => 8, 355 | Resolution::Bits9 => 9, 356 | Resolution::Bits10 => 10, 357 | Resolution::Bits11 => 11, 358 | Resolution::Bits12 => 12, 359 | Resolution::Bits13 => 13, 360 | Resolution::Bits14 => 14, 361 | #[cfg(esp32)] 362 | Resolution::Bits15 => 15, 363 | #[cfg(esp32)] 364 | Resolution::Bits16 => 16, 365 | #[cfg(esp32)] 366 | Resolution::Bits17 => 17, 367 | #[cfg(esp32)] 368 | Resolution::Bits18 => 18, 369 | #[cfg(esp32)] 370 | Resolution::Bits19 => 19, 371 | #[cfg(esp32)] 372 | Resolution::Bits20 => 20, 373 | } 374 | } 375 | 376 | pub const fn max_duty(&self) -> u32 { 377 | (1 << self.bits()) - 1 378 | } 379 | 380 | pub(crate) const fn timer_bits(&self) -> ledc_timer_bit_t { 381 | match self { 382 | Resolution::Bits1 => ledc_timer_bit_t_LEDC_TIMER_1_BIT, 383 | Resolution::Bits2 => ledc_timer_bit_t_LEDC_TIMER_2_BIT, 384 | Resolution::Bits3 => ledc_timer_bit_t_LEDC_TIMER_3_BIT, 385 | Resolution::Bits4 => ledc_timer_bit_t_LEDC_TIMER_4_BIT, 386 | Resolution::Bits5 => ledc_timer_bit_t_LEDC_TIMER_5_BIT, 387 | Resolution::Bits6 => ledc_timer_bit_t_LEDC_TIMER_6_BIT, 388 | Resolution::Bits7 => ledc_timer_bit_t_LEDC_TIMER_7_BIT, 389 | Resolution::Bits8 => ledc_timer_bit_t_LEDC_TIMER_8_BIT, 390 | Resolution::Bits9 => ledc_timer_bit_t_LEDC_TIMER_9_BIT, 391 | Resolution::Bits10 => ledc_timer_bit_t_LEDC_TIMER_10_BIT, 392 | Resolution::Bits11 => ledc_timer_bit_t_LEDC_TIMER_11_BIT, 393 | Resolution::Bits12 => ledc_timer_bit_t_LEDC_TIMER_12_BIT, 394 | Resolution::Bits13 => ledc_timer_bit_t_LEDC_TIMER_13_BIT, 395 | Resolution::Bits14 => ledc_timer_bit_t_LEDC_TIMER_14_BIT, 396 | #[cfg(esp32)] 397 | Resolution::Bits15 => ledc_timer_bit_t_LEDC_TIMER_15_BIT, 398 | #[cfg(esp32)] 399 | Resolution::Bits16 => ledc_timer_bit_t_LEDC_TIMER_16_BIT, 400 | #[cfg(esp32)] 401 | Resolution::Bits17 => ledc_timer_bit_t_LEDC_TIMER_17_BIT, 402 | #[cfg(esp32)] 403 | Resolution::Bits18 => ledc_timer_bit_t_LEDC_TIMER_18_BIT, 404 | #[cfg(esp32)] 405 | Resolution::Bits19 => ledc_timer_bit_t_LEDC_TIMER_19_BIT, 406 | #[cfg(esp32)] 407 | Resolution::Bits20 => ledc_timer_bit_t_LEDC_TIMER_20_BIT, 408 | } 409 | } 410 | } 411 | 412 | /// LED Control peripheral timer 413 | pub trait HwTimer { 414 | fn timer() -> ledc_timer_t; 415 | } 416 | 417 | /// LED Control peripheral output channel 418 | pub trait HwChannel { 419 | fn channel() -> ledc_channel_t; 420 | } 421 | 422 | macro_rules! impl_timer { 423 | ($instance:ident: $timer:expr) => { 424 | pub struct $instance { 425 | _marker: PhantomData, 426 | } 427 | 428 | impl $instance { 429 | /// # Safety 430 | /// 431 | /// It is safe to instantiate this timer exactly one time. 432 | pub unsafe fn new() -> Self { 433 | $instance { 434 | _marker: PhantomData, 435 | } 436 | } 437 | } 438 | 439 | impl HwTimer for $instance { 440 | fn timer() -> ledc_timer_t { 441 | $timer 442 | } 443 | } 444 | }; 445 | } 446 | 447 | impl_timer!(TIMER0: ledc_timer_t_LEDC_TIMER_0); 448 | impl_timer!(TIMER1: ledc_timer_t_LEDC_TIMER_1); 449 | impl_timer!(TIMER2: ledc_timer_t_LEDC_TIMER_2); 450 | impl_timer!(TIMER3: ledc_timer_t_LEDC_TIMER_3); 451 | 452 | macro_rules! impl_channel { 453 | ($instance:ident: $channel:expr) => { 454 | pub struct $instance { 455 | _marker: PhantomData, 456 | } 457 | 458 | impl $instance { 459 | /// # Safety 460 | /// 461 | /// It is safe to instantiate this output channel exactly one 462 | /// time. 463 | pub unsafe fn new() -> Self { 464 | $instance { 465 | _marker: PhantomData, 466 | } 467 | } 468 | } 469 | 470 | impl HwChannel for $instance { 471 | fn channel() -> ledc_channel_t { 472 | $channel 473 | } 474 | } 475 | }; 476 | } 477 | 478 | impl_channel!(CHANNEL0: ledc_channel_t_LEDC_CHANNEL_0); 479 | impl_channel!(CHANNEL1: ledc_channel_t_LEDC_CHANNEL_1); 480 | impl_channel!(CHANNEL2: ledc_channel_t_LEDC_CHANNEL_2); 481 | impl_channel!(CHANNEL3: ledc_channel_t_LEDC_CHANNEL_3); 482 | impl_channel!(CHANNEL4: ledc_channel_t_LEDC_CHANNEL_4); 483 | impl_channel!(CHANNEL5: ledc_channel_t_LEDC_CHANNEL_5); 484 | #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] 485 | impl_channel!(CHANNEL6: ledc_channel_t_LEDC_CHANNEL_6); 486 | #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] 487 | impl_channel!(CHANNEL7: ledc_channel_t_LEDC_CHANNEL_7); 488 | 489 | /// The LED Control device peripheral 490 | pub struct Peripheral { 491 | pub timer0: TIMER0, 492 | pub timer1: TIMER1, 493 | pub timer2: TIMER2, 494 | pub timer3: TIMER3, 495 | pub channel0: CHANNEL0, 496 | pub channel1: CHANNEL1, 497 | pub channel2: CHANNEL2, 498 | pub channel3: CHANNEL3, 499 | pub channel4: CHANNEL4, 500 | pub channel5: CHANNEL5, 501 | #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] 502 | pub channel6: CHANNEL6, 503 | #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] 504 | pub channel7: CHANNEL7, 505 | } 506 | 507 | impl Peripheral { 508 | /// Creates a new instance of the LEDC peripheral. Typically one wants 509 | /// to use the instance [`ledc`](crate::peripherals::Peripherals::ledc) from 510 | /// the device peripherals obtained via 511 | /// [`peripherals::Peripherals::take()`](crate::peripherals::Peripherals::take()). 512 | /// 513 | /// # Safety 514 | /// 515 | /// It is safe to instantiate the LEDC peripheral exactly one time. 516 | /// Care has to be taken that this has not already been done elsewhere. 517 | pub unsafe fn new() -> Self { 518 | Self { 519 | timer0: TIMER0::new(), 520 | timer1: TIMER1::new(), 521 | timer2: TIMER2::new(), 522 | timer3: TIMER3::new(), 523 | channel0: CHANNEL0::new(), 524 | channel1: CHANNEL1::new(), 525 | channel2: CHANNEL2::new(), 526 | channel3: CHANNEL3::new(), 527 | channel4: CHANNEL4::new(), 528 | channel5: CHANNEL5::new(), 529 | #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] 530 | channel6: CHANNEL6::new(), 531 | #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] 532 | channel7: CHANNEL7::new(), 533 | } 534 | } 535 | } 536 | } 537 | -------------------------------------------------------------------------------- /src/can.rs: -------------------------------------------------------------------------------- 1 | //! CAN bus peripheral control. 2 | //! 3 | //! It is called Two-Wire Automotive Interface (TWAI) in ESP32 documentation. 4 | //! 5 | //! # Example 6 | //! 7 | //! Create a CAN peripheral and then transmit and receive a message. 8 | //! ``` 9 | //! use embedded_hal::can::nb::Can; 10 | //! use embedded_hal::can::Frame; 11 | //! use embedded_hal::can::StandardId; 12 | //! use esp_idf_hal::prelude::*; 13 | //! use esp_idf_hal::can; 14 | //! 15 | //! let peripherals = Peripherals::take().unwrap(); 16 | //! let pins = peripherals.pins; 17 | //! 18 | //! // filter to accept only CAN ID 881 19 | //! let filter = can::config::Filter::Standard {filter: 881, mask: 0x7FF }; 20 | //! // filter that accepts all CAN IDs 21 | //! // let filter = can::config::Filter::standard_allow_all(); 22 | //! 23 | //! let timing = can::config::Timing::B500K; 24 | //! let config = can::config::Config::new().filter(filter).timing(timing); 25 | //! let mut can = can::CanBus::new(peripherals.can, pins.gpio5, pins.gpio4, config).unwrap(); 26 | //! 27 | //! let tx_frame = can::Frame::new(StandardId::new(0x042).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7]).unwrap(); 28 | //! nb::block!(can.transmit(&tx_frame)).unwrap(); 29 | //! 30 | //! if let Ok(rx_frame) = nb::block!(can.receive()) { 31 | //! info!("rx {:}:", rx_frame); 32 | //! } 33 | //! ``` 34 | 35 | use core::marker::PhantomData; 36 | 37 | use esp_idf_sys::*; 38 | 39 | use crate::delay::portMAX_DELAY; 40 | use crate::gpio::*; 41 | 42 | crate::embedded_hal_error!( 43 | CanError, 44 | embedded_hal::can::Error, 45 | embedded_hal::can::ErrorKind 46 | ); 47 | 48 | crate::embedded_hal_error!( 49 | Can02Error, 50 | embedded_hal_0_2::can::Error, 51 | embedded_hal_0_2::can::ErrorKind 52 | ); 53 | 54 | pub mod config { 55 | use esp_idf_sys::*; 56 | 57 | /// CAN timing 58 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 59 | pub enum Timing { 60 | B25K, 61 | B50K, 62 | B100K, 63 | B125K, 64 | B250K, 65 | B500K, 66 | B800K, 67 | B1M, 68 | } 69 | 70 | impl From for twai_timing_config_t { 71 | fn from(resolution: Timing) -> Self { 72 | match resolution { 73 | Timing::B25K => twai_timing_config_t { 74 | brp: 128, 75 | tseg_1: 16, 76 | tseg_2: 8, 77 | sjw: 3, 78 | triple_sampling: false, 79 | }, 80 | Timing::B50K => twai_timing_config_t { 81 | brp: 80, 82 | tseg_1: 15, 83 | tseg_2: 4, 84 | sjw: 3, 85 | triple_sampling: false, 86 | }, 87 | Timing::B100K => twai_timing_config_t { 88 | brp: 40, 89 | tseg_1: 15, 90 | tseg_2: 4, 91 | sjw: 3, 92 | triple_sampling: false, 93 | }, 94 | Timing::B125K => twai_timing_config_t { 95 | brp: 32, 96 | tseg_1: 15, 97 | tseg_2: 4, 98 | sjw: 3, 99 | triple_sampling: false, 100 | }, 101 | Timing::B250K => twai_timing_config_t { 102 | brp: 16, 103 | tseg_1: 15, 104 | tseg_2: 4, 105 | sjw: 3, 106 | triple_sampling: false, 107 | }, 108 | Timing::B500K => twai_timing_config_t { 109 | brp: 8, 110 | tseg_1: 15, 111 | tseg_2: 4, 112 | sjw: 3, 113 | triple_sampling: false, 114 | }, 115 | Timing::B800K => twai_timing_config_t { 116 | brp: 4, 117 | tseg_1: 16, 118 | tseg_2: 8, 119 | sjw: 3, 120 | triple_sampling: false, 121 | }, 122 | Timing::B1M => twai_timing_config_t { 123 | brp: 4, 124 | tseg_1: 15, 125 | tseg_2: 4, 126 | sjw: 3, 127 | triple_sampling: false, 128 | }, 129 | } 130 | } 131 | } 132 | 133 | impl Default for Timing { 134 | fn default() -> Self { 135 | Self::B500K 136 | } 137 | } 138 | 139 | /// Is used to filter out unwanted CAN IDs (messages). 140 | /// 141 | /// Notice that Espressif TWAI (CAN in rest of the world) acceptance filtering 142 | /// works differently than common CAN filtering (for example mask bits are inversed). 143 | /// However here those differences are hidden away from the user and common CAN filtering is used. 144 | /// 145 | /// `mask` is used to determine which bits in the incoming CAN ID are compared with the `filter` value. 146 | /// Bits in `mask` mean: 147 | /// `0`: do not care - the bit is not used for the comparison 148 | /// `1`: must match - the bit of the incoming CAN ID must have the same state as in `filter` 149 | /// 150 | /// Notice that if `mask` is `0`, all CAN IDs are accepted regardless of `filter` value. 151 | /// 152 | /// ## Examples 153 | /// 154 | /// This shows how 11 bit CAN ID `0x3AA` goes through filtering engine and is finally accepted: 155 | /// ``` 156 | /// // incoming id [ 0 1 1 1 0 1 0 1 0 1 0 ] 157 | /// // mask [ 1 0 1 0 0 1 1 1 0 0 0 ] 158 | /// // 1 = compare 159 | /// // 0 = do not care 160 | /// // masked id [ 0 _ 1 _ _ 1 0 1 _ _ _ ] 161 | /// // filter [ 0 0 1 1 1 1 0 1 0 1 1 ] 162 | /// 163 | /// // incoming id [ 0 1 1 1 0 1 0 1 0 1 0 ] 164 | /// // accepted 165 | /// ``` 166 | /// 167 | /// Notice that for example `0x7AA` would not be accepted because its MSB bit is `1`, 168 | /// but `filter` only accepts `0` in this bit position and `mask` says that this bit must be compared. 169 | /// 170 | /// Accept only CAN ID `0x567` 171 | /// ``` 172 | /// let filter = 0x567; 173 | /// // every bit must match filter 174 | /// let mask = 0x7FF; 175 | /// let f = Filter::Standard { filter, mask }; 176 | /// ``` 177 | /// 178 | /// Accept CAN IDs `0x560 - 0x56F` 179 | /// ``` 180 | /// let filter = 0x560; 181 | /// // do not care about 4 LSB bits 182 | /// let mask = 0x7F0; 183 | /// let f = Filter::Standard { filter, mask }; 184 | /// ``` 185 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 186 | pub enum Filter { 187 | // Filter for 11 bit standard CAN IDs 188 | Standard { filter: u16, mask: u16 }, 189 | // Filter for 29 bit extended CAN IDs 190 | Extended { filter: u32, mask: u32 }, 191 | } 192 | 193 | impl Filter { 194 | /// Filter that allows all standard CAN IDs. 195 | pub fn standard_allow_all() -> Self { 196 | Self::Standard { filter: 0, mask: 0 } 197 | } 198 | 199 | /// Filter that accepts all extended CAN IDs. 200 | pub fn extended_allow_all() -> Self { 201 | Self::Extended { filter: 0, mask: 0 } 202 | } 203 | } 204 | 205 | impl Default for Filter { 206 | fn default() -> Self { 207 | Filter::standard_allow_all() 208 | } 209 | } 210 | 211 | #[derive(Debug, Copy, Clone, Default)] 212 | pub struct Config { 213 | pub timing: Timing, 214 | pub filter: Filter, 215 | } 216 | 217 | impl Config { 218 | pub fn new() -> Self { 219 | Default::default() 220 | } 221 | 222 | #[must_use] 223 | pub fn timing(mut self, timing: Timing) -> Self { 224 | self.timing = timing; 225 | self 226 | } 227 | 228 | #[must_use] 229 | pub fn filter(mut self, filter: Filter) -> Self { 230 | self.filter = filter; 231 | self 232 | } 233 | } 234 | } 235 | 236 | /// CAN abstraction 237 | pub struct CanBus { 238 | can: CAN, 239 | tx: TX, 240 | rx: RX, 241 | } 242 | 243 | unsafe impl Send for CanBus {} 244 | 245 | impl CanBus { 246 | pub fn new(can: CAN, tx: TX, rx: RX, config: config::Config) -> Result { 247 | let general_config = twai_general_config_t { 248 | mode: twai_mode_t_TWAI_MODE_NORMAL, 249 | tx_io: tx.pin(), 250 | rx_io: rx.pin(), 251 | clkout_io: -1, 252 | bus_off_io: -1, 253 | tx_queue_len: 5, 254 | rx_queue_len: 5, 255 | alerts_enabled: TWAI_ALERT_NONE, 256 | clkout_divider: 0, 257 | intr_flags: ESP_INTR_FLAG_LEVEL1 as i32, 258 | }; 259 | 260 | let timing_config = config.timing.into(); 261 | 262 | // modify filter and mask to be compatible with TWAI acceptance filter 263 | let (filter, mask) = match config.filter { 264 | config::Filter::Standard { filter, mask } => { 265 | ((filter as u32) << 21, !((mask as u32) << 21)) 266 | } 267 | config::Filter::Extended { filter, mask } => (filter << 3, !(mask << 3)), 268 | }; 269 | 270 | let filter_config = twai_filter_config_t { 271 | acceptance_code: filter, 272 | acceptance_mask: mask, 273 | single_filter: true, 274 | }; 275 | 276 | esp!(unsafe { twai_driver_install(&general_config, &timing_config, &filter_config) })?; 277 | esp!(unsafe { twai_start() })?; 278 | 279 | Ok(Self { can, tx, rx }) 280 | } 281 | 282 | pub fn release(self) -> Result<(CAN, TX, RX), EspError> { 283 | esp!(unsafe { twai_stop() })?; 284 | esp!(unsafe { twai_driver_uninstall() })?; 285 | 286 | Ok((self.can, self.tx, self.rx)) 287 | } 288 | 289 | fn transmit_internal(&mut self, frame: &Frame, delay: TickType_t) -> Result<(), EspError> { 290 | esp!(unsafe { twai_transmit(&frame.0, delay) }) 291 | } 292 | 293 | fn receive_internal(&mut self, delay: TickType_t) -> Result { 294 | let mut rx_msg = twai_message_t { 295 | ..Default::default() 296 | }; 297 | 298 | match esp_result!(unsafe { twai_receive(&mut rx_msg, delay) }, ()) { 299 | Ok(_) => Ok(Frame(rx_msg)), 300 | Err(err) => Err(err), 301 | } 302 | } 303 | } 304 | 305 | impl embedded_hal_0_2::blocking::can::Can for CanBus { 306 | type Frame = Frame; 307 | type Error = Can02Error; 308 | 309 | fn transmit(&mut self, frame: &Self::Frame) -> Result<(), Self::Error> { 310 | self.transmit_internal(frame, portMAX_DELAY) 311 | .map_err(Can02Error::other) 312 | } 313 | 314 | fn receive(&mut self) -> Result { 315 | self.receive_internal(portMAX_DELAY) 316 | .map_err(Can02Error::other) 317 | } 318 | } 319 | 320 | impl embedded_hal::can::blocking::Can for CanBus { 321 | type Frame = Frame; 322 | type Error = CanError; 323 | 324 | fn transmit(&mut self, frame: &Self::Frame) -> Result<(), Self::Error> { 325 | self.transmit_internal(frame, portMAX_DELAY) 326 | .map_err(CanError::other) 327 | } 328 | 329 | fn receive(&mut self) -> Result { 330 | self.receive_internal(portMAX_DELAY) 331 | .map_err(CanError::other) 332 | } 333 | } 334 | 335 | impl embedded_hal_0_2::can::nb::Can for CanBus { 336 | type Frame = Frame; 337 | type Error = Can02Error; 338 | 339 | fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { 340 | match self.transmit_internal(frame, 0) { 341 | Ok(_) => Ok(None), 342 | Err(e) if e.code() == ESP_FAIL => Err(nb::Error::WouldBlock), 343 | Err(e) if e.code() == ESP_ERR_TIMEOUT as i32 => Err(nb::Error::WouldBlock), 344 | Err(e) => Err(nb::Error::Other(Can02Error::other(e))), 345 | } 346 | } 347 | 348 | fn receive(&mut self) -> nb::Result { 349 | match self.receive_internal(0) { 350 | Ok(frame) => Ok(frame), 351 | Err(e) if e.code() == ESP_ERR_TIMEOUT as i32 => Err(nb::Error::WouldBlock), 352 | Err(e) => Err(nb::Error::Other(Can02Error::other(e))), 353 | } 354 | } 355 | } 356 | 357 | impl embedded_hal::can::nb::Can for CanBus { 358 | type Frame = Frame; 359 | type Error = CanError; 360 | 361 | fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { 362 | match self.transmit_internal(frame, 0) { 363 | Ok(_) => Ok(None), 364 | Err(e) if e.code() == ESP_FAIL => Err(nb::Error::WouldBlock), 365 | Err(e) if e.code() == ESP_ERR_TIMEOUT as i32 => Err(nb::Error::WouldBlock), 366 | Err(e) => Err(nb::Error::Other(CanError::other(e))), 367 | } 368 | } 369 | 370 | fn receive(&mut self) -> nb::Result { 371 | match self.receive_internal(0) { 372 | Ok(frame) => Ok(frame), 373 | Err(e) if e.code() == ESP_ERR_TIMEOUT as i32 => Err(nb::Error::WouldBlock), 374 | Err(e) => Err(nb::Error::Other(CanError::other(e))), 375 | } 376 | } 377 | } 378 | 379 | pub struct Frame(twai_message_t); 380 | 381 | impl Frame { 382 | fn new(id: u32, extended: bool, data: &[u8]) -> Option { 383 | let dlc = data.len(); 384 | 385 | if dlc <= 8 { 386 | // unions are not very well supported in rust 387 | // therefore setting those union flags is quite hairy 388 | let mut flags = twai_message_t__bindgen_ty_1::default(); 389 | 390 | // set bits in an union 391 | unsafe { flags.__bindgen_anon_1.set_ss(1) }; 392 | if extended { 393 | unsafe { flags.__bindgen_anon_1.set_extd(1) }; 394 | } 395 | 396 | let mut payload = [0; 8]; 397 | payload[..dlc].copy_from_slice(data); 398 | 399 | let twai_message = twai_message_t { 400 | __bindgen_anon_1: flags, 401 | identifier: id, 402 | data_length_code: dlc as u8, 403 | data: payload, 404 | }; 405 | 406 | Some(Frame(twai_message)) 407 | } else { 408 | None 409 | } 410 | } 411 | 412 | fn new_remote(id: u32, extended: bool, dlc: usize) -> Option { 413 | if dlc <= 8 { 414 | // unions are not very well supported in rust 415 | // therefore setting those union flags is quite hairy 416 | let mut flags = twai_message_t__bindgen_ty_1::default(); 417 | 418 | // set bits in an union 419 | unsafe { flags.__bindgen_anon_1.set_rtr(1) }; 420 | unsafe { flags.__bindgen_anon_1.set_ss(1) }; 421 | if extended { 422 | unsafe { flags.__bindgen_anon_1.set_extd(1) }; 423 | } 424 | 425 | let twai_message = twai_message_t { 426 | __bindgen_anon_1: flags, 427 | identifier: id, 428 | data_length_code: dlc as u8, 429 | data: [0; 8], 430 | }; 431 | 432 | Some(Frame(twai_message)) 433 | } else { 434 | None 435 | } 436 | } 437 | 438 | fn get_extended(&self) -> bool { 439 | unsafe { self.0.__bindgen_anon_1.__bindgen_anon_1.extd() == 1 } 440 | } 441 | 442 | fn get_remote_frame(&self) -> bool { 443 | unsafe { self.0.__bindgen_anon_1.__bindgen_anon_1.rtr() == 1 } 444 | } 445 | 446 | fn get_identifier(&self) -> u32 { 447 | self.0.identifier 448 | } 449 | 450 | fn get_dlc(&self) -> usize { 451 | self.0.data_length_code as usize 452 | } 453 | 454 | fn get_data(&self) -> &[u8] { 455 | &self.0.data[..self.get_dlc()] 456 | } 457 | } 458 | 459 | impl core::fmt::Display for Frame { 460 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 461 | write!( 462 | f, 463 | "Frame {{ id: {}, remote: {}, data: {:?} }}", 464 | self.get_identifier(), 465 | self.get_remote_frame(), 466 | self.get_data() 467 | ) 468 | } 469 | } 470 | 471 | impl embedded_hal_0_2::can::Frame for Frame { 472 | fn new(id: impl Into, data: &[u8]) -> Option { 473 | let (id, extended) = match id.into() { 474 | embedded_hal_0_2::can::Id::Standard(id) => (id.as_raw() as u32, false), 475 | embedded_hal_0_2::can::Id::Extended(id) => (id.as_raw(), true), 476 | }; 477 | 478 | Self::new(id, extended, data) 479 | } 480 | 481 | fn new_remote(id: impl Into, dlc: usize) -> Option { 482 | let (id, extended) = match id.into() { 483 | embedded_hal_0_2::can::Id::Standard(id) => (id.as_raw() as u32, false), 484 | embedded_hal_0_2::can::Id::Extended(id) => (id.as_raw(), true), 485 | }; 486 | 487 | Self::new_remote(id, extended, dlc) 488 | } 489 | 490 | fn is_extended(&self) -> bool { 491 | self.get_extended() 492 | } 493 | 494 | fn is_standard(&self) -> bool { 495 | !self.is_extended() 496 | } 497 | 498 | fn is_remote_frame(&self) -> bool { 499 | self.get_remote_frame() 500 | } 501 | 502 | fn is_data_frame(&self) -> bool { 503 | !self.is_remote_frame() 504 | } 505 | 506 | fn id(&self) -> embedded_hal_0_2::can::Id { 507 | if self.is_standard() { 508 | let id = unsafe { 509 | embedded_hal_0_2::can::StandardId::new_unchecked(self.get_identifier() as u16) 510 | }; 511 | embedded_hal_0_2::can::Id::Standard(id) 512 | } else { 513 | let id = 514 | unsafe { embedded_hal_0_2::can::ExtendedId::new_unchecked(self.get_identifier()) }; 515 | embedded_hal_0_2::can::Id::Extended(id) 516 | } 517 | } 518 | 519 | fn dlc(&self) -> usize { 520 | self.get_dlc() 521 | } 522 | 523 | fn data(&self) -> &[u8] { 524 | self.get_data() 525 | } 526 | } 527 | 528 | impl embedded_hal::can::Frame for Frame { 529 | fn new(id: impl Into, data: &[u8]) -> Option { 530 | let (id, extended) = match id.into() { 531 | embedded_hal::can::Id::Standard(id) => (id.as_raw() as u32, false), 532 | embedded_hal::can::Id::Extended(id) => (id.as_raw(), true), 533 | }; 534 | 535 | Self::new(id, extended, data) 536 | } 537 | 538 | fn new_remote(id: impl Into, dlc: usize) -> Option { 539 | let (id, extended) = match id.into() { 540 | embedded_hal::can::Id::Standard(id) => (id.as_raw() as u32, false), 541 | embedded_hal::can::Id::Extended(id) => (id.as_raw(), true), 542 | }; 543 | 544 | Self::new_remote(id, extended, dlc) 545 | } 546 | 547 | fn is_extended(&self) -> bool { 548 | self.get_extended() 549 | } 550 | 551 | fn is_standard(&self) -> bool { 552 | !self.is_extended() 553 | } 554 | 555 | fn is_remote_frame(&self) -> bool { 556 | self.get_remote_frame() 557 | } 558 | 559 | fn is_data_frame(&self) -> bool { 560 | !self.is_remote_frame() 561 | } 562 | 563 | fn id(&self) -> embedded_hal::can::Id { 564 | if self.is_standard() { 565 | let id = unsafe { 566 | embedded_hal::can::StandardId::new_unchecked(self.get_identifier() as u16) 567 | }; 568 | embedded_hal::can::Id::Standard(id) 569 | } else { 570 | let id = unsafe { embedded_hal::can::ExtendedId::new_unchecked(self.get_identifier()) }; 571 | embedded_hal::can::Id::Extended(id) 572 | } 573 | } 574 | 575 | fn dlc(&self) -> usize { 576 | self.get_dlc() 577 | } 578 | 579 | fn data(&self) -> &[u8] { 580 | self.get_data() 581 | } 582 | } 583 | 584 | pub struct CAN(PhantomData<*const ()>); 585 | 586 | impl CAN { 587 | /// # Safety 588 | /// 589 | /// Care should be taken not to instnatiate this CAN instance, if it is already instantiated and used elsewhere 590 | pub unsafe fn new() -> Self { 591 | CAN(PhantomData) 592 | } 593 | } 594 | 595 | unsafe impl Send for CAN {} 596 | -------------------------------------------------------------------------------- /src/i2c.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource}; 3 | use esp_idf_sys::*; 4 | 5 | use crate::{delay::*, gpio::*, units::*}; 6 | 7 | crate::embedded_hal_error!( 8 | I2cError, 9 | embedded_hal::i2c::Error, 10 | embedded_hal::i2c::ErrorKind 11 | ); 12 | 13 | pub struct MasterPins { 14 | pub sda: SDA, 15 | pub scl: SCL, 16 | } 17 | 18 | pub struct SlavePins { 19 | pub sda: SDA, 20 | pub scl: SCL, 21 | } 22 | 23 | /// I2C configuration 24 | pub mod config { 25 | use crate::units::*; 26 | use core::time::Duration; 27 | 28 | /// I2C Master configuration 29 | #[derive(Copy, Clone)] 30 | pub struct MasterConfig { 31 | pub baudrate: Hertz, 32 | pub timeout: Option, 33 | pub sda_pullup_enabled: bool, 34 | pub scl_pullup_enabled: bool, 35 | } 36 | 37 | impl MasterConfig { 38 | pub fn new() -> Self { 39 | Default::default() 40 | } 41 | 42 | #[must_use] 43 | pub fn baudrate(mut self, baudrate: Hertz) -> Self { 44 | self.baudrate = baudrate; 45 | self 46 | } 47 | 48 | #[must_use] 49 | pub fn timeout(mut self, timeout: Option) -> Self { 50 | self.timeout = timeout; 51 | self 52 | } 53 | 54 | #[must_use] 55 | pub fn sda_enable_pullup(mut self, enable: bool) -> Self { 56 | self.sda_pullup_enabled = enable; 57 | self 58 | } 59 | 60 | #[must_use] 61 | pub fn scl_enable_pullup(mut self, enable: bool) -> Self { 62 | self.scl_pullup_enabled = enable; 63 | self 64 | } 65 | } 66 | 67 | impl Default for MasterConfig { 68 | fn default() -> Self { 69 | Self { 70 | baudrate: Hertz(1_000_000), 71 | timeout: None, 72 | sda_pullup_enabled: true, 73 | scl_pullup_enabled: true, 74 | } 75 | } 76 | } 77 | 78 | /// I2C Slave configuration 79 | #[derive(Copy, Clone)] 80 | pub struct SlaveConfig { 81 | pub timeout: Option, 82 | pub sda_pullup_enabled: bool, 83 | pub scl_pullup_enabled: bool, 84 | pub rx_buf_len: usize, 85 | pub tx_buf_len: usize, 86 | } 87 | 88 | impl SlaveConfig { 89 | pub fn new() -> Self { 90 | Default::default() 91 | } 92 | 93 | #[must_use] 94 | pub fn timeout(mut self, timeout: Option) -> Self { 95 | self.timeout = timeout; 96 | self 97 | } 98 | 99 | #[must_use] 100 | pub fn sda_enable_pullup(mut self, enable: bool) -> Self { 101 | self.sda_pullup_enabled = enable; 102 | self 103 | } 104 | 105 | #[must_use] 106 | pub fn scl_enable_pullup(mut self, enable: bool) -> Self { 107 | self.scl_pullup_enabled = enable; 108 | self 109 | } 110 | 111 | #[must_use] 112 | pub fn rx_buffer_length(mut self, len: usize) -> Self { 113 | self.rx_buf_len = len; 114 | self 115 | } 116 | 117 | #[must_use] 118 | pub fn tx_buffer_length(mut self, len: usize) -> Self { 119 | self.tx_buf_len = len; 120 | self 121 | } 122 | } 123 | 124 | impl Default for SlaveConfig { 125 | fn default() -> Self { 126 | Self { 127 | timeout: None, 128 | sda_pullup_enabled: true, 129 | scl_pullup_enabled: true, 130 | rx_buf_len: 0, 131 | tx_buf_len: 0, 132 | } 133 | } 134 | } 135 | } 136 | 137 | pub trait I2c: Send { 138 | fn port() -> i2c_port_t; 139 | } 140 | 141 | unsafe impl Send for Master {} 142 | 143 | pub struct Master 144 | where 145 | I2C: I2c, 146 | SDA: OutputPin + InputPin, 147 | SCL: OutputPin, 148 | { 149 | i2c: I2C, 150 | pins: MasterPins, 151 | timeout: TickType_t, 152 | } 153 | 154 | pub struct Slave 155 | where 156 | I2C: I2c, 157 | SDA: OutputPin + InputPin, 158 | SCL: InputPin, 159 | { 160 | i2c: I2C, 161 | pins: SlavePins, 162 | timeout: TickType_t, 163 | } 164 | 165 | unsafe impl Send for Slave {} 166 | 167 | impl Master 168 | where 169 | I2C: I2c, 170 | SDA: OutputPin + InputPin, 171 | SCL: OutputPin, 172 | { 173 | pub fn new( 174 | i2c: I2C, 175 | pins: MasterPins, 176 | config: config::MasterConfig, 177 | ) -> Result, EspError> { 178 | // i2c_config_t documentation says that clock speed must be no higher than 1 MHz 179 | if config.baudrate > 1.MHz().into() { 180 | return Err(EspError::from(ESP_ERR_INVALID_ARG as i32).unwrap()); 181 | } 182 | 183 | let sys_config = i2c_config_t { 184 | mode: i2c_mode_t_I2C_MODE_MASTER, 185 | sda_io_num: pins.sda.pin(), 186 | sda_pullup_en: config.sda_pullup_enabled, 187 | scl_io_num: pins.scl.pin(), 188 | scl_pullup_en: config.scl_pullup_enabled, 189 | __bindgen_anon_1: i2c_config_t__bindgen_ty_1 { 190 | master: i2c_config_t__bindgen_ty_1__bindgen_ty_1 { 191 | clk_speed: config.baudrate.into(), 192 | }, 193 | }, 194 | ..Default::default() 195 | }; 196 | 197 | esp!(unsafe { i2c_param_config(I2C::port(), &sys_config) })?; 198 | 199 | esp!(unsafe { 200 | i2c_driver_install( 201 | I2C::port(), 202 | i2c_mode_t_I2C_MODE_MASTER, 203 | 0, // Not used in master mode 204 | 0, // Not used in master mode 205 | 0, 206 | ) // TODO: set flags 207 | })?; 208 | 209 | Ok(Master { 210 | i2c, 211 | pins, 212 | timeout: TickType::from(config.timeout).0, 213 | }) 214 | } 215 | 216 | pub fn release(self) -> Result<(I2C, MasterPins), EspError> { 217 | esp!(unsafe { i2c_driver_delete(I2C::port()) })?; 218 | 219 | //self.pins.sda.reset()?; 220 | //self.pins.scl.reset()?; 221 | 222 | Ok((self.i2c, self.pins)) 223 | } 224 | 225 | fn cmd_begin( 226 | &mut self, 227 | command_link: &CommandLink, 228 | timeout: TickType_t, 229 | ) -> Result<(), EspError> { 230 | esp!(unsafe { i2c_master_cmd_begin(I2C::port(), command_link.0, timeout) }) 231 | } 232 | 233 | fn submit(&mut self, command_link: &CommandLink) -> Result<(), I2cError> { 234 | if let Err(err) = self.cmd_begin(command_link, self.timeout) { 235 | let err = if err.code() == ESP_FAIL { 236 | I2cError::new(ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), err) 237 | } else { 238 | I2cError::other(err) 239 | }; 240 | Err(err) 241 | } else { 242 | Ok(()) 243 | } 244 | } 245 | } 246 | 247 | impl embedded_hal_0_2::blocking::i2c::Read for Master 248 | where 249 | I2C: I2c, 250 | SDA: OutputPin + InputPin, 251 | SCL: OutputPin, 252 | { 253 | type Error = I2cError; 254 | 255 | fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 256 | embedded_hal::i2c::blocking::I2c::read(self, addr, buffer) 257 | } 258 | } 259 | 260 | impl embedded_hal_0_2::blocking::i2c::Write for Master 261 | where 262 | I2C: I2c, 263 | SDA: OutputPin + InputPin, 264 | SCL: OutputPin, 265 | { 266 | type Error = I2cError; 267 | 268 | fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { 269 | embedded_hal::i2c::blocking::I2c::write(self, addr, bytes) 270 | } 271 | } 272 | 273 | impl embedded_hal_0_2::blocking::i2c::WriteRead for Master 274 | where 275 | I2C: I2c, 276 | SDA: OutputPin + InputPin, 277 | SCL: OutputPin, 278 | { 279 | type Error = I2cError; 280 | 281 | fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { 282 | embedded_hal::i2c::blocking::I2c::write_read(self, addr, bytes, buffer) 283 | } 284 | } 285 | 286 | impl embedded_hal::i2c::ErrorType for Master 287 | where 288 | I2C: I2c, 289 | SDA: OutputPin + InputPin, 290 | SCL: OutputPin, 291 | { 292 | type Error = I2cError; 293 | } 294 | 295 | impl embedded_hal::i2c::blocking::I2c 296 | for Master 297 | where 298 | I2C: I2c, 299 | SDA: OutputPin + InputPin, 300 | SCL: OutputPin, 301 | { 302 | fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 303 | let mut command_link = CommandLink::new().map_err(I2cError::other)?; 304 | 305 | command_link.master_start().map_err(I2cError::other)?; 306 | command_link 307 | .master_write_byte((addr << 1) | (i2c_rw_t_I2C_MASTER_READ as u8), true) 308 | .map_err(I2cError::other)?; 309 | if !buffer.is_empty() { 310 | command_link 311 | .master_read(buffer, AckType::LastNack) 312 | .map_err(I2cError::other)?; 313 | } 314 | command_link.master_stop().map_err(I2cError::other)?; 315 | 316 | self.submit(&command_link) 317 | } 318 | 319 | fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { 320 | let mut command_link = CommandLink::new().map_err(I2cError::other)?; 321 | 322 | command_link.master_start().map_err(I2cError::other)?; 323 | 324 | command_link 325 | .master_write_byte((addr << 1) | (i2c_rw_t_I2C_MASTER_WRITE as u8), true) 326 | .map_err(I2cError::other)?; 327 | 328 | if !bytes.is_empty() { 329 | command_link 330 | .master_write(bytes, true) 331 | .map_err(I2cError::other)?; 332 | } 333 | command_link.master_stop().map_err(I2cError::other)?; 334 | 335 | self.submit(&command_link) 336 | } 337 | 338 | fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { 339 | let mut command_link = CommandLink::new().map_err(I2cError::other)?; 340 | 341 | command_link.master_start().map_err(I2cError::other)?; 342 | command_link 343 | .master_write_byte((addr << 1) | (i2c_rw_t_I2C_MASTER_WRITE as u8), true) 344 | .map_err(I2cError::other)?; 345 | if !bytes.is_empty() { 346 | command_link 347 | .master_write(bytes, true) 348 | .map_err(I2cError::other)?; 349 | } 350 | 351 | command_link.master_start().map_err(I2cError::other)?; 352 | command_link 353 | .master_write_byte((addr << 1) | (i2c_rw_t_I2C_MASTER_READ as u8), true) 354 | .map_err(I2cError::other)?; 355 | if !buffer.is_empty() { 356 | command_link 357 | .master_read(buffer, AckType::LastNack) 358 | .map_err(I2cError::other)?; 359 | } 360 | 361 | command_link.master_stop().map_err(I2cError::other)?; 362 | 363 | self.submit(&command_link) 364 | } 365 | 366 | fn write_iter(&mut self, _address: u8, _bytes: B) -> Result<(), Self::Error> 367 | where 368 | B: IntoIterator, 369 | { 370 | todo!() 371 | } 372 | 373 | fn write_iter_read( 374 | &mut self, 375 | _address: u8, 376 | _bytes: B, 377 | _buffer: &mut [u8], 378 | ) -> Result<(), Self::Error> 379 | where 380 | B: IntoIterator, 381 | { 382 | todo!() 383 | } 384 | 385 | fn transaction<'a>( 386 | &mut self, 387 | address: u8, 388 | operations: &mut [embedded_hal::i2c::blocking::Operation<'a>], 389 | ) -> Result<(), Self::Error> { 390 | use embedded_hal::i2c::blocking::Operation; 391 | 392 | let mut command_link = CommandLink::new().map_err(I2cError::other)?; 393 | 394 | command_link.master_start().map_err(I2cError::other)?; 395 | 396 | let last_op_index = operations.len() - 1; 397 | let mut prev_was_read = None; 398 | 399 | for (i, operation) in operations.iter_mut().enumerate() { 400 | match operation { 401 | Operation::Read(buf) => { 402 | if let Some(false) = prev_was_read { 403 | command_link.master_start().map_err(I2cError::other)?; 404 | } 405 | prev_was_read = Some(true); 406 | 407 | command_link 408 | .master_write_byte((address << 1) | (i2c_rw_t_I2C_MASTER_READ as u8), true) 409 | .map_err(I2cError::other)?; 410 | if !buf.is_empty() { 411 | let ack = if i == last_op_index { 412 | AckType::LastNack 413 | } else { 414 | AckType::Ack 415 | }; 416 | command_link 417 | .master_read(*buf, ack) 418 | .map_err(I2cError::other)?; 419 | } 420 | } 421 | Operation::Write(buf) => { 422 | if let Some(true) = prev_was_read { 423 | command_link.master_start().map_err(I2cError::other)?; 424 | } 425 | prev_was_read = Some(false); 426 | 427 | command_link 428 | .master_write_byte((address << 1) | (i2c_rw_t_I2C_MASTER_WRITE as u8), true) 429 | .map_err(I2cError::other)?; 430 | if !buf.is_empty() { 431 | command_link 432 | .master_write(buf, true) 433 | .map_err(I2cError::other)?; 434 | } 435 | } 436 | } 437 | } 438 | 439 | command_link.master_stop().map_err(I2cError::other)?; 440 | 441 | self.submit(&command_link) 442 | } 443 | 444 | fn transaction_iter<'a, O>(&mut self, _address: u8, _operations: O) -> Result<(), Self::Error> 445 | where 446 | O: IntoIterator>, 447 | { 448 | todo!() 449 | } 450 | } 451 | 452 | impl Slave 453 | where 454 | I2C: I2c, 455 | SDA: OutputPin + InputPin, 456 | SCL: InputPin, 457 | { 458 | pub fn new( 459 | i2c: I2C, 460 | pins: SlavePins, 461 | slave_addr: u8, 462 | config: config::SlaveConfig, 463 | ) -> Result { 464 | #[cfg(any(esp_idf_version = "4.4", esp_idf_version_major = "5"))] 465 | let sys_config = i2c_config_t { 466 | mode: i2c_mode_t_I2C_MODE_SLAVE, 467 | sda_io_num: pins.sda.pin(), 468 | sda_pullup_en: config.sda_pullup_enabled, 469 | scl_io_num: pins.scl.pin(), 470 | scl_pullup_en: config.scl_pullup_enabled, 471 | __bindgen_anon_1: i2c_config_t__bindgen_ty_1 { 472 | slave: i2c_config_t__bindgen_ty_1__bindgen_ty_2 { 473 | slave_addr: slave_addr as u16, 474 | addr_10bit_en: 0, // For now; to become configurable with embedded-hal V1.0 475 | maximum_speed: 0, 476 | }, 477 | }, 478 | ..Default::default() 479 | }; 480 | 481 | #[cfg(not(any(esp_idf_version = "4.4", esp_idf_version_major = "5")))] 482 | let sys_config = i2c_config_t { 483 | mode: i2c_mode_t_I2C_MODE_SLAVE, 484 | sda_io_num: pins.sda.pin(), 485 | sda_pullup_en: config.sda_pullup_enabled, 486 | scl_io_num: pins.scl.pin(), 487 | scl_pullup_en: config.scl_pullup_enabled, 488 | __bindgen_anon_1: i2c_config_t__bindgen_ty_1 { 489 | slave: i2c_config_t__bindgen_ty_1__bindgen_ty_2 { 490 | slave_addr: slave_addr as u16, 491 | addr_10bit_en: 0, // For now; to become configurable with embedded-hal V1.0 492 | }, 493 | }, 494 | ..Default::default() 495 | }; 496 | 497 | esp!(unsafe { i2c_param_config(I2C::port(), &sys_config) })?; 498 | 499 | esp!(unsafe { 500 | i2c_driver_install( 501 | I2C::port(), 502 | i2c_mode_t_I2C_MODE_SLAVE, 503 | config.rx_buf_len as u32, 504 | config.tx_buf_len as u32, 505 | 0, // TODO: set flags 506 | ) 507 | })?; 508 | 509 | Ok(Self { 510 | i2c, 511 | pins, 512 | timeout: TickType::from(config.timeout).0, 513 | }) 514 | } 515 | 516 | pub fn release(self) -> Result<(I2C, SlavePins), EspError> { 517 | esp!(unsafe { i2c_driver_delete(I2C::port()) })?; 518 | 519 | //self.pins.sda.reset()?; 520 | //self.pins.scl.reset()?; 521 | 522 | Ok((self.i2c, self.pins)) 523 | } 524 | 525 | pub fn read(&mut self, buffer: &mut [u8]) -> Result { 526 | let n = unsafe { 527 | i2c_slave_read_buffer( 528 | I2C::port(), 529 | buffer.as_mut_ptr(), 530 | buffer.len() as u32, 531 | self.timeout, 532 | ) 533 | }; 534 | 535 | if n > 0 { 536 | Ok(n as usize) 537 | } else { 538 | Err(EspError::from(ESP_ERR_TIMEOUT as i32).unwrap()) 539 | } 540 | } 541 | 542 | pub fn write(&mut self, bytes: &[u8]) -> Result { 543 | let n = unsafe { 544 | i2c_slave_write_buffer( 545 | I2C::port(), 546 | bytes.as_ptr() as *const u8 as *mut u8, 547 | bytes.len() as i32, 548 | self.timeout, 549 | ) 550 | }; 551 | 552 | if n > 0 { 553 | Ok(n as usize) 554 | } else { 555 | Err(EspError::from(ESP_ERR_TIMEOUT as i32).unwrap()) 556 | } 557 | } 558 | } 559 | 560 | #[repr(u32)] 561 | enum AckType { 562 | Ack = i2c_ack_type_t_I2C_MASTER_ACK, 563 | #[allow(dead_code)] 564 | Nack = i2c_ack_type_t_I2C_MASTER_NACK, 565 | LastNack = i2c_ack_type_t_I2C_MASTER_LAST_NACK, 566 | } 567 | 568 | struct CommandLink<'buffers>(i2c_cmd_handle_t, PhantomData<&'buffers u8>); 569 | 570 | impl<'buffers> CommandLink<'buffers> { 571 | fn new() -> Result { 572 | let handle = unsafe { i2c_cmd_link_create() }; 573 | 574 | if handle.is_null() { 575 | return Err(EspError::from(ESP_ERR_NO_MEM as i32).unwrap()); 576 | } 577 | 578 | Ok(CommandLink(handle, PhantomData)) 579 | } 580 | 581 | fn master_start(&mut self) -> Result<(), EspError> { 582 | esp!(unsafe { i2c_master_start(self.0) }) 583 | } 584 | 585 | fn master_stop(&mut self) -> Result<(), EspError> { 586 | esp!(unsafe { i2c_master_stop(self.0) }) 587 | } 588 | 589 | fn master_write_byte(&mut self, data: u8, ack_en: bool) -> Result<(), EspError> { 590 | esp!(unsafe { i2c_master_write_byte(self.0, data, ack_en) }) 591 | } 592 | 593 | fn master_write(&mut self, buf: &'buffers [u8], ack_en: bool) -> Result<(), EspError> { 594 | esp!(unsafe { 595 | i2c_master_write( 596 | self.0, 597 | buf.as_ptr() as *const u8 as *mut u8, 598 | buf.len() as u32, 599 | ack_en, 600 | ) 601 | }) 602 | } 603 | 604 | fn master_read(&mut self, buf: &'buffers mut [u8], ack: AckType) -> Result<(), EspError> { 605 | esp!(unsafe { 606 | i2c_master_read( 607 | self.0, 608 | buf.as_ptr() as *const u8 as *mut u8, 609 | buf.len() as u32, 610 | ack as u32, 611 | ) 612 | }) 613 | } 614 | } 615 | 616 | impl<'buffers> Drop for CommandLink<'buffers> { 617 | fn drop(&mut self) { 618 | unsafe { 619 | i2c_cmd_link_delete(self.0); 620 | } 621 | } 622 | } 623 | 624 | macro_rules! impl_i2c { 625 | ($i2c:ident: $port:expr) => { 626 | pub struct $i2c(::core::marker::PhantomData<*const ()>); 627 | 628 | impl $i2c { 629 | /// # Safety 630 | /// 631 | /// Care should be taken not to instnatiate this I2C instance, if it is already instantiated and used elsewhere 632 | pub unsafe fn new() -> Self { 633 | $i2c(::core::marker::PhantomData) 634 | } 635 | } 636 | 637 | unsafe impl Send for $i2c {} 638 | 639 | impl I2c for $i2c { 640 | #[inline(always)] 641 | fn port() -> i2c_port_t { 642 | $port 643 | } 644 | } 645 | }; 646 | } 647 | 648 | impl_i2c!(I2C0: 0); 649 | 650 | #[cfg(not(esp32c3))] 651 | impl_i2c!(I2C1: 1); 652 | --------------------------------------------------------------------------------