├── .cargo └── config.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── partitions.csv ├── qemu.sh ├── sdkconfig.defaults ├── sdkconfig.defaults.esp32 ├── sdkconfig.defaults.esp32s2 ├── src └── main.rs └── ulp └── rust-esp32-ulp-blink /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3) 3 | #target = "xtensa-esp32-espidf" 4 | #target = "xtensa-esp32s2-espidf" 5 | #target = "xtensa-esp32s3-espidf" 6 | target = "riscv32imc-esp-espidf" 7 | #target = "riscv32imac-esp-espidf" 8 | 9 | [target.xtensa-esp32-espidf] 10 | linker = "ldproxy" 11 | # Comment the line below for ESP IDF 4. Don't forget to also uncomment the `ESP_IDF_VERSION = "v4.4.6"` parameter in the `[env]` section below 12 | rustflags = ["--cfg", "espidf_time64"] 13 | 14 | [target.xtensa-esp32s2-espidf] 15 | linker = "ldproxy" 16 | # Comment the line below for ESP IDF 4. Don't forget to also uncomment the `ESP_IDF_VERSION = "v4.4.6"` parameter in the `[env]` section below 17 | rustflags = ["--cfg", "espidf_time64"] 18 | 19 | [target.xtensa-esp32s3-espidf] 20 | linker = "ldproxy" 21 | # Comment the line below for ESP IDF 4. Don't forget to also uncomment the `ESP_IDF_VERSION = "v4.4.6"` parameter in the `[env]` section below 22 | rustflags = ["--cfg", "espidf_time64"] 23 | 24 | [target.riscv32imc-esp-espidf] 25 | linker = "ldproxy" 26 | # Comment the line below for ESP IDF 4. Don't forget to also uncomment the `ESP_IDF_VERSION = "v4.4.6"` parameter in the `[env]` section below 27 | rustflags = ["--cfg", "espidf_time64"] 28 | 29 | [target.riscv32imac-esp-espidf] 30 | linker = "ldproxy" 31 | # Comment the line below for ESP IDF 4. Don't forget to also uncomment the `ESP_IDF_VERSION = "v4.4.6"` parameter in the `[env]` section below 32 | rustflags = ["--cfg", "espidf_time64"] 33 | 34 | [unstable] 35 | build-std = ["std", "panic_abort"] 36 | #build-std-features = ["panic_immediate_abort"] # Only necessary if building against ESP-IDF tag `v4.3.2` (the minimum supported version); using it reduces the binary size by ~ 10% to 20% 37 | 38 | [env] 39 | # Select ESP IDF version in embuild's format described here: 40 | # https://github.com/esp-rs/esp-idf-sys/blob/master/README.md#esp_idf_version-esp_idf_version-native-builder-only 41 | # (Not used by PlatformIO, i.e. `cargo build --features pio`) 42 | # 43 | # Uncomment this to build against ESP-IDF master 44 | #ESP_IDF_VERSION = "master" 45 | # Don't forget to uncomment also the `rustflags` parameter in your "target" section above 46 | # 47 | # Uncomment this to build against ESP-IDF 5.0 48 | # Don't forget to uncomment also the `rustflags` parameter in your "target" section above 49 | #ESP_IDF_VERSION = "v5.0.4" 50 | # 51 | # Uncomment this to build against ESP-IDF 5.1 52 | # Don't forget to uncomment also the `rustflags` parameter in your "target" section above 53 | #ESP_IDF_VERSION = "v5.1.1" 54 | # 55 | # Comment out this when using the PlatformIO build, i.e. `cargo build --features pio` (it only supports `v4.3.2`) 56 | #ESP_IDF_VERSION = "v4.4.6" 57 | ESP_IDF_VERSION = "v5.1.1" 58 | 59 | # These configurations will pick up your custom "sdkconfig.release", "sdkconfig.debug" or "sdkconfig.defaults[.*]" files 60 | # that you might put in the root of the project 61 | # The easiest way to generate a full "sdkconfig" configuration (as opposed to manually enabling only the necessary flags via "sdkconfig.defaults[.*]" 62 | # is by running "cargo pio espidf menuconfig" (that is, if using the pio builder) 63 | #ESP_IDF_SDKCONFIG = "sdkconfig.release;sdkconfig.debug" 64 | ESP_IDF_SDKCONFIG_DEFAULTS = "sdkconfig.defaults;sdkconfig.defaults.esp32;sdkconfig.defaults.esp32s2" 65 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | schedule: 9 | - cron: '50 6 * * *' 10 | 11 | env: 12 | rust_toolchain: nightly 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: Setup | ldproxy 31 | run: cargo install ldproxy 32 | - name: Build | Fmt Check 33 | run: cargo fmt -- --check 34 | - name: Build | Clippy 35 | run: export RUST_ESP32_STD_DEMO_WIFI_SSID=ssid; export RUST_ESP32_STD_DEMO_WIFI_PASS=pass; cargo clippy --no-deps --target riscv32imc-esp-espidf -- -Dwarnings 36 | - name: Build | Compile 37 | run: export RUST_ESP32_STD_DEMO_WIFI_SSID=ssid; export RUST_ESP32_STD_DEMO_WIFI_PASS=pass; cargo build --target riscv32imc-esp-espidf 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /.espressif 3 | /.embuild 4 | /target 5 | /Cargo.lock 6 | **/*.rs.bk 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-esp32-std-demo" 3 | version = "0.30.1" 4 | authors = ["ivmarkov"] 5 | edition = "2021" 6 | categories = ["embedded", "hardware-support"] 7 | keywords = ["embedded", "svc", "idf", "esp-idf", "esp32"] 8 | description = "A demo binary crate for the ESP32 and ESP-IDF, which connects to WiFi, Ethernet, drives a small HTTP server and draws on a LED screen" 9 | repository = "https://github.com/ivmarkov/rust-esp32-std-demo" 10 | license = "MIT OR Apache-2.0" 11 | readme = "README.md" 12 | 13 | [profile.release] 14 | opt-level = "s" 15 | 16 | [profile.dev] 17 | debug = true # Symbols are nice and they don't increase the size on Flash 18 | opt-level = "z" 19 | 20 | [features] 21 | default = [] 22 | 23 | # Enable this feature for the build to use the PlatformIO tooling instead of the native ESP-IDF tooling under the hood 24 | pio = ["esp-idf-svc/pio"] 25 | 26 | # Enable this feature if you are building for QEMU 27 | qemu = [] 28 | 29 | # Enable this feature in case you have a Kaluga board and would like to see a LED screen demo 30 | kaluga = [] 31 | 32 | # Enable this feature in case you have a TTGO board and would like to see a LED screen demo 33 | ttgo = [] 34 | 35 | # Enable this feature in case you have an ESP32S3-USB-OTG board and would like to see a LED screen demo 36 | heltec = [] 37 | 38 | # Enable this feature in case you have a generic SSD1306 Display connected via SPI to pins 3, 4, 5, 16, 18, 23 (SPI3) of your board 39 | ssd1306g_spi = [] 40 | 41 | # Enable this feature in case you have a generic SSD1306 screen connected to pins 14, 22 and 21 of your board 42 | ssd1306g = [] 43 | 44 | esp32s3_usb_otg = [] 45 | 46 | # Enable this feature in case you have a Waveshare board and 4.2" e-paper 47 | waveshare_epd = [] 48 | 49 | # Enable this feature in case you have an RMII IP101 Ethernet adapter 50 | ip101 = [] 51 | 52 | # Enable this feature in case you have an SPI W5500 Ethernet adapter 53 | w5500 = [] 54 | 55 | [dependencies] 56 | anyhow = {version = "1", features = ["backtrace"]} 57 | log = "0.4" 58 | url = "2" 59 | esp-idf-svc = "0.48" 60 | embedded-graphics = "0.7" 61 | display-interface = "0.4" 62 | display-interface-spi = "0.4" 63 | mipidsi = "0.5" 64 | ssd1306 = "0.7" 65 | epd-waveshare = "0.5.0" 66 | async-io = "2" 67 | async-executor = "1" 68 | futures-lite = "1" 69 | 70 | [build-dependencies] 71 | embuild = { version = "0.31.3", features = ["elf"] } 72 | 73 | # Future; might be possible once https://github.com/rust-lang/cargo/issues/9096 hits Cargo nightly: 74 | #rust-esp32-ulp-blink = { git = "https://github.com/ivmarkov/rust-esp32-ulp-blink", artifact = "bin" } 75 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust on ESP32 STD demo app 2 | 3 | A demo STD binary crate for the ESP32[XX] and ESP-IDF, which connects to WiFi, Ethernet, drives a small HTTP server and draws on a LED screen. 4 | 5 | ![CI](https://github.com/ivmarkov/rust-esp32-std-demo/actions/workflows/ci.yml/badge.svg) 6 | 7 | [Join in](https://matrix.to/#/#esp-rs:matrix.org) on the discussion! 8 | 9 | # Important Update 10 | 11 | This GH repository is now **deprecated**, because all of the functionalities it demonstrates (and more!) are available in the form of **examples/** directly in the `esp-idf-sys`/`esp-idf-hal`/`esp-idf-svc` crates (see below). 12 | 13 | **To easily generate a "Hello, world!" binary crate for Espressif MCUs with ESP-IDF, use the [`esp-idf-template`](https://github.com/esp-rs/esp-idf-template)**. 14 | 15 | Examples: 16 | * `esp-idf-svc` [examples](https://github.com/esp-rs/esp-idf-svc/tree/master/examples) 17 | * `esp-idf-hal` [examples](https://github.com/esp-rs/esp-idf-hal/tree/master/examples) 18 | * If you use `esp-idf-svc` already, just replace all `use esp_idf_hal::...` with `use esp_idf_svc::hal::...` 19 | * `esp-idf-sys` [examples](https://github.com/esp-rs/esp-idf-sys/tree/master/examples) 20 | * If you use `esp-idf-svc` already, just replace all `use esp_idf_sys::...` with `use esp_idf_svc::sys::...` 21 | 22 | The repository might also be *archived* soon. 23 | 24 | ## Highlights: 25 | 26 | - **Pure Rust and pure Cargo build!** No CMake, no PlatformIO, no C helpers 27 | - ... via [esp-idf-sys](https://crates.io/crates/esp-idf-sys) and [embuild](https://crates.io/crates/embuild) 28 | - **Support for Rust STD** (threads, console, TCP/IP) safe APIs 29 | - ... upstreamed and [part of the Rust STD library](https://github.com/rust-lang/rust/pull/87666) 30 | - **New, experimental!** Support for asynchronous networking using [smol](https://github.com/smol-rs/smol) 31 | - Support for running in the [Espressif fork of QEMU](https://github.com/espressif/qemu/wiki) 32 | - Rust Safe APIs for various ESP-IDF services like WiFi, Ping, Httpd and logging 33 | - ... via [esp-idf-svc](https://crates.io/crates/esp-idf-svc) ([embedded-svc](https://crates.io/crates/embedded-svc) abstractions implemented on top of ESP-IDF) 34 | - NAPT support (Router from the SoftAP to the STA interface). **NOTE**: In production, do NOT leave the SoftAP interface open (without password)! 35 | - Driving a LED screen with the [embedded-graphics](https://crates.io/crates/embedded-graphics) Rust crate 36 | - ... via [esp-idf-hal](https://crates.io/crates/esp-idf-hal) ([embedded-hal](https://crates.io/crates/embedded-hal) drivers implemented on top of ESP-IDF) 37 | - (ESP32-S2 only) [Blink a LED](https://github.com/ivmarkov/rust-esp32-ulp-blink) by loading a pure Rust program onto the RiscV Ultra Low Power CPU 38 | 39 | ## Build 40 | 41 | - Install the [Rust Espressif compiler toolchain and the Espressif LLVM Clang toolchain](https://github.com/esp-rs/rust-build) 42 | - This is necessary, because support for the Xtensa architecture (ESP32 / ESP32-S2 / ESP32-S3) is not upstreamed in LLVM yet 43 | - Switch to the `esp` toolchain from the pre-built binaries: `rustup default esp` 44 | - (You can also skip this step and switch to the `esp` toolchain *for the demo crate only* by executing `rustup override set esp` inside the `rust-esp32-std-demo` directory once you have cloned the demo as per below) 45 | - **NOTE** For ESP32-C3 - which runs a RiscV32 chip - you can just use the stock nightly Rust compiler, and a recent, stock Clang (as in Clang 11+) 46 | - (You can do this by issuing `rustup install nightly` and then `rustup default nightly` instead of installing/building the Rust & Clang ESP forks and switching to their `esp` toolchain as advised above) 47 | - If using the custom Espressif Clang, make sure that you DON'T have a system Clang installed as well, because even if you have the Espressif one first on your `$PATH`, Bindgen will still pick the system one 48 | - A workaround that does not require uninstalling the system Clang is to do `export LIBCLANG_PATH=` prior to continuing the build process 49 | - `cargo install ldproxy` 50 | - Clone this repo: `git clone https://github.com/ivmarkov/rust-esp32-std-demo` 51 | - Enter it: `cd rust-esp32-std-demo` 52 | - Export two environment variables that would contain the SSID & password of your wireless network: 53 | - `export RUST_ESP32_STD_DEMO_WIFI_SSID=` 54 | - `export RUST_ESP32_STD_DEMO_WIFI_PASS=` 55 | - To configure the demo for your particular board, please uncomment the relevant [Rust target for your board](https://github.com/ivmarkov/rust-esp32-std-demo/blob/main/.cargo/config.toml#L2) and comment the others. Alternatively, just append the `--target ` flag to all `cargo build` lines below. 56 | - Build: `cargo build` or `cargo build --release` 57 | - (Only if you happen to have a [TTGO T-Display board](http://www.lilygo.cn/prod_view.aspx?TypeId=50033&Id=1126&FId=t3:50033:3)): Add `ttgo` to the `--features` build flags above (as in `cargo build --features ttgo`) to be greeted with a `Hello Rust!` message on the board's LED screen 58 | - (Only if you happen to have a [Waveshare board](https://www.waveshare.com/wiki/E-Paper_ESP32_Driver_Board) and a [waveshare 4.2" e-paper screen](https://www.waveshare.com/wiki/4.2inch_e-Paper_Module)): Add `waveshare_epd` to the `--features` build flags above (as in `cargo build --features waveshare_epd`) to be greeted with a `Hello Rust!` message on the e-paper screen 59 | - (Only if you happen to have an [ESP32-S2-Kaluga-1 board](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/hw-reference/esp32s2/user-guide-esp32-s2-kaluga-1-kit.html)): Add `kaluga` to the `--features` build flags above (as in `cargo build --features kaluga`) to be greeted with a `Hello Rust!` message on the board's LED screen 60 | - (Only if you happen to have a [Heltec LoRa 32 board](https://heltec.org/project/wifi-lora-32/)): Add `heltec` to the `--features` build flags above (as in `cargo build --features heltec`) to be greeted with a `Hello Rust!` message on the board's LED screen 61 | - (Only if you happen to have an [ESP32-S3-USB-OTG](https://www.espressif.com/en/products/devkits)): Add `esp32s3_usb_otg` to the `--features` build flags above (as in `cargo build --features esp32s3_usb_otg`) to be greeted with a `Hello Rust!` message on the board's LED screen 62 | - (Only if you happen to have an [Ethernet-to-SPI board based on the W5500 chip](https://www.wiznet.io/product-item/w5500/)): Add `w5500` to the `--features` build flags above (as in `cargo build --features w5500`) to have Ethernet connectivity as part of the demo 63 | - Note that other Ethernet-to-SPI boards might work just fine as well, but you'll have to change the chip from `SpiEthDriver::W5500` to whatever chip your SPI board is using, in the demo code itself. 64 | - (Only if you happen to have an [ESP32 board with an onboard IP101 LAN chip and/or a stock ESP32 board connected to an IP101 Ethernet board via RMII](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit.html)): Add `ip101` to the `--features` build flags above (as in `cargo build --features ip101`) to have Ethernet connectivity as part of the demo 65 | - Note that other RMII Ethernet boards might work just fine as well, but you'll have to change the chip from `RmiiEthDriver::IP101` to whatever chip your board is using, in the demo code itself. 66 | - (Only if you happen to have an ESP32-S2 board and can connect a LED to GPIO Pin 04 and GND): Try accessing `http://>/ulp` once build is flashed on the MCU 67 | 68 | ## QEMU 69 | 70 | - Rather than flashing on the chip, you can now run the demo in QEMU: 71 | - Clone and then build [the Espressif fork of QEMU](https://github.com/espressif/qemu) by following the [build instructions](https://github.com/espressif/esp-toolchain-docs/blob/main/qemu/README.md) 72 | - Uncomment `CONFIG_ETH_USE_OPENETH=y`, `CONFIG_MBEDTLS_HARDWARE_AES=n`, and `CONFIG_MBEDTLS_HARDWARE_SHA=n` in `sdkconfig.defaults.esp32` (it is not enabled by default because this somehow causes issues when compiling for the ESP32S2) 73 | - Build the app with `cargo build --features qemu` 74 | - NOTE: Only ESP32 is supported for the moment, so make sure that the `xtensa-esp32-espidf` target (the default one) is active in your `.cargo/config.toml` file (or override with `cargo build --features qemu --target xtensa-esp32-espidf`) 75 | - Run it in QEMU by typing `./qemu.sh`. NOTE: You might have to change the `ESP_QEMU_PATH` in that script to point to the `build` subdirectory of your QEMU Espressif clone 76 | 77 | ## Flash 78 | 79 | - `cargo install espflash` 80 | - `espflash flash -p /dev/ttyUSB0 target/[xtensa-esp32-espidf|xtensa-esp32s2-espidf|riscv32imc-esp-espidf]/debug/rust-esp32-std-demo` 81 | - Replace `dev/ttyUSB0` above with the USB port where you've connected the board 82 | 83 | **NOTE**: The above commands do use [`espflash`](https://crates.io/crates/espflash) and NOT [`cargo espflash`](https://crates.io/crates/cargo-espflash), even though both can be installed via Cargo. `cargo espflash` is essentially `espflash` but it has some extra superpowers, like the capability to build the project before flashing, or to generate an ESP32 .BIN file from the built .ELF image. 84 | 85 | ## Alternative flashing 86 | 87 | - You can also flash with the [esptool.py](https://github.com/espressif/esptool) utility which is part of the Espressif toolset 88 | - Use the instructions below **only** if you have flashed successfully with `espflash` at least once, or else you might not have a valid bootloader and partition table! 89 | - The instructions below only (re)flash the application image, as the (one and only) factory image starting from 0x10000 in the partition table! 90 | - Install esptool using Python: `pip install esptool` 91 | - (After each cargo build) Convert the elf image to binary: `esptool.py --chip [esp32|esp32s2|esp32c3] elf2image target/xtensa-esp32-espidf/debug/rust-esp32-std-demo` 92 | - (After each cargo build) Flash the resulting binary: `esptool.py --chip [esp32|esp32s2|esp32c3] -p /dev/ttyUSB0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 4MB 0x10000 target/xtensa-esp32-espidf/debug/rust-esp32-std-demo.bin` 93 | 94 | ## Monitor 95 | 96 | - Once flashed, the board can be connected with any suitable serial monitor, e.g.: 97 | - (Recommended) `espflash`: `espflash serial-monitor` 98 | - Built-in Linux/MacOS screen: `screen /dev/ttyUSB0 115200` (use `Ctrl+A` and then type `:quit` to stop it) 99 | - Miniterm: `miniterm --raw /dev/ttyUSB0 115200` 100 | 101 | - If the app starts successfully, it should be listening on the printed IP address from the WiFi connection logs, port 80. 102 | 103 | - Open a browser, and navigate to one of these: 104 | - `http://` 105 | - `http:///foo?key=value` 106 | - `http:///bar` 107 | - `http:///ulp` (ESP32-S2 only) 108 | 109 | - Alternatively you can connect directly to the ESP Accesspoint by connecting to the 'aptest' network using the default IP address: 110 | - `http://192.168.71.1` 111 | 112 | - The monitor should output more or less the following: 113 | ``` 114 | Hello, world from Rust! 115 | More complex print [foo, bar] 116 | Rust main thread: ... 117 | This is thread number 0 ... 118 | This is thread number 1 ... 119 | This is thread number 2 ... 120 | This is thread number 3 ... 121 | This is thread number 4 ... 122 | About to join the threads. If ESP-IDF was patched successfully, joining will NOT crash 123 | Joins were successful. 124 | I (4761) wifi:wifi driver task: 3ffc1d80, prio:23, stack:6656, core=0 125 | I (4761) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE 126 | I (4761) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE 127 | I (4771) wifi:wifi firmware version: 3ea4c76 128 | I (4771) wifi:config NVS flash: disabled 129 | I (4781) wifi:config nano formating: disabled 130 | I (4781) wifi:Init dynamic tx buffer num: 32 131 | I (4791) wifi:Init data frame dynamic rx buffer num: 32 132 | I (4791) wifi:Init management frame dynamic rx buffer num: 32 133 | I (4801) wifi:Init management short buffer num: 32 134 | I (4801) wifi:Init static rx buffer size: 1600 135 | I (4811) wifi:Init static rx buffer num: 10 136 | I (4811) wifi:Init dynamic rx buffer num: 32 137 | I (4811) esp_idf_svc::wifi: Driver initialized 138 | I (4821) esp_idf_svc::wifi: Event handlers registered 139 | I (4821) esp_idf_svc::wifi: Initialization complete 140 | I (4831) rust_esp32_std_demo: Wifi created 141 | I (4831) esp_idf_svc::wifi: Setting configuration: Client(ClientConfiguration { ssid: "", bssid: None, auth_method: WPA2Personal, password: "", ip_conf: Some(DHCP) }) 142 | I (4851) esp_idf_svc::wifi: Stopping 143 | I (4861) esp_idf_svc::wifi: Disconnect requested 144 | I (4861) esp_idf_svc::wifi: Stop requested 145 | I (4871) esp_idf_svc::wifi: About to wait for status 146 | I (4871) esp_idf_svc::wifi: Providing status: Status(Stopped, Stopped) 147 | I (4881) esp_idf_svc::wifi: Waiting for status done - success 148 | I (4881) esp_idf_svc::wifi: Stopped 149 | I (4891) esp_idf_svc::wifi: Wifi mode STA set 150 | I (4891) esp_idf_svc::wifi: Setting STA configuration: ClientConfiguration { ssid: "", bssid: None, auth_method: WPA2Personal, password: "", ip_conf: Some(DHCP) } 151 | I (4911) esp_idf_svc::wifi: Setting STA IP configuration: DHCP 152 | I (4921) esp_idf_svc::wifi: STA netif allocated: 0x3ffc685c 153 | I (4921) esp_idf_svc::wifi: STA IP configuration done 154 | I (4931) esp_idf_svc::wifi: STA configuration done 155 | I (4931) esp_idf_svc::wifi: Starting with status: Status(Starting, Stopped) 156 | I (4941) esp_idf_svc::wifi: Status is of operating type, starting 157 | I (5041) phy: phy_version: 4180, cb3948e, Sep 12 2019, 16:39:13, 0, 0 158 | I (5041) wifi:mode : sta (f0:08:d1:77:68:f0) 159 | I (5041) esp_idf_svc::wifi: Got wifi event: 2 160 | I (5051) esp_idf_svc::wifi: Recconecting 161 | I (5051) esp_idf_svc::wifi: Start requested 162 | I (5051) esp_idf_svc::wifi: Set status: Status(Started(Connecting), Stopped) 163 | I (5061) esp_idf_svc::wifi: About to wait for status with timeout 10s 164 | I (5071) esp_idf_svc::wifi: Wifi event 2 handled 165 | I (5091) esp_idf_svc::wifi: Providing status: Status(Started(Connecting), Stopped) 166 | I (5171) wifi:new:<1,1>, old:<1,0>, ap:<255,255>, sta:<1,1>, prof:1 167 | I (5941) wifi:state: init -> auth (b0) 168 | I (5951) esp_idf_svc::wifi: Providing status: Status(Started(Connecting), Stopped) 169 | I (5951) wifi:state: auth -> assoc (0) 170 | I (5961) wifi:state: assoc -> run (10) 171 | I (5981) wifi:connected with muci, aid = 1, channel 1, 40U, bssid = 08:55:31:2e:c3:cf 172 | I (5981) wifi:security: WPA2-PSK, phy: bgn, rssi: -54 173 | I (5981) wifi:pm start, type: 1 174 | 175 | I (5991) esp_idf_svc::wifi: Got wifi event: 4 176 | I (5991) esp_idf_svc::wifi: Set status: Status(Started(Connected(Waiting)), Stopped) 177 | I (6001) esp_idf_svc::wifi: Wifi event 4 handled 178 | I (6011) wifi:AP's beacon interval = 102400 us, DTIM period = 1 179 | I (6451) esp_idf_svc::wifi: Providing status: Status(Started(Connected(Waiting)), Stopped) 180 | I (6951) esp_idf_svc::wifi: Providing status: Status(Started(Connected(Waiting)), Stopped) 181 | I (7451) esp_idf_svc::wifi: Providing status: Status(Started(Connected(Waiting)), Stopped) 182 | I (7951) esp_idf_svc::wifi: Providing status: Status(Started(Connected(Waiting)), Stopped) 183 | I (8221) esp_idf_svc::wifi: Got IP event: 0 184 | I (8221) esp_idf_svc::wifi: Set status: Status(Started(Connected(Done(ClientSettings { ip: 192.168.10.155, subnet: Subnet { gateway: 192.168.10.1, mask: Mask(24) }, dns: None, secondary_dns: None }))), Stopped) 185 | I (8231) esp_idf_svc::wifi: IP event 0 handled 186 | I (8241) esp_netif_handlers: staSTA netif allocated: ip: 192.168.10.155, mask: 255.255.255.0, gw: 192.168.10.1 187 | I (8451) esp_idf_svc::wifi: Providing status: Status(Started(Connected(Done(ClientSettings { ip: 192.168.10.155, subnet: Subnet { gateway: 192.168.10.1, mask: Mask(24) }, dns: None, secondary_dns: None }))), Stopped) 188 | I (8461) esp_idf_svc::wifi: Waiting for status done - success 189 | I (8461) esp_idf_svc::wifi: Started 190 | I (8471) esp_idf_svc::wifi: Configuration set 191 | I (8471) rust_esp32_std_demo: Wifi configuration set, about to get status 192 | I (8481) esp_idf_svc::wifi: Providing status: Status(Started(Connected(Done(ClientSettings { ip: 192.168.10.155, subnet: Subnet { gateway: 192.168.10.1, mask: Mask(24) }, dns: None, secondary_dns: None }))), Stopped) 193 | I (8501) rust_esp32_std_demo: Wifi connected, about to do some pings 194 | I (8511) esp_idf_svc::ping: About to run a summary ping 192.168.10.1 with configuration Configuration { count: 5, interval: 1s, timeout: 1s, data_size: 56, tos: 0 } 195 | I (8521) esp_idf_svc::ping: Ping session established, got handle 0x3ffc767c 196 | I (8531) esp_idf_svc::ping: Ping session started 197 | I (8531) esp_idf_svc::ping: Waiting for the ping session to complete 198 | I (8541) esp_idf_svc::ping: Ping success callback invoked 199 | I (8551) esp_idf_svc::ping: From 192.168.10.1 icmp_seq=1 ttl=64 time=14ms bytes=64 200 | I (9531) esp_idf_svc::ping: Ping success callback invoked 201 | I (9531) esp_idf_svc::ping: From 192.168.10.1 icmp_seq=2 ttl=64 time=1ms bytes=64 202 | I (10531) esp_idf_svc::ping: Ping success callback invoked 203 | I (10531) esp_idf_svc::ping: From 192.168.10.1 icmp_seq=3 ttl=64 time=2ms bytes=64 204 | I (11531) esp_idf_svc::ping: Ping success callback invoked 205 | I (11531) esp_idf_svc::ping: From 192.168.10.1 icmp_seq=4 ttl=64 time=0ms bytes=64 206 | I (12531) esp_idf_svc::ping: Ping success callback invoked 207 | I (12531) esp_idf_svc::ping: From 192.168.10.1 icmp_seq=5 ttl=64 time=1ms bytes=64 208 | I (13531) esp_idf_svc::ping: Ping end callback invoked 209 | I (13531) esp_idf_svc::ping: 5 packets transmitted, 5 received, time 18ms 210 | I (13531) esp_idf_svc::ping: Ping session stopped 211 | I (13531) esp_idf_svc::ping: Ping session 0x3ffc767c removed 212 | I (13541) rust_esp32_std_demo: Pinging done 213 | I (13551) esp_idf_svc::httpd: Started Httpd IDF server with config Configuration { http_port: 80, https_port: 443 } 214 | I (13561) esp_idf_svc::httpd: Registered Httpd IDF server handler Get for URI "/" 215 | I (13561) esp_idf_svc::httpd: Registered Httpd IDF server handler Get for URI "/foo" 216 | I (13571) esp_idf_svc::httpd: Registered Httpd IDF server handler Get for URI "/bar" 217 | ``` 218 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use embuild::{bingen, cargo, symgen}; 4 | 5 | fn main() { 6 | embuild::espidf::sysenv::output(); 7 | 8 | let cfg = embuild::espidf::sysenv::cfg_args().unwrap(); 9 | if cfg.get("esp32s2").is_some() { 10 | // Future; might be possible once https://github.com/rust-lang/cargo/issues/9096 hits Cargo nightly: 11 | //let ulp_elf = PathBuf::from(env::var_os("CARGO_BIN_FILE_RUST_ESP32_ULP_BLINK_rust_esp32_ulp_blink").unwrap()); 12 | 13 | let ulp_elf = PathBuf::from("ulp").join("rust-esp32-ulp-blink"); 14 | cargo::track_file(&ulp_elf); 15 | 16 | // This is where the RTC Slow Mem is mapped within the ESP32-S2 memory space 17 | let ulp_bin = symgen::Symgen::new(&ulp_elf, 0x5000_0000_u64) 18 | .run() 19 | .unwrap(); 20 | cargo::track_file(ulp_bin); 21 | 22 | let ulp_sym = bingen::Bingen::new(ulp_elf).run().unwrap(); 23 | cargo::track_file(ulp_sym); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, , 0x6000, 4 | phy_init, data, phy, , 0x1000, 5 | factory, app, factory, , 3M, -------------------------------------------------------------------------------- /qemu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # You might need to change this... 4 | ESP_QEMU_PATH=~/src/qemu-espressif/build 5 | BUILD=debug 6 | 7 | TARGET=xtensa-esp32-espidf # Don't change this. Only the ESP32 chip is supported in QEMU for now 8 | 9 | cargo espflash save-image --features qemu --merge target/$TARGET/$BUILD/rust-esp32-std-demo.bin --chip esp32 -s 4mb -T partitions.csv 10 | $ESP_QEMU_PATH/qemu-system-xtensa -nographic -machine esp32 -nic user,model=open_eth,id=lo0,hostfwd=tcp:127.0.0.1:7888-:80 -drive file=target/$TARGET/$BUILD/rust-esp32-std-demo.bin,if=mtd,format=raw 11 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Necessary for the LED screen demos 2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=20000 3 | 4 | # Necessary for async-io 5 | CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=4096 6 | 7 | CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 8 | 9 | # NAPT demo (router) 10 | CONFIG_LWIP_L2_TO_L3_COPY=y 11 | CONFIG_LWIP_IP_FORWARD=y 12 | CONFIG_LWIP_IPV4_NAPT=y 13 | 14 | # SPI Ethernet demo 15 | CONFIG_ETH_SPI_ETHERNET_DM9051=y 16 | CONFIG_ETH_SPI_ETHERNET_W5500=y 17 | CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y 18 | 19 | # Bigger headers are necessary for the QEMU demo 20 | CONFIG_HTTPD_MAX_URI_LEN=1024 21 | CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 22 | 23 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y 24 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y 25 | 26 | # Future: proper back-trace for esp32c3 27 | #CONFIG_ESP_SYSTEM_USE_EH_FRAME=y 28 | -------------------------------------------------------------------------------- /sdkconfig.defaults.esp32: -------------------------------------------------------------------------------- 1 | # Disabled, because somehow causes issues when compiling for ESP32S2, where it is not supported. 2 | # In theory, this `sdkconfig.defaults.esp32` file should not be picked up when ESP32S2 is the target, but it somehow does get picked up 3 | #CONFIG_ETH_USE_OPENETH=y 4 | #CONFIG_MBEDTLS_HARDWARE_AES=n 5 | #CONFIG_MBEDTLS_HARDWARE_SHA=n 6 | -------------------------------------------------------------------------------- /sdkconfig.defaults.esp32s2: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32S2_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32S2_ULP_COPROC_RESERVE_MEM=4096 3 | CONFIG_ESP32S2_ULP_COPROC_RISCV=y 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(clippy::single_component_path_imports)] 3 | #![allow(renamed_and_removed_lints)] 4 | #![allow(unexpected_cfgs)] 5 | //#![feature(backtrace)] 6 | 7 | #[cfg(all(feature = "qemu", not(esp32)))] 8 | compile_error!("The `qemu` feature can only be built for the `xtensa-esp32-espidf` target."); 9 | 10 | #[cfg(all(feature = "ip101", not(esp32)))] 11 | compile_error!("The `ip101` feature can only be built for the `xtensa-esp32-espidf` target."); 12 | 13 | #[cfg(all(feature = "kaluga", not(esp32s2)))] 14 | compile_error!("The `kaluga` feature can only be built for the `xtensa-esp32s2-espidf` target."); 15 | 16 | #[cfg(all(feature = "ttgo", not(esp32)))] 17 | compile_error!("The `ttgo` feature can only be built for the `xtensa-esp32-espidf` target."); 18 | 19 | #[cfg(all(feature = "heltec", not(esp32)))] 20 | compile_error!("The `heltec` feature can only be built for the `xtensa-esp32-espidf` target."); 21 | 22 | #[cfg(all(feature = "esp32s3_usb_otg", not(esp32s3)))] 23 | compile_error!( 24 | "The `esp32s3_usb_otg` feature can only be built for the `xtensa-esp32s3-espidf` target." 25 | ); 26 | 27 | use core::cell::RefCell; 28 | use core::ffi::{self, CStr}; 29 | use core::fmt::{self, Debug}; 30 | use core::sync::atomic::*; 31 | 32 | use std::fs; 33 | use std::io::{Read as _, Write as _}; 34 | use std::net::{TcpListener, TcpStream, ToSocketAddrs}; 35 | use std::os::fd::{AsRawFd, IntoRawFd}; 36 | use std::path::PathBuf; 37 | use std::sync::{Condvar, Mutex}; 38 | use std::{env, sync::Arc, thread, time::*}; 39 | 40 | use anyhow::{bail, Result}; 41 | 42 | use async_io::{Async, Timer}; 43 | use esp_idf_svc::http::server::{EspHttpConnection, Request}; 44 | use esp_idf_svc::io::{EspIOError, Write}; 45 | use log::*; 46 | 47 | use esp_idf_svc::sys::EspError; 48 | 49 | use esp_idf_svc::hal::adc; 50 | use esp_idf_svc::hal::delay; 51 | use esp_idf_svc::hal::gpio; 52 | use esp_idf_svc::hal::i2c; 53 | use esp_idf_svc::hal::peripheral; 54 | use esp_idf_svc::hal::prelude::*; 55 | use esp_idf_svc::hal::spi; 56 | 57 | use esp_idf_svc::eventloop::*; 58 | use esp_idf_svc::ipv4; 59 | use esp_idf_svc::mqtt::client::*; 60 | use esp_idf_svc::ping; 61 | use esp_idf_svc::sntp; 62 | use esp_idf_svc::systime::EspSystemTime; 63 | use esp_idf_svc::timer::*; 64 | use esp_idf_svc::wifi::*; 65 | 66 | use display_interface_spi::SPIInterfaceNoCS; 67 | 68 | use embedded_graphics::mono_font::{ascii::FONT_10X20, MonoTextStyle}; 69 | use embedded_graphics::pixelcolor::*; 70 | use embedded_graphics::prelude::*; 71 | use embedded_graphics::primitives::*; 72 | use embedded_graphics::text::*; 73 | 74 | use mipidsi; 75 | use ssd1306; 76 | use ssd1306::mode::DisplayConfig; 77 | 78 | use epd_waveshare::{epd4in2::*, graphics::VarDisplay, prelude::*}; 79 | 80 | #[allow(dead_code)] 81 | #[cfg(not(feature = "qemu"))] 82 | const SSID: &str = env!("RUST_ESP32_STD_DEMO_WIFI_SSID"); 83 | #[allow(dead_code)] 84 | #[cfg(not(feature = "qemu"))] 85 | const PASS: &str = env!("RUST_ESP32_STD_DEMO_WIFI_PASS"); 86 | 87 | #[cfg(esp32s2)] 88 | include!(env!("EMBUILD_GENERATED_SYMBOLS_FILE")); 89 | 90 | #[cfg(esp32s2)] 91 | const ULP: &[u8] = include_bytes!(env!("EMBUILD_GENERATED_BIN_FILE")); 92 | 93 | thread_local! { 94 | #[allow(clippy::thread_local_initializer_can_be_made_const)] 95 | static TLS: RefCell = const { RefCell::new(13) }; 96 | } 97 | 98 | static CS: esp_idf_svc::hal::task::CriticalSection = esp_idf_svc::hal::task::CriticalSection::new(); 99 | 100 | fn main() -> Result<()> { 101 | esp_idf_svc::sys::link_patches(); 102 | 103 | test_print(); 104 | 105 | test_atomics(); 106 | 107 | test_threads(); 108 | 109 | #[cfg(not(esp_idf_version = "4.3"))] 110 | test_fs()?; 111 | 112 | // Bind the log crate to the ESP Logging facilities 113 | esp_idf_svc::log::EspLogger::initialize_default(); 114 | 115 | // Get backtraces from anyhow; only works for Xtensa arch currently 116 | // TODO: No longer working with ESP-IDF 4.3.1+ 117 | //#[cfg(target_arch = "xtensa")] 118 | //env::set_var("RUST_BACKTRACE", "1"); 119 | 120 | #[allow(unused)] 121 | let peripherals = Peripherals::take().unwrap(); 122 | #[allow(unused)] 123 | let pins = peripherals.pins; 124 | 125 | // If interrupt critical sections work fine, the code below should panic with the IWDT triggering 126 | // { 127 | // info!("Testing interrupt critical sections"); 128 | 129 | // let mut x = 0; 130 | 131 | // esp_idf_svc::hal::interrupt::free(move || { 132 | // for _ in 0..2000000 { 133 | // for _ in 0..2000000 { 134 | // x += 1; 135 | 136 | // if x == 1000000 { 137 | // break; 138 | // } 139 | // } 140 | // } 141 | // }); 142 | // } 143 | 144 | { 145 | info!("Testing critical sections"); 146 | 147 | { 148 | let th = { 149 | let _guard = CS.enter(); 150 | 151 | let th = std::thread::spawn(move || { 152 | info!("Waiting for critical section"); 153 | let _guard = CS.enter(); 154 | 155 | info!("Critical section acquired"); 156 | }); 157 | 158 | std::thread::sleep(Duration::from_secs(5)); 159 | 160 | th 161 | }; 162 | 163 | th.join().unwrap(); 164 | } 165 | } 166 | 167 | #[allow(unused)] 168 | let sysloop = EspSystemEventLoop::take()?; 169 | 170 | #[cfg(feature = "ttgo")] 171 | ttgo_hello_world( 172 | pins.gpio4, 173 | pins.gpio16, 174 | pins.gpio23, 175 | peripherals.spi2, 176 | pins.gpio18, 177 | pins.gpio19, 178 | pins.gpio5, 179 | )?; 180 | 181 | #[cfg(feature = "waveshare_epd")] 182 | waveshare_epd_hello_world( 183 | peripherals.spi2, 184 | pins.gpio13.into(), 185 | pins.gpio14.into(), 186 | pins.gpio15.into(), 187 | pins.gpio25.into(), 188 | pins.gpio27.into(), 189 | pins.gpio26.into(), 190 | )?; 191 | 192 | #[cfg(feature = "kaluga")] 193 | kaluga_hello_world( 194 | pins.gpio6, 195 | pins.gpio13, 196 | pins.gpio16, 197 | peripherals.spi3, 198 | pins.gpio15, 199 | pins.gpio9, 200 | pins.gpio11, 201 | )?; 202 | 203 | #[cfg(feature = "heltec")] 204 | heltec_hello_world(pins.gpio16, peripherals.i2c0, pins.gpio4, pins.gpio15)?; 205 | 206 | #[cfg(feature = "ssd1306g_spi")] 207 | ssd1306g_hello_world_spi( 208 | pins.gpio4.into(), 209 | pins.gpio16.into(), 210 | peripherals.spi3, 211 | pins.gpio18.into(), 212 | pins.gpio23.into(), 213 | pins.gpio5.into(), 214 | )?; 215 | 216 | #[cfg(feature = "ssd1306g")] 217 | let mut led_power = ssd1306g_hello_world( 218 | peripherals.i2c0, 219 | pins.gpio14.into(), 220 | pins.gpio22.into(), 221 | pins.gpio21.into(), 222 | )?; 223 | 224 | #[cfg(feature = "esp32s3_usb_otg")] 225 | esp32s3_usb_otg_hello_world( 226 | pins.gpio9, 227 | pins.gpio4, 228 | pins.gpio8, 229 | peripherals.spi3, 230 | pins.gpio6, 231 | pins.gpio7, 232 | pins.gpio5, 233 | )?; 234 | 235 | #[allow(clippy::redundant_clone)] 236 | #[cfg(not(feature = "qemu"))] 237 | #[allow(unused_mut)] 238 | let mut wifi = wifi(peripherals.modem, sysloop.clone())?; 239 | 240 | #[allow(clippy::redundant_clone)] 241 | #[cfg(feature = "qemu")] 242 | let eth = { 243 | let mut eth = Box::new(esp_idf_svc::eth::EspEth::wrap( 244 | esp_idf_svc::eth::EthDriver::new_openeth(peripherals.mac, sysloop.clone())?, 245 | )?); 246 | eth_configure(&sysloop, &mut eth)?; 247 | 248 | eth 249 | }; 250 | 251 | #[allow(clippy::redundant_clone)] 252 | #[cfg(feature = "ip101")] 253 | let eth = { 254 | let mut eth = Box::new(esp_idf_svc::eth::EspEth::wrap( 255 | esp_idf_svc::eth::EthDriver::new_rmii( 256 | peripherals.mac, 257 | pins.gpio25, 258 | pins.gpio26, 259 | pins.gpio27, 260 | pins.gpio23, 261 | pins.gpio22, 262 | pins.gpio21, 263 | pins.gpio19, 264 | pins.gpio18, 265 | esp_idf_svc::eth::RmiiClockConfig::::Input( 266 | pins.gpio0, 267 | ), 268 | Some(pins.gpio5), 269 | esp_idf_svc::eth::RmiiEthChipset::IP101, 270 | None, 271 | sysloop.clone(), 272 | )?, 273 | )?); 274 | eth_configure(&sysloop, &mut eth)?; 275 | 276 | eth 277 | }; 278 | 279 | #[cfg(feature = "w5500")] 280 | let eth = { 281 | let mut eth = Box::new(esp_idf_svc::eth::EspEth::wrap( 282 | esp_idf_svc::eth::EthDriver::new_spi( 283 | spi::SpiDriver::new( 284 | peripherals.spi2, 285 | pins.gpio13, 286 | pins.gpio12, 287 | Some(pins.gpio26), 288 | &spi::SpiDriverConfig::new().dma(spi::Dma::Auto(4096)), 289 | )?, 290 | pins.gpio27, 291 | Some(pins.gpio14), 292 | Some(pins.gpio25), 293 | esp_idf_svc::eth::SpiEthChipset::W5500, 294 | 20.MHz().into(), 295 | Some(&[0x02, 0x00, 0x00, 0x12, 0x34, 0x56]), 296 | None, 297 | sysloop.clone(), 298 | )?, 299 | )?); 300 | 301 | eth_configure(&sysloop, &mut eth)?; 302 | 303 | eth 304 | }; 305 | 306 | test_tcp()?; 307 | 308 | test_tcp_bind()?; 309 | 310 | let _sntp = sntp::EspSntp::new_default()?; 311 | info!("SNTP initialized"); 312 | 313 | let (eventloop, _subscription) = test_eventloop()?; 314 | 315 | let mqtt_client = test_mqtt_client()?; 316 | 317 | let _timer = test_timer(eventloop, mqtt_client)?; 318 | 319 | #[allow(clippy::needless_update)] 320 | { 321 | esp_idf_svc::sys::esp!(unsafe { 322 | esp_idf_svc::sys::esp_vfs_eventfd_register( 323 | &esp_idf_svc::sys::esp_vfs_eventfd_config_t { 324 | max_fds: 5, 325 | ..Default::default() 326 | }, 327 | ) 328 | })?; 329 | } 330 | 331 | #[cfg(not(esp_idf_version = "4.3"))] 332 | test_tcp_bind_async()?; 333 | 334 | test_https_client()?; 335 | 336 | #[cfg(not(feature = "qemu"))] 337 | #[cfg(esp_idf_lwip_ipv4_napt)] 338 | enable_napt(&mut wifi)?; 339 | 340 | let mutex = Arc::new((Mutex::new(None), Condvar::new())); 341 | 342 | let httpd = httpd(mutex.clone())?; 343 | 344 | #[cfg(feature = "ssd1306g")] 345 | { 346 | for s in 0..3 { 347 | info!("Powering off the display in {} secs", 3 - s); 348 | thread::sleep(Duration::from_secs(1)); 349 | } 350 | 351 | led_power.set_low()?; 352 | } 353 | 354 | let mut wait = mutex.0.lock().unwrap(); 355 | 356 | #[cfg(all(esp32, esp_idf_version_major = "4"))] 357 | let mut hall_sensor = peripherals.hall_sensor; 358 | 359 | #[cfg(esp32)] 360 | let adc_pin = pins.gpio34; 361 | #[cfg(not(esp32))] 362 | let adc_pin = pins.gpio2; 363 | 364 | let mut a2 = adc::AdcChannelDriver::<{ adc::attenuation::DB_11 }, _>::new(adc_pin)?; 365 | 366 | let mut powered_adc1 = adc::AdcDriver::new( 367 | peripherals.adc1, 368 | &adc::config::Config::new().calibration(true), 369 | )?; 370 | 371 | #[allow(unused)] 372 | let cycles = loop { 373 | if let Some(cycles) = *wait { 374 | break cycles; 375 | } else { 376 | wait = mutex 377 | .1 378 | .wait_timeout(wait, Duration::from_secs(1)) 379 | .unwrap() 380 | .0; 381 | 382 | #[cfg(all(esp32, esp_idf_version_major = "4"))] 383 | log::info!( 384 | "Hall sensor reading: {}mV", 385 | powered_adc1.read_hall(&mut hall_sensor).unwrap() 386 | ); 387 | log::info!( 388 | "A2 sensor reading: {}mV", 389 | powered_adc1.read(&mut a2).unwrap() 390 | ); 391 | } 392 | }; 393 | 394 | for s in 0..3 { 395 | info!("Shutting down in {} secs", 3 - s); 396 | thread::sleep(Duration::from_secs(1)); 397 | } 398 | 399 | drop(httpd); 400 | info!("Httpd stopped"); 401 | 402 | #[cfg(not(feature = "qemu"))] 403 | { 404 | drop(wifi); 405 | info!("Wifi stopped"); 406 | } 407 | 408 | #[cfg(any(feature = "qemu", feature = "w5500", feature = "ip101"))] 409 | { 410 | drop(eth); 411 | info!("Eth stopped"); 412 | } 413 | 414 | #[cfg(esp32s2)] 415 | start_ulp(peripherals.ulp, cycles)?; 416 | 417 | Ok(()) 418 | } 419 | 420 | #[allow(clippy::vec_init_then_push)] 421 | fn test_print() { 422 | // Start simple 423 | println!("Hello from Rust!"); 424 | 425 | // Check collections 426 | let mut children = vec![]; 427 | 428 | children.push("foo"); 429 | children.push("bar"); 430 | println!("More complex print {children:?}"); 431 | } 432 | 433 | #[allow(deprecated)] 434 | fn test_atomics() { 435 | let a = AtomicUsize::new(0); 436 | let v1 = a.compare_and_swap(0, 1, Ordering::SeqCst); 437 | let v2 = a.swap(2, Ordering::SeqCst); 438 | 439 | let (r1, r2) = unsafe { 440 | // don't optimize our atomics out 441 | let r1 = core::ptr::read_volatile(&v1); 442 | let r2 = core::ptr::read_volatile(&v2); 443 | 444 | (r1, r2) 445 | }; 446 | 447 | println!("Result: {r1}, {r2}"); 448 | } 449 | 450 | fn test_threads() { 451 | let mut children = vec![]; 452 | 453 | println!("Rust main thread: {:?}", thread::current()); 454 | 455 | TLS.with(|tls| { 456 | println!("Main TLS before change: {}", *tls.borrow()); 457 | }); 458 | 459 | TLS.with(|tls| *tls.borrow_mut() = 42); 460 | 461 | TLS.with(|tls| { 462 | println!("Main TLS after change: {}", *tls.borrow()); 463 | }); 464 | 465 | for i in 0..5 { 466 | // Spin up another thread 467 | children.push(thread::spawn(move || { 468 | println!("This is thread number {}, {:?}", i, thread::current()); 469 | 470 | TLS.with(|tls| *tls.borrow_mut() = i); 471 | 472 | TLS.with(|tls| { 473 | println!("Inner TLS: {}", *tls.borrow()); 474 | }); 475 | })); 476 | } 477 | 478 | println!( 479 | "About to join the threads. If ESP-IDF was patched successfully, joining will NOT crash" 480 | ); 481 | 482 | for child in children { 483 | // Wait for the thread to finish. Returns a result. 484 | let _ = child.join(); 485 | } 486 | 487 | TLS.with(|tls| { 488 | println!("Main TLS after threads: {}", *tls.borrow()); 489 | }); 490 | 491 | thread::sleep(Duration::from_secs(2)); 492 | 493 | println!("Joins were successful."); 494 | } 495 | 496 | #[cfg(not(esp_idf_version = "4.3"))] 497 | fn test_fs() -> Result<()> { 498 | assert_eq!(fs::canonicalize(PathBuf::from("."))?, PathBuf::from("/")); 499 | assert_eq!( 500 | fs::canonicalize( 501 | PathBuf::from("/") 502 | .join("foo") 503 | .join("bar") 504 | .join(".") 505 | .join("..") 506 | .join("baz") 507 | )?, 508 | PathBuf::from("/foo/baz") 509 | ); 510 | 511 | Ok(()) 512 | } 513 | 514 | fn test_tcp() -> Result<()> { 515 | info!("About to open a TCP connection to 1.1.1.1 port 80"); 516 | 517 | let mut stream = TcpStream::connect("one.one.one.one:80")?; 518 | 519 | let err = stream.try_clone(); 520 | if let Err(err) = err { 521 | info!( 522 | "Duplication of file descriptors does not work (yet) on the ESP-IDF, as expected: {}", 523 | err 524 | ); 525 | } 526 | 527 | stream.write_all("GET / HTTP/1.0\n\n".as_bytes())?; 528 | 529 | let mut result = Vec::new(); 530 | 531 | stream.read_to_end(&mut result)?; 532 | 533 | info!( 534 | "1.1.1.1 returned:\n=================\n{}\n=================\nSince it returned something, all is OK", 535 | std::str::from_utf8(&result)?); 536 | 537 | Ok(()) 538 | } 539 | 540 | fn test_tcp_bind() -> Result<()> { 541 | fn test_tcp_bind_accept() -> Result<()> { 542 | info!("About to bind a simple echo service to port 8080"); 543 | 544 | let listener = TcpListener::bind("0.0.0.0:8080")?; 545 | 546 | for stream in listener.incoming() { 547 | match stream { 548 | Ok(stream) => { 549 | info!("Accepted client"); 550 | 551 | thread::spawn(move || { 552 | test_tcp_bind_handle_client(stream); 553 | }); 554 | } 555 | Err(e) => { 556 | error!("Error: {}", e); 557 | } 558 | } 559 | } 560 | 561 | unreachable!() 562 | } 563 | 564 | fn test_tcp_bind_handle_client(mut stream: TcpStream) { 565 | // read 20 bytes at a time from stream echoing back to stream 566 | loop { 567 | let mut read = [0; 128]; 568 | 569 | match stream.read(&mut read) { 570 | Ok(n) => { 571 | if n == 0 { 572 | // connection was closed 573 | break; 574 | } 575 | stream.write_all(&read[0..n]).unwrap(); 576 | } 577 | Err(err) => { 578 | panic!("{}", err); 579 | } 580 | } 581 | } 582 | } 583 | 584 | thread::spawn(|| test_tcp_bind_accept().unwrap()); 585 | 586 | Ok(()) 587 | } 588 | 589 | fn test_timer( 590 | eventloop: EspBackgroundEventLoop, 591 | mut client: EspMqttClient<'static>, 592 | ) -> Result { 593 | info!("About to schedule a one-shot timer for after 2 seconds"); 594 | let once_timer = EspTaskTimerService::new()?.timer(|| { 595 | info!("One-shot timer triggered"); 596 | })?; 597 | 598 | once_timer.after(Duration::from_secs(2))?; 599 | 600 | thread::sleep(Duration::from_secs(3)); 601 | 602 | info!("About to schedule a periodic timer every five seconds"); 603 | let periodic_timer = unsafe { 604 | EspTaskTimerService::new()?.timer_nonstatic(move || { 605 | info!("Tick from periodic timer"); 606 | 607 | let now = EspSystemTime {}.now(); 608 | 609 | eventloop 610 | .post::(&CustomEvent::new(now), delay::NON_BLOCK) 611 | .unwrap(); 612 | 613 | client 614 | .publish( 615 | "rust-esp32-std-demo", 616 | QoS::AtMostOnce, 617 | false, 618 | format!("Now is {now:?}").as_bytes(), 619 | ) 620 | .unwrap(); 621 | })? 622 | }; 623 | 624 | periodic_timer.every(Duration::from_secs(5))?; 625 | 626 | Ok(periodic_timer) 627 | } 628 | 629 | #[derive(Copy, Clone, Debug)] 630 | struct CustomEvent(Duration); 631 | 632 | impl CustomEvent { 633 | pub fn new(duration: Duration) -> Self { 634 | Self(duration) 635 | } 636 | } 637 | 638 | unsafe impl EspEventSource for CustomEvent { 639 | fn source() -> Option<&'static CStr> { 640 | // String should be unique across the whole project and ESP IDF 641 | Some(CStr::from_bytes_with_nul(b"DEMO-SERVICE\0").unwrap()) 642 | } 643 | } 644 | 645 | impl EspEventSerializer for CustomEvent { 646 | type Data<'a> = CustomEvent; 647 | 648 | fn serialize(event: &Self::Data<'_>, f: F) -> R 649 | where 650 | F: FnOnce(&EspEventPostData) -> R, 651 | { 652 | // Go the easy way since our payload implements Copy and is `'static` 653 | f(&unsafe { EspEventPostData::new(Self::source().unwrap(), Self::event_id(), event) }) 654 | } 655 | } 656 | 657 | impl EspEventDeserializer for CustomEvent { 658 | type Data<'a> = CustomEvent; 659 | 660 | fn deserialize<'a>(data: &EspEvent<'a>) -> Self::Data<'a> { 661 | // Just as easy as serializing 662 | *unsafe { data.as_payload::() } 663 | } 664 | } 665 | 666 | fn test_eventloop() -> Result<(EspBackgroundEventLoop, EspBackgroundSubscription<'static>)> { 667 | info!("About to start a background event loop"); 668 | let eventloop = EspBackgroundEventLoop::new(&Default::default())?; 669 | 670 | info!("About to subscribe to the background event loop"); 671 | let subscription = eventloop.subscribe::(|message| { 672 | info!("Got message from the event loop: {:?}", message.0); 673 | })?; 674 | 675 | Ok((eventloop, subscription)) 676 | } 677 | 678 | fn test_mqtt_client() -> Result> { 679 | info!("About to start MQTT client"); 680 | 681 | let conf = MqttClientConfiguration { 682 | client_id: Some("rust-esp32-std-demo"), 683 | crt_bundle_attach: Some(esp_idf_svc::sys::esp_crt_bundle_attach), 684 | 685 | ..Default::default() 686 | }; 687 | 688 | let (mut client, mut connection) = EspMqttClient::new("mqtts://broker.emqx.io:8883", &conf)?; 689 | 690 | info!("MQTT client started"); 691 | 692 | // Need to immediately start pumping the connection for messages, or else subscribe() and publish() below will not work 693 | // Note that when using the alternative constructor - `EspMqttClient::new` - you don't need to 694 | // spawn a new thread, as the messages will be pumped with a backpressure into the callback you provide. 695 | // Yet, you still need to efficiently process each message in the callback without blocking for too long. 696 | // 697 | // Note also that if you go to http://tools.emqx.io/ and then connect and send a message to topic 698 | // "rust-esp32-std-demo", the client configured here should receive it. 699 | thread::spawn(move || { 700 | info!("MQTT Listening for messages"); 701 | 702 | while let Ok(event) = connection.next() { 703 | info!("MQTT Event: {}", event.payload()); 704 | } 705 | 706 | info!("MQTT connection loop exit"); 707 | }); 708 | 709 | client.subscribe("rust-esp32-std-demo", QoS::AtMostOnce)?; 710 | 711 | info!("Subscribed to all topics (rust-esp32-std-demo)"); 712 | 713 | client.publish( 714 | "rust-esp32-std-demo", 715 | QoS::AtMostOnce, 716 | false, 717 | "Hello from rust-esp32-std-demo!".as_bytes(), 718 | )?; 719 | 720 | info!("Published a hello message to topic \"rust-esp32-std-demo\""); 721 | 722 | Ok(client) 723 | } 724 | 725 | #[cfg(not(esp_idf_version = "4.3"))] 726 | fn test_tcp_bind_async() -> anyhow::Result<()> { 727 | use std::pin::pin; 728 | 729 | use async_executor::LocalExecutor; 730 | 731 | async fn test_tcp_bind(executor: &LocalExecutor<'_>) -> std::io::Result<()> { 732 | /// Echoes messages from the client back to it. 733 | async fn echo(stream: async_io::Async) -> std::io::Result<()> { 734 | futures_lite::io::copy(&stream, &mut &stream).await?; 735 | Ok(()) 736 | } 737 | 738 | // Create a listener. 739 | let listener = async_io::Async::::bind(([0, 0, 0, 0], 8081))?; 740 | 741 | // Accept clients in a loop. 742 | loop { 743 | let (stream, peer_addr) = listener.accept().await?; 744 | info!("Accepted client: {}", peer_addr); 745 | 746 | // Spawn a task that echoes messages from the client back to it. 747 | executor.spawn(async { echo(stream).await }).detach(); 748 | } 749 | } 750 | 751 | info!("About to bind a simple echo service to port 8081 using async (with async-io)!"); 752 | 753 | thread::Builder::new().stack_size(20000).spawn(move || { 754 | let executor = LocalExecutor::new(); 755 | 756 | let fut = &mut pin!(test_tcp_bind(&executor)); 757 | 758 | async_io::block_on(executor.run(fut)).unwrap(); 759 | })?; 760 | 761 | Ok(()) 762 | } 763 | 764 | fn test_https_client() -> anyhow::Result<()> { 765 | async fn test() -> anyhow::Result<()> { 766 | // Implement `esp_idf_svc::tls::PollableSocket` for async-io sockets 767 | //////////////////////////////////////////////////////////////////// 768 | 769 | pub struct EspTlsSocket(Option>); 770 | 771 | impl EspTlsSocket { 772 | pub const fn new(socket: async_io::Async) -> Self { 773 | Self(Some(socket)) 774 | } 775 | 776 | pub fn handle(&self) -> i32 { 777 | self.0.as_ref().unwrap().as_raw_fd() 778 | } 779 | 780 | pub fn poll_readable( 781 | &self, 782 | ctx: &mut core::task::Context, 783 | ) -> core::task::Poll> { 784 | self.0 785 | .as_ref() 786 | .unwrap() 787 | .poll_readable(ctx) 788 | .map_err(|_| EspError::from_infallible::<{ esp_idf_svc::sys::ESP_FAIL }>()) 789 | } 790 | 791 | pub fn poll_writeable( 792 | &self, 793 | ctx: &mut core::task::Context, 794 | ) -> core::task::Poll> { 795 | self.0 796 | .as_ref() 797 | .unwrap() 798 | .poll_writable(ctx) 799 | .map_err(|_| EspError::from_infallible::<{ esp_idf_svc::sys::ESP_FAIL }>()) 800 | } 801 | 802 | fn release(&mut self) -> Result<(), esp_idf_svc::sys::EspError> { 803 | let socket = self.0.take().unwrap(); 804 | socket.into_inner().unwrap().into_raw_fd(); 805 | 806 | Ok(()) 807 | } 808 | } 809 | 810 | impl esp_idf_svc::tls::Socket for EspTlsSocket { 811 | fn handle(&self) -> i32 { 812 | EspTlsSocket::handle(self) 813 | } 814 | 815 | fn release(&mut self) -> Result<(), esp_idf_svc::sys::EspError> { 816 | EspTlsSocket::release(self) 817 | } 818 | } 819 | 820 | impl esp_idf_svc::tls::PollableSocket for EspTlsSocket { 821 | fn poll_readable( 822 | &self, 823 | ctx: &mut core::task::Context, 824 | ) -> core::task::Poll> { 825 | EspTlsSocket::poll_readable(self, ctx) 826 | } 827 | 828 | fn poll_writable( 829 | &self, 830 | ctx: &mut core::task::Context, 831 | ) -> core::task::Poll> { 832 | EspTlsSocket::poll_writeable(self, ctx) 833 | } 834 | } 835 | 836 | //////////////////////////////////////////////////////////////////// 837 | 838 | let addr = "google.com:443".to_socket_addrs()?.next().unwrap(); 839 | let socket = Async::::connect(addr).await?; 840 | 841 | let mut tls = esp_idf_svc::tls::EspAsyncTls::adopt(EspTlsSocket::new(socket))?; 842 | 843 | tls.negotiate("google.com", &esp_idf_svc::tls::Config::new()) 844 | .await?; 845 | 846 | tls.write_all(b"GET / HTTP/1.0\r\n\r\n").await?; 847 | 848 | let mut body = [0_u8; 3048]; 849 | 850 | let read = esp_idf_svc::io::utils::asynch::try_read_full(&mut tls, &mut body) 851 | .await 852 | .map_err(|(e, _)| e)?; 853 | 854 | info!( 855 | "Body (truncated to 3K):\n{:?}", 856 | String::from_utf8_lossy(&body[..read]).into_owned() 857 | ); 858 | 859 | Ok(()) 860 | } 861 | 862 | let th = thread::Builder::new() 863 | .stack_size(20000) 864 | .spawn(move || async_io::block_on(test()))?; 865 | 866 | th.join().unwrap() 867 | } 868 | 869 | #[cfg(feature = "ttgo")] 870 | fn ttgo_hello_world( 871 | backlight: gpio::Gpio4, 872 | dc: gpio::Gpio16, 873 | rst: gpio::Gpio23, 874 | spi: spi::SPI2, 875 | sclk: gpio::Gpio18, 876 | sdo: gpio::Gpio19, 877 | cs: gpio::Gpio5, 878 | ) -> Result<()> { 879 | info!("About to initialize the TTGO ST7789 LED driver"); 880 | 881 | let mut backlight = gpio::PinDriver::output(backlight)?; 882 | backlight.set_high()?; 883 | 884 | let di = SPIInterfaceNoCS::new( 885 | spi::SpiDeviceDriver::new_single( 886 | spi, 887 | sclk, 888 | sdo, 889 | Option::::None, 890 | Some(cs), 891 | &spi::SpiDriverConfig::new().dma(spi::Dma::Disabled), 892 | &spi::SpiConfig::new().baudrate(26.MHz().into()), 893 | )?, 894 | gpio::PinDriver::output(dc)?, 895 | ); 896 | 897 | let mut display = mipidsi::Builder::st7789(di) 898 | .init(&mut delay::Ets, Some(gpio::PinDriver::output(rst)?)) 899 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 900 | 901 | display 902 | .set_orientation(mipidsi::options::Orientation::Portrait(false)) 903 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 904 | 905 | // The TTGO board's screen does not start at offset 0x0, and the physical size is 135x240, instead of 240x320 906 | let top_left = Point::new(52, 40); 907 | let size = Size::new(135, 240); 908 | 909 | led_draw(&mut display.cropped(&Rectangle::new(top_left, size))) 910 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e)) 911 | } 912 | 913 | #[cfg(feature = "kaluga")] 914 | fn kaluga_hello_world( 915 | backlight: gpio::Gpio6, 916 | dc: gpio::Gpio13, 917 | rst: gpio::Gpio16, 918 | spi: spi::SPI3, 919 | sclk: gpio::Gpio15, 920 | sdo: gpio::Gpio9, 921 | cs: gpio::Gpio11, 922 | ) -> Result<()> { 923 | info!("About to initialize the Kaluga ST7789 SPI LED driver"); 924 | 925 | let mut backlight = gpio::PinDriver::output(backlight)?; 926 | backlight.set_high()?; 927 | 928 | let di = SPIInterfaceNoCS::new( 929 | spi::SpiDeviceDriver::new_single( 930 | spi, 931 | sclk, 932 | sdo, 933 | Option::::None, 934 | Some(cs), 935 | &spi::SpiDriverConfig::new().dma(spi::Dma::Disabled), 936 | &spi::SpiConfig::new().baudrate(80.MHz().into()), 937 | )?, 938 | gpio::PinDriver::output(dc)?, 939 | ); 940 | 941 | let mut display = mipidsi::Builder::st7789(di) 942 | .init(&mut delay::Ets, Some(gpio::PinDriver::output(rst)?)) 943 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 944 | 945 | display 946 | .set_orientation(mipidsi::options::Orientation::Landscape(false)) 947 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 948 | 949 | led_draw(&mut display).map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 950 | 951 | Ok(()) 952 | } 953 | 954 | #[cfg(feature = "heltec")] 955 | fn heltec_hello_world( 956 | rst: gpio::Gpio16, 957 | i2c: i2c::I2C0, 958 | sda: gpio::Gpio4, 959 | scl: gpio::Gpio15, 960 | ) -> Result<()> { 961 | info!("About to initialize the Heltec SSD1306 I2C LED driver"); 962 | 963 | let di = ssd1306::I2CDisplayInterface::new(i2c::I2cDriver::new( 964 | i2c, 965 | sda, 966 | scl, 967 | &i2c::I2cConfig::new().baudrate(400.kHz().into()), 968 | )?); 969 | 970 | let mut reset = gpio::PinDriver::output(rst)?; 971 | 972 | reset.set_high()?; 973 | delay::Ets::delay_ms(1 as u32); 974 | 975 | reset.set_low()?; 976 | delay::Ets::delay_ms(10 as u32); 977 | 978 | reset.set_high()?; 979 | 980 | let mut display = ssd1306::Ssd1306::new( 981 | di, 982 | ssd1306::size::DisplaySize128x64, 983 | ssd1306::rotation::DisplayRotation::Rotate0, 984 | ) 985 | .into_buffered_graphics_mode(); 986 | 987 | display 988 | .init() 989 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 990 | 991 | led_draw_custom( 992 | &mut display, 993 | BinaryColor::Off, 994 | BinaryColor::On, 995 | BinaryColor::On, 996 | BinaryColor::On, 997 | ) 998 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 999 | 1000 | display 1001 | .flush() 1002 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1003 | 1004 | Ok(()) 1005 | } 1006 | 1007 | #[cfg(feature = "ssd1306g_spi")] 1008 | fn ssd1306g_hello_world_spi( 1009 | dc: gpio::AnyOutputPin, 1010 | rst: gpio::AnyOutputPin, 1011 | spi: impl peripheral::Peripheral

+ 'static, 1012 | sclk: gpio::AnyOutputPin, 1013 | sdo: gpio::AnyOutputPin, 1014 | cs: gpio::AnyOutputPin, 1015 | ) -> Result<()> { 1016 | info!("About to initialize the SSD1306 SPI LED driver"); 1017 | 1018 | let di = SPIInterfaceNoCS::new( 1019 | spi::SpiDeviceDriver::new_single( 1020 | spi, 1021 | sclk, 1022 | sdo, 1023 | Option::::None, 1024 | Some(cs), 1025 | &spi::SpiDriverConfig::new().dma(spi::Dma::Disabled), 1026 | &spi::SpiConfig::new().baudrate(10.MHz().into()), 1027 | )?, 1028 | gpio::PinDriver::output(dc)?, 1029 | ); 1030 | 1031 | let mut reset = gpio::PinDriver::output(rst)?; 1032 | 1033 | reset.set_high()?; 1034 | delay::Ets::delay_ms(1 as u32); 1035 | 1036 | reset.set_low()?; 1037 | delay::Ets::delay_ms(10 as u32); 1038 | 1039 | reset.set_high()?; 1040 | 1041 | let mut display = ssd1306::Ssd1306::new( 1042 | di, 1043 | ssd1306::size::DisplaySize128x64, 1044 | ssd1306::rotation::DisplayRotation::Rotate180, 1045 | ) 1046 | .into_buffered_graphics_mode(); 1047 | 1048 | display 1049 | .init() 1050 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1051 | 1052 | led_draw_custom( 1053 | &mut display, 1054 | BinaryColor::Off, 1055 | BinaryColor::On, 1056 | BinaryColor::On, 1057 | BinaryColor::On, 1058 | ) 1059 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1060 | 1061 | display 1062 | .flush() 1063 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1064 | 1065 | Ok(()) 1066 | } 1067 | 1068 | #[cfg(feature = "ssd1306g")] 1069 | fn ssd1306g_hello_world( 1070 | i2c: impl peripheral::Peripheral

+ 'static, 1071 | pwr: gpio::AnyOutputPin, 1072 | scl: gpio::AnyIOPin, 1073 | sda: gpio::AnyIOPin, 1074 | ) -> Result> { 1075 | info!("About to initialize a generic SSD1306 I2C LED driver"); 1076 | 1077 | let di = ssd1306::I2CDisplayInterface::new(i2c::I2cDriver::new( 1078 | i2c, 1079 | sda, 1080 | scl, 1081 | &i2c::I2cConfig::new().baudrate(400.kHz().into()), 1082 | )?); 1083 | 1084 | let mut power = gpio::PinDriver::output(pwr)?; 1085 | 1086 | // Powering an OLED display via an output pin allows one to shutdown the display 1087 | // when it is no longer needed so as to conserve power 1088 | // 1089 | // Of course, the I2C driver should also be properly de-initialized etc. 1090 | power.set_drive_strength(gpio::DriveStrength::I40mA)?; 1091 | power.set_high()?; 1092 | delay::Ets::delay_ms(10_u32); 1093 | 1094 | let mut display = ssd1306::Ssd1306::new( 1095 | di, 1096 | ssd1306::size::DisplaySize128x64, 1097 | ssd1306::rotation::DisplayRotation::Rotate0, 1098 | ) 1099 | .into_buffered_graphics_mode(); 1100 | 1101 | display 1102 | .init() 1103 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1104 | 1105 | led_draw_custom( 1106 | &mut display, 1107 | BinaryColor::Off, 1108 | BinaryColor::On, 1109 | BinaryColor::On, 1110 | BinaryColor::On, 1111 | ) 1112 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1113 | 1114 | display 1115 | .flush() 1116 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1117 | 1118 | Ok(power) 1119 | } 1120 | 1121 | #[cfg(feature = "esp32s3_usb_otg")] 1122 | fn esp32s3_usb_otg_hello_world( 1123 | backlight: gpio::Gpio9, 1124 | dc: gpio::Gpio4, 1125 | rst: gpio::Gpio8, 1126 | spi: spi::SPI3, 1127 | sclk: gpio::Gpio6, 1128 | sdo: gpio::Gpio7, 1129 | cs: gpio::Gpio5, 1130 | ) -> Result<()> { 1131 | info!("About to initialize the ESP32-S3-USB-OTG SPI LED driver ST7789VW"); 1132 | 1133 | let mut backlight = gpio::PinDriver::output(backlight)?; 1134 | backlight.set_high()?; 1135 | 1136 | let di = SPIInterfaceNoCS::new( 1137 | spi::SpiDeviceDriver::new_single( 1138 | spi, 1139 | sclk, 1140 | sdo, 1141 | Option::::None, 1142 | Some(cs), 1143 | &spi::SpiDriverConfig::new().dma(spi::Dma::Disabled), 1144 | &spi::SpiConfig::new().baudrate(80.MHz().into()), 1145 | )?, 1146 | gpio::PinDriver::output(dc)?, 1147 | ); 1148 | 1149 | let mut display = mipidsi::Builder::st7789(di) 1150 | .init(&mut delay::Ets, Some(gpio::PinDriver::output(rst)?)) 1151 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1152 | 1153 | display 1154 | .set_orientation(mipidsi::options::Orientation::Landscape(false)) 1155 | .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; 1156 | 1157 | led_draw(&mut display).map_err(|e| anyhow::anyhow!("Led draw error: {:?}", e)) 1158 | } 1159 | 1160 | #[allow(dead_code)] 1161 | fn led_draw(display: &mut D) -> Result<(), D::Error> 1162 | where 1163 | D: DrawTarget + Dimensions, 1164 | D::Color: RgbColor, 1165 | { 1166 | led_draw_custom( 1167 | display, 1168 | RgbColor::BLACK, 1169 | RgbColor::WHITE, 1170 | RgbColor::BLUE, 1171 | RgbColor::YELLOW, 1172 | ) 1173 | } 1174 | 1175 | #[allow(dead_code)] 1176 | fn led_draw_custom( 1177 | display: &mut D, 1178 | bg: D::Color, 1179 | fg: D::Color, 1180 | fill: D::Color, 1181 | stroke: D::Color, 1182 | ) -> Result<(), D::Error> 1183 | where 1184 | D: DrawTarget + Dimensions, 1185 | { 1186 | display.clear(bg)?; 1187 | 1188 | Rectangle::new(display.bounding_box().top_left, display.bounding_box().size) 1189 | .into_styled( 1190 | PrimitiveStyleBuilder::new() 1191 | .fill_color(fill) 1192 | .stroke_color(stroke) 1193 | .stroke_width(1) 1194 | .build(), 1195 | ) 1196 | .draw(display)?; 1197 | 1198 | Text::new( 1199 | "Hello Rust!", 1200 | Point::new(10, (display.bounding_box().size.height - 10) as i32 / 2), 1201 | MonoTextStyle::new(&FONT_10X20, fg), 1202 | ) 1203 | .draw(display)?; 1204 | 1205 | info!("LED rendering done"); 1206 | 1207 | Ok(()) 1208 | } 1209 | 1210 | #[allow(unused_variables)] 1211 | fn httpd( 1212 | mutex: Arc<(Mutex>, Condvar)>, 1213 | ) -> Result> { 1214 | use esp_idf_svc::http::server::{ 1215 | fn_handler, Connection, EspHttpServer, Handler, Method, Middleware, 1216 | }; 1217 | 1218 | struct SampleMiddleware; 1219 | 1220 | impl<'a, H> Middleware, H> for SampleMiddleware 1221 | where 1222 | H: Handler>, 1223 | H::Error: Debug + fmt::Display + Send + Sync + 'static, 1224 | { 1225 | type Error = anyhow::Error; 1226 | 1227 | fn handle(&self, conn: &mut EspHttpConnection<'a>, handler: &H) -> Result<(), Self::Error> { 1228 | info!("Middleware called with uri: {}", conn.uri()); 1229 | 1230 | if let Err(err) = handler.handle(conn) { 1231 | if !conn.is_response_initiated() { 1232 | let mut resp = Request::wrap(conn).into_status_response(500)?; 1233 | 1234 | write!(&mut resp, "ERROR: {err:?}")?; 1235 | } else { 1236 | // Nothing can be done as the error happened after the response was initiated, propagate further 1237 | Err(anyhow::Error::msg(err))?; 1238 | } 1239 | } 1240 | 1241 | Ok(()) 1242 | } 1243 | } 1244 | 1245 | struct SampleMiddleware2; 1246 | 1247 | impl<'a, H> Middleware, H> for SampleMiddleware2 1248 | where 1249 | H: Handler>, 1250 | { 1251 | type Error = H::Error; 1252 | 1253 | fn handle(&self, conn: &mut EspHttpConnection<'a>, handler: &H) -> Result<(), H::Error> { 1254 | info!("Middleware2 called"); 1255 | 1256 | handler.handle(conn) 1257 | } 1258 | } 1259 | 1260 | let mut server = EspHttpServer::new(&Default::default())?; 1261 | 1262 | server 1263 | .fn_handler("/", Method::Get, |req| { 1264 | req.into_ok_response()? 1265 | .write_all("Hello from Rust!".as_bytes())?; 1266 | 1267 | Result::<_, EspIOError>::Ok(()) 1268 | })? 1269 | .fn_handler("/foo", Method::Get, |_| bail!("Boo, something happened!"))? 1270 | .fn_handler("/bar", Method::Get, |req| { 1271 | req.into_response(403, Some("No permissions"), &[])? 1272 | .write_all("You have no permissions to access this page".as_bytes()) 1273 | })? 1274 | .fn_handler::<(), _>("/panic", Method::Get, |_| panic!("User requested a panic!"))? 1275 | .handler( 1276 | "/middleware", 1277 | Method::Get, 1278 | SampleMiddleware {}.compose(fn_handler(|_| bail!("Boo, something happened!"))), 1279 | )? 1280 | .handler( 1281 | "/middleware2", 1282 | Method::Get, 1283 | SampleMiddleware2 {}.compose(SampleMiddleware {}.compose(fn_handler(|req| { 1284 | req.into_ok_response()? 1285 | .write_all("Middleware2 handler called".as_bytes()) 1286 | }))), 1287 | )?; 1288 | 1289 | #[cfg(esp32s2)] 1290 | httpd_ulp_endpoints(&mut server, mutex)?; 1291 | 1292 | Ok(server) 1293 | } 1294 | 1295 | #[cfg(esp32s2)] 1296 | fn httpd_ulp_endpoints( 1297 | server: &mut esp_idf_svc::http::server::EspHttpServer, 1298 | mutex: Arc<(Mutex>, Condvar)>, 1299 | ) -> Result<()> { 1300 | server 1301 | .handlee("/ulp", Method::Get, |conn| { 1302 | conn.initiate_ok_response()?; 1303 | conn.write_all( 1304 | r#" 1305 | 1306 | 1307 | 1308 |

1309 | Connect a LED to ESP32-S2 GPIO Pin 04 and GND.
1310 | Blink it with ULP times 1311 | 1312 |
1313 | 1314 | 1315 | "# 1316 | .as_bytes())?; 1317 | 1318 | Ok(()) 1319 | })? 1320 | .handler("/ulp_start", Method::Post, move |conn| { 1321 | let mut body = Vec::new(); 1322 | 1323 | ToStd::new(req.reader()).read_to_end(&mut body)?; 1324 | 1325 | let cycles = url::form_urlencoded::parse(&body) 1326 | .filter(|p| p.0 == "cycles") 1327 | .map(|p| str::parse::(&p.1).map_err(Error::msg)) 1328 | .next() 1329 | .ok_or(anyhow::anyhow!("No parameter cycles"))??; 1330 | 1331 | let mut wait = mutex.0.lock().unwrap(); 1332 | 1333 | *wait = Some(cycles); 1334 | mutex.1.notify_one(); 1335 | 1336 | conn.write_all( 1337 | &format!( 1338 | r#" 1339 | 1340 | 1341 | 1342 | About to sleep now. The ULP chip should blink the LED {} times and then wake me up. Bye! 1343 | 1344 | 1345 | "#, 1346 | cycles) 1347 | .as_bytes())?; 1348 | 1349 | Ok(()) 1350 | })?; 1351 | 1352 | Ok(()) 1353 | } 1354 | 1355 | #[cfg(esp32s2)] 1356 | fn start_ulp(mut ulp: esp_idf_svc::hal::ulp::ULP, cycles: u32) -> Result<()> { 1357 | let cycles_var = CYCLES as *mut u32; 1358 | 1359 | unsafe { 1360 | ulp.load(ULP)?; 1361 | info!("RiscV ULP binary loaded successfully"); 1362 | 1363 | info!( 1364 | "Default ULP LED blink cycles: {}", 1365 | ulp.read_var(cycles_var)? 1366 | ); 1367 | 1368 | ulp.write_var(cycles_var, cycles)?; 1369 | info!( 1370 | "Sent {} LED blink cycles to the ULP", 1371 | ulp.read_var(cycles_var)? 1372 | ); 1373 | 1374 | ulp.start()?; 1375 | info!("RiscV ULP started"); 1376 | 1377 | esp_idf_svc::sys::esp!(esp_idf_svc::sys::esp_sleep_enable_ulp_wakeup())?; 1378 | info!("Wakeup from ULP enabled"); 1379 | 1380 | // Wake up by a timer in 60 seconds 1381 | info!("About to get to sleep now. Will wake up automatically either in 1 minute, or once the ULP has done blinking the LED"); 1382 | esp_idf_svc::sys::esp_deep_sleep(Duration::from_secs(60).as_micros() as u64); 1383 | } 1384 | 1385 | Ok(()) 1386 | } 1387 | 1388 | #[cfg(not(feature = "qemu"))] 1389 | #[allow(dead_code)] 1390 | fn wifi( 1391 | modem: impl peripheral::Peripheral

+ 'static, 1392 | sysloop: EspSystemEventLoop, 1393 | ) -> Result>> { 1394 | let mut esp_wifi = EspWifi::new(modem, sysloop.clone(), None)?; 1395 | 1396 | let mut wifi = BlockingWifi::wrap(&mut esp_wifi, sysloop)?; 1397 | 1398 | wifi.set_configuration(&Configuration::Client(ClientConfiguration::default()))?; 1399 | 1400 | info!("Starting wifi..."); 1401 | 1402 | wifi.start()?; 1403 | 1404 | info!("Scanning..."); 1405 | 1406 | let ap_infos = wifi.scan()?; 1407 | 1408 | let ours = ap_infos.into_iter().find(|a| a.ssid == SSID); 1409 | 1410 | let channel = if let Some(ours) = ours { 1411 | info!( 1412 | "Found configured access point {} on channel {}", 1413 | SSID, ours.channel 1414 | ); 1415 | Some(ours.channel) 1416 | } else { 1417 | info!( 1418 | "Configured access point {} not found during scanning, will go with unknown channel", 1419 | SSID 1420 | ); 1421 | None 1422 | }; 1423 | 1424 | wifi.set_configuration(&Configuration::Mixed( 1425 | ClientConfiguration { 1426 | ssid: SSID.try_into().unwrap(), 1427 | password: PASS.try_into().unwrap(), 1428 | channel, 1429 | ..Default::default() 1430 | }, 1431 | AccessPointConfiguration { 1432 | ssid: "aptest".try_into().unwrap(), 1433 | channel: channel.unwrap_or(1), 1434 | ..Default::default() 1435 | }, 1436 | ))?; 1437 | 1438 | info!("Connecting wifi..."); 1439 | 1440 | wifi.connect()?; 1441 | 1442 | info!("Waiting for DHCP lease..."); 1443 | 1444 | wifi.wait_netif_up()?; 1445 | 1446 | let ip_info = wifi.wifi().sta_netif().get_ip_info()?; 1447 | 1448 | info!("Wifi DHCP info: {:?}", ip_info); 1449 | 1450 | ping(ip_info.subnet.gateway)?; 1451 | 1452 | Ok(Box::new(esp_wifi)) 1453 | } 1454 | 1455 | #[cfg(any(feature = "qemu", feature = "w5500", feature = "ip101"))] 1456 | fn eth_configure<'d, T>( 1457 | sysloop: &EspSystemEventLoop, 1458 | eth: &mut esp_idf_svc::eth::EspEth<'d, T>, 1459 | ) -> Result<()> { 1460 | info!("Eth created"); 1461 | 1462 | let mut eth = esp_idf_svc::eth::BlockingEth::wrap(eth, sysloop.clone())?; 1463 | 1464 | info!("Starting eth..."); 1465 | 1466 | eth.start()?; 1467 | 1468 | info!("Waiting for DHCP lease..."); 1469 | 1470 | eth.wait_netif_up()?; 1471 | 1472 | let ip_info = eth.eth().netif().get_ip_info()?; 1473 | 1474 | info!("Eth DHCP info: {:?}", ip_info); 1475 | 1476 | ping(ip_info.subnet.gateway)?; 1477 | 1478 | Ok(()) 1479 | } 1480 | 1481 | fn ping(ip: ipv4::Ipv4Addr) -> Result<()> { 1482 | info!("About to do some pings for {:?}", ip); 1483 | 1484 | let ping_summary = ping::EspPing::default().ping(ip, &Default::default())?; 1485 | if ping_summary.transmitted != ping_summary.received { 1486 | bail!("Pinging IP {} resulted in timeouts", ip); 1487 | } 1488 | 1489 | info!("Pinging done"); 1490 | 1491 | Ok(()) 1492 | } 1493 | 1494 | #[cfg(not(feature = "qemu"))] 1495 | #[cfg(esp_idf_lwip_ipv4_napt)] 1496 | fn enable_napt(wifi: &mut EspWifi) -> Result<()> { 1497 | wifi.ap_netif_mut().enable_napt(true); 1498 | 1499 | info!("NAPT enabled on the WiFi SoftAP!"); 1500 | 1501 | Ok(()) 1502 | } 1503 | 1504 | #[cfg(feature = "waveshare_epd")] 1505 | fn waveshare_epd_hello_world( 1506 | spi: impl peripheral::Peripheral

+ 'static, 1507 | sclk: gpio::AnyOutputPin, 1508 | sdo: gpio::AnyOutputPin, 1509 | cs: gpio::AnyOutputPin, 1510 | busy_in: gpio::AnyInputPin, 1511 | dc: gpio::AnyOutputPin, 1512 | rst: gpio::AnyOutputPin, 1513 | ) -> Result<()> { 1514 | info!("About to initialize Waveshare 4.2 e-paper display"); 1515 | 1516 | let mut driver = spi::SpiDeviceDriver::new_single( 1517 | spi, 1518 | sclk, 1519 | sdo, 1520 | Option::::None, 1521 | Option::::None, 1522 | &spi::SpiDriverConfig::new().dma(spi::Dma::Disabled), 1523 | &spi::SpiConfig::new().baudrate(26.MHz().into()), 1524 | )?; 1525 | 1526 | // Setup EPD 1527 | let mut epd = Epd4in2::new( 1528 | &mut driver, 1529 | gpio::PinDriver::output(cs)?, 1530 | gpio::PinDriver::input(busy_in)?, 1531 | gpio::PinDriver::output(dc)?, 1532 | gpio::PinDriver::output(rst)?, 1533 | &mut delay::Ets, 1534 | ) 1535 | .unwrap(); 1536 | 1537 | // Use display graphics from embedded-graphics 1538 | let mut buffer = 1539 | vec![DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH as usize / 8 * HEIGHT as usize]; 1540 | let mut display = VarDisplay::new(WIDTH, HEIGHT, &mut buffer); 1541 | 1542 | let style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On); 1543 | 1544 | // Create a text at position (20, 30) and draw it using the previously defined style 1545 | Text::new("Hello Rust!", Point::new(20, 30), style).draw(&mut display)?; 1546 | 1547 | // Display updated frame 1548 | epd.update_frame(&mut driver, &display.buffer(), &mut delay::Ets)?; 1549 | epd.display_frame(&mut driver, &mut delay::Ets)?; 1550 | 1551 | Ok(()) 1552 | } 1553 | -------------------------------------------------------------------------------- /ulp/rust-esp32-ulp-blink: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivmarkov/rust-esp32-std-demo/46c59d56c48831907bebf6e96e234b0b74e20d30/ulp/rust-esp32-ulp-blink --------------------------------------------------------------------------------