Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions general/package/gpiostep-openipc/Config.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ config BR2_PACKAGE_GPIOSTEP_OPENIPC
userspace CLI (gpiostep-ctl). A clean reimplementation of the
vendor gpioStep/motor behaviour seen on Goke GK7205V510 PTZ
cameras: two 4-wire stepper coils driven over GPIO from kernel
context (steadier timing, lower CPU than the userspace
gpio-motors tool).
context, avoiding the per-write syscall cost of the userspace
gpio-motors tool. Sub-tick step delays busy-wait between
scheduler yields, because the kernels that ship this package
have no high-resolution timers and would otherwise round every
sleep up to a whole 10ms tick; the sub-tick pacing holds on an
idle core, not under load.

Pin map defaults to the GK7205V510 (NC-IPTC2200_DL) layout and is
overridable via module params, e.g.
Expand Down
12 changes: 9 additions & 3 deletions general/package/gpiostep-openipc/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ cameras whose pan/tilt motors are 4-wire steppers wired straight to GPIO
This is the kernel-side counterpart to the userspace `gpio-motors` tool. Both
implement the same 8-phase half-step sequence and the same
`<pan> <tilt> <delay_ms>` command signature, so they can be compared 1:1. The
difference: `gpio-motors` toggles `/sys/class/gpio` (open/write/close per pin
per microstep) from userspace, whereas `gpiostep` does `gpio_set_value()`
directly in kernel context — steadier timing and far lower CPU.
difference: `gpio-motors` writes `/sys/class/gpio` from userspace, whereas
`gpiostep` does `gpio_set_value()` directly in kernel context, which skips the
per-write syscall cost. Timing granularity is the same for both: the kernels
Comment thread
phedoreanu marked this conversation as resolved.
that ship this package have no high-resolution timers, so any sleep rounds up
to a whole 10ms tick. Delays under a quarter tick therefore busy-wait between
scheduler yields in `gpiostep` (see `step_delay()` in `src/gpiostep.c`); longer
ones sleep and accept the rounding. The sub-tick pacing holds on an idle core:
under load the yield after each micro-step can hand the core away for several
ticks.

### Load

Expand Down
10 changes: 8 additions & 2 deletions general/package/gpiostep-openipc/src/gpiostep-ctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* gpiostep-ctl 0 -20 30 # tilt -20 steps
*/
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand All @@ -24,7 +25,7 @@
int main(int argc, char *argv[])
{
struct gpiostep_move m;
int fd, ret;
int fd, ret, delay_ms;

if (argc != 4) {
fprintf(stderr, "Usage: %s <pan steps> <tilt steps> <delay (ms)>\n",
Expand All @@ -35,7 +36,12 @@ int main(int argc, char *argv[])
memset(&m, 0, sizeof(m));
m.pan = atoi(argv[1]);
m.tilt = atoi(argv[2]);
m.delay_us = atoi(argv[3]) * 1000;
delay_ms = atoi(argv[3]);
if (delay_ms < 0 || delay_ms > INT_MAX / 1000) {
fprintf(stderr, "delay must be between 0 and %d ms\n", INT_MAX / 1000);
return 1;
}
m.delay_us = delay_ms * 1000;

fd = open(DEV, O_RDWR);
if (fd < 0) {
Expand Down
52 changes: 47 additions & 5 deletions general/package/gpiostep-openipc/src/gpiostep.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
*
* A clean reimplementation of the vendor "gpioStep"/"motor" behaviour observed
* on Goke GK7205V510 cameras (model NC-IPTC2200_DL): two 4-wire stepper coils
* driven over GPIO. Unlike the userspace gpio-motors tool (which does
* open/write/close on /sys/class/gpio per pin per microstep), the stepping here
* runs entirely in kernel context with direct gpio_set_value(), so timing is
* far steadier and CPU cost much lower.
* driven over GPIO. The stepping runs entirely in kernel context with direct
* gpio_set_value(), which avoids the syscall traffic of the userspace
* gpio-motors tool. Timing granularity, however, is still bounded by the
* tick on kernels without CONFIG_HIGH_RES_TIMERS - which is every kernel
* that ships this package - so sub-tick delays busy-wait between scheduler
* yields (see step_delay()).
*
* Control is via a misc char device /dev/motorDev and a single ioctl. The pin
* map defaults to the GK7205V510 layout and is overridable with module params:
Expand All @@ -20,9 +22,11 @@
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/jiffies.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/uaccess.h>

#include "gpiostep.h"
Expand All @@ -48,6 +52,44 @@ static const int rev_step_seq[8][4] = {

static DEFINE_MUTEX(gpiostep_lock);

/*
* usleep_range() runs on hrtimers, but without CONFIG_HIGH_RES_TIMERS those
* expire with jiffy granularity, so a sub-tick sleep rounds up to the next
* tick (10ms at HZ=100) exactly like a userspace usleep - and every defconfig
* that ships this package builds such a kernel. Busy-wait instead while the
* requested delay is under a quarter tick, where that rounding would at least
* quadruple the step period; from a quarter tick up, sleep and accept the
* rounding, since the busy-wait cost grows with the delay while its benefit
* shrinks. The cond_resched() keeps a move from monopolising the core: these
* kernels are !SMP and !PREEMPT, so without it the encoder would not run at
* all until the whole move finished. It also means the sub-tick pacing only
* holds on an idle core - under load the yield can hand the core away for
* several ticks between two micro-steps.
*
* A zero delay keeps its usleep_range(0, 1). That is an already-expired
* hrtimer and returns at once - measured on a Hi3518EV200, 320 micro-steps at
* delay 0 finish in under 10ms with either version of this module - so zero
* has never had a floor; this just leaves that unchanged rather than sending
* it down the busy-wait path, where the guard would be the only thing between
* a negative and udelay().
*/
static void step_delay(int delay_us)
{
if (delay_us > 0 && !IS_ENABLED(CONFIG_HIGH_RES_TIMERS) &&
delay_us < (int)(jiffies_to_usecs(1) / 4)) {
/* udelay() on ARM is bounded at ~2ms per call; chunk it */
while (delay_us > 1000) {
udelay(1000);
delay_us -= 1000;
}
udelay(delay_us);
cond_resched();
Comment thread
phedoreanu marked this conversation as resolved.
return;
}

usleep_range(delay_us, delay_us + (delay_us >> 4) + 1);
}

static void axis_run(const int pins[4], int steps, int delay_us)
{
const int (*seq)[4] = (steps < 0) ? rev_step_seq : step_seq;
Expand All @@ -62,7 +104,7 @@ static void axis_run(const int pins[4], int steps, int delay_us)
for (i = 0; i < 4; i++)
gpio_set_value(pins[i], seq[micro][i]);

usleep_range(delay_us, delay_us + (delay_us >> 4) + 1);
step_delay(delay_us);

if (++micro >= 8) {
micro = 0;
Expand Down
Loading