定时器编码器程序
代码来源工程:Intelligent_Agricultural_Equipment_Innovation_Competition(
Mycode/encoder.c)
TIM 编码器模式读取转速(差分法)
TIM 配置为 TIM_ENCODERMODE_TI12(双通道四倍频),预分频 = 0,周期 = 65535。每个采样周期读取计数器当前值,与上次差分得到脉冲增量,再换算为 RPM。
16bit 计数器用 short 类型做差分,溢出时自动回绕,无需额外处理。
/* 关键参数(按实际电机修改) */
#define ENCODER_LINES 11 /* 编码器线数 */
#define ENCODER_MULTI 4 /* 倍频(TI12 模式固定为 4) */
#define GEAR_RATIO 14 /* 减速比 */
#define SAMPLE_MS 10 /* 采样周期 ms */
/* 每圈脉冲数 */
#define PPR (ENCODER_LINES * ENCODER_MULTI * GEAR_RATIO) /* = 616 */
/* RPM 换算系数:60 / (SAMPLE_MS/1000) / PPR */
#define RPM_COEF (60000.0f / SAMPLE_MS / PPR) /* = 6000/616 ≈ 9.74 */
static short last_cnt = 0;
/**
* @brief 单路编码器读取转速(在固定周期定时器中断里调用)
* @return 转速 RPM(正转为正)
*/
float encoder_read_rpm(TIM_HandleTypeDef *htim)
{
short cur = (short)__HAL_TIM_GetCounter(htim); /* 读当前值 */
short diff = cur - last_cnt; /* short 差分,自动处理溢出 */
last_cnt = cur;
return (float)diff * RPM_COEF;
}
/* 使用示例 */
/*
// 在 10ms 定时中断中调用:
float rpm = encoder_read_rpm(&htim1);
*/四路编码器读取(多路差分)
四路电机各占一个 TIM(TIM1/TIM3/TIM4 为 16bit,TIM5 为 32bit)。安装方向相反的轴读取后取负,统一正方向。
typedef struct {
float speedA; /* 左前,RPM */
float speedB; /* 右前,RPM */
float speedC; /* 左后,RPM */
float speedD; /* 右后,RPM */
} EncoderSpeed_t;
static short last16[3] = {0}; /* TIM1/3/4 上次计数 */
static int32_t last32 = 0; /* TIM5 上次计数 */
/**
* @brief 四路编码器读取,在 10ms 定时中断中调用
* @param out 输出四路转速(RPM)
*/
void encoder_calc(EncoderSpeed_t *out)
{
/* 1. 读取当前计数值 */
short cur16[3];
int32_t cur32;
cur16[0] = (short) __HAL_TIM_GetCounter(&htim1);
cur16[1] = (short) __HAL_TIM_GetCounter(&htim3);
cur16[2] = (short) __HAL_TIM_GetCounter(&htim4);
cur32 = (int32_t)__HAL_TIM_GetCounter(&htim5);
/* 2. 差分:16bit 用 short 自动回绕,32bit 显式处理 */
short diff16[3];
int32_t diff32;
for (int i = 0; i < 3; i++) {
diff16[i] = cur16[i] - last16[i];
last16[i] = cur16[i];
}
diff32 = cur32 - last32;
last32 = cur32;
/* 32bit 溢出保护(正常运行基本不触发) */
if (diff32 > 2147483647) diff32 -= (int64_t)4294967296;
if (diff32 < -2147483648) diff32 += (int64_t)4294967296;
/* 3. 换算 RPM,B/C/D 安装方向相反取负 */
out->speedA = (float)diff16[0] * RPM_COEF;
out->speedB = -(float)diff16[1] * RPM_COEF;
out->speedC = -(float)diff16[2] * RPM_COEF;
out->speedD = -(float)diff32 * RPM_COEF;
}
/* 使用示例 */
/*
EncoderSpeed_t raw_speed;
// 10ms 定时中断中:
encoder_calc(&raw_speed);
// raw_speed.speedA ~ speedD 即为四路原始转速
*/三级滤波(去极值 + 幅值限制 + 斜率限制)
与超声波滤波同一套算法,参数按电机转速场景调整(窗口 10,去极值 2,幅值上限 500 rpm,斜率限制 30 rpm/次)。
typedef struct {
uint8_t count; /* 环形缓冲写入位置 */
uint8_t complete; /* 缓冲区已满标志 */
float last_valid; /* 上次有效输出 */
float buf[32]; /* 历史数据缓冲 */
} EncoderFilter_t;
/**
* @brief 三级滤波
*
* @param f 滤波器状态
* @param new_val 本次原始转速(RPM)
* @param win 滤波窗口大小
* @param remove_n 每端去极值个数
* @param max_amp 幅值上限(RPM),超出则保持上次有效值
* @param max_slope 单周期最大变化量(RPM)
* @param out 输出滤波后转速
*/
void encoder_filter(EncoderFilter_t *f, float new_val,
uint8_t win, uint8_t remove_n,
float max_amp, float max_slope,
float *out)
{
/* 1. 写入环形缓冲 */
f->buf[f->count % win] = new_val;
f->count++;
if (f->count >= win) f->complete = 1;
uint8_t n = f->complete ? win : f->count;
/* 2. 初始化阶段直接透传 */
if (!f->complete) { *out = f->last_valid = new_val; return; }
/* 3. 复制并冒泡排序 */
float tmp[32];
uint8_t idx = f->count % win;
for (uint8_t i = 0; i < n; i++) {
tmp[i] = f->buf[idx];
if (++idx >= win) idx = 0;
}
for (uint8_t i = 0; i < n - 1; i++)
for (uint8_t j = 0; j < n - 1 - i; j++)
if (tmp[j] > tmp[j+1]) { float t = tmp[j]; tmp[j] = tmp[j+1]; tmp[j+1] = t; }
/* 4. 去极值求均值 */
float sum = 0;
uint8_t valid_n = n - 2 * remove_n;
if (valid_n < 1) valid_n = 1;
for (uint8_t i = remove_n; i < n - remove_n; i++) sum += tmp[i];
float avg = sum / valid_n;
/* 5. 幅值限制 */
if (avg > max_amp || avg < -max_amp) { *out = f->last_valid; return; }
/* 6. 斜率限制 */
float diff = avg - f->last_valid;
if (diff > max_slope) avg = f->last_valid + max_slope;
if (diff < -max_slope) avg = f->last_valid - max_slope;
f->last_valid = avg;
*out = avg;
}
/* 使用示例 */
/*
static EncoderFilter_t filt_A = {0};
float speed_A_filtered;
// 每次 encoder_calc 后调用:
encoder_filter(&filt_A, raw_speed.speedA,
10, 2, 500.0f, 30.0f, &speed_A_filtered);
// 四路各维护一个 EncoderFilter_t 实例
*/