1、配置app.overlay
c
&timers3 {
status = "okay";
st,prescaler = <0>;
/* 配置定时器中断(通常使用更新中断 Update Interrupt) */
interrupts = <29 0>;
// interrupt-names = "update";
interrupt-names = "cc";
qdec: qdec {
compatible = "st,stm32-qdec";
status = "okay";
/* 引用上面定义的引脚标签 */
pinctrl-0 = <&tim3_ch1_pa6 &tim3_ch2_pa7>;
pinctrl-names = "default";
/* 可选:输入极性反转 */
st,input-polarity-inverted;
/* 配置输入滤波器级别,用于消除编码器机械抖动 */
st,input-filter-level = <5>;
/* 编码器每转产生的脉冲数 */
st,counts-per-revolution = <700>;
/* 编码器模式(例如 1 表示特定的计数模式) */
st,encoder-mode = <1>;
};
};
/* 定义 PB10 对应的 GPIO 节点 */
my_gpio_input {
compatible = "gpio-keys"; /* 如果是按键,推荐使用此兼容字符串 */
debounce-interval-ms = <10>;
my_signal: signal_0 {
gpios = <&gpiob 10 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; /* PB10,上拉,低电平有效 */
label = "My PB10 Signal";
zephyr,code = <INPUT_KEY_0>;
};
};
配置prj.conf
c
CONFIG_COUNTER=y
CONFIG_SENSOR=y
CONFIG_INPUT=y
CONFIG_QDEC_STM32=y
代码段:
c
#include <zephyr/drivers/counter.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys_clock.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/sensor.h>
#include <stm32_ll_tim.h>
// 1. 定义触发阈值(例如每 100 个脉冲触发一次)
#define PULSES_PER_INTERRUPT 100
#define QDEC_NODE DT_ALIAS(qdec0)
static const struct device *const qdec_dev = DEVICE_DT_GET(QDEC_NODE);
// 2. 中断服务例程 (ISR)
void qdec_update_isr(const void *param)
{
//必须清除 TIM3 的更新中断标志位,防止陷入死循环
LL_TIM_ClearFlag_UPDATE(TIM3);
// 业务逻辑:读取当前绝对位置并打印
struct sensor_value position;
if (sensor_sample_fetch(qdec_dev) == 0) {
if (sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &position) == 0) {
LOG_INF("TIM3 IRQ Triggered! Current Pos: %d\n", position.val1);
}
}
}
K_SEM_DEFINE(qdec_sem, 0, 1);
static atomic_t isr_trigger_count = 0;
// 1. CC 中断服务例程 (ISR)
void qdec_cc_isr(const void *param)
{
// 清除 CC1 中断标志位(假设使用通道1)
LL_TIM_ClearFlag_CC1(TIM3);
// 更新下一次比较值(实现周期性触发)
uint32_t current_ccr = LL_TIM_OC_GetCompareCH1(TIM3);
LL_TIM_OC_SetCompareCH1(TIM3, current_ccr + PULSES_PER_INTERRUPT);
atomic_inc(&isr_trigger_count);
// 释放信号量
k_sem_give(&qdec_sem);
}
static const struct gpio_dt_spec my_signal = GPIO_DT_SPEC_GET(DT_NODELABEL(my_signal), gpios);
/* 2. 定义 GPIO 中断回调结构体 */
static struct gpio_callback gpio_cb_data;
/* 3. GPIO 中断服务例程 (ISR) */
static void gpio_signal_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
LOG_INF("PB10 Interrupt Triggered! Pin mask: 0x%x\n", pins);
}
void led_thread_entry4(void *p1, void *p2, void *p3)
{
struct sensor_value position; // 必须保留这一行声明
int32_t last_raw_pos = 0;
int32_t absolute_pos = 0; // 用于记录真实的绝对位置(不受16位溢出影响)
int32_t delta;
/* 检查设备是否就绪 */
if (!device_is_ready(my_signal.port)) {
LOG_INF("GPIO port is not ready!\n");
}
/* 配置引脚为输入模式 */
int ret = gpio_pin_configure_dt(&my_signal, GPIO_INPUT);
if (ret < 0) {
LOG_INF("Failed to configure PB10 as input: %d\n", ret);
}
/*
* 配置中断触发条件
* GPIO_INT_EDGE_TO_ACTIVE 会根据设备树中的极性(ACTIVE_LOW/HIGH)
* 自动选择下降沿或上升沿触发,非常推荐这种跨平台写法。
*/
ret = gpio_pin_interrupt_configure_dt(&my_signal, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
LOG_INF("Failed to configure PB10 interrupt: %d\n", ret);
}
/* 初始化并添加回调函数 */
gpio_init_callback(&gpio_cb_data, gpio_signal_handler, BIT(my_signal.pin));
ret = gpio_add_callback(my_signal.port, &gpio_cb_data);
if (ret < 0) {
LOG_INF("Failed to add GPIO callback: %d\n", ret);
}
LOG_INF("PB10 GPIO Input Interrupt Initialized.\n");
// 检查设备是否绑定成功
if (!device_is_ready(qdec_dev))
{
LOG_ERR("QDEC device is not ready!");
}
// 设置自动重装载值(ARR),计满 100 个脉冲触发一次
// LL_TIM_SetAutoReload(TIM3, PULSES_PER_INTERRUPT - 1);
// 设置初始比较值
LL_TIM_OC_SetCompareCH1(TIM3, PULSES_PER_INTERRUPT);
// 使能 CC1 中断
LL_TIM_EnableIT_CC1(TIM3);
// 使能 TIM3 更新中断
// LL_TIM_EnableIT_UPDATE(TIM3);
// 使用 Zephyr 的 IRQ_CONNECT 绑定中断
// 确保 timers3 节点在你的设备树中定义了正确的中断号
IRQ_CONNECT(DT_IRQN(DT_NODELABEL(timers3)),
3, // 优先级(0为最高,建议设为 3 或 4)
qdec_cc_isr, // qdec_update_isr,
NULL,
0);
// 使能该中断线
irq_enable(DT_IRQN(DT_NODELABEL(timers3)));
LOG_INF("TIM3 QDEC Update Interrupt Initialized (Threshold: %d)\n", PULSES_PER_INTERRUPT);
LOG_INF("Hardware QDEC initialized successfully.");
while (1) {
k_sem_take(&qdec_sem, K_FOREVER);
// 读取并打印中断触发次数
uint32_t count = atomic_get(&isr_trigger_count);
if (sensor_sample_fetch(qdec_dev) == 0) {
if (sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &position) == 0) {
int32_t current_raw = position.val1;
// 1. 使用 int16_t 计算差值,完美解决 16位定时器溢出回绕问题
delta = (int16_t)(current_raw - last_raw_pos);
// 2. 【软件消抖】如果单次采样差值过大(例如 > 5),说明是机械抖动引起的跳变,直接忽略
// 正常的旋钮旋转在 100ms 内不可能瞬间转过几十个脉冲
if (delta > 5 || delta < -5) {
// LOG_WRN("Ignored bounce: %d", delta); // 可选:打印警告
continue;
}
// 3. 累加到绝对位置
if (delta != 0) {
absolute_pos += delta;
last_raw_pos = current_raw;
LOG_INF("ISR Count: %d | Abs Pos: %d, Delta: %d", count, absolute_pos, delta);
}
}
}
}
}
结果:

定时器输出pwm
官方也有:简单配置一下:
c
app.overlay
&timers4 {
status = "okay";
pwm4c1: pwm {
compatible = "st,stm32-pwm";
status = "okay";
pinctrl-0 = <&tim4_ch1_pd12>;
pinctrl-names = "default";
};
};
aliases {
pwmgpiod12 = &pwm4c1;
};
prj.conf
CONFIG_PWM_CAPTURE=y
CONFIG_PWM_STM32=y
CONFIG_PWM=y
主程序:
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/counter.h>
#define PWM_NODE DT_ALIAS(pwmgpiod12)
static const struct device *const pwm_dev = DEVICE_DT_GET(PWM_NODE);
#define PWM_CHANNEL 1// 使用通道0,即上面设备树中配置的第一个pwm输出引脚
#define PWM_PERIOD_NS (PWM_USEC(100)) // 100 µs -> 100,000 ns
#define PWM_PULSE_NS (PWM_USEC(80)) // 80 µs -> 80,000 ns,占空比 = 80000/100000 = 80%
#define PWM_FLAGS PWM_POLARITY_NORMAL
device_is_ready(pwm_dev);
pwm_set(pwm_dev, PWM_CHANNEL, PWM_PERIOD_NS,PWM_PULSE_NS, PWM_FLAGS);
效果如下:
