204 lines
8.9 KiB
Rust
204 lines
8.9 KiB
Rust
#![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 core::ptr::addr_of_mut;
|
|
|
|
use alloc::sync::Arc;
|
|
use embassy_executor::Spawner;
|
|
use embassy_time::{Instant, Timer};
|
|
|
|
use esp_hal::{gpio::{Output, OutputConfig}, time::Rate};
|
|
use esp_hal::{
|
|
clock::CpuClock, interrupt::software::SoftwareInterruptControl, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}},
|
|
gpio::Pin
|
|
};
|
|
|
|
use esp_hal_embassy::{Executor, InterruptExecutor};
|
|
use log::*;
|
|
use renderbug_embassy::{logging::RenderbugLogger, tasks::{oled::{oled_ui, OledUI, OledUiSurfacePool}, safetyui::{safety_ui_main, SafetyUi}, ui::UiSurfacePool}};
|
|
use renderbug_embassy::events::BusGarage;
|
|
use static_cell::StaticCell;
|
|
use esp_backtrace as _;
|
|
|
|
use renderbug_embassy::tasks::{
|
|
motion::motion_task,
|
|
ui::{Ui, ui_main}
|
|
};
|
|
|
|
use figments_render::output::OutputAsync;
|
|
|
|
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 STATIC_HI_EXEC: StaticCell<InterruptExecutor<0>> = StaticCell::new();
|
|
|
|
static BUS_GARAGE: StaticCell<BusGarage> = StaticCell::new();
|
|
static mut CORE2_STACK: Stack<16384> = Stack::new();
|
|
static CORE_HANDLE: StaticCell<AppCoreGuard> = StaticCell::new();
|
|
|
|
#[esp_hal_embassy::main]
|
|
async fn main(spawner: Spawner) {
|
|
critical_section::with(|_| {
|
|
RenderbugLogger::init_logger();
|
|
});
|
|
|
|
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
|
let peripherals = esp_hal::init(config);
|
|
|
|
esp_alloc::heap_allocator!(size: 128 * 1024);
|
|
|
|
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
|
|
esp_hal_embassy::init([sys_timer.alarm0, sys_timer.alarm1, sys_timer.alarm2]);
|
|
info!("Embassy initialized!");
|
|
|
|
let timer0 = TimerGroup::new(peripherals.TIMG0);
|
|
|
|
let timer1 = TimerGroup::new(peripherals.TIMG1);
|
|
let mut ui_wdt = timer1.wdt;
|
|
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(10));
|
|
ui_wdt.enable();
|
|
|
|
let garage = BUS_GARAGE.init(Default::default());
|
|
|
|
info!("Setting up rendering pipeline");
|
|
let mut surfaces = UiSurfacePool::default();
|
|
let ui = Ui::new(&mut surfaces);
|
|
|
|
let mut oled_surfaces = OledUiSurfacePool::default();
|
|
let oled_uniforms = Default::default();
|
|
let oledui = OledUI::new(&mut oled_surfaces, garage.oled_display.clone(), Arc::clone(&oled_uniforms));
|
|
|
|
let mut safety_surfaces = UiSurfacePool::default();
|
|
let safety_ui = SafetyUi::new(&mut safety_surfaces, garage.display.clone());
|
|
|
|
let mut wdt = timer0.wdt;
|
|
wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(5));
|
|
let swi = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
|
let hi_exec = STATIC_HI_EXEC.init(InterruptExecutor::new(swi.software_interrupt0));
|
|
let hi_spawn = hi_exec.start(esp_hal::interrupt::Priority::max());
|
|
|
|
wdt.enable();
|
|
hi_spawn.must_spawn(renderbug_embassy::tasks::render::render(peripherals.RMT, peripherals.GPIO5.degrade(), surfaces, safety_surfaces, garage.display.clone(), wdt));
|
|
|
|
#[cfg(feature="motion")]
|
|
{
|
|
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
|
use esp_hal::{i2c::master::{Config, I2c}, Async};
|
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
|
|
|
|
static I2C_BUS: StaticCell<Mutex<CriticalSectionRawMutex, I2c<'static, Async>>> = StaticCell::new();
|
|
|
|
info!("Launching i2c sensor tasks");
|
|
let sda = peripherals.GPIO3;
|
|
let scl = peripherals.GPIO4;
|
|
let i2c = I2c::new(peripherals.I2C1, Config::default()).unwrap().with_scl(scl).with_sda(sda).into_async();
|
|
let i2c_bus = I2C_BUS.init(Mutex::new(i2c));
|
|
#[cfg(feature="mpu")]
|
|
spawner.must_spawn(renderbug_embassy::tasks::mpu::mpu_task(garage.motion.dyn_sender(), I2cDevice::new(i2c_bus)));
|
|
#[cfg(feature="gps")]
|
|
spawner.must_spawn(renderbug_embassy::tasks::gps::gps_task(garage.motion.dyn_sender(), I2cDevice::new(i2c_bus)));
|
|
}
|
|
|
|
#[cfg(feature="oled")]
|
|
{
|
|
use esp_hal::i2c::master::{Config, I2c};
|
|
use renderbug_embassy::graphics::ssd1306::SsdOutput;
|
|
|
|
let rst = Output::new(peripherals.GPIO21, esp_hal::gpio::Level::Low, OutputConfig::default());
|
|
let i2c = I2c::new(
|
|
peripherals.I2C0,
|
|
Config::default().with_frequency(Rate::from_khz(400))
|
|
).unwrap().with_scl(peripherals.GPIO18).with_sda(peripherals.GPIO17).into_async();
|
|
let output = SsdOutput::new(i2c, rst, garage.oled_display.clone()).await;
|
|
spawner.must_spawn(renderbug_embassy::tasks::oled_render::oled_render(output, oled_surfaces, oled_uniforms));
|
|
}
|
|
|
|
#[cfg(feature="simulation")]
|
|
{
|
|
use esp_storage::FlashStorage;
|
|
use renderbug_embassy::tasks::simulation::{SharedFlash, SimDataTable};
|
|
let mut storage = SharedFlash::new(FlashStorage::new());
|
|
let mut buf = [8; 1024];
|
|
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buf).unwrap();
|
|
let data_partition = partitions.find_partition(
|
|
esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
|
esp_bootloader_esp_idf::partitions::DataPartitionSubType::Undefined
|
|
)).expect("Unable to read partition table").expect("Could not find data partition!");
|
|
let data_offset = data_partition.offset() as usize;
|
|
info!("Loading simulation data starting at {data_offset:#02x}");
|
|
for sim_data in SimDataTable::open(storage.clone(), data_offset) {
|
|
info!("Found simulation data for {:?}", sim_data.srcid());
|
|
if spawner.spawn(renderbug_embassy::tasks::simulation::simulation_task(sim_data, garage.motion.dyn_sender())).is_err() {
|
|
error!("Unable to spawn simulation task! Increase the task pool size.");
|
|
}
|
|
}
|
|
}
|
|
|
|
info!("Launching motion engine");
|
|
spawner.must_spawn(motion_task(garage.motion.dyn_receiver(), garage.notify.dyn_publisher().unwrap(), garage.predict.dyn_sender()));
|
|
|
|
#[cfg(feature="radio")]
|
|
let wifi_init = {
|
|
info!("Configuring wifi");
|
|
|
|
static WIFI_INIT: StaticCell<esp_wifi::EspWifiController<'static>> = StaticCell::new();
|
|
let rng = esp_hal::rng::Rng::new(peripherals.RNG);
|
|
WIFI_INIT.init_with(|| {esp_wifi::init(timer0.timer0, rng).expect("Failed to initialize radio controller")})
|
|
};
|
|
|
|
info!("Starting core 2");
|
|
let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL);
|
|
CORE_HANDLE.init_with(|| {
|
|
cpu_control.start_app_core(unsafe { &mut *addr_of_mut!(CORE2_STACK) }, || {
|
|
let exec = CORE2_EXEC.init_with(|| { Executor::new() });
|
|
exec.run(|spawner| {
|
|
info!("Launching Safety UI");
|
|
spawner.must_spawn(safety_ui_main(garage.notify.dyn_subscriber().unwrap(), safety_ui));
|
|
info!("Launching UI");
|
|
spawner.must_spawn(ui_main(garage.notify.dyn_subscriber().unwrap(), garage.telemetry.dyn_publisher().unwrap(), ui));
|
|
info!("Launching OLED UI");
|
|
spawner.must_spawn(oled_ui(garage.telemetry.dyn_subscriber().unwrap(), oledui));
|
|
|
|
#[cfg(feature="radio")]
|
|
{
|
|
info!("Launching networking stack");
|
|
spawner.must_spawn(renderbug_embassy::tasks::wifi::wireless_task(garage.telemetry.dyn_subscriber().unwrap(), wifi_init, peripherals.WIFI));
|
|
}
|
|
|
|
#[cfg(feature="demo")]
|
|
{
|
|
warn!("Launching with demo sequencer");
|
|
spawner.must_spawn(renderbug_embassy::tasks::demo::demo_task(garage.notify.dyn_publisher().unwrap()));
|
|
}
|
|
#[cfg(not(feature="demo"))]
|
|
{
|
|
info!("Launching prediction engine");
|
|
spawner.must_spawn(renderbug_embassy::tasks::predict::prediction_task(garage.predict.dyn_receiver(), garage.notify.dyn_publisher().unwrap(), garage.telemetry.dyn_publisher().unwrap()));
|
|
}
|
|
|
|
info!("Launching core 2 watchdog");
|
|
spawner.must_spawn(wdt_task(ui_wdt));
|
|
|
|
info!("System is ready in {}ms", Instant::now().as_millis());
|
|
});
|
|
}).unwrap()
|
|
});
|
|
}
|
|
|
|
#[embassy_executor::task]
|
|
async fn wdt_task(mut wdt: Wdt<esp_hal::peripherals::TIMG1<'static>>) {
|
|
loop {
|
|
// Watchdog is set to trip after 5 seconds, so we wait just long enough before it trips
|
|
// TODO: We should maybe extend this timeout to 30s or 1m when the system is sleeping
|
|
wdt.feed();
|
|
Timer::after_secs(3).await;
|
|
}
|
|
} |