4、EPS关键算法:扭矩信号处理与滤波、电机电流环PID控制、车速感应式助力曲线设计、故障诊断与容错策略

各位同学,欢迎来到第四章。这一章,咱们要啃的可是EPS控制器的“灵魂”——关键算法。

说实话,很多刚入行的工程师觉得硬件设计最难,画板子、选芯片。但以我多年的经验来看,真正决定一台EPS手感好坏、安不安全、耐不耐用的,恰恰是软件算法。你硬件再牛,算法写得稀烂,那方向盘转起来就跟开拖拉机似的,又涩又抖。

这一章内容比较多,我把它拆成四个核心模块:扭矩信号处理、电流环PID、助力曲线,还有故障诊断。咱们一个一个来。

4.1 扭矩信号处理与滤波

EPS控制器的第一个输入信号,就是驾驶员施加在方向盘上的扭矩。这个信号来自扭矩传感器,通常是模拟电压或者数字SPI接口。

但问题来了——传感器信号天生就带着噪声。发动机的振动、路面颠簸、电磁干扰,都会叠加在扭矩信号上。如果你直接拿这个带毛刺的信号去算助力,那电机就会跟着一起抖,方向盘手感会非常糟糕。

所以,滤波是第一道关。

我个人习惯,在扭矩信号处理上采用两级滤波策略:

  • 硬件滤波:在传感器输出端加一个RC低通滤波器,截止频率通常设在100Hz左右。这能干掉大部分高频噪声。
  • 软件滤波:进入MCU的ADC之后,再做一次软件滤波。我常用的是滑动平均滤波和限幅滤波的组合。

这里给出一段我常用的滑动平均滤波代码,窗口大小我一般取8或16,兼顾响应速度和平滑度:

#define FILTER_WINDOW_SIZE 8
static uint16_t torque_buffer[FILTER_WINDOW_SIZE];
static uint8_t buffer_index = 0;

uint16_t torque_filter(uint16_t raw_adc_value)
{
    uint32_t sum = 0;
    uint8_t i;

    // 存入最新值
    torque_buffer[buffer_index] = raw_adc_value;
    buffer_index = (buffer_index + 1) % FILTER_WINDOW_SIZE;

    // 求和
    for (i = 0; i < FILTER_WINDOW_SIZE; i++)
    {
        sum += torque_buffer[i];
    }

    // 返回平均值
    return (uint16_t)(sum / FILTER_WINDOW_SIZE);
}
我的经验: 滑动平均滤波的窗口大小不是越大越好。窗口太大,信号延迟就大,驾驶员会感觉助力“慢半拍”。我一般控制在8-16个采样点,采样频率1kHz的话,延迟也就8-16ms,人基本感觉不到。

除了滑动平均,我还会加一个限幅滤波。说白了,就是判断相邻两次采样值的差值。如果差值超过一个阈值(比如50个ADC码值),我就认为这是干扰,直接丢弃,用上一次的值代替。

为什么会这样?因为驾驶员的手力变化再快,也不可能在1ms内突变50个码值。这招对付尖峰脉冲特别有效。

4.2 电机电流环PID控制

扭矩信号处理好了,接下来就要算助力了。但助力最终是通过电机电流来实现的。你给电机发一个目标电流值,电机就得老老实实输出这个电流。这就是电流环PID的活。

EPS电机通常是三相无刷直流电机(BLDC)或者永磁同步电机(PMSM)。电流环控制的核心,就是让实际电流快速、准确地跟踪目标电流。

我一般用PI控制器,不加微分项。为什么?因为电流环的响应速度要求很高,微分项容易引入高频噪声,反而让系统不稳定。

PI控制器的离散化公式很简单:

u(k) = Kp * e(k) + Ki * sum(e(k))

其中,e(k)是目标电流与实际电流的误差,u(k)是输出的PWM占空比。

这里我贴一段实际项目中的PI代码,带积分限幅和输出限幅:

typedef struct {
    float Kp;
    float Ki;
    float integral;
    float integral_max;  // 积分限幅
    float output_max;    // 输出限幅
} PI_Controller;

float pi_update(PI_Controller *pi, float target, float actual)
{
    float error = target - actual;
    float output;

    // 积分项,带限幅
    pi->integral += pi->Ki * error;
    if (pi->integral > pi->integral_max)
        pi->integral = pi->integral_max;
    else if (pi->integral < -pi->integral_max)
        pi->integral = -pi->integral_max;

    // 比例+积分
    output = pi->Kp * error + pi->integral;

    // 输出限幅
    if (output > pi->output_max)
        output = pi->output_max;
    else if (output < -pi->output_max)
        output = -pi->output_max;

    return output;
}
注意: 积分限幅非常重要!我曾经在一个项目中忘了加积分限幅,结果电机堵转时积分项一直累加,最后输出饱和,电机直接“飞车”。嗯,从那以后,我每次写PI都会先检查积分限幅。

电流环的采样频率,我建议至少10kHz,最好是20kHz。这样PWM频率(通常20kHz)和电流采样能同步,控制效果最好。

4.3 车速感应式助力曲线设计

助力曲线,说白了就是“给多少力”的问题。低速时,方向盘要轻,方便泊车;高速时,方向盘要重,保证稳定。这就是车速感应式助力。

我习惯用二维查表法来实现。横轴是方向盘扭矩,纵轴是车速,表格里存的是目标助力电流。

一个典型的助力曲线长这样:

车速 (km/h) 扭矩 0 Nm 扭矩 2 Nm 扭矩 4 Nm 扭矩 6 Nm 扭矩 8 Nm
0 0 A 10 A 20 A 30 A 35 A
30 0 A 8 A 16 A 24 A 28 A
60 0 A 5 A 10 A 15 A 18 A
100 0 A 2 A 4 A 6 A 8 A

你看,车速越高,同样扭矩下给的助力电流越小。这就是“高速稳重”的来源。

实际应用中,表格里的数据不是随便填的。你需要根据整车匹配标定。我记得有一次,我们给一款SUV做标定,低速助力给得太大了,结果方向盘轻得像玩具,完全没有路感。后来反复调整曲线斜率,才找到平衡点。

查表之后,还需要做线性插值。因为车速和扭矩不一定是表格里的整数点。插值算法很简单,我就不贴代码了,但要注意:插值计算要快,最好用定点数,别用浮点,否则CPU扛不住。

4.4 故障诊断与容错策略

最后,也是最重要的——故障诊断。EPS是安全件,一旦失效,驾驶员可能失去转向能力。所以,你必须提前想到“如果这个坏了怎么办”。

我一般把故障分为三级:

  • 一级故障(致命):比如扭矩传感器完全失效、电机短路、MCU死机。此时必须立即切断助力,并点亮故障灯,提醒驾驶员。
  • 二级故障(严重):比如电流传感器偏差过大、温度过高。此时可以降级运行,比如限制最大助力电流,或者只提供50%助力。
  • 三级故障(轻微):比如CAN通信偶尔丢帧、ADC采样值轻微漂移。此时可以尝试恢复,或者记录故障码,下次上电再处理。

容错策略的核心思想是:宁可没助力,也不能给错误助力。你想想看,如果传感器坏了,电机却还在乱转,那后果不堪设想。

我举一个具体的例子——扭矩传感器冗余设计。很多EPS会用两个扭矩传感器(主传感器和副传感器)。正常工作时,取两个传感器的平均值。如果其中一个失效,就切换到另一个。如果两个都失效,那就立即切断助力。

诊断逻辑大概是这样:

if (abs(sensor1 - sensor2) > THRESHOLD)
{
    // 两个传感器偏差过大,判断哪个失效
    if (sensor1 超出合理范围)
    {
        use_sensor = 2;  // 切换到传感器2
        set_fault_code(FAULT_TORQUE_SENSOR1);
    }
    else if (sensor2 超出合理范围)
    {
        use_sensor = 1;
        set_fault_code(FAULT_TORQUE_SENSOR2);
    }
    else
    {
        // 两个都正常但偏差大,可能是干扰,暂时用上一次值
        use_sensor = LAST_VALID;
    }
}
核心原则: 故障诊断不是“出了问题再处理”,而是“时刻在检查”。每个控制周期,都要跑一遍诊断逻辑。我习惯把诊断放在主循环的最前面,优先级最高。

另外,看门狗是必须的。万一程序跑飞了,看门狗能复位MCU。但要注意,复位后不能立即恢复助力,要先做一次自检,确认所有传感器都正常,才能重新上电。

好了,这一章的内容就到这里。扭矩滤波、电流环PID、助力曲线、故障诊断,这四个模块构成了EPS算法的骨架。下一章,咱们会把这些模块整合起来,讲一讲整个软件架构怎么搭。