保姆级教程:在NUCLEO-G431RB上模拟编码器,自闭环测试T法测速代码
在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 | 启用非对称模式 |
关键配置步骤:
- 在"Parameter Settings"中启用Asymmetric PWM模式
- 配置CH1和CH2为PWM Generation
- 设置CH3和CH4的比较值为ARR值的一半
- 在"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来搬运捕获数据:
- 配置TIM2的捕获事件触发DMA
- 设置循环缓冲区存储捕获值
- 在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系列 :需要简化方案,可能牺牲部分性能
更多推荐

所有评论(0)