From 771caa36452fa6013bea027b20b0dc8d9223316a Mon Sep 17 00:00:00 2001 From: Adrian Fedoreanu Date: Sun, 9 Aug 2026 18:22:53 +0200 Subject: [PATCH 1/2] gpio-motors: deliver sub-tick delays on HZ=100 kernels The delay argument has never actually worked below 10ms. These cameras run HZ=100 kernels without high-resolution timers, so every usleep() rounds up to a 10ms tick: usleep(1500) waits ~10ms, and 8 micro-steps x 10ms puts a hard ~80ms floor under every step regardless of the requested delay. Measured on a Hi3518EV200 (28BYJ-48 steppers): 200 steps took 33s at delay 15 and still 18s at delay 4 - the delay barely mattered, because the tick rounding dominated. With a CLOCK_MONOTONIC spin for delays below one tick the same 200 steps complete in ~4s at 1.5ms per micro-step, and the delay argument finally means what it says. Delays of 10ms and up still use usleep, so slow moves do not spin. Busy-waiting below that is a deliberate trade: moves are short and bounded, and a stepper mid-move needs the CPU for milliseconds, not ticks. --- general/package/gpio-motors/src/gpio-motors.c | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/general/package/gpio-motors/src/gpio-motors.c b/general/package/gpio-motors/src/gpio-motors.c index 9a4e3bc3f8..ab07e11676 100644 --- a/general/package/gpio-motors/src/gpio-motors.c +++ b/general/package/gpio-motors/src/gpio-motors.c @@ -3,6 +3,7 @@ #include #include #include +#include #include int PAN_PINS[4]; @@ -128,6 +129,34 @@ void gpio_config() { pclose(fp); } +/* + * usleep() cannot deliver sub-tick delays on the kernels these cameras run: + * they are HZ=100 with no high-resolution timers, so every sleep rounds up to + * a 10ms tick. usleep(1500) waits ~10ms, and 8 micro-steps x 10ms puts a hard + * ~80ms floor under every step no matter how small the requested delay is + * (measured on Hi3518EV200: 200 steps took 33s at delay 15 and still 18s at + * delay 4). Spin on CLOCK_MONOTONIC for anything below a tick instead; moves + * are short and bounded, so burning the CPU for their duration is a fair + * trade, and longer delays still go to usleep so we do not spin needlessly. + */ +void delay_us(long us) { + if (us >= 10000) { + usleep(us); + return; + } + + struct timespec start, now; + clock_gettime(CLOCK_MONOTONIC, &start); + long target = us * 1000; + for (;;) { + clock_gettime(CLOCK_MONOTONIC, &now); + long elapsed = (now.tv_sec - start.tv_sec) * 1000000000L + (now.tv_nsec - start.tv_nsec); + if (elapsed >= target) { + return; + } + } +} + void axis_run(const int pins[4], int level, int steps, int delay) { int remaining = abs(steps); if (remaining == 0) { @@ -146,7 +175,7 @@ void axis_run(const int pins[4], int level, int steps, int delay) { gpio_set(pins[i], seq[micro][i]); } - usleep(delay); + delay_us(delay); if (++micro >= 8) { micro = 0; --remaining; From f9654c9fd3ad6efaa7a4505e32e1f10f6c17e5dc Mon Sep 17 00:00:00 2001 From: Adrian Fedoreanu Date: Sun, 9 Aug 2026 18:37:49 +0200 Subject: [PATCH 2/2] gpio-motors: harden delay_us against overflow and bad input Review follow-up: - compute the elapsed time in long long: on 32-bit targets a long overflows after ~2.1s, which a preemption in the middle of the spin can reach, and signed overflow is undefined behavior - reject a negative delay at the CLI and treat non-positive delays as zero in delay_us, instead of spinning unthrottled - fall back to usleep if clock_gettime fails, so the wait stays bounded --- general/package/gpio-motors/src/gpio-motors.c | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/general/package/gpio-motors/src/gpio-motors.c b/general/package/gpio-motors/src/gpio-motors.c index ab07e11676..feccbe1636 100644 --- a/general/package/gpio-motors/src/gpio-motors.c +++ b/general/package/gpio-motors/src/gpio-motors.c @@ -140,17 +140,27 @@ void gpio_config() { * trade, and longer delays still go to usleep so we do not spin needlessly. */ void delay_us(long us) { + if (us <= 0) { + return; + } + if (us >= 10000) { usleep(us); return; } struct timespec start, now; - clock_gettime(CLOCK_MONOTONIC, &start); - long target = us * 1000; + if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) { + usleep(us); + return; + } + + /* 64-bit on purpose: a 32-bit long overflows after ~2.1s of elapsed + * time, which a preemption in the middle of the spin can reach */ + long long target = (long long)us * 1000; for (;;) { clock_gettime(CLOCK_MONOTONIC, &now); - long elapsed = (now.tv_sec - start.tv_sec) * 1000000000L + (now.tv_nsec - start.tv_nsec); + long long elapsed = (long long)(now.tv_sec - start.tv_sec) * 1000000000LL + (now.tv_nsec - start.tv_nsec); if (elapsed >= target) { return; } @@ -195,7 +205,12 @@ int main(int argc, char *argv[]) { int pan_steps = atoi(argv[1]); int tilt_steps = atoi(argv[2]); - int delay = atoi(argv[3]) * 1000; + int delay_ms = atoi(argv[3]); + if (delay_ms < 0) { + fprintf(stderr, "delay must be >= 0\n"); + return 1; + } + int delay = delay_ms * 1000; gpio_config(); for (int i = 0; i < 4; i++) {