Skip to content

Commit

Permalink
add a component to detect clicks, double-clicks, etc
Browse files Browse the repository at this point in the history
This one goes up to quadruple clicks.  I find it useful for overloading
the functionality of the hard buttons on the control panel of my mill.

For example, in my config, multi-clicks on the Z+ jog button are detected
and routed to halui, where they trigger MDI commands:

    Double-clicking runs "quill up".

    Triple-clicking runs my "present work" subroutine: quill up and
        bring the table to the front and center for operator access.

    Quadruple-clicking runs my "stow" subroutine: quill up and bring
        the table to the back and center for out-of-the way storage at
        the end of the night.

A debounce component may be advisable between the input signal and the
multiclick component.
  • Loading branch information
SebKuzminsky committed Dec 23, 2012
1 parent 06500f5 commit a307a69
Show file tree
Hide file tree
Showing 6 changed files with 672 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/man/man9/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ mesa_7i65.9
message.9
minmax.9
mult2.9
multiclick.9
multiswitch.9
mux2.9
mux4.9
Expand Down
387 changes: 387 additions & 0 deletions src/hal/components/multiclick.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@

//
// This is a single-, double-, triple-, and quadruple-click detector
// component for LinuxCNC.
//
// Copyright 2012 Sebastian Kuzminsky <[email protected]>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

component multiclick "Single-, double-, triple-, and quadruple-click detector";
license "GPL";

pin in bit in "The input line, this is where we look for clicks.";

pin out bit single_click
"Goes high briefly when a single-click is detected on the 'in' pin.";

pin out bit single_click_only
"""Goes high briefly when a single-click is detected on the 'in' pin
and no second click followed it.""";

pin out bit double_click
"Goes high briefly when a double-click is detected on the 'in' pin.";

pin out bit double_click_only
"""Goes high briefly when a double-click is detected on the 'in' pin
and no third click followed it.""";

pin out bit triple_click
"Goes high briefly when a triple-click is detected on the 'in' pin.";

pin out bit triple_click_only
"""Goes high briefly when a triple-click is detected on the 'in' pin
and no fourth click followed it.""";

pin out bit quadruple_click
"Goes high briefly when a quadruple-click is detected on the 'in' pin.";

pin out bit quadruple_click_only
"""Goes high briefly when a quadruple-click is detected on the 'in'
pin and no fifth click followed it.""";


// for debugging
pin out s32 state;


param rw bit invert_input = FALSE
"""If FALSE (the default), clicks start with rising edges. If TRUE,
clicks start with falling edges.""";

param rw u32 max_hold_ns = 250000000
"""If the input is held down longer than this, it's not part of a
multi-click. (Default 250,000,000 ns, 250 ms.)""";

param rw u32 max_space_ns = 250000000
"""If the input is released longer than this, it's not part of a
multi-click. (Default 250,000,000 ns, 250 ms.)""";

param rw u32 output_hold_ns = 100000000
"""Positive pulses on the output pins last this long. (Default
100,000,000 ns, 100 ms.)""";


variable int click_state = 0; // wish i could have an enum type here


//
// When entering a new non-IDLE state in the state machine, timer is set to
// 0 and timeout is set to the timeout for the new state.
//
// Each time the component's function runs and the state machine is not
// IDLE, the timer is incremented by the thread period. When the timer
// exceeds the timeout, the state machine resets.
//

variable unsigned timer;
variable unsigned timeout;


//
// When an "X"-click (for X={single,double,triple,quadruple}) is detected,
// the X-click pin goes high and the X_click_hold_timer is set to the
// output_hold_ns param. Each invocation of the function, the
// X_click_hold_timer is decremented by the thread period, and when the
// timer falls below 0, the X-click output pin goes back to low again.
//
// Similarly for the X_click_only_hold_timer, but it doesnt start until
// max_space_ns after the click is detected (to ensure that this is not an
// X+1 click).
//

variable unsigned single_click_hold_timer;
variable unsigned single_click_only_hold_timer;

variable unsigned double_click_hold_timer;
variable unsigned double_click_only_hold_timer;

variable unsigned triple_click_hold_timer;
variable unsigned triple_click_only_hold_timer;

variable unsigned quadruple_click_hold_timer;
variable unsigned quadruple_click_only_hold_timer;


description """A click is defined as a rising edge on the 'in' pin,
followed by the 'in' pin being True for at most 'max-hold-ns' nanoseconds,
followed by a falling edge.

A double-click is defined as two clicks, separated by at
most 'max-space-ns' nanoseconds with the 'in' pin in the False state.

I bet you can guess the definition of triple- and quadruple-click.

You probably want to run the input signal through a debounce component
before feeding it to the multiclick detector, if the input is at all
noisy.

The '*-click' pins go high as soon as the input detects the correct
number of clicks.

The '*-click-only' pins go high a short while after the click, after
the click separator space timeout has expired to show that no further
click is coming. This is useful for triggering halui MDI commands.""";


function _ nofp "Detect single-, double-, triple-, and quadruple-clicks";

;;

typedef enum {
IDLE = 0,
SAW_FIRST_RISING_EDGE,
SAW_FIRST_CLICK,
SAW_SECOND_RISING_EDGE,
SAW_SECOND_CLICK,
SAW_THIRD_RISING_EDGE,
SAW_THIRD_CLICK,
SAW_FOURTH_RISING_EDGE,
SAW_FOURTH_CLICK,
HELD_TOO_LONG
} state_t;

FUNCTION(_) {
int new_in = in;

if (1 == invert_input) {
new_in = !new_in;
}

if (click_state != IDLE) {
timer += period;
}


//
// update the output pins
//

if (single_click_hold_timer > 0) {
if (single_click_hold_timer > period) {
single_click_hold_timer -= period;
} else {
single_click_hold_timer = 0;
single_click = 0;
}
}

if (single_click_only_hold_timer > 0) {
if (single_click_only_hold_timer > period) {
single_click_only_hold_timer -= period;
} else {
single_click_only_hold_timer = 0;
single_click_only = 0;
}
}

if (double_click_hold_timer > 0) {
if (double_click_hold_timer > period) {
double_click_hold_timer -= period;
} else {
double_click_hold_timer = 0;
double_click = 0;
}
}

if (double_click_only_hold_timer > 0) {
if (double_click_only_hold_timer > period) {
double_click_only_hold_timer -= period;
} else {
double_click_only_hold_timer = 0;
double_click_only = 0;
}
}

if (triple_click_hold_timer > 0) {
if (triple_click_hold_timer > period) {
triple_click_hold_timer -= period;
} else {
triple_click_hold_timer = 0;
triple_click = 0;
}
}

if (triple_click_only_hold_timer > 0) {
if (triple_click_only_hold_timer > period) {
triple_click_only_hold_timer -= period;
} else {
triple_click_only_hold_timer = 0;
triple_click_only = 0;
}
}

if (quadruple_click_hold_timer > 0) {
if (quadruple_click_hold_timer > period) {
quadruple_click_hold_timer -= period;
} else {
quadruple_click_hold_timer = 0;
quadruple_click = 0;
}
}

if (quadruple_click_only_hold_timer > 0) {
if (quadruple_click_only_hold_timer > period) {
quadruple_click_only_hold_timer -= period;
} else {
quadruple_click_only_hold_timer = 0;
quadruple_click_only = 0;
}
}


//
// update state, if needed
//

switch (click_state) {
case IDLE: {
if (1 == new_in) {
click_state = SAW_FIRST_RISING_EDGE;
timer = 0;
timeout = max_hold_ns;
}
break;
}

case SAW_FIRST_RISING_EDGE: {
if (0 == new_in) {
click_state = SAW_FIRST_CLICK;
timer = 0;
timeout = max_space_ns;
single_click = 1;
single_click_hold_timer = output_hold_ns;
}
break;
}

case SAW_FIRST_CLICK: {
if (1 == new_in) {
click_state = SAW_SECOND_RISING_EDGE;
timer = 0;
timeout = max_hold_ns;
}
break;
}

case SAW_SECOND_RISING_EDGE: {
if (0 == new_in) {
click_state = SAW_SECOND_CLICK;
timer = 0;
timeout = max_space_ns;
double_click = 1;
double_click_hold_timer = output_hold_ns;
}
break;
}

case SAW_SECOND_CLICK: {
if (1 == new_in) {
click_state = SAW_THIRD_RISING_EDGE;
timer = 0;
timeout = max_hold_ns;
}
break;
}

case SAW_THIRD_RISING_EDGE: {
if (0 == new_in) {
click_state = SAW_THIRD_CLICK;
timer = 0;
timeout = max_space_ns;
triple_click = 1;
triple_click_hold_timer = output_hold_ns;
}
break;
}

case SAW_THIRD_CLICK: {
if (1 == new_in) {
click_state = SAW_FOURTH_RISING_EDGE;
timer = 0;
timeout = max_hold_ns;
}
break;
}

case SAW_FOURTH_RISING_EDGE: {
if (0 == new_in) {
// four clicks is the most we look for, and we just saw it,
// so we're done now
click_state = SAW_FOURTH_CLICK;
timer = 0;
timeout = max_space_ns;
quadruple_click = 1;
quadruple_click_hold_timer = output_hold_ns;
}
break;
}

case SAW_FOURTH_CLICK: {
if (1 == new_in) {
click_state = HELD_TOO_LONG;
}
break;
}

case HELD_TOO_LONG: {
if (0 == new_in) {
click_state = IDLE;
}
break;
}

default: {
// invalid click_state!
click_state = IDLE;
}
}

if ((click_state != IDLE) && (click_state != HELD_TOO_LONG) && (timer > timeout)) {
if (1 == new_in) {
click_state = HELD_TOO_LONG;
} else {
// timeout after some activity on the input line, trigger one
// of the "only" outputs if appropriate
switch (click_state) {
case SAW_FIRST_CLICK: {
single_click_only = 1;
single_click_only_hold_timer = output_hold_ns;
break;
}
case SAW_SECOND_CLICK: {
double_click_only = 1;
double_click_only_hold_timer = output_hold_ns;
break;
}
case SAW_THIRD_CLICK: {
triple_click_only = 1;
triple_click_only_hold_timer = output_hold_ns;
break;
}
case SAW_FOURTH_CLICK: {
quadruple_click_only = 1;
quadruple_click_only_hold_timer = output_hold_ns;
break;
}
}
click_state = IDLE;
}
}

state = click_state;
}

1 change: 1 addition & 0 deletions tests/multiclick/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
regression test for multi-click component
Loading

0 comments on commit a307a69

Please sign in to comment.