shibosoftwaredev/f1c100s-linux-nema17-stepper-controller

A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.

Version
0.4.56
License
unset
Stars
0

firmware/stm32/main.c

#include "vendor/stm32g030xx.h"
#include "motion.h"
#include "current.h"
#include "limit.h"

uint32_t SystemCoreClock=16000000;
static Motion motion;
static volatile uint32_t milliseconds;
static volatile uint8_t rx[128],rx_write,rx_read;
static uint32_t configured_rate;
static bool temperature_ok;
static int32_t temperature_mc;
static uint32_t temperature_time;
static uint32_t led_wave[96],last_led_time;
static uint8_t last_led_state=255;
static CurrentTelemetry coil_current;
static uint16_t dac_code=0xffff,dac_requested=0xffff,dac_vdda;
static uint32_t dac_written_at,dac_checked_at;

typedef struct { unsigned pin, mode; } GpioMode;
static void gpio(GPIO_TypeDef *port, GpioMode config) {
  unsigned pin=config.pin,mode=config.mode;
  port->MODER=(port->MODER&~(3u<<(pin*2)))|(mode<<(pin*2));
  port->PUPDR&=~(3u<<(pin*2));
}
static void delay_us(unsigned us) { for(volatile unsigned n=0;n<us*4;n++)__NOP(); }
static void status_led(void) {
  if(motion.remaining || motion.step_high)return;
  uint8_t state=motion.faults?2:(motion.armed?1:0);
  if(state==last_led_state || (uint32_t)(milliseconds-last_led_time)<100)return;
  // TIM16 update request 0x2e is defined by ST's stm32g0xx_ll_dmamux.h.
  // Four 250 ns GPIO-DMA cells per WS2812 bit: 0=1000, 1=1110.
  uint32_t grb=state==2?0x000800:(state==1?0x080000:0x000008);
  for(unsigned bit=0;bit<24;bit++) {
    bool one=!!(grb&(1u<<(23-bit)));
    for(unsigned cell=0;cell<4;cell++)led_wave[bit*4+cell]=(cell==0 || (one && cell<3))?1u<<13:1u<<29;
  }
  GPIOA->BSRR=1u<<29;gpio(GPIOA,(GpioMode){13,1});
  DMA1_Channel2->CCR=0;DMA1->IFCR=DMA_IFCR_CGIF2;
  DMAMUX1_Channel1->CCR=0x2e;
  DMA1_Channel2->CPAR=(uint32_t)&GPIOA->BSRR;
  DMA1_Channel2->CMAR=(uint32_t)led_wave;DMA1_Channel2->CNDTR=96;
  DMA1_Channel2->CCR=DMA_CCR_DIR|DMA_CCR_MINC|DMA_CCR_PSIZE_1|DMA_CCR_MSIZE_1|DMA_CCR_PL_1|DMA_CCR_EN;
  TIM16->CR1=0;TIM16->PSC=0;TIM16->ARR=3;TIM16->EGR=TIM_EGR_UG;TIM16->SR=0;
  TIM16->DIER=TIM_DIER_UDE;TIM16->CR1=TIM_CR1_CEN;
  uint32_t start=milliseconds;
  while(!(DMA1->ISR&DMA_ISR_TCIF2) && (uint32_t)(milliseconds-start)<2){}
  TIM16->CR1=0;TIM16->DIER=0;DMA1_Channel2->CCR=0;GPIOA->BSRR=1u<<29;
  delay_us(300);last_led_time=milliseconds;last_led_state=state;
}
static void hardware_sync(void) {
  bool enabled=motion.armed && motion.limit_ready && motion.limit_io_ok && motion.limit_ma && !motion.faults;
  GPIOA->BSRR=enabled ? 1u<<4 : 1u<<20;
  GPIOA->BSRR=motion.reverse ? 1u<<6 : 1u<<22;
  GPIOA->BSRR=motion.step_high ? 1u<<7 : 1u<<23;
  if(!motion.armed || (!motion.remaining && !motion.step_high)) {
    TIM3->CR1=0; configured_rate=0; return;
  }
  if(configured_rate!=motion.rate || !(TIM3->CR1&TIM_CR1_CEN)) {
    TIM3->CR1=0;
    uint32_t cycles=SystemCoreClock/(motion.rate*2);
    uint32_t divider=(cycles+65534)/65535;
    TIM3->PSC=divider-1; TIM3->ARR=cycles/divider-1;
    TIM3->EGR=TIM_EGR_UG;TIM3->SR=0;TIM3->CNT=0;
    configured_rate=motion.rate;TIM3->CR1=TIM_CR1_CEN;
  }
}
void TIM3_IRQHandler(void) {
  TIM3->SR=0;
  motion_step_tick(&motion);
  GPIOA->BSRR=motion.step_high ? 1u<<7 : 1u<<23;
  if(!motion.armed || (!motion.remaining && !motion.step_high))TIM3->CR1=0;
}
void SysTick_Handler(void) {
  ++milliseconds;
  __disable_irq();motion_tick(&motion,milliseconds);hardware_sync();__enable_irq();
}
void USART2_IRQHandler(void) {
  uint32_t flags=USART2->ISR;
  if(flags&(USART_ISR_ORE|USART_ISR_FE|USART_ISR_NE|USART_ISR_PE)) {
    USART2->ICR=USART_ICR_ORECF|USART_ICR_FECF|USART_ICR_NECF|USART_ICR_PECF;
    motion_fault(&motion,PROTOCOL_FAULT);hardware_sync();
  }
  while(USART2->ISR&USART_ISR_RXNE_RXFNE) {
    uint8_t b=(uint8_t)USART2->RDR;
    uint8_t next=(uint8_t)((rx_write+1)&127);
    if(next==rx_read){motion_fault(&motion,PROTOCOL_FAULT);hardware_sync();}
    else {rx[rx_write]=b;rx_write=next;}
  }
}
void TIM17_IRQHandler(void) {
  TIM17->SR=0;
  if(motion.faults)GPIOB->ODR^=1u<<9;
  else GPIOB->BSRR=1u<<25;
}
static bool scl_high(void) {
  GPIOA->BSRR=1u<<11;
  for(unsigned i=0;i<100;i++) {if(GPIOA->IDR&(1u<<11))return true;delay_us(1);}
  return false;
}
static void i2c_stop(void) {
  GPIOA->BSRR=1u<<28;delay_us(5);(void)scl_high();delay_us(5);
  GPIOA->BSRR=1u<<12;delay_us(5);
}
static bool i2c_start(void) {
  GPIOA->BSRR=1u<<12;
  if(!scl_high())return false;
  delay_us(5);GPIOA->BSRR=1u<<28;delay_us(5);GPIOA->BSRR=1u<<27;return true;
}
static bool i2c_write(uint8_t data) {
  for(unsigned bit=0;bit<8;bit++) {
    GPIOA->BSRR=(data&0x80)?1u<<12:1u<<28;data<<=1;delay_us(5);
    if(!scl_high())return false;
    delay_us(5);GPIOA->BSRR=1u<<27;
  }
  GPIOA->BSRR=1u<<12;delay_us(5);
  if(!scl_high())return false;
  delay_us(5);bool ack=!(GPIOA->IDR&(1u<<12));GPIOA->BSRR=1u<<27;return ack;
}
static bool i2c_read(uint8_t *data, bool ack) {
  *data=0;GPIOA->BSRR=1u<<12;
  for(unsigned bit=0;bit<8;bit++) {
    delay_us(5);if(!scl_high())return false;
    delay_us(5);*data=(uint8_t)((*data<<1)|!!(GPIOA->IDR&(1u<<12)));GPIOA->BSRR=1u<<27;
  }
  GPIOA->BSRR=ack?1u<<28:1u<<12;delay_us(5);
  if(!scl_high())return false;
  delay_us(5);GPIOA->BSRR=1u<<27;GPIOA->BSRR=1u<<12;return true;
}
static bool temp_write(uint8_t reg, uint16_t value) {
  bool ok=i2c_start() && i2c_write(0x90) && i2c_write(reg)
    && i2c_write((uint8_t)(value>>8)) && i2c_write((uint8_t)value);
  i2c_stop();return ok;
}
static bool temp_read(void) {
  uint8_t high=0,low=0;
  bool ok=i2c_start() && i2c_write(0x90) && i2c_write(0)
    && i2c_start() && i2c_write(0x91) && i2c_read(&high,true) && i2c_read(&low,false);
  i2c_stop();
  if(ok)temperature_mc=((int16_t)((uint16_t)high<<8|low)>>4)*625/10;
  return ok;
}
static bool dac_write_code(uint16_t code) {
  /* MCP4725 A0 variant, A0 grounded: 0x60. Fast write NEVER writes EEPROM. */
  bool ok=i2c_start() && i2c_write(0xc0)
    && i2c_write((uint8_t)(code>>8)) && i2c_write((uint8_t)code);
  i2c_stop();return ok;
}
static bool dac_verify_code(uint16_t code) {
  uint8_t b[5]={0};
  bool ok=i2c_start() && i2c_write(0xc1);
  for(unsigned i=0;ok && i<5;i++)ok=i2c_read(&b[i],i<4);
  i2c_stop();return ok && limit_readback_ok(b,code);
}
static void dac_failure(void) {
  __disable_irq();motion.limit_io_ok=false;
  motion_fault(&motion,CURRENT_LIMIT_FAULT);hardware_sync();__enable_irq();
}
static void service_current_limit(uint16_t vdda_mv) {
  uint32_t now=milliseconds;
  uint16_t target=motion.faults?0:motion.limit_ma;
  bool changed=target!=dac_requested || !motion.limit_io_ok;
  if(!changed && (uint32_t)(now-dac_checked_at)<20) {
    __disable_irq();
    if(!motion.faults && (uint32_t)(now-dac_written_at)>=CURRENT_LIMIT_SETTLE_MS)
      motion.limit_ready=true;
    __enable_irq();
    return;
  }
  if(changed && target && (vdda_mv<2700 || vdda_mv>3600))return;
  dac_checked_at=now;
  if(changed) {
    uint16_t code=limit_code(target,vdda_mv);
    if(!dac_write_code(code) || !dac_verify_code(code)){dac_failure();return;}
    dac_code=code;dac_requested=target;dac_vdda=vdda_mv;dac_written_at=milliseconds;
    __disable_irq();motion.limit_io_ok=true;motion.limit_ready=false;__enable_irq();
  } else if(!dac_verify_code(dac_code))dac_failure();
}
static bool adc_read(unsigned channel, uint16_t *value) {
  ADC1->ISR=ADC_ISR_CCRDY;
  ADC1->CHSELR=1u<<channel;
  unsigned budget=30000;
  while(!(ADC1->ISR&ADC_ISR_CCRDY) && --budget){}
  if(!budget)return false;
  ADC1->ISR=ADC_ISR_EOC|ADC_ISR_EOS;ADC1->CR|=ADC_CR_ADSTART;
  budget=30000;while(!(ADC1->ISR&ADC_ISR_EOC) && --budget){}
  if(!budget)return false;
  *value=(uint16_t)ADC1->DR;return true;
}
static bool adc_init(void) {
  RCC->APBENR2|=RCC_APBENR2_ADCEN;
  ADC1->CR=ADC_CR_ADVREGEN;delay_us(30);
  ADC1->CFGR2=ADC_CFGR2_CKMODE_1;
  ADC1_COMMON->CCR=ADC_CCR_VREFEN;
  ADC1->SMPR=ADC_SMPR_SMP1;
  ADC1->CR|=ADC_CR_ADCAL;
  unsigned budget=30000;while((ADC1->CR&ADC_CR_ADCAL) && --budget){}
  if(!budget)return false;
  delay_us(5);ADC1->ISR=ADC_ISR_ADRDY;ADC1->CR|=ADC_CR_ADEN;
  budget=30000;while(!(ADC1->ISR&ADC_ISR_ADRDY) && --budget){}
  return budget!=0;
}
static void tx_bytes(const uint8_t *data, unsigned count) {
  for(unsigned i=0;i<count;i++) {
    uint32_t began=milliseconds;
    while(!(USART2->ISR&USART_ISR_TXE_TXFNF))if((uint32_t)(milliseconds-began)>10)return;
    USART2->TDR=data[i];
  }
}
static void put32(uint8_t *p,uint32_t n) {for(unsigned i=0;i<4;i++)p[i]=(uint8_t)(n>>(i*8));}
static void put16(uint8_t *p,uint16_t n) {p[0]=(uint8_t)n;p[1]=(uint8_t)(n>>8);}
static uint8_t saved_request[41],saved_reply[32],saved_count,saved_reply_count;
static void dispatch(const uint8_t *frame,unsigned count) {
  bool duplicate=count==saved_count;
  for(unsigned i=0;duplicate && i<count;i++)if(frame[i]!=saved_request[i])duplicate=false;
  // Reads may be repeated without replaying an old freshness flag. All
  // existing commands retain the original duplicate-reply semantics.
  if(duplicate && frame[4]!=0x21 && frame[4]!=0x23){tx_bytes(saved_reply,saved_reply_count);return;}
  Request r={frame[4],frame[5]|(uint16_t)frame[6]<<8,frame+7};
  __disable_irq();uint8_t status=motion_command(&motion,&r);hardware_sync();__enable_irq();
  uint8_t response[32]={0xa5,0x5a,1,frame[3],frame[4]|0x80,1,0,status};
  unsigned size=8;
  if(frame[4]==0x20 && !status) {
    response[5]=19;response[8]=motion.armed;response[9]=motion.faults;
    put32(response+10,motion.remaining);put32(response+14,motion.rate);
    put32(response+18,motion.inputs.pd_mv);put32(response+22,(uint32_t)motion.inputs.temperature_mc);
    size=26;
  }
  if(frame[4]==0x21 && !status) {
    CurrentTelemetry t=current_snapshot(&coil_current,milliseconds);
    response[5]=18;response[8]=t.flags;
    put32(response+9,(uint32_t)t.a_ma);put32(response+13,(uint32_t)t.b_ma);
    put16(response+17,t.raw_a);put16(response+19,t.raw_b);
    put16(response+21,t.vdda_mv);put16(response+23,t.age_ms);size=25;
  }
  if(frame[4]==0x23 && !status) {
    response[5]=10;
    response[8]=(motion.limit_ready?1:0)|(motion.limit_io_ok?2:0);
    put16(response+9,motion.limit_ma);put16(response+11,dac_code);
    put16(response+13,limit_nominal_ma(dac_code>4095?0:dac_code,dac_vdda));
    put16(response+15,dac_vdda);size=17;
  }
  uint16_t crc=motion_crc16(response+2,size-2);
  response[size++]=(uint8_t)crc;response[size++]=(uint8_t)(crc>>8);
  for(unsigned i=0;i<count;i++)saved_request[i]=frame[i];
  saved_count=(uint8_t)count;
  for(unsigned i=0;i<size;i++)saved_reply[i]=response[i];
  saved_reply_count=(uint8_t)size;
  tx_bytes(response,size);
}
static void receive(void) {
  static uint8_t frame[41];static unsigned used;static uint32_t last_byte;
  if(used && (uint32_t)(milliseconds-last_byte)>20) {
    used=0;__disable_irq();motion_fault(&motion,PROTOCOL_FAULT);hardware_sync();__enable_irq();
  }
  while(rx_read!=rx_write) {
    uint8_t byte=rx[rx_read];rx_read=(uint8_t)((rx_read+1)&127);last_byte=milliseconds;
    if(!used && byte!=0xa5)goto invalid;
    if(used==1 && byte!=0x5a)goto invalid;
    frame[used++]=byte;
    if(used==7 && (frame[2]!=1 || frame[6] || frame[5]>32))goto invalid;
    if(used>=9 && used==(unsigned)(9+frame[5])) {
      uint16_t crc=frame[used-2]|(uint16_t)frame[used-1]<<8;
      if(motion_crc16(frame+2,used-4)!=crc)goto invalid;
      dispatch(frame,used);used=0;
    }
    continue;
invalid:
    used=0;__disable_irq();motion_fault(&motion,PROTOCOL_FAULT);hardware_sync();__enable_irq();
  }
}
static void board_init(void) {
  RCC->IOPENR|=RCC_IOPENR_GPIOAEN|RCC_IOPENR_GPIOBEN;
  GPIOA->BSRR=(1u<<20)|(1u<<22)|(1u<<23);
  gpio(GPIOA,(GpioMode){4,1});gpio(GPIOA,(GpioMode){6,1});gpio(GPIOA,(GpioMode){7,1});
  // Bonded-together GPIOs stay analog except the one named in the schematic.
  for(unsigned pin=0;pin<16;pin++)if(pin!=14)gpio(GPIOB,(GpioMode){pin,3});
  gpio(GPIOB,(GpioMode){7,0});gpio(GPIOB,(GpioMode){3,0});gpio(GPIOB,(GpioMode){9,1});
  GPIOB->BSRR=1u<<25;
  gpio(GPIOA,(GpioMode){0,0});gpio(GPIOA,(GpioMode){1,3});
  // PA5=ADC5, PB0=ADC8. PB1/PB2/PA8 share the PB0 package pin and must
  // remain analog; never configure any of them as a digital output.
  gpio(GPIOA,(GpioMode){5,3});gpio(GPIOA,(GpioMode){8,3});
  gpio(GPIOA,(GpioMode){2,2});gpio(GPIOA,(GpioMode){3,2});
  GPIOA->AFR[0]=(GPIOA->AFR[0]&~((15u<<8)|(15u<<12)))|(1u<<8)|(1u<<12);
  gpio(GPIOA,(GpioMode){11,1});gpio(GPIOA,(GpioMode){12,1});
  GPIOA->OTYPER|=(1u<<11)|(1u<<12);GPIOA->BSRR=(1u<<11)|(1u<<12);
  RCC->APBENR2|=RCC_APBENR2_SYSCFGEN;
  SYSCFG->CFGR1&=~(SYSCFG_CFGR1_PA11_RMP|SYSCFG_CFGR1_PA12_RMP);
  RCC->APBENR1|=RCC_APBENR1_TIM3EN|RCC_APBENR1_USART2EN;
  RCC->APBENR2|=RCC_APBENR2_TIM17EN|RCC_APBENR2_TIM16EN;
  RCC->AHBENR|=RCC_AHBENR_DMA1EN;
  TIM3->DIER=TIM_DIER_UIE;NVIC_SetPriority(TIM3_IRQn,0);NVIC_EnableIRQ(TIM3_IRQn);
  TIM17->PSC=15;TIM17->ARR=249;TIM17->EGR=TIM_EGR_UG;TIM17->SR=0;
  TIM17->DIER=TIM_DIER_UIE;TIM17->CR1=TIM_CR1_CEN;
  NVIC_SetPriority(TIM17_IRQn,3);NVIC_EnableIRQ(TIM17_IRQn);
  USART2->BRR=139;USART2->CR1=USART_CR1_TE|USART_CR1_RE|USART_CR1_RXNEIE_RXFNEIE|USART_CR1_UE;
  NVIC_SetPriority(USART2_IRQn,2);NVIC_EnableIRQ(USART2_IRQn);
  SysTick_Config(SystemCoreClock/1000);NVIC_SetPriority(SysTick_IRQn,1);
  RCC->CSR|=RCC_CSR_LSION;
  IWDG->KR=0xcccc;IWDG->KR=0x5555;IWDG->PR=3;IWDG->RLR=99;IWDG->KR=0xaaaa;
}
int main(void) {
  motion_init(&motion);board_init();__enable_irq();
  bool adc_ok=adc_init();
  // Recover a slave interrupted mid-byte, then program comparator thresholds.
  for(unsigned i=0;i<9;i++){GPIOA->BSRR=1u<<27;delay_us(5);(void)scl_high();delay_us(5);}
  i2c_stop();
  /* Never trust EEPROM's retained/default midscale setting after power-up.
   * The bridge is hardware-pulled asleep until this write/readback and the
   * requested setting have both passed. No nonvolatile write is issued. */
  if(dac_write_code(0) && dac_verify_code(0)) {
    motion.limit_io_ok=true;dac_code=0;dac_requested=0;dac_written_at=milliseconds;
  } else dac_failure();
  bool temp_configured=temp_write(1,0x60a0)&&temp_write(2,0x3c00)&&temp_write(3,0x4600);
  uint32_t last_sample=0,last_current=0;
  uint16_t vdda_mv=0;
  for(;;) {
    receive();
    uint32_t now=milliseconds;
    if((uint32_t)(now-last_current)>=1) {
      last_current=now;uint16_t a=0,b=0;
      bool ok=adc_ok && adc_read(5,&a) && adc_read(8,&b);
      CurrentSample sample={.raw_a=a,.raw_b=b,.vdda_mv=vdda_mv,.now=now,.adc_ok=ok};
      current_update(&coil_current,&sample);
    }
    if((uint32_t)(now-last_sample)>=10) {
      last_sample=now;
      if((uint32_t)(now-temperature_time)>=100) {
        temperature_time=now;temperature_ok=temp_configured && temp_read() && now>=300;
      }
      uint16_t raw=0,ref=0;
      bool power_adc_ok=adc_ok && adc_read(1,&raw) && adc_read(13,&ref);
      uint16_t cal=*(const uint16_t*)0x1fff75aa;
      uint32_t vdda=ref ? (uint32_t)cal*3000/ref : 0;
      vdda_mv=vdda<=65535 ? (uint16_t)vdda:0;
      Inputs in={.pd_good=!!(GPIOB->IDR&(1u<<3)),.driver_ok=!!(GPIOB->IDR&(1u<<7)),
        .sensor_ok=temperature_ok && power_adc_ok && vdda>=2700 && vdda<=3600,
        .pd_mv=(uint32_t)(((uint64_t)raw*vdda*11)/4095),.temperature_mc=temperature_mc};
      if(!(GPIOA->IDR&1u))in.temperature_mc=70000;
      __disable_irq();motion_sample(&motion,&in);hardware_sync();__enable_irq();
    }
    service_current_limit(vdda_mv);
    status_led();IWDG->KR=0xaaaa;__WFI();
  }
}
extern uint32_t _estack,_sidata,_sdata,_edata,_sbss,_ebss;
static void Default_Handler(void) {GPIOA->BSRR=1u<<20;for(;;){} }
void Reset_Handler(void) {
  __disable_irq();uint32_t *src=&_sidata;
  for(uint32_t *p=&_sdata;p<&_edata;)*p++=*src++;
  for(uint32_t *p=&_sbss;p<&_ebss;)*p++=0;
  // Reset state selects HSI16; explicitly retain /1 rather than enabling a PLL.
  RCC->CR&=~RCC_CR_HSIDIV;SCB->VTOR=0x08000000;
  (void)main();Default_Handler();
}
__attribute__((section(".isr_vector"),used))
void (*const vectors[48])(void)={
  [0]=(void (*)(void))&_estack,[1]=Reset_Handler,[2]=Default_Handler,[3]=Default_Handler,
  [11]=Default_Handler,[14]=Default_Handler,[15]=SysTick_Handler,
  [16+TIM3_IRQn]=TIM3_IRQHandler,[16+TIM17_IRQn]=TIM17_IRQHandler,[16+USART2_IRQn]=USART2_IRQHandler,
};