八、在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);

效果如下:

相关推荐
zhangzhangkeji3 天前
FreeRTos笔记(12c):从事件链表里删除节点 xTaskRemoveFromEventList、
freertos
La Pulga4 天前
【FreeRTOS源码学习笔记】任务延时列表
笔记·mcu·学习·嵌入式·freertos·实时操作系统
橘色的喵4 天前
coact:让嵌入式事件处理更可控
freertos·rt-thread·事件驱动·coact
人邮异步社区5 天前
学习FreeRTOS推荐哪本书?
freertos·rtos·嵌入式固件工程师
La Pulga7 天前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga8 天前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga10 天前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
La Pulga10 天前
【FreeRTOS源码学习笔记】任务创建
笔记·stm32·mcu·学习·嵌入式·freertos·实时操作系统
天吾cc15 天前
FreeRTOS 事件标志组
freertos
编码追梦人17 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计