跳转到主要内容
杨月昌的硬件博客
返回

遗传算法优化 PID 控制器的 C 语言实现

1. 为什么使用遗传算法整定 PID?

在复杂的非线性、大滞后硬件系统中,传统的齐格勒-尼科尔斯(Z-N)法往往难以获取理想的参数。遗传算法(Genetic Algorithm)通过模拟“物竞天择”的生物进化过程,能够有效跳出局部最优,自动搜索出使系统性能指标最优的全局解。

核心进化逻辑:

  1. 个体编码:将 (Kp, Ki, Kd) 三个参数视为个体的染色体,通常采用实数编码。
  2. 适应度评估:这是算法的“灵魂”,决定了什么是“好”的 PID 参数组合。
  3. 算子操作
    • 选择(Selection):保留优良基因。
    • 交叉(Crossover):融合父母代优势,探索新搜索区域。
    • 变异(Mutation):引入随机扰动,维持种群多样性,防止早熟收敛。

2. 适应度函数设计:引入 ITAE 指标

在原始博文中,适应度仅根据绝对误差计算。在工业控制领域,我们推荐采用 ITAE(时间乘绝对误差积分) 指标。ITAE 对系统后期的残余误差惩罚更重,有助于获得更好的快速性、无超调和平稳性。

对应的适应度函数可设计为: [ fitness = \frac{1}{ITAE + \epsilon} ] 其中 ( ITAE = \int_0^{t_{sim}} t |e(t)| , dt ),(\epsilon) 是一个很小的正数(如 0.0001),用于防止除以零。


3. C 语言代码实现

以下是优化后的 C 代码,增强了结构体、边界约束、交叉/变异算子的完整性和精英保留策略。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#define POP_SIZE 100        // 种群规模,可根据嵌入式资源调整
#define MAX_GEN 200         // 最大迭代代数
#define MUTATION_RATE 0.05  // 变异率建议 0.01~0.1
#define CROSS_RATE 0.8      // 交叉率

typedef struct {
    float Kp, Ki, Kd;
    float fitness;
} Individual;

// 随机数生成:[min, max]
float rand_range(float min, float max) {
    return min + ((float)rand() / RAND_MAX) * (max - min);
}

// 参数钳位,防止出现物理上不可实现的负值
void clamp_params(Individual *ind) {
    if (ind->Kp < 0.0f) ind->Kp = 0.0f;
    if (ind->Ki < 0.0f) ind->Ki = 0.0f;
    if (ind->Kd < 0.0f) ind->Kd = 0.0f;
}

// 核心:模拟系统响应并计算适应度 (基于 ITAE)
// 注意:在实际硬件/嵌入式应用中,此函数应替换为真实的系统仿真或实时误差采集
// 示例中用简单距离演示,实际项目中必须替换
float evaluate_fitness(Individual *ind) {
    // TODO: 这里实现实际的系统响应仿真,计算 e(t) = r(t) - y(t)
    // 以下为演示逻辑,实际项目中必须替换
    float total_error = fabs(ind->Kp - 4.5f) + fabs(ind->Ki - 0.2f) + fabs(ind->Kd - 1.1f); 
    return 1.0f / (total_error + 0.0001f);
}

// 轮盘赌选择
int roulette_wheel_selection(Individual *pop, float total_f) {
    float r = rand_range(0, total_f);
    float sum = 0;
    for (int i = 0; i < POP_SIZE; i++) {
        sum += pop[i].fitness;
        if (sum >= r) return i;
    }
    return POP_SIZE - 1;
}

// 算法主循环
void run_ga() {
    Individual population[POP_SIZE];
    Individual next_gen[POP_SIZE];

    srand(time(NULL));  // 初始化随机种子

    // 1. 初始化种群
    for (int i = 0; i < POP_SIZE; i++) {
        population[i].Kp = rand_range(0.1f, 20.0f);  // 给予合理初始范围
        population[i].Ki = rand_range(0.0f, 10.0f);
        population[i].Kd = rand_range(0.0f, 5.0f);
        clamp_params(&population[i]);
    }

    for (int gen = 0; gen < MAX_GEN; gen++) {
        float total_f = 0.0f;
        int best_idx = 0;

        // 2. 适应度评估
        for (int i = 0; i < POP_SIZE; i++) {
            population[i].fitness = evaluate_fitness(&population[i]);
            total_f += population[i].fitness;
            if (population[i].fitness > population[best_idx].fitness) {
                best_idx = i;
            }
        }

        if (gen % 20 == 0 || gen == MAX_GEN - 1) {
            Individual *best = &population[best_idx];
            printf("Gen %d: Best [P=%.3f I=%.3f D=%.3f] Fit=%.4f\n", 
                   gen, best->Kp, best->Ki, best->Kd, best->fitness);
        }

        // 3. 精英保留策略
        next_gen[0] = population[best_idx];

        // 4. 产生下一代
        for (int i = 1; i < POP_SIZE; i += 2) {
            int p1 = roulette_wheel_selection(population, total_f);
            int p2 = roulette_wheel_selection(population, total_f);

            Individual child1 = population[p1];
            Individual child2 = population[p2];

            // 算术交叉
            if (rand_range(0, 1) < CROSS_RATE) {
                float alpha = rand_range(0, 1);
                child1.Kp = alpha * population[p1].Kp + (1 - alpha) * population[p2].Kp;
                child1.Ki = alpha * population[p1].Ki + (1 - alpha) * population[p2].Ki;
                child1.Kd = alpha * population[p1].Kd + (1 - alpha) * population[p2].Kd;

                child2.Kp = alpha * population[p2].Kp + (1 - alpha) * population[p1].Kp;
                child2.Ki = alpha * population[p2].Ki + (1 - alpha) * population[p1].Ki;
                child2.Kd = alpha * population[p2].Kd + (1 - alpha) * population[p1].Kd;
            }

            // 变异
            if (rand_range(0, 1) < MUTATION_RATE) {
                child1.Kp += rand_range(-1.0f, 1.0f);
                child1.Ki += rand_range(-0.5f, 0.5f);
                child1.Kd += rand_range(-0.3f, 0.3f);
            }
            if (rand_range(0, 1) < MUTATION_RATE) {
                child2.Kp += rand_range(-1.0f, 1.0f);
                child2.Ki += rand_range(-0.5f, 0.5f);
                child2.Kd += rand_range(-0.3f, 0.3f);
            }

            clamp_params(&child1);
            clamp_params(&child2);

            next_gen[i] = child1;
            if (i + 1 < POP_SIZE) next_gen[i + 1] = child2;
        }

        // 复制下一代到当前种群
        for (int i = 0; i < POP_SIZE; i++) {
            population[i] = next_gen[i];
        }
    }
}

int main() {
    run_ga();
    return 0;
}

4. 关键设计细节优化

4.1 交叉算子的改进

原博文中交叉实现不完整。在硬件参数整定中,推荐使用算术交叉(Arithmetic Crossover)。这种方法生成的子代参数自然处于父母参数之间,能较好地保持在合理搜索空间内。

4.2 避免“早熟”与收敛性


5. 结论

遗传算法为 PID 参数整定提供了一种鲁棒、高效的“自适应”解决方案。通过在嵌入式系统中用 C 语言实现该算法,可以让硬件设备具备真正的自整定(Auto-Tuning) 能力,大幅降低人工调试成本,提升控制性能。



上一篇
STM32传感器标定实战:最小二乘法二次曲线拟合工程实现(附完整代码)
下一篇
低功耗反激转换器实战:10mW待机效率优化与UCC28710/PSR设计指南