Last edited one week ago

Counter overview

Applicable for STM32MP13x lines, STM32MP15x lines, STM32MP21x lines, STM32MP23x lines, STM32MP25x lines

This article gives information about the Linux® Counter subsystem.
It explains how to activate the Counter interface and, based on examples, how to use it.

1. Framework purpose[edit | edit source]

The counter subsystem offers a generic interface for the users to:

  • Simply count on an external signal.
  • Read the position of a rotary element with a quadrature encoder[1].
  • Capture counter value on an external signal, with time-stamping.

It is exposed to the user space through:

  • sysfs interface
  • character device interfaces (supported only by TIM Linux driver).

It is described in details in "Generic Counter Interface"[2] article.

2. System overview[edit | edit source]

Counter sysfs interfaceCounter chrdev interfaceSTM32 TIM Linux driverSTM32 LPTIM Linux driverSTM32 TIM internal peripheralSTM32 LPTIM internal peripheral
Counter Implementation architecture

2.1. Component description[edit | edit source]

  • sysfs interface provides an information and configuration interface available through the user terminal.
  • chrdev interface provides more functionalities like time-stamping, configuring watch events, gathering data.
  • Counter device drivers (Kernel Space): Linux kernel drivers to handle TIM and LPTIM internal peripherals.
  • STM32 peripherals (Hardware): TIM and LPTIM internal peripherals connected to the external signals or devices.

2.2. API description[edit | edit source]

All APIs are described in "Generic Counter Interface"[2].

2.2.1. Sysfs interface[edit | edit source]

Sysfs interface is described in Documentation/ABI/testing/sysfs-bus-counter[3].

2.2.2. Chrdev interface[edit | edit source]

User space programs can configure events thanks to ioctl operations, as defined in "Generic Counter Interface" user space[4] section.

3. Configuration[edit | edit source]

3.1. Kernel configuration[edit | edit source]

Counter subsystem is activated by default in ST deliveries. Nevertheless, if a specific configuration is needed, this section indicates how it can be activated/deactivated in the kernel.

Activate Counter in kernel configuration with Linux Menuconfig tool: Menuconfig or how to configure kernel

Device Drivers  --->
 <*> Counter support  --->
  <M>   STM32 LP Timer encoder counter driver
  <M>   STM32 Timer encoder counter driver

Refer to TIM OpenSTLinux drivers and LPTIM OpenSTLinux drivers articles for each peripheral.

3.2. Device tree configuration[edit | edit source]

Refer to the TIM device tree configuration article when configuring the TIM OpenSTLinux drivers.

Refer to the LPTIM device tree configuration article when configuring the LPTIM OpenSTLinux drivers.

4. How to use the framework[edit | edit source]

This section describes how to use the counter framework from the user space interface, by using sysfs and character device interfaces from the console.

4.1. How to use the quadrature encoder with the sysfs interface[edit | edit source]

This example shows how to monitor the position (count) of a linear (or rotary) encoder.

It uses quadrature the encoder[1] interface available on the TIM and LPTIM internal peripherals.

4.1.1. How to set up the TIM quadrature encoder with the sysfs interface[edit | edit source]

Information
The TIM quadrature encoder is enabled by the device tree: TIM configured as quadrature encoder interface
grep -H "" /sys/bus/counter/devices/*/name   # Look for TIM counter devices
/sys/bus/counter/devices/counter0/name:44000000.timer:counter
cd /sys/bus/counter/devices/counter0
cat count0/function_available                # List available modes:
quadrature x2 a
quadrature x2 b
quadrature x4
echo "quadrature x4" > count0/function       # set quadrature mode
echo 65535 > count0/ceiling                  # set ceiling value (upper limit for the counter)
echo 0 > count0/count                        # reset the counter

Runtime configuration is performed using the sysfs interface[5]:

echo 1 > count0/enable                       # enable the counter

Once started, the encoder value and direction are available using:

cat count0/count
0
cat count0/direction
forward

4.1.2. How to set up the LPTIM quadrature encoder with the sysfs interface[edit | edit source]

Information
The LPTIM quadrature encoder is enabled by the device tree: LPTIM configured as quadrature encoder interface

Runtime configuration is performed using the sysfs interface[5]:

grep -H "" /sys/bus/counter/devices/*/name   # Look for TIM counter devices
/sys/bus/counter/devices/counter0/name:40009000.timer:counter
cd /sys/bus/counter/devices/counter0
cat count0/function_available                # List available modes:
increase
quadrature x4
echo "quadrature x4" > count0/function       # set quadrature mode
echo 65535 > count0/ceiling                  # set ceiling value (upper limit for the counter)
echo 1 > count0/enable                       # enable the counter

Once started, the encoder value is available using:

cat count0/count
0

4.1.3. How to use the TIM or LPTIM quadrature encoder with the sysfs interface[edit | edit source]

This example shows how to monitor the TIM quadrature encoder interface via sysfs (the LPTIM case is very similar):

  • In this example, two GPIO lines (PD1, PG3) are externally connected to the TIM (or LPTIM)
  • Then libgpiod[6] is used to set and clear the encoder input pins, to 'emulate' an external quadrature encoder device.
Information
On the STM32MP157X-DKX discovery board, PD1, PG3, TIM1_CH1 and TIM1_CH2 signals are accessible via respectively the D7, D8, D6 and D10 pins of the Arduino Uno connector.

Step-by-step example:

  • Externally connect and initialise GPIO pins to TIM or LPTIM encoder input pins, to 'emulate' an external quadrature encoder
gpiodetect
...
gpiochip6 [GPIOG] (16 lines)
...
gpiochip3 [GPIOD] (16 lines)
...
gpioset -c gpiochip3 1=0 & pid=$!; sleep 0.1; kill $pid     # initialize PD1 to 0 as GPIO, connect it to TIM or LPTIM channel A input
gpioset -c gpiochip6 3=0 & pid=$!; sleep 0.1; kill $pid     # initialize PG3 to 0 as GPIO, connect it to TIM or LPTIM channel B input
Information
Note: The gpioset command is invoked in background in this example, and then killed. It allows to keep the control of the console, otherwise the gpioset command keeps it by default. It's possible as the GPIO value is persistent in between two gpioset calls.

cd /sys/bus/counter/devices/counter0/
cat count0/count                                         # [channel A, channel B] = [0, 0]
 0
gpioset -c gpiochip3 1=1 & pid=$!; sleep 0.1; kill $pid  # [channel A, channel B] = [1, 0]
cat count0/count
 1
gpioset -c gpiochip6 3=1 & pid=$!; sleep 0.1; kill $pid  # [channel A, channel B] = [1, 1]
cat count0/count
 2
gpioset -c gpiochip3 1=0 & pid=$!; sleep 0.1; kill $pid  # [channel A, channel B] = [0, 1]
cat count0/count
 3
gpioset -c gpiochip6 3=0 & pid=$!; sleep 0.1; kill $pid  # [channel A, channel B] = [0, 0]
cat count0/count
 4
cat count0/direction
 forward
gpioset -c gpiochip6 3=1 & pid=$!; sleep 0.1; kill $pid  # [channel A, channel B] = [0, 1]
cat count0/count
 3
cat count0/direction                                     # Direction has changed, down-counting now
 backward
...

4.2. How to capture a signal with the counter subsystem[edit | edit source]

Information
Capture use case is supported only by the TIM Linux driver (not by the LPTIM Linux driver)

This example describes how to capture events on a timer input, available on STM32MP257x-EV1 GPIO expansion connector. It requires two pins to be connected together:

  • TIM10_CH1 (pin32) used to capture a signal.
  • TIM8_CH1 (pin33) optionally used to generate a PWM signal. It can be replaced by an external square wave signal generator wired directly to TIM10_CH1.

Steps to configure two timers (need wire on expansion connector for loop-back):

  1. Enable timers in the Linux® device tree, example on STM32MP257F EV1.
  2. Configure and enable the timer 10, with the sysfs interface.
  3. Optionally configure and enable the timer 8, as PWM generator to trigger events (via loop-back wire). It could be replaced by any square wave generator.
  4. Start capture on timer 10, by using counter_watch_events tool: watch overflow and capture events.

4.2.1. Configure and enable the timer with the sysfs interface[edit | edit source]

Once the timer has been enabled in the device tree, it can be configured and enabled by using following commands.

# check counter name matches TIM10
cat /sys/bus/counter/devices/counter0/name
401c0000.timer:counter
# Go to counter device folder
cd /sys/bus/counter/devices/counter0/count0/
# Identify all signals, especially the counter clock
grep -H "" ../signal*/name
../signal0/name:Channel 1     # Either CH1 capture signal or CH1 clock signal
../signal1/name:Channel 2     # Either CH2 capture signal or CH1 clock signal
../signal2/name:Clock         # Clock signal
../signal3/name:Channel 3     # CH3 capture signal
../signal4/name:Channel 4     # CH4 capture signal
../signal5/name:Channel 5     # External trigger (ETR)


# Get clock signal (counting frequency) used for event calculation later
cat ../signal2/frequency
200000000
# Now to identify capture0
cat capture0_component_id
4
# Configure timer 10 as up counter (increase)
echo increase > function
# Check prescaler (optionally tune it)
cat prescaler
1
# Enable the counter
echo 1 > enable
# Counter should increment now, it can be checked by doing repeatedly:
cat count
13281
cat count
53437

Before going to the next step, start to stimulate TIM10_CH1 pin with an external digital signal generator.

* As an alternative, a PWM output can be used to trigger events, as a loop-back wired to TIM10_CH1 pin.
cd /sys/class/pwm/pwmchip0/
echo 0 > export
echo 1000000 > pwm0/period
echo 500000 > pwm0/duty_cycle
echo 1 > pwm0/enable

Configure channels polarity for ecosystem release ≥ v6.2.0 [edit | edit source]

In capture mode, each channel detector can be configured individually for rising edge, falling edge, or both edges detection. The corresponding settings are "capture rising edge", "capture falling edge" and "capture both edges". By default, each capture channel is configured as "capture rising edge".

cd /sys/bus/counter/devices/counter0/count0/   # Go to counter device folder
echo increase > function
echo "capture falling edge" > signal0_action   # Configure the channel edge detector to falling edge for CH1 channel
Warning
On ecosystem release ≤ v6.1.0 channel edge detector is fixed to both edges detection
Information
In encoder mode, CH1 and CH2 channels polarity is forced to "none" or "both edges", depending on the selected quadrature mode.

Configure channels capture source for ecosystem release ≥ v6.2.0 [edit | edit source]

Timer channels CH1/CH2 and CH3/CH4, when available in a timer instance, can operate as paired channels. By default, each Capture/Compare unit (CCx), processes the input signal from its own channel (CHx). However, the Capture/Compare unit within a pair can be configured to process the same input signal. The source selector can be set so that one Capture/Compare unit uses the input signal of its associated channel within the pair.

The source selector of a Capture/Compare unit CCx is represented by the sysfs attribute capture_source<x-1>. (i.e. capture_source0 stands for CC1 unit, capture_source1 for CC2 unit ...).

The capture_source<x-1> attribute can be set to the following values:

  • 0: Select the tixfpx input, which corresponds to the CCx unit’s own channel.
  • 1: Select the ti<x+1>fpx or ti<x-1>fpx input, depending on the index of the adjacent channel of the CCx unit.
cd /sys/bus/counter/devices/counter0/count0/  # Go to counter device folder
echo increase > function
echo 0 > capture_source0                      # Configure CC1 to process signal from tim_ti1fp1 input (CH1 input signal)
echo 1 > capture_source1                      # Configure CC2 to process signal from tim_ti1fp2 input (CH1 input signal)
Information
Capture source and channel polarity configurations can be used to implement duty cycle measurement on an PWM input signal. See PWM duty cycle measurement example below.

Configure channel input selector for ecosystem release ≥ v6.2.0 [edit | edit source]

Each timer channel includes an input selection multiplexer that allows selection from 16 sources (0 to 15). By default, each timer input channel is assigned to tim_tix_in0, which is mapped as an alternate function to a GPIO port. The remaining sources, tim_tix_in1 to tim_tix_in15, are routed to internal SoC signals or left unconnected, depending on the specific TIM instance.

The signal attribute tisel allows to select channel signal source.

cd /sys/bus/counter/devices/counter0/count0/  # Go to counter device folder
echo 2 > ../signal0/tisel                     # Selects tim_ti1_in2 input

Configure external clock source ecosystem release ≥ v6.2.0 [edit | edit source]

The timer can count on filtered CH1 and CH2 external signals (tim_ti1fp1 or tim_ti2fp2). By default, the counter uses the internal clock when the "increase" function is used. CH1 and CH2 are configured for capture by default, with "capture rising edge" detection. Changing CH1 or CH2 signal action to "rising edge" configures the channel as a clock source. Setting "none" action restores the timer clock to the default internal clock source.

cd /sys/bus/counter/devices/counter0/count0/  # Go to counter device folder
echo "rising edge" > signal1_action           # Configure CH2 as clock source

How to use the timer external trigger (ETR) for ecosystem release ≥ v6.2.0 [edit | edit source]

4.2.1.1. Configure external trigger input source[edit | edit source]

The TIM_ETR (tim_etr0) pin is the default external trigger input signal. A multiplexer allows selection of other inputs. These inputs are internal signals, named tim_etr1 to tim_etr15.

These inputs are identified in the etrsel attribute by their index.

cd /sys/bus/counter/devices/counter0/count0/  # Go to counter device folder
cat ../signal5/name                           # Check that signal name is "ETR"
echo 7 > ../signal5/etrsel                    # Configure ETR input selector. Select tim_etr7 as input 
4.2.1.2. Configure timer trigger as external clock source[edit | edit source]

The timer can increment its counter based on transitions on the external trigger (ETR) input, instead of the internal clock. The ETR signal action can be either "rising edge" or "none". Setting "none" action restores the timer clock to the default internal clock source.

cd /sys/bus/counter/devices/counter0/count0/  # Go to counter device folder
cat ../signal5/name                           # Check that signal name is "ETR"
echo "rising edge" > signal5_action           #  Configure ETR as clock source
4.2.1.3. Configure external trigger modes[edit | edit source]

The timer can use a trigger input that influences how the counter updates. This signal can synchronize the counter start, reset the counter, or gate the clock, or combine these modes, based on the trigger input edge or level. In these modes, the counter continues to count using its internal clock.

The available count modes are "hardware triggered reset", "hardware triggered gated", "hardware triggered start", "hardware triggered reset start" and "hardware triggered gated reset"

These modes can be selected through the count_mode.

cd /sys/bus/counter/devices/counter0/count0/            # Go to counter device folder
cat ../signal5/name                                     # Check that signal name is "ETR"
echo "rising edge" > signal5_action                     # Configure ETR as clock source
echo "hardware triggered reset start" > count_mode      # Reset and start the counter on rising edge of the trigger

4.2.2. Capture events by using counter_watch_events tool[edit | edit source]

Information
Users can write them own application, by using the counter user space API . It offers the structures, and ioctl() definitions to get and process the data provided by the counter device

Linux® kernel tools provide an application called counter_watch_events . It allows to configure events from the command line, by using the chrdev interface. In this example, two event types are being watched:

  • Watch overflow events
  • Watch capture events on TIM10_CH1 (chan=0) to report CC register data e.g. capture0 (id=4)
# Request 10 events (e.g. -l 10) on counter0 (-n 0)
# Other options can be listed by typing "counter_watch_events -h"
counter_watch_events -d \
       -w comp_count,scope_count,evt_ovf_udf \
       -w comp_extension,scope_count,evt_capture,id=4,chan=0 \
       -l 10 -n 0 &
Timestamp: 1245482541075        Data: 4214       event: COUNTER_EVENT_OVERFLOW_UNDERFLOW        ch: 0
Timestamp: 1245482852850        Data: 906        event: COUNTER_EVENT_OVERFLOW_UNDERFLOW        ch: 0
Timestamp: 1245482914875        Data: 12489      event: COUNTER_EVENT_CAPTURE   ch: 0
Timestamp: 1245483180000        Data: 787        event: COUNTER_EVENT_OVERFLOW_UNDERFLOW        ch: 0
Timestamp: 1245483414825        Data: 46953      event: COUNTER_EVENT_CAPTURE   ch: 0
Timestamp: 1245483507750        Data: 799        event: COUNTER_EVENT_OVERFLOW_UNDERFLOW        ch: 0
Timestamp: 1245483835475        Data: 806        event: COUNTER_EVENT_OVERFLOW_UNDERFLOW        ch: 0
Timestamp: 1245483914800        Data: 15881      event: COUNTER_EVENT_CAPTURE   ch: 0
Timestamp: 1245484170450        Data: 2292       event: COUNTER_EVENT_OVERFLOW_UNDERFLOW        ch: 0
Timestamp: 1245484421600        Data: 50345      event: COUNTER_EVENT_CAPTURE   ch: 0

Now the results are checked (in between COUNTER_EVENT_CAPTURE events, e.g. complete periods).

From Timestamp: 1245482914875 to 1245483414825 (with 1 overflow)

(46953+65536-12489)/200000000
= 0.0005

From Timestamp: 1245483414825 to 1245483914800 (with 2 overflows)

(15881+65536+65536-46953)/200000000
= 0.0005

Results (in second) match the requested 500000 ns period.

4.2.3. Example of Duty Cycle Measurement from PWM Capture[edit | edit source]

This example shows how to measure both the PWM period and duty cycle from capture events.

Two capture channels are required: one for the rising edges and the other for the falling edges.

  • CC1 (channel 0) captures rising edges (red path in the figure)
  • CC2 (channel 1) captures falling edges (blue path in the figure)

The PWM signal is connected to a single input TIM_CH1.

Information
This use case is supported by TIM counter capture only. It is not supported by LPTIM.

Configure counter runtime settings:

echo 65535 > ceiling
echo increase > function

Configure capture edges and sources:

echo "capture rising edge" > signal0_action   # Configure the channel edge detector to rising edge for tim_ti1fp1 input
echo "capture falling edge" > signal1_action  # Configure the channel edge detector to falling edge for tim_ti1fp2 input
echo 0 > capture_source0                      # Configure CC1 to process signal from tim_ti1fp1 input (CH1 input signal)
echo 1 > capture_source1                      # Configure CC2 to process signal from tim_ti1fp2 input (CH1 input signal)
echo 1 > enable

Start event capture:

# Request 10 events (e.g. -l 10) on counter0 (-n 0)
counter_watch_events -n 0 -l 10 \
-w comp_count,scope_count,evt_ovf_udf,chan=0,id=0 \
-w comp_count,scope_count,evt_capture,chan=0,id=$(cat capture0_component_id) \
-w comp_count,scope_count,evt_capture,chan=1,id=$(cat capture1_component_id)

Compute timings from timestamps:

  • PWM period: the time between two consecutive rising-edge capture events on channel 0
  • PWM active time: the time between a rising-edge event on channel 0 and the next falling-edge event on channel 1

5. Source code location[edit | edit source]

Linux® counter subsystem sources and tools can be found in:

6. References[edit | edit source]