单片机论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 974|回复: 8
打印 上一主题 下一主题
收起左侧

使用增量式PID对直流电机进行简单的控制

[复制链接]
跳转到指定楼层
楼主
本帖最后由 kaixuan520 于 2019-7-12 10:04 编辑

位置式PID控制
位置式PID控制是指在积分环节,对从0时刻到当前时刻的所有偏差进行积分,是非递推式的全局积分。
增量式PID控制
和位置式PID控制不同,增量式PID控制将当前时刻的控制量和上一时刻的控制量做差,以差值为新的控制量,是一种递推式的算法。
增量式PID控制的主要优点为:
①算式中不需要累加。控制增量Δu(k)的确定仅与最近3次的采样值有关,容易通过加权处理获得比较好的控制效果;
②计算机每次只输出控制增量,即对应执行机构位置的变化量,故机器发生故障时影响范围小、不会严重影响生产过程;
③手动—自动切换时冲击小。当控制从手动向自动切换时,可以作到无扰动切换[2]  。
由于增量式需要对控制量进行记忆,所以对于不带记忆装置的系统,只能使用位置式PID控制方式进行控制。


公式:




上代码:
使用定时器3作为PWM输出,频率为20KHz,调节占空比0-100%来改变电机速度
    //TIM3初始化和输出GPIO初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
    GPIO_InitTypeDef GPIO_InitTypeStruct;
    GPIO_InitTypeStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitTypeStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitTypeStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitTypeStruct);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
    TIM_TimeBaseInitTypeStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitTypeStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitTypeStruct.TIM_Period = 99;
    TIM_TimeBaseInitTypeStruct.TIM_Prescaler = 35;                //20KHz
    TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);

    TIM_OCInitTypeDef TIM_OCInitTypeStruct;
    TIM_OCInitTypeStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitTypeStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitTypeStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitTypeStruct.TIM_Pulse = DutyCycle;                                                                //占空比
    TIM_OC1Init(TIM3,&TIM_OCInitTypeStruct);

    TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);

    TIM_Cmd(TIM3,ENABLE);





   //使用定时器1作输入捕获,PWM输入模式,
   //GPIO和定时器1初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    GPIO_InitTypeDef GPIO_InitTypeStruct;
    GPIO_InitTypeStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitTypeStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitTypeStruct.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOA,&GPIO_InitTypeStruct);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
    TIM_TimeBaseInitTypeStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitTypeStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitTypeStruct.TIM_Period = TIM1_ARR;
    TIM_TimeBaseInitTypeStruct.TIM_Prescaler = TIM1_PSC;
    TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitTypeStruct);

    TIM_ICInitTypeDef TIM_ICInitTypeStruct;
    TIM_ICInitTypeStruct.TIM_Channel = TIM_Channel_1;
    TIM_ICInitTypeStruct.TIM_ICFilter = 0x0f;//对输入的脉冲进行滤波
    TIM_ICInitTypeStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitTypeStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitTypeStruct.TIM_ICSelection =  TIM_ICSelection_DirectTI;
    TIM_PWMIConfig(TIM1,&TIM_ICInitTypeStruct);

    TIM_SelectInputTrigger(TIM1,TIM_TS_TI1FP1);                //选择输入触发方式
    TIM_SelectSlaveMode(TIM1,TIM_SlaveMode_Reset);        //设置从机模式
    TIM_SelectMasterSlaveMode(TIM1,TIM_MasterSlaveMode_Enable);//

    NVIC_InitTypeDef NVIC_InitTypeStruct;
    NVIC_InitTypeStruct.NVIC_IRQChannel = TIM1_CC_IRQn;//输入捕获中断线
    NVIC_InitTypeStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitTypeStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitTypeStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitTypeStruct);

    TIM_ITConfig(TIM1,TIM_IT_CC1,ENABLE);//使能通道1捕获中断

    TIM_ClearITPendingBit(TIM1,TIM_IT_CC1);

    TIM_Cmd(TIM1,ENABLE);

//通过输入捕获中断函数来取得电机编码器的频率,占空比,周期,从而计算出速度
uint16_t IC1V;
uint16_t IC2V;
void TIM1_CC_IRQHandler(void)
{
    TIM_ClearITPendingBit(TIM1,TIM_IT_CC1);

    IC1V = TIM_GetCapture1(TIM1);//周期us
    IC2V = TIM_GetCapture2(TIM1);//占空比
    if(IC1V!=0)
    {
        IC1V+=1;
        IC2V+=1;
        float d1 = ((float)IC1V/1000);
        float d2 = ((float)IC2V/1000);
        float f = (float)IC1V/1000000;
        DutyCycle = 100*d2/d1;//占空比
        Frequency = 1/f;        //计算出编码器的频率
        Period = (float)IC1V/1000;//周期ms
    } else {
        Frequency = 0;
        DutyCycle = 0;
    }
}


//再使用一个100ms的定时器来每秒10次的数据采集和控制,定时器初始化就不贴了,具体看附件

float M_PWM = 0;
float M_Speed = 0;
uint8_t xi=0;
void TIM2_IRQHandler(void)//100ms定时器中断
{
    if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
    {
        //if(Frequency!=0) {
        GPIOB->ODR ^= GPIO_Pin_5;
        M_Speed = Frequency;
        M_Speed /= 20;
                Frequency = 0;

        int value = Compute_PID((uint16_t)(M_Speed*100));//进行PID计算,返回PWM占空比
                int y = value;
        float n = value/100;
        M_PWM += n;

        if(xi==0) {//没500ms向串口发送一次数据
                        printf("当前转速:%0.2f\r\n",M_Speed);
                        printf("目标转速:%d\r\n",(uint16_t)PID_Struct->SetPoint/100);
            printf("P:%0.4f,I:%0.4f,D:%0.4f,LE:%d,PE:%d,VO:%d,MO:%d\r\n",//PID相关参数信息,
                        PID_Struct->Proportion,PID_Struct->Integral,PID_Struct->Derivaltive,
                        PID_Struct->LastError,PID_Struct->PreError,y,(int)M_PWM);  
            printf("Period:%0.4fms, Frequency:%0.4fHz, DutyCycle:%0.4f%%\r\n",Period,Frequency,DutyCycle);//PWM输出的周期,频率,占空比
            printf("\r\n\r\n");
        }
        xi++;
        xi%= 5;
        if(M_PWM>100)M_PWM = 100;                //防止越过最大和最小控制量
        if(M_PWM<0)M_PWM = 0;
        TIM_SetCompare1(TIM3,(uint16_t)M_PWM);
        //}
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    }
}


PID算法结构体
typedef struct {
    int SetPoint;                        //设定值
    long SumError;                        //累计误差

    double Proportion;                //比例
    double Integral;                //积分
    double Derivaltive;                //微分

    int LastError;                        //上次误差
    int PreError;                        //上上次误差
} PID_S;

static PID_S PID_Struct_;
static PID_S *PID_Struct = &PID_Struct_;


//main:
/PID参数整定方法请转各大论坛查找教程,这里不详细介绍
PID_Struct->Proportion = 1.92;
    PID_Struct->Integral = 1.65;
    PID_Struct->Derivaltive = 0.116;
    PID_Struct->SetPoint = 80*100;


//PID计算
int Compute_PID(uint16_t nowValue)
{
    register int iError,Ouk;
    iError = PID_Struct->SetPoint - nowValue;//当前误差
//    PID_Struct ->SumError += iError;

    Ouk = (PID_Struct->Proportion * iError)
          - (PID_Struct->Integral * PID_Struct->LastError)
          + (PID_Struct->Derivaltive *PID_Struct->PreError);

    PID_Struct->PreError = PID_Struct->LastError;

    PID_Struct->LastError = iError;
    return Ouk;
}


电路方面制作
电机使用12V扫地机器人的电机,编码器使用每圈20次脉冲的光电编码器
使用LM393电压比较器对光电编码器输出进行稳定和优化
电路图:







编码器输出波形图:


波形还是比较完美的

电机驱动使用L298N,连接图:


串口打印数据:

视频演示PWM输出效果:https://share.weiyun.com/5idpKLF



增大阻力和调节速度都能快速回到设定值以下为附件信息
STM32源码: PID_直流调速.rar (349.35 KB, 下载次数: 43)

PID学习资料: PID算法.zip (1.81 MB, 下载次数: 39)






评分

参与人数 1黑币 +100 收起 理由
admin + 100 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏3 转播转播 分享分享 分享淘帖 顶 踩
回复

使用道具 举报

沙发
小林大哥哥 发表于 2019-7-14 20:52 | 只看该作者
VO:%d,MO:%d这两个参数表示pid的啥
回复

使用道具 举报

板凳
小林大哥哥 发表于 2019-7-14 20:52 | 只看该作者
求大佬解释
回复

使用道具 举报

地板
 楼主| kaixuan520 发表于 2019-7-15 15:05 | 只看该作者
小林大哥哥 发表于 2019-7-14 20:52
VO:%d,MO:%d这两个参数表示pid的啥

VO是PID计算出的结果,MO是当前输出PWM的占空比
回复

使用道具 举报

5#
18239311863 发表于 2019-7-16 17:17 来自手机 | 只看该作者
很好的学习资料,在学习平衡小车,研究下
回复

使用道具 举报

6#
18239311863 发表于 2019-7-17 17:43 来自手机 | 只看该作者
电路图提供一下呗
回复

使用道具 举报

7#
 楼主| kaixuan520 发表于 2019-7-20 19:41 | 只看该作者
18239311863 发表于 2019-7-17 17:43
电路图提供一下呗

没有电路图,电路自己设计的只有编码器那块的,电路图在贴子中,别的连接都是杜邦线直接连的
回复

使用道具 举报

8#
13958988573 发表于 2019-8-1 10:03 | 只看该作者
没有PID.c文件
回复

使用道具 举报

9#
 楼主| kaixuan520 发表于 2019-9-20 13:24 来自手机 | 只看该作者
13958988573 发表于 2019-8-1 10:03
没有PID.c文件

没用到,代码都在main.c里
回复

使用道具 举报

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

本版积分规则

手机版|小黑屋|单片机论坛 |51黑电子论坛技术交流 QQ 管理员QQ:125739409;技术交流QQ群636986012

Powered by 单片机教程网

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