esp32 folder
This commit is contained in:
parent
922f2415a4
commit
126c98e5af
23 changed files with 3375 additions and 48 deletions
9
esp32/.cargo/config.toml
Normal file
9
esp32/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
[target.xtensa-esp32-none-elf]
|
||||||
|
runner = "espflash flash --monitor"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "xtensa-esp32-none-elf"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
DEFMT_LOG = "debug"
|
||||||
1
esp32/.gitignore
vendored
Normal file
1
esp32/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
target
|
||||||
2116
esp32/Cargo.lock
generated
Normal file
2116
esp32/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
52
esp32/Cargo.toml
Normal file
52
esp32/Cargo.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
[package]
|
||||||
|
name = "pico"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embedded-hal-compat = "0.13.0"
|
||||||
|
embassy-usb-logger = "0.5.1"
|
||||||
|
ufmt = "0.2.0"
|
||||||
|
log = "0.4"
|
||||||
|
ag-lcd={ version = "0.3", features = ["ufmt"]}
|
||||||
|
as5600 = "0.8.0"
|
||||||
|
embassy-futures = "0.1.2"
|
||||||
|
embedded-io-async = "0.7.0"
|
||||||
|
embedded-io = "0.7.1"
|
||||||
|
owned_str = "0.1.2"
|
||||||
|
embassy-sync = "0.8.0"
|
||||||
|
arrayvec = { version = "0.7.6", default-features = false }
|
||||||
|
embassy-net = { version = "0.9.1",features = ["defmt", "icmp", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"]}
|
||||||
|
embassy-executor = { version = "0.10.0", features = [
|
||||||
|
"platform-cortex-m",
|
||||||
|
"executor-thread",
|
||||||
|
"executor-interrupt",
|
||||||
|
"defmt",
|
||||||
|
] }
|
||||||
|
embassy-rp = { version = "0.10.0", features = [
|
||||||
|
"defmt",
|
||||||
|
"unstable-pac",
|
||||||
|
"time-driver",
|
||||||
|
"critical-section-impl",
|
||||||
|
"rp2040",
|
||||||
|
] }
|
||||||
|
embassy-time = { version = "0.5.1", features = [
|
||||||
|
"defmt",
|
||||||
|
"defmt-timestamp-uptime",
|
||||||
|
] }
|
||||||
|
|
||||||
|
cortex-m = { version = "0.7.7", features = ["inline-asm"] }
|
||||||
|
cortex-m-rt = "0.7.5"
|
||||||
|
critical-section = "1.2.0"
|
||||||
|
static_cell = "2.1.1"
|
||||||
|
portable-atomic = { version = "1.13.1", features = ["critical-section"] }
|
||||||
|
|
||||||
|
|
||||||
|
defmt = "1.0.1"
|
||||||
|
defmt-rtt = "1.1.0"
|
||||||
|
|
||||||
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
|
|
||||||
|
cyw43 = { version = "0.7.0", features = ["defmt", "firmware-logs"] }
|
||||||
|
cyw43-pio = { version = "0.10.0", features = ["defmt"] }
|
||||||
36
esp32/build.rs
Normal file
36
esp32/build.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
//! This build script copies the `memory.x` file from the crate root into
|
||||||
|
//! a directory where the linker can always find it at build time.
|
||||||
|
//! For many projects this is optional, as the linker always searches the
|
||||||
|
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||||
|
//! are using a workspace or have a more complicated build setup, this
|
||||||
|
//! build script becomes required. Additionally, by requesting that
|
||||||
|
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||||
|
//! updating `memory.x` ensures a rebuild of the application with the
|
||||||
|
//! new memory settings.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Put `memory.x` in our output directory and ensure it's
|
||||||
|
// on the linker search path.
|
||||||
|
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||||
|
File::create(out.join("memory.x"))
|
||||||
|
.unwrap()
|
||||||
|
.write_all(include_bytes!("memory.x"))
|
||||||
|
.unwrap();
|
||||||
|
println!("cargo:rustc-link-search={}", out.display());
|
||||||
|
|
||||||
|
// By default, Cargo will re-run a build script whenever
|
||||||
|
// any file in the project changes. By specifying `memory.x`
|
||||||
|
// here, we ensure the build script is only re-run when
|
||||||
|
// `memory.x` is changed.
|
||||||
|
println!("cargo:rerun-if-changed=memory.x");
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||||
|
}
|
||||||
26
esp32/elf2uf2.nix
Normal file
26
esp32/elf2uf2.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{ lib, stdenv, rustPlatform, fetchFromGitHub, pkg-config, libudev-zero }:
|
||||||
|
|
||||||
|
rustPlatform.buildRustPackage rec {
|
||||||
|
pname = "elf2uf2-rs";
|
||||||
|
version = "2038e9a199101ee8a16d046a87136be2a607001d";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "JoNil";
|
||||||
|
repo = pname;
|
||||||
|
rev = "${version}";
|
||||||
|
# sha256 = "sha256-CHuTpnAnD4I5PY47sceSxfjGdPZznXWfARE4YBfYoIg=";
|
||||||
|
# sha256 = "sha256-tvjncEmVHFurhRtWN6m8JT0XIgrti4QTnZ2XioBtqxo=";
|
||||||
|
sha256 = "sha256-CHuTpnAnD4I5PY47sceSxfjGdPZznXWfARE4YBfYoIg=";
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
libudev-zero
|
||||||
|
];
|
||||||
|
|
||||||
|
# cargoHash = "sha256-OefAKK5rvh4HSHd26Pac4BSbcNO3ntqBReYepulV8Dk=";
|
||||||
|
cargoHash = "sha256-tvjncEmVHFurhRtWN6m8JT0XIgrti4QTnZ2XioBtqxo=";
|
||||||
|
}
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#include <WiFi.h>
|
|
||||||
#include <LiquidCrystal.h>
|
|
||||||
|
|
||||||
// LCD pins: RS, EN, D4, D5, D6, D7
|
|
||||||
LiquidCrystal lcd(18, 19, 13, 14, 15, 26);
|
|
||||||
|
|
||||||
const int LED_PIN = 2; // built-in LED on most ESP32 devkits
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
// WiFi chip init (not connected yet)
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
WiFi.disconnect();
|
|
||||||
Serial.println("WiFi initialized");
|
|
||||||
|
|
||||||
// Let LCD power up before init (ESP32 boots faster than LCD expects)
|
|
||||||
delay(500);
|
|
||||||
lcd.begin(16, 2);
|
|
||||||
lcd.clear();
|
|
||||||
|
|
||||||
pinMode(LED_PIN, OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
Serial.println("led on!");
|
|
||||||
digitalWrite(LED_PIN, HIGH);
|
|
||||||
delay(1000);
|
|
||||||
|
|
||||||
lcd.clear();
|
|
||||||
lcd.print("Test message!");
|
|
||||||
|
|
||||||
Serial.println("led off!");
|
|
||||||
digitalWrite(LED_PIN, LOW);
|
|
||||||
delay(1000);
|
|
||||||
|
|
||||||
lcd.clear();
|
|
||||||
lcd.print("Test message2!");
|
|
||||||
}
|
|
||||||
BIN
esp32/firmware/43439A0.bin
Normal file
BIN
esp32/firmware/43439A0.bin
Normal file
Binary file not shown.
BIN
esp32/firmware/43439A0_clm.bin
Normal file
BIN
esp32/firmware/43439A0_clm.bin
Normal file
Binary file not shown.
BIN
esp32/firmware/nvram_rp2040.bin
Normal file
BIN
esp32/firmware/nvram_rp2040.bin
Normal file
Binary file not shown.
130
esp32/flake.lock
Normal file
130
esp32/flake.lock
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"esp-rs": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1772358514,
|
||||||
|
"narHash": "sha256-EV0KKHtOmqFyAX4HakujCYwnX7ef+Hr8AKf73cd9n7s=",
|
||||||
|
"owner": "leighleighleigh",
|
||||||
|
"repo": "esp-rs-nix",
|
||||||
|
"rev": "8baa40f096e7f52a10e8438b0bd55ef5dc280164",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "leighleighleigh",
|
||||||
|
"repo": "esp-rs-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1759362264,
|
||||||
|
"narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "758cf7296bee11f1706a574c77d072b8a7baa881",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1760038930,
|
||||||
|
"narHash": "sha256-Oncbh0UmHjSlxO7ErQDM3KM0A5/Znfofj2BSzlHLeVw=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0b4defa2584313f3b781240b29d61f6f9f7e0df3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1754788789,
|
||||||
|
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777383249,
|
||||||
|
"narHash": "sha256-b6T90GXUr21iIu6Aw+KGy5uSiB/cVT/UecId1YSESys=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "61a7520db583d9b41be602b4c1e1b1b1b1faa1f4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1744536153,
|
||||||
|
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"esp-rs": "esp-rs",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777346187,
|
||||||
|
"narHash": "sha256-oVxyGjpiIsrXhWTJVUOs38fZQkLjd0nZGOY9K7Kfot8=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "146e7bf7569b8288f24d41d806b9f584f7cfd5b5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
49
esp32/flake.nix
Normal file
49
esp32/flake.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
|
esp-rs.url = "github:leighleighleigh/esp-rs-nix";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
rust-overlay,
|
||||||
|
nixpkgs,
|
||||||
|
esp-rs,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
overlays = [ (import rust-overlay) ];
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
inherit overlays;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages.x86_64-linux.elf2uf2-rs = pkgs.callPackage ./elf2uf2.nix { };
|
||||||
|
devShell.x86_64-linux = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
espflash
|
||||||
|
esptool
|
||||||
|
esp-rs.packages.${system}.default
|
||||||
|
cargo-espmonitor
|
||||||
|
# (pkgs.rust-bin.selectLatestNightlyWith (
|
||||||
|
# toolchain:
|
||||||
|
# toolchain.default.override {
|
||||||
|
# targets = [
|
||||||
|
# "thumbv6m-none-eabi"
|
||||||
|
# "xtensa-esp32-none-eabi"
|
||||||
|
# ];
|
||||||
|
# extensions = [ "rust-src" ];
|
||||||
|
# }
|
||||||
|
# ))
|
||||||
|
pkgs.rust-analyzer
|
||||||
|
pkgs.flip-link
|
||||||
|
pkgs.probe-rs-tools
|
||||||
|
self.packages.x86_64-linux.elf2uf2-rs
|
||||||
|
pkgs.rustfmt
|
||||||
|
# pkgs.picotool
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
17
esp32/memory.x
Normal file
17
esp32/memory.x
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
MEMORY {
|
||||||
|
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
|
||||||
|
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
|
||||||
|
|
||||||
|
/* Pick one of the two options for RAM layout */
|
||||||
|
|
||||||
|
/* OPTION A: Use all RAM banks as one big block */
|
||||||
|
/* Reasonable, unless you are doing something */
|
||||||
|
/* really particular with DMA or other concurrent */
|
||||||
|
/* access that would benefit from striping */
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 264K
|
||||||
|
|
||||||
|
/* OPTION B: Keep the unstriped sections separate */
|
||||||
|
/* RAM: ORIGIN = 0x20000000, LENGTH = 256K */
|
||||||
|
/* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */
|
||||||
|
/* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */
|
||||||
|
}
|
||||||
36
esp32/src/bin/logger.rs
Normal file
36
esp32/src/bin/logger.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
|
||||||
|
//!
|
||||||
|
//! This creates the possibility to send log::info/warn/error/debug! to USB serial port.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
|
use embassy_rp::peripherals::USB;
|
||||||
|
use embassy_rp::usb::{Driver, InterruptHandler};
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
USBCTRL_IRQ => InterruptHandler<USB>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn logger_task(driver: Driver<'static, USB>) {
|
||||||
|
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
let driver = Driver::new(p.USB, Irqs);
|
||||||
|
spawner.spawn(logger_task(driver).unwrap());
|
||||||
|
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
counter += 1;
|
||||||
|
log::info!("Tick {}", counter);
|
||||||
|
Timer::after_secs(1).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
esp32/src/bin/scan.rs
Normal file
101
esp32/src/bin/scan.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
//! This example uses the RP Pico W board Wifi chip (cyw43).
|
||||||
|
//! Scans Wifi for ssid names.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![allow(async_fn_in_trait)]
|
||||||
|
|
||||||
|
use core::str;
|
||||||
|
|
||||||
|
use cyw43::aligned_bytes;
|
||||||
|
use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
|
||||||
|
use defmt::*;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::gpio::{Level, Output};
|
||||||
|
use embassy_rp::peripherals::{DMA_CH0, PIO0, USB};
|
||||||
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
|
use embassy_rp::{bind_interrupts, dma, usb};
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
||||||
|
USBCTRL_IRQ => usb::InterruptHandler<USB>;
|
||||||
|
DMA_IRQ_0 => dma::InterruptHandler<DMA_CH0>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn cyw43_task(
|
||||||
|
runner: cyw43::Runner<'static, cyw43::SpiBus<Output<'static>, PioSpi<'static, PIO0, 0>>>,
|
||||||
|
) -> ! {
|
||||||
|
runner.run().await
|
||||||
|
}
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn logger_task(usb: embassy_rp::Peri<'static, embassy_rp::peripherals::USB>) {
|
||||||
|
let driver = embassy_rp::usb::Driver::new(usb, Irqs);
|
||||||
|
|
||||||
|
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
|
||||||
|
}
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn periodic_log() {
|
||||||
|
loop {
|
||||||
|
info!("periodic log");
|
||||||
|
Timer::after_millis(1000).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
spawner.spawn(unwrap!(logger_task(p.USB)));
|
||||||
|
spawner.spawn(unwrap!(periodic_log()));
|
||||||
|
|
||||||
|
info!("Hello World!");
|
||||||
|
|
||||||
|
let fw = aligned_bytes!("../../firmware/43439A0.bin");
|
||||||
|
let clm = aligned_bytes!("../../firmware/43439A0_clm.bin");
|
||||||
|
let nvram = aligned_bytes!("../../firmware/nvram_rp2040.bin");
|
||||||
|
|
||||||
|
// To make flashing faster for development, you may want to flash the firmwares independently
|
||||||
|
// at hardcoded addresses, instead of baking them into the program with `include_bytes!`:
|
||||||
|
// probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000
|
||||||
|
// probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000
|
||||||
|
//let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) };
|
||||||
|
//let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) };
|
||||||
|
|
||||||
|
let pwr = Output::new(p.PIN_23, Level::Low);
|
||||||
|
let cs = Output::new(p.PIN_25, Level::High);
|
||||||
|
let mut pio = Pio::new(p.PIO0, Irqs);
|
||||||
|
let spi = PioSpi::new(
|
||||||
|
&mut pio.common,
|
||||||
|
pio.sm0,
|
||||||
|
DEFAULT_CLOCK_DIVIDER,
|
||||||
|
pio.irq0,
|
||||||
|
cs,
|
||||||
|
p.PIN_24,
|
||||||
|
p.PIN_29,
|
||||||
|
dma::Channel::new(p.DMA_CH0, Irqs),
|
||||||
|
);
|
||||||
|
|
||||||
|
static STATE: StaticCell<cyw43::State> = StaticCell::new();
|
||||||
|
let state = STATE.init(cyw43::State::new());
|
||||||
|
let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw, nvram).await;
|
||||||
|
spawner.spawn(unwrap!(cyw43_task(runner)));
|
||||||
|
|
||||||
|
control.init(clm).await;
|
||||||
|
control
|
||||||
|
.set_power_management(cyw43::PowerManagementMode::PowerSave)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
info!("scan loop");
|
||||||
|
let mut scanner = control.scan(Default::default()).await;
|
||||||
|
while let Some(bss) = scanner.next().await {
|
||||||
|
if let Ok(ssid_str) = str::from_utf8(&bss.ssid) {
|
||||||
|
info!("scanned {} == {:x}", ssid_str, bss.bssid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timer::after_millis(1000).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
esp32/src/buffer.rs
Normal file
63
esp32/src/buffer.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use core::{convert::Infallible, fmt::Display, ops::Deref};
|
||||||
|
|
||||||
|
use arrayvec::ArrayVec;
|
||||||
|
use embassy_futures::yield_now;
|
||||||
|
use embassy_net::Stack;
|
||||||
|
|
||||||
|
pub struct WriteBuffer<const CAP: usize>(ArrayVec<u8, CAP>);
|
||||||
|
|
||||||
|
impl<const CAP: usize> Display for WriteBuffer<CAP> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.write_str(str::from_utf8(self).map_err(|_| core::fmt::Error)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> Deref for WriteBuffer<CAP> {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> WriteBuffer<CAP> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(ArrayVec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> embedded_io::ErrorType for WriteBuffer<CAP> {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> embedded_io::Write for WriteBuffer<CAP> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||||
|
let _ = self.0.try_extend_from_slice(buf); //silently fails!
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
self.write(buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 {
|
||||||
|
loop {
|
||||||
|
if let Some(config) = stack.config_v4() {
|
||||||
|
return config.clone();
|
||||||
|
}
|
||||||
|
yield_now().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
esp32/src/input.rs
Normal file
90
esp32/src/input.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
use as5600::As5600;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::{
|
||||||
|
Peri,
|
||||||
|
gpio::{AnyPin, Input},
|
||||||
|
i2c::{Config, I2c},
|
||||||
|
peripherals::{I2C0, PIN_4, PIN_5},
|
||||||
|
};
|
||||||
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex};
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
use ufmt::uwrite;
|
||||||
|
|
||||||
|
use crate::{Irqs, WHEEL_VALUE, screen::SCREEN_BUFFER, unwrap};
|
||||||
|
|
||||||
|
/// Button input notification channel
|
||||||
|
pub static INPUT: Channel<CriticalSectionRawMutex, u8, 64> = Channel::new();
|
||||||
|
|
||||||
|
#[embassy_executor::task(pool_size = 4)]
|
||||||
|
/// Polls a single pin for falling edge (assumes buttons are pulled up and shorted when pressed)
|
||||||
|
/// Sends [INPUT] with given id
|
||||||
|
pub async fn button_poll_task(mut button: Input<'static>, id: u8) {
|
||||||
|
loop {
|
||||||
|
button.wait_for_falling_edge().await;
|
||||||
|
INPUT.send(id).await;
|
||||||
|
// uwrite!(SCREEN_BUFFER.lock().await.line1(), "btn {}", id);
|
||||||
|
Timer::after(Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static ANGLE: Mutex<CriticalSectionRawMutex, u16> = Mutex::new(0);
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn rotation_read_task(
|
||||||
|
i2c: Peri<'static, I2C0>,
|
||||||
|
scl: Peri<'static, PIN_5>,
|
||||||
|
sda: Peri<'static, PIN_4>,
|
||||||
|
) {
|
||||||
|
let i2c = I2c::new_async(i2c, scl, sda, Irqs, Config::default());
|
||||||
|
let mut as5600 = As5600::new(i2c);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
Timer::after(Duration::from_millis(50)).await;
|
||||||
|
let angle = as5600.angle().unwrap_or(0);
|
||||||
|
let mut locked = ANGLE.lock().await;
|
||||||
|
let old = *locked as i32;
|
||||||
|
*locked = angle;
|
||||||
|
drop(locked);
|
||||||
|
let left_diff = old - angle as i32;
|
||||||
|
let right_diff = angle as i32 - old;
|
||||||
|
let diff = if left_diff.abs() < right_diff.abs() {
|
||||||
|
-left_diff
|
||||||
|
} else {
|
||||||
|
right_diff
|
||||||
|
};
|
||||||
|
let mut wheel = WHEEL_VALUE.lock().await;
|
||||||
|
if wheel.max != wheel.min {
|
||||||
|
let wheel_range = wheel.max - wheel.min;
|
||||||
|
wheel.value += diff * wheel_range / 4096;
|
||||||
|
if wheel.value < wheel.min {
|
||||||
|
wheel.value = wheel.min;
|
||||||
|
} else if wheel.value > wheel.max {
|
||||||
|
wheel.value = wheel.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(wheel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputConfig {
|
||||||
|
pub i2c0: Peri<'static, I2C0>,
|
||||||
|
pub scl: Peri<'static, PIN_5>,
|
||||||
|
pub sda: Peri<'static, PIN_4>,
|
||||||
|
|
||||||
|
pub button_pins: [(Peri<'static, AnyPin>, u8); 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(spawner: Spawner, config: InputConfig) {
|
||||||
|
spawner.spawn(unwrap!(rotation_read_task(
|
||||||
|
config.i2c0,
|
||||||
|
config.scl,
|
||||||
|
config.sda,
|
||||||
|
)));
|
||||||
|
|
||||||
|
for (pin, id) in config.button_pins {
|
||||||
|
spawner.spawn(unwrap!(button_poll_task(
|
||||||
|
Input::new(pin, embassy_rp::gpio::Pull::Up),
|
||||||
|
id,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
312
esp32/src/main.rs
Normal file
312
esp32/src/main.rs
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use core::str::FromStr;
|
||||||
|
|
||||||
|
use embassy_net::tcp::{TcpReader, TcpWriter};
|
||||||
|
use embassy_rp::multicore::{Stack, spawn_core1};
|
||||||
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
|
use embassy_sync::mutex::Mutex;
|
||||||
|
use embassy_sync::signal::Signal;
|
||||||
|
use embedded_io::Write;
|
||||||
|
use log::info;
|
||||||
|
use owned_str::OwnedStr;
|
||||||
|
use ufmt::uwrite;
|
||||||
|
|
||||||
|
use crate::buffer::WriteBuffer;
|
||||||
|
use crate::input::{ANGLE, INPUT, InputConfig};
|
||||||
|
use crate::net::network_setup_task;
|
||||||
|
use crate::owned_str_writer::{OwnedStrWriter, center_str};
|
||||||
|
use crate::screen::{lcd_display_task, overwrite_lcd};
|
||||||
|
use ag_lcd::{Blink, Cursor, Display, LcdDisplay};
|
||||||
|
use defmt::*;
|
||||||
|
use embassy_executor::{Executor, Spawner};
|
||||||
|
use embassy_rp::gpio::{Level, Output};
|
||||||
|
use embassy_rp::i2c::{self};
|
||||||
|
use embassy_rp::peripherals::{DMA_CH0, I2C0, PIO0};
|
||||||
|
use embassy_rp::pio::InterruptHandler;
|
||||||
|
use embassy_rp::{bind_interrupts, dma};
|
||||||
|
use embassy_rp::{peripherals::USB, usb};
|
||||||
|
use embassy_time::{Delay, Timer};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
mod buffer;
|
||||||
|
mod input;
|
||||||
|
mod net;
|
||||||
|
mod owned_str_writer;
|
||||||
|
mod screen;
|
||||||
|
|
||||||
|
const WIFI_NETWORK: &str = "flamme";
|
||||||
|
const WIFI_PASSWORD: &str = "12345678";
|
||||||
|
const TARGET_IP: &str = "84.238.32.253";
|
||||||
|
const TARGET_PORT: u16 = 7070;
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
||||||
|
DMA_IRQ_0 => dma::InterruptHandler<DMA_CH0>;
|
||||||
|
USBCTRL_IRQ => usb::InterruptHandler<USB>;
|
||||||
|
I2C0_IRQ => i2c::InterruptHandler<I2C0>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn logger_task(usb: embassy_rp::Peri<'static, embassy_rp::peripherals::USB>) {
|
||||||
|
let driver = embassy_rp::usb::Driver::new(usb, Irqs);
|
||||||
|
|
||||||
|
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QuestionType {
|
||||||
|
Choice,
|
||||||
|
Numeric { min: i32, max: i32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QuestionData {
|
||||||
|
text: OwnedStr<256>,
|
||||||
|
q_type: QuestionType,
|
||||||
|
points: i32,
|
||||||
|
generation: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct WheelData {
|
||||||
|
value: i32,
|
||||||
|
min: i32,
|
||||||
|
max: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static QUESTION: Mutex<CriticalSectionRawMutex, Option<QuestionData>> = Mutex::new(None);
|
||||||
|
static QUESTION_UPDATE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||||
|
static WHEEL_VALUE: Mutex<CriticalSectionRawMutex, WheelData> = Mutex::new(WheelData {
|
||||||
|
value: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub async fn tcp_read_loop(mut read: TcpReader<'_>) {
|
||||||
|
let mut buf = [0u8; 1024];
|
||||||
|
let mut generation = 1;
|
||||||
|
|
||||||
|
while let Ok(len) = read.read(&mut buf).await {
|
||||||
|
if len == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let Ok(str) = str::from_utf8(&buf[..len]) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut counter = 0;
|
||||||
|
let mut question_data = None;
|
||||||
|
let mut future_wheel = WheelData {
|
||||||
|
value: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
};
|
||||||
|
for line in str.lines() {
|
||||||
|
if line == "$$" {
|
||||||
|
counter = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if counter == 1 {
|
||||||
|
let mut q_type = QuestionType::Choice;
|
||||||
|
let mut points = -1;
|
||||||
|
|
||||||
|
for pairs in line.split(" ") {
|
||||||
|
let (key, value) = pairs.split_once("=").unwrap();
|
||||||
|
if key == "type" {
|
||||||
|
q_type = if value == "choice" {
|
||||||
|
QuestionType::Choice
|
||||||
|
} else {
|
||||||
|
QuestionType::Numeric { min: -1, max: -1 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if key == "points" {
|
||||||
|
points = value.parse().unwrap();
|
||||||
|
}
|
||||||
|
if key == "rangeMin" || key == "rangeMax" {
|
||||||
|
match q_type {
|
||||||
|
QuestionType::Choice => {}
|
||||||
|
QuestionType::Numeric {
|
||||||
|
ref mut min,
|
||||||
|
ref mut max,
|
||||||
|
} => {
|
||||||
|
if key == "rangeMin" {
|
||||||
|
*min = value.parse().unwrap();
|
||||||
|
future_wheel.min = *min;
|
||||||
|
} else {
|
||||||
|
*max = value.parse().unwrap();
|
||||||
|
future_wheel.max = *max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match q_type {
|
||||||
|
QuestionType::Numeric { min, max } => {
|
||||||
|
let diff = max - min;
|
||||||
|
future_wheel.value = min + diff / 2;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
question_data = Some(QuestionData {
|
||||||
|
text: OwnedStr::new(),
|
||||||
|
q_type,
|
||||||
|
points,
|
||||||
|
generation,
|
||||||
|
});
|
||||||
|
counter = 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if counter == 2 {
|
||||||
|
question_data.as_mut().unwrap().text = OwnedStr::from_str(line).unwrap();
|
||||||
|
generation += 1;
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(question_data) = question_data {
|
||||||
|
*QUESTION.lock().await = Some(question_data);
|
||||||
|
*WHEEL_VALUE.lock().await = future_wheel;
|
||||||
|
QUESTION_UPDATE.signal(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn tcp_write_loop(mut write: TcpWriter<'_>) {
|
||||||
|
let mut buffer = WriteBuffer::<256>::new();
|
||||||
|
loop {
|
||||||
|
let data = INPUT.receive().await;
|
||||||
|
info!("button={}", data);
|
||||||
|
let angle = *ANGLE.lock().await;
|
||||||
|
core::writeln!(buffer, "button={} angle={}", data, angle).ok();
|
||||||
|
info!("write: {}", &buffer);
|
||||||
|
write.write(&buffer).await.ok();
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARROW_RIGHT: char = char::from_u32(0b0111_1110).unwrap();
|
||||||
|
const ARROW_LEFT: char = char::from_u32(0b0111_1111).unwrap();
|
||||||
|
const DOT: char = char::from_u32(0b1010_0101).unwrap();
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn main_loop() {
|
||||||
|
let mut last_gen = 0;
|
||||||
|
let mut title_offset = 0;
|
||||||
|
info!("Main loop started");
|
||||||
|
loop {
|
||||||
|
Timer::after_millis(50).await;
|
||||||
|
let wheel = *WHEEL_VALUE.lock().await;
|
||||||
|
let question = QUESTION.lock().await;
|
||||||
|
let Some(question) = question.as_ref() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
title_offset += 1;
|
||||||
|
if question.generation != last_gen {
|
||||||
|
last_gen = question.generation;
|
||||||
|
title_offset = 0;
|
||||||
|
}
|
||||||
|
let title_line = if question.text.len() > 16 {
|
||||||
|
title_offset %= question.text.len() - 16;
|
||||||
|
&question.text[title_offset..title_offset + 16]
|
||||||
|
} else {
|
||||||
|
&question.text
|
||||||
|
};
|
||||||
|
let number_str: OwnedStr<16> = match question.q_type {
|
||||||
|
QuestionType::Choice => {
|
||||||
|
let mut writer = OwnedStrWriter::new();
|
||||||
|
writer.push(DOT).unwrap();
|
||||||
|
writer.push(' ').unwrap();
|
||||||
|
uwrite!(writer, "{}", question.points).unwrap();
|
||||||
|
writer.into()
|
||||||
|
}
|
||||||
|
QuestionType::Numeric { min, max } => {
|
||||||
|
let mut writer = OwnedStrWriter::new();
|
||||||
|
if wheel.value > min {
|
||||||
|
writer.push(ARROW_LEFT).unwrap();
|
||||||
|
writer.push(' ').unwrap();
|
||||||
|
}
|
||||||
|
uwrite!(writer, "{}", wheel.value).unwrap();
|
||||||
|
if wheel.value < max {
|
||||||
|
writer.push(ARROW_RIGHT).unwrap();
|
||||||
|
writer.push(' ').unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let second_line = center_str::<16>(&number_str, 16).unwrap();
|
||||||
|
info!("lcd: {} {}", title_line, second_line);
|
||||||
|
overwrite_lcd(title_line, &second_line).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut CORE1_STACK: Stack<4096> = Stack::new();
|
||||||
|
// static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
|
||||||
|
static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
|
||||||
|
|
||||||
|
// #[cortex_m_rt::entry]
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) -> () {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
|
let lcd: LcdDisplay<_, _> = LcdDisplay::new(
|
||||||
|
Output::new(p.PIN_10, Level::Low),
|
||||||
|
Output::new(p.PIN_11, Level::Low),
|
||||||
|
Delay,
|
||||||
|
)
|
||||||
|
.with_half_bus(
|
||||||
|
Output::new(p.PIN_12, Level::Low),
|
||||||
|
Output::new(p.PIN_13, Level::Low),
|
||||||
|
Output::new(p.PIN_14, Level::Low),
|
||||||
|
Output::new(p.PIN_15, Level::Low),
|
||||||
|
)
|
||||||
|
.with_autoscroll(ag_lcd::AutoScroll::Off)
|
||||||
|
.with_display(Display::On)
|
||||||
|
.with_blink(Blink::On)
|
||||||
|
.with_cursor(Cursor::On)
|
||||||
|
.with_lines(ag_lcd::Lines::TwoLines);
|
||||||
|
|
||||||
|
spawn_core1(
|
||||||
|
p.CORE1,
|
||||||
|
unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) },
|
||||||
|
move || {
|
||||||
|
let executor1 = EXECUTOR1.init(Executor::new());
|
||||||
|
executor1.run(|spawner| {
|
||||||
|
let mut lcd = lcd.build();
|
||||||
|
lcd.set_blink(Blink::Off);
|
||||||
|
lcd.set_cursor(Cursor::Off);
|
||||||
|
spawner.spawn(unwrap!(lcd_display_task(lcd)));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// let executor0 = EXECUTOR0.init(Executor::new());
|
||||||
|
// executor0.run(|spawner| {
|
||||||
|
spawner.spawn(unwrap!(logger_task(p.USB)));
|
||||||
|
spawner.spawn(unwrap!(network_setup_task(
|
||||||
|
p.PIN_23, p.PIN_25, p.PIO0, p.PIN_24, p.PIN_29, p.DMA_CH0, spawner
|
||||||
|
)));
|
||||||
|
// let mut lcd = lcd.build();
|
||||||
|
// lcd.set_blink(Blink::Off);
|
||||||
|
// lcd.set_cursor(Cursor::Off);
|
||||||
|
// spawner.spawn(unwrap!(lcd_display_task(lcd)));
|
||||||
|
crate::input::setup(
|
||||||
|
spawner,
|
||||||
|
InputConfig {
|
||||||
|
i2c0: p.I2C0,
|
||||||
|
scl: p.PIN_5,
|
||||||
|
sda: p.PIN_4,
|
||||||
|
button_pins: [
|
||||||
|
(p.PIN_18.into(), 1),
|
||||||
|
(p.PIN_19.into(), 2),
|
||||||
|
(p.PIN_20.into(), 3),
|
||||||
|
(p.PIN_21.into(), 4),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// });
|
||||||
|
}
|
||||||
183
esp32/src/net.rs
Normal file
183
esp32/src/net.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
use core::net::SocketAddrV4;
|
||||||
|
use core::str::FromStr;
|
||||||
|
use embassy_futures::join::join;
|
||||||
|
use embassy_net::tcp::TcpSocket;
|
||||||
|
|
||||||
|
use crate::buffer::wait_for_config;
|
||||||
|
use crate::screen::SCREEN_BUFFER;
|
||||||
|
use crate::{
|
||||||
|
Irqs, TARGET_IP, TARGET_PORT, WIFI_NETWORK, WIFI_PASSWORD, main_loop, tcp_read_loop,
|
||||||
|
tcp_write_loop,
|
||||||
|
};
|
||||||
|
use cyw43::{JoinOptions, ScanOptions, aligned_bytes};
|
||||||
|
use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
|
||||||
|
use defmt::*;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_net::StackResources;
|
||||||
|
use embassy_rp::clocks::RoscRng;
|
||||||
|
use embassy_rp::gpio::{Level, Output};
|
||||||
|
use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_24, PIN_25, PIN_29, PIO0};
|
||||||
|
use embassy_rp::pio::Pio;
|
||||||
|
use embassy_rp::{Peri, dma};
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
use ufmt::uwrite;
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn cyw43_task(
|
||||||
|
runner: cyw43::Runner<'static, cyw43::SpiBus<Output<'static>, PioSpi<'static, PIO0, 0>>>,
|
||||||
|
) -> ! {
|
||||||
|
runner.run().await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! {
|
||||||
|
runner.run().await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn network_setup_task(
|
||||||
|
pin23: Peri<'static, PIN_23>,
|
||||||
|
pin25: Peri<'static, PIN_25>,
|
||||||
|
pio: Peri<'static, PIO0>,
|
||||||
|
pin24: Peri<'static, PIN_24>,
|
||||||
|
pin29: Peri<'static, PIN_29>,
|
||||||
|
dma: Peri<'static, DMA_CH0>,
|
||||||
|
spawner: Spawner,
|
||||||
|
) {
|
||||||
|
let fw = aligned_bytes!("../firmware/43439A0.bin");
|
||||||
|
let clm = aligned_bytes!("../firmware/43439A0_clm.bin");
|
||||||
|
let nvram = aligned_bytes!("../firmware/nvram_rp2040.bin");
|
||||||
|
let mut rng = RoscRng;
|
||||||
|
|
||||||
|
// To make flashing faster for development, you may want to flash the firmwares independently
|
||||||
|
// at hardcoded addresses, instead of baking them into the program with `include_bytes!`:
|
||||||
|
// probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000
|
||||||
|
// probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000
|
||||||
|
//let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) };
|
||||||
|
//let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) };
|
||||||
|
|
||||||
|
let pwr = Output::new(pin23, Level::Low);
|
||||||
|
let cs = Output::new(pin25, Level::High);
|
||||||
|
let mut pio = Pio::new(pio, Irqs);
|
||||||
|
let spi = PioSpi::new(
|
||||||
|
&mut pio.common,
|
||||||
|
pio.sm0,
|
||||||
|
DEFAULT_CLOCK_DIVIDER,
|
||||||
|
pio.irq0,
|
||||||
|
cs,
|
||||||
|
pin24,
|
||||||
|
pin29,
|
||||||
|
dma::Channel::new(dma, Irqs),
|
||||||
|
);
|
||||||
|
|
||||||
|
static STATE: StaticCell<cyw43::State> = StaticCell::new();
|
||||||
|
let state = STATE.init(cyw43::State::new());
|
||||||
|
let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw, nvram).await;
|
||||||
|
spawner.spawn(unwrap!(cyw43_task(runner)));
|
||||||
|
|
||||||
|
control.init(clm).await;
|
||||||
|
control
|
||||||
|
.set_power_management(cyw43::PowerManagementMode::Performance)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new();
|
||||||
|
let (stack, runner) = embassy_net::new(
|
||||||
|
net_device,
|
||||||
|
embassy_net::Config::dhcpv4(Default::default()),
|
||||||
|
RESOURCES.init(StackResources::new()),
|
||||||
|
rng.next_u64(),
|
||||||
|
);
|
||||||
|
spawner.spawn(unwrap!(net_task(runner)));
|
||||||
|
Timer::after_millis(1000).await;
|
||||||
|
uwrite!(SCREEN_BUFFER.lock().await.line1(), "con - scan");
|
||||||
|
|
||||||
|
info!("scan started");
|
||||||
|
|
||||||
|
let mut opts = ScanOptions::default();
|
||||||
|
opts.scan_type = cyw43::ScanType::Active;
|
||||||
|
let mut scanner = control.scan(opts).await;
|
||||||
|
|
||||||
|
while let Some(bss) = scanner.next().await {
|
||||||
|
let mut buf = SCREEN_BUFFER.lock().await;
|
||||||
|
if let Ok(ssid_str) = str::from_utf8(&bss.ssid) {
|
||||||
|
info!("scanned {} == {:x}", ssid_str, bss.bssid);
|
||||||
|
uwrite!(buf.line1(), "{}", ssid_str);
|
||||||
|
// uwrite!(buf.line2(), "{:x}", bss.bssid);
|
||||||
|
} else {
|
||||||
|
info!("scanned {:x}", bss.bssid);
|
||||||
|
uwrite!(buf.line1(), "not-utf8");
|
||||||
|
// uwrite!(buf.line2(), "{:x}", bss.bssid);
|
||||||
|
}
|
||||||
|
drop(buf);
|
||||||
|
Timer::after_millis(500).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("scan completed");
|
||||||
|
|
||||||
|
uwrite!(SCREEN_BUFFER.lock().await.line1(), "scan end.");
|
||||||
|
Timer::after_millis(500).await;
|
||||||
|
drop(scanner);
|
||||||
|
|
||||||
|
while let Err(err) = control
|
||||||
|
.join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes()))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let mut buf = SCREEN_BUFFER.lock().await;
|
||||||
|
buf.clear();
|
||||||
|
match err {
|
||||||
|
cyw43::JoinError::AuthenticationFailure => {
|
||||||
|
uwrite!(buf.line1(), "join failure");
|
||||||
|
uwrite!(buf.line2(), "bad password");
|
||||||
|
info!("authentication failure");
|
||||||
|
}
|
||||||
|
cyw43::JoinError::JoinFailure(e) => {
|
||||||
|
uwrite!(buf.line1(), "join failure");
|
||||||
|
uwrite!(buf.line2(), "code {}", e);
|
||||||
|
info!("join failure: {}", e);
|
||||||
|
}
|
||||||
|
cyw43::JoinError::NetworkNotFound => {
|
||||||
|
uwrite!(buf.line1(), "join failure");
|
||||||
|
uwrite!(buf.line2(), "wifi not found");
|
||||||
|
info!("network not found");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Timer::after_millis(200).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = SCREEN_BUFFER.lock().await;
|
||||||
|
buf.clear();
|
||||||
|
uwrite!(buf.line1(), "link.");
|
||||||
|
drop(buf);
|
||||||
|
|
||||||
|
stack.wait_link_up().await;
|
||||||
|
|
||||||
|
uwrite!(SCREEN_BUFFER.lock().await.line1(), "dhcp.");
|
||||||
|
|
||||||
|
stack.wait_config_up().await;
|
||||||
|
let cfg = wait_for_config(stack).await;
|
||||||
|
let local_addr = cfg.address.address();
|
||||||
|
info!("IP address: {:?}", local_addr);
|
||||||
|
|
||||||
|
let host_addr = embassy_net::Ipv4Address::from_str(TARGET_IP).unwrap();
|
||||||
|
let mut rx_buffer = [0; 4096];
|
||||||
|
let mut tx_buffer = [0; 4096];
|
||||||
|
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
|
||||||
|
socket.set_timeout(Some(Duration::from_secs(10)));
|
||||||
|
socket.set_keep_alive(Some(Duration::from_secs(1)));
|
||||||
|
if let Err(e) = socket
|
||||||
|
.connect(SocketAddrV4::new(host_addr, TARGET_PORT))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
uwrite!(SCREEN_BUFFER.lock().await.line1(), "tcperr");
|
||||||
|
error!("tcp connect error: {}", e);
|
||||||
|
} else {
|
||||||
|
uwrite!(SCREEN_BUFFER.lock().await.line1(), "tcpok");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (read, write) = socket.split();
|
||||||
|
|
||||||
|
spawner.spawn(unwrap!(main_loop()));
|
||||||
|
|
||||||
|
join(tcp_read_loop(read), tcp_write_loop(write)).await;
|
||||||
|
}
|
||||||
50
esp32/src/owned_str_writer.rs
Normal file
50
esp32/src/owned_str_writer.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use owned_str::{Error, OwnedStr};
|
||||||
|
use ufmt::uWrite;
|
||||||
|
|
||||||
|
pub struct OwnedStrWriter<const CAP: usize>(OwnedStr<CAP>);
|
||||||
|
|
||||||
|
impl<const CAP: usize> OwnedStrWriter<CAP> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(OwnedStr::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, c: char) -> Result<(), Error> {
|
||||||
|
self.0.try_push(c).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> From<OwnedStr<CAP>> for OwnedStrWriter<CAP> {
|
||||||
|
fn from(owned_str: OwnedStr<CAP>) -> Self {
|
||||||
|
Self(owned_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> From<OwnedStrWriter<CAP>> for OwnedStr<CAP> {
|
||||||
|
fn from(writer: OwnedStrWriter<CAP>) -> Self {
|
||||||
|
writer.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CAP: usize> uWrite for OwnedStrWriter<CAP> {
|
||||||
|
type Error = owned_str::Error;
|
||||||
|
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
|
||||||
|
self.0.try_push_str(s).map(|_| ())
|
||||||
|
}
|
||||||
|
fn write_char(&mut self, c: char) -> Result<(), Self::Error> {
|
||||||
|
self.0.try_push(c).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center_str<const CAP: usize>(
|
||||||
|
str: &str,
|
||||||
|
width: usize,
|
||||||
|
) -> Result<OwnedStr<CAP>, owned_str::Error> {
|
||||||
|
let mut res = OwnedStr::new();
|
||||||
|
let len = str.len();
|
||||||
|
let padding = (width.saturating_sub(len) + 1) / 2;
|
||||||
|
for _ in 0..padding {
|
||||||
|
res.try_push(' ')?;
|
||||||
|
}
|
||||||
|
res.try_push_str(str)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
103
esp32/src/screen.rs
Normal file
103
esp32/src/screen.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
use core::convert::Infallible;
|
||||||
|
|
||||||
|
use ag_lcd::LcdDisplay;
|
||||||
|
use embassy_rp::gpio::Output;
|
||||||
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
|
||||||
|
use embassy_time::{Delay, Timer};
|
||||||
|
use ufmt::uWrite;
|
||||||
|
|
||||||
|
pub struct ScreenBuffer {
|
||||||
|
line1: [u8; 16],
|
||||||
|
line2: [u8; 16],
|
||||||
|
line1_ptr: u8,
|
||||||
|
line2_ptr: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScreenBuffer {
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.line1.fill(0);
|
||||||
|
self.line2.fill(0);
|
||||||
|
self.line1_ptr = 0;
|
||||||
|
self.line2_ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line1(&mut self) -> ScreenBufferWriter<'_> {
|
||||||
|
ScreenBufferWriter { buf: self, line: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line2(&mut self) -> ScreenBufferWriter<'_> {
|
||||||
|
ScreenBufferWriter { buf: self, line: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScreenBufferWriter<'a> {
|
||||||
|
buf: &'a mut ScreenBuffer,
|
||||||
|
line: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> uWrite for ScreenBufferWriter<'a> {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn write_char(&mut self, c: char) -> Result<(), Self::Error> {
|
||||||
|
if self.line == 0 {
|
||||||
|
let ptr = self.buf.line1_ptr as usize;
|
||||||
|
self.buf.line1[ptr] = c as u8;
|
||||||
|
self.buf.line1_ptr = (self.buf.line1_ptr + 1) % 16;
|
||||||
|
} else {
|
||||||
|
let ptr = self.buf.line2_ptr as usize;
|
||||||
|
self.buf.line2[ptr] = c as u8;
|
||||||
|
self.buf.line2_ptr = (self.buf.line2_ptr + 1) % 16;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
|
||||||
|
for c in s.chars() {
|
||||||
|
self.write_char(c)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScreenBufferWriter<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
LCD_UPDATE.signal(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static SCREEN_BUFFER: Mutex<CriticalSectionRawMutex, ScreenBuffer> = Mutex::new(ScreenBuffer {
|
||||||
|
line1: [0; 16],
|
||||||
|
line1_ptr: 0,
|
||||||
|
line2: [0; 16],
|
||||||
|
line2_ptr: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
static LCD_UPDATE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn lcd_display_task(mut lcd: LcdDisplay<Output<'static>, Delay>) {
|
||||||
|
loop {
|
||||||
|
LCD_UPDATE.wait().await;
|
||||||
|
let buffer = SCREEN_BUFFER.lock().await;
|
||||||
|
lcd.clear();
|
||||||
|
for byte in &buffer.line1 {
|
||||||
|
lcd.write(*byte);
|
||||||
|
}
|
||||||
|
lcd.set_position(0, 1);
|
||||||
|
for byte in &buffer.line2 {
|
||||||
|
lcd.write(*byte);
|
||||||
|
}
|
||||||
|
Timer::after_millis(20).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn overwrite_lcd(line1: &str, line2: &str) {
|
||||||
|
let mut buffer = SCREEN_BUFFER.lock().await;
|
||||||
|
buffer.line1_ptr = 0;
|
||||||
|
buffer.line2_ptr = 0;
|
||||||
|
buffer.line1.fill(0);
|
||||||
|
buffer.line2.fill(0);
|
||||||
|
buffer.line1[..line1.len()].copy_from_slice(line1.as_bytes());
|
||||||
|
buffer.line2[..line2.len()].copy_from_slice(line2.as_bytes());
|
||||||
|
LCD_UPDATE.signal(());
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,8 @@
|
||||||
# runner = "probe-rs run --chip RP2040"
|
# runner = "probe-rs run --chip RP2040"
|
||||||
runner = "sudo picotool load -u -v -x -t elf"
|
runner = "sudo picotool load -u -v -x -t elf"
|
||||||
|
|
||||||
[target.xtensa-esp32-none-elf]
|
|
||||||
runner = "espflash flash --monitor"
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
target = "xtensa-esp32-none-elf"
|
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
DEFMT_LOG = "debug"
|
DEFMT_LOG = "debug"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
esp-rs.url = "github:leighleighleigh/esp-rs-nix";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|
@ -10,7 +9,6 @@
|
||||||
self,
|
self,
|
||||||
rust-overlay,
|
rust-overlay,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
esp-rs,
|
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
overlays = [ (import rust-overlay) ];
|
overlays = [ (import rust-overlay) ];
|
||||||
|
|
@ -23,14 +21,11 @@
|
||||||
packages.x86_64-linux.elf2uf2-rs = pkgs.callPackage ./elf2uf2.nix { };
|
packages.x86_64-linux.elf2uf2-rs = pkgs.callPackage ./elf2uf2.nix { };
|
||||||
devShell.x86_64-linux = pkgs.mkShell {
|
devShell.x86_64-linux = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
espflash
|
|
||||||
esptool
|
|
||||||
(pkgs.rust-bin.selectLatestNightlyWith (
|
(pkgs.rust-bin.selectLatestNightlyWith (
|
||||||
toolchain:
|
toolchain:
|
||||||
toolchain.default.override {
|
toolchain.default.override {
|
||||||
targets = [
|
targets = [
|
||||||
"thumbv6m-none-eabi"
|
"thumbv6m-none-eabi"
|
||||||
"xtensa-esp32-none-eabi"
|
|
||||||
];
|
];
|
||||||
extensions = [ "rust-src" ];
|
extensions = [ "rust-src" ];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue