八、在zephyr配置旋转编码器+timer配置演示

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);

效果如下:

相关推荐
离凌寒2 天前
六、在zephyr上配置sdmmc和fatfs演示
freertos
追兮兮4 天前
MCUQuickStart:一键生成 STM32 / GD32 的 Keil、CMake、RTOS 工程
stm32·单片机·freertos·gd32·gcc·工程模版
欢乐熊嵌入式编程1 个月前
嵌入式 + MQTT:数据上传到阿里云实战(从0到1完整教程)
stm32·单片机·mqtt·freertos·嵌入式架构·efr32
离凌寒1 个月前
一、关于zephyr的开发环境搭建。
freertos
欢乐熊嵌入式编程1 个月前
第2讲:什么是优秀的软件架构?
stm32·单片机·freertos·低功耗蓝牙·嵌入式架构·efr32
bbaydnog1 个月前
FreeRTOS学习笔记 18:调试方法论——HardFault排查、栈溢出检测、运行时统计,RTOS调试三板斧
笔记·单片机·freertos
一支闲人1 个月前
Free RTOS:任务通知实验
freertos
一支闲人1 个月前
Free RTOS:任务通知的两类函数
freertos
007张三丰1 个月前
AIoT与嵌入式系统深度解析:2026软考案例核心考点全攻略
物联网·mqtt·kafka·freertos·时序数据库·tdengine·aiot