With the goal of spinning a motor, I wrote some rust based on my reverse engineering of the boards. Do not use rust-embedded. It’s not nice.

 

#![no_std]

#![no_main]

use cortex_m::asm;

use defmt_rtt as _;

use panic_halt as _;

use core::cell::RefCell;

use cortex_m::asm::delay;

use cortex_m_rt::entry;

use critical_section::Mutex;

#[allow(deprecated)]

use stm32f3xx_hal::adc::OneShot;

use stm32f3xx_hal::gpio::GpioExt;

use stm32f3xx_hal::{

adc,
pac::{self, interrupt},
prelude::*,
timer,

};

use stm32f3xx_hal::pac::usb::*;

use stm32f3xx_hal::usb::{Peripheral, UsbBus};

use usb_device::prelude::*;

use usbd_serial::{SerialPort, USB_CLASS_CDC};

static TIMER: Mutex<RefCell<Option<timer::Timerpac::TIM2>>> = Mutex::new(RefCell::new(None));

#[entry]

fn main() ! {

let dp = pac::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let mut flash = dp.FLASH.constrain();
//    let clocks: stm32f3xx_hal::rcc::Clocks = rcc.cfgr.freeze(&mut dp.FLASH.constrain().acr);
let clocks = rcc
    .cfgr
    .use_hse(8.MHz())
    .sysclk(48.MHz())
    .pclk1(24.MHz())
    .pclk2(24.MHz())
    .freeze(&mut flash.acr);
assert!(clocks.usbclk_valid());
let mut gpioc = dp.GPIOC.split(&mut rcc.ahb);
let mut gpiob = dp.GPIOB.split(&mut rcc.ahb);
let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);
let mut usb_dp = gpioa
    .pa12
    .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
usb_dp.set_low().ok();
delay(clocks.sysclk().0 / 100);
let usb_dm = gpioa
    .pa11
    .into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh);
let usb_dp = usb_dp.into_af_push_pull(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh);
let usb = Peripheral {
    usb: dp.USB,
    pin_dm: usb_dm,
    pin_dp: usb_dp,
};
let usb_bus = UsbBus::new(usb);
let mut serial = SerialPort::new(&usb_bus);
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
    .manufacturer("YonesWire")
    .product("SporkMax")
    .serial_number("bruh we aint doin all that")
    .device_class(USB_CLASS_CDC)
    .build();
let mut adc_common = adc::CommonAdc::new(dp.ADC1_2, &clocks, &mut rcc.ahb);
let mut tuple = (dp.ADC1, dp.ADC2);
let mut ts = adc::TemperatureSensor::new(&mut adc_common, &mut tuple);
// Set up ADC1
let mut adc = adc::Adc::new(
    tuple.0, // The ADC we are going to control
    adc::config::Config::default(),
    // The following is only needed to make sure the clock signal for the ADC is set up
    // correctly.
    &clocks,
    &adc_common,
)
.into_oneshot();
let mut adc2 = adc::Adc::new_disabled(tuple.1);
adc2.calibrate(&clocks, &adc_common);
adc2.set_config(adc::config::Config::default());
let _ = adc2.into_enabled();
// Set up pin PA0 as analog pin.
// This pin is connected to the user button on the stm32f3discovery board.
let mut analog_pin = gpioa.pa2.into_analog(&mut gpioa.moder, &mut gpioa.pupdr);
let mut timer = timer::Timer::new(dp.TIM2, clocks, &mut rcc.apb1);
unsafe {
    cortex_m::peripheral::NVIC::unmask(timer.interrupt());
}
timer.enable_interrupt(timer::Event::Update);
// Start a timer which fires regularly to wake up from `asm::wfi`
timer.start(500.milliseconds());
// Put the timer in the global context.
critical_section::with(|cs| {
    TIMER.borrow(cs).replace(Some(timer));
});
let mut led_b = gpioc
    .pc15
    .into_push_pull_output(&mut gpioc.moder, &mut gpioc.otyper);
let mut led_g = gpioc
    .pc14
    .into_push_pull_output(&mut gpioc.moder, &mut gpioc.otyper);
let mut led_r = gpioc
    .pc13
    .into_push_pull_output(&mut gpioc.moder, &mut gpioc.otyper);
led_b.set_high().unwrap();
led_g.set_high().unwrap();
led_r.set_high().unwrap();
let mut enable = gpioa
    .pa0
    .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
let mut inha = gpiob
    .pb4
    .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
let mut inla = gpioa
    .pa1
    .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
let mut inhb = gpiob
    .pb5
    .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
let mut inlb = gpiob
    .pb10
    .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
let mut inhc = gpiob
    .pb0
    .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
let mut inlc = gpiob
    .pb7
    .into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
enable.set_low().unwrap();
inha.set_low().unwrap();
inla.set_low().unwrap();
inhb.set_low().unwrap();
inlb.set_low().unwrap();
inhc.set_low().unwrap();
inlc.set_low().unwrap();
let mut delay_time: u32 = 10000;
loop {
    let adc_data: u16 = adc.read(&mut analog_pin).unwrap();
    led_r.toggle();
    asm::delay(delay_time);
}

}

I flashed it using picoprobe

It does work: (video from March 1, 2024)

This does not have back-emf sensing, so it really can’t be used reliably. The phase connections on a spark max are not tapped to the ADC of the micro controller, so you can only use spark-max based hardware with motors that have encoders.