搜索
bottom↓
回复: 0

《DMF407电机控制专题教程_V1.0》第24章

[复制链接]

出0入234汤圆

发表于 2022-8-20 10:57:35 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2022-8-20 10:27 编辑

1)实验平台:正点原子DMF407电机开发板
2)平台购买地址: https://detail.tmall.com/item.htm?&id=677230699323
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/stm32dj/ATK-DMF407.html
4)对正点原子电机开发板感兴趣的同学可以加群讨论: 592929122 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png

lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png


第24章 无刷电机有感方波闭环控制


        前面已经实现了无刷电机的基本控制和电机的模拟信号采集,本章将利用PID来实现速度环闭环控制以及速度+电流双闭环控制。
本章分为如下几个小节:
24.1 速度闭环控制
24.2 速度&电流双闭环控制


24.1 速度闭环控制

        要实现速度闭环,首先需要先了解下无刷有感驱动的测速原理,这样才可以知道当前转速是否符合我们的设定值。
24.1.1 无刷有感测速原理
        从前面的学习得知,当转子磁极只有一对级时,转子旋转一圈三个霍尔信号都会输出相应波形,这些波形的特点:高低电平的持续时间是一样的,均为180°电角度(对于店铺BLDC的霍尔传感器来说,N级靠近输出:1,S级靠近输出:0)。如下:
image002.jpg
图24.1.1.1 无刷电机与霍尔位置示意图

image004.jpg
图24.1.1.2 霍尔输出波形示意图

        第一步:当只有一对级时,转子旋转一圈,霍尔输出一个完整脉冲(其中高电平与低电平持续时间均为180°电角度);
        第二步:计算其中高电平的持续时间,即:t = C / Ft (其中t为180°电角度所代表的时间,Ft是霍尔脉冲的频率,C为计数次数)
        第三步:所以旋转一圈,需要的总时间为T = 2*C/Ft;
        第四步:所得出的结果单位为:s/圈 ,倒数即为:圈/s ,需将其单位转化为RPM即:Ft/(2*C) *60 。
        第五步:当转子为2对级时,霍尔输出的高低电平时间均为360°电角度,所以速度公式为:Ft/(4*C)*60。
24.1.2 硬件设计
1、例程功能
1、本实验以电机开发板的直流有刷/无刷电机驱动接口1为例,基于无刷电机参数采集实验,加入速度环PID控制算法,对电机的速度进行闭环控制。
2、当按键0按下,就增大目标速度值;当按键1按下,就减小目标速度值。目标速度的绝对值大小决定电机的速度,它的正负决定电机的正反转方向。按下按键2则马上停止电机。
3、屏幕显示按键功能、占空比、目标转速以及实际转速以及显示无刷电机电压、电流和温度、速度等等信息。
4、可通过串口1即USB_TTL接口连接正点原子PID调试助手,查看PID波形。
5、LED0闪烁指示程序运行。

2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
    KEY0 – PE2
        KEY1 – PE3
        KEY2 – PE4
3)定时器:  
        TIM1_CH1:PA8
        TIM1_CH2:PA9
        TIM1_CH3:PA10
   IO:PB13\14\15
   使能引脚:SHDN: PF10
4)无刷电机
5)无刷电机驱动板
6)12-60V的DC电源
7)ADC:ADC1_CH9:PB1
         ADC1_CH0:PA0
         ADC1_CH8:PB0
         ADC1_CH6:PA6
         ADC1_CH3:PA3
3、原理图
image006.jpg
图24.1.2.1 无刷电机接口1原理图

        接口涉及的IO如下。
lQLPJxabhyPHpWbNAXTM-7DjKx-rc-e3hwL_4hnVQCwA_251_372.png
表24.1.2.1 无刷相关IO口说明

        驱动板和电机的连接方式之前已经介绍过了,驱动板和开发板连接只需要使用排线连接即可(注意如使用PID调试助手查看波形,需将开发板串口1的USB_TTL连接至电脑),实物连接如下。
image008.jpg
图24.1.2.2 开发板&驱动板连接图

24.1.3 程序设计
24.1.3.1 无刷电机速度闭环的配置步骤

1)实现电机基本驱动
        实现电机的基础驱动函数,启停、6步换向组合等等
2)初始化ADC(保留上节课的参数采集功能)
        初始化ADC通道的IO,设置ADC工作方式、DMA等等
3)PID闭环控制实现
        实现PID的初始参数赋值、PID计算等等
4)上位机通信协议
        编写上位机通信代码,可在上位机上实时显示当前速度与目标速度的波形变化
5)编写中断服务函数
        PWM中断用于换向控制、堵转检测等等,添加PID的周期计算调用
24.1.3.2 程序流程图
image010.png
图24.1.3.2.1 无刷电机速度闭环控制流程图

24.1.3.3 程序解析
这里我们只讲解核心代码,定时器及ADC的相关程序都和之前一致,这里就不重复列出了,首先需要关注的是pid.h头文件中PID结构体和默认的P、I、D参数的定义。如下所示。
/* PID相关参数 */

#define  INCR_LOCT_SELECT  1                /*0选择位置式1:增量式控制*/
#if INCR_LOCT_SELECT
#define  S_KP      0.00800f               /* P参数*/
#define  S_KI      0.00025f               /* I参数*/
#define  S_KD      0.00020f               /* D参数*/
#else

#define  S_KP      0.00800f               /* P参数*/
#define  S_KI      0.00025f               /* I参数*/
#define  S_KD      0.00020f                /* D参数*/
#endif
#define SMAPLSE_PID_SPEED  40             /* 采样率 单位ms*/

/*定义位置PID参数相关宏*/
/*PID结构体*/
typedef struct
{
    __IO float  SetPoint;                    /* 设定目标 */
    __IO float  ActualValue;                 /* 实际值*/
    __IO float  SumError;                    /* 误差累计*/
    __IO float  Proportion;                  /* 比例常数 P*/
    __IO float  Integral;                    /* 积分常数 I*/
    __IO float  Derivative;                  /* 微分常数 D*/
    __IO float  Error;                               /* Error[-1]*/
    __IO float  LastError;                   /* Error[-1]*/
    __IO float  PrevError;                   /* Error[-2]*/
    __IO float  IngMin;
    __IO float  IngMax;
    __IO float  OutMin;
    __IO float  OutMax;
} PID_TypeDef;
        上述代码可以通过宏INCR_LOCT_SELECT 选择使用哪种PID控制算法,当为0的时候选用位置式PID算法;为1时,选用增量式PID算法。然后接着是关于PID算法相关的结构体PID_TypeDef,在后续的PID控制算法中使用到。
        我们接着看下pid.c的内容:
PID_TypeDef  g_speed_pid;       /*位置PID参数结构体*/
/**
* @brief       初始化LED相关IO口, 并使能时钟
* @param       无
* @retval      无
*/
void pid_init(void)
{
    g_speed_pid.SetPoint = 0;                 /* 设定目标Desired Value*/
    g_speed_pid.ActualValue = 0;         /* 设定目标Desired Value*/
    g_speed_pid.SumError = 0;                 /* 积分值*/
    g_speed_pid.Error = 0;                   /* Error[1]*/
    g_speed_pid.LastError = 0;                 /* Error[-1]*/
    g_speed_pid.PrevError = 0;                 /* Error[-2]*/
    g_speed_pid.Proportion = KP;         /* 比例常数 Proportional Const*/
    g_speed_pid.Integral = KI;                 /* 积分常数 Integral Const*/
    g_speed_pid.Derivative = KD;         /* 微分常数 Derivative Const*/
    g_speed_pid.IngMax = 4000;
    g_speed_pid.IngMin = -4000;
    g_speed_pid.OutMax = 4000;              /* 输出限制 */
    g_speed_pid.OutMin = -4000;   
}

/**
  * 函数名称:位置闭环PID控制设计
  * 输入参数:当前控制量
  * 返 回 值:目标控制量
  * 说    明:无
  */
int32_t increment_pid_ctrl(PID_TypeDef *PID,float Feedback_value)
{
    PID->Error = (float)(PID->SetPoint - Feedback_value);   /* 速度档位偏差*/
#if  INCR_LOCT_SELECT
PID->ActualValue += (PID->Proportion * (PID->Error - PID->LastError))
                                                                                                                        /* E[k]项*/
                        + (PID->Integral * PID->Error)                    /* E[k-1]项*/
                        + (PID->Derivative * (PID->Error - 2 * PID->LastError + PID->PrevError));                                                                                         /* E[k-2]项*/
    PID->PrevError = PID->LastError;                                      /* 存储误差,下次计算*/
    PID->LastError = PID->Error;
#else
    PID->SumError += PID->Error;
    PID->ActualValue = (PID->Proportion * PID->Error)                 /* E[k]项*/
                       + (PID->Integral * PID->SumError)                 /* E[k-1]项*/
                       + (PID->Derivative * (PID->Error - PID->LastError));
                                                                                        /* E[k-2]项*/
    PID->LastError = PID->Error;
#endif
    if(PID->ActualValue > PID->OutMax)
    {
        PID->ActualValue = PID->OutMax;
    }
    else if(PID->ActualValue < PID->OutMin)
    {
        PID->ActualValue = PID->OutMin;
    }
    return ((int32_t)(PID->ActualValue));                                /*返回实际控制数值*/
}
        上述代码可以分为PID初始化及PID控制算法两部分。第一部分主要是将PID算法相关结构体变量进行初始赋值;第二部分是PID控制算法的计算过程,这部分与之前介绍的PID算法一致,这里不多赘述。
        重点内容是在定时器中断服务函数里边定时调用PID控制以及速度的计算,这部分内容在bldc_tim.c中实现,程序如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    uint8_t bldc_dir=0;
    static uint8_t times_count=0;               /* 定时器时间记录 */
    int16_t temp_speed=0;                       /* 临时速度存储 */
    if(htim->Instance == ATIM_TIMX_PWM)        /* 55us */
    {
        /******************* 六步换向 ********************/
        if(g_bldc_motor1.run_flag == RUN)
        {
            if(g_bldc_motor1.dir == CW)
            {
                g_bldc_motor1.step_sta = hallsensor_get_state(MOTOR_1);
            }
            else
            {
                g_bldc_motor1.step_sta = 7 - hallsensor_get_state(MOTOR_1);
            }
            if((g_bldc_motor1.step_sta <= 6)&&(g_bldc_motor1.step_sta >= 1))
            {
                pfunclist_m1[g_bldc_motor1.step_sta-1]();
            }
            else    /* 编码器错误、接触不良、断开等情况 */
            {
                stop_motor1();
                g_bldc_motor1.run_flag = STOP;
            }
            
            /****************** 速度计算 *****************/
            g_bldc_motor1.count_j++;                                /* 计算速度专用计数值 */
            g_bldc_motor1.hall_sta_edge =
                        uemf_edge(g_bldc_motor1.hall_single_sta);        /* 检测单个霍尔信号的变化 */
                     if(g_bldc_motor1.hall_sta_edge == 0)   
            {
                /*计算速度*/
                if(g_bldc_motor1.dir == CW)
                    temp_speed = (SPEED_COEFF/g_bldc_motor1.count_j);
                else
                    temp_speed = -(SPEED_COEFF/g_bldc_motor1.count_j);
                FirstOrderRC_LPF(g_bldc_motor1.speed,temp_speed,0.2379f);
                g_bldc_motor1.no_single = 0;
                g_bldc_motor1.count_j = 0;
            }
            if(g_bldc_motor1.hall_sta_edge == 1)   
            {
                g_bldc_motor1.no_single = 0;
                g_bldc_motor1.count_j = 0;
            }
            if(g_bldc_motor1.hall_sta_edge == 2)  /* 霍尔值一直不变代表未换向 */
            {
                g_bldc_motor1.no_single++;          /*不换相时间累计超时则判定速度为0*/
               
                if(g_bldc_motor1.no_single > 15000)
                {
                    
                    g_bldc_motor1.no_single = 0;
                    g_bldc_motor1.speed = 0;        /* 超时换向 判定为停止 速度为0 */
                }
            }
            /****************** 位置记录 *******************/
            if(g_bldc_motor1.step_last != g_bldc_motor1.step_sta)
            {
                bldc_dir = check_hall_dir(&g_bldc_motor1);
                if(bldc_dir == CCW)
                {
                    g_bldc_motor1.pos -=1;
                }
                else if(bldc_dir == CW)
                {
                    g_bldc_motor1.pos +=1;
                }
                g_bldc_motor1.step_last = g_bldc_motor1.step_sta;
            }
            /****************** PID控制 ********************/
                temp_pwm1 = increment_pid_ctrl(&g_speed_pid,g_bldc_motor1.speed);
                FirstOrderRC_LPF(motor_pwm_s,temp_pwm1,0.085);
                if(motor_pwm_s < 0)
                {
                    g_bldc_motor1.pwm_duty = -motor_pwm_s;
                }
                else
                {
                   g_bldc_motor1.pwm_duty = motor_pwm_s;
                }
            /***************** 三相电流计算 ****************/
            for(i=0; i<3; i++)
            {
                adc_val_m1 = g_adc_val[i+2];
                adc_amp
= adc_val_m1
                                adc_amp_offset
[ADC_AMP_OFFSET_TIMES];
               
                if(adc_amp
>=0)   /* 去除反电动势引起的负电流数据 */
                 adc_amp_un
=adc_amp;
            }
            /* 运算母线电流(母线电流为任意两个有开关动作的相电流之和)*/
            if(g_bldc_motor1.step_sta == 0x05)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[1])*ADC2CURT;/* UV */
            }
            else if(g_bldc_motor1.step_sta== 0x01)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[2])*ADC2CURT;/* UW */
            }
            else if(g_bldc_motor1.step_sta== 0x03)
            {
                adc_amp_bus= (adc_amp_un[1]+ adc_amp_un[2])*ADC2CURT;/* VW */
            }
            else if(g_bldc_motor1.step_sta== 0x02)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[1])*ADC2CURT;/* UV */
            }
            else if(g_bldc_motor1.step_sta == 0x06)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[2])*ADC2CURT;/* WU */
            }
            else if(g_bldc_motor1.step_sta == 0x04)
            {
                adc_amp_bus= (adc_amp_un[2]+ adc_amp_un[1])*ADC2CURT;/* WV */
            }
        }
    }
    else if(htim->Instance == TIM6)
    {
        /************ 采集电机停机状态下的偏置电压 **************/
        times_count++;
        if(g_bldc_motor1.run_flag == STOP)
        {
            uint8_t i;
            uint32_t avg[3] = {0,0,0};
            adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];
            adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3];
            
            adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];

            adc_amp_offset_p ++;
            NUM_CLEAR(adc_amp_offset_p,ADC_AMP_OFFSET_TIMES);
            for(i=0; i<ADC_AMP_OFFSET_TIMES; i++)
            {
                avg[0] += adc_amp_offset[0]
;
                avg[1] += adc_amp_offset[1]
;
                avg[2] += adc_amp_offset[2]
;
            }
            for(i=0; i<3; i++)
            {
                avg
/= ADC_AMP_OFFSET_TIMES;
                adc_amp_offset
[ADC_AMP_OFFSET_TIMES] = avg;
            }
        }
    }
}

        主要看下速度计算部分,通过函数uemf_edge实现对霍尔状态变化的判定,当返回值会0时,开始计算电机转速,其中g_bldc_motor1.count_j代表霍尔的计数值,而进入定时器更新中断的频率18KHz即为霍尔的脉冲频率,我们所使用的无刷电机为2对级,所以根据前面所介绍的测速原理即可算出电机的实际转速,然后根据旋转方向设置速度正负,接着使用滤波算法过滤速度就得到了实际速度值,经过计数分频,进入PID计算函数,将上述计算的速度值代入PID控制算法,得到PWM输出值,最终赋值到定时器,在下一次中断时使用新的PWM用于驱动无刷电机。相应的主函数的PWM设定只需要换成PID的目标值设定即可,主函数main.c内容如下。
int main(void)
{
    uint16_t adc_vbus,adc_temp;
    uint8_t debug_cmd=0;
    float current_lpf[4]= {0.0f};
    uint8_t key,t;
    char buf[32];
    float current[3]= {0.0f};

    HAL_Init();                                      /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);             /* 设置时钟,168Mhz */
    delay_init(168);                                 /* 延时初始化 */
    usart_init(115200);                              /* 串口初始化为115200 */
    led_init();                                      /* 初始化LED */
    key_init();                                      /* 初始化按键 */
    lcd_init();                                      /* 初始化LCD */
    bldc_init(168000/18-1,0);
    bldc_ctrl(MOTOR_1,CCW,0);                            /* 初始无刷电机接口1速度 */
    pid_init();
    g_point_color = WHITE;
    g_back_color  = BLACK;
    lcd_show_string(10,10,200,16,16,"BLDC Motor Test",g_point_color);
    lcd_show_string(10,30,200,16,16,"KEY0:Step++",g_point_color);
    lcd_show_string(10,50,200,16,16,"KEY1:Step--",g_point_color);
    lcd_show_string(10,70,200,16,16,"KEY2:Stop",g_point_color);
    adc_nch_dma_init();
   
#if DEBUG_ENABLE                                                 /* 开启调试 */
    debug_init();                                                 /* PID调试初始化 */
    debug_send_motorcode(BLDC_MOTOR );        /* 直流无刷电机 */
    debug_send_motorstate(IDLE_STATE);        /* 电机空闲 */
    /* 初始化同步数据(选择第x组PIDX,目标速度地址,P,I,D参数)到上位机 */
debug_send_initdata(TYPE_PID1,User_SetPoint,S_KP,S_KI,S_KD);
/*位置环PID参数(PID1)*/
#endif

    while (1)
    {
        t++;
        if(t % 20 == 0)
        {
            sprintf(buf,"PWM_Duty:%.1f%%",(float)((g_bldc_motor1.pwm_duty/
MAX_PWM_DUTY)*100));                                         /* 显示控制PWM占空比 */
            lcd_show_string(10,110,200,16,16,buf,g_point_color);
            sprintf(buf,"SetSpeed:%4d   ",(int16_t)*User_SetPoint);   
/* 显示设置速度 */
            lcd_show_string(10,110,200,16,16,buf,g_point_color);
            sprintf(buf,"M1 Speed:%4d   ",(int16_t)g_bldc_motor1.speed);
            lcd_show_string(10,130,200,16,16,buf,g_point_color);
            sprintf(buf,"M1 pos:%4d",g_bldc_motor1.pos); /* 显示测量速度 */
            lcd_show_string(10,150,200,16,16,buf,g_point_color);
            sprintf(buf,"Power:%.3fV ",g_adc_value[0]*ADC2VBUS);
            lcd_show_string(10,190,200,16,16,buf,g_point_color);
            sprintf(buf,"Temp:%.1fC ",get_temp(g_adc_value[1]));
            lcd_show_string(10,210,200,16,16,buf,g_point_color);

            LED0_TOGGLE();                      /* LED0(红灯) 翻转 */
            
            printf("PB1数字原始值为:%d\r\n",(g_adc_value[0]));
            printf("PA0数字原始值为:%d\r\n",(g_adc_value[1]));
            printf("PB0数字原始值为:%d\r\n",(g_adc_value[2]));
            printf("PA6数字原始值为:%d\r\n",(g_adc_value[3]));
            printf("PA3数字原始值为:%d\r\n",(g_adc_value[4]));

            printf("Valtage:%.1fV \r\n",    g_adc_value[0]*ADC2VBUS);
            printf("Temp:%.1fC \r\n",       get_temp(g_adc_value[1]));
            printf("\r\n");
            
            if(g_bldc_motor1.run_flag==STOP)/* 停机的电流显示 */
            {
                current_lpf[0]=0;
                current_lpf[1]=0;
                current_lpf[2]=0;
            }
            current[0]=adc_amp_un[0]* ADC2CURT;/* U */
            current[1]=adc_amp_un[1]* ADC2CURT;/* V */
            current[2]=adc_amp_un[2]* ADC2CURT;/* W */
            
            /* 一阶数字滤波 滤波系数0.1 用于显示 */
            FirstOrderRC_LPF(current_lpf[0],current[0],0.1f);
            FirstOrderRC_LPF(current_lpf[1],current[1],0.1f);
            FirstOrderRC_LPF(current_lpf[2],current[2],0.1f);
            FirstOrderRC_LPF(current_lpf[3],adc_amp_bus,0.1f);
            sprintf(buf,"Amp U:%.3fmA ",(float)current_lpf[0]);
            lcd_show_string(10,230,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp V:%.3fmA ",(float)current_lpf[1]);
            lcd_show_string(10,250,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp W:%.3fmA ",(float)current_lpf[2]);
            lcd_show_string(10,270,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp Bus:%.3fmA ",(float)adc_amp_bus);
            lcd_show_string(10,290,200,16,16,buf,g_point_color);
            
            printf("Amp U:%.3fmA \r\n",(float)current_lpf[0]);
            printf("Amp V:%.3fmA \r\n",(float)current_lpf[1]);
            printf("Amp W:%.3fmA \r\n",(float)current_lpf[2]);
            printf("Amp Bus:%.3fmA \r\n",(float)adc_amp_bus);
        }

        key = key_scan(0);
        if(key == KEY0_PRES)
        {
            g_bldc_motor1.run_flag=RUN;                                /* 开启运行 */
            start_motor1();                                                        /* 开启运行 */
            if(*user_setpoint==0 && g_bldc_motor1.dir == CW)
            {
                g_bldc_motor1.dir = CCW;
            }

            *user_setpoint+=400;                                                /* 逆时针旋转下递增 */
            if(*user_setpoint >= 4000)
                *user_setpoint = 4000;
            if(*user_setpoint==0)
            {
                g_bldc_motor1.run_flag = STOP;                  /* 标记停机 */
                stop_motor1();                                          /* 停机 */
                g_bldc_motor1.speed = 0;
                motor_pwm_s = 0;
                g_bldc_motor1.pwm_duty = 0;
            }
        }
        if(key == KEY1_PRES)                                  /* 按下KEY1开启电机 */
        {
            g_bldc_motor1.run_flag=RUN;                                /* 开启运行 */
            start_motor1();                                                        /* 开启运行 */
            if(*user_setpoint==0 && g_bldc_motor1.dir == CCW)
            {
                g_bldc_motor1.dir = CW;
            }
            *user_setpoint -= 400;                                        /* 逆时针旋转下递增 */
            if(*user_setpoint <= -4000)
                *user_setpoint = -4000;
            if(*user_setpoint==0)
            {
                g_bldc_motor1.run_flag = STOP;                  /* 标记停机 */
                stop_motor1();                                          /* 停机 */
                g_bldc_motor1.speed = 0;
                motor_pwm_s = 0;
                g_bldc_motor1.pwm_duty = 0;
            }
        }
        if(key == KEY2_PRES)                                  /* 按下KEY2关闭电机 */
        {
            pid_init();
            g_bldc_motor1.count_j=0;
            bldc_speed_stop();
        }
        
#if DEBUG_ENABLE
        /* Debug发送部分 */
        /* 主要显示参数 */
        debug_send_valtage(adc_vbus * ADC2VBUS);                                /* 发送电压 */
        debug_send_speed(g_bldc_motor1.speed);                                        /* 发送速度 */
        debug_send_distance((uint64_t)(g_bldc_motor1.sum_pos));        /* 发送总圈数 */
        debug_send_temp(50,get_temp(adc_temp));/* 发送电机温度、驱动板温度 */
        debug_send_current((float)(current_lpf[0]/1000),(float)(current_lpf[0]\
/1000),(float)(current_lpf[0]/1000));/*发送电流*/
        /* 电流波形和速度波形 */
        debug_send_wave_data(1,(int16_t)g_bldc_motor1.speed);
/* 选择通道1 发送实际速度 */
        debug_send_wave_data(2,(int16_t)*user_setpoint);/*选择通道1 发送实际速度*/
        debug_send_wave_data(3,current_lpf[0]);                /*选择通道1 发送实际电流u*/
        debug_send_wave_data(4,current_lpf[1]);                /*选择通道2 发送实际电流v*/
        debug_send_wave_data(5,current_lpf[2]);                /*选择通道3 发送实际电流w*/
        debug_send_wave_data(6,current_lpf[3]);                /*选择通道3 发送实际母线电流*/
        /* debug接收部分 */
        debug_receive_pid(TYPE_PID1,(float*)&g_speed_pid.Proportion,
/* 查询接收PID助手的PID1参数 */
        (float*)&g_speed_pid.Integral,(float*)&g_speed_pid.Derivative);
        debug_cmd=debug_receive_ctrl_code();                        /* 读取命令 */
        if(debug_cmd==HALT_CODE)                                                /* 停机 */
        {
            stop_motor1();
            pid_init();
            g_bldc_motor1.run_flag=STOP;                                /* 标记停机 */
            g_bldc_motor1.pwm_duty=0;
        }
        else if(debug_cmd==RUN_CODE)                                        /* 运行 */
        {
            g_bldc_motor1.run_flag=RUN;                                /* 运行标记 */
            *user_setpoint=400;                                                /* 自动设置目标 */
            debug_data_temp=*user_setpoint;
            start_motor1();                                                        /* 启动电机 */
            debug_send_motorstate(RUN_STATE);                /* 电机运行 */
        }
        else if (debug_cmd==BREAKED)/* 刹车(电机停止 点击电机运行才可解除)*/
        {
            *user_setpoint=0;                                                        /* 减速直至0 */
            debug_send_motorstate(BREAKED_STATE);        /* 电机刹车 */
        }
#endif
        
        delay_ms(10);
    }
}

void bldc_speed_stop(void)
{
    pid_init();                             /* 重新初始化PID,防止积分过大失控 */
    g_bldc_motor1.run_flag = STOP;  /* 标记停机 */
    stop_motor1();                          /* 停机 */
    g_bldc_motor1.speed = 0;
    motor_pwm_s = 0;
    g_bldc_motor1.pwm_duty = 0;
}
        main函数主要初始化基本外设以及BLDC等等,然后添加上位机通信协议,将实际转速数据发送给上位机的通道1上进行显示,目标转速数据发送给上位机的通道2上进行显示。按键判断逻辑:按下KEY0目标转速加400RPM,按下KEY1目标转速减400RPM,按下KEY2停止电机旋转。
24.1.4 下载验证
下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD上显示按键功能、占空比以及电机速度信息和显示程序计算的电压、电流等信息,当我们按下KEY0,目标速度将增大;按下KEY1,目标速度将减小;按下KEY2,电机将停止。目标速度为正数时,电机正转,反之电机反转。我们再打开PID调试助手(注意接上串口1的USB_TTL连接至电脑),选择对应的串口端口,接着勾选通道1和2,点击“开始”按钮,即可开始显示波形,如下图24.1.3.1,大家可以自己下载程序并点击按键测试效果。
image012.jpg
图24.1.4.1 速度PID调节效果

        图24.1.4.1中,橙线代表目标速度,红线代表实际速度,当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。
        注意:1、本实验需要使用USB数据线连接开发板的串口1到电脑,并启动电机之后才会有波形变化;2、如果发现波形不对,请检查电机接线;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节。
image014.jpg
图24.1.4.2 屏幕显示效果


24.2 速度&电流双闭环控制
        要实现速度+电流双闭环控制,只需在速度环的基础上加上电流环部分即可。三相电流的采集已经在前面的课程(第23章)学习过了,所以下面我们直接进入代码实现。
24.2.1 硬件设计
1、例程功能
        本实验以电机开发板的直流有刷/无刷电机驱动接口1为例,基于无刷速度闭环实验,加入电流环控制,对电机进行速度环+电流环双闭环控制。
        通过PID电流环的初始化设置转矩大小,即电流大小。目标电流越大,转矩越大。
        当按键0按下,就增大目标速度值;当按键1按下,就减小目标速度值。目标速度的绝对值大小决定电机的速度,它的正负决定电机的正反转方向。按下按键2则马上停止电机。
        屏幕显示按键功能、占空比、目标转速以及实际转速以及显示无刷电机电压、电流和温度、速度等等信息。
        可通过串口1即USB_TTL接口连接正点原子PID调试助手,查看PID波形。
        LED0闪烁指示程序运行。

2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
    KEY0 – PE2
        KEY1 – PE3
        KEY2 – PE4
3)定时器:
        TIM1_CH1:PA8
        TIM1_CH2:PA9
        TIM1_CH3:PA10
   IO:PB13\14\15
   使能引脚:SHDN: PF10
4)无刷电机
5)无刷电机驱动板
6)12-60V的DC电源
7)ADC:ADC1_CH9:PB1
         ADC1_CH0:PA0
         ADC1_CH8:PB0
         ADC1_CH6:PA6
         ADC1_CH3:PA3
3、原理图
image015.jpg
图24.2.1.1 无刷电机接口1原理图

        接口涉及的IO如下。
表24.2.1.1 无刷相关IO口说明

        驱动板和电机的连接方式之前已经介绍过了,驱动板和开发板连接只需要使用排线连接即可,实物连接如下。
图24.2.1.2 开发板&驱动板连接图

24.2.2 程序设计
24.2.2.1 无刷电机速度+电流双闭环的配置步骤

1)实现电机基本驱动
        实现电机的基础驱动函数,启停、6步换向组合等等
2)初始化ADC
        初始化ADC通道的IO,设置ADC工作方式、DMA等等
3)PID闭环控制实现
        实现PID的初始参数赋值、PID计算等等
4)上位机通信协议
        编写上位机通信代码,可在上位机上实时显示当前速度与目标速度的波形变化
5)编写中断服务函数
        PWM中断用于换向控制、堵转检测等等,添加PID的周期计算调用
24.2.2.2 程序流程图
image017.png
图24.2.2.2.1 无刷电机速度+电流双闭环控制流程图

24.2.2.3 程序解析
这里我们只讲解核心代码,定时器及ADC的相关程序都和之前一致,这里就不重复列出了,速度+电流双闭环的PID参数的定义有不同。实际内容如下所示。
/* PID相关参数 */

#define  C_KP      2.00f                    /* P参数 */
#define  C_KI      0.20f                    /* I参数 */
#define  C_KD      0.01f                    /* D参数 */

#define  S_KP      0.00800f                 /* P参数 */
#define  S_KI      0.00025f                 /* I参数 */
#define  S_KD      0.00020f                 /* D参数 */
#define SMAPLSE_PID_SPEED  40             /* 采样率 单位ms */
其中C_KP,C_KI,C_KD为电流环的PID参数,S_KP,S_KI,S_KD为速度环的PID参数,PID的计算过程和之前的一致,这里就不贴出来了,大家感兴趣可以打开源码查看。重点内容放在定时器中断回调函数中,在bldc_tim.c中实现,贴出的程序删除了部分换向相关的代码避免篇幅太长,中断回调函数内容如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    uint8_t bldc_dir=0;
    static uint8_t times_count=0;                       /* 定时器时间记录 */
    int16_t temp_speed=0;                               /* 临时速度存储 */
    if(htim->Instance == ATIM_TIMX_PWM)         /* 55us */
    {
        if(g_bldc_motor1.run_flag == RUN)
        {
            … …
            /*********************** 速度计算 **********************/
            g_bldc_motor1.count_j++;                        /* 计算速度专用计数值 */
            g_bldc_motor1.hall_sta_edge =
                   uemf_edge(g_bldc_motor1.hall_single_sta);
            if(g_bldc_motor1.hall_sta_edge == 0)            /*统计单个霍尔信号的高电平时间*/
            {
                /* 计算速度 */
                if(g_bldc_motor1.dir == CW)
                    temp_speed = (SPEED_COEFF/g_bldc_motor1.count_j);
                else
                    temp_speed = -(SPEED_COEFF/g_bldc_motor1.count_j);
                FirstOrderRC_LPF(g_bldc_motor1.speed,temp_speed,0.2379);
                g_bldc_motor1.no_single = 0;
                g_bldc_motor1.count_j = 0;
            }
            if(g_bldc_motor1.hall_sta_edge == 1)
            {
                g_bldc_motor1.no_single = 0;
                g_bldc_motor1.count_j = 0;
            }
            if(g_bldc_motor1.hall_sta_edge == 2)                    /* 霍尔值一直不变代表未换向 */
            {
                g_bldc_motor1.no_single++;                  /* 不换相时间累计*/
                if(g_bldc_motor1.no_single > 15000)
                {
                    g_bldc_motor1.no_single = 0;
                    g_bldc_motor1.speed = 0;                 /* 超时换向 判定为停止 */
                }
            }
            /******************* 位置记录以及堵转标记 **************/
            if(g_bldc_motor1.step_last != g_bldc_motor1.step_sta)
            {
                g_bldc_motor1.hall_keep_t = 0;
                bldc_dir = check_hall_dir(&g_bldc_motor1);
                if(bldc_dir == CCW)
                {
                    g_bldc_motor1.pos -=1;
                }
                else if(bldc_dir == CW)
                {
                    g_bldc_motor1.pos +=1;
                }
                g_bldc_motor1.step_last = g_bldc_motor1.step_sta;
            }
            else if(g_bldc_motor1.run_flag == RUN)              /* 运行且霍尔保持时 */
            {
                g_bldc_motor1.hall_keep_t++;                          /* 换向一次所需计数值*/
                if(g_bldc_motor1.hall_keep_t > 15000)                /* 堵转 */
                {
                    g_bldc_motor1.hall_keep_t = 0;
#if LOCK_TAC
                    stop_motor1();
                    g_bldc_motor1.run_flag = STOP;;            /* 标记停机 */
                    g_bldc_motor1.pwm_duty = 0;
#endif
                    g_bldc_motor1.locked_rotor = 1;               /* 标记堵转 */
                }
            }
        /********************* PID控制 **********************/
        if(g_bldc_motor1.run_flag == RUN)                                /*进入PID闭环控制*/
        {
            pid_c_count++;
            pid_s_count++;

            if(pid_s_count > 2)
            {
/* 开启上位机调试 在PID执行之前对调节数值进行预先判断 */
#if DEBUG_ENABLE
                          /* 控制目标调节范围(3000~-3000)并且最大步进值不超过6000 RPM */
                debug_set_point_range(3000,-3000,6000);         
                debug_data_temp = *user_setpoint;
                if(*user_setpoint < 0)                              /* 上位机指令切换旋转方向 */
                {
                    /* 为简化控制逻辑 只以电机状态作为判依据(同步)*/
                    if(g_bldc_motor1.speed == 0)
                    {
                        g_bldc_motor1.dir = CCW;
                    }
                    else if(g_bldc_motor1.dir == CW &&g_bldc_motor1.speed != 0)
                    {
                        *user_setpoint = 0;
                    }
                }
                else if(*user_setpoint > 0)
                {
                    if(g_bldc_motor1.speed == 0)
                    {
                        g_bldc_motor1.dir = CW;
                    }
                    else if(g_bldc_motor1.dir == CCW&&g_bldc_motor1.speed != 0)
                        *user_setpoint = 0;
                    }
                }
#endif
                           /******************* PID计算 ********************/
                temp_pwm1 = increment_pid_ctrl(&g_speed_pid,g_bldc_motor1.speed);
                FirstOrderRC_LPF(motor_pwm_s,temp_pwm1,0.085);
                if(motor_pwm_s < 0)
                {
                    motor_pwm_sl = -motor_pwm_s;
                }
                else
                {
                    motor_pwm_sl = motor_pwm_s;
                }

                *user_setpoint = debug_data_temp;           /* 重新保持上位机指令要求 */
                pid_s_count = 0;
            }

            if(pid_c_count > 1)                                     /* 电流环 */
            {
                /* 换向尖峰电流大于设定的电流值将导致PID调节转至电流环调节 */
                if(adc_amp_bus > (g_current_pid.SetPoint - 20))
                {
                    cf_count++;                                     /* 滤除换向尖峰电流的影响 */
                    if(cf_count > 4)
                    {
                        cf_count = 0;
                        temp_pwm2 =
                                         increment_pid_ctrl(&g_current_pid,adc_amp_bus);
                        FirstOrderRC_LPF(motor_pwm_c,temp_pwm2,0.085);
                    }
                }
                else
                {
                    cf_count = 0;
                    temp_pwm2 = increment_pid_ctrl(&g_current_pid,adc_amp_bus);
                    FirstOrderRC_LPF(motor_pwm_c,temp_pwm2,0.085);
                }
                pid_c_count = 0;
            }
            /* 电流环输出值大于速度环输出则使用速度环调节 */
            if(motor_pwm_c > motor_pwm_sl)
            {
                g_bldc_motor1.pwm_duty = motor_pwm_sl;
                if(motor_pwm_s < 0)                             /* 正反转积分控制 */
                    g_current_pid.Ui = -g_speed_pid.Ui;
                else
                    g_current_pid.Ui = g_speed_pid.Ui;
            }
            else  /* 速度环输出值大于电流环输出则使用电流环调节 */
            {
                g_bldc_motor1.pwm_duty = motor_pwm_c;
                if(motor_pwm_s < 0)
                    g_speed_pid.Ui = -g_current_pid.Ui;
                else
                    g_speed_pid.Ui = g_current_pid.Ui;
            }
        }
        }
    }
    if(htim->Instance == TIM6)
    {
        /***************** 采集电机停机状态下的偏置电压 *****************/
        times_count++;
        if(g_bldc_motor1.run_flag == STOP)
        {
            uint8_t i;
            uint32_t avg[3] = {0,0,0};
               /* 得到还未开始运动时三相的基准电压 */
            adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];     
            adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3];
            adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];

            adc_amp_offset_p++;
                  /* 最大采集ADC_AMP_OFFSET_TIMES次,超过即从0开始继续采集 */
            NUM_CLEAR(adc_amp_offset_p,ADC_AMP_OFFSET_TIMES);  
                  /* 将采集的每个通道值累加 */     
            for(i = 0; i < ADC_AMP_OFFSET_TIMES; i++)               
            {
                avg[0] += adc_amp_offset[0]
;
                avg[1] += adc_amp_offset[1]
;
                avg[2] += adc_amp_offset[2]
;
            }
            for(i = 0; i < 3; i++)                                /* 取平均即软件滤波 */
            {
                avg
/= ADC_AMP_OFFSET_TIMES;
                adc_amp_offset
[ADC_AMP_OFFSET_TIMES] = avg;   
            }
        }
        /****************** 定时判断电机是否发生堵塞 ********************/
        if(times_count == SMAPLSE_PID_SPEED)
        {

            if(g_bldc_motor1.locked_rotor == 1)                         /* 堵转处理*/
            {
                clc++;
                if(clc > 50)                                              /* 延迟2s后重新启动 */
                {
#if DEBUG_ENABLE /*开启调试*/
                    debug_send_motorstate(RUN_STATE);                 /* 电机运行*/
#endif
                    clc = 0;
                    pid_init();
                    stop_motor1();
                    g_speed_pid.SetPoint = 400.0;               /* 400PRM */
                    g_bldc_motor1.pwm_duty = 500;               /* 加速启动速度 */
                    g_bldc_motor1.run_flag = RUN;               /* 开启运行 */
                    start_motor1();                                     /* 运行电机 */
                    g_bldc_motor1.locked_rotor = 0;
                }
            }

            times_count = 0;
        }

    }
}
        在定时器1中断里首先优先进行电流环的PID计算,这里需要进行滤波,避免换向尖峰对PID的影响,接着通过霍尔跳变数量计算电机转速,经过计数分频,进入速度环PID计算函数,带入上述计算的速度,得到速度环计算出的PWM输出值,然后将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出,最终赋值到定时器,在下一次中断时使用新的PWM用于驱动无刷电机。这里我们还在中断中加上了堵转处理,当电机因意外或过流导致停机时,当堵转时间超过2s,此时会重新初始化PID并以400RPM的初始速度重新开启电机旋转。接着看来先下主函数main.c内容如下。
int main(void)
{
    uint16_t adc_vbus,adc_temp;
    uint8_t debug_cmd=0;
    float current_lpf[4]= {0.0f};
    uint8_t key,t;
    char buf[32];
    float current[3]= {0.0f};
    uint16_t strar_sf=0;//启动速度标识
    int16_t speed_diplay=0;
    float User_SetPoint_temp=0.0;

    HAL_Init();                                      /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);           /* 设置时钟,168Mhz */
    delay_init(168);                                 /* 延时初始化 */
    usart_init(115200);                              /* 串口初始化为115200 */
    led_init();                                      /* 初始化LED */
    key_init();                                      /* 初始化按键 */
    lcd_init();                                      /* 初始化LCD */
    bldc_init(168000/18-1,0);
    bldc_ctrl(MOTOR_1,CCW,0);                            /* 初始无刷电机接口1速度 */
    pid_init();
    g_point_color = WHITE;
    g_back_color  = BLACK;
    lcd_show_string(10,10,200,16,16,"BLDC Motor Test",g_point_color);
    lcd_show_string(10,30,200,16,16,"KEY0:Step++",g_point_color);
    lcd_show_string(10,50,200,16,16,"KEY1:Step--",g_point_color);
    lcd_show_string(10,70,200,16,16,"KEY2:Stop",g_point_color);
    adc_nch_dma_init();

#if DEBUG_ENABLE         /* 开启调试 */
    debug_init();        /* PID调试初始化 */
    debug_send_motorcode(BLDC_MOTOR );/* 直流电机 */
    debug_send_motorstate(IDLE_STATE);/* 电机空闲 */
    /* 初始化同步数据(选择第x组PIDX,目标速度地址,P,I,D参数)到上位机 */
debug_send_initdata(TYPE_PID1,User_SetPoint,C_KP,C_KI,C_KD);
/* 位置环PID参数(PID1) */
debug_send_initdata(TYPE_PID2,User_SetPoint,S_KP,S_KI,S_KD);
/* 速度环PID参数(PID2) */
#endif
    while (1)
    {
        t++;
        if(t % 20 == 0)
        {
             sprintf(buf,"PWM_Duty:%.1f%%  ",
(float)((g_bldc_motor1.pwm_duty/MAX_PWM_DUTY)*100));         
            lcd_show_string(10,110,200,16,16,buf,g_point_color);

            user_setpoint_temp = (*user_setpoint);
            speed_diplay = g_bldc_motor1.speed;
/* 显示设置速度 */
            sprintf(buf,"SetSpeed:%4d   ",(int16_t)user_setpoint_temp);                                 
            lcd_show_string(10,110,200,16,16,buf,g_point_color);
/* 显示转速 */
            sprintf(buf,"M1 speed:%4d   ",speed_diplay);                                                
            lcd_show_string(10,130,200,16,16,buf,g_point_color);
/* 显示位置变化 */
            sprintf(buf,"M1 pos:%4d",g_bldc_motor1.pos);                                                
            lcd_show_string(10,150,200,16,16,buf,g_point_color);
            sprintf(buf,"PWM_Duty:%.1f%%  ",
(float)((g_bldc_motor1.pwm_duty/(float)MAX_PWM_DUTY)*100));
/* 显示控制PWM占空比 */
            lcd_show_string(10,170,200,16,16,buf,g_point_color);


            sprintf(buf,"Power:%.3fV ",g_adc_value[0]*ADC2VBUS);
            lcd_show_string(10,190,200,16,16,buf,g_point_color);
            sprintf(buf,"Temp:%.1fC ",get_temp(g_adc_value[1]));
            lcd_show_string(10,210,200,16,16,buf,g_point_color);
            LED0_TOGGLE();                                                                           /* LED0(红灯) 翻转 */
            /* 三相电流计算 */
            current[0] = adc_amp_un[0]* ADC2CURT;/*U*/
            current[1] = adc_amp_un[1]* ADC2CURT;/*V*/
            current[2] = adc_amp_un[2]* ADC2CURT;/*W*/

            /*一阶数字滤波 滤波系数0.1 用于显示*/
            FirstOrderRC_LPF(current_lpf[0],current[0],0.1f);
            FirstOrderRC_LPF(current_lpf[1],current[1],0.1f);
            FirstOrderRC_LPF(current_lpf[2],current[2],0.1f);
            FirstOrderRC_LPF(current_lpf[3],adc_amp_bus,0.1f);
            if(g_bldc_motor1.run_flag == STOP)                      /* 停机的电流显示 */
            {
                current_lpf[0]=0;
                current_lpf[1]=0;
                current_lpf[2]=0;
                current_lpf[3]=0;
            }
            
            sprintf(buf,"Amp U:%.3fmA ",(float)current_lpf[0]);
            lcd_show_string(10,230,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp V:%.3fmA ",(float)current_lpf[1]);
            lcd_show_string(10,250,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp W:%.3fmA ",(float)current_lpf[2]);
            lcd_show_string(10,270,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp Bus:%.3fmA ",(float)adc_amp_bus);
            lcd_show_string(10,290,200,16,16,buf,g_point_color);
        }

        key = key_scan(0);
        if(key == KEY0_PRES)                                      /* 按下KEY0目标速度值++*/
        {
            g_bldc_motor1.run_flag = RUN;                          /* 开启运行 */
            start_motor1();                                          /* 开启运行 */

            if(g_bldc_motor1.dir == CCW && *user_setpoint == 0) /* 切换方向条件*/
            {
                g_bldc_motor1.dir = CW;
            }
            *user_setpoint += 400;                                  /* 逆时针旋转下递增 */
            if(*user_setpoint >= 3000)                          /* 最高不超过3000PRM */
                *user_setpoint = 3000;
            if(*user_setpoint == 0)
            {
                pid_init();                                         /* 初始化PID */
                g_bldc_motor1.run_flag = STOP;                  /* 标记停机 */
                stop_motor1();                                      /* 停机 */
                g_bldc_motor1.speed = 0;
                motor_pwm_s = 0;
                g_bldc_motor1.pwm_duty = 0;
            }
            debug_data_temp = *user_setpoint;
        }
        else if(key == KEY1_PRES)                                 /* 按下KEY1目标速度值--*/
        {
            g_bldc_motor1.run_flag = RUN;                          /* 开启运行 */
            start_motor1();                                         /* 运行电机 */
            /* 切换方向条件 */
            if(g_bldc_motor1.dir == CW && *user_setpoint == 0)
            {
                g_bldc_motor1.dir = CCW;
            }
            *user_setpoint -= 400;                                  /* 逆时针旋转下递增 */
            if(*user_setpoint <= -3000)                          /* 最高不超过300PRM */
                *user_setpoint = -3000;
            if(*user_setpoint == 0)
            {
                pid_init();                                         /* 初始化PID */
                g_bldc_motor1.run_flag = STOP;                    /* 标记停机 */
                stop_motor1();                                       /* 停机 */
                g_bldc_motor1.speed = 0;
                motor_pwm_s = 0;
                g_bldc_motor1.pwm_duty = 0;
            }

            debug_data_temp = *user_setpoint;
        }
        else if(key == KEY2_PRES)                               /* 按下KEY2关闭电机 */
        {
            bldc_speed_stop();
        }

#if DEBUG_ENABLE
        /* Debug发送部分 */
        /* 主要显示参数 */
        debug_send_valtage(adc_vbus * ADC2VBUS);                                /* 发送电压 */
        debug_send_speed(g_bldc_motor1.speed);                                        /* 发送速度 */
        debug_send_distance((uint64_t)(g_bldc_motor1.sum_pos));        /* 发送总圈数 */
        debug_send_temp(50,get_temp(adc_temp));/* 发送电机温度、驱动板温度 */
        debug_send_current((float)(current_lpf[0]/1000),(float)(current_lpf[0]/
1000),(float)(current_lpf[0]/1000));/* 发送电流 */
/* 选择通道1 发送实际速度 */
        debug_send_wave_data(1,(int16_t)g_bldc_motor1.speed);
/* 选择通道2 发送目标速度 */
        debug_send_wave_data(2,(int16_t)*user_setpoint);     
/* 选择通道3 发送实际电流U */           
        debug_send_wave_data(3,current_lpf[0]);   
/* 选择通道4 发送实际电流V */                     
        debug_send_wave_data(4,current_lpf[1]);      
/* 选择通道5 发送实际电流W */                  
        debug_send_wave_data(5,current_lpf[2]);  
/* 选择通道7 发送实际电流 */                       
        debug_send_wave_data(7,current_lpf[3]);         
/* 选择通道8 发送目标电流 */               
        debug_send_wave_data(8,g_current_pid.SetPoint);                 
        /* Debug接收部分 */
        debug_receive_pid(TYPE_PID1,(float*)&g_current_pid.Proportion,
/* 查询接收PID助手的PID1参数 */
        (float*)&g_current_pid.Integral,(float*)&g_current_pid.Derivative);
        debug_receive_pid(TYPE_PID2,(float*)&g_speed_pid.Proportion,
/* 查询接收PID助手的PID2参数 */
        (float*)&g_speed_pid.Integral,(float*)&g_speed_pid.Derivative);
        debug_cmd=debug_receive_ctrl_code();/* 读取命令 */
        if(debug_cmd==HALT_CODE)/* 停机 */
        {
            stop_motor1();
            pid_init();
            g_bldc_motor1.run_flag=STOP;                /* 标记停机 */
            g_bldc_motor1.pwm_duty=0;
        }
        else if(debug_cmd==RUN_CODE)                        /* 运行 */
        {
            g_bldc_motor1.run_flag=RUN;                /* 运行标记 */
            *user_setpoint=400;                                /* 自动设置目标 */
            debug_data_temp=*user_setpoint;
            start_motor1();                                        /* 启动电机 */
            debug_send_motorstate(RUN_STATE);/* 电机运行 */
        }
        else if (debug_cmd==BREAKED)                        /* 刹车(电机停止 点击电机运行才可解除)*/
        {
            *user_setpoint=0;                                        /* 减速直至0*/
            debug_send_motorstate(BREAKED_STATE);/*电机刹车*/
        }
#endif

        delay_ms(10);
    }
}

void bldc_speed_stop(void)
{
    pid_init();
    g_bldc_motor1.run_flag = STOP;  /* 标记停机 */
    stop_motor1();                          /* 停机 */
    g_bldc_motor1.speed = 0;
    motor_pwm_s = 0;
    g_bldc_motor1.pwm_duty = 0;
}
        main函数主要初始化基本外设以及BLDC等等,然后添加上位机通信协议,将实际转速数据、目标转速数据、实际电流、目标电流分别发送给上位机的通道1、2、7、8上进行显示。按键判断逻辑:按下KEY0目标转速加400RPM,按下KEY1目标转速减400RPM,按下KEY2停止电机旋转
24.2.3 下载验证
下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD上显示按键功能、占空比以及电机速度信息和显示程序计算的电压、电流等信息,当我们按下KEY0,目标速度将增大;按下KEY1,目标速度将减小;按下KEY2,电机将停止。目标速度为正数时,电机正转,反之电机反转。我们再打开PID调试助手(注意接上串口1的USB_TTL连接至电脑),选择对应的串口端口,接着勾选通道1和2以及通道7和8,点击“开始”按钮,即可开始显示波形。大家可以使用手给电机增加点负载,可以明显感觉到电机一开始会因为负载增加速度减慢,后续逐渐加速上去直至和目标速度一致,如下图24.2.3.1,大家可以自己下载程序并点击按键测试效果。
image019.jpg
图24.2.3.1 PID调节效果

图24.2.3.1中,橙线代表目标速度,红线代表实际速度,紫线代表实际母线电流,粉线代表目标电流(由于PID初始化时已经设定了电流环的目标值,所以粉线为直线),当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);可以看到紫线(实际电流)并未靠近粉线(目标电流),是由于代码中将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出导致的。当我们手动给电机加负载,就能明显看到实际电流会在目标电流附近波动;按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。
        注意:1、电流环的波形存在小幅振荡属于正常现象,如果希望波形更稳定,可以适当地调整PID系数以及增大滤波次数;2、滤波次数越多则系统的响应越慢,大家需要在系统的响应速度和稳定性之间寻找平衡点;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节。






回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-18 15:26

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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