wip
This commit is contained in:
15
.cargo/config.toml
Normal file
15
.cargo/config.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[target.xtensa-esp32s3-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32s3"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
]
|
||||
|
||||
target = "xtensa-esp32s3-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
.vscode/
|
||||
.zed/
|
||||
.helix/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1645
Cargo.lock
generated
Normal file
1645
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
101
Cargo.toml
Normal file
101
Cargo.toml
Normal file
@@ -0,0 +1,101 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "renderbug-embassy"
|
||||
rust-version = "1.86"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "renderbug-embassy"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
figments = { path = "../figments/figments/", features = ["alloc"] }
|
||||
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32s3"] }
|
||||
esp-hal = { version = "=1.0.0-rc.0", features = [
|
||||
"esp32s3",
|
||||
"log-04",
|
||||
"unstable",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
|
||||
embassy-net = { version = "0.7.0", features = [
|
||||
"dhcpv4",
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"tcp",
|
||||
"udp",
|
||||
] }
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-backtrace = { version = "0.17.0", features = [
|
||||
"esp32s3",
|
||||
"exception-handler",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32s3", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"log",
|
||||
"task-arena-size-40960",
|
||||
"executor-thread"
|
||||
] }
|
||||
embassy-time = { version = "0.5.0", features = ["log"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32s3", "log-04"] }
|
||||
esp-wifi = { version = "0.15.0", features = [
|
||||
"builtin-scheduler",
|
||||
"esp-alloc",
|
||||
"esp32s3",
|
||||
"log-04",
|
||||
"smoltcp",
|
||||
"wifi",
|
||||
] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"log",
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
static_cell = "2.1.1"
|
||||
# esp-hal-smartled = { version = "0.15.0", features = ["esp32c3"] }
|
||||
esp-hal-smartled = { git = "https://github.com/esp-rs/esp-hal-community.git", features = ["esp32s3"] }
|
||||
smart-leds = "0.4.0"
|
||||
rgb = "0.8.52"
|
||||
nmea = { version = "0.7.0", default-features = false, features = [
|
||||
"GGA",
|
||||
"GSA",
|
||||
"GSV",
|
||||
"RMC",
|
||||
"VTG",
|
||||
|
||||
"GLL",
|
||||
"GST",
|
||||
] }
|
||||
embassy-sync = "0.7.2"
|
||||
mpu6050-dmp = { version = "0.6.1", features = ["async"] }
|
||||
embassy-embedded-hal = "0.5.0"
|
||||
embedded-hal-async = "1.0.0"
|
||||
|
||||
|
||||
[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
|
||||
52
build.rs
Normal file
52
build.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
fn main() {
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
"esp_wifi_preempt_enable"
|
||||
| "esp_wifi_preempt_yield_task"
|
||||
| "esp_wifi_preempt_task_create" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
|
||||
eprintln!();
|
||||
}
|
||||
"embedded_test_linker_file_not_added_to_rustflags" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "esp"
|
||||
261
src/bin/main.rs
Normal file
261
src/bin/main.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
|
||||
use alloc::string::String;
|
||||
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
use embedded_hal_async::i2c::I2c as _;
|
||||
use esp_backtrace as _;
|
||||
use embassy_time::Delay;
|
||||
use esp_hal::i2c::master::{Config, I2c};
|
||||
use esp_hal::{Async, Blocking};
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::rmt::{ChannelCreator, Rmt};
|
||||
use esp_hal::time::Rate;
|
||||
use esp_hal::timer::systimer::SystemTimer;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_hal_smartled::{smart_led_buffer, SmartLedsAdapter};
|
||||
use esp_hal::peripherals::GPIO5;
|
||||
use figments::liber8tion::trig::sin8;
|
||||
use figments::prelude::*;
|
||||
use log::{info, warn};
|
||||
use mpu6050_dmp::address::Address;
|
||||
use mpu6050_dmp::calibration::CalibrationParameters;
|
||||
use mpu6050_dmp::gyro;
|
||||
use mpu6050_dmp::sensor_async::Mpu6050;
|
||||
use nmea::{Nmea, SentenceType};
|
||||
use renderbug_embassy::{as_milliwatts, brightness_for_mw};
|
||||
use smart_leds::{
|
||||
brightness,
|
||||
SmartLedsWrite,
|
||||
};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
static I2C_BUS: StaticCell<Mutex<NoopRawMutex, I2c<'static, Async>>> = StaticCell::new();
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
// generator version: 0.5.0
|
||||
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 64 * 1024);
|
||||
|
||||
let timer0 = SystemTimer::new(peripherals.SYSTIMER);
|
||||
esp_hal_embassy::init(timer0.alarm0);
|
||||
|
||||
info!("Embassy initialized!");
|
||||
|
||||
let rng = esp_hal::rng::Rng::new(peripherals.RNG);
|
||||
let timer1 = TimerGroup::new(peripherals.TIMG0);
|
||||
let wifi_init =
|
||||
esp_wifi::init(timer1.timer0, rng).expect("Failed to initialize WIFI/BLE controller");
|
||||
let (mut _wifi_controller, _interfaces) = esp_wifi::wifi::new(&wifi_init, peripherals.WIFI)
|
||||
.expect("Failed to initialize WIFI controller");
|
||||
|
||||
let frequency: Rate = Rate::from_mhz(80);
|
||||
let rmt: Rmt<'_, esp_hal::Blocking> = Rmt::new(peripherals.RMT, frequency)
|
||||
.expect("Failed to initialize RMT");
|
||||
|
||||
let rmt_channel = rmt.channel0;
|
||||
|
||||
spawner.spawn(render(rmt_channel, peripherals.GPIO5)).unwrap();
|
||||
|
||||
//static I2C_BUS: StaticCell<Mutex<NoopRawMutex, I2c<'static, Async>>> = StaticCell::new();
|
||||
let i2c = I2c::new(peripherals.I2C1, Config::default()).unwrap().with_scl(peripherals.GPIO36).with_sda(peripherals.GPIO33).into_async();
|
||||
let i2c_bus = I2C_BUS.init(Mutex::new(i2c));
|
||||
|
||||
//spawner.spawn(i2c_reader(i2c_bus)).unwrap();
|
||||
spawner.spawn(gps_task(I2cDevice::new(i2c_bus))).unwrap();
|
||||
//spawner.spawn(mpu_task(I2cDevice::new(i2c_bus))).unwrap();
|
||||
|
||||
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-rc.0/examples/src/bin
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn mpu_task(bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) {
|
||||
info!("Initializing MPU");
|
||||
let mut sensor = Mpu6050::new(bus, Address::default()).await.unwrap();
|
||||
let mut delay = Delay;
|
||||
sensor.initialize_dmp(&mut delay).await.unwrap();
|
||||
sensor.calibrate(&mut delay, &CalibrationParameters::new(
|
||||
mpu6050_dmp::accel::AccelFullScale::G2,
|
||||
mpu6050_dmp::gyro::GyroFullScale::Deg2000,
|
||||
mpu6050_dmp::calibration::ReferenceGravity::ZN
|
||||
)).await.unwrap();
|
||||
sensor.set_sample_rate_divider(255).await.unwrap();
|
||||
info!("MPU is ready!");
|
||||
loop {
|
||||
let (accel_data, gyro_data) = sensor.motion6().await.unwrap();
|
||||
info!("Accel x={} y={} z={}", accel_data.x() as i32, accel_data.y() as i32, accel_data.z() as i32);
|
||||
info!("Gyro x={} y={} z={}", gyro_data.x() as i32, gyro_data.y() as i32, gyro_data.z() as i32);
|
||||
Timer::after_millis(50).await;
|
||||
}
|
||||
}
|
||||
|
||||
//use embedded_hal_async::i2c::I2c;
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn gps_task(mut i2c_bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) {
|
||||
info!("Initializing GPS");
|
||||
//let bytes = "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n";
|
||||
//let bytes = "$PMTK314,1,1,1,1,1,5,1,1,1,1,1,1,0,1,1,1,1*2C\r\n";
|
||||
// Enable a bunch of data? idk
|
||||
let bytes = "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n";
|
||||
i2c_bus.write(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
// 1hz updates
|
||||
let bytes = "$PMTK220,1000*1F\r\n";
|
||||
i2c_bus.write(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
// 1hz position fix
|
||||
let bytes = "$PMTK300,1000,0,0,0,0*1C\r\n";
|
||||
i2c_bus.write(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
// Antenna updates
|
||||
let bytes = "$PGCMD,33,1*6C\r\n";
|
||||
i2c_bus.write(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
let mut strbuf = String::new();
|
||||
|
||||
let mut parser = Nmea::default();
|
||||
let mut parsing = false;
|
||||
info!("GPS is ready!");
|
||||
loop {
|
||||
let mut buf = [0; 1];
|
||||
i2c_bus.read(0x10, &mut buf).await.unwrap();
|
||||
if (buf[0] as char == '\n' || buf[0] as char == '\r') && !strbuf.is_empty() {
|
||||
if let Ok(result) = parser.parse(&strbuf) {
|
||||
log::info!("nmea={:?} raw={:?}", result, strbuf);
|
||||
log::info!("speed={:?} altitude={:?} lat={:?} lng={:?} fix={:?} nmea={:?}", parser.speed_over_ground, parser.altitude, parser.latitude, parser.longitude, parser.fix_type, parser);
|
||||
} else {
|
||||
log::warn!("Unhandled NMEA {:?}", strbuf);
|
||||
}
|
||||
strbuf = String::new();
|
||||
parsing = false;
|
||||
// Update frequency is 1hz, so we should never get an update faster than once per second
|
||||
Timer::after_secs(1).await;
|
||||
} else if strbuf.is_empty() && (buf[0] as char == '$' || buf[0] as char == '!') {
|
||||
parsing = true;
|
||||
strbuf.push(buf[0] as char);
|
||||
} else if parsing {
|
||||
strbuf.push(buf[0] as char);
|
||||
} else {
|
||||
// If there is no data ready for some reason, wait 500ms, which should place us at least somewhere after the next data frame is ready to read.
|
||||
Timer::after_millis(500).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn i2c_reader(mut i2c_bus: I2c<'static, Async>) {
|
||||
info!("Starting i2c bus");
|
||||
|
||||
// Enable status lines
|
||||
let bytes = "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28";
|
||||
i2c_bus.write_async(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
// 1hz updates
|
||||
let bytes = "$PMTK220,1000*1F";
|
||||
i2c_bus.write_async(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
// Antenna updates
|
||||
let bytes = "$PGCMD,33,1*6C";
|
||||
i2c_bus.write_async(0x10, bytes.as_bytes()).await.unwrap();
|
||||
|
||||
let mut strbuf = String::new();
|
||||
|
||||
let mut parser = Nmea::default();
|
||||
let mut parsing = false;
|
||||
loop {
|
||||
let mut buf = [0; 1];
|
||||
i2c_bus.read_async(0x10, &mut buf).await.unwrap();
|
||||
if (buf[0] as char == '\n' || buf[0] as char == '\r') && !strbuf.is_empty() {
|
||||
//log::info!("buf={:?}", strbuf);
|
||||
if let Ok(result) = parser.parse(&strbuf) {
|
||||
log::info!("nmea={:?}", result);
|
||||
log::info!("speed={:?} altitude={:?} lat={:?} lng={:?} fix={:?} nmea={:?}", parser.speed_over_ground, parser.altitude, parser.latitude, parser.longitude, parser.fix_type, parser);
|
||||
} else {
|
||||
log::warn!("Unhandled NMEA {:?}", strbuf);
|
||||
}
|
||||
strbuf = String::new();
|
||||
parsing = false;
|
||||
} else if strbuf.is_empty() && (buf[0] as char == '$' || buf[0] as char == '!') {
|
||||
parsing = true;
|
||||
strbuf.push(buf[0] as char);
|
||||
} else if parsing {
|
||||
strbuf.push(buf[0] as char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn render(rmt_channel: ChannelCreator<Blocking, 0>, gpio: GPIO5<'static>) {
|
||||
let rmt_buffer = smart_led_buffer!(255);
|
||||
|
||||
let mut target = SmartLedsAdapter::new(rmt_channel, gpio, rmt_buffer);
|
||||
|
||||
// Change this number to use a different number of LEDs
|
||||
let mut pixbuf = [Default::default(); 255];
|
||||
|
||||
// Change this to adjust the power available; the USB spec says 500ma is the standard limit, but sometimes you can draw more from a power brick
|
||||
const POWER_MA : u32 = 500;
|
||||
|
||||
// You probably don't need to change these values, unless your LED strip is somehow not 5 volts
|
||||
const POWER_VOLTS : u32 = 5;
|
||||
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
|
||||
|
||||
// This value is used as the 'seed' for rendering each frame, allowing us to do things like run the animation backwards, frames for double FPS, or even use system uptime for more human-paced animations
|
||||
let mut frame = 0;
|
||||
|
||||
info!("Rendering started!");
|
||||
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
// Clear the pixbuf to black
|
||||
pixbuf.blank();
|
||||
|
||||
// Render the frame to the pixbuf, while also calculating the power consumption as we go
|
||||
let mut total_power = 0;
|
||||
for (coords, pix) in pixbuf.sample(&Rectangle::everything()) {
|
||||
*pix = Rgb::new(sin8(coords.x.wrapping_mul(3).wrapping_add(coords.y.wrapping_mul(3)).wrapping_add(frame)), 0, 0);
|
||||
total_power += as_milliwatts(pix);
|
||||
}
|
||||
|
||||
// Scale the total brightness down so we don't immediately trigger a brownout
|
||||
let scaled_brightness = brightness_for_mw(total_power, 255, MAX_POWER_MW);
|
||||
|
||||
// Finally, write out the rendered frame
|
||||
target.write(brightness(pixbuf.iter().cloned(), scaled_brightness)).expect("Failed to write to LEDs!");
|
||||
|
||||
let render_duration = Instant::now() - start;
|
||||
let render_budget = Duration::from_millis(16);
|
||||
|
||||
if render_duration < render_budget {
|
||||
let remaining_budget = render_budget - render_duration;
|
||||
Timer::after(remaining_budget).await;
|
||||
} else {
|
||||
warn!("Render stall! Frame took {render_duration:?}");
|
||||
}
|
||||
|
||||
// Increment the frame counter
|
||||
frame += 1;
|
||||
}
|
||||
}
|
||||
30
src/lib.rs
Normal file
30
src/lib.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
#![no_std]
|
||||
|
||||
use rgb::Rgb;
|
||||
|
||||
/// Scales the requested brightness to stay within power consumption limits
|
||||
pub fn brightness_for_mw(total_mw : u32, target : u8, max_power: u32) -> u8 {
|
||||
let target32 = target as u32;
|
||||
let requested_mw = (total_mw * target32) / 256;
|
||||
|
||||
if requested_mw > max_power {
|
||||
((target32 * max_power) / requested_mw) as u8
|
||||
} else {
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the estimated power draw of a single pixel, in milliwatts
|
||||
pub fn as_milliwatts(pixel: &Rgb<u8>) -> u32 {
|
||||
// These values are copied from the original FastLED implementation
|
||||
const RED_MW : u32 = 16 * 5; //< 16mA @ 5v = 80mW
|
||||
const GREEN_MW : u32 = 11 * 5; //< 11mA @ 5v = 55mW
|
||||
const BLUE_MW : u32 = 15 * 5; //< 15mA @ 5v = 75mW
|
||||
const DARK_MW : u32 = 5; //< 1mA @ 5v = 5mW
|
||||
|
||||
let red = (pixel.r as u32 * RED_MW).wrapping_shr(8);
|
||||
let green = (pixel.g as u32 * GREEN_MW).wrapping_shr(8);
|
||||
let blue = (pixel.b as u32 * BLUE_MW).wrapping_shr(8);
|
||||
|
||||
red + green + blue + DARK_MW
|
||||
}
|
||||
Reference in New Issue
Block a user