ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《DMG474电机控制专题教程V1.1 》第24章 无刷电机有感方波闭环控制

《DMG474电机控制专题教程V1.1 》第24章 无刷电机有感方波闭环控制

原创 嵌入式/内核开发 作者:正点原子 时间:2026-05-06 14:24:01 0 删除 编辑

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


       前面已经实现了无刷电机的基本控制和电机的模拟信号采集,本章将利用PID来实现速度闭环控制以及速度+电流双闭环控制。

       本章分为如下几个小节:

       24.1 速度闭环控制

       24.2 速度&电流双闭环控制


        24.1 速度闭环控制

       要实现速度闭环,首先需要先了解下无刷有感驱动的测速原理,这样才可以知道当前转速是否符合我们的设定值。


       24.1.1 无刷有感测速原理

       从前面的学习得知,当转子磁极只有一对级时,转子旋转一圈三个霍尔信号都会输出相应波形,这些波形的特点:高低电平的持续时间是一样的,均为180°电角度(对于店铺BLDC的霍尔传感器来说,N级靠近输出:1,S级靠近输出:0)。示意图如下:


图24.1.1.1 无刷电机与霍尔位置示意图


图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 – PE12

              KEY1 – PE13

              KEY2 – PE14

       3)定时器:  

              TIM1_CH1:PA8

              TIM1_CH2:PA9

              TIM1_CH3:PA10

       4)IO:

              下桥臂IO:PB13\14\15

              霍尔IO: PA0\1\2

              使能IO:SHDN: PC13

       5)无刷电机

       6)无刷电机驱动板

       7)12-60V的DC电源(建议使用电机额定电压24V)

       8)ADC:

              ADC12_CH6(温度)  :PC0

              ADC1_CH4(电源电压) :PA3

              ADC12_CH7(U相电流) :PC1

              ADC12_CH8(V相电流) :PC2

              ADC12_CH9(W相电流) :PC3


       3、原理图


图24.1.2.1 无刷电机接口1原理图


       接口涉及的IO如下:


表24.1.2.1 无刷相关IO口说明


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


图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 程序流程图


图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       pid初始化
 * @param       无
 * @retval      无
 */
void pid_init(void)
{
    g_speed_pid.SetPoint = 0;       /* 设定目标值 */
    g_speed_pid.ActualValue = 0.0;  /* 期望值输出 */
    g_speed_pid.SumError = 0.0;     /* 积分值 */
    g_speed_pid.Error = 0.0;        /* Error[1] */
    g_speed_pid.LastError = 0.0;    /* Error[-1] */
    g_speed_pid.PrevError = 0.0;    /* Error[-2] */
    g_speed_pid.Proportion = S_KP;  /* 比例常数 Proportional Const */
    g_speed_pid.Integral = S_KI;    /* 积分常数 Integral Const */
    g_speed_pid.Derivative = S_KD;  /* 微分常数 Derivative Const */ 
    g_speed_pid.IngMax = 20;
    g_speed_pid.IngMin = -20;
    g_speed_pid.OutMax = 150;       /* 输出限制 */
    g_speed_pid.OutMin = -150;     
}
 
/**
 * @brief       闭环PID控制算法设计
 * @note        通过宏 INCR_LOCT_SELECT 选择使用位置式算法/增量式算法
 * @param       *PID:PID结构体句柄所对应的目标值
 * @param       Feedback_value : 实际值
 * @retval      目标控制量
 */
int32_t increment_pid_ctrl(PID_TypeDef *PID,float Feedback_value)
{
    PID->Error = (float)(PID->SetPoint - Feedback_value); /* 速度档位偏差*/
#if  INCR_LOCT_SELECT
  /* E[k]项*/
    PID->ActualValue += (PID->Proportion * (PID->Error - PID->LastError))   
                        + (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]项*/
                                                         /* E[k-2]项*/
                       + (PID->Derivative * (PID->Error - PID->LastError)); 
    PID->LastError = PID->Error;
#endif
    return ((int32_t)(PID->ActualValue));         /* 返回实际控制数值*/
}

       上述代码可以分为PID初始化及PID控制算法两部分。第一部分主要是将PID算法相关结构体变量进行初始赋值;第二部分是PID控制算法的计算过程,这部分与之前介绍的PID算法一致,这里不多赘述。

       重点内容是在定时器中断服务函数里边定时调用PID控制以及速度的计算,这部分内容在bldc_tim.c中实现,程序如下:

/**
 * @brief       定时器中断回调
 * @param       htim:定时器句柄
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    static uint8_t times_count = 0;                      /* 定时器时间记录 */
    int16_t temp_speed = 0;                             /* 临时速度存储 */
    if(htim->Instance == ATIM_TIMX_PWM)                 /* 55us */
    {
#ifdef H_PWM_L_ON
        if(g_bldc_motor1.run_flag == RUN)
        {
            if(g_bldc_motor1.dir == CW)                 /* 正转 */
            {
           /* 顺序6,2,3,1,5,4 */
                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) /* 当采集到下降沿时数据清0 */
            {
                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*/
                }
            }
            /******************** PID控制 ***********************/
                temp_pwm1 = increment_pid_ctrl(&g_speed_pid, 
g_bldc_motor1.speed);      /* PID控制算法,输出期望值 */
                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值 - 停机状态ADC值 = 实际作用ADC值 */
                adc_val_m1[i] = g_adc_val[i + 2];
                adc_amp[i] = adc_val_m1[i] – 
adc_amp_offset[i][ADC_AMP_OFFSET_TIMES];   
                if(adc_amp[i] >= 0)     /* 去除反电动势引起的负电流数据 */                                                 
                {
                    adc_amp_un[i] = adc_amp[i];
                }  
            }
            /* 运算母线电流(母线电流为任意两个有开关动作的相电流之和) */
            if(g_bldc_motor1.step_sta == 0x05)
            {
/* UV */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x01)
            {
              /* UW */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x03)
            {
             /* VW */
                adc_amp_bus= (adc_amp_un[1] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x02)
            {
              /* UV */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x06)
            {
              /* WU */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x04)
            {
              /* WV */
                adc_amp_bus= (adc_amp_un[2] + adc_amp_un[1]) * ADC2CURT;   
            } 
        }
#endif
    }
    else if(htim->Instance == BTIM_TIMX_INT)
    {
        /****************** 采集电机停机状态下的偏置电压 *******************/
        times_count++;
        if(g_bldc_motor1.run_flag == STOP)
        {
            uint32_t avg[3] = {0,0,0};
          /* 获取电机停机状态下的三相电流 U */
            adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];         
            adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3];   /* V */
            adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];   /* W */
            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][i];             /* 各相数值累加 */
                avg[1] += adc_amp_offset[1][i];
                avg[2] += adc_amp_offset[2][i];
            }
            for(i = 0; i < 3; i++)
            {
                avg[i] /= ADC_AMP_OFFSET_TIMES;            /* 取平均 */
                adc_amp_offset[i][ADC_AMP_OFFSET_TIMES] = avg[i]; /* 赋值 */
            }
        }
    }
}

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

int main(void)
{
    uint8_t debug_cmd = 0;                      /* 存放上位机指令 */
    float current_lpf[4] = {0.0f};              /* 存放三相电流以及母线电流 */
    uint8_t key, t;
    char buf[32];
    float current[3] = {0.0f};
    float last_user_setpoint = 0;               /* 记录上次目标速度 */
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
lcd_init();                                 /* 初始化LCD */
/* 中断频率18K,约55us, 频率不能过慢或过快,否则都会导致电机堵转 */
    bldc_init((170000 / 18) - 1, 0);            
    btim_timx_int_init(1000 - 1, 170 - 1);      /* 基本定时器初始化,1ms中断一次*/
    bldc_ctrl(MOTOR_1, CW, 0);                  /* 初始无刷电机接口1速度 */
    adc_dma_init();                             /* ADC初始化 */
    pid_init();                                 /* PID初始化 */
 
#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, (float*)(&g_speed_pid.SetPoint), 
S_KP, S_KI, S_KD);        /* 速度环PID参数(PID1)*/
#endif
    
    /* 显示提示信息 */    
    lcd_show_string(10, 10, 239, 16, 16, "KEY0:Speed ++", RED);
    lcd_show_string(10, 30, 239, 16, 16, "KEY1:Speed --", RED);
    lcd_show_string(10, 50, 239, 16, 16, "KEY2:Stop", RED);
    printf("按下KEY0 增加目标转速\r\n");
    printf("按下KEY1 减少目标转速\r\n");
    printf("按下KEY2 停止电机\r\n");
    
    while (1)
    {
        t++;
        if (t % 20 == 0)
        {
        /* 显示目标速度 */
            sprintf(buf,"SetSpeed:%4d   ", (int16_t)*user_setpoint);    
            lcd_show_string(10, 70, 200, 16, 16, buf, BLUE);
            sprintf(buf,"M1 Speed:%4d   ",(int16_t)g_bldc_motor1.speed);
            lcd_show_string(10, 90, 200, 16, 16, buf, BLUE);  /* 显示实际速度*/
            sprintf(buf,"Power:%.3fV ", g_adc_val[0] * ADC2VBUS);
            lcd_show_string(10, 110, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Temp:%.1fC ", get_temp(g_adc_val[1]));
            lcd_show_string(10, 130, 200, 16, 16, buf, BLUE);          
            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); /* U相电流 */
            FirstOrderRC_LPF(current_lpf[1], current[1], 0.1f); /* V相电流 */
            FirstOrderRC_LPF(current_lpf[2], current[2], 0.1f); /* W相电流 */
            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;
            }
            /* LCD显示提示信息 */
            sprintf(buf,"Amp U:%.3fmA ", (float)current_lpf[0]);
            lcd_show_string(10, 150, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp V:%.3fmA ", (float)current_lpf[1]);
            lcd_show_string(10, 170, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp W:%.3fmA ", (float)current_lpf[2]);
            lcd_show_string(10, 190, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp Bus:%.3fmA ", (float)current_lpf[3]);
            lcd_show_string(10, 210, 200, 16, 16, buf, BLUE);
            
            /* 串口打印信息 */
            printf("Valtage:%.1fV \r\n", g_adc_val[0] * ADC2VBUS);
            printf("Temp:%.1fC \r\n", get_temp(g_adc_val[1]));
            printf("U相电流为:%.3fmA\r\n", (current_lpf[0]));
            printf("V相电流为:%.3fmA\r\n", (current_lpf[1]));
            printf("W相电流为:%.3fmA\r\n", (current_lpf[2]));
            printf("母线电流为:%.3fmA\r\n", (current_lpf[3]));
            printf("\r\n");
        }
 
        key = key_scan(0);
        if (key == KEY0_PRES)               /* 按下KEY1速度目标值++ */
        {
            g_bldc_motor1.run_flag = RUN;   /* 开启运行 */
            start_motor1();                 /* 开启运行 */
            if (*user_setpoint == 0 && g_bldc_motor1.dir == CCW)
            {
                pid_init();                 /* 换向时刻重新初始化PID防止速度突变*/
                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;
            }
        }
        else if (key == KEY1_PRES)          /* 按下KEY1速度目标值-- */
        {
            g_bldc_motor1.run_flag = RUN;   /* 开启运行 */
            start_motor1();                 /* 开启运行 */
            if (*user_setpoint == 0 && g_bldc_motor1.dir == CW)
            {     
                pid_init();
                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;
            }
        }
        else if (key == KEY2_PRES)          /* 按下KEY2关闭电机 */
        {
            bldc_speed_stop();              /* 清除电机状态并关闭电机 */
        }
        
#if DEBUG_ENABLE
        /* Debug发送部分 */
        /* 主要显示参数 */
        debug_send_valtage(g_adc_val[0] * ADC2VBUS);   /* 发送电压 */
        debug_send_speed(g_bldc_motor1.speed);        /* 发送速度 */
        debug_send_temp(50, get_temp(g_adc_val[1]));  /* 发送电机温度驱动板温度*/
/* 发送电流 */
        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);           
        debug_send_wave_data(3, current_lpf[0]); /* 选择通道3 发送实际电流U */
        debug_send_wave_data(4, current_lpf[1]);  /* 选择通道4 发送实际电流V */
        debug_send_wave_data(5, current_lpf[2]);  /* 选择通道5 发送实际电流W */
        debug_send_wave_data(6, current_lpf[3]);  /* 选择通道6 发送实际母线电流*/
        /* Debug接收部分 */
       /* 查询接收PID助手的PID1参数 */
        debug_receive_pid(TYPE_PID1,(float*)&g_speed_pid.Proportion,                       
(float*)&g_speed_pid.Integral,(float*)&g_speed_pid.Derivative);
        debug_cmd = debug_receive_ctrl_code();  /* 读取命令 */
        if(debug_cmd == HALT_CODE)               /* 停机 */
        {
            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;
        }
        else if(debug_cmd == RUN_CODE)          /* 运行 */
        {
            g_bldc_motor1.dir = CW;
            g_bldc_motor1.run_flag = RUN;         /* 运行标记 */
            *user_setpoint = 400;                  /* 自动设置目标 */
            start_motor1();                     /* 启动电机 */
            debug_send_motorstate(RUN_STATE);   /* 电机运行 */
        }
      /* 刹车(电机停止 点击电机运行才可解除) */
        else if (debug_cmd == BREAKED)           
        {
            *user_setpoint = 0;                  /* 减速直至0 */
            debug_send_motorstate(BREAKED_STATE);  /* 电机刹车 */
        }
        /* PID调试助手的目标参数设置 */
        /* 控制目标调节范围(3000~-3000)并且最大步进值不超过3000 RPM */
debug_set_point_range(3000, -3000, 3000);  
        if(last_user_setpoint < 0)    /* 判断上次速度如果是小于0的 */
        {
            g_bldc_motor1.dir = CCW;          /* 反转 */
            g_bldc_motor1.run_flag = RUN;
            start_motor1();                   /* 启动电机 */
            if(*user_setpoint > 0)            /* 上次为反转状态时,不可以直接切换到
正转,即当前状态不能立刻切换 */
            {
                *user_setpoint = 0;
            }
        }else if(last_user_setpoint > 0)
        {
            g_bldc_motor1.dir = CW;          /* 正转 */
            g_bldc_motor1.run_flag = RUN;
            start_motor1();
            if(*user_setpoint < 0)    /* 上次为正转状态时,不可以直接切换到
反转,即当前状态不能立刻切换 */
            {
                *user_setpoint = 0;
            }
        }else if(*user_setpoint == 0)        /* 目标速度设置为0时 */
        {
            bldc_speed_stop();
        }
       last_user_setpoint = *user_setpoint;  /* 将当前目标速度存至上次目标速度以便
下次判断 */
#endif
        
        delay_ms(10);
    }
}
/**
 * @brief       清除电机状态并关闭电机
 * @param       无
 * @retval      无
 */
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.c的代码逻辑如下:

       第一步,初始化基本外设以及BLDC、串口,本实验需要利用串口1和上位机进行通讯。

       第二步,初始化PID参数、上位机调试,它们分别调用的是pid_init和debug_init函数。

       第三步,上传电机的状态(空闲)、类型(直流无刷电机)、PID参数到上位机,它们分别调用的是debug_send_motorcode、debug_send_motorstate以及debug_send_initdata函数。

       第四步,在while循环里面检测按键是否按下,如果key0按下,则目标速度增加400RPM;如果key1按下,则目标速度减小400RPM;如果key2按下,则停止电机,重置PID参数。

       第五步,接收上位机下发的PID参数、设置目标速度的调节范围,它们分别调用的是debug_receive_pid以及debug_set_point_range函数。我们这里重点介绍一下后者,debug_set_point_range函数的第一、二个入口参数分别传入目标速度的最大值、最小值,它们的正负号代表电机的正反转方向,第三个入口参数传入的是电机速度的最大突变值。

       第六步,调用debug_receive_ctrl_code函数,接收上位机下发的命令。如果上位机的命令为BREAKED(刹车),则停止电机,重置PID参数,并重新上传电机状态(刹车)以及PID参数到上位机;如果上位机的命令是RUN_CODE,则开启电机,设置目标速度为400RPM,上传电机状态(运行)到上位机。

       第七步,每隔200ms更新一次数据到屏幕,调用debug_send_speed函数发送实际速度值到上位机。将实际转速数据发送给上位机的通道1上进行显示,目标转速数据发送给上位机的通道2上进行显示。


       24.1.4 下载验证

       下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD上显示按键功能以及电机速度信息和显示程序计算的电压、电流等信息,当我们按下KEY0,目标速度将增大;按下KEY1,目标速度将减小;按下KEY2,电机将停止。目标速度为正数时,电机正转,反之电机反转。我们再打开PID调试助手(注意接上串口1的USB_TTL连接至电脑),选择对应的串口端口,接着勾选通道1和2,点击“开始”按钮,即可开始显示波形,如下图24.1.4.1,大家可以自己下载程序并点击按键测试效果。


图24.1.4.1 速度PID调节效果


       图24.1.4.1中,橙线代表目标速度,红线代表实际速度,当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。

       注意:1、本实验需要使用USB数据线连接开发板的串口1到电脑,并启动电机之后才会有波形变化;2、如果发现波形不对,请检查电机接线;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节。


图24.1.4.2 屏幕显示效果


        24.2 速度&电流双闭环控制

       要实现速度+电流双闭环控制,只需在速度环的基础上加上电流环部分即可。三相电流的采集已经在前面的课程(第23章)学习过了,所以下面我们直接进入代码实现。


       24.2.1 硬件设计


       1、例程功能

       1、本实验以电机开发板的直流有刷/无刷电机驱动接口1为例,基于无刷速度闭环实验,加入电流环控制,对电机进行速度环+电流环双闭环控制。

       2、通过PID电流环的初始化设置转矩大小,即电流大小。目标电流越大,转矩越大。

       3、当按键0按下,就增大目标速度值;当按键1按下,就减小目标速度值。目标速度的绝对值大小决定电机的速度,它的正负决定电机的正反转方向。按下按键2则马上停止电机。

       4、屏幕显示按键功能、目标转速以及实际转速以及显示无刷电机电压、电流和温度、速度等等信息。

       5、可通过串口1即USB_TTL接口连接正点原子PID调试助手,查看PID波形。

       6、LED0闪烁指示程序运行。


       2、硬件资源

       1)LED灯:LED0 – PE0

       2)独立按键

              KEY0 – PE12

              KEY1 – PE13

              KEY2 – PE14

       3)定时器:  

              TIM1_CH1:PA8

              TIM1_CH2:PA9

              TIM1_CH3:PA10

       4)IO:

               下桥臂IO:PB13\14\15

               霍尔IO: PA0\1\2

               使能IO:SHDN: PC13

       5)无刷电机

       6)无刷电机驱动板

       7)12-60V的DC电源(建议使用电机额定电压24V)

       8)ADC:

              ADC12_CH6(温度)  :PC0

              ADC1_CH4(电源电压) :PA3

              ADC12_CH7(U相电流) :PC1

              ADC12_CH8(V相电流) :PC2

              ADC12_CH9(W相电流) :PC3


       3、原理图


图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 程序流程图


图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中实现,中断回调函数内容如下:

/**
 * @brief       定时器中断回调
 * @param       htim:定时器句柄
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    static uint8_t times_count = 0;           /* 定时器时间记录 */
    int16_t temp_speed = 0;                  /* 临时速度存储 */
    if(htim->Instance == ATIM_TIMX_PWM)      /* 55us */
    {
#ifdef H_PWM_L_ON
        if(g_bldc_motor1.run_flag == RUN)
        {
            /*************************** 三相电流采集 *********************/
            for(i = 0; i < 3; i++)
            {
                adc_val_m1[i] = g_adc_val[i + 2];
                adc_amp[i] = adc_val_m1[i] – 
adc_amp_offset[i][ADC_AMP_OFFSET_TIMES];   
                if(adc_amp[i] >= 0)         /* 去除反电动势引起的负电流数据 */                                              
                {
                    adc_amp_un[i] = adc_amp[i];
                }  
            }
            /* 运算母线电流(母线电流为任意两个有开关动作的相电流之和) */
            if(g_bldc_motor1.step_sta == 0x05)
            {
                adc_amp_bus = (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x01)
            {
                adc_amp_bus = (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x03)
            {
                adc_amp_bus = (adc_amp_un[1] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x02)
            {
                adc_amp_bus = (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x06)
            {
                adc_amp_bus = (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x04)
            {
                adc_amp_bus = (adc_amp_un[2] + adc_amp_un[1]) * ADC2CURT;   
            } 
            /***************** 六步换向 *************************/
            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)  /* 当采集到下降沿时数据清0 */
            {
                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*/
                }
            }
            /******************** PID控制 ***********************/
            pid_c_count++;           /* 电流环计算频次 */
            pid_s_count++;           /* 速度环计算频次 */
            if(pid_s_count > 2)
            {     
                temp_pwm1 = increment_pid_ctrl(&g_speed_pid, 
g_bldc_motor1.speed); /* PID控制算法,输出期望值 */
                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;
                }
                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(g_bldc_motor1.step_last != g_bldc_motor1.step_sta)
            {
                g_bldc_motor1.hall_keep_t = 0;
                g_bldc_motor1.step_last = g_bldc_motor1.step_sta;
            }
            else if(g_bldc_motor1.run_flag == RUN)    /* 运行且霍尔保持时 */
            {
/* 换向一次所需计数值(时间) 单位1/18k */
                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;   /* 标记堵转 */
                }
            }
            
        }
#endif
    }
    else if(htim->Instance == BTIM_TIMX_INT)
    {
        /******************* 采集电机停机状态下的偏置电压 *******************/
        times_count++;
        if(g_bldc_motor1.run_flag == STOP)
        {
            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];   /* V */
            adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];  /* W */
            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][i];         /* 各相数值累加 */
                avg[1] += adc_amp_offset[1][i];
                avg[2] += adc_amp_offset[2][i];
            }
            for(i = 0; i < 3; i++)
            {
                avg[i] /= ADC_AMP_OFFSET_TIMES;          /* 取平均 */
                adc_amp_offset[i][ADC_AMP_OFFSET_TIMES] = avg[i];
            }
        }
        /**************** 定时判断电机是否发生堵塞 ****************/
        if(times_count == SMAPLSE_PID_SPEED)
        {
#if LOCK_TAC
            /* 堵转处理,当到达一定速度后可进入闭环控制 */
            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;
                }
            }
#endif
            times_count = 0;
        }
    }
}

       在定时器1中断里首先优先进行电流环的PID计算,这里需要进行滤波,避免换向尖峰对PID的影响,接着通过霍尔跳变数量计算电机转速,经过计数分频,进入速度环PID计算函数,带入上述计算的速度,得到速度环计算出的PWM输出值,然后将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出,最终赋值到定时器,在下一次中断时使用新的PWM用于驱动无刷电机。这里我们还在中断中加上了堵转处理,当电机因意外或过流导致停机时,当堵转时间超过2s,此时会重新初始化PID并以400RPM的初始速度重新开启电机旋转。接着看来先下主函数main.c内容如下:

int main(void)
{
    uint8_t debug_cmd = 0;                      /* 存放上位机指令 */
    float current_lpf[4] = {0.0f};              /* 存放三相电流以及母线电流 */
    uint8_t key, t;
    char buf[32];
    float current[3] = {0.0f};
    float last_user_setpoint = 0;               /* 记录上次目标速度 */
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
lcd_init();                                 /* 初始化LCD */
/* 中断频率18K,约55us, 频率不能过慢或过快,否则都会导致电机堵转 */
    bldc_init((170000 / 18) - 1, 0);            
    btim_timx_int_init(1000 - 1, 170 - 1);      /* 基本定时器初始化,1ms中断一次*/
    bldc_ctrl(MOTOR_1, CW, 0);                  /* 初始无刷电机接口1速度 */
    adc_dma_init();                             /* ADC初始化 */
    pid_init();                                 /* PID初始化 */
 
#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, (float*)(&g_current_pid.SetPoint), C_KP, 
C_KI, C_KD);   /* 电流环PID参数(PID1)*/
debug_send_initdata(TYPE_PID2, (float*)(&g_speed_pid.SetPoint), S_KP, S_KI, 
S_KD);       /* 速度环PID参数(PID2)*/
#endif
    
    /* 显示提示信息 */    
    lcd_show_string(10, 10, 239, 16, 16, "KEY0:Speed ++", RED);
    lcd_show_string(10, 30, 239, 16, 16, "KEY1:Speed --", RED);
    lcd_show_string(10, 50, 239, 16, 16, "KEY2:Stop", RED);
    printf("按下KEY0 增加目标转速\r\n");
    printf("按下KEY1 减少目标转速\r\n");
    printf("按下KEY2 停止电机\r\n");
    
    while (1)
    {
        t++;
        if (t % 20 == 0)
        {
/* 显示目标速度 */
            sprintf(buf,"SetSpeed:%4d   ", (int16_t)*user_setpoint);    
            lcd_show_string(10, 70, 200, 16, 16, buf, BLUE);
            sprintf(buf,"M1 Speed:%4d   ",(int16_t)g_bldc_motor1.speed);
            lcd_show_string(10, 90, 200, 16, 16, buf, BLUE);  /* 显示实际速度 */
            sprintf(buf,"Power:%.3fV ", g_adc_val[0] * ADC2VBUS);
            lcd_show_string(10, 110, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Temp:%.1fC ", get_temp(g_adc_val[1]));
            lcd_show_string(10, 130, 200, 16, 16, buf, BLUE);           
            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); /* U相电流 */
            FirstOrderRC_LPF(current_lpf[1], current[1], 0.1f); /* V相电流 */
            FirstOrderRC_LPF(current_lpf[2], current[2], 0.1f); /* W相电流 */
            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;
            }
            /* LCD显示提示信息 */
            sprintf(buf,"Amp U:%.3fmA ", (float)current_lpf[0]);
            lcd_show_string(10, 150, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp V:%.3fmA ", (float)current_lpf[1]);
            lcd_show_string(10, 170, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp W:%.3fmA ", (float)current_lpf[2]);
            lcd_show_string(10, 190, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp Bus:%.3fmA ", (float)current_lpf[3]);
            lcd_show_string(10, 210, 200, 16, 16, buf, BLUE);
            
            /* 串口打印信息 */
            printf("Valtage:%.1fV \r\n", g_adc_val[0] * ADC2VBUS);
            printf("Temp:%.1fC \r\n", get_temp(g_adc_val[1]));
            printf("U相电流为:%.3fmA\r\n", (current_lpf[0]));
            printf("V相电流为:%.3fmA\r\n", (current_lpf[1]));
            printf("W相电流为:%.3fmA\r\n", (current_lpf[2]));
            printf("母线电流为:%.3fmA\r\n", (current_lpf[3]));
            printf("\r\n");
        }
 
        key = key_scan(0);
        if (key == KEY0_PRES)               /* 按下KEY1速度目标值++ */
        {
            g_bldc_motor1.run_flag = RUN;   /* 开启运行 */
            start_motor1();                 /* 开启运行 */
            if (*user_setpoint == 0 && g_bldc_motor1.dir == CCW)
            {
                pid_init();                 /* 换向时刻重新初始化PID防止速度突变*/
                g_bldc_motor1.dir = CW;
            }
 
            *user_setpoint += 400;          /* 顺时针旋转下递增 */
            if (*user_setpoint >= 4000)
                *user_setpoint = 4000;
            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;
            }
        }
        else if (key == KEY1_PRES)          /* 按下KEY1速度目标值-- */
        {
            g_bldc_motor1.run_flag = RUN;   /* 开启运行 */
            start_motor1();                 /* 开启运行 */
            if (*user_setpoint == 0 && g_bldc_motor1.dir == CW)
            {     
                pid_init();
                g_bldc_motor1.dir = CCW;
            }
            *user_setpoint -= 400;          /* 逆时针旋转下递增 */
            if (*user_setpoint <= -4000)
                *user_setpoint = -4000;
            if (*user_setpoint == 0)
            {
                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;
            }
        }
        else if (key == KEY2_PRES)          /* 按下KEY2关闭电机 */
        {
            bldc_speed_stop();              /* 清除电机状态并关闭电机 */
        }
        
#if DEBUG_ENABLE
        /* Debug发送部分 */
        /* 主要显示参数 */
        debug_send_valtage(g_adc_val[0] * ADC2VBUS);     /* 发送电压 */
        debug_send_speed(g_bldc_motor1.speed);          /* 发送速度 */
        debug_send_temp(50, get_temp(g_adc_val[1]));
        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);      
        debug_send_wave_data(2, (int16_t)*user_setpoint);           
        debug_send_wave_data(3, current_lpf[0]); /* 选择通道3 发送实际电流U */
        debug_send_wave_data(4, current_lpf[1]); /* 选择通道4 发送实际电流V */
        debug_send_wave_data(5, current_lpf[2]);  /* 选择通道5 发送实际电流W */
        debug_send_wave_data(6, current_lpf[3]);  /* 选择通道6 发送实际母线电流*/
        debug_send_wave_data(7, g_current_pid.SetPoint);            
        /* Debug接收部分 */
        debug_receive_pid(TYPE_PID1,(float*)&g_current_pid.Proportion,                       
(float*)&g_current_pid.Integral,(float*)&g_current_pid.Derivative);
        debug_receive_pid(TYPE_PID2,(float*)&g_speed_pid.Proportion,                    
(float*)&g_speed_pid.Integral,(float*)&g_speed_pid.Derivative);
                          
        debug_cmd = debug_receive_ctrl_code();    /* 读取命令 */
        if(debug_cmd == HALT_CODE)                /* 停机 */
        {
            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;
        }
        else if(debug_cmd == RUN_CODE)           /* 运行 */
        {
            g_bldc_motor1.dir = CW;
            g_bldc_motor1.run_flag = RUN;       /* 运行标记 */
            *user_setpoint = 400;               /* 自动设置目标 */
            start_motor1();                       /* 启动电机 */
            debug_send_motorstate(RUN_STATE);     /* 电机运行 */
        }
        else if (debug_cmd == BREAKED) 
        {
            *user_setpoint = 0;                    /* 减速直至0 */
            debug_send_motorstate(BREAKED_STATE); /* 电机刹车 */
        }
        /* PID调试助手的目标参数设置 */
        debug_set_point_range(4000, -4000, 4000);
        if(last_user_setpoint < 0)     /* 判断上次速度如果是小于0的*/
        {
            g_bldc_motor1.dir = CCW;            /* 反转 */
            g_bldc_motor1.run_flag = RUN;
            start_motor1();                       /* 启动电机 */
            if(*user_setpoint > 0)                                 
            {
                *user_setpoint = 0;
            }
        }else if(last_user_setpoint > 0)
        {
            g_bldc_motor1.dir = CW;                /* 正转 */
            g_bldc_motor1.run_flag = RUN;
            start_motor1();
            if(*user_setpoint < 0)                                  
            {
                *user_setpoint = 0;
            }
        }else if(*user_setpoint == 0)           /* 目标速度设置为0时 */
        {
            bldc_speed_stop();
        }
       last_user_setpoint = *user_setpoint;     
#endif
        delay_ms(10);
    }
}
/**
 * @brief       清除电机状态并关闭电机
 * @param       无
 * @retval      无
 */
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、6、7上进行显示。按键判断逻辑:按下KEY0目标转速加400RPM,按下KEY1目标转速减400RPM,按下KEY2停止电机旋转。


       24.2.3 下载验证

       下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD上显示按键功能、占空比以及电机速度信息和显示程序计算的电压、电流等信息,当我们按下KEY0,目标速度将增大;按下KEY1,目标速度将减小;按下KEY2,电机将停止。目标速度为正数时,电机正转,反之电机反转。我们再打开PID调试助手(注意:接上串口1的USB_TTL连接至电脑),选择对应的串口端口,接着勾选通道1和2以及通道6和7,点击“开始”按钮,即可开始显示波形。大家可以使用手给电机增加点负载,可以明显感觉到电机一开始会因为负载增加速度减慢,后续逐渐加速上去直至和目标速度一致,如下图24.2.3.1,大家可以自己下载程序并点击按键测试效果。


图24.2.3.1 PID调节效果


       图24.2.3.1中,橙线代表目标速度,红线代表实际速度,蓝线代表实际母线电流,紫线代表目标电流(由于PID初始化时已经设定了电流环的目标值,所以紫线为直线),当我们按下KEY0,目标速度增大,橙线先发生变化,而红线(实际速度)会逐渐靠近橙线(目标速度);可以看到蓝线(实际电流)并未靠近紫线(目标电流),是由于代码中将两个环的输出做比较,如果电流环较大则使用速度环输出,否则使用电流环输出导致的。当我们手动给电机加负载,就能明显看到实际电流会在目标电流附近波动;按下KEY1,目标速度将减小,曲线的变化同理;按下KEY2,电机将停止,目标速度将为0。

       注意:1、电流环的波形存在小幅振荡属于正常现象,如果希望波形更稳定,可以适当地调整PID系数以及增大滤波次数;2、滤波次数越多则系统的响应越慢,大家需要在系统的响应速度和稳定性之间寻找平衡点;3、PID系数并不是通用的,如果PID曲线不理想,大家需要根据自己的实际系统去调节。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3117167/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    763886