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]

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" userspace[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
Please refer to TIM OpenSTLinux drivers and LPTIM Linux driver 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 Linux driver.
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]
![]() |
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]
![]() |
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.
![]() |
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
![]() |
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. |
- Set up the TIM or LPTIM quadrature encoder with the sysfs interface, see How to set up the TIM quadrature encoder with the sysfs interface or How to set up the LPTIM quadrature encoder with the sysfs interface
- GPIO pins are then set or cleared as follows:
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]
![]() |
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):
- Enable timers in the Linux® device tree, example on STM32MP257F EV1.
- Configure and enable the timer 10, with the sysfs interface.
- 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.
- 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 ../signal1/name:Channel 2 ../signal2/name:Clock ../signal3/name:Channel 3 ../signal4/name:Channel 4 # 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 next step, start to stimulate TIM10_CH1 pin with an external digital signal generator.
cd /sys/class/pwm/pwmchip0/ echo 0 > export echo 1000000 > pwm0/period echo 500000 > pwm0/duty_cycle echo 1 > pwm0/enable
4.2.2. Capture events by using counter_watch_events tool[edit | edit source]
![]() |
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.
5. Source code location[edit | edit source]
Linux® counter subsystem sources and tools can be found in:
- drivers/counter/ , Counter subsystem and drivers
- tools/counter/ , Counter tools
- include/uapi/linux/counter.h , Userspace API header
- tools/counter/counter_watch_events.c , a flexible counter tool to watch various events from the command line
6. References[edit | edit source]
- ↑ 1.0 1.1 Quadrature encoder, Incremental encoder overview
- ↑ 2.0 2.1 https://docs.kernel.org/driver-api/generic-counter.html, Generic Counter Interface
- ↑ Documentation/ABI/testing/sysfs-bus-counter , Counter sysfs ABI
- ↑ https://docs.kernel.org/driver-api/generic-counter.html#userspace, "Generic Counter Interface" userspace section
- ↑ 5.0 5.1 Documentation/ABI/testing/sysfs-bus-counter , Counter sysfs ABI
- ↑ Control GPIO through libgpiod