So I’m doing a rust rewrite of sfoc in rust, and I’m trying to encapsulate the different elements purely in terms of interfaces. My question "What traits, methods, compile-time settings, and runtime-controlable configurations, etc are needed to provide the prerequisites of "a thing that can run an foc_loop forever
?
The Rust code at present:
After using a platform specific implementation to create a struct (let’s name it driver
), you can then implement:
PhasePwmPins
: giving you thePhasePwmPins::set_pwms(&mut driver, ....)
methodFocController
: Giving the ``FocController::set_phase_angle(&mut driver, …)` methodPosSensor
: Givinglet pos = PosSensor::read_elec_angle(&driver);
- With
PosSensor
, andTimeSource
implemented, you can then implementMotionTracker
, giving access to the that traits interface, which would include methods such as:push_pos_time
latest_velocity
latest_accel
latest_jerk
telemtetry_dump
So if you want to just set foo_pwm
s, you do this in rust code:
#[esp_hal::entry]
fn main() -> ! {
// esp32 boilerplate to get access/control of relevant peripherals
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clock_ctrl = ClockControl::boot_defaults(system.clock_control);
let clocks: Clocks = clock_ctrl.freeze();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let pins = io.pins;
// Pass peripherals into driver constructor
let mut driver = Esp3PWM::new(
&clocks,
peripherals.TIMG0,
peripherals.MCPWM0,
peripherals.PCNT,
(pins.gpio1, pins.gpio2, pins.gpio3),
(pins.gpio4, pins.gpio5),
);
// set duty cycles to "meaning of life, universe and everything"
let foo_a = DutyCycle(0.42f32);
let foo_b = DutyCycle(0.42f32);
let foo_c = DutyCycle(0.42f32);
MotorHiPins::set_pwms(&mut driver, foo_a, foo_b, foo_c);
// and let's not burn things out?
MotorHiPins::set_zero(&mut driver);
loop {}
}
…you could, of course, use the different elements to bring together the different things that make up FOC to create a true FOC driver.
// If something implements MotorHiPins, PosSensor, TimeSorce, etc..., then it can expose the
// FOCMotor interface
pub trait FOCMotor: MotorHiPins + PosSensor + TimeSource + etc... {
pub fn default_foc_loop(self) -> ! {
...
}
}
So back to my original question: What are some of the pre-requisites and interfaces needed in order to provide the end user with a “primary playground” looking a bit like this?:
// Pass in a thing with all the resources needed to make a motor...
fn portable_main(mut driver_uninit: impl UninitFOCMotor) -> ! {
// Let's adjust the default initial config a touch...
let foc_initial_config = ...;
// And add some "extras"...
let driver = driver_uninit
// Pass something in to define the nature of this listener. spi? usb? tx/rx channel?
.with_listen_commands(...);
// suppose we want to cache telemetry such as movement, phase-energisation, etc...
.with_telemetry_dump_channel(...);
// Probably pass in a UART thing of some sort here
.with_debug_output(...)
.set_psu_decivoltage_limit::<U120>()
.set_some_other_compiletime_limit::<U42>()
// ...and point of no-return.
// Before: - An interface backed by a platform specific struct that has cached things
// like gpio pins, pulse-count peripherals, etc.
// - Compile-time configurations baked into the generics of the contructor.
// After: - An interface backed by a platform spicific struct that owns initialised
// hardware such as pwm pins, pulse counters, etc.
// - Compile-time configurations such as voltage limit, speed limit, etc.
// optimised and baked directly into the compiled binary.
.init(foc_initial_config);
// jump into the infinate foc-loop
driver.foc_loop()
}