Skip to main content

Limit Switch (limiter)

Limit switch / endstop subsystem. Returns a binary status indicating whether the mechanical limit has been reached. Commonly used in CNC, 3D printers, robotics, automatic doors, level sensing, etc.

Driver Framework

Device Type

DEVICE_TYPE_LIMITER

Core Data Structure

struct limiter_t {
char * name;
int (*get)(struct limiter_t * limiter);
void * priv;
};
  • name: Globally unique device name, lookable via search_limiter().
  • get: Status read callback implemented by the concrete driver. Returns 1 when the limit is triggered, 0 otherwise.
  • priv: Private data pointer for the concrete driver.

Core API

FunctionDescription
search_limiter(name)Find limiter by name
register_limiter(limiter, drv)Register a limiter; the sysfs node is created automatically
unregister_limiter(limiter)Unregister a limiter
limiter_get_status(limiter)Get current limit status (active query)

sysfs Node

Upon registration the core layer automatically creates a read-only status node, which can be inspected in real time via the kobj virtual file system in the debug shell.

Event Notification Mechanism

The limiter subsystem uses the psub (publish/subscribe) pattern to asynchronously notify upper-layer applications about state changes, eliminating the need for active polling from the application side.

Event Topics

TopicTriggered WhenData
limiter.triggeredLimit is triggered (status changes from 0 to 1)struct limiter_t *
limiter.releasedLimit is released (status changes from 1 to 0)struct limiter_t *

The event payload is a struct limiter_t * pointer, so subscribers can distinguish between multiple limiters by limiter->name.

Subscriber Example (Application)

Upper-layer applications register callbacks via psub_subscribe() to receive events:

#include <kernel/core/psub.h>
#include <driver/limiter/limiter.h>

static void on_limiter_triggered(const char * topic, void * data, void * priv)
{
struct limiter_t * limiter = (struct limiter_t *)data;
/* Emergency stop, axis homing completion, full-bin detection, etc. */
}

static void on_limiter_released(const char * topic, void * data, void * priv)
{
struct limiter_t * limiter = (struct limiter_t *)data;
/* Resume operation */
}

psub_subscribe("limiter.triggered", on_limiter_triggered, NULL);
psub_subscribe("limiter.released", on_limiter_released, NULL);

Design Highlights

  • Decoupling: The driver only publishes events without knowing the number of subscribers, allowing motion control, HMI, logging, and other modules to react simultaneously.
  • Edge-triggered: Events are published only on state transitions, avoiding redundant notifications. Applications can call limiter_get_status() at any time to query the instantaneous state.
  • Portability: Different concrete drivers (polling, interrupt-driven, I2C IO expanders, etc.) only need to follow the same event topic convention, and no changes are required on the application side.

Concrete Driver: limiter-gpio-polled

File: xstar/driver/limiter/limiter-gpio-polled.c Config: CONFIG_DRV_LIMITER_GPIO_POLLED

A GPIO software-timer polling based limiter driver. Suitable for platforms without external interrupt resources or when unified software-layer debouncing is desired.

Device Tree Configuration

Add the following entry under /romdisk/dtree/default.json using limiter-gpio-polled as the node name:

"limiter-gpio-polled@0": {
"gpio": 123,
"gpio-config": -1,
"active-low": true,
"poll-interval-ms": 10
}
FieldTypeDefaultDescription
gpioint-1 (required)Input GPIO number; must pass gpio_is_valid()
gpio-configint-1 (no change)Platform-specific pinmux configuration
active-lowboolfalseLow-level triggered. When true, pull-up is enabled; when false, pull-down is enabled
poll-interval-msint10Polling interval in milliseconds. Determines response latency and debounce window

During probe the driver performs:

  1. Validate the GPIO;
  2. Configure the GPIO as input with pull-up/pull-down according to active-low;
  3. Sample the initial level as the initial state and fill the debounce buffer;
  4. Start the periodic timer to begin polling.

Event-driven Flow

The driver publishes events by periodic sampling + software debouncing + edge detection:

static int limiter_gpio_polled_timer_function(struct timer_t * timer, void * data)
{
struct limiter_t * limiter = (struct limiter_t *)(data);
struct limiter_gpio_polled_pdata_t * pdat = limiter->priv;
int status;

if(gpio_get_value(pdat->gpio))
status = pdat->active_low ? 0 : 1;
else
status = pdat->active_low ? 1 : 0;

pdat->buffer[pdat->bufidx] = status;
pdat->bufidx = (pdat->bufidx + 1) & 0x3;

if(pdat->status != status)
{
if((pdat->buffer[0] == status) && (pdat->buffer[1] == status) &&
(pdat->buffer[2] == status) && (pdat->buffer[3] == status))
{
pdat->status = status;
if(status)
psub_publish("limiter.triggered", limiter);
else
psub_publish("limiter.released", limiter);
}
}
timer_forward(timer, ms_to_ktime(pdat->interval));
return 1;
}

Workflow:

  1. Polling: The timer samples the GPIO level every poll-interval-ms and computes the logical state based on active-low.
  2. Software debounce: A 4-sample ring buffer (buffer[4]). A state change is only accepted when four consecutive samples agree and differ from the previously stable state, effectively filtering mechanical bounce and electrical glitches.
  3. Edge event publishing: Once the state change is confirmed, psub_publish() is called with limiter.triggered or limiter.released.

The response latency is approximately poll-interval-ms × 4. Trade off between responsiveness (smaller interval) and noise immunity (larger interval) based on the application.

Active Query (Polling Query)

In addition to events, the upper layer can query the current stable status at any time:

struct limiter_t * lim = search_limiter("limiter-gpio-polled.0");
if(lim && limiter_get_status(lim))
{
/* Currently triggered */
}

limiter_get_status() ultimately invokes the driver's get() callback, which returns the debounced stable status pdat->status. It is not affected by transient bouncing.

Power Management

  • suspend: Cancels the polling timer and stops sampling.
  • resume: Restarts the timer and resumes sampling from the current GPIO level. (Note: level changes that occur during suspend are not replayed; the new stable state after wakeup is taken as the current state.)