Initial commit
This commit is contained in:
1378
Cargo.lock
generated
Normal file
1378
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
Cargo.toml
Normal file
70
Cargo.toml
Normal file
@@ -0,0 +1,70 @@
|
||||
[package]
|
||||
name = "wallas-esp32c3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
esp-backtrace = { version = "0.14.2", features = [
|
||||
"esp32c3",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
]}
|
||||
|
||||
esp-hal = { version = "0.22.0", features = [
|
||||
"esp32c3",
|
||||
] }
|
||||
esp-println = { version = "0.12.0", features = ["esp32c3", "log"] }
|
||||
log = { version = "0.4.21" }
|
||||
esp-alloc = { version = "0.5.0" }
|
||||
embedded-io = "0.6.1"
|
||||
|
||||
embedded-io-async = "0.6.1"
|
||||
embassy-net = { version = "0.5.0", features = [ "tcp", "udp", "dhcpv4", "medium-ethernet"] }
|
||||
|
||||
esp-wifi = { version = "0.11.0", default-features=false, features = [
|
||||
"esp32c3",
|
||||
"utils",
|
||||
"wifi",
|
||||
"esp-alloc",
|
||||
"log",
|
||||
] }
|
||||
embassy-sync = "0.6.1"
|
||||
rand_core = "0.6.4"
|
||||
heapless = { version = "0.8.0", default-features = false }
|
||||
smoltcp = { version = "0.11.0", default-features = false, features = [
|
||||
"medium-ethernet",
|
||||
"proto-dhcpv4",
|
||||
"proto-igmp",
|
||||
"proto-ipv4",
|
||||
"socket-dhcpv4",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
embassy-executor = { version = "0.6.0", features = [
|
||||
"task-arena-size-40960"
|
||||
] }
|
||||
embassy-time = { version = "0.3.1", features = ["generic-queue-8"] }
|
||||
esp-hal-embassy = { version = "0.5.0", features = ["esp32c3"] }
|
||||
static_cell = { version = "2.1.0", features = ["nightly"] }
|
||||
critical-section = "1.2.0"
|
||||
maud = { path = "/home/jda/src/rust/maud/target/package/maud-0.26.0", features = ["alloc"] }
|
||||
picoserve = { version = "0.13.3", default-features = false, features = [
|
||||
"embassy"
|
||||
] }
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
3
build.rs
Normal file
3
build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
|
||||
}
|
||||
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["rust-src"]
|
||||
targets = ["riscv32imc-unknown-none-elf"]
|
||||
5
set-env.sh
Normal file
5
set-env.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
echo "Execute as '. ./set-env.sh'"
|
||||
read -p "wifi ssid " WIFI_SSID
|
||||
read -p "wifi password " WIFI_PASSWORD
|
||||
read -p "esp loglevel (trace, debug, info, warn, error) " ESP_LOGLEVEL
|
||||
export WIFI_SSID WIFI_PASSWORD ESP_LOGLEVEL
|
||||
58
src/httpd.rs
Normal file
58
src/httpd.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use picoserve::{make_static, routing::get, AppBuilder, AppRouter, Router};
|
||||
use picoserve::routing::PathRouter;
|
||||
use embassy_time::Duration;
|
||||
use embassy_net::Stack;
|
||||
|
||||
use static_cell::StaticCell;
|
||||
|
||||
static PICO_CONFIG : picoserve::Config<Duration> = picoserve::Config::new(
|
||||
picoserve::Timeouts {
|
||||
start_read_request: Some(Duration::from_secs(5)),
|
||||
read_request: Some(Duration::from_secs(1)),
|
||||
write: Some(Duration::from_secs(1)),
|
||||
}).keep_connection_alive();
|
||||
|
||||
/**
|
||||
struct AppProps;
|
||||
|
||||
impl AppBuilder for AppProps {
|
||||
type PathRouter = impl picoserve::routing::PathRouter;
|
||||
|
||||
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
|
||||
picoserve::Router::new().route("/", get(|| async move { "Hello World" }))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn httpd_spawn(spawner: embassy_executor::Spawner, size: usize, stack: Stack<'static>) -> Result<(), super::Error> {
|
||||
for i in 0..size {
|
||||
spawner.must_spawn(web_task(i, stack));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = super::MAX_CONCURRENT_SOCKETS)]
|
||||
async fn web_task(
|
||||
id: usize,
|
||||
stack: Stack<'static>,
|
||||
) -> ! {
|
||||
let app =
|
||||
picoserve::Router::new().route("/", get(|| async { "Hello World" })).route("/test", get(|| async { "Test" }));
|
||||
|
||||
let port = 80;
|
||||
let mut tcp_rx_buffer = [0; 1024];
|
||||
let mut tcp_tx_buffer = [0; 1024];
|
||||
let mut http_buffer = [0; 2048];
|
||||
|
||||
picoserve::listen_and_serve(
|
||||
id,
|
||||
&app,
|
||||
&PICO_CONFIG,
|
||||
stack,
|
||||
port,
|
||||
&mut tcp_rx_buffer,
|
||||
&mut tcp_tx_buffer,
|
||||
&mut http_buffer,
|
||||
)
|
||||
.await
|
||||
}
|
||||
111
src/main.rs
Normal file
111
src/main.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::prelude::*;
|
||||
use log::{info, error};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use heapless::String;
|
||||
use esp_hal::rng::Rng;
|
||||
use esp_hal::timer::systimer::SystemTimer;
|
||||
|
||||
/**
|
||||
* Embassy includes
|
||||
*/
|
||||
use esp_hal_embassy::init as initialize_embassy;
|
||||
use esp_hal::timer::systimer::Target;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
|
||||
/**
|
||||
* Rng functionality
|
||||
*/
|
||||
mod random;
|
||||
use self::random::RngWrapper;
|
||||
|
||||
/**
|
||||
* Wifi functionality
|
||||
*/
|
||||
mod wifi;
|
||||
use self::wifi::connect as connect_to_wifi;
|
||||
use self::wifi::Error as WifiError;
|
||||
|
||||
/**
|
||||
* httpd
|
||||
*/
|
||||
mod httpd;
|
||||
use httpd::httpd_spawn;
|
||||
|
||||
/**
|
||||
* serial
|
||||
*/
|
||||
mod serial;
|
||||
use serial::serial_spawn;
|
||||
|
||||
/// SSID for WiFi network
|
||||
const WIFI_SSID: &str = env!("WIFI_SSID");
|
||||
|
||||
/// Password for WiFi network
|
||||
const WIFI_PASSWORD: &str = env!("WIFI_PASSWORD");
|
||||
|
||||
/// Size of heap for dynamically-allocated memory
|
||||
const HEAP_MEMORY_SIZE: usize = 72 * 1024;
|
||||
|
||||
const MAX_CONCURRENT_SOCKETS: usize = 5;
|
||||
|
||||
#[main]
|
||||
async fn main(spawner: Spawner) {
|
||||
|
||||
info!("booting firmware");
|
||||
|
||||
if let Err(error) = main_fallible(spawner).await {
|
||||
error!("Error while running firmware: {error:?}");
|
||||
}
|
||||
|
||||
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/v0.22.0/examples/src/bin
|
||||
}
|
||||
|
||||
async fn main_fallible(
|
||||
spawner: Spawner,
|
||||
) -> Result<(), Error> {
|
||||
let peripherals = esp_hal::init({
|
||||
let mut config = esp_hal::Config::default();
|
||||
config.cpu_clock = CpuClock::max();
|
||||
config
|
||||
});
|
||||
|
||||
esp_alloc::heap_allocator!(HEAP_MEMORY_SIZE);
|
||||
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let systimer = SystemTimer::new(peripherals.SYSTIMER).split::<Target>();
|
||||
initialize_embassy(systimer.alarm0);
|
||||
|
||||
let rng = Rng::new(peripherals.RNG);
|
||||
|
||||
let ssid = String::<32>::try_from(WIFI_SSID).map_err(|()| Error::ParseCredentials)?;
|
||||
let password =
|
||||
String::<64>::try_from(WIFI_PASSWORD).map_err(|()| Error::ParseCredentials)?;
|
||||
|
||||
let stack = connect_to_wifi(spawner, TimerGroup::new(peripherals.TIMG0), rng, peripherals.WIFI, peripherals.RADIO_CLK, (ssid, password)).await.unwrap();
|
||||
|
||||
httpd_spawn(spawner, MAX_CONCURRENT_SOCKETS-1, stack);
|
||||
|
||||
serial_spawn(spawner, peripherals.UART0.into(), peripherals.GPIO20.into(), peripherals.GPIO21.into());
|
||||
|
||||
info!("firmware done booting");
|
||||
|
||||
// we got here - all is fine
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An error
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Error while parsing SSID or password
|
||||
ParseCredentials,
|
||||
}
|
||||
|
||||
60
src/random.rs
Normal file
60
src/random.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright Claudio Mattera 2024.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Random numbers generator
|
||||
|
||||
use rand_core::CryptoRng;
|
||||
use rand_core::Error;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use esp_hal::rng::Rng;
|
||||
|
||||
/// A wrapper for ESP random number generator that implement traits form
|
||||
/// `rand_core`
|
||||
#[derive(Clone)]
|
||||
pub struct RngWrapper(Rng);
|
||||
|
||||
impl From<Rng> for RngWrapper {
|
||||
fn from(rng: Rng) -> Self {
|
||||
Self(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl RngCore for RngWrapper {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.random()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
u32_pair_to_u64(self.next_u32(), self.next_u32())
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
for value in dest.iter_mut() {
|
||||
let [random_value, _, _, _] = self.next_u32().to_ne_bytes();
|
||||
*value = random_value;
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptoRng for RngWrapper {}
|
||||
|
||||
/// Join a pair of `u32` into a `u64`
|
||||
#[allow(
|
||||
clippy::many_single_char_names,
|
||||
clippy::min_ident_chars,
|
||||
reason = "This is still readable"
|
||||
)]
|
||||
fn u32_pair_to_u64(first: u32, second: u32) -> u64 {
|
||||
let [a, b, c, d] = first.to_ne_bytes();
|
||||
let [e, f, g, h] = second.to_ne_bytes();
|
||||
u64::from_ne_bytes([a, b, c, d, e, f, g, h])
|
||||
}
|
||||
36
src/serial.rs
Normal file
36
src/serial.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use esp_hal::{
|
||||
// clock::ClockControl,
|
||||
peripherals::{Peripherals},
|
||||
prelude::*,
|
||||
uart::{AtCmdConfig, AnyUart, Uart, UartRx, UartTx, Config},
|
||||
gpio::AnyPin,
|
||||
Async,
|
||||
};
|
||||
use log::{info, error};
|
||||
|
||||
const BUFFER_SIZE: usize = 64;
|
||||
|
||||
pub fn serial_spawn(spawner: embassy_executor::Spawner, peri_uart: AnyUart, rx_pin: AnyPin, tx_pin: AnyPin) {
|
||||
// Initialize and configure UART0
|
||||
let config = Config::default().rx_fifo_full_threshold(BUFFER_SIZE as u16);
|
||||
let my_uart = Uart::new_with_config(peri_uart, config, rx_pin, tx_pin).unwrap().into_async();
|
||||
// Split UART0 to create seperate Tx and Rx handles
|
||||
let (rx, _tx) = my_uart.split();
|
||||
|
||||
spawner.spawn(reader(rx)).ok();
|
||||
//spawner.spawn(writer(tx)).ok();
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn reader(mut rx: UartRx<'static, Async>) {
|
||||
let mut rbuf: [u8; BUFFER_SIZE] = [0u8; BUFFER_SIZE];
|
||||
loop {
|
||||
let r = embedded_io_async::Read::read(&mut rx, &mut rbuf[0..]).await;
|
||||
match r {
|
||||
Ok(len) => {
|
||||
info!("Read: {len}, data: {:?}", &rbuf[..len]);
|
||||
}
|
||||
Err(e) => error!("RX Error: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/wifi.rs
Normal file
203
src/wifi.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright Claudio Mattera 2024.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Functions and task for WiFi connection
|
||||
|
||||
use log::debug;
|
||||
use log::error;
|
||||
use log::info;
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
|
||||
use embassy_net::new as new_network_stack;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::signal::Signal;
|
||||
|
||||
use esp_wifi::init as initialize_wifi;
|
||||
use esp_wifi::wifi::new_with_mode as new_wifi_with_mode;
|
||||
use esp_wifi::wifi::wifi_state;
|
||||
use esp_wifi::wifi::ClientConfiguration;
|
||||
use esp_wifi::wifi::Configuration;
|
||||
use esp_wifi::wifi::WifiController;
|
||||
use esp_wifi::wifi::WifiDevice;
|
||||
use esp_wifi::wifi::WifiError as EspWifiError;
|
||||
use esp_wifi::wifi::WifiEvent;
|
||||
use esp_wifi::wifi::WifiStaDevice;
|
||||
use esp_wifi::wifi::WifiState;
|
||||
use esp_wifi::EspWifiController;
|
||||
use esp_wifi::InitializationError as WifiInitializationError;
|
||||
|
||||
use embassy_net::Config;
|
||||
use embassy_net::DhcpConfig;
|
||||
use embassy_net::Runner;
|
||||
use embassy_net::Stack;
|
||||
use embassy_net::StackResources;
|
||||
|
||||
use embassy_time::Duration;
|
||||
use embassy_time::Timer;
|
||||
|
||||
use esp_hal::peripherals::RADIO_CLK;
|
||||
use esp_hal::peripherals::TIMG0;
|
||||
use esp_hal::peripherals::WIFI;
|
||||
use esp_hal::rng::Rng;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_hal::Blocking;
|
||||
|
||||
use heapless::String;
|
||||
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use rand_core::RngCore as _;
|
||||
|
||||
use crate::RngWrapper;
|
||||
|
||||
/// max number of concurrent connections
|
||||
static STACK_COUNT : usize = super::MAX_CONCURRENT_SOCKETS;
|
||||
|
||||
/// Static cell for network stack resources
|
||||
static STACK_RESOURCES: StaticCell<StackResources<STACK_COUNT>> = StaticCell::new();
|
||||
|
||||
/// Static cell for WiFi controller
|
||||
static WIFI_CONTROLLER: StaticCell<EspWifiController<'static>> = StaticCell::new();
|
||||
|
||||
/// Signal to request to stop WiFi
|
||||
pub static STOP_WIFI_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||
|
||||
/// Connect to WiFi
|
||||
pub async fn connect(
|
||||
spawner: Spawner,
|
||||
timg0: TimerGroup<'static, TIMG0, Blocking>,
|
||||
rng: Rng,
|
||||
wifi: WIFI,
|
||||
radio_clock_control: RADIO_CLK,
|
||||
(ssid, password): (String<32>, String<64>),
|
||||
) -> Result<Stack<'static>, Error> {
|
||||
let mut rng_wrapper = RngWrapper::from(rng);
|
||||
let seed = rng_wrapper.next_u64();
|
||||
debug!("Use random seed 0x{seed:016x}");
|
||||
|
||||
let wifi_controller = initialize_wifi(timg0.timer0, rng, radio_clock_control)?;
|
||||
let wifi_controller: &'static mut _ = WIFI_CONTROLLER.init(wifi_controller);
|
||||
|
||||
let (wifi_interface, controller) = new_wifi_with_mode(wifi_controller, wifi, WifiStaDevice)?;
|
||||
|
||||
let config = Config::dhcpv4(DhcpConfig::default());
|
||||
|
||||
debug!("Initialize network stack");
|
||||
let stack_resources: &'static mut _ = STACK_RESOURCES.init(StackResources::new());
|
||||
let (stack, runner) = new_network_stack(wifi_interface, config, stack_resources, seed);
|
||||
|
||||
spawner.must_spawn(connection(controller, ssid, password));
|
||||
spawner.must_spawn(net_task(runner));
|
||||
|
||||
debug!("Wait for network link");
|
||||
loop {
|
||||
if stack.is_link_up() {
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
debug!("Wait for IP address");
|
||||
loop {
|
||||
if let Some(config) = stack.config_v4() {
|
||||
info!("Connected to WiFi with IP address {}", config.address);
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
Ok(stack)
|
||||
}
|
||||
|
||||
/// Task for ongoing network processing
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static, WifiStaDevice>>) {
|
||||
runner.run().await;
|
||||
}
|
||||
|
||||
/// Task for WiFi connection
|
||||
///
|
||||
/// This will wrap [`connection_fallible()`] and trap any error.
|
||||
#[embassy_executor::task]
|
||||
async fn connection(controller: WifiController<'static>, ssid: String<32>, password: String<64>) {
|
||||
if let Err(error) = connection_fallible(controller, ssid, password).await {
|
||||
error!("Cannot connect to WiFi: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallible task for WiFi connection
|
||||
async fn connection_fallible(
|
||||
mut controller: WifiController<'static>,
|
||||
ssid: String<32>,
|
||||
password: String<64>,
|
||||
) -> Result<(), Error> {
|
||||
debug!("Start connection");
|
||||
debug!("Device capabilities: {:?}", controller.capabilities());
|
||||
loop {
|
||||
if wifi_state() == WifiState::StaConnected {
|
||||
// wait until we're no longer connected
|
||||
controller.wait_for_event(WifiEvent::StaDisconnected).await;
|
||||
Timer::after(Duration::from_millis(5000)).await;
|
||||
}
|
||||
|
||||
if !matches!(controller.is_started(), Ok(true)) {
|
||||
let client_config = Configuration::Client(ClientConfiguration {
|
||||
ssid: ssid.clone(),
|
||||
password: password.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
controller.set_configuration(&client_config)?;
|
||||
debug!("Starting WiFi controller");
|
||||
controller.start_async().await?;
|
||||
debug!("WiFi controller started");
|
||||
}
|
||||
|
||||
debug!("Connect to WiFi network");
|
||||
|
||||
match controller.connect_async().await {
|
||||
Ok(()) => {
|
||||
debug!("Connected to WiFi network");
|
||||
|
||||
debug!("Wait for request to stop wifi");
|
||||
STOP_WIFI_SIGNAL.wait().await;
|
||||
info!("Received signal to stop wifi");
|
||||
controller.stop_async().await?;
|
||||
break;
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to connect to WiFi network: {error:?}");
|
||||
Timer::after(Duration::from_millis(5000)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Leave connection task");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Error within WiFi connection
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error during WiFi initialization
|
||||
WifiInitialization(#[expect(unused, reason = "Never read directly")] WifiInitializationError),
|
||||
|
||||
/// Error during WiFi operation
|
||||
Wifi(#[expect(unused, reason = "Never read directly")] EspWifiError),
|
||||
}
|
||||
|
||||
impl From<WifiInitializationError> for Error {
|
||||
fn from(error: WifiInitializationError) -> Self {
|
||||
Self::WifiInitialization(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EspWifiError> for Error {
|
||||
fn from(error: EspWifiError) -> Self {
|
||||
Self::Wifi(error)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user