Initial commit

This commit is contained in:
2025-01-02 22:57:36 +01:00
commit 60b394f24d
10 changed files with 1928 additions and 0 deletions

1378
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

70
Cargo.toml Normal file
View 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
View File

@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
}

4
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rust-src"]
targets = ["riscv32imc-unknown-none-elf"]

5
set-env.sh Normal file
View 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
View 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
View 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
View 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
View 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
View 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)
}
}