拙网论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 170|回复: 0

FIRA小车“习武记”之十:PID调速探究(PWM频率)

[复制链接]

949

主题

1001

帖子

3736

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3736
发表于 2018-5-2 19:43:06 | 显示全部楼层 |阅读模式


2016-04-11 10:22:05
分享:




标签  PID调速         PWM调功         PWM频率         直流有刷电机



    上一步初步尝试了PID调速,使得我可以控制行动的速度,做到收放自如,但由于对 PID 调速掌握的比较肤浅,加之对自己身体的性能把握不准,导致调速效果不是很理想。因为 PID 的参数必须和我的身体素质相匹配,才能发挥到最好。
    虽说 PID 调速不是本次习武的主题,但调速性能的好坏直接影响到我日后执行任务的能力,所以还是专门花点时间探究一下我的身体素质,尽量能使 PID 这一经典“武功”可以在我身上达到最佳的效果。

10.1 自我“体检”
    “PWM调速”是直流电机控制的常用的“俗语”。
    为什么说是“俗语”而非“术语”,因为这个表述不准确,正确的表述应该是“PWM调功”,也就是通过调节电机供电的占空比,改变供给电机的平均功率,但这不一定能改变电机的速度。
    当电机负载较轻时,PWM 不到100% 就可以让电机达到最快转速,之后 PWM 的改变就不再影响电机速度了(这个概念还是在玩 LEGO 时看到的,LEGO 的 RCX 只能通过 PWM 改变电机速度,所以有时需要额外增加阻力以使得转速可以随 PWM 的变化而变化)。
    为了验证这个描述,同时也算是对我的体质做一个检测,用 Processing 做了一个测试程序,通过通讯命令实现 PWM 从 100% 到 0% 扫描,记录速度随 PWM 的变化曲线。
    同时,尝试在不同的 PWM 的频率下,我的速度随 PWM 变化的性能会如何?为更好的实现 PID 调速打下基础。
    这次尝到了自己掌控测试程序的好处了,可以随时按照需要实现方便、直观的测试。

    基本控制方式构思为:
    以我的 20ms 速度输出作为 Processing 程序控制的计时依据,从 PWM100% 开始,持续接收 75 次速度数据(20*75 = 1500ms),确保转速达到最快值;之后每接收 15 个速度数据 PWM 减少 5%,直到 0%。记录上述过程的速度和对应的PWM值,谱出变化曲线。
    为了保证 PWM 扫描过程的通讯计数不失步,这个过程 Processing 程序不执行 draw()循环,不刷新显示,扫描结束后在恢复显示(具体实现见Processing程序)。
    修改我的STM32程序中的 PWM 频率,分别测试以下频率的“PWM-速度”关联曲线:
    60kHz, 40Khz, 20kHz, 10kHz, 5Khz

    先看一下用所编写的 Processing 程序所记录的曲线(测试方式为单轮转动,我原地打转,基本和实际的工况等效):


    从上图可以看出,在 60kHz 的 PWM 频率下,PWM 降低到 50% 速度才开始变化,而且之后的变化也不线性,有效的 PWM 调速范围只有 50% - 25%。



    这是 40kHz 下的变化曲线,PWM 降低到 60% 速度才开始变化,后面还算比较线性,有效的 PWM 调节范围60% - 5%,有 55% 调节空间。目前就是这个频率,所以还勉强可以实现 PID 调速。



    频率降低到 20kHz,速度开始变化的 PWM 约为 75%,但到 5% 电机就不转了。



    PWM 频率再下调到 10kHz,PWM 到85% 速度开始变化,到 10% 电机不转。从下面的放大图观察开始变化点的位置:


    从图中可以看出,85% 之后的速度随 PWM 变化还算是线性,有效的调节范围接近 75%。


    当频率降低到5kHz 时,PWM 到 15% 就无法驱动电机了;开始变化点提升到 95%:



    从以上测试结果看,如果从调速的角度考虑,并不是 PWM 频率越高越好,至少我的体质如此。但这并不是说前面所下的“PWM频率越高越好”的结论是错的,从电机续流角度考虑,仍是正确的!
    可从调速角度考虑,电机断流使电机短时间失去动力,从而引起速度变化,这反而对调速是好事了。
    从上面的测试还可以发现:PWM 频率并非一味降低就好,需要和我的特性相配才行。

10.2 改善“体质”
    综合上述测试结果,想要实现最佳的调速性能,应该是在 PWM 高时使用较低的 PWM 频率,而 PWM 值较低时使用较高的 PWM 频率。
    可我目前的身体是两个轮子使用同一个定时器,无法实现不同频率的硬件 PWM 输出了,看来以后如果要达到最佳效果,最好是一个电机对应一个定时器。
    不过打算采用变通的方式:只有一个轮子在转动时,根据转动轮子的 PWM 值设定 PWM 频率,如两轮都在转,则取两轮 PWM 的平均值作为设定 PWM 频率的依据。不知道这样处理的效果如何。

    初步确定将PWM分为3段:
      75% - 100%,使用5kHz PWM频率;
      25% - 75%, 使用 10KHz PWM频率;
      0% - 25%,  使用 20kHz PWM频率;
    在每次刷新 PWM 时,同时刷新定时器的重加载值。

    修改电机驱动函数 BSP_DriveMotor(),将一次驱动一个轮子,改为同时控制两个轮子,正好改进两轮控制的同步性。
    初始 PWM 值均按 5kHz,在 BSP_DriveMotor()函数中根据 PWM 的占空比作相应修改,并刷新定时器的重新加载值和比较值。
    我大脑中的程序修改后测试效果如下:


    从速度变化曲线上看,在 25% 那个切换点有点不连续,看来从 10kHz 变化到 20kHz 驱动功率变化偏大,所以改为4段控制,即:
    75% - 100%,使用  5kHz PWM频率;
    50% - 75%, 使用 10KHz PWM频率;
    25% - 50%, 使用 15KHz PWM频率;
    0% - 25%,  使用 20kHz PWM频率;
    修改后测试的结果改善了很多:


    从上图看,速度随 PWM 的变化已经比较满意了。这样处理后,PWM 有效的调节范围扩大到接近 90%,这对实现 PID 调速应该很有益处:一个近似线性的系统是最佳的调节对象!

    原来的电机驱动函数如下:
void  BSP_DriveMotor (INT8U u8MotorFlag, INT8U u8MotorStat,INT16U u16PwmVal)
{
  switch (u8MotorStat)
  {
    case FORWARD:
    {
      if(u8MotorFlag == LEFT_MOTOR)
      {
        GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L2);
        GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      }
      else
      {
        GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R2);
        GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      }
      break;
    }
    case BACKWARD:
    {
      if(u8MotorFlag == LEFT_MOTOR)
      {
        GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L2);
        GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      }
      else
      {
        GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R2);
        GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      }
      break;
    }
    case BRAKE:
    {
      if(u8MotorFlag == LEFT_MOTOR)
      {
        GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L2);
        GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      }
      else
      {
        GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R2);
        GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      }
      break;
    }
    case FLOAT:
    {
      // 必须保证 PWM 输出无效!(0)
      if(u8MotorFlag == LEFT_MOTOR)
      {
        GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L2);
        GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      }
      else
      {
        GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R2);
        GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      }
      break;
    }

    default: break;
  }
  if(u8MotorFlag == LEFT_MOTOR)
  {
    TIM_SetCompare3(TIM2, u16PwmVal);
  }
  else
  {
    TIM_SetCompare4(TIM2, u16PwmVal);
  }
}
     使用 Timer2 的硬件 PWM 输出,每次只修改比较值,改变占空比,PWM 频率由 Timer2 的自动重加载值确定,初始化后不再改变。

    为了实现 PWM 频率可以随 PWM 改变,修改后的电机驱动函数如下:
void  BSP_DriveMotor (MotorCtrlPara sLeft, MotorCtrlPara sRight)
{
  INT8U u8StatFlag = 0;
  INT8U u8PwmArea;

  switch (sLeft.u8MotorStat)
  {
    case FORWARD:
    {
      GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L2);
      GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      u8StatFlag |= 0x01;
      break;
    }

    case BACKWARD:
    {
      GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L2);
      GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      u8StatFlag |= 0x01;
      break;
    }

    case BRAKE:
    {
      GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L2);
      GPIO_ResetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      u8StatFlag &= 0xFE;
      break;
    }

    case FLOAT:
    {
      // 必须保证 PWM 输出无效!(0)
      GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L2);
      GPIO_SetBits(GPIOA, BSP_GPIOA_CTRL_L3);
      u8StatFlag &= 0xFE;
      break;
    }

    default: break;
  }

  switch (sRight.u8MotorStat)
  {
    case FORWARD:
    {
      GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R2);
      GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      u8StatFlag |= 0x02;
      break;
    }

    case BACKWARD:
    {
      GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R2);
      GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      u8StatFlag |= 0x02;
      break;
    }

    case BRAKE:
    {
      GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R2);
      GPIO_ResetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      u8StatFlag &= 0xFD;
      break;
    }

    case FLOAT:
    {
      // 必须保证 PWM 输出无效!(0)
      GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R2);
      GPIO_SetBits(GPIOC, BSP_GPIOC_CTRL_R3);
      u8StatFlag &= 0xFD;
      break;
    }

    default: break;
  }

  switch(u8StatFlag)
  {
    case 1:        // 左侧转
      u8PwmArea = sLeft.u16PwmVal/PWM_25;
      break;

    case 2:        // 右侧转
      u8PwmArea = sRight.u16PwmVal/PWM_25;
      break;

    case 3:        // 两侧都转
      u8PwmArea = (sLeft.u16PwmVal + sRight.u16PwmVal)/(2*PWM_25);   // 取两侧的 PWM 平均值
      break;

    default:       // 两侧都不转
      u8PwmArea = sLeft.u16PwmVal;
      break;
  }

  switch(u8PwmArea)
  {
    case 0:                  // PWM < 25%
      TIM_SetCompare3(TIM2, sLeft.u16PwmVal/4);
      TIM_SetCompare4(TIM2, sRight.u16PwmVal/4);
      TIM_SetAutoreload(TIM2,PWM_100_5KHZ/4 - 1);          // PWM 频率设置为 20kHz
      break;

    case 1:                 // PWM 25 - 50%
      TIM_SetCompare3(TIM2, sLeft.u16PwmVal/3);
      TIM_SetCompare4(TIM2, sRight.u16PwmVal/3);
      TIM_SetAutoreload(TIM2,PWM_100_5KHZ/3 -1);        // PWM 频率设置为 15kHz
      break;
      
    case 2:                // PWM  50 - 75%
      TIM_SetCompare3(TIM2, sLeft.u16PwmVal/2);
      TIM_SetCompare4(TIM2, sRight.u16PwmVal/2);
      TIM_SetAutoreload(TIM2,PWM_100_5KHZ/2 -1);         // PWM 频率设置为 10kHz
      break;

    default:
      TIM_SetCompare3(TIM2, sLeft.u16PwmVal);
      TIM_SetCompare4(TIM2, sRight.u16PwmVal);
      TIM_SetAutoreload(TIM2,PWM_100_5KHZ -1);          // PWM 频率设置为 5kHz
      break;
  }
}
     因为需要两侧的 PWM 值来确定 PWM 频率,所以必须同时处理两侧的电机。这样带来一个好处:两侧电机处理应该更容易同步,避免由于先后的时间差,产生偏航,但实际的效果还有待考验。
    经过这样的修改,应该说大大改善了我的素质,可以更好的实现PID调速了!

10.3 改进后的 PID 调速性能
    为这一步测试所编写的 Processing 程序,不只是增加了测试“PWM-速度”关联性的功能,同时还为了便于整定 PID 参数,设置了方便的 PID 参数改进操作界面。还是利用内存读写功能,在我的程序中设计相应的全局变量,通过 map 文件得到地址,直接修改内存值实现参数的改变。配合速度曲线显示功能,便可以直观的调整PID参数,从而达到最佳效果。
    改进后的测试程序界面如下:


    “PWM Scan”为自动扫描 PWM 功能按钮,只是针对左侧轮子测试,需要先设置 SoutCtrl 为左侧速度输出到RF通道(3 - L to RF),详细过程见程序。
    (这类程序是辅助测试的,所以不必过于精细,但要便于修改,以便随时根据需要增减功能。)

    下面一组输入和按钮是为了快速修改 PID 参数所设,此时所修改的只是本次我上电运行的参数,断电后重新上电,则会恢复到程序的默认值。主要目的是观察 PID 参数改变对调速性能的影响,找到最佳的 PID 参数,完成参数整定过程。最佳参数确定后,作为默认值写入我的程序中。
    PID 调速是一个可以无限探究的主题,此处不再深入,只是将所需要的手段配齐,日后在实战中逐步完善。

    提升我的素质后,仍然使用原来的PID整定参数,测试了以下几个状态:













    感觉虽然速度还不是很稳定,但调速的范围加大了,原来低速时会出现走走停停现象,改进后可以持续运动了,到 50mm/s 时才出现走走停停的现象。
    虽说从曲线上看速度波动挺大,可因为图中每一点对应时间只有 20ms,拿设定速度 80mm/s 的曲线来说,20ms 只走了1.6mm,因此即便有点波动,范围在设定值上下基本对称,且能在较短的时间内纠正,我看上去走得还是挺流畅的,而且基本可以实现走直线,只是在启 动和停止时还有点问题,似乎不同步,还需要不断修炼!
    以下是小车测试的视频,用手机单手拍摄的,凑合看看吧:
    http://v.youku.com/v_show/id_XMTUzMTEyNDQwMA==.html

10.4 感悟
    当接触到具体的物理世界后,诸多非理想的因素都显现了。这和在计算机中运行的软件不同,在那种理想环境中,程序处理的结果是确定的;而我的程序改进必须和实际的对象相融合,才能产生效果。这就是嵌入式控制程序的难点,它需要在掌握程序控制逻辑之外,了解控制对象的特性,要想办法测得对象的特性,从而有针对性的编写控制程序,尤其是面对不理想的控制对象时。
    所以,从学习角度,一个不甚理想的控制对象恰恰是好的学习素材,它会给你增加麻烦,让你有挫败感,可正是这些不利的因素促使你去尝试不同的方式,探究问题的原因,找到合理的方式补救,在不理想的平台上做出最理想的效果。通过这个过程练出“真功夫”!
    这一步算是个插曲,下一步继续前面的任务,盘点到此为止所所用的资源,看看还有多少余力去继续练习。
————————————


注:第八步改进的程序(含 Processing 程序)在 QQ 群 180154446 共享文件中,供参考。欢迎交流、讨论!





回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|抱朴守拙BBS

GMT+8, 2025-5-26 05:03 , Processed in 0.186988 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表