在NUCLEO-G431RB上实现编码器模拟与T法测速的全闭环验证

当我们需要验证电机控制算法时,编码器信号的获取往往是个难题——要么没有实体编码器,要么测试环境受限。本文将展示如何利用STM32G4系列开发板的定时器高级功能,构建一个完整的虚拟编码器测试系统。这个方案特别适合项目初期验证或教学演示场景,仅需一块NUCLEO-G431RB开发板就能完成从信号生成到速度测量的全流程。

1. 环境搭建与硬件配置

在开始之前,我们需要明确几个关键点:STM32G4的定时器具有独特的"非对称PWM模式"(Asymmetric PWM Mode),这将成为我们模拟正交编码器信号的核心。同时,Timer之间的内部触发连接特性,使得信号生成和采集能够实现硬件级同步,大大提高了测试的可靠性。

开发板的基础连接非常简单:

  • 无需任何外接硬件
  • 仅需USB数据线供电
  • 所有信号通过内部定时器互联完成

核心定时器分工

  • TIM1 :配置为非对称PWM模式,生成正交编码脉冲
  • TIM3 :工作在编码器接口模式,解码虚拟编码器信号
  • TIM2 :用于捕获脉冲宽度,实现T法测速

提示:虽然使用硬件定时器互联,但建议仍然在CubeMX中配置好相关GPIO,方便后续用逻辑分析仪观察信号。

2. CubeMX的定时器配置详解

2.1 TIM1的非对称PWM配置

TIM1需要产生两路相位差90度的PWM信号来模拟正交编码器输出。在CubeMX中按照以下参数配置:

参数 说明
Clock Source Internal 使用内部时钟源
Counter Mode Up 向上计数模式
Prescaler 0 不分频
Auto-Reload 可变 根据目标速度动态调整
PWM Mode PWM Mode 1 标准PWM模式
Pulse 可变 控制占空比
Asymmetric PWM Enabled 启用非对称模式

关键配置步骤:

  1. 在"Parameter Settings"中启用Asymmetric PWM模式
  2. 配置CH1和CH2为PWM Generation
  3. 设置CH3和CH4的比较值为ARR值的一半
  4. 在"NVIC Settings"中使能更新中断
// TIM1初始化代码片段
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1000;  // 初始值,后续动态调整
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

2.2 TIM3的编码器接口配置

TIM3需要配置为标准正交编码器接口模式,用于解码TIM1生成的模拟信号:

参数 说明
Encoder Mode Encoder Mode TI1 and TI2 双通道编码器模式
IC1/IC2 Polarity Rising 上升沿触发
IC1/IC2 Filter 6 适度的输入滤波
Slave Mode Disabled 独立工作模式
// TIM3编码器模式配置
TIM_Encoder_InitTypeDef sConfig = {0};
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 6;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 6;
HAL_TIM_Encoder_Init(&htim3, &sConfig);

2.3 TIM2的输入捕获配置

TIM2将用于测量脉冲宽度,这是T法测速的核心:

参数 说明
Clock Source Internal 内部时钟
Trigger Source ITR2 使用TIM3的TRGO作为触发源
Input Capture Direct 直接捕获模式
Prescaler 0 无分频
Counter Period 0xFFFFFFFF 32位计数器
Capture Compare Enabled 使能捕获功能
// TIM2捕获配置
TIM_IC_InitTypeDef sConfigIC;
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);

3. 动态速度模拟与算法实现

3.1 虚拟编码器速度控制

我们需要一个机制来动态调整TIM1的输出频率,模拟不同转速下的编码器信号。这里采用在主循环中定期更新目标速度的方式:

// 全局变量定义
static int32_t targetSpeedRPM = 0;  // 目标转速(RPM)
static uint32_t speedUpdateTime = 0;  // 速度更新时间戳

// 在主循环中调用
void SpeedControl_Update(void)
{
    if(HAL_GetTick() - speedUpdateTime > 10)  // 每10ms更新一次
    {
        // 简单的斜坡函数,实际项目可以替换为更复杂的控制
        if(targetSpeedRPM < 3000) {
            targetSpeedRPM += 10;
        }
        speedUpdateTime = HAL_GetTick();
    }
}

3.2 T法测速的核心算法

T法测速的核心是通过测量脉冲宽度来计算转速。在STM32G4上,我们可以利用32位TIM2来获得高精度的时基测量:

// 获取速度(RPM)
float Get_Speed_RPM(void)
{
    static uint32_t lastCapture = 0;
    uint32_t currentCapture = TIM2->CCR1;
    uint32_t pulseWidth = currentCapture - lastCapture;
    lastCapture = currentCapture;
    
    if(pulseWidth == 0) return 0.0f;  // 防止除以零
    
    // 计算RPM: (时钟频率/脉冲宽度) * (60/PPR)
    float speed = (float)(SystemCoreClock) / (float)pulseWidth;
    speed *= (60.0f / ENCODER_PPR);  // ENCODER_PPR为编码器线数
    
    return speed;
}

3.3 数据可视化与调试

为了验证算法效果,我们可以通过串口输出速度数据:

void Debug_Print_Speed(void)
{
    static uint32_t printTime = 0;
    if(HAL_GetTick() - printTime > 100)  // 每100ms打印一次
    {
        float speed = Get_Speed_RPM();
        printf("Speed: %.2f RPM\r\n", speed);
        printTime = HAL_GetTick();
    }
}

4. 系统优化与误差分析

4.1 定时器参数优化策略

为了在不同速度段都能获得最佳测量效果,我们需要动态调整TIM1的参数:

速度范围(RPM) 优化策略 效果
< 10 增加TIM1预分频 扩展测量范围
10-100 使用32位计数器 提高分辨率
> 100 减少捕获滤波 提高响应速度
void TIM1_Update_Settings(int32_t rpm)
{
    uint32_t pulsesPerSec = rpm * ENCODER_PPR / 60;
    uint32_t arrValue = (SystemCoreClock / 2) / pulsesPerSec;
    
    if(arrValue > 0xFFFF) {
        uint16_t prescaler = (arrValue >> 16) + 1;
        __HAL_TIM_SET_PRESCALER(&htim1, prescaler);
        __HAL_TIM_SET_AUTORELOAD(&htim1, arrValue/prescaler);
    } else {
        __HAL_TIM_SET_PRESCALER(&htim1, 0);
        __HAL_TIM_SET_AUTORELOAD(&htim1, arrValue);
    }
}

4.2 常见问题排查指南

在实际测试中可能会遇到以下典型问题:

  • 信号抖动严重 :增加TIM3输入捕获滤波值(ICFilter)
  • 低速测量不准 :检查TIM2是否配置为32位模式,适当增加测量时间窗口
  • 高速时丢失脉冲 :降低TIM1的ARR值,检查是否有中断堆积
  • 数据跳变过大 :添加简单的滑动平均滤波算法

4.3 性能对比测试

我们在不同速度段对M法和T法进行了对比测试:

速度(RPM) M法误差(%) T法误差(%) 优势方法
0.5 ±50 ±5 T法
5 ±20 ±2 T法
50 ±5 ±1 相当
500 ±1 ±2 M法
5000 ±0.5 ±5 M法

从测试数据可以看出:

  • 低速段(T法优势区):T法精度明显高于M法
  • 中速段(过渡区):两种方法性能相当
  • 高速段(M法优势区):M法更稳定

5. 进阶应用与扩展思路

5.1 多速度段混合测速算法

结合M法和T法的优势,我们可以实现一个自适应混合算法:

float Get_Hybrid_Speed(void)
{
    float m_speed = Get_Method_Speed();  // M法速度
    float t_speed = Get_T_Speed();       // T法速度
    
    if(m_speed < 50.0f) {
        return t_speed;  // 低速使用T法
    } else if(m_speed < 100.0f) {
        return (0.7f * t_speed + 0.3f * m_speed);  // 过渡区混合
    } else {
        return m_speed;  // 高速使用M法
    }
}

5.2 利用DMA减轻CPU负担

对于高性能应用,可以使用DMA来搬运捕获数据:

  1. 配置TIM2的捕获事件触发DMA
  2. 设置循环缓冲区存储捕获值
  3. 在DMA半/完成中断中处理数据
// DMA配置示例
hdma_tim2_ch1.Instance = DMA1_Channel1;
hdma_tim2_ch1.Init.Request = DMA_REQUEST_TIM2_CH1;
hdma_tim2_ch1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_tim2_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim2_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim2_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim2_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_tim2_ch1.Init.Mode = DMA_CIRCULAR;
hdma_tim2_ch1.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_tim2_ch1);

5.3 扩展到其他STM32系列

虽然本文基于STM32G4,但类似思路可以应用于其他系列:

  • F4系列 :使用TIM1+TIM2+TIM3组合
  • H7系列 :利用更高性能定时器和硬件滤波器
  • F0系列 :需要简化方案,可能牺牲部分性能
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐