AnasSarkiz/rp2040-usb-c-single-axis-dm542t-motion-controller--01a0e89c
A USB‑C RP2040 controller board with external QSPI flash, crystal clock, regulated 3.3/1.1/5 V power, boost-converted motor-driver supply, protected USB and limit inputs, status LEDs, reset/boot switches, and AO3400A MOSFET outputs for stepper-control signals.
- Version
- 0.1.0-prototype.19
- License
- unset
- Stars
- 0
firmware/main.c
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/clocks.h"
#include "hardware/pio.h"
#include "hardware/sync.h"
#include "hardware/watchdog.h"
#include "motion.h"
#include "step.pio.h"
#include "tusb.h"
#include "usb_sleep.h"
#include "power_policy.h"
#define STEP_PIN 0
#define DIR_PIN 1
#define ENABLE_PIN 2
#define ARM_PIN 3
#define MIN_PIN 4
#define MAX_PIN 5
#define LED_PIN 25
#define SENSE_PIN 26
#define DRIVER_POWER_PIN 6
#define DRIVER_REG_ENABLE_PIN 7
#define DRIVER_FAULT_PIN 8
static PowerPolicy power;
static Motion motion;
static volatile bool host_connected;
static volatile bool supply_ok;
static PIO step_pio=pio0;
static uint step_sm;
static uint64_t previous_tick;
static void usb_abort(const char *reason) {
uint32_t irq=save_and_disable_interrupts();
gpio_put(ARM_PIN,0);
motion_abort(&motion,reason);
host_connected=false;
gpio_put(ENABLE_PIN,0);
restore_interrupts(irq);
}
void tud_suspend_cb(bool remote_wakeup_enabled) {
(void)remote_wakeup_enabled;
usb_abort("usb_suspend");
gpio_put(DRIVER_REG_ENABLE_PIN,0);
gpio_put(DRIVER_POWER_PIN,0);
power=(PowerPolicy){.fault_latched=power.fault_latched};
}
void tud_umount_cb(void) {
usb_abort("usb_reset");
gpio_put(DRIVER_REG_ENABLE_PIN,0);
gpio_put(DRIVER_POWER_PIN,0);
power=(PowerPolicy){0};
}
void tud_cdc_line_state_cb(uint8_t interface, bool dtr, bool rts) {
(void)rts;
if (interface==0 && !dtr) usb_abort("serial_closed");
}
static bool tick(struct repeating_timer *timer) {
(void)timer;
uint64_t now=time_us_64();
if (previous_tick && (now-previous_tick<90 || now-previous_tick>150)) motion_abort(&motion,"timer_overrun");
previous_tick=now;
// FIFO must be empty before another pulse is counted/queued.
if (!pio_sm_is_tx_fifo_empty(step_pio,step_sm)) motion_abort(&motion,"pulse_engine_busy");
motion_tick(&motion,(MotionInputs){host_connected,supply_ok,gpio_get(MIN_PIN),gpio_get(MAX_PIN)});
if (motion.enabled && motion.enable_ticks>=2000) gpio_put(DIR_PIN,motion.direction>0);
gpio_put(ENABLE_PIN,motion.enabled);
gpio_put(ARM_PIN,motion.enabled);
gpio_put(LED_PIN,motion.enabled);
if (motion.pulse) pio_sm_put(step_pio,step_sm,0);
return true;
}
static void print_status(Motion snapshot) {
printf("STATUS position=%" PRId64 " enabled=%d homed=%d mode=%d min=%d max=%d supply_ok=%d fault=%s\n",snapshot.position,snapshot.enabled,snapshot.homed,snapshot.mode,snapshot.inputs.minimum,snapshot.inputs.maximum,snapshot.inputs.supply_ok,snapshot.fault);
}
int main(void) {
for (uint pin=0;pin<=3;pin++) { gpio_init(pin); gpio_put(pin,0); gpio_set_dir(pin,GPIO_OUT); }
gpio_init(DRIVER_POWER_PIN); gpio_put(DRIVER_POWER_PIN,0); gpio_set_dir(DRIVER_POWER_PIN,GPIO_OUT);
gpio_init(DRIVER_REG_ENABLE_PIN); gpio_put(DRIVER_REG_ENABLE_PIN,0); gpio_set_dir(DRIVER_REG_ENABLE_PIN,GPIO_OUT);
gpio_init(DRIVER_FAULT_PIN); gpio_set_dir(DRIVER_FAULT_PIN,GPIO_IN);
gpio_init(LED_PIN); gpio_set_dir(LED_PIN,GPIO_OUT);
gpio_init(MIN_PIN); gpio_set_dir(MIN_PIN,GPIO_IN); gpio_set_input_hysteresis_enabled(MIN_PIN,true);
gpio_init(MAX_PIN); gpio_set_dir(MAX_PIN,GPIO_IN); gpio_set_input_hysteresis_enabled(MAX_PIN,true);
set_sys_clock_48mhz();
stdio_init_all();
adc_init(); adc_gpio_init(SENSE_PIN); adc_select_input(0);
motion_init(&motion);
step_sm=pio_claim_unused_sm(step_pio,true);
uint offset=pio_add_program(step_pio,&step_pulse_program);
pio_sm_config config=step_pulse_program_get_default_config(offset);
sm_config_set_set_pins(&config,STEP_PIN,1);
sm_config_set_clkdiv(&config,48.0f); // 48 MHz / 48: exactly 1 us per PIO cycle.
pio_gpio_init(step_pio,STEP_PIN);
pio_sm_set_consecutive_pindirs(step_pio,step_sm,STEP_PIN,1,true);
pio_sm_init(step_pio,step_sm,offset,&config);
pio_sm_set_pins_with_mask(step_pio,step_sm,0,1u<<STEP_PIN);
pio_sm_set_enabled(step_pio,step_sm,true);
struct repeating_timer timer;
if (!add_repeating_timer_us(-100,tick,NULL,&timer)) panic("motion timer unavailable");
watchdog_enable(250,true);
char line[80]; size_t length=0; bool overflow=false;
while (true) {
tud_task_ext(0,false);
if (tud_suspended()) {
uint32_t irq=save_and_disable_interrupts();
// Break the series STEP path before stopping or clearing PIO.
gpio_put(ARM_PIN,0);
motion_abort(&motion,"usb_suspend");
host_connected=false;
supply_ok=false;
cancel_repeating_timer(&timer);
pio_sm_set_enabled(step_pio,step_sm,false);
pio_sm_clear_fifos(step_pio,step_sm);
pio_sm_restart(step_pio,step_sm);
pio_sm_set_pins_with_mask(step_pio,step_sm,0,1u<<STEP_PIN);
gpio_put(DIR_PIN,0);
gpio_put(LED_PIN,0);
// Remove the optocoupler supply. No driver current is taken from
// USB in suspend. ENA release can restore holding torque.
gpio_put(ENABLE_PIN,0);
gpio_put(DRIVER_REG_ENABLE_PIN,0);
gpio_put(DRIVER_POWER_PIN,0);
power=(PowerPolicy){.fault_latched=power.fault_latched};
watchdog_disable();
adc_run(false);
adc_hw->cs &= ~ADC_CS_EN_BITS;
restore_interrupts(irq);
length=0; overflow=false;
usb_sleep_until_resume();
tud_cdc_read_flush();
gpio_put(ENABLE_PIN,0);
adc_init(); adc_select_input(0);
// Reinitialize PC as well as FIFO: no pre-suspend pulse survives.
pio_sm_init(step_pio,step_sm,offset,&config);
pio_sm_set_pins_with_mask(step_pio,step_sm,0,1u<<STEP_PIN);
pio_sm_set_enabled(step_pio,step_sm,true);
previous_tick=0;
if (!add_repeating_timer_us(-100,tick,NULL,&timer)) panic("motion timer unavailable");
watchdog_enable(250,true);
continue;
}
watchdog_update();
host_connected=stdio_usb_connected();
// The buck-boost regulates VDRV; this ADC monitors its upstream V5.
// This is a coarse cable/supply fault threshold, not a voltage reference.
bool upstream_ok=adc_read()>=2600; // approximately 4.19 V nominal.
power_policy_update(&power,(PowerInputs){tud_mounted(),false,upstream_ok,!gpio_get(DRIVER_FAULT_PIN),to_ms_since_boot(get_absolute_time())});
if (power.fault_latched) usb_abort("driver_overcurrent");
// On shutdown remove regulator enable before its input supply.
if (!power.regulator_on) gpio_put(DRIVER_REG_ENABLE_PIN,0);
gpio_put(DRIVER_POWER_PIN,power.switch_on);
if (power.regulator_on) gpio_put(DRIVER_REG_ENABLE_PIN,1);
supply_ok=power.ready;
if (!host_connected) { length=0; overflow=false; }
int ch=getchar_timeout_us(0);
if (ch==PICO_ERROR_TIMEOUT) { tight_loop_contents(); continue; }
if (ch=='\r') continue;
if (ch=='\n') {
uint32_t irq=save_and_disable_interrupts();
const char *reply;
if (overflow) { motion_abort(&motion,"line_overflow"); reply="ERR line_too_long"; }
else { line[length]='\0'; reply=motion_command(&motion,line); }
Motion snapshot=motion;
restore_interrupts(irq);
if (!strcmp(reply,"STATUS")) print_status(snapshot); else puts(reply);
length=0; overflow=false;
} else if (ch<32 || ch>126 || length>=sizeof(line)-1) overflow=true;
else if (!overflow) line[length++]=(char)ch;
}
}