From 7deb880e499e2f431f9562242e3a3a4837d6c43a Mon Sep 17 00:00:00 2001 From: Felix Wirth Date: Fri, 21 Jun 2024 13:44:26 +0200 Subject: [PATCH] add esp32c3 examples (#37) --- .github/workflows/quickstart.yml | 30 +++ CHANGELOG.md | 4 + Cargo.toml | 2 +- .../.cargo/config.toml | 18 ++ .../esp32c3-power-meter-mock/.gitignore | 10 + .../.vscode/settings.json | 3 + .../esp32c3-power-meter-mock/Cargo.toml | 40 ++++ .../esp32c3-power-meter-mock/README.md | 52 +++++ .../esp32c3-power-meter-mock/build.rs | 3 + .../rust-toolchain.toml | 5 + .../esp32c3-power-meter-mock/src/main.rs | 112 ++++++++++ .../.cargo/config.toml | 18 ++ .../esp32c3-sml-reader-async/.gitignore | 10 + .../.vscode/settings.json | 3 + .../esp32c3-sml-reader-async/Cargo.toml | 43 ++++ .../esp32c3-sml-reader-async/README.md | 56 +++++ .../esp32c3-sml-reader-async/build.rs | 3 + .../rust-toolchain.toml | 5 + .../esp32c3-sml-reader-async/src/main.rs | 172 ++++++++++++++ .../esp32c3-sml-reader/.cargo/config.toml | 18 ++ .../embedded/esp32c3-sml-reader/.gitignore | 10 + .../esp32c3-sml-reader/.vscode/settings.json | 3 + .../embedded/esp32c3-sml-reader/Cargo.toml | 45 ++++ .../embedded/esp32c3-sml-reader/README.md | 79 +++++++ examples/embedded/esp32c3-sml-reader/build.rs | 3 + .../esp32c3-sml-reader/rust-toolchain.toml | 5 + .../embedded/esp32c3-sml-reader/src/main.rs | 210 ++++++++++++++++++ examples/serialport/Cargo.toml | 2 +- 28 files changed, 962 insertions(+), 2 deletions(-) create mode 100644 examples/embedded/esp32c3-power-meter-mock/.cargo/config.toml create mode 100644 examples/embedded/esp32c3-power-meter-mock/.gitignore create mode 100644 examples/embedded/esp32c3-power-meter-mock/.vscode/settings.json create mode 100644 examples/embedded/esp32c3-power-meter-mock/Cargo.toml create mode 100644 examples/embedded/esp32c3-power-meter-mock/README.md create mode 100644 examples/embedded/esp32c3-power-meter-mock/build.rs create mode 100644 examples/embedded/esp32c3-power-meter-mock/rust-toolchain.toml create mode 100644 examples/embedded/esp32c3-power-meter-mock/src/main.rs create mode 100644 examples/embedded/esp32c3-sml-reader-async/.cargo/config.toml create mode 100644 examples/embedded/esp32c3-sml-reader-async/.gitignore create mode 100644 examples/embedded/esp32c3-sml-reader-async/.vscode/settings.json create mode 100644 examples/embedded/esp32c3-sml-reader-async/Cargo.toml create mode 100644 examples/embedded/esp32c3-sml-reader-async/README.md create mode 100644 examples/embedded/esp32c3-sml-reader-async/build.rs create mode 100644 examples/embedded/esp32c3-sml-reader-async/rust-toolchain.toml create mode 100644 examples/embedded/esp32c3-sml-reader-async/src/main.rs create mode 100644 examples/embedded/esp32c3-sml-reader/.cargo/config.toml create mode 100644 examples/embedded/esp32c3-sml-reader/.gitignore create mode 100644 examples/embedded/esp32c3-sml-reader/.vscode/settings.json create mode 100644 examples/embedded/esp32c3-sml-reader/Cargo.toml create mode 100644 examples/embedded/esp32c3-sml-reader/README.md create mode 100644 examples/embedded/esp32c3-sml-reader/build.rs create mode 100644 examples/embedded/esp32c3-sml-reader/rust-toolchain.toml create mode 100644 examples/embedded/esp32c3-sml-reader/src/main.rs diff --git a/.github/workflows/quickstart.yml b/.github/workflows/quickstart.yml index 53add46..5dc0912 100644 --- a/.github/workflows/quickstart.yml +++ b/.github/workflows/quickstart.yml @@ -38,6 +38,36 @@ jobs: - uses: dtolnay/rust-toolchain@stable - run: cargo build -p sml-rs-serialport-example + build_esp_examples: + name: ESP Examples + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + project: + - esp32c3-power-meter-mock + - esp32c3-sml-reader + - esp32c3-sml-reader-async + action: + - command: build + args: --release + - command: build + args: --release --all-features + - command: fmt + args: --all -- --check --color always + - command: clippy + args: --all-features --workspace -- -D warnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf + toolchain: nightly + components: rust-src, rustfmt, clippy + - name: Build + working-directory: examples/embedded/${{ matrix.project }} + run: cargo ${{ matrix.action.command }} ${{ matrix.action.args }} + lints: name: Lints runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index a7d4543..b3e3ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Embedded examples for the ESP32-C3 (#37) + ## [0.4.0] - 2024-06-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index c3f3f6d..afe8adf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [".", "examples/serialport"] [package] name = "sml-rs" version = "0.4.0" -authors = ["Felix Wirth "] +authors = ["Felix Wirth "] description = "Smart Message Language (SML) parser written in Rust" repository = "https://github.com/felixwrt/sml-rs" license = "MIT OR Apache-2.0" diff --git a/examples/embedded/esp32c3-power-meter-mock/.cargo/config.toml b/examples/embedded/esp32c3-power-meter-mock/.cargo/config.toml new file mode 100644 index 0000000..092b231 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/.cargo/config.toml @@ -0,0 +1,18 @@ +[target.riscv32imc-unknown-none-elf] +runner = "espflash flash --monitor" + + +[env] +ESP_LOGLEVEL="INFO" + +[build] +rustflags = [ + # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.) + # NOTE: May negatively impact performance of produced code + "-C", "force-frame-pointers", +] + +target = "riscv32imc-unknown-none-elf" + +[unstable] +build-std = ["core"] diff --git a/examples/embedded/esp32c3-power-meter-mock/.gitignore b/examples/embedded/esp32c3-power-meter-mock/.gitignore new file mode 100644 index 0000000..73fab07 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/examples/embedded/esp32c3-power-meter-mock/.vscode/settings.json b/examples/embedded/esp32c3-power-meter-mock/.vscode/settings.json new file mode 100644 index 0000000..979d509 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.check.allTargets": false, +} diff --git a/examples/embedded/esp32c3-power-meter-mock/Cargo.toml b/examples/embedded/esp32c3-power-meter-mock/Cargo.toml new file mode 100644 index 0000000..57fb518 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "esp32c3-power-meter-mock" +version = "0.1.0" +authors = ["Felix Wirth "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[workspace] + +[dependencies] +embedded-io = "0.6.1" +esp-backtrace = { version = "0.12.0", features = [ + "esp32c3", + "exception-handler", + "panic-handler", + "println", +] } +esp-hal = { version = "0.18.0", features = ["embedded-io", "esp32c3"] } +esp-hal-smartled = { version = "0.11.0", features = ["esp32c3"], optional = true} +esp-println = { version = "0.9.1", features = ["esp32c3", "log"] } +hex-literal = "0.4.1" +log = { version = "0.4.21" } +smart-leds = { version = "0.4.0", optional = true } + +[features] +smart-led = ["dep:smart-leds", "dep:esp-hal-smartled"] + +[profile.dev] +# Rust debug is too slow. +# For debug builds always builds with some optimization +opt-level = "s" + +[profile.release] +codegen-units = 1 # LLVM can perform better optimizations using a single thread +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 's' +overflow-checks = false diff --git a/examples/embedded/esp32c3-power-meter-mock/README.md b/examples/embedded/esp32c3-power-meter-mock/README.md new file mode 100644 index 0000000..6921735 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/README.md @@ -0,0 +1,52 @@ +# esp32c3 Power Meter Mock + +This is a simple program for the ESP32-C3 that continuously sends sml messages via UART. + +The example is used to mock a digital german power meter and test the [`sml-rs`][1] library. + +It sends an sml message approximately every second and turns an LED on while sending the data. + +## Configuration + +By default, the following pins are used: + +- GPIO 9: UART TX pin used to send data +- GPIO 10: UART RX pin (unused, but needs to be provided to the UART peripheral) +- GPIO 18: LED pin (see below) + +You can adapt the pin configuration in the source code (see the comment block "Pin configuration"). + +### Led configuration + +This project can use either a smart RGB LED (such as the one found on the [ESP32-C3-DevKitC-02][2]) or a simple LED that can +be driven by setting the output to high / low. + +By default, this project assumes a regular LED. Activate the `smart-led` feature to use a smart RGB LED: + +``` +cargo ... --features smart-led +``` + +## Usage + +Install [`espflash`][3]: + +``` +cargo install espflash +``` + +Flash and run the example: + +``` +cargo run --relase +``` + +When using a smart RGB LED: + +``` +cargo run --relase --features smart-led +``` + +[1]: https://github.com/felixwrt/sml-rs +[2]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitc-02.html +[3]: https://github.com/esp-rs/espflash/tree/main/espflash \ No newline at end of file diff --git a/examples/embedded/esp32c3-power-meter-mock/build.rs b/examples/embedded/esp32c3-power-meter-mock/build.rs new file mode 100644 index 0000000..5efe9c9 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=-Tlinkall.x"); +} diff --git a/examples/embedded/esp32c3-power-meter-mock/rust-toolchain.toml b/examples/embedded/esp32c3-power-meter-mock/rust-toolchain.toml new file mode 100644 index 0000000..804e8a7 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] +targets = ["riscv32imc-unknown-none-elf"] + diff --git a/examples/embedded/esp32c3-power-meter-mock/src/main.rs b/examples/embedded/esp32c3-power-meter-mock/src/main.rs new file mode 100644 index 0000000..2a64052 --- /dev/null +++ b/examples/embedded/esp32c3-power-meter-mock/src/main.rs @@ -0,0 +1,112 @@ +#![no_std] +#![no_main] + +use esp_backtrace as _; +#[cfg(not(feature = "smart-led"))] +use esp_hal::gpio::AnyOutput; +use esp_hal::{ + clock::ClockControl, + delay::Delay, + gpio::{Io, Level}, + peripherals::Peripherals, + prelude::*, + system::SystemControl, + uart::{ + config::{Config, StopBits}, + TxRxPins, Uart, + }, +}; +use hex_literal::hex; + +#[cfg(feature = "smart-led")] +use esp_hal::rmt::Rmt; +#[cfg(feature = "smart-led")] +use esp_hal_smartled::{smartLedBuffer, SmartLedsAdapter}; +#[cfg(feature = "smart-led")] +use smart_leds::RGB; + +use embedded_io::Write; + +// test data taken from https://github.com/devZer0/libsml-testing/blob/master/ISKRA_MT631-D1A52-K0z-H01_with_PIN.hex +const TEST_DATA: &[&[u8]] = &[ + &hex!("1B1B1B1B010101017605099DFAAC6200620072630101760101050334A8E40B0A0149534B00047A5544726201650334A737620163D0BB007605099DFAAD620062007263070177010B0A0149534B00047A5544070100620AFFFF726201650334A737757707010060320101010101010449534B0177070100600100FF010101010B0A0149534B00047A55440177070100010800FF650010010401621E52FF650137C0C70177070100020800FF0101621E52FF62000177070100100700FF0101621B52005300C101010163F376007605099DFAAE620062007263020171016342DD001B1B1B1B1A0038EB"), + &hex!("1B1B1B1B010101017605099DFAAF6200620072630101760101050334A8E50B0A0149534B00047A5544726201650334A73862016303AF007605099DFAB0620062007263070177010B0A0149534B00047A5544070100620AFFFF726201650334A738757707010060320101010101010449534B0177070100600100FF010101010B0A0149534B00047A55440177070100010800FF650010010401621E52FF650137C0C80177070100020800FF0101621E52FF62000177070100100700FF0101621B52005300C201010163EA6F007605099DFAB162006200726302017101634BB0001B1B1B1B1A000705"), + &hex!("1B1B1B1B010101017605099DFAB26200620072630101760101050334A8E60B0A0149534B00047A5544726201650334A739620163DC95007605099DFAB3620062007263070177010B0A0149534B00047A5544070100620AFFFF726201650334A739757707010060320101010101010449534B0177070100600100FF010101010B0A0149534B00047A55440177070100010800FF650010010401621E52FF650137C0C80177070100020800FF0101621E52FF62000177070100100700FF0101621B52005300C101010163B703007605099DFAB462006200726302017101638FBB001B1B1B1B1A00D07B"), + &hex!("1B1B1B1B010101017605099DFAB56200620072630101760101050334A8E70B0A0149534B00047A5544726201650334A73A620163AE9F007605099DFAB6620062007263070177010B0A0149534B00047A5544070100620AFFFF726201650334A73A757707010060320101010101010449534B0177070100600100FF010101010B0A0149534B00047A55440177070100010800FF650010010401621E52FF650137C0C90177070100020800FF0101621E52FF62000177070100100700FF0101621B52005300C20101016324B6007605099DFAB762006200726302017101633C45001B1B1B1B1A00C2C5"), + &hex!("1B1B1B1B010101017605099DFAB86200620072630101760101050334A8E80B0A0149534B00047A5544726201650334A73B620163B08E007605099DFAB9620062007263070177010B0A0149534B00047A5544070100620AFFFF726201650334A73B757707010060320101010101010449534B0177070100600100FF010101010B0A0149534B00047A55440177070100010800FF650010010401621E52FF650137C0C90177070100020800FF0101621E52FF62000177070100100700FF0101621B52005300C201010163EAD0007605099DFABA620062007263020171016352F2001B1B1B1B1A00382E"), +]; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::max(system.clock_control).freeze(); + let delay = Delay::new(&clocks); + let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); + + // ----------------------------------------------------------------------- + // Pin configuration - adapt to your board + // ----------------------------------------------------------------------- + let led_pin = io.pins.gpio18; + let tx_pin = io.pins.gpio9; + // rx is unused, but needs to be provided for UART + let rx_pin = io.pins.gpio10; + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + + // Init logging + esp_println::logger::init_logger_from_env(); + + // LED Configuration + let mut led; + #[cfg(feature = "smart-led")] + { + let rmt = Rmt::new(peripherals.RMT, 80.MHz(), &clocks, None).unwrap(); + let rmt_buffer = smartLedBuffer!(1); + led = SmartLedsAdapter::new(rmt.channel0, led_pin, rmt_buffer, &clocks); + } + #[cfg(not(feature = "smart-led"))] + { + led = AnyOutput::new(led_pin, Level::High); + } + + // UART Configuration + let pins = TxRxPins::new_tx_rx(tx_pin, rx_pin); + let uart_config = Config::default() + .baudrate(9600) + .parity_none() + .stop_bits(StopBits::STOP1); + let mut uart1 = + Uart::new_with_config(peripherals.UART1, uart_config, Some(pins), &clocks, None); + + // Main Loop + let mut data_iter = TEST_DATA.iter().cycle(); + loop { + let data = data_iter.next().unwrap(); + log::info!("Sending data!"); + led_write(&mut led, Level::High); + + uart1.write(data).unwrap(); + + led_write(&mut led, Level::Low); + delay.delay(1000.millis()); + } +} + +#[cfg(feature = "smart-led")] +fn led_write(led: &mut S, level: Level) +where + S: smart_leds::SmartLedsWrite>, + S::Error: core::fmt::Debug, +{ + let color = match level { + Level::High => RGB::new(0, 0, 2), + Level::Low => RGB::new(0, 0, 0), + }; + led.write([color]).unwrap() +} +#[cfg(not(feature = "smart-led"))] +fn led_write(led: &mut AnyOutput, level: Level) { + led.set_level(level) +} diff --git a/examples/embedded/esp32c3-sml-reader-async/.cargo/config.toml b/examples/embedded/esp32c3-sml-reader-async/.cargo/config.toml new file mode 100644 index 0000000..092b231 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/.cargo/config.toml @@ -0,0 +1,18 @@ +[target.riscv32imc-unknown-none-elf] +runner = "espflash flash --monitor" + + +[env] +ESP_LOGLEVEL="INFO" + +[build] +rustflags = [ + # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.) + # NOTE: May negatively impact performance of produced code + "-C", "force-frame-pointers", +] + +target = "riscv32imc-unknown-none-elf" + +[unstable] +build-std = ["core"] diff --git a/examples/embedded/esp32c3-sml-reader-async/.gitignore b/examples/embedded/esp32c3-sml-reader-async/.gitignore new file mode 100644 index 0000000..73fab07 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/examples/embedded/esp32c3-sml-reader-async/.vscode/settings.json b/examples/embedded/esp32c3-sml-reader-async/.vscode/settings.json new file mode 100644 index 0000000..979d509 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.check.allTargets": false, +} diff --git a/examples/embedded/esp32c3-sml-reader-async/Cargo.toml b/examples/embedded/esp32c3-sml-reader-async/Cargo.toml new file mode 100644 index 0000000..0e1f391 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "esp32c3-sml-reader-async" +version = "0.1.0" +authors = ["Felix Wirth "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[workspace] + +[dependencies] +embassy-executor = { version = "0.5.0", features = ["task-arena-size-40960"] } +embassy-time = { version = "0.3.0", features = ["generic-queue-8"] } +embedded-io-async = "0.6.1" +esp-backtrace = { version = "0.12.0", features = [ + "esp32c3", + "exception-handler", + "panic-handler", + "println", +] } +esp-hal = { version = "0.18.0", features = ["embedded-io-async", "esp32c3", "async"] } +esp-hal-embassy = { version = "0.1.0", features = ["esp32c3", "time-timg0"] } +esp-hal-smartled = { version = "0.11.0", features = ["esp32c3"], optional = true} +esp-println = { version = "0.9.1", features = ["esp32c3", "log"] } +log = { version = "0.4.21" } +smart-leds = { version = "0.4.0", optional = true } +sml-rs = { version = "0.4.0", default-features = false } + +[features] +smart-led = ["dep:smart-leds", "dep:esp-hal-smartled"] + +[profile.dev] +# Rust debug is too slow. +# For debug builds always builds with some optimization +opt-level = "s" + +[profile.release] +codegen-units = 1 # LLVM can perform better optimizations using a single thread +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 's' +overflow-checks = false diff --git a/examples/embedded/esp32c3-sml-reader-async/README.md b/examples/embedded/esp32c3-sml-reader-async/README.md new file mode 100644 index 0000000..2004164 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/README.md @@ -0,0 +1,56 @@ +# esp32c3 Async SML Reader + +This is a simple program for the ESP32-C3 that reads sml messages from a UART pin. + +The example shows how to use the [`sml-rs`][1] library in an async embedded context. + +It receives data from a UART pin, tries to parse it as an sml message and prints the result on the terminal. +Additionally, it toggles an LED whenever a byte is received on the UART pin. + +Note: also take a look at the [`esp32c3-power-meter-mock`](../esp32c3-power-meter-mock/) example which can be used to generate +sml messages that can be consumed by this example. + +## Configuration + +By default, the following pins are used: + +- GPIO 10: UART TX pin (unused, but needs to be provided to the UART peripheral) +- GPIO 9: UART RX pin used to receive data +- GPIO 8: LED pin (see below) + +You can adapt the pin configuration in the source code (see the comment block "Pin configuration"). + +### Led configuration + +This project can use either a smart RGB LED (such as the one found on the [ESP32-C3-DevKitC-02][2]) or a simple LED that can +be driven by setting the output to high / low. + +By default, this project assumes a regular LED. Activate the `smart-led` feature to use a smart RGB LED: + +``` +cargo ... --features smart-led +``` + +## Usage + +Install [`espflash`][3]: + +``` +cargo install espflash +``` + +Flash and run the example: + +``` +cargo run --relase +``` + +When using a smart RGB LED: + +``` +cargo run --relase --features smart-led +``` + +[1]: https://github.com/felixwrt/sml-rs +[2]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitc-02.html +[3]: https://github.com/esp-rs/espflash/tree/main/espflash diff --git a/examples/embedded/esp32c3-sml-reader-async/build.rs b/examples/embedded/esp32c3-sml-reader-async/build.rs new file mode 100644 index 0000000..5efe9c9 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=-Tlinkall.x"); +} diff --git a/examples/embedded/esp32c3-sml-reader-async/rust-toolchain.toml b/examples/embedded/esp32c3-sml-reader-async/rust-toolchain.toml new file mode 100644 index 0000000..804e8a7 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] +targets = ["riscv32imc-unknown-none-elf"] + diff --git a/examples/embedded/esp32c3-sml-reader-async/src/main.rs b/examples/embedded/esp32c3-sml-reader-async/src/main.rs new file mode 100644 index 0000000..d1b37c4 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader-async/src/main.rs @@ -0,0 +1,172 @@ +#![no_std] +#![no_main] + +use core::slice; + +use embassy_executor::Spawner; +use embassy_time::{Duration, Timer}; +use esp_backtrace as _; +#[cfg(not(feature = "smart-led"))] +use esp_hal::gpio::AnyOutput; +use esp_hal::{ + clock::ClockControl, + gpio::{Io, Level}, + peripherals::{Peripherals, UART1}, + prelude::*, + system::SystemControl, + timer::timg::TimerGroup, + uart::{ + config::{Config, StopBits}, + TxRxPins, Uart, UartRx, + }, + Async, +}; + +#[cfg(feature = "smart-led")] +use esp_hal::rmt::Rmt; +#[cfg(feature = "smart-led")] +use esp_hal_smartled::{smartLedBuffer, SmartLedsAdapter}; +#[cfg(feature = "smart-led")] +use smart_leds::RGB; + +use sml_rs::{transport::Decoder, util::ArrayBuf}; + +#[main] +async fn main(spawner: Spawner) { + let peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + let timg0 = TimerGroup::new_async(peripherals.TIMG0, &clocks); + let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); + + // ----------------------------------------------------------------------- + // Pin configuration - adapt to your board + // ----------------------------------------------------------------------- + let led_pin = io.pins.gpio8; + // tx is unused, but needs to be provided for UART + let tx_pin = io.pins.gpio10; + let rx_pin = io.pins.gpio9; + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + + // Init logging + esp_println::logger::init_logger_from_env(); + + let mut led; + // LED Configuration + #[cfg(feature = "smart-led")] + { + let rmt = Rmt::new(peripherals.RMT, 80.MHz(), &clocks, None).unwrap(); + let rmt_buffer = smartLedBuffer!(1); + led = SmartLedsAdapter::new(rmt.channel0, led_pin, rmt_buffer, &clocks); + } + #[cfg(not(feature = "smart-led"))] + { + led = AnyOutput::new(led_pin, Level::High); + } + + // UART Configuration + let pins = TxRxPins::new_tx_rx(tx_pin, rx_pin); + let uart_config = Config::default() + .baudrate(9600) + .parity_none() + .stop_bits(StopBits::STOP1); + let mut uart1 = + Uart::new_async_with_config(peripherals.UART1, uart_config, Some(pins), &clocks); + uart1.set_rx_fifo_full_threshold(0).unwrap(); + let (_tx, rx) = uart1.split(); + + // Init embassy + log::info!("Initializing embassy..."); + esp_hal_embassy::init(&clocks, timg0); + + // Test LED + log::info!("Testing LED..."); + let mut led_state = false; + for _ in 0..8 { + toggle_led(&mut led_state, &mut led); + Timer::after(Duration::from_millis(200)).await; + } + + // Spawn print task + log::info!("Spawning print task..."); + spawner.spawn(print()).ok(); + + // Spawn reader task + log::info!("Spawning reader task..."); + spawner.spawn(reader(rx, led)).ok(); +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART1, Async>, mut led: LedTy) { + log::info!("Starting reader task!"); + let mut led_state = false; + + let buf = ArrayBuf::<4069>::default(); + let mut decoder = Decoder::from_buf(buf); + + loop { + // read byte from the pin + let mut b = 0u8; + let r = embedded_io_async::Read::read(&mut rx, slice::from_mut(&mut b)).await; + + // toggle the LED + toggle_led(&mut led_state, &mut led); + + match r { + Ok(_) => { + // process the read byte + match decoder.push_byte(b) { + Ok(None) => { + continue; + } + Ok(Some(bytes)) => { + log::info!("Got data: {bytes:?}") + } + Err(e) => { + log::info!("Error receiving data: {e}") + } + } + } + Err(e) => { + log::warn!("UART RX Error: {:?}", e) + } + } + } +} + +#[embassy_executor::task] +async fn print() { + loop { + log::info!("Hello from the print task!"); + Timer::after(Duration::from_millis(5000)).await; + } +} + +#[cfg(feature = "smart-led")] +type LedTy = SmartLedsAdapter, 25>; +#[cfg(not(feature = "smart-led"))] +type LedTy = AnyOutput<'static>; + +#[cfg(feature = "smart-led")] +fn led_write(led: &mut S, level: Level) +where + S: smart_leds::SmartLedsWrite>, + S::Error: core::fmt::Debug, +{ + let color = match level { + Level::High => RGB::new(0, 0, 2), + Level::Low => RGB::new(0, 0, 0), + }; + led.write([color]).unwrap() +} +#[cfg(not(feature = "smart-led"))] +fn led_write(led: &mut AnyOutput, level: Level) { + led.set_level(level) +} + +fn toggle_led(state: &mut bool, led: &mut LedTy) { + *state = !*state; + led_write(led, (*state).into()) +} diff --git a/examples/embedded/esp32c3-sml-reader/.cargo/config.toml b/examples/embedded/esp32c3-sml-reader/.cargo/config.toml new file mode 100644 index 0000000..092b231 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/.cargo/config.toml @@ -0,0 +1,18 @@ +[target.riscv32imc-unknown-none-elf] +runner = "espflash flash --monitor" + + +[env] +ESP_LOGLEVEL="INFO" + +[build] +rustflags = [ + # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.) + # NOTE: May negatively impact performance of produced code + "-C", "force-frame-pointers", +] + +target = "riscv32imc-unknown-none-elf" + +[unstable] +build-std = ["core"] diff --git a/examples/embedded/esp32c3-sml-reader/.gitignore b/examples/embedded/esp32c3-sml-reader/.gitignore new file mode 100644 index 0000000..73fab07 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/examples/embedded/esp32c3-sml-reader/.vscode/settings.json b/examples/embedded/esp32c3-sml-reader/.vscode/settings.json new file mode 100644 index 0000000..979d509 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.check.allTargets": false, +} diff --git a/examples/embedded/esp32c3-sml-reader/Cargo.toml b/examples/embedded/esp32c3-sml-reader/Cargo.toml new file mode 100644 index 0000000..61af7d2 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "esp32c3-sml-reader" +version = "0.1.0" +authors = ["Felix Wirth "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[workspace] + +[dependencies] +embedded-io = "0.6.1" +esp-backtrace = { version = "0.12.0", features = [ + "esp32c3", + "exception-handler", + "panic-handler", + "println", +] } +esp-hal = { version = "0.18.0", features = ["embedded-io", "esp32c3"] } +esp-hal-smartled = { version = "0.11.0", features = ["esp32c3"], optional = true} +esp-println = { version = "0.9.1", features = ["esp32c3", "log"] } +log = { version = "0.4.21" } +smart-leds = { version = "0.4.0", optional = true } +sml-rs = { version = "0.4.0", default-features = false } + +[features] +smart-led = ["dep:smart-leds", "dep:esp-hal-smartled"] +polling = [] + +[profile.dev] +# Rust debug is too slow. +# For debug builds always builds with some optimization +opt-level = "s" + +[profile.release] +codegen-units = 1 # LLVM can perform better optimizations using a single thread +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 's' +overflow-checks = false + +[patch.crates-io] +# TODO: remove once https://github.com/esp-rs/esp-hal/pull/1702 is merged and released +esp-hal = { git = "https://github.com/felixwrt/esp-hal.git", rev = "c7bdd3519ace4e392b7c9f38d41c5cbc96f51bc8" } \ No newline at end of file diff --git a/examples/embedded/esp32c3-sml-reader/README.md b/examples/embedded/esp32c3-sml-reader/README.md new file mode 100644 index 0000000..1551405 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/README.md @@ -0,0 +1,79 @@ +# esp32c3 SML Reader + +This is a simple program for the ESP32-C3 that reads sml messages from a UART pin. + +The example shows how to use the [`sml-rs`][1] library in an embedded context. + +It receives data from a UART pin, tries to parse it as an sml message and prints the result on the terminal. +Additionally, it toggles an LED whenever a byte is received on the UART pin. + +Note: also take a look at the [`esp32c3-power-meter-mock`](../esp32c3-power-meter-mock/) example which can be used to generate +sml messages that can be consumed by this example. + +## Configuration + +By default, the following pins are used: + +- GPIO 10: UART TX pin (unused, but needs to be provided to the UART peripheral) +- GPIO 9: UART RX pin used to receive data +- GPIO 8: LED pin (see below) + +You can adapt the pin configuration in the source code (see the comment block "Pin configuration"). + +### Led configuration + +This project can use either a smart RGB LED (such as the one found on the [ESP32-C3-DevKitC-02][2]) or a simple LED that can +be driven by setting the output to high / low. + +By default, this project assumes a regular LED. Activate the `smart-led` feature to use a smart RGB LED: + +``` +cargo ... --features smart-led +``` + +## Usage + +Install [`espflash`][3]: + +``` +cargo install espflash +``` + +Flash and run the example: + +``` +cargo run --relase +``` + +When using a smart RGB LED: + +``` +cargo run --relase --features smart-led +``` + +### Polling mode + +The example contains two working modes: blocking (default) and polling. In the blocking mode, the implementation +continuously tries to read from the UART pin, which blocks if no data is available currently. This is simple to implement, but +also means that it's not possible to concurrently work on several tasks within the main loop. + +In the polling mode, the implementation first checks whether data is available and only reads from the pin if that is the case. +This allows doing additional work in the main loop. The example prints a message to the terminal every 5 seconds. + +Note: also take a look at the [`esp32c3-sml-reader-async`](../esp32c3-sml-reader-async/) example which uses `embassy` and `async/await` to enable multitasking. + +The polling mode can be activated using the `polling` feature: + +``` +cargo run --relase --features polling +``` + +Or, when using a smart RGB LED: + +``` +cargo run --relase --features smart-led,polling +``` + +[1]: https://github.com/felixwrt/sml-rs +[2]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitc-02.html +[3]: https://github.com/esp-rs/espflash/tree/main/espflash \ No newline at end of file diff --git a/examples/embedded/esp32c3-sml-reader/build.rs b/examples/embedded/esp32c3-sml-reader/build.rs new file mode 100644 index 0000000..5efe9c9 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=-Tlinkall.x"); +} diff --git a/examples/embedded/esp32c3-sml-reader/rust-toolchain.toml b/examples/embedded/esp32c3-sml-reader/rust-toolchain.toml new file mode 100644 index 0000000..804e8a7 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] +targets = ["riscv32imc-unknown-none-elf"] + diff --git a/examples/embedded/esp32c3-sml-reader/src/main.rs b/examples/embedded/esp32c3-sml-reader/src/main.rs new file mode 100644 index 0000000..1711934 --- /dev/null +++ b/examples/embedded/esp32c3-sml-reader/src/main.rs @@ -0,0 +1,210 @@ +#![no_std] +#![no_main] + +use core::slice; + +use esp_backtrace as _; +#[cfg(not(feature = "smart-led"))] +use esp_hal::gpio::AnyOutput; +use esp_hal::{ + clock::ClockControl, + delay::Delay, + gpio::{Io, Level}, + peripherals::Peripherals, + prelude::*, + system::SystemControl, + uart::{ + config::{Config, StopBits}, + Uart, + }, +}; + +#[cfg(feature = "smart-led")] +use esp_hal::rmt::Rmt; +#[cfg(feature = "smart-led")] +use esp_hal_smartled::{smartLedBuffer, SmartLedsAdapter}; +#[cfg(feature = "smart-led")] +use smart_leds::RGB; + +use sml_rs::{transport::Decoder, util::ArrayBuf}; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::max(system.clock_control).freeze(); + let delay = Delay::new(&clocks); + let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); + + // ----------------------------------------------------------------------- + // Pin configuration - adapt to your board + // ----------------------------------------------------------------------- + let led_pin = io.pins.gpio8; + // tx is unused, but needs to be provided for UART + let tx_pin = io.pins.gpio10; + let rx_pin = io.pins.gpio9; + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + + // Init logging + esp_println::logger::init_logger_from_env(); + + // LED Configuration + let mut led; + #[cfg(feature = "smart-led")] + { + let rmt = Rmt::new(peripherals.RMT, 80.MHz(), &clocks, None).unwrap(); + let rmt_buffer = smartLedBuffer!(1); + led = SmartLedsAdapter::new(rmt.channel0, led_pin, rmt_buffer, &clocks); + } + #[cfg(not(feature = "smart-led"))] + { + led = AnyOutput::new(led_pin, Level::High); + } + + // UART Configuration + let uart_config = Config::default() + .baudrate(9600) + .parity_none() + .stop_bits(StopBits::STOP1); + let mut uart1 = Uart::new_with_config( + peripherals.UART1, + uart_config, + &clocks, + None, + tx_pin, + rx_pin, + ) + .unwrap(); + + // Closure toggling the LED + let mut led_state = false; + let mut toggle_led = || { + led_state = !led_state; + led_write(&mut led, led_state.into()); + }; + + // Test LED + log::info!("Testing LED..."); + for _ in 0..8 { + toggle_led(); + delay.delay(200.millis()); + } + + log::info!("Reading SML messages..."); + + #[cfg(not(feature = "polling"))] + { + read_blocking(&mut uart1, toggle_led) + } + #[cfg(feature = "polling")] + { + read_polling(&mut uart1, toggle_led) + } +} + +#[allow(unused)] +fn read_blocking(pin: &mut impl embedded_io::Read, mut toggle_led: impl FnMut()) -> ! { + let buf = ArrayBuf::<4069>::default(); + let mut decoder = Decoder::from_buf(buf); + + loop { + // read byte from the pin + let mut b = 0u8; + let r = pin.read(slice::from_mut(&mut b)); + + // toggle the LED + toggle_led(); + + match r { + Ok(_) => { + // process the read byte + match decoder.push_byte(b) { + Ok(None) => { + continue; + } + Ok(Some(bytes)) => { + log::info!("Got data: {bytes:?}") + } + Err(e) => { + log::error!("Error receiving data: {e}") + } + } + } + Err(e) => { + log::warn!("UART RX Error: {:?}", e) + } + } + } +} + +#[allow(unused)] +fn read_polling( + pin: &mut PIN, + mut toggle_led: impl FnMut(), +) -> ! { + let buf = ArrayBuf::<4069>::default(); + let mut decoder = Decoder::from_buf(buf); + + let mut last_print_time = 0; + + // main loop + loop { + // reading only when data is available (non-blocking) + if pin.read_ready().unwrap() { + // read byte from the pin + let mut b = 0u8; + let r = pin.read(slice::from_mut(&mut b)); + + // toggle the LED + toggle_led(); + + match r { + Ok(_) => { + // process the read byte + match decoder.push_byte(b) { + Ok(None) => { + continue; + } + Ok(Some(bytes)) => { + log::info!("Got data: {bytes:?}") + } + Err(e) => { + log::error!("Error receiving data: {e}") + } + } + } + Err(e) => { + log::warn!("UART RX Error: {:?}", e) + } + } + } + + // print a message every 5 seconds + let mut secs_since_start = esp_hal::time::current_time() + .duration_since_epoch() + .to_secs(); + if secs_since_start >= last_print_time + 5 { + last_print_time = secs_since_start; + log::info!("Hello from the print task!"); + } + } +} + +#[cfg(feature = "smart-led")] +fn led_write(led: &mut S, level: Level) +where + S: smart_leds::SmartLedsWrite>, + S::Error: core::fmt::Debug, +{ + let color = match level { + Level::High => RGB::new(0, 0, 2), + Level::Low => RGB::new(0, 0, 0), + }; + led.write([color]).unwrap() +} +#[cfg(not(feature = "smart-led"))] +fn led_write(led: &mut AnyOutput, level: Level) { + led.set_level(level) +} diff --git a/examples/serialport/Cargo.toml b/examples/serialport/Cargo.toml index 281e78e..5f24afc 100644 --- a/examples/serialport/Cargo.toml +++ b/examples/serialport/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sml-rs-serialport-example" version = "0.1.0" -authors = ["Felix Wirth "] +authors = ["Felix Wirth "] description = "Example of using `sml-rs` with `serialport`" repository = "https://github.com/felixwrt/sml-rs" license = "MIT OR Apache-2.0"