Counters and timeouts
Counting ticks, timeouts measured in ticks, frequency division, and when to use a count delay instead of a counter.
A synchronous chart has no clock of its own; it has ticks. If your system ticks at a fixed rate, say
every 10 ms, then counting ticks is measuring time: 100 ticks are a second. This page shows the two ways
SCCharts gives you to count: count delays (if 4 true go to …), where KIELER keeps the count for you,
and counters, ordinary int variables you increment yourself. It uses both for timeouts and for
frequency division, and ends with a comparison to help you choose.
Everything here assumes the ticks come at a steady rate. If they do not, see Time for clocks that measure real time.
Counting ticks
Section titled “Counting ticks”The simplest counter is a variable and a during action:
Diagram
Code
scchart TickCounter { output int tickNo = 0, later = 0
immediate during do tickNo++ during do later++}| Tick | output int tickNo | output int later |
|---|
Guided tour, 5 ticks:
- Tick 1. The
immediate duringaction runs in the tick its state (here: the chart itself) is entered, sotickNois already 1. The plainduringaction waits for the next tick, solateris still 0. - Both actions run:
tickNois 2,lateris 1. tickNois always the number of the current tick.latercounts the ticks after the first: one less.- Tick 5.
tickNo is the number of the current tick; later is always one less.The two actions differ only in the first tick. A during action runs in every tick in which its state
is active except the tick it was entered; for actions written directly in the chart, that is tick 1.
immediate during runs in the entry tick as well. So immediate during do tickNo++ makes tickNo the
number of the current tick, and during do later++ counts the ticks after the first. Pick whichever
matches the question you ask: “which tick is this?” or “how many ticks have passed since we started?”.
A timeout: the stairwell light
Section titled “A timeout: the stairwell light”Many buildings have a stairwell light on a timer: press the button and the light stays on for a while, then goes off by itself. Pressing again while it is on restarts the time. With a count delay that is three transitions:
Diagram
Code
scchart StairLight { input bool button output bool light
initial state Off if button go to On
state On { entry do light = true exit do light = false } if button go to On if 4 true go to Off}| Tick | input bool button | output bool light | Active state of the chart |
|---|
Guided tour, 17 ticks:
- button = falseTick 1: dark.
- button = truePressed:
Onis entered and its entry action switches the light on. - button = false
if 4 truecounts its first tick (the entry tick does not count). - button = falseTwo.
- button = falseThree.
- button = falseFour: the timeout fires and
On’s exit action switches the light off. On for ticks 2 to 5: four ticks. - button = falseDark.
- button = truePressed: on.
- button = falseOne.
- button = falseTwo.
- button = truePressed again before the timeout.
if button go to Onis written first, so it wins:Onis left and entered again, and the re-entry restarts the count. The exit and entry actions run, but the light ends the tick on. - button = falseOne, counted from the new entry.
- button = falseTwo.
- button = falseThree.
- button = falseFour: off. The second press bought four more ticks from tick 11.
- button = falseDark.
- button = falseDark.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 8, column 9KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays and entry and exit actions are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughOn. The chart works as written.About this message
if 4 true go to Off is a count delay: it fires in the fourth tick in which its trigger (here
true) holds while On is active. Like any delayed transition it ignores the entry tick, so it
fires four ticks after the tick On was entered. The restart comes for free: if button go to On is a
self-loop, and every entry of a state restarts its count delays. It is written first, so a press in the
very tick the timeout would fire still wins and keeps the light on. (Time
builds the same light with a clock, which measures real time.)
Two consequences of that restart are worth knowing:
- Holding the button keeps the light on. In every tick of a long press
if button go to Onfires again and restarts the count; the four ticks start when the button is released. If that is not what you want, restart only on a rising edge,if button && !pre(button) go to On(see edges). - The count belongs to the state. KIELER keeps it in a hidden variable that it sets to 0 whenever
Onis entered. You cannot read it, show it, or change it.
The same timeout with a counter
Section titled “The same timeout with a counter”Here is the stairwell light with an explicit counter, left, the number of ticks the light has left:
Diagrams
StairLight
StairLightCounter
Code
StairLight
scchart StairLight { input bool button output bool light
initial state Off if button go to On
state On { entry do light = true exit do light = false } if button go to On if 4 true go to Off}StairLightCounter
scchart StairLightCounter { input bool button output bool light output int left = 0
initial state Off if button do light = true; left = 4 go to On
state On if button do left = 4 go to On if left == 1 do left = 0; light = false go to Off do left-- go to On}| Tick | input bool button | StairLight: output bool light | StairLight: Active state of the chart | StairLightCounter: output bool light | StairLightCounter: output int left | StairLightCounter: Active state of the chart |
|---|
Guided tour, 17 ticks, the same inputs for both charts:
- button = falseTick 1: both lights are off, and
leftis 0. - button = truePressed.
StairLightentersOn, whose entry action switches the light on.StairLightCounterswitches it on in the transition and setsleftto 4. - button = false
StairLight’s count delay counts its first tick: the entry tick does not count.StairLightCounter’s transitions are delayed too, so they are tried for the first time now:leftcounts down to 3. - button = falseTwo ticks counted;
leftis 2. - button = falseThree;
leftis 1. - button = falseBoth switch off:
if 4 truecompletes inStairLight, andleft == 1fires inStairLightCounter, which setsleftback to 0. The light was on in ticks 2 to 5. - button = falseDark.
- button = truePressed: both lights on,
leftis 4. - button = falseOne tick counted;
leftis 3. - button = falseTwo;
leftis 2. - button = truePressed again before the timeout. In both charts the press is
On’s first transition, a self-loop. InStairLightthe re-entry restarts the count delay;StairLightCountersetsleftback to 4. The light stays on. - button = falseOne tick counted from the new press;
leftis 3. - button = falseTwo;
leftis 2. - button = falseThree;
leftis 1. - button = falseOff in both, four ticks after the second press.
- button = falseDark.
- button = falseDark.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly (StairLight)
- Warning in
StairLight, line 8, column 9KIELER says:Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays and entry and exit actions are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughOn. The chart works as written.About this message
The transitions out of On say, in priority order: a press sets left back to 4; on the last tick, switch
off; otherwise count down. With a press in tick 2, the two charts agree tick for tick. Delay and
Counter are light in StairLight and in StairLightCounter:
| Tick | Delay | Counter | left |
|---|---|---|---|
| 1 | false | false | 0 |
| 2 | true | true | 4 |
| 3 | true | true | 3 |
| 4 | true | true | 2 |
| 5 | true | true | 1 |
| 6 | false | false | 0 |
The counter version is longer, but you can watch left in the simulator, other parts of the chart can
read it (to make the light warn before it goes off, say), and the limit could come from a variable
instead of being a number written into the transition.
Note where left is declared: at the top of the chart. On is re-entered by its self-loop, and a
variable declared inside a state is initialised again on every entry of that state. Predict what that
does to a counter:
Diagram
Code
scchart CounterInside { output int shown = 0
initial state S { int n = 0 } do n++; shown = n go to S}S declares int n = 0 and loops to itself in every tick with do n++; shown = n go to S. What is shown after ticks 2 and 4?
Explanation
shown is 1 after both ticks. In every tick the self-loop runs n++, which turns 0 into 1, and
shown copies the 1; then the re-entry of S sets n back to 0. The counter never gets past 1.
Declare counters one level up, in the chart or
in a superstate that stays active, as StairLightCounter does with left. The
debouncing page uses the same idea with a counter declared outside its looping
state.
Frequency division
Section titled “Frequency division”Counting is also how you make something slower than the tick. A blinker that is lit for two ticks and dark for two divides the tick rate by four:
Diagram
Code
scchart Blinker { output bool lamp
initial state Dark if 2 true go to Lit
state Lit { entry do lamp = true exit do lamp = false } if 2 true go to Dark}| Tick | output bool lamp | Active state of the chart |
|---|
Guided tour, 9 ticks:
- Tick 1:
Darkis entered; the lamp is off. if 2 truehas counted one tick.- Two:
Litis entered, the lamp goes on. - One tick in
Lit. - Two: back to
Dark. - Dark.
- Lit.
- Lit.
- Dark.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 4, column 17KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays and entry and exit actions are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughLitandDark. The chart works as written.About this message
Each state waits for its own count delay and hands over to the other. (KIELER warns about this chart too, as above.) To produce a single-tick pulse every N ticks, a counter and a self-loop are more direct:
Diagram
Code
scchart Divider { output signal beat int phase = 0
initial state Count if phase == 2 do phase = 0; beat go to Count do phase++ go to Count}| Tick | output signal beat | local int phase | Active state of the chart |
|---|
Guided tour, 10 ticks:
- Tick 1:
Countis entered;phaseis 0. Its transitions are delayed, so nothing else happens. phaseis 0, not 2: the untriggered transition counts up to 1.- 2.
phase == 2:beatis emitted andphasestarts again at 0.- 1.
- 2.
beat: every third tick.- 1.
- 2.
beat.
phase runs 1, 2, 0, 1, 2, 0, … and beat comes in ticks 4, 7 and 10.phase counts 0, 1, 2, 0, …, and the tick in which it would pass 2 emits beat instead. Because the
transitions of Count are delayed, the first beat comes in tick 4: tick 1 only enters the state. The same
chart divides events instead of ticks if you put the event into the triggers; the packing exercise
below does that.
Count delay or counter?
Section titled “Count delay or counter?”A count delay, such as if 4 true go to Off, against a counter, an int you update yourself:
| What differs | Count delay | Counter |
|---|---|---|
| Length | one transition | a declaration and two or three transitions |
| Visible | no: KIELER’s hidden variable | yes, like any variable |
| What it counts | ticks in which the trigger holds while the state is active, not the entry tick; they need not be consecutive | whatever you program |
| Restarts | on every entry of the source state | when you assign it |
| The limit | a number literal only | any expression: a const, a variable, an input |
| Where | on transitions only | anywhere |
Rules of thumb: use a count delay for a fixed timeout on a single state (“leave after 50 ticks”). Use a counter when the count must be seen or shared, when the limit is not a fixed number, when the count must survive leaving the state, or when you count across several states.
How a count delay counts is the subject of The previous tick: the ticks need
not be consecutive (debouncing shows how to demand ticks in a row), every entry
restarts the count, if 0 A is rejected, if 1 A means if A, and an immediate count delay can fire
too early when its state is left and entered again. One limit matters when you choose: count delays work
only on transitions. KIELER accepts during if 3 A do …, but treats it as during if A do … and
warns (sic):
Count Delays on During Actions are not supported yet. The valued will be ignored by the transformation.On entry and exit actions a count is a syntax error. To count inside an action, use a counter.
Try it
Section titled “Try it”A short flash
Change the blinker so that the lamp is lit for one tick and dark for three, so that it flashes once every four ticks. The starter is lit in ticks 3, 4, 7 and 8; your version should be lit in ticks 4 and 8 only.
Show the starter chart
scchart Blinker { output bool lamp
initial state Dark if 2 true go to Lit
state Lit { entry do lamp = true exit do lamp = false } if 2 true go to Dark}Show a solution
scchart Blinker { output bool lamp
initial state Dark if 3 true go to Lit
state Lit { entry do lamp = true exit do lamp = false } go to Dark}Boxes of four
Items pass a light barrier on a conveyor belt; sensor is true while an item is in front of it. The
starter counts the items (once per item, thanks to the edge). Extend it to pack them in boxes of four:
when the fourth item of a box passes, emit boxFull and start counting the next box from 0. To test
it: items in ticks 2, 4 to 5, 7 and 9 fill the first box, so boxFull comes in tick 9 and inBox is 0
again; items in ticks 11 and 13 make it 1 and 2.
Show the starter chart
scchart Packer { input bool sensor output int inBox = 0 output signal boxFull
initial state Counting if sensor && !pre(sensor) do inBox++ go to Counting}Show a solution
scchart Packer { input bool sensor output int inBox = 0 output signal boxFull
initial state Counting if sensor && !pre(sensor) && inBox == 3 do inBox = 0; boxFull go to Counting if sensor && !pre(sensor) do inBox++ go to Counting}Check your understanding
Section titled “Check your understanding”Diagram
Code
scchart StairLight { input bool button output bool light
initial state Off if button go to On
state On { entry do light = true exit do light = false } if button go to On if 4 true go to Off}The stairwell light with a new sequence of presses. Is the light on after ticks 7, 8, 15 and 16?
Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.
| Tick | button |
|---|---|
| 1 | false |
| 2 | true |
| 3 | false |
| 4 | true |
| 5 | false |
| 6 | false |
| 7 | false |
| 8 | false |
| 9 | false |
| 10 | true |
| 11 | true |
| 12 | true |
| 13 | false |
| 14 | false |
| 15 | false |
| 16 | false |
Explanation
The light is on after tick 7, off after tick 8, on after tick 15 and off after tick 16. The press in
tick 4 re-enters On and restarts the count, so the four ticks are 5 to 8. From tick 10 the button is
held for three ticks, and every tick of the press restarts the count again; the four ticks begin when
it is released, in tick 13, and end in tick 16.
Show the answer
Answer C: Tick 15
A delayed count delay does not count the entry tick. It counts ticks 11, 12, 13, 14 and 15, and fires in the fifth of them, tick 15: five ticks after the entry.
Show the answer
Answer A: With a counter, set to limit on every press
The count of a count delay must be a number literal, so if limit true is a syntax error. A counter
can start from any expression: set left = limit where StairLightCounter sets left = 4.
Key ideas
- With a steady tick rate, counting ticks measures time.
duringskips the tick its state is entered;immediate duringdoes not.- A count delay
if N truefires N ticks after its state was entered, restarts on every entry, and hides its count. A self-loop that restarts it gives a retriggerable timeout. - A counter is a variable you update: visible, shareable and free to compare with anything. Declare it outside any state that is re-entered.
- Frequency division is counting with a hand-over: two states with count delays, or a counter that wraps.
Next, Latches: remembering a request until it has been served.