Configuration¶
Stepper-C has two kinds of configuration. Build-time options select the code that is
compiled in (numeric backend, topologies, table size); they are macros in
inc/sstep_config.h. Run-time configuration describes the motor, the power stage and
the control; it is a struct sstep_config passed to sstep_init(). This chapter lists
both, then the memory footprint and the measured stack usage.
Build-time options¶
Each macro has a default, is range-checked with #error and can be overridden on the
compiler command line or in a header sstep_user_config.h (included when
SSTEP_USER_CONFIG is defined; with CMake, set SSTEP_CONFIG_DIR to its folder).
The application must be compiled with the same values as the library: they change the
size of struct sstep.
| Macro | Default | Range | Meaning |
|---|---|---|---|
SSTEP_CFG_MATH_BACKEND |
SSTEP_MATH_BACKEND_FIXED |
…_FIXED, …_FLOAT |
Numeric backend (CMake: SSTEP_MATH_BACKEND=FIXED or FLOAT) |
SSTEP_CFG_TOPOLOGY_FULL_BRIDGE |
1 |
0, 1 | Compile the two-H-bridge topology |
SSTEP_CFG_TOPOLOGY_SHARED_LEG |
1 |
0, 1 | Compile the three-leg topology; at least one must be 1 |
SSTEP_CFG_MICROSTEPS_MAX |
256U |
power of two, 1 .. 256 | Largest microsteps accepted by sstep_init() |
SSTEP_CFG_SINE_TABLE_BITS |
8U |
6 .. 10 | Quarter-wave table of \(2^\mathrm{bits} + 1\) entries (see the accuracy table) |
SSTEP_CFG_ARG_CHECKS |
1 |
0, 1 | 0 removes the NULL checks from sstep_fast_loop() only |
Run-time configuration¶
struct sstep_config has four parts. All values are integers in engineering units;
sstep_init() copies the structure, so it may be a temporary or a const in flash.
Fields outside their range make sstep_init() return SSTEP_ERR_PARAM; combinations
that do not fit return SSTEP_ERR_CONFIG.
Motor¶
| Field | Unit | Range | 17HS19-2004S1 | Where to get it |
|---|---|---|---|---|
r_mohm |
mΩ | 1 .. \(10^7\) | 1600 | Datasheet (phase resistance at 25 °C) or an ohmmeter |
l_uh |
µH | 1 .. \(10^7\) | 3000 | Datasheet (phase inductance) or an LCR meter at 1 kHz |
i_rated_ma |
mA | 1 .. \(10^7\) | 2000 | Datasheet; i_run_ma may not exceed it |
ke_uv_s_per_rad |
µV·s/rad | 0 .. \(10^7\) | 208600 | \(T_\mathrm{hold} / (\sqrt{2}\, I_\mathrm{rated})\) in µN·m/A (\(0.59 / (\sqrt{2} \cdot 2) = 0.2086\) N·m/A); 0 disables the feed-forward |
full_steps_per_rev |
– | ≥ 1 | 200 | 200 for 1.8°, 400 for 0.9° |
Power stage and measurement¶
| Field | Unit | Range | B-G431B-ESC1 example | Meaning |
|---|---|---|---|---|
topology |
– | compiled-in value | SSTEP_TOPOLOGY_SHARED_LEG |
See Voltage limits |
vbus_nominal_mv |
mV | 1 .. \(10^7\) | 24000 | Nominal supply; base of the per-unit voltage |
vbus_min_mv |
mV | 0 or below nominal | 10000 | Undervoltage trip; 0 disables it |
vbus_max_mv |
mV | 0 or above nominal | 28000 | Overvoltage trip; 0 disables it |
i_full_scale_ma |
mA | 1 .. \(10^7\) | 4000 | Phase current at the ADC reading adc_i_zero + adc_i_span; base of the per-unit current |
i_trip_ma |
mA | 1 .. i_full_scale_ma |
3500 | Overcurrent trip (either polarity) |
pwm_period_ticks |
timer ticks | ≥ 2 | 4250 | Compare value for 100 % duty cycle (timer auto-reload) |
pwm_freq_hz |
Hz | – | 20000 | Reserved; not used by this version |
dead_time_ns |
ns | – | 0 | Reserved for dead-time compensation; not used by this version |
adc_i_zero |
counts | ≤ adc_full |
2048 | Current ADC reading at 0 A (measure with the outputs off) |
adc_i_span |
counts | ±1 .. ±65535 | 2047 | Counts from 0 A to +i_full_scale_ma; negative for an inverting amplifier |
adc_vbus_mv_full |
mV | 1 .. \(10^7\) | 34320 | Bus voltage that gives the reading adc_full (divider ratio × ADC reference) |
adc_full |
counts | ≥ 1 | 4095 | Largest ADC reading |
The B-G431B-ESC1 values above are placeholders derived from the schematic; measure
adc_i_zero, adc_i_span and adc_vbus_mv_full on your board.
Control and motion¶
| Field | Unit | Range | Example | Meaning |
|---|---|---|---|---|
drive_mode |
– | SSTEP_DRIVE_CURRENT, SSTEP_DRIVE_VOLTAGE |
current | Closed-loop current or open-loop voltage drive |
fast_loop_hz |
Hz | 1 .. \(10^7\) | 20000 | Rate of sstep_fast_loop() |
slow_loop_hz |
Hz | divides fast_loop_hz, ratio ≤ 65535 |
1000 | Rate of sstep_slow_loop() |
microsteps |
– | power of two, 1 .. SSTEP_CFG_MICROSTEPS_MAX |
16 | Microsteps per full step |
current_bw_hz |
Hz | 1 .. fast_loop_hz / 4 |
1000 | Current loop bandwidth; the PI gains follow from it |
i_run_ma |
mA | 1 .. min(i_trip_ma, i_rated_ma) |
1500 | Peak phase current while moving |
i_hold_ma |
mA | 0 .. i_run_ma |
500 | Peak phase current at standstill |
hold_delay_ms |
ms | any | 500 | Standstill time before the hold current |
v_max |
microsteps/s | see below | 16000 | Velocity limit |
accel |
microsteps/s² | see below | 64000 | Acceleration |
decel |
microsteps/s² | as accel, or 0 |
0 | Deceleration; 0 = same as accel |
stall_time_ms |
ms | 0, or 1 .. \(10^7\) and at least one slow period | 50 | Stall detection: time the condition must persist before SSTEP_FAULT_STALL; 0 = off (default) and the other stall_* fields are ignored |
stall_filter_us |
µs | ≥ one fast period, ≤ \(10^7\) | 1000 | Time constant of the back-EMF low-pass filter |
stall_speed |
microsteps/s | 1 .. \(10^7\); its back-EMF 1 mV .. vbus_nominal_mv |
1600 | Slip threshold at rest and below this commanded speed; synchronism check above it |
stall_sync_pct |
% | 1 .. 100 | 50 | Smallest back-EMF along the commanded motion, relative to \(K_e\,\omega\) |
Stall detection requires SSTEP_DRIVE_CURRENT and ke_uv_s_per_rad > 0
(SSTEP_ERR_CONFIG otherwise); the Stall detection
section explains the algorithm and how to choose the four values.
The motion limits are converted to 1/65 536 microstep per slow period. Each must round
to at least 1 and stay below \(2^{30}\): with slow_loop_hz = 1000, v_max from 0.008 to
below 16 384 000 microsteps/s, accel and decel from 8 to below
\(1.638 \cdot 10^{10}\) microsteps/s².
Callbacks¶
| Field | Required | Called from | Contract |
|---|---|---|---|
context |
– | – | Passed unchanged to every callback |
pwm_write |
Yes | sstep_fast_loop() |
Load one compare value per leg (compare[0..3], compare[3] is 0 for the shared leg) for the next PWM period; return 0, or a negative value on error |
output_enable |
Yes | Any function that enables, disables or faults | true: enable the gate drivers; false: all legs high impedance. Return 0, or a negative value on error |
on_fault |
No | The context that detects the fault (may be the interrupt) | Informs the application of new fault flags; outputs are already off |
on_event |
No | sstep_slow_loop() |
SSTEP_EVENT_MOVE_DONE, SSTEP_EVENT_HOLD_CURRENT |
Memory footprint¶
Measured with arm-none-eabi-gcc 15.2.1 for Cortex-M4 (-mthumb, float backend with
-mfpu=fpv4-sp-d16 -mfloat-abi=hard), default build-time options, both topologies.
| Object | Fixed point | Float |
|---|---|---|
struct sstep (RAM per motor) |
432 B | 456 B |
Code and constants, -Os (flash) |
9216 B | 9894 B |
| of which the sine table | 514 B | 1028 B |
.data + .bss |
0 B | 0 B |
struct sstep is the configuration copy (SSTEP_CONFIG_BYTES: 152 B on Cortex-M4,
176 B on 64-bit hosts) plus the private state (SSTEP_STATE_BYTES: 280 B fixed point,
304 B float on Cortex-M4; 288 B and 312 B on 64-bit hosts); on a 64-bit host the instance
is 464 B and 488 B. Stall detection accounts for 96 B of it on Cortex-M4, and for 2236 B
(fixed point) or 2448 B (float) of the flash whether it is enabled or not.
Stack usage¶
Worst-case stack of each public function including the library functions it calls,
measured with -fstack-usage -fcallgraph-info=su and tools/docs/stack_usage.py
(Cortex-M4, toolchain as above). Calls through the callbacks are not included: add the
stack of your pwm_write(), output_enable(), on_fault() and on_event() to the
functions that call them.
| Function | Fixed -Os |
Fixed -O0 |
Float -Os |
Float -O0 |
|---|---|---|---|---|
sstep_init() |
224 | 1544 | 200 | 1432 |
sstep_enable() |
48 | 80 | 64 | 88 |
sstep_disable() |
24 | 40 | 24 | 40 |
sstep_fast_loop() |
216 | 304 | 128 | 312 |
sstep_slow_loop() |
152 | 696 | 140 | 624 |
sstep_move_to() |
0 | 48 | 0 | 48 |
sstep_move_by() |
0 | 112 | 0 | 112 |
sstep_run() |
16 | 104 | 16 | 104 |
sstep_stop() |
0 | 40 | 0 | 40 |
sstep_set_position() |
16 | 72 | 16 | 72 |
sstep_set_current() |
52 | 216 | 36 | 128 |
sstep_set_motion_limits() |
72 | 224 | 72 | 224 |
sstep_report_fault() |
24 | 40 | 24 | 40 |
sstep_clear_faults() |
0 | 40 | 0 | 40 |
sstep_get_status() |
92 | 536 | 92 | 496 |
The interrupt stack must hold the frame of sstep_fast_loop() plus pwm_write() (and
output_enable() + on_fault() for the fault path) plus the interrupt entry. There is
no recursion; every frame is static.
To re-measure:
mkdir stack && cd stack
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -std=c11 -fstack-usage -fcallgraph-info=su \
-I.. -I../inc -c ../src/*.c
python ../tools/docs/stack_usage.py . --markdown
CMake options¶
| Option | Default | Meaning |
|---|---|---|
SSTEP_MATH_BACKEND |
FIXED |
Numeric backend: FIXED or FLOAT |
SSTEP_CONFIG_DIR |
empty | Folder with sstep_user_config.h |
UNIT_TESTS |
OFF |
GoogleTest suite and coverage target |
SSTEP_COVERAGE_MIN |
100 |
Coverage gate (lines and branches, %) |
SSTEP_MISRA_FAIL_ON_VIOLATION |
ON |
misra-analyze fails on any violation |
SSTEP_MISRA_RULE_TEXTS |
empty | Licensed MISRA rule texts for readable reports |