techmannih/nema34-mounted-stepper-controller

A 48 V USB‑C PD 3.1 EPR NEMA‑34 stepper controller PCB combining STM32/TMC5160 control, external MOSFET motor bridges, protected power and regenerative braking, USB data, telemetry, diagnostics, status LEDs, and buzzer alarms.

Version
0.3.92
License
unset
Stars
1

firmware/tests/test_tmc5160.c

#include "tmc5160.h"
#include "motor_service.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>

typedef struct { uint32_t regs[128], pending; unsigned calls; bool fail, corrupt_echo; uint8_t corrupt_write_address; } fake_t;
static bool transfer(void *context,const uint8_t tx[5],uint8_t rx[5]) {
  fake_t *f=context; ++f->calls;
  if(f->fail)return false;
  rx[0]=0; for(unsigned i=1;i<5;++i)rx[i]=(uint8_t)(f->pending>>(8u*(4u-i)));
  const uint8_t addr=tx[0]&127u;
  const uint32_t value=((uint32_t)tx[1]<<24)|((uint32_t)tx[2]<<16)|((uint32_t)tx[3]<<8)|tx[4];
  if(tx[0]&128u) {
    assert(addr!=0x06); // OTP programming is never allowed by this driver.
    if(addr==1)f->regs[addr]&=~value; else f->regs[addr]=value;
    if(addr==0x09 || addr==0x0A) assert(!(f->regs[0x6C]&15u));
    f->pending=value^((f->corrupt_echo || (f->corrupt_write_address && addr==f->corrupt_write_address))?1u:0u);
  } else f->pending=f->regs[addr];
  return true;
}
int main(void) {
  fake_t f={0}; f.regs[4]=0x30000010;f.regs[1]=1;
  f.regs[0x09]=0x10606u; f.regs[0x0A]=0x200u;
  pd1180_tmc_t d={.frame=transfer,.context=&f};
  uint32_t value;
  assert(pd1180_tmc_read(&d,4,&value) && value==0x30000010 && f.calls==2);
  assert(pd1180_tmc_configure_ramp(&d,240)); // Approx. 0.827 A RMS first tuning stage.
  assert(d.configured && !(f.regs[0x6C]&15u));
  assert(f.regs[0x09]==0x10C06u && f.regs[0x0A]==0x400u);
  assert(((f.regs[0x6C]>>24)&15u)==0 && !(f.regs[0x6C]&(3u<<30)));
  assert(((f.regs[0x10]>>8)&31u)==6 && f.regs[0x0B]==139);
  assert(pd1180_tmc_set_chopper(&d,true));
  assert(f.regs[0x27]==0 && f.regs[0x20]==1 && f.regs[0x26]==130 && f.regs[0x34]==0);
  assert(pd1180_tmc_velocity(&d,11930) && f.regs[0x27]==11930);
  assert(!pd1180_tmc_velocity(&d,11931));
  assert(pd1180_tmc_velocity(&d,0));
  int32_t velocity=0; f.regs[0x22]=0xFFFFFF;
  assert(pd1180_tmc_actual_velocity(&d,&velocity) && velocity==-1);
  f.regs[0x22]=0;
  assert(pd1180_tmc_set_current(&d,125) && d.irun==3);
  for(unsigned request=241;request<=1001;++request) {
    const unsigned before=f.calls;
    assert(!pd1180_tmc_set_current(&d,(uint16_t)request));
    assert(f.calls==before && d.irun==3); // Rejection must not write a higher current.
  }
  pd1180_tmc_status_t status;
  for(unsigned bit=0;bit<3;++bit) {
    f.regs[1]=1u<<bit; assert(!pd1180_tmc_status(&d,&status) && !d.configured);
    assert(!pd1180_tmc_set_chopper(&d,true));f.regs[1]=0;
  }
  for(unsigned bit=0;bit<32;++bit) if((1u<<bit)&0x1E003000u) {
    f.regs[0x6F]=1u<<bit; assert(!pd1180_tmc_status(&d,&status));
  }
  f.regs[0x6F]=0;f.regs[4]=0xFFFFFFFF;assert(!pd1180_tmc_configure_ramp(&d,240));
  f.regs[4]=0;assert(!pd1180_tmc_configure_ramp(&d,240));
  f.regs[4]=0x30000000;assert(!pd1180_tmc_configure_ramp(&d,240)); // Enabled bridge.
  f.regs[4]=0x30000050;assert(!pd1180_tmc_configure_ramp(&d,240)); // Wrong mode.
  f.regs[4]=0x30000014;assert(!pd1180_tmc_configure_ramp(&d,240)); // dcStep unexpectedly enabled.
  f.regs[4]=0x30000010;f.corrupt_echo=true;assert(!pd1180_tmc_configure_ramp(&d,240));
  f.corrupt_echo=false;f.fail=true;assert(!pd1180_tmc_status(&d,&status) && !status.valid);
  f.fail=false;f.regs[1]=0;f.regs[0x6F]=0;f.regs[4]=0x30000010;
  pd1180_motor_service_t motor={.driver={.frame=transfer,.context=&f}};
  pd1180_control_t c={.armed=true,.power_permit=true,.current_limit_permille=1000};
  pd1180_inputs_t in={.motor_power_good=true,.vmotor_in_range=true};
  unsigned calls=f.calls;
  assert(!pd1180_motor_service(&motor,&c,&in,240) && f.calls==calls);
  in.configuration_verified=in.motion_wiring_verified=true;
  assert(pd1180_motor_service(&motor,&c,&in,240) && in.driver_ready);
  f.regs[1]=1;
  assert(!pd1180_motor_service(&motor,&c,&in,240) && !in.driver_ready);
  f.regs[1]=0;calls=f.calls;
  assert(!pd1180_motor_service(&motor,&c,&in,240) && f.calls==calls); // No automatic reconfigure after reset.
  c.armed=false;assert(!pd1180_motor_service(&motor,&c,&in,240));
  c.armed=true;assert(pd1180_motor_service(&motor,&c,&in,240));
  c.current_limit_permille=550;assert(pd1180_motor_service(&motor,&c,&in,240));
  assert(motor.driver.irun==3); // Thermal derating reaches actual current register.
  calls=f.calls;
  assert(!pd1180_motor_service(&motor,&c,&in,241) && !in.driver_ready);
  assert(f.calls==calls && !motor.driver.configured);
  in.vmotor_in_range=false;assert(!pd1180_motor_service(&motor,&c,&in,240) && !in.driver_ready);
  for(unsigned request=241;request<=1001;++request) {
    calls=f.calls;
    assert(!pd1180_tmc_configure_ramp(&d,(uint16_t)request));
    assert(!d.configured && f.calls==calls);
  }
  for(unsigned levels=6;levels<=12;levels+=6) for(unsigned clocks=2;clocks<=4;clocks+=2) {
    fake_t defaults={0}; defaults.regs[4]=0x30000010;
    defaults.regs[0x09]=(1u<<16)|(levels<<8)|levels;
    defaults.regs[0x0A]=clocks<<8;
    pd1180_tmc_t chip={.frame=transfer,.context=&defaults};
    assert(pd1180_tmc_configure_ramp(&chip,240));
    assert(defaults.regs[0x09]==0x10C06u && defaults.regs[0x0A]==0x400u);
    assert(!(defaults.regs[0x6C]&15u));
  }
  for(unsigned address=0x09;address<=0x0A;++address) {
    fake_t bad={0}; bad.regs[4]=0x30000010; bad.corrupt_write_address=(uint8_t)address;
    pd1180_tmc_t chip={.frame=transfer,.context=&bad};
    assert(!pd1180_tmc_configure_ramp(&chip,240) && !chip.configured);
    assert(!(bad.regs[0x6C]&15u) && !pd1180_tmc_set_chopper(&chip,true));
  }
  puts("PASS: TMC explicit protection defaults, write failures, pipelined SPI, write echo, disabled initialization, mode checks, current limits and latched faults");
}