Skip to content

Edges and pulses

Turning levels into events: rising and falling edges, one-shot pulses, and toggling once per press.

A chart samples its inputs once per tick. A button that is held for half a second is read as true in every tick of that half second: at a thousand ticks a second, that is five hundred trues for one press. Most of the time you do not care about the level (“is it pressed?”) but about the moment it changes (“has it just been pressed?”). That moment is an edge: a rising edge when the input goes from false to true, a falling edge when it goes back.

The previous tick introduced edges with pre. This page collects the recipes: edges with pre, the same with states (which treats a button held at start-up differently), and what you build on top of edges: a one-shot pulse of fixed length here, a long press and a toggle on release in the exercises.

pre(button) is the value button had at the end of the previous tick. Comparing it with the value now gives all three edges:

immediate during do rise = button && !pre(button) // was up, is down
immediate during do fall = !button && pre(button) // was down, is up
immediate during do change = button != pre(button) // either

The equations sit in immediate during actions written directly in the chart, so they are evaluated in every tick, the first one included. (A plain during action skips the tick its state is entered; for the chart itself, that is tick 1.)

EdgesSimulation

Diagram

Diagram of EdgesStates: none. Transitions: none.Edgesinput boolbuttonoutput boolrise, fall, changeimmediate during/ rise = button && !pre(button)immediate during/ fall = !button && pre(button)immediate during/ change = button != pre(button)

Code

scchart Edges {
input bool button
output bool rise, fall, change
immediate during do rise = button && !pre(button) // was up, is down
immediate during do fall = !button && pre(button) // was down, is up
immediate during do change = button != pre(button) // either
}
Tick 0 of 9
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Edges
Tickinput bool buttonoutput bool riseoutput bool falloutput bool change
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 9 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 9 ticks:

  1. button = trueHeld at start-up: pre(button) is false in tick 1, so rise and change are true.
  2. button = trueHeld: no edge of either kind.
  3. button = falseReleased: button is false and pre(button) is true, so fall and change are true.
  4. button = truePressed: rise and change.
  5. button = trueHeld.
  6. button = trueHeld.
  7. button = falseReleased: fall and change.
  8. button = truePressed: rise and change.
  9. button = falseReleased: fall and change. Each edge lasts exactly one tick.
Every edge lasts exactly one tick. change is true whenever rise or fall is.

Two ticks in the run deserve a second look:

  • Tick 1. The button is already held when the chart starts, and rise is true. In tick 1 there is no previous tick, and pre(button) is the default of its type, false: not the button’s real past, and not the variable’s initial value either. So a button held at start-up counts as a press. Whether that is right depends on your device; the state-based detector below decides otherwise.
  • Ticks 4 to 6. However long the press lasts, rise is true only in its first tick.

The same expressions work in a transition trigger, if button && !pre(button) go to …, as the one-shot below shows. If you would rather produce an event than a bool, emit a signal instead of assigning a variable:

output signal pressed
immediate during if button && !pre(button) do pressed

A signal is present only in a tick in which it is emitted, so pressed is a one-tick event by construction. pre works on signals too: pre(S) is true if S was present in the previous tick, so S && !pre(S) is the first tick of a run of ticks in which S is present.

You can also detect edges without pre, by remembering the button’s position in a state. Up waits for the button to go down, Down waits for it to come up, and each transition emits its edge as a signal on the way. Here is that chart next to Edges, with the same inputs:

Edges and PressEventsComparison

Diagrams

Edges

Diagram of EdgesStates: none. Transitions: none.Edgesinput boolbuttonoutput boolrise, fall, changeimmediate during/ rise = button && !pre(button)immediate during/ fall = !button && pre(button)immediate during/ change = button != pre(button)

PressEvents

Diagram of PressEventsStates: Start (initial), Up, Down. Transitions: Start to Down: 1: button; Start to Up: 2:; Up to Down: button / rise; Down to Up: !button / fall.PressEventsinput boolbuttonoutput signalrise, fallStartUpDown1: button2:button/ rise!button/ fall

Code

Edges

scchart Edges {
input bool button
output bool rise, fall, change
immediate during do rise = button && !pre(button) // was up, is down
immediate during do fall = !button && pre(button) // was down, is up
immediate during do change = button != pre(button) // either
}

PressEvents

scchart PressEvents {
input bool button
output signal rise, fall
initial state Start
immediate if button go to Down
immediate go to Up
state Up
if button do rise go to Down
state Down
if !button do fall go to Up
}
Tick 0 of 9
Inputs for tick 1, for both charts Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Edges and PressEvents, no ticks yet
Tickinput bool buttonEdges: output bool riseEdges: output bool fallEdges: output bool changePressEvents: output signal risePressEvents: output signal fallPressEvents: Active state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 9 ticks Both charts get the same inputs. Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 9 ticks, the same inputs for both charts:

  1. button = trueThe button is already held when the charts start. Edges reports a rising edge: in tick 1, pre(button) is false. PressEvents passes through Start, whose transitions are immediate, straight to Down, and emits nothing: a button that is already down is not a new press.
  2. Held: no edge in either chart.
  3. button = falseReleased. Edges computes fall (and change); PressEvents emits fall on its way from Down to Up.
  4. button = truePressed: rise in both.
  5. Held: nothing.
  6. Held: however long the press lasts, each edge lasts one tick.
  7. button = falseReleased: fall in both.
  8. button = truePressed: rise.
  9. button = falseReleased after a press of one tick: fall. From tick 2 on the two charts agree; only tick 1 differs.
From tick 2 on, rise and fall agree. In tick 1, with the button already held, only Edges reports a rising edge.

The extra state Start decides where to begin. Its transitions are immediate, so in tick 1 the chart passes straight through it to Down or Up, and a button that is already held when the chart starts is treated as “already down”, not as a new press. Without Start, with Up as the initial state, the press would be reported a tick late, in tick 2: Up’s transition is delayed, so it cannot fire in tick 1, and in tick 2 the button is still down.

Which style should you use? The pre version is one line per edge, and you can drop it into any chart. The state version is easier to extend: once you have Up and Down, a long press or a debounce is a matter of adding states, as the first exercise below shows.

Whenever one press should do one thing, trigger on the edge, never on the level:

  • Toggling. A light that if button switches on and off flickers for as long as the button is held, and ends up on or off depending on whether the press lasted an odd or an even number of ticks. The previous tick runs the two versions side by side. KIELER gives no warning for the level version: the chart is perfectly legal; it does not do what you meant.
  • Counting presses. immediate during if button && !pre(button) do presses++ counts each press once, however long it lasts.
  • Starting something of fixed length, whatever the length of the press. That is the one-shot.

A one-shot (or monostable) turns a press of any length into a pulse of a fixed length. Here a rising edge starts a pulse of three ticks:

OneShotSimulation

Diagram

Diagram of OneShotStates: Ready (initial), Firing (superstate). Transitions: Ready to Firing: button && !pre(button); Firing to Ready: 3 true.OneShotinput boolbuttonoutput boolpulseReadyFiringentry/ pulse = trueexit/ pulse = falsebutton && !pre(button)3 true

Code

scchart OneShot {
input bool button
output bool pulse
initial state Ready
if button && !pre(button) go to Firing
state Firing {
entry do pulse = true
exit do pulse = false
}
if 3 true go to Ready
}
Tick 0 of 13
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of OneShot
Tickinput bool buttonoutput bool pulseActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 13 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 13 ticks:

  1. button = falseTick 1: ready.
  2. button = truePressed: a rising edge, so the chart enters Firing, whose entry action switches pulse on.
  3. button = truepulse stays on, although nothing happens to the button.
  4. button = trueThird tick of the pulse.
  5. button = trueif 3 true has now counted three ticks after the entry tick: Firing is left, its exit action switches pulse off. The button is still held, but that does not matter.
  6. button = falseReleased.
  7. button = trueA short press of one tick ...
  8. button = false... still gives a pulse of three ticks: the length of the pulse does not depend on the press.
  9. button = truePressed again while the pulse is running: Firing has no transition for the button, so the new press is ignored.
  10. button = trueThe pulse ends. Ready is entered while the button is held, and that is no new rising edge: no new pulse.
  11. button = trueHeld: still nothing.
  12. button = falseReleased.
  13. button = falseReady for the next press.
Ticks 2 to 4 and 7 to 9: three ticks of pulse for a long press and for a short one. The press in tick 9 is ignored.

Two idioms do the work:

  • An output that is true exactly while a state is active. Firing’s entry action switches pulse on and its exit action switches it off. Entry and exit actions run however the state is entered and left, so pulse cannot be left on by mistake (see Entry, during and exit actions).
  • A count delay. if 3 true go to Ready fires in the third tick after Firing was entered, counting only the ticks after the entry tick (see The previous tick). The counters page compares this with an explicit counter.

A press during the pulse is ignored, because Firing has no transition that looks at the button. And when the pulse ends while the button is still held (tick 10), Ready does not see a new rising edge, so one long press gives one pulse. If you wanted a held button to retrigger, you would test the level in Ready instead of the edge.

A long press

Many devices give a button a second meaning when it is held. Extend the state-based detector so that it also emits longPress once per press, in the tick in which the button has been held for three ticks in a row. A shorter press emits only rise and fall, and fall still comes when the button is released. To test it: a press in ticks 2 and 3 gives no longPress; a press from tick 5 to tick 9 gives longPress in tick 7, and nothing more until fall in tick 10. Hint: a count delay on Down, and one more state. The tick in which Down is entered is already the first tick of the press, and a delayed count delay does not count it.

Show the starter chart
scchart LongPress {
input bool button
output signal rise, fall, longPress
initial state Start
immediate if button go to Down
immediate go to Up
state Up
if button do rise go to Down
state Down
if !button do fall go to Up
}
Show a solution
scchart LongPress {
input bool button
output signal rise, fall, longPress
initial state Start
immediate if button go to Down
immediate go to Up
state Up
if button do rise go to Down
state Down
if !button do fall go to Up
if 2 true do longPress go to Held
state Held
if !button do fall go to Up
}

Toggle on release

Some devices switch when the button is released, not when it is pressed, so that a long press can mean something else. Change the starter so that the light toggles on the falling edge. To test it: hold the button in ticks 2 and 3, and again in tick 6. The starter switches on in tick 2 and off in tick 6; your version should switch on in tick 4 and off in tick 7.

Show the starter chart
scchart ToggleOnRelease {
input bool button
output bool light
initial state Off
if button && !pre(button) do light = true go to On
state On
if button && !pre(button) do light = false go to Off
}
Show a solution
scchart ToggleOnRelease {
input bool button
output bool light
initial state Off
if !button && pre(button) do light = true go to On
state On
if !button && pre(button) do light = false go to Off
}
OneShotPrediction

Diagram

Diagram of OneShotStates: Ready (initial), Firing (superstate). Transitions: Ready to Firing: button && !pre(button); Firing to Ready: 3 true.OneShotinput boolbuttonoutput boolpulseReadyFiringentry/ pulse = trueexit/ pulse = falsebutton && !pre(button)3 true

Code

scchart OneShot {
input bool button
output bool pulse
initial state Ready
if button && !pre(button) go to Firing
state Firing {
entry do pulse = true
exit do pulse = false
}
if 3 true go to Ready
}

The one-shot from above, with new inputs. What is pulse after ticks 4, 5, 9 and 11?

Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.

Tickbutton
1false
2true
3false
4true
5false
6false
7true
8true
9true
10true
11true
Plays the scenario on the diagram, then marks each answer.
Explanation

pulse is true after ticks 4 and 9, and false after ticks 5 and 11. The press in tick 2 starts a pulse for ticks 2, 3 and 4; the press in tick 4 comes during the pulse, and Firing has no transition that looks at the button. The press from tick 7 starts a pulse for ticks 7, 8 and 9. The button is still held when the pulse ends, so Ready sees no new rising edge in tick 11.

A chart declares input bool sensor and computes rise = sensor && !pre(sensor) in an immediate during action. The sensor is true from tick 1 on and never changes. In which ticks is rise true?
Show the answer

Answer D: Only in tick 1

In tick 1, pre(sensor) is false, the default for a bool, because there is no previous tick. So rise is true in tick 1. From tick 2 on, pre(sensor) is true and rise is false. If a sensor that is already active at start-up must not count as an edge, use a state-based detector with a Start state, as in PressEvents.

You want a retriggerable one-shot: a new press during the pulse should restart its three ticks. Which change to OneShot does that?
Show the answer

Answer B: Give Firing a first transition if button && !pre(button) go to Firing

A self-loop leaves Firing and enters it again, and every entry restarts the count delay, as for the stairwell light. Written first, it also wins over the end of the pulse when a press comes in the pulse’s last tick. pulse stays on throughout: in the re-entry tick the exit action switches it off and the entry action on again. (KIELER warns about a potential instantaneous loop for this chart, a false alarm.) The other two changes leave Firing without a transition for the button, so a press during the pulse still does nothing.

Key ideas

  • An input is a level; an edge is the tick in which it changes. Rising: x && !pre(x). Falling: !x && pre(x). Any change: x != pre(x).
  • pre(x) in tick 1 is the type’s default (false, 0), so an input that is already active at start-up shows a rising edge in tick 1.
  • Put an edge equation in an immediate during action to evaluate it in every tick, tick 1 included, or use the expression directly in a trigger.
  • States can detect edges too; a Start state with immediate transitions decides how to treat a button that is held at start-up, and more states give a long press.
  • Toggle on edges, never on levels. For a pulse of fixed length, combine an edge, a state with entry and exit actions, and a count delay.

Next, Debouncing: a real button bounces, and edge detection alone counts every bounce.

Report a mistake on this page