基于 GD32F470 的编码器接口应用:实现防抖、方向校验、溢出处理与位置速度计算

以下是关于如何使用GD32F4xx系列微控制器的定时器实现编码器功能的总结,涵盖了所有关键细节和步骤:
目标
使用GD32F4xx定时器实现旋转编码器的计数、方向检测、速度计算、位置计算,并通过定时器中断实现实时更新。

关键步骤

  1. 定时器初始化为编码器模式
    GPIO配置:
    将编码器的A和B信号连接到定时器的输入通道对应的GPIO引脚(如PA8和PA9)。
    配置这些GPIO引脚为复用输入模式,并启用内部上拉/下拉电阻(根据实际硬件需求)。
    启用定时器的GPIO重映射(如果需要)。
    定时器基本参数配置:
    初始化定时器的基本参数,包括预分频器、计数周期(自动重载值ARR)、计数方向(向上计数)等。
    设置定时器为边沿对齐模式。
    从模式配置:
    配置定时器为编码器模式(如TIMER_ENCODER_MODE0)。
    选择输入触发源(如TIMER_SMCFG_TRGSEL_CI0FE0)。
    配置外部触发输入的相位、滤波等参数。
    输入捕获通道配置:
    配置输入捕获通道(如CH0和CH1)的极性(上升沿、下降沿或双边沿)。
    设置输入捕获的预分频器和滤波器参数。

  2. 定时器中断配置
    启用定时器中断:
    启用定时器的更新中断(TIMER_INT_UP),以便在定时器计数值更新时触发中断。
    配置中断优先级,并通过NVIC使能定时器中断。
    中断服务程序(ISR):
    在定时器更新中断服务程序中,检查是否是更新中断。
    清除中断标志后调用encoder_update函数,处理编码器的状态更新。

  3. 编码器状态更新函数
    读取当前计数值:
    从定时器的计数寄存器(CNT)读取当前计数值。
    计算计数变化量(Delta):
    计算当前计数值与上次采样计数值的差值(delta)。
    修正溢出/下溢情况,确保delta值正确。
    软件防抖处理:
    如果delta值异常跳变(大于或小于某个阈值),丢弃本次采样数据,避免误判。
    更新累计计数:
    将delta值累加到总计数值(encoder_total_count)。
    计算速度:
    根据delta值和采样周期(SAMPLE_PERIOD_MS),计算编码器的速度(脉冲/秒)。
    限制速度范围,避免超出最大速度限制(MAX_SPEED)。
    方向判断:
    根据delta值的正负,判断编码器的旋转方向(正向或反向)。
    计算位置:
    将累计计数值转换为角度(encoder_position_deg),根据每转脉冲数(ENCODER_PPR)计算。
    限制位置范围,避免超出最大位置限制(MAX_POSITION)。
    保存本次采样计数值:
    将当前计数值保存为下次采样的参考值。

  4. 主函数
    在主函数中初始化定时器为编码器模式。
    主循环中可以执行其他任务,编码器状态更新由定时器中断触发。
    关键代码片段
    定时器初始化

void timer_encoder_init(uint32_t timer_periph)
{
    // GPIO配置
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_TIMER1);
    gpio_init(GPIOA, GPIO_MODE_AF, GPIO_OSPEED_50MHZ, GPIO_PIN_8 | GPIO_PIN_9);
    gpio_pin_remap_config(GPIO_PARTIAL_REMAP_TIMER1, ENABLE);

    // 定时器基本参数配置
    timer_parameter_struct timer_initpara;
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler = 0;
    timer_initpara.period = ENCODER_ARR;
    timer_initpara.clockdivision = 0;
    timer_initpara.counterdirection = TIMER_COUNTER_UP;
    timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
    timer_initpara.repetitioncounter = 0;
    timer_init(timer_periph, &timer_initpara);

    // 从模式配置
    timer_slave_mode_select(timer_periph, TIMER_ENCODER_MODE0);
    timer_input_trigger_source_select(timer_periph, TIMER_SMCFG_TRGSEL_CI0FE0);
    timer_external_trigger_config(timer_periph, TIMER_EXT_TRI_PSC_OFF, TIMER_ETP_RISING, 0);

    // 输入捕获通道配置
    timer_ic_parameter_struct icpara;
    timer_channel_input_struct_para_init(&icpara);
    icpara.icpolarity = TIMER_IC_POLARITY_RISING;
    icpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    icpara.icprescaler = TIMER_IC_PSC_DIV1;
    icpara.icfilter = 0;
    timer_input_capture_config(timer_periph, TIMER_CH_0, &icpara);
    timer_input_capture_config(timer_periph, TIMER_CH_1, &icpara);

    // 启用定时器更新中断
    timer_interrupt_enable(timer_periph, TIMER_INT_UP);
    nvic_irq_enable(TIMER1_UP_IRQn, 0, 0);
}

中断服务程序

void TIMER1_UP_IRQHandler(void)
{
    if (timer_interrupt_flag_get(TIMER1, TIMER_INT_FLAG_UP) != RESET)
    {
        timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP);
        encoder_update();
    }
}

编码器状态更新

void encoder_update(void)
{
    int32_t current_count = TIMER1->CNT;
    int32_t delta = current_count - encoder_last_count;

    if (delta > (ENCODER_ARR / 2))
    {
        delta -= (ENCODER_ARR + 1);
    }
    else if (delta < -(ENCODER_ARR / 2))
    {
        delta += (ENCODER_ARR + 1);
    }

    if (delta > (ENCODER_PPR / 2) || delta < -(ENCODER_PPR / 2))
    {
        delta = 0;
    }

    encoder_total_count += delta;
    encoder_speed = delta * (1000 / SAMPLE_PERIOD_MS);

    if (encoder_speed > MAX_SPEED)
    {
        encoder_speed = MAX_SPEED;
    }
    else if (encoder_speed < -MAX_SPEED)
    {
        encoder_speed = -MAX_SPEED;
    }

    if (delta > 0)
    {
        encoder_direction = 1;
    }
    else if (delta < 0)
    {
        encoder_direction = -1;
    }
    else
    {
        encoder_direction = 0;
    }

    encoder_position_deg = ((float)encoder_total_count * 360.0f) / ENCODER_PPR;

    if (encoder_position_deg >= MAX_POSITION)
    {
        encoder_position_deg -= MAX_POSITION;
    }
    else if (encoder_position_deg < 0)
    {
        encoder_position_deg += MAX_POSITION;
    }

    encoder_last_count = current_count;
}

注意事项

中断优先级:根据实际需求调整中断优先级,确保不会影响其他高优先级任务。
定时器周期:根据实际需求调整定时器的自动重载值(ARR)和采样周期(SAMPLE_PERIOD_MS)。
防抖处理:在encoder_update函数中加入了简单的防抖逻辑,可以根据实际需求进一步优化。
硬件连接:确保编码器的A和B信号正确连接到定时器的输入通道对应的GPIO引脚。

通过以上步骤和代码,可以实现使用GD32F4xx定时器对旋转编码器的实时监控和处理。

在GD32F4xx系列微控制器中,配置定时器为编码器模式时,仅配置输入捕获是不够的。编码器模式需要结合特定的从模式(Slave Mode)配置,才能实现对旋转编码器信号的正确处理。以下是为什么仅配置输入捕获不够,以及如何完整配置定时器为编码器模式的详细说明。

为什么仅配置输入捕获不够?

输入捕获的作用:
输入捕获功能主要用于捕获外部信号的边沿(上升沿、下降沿或双边沿),并将这些边沿的时间戳记录到捕获寄存器中。
输入捕获功能本身不支持自动根据两路信号的相位关系判断旋转方向和计数。

编码器模式的特殊需求:
旋转编码器通常有两路输出信号(A和B),这两路信号的相位差为90°。
为了实现编码器功能,需要定时器硬件自动检测A和B信号的边沿变化,并根据它们的相位关系判断旋转方向(正向或反向)。
定时器需要在检测到A和B信号的边沿变化时,自动更新计数器的值(向上计数或向下计数)。

完整配置定时器为编码器模式

为了实现编码器功能,需要同时配置以下内容:

GPIO配置:

配置编码器的A和B信号对应的GPIO引脚为复用输入模式。
启用内部上拉或下拉电阻(根据实际硬件需求)。

定时器基本参数配置:
初始化定时器的基本参数(如预分频器、计数周期等)。
配置定时器为向上计数或向下计数模式。

从模式配置:

配置定时器为编码器模式(TIMER_SLAVE_MODE_EXTERNAL0 或其他编码器模式)。
配置输入触发源(如TIMER_SMCFG_TRGSEL_CI0FE0)。
配置外部触发输入(如相位、滤波等)。

输入捕获配置:
配置输入捕获通道(如CH0和CH1)。
配置输入捕获的极性(上升沿、下降沿或双边沿)。
配置输入捕获的预分频器和滤波器。

你可能感兴趣的:(c语言,stm32)