• PID 控制理论


    OUTput与INput区别

    程序到引脚那就是,程序是output(理解成给予),从引脚到程序,程序就是intput(理解成获取)

    问题背景

    速度信息可以以m/s为单位,或者也可以转换成转速 r/s,而电机的转速是由PWM脉冲宽度来控制的,如何根据速度信息量化成合适的PWM值呢?

    PWM解释
    例如控制LED灯亮度
    PWM:脉冲宽度调制技术,设置占空比为LED间歇性供电,PWM的取值范围是[0,255]
    0——low,255——high
    假设是PWM = 50,high比例是 50/255 约等于1/5,low比例约是4/5
    假设是PWM = 100,high比例是 100/255 约等于2/5,low比例约是3/5
    亮度多少就是通过设置的数字与255相除,然后计算出来百分比

    PID

    在这里插入图片描述

    • e(t)作为 PID 控制的输入
    • u(t)作为 PID 控制器的输出和被控对象的输入
    • Kp 控制器的比例系数
    • Ki控制器的积分时间,也称积分系数
    • Kd控制器的微分时间,也称微分系数

    在这里插入图片描述

    积分调节

    在这里插入图片描述

    目标速度为100Km/h,假设P为2,初始速度为0
    步骤1:PWM = 2*(100-0) = 200,即油门加到200,加到120时,测下速度。
    步骤2:PWM = 2*(100-120) = -40,油门给到0,让汽车减速,减到60,测下速度。
    步骤3:PWM = 2*(100-60) = 80,油门给到80,油门加到75,测下速度。
    步骤4:PWM = 2*(100-75) = 50,油门给到50,测下速度
    依次类推

    在这里插入图片描述
    设置油门与速度比为1:1
    在这里插入图片描述
    在这里插入图片描述
    由此可知,使用比例调节永远不能达到目标值,只能无限接近

    积分调节

    在这里插入图片描述
    在这里插入图片描述

    微分调节

    在这里插入图片描述
    在这里插入图片描述

    使用PID控制小车转速 motor04_pid

    /*
     * PID代码实现:
     * 准备工作: 复制之前的测速代码 + 控制电机转动的代码 + PID库安装成功
     * 修改:
     *  1. 修改测速的单位时间(50)
     *  2. 添加电机转向的PWM控制引脚
     * 
     * 步骤:
     *  1. 包含头文件
     *  2. 创建PID对象
     *  3. setUp中启用PID对象 
     *  4. 调用 Compute() 输出控制电机的PWM值
     */
    
    /*
     * 实现脉冲计数
     * 
     * 流程:
     *  1.将使用的引脚封装为变量,封装计数变量
     *  2.setup中设置引脚的操作模式(读取编码器相关引脚的输出信号INPUT),设置波特率(结果输出到上位机)
     *  3.为引脚添加中断事件
     *  4.计数逻辑实现
     *  5.要输出到上位机
     */
    
    #include
    
    // 1.将使用的引脚封装为变量,封装计数变量
    int encoder_A = 21;// 中断口2
    int encoder_B = 20;// 中断口3
    volatile int count = 0;
    int DIR_LEFT = 4;// 控制转向
    int PWM_LEFT = 5;// 控制PWM
    // 左电机的M1与M2对应的是引脚4(DIRA)和引脚5(PWMA),引脚4控制转向,引脚5输出PWM
    // 右电机的M1与M2对应的是引脚6(PWMB)和引脚7(DIRB),引脚7控制转向,引脚6输出PWM
    
    // 4.计数逻辑实现
    void count_a(){
      // 先判断A是否跳变到高电压
      if(digitalRead(encoder_A) == HIGH){
        // 再判断B的电压
        if(digitalRead(encoder_B) == HIGH){
          count ++;// 正转
         } else {
           count --;// 反转
         }
       } else {
        // 再判断B的电压
        if(digitalRead(encoder_B) == LOW){
          count ++;// 正转
         } else {
           count --;// 反转
         }
       }
     }
    
    void count_b(){
      if(digitalRead(encoder_B) == HIGH)
      {
        if(digitalRead(encoder_A) == LOW){
          count ++;
        } else {
          count --;
        }
      }
      else
      {
        if(digitalRead(encoder_A) == HIGH){
          count ++;
        } else {
          count --;
        }
      }
     }
     
    
    // 测试流程:
    /*
     * 1. 封装变量 --- 开始时间、单位时间、减速比、一圈输出的脉冲数、使用的N倍频测速
     * 2. 实现逻辑
     *  2.1 获取时间时间戳(当前时间)
     *  2.2 if(当前时间 - 开始时间 >= 单位时间){
     *        // 取消中断
     *        // 计算转速(count)
     *        // count置零
     *        // 将开始时间重置为当前时间,进行重新测速
     *        // 重启中断
     *  }
     *  
     */
    long start_time = millis();//一个计算周期的开始时刻,初始值为millis();
    int interval_time = 50;//一个计算周期 50ms
    int per_round = 90 * 11 * 4;// 四倍频,90 的 减速比,电机转1圈是11个脉冲
    double vel;
    void get_current_vel(){
      // 获取当前的时间
      long right_now = millis();
      // 判断逝去的时间是否大于单位时间
      long past_time = right_now - start_time;
      if(past_time >= interval_time){
        // 取消中断,中断里面做着count++ 或者count--,取消中断不再做count++或者--的操作
        noInterrupts();
        // 计算转速(count),数据类型转换
        // 转的圈数/时间*1000*60,将原来的r/ms转换成r/min
        double vel = (double)count / per_round / past_time * 1000 * 60;
        Serial.println(vel);
        // count置零
        count = 0;
        // 将开始时间重置为当前时间,进行重新测速
        start_time = right_now;
        // 重启中断进行重新测速
        interrupts();
      }
    }
    
    /*
     * 参数1: 输入(当前转速)
     * 参数2: 输出(根据PID算法计算PWM值---油门)
     * 参数3: 目标值
     * 参数4-6: PID系数值
     * 参数7: DIRECT | REVERSE (后者的P、I、D值会取反)
     * 
     */
    double pwm;
    double target = 80;
    double Kp = 2, Ki = 5, Kd = 1;
    PID pid(&vel,&pwm,&target,Kp,Ki,Kd,DIRECT);//PID对象
    
    // 调速函数
    void update_vel(){
      // 调用测速函数
      get_current_vel();
      // 调用 Compute 函数生成 PWM 值
      pid.Compute();
      // 将 PWM 值写入电机
      digitalWrite(DIR_LEFT,HIGH);
      analogWrite(PWM_LEFT,pwm);  
     }
    
    void setup() {
      // put your setup code here, to run once:
      // 设置波特率
      Serial.begin(57600);
      // 2.setup中设置引脚的操作模式(INPUT)
      pinMode(encoder_A,INPUT);// 从引脚读数据
      pinMode(encoder_B,INPUT);
      pinMode(DIR_LEFT,4);
      pinMode(PWM_LEFT,5);
      // 3.为引脚添加中断函数
      // 参数1: 中断口 参数2: 回调函数 参数3: 触发时机
      // 单倍频或双倍频只需要为编码器的A相添加中断函数,CHANGE 当引脚电平发生改变时,触发中断
      attachInterrupt(2,count_a,CHANGE);// 2对应的中断口是21,3对应的中断口是20
      attachInterrupt(3,count_b,CHANGE);//当电平发生改变时触发中断函数
      pid.SetMode(AUTOMATIC);// 自动
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
      // delay(2000);
      // Serial.println(count);
      // get_current_vel();//获取当前速度,通过串口输出
      delay(10);//每隔10ms调用
      update_vel();// 调速
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
  • 相关阅读:
    【C++百宝箱】语法总结:引用 | 内联函数 | auto | 范围for循环
    躲避雪糕刺客?通过爬虫爬取雪糕价格
    SQL优化策略
    从网页的canvas上保存渲染的图片
    腾讯蝉联JDK17贡献国内第一,自研Kona JDK两大新版本正式开源
    万博智云将亮相 2023 长沙·中国 1024 程序员节:普惠云容灾及提升碳效率的最佳实践
    负载均衡的算法(静态算法与动态算法)
    【思科、华为、华三这三大认证,选哪个考最好?】
    C++类模板继承关系
    ImportError: cannot import name ‘Mapping‘ from ‘collections‘
  • 原文地址:https://blog.csdn.net/qq_42731062/article/details/126166766