From f125c20cf21bef9ec1d2f228cf54129af64922e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Quentin?= Date: Wed, 5 Jun 2024 12:44:17 +0200 Subject: [PATCH] Add auto-detection feature to `esp-println` (#1658) * Add auto-detection feature to esp-println * CHANGELOG.md * Minor README change * Build `esp-println` in CI --- .github/workflows/ci.yml | 47 ++++++++++++++ esp-println/.cargo/config.toml | 2 + esp-println/CHANGELOG.md | 43 +++++++++++++ esp-println/Cargo.toml | 9 ++- esp-println/README.md | 13 ++-- esp-println/build.rs | 2 +- esp-println/src/lib.rs | 111 +++++++++++++++++++++++++++------ 7 files changed, 197 insertions(+), 30 deletions(-) create mode 100644 esp-println/.cargo/config.toml create mode 100644 esp-println/CHANGELOG.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79d0f58af..53fc345cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,6 +152,53 @@ jobs: - name: Build esp-riscv-rt (riscv32imac, all features) run: cd esp-riscv-rt/ && cargo build --target=riscv32imac-unknown-none-elf --features=ci + esp-println: + name: esp-println (${{ matrix.device.soc }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + device: [ + # RISC-V devices: + { soc: "esp32c2", target: "riscv32imc-unknown-none-elf" }, + { soc: "esp32c3", target: "riscv32imc-unknown-none-elf" }, + { soc: "esp32c6", target: "riscv32imac-unknown-none-elf" }, + { soc: "esp32h2", target: "riscv32imac-unknown-none-elf" }, + # Xtensa devices: + { soc: "esp32", target: "xtensa-esp32-none-elf" }, + { soc: "esp32s2", target: "xtensa-esp32s2-none-elf" }, + { soc: "esp32s3", target: "xtensa-esp32s3-none-elf" }, + ] + + steps: + - uses: actions/checkout@v4 + + # Install the Rust toolchain for RISC-V devices: + - if: ${{ !contains(fromJson('["esp32", "esp32s2", "esp32s3"]'), matrix.device.soc) }} + uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf + toolchain: stable + components: rust-src + # Install the Rust toolchain for Xtensa devices: + - if: contains(fromJson('["esp32", "esp32s2", "esp32s3"]'), matrix.device.soc) + uses: esp-rs/xtensa-toolchain@v1.5 + with: + buildtargets: ${{ matrix.device.soc }} + default: true + ldproxy: false + + - uses: Swatinem/rust-cache@v2 + + # Make sure we're able to build with the default features and most common features enabled + - name: Build (no features) + run: | + cargo xtask build-package \ + --features=${{ matrix.device.soc }},log \ + --target=${{ matrix.device.target }} \ + esp-println + extras: runs-on: ubuntu-latest diff --git a/esp-println/.cargo/config.toml b/esp-println/.cargo/config.toml new file mode 100644 index 000000000..327219f7d --- /dev/null +++ b/esp-println/.cargo/config.toml @@ -0,0 +1,2 @@ +[unstable] +build-std = ["alloc", "core"] diff --git a/esp-println/CHANGELOG.md b/esp-println/CHANGELOG.md new file mode 100644 index 000000000..df8b852cf --- /dev/null +++ b/esp-println/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Add `auto` feature to auto-detect Serial-JTAG/UART communication (#1658) + +### Fixed + +### Changed + +- `auto` is the default communication method (#1658) + +### Removed + +## [0.9.1] - 2024-03-11 + +### Changed + +- Un-pinned the defmt package's version number + +## [0.9.0] - 2024-02-07 + +### Added + +- Add support for ESP32-P4 + +### Removed + +- Remove ESP 8266 support + +## [0.8.0] - 2023-12-21 + +### Removed + +- Remove RTT and defmt-raw support + diff --git a/esp-println/Cargo.toml b/esp-println/Cargo.toml index 43f1c7fe1..39191667d 100644 --- a/esp-println/Cargo.toml +++ b/esp-println/Cargo.toml @@ -22,7 +22,7 @@ portable-atomic = { version = "1.6.0", optional = true, default-features = fal esp-build = { version = "0.1.0", path = "../esp-build" } [features] -default = ["dep:critical-section", "colors", "uart"] +default = ["dep:critical-section", "colors", "auto"] log = ["dep:log"] # You must enable exactly 1 of the below features to support the correct chip: @@ -36,10 +36,13 @@ esp32s2 = [] esp32s3 = [] # You must enable exactly 1 of the below features to enable to intended -# communication method (note that "uart" is enabled by default): +# communication method (note that "auto" is enabled by default): jtag-serial = ["dep:portable-atomic"] # C3, C6, H2, P4, and S3 only! -no-op = [] uart = [] +auto = ["dep:portable-atomic"] + +# Don't print anything +no-op = [] # Enables a `defmt` backend usable with espflash. We force rzcobs encoding to simplify implementation defmt-espflash = ["dep:defmt", "defmt?/encoding-rzcobs"] diff --git a/esp-println/README.md b/esp-println/README.md index 83bca59e1..b2d51f7b1 100644 --- a/esp-println/README.md +++ b/esp-println/README.md @@ -39,22 +39,23 @@ You can now `println!("Hello world")` as usual. `esp32c3`, `esp32c6`, `esp32h2`, `esp32s2`, and `esp32s3`. - One of these features must be enabled. - Only one of these features can be enabled at a time. -- There is one feature for each supported communication method: `uart`, `jtag-serial` and `no-op`. +- There is one feature for each supported communication method: `uart`, `jtag-serial` and `auto`. - Only one of these features can be enabled at a time. +- `no-op`: Don't print anything. - `log`: Enables logging using [`log` crate]. -- `colors` enable colored logging. +- `colors`: Enable colored logging. - Only effective when using the `log` feature. -- `critical-section` enables critical sections. +- `critical-section`: Enables critical sections. - `defmt-espflash`: This is intended to be used with [`espflash`], see `-L/--log-format` argument of `flash` or `monitor` subcommands of `espflash` and `cargo-espflash`. Uses [rzCOBS] encoding and adds framing. ## Default Features -By default, we use the `uart`, `critial-section` and `colors` features. -Which means that it will print to the UART, use critical sections and output +By default, we use the `auto`, `critial-section` and `colors` features. +Which means that it will auto-detect if it needs to print to the UART or JTAG-Serial, use critical sections and output messages will be colored. -If we want to use a communication method that is not `uart`, the default +If we want to use a communication method that is not `auto`, the default one, we need to [disable the default features]. ## Logging diff --git a/esp-println/build.rs b/esp-println/build.rs index cd481a7b2..b81e5604b 100644 --- a/esp-println/build.rs +++ b/esp-println/build.rs @@ -7,7 +7,7 @@ fn main() { ); // Ensure that only a single communication method is specified - assert_unique_used_features!("jtag-serial", "uart"); + assert_unique_used_features!("jtag-serial", "uart", "auto"); // Ensure that, if the `jtag-serial` communication method feature is enabled, // either the `esp32c3`, `esp32c6`, `esp32h2`, or `esp32s3` chip feature is diff --git a/esp-println/src/lib.rs b/esp-println/src/lib.rs index 2ec425cb0..a31790586 100644 --- a/esp-println/src/lib.rs +++ b/esp-println/src/lib.rs @@ -68,26 +68,94 @@ macro_rules! dbg { }; } +#[cfg(any(feature = "jtag-serial", feature = "auto"))] +pub struct PrinterSerialJtag; + +#[cfg(any(feature = "uart", feature = "auto"))] +pub struct PrinterUart; + +#[cfg(feature = "jtag-serial")] +pub type PrinterImpl = PrinterSerialJtag; + +#[cfg(feature = "uart")] +pub type PrinterImpl = PrinterUart; + pub struct Printer; impl core::fmt::Write for Printer { fn write_str(&mut self, s: &str) -> core::fmt::Result { - Printer.write_bytes(s.as_bytes()); + Printer::write_bytes(s.as_bytes()); Ok(()) } } impl Printer { - pub fn write_bytes(&mut self, bytes: &[u8]) { + #[cfg(not(feature = "auto"))] + pub fn write_bytes(bytes: &[u8]) { with(|| { - self.write_bytes_assume_cs(bytes); - self.flush(); + PrinterImpl::write_bytes_assume_cs(bytes); + PrinterImpl::flush(); + }) + } + + #[cfg(feature = "auto")] + pub fn write_bytes(bytes: &[u8]) { + #[cfg(any( + feature = "esp32c3", + feature = "esp32c6", + feature = "esp32h2", + feature = "esp32s3" + ))] + { + // Decide if serial-jtag is used by checking SOF interrupt flag. + // SOF packet is sent by the HOST every 1ms on a full speed bus. + // Between two consecutive ticks, there will be at least 1ms (selectable tick + // rate range is 1 - 1000Hz). + // We don't reset the flag - if it was ever connected we assume serial-jtag is + // used + + #[cfg(feature = "esp32c3")] + const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32; + #[cfg(feature = "esp32c6")] + const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32; + #[cfg(feature = "esp32h2")] + const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32; + #[cfg(feature = "esp32s3")] + const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32; + + const SOF_INT_MASK: u32 = 0b10; + + let is_serial_jtag = + unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 }; + + if is_serial_jtag { + with(|| { + PrinterSerialJtag::write_bytes_assume_cs(bytes); + PrinterSerialJtag::flush(); + }) + } else { + with(|| { + PrinterUart::write_bytes_assume_cs(bytes); + PrinterUart::flush(); + }) + } + } + + #[cfg(not(any( + feature = "esp32c3", + feature = "esp32c6", + feature = "esp32h2", + feature = "esp32s3" + )))] + with(|| { + PrinterUart::write_bytes_assume_cs(bytes); + PrinterUart::flush(); }) } } #[cfg(all( - feature = "jtag-serial", + any(feature = "jtag-serial", feature = "auto"), any( feature = "esp32c3", feature = "esp32c6", @@ -154,8 +222,8 @@ mod serial_jtag_printer { true } - impl super::Printer { - pub fn write_bytes_assume_cs(&mut self, bytes: &[u8]) { + impl super::PrinterSerialJtag { + pub fn write_bytes_assume_cs(bytes: &[u8]) { if fifo_full() { // The FIFO is full. Let's see if we can progress. @@ -188,17 +256,17 @@ mod serial_jtag_printer { } } - pub fn flush(&mut self) { + pub fn flush() { fifo_flush(); } } } -#[cfg(all(feature = "uart", feature = "esp32"))] +#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32"))] mod uart_printer { const UART_TX_ONE_CHAR: usize = 0x4000_9200; - impl super::Printer { - pub fn write_bytes_assume_cs(&mut self, bytes: &[u8]) { + impl super::PrinterUart { + pub fn write_bytes_assume_cs(bytes: &[u8]) { for &b in bytes { unsafe { let uart_tx_one_char: unsafe extern "C" fn(u8) -> i32 = @@ -208,14 +276,14 @@ mod uart_printer { } } - pub fn flush(&mut self) {} + pub fn flush() {} } } -#[cfg(all(feature = "uart", feature = "esp32s2"))] +#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32s2"))] mod uart_printer { - impl super::Printer { - pub fn write_bytes_assume_cs(&mut self, bytes: &[u8]) { + impl super::PrinterUart { + pub fn write_bytes_assume_cs(bytes: &[u8]) { // On ESP32-S2 the UART_TX_ONE_CHAR ROM-function seems to have some issues. for chunk in bytes.chunks(64) { for &b in chunk { @@ -234,11 +302,14 @@ mod uart_printer { } } - pub fn flush(&mut self) {} + pub fn flush() {} } } -#[cfg(all(feature = "uart", not(any(feature = "esp32", feature = "esp32s2"))))] +#[cfg(all( + any(feature = "uart", feature = "auto"), + not(any(feature = "esp32", feature = "esp32s2")) +))] mod uart_printer { trait Functions { const TX_ONE_CHAR: usize; @@ -350,8 +421,8 @@ mod uart_printer { } } - impl super::Printer { - pub fn write_bytes_assume_cs(&mut self, bytes: &[u8]) { + impl super::PrinterUart { + pub fn write_bytes_assume_cs(bytes: &[u8]) { for chunk in bytes.chunks(Device::CHUNK_SIZE) { for &b in chunk { Device::tx_byte(b); @@ -361,7 +432,7 @@ mod uart_printer { } } - pub fn flush(&mut self) {} + pub fn flush() {} } }