From fe8942575afe8f73c4c82a00a3fed1e751b4f065 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Fri, 23 Jan 2026 21:28:46 +0100 Subject: [PATCH 1/3] *: cargo fmt --- spinnyboy_rust/src/bin/dc_driver/afroesc.rs | 109 +++++++++--------- spinnyboy_rust/src/bin/dc_driver/dshot.rs | 25 ++-- spinnyboy_rust/src/bin/dc_driver/mod.rs | 12 +- spinnyboy_rust/src/bin/main.rs | 99 +++++++++------- spinnyboy_rust/src/bin/peripherals/mod.rs | 10 +- spinnyboy_rust/src/bin/peripherals/nextion.rs | 50 ++++---- spinnyboy_rust/src/lib.rs | 2 +- 7 files changed, 156 insertions(+), 151 deletions(-) diff --git a/spinnyboy_rust/src/bin/dc_driver/afroesc.rs b/spinnyboy_rust/src/bin/dc_driver/afroesc.rs index 9dcf89d..8469ca0 100644 --- a/spinnyboy_rust/src/bin/dc_driver/afroesc.rs +++ b/spinnyboy_rust/src/bin/dc_driver/afroesc.rs @@ -1,56 +1,59 @@ -use esp_hal::mcpwm::operator::PwmPin; -use esp_hal::delay::Delay; use crate::dc_driver::EscState; -pub struct AfroEsc<'a>{ - pub pwm_pin: &'a mut PwmPin<'a, esp_hal::peripherals::MCPWM0<'a>, 0,true>, - state: crate::dc_driver::EscState, - +use esp_hal::delay::Delay; +use esp_hal::mcpwm::operator::PwmPin; +pub struct AfroEsc<'a> { + pub pwm_pin: &'a mut PwmPin<'a, esp_hal::peripherals::MCPWM0<'a>, 0, true>, + state: crate::dc_driver::EscState, } -const ARMING_SEQUENCE:u16 = 1055; -const MIN_THROTTLE:u16 = 1121; -const MAX_THROTTLE:u16 = 1421; -const GAP:u16 = (MAX_THROTTLE - MIN_THROTTLE)/100; +const ARMING_SEQUENCE: u16 = 1055; +const MIN_THROTTLE: u16 = 1121; +const MAX_THROTTLE: u16 = 1421; +const GAP: u16 = (MAX_THROTTLE - MIN_THROTTLE) / 100; -impl AfroEsc<'_>{ - //this is a little hacky tbh - pub fn new<'a>(pwm_pin:&'a mut PwmPin<'a, esp_hal::peripherals::MCPWM0<'a>, 0,true>)-> AfroEsc<'a> { - let mut esc = AfroEsc{pwm_pin:pwm_pin,state:EscState::Starting}; - let delay = Delay::new(); - esc.send_arming_sequence(); - delay.delay_millis(3000); - esc - } - pub fn set_timestamp(&mut self,value:u16){ - - self.pwm_pin.set_timestamp(value); - } - //range is from 1121 till 1421 - pub fn set_duty_percent(&mut self,value:u16){ - if value > 100 { - // failsafe! - self.pwm_pin.set_timestamp(1055); - } - let new_timestamp = MIN_THROTTLE+value*GAP; - self.pwm_pin.set_timestamp(new_timestamp); - } - pub fn send_arming_sequence(&mut self){ - self.set_timestamp(1055); - } - fn set_state(&mut self, state:EscState){ - self.state = state; - } - pub fn get_state(self) -> EscState{ - self.state - } - pub fn map_duty(&mut self, x:i32){ - if x <=0 { - self.pwm_pin.set_timestamp(1055); - } - else{ - let duty:u16 = crate::dc_driver::arduino_map(x, 0, 100, MIN_THROTTLE.into(), MAX_THROTTLE.into()).try_into().expect("INTEGER TOO LARGE"); - self.pwm_pin.set_timestamp(duty); - - } - } - -} \ No newline at end of file +impl AfroEsc<'_> { + //this is a little hacky tbh + pub fn new<'a>( + pwm_pin: &'a mut PwmPin<'a, esp_hal::peripherals::MCPWM0<'a>, 0, true>, + ) -> AfroEsc<'a> { + let mut esc = AfroEsc { + pwm_pin: pwm_pin, + state: EscState::Starting, + }; + let delay = Delay::new(); + esc.send_arming_sequence(); + delay.delay_millis(3000); + esc + } + pub fn set_timestamp(&mut self, value: u16) { + self.pwm_pin.set_timestamp(value); + } + //range is from 1121 till 1421 + pub fn set_duty_percent(&mut self, value: u16) { + if value > 100 { + // failsafe! + self.pwm_pin.set_timestamp(1055); + } + let new_timestamp = MIN_THROTTLE + value * GAP; + self.pwm_pin.set_timestamp(new_timestamp); + } + pub fn send_arming_sequence(&mut self) { + self.set_timestamp(1055); + } + fn set_state(&mut self, state: EscState) { + self.state = state; + } + pub fn get_state(self) -> EscState { + self.state + } + pub fn map_duty(&mut self, x: i32) { + if x <= 0 { + self.pwm_pin.set_timestamp(1055); + } else { + let duty: u16 = + crate::dc_driver::arduino_map(x, 0, 100, MIN_THROTTLE.into(), MAX_THROTTLE.into()) + .try_into() + .expect("INTEGER TOO LARGE"); + self.pwm_pin.set_timestamp(duty); + } + } +} diff --git a/spinnyboy_rust/src/bin/dc_driver/dshot.rs b/spinnyboy_rust/src/bin/dc_driver/dshot.rs index 54dc764..519bb38 100644 --- a/spinnyboy_rust/src/bin/dc_driver/dshot.rs +++ b/spinnyboy_rust/src/bin/dc_driver/dshot.rs @@ -41,11 +41,11 @@ impl BitTicks { Self { t0_h, t1_h, - t0_l:t1_h, - t1_l:t0_h, + t0_l: t1_h, + t1_l: t0_h, } } - + pub fn from_clk( clk_speed: u32, clk_divider: u8, @@ -57,11 +57,10 @@ impl BitTicks { let bit_ticks = (bit_period_us / tick_len).round() as u16; let t1_h = (speed.bit_times().t1_h / tick_len).round() as u16; let t0_h = (speed.bit_times().t0_h / tick_len).round() as u16; - let mut bittick = Self::new(t1_h, t0_h); - bittick.t0_l = bit_ticks -t0_h; + let mut bittick = Self::new(t1_h, t0_h); + bittick.t0_l = bit_ticks - t0_h; bittick.t1_l = bit_ticks - t1_h; bittick - } } @@ -140,8 +139,14 @@ impl<'a> DShot<'a> { ) -> Self { let clk_speed = clk_speed.unwrap_or(80_000_000); let clk_divider = clk_divider.unwrap_or(1); - let bit_ticks = BitTicks::from_clk(clk_speed, clk_divider, speed.bit_times(),speed); - rprint!("bit_ticks.t1_h:{},t1_l:{},t0_h:{},t0_l{}",bit_ticks.t1_h,bit_ticks.t1_l,bit_ticks.t0_h,bit_ticks.t0_l); + let bit_ticks = BitTicks::from_clk(clk_speed, clk_divider, speed.bit_times(), speed); + rprint!( + "bit_ticks.t1_h:{},t1_l:{},t0_h:{},t0_l{}", + bit_ticks.t1_h, + bit_ticks.t1_l, + bit_ticks.t0_h, + bit_ticks.t0_l + ); Self { rx_channel: rx_channel, tx_channel: tx_channel, @@ -171,7 +176,7 @@ impl<'a> DShot<'a> { rprintln!("--------------------"); for i in 0..16 { let bit = (frame >> (15 - i)) & 1; - + pulses[i] = if bit == 1 { rprint!("1"); PulseCode::new( @@ -195,7 +200,7 @@ impl<'a> DShot<'a> { rprintln!(""); rprintln!("--------------------"); - pulses[16] =0; + pulses[16] = 0; pulses } diff --git a/spinnyboy_rust/src/bin/dc_driver/mod.rs b/spinnyboy_rust/src/bin/dc_driver/mod.rs index 40e125f..e0fbb96 100644 --- a/spinnyboy_rust/src/bin/dc_driver/mod.rs +++ b/spinnyboy_rust/src/bin/dc_driver/mod.rs @@ -4,14 +4,10 @@ pub enum EscState { Starting, Running, Stopping, - Stop - + Stop, } //taken from: https://docs.arduino.cc/language-reference/en/functions/math/map/ //used for the ramp up of the spincoater -pub fn arduino_map(x:i32, in_min:i32, in_max:i32, out_min:i32, out_max:i32) -> i32 { - - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; - -} - +pub fn arduino_map(x: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32) -> i32 { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} diff --git a/spinnyboy_rust/src/bin/main.rs b/spinnyboy_rust/src/bin/main.rs index 61589fb..6583f9b 100644 --- a/spinnyboy_rust/src/bin/main.rs +++ b/spinnyboy_rust/src/bin/main.rs @@ -6,18 +6,18 @@ holding buffers for the duration of a data transfer." )] use alloc::string::ToString; -use esp_hal::uart::{Config, Uart}; use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; use esp_hal::main; +use esp_hal::uart::{Config, Uart}; use esp_hal::gpio::Event; use esp_hal::gpio::{Input, InputConfig}; +use esp_hal::handler; use esp_hal::mcpwm::operator::PwmPinConfig; use esp_hal::mcpwm::timer::PwmWorkingMode; -use esp_hal::time::Rate; use esp_hal::rmt::{Rmt, TxChannelConfig, TxChannelCreator}; -use esp_hal::handler; +use esp_hal::time::Rate; use core::cell::RefCell; use critical_section::Mutex; @@ -36,10 +36,10 @@ use peripherals::nextion::Nextion; use dc_driver::dshot::DShot; use dc_driver::dshot::DShotSpeed; -use esp_hal::rmt::RxChannelCreator; -use esp_hal::rmt::RxChannelConfig; use crate::dc_driver::dshot; use crate::peripherals::ErrCommand; +use esp_hal::rmt::RxChannelConfig; +use esp_hal::rmt::RxChannelCreator; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { rprintln!("PANIC!"); @@ -57,8 +57,8 @@ const DEFAULT_SPIN_TIME: u32 = 10; // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); use crate::peripherals::Command; -use esp_hal::gpio::Output; use esp_hal::gpio::Level; +use esp_hal::gpio::Output; use esp_hal::gpio::OutputConfig; #[main] @@ -85,23 +85,34 @@ fn main() -> ! { .timer_clock_with_frequency(19_999, PwmWorkingMode::Increase, Rate::from_hz(50)) .unwrap(); mcpwm.timer0.start(timer_clock_cfg); - + //setup RMT let freq = Rate::from_mhz(80); let rmt = Rmt::new(peripherals.RMT, freq).expect("CAN NOT SET FREQUENCY"); let rx_config = RxChannelConfig::default().with_clk_divider(1); let tx_config = TxChannelConfig::default().with_clk_divider(1); - let mut toggle_pin = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default()); - let mut tx_channel = rmt.channel0.configure_tx(peripherals.GPIO23,tx_config ).expect("creation of TX_CHANNEL FAILED!"); - let mut rx_channel = rmt.channel3.configure_rx(peripherals.GPIO14,rx_config).unwrap(); - let mut dshot_esc = dshot::DShot::new(&mut rx_channel, &mut tx_channel, DShotSpeed::DShot600, Some(80_000_000), Some(1)); + let mut toggle_pin = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default()); + let mut tx_channel = rmt + .channel0 + .configure_tx(peripherals.GPIO23, tx_config) + .expect("creation of TX_CHANNEL FAILED!"); + let mut rx_channel = rmt + .channel3 + .configure_rx(peripherals.GPIO14, rx_config) + .unwrap(); + let mut dshot_esc = dshot::DShot::new( + &mut rx_channel, + &mut tx_channel, + DShotSpeed::DShot600, + Some(80_000_000), + Some(1), + ); rprintln!("SENDING RMT"); - loop{ - delay.delay_millis(1000); - toggle_pin.set_high(); - dshot_esc.write_throttle(2047,true); - toggle_pin.set_low(); - + loop { + delay.delay_millis(1000); + toggle_pin.set_high(); + dshot_esc.write_throttle(2047, true); + toggle_pin.set_low(); } // rprintln!("RMT SENT!"); // let mut esc = AfroEsc::new(&mut pwm_pin);; @@ -144,35 +155,35 @@ fn main() -> ! { loop { if display.read_ready() { match display.read_command() { - Ok(Command::CommandSuccess) => { - rprintln!("COMMAND SUCCESSFULLY executed"); - } - Ok(Command::Start) => { - rprintln!("START"); - started = true; - }, - Ok(Command::Stop) => { - rprintln!("STOP"); - started = false; - // spincoater.stop(); - }, - Ok(Command::SetRpm(x)) => { - rprintln!("SET_RPM with {}", x); - rpm = x; - }, - Ok(Command::SetTimer(x)) => { - rprintln!("SETTING TIMER {}", x); - timer = x; - }, - Ok(Command::SendConfig) => { - rprintln!("SEND CONFIG"); - let command = format!("rpm.val={}",DEFAULT_TARGET_RPM); - display.send_command(command.to_string().as_bytes()); - }, + Ok(Command::CommandSuccess) => { + rprintln!("COMMAND SUCCESSFULLY executed"); + } + Ok(Command::Start) => { + rprintln!("START"); + started = true; + } + Ok(Command::Stop) => { + rprintln!("STOP"); + started = false; + // spincoater.stop(); + } + Ok(Command::SetRpm(x)) => { + rprintln!("SET_RPM with {}", x); + rpm = x; + } + Ok(Command::SetTimer(x)) => { + rprintln!("SETTING TIMER {}", x); + timer = x; + } + Ok(Command::SendConfig) => { + rprintln!("SEND CONFIG"); + let command = format!("rpm.val={}", DEFAULT_TARGET_RPM); + display.send_command(command.to_string().as_bytes()); + } Err(ErrCommand::NoValidCmd) => { rprintln!(" NOT A VALID CMD!"); - }, - Err(ErrCommand::ReadError) =>{ + } + Err(ErrCommand::ReadError) => { rprintln!("READ FAILED!"); } } diff --git a/spinnyboy_rust/src/bin/peripherals/mod.rs b/spinnyboy_rust/src/bin/peripherals/mod.rs index fa7fc3a..9ff877d 100644 --- a/spinnyboy_rust/src/bin/peripherals/mod.rs +++ b/spinnyboy_rust/src/bin/peripherals/mod.rs @@ -1,15 +1,15 @@ pub mod nextion; #[derive(Debug)] -pub enum Command{ +pub enum Command { SetRpm(u32), SetTimer(u32), Start, Stop, SendConfig, - CommandSuccess + CommandSuccess, } #[derive(Debug)] -pub enum ErrCommand{ +pub enum ErrCommand { NoValidCmd, - ReadError -} \ No newline at end of file + ReadError, +} diff --git a/spinnyboy_rust/src/bin/peripherals/nextion.rs b/spinnyboy_rust/src/bin/peripherals/nextion.rs index 4827b0e..69b3f45 100644 --- a/spinnyboy_rust/src/bin/peripherals/nextion.rs +++ b/spinnyboy_rust/src/bin/peripherals/nextion.rs @@ -1,9 +1,9 @@ +use esp_hal::Blocking; use esp_hal::uart::RxError; -use esp_hal::uart::{Uart}; -use esp_hal::{Blocking }; +use esp_hal::uart::Uart; use rtt_target::rprintln; -use crate::peripherals::{ErrCommand,Command}; +use crate::peripherals::{Command, ErrCommand}; enum UartStatemachine { WaitingP, @@ -93,42 +93,32 @@ impl<'a> Nextion<'a> { } } } - pub fn read_command(&mut self) -> Result{ - let mut buf:[u8;8] = [0;8]; - let _read_bytes = match self.read_frame(&mut buf){ + pub fn read_command(&mut self) -> Result { + let mut buf: [u8; 8] = [0; 8]; + let _read_bytes = match self.read_frame(&mut buf) { Ok(x) => x, - Err(e) => {rprintln!("ERROR while reading"); - return Err(ErrCommand::NoValidCmd); - } + Err(e) => { + rprintln!("ERROR while reading"); + return Err(ErrCommand::NoValidCmd); + } }; - rprintln!("READ SUCCESS!:{:?}",buf); - match buf[0]{ - - 01 => {Ok(Command::Start)}, - 02 =>{Ok(Command::Stop)}, + rprintln!("READ SUCCESS!:{:?}", buf); + match buf[0] { + 01 => Ok(Command::Start), + 02 => Ok(Command::Stop), 03 => { let rpm = u32::from_le_bytes(buf[1..5].try_into().expect("failed to parse rpm!")); Ok(Command::SetRpm(rpm)) - }, + } 04 => { let time = u32::from_le_bytes(buf[1..5].try_into().expect("failed to parse time!")); - Ok(Command::SetTimer(time)) + Ok(Command::SetTimer(time)) } - 05 =>{ - Ok(Command::SendConfig) - } - 00 => { - Ok(Command::CommandSuccess) - }, - _ => { - Err(ErrCommand::NoValidCmd) - } - } - - - - + 05 => Ok(Command::SendConfig), + 00 => Ok(Command::CommandSuccess), + _ => Err(ErrCommand::NoValidCmd), + } } pub fn read_ready(&mut self) -> bool { self.interface.read_ready() diff --git a/spinnyboy_rust/src/lib.rs b/spinnyboy_rust/src/lib.rs index 2e7f0d4..0c9ac1a 100644 --- a/spinnyboy_rust/src/lib.rs +++ b/spinnyboy_rust/src/lib.rs @@ -1 +1 @@ -#![no_std] \ No newline at end of file +#![no_std] From 06fd42430f1bc3a15c00e599185508bab003ce07 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Fri, 23 Jan 2026 23:19:20 +0100 Subject: [PATCH 2/3] rewrite esc controller: periodic process func --- spinnyboy_rust/src/bin/dc_driver/dshot.rs | 99 +++++++++++-------- spinnyboy_rust/src/bin/dc_driver/mod.rs | 13 +-- spinnyboy_rust/src/bin/main.rs | 76 ++++---------- spinnyboy_rust/src/bin/peripherals/mod.rs | 1 + spinnyboy_rust/src/bin/peripherals/nextion.rs | 2 +- 5 files changed, 78 insertions(+), 113 deletions(-) diff --git a/spinnyboy_rust/src/bin/dc_driver/dshot.rs b/spinnyboy_rust/src/bin/dc_driver/dshot.rs index 519bb38..ca6dfc8 100644 --- a/spinnyboy_rust/src/bin/dc_driver/dshot.rs +++ b/spinnyboy_rust/src/bin/dc_driver/dshot.rs @@ -2,8 +2,9 @@ use esp_hal::{ delay::Delay, gpio::Level, - rmt::{Channel, PulseCode, Rx, Tx, TxChannelCreator}, + rmt::{Channel, PulseCode, SingleShotTxTransaction}, }; +use core::cell::{Ref, RefCell}; use num_traits::float::FloatCore; use rtt_target::{rprint, rprintln}; @@ -49,7 +50,6 @@ impl BitTicks { pub fn from_clk( clk_speed: u32, clk_divider: u8, - bit_times: BitTimes, speed: DShotSpeed, ) -> Self { let tick_len = (1. / clk_speed as f32) * (clk_divider as f32) * 1_000_000.; @@ -78,6 +78,7 @@ impl BitTimes { } #[derive(Debug, Clone, Copy)] +#[allow(dead_code)] pub enum DShotSpeed { DShot150, DShot300, @@ -108,24 +109,18 @@ impl DShotSpeed { Self::DShot1200 => BitTimes::new(0.625, 0.313), } } - - /// These are for an 80 MHz clock with a clock divider setting of 1 - pub fn default_bit_ticks(&self) -> BitTicks { - match &self { - Self::DShot150 => BitTicks::new(400, 200), - Self::DShot300 => BitTicks::new(200, 100), - Self::DShot600 => BitTicks::new(100, 50), - Self::DShot1200 => BitTicks::new(50, 25), - } - } } #[allow(dead_code)] pub struct DShot<'a> { rx_channel: &'a mut Channel<'static, esp_hal::Blocking, esp_hal::rmt::Rx>, - tx_channel: &'a mut Channel<'static, esp_hal::Blocking, esp_hal::rmt::Tx>, speed: DShotSpeed, bit_ticks: BitTicks, + + pulses: RefCell<[u32; 17]>, + requested_throttle: RefCell, + current_tx: RefCell>>, + } impl<'a> DShot<'a> { @@ -139,19 +134,17 @@ impl<'a> DShot<'a> { ) -> Self { let clk_speed = clk_speed.unwrap_or(80_000_000); let clk_divider = clk_divider.unwrap_or(1); - let bit_ticks = BitTicks::from_clk(clk_speed, clk_divider, speed.bit_times(), speed); - rprint!( - "bit_ticks.t1_h:{},t1_l:{},t0_h:{},t0_l{}", - bit_ticks.t1_h, - bit_ticks.t1_l, - bit_ticks.t0_h, - bit_ticks.t0_l - ); + let bit_ticks = BitTicks::from_clk(clk_speed, clk_divider, speed); + + let txc = tx_channel.reborrow().transmit(&[0u32]).unwrap(); Self { rx_channel: rx_channel, - tx_channel: tx_channel, speed, bit_ticks, + + pulses: RefCell::new([0u32; 17]), + requested_throttle: RefCell::new(0), + current_tx: RefCell::new(Some(txc)), } } @@ -169,16 +162,13 @@ impl<'a> DShot<'a> { } #[allow(clippy::needless_range_loop)] - pub fn create_pulses(&mut self, throttle_value: u16, telemetry: bool) -> [u32; 17] { + pub fn create_pulses(&self, throttle_value: u16, telemetry: bool) -> [u32; 17] { let frame = Self::create_frame(throttle_value, telemetry); let mut pulses = [0; 17]; - rprintln!("CREATING NEW FRAME!"); - rprintln!("--------------------"); for i in 0..16 { let bit = (frame >> (15 - i)) & 1; pulses[i] = if bit == 1 { - rprint!("1"); PulseCode::new( Level::High, self.bit_ticks.t1_h, @@ -187,7 +177,6 @@ impl<'a> DShot<'a> { ) .into() } else { - rprint!("0"); PulseCode::new( Level::High, self.bit_ticks.t0_h, @@ -197,30 +186,56 @@ impl<'a> DShot<'a> { .into() }; } - rprintln!(""); - rprintln!("--------------------"); pulses[16] = 0; pulses } - pub fn write_throttle(&mut self, throttle: u16, telemetry: bool) -> Result<(), &'static str> { - let pulses = self.create_pulses(throttle, telemetry); - let tx_chann = self.tx_channel.reborrow(); - let transaction = tx_chann - .transmit(&pulses) - .unwrap_or(return Err("Failed to send frame")) - .wait() - .unwrap_or(return Err("Waiting failed!")); + pub fn process(&'a self) { + let mut rc = self.current_tx.borrow_mut(); + if let Some(current) = rc.as_mut() { + if current.poll() { + // Schedule new transaction + let tx_chann = rc.take().unwrap().wait().unwrap(); + let mut pulses = self.pulses.borrow_mut(); + let throttle = self.requested_throttle.borrow(); + *pulses = self.create_pulses(*throttle, false); + let pulses = pulses.as_ref(); + // SAFETY: eat shit, rust + let pulses = unsafe { + core::mem::transmute(pulses) + }; + let m = tx_chann + .transmit(pulses) + .unwrap(); - Ok(()) + //self.current_tx = Some(m); + *rc = Some(m); + } + } else { + // Nothing to do, twiddle our thumbs. + } } - pub fn arm(&mut self, delay: &mut Delay) -> Result<(), &'static str> { - for _ in 0..100 { - self.write_throttle(0, false)?; - delay.delay_millis(50); + pub fn set_throttle(&self, v: u16) { + *self.requested_throttle.borrow_mut() = v; + } + + #[allow(dead_code)] + pub fn arm(&'a self) -> Result<(), &'static str> { + let start = esp_hal::time::Instant::now(); + + rprintln!("ARM: Sending pre-throttle..."); + while start.elapsed().as_secs() < 2 { + self.set_throttle(100); + self.process(); } + rprintln!("ARM: Sending 0..."); + while start.elapsed().as_secs() < 4 { + self.set_throttle(0); + self.process(); + } + rprintln!("ARM: Done."); Ok(()) } diff --git a/spinnyboy_rust/src/bin/dc_driver/mod.rs b/spinnyboy_rust/src/bin/dc_driver/mod.rs index e0fbb96..564bce8 100644 --- a/spinnyboy_rust/src/bin/dc_driver/mod.rs +++ b/spinnyboy_rust/src/bin/dc_driver/mod.rs @@ -1,13 +1,2 @@ -pub mod afroesc; +//pub mod afroesc; pub mod dshot; -pub enum EscState { - Starting, - Running, - Stopping, - Stop, -} -//taken from: https://docs.arduino.cc/language-reference/en/functions/math/map/ -//used for the ramp up of the spincoater -pub fn arduino_map(x: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32) -> i32 { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} diff --git a/spinnyboy_rust/src/bin/main.rs b/spinnyboy_rust/src/bin/main.rs index 6583f9b..c6b4a9e 100644 --- a/spinnyboy_rust/src/bin/main.rs +++ b/spinnyboy_rust/src/bin/main.rs @@ -5,6 +5,7 @@ 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::boxed::Box; use alloc::string::ToString; use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; @@ -14,26 +15,21 @@ use esp_hal::uart::{Config, Uart}; use esp_hal::gpio::Event; use esp_hal::gpio::{Input, InputConfig}; use esp_hal::handler; -use esp_hal::mcpwm::operator::PwmPinConfig; -use esp_hal::mcpwm::timer::PwmWorkingMode; use esp_hal::rmt::{Rmt, TxChannelConfig, TxChannelCreator}; use esp_hal::time::Rate; use core::cell::RefCell; +use core::mem::forget; use critical_section::Mutex; use esp_backtrace as _; -use esp_hal::time::{Duration, Instant}; -use esp_hal::timer::timg::TimerGroup; use rtt_target::rprintln; mod dc_driver; mod peripherals; -use dc_driver::afroesc::AfroEsc; use peripherals::nextion::Nextion; //DSHOT via rmt! -use dc_driver::dshot::DShot; use dc_driver::dshot::DShotSpeed; use crate::dc_driver::dshot; @@ -57,9 +53,6 @@ const DEFAULT_SPIN_TIME: u32 = 10; // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); use crate::peripherals::Command; -use esp_hal::gpio::Level; -use esp_hal::gpio::Output; -use esp_hal::gpio::OutputConfig; #[main] fn main() -> ! { @@ -69,29 +62,15 @@ fn main() -> ! { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); - let system = peripherals.SYSTEM; - let mut delay = Delay::new(); + let delay = Delay::new(); esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 65536); - let clock_config = - esp_hal::mcpwm::PeripheralClockConfig::with_frequency(Rate::from_mhz(32)).unwrap(); - let mut mcpwm = esp_hal::mcpwm::McPwm::new(peripherals.MCPWM0, clock_config); - mcpwm.operator0.set_timer(&mcpwm.timer0); - - let mut pwm_pin = mcpwm - .operator0 - .with_pin_a(peripherals.GPIO18, PwmPinConfig::UP_ACTIVE_HIGH); - let timer_clock_cfg = clock_config - .timer_clock_with_frequency(19_999, PwmWorkingMode::Increase, Rate::from_hz(50)) - .unwrap(); - mcpwm.timer0.start(timer_clock_cfg); //setup RMT let freq = Rate::from_mhz(80); let rmt = Rmt::new(peripherals.RMT, freq).expect("CAN NOT SET FREQUENCY"); let rx_config = RxChannelConfig::default().with_clk_divider(1); let tx_config = TxChannelConfig::default().with_clk_divider(1); - let mut toggle_pin = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default()); let mut tx_channel = rmt .channel0 .configure_tx(peripherals.GPIO23, tx_config) @@ -100,20 +79,24 @@ fn main() -> ! { .channel3 .configure_rx(peripherals.GPIO14, rx_config) .unwrap(); - let mut dshot_esc = dshot::DShot::new( + let dshot_esc = dshot::DShot::new( &mut rx_channel, &mut tx_channel, DShotSpeed::DShot600, Some(80_000_000), Some(1), ); - rprintln!("SENDING RMT"); + let dshot_esc = Box::new(dshot_esc); + let dshot_esc = Box::leak(dshot_esc); + + //dshot_esc.arm(&delay).unwrap(); + + dshot_esc.arm(); loop { - delay.delay_millis(1000); - toggle_pin.set_high(); - dshot_esc.write_throttle(2047, true); - toggle_pin.set_low(); + dshot_esc.process(); + dshot_esc.set_throttle(200); } + // rprintln!("RMT SENT!"); // let mut esc = AfroEsc::new(&mut pwm_pin);; // esc.set_timestamp(1000); @@ -149,8 +132,8 @@ fn main() -> ! { let mut display = Nextion::new(&mut uart0); display.send_command(b"page page0"); - let mut rpm = DEFAULT_TARGET_RPM; - let mut timer = DEFAULT_SPIN_TIME; + let mut _rpm = DEFAULT_TARGET_RPM; + let mut _timer = DEFAULT_SPIN_TIME; let mut started = false; loop { if display.read_ready() { @@ -169,11 +152,11 @@ fn main() -> ! { } Ok(Command::SetRpm(x)) => { rprintln!("SET_RPM with {}", x); - rpm = x; + _rpm = x; } Ok(Command::SetTimer(x)) => { rprintln!("SETTING TIMER {}", x); - timer = x; + _timer = x; } Ok(Command::SendConfig) => { rprintln!("SEND CONFIG"); @@ -193,30 +176,7 @@ fn main() -> ! { // spincoater.armed(); rprintln!("STARTING!") } - //display.send_command(b"page page0"); - //uart0.write(b"page page0\xff\xff\xff"); } - // Example: Ramp from 0% to 50% throttle - //the afro esc starts turning at roughly setup gets a PWM at - //loop{ - // for pulse in 1120..1500 { - // rprintln!("pulse:{}",pulse); - // pwm_pin.set_timestamp(pulse); - // Timer::after_millis(3000).await; - // } - - // Timer::after_millis(1000).await; - - // // Return to idle - // pwm_pin.set_timestamp(1000); - // Timer::after_millis(1000).await; - // } - loop { - rprintln!("Hello world!"); - delay.delay_millis(3000); - } - - // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin } #[handler] @@ -224,7 +184,7 @@ fn main() -> ! { fn handler() { critical_section::with(|cs| { let mut binding = EMERGENCY_BUTTON.borrow_ref_mut(cs); - let mut button = binding.as_mut().unwrap(); + let button = binding.as_mut().unwrap(); if button.is_interrupt_set() { // do the thing rprintln!("BUTTON1 WAS PRESSED!"); diff --git a/spinnyboy_rust/src/bin/peripherals/mod.rs b/spinnyboy_rust/src/bin/peripherals/mod.rs index 9ff877d..5998a6e 100644 --- a/spinnyboy_rust/src/bin/peripherals/mod.rs +++ b/spinnyboy_rust/src/bin/peripherals/mod.rs @@ -11,5 +11,6 @@ pub enum Command { #[derive(Debug)] pub enum ErrCommand { NoValidCmd, + #[allow(dead_code)] ReadError, } diff --git a/spinnyboy_rust/src/bin/peripherals/nextion.rs b/spinnyboy_rust/src/bin/peripherals/nextion.rs index 69b3f45..b25ae65 100644 --- a/spinnyboy_rust/src/bin/peripherals/nextion.rs +++ b/spinnyboy_rust/src/bin/peripherals/nextion.rs @@ -97,7 +97,7 @@ impl<'a> Nextion<'a> { let mut buf: [u8; 8] = [0; 8]; let _read_bytes = match self.read_frame(&mut buf) { Ok(x) => x, - Err(e) => { + Err(_e) => { rprintln!("ERROR while reading"); return Err(ErrCommand::NoValidCmd); } From 6c38cd59eeca09fe6ad8c02ae86e6eb4be13f718 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 24 Jan 2026 02:24:17 +0100 Subject: [PATCH 3/3] rpm get The fucking receive data format/encoding/modulation is just bonkers. But I think I got it. --- spinnyboy_rust/src/bin/dc_driver/dshot.rs | 181 ++++++++++++++++++---- spinnyboy_rust/src/bin/main.rs | 19 ++- 2 files changed, 164 insertions(+), 36 deletions(-) diff --git a/spinnyboy_rust/src/bin/dc_driver/dshot.rs b/spinnyboy_rust/src/bin/dc_driver/dshot.rs index ca6dfc8..b6a2f42 100644 --- a/spinnyboy_rust/src/bin/dc_driver/dshot.rs +++ b/spinnyboy_rust/src/bin/dc_driver/dshot.rs @@ -1,13 +1,18 @@ // use embedded_hal_async::delay::DelayNs; use esp_hal::{ - delay::Delay, + delay::{self, Delay}, gpio::Level, rmt::{Channel, PulseCode, SingleShotTxTransaction}, }; -use core::cell::{Ref, RefCell}; +use core::{cell::{Ref, RefCell}, ops::Index}; use num_traits::float::FloatCore; use rtt_target::{rprint, rprintln}; +fn calculate_crc(frame: u16) -> u16 { + (0xffff ^ (frame ^ (frame >> 4) ^ (frame >> 8))) & 0xF +} + + #[allow(dead_code)] #[allow(non_camel_case_types)] #[derive(Debug)] @@ -111,16 +116,91 @@ impl DShotSpeed { } } +fn decode_bidi_telemetry(pulses: &[PulseCode]) -> Option { + // Convert to simple list of edges. + let mut edges = [(false, 0u32); 64]; + let mut nedges = 0usize; + let mut time = 0u32; + let mut plength = 0u32; + for (i, p) in pulses.iter().enumerate() { + if p.length1() == 0 { + break; + } else { + nedges += 1; + edges[i*2] = (p.level1() == Level::High, time + plength); + time += plength; + plength = p.length1() as u32; + } + if p.length2() == 0 { + break; + } else { + nedges += 1; + edges[i*2+1] = (p.level2() == Level::High, time + plength); + time += plength; + plength = p.length2() as u32; + } + } + edges[nedges] = (!edges[nedges-1].0, time + plength); + nedges += 1; + + // Process edges. + // TODO: strip until we find the first negative edge. + assert_eq!(edges[0].0, false); + + // Approximate. Should be calculated instead, in practice these pulses are + // around 1.34us. + let period = 104u32; + + // Grad middle of each period, find latest applying period. Shitty + // algorithm. + let mut res = [false; 22]; + for i in 0..res.len() { + let pos = (i as u32)*period + period/2; + let mut level = edges[0].0; + for edge in edges[..nedges].iter() { + if edge.1 > pos { + break; + } + level = edge.0; + } + res[i] = level; + } + + let mut gcr_diff = 0u32; + for i in 0..21 { + gcr_diff |= (res[20-i] as u32) << i; + } + + let gcr = (gcr_diff>>1) ^ gcr_diff; + + // Shitty LUT decode for GCR; + let lut: [u8; 16] = [ + 0x19, 0x1b, 0x12, 0x13, 0x1d, 0x15, 0x16, 0x17, 0x1a, 0x09, 0x0a, 0x0b, + 0x1e, 0x0d, 0x0e, 0x0f, + ]; + + let mut res = 0u16; + for i in 0..4 { + let fibble = ((gcr >> (i*5)) & 0b11111) as u8; + if let Some(nibble) = lut.iter().position(|&v| v == fibble) { + res |= (nibble as u16) << (i*4); + } else { + return None; + } + } + Some(res) +} + #[allow(dead_code)] pub struct DShot<'a> { - rx_channel: &'a mut Channel<'static, esp_hal::Blocking, esp_hal::rmt::Rx>, + rx_channel: RefCell<&'a mut Channel<'static, esp_hal::Blocking, esp_hal::rmt::Rx>>, speed: DShotSpeed, bit_ticks: BitTicks, pulses: RefCell<[u32; 17]>, requested_throttle: RefCell, current_tx: RefCell>>, - + current_tx_done_at: RefCell>, } impl<'a> DShot<'a> { @@ -138,25 +218,22 @@ impl<'a> DShot<'a> { let txc = tx_channel.reborrow().transmit(&[0u32]).unwrap(); Self { - rx_channel: rx_channel, + rx_channel: RefCell::new(rx_channel), speed, bit_ticks, pulses: RefCell::new([0u32; 17]), requested_throttle: RefCell::new(0), current_tx: RefCell::new(Some(txc)), + current_tx_done_at: RefCell::new(None), } } - pub fn calculate_crc(frame: u16) -> u16 { - (frame ^ (frame >> 4) ^ (frame >> 8)) & 0xF - } - pub fn create_frame(value: u16, telemetry: bool) -> u16 { // Mask to 11 bits (0-2047 range) and set telemetry bit let frame = ((value & 0x07FF) << 1) | telemetry as u16; - let crc = Self::calculate_crc(frame); + let crc = calculate_crc(frame); (frame << 4) | crc } @@ -170,17 +247,17 @@ impl<'a> DShot<'a> { pulses[i] = if bit == 1 { PulseCode::new( - Level::High, - self.bit_ticks.t1_h, Level::Low, + self.bit_ticks.t1_h, + Level::High, self.bit_ticks.t1_l, ) .into() } else { PulseCode::new( - Level::High, - self.bit_ticks.t0_h, Level::Low, + self.bit_ticks.t0_h, + Level::High, self.bit_ticks.t0_l, ) .into() @@ -195,22 +272,68 @@ impl<'a> DShot<'a> { let mut rc = self.current_tx.borrow_mut(); if let Some(current) = rc.as_mut() { if current.poll() { - // Schedule new transaction - let tx_chann = rc.take().unwrap().wait().unwrap(); - let mut pulses = self.pulses.borrow_mut(); - let throttle = self.requested_throttle.borrow(); - *pulses = self.create_pulses(*throttle, false); - let pulses = pulses.as_ref(); - // SAFETY: eat shit, rust - let pulses = unsafe { - core::mem::transmute(pulses) - }; - let m = tx_chann - .transmit(pulses) - .unwrap(); + // We're done - but we might still be waiting for a response over bidi. + let mut ended = self.current_tx_done_at.borrow_mut(); + if ended.is_none() { + let start = esp_hal::time::Instant::now(); + *ended = Some(start); + + // Try to receive a response for up to 100us. + let mut rx = self.rx_channel.borrow_mut(); + let rx = rx.reborrow(); + let mut buf = [PulseCode::default(); 32]; + { + let mut rxt = rx.receive(&mut buf).unwrap(); + while start.elapsed().as_micros() < 100 { + // Got something within this time. + if rxt.poll() { + // Receive it fully. + let (len, _) = rxt.wait().unwrap(); - //self.current_tx = Some(m); - *rc = Some(m); + // Parse out telemetry data. + if len != 0 { + // Decode to a 16 bit value. + if let Some(frame) = decode_bidi_telemetry(&buf[..len]) { + // Check CRC. + let data = frame >> 4; + let crc = calculate_crc(data); + if data != 0xfff && crc == (frame&0b1111) { + // Fucking got it. + let exp = data >> 9; + let mantissa = data & 0b111111111; + let rpm = mantissa << exp; + rprintln!("RPM: {}", rpm); + } + } + } + break; + } + } + } + } + + let ended_at = *ended.as_ref().unwrap(); + + if ended_at.elapsed().as_millis() > 1 { + // Schedule new transaction + let tx_chann = rc.take().unwrap().wait().unwrap(); + let mut pulses = self.pulses.borrow_mut(); + let throttle = self.requested_throttle.borrow(); + *pulses = self.create_pulses(*throttle, false); + let pulses = pulses.as_ref(); + // SAFETY: eat shit, rust + let pulses = unsafe { + core::mem::transmute(pulses) + }; + let m = tx_chann + .transmit(pulses) + .unwrap(); + + *rc = Some(m); + *ended = None; + } else{ + // Wait for bidi dshot data. + } } } else { // Nothing to do, twiddle our thumbs. diff --git a/spinnyboy_rust/src/bin/main.rs b/spinnyboy_rust/src/bin/main.rs index c6b4a9e..6c043f1 100644 --- a/spinnyboy_rust/src/bin/main.rs +++ b/spinnyboy_rust/src/bin/main.rs @@ -9,10 +9,10 @@ use alloc::boxed::Box; use alloc::string::ToString; use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; -use esp_hal::main; +use esp_hal::{gpio, main}; use esp_hal::uart::{Config, Uart}; -use esp_hal::gpio::Event; +use esp_hal::gpio::{Event, OutputConfig}; use esp_hal::gpio::{Input, InputConfig}; use esp_hal::handler; use esp_hal::rmt::{Rmt, TxChannelConfig, TxChannelCreator}; @@ -69,15 +69,20 @@ fn main() -> ! { //setup RMT let freq = Rate::from_mhz(80); let rmt = Rmt::new(peripherals.RMT, freq).expect("CAN NOT SET FREQUENCY"); - let rx_config = RxChannelConfig::default().with_clk_divider(1); - let tx_config = TxChannelConfig::default().with_clk_divider(1); + + let rx_config = RxChannelConfig::default().with_clk_divider(1).with_idle_threshold(1000); + let tx_config = TxChannelConfig::default().with_clk_divider(1).with_idle_output(true).with_idle_output_level(esp_hal::gpio::Level::High); + let oc = OutputConfig::default().with_drive_mode(esp_hal::gpio::DriveMode::OpenDrain); + let ic = InputConfig::default(); + let rx_pin = gpio::Input::new(unsafe { peripherals.GPIO23.clone_unchecked() }, ic); + let tx_pin = gpio::Output::new(peripherals.GPIO23, esp_hal::gpio::Level::High, oc); let mut tx_channel = rmt .channel0 - .configure_tx(peripherals.GPIO23, tx_config) + .configure_tx(tx_pin, tx_config) .expect("creation of TX_CHANNEL FAILED!"); let mut rx_channel = rmt .channel3 - .configure_rx(peripherals.GPIO14, rx_config) + .configure_rx(rx_pin, rx_config) .unwrap(); let dshot_esc = dshot::DShot::new( &mut rx_channel, @@ -94,7 +99,7 @@ fn main() -> ! { dshot_esc.arm(); loop { dshot_esc.process(); - dshot_esc.set_throttle(200); + dshot_esc.set_throttle(150); } // rprintln!("RMT SENT!");