16. 无刷电机速度环控制(BLDC)

您所在的位置:网站首页 speedgroup 16. 无刷电机速度环控制(BLDC)

16. 无刷电机速度环控制(BLDC)

2024-07-17 03:03:52| 来源: 网络整理| 查看: 265

16.2. 直流无刷电机速度环控制-位置式PID实现¶ 16.2.1. 软件设计1¶

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到, 还有一些在前章节章节分析过的代码在这里也不在重复讲解,完整的代码请参考本章配套的工程。 本章代码在野火电机驱动例程中:\base_code\improve_part\F407\直流无刷电机-速度环控制-位置式PID目录下。

16.2.1.1. 编程要点1¶

高级定时器 IO 配置

定时器时基结构体TIM_HandleTypeDef配置

定时器输出比较结构体TIM_OC_InitTypeDef配置

根据电机的换相表编写换相中断回调函数

根据定时器定义电机控制相关函数.

配置基本定时器可以产生定时中断来执行PID运算

编写位置式PID算法

编写速度控制函数

增加上位机曲线观察相关代码

编写按键控制代码

16.2.2. 软件分析1¶

在编程要点中的1和2在前章节已经讲解过,这里就不在详细讲解, 如果不明白请先学习前面相关章节的内容。这里主要讲解速度的获取方法和分析PID算法的控制实现部分。

bsp_motor_tim.c-设置pwm输出的占空比¶ 1 2 3 4 5 6 7 8 void set_pwm_pulse(uint16_t pulse) { /* 设置定时器通道输出 PWM 的占空比 */ bldcm_pulse = pulse; if (motor_drive.enable_flag) HAL_TIM_TriggerCallback(NULL); // 执行一次换相 }

该函数用于设置pwm输出的占空比,本函数只是将占空比存放在了bldcm_pulse变量中, 而真正设置正空比是在HAL_TIM_TriggerCallback()函数中, 所以在电机使能时需要调用HAL_TIM_TriggerCallback()设置pwm输出的占空比。

bsp_motor_tim.c-更新电机速度¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static void update_motor_speed(uint8_t dir_in, uint32_t time) { int speed_temp = 0; static int flag = 0; float f = 0; /* 计算速度: 电机每转一圈共用12个脉冲,(1.0/(84000000.0/128.0)为计数器的周期,(1.0/(84000000.0/128.0) * time)为时间长。 */ if (time == 0) motor_drive.speed_group[count++] = 0; else { f = (1.0f / (84000000.0f / HALL_PRESCALER_COUNT) * time); f = (1.0f / 12.0f) / (f / 60.0f); motor_drive.speed_group[count++] = f; } update_speed_dir(dir_in); // motor_drive.speed = motor_drive.speed_group[count-1]; if (count >= SPEED_FILTER_NUM) { flag = 1; count = 0; } // return ; speed_temp = 0; /* 计算近 SPEED_FILTER_NUM 次的速度平均值(滤波) */ if (flag) { for (uint8_t c=0; cPSC) // Get TIM Prescaler. //#define GET_BASIC_TIM_PERIOD(__HANDLE__) (1.0/(HAL_RCC_GetPCLK2Freq()/(__HAL_TIM_GET_PRESCALER(__HANDLE__)+1)/(__HAL_TIM_GET_AUTORELOAD(__HANDLE__)+1))*1000) /* 以下两宏仅适用于定时器时钟源TIMxCLK=84MHz,预分频器为:1680-1 的情况 */ #define SET_BASIC_TIM_PERIOD(T) __HAL_TIM_SET_AUTORELOAD(&TIM_TimeBaseStructure, (T)*50 - 1) // 设置定时器的周期(1~1000ms) #define GET_BASIC_TIM_PERIOD() ((__HAL_TIM_GET_AUTORELOAD(&TIM_TimeBaseStructure)+1)/50.0) // 获取定时器的周期,单位ms

这里封装了定时器的一些相关的宏,使用宏定义非常方便程序升级、移植。使用SET_BASIC_TIM_PERIOD(T)这个宏可以设置定时器的周期, 这样可以通过按键或者上位机来设置这个定时器的中断周期,使用GET_BASIC_TIM_PERIOD()这个宏可以得到定时器的当前周期, 不过使用的两个宏是有要求的,需要定时器时钟源的频率是84MHz,且预分频系数为1680。 如果更换定时器和修改预分频器则需要重新计算这个宏里面的参数.我们来看一下当前宏中周期的计算:84000000/1680/50 = 1000, 84000000为时钟源的频率,1680为预分频系数,50为自动重装载值,1000为定时器产生更新中断的频率, 当定时器以(84000000/1680)Hz的频率计数到50时刚好是1ms,所以只要设置自动重装载值为50的n倍减一时, 就可以得到n毫秒的更新中断,注意n是1到1000的正整数。

bsp_basic_tim.c-定时器配置函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void TIM_Mode_Config(void) { // 开启TIMx_CLK,x[6,7] BASIC_TIM_CLK_ENABLE(); TIM_TimeBaseStructure.Instance = BASIC_TIM; /* 累计 TIM_Period个后产生一个更新或者中断*/ //当定时器从0计数到BASIC_PERIOD_COUNT-1,即为BASIC_PERIOD_COUNT次,为一个定时周期 TIM_TimeBaseStructure.Init.Period = BASIC_PERIOD_COUNT - 1; //定时器时钟源TIMxCLK = 2 * PCLK1 // PCLK1 = HCLK / 4 // => TIMxCLK=HCLK/2=SystemCoreClock/2=84MHz // 设定定时器频率为=TIMxCLK/BASIC_PRESCALER_COUNT TIM_TimeBaseStructure.Init.Prescaler = BASIC_PRESCALER_COUNT - 1; TIM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数 TIM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频 // 初始化定时器TIMx, x[2,3,4,5] HAL_TIM_Base_Init(&TIM_TimeBaseStructure); // 开启定时器更新中断 HAL_TIM_Base_Start_IT(&TIM_TimeBaseStructure); }

首先定义两个定时器初始化结构体,定时器模式配置函数主要就是对这两个结构体的成员进行初始化, 然后通过调用的初始化函数HAL_TIM_Base_Init()把这些参数写入定时器的寄存器中。 有关结构体的成员介绍请参考定时器详解章节。 最后通过调用函数HAL_TIM_Base_Start_IT()使能定时器的更新中断。

bsp_basic_tim.c-定时器初始¶ 1 2 3 4 5 6 7 8 9 10 11 12 void TIMx_Configuration(void) { TIMx_NVIC_Configuration(); TIM_Mode_Config(); #if defined(PID_ASSISTANT_EN) uint32_t temp = GET_BASIC_TIM_PERIOD(); // 计算周期,单位ms set_computer_value(SEED_PERIOD_CMD, CURVES_CH1, &temp, 1); // 给通道 1 发送目标值 #endif }

该函数主要配置了定时器的中断设置和定时器模式配置,最后调用set_computer_value()函数设置了上位机的周期值, 这里只是同步一下上位机显示的周期值。PID_ASSISTANT_EN是用于选择是否使用上位机的宏, 当我们在调试阶段时可以定义这个宏,方便使用上位机(野火调试助手-PID调试助手)来观察电机的运行效果, 在完成调试后我们可以直接不定义这个宏,这样就去掉了上位机相关部分。

bsp_pid.c-位置式PID参数初始化¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void PID_param_init() { /* 初始化参数 */ pid.target_val=0.0; pid.actual_val=0.0; pid.err=0.0; pid.err_last=0.0; pid.integral=0.0; pid.Kp = 0.3;//24 pid.Ki = 0.2; pid.Kd = 0.0; #if defined(PID_ASSISTANT_EN) float pid_temp[3] = {pid.Kp, pid.Ki, pid.Kd}; set_computer_value(SEND_P_I_D_CMD, CURVES_CH1, pid_temp, 3); // 给通道 1 发送 P I D 值 #endif }

PID_param_init()函数把结构体pid参数初始化,将目标、实际值、偏差值和积分项等初始化为0, 其中pid.Kp、pid.Ki和pid.Kd是我们配套电机运行效果相对比较好的参数,不同的电机该参数是不同的。 set_computer_value()函数用来同步上位机显示的PID值。

bsp_pid.c-设置速度目标值¶ 1 2 3 4 void set_pid_target(float temp_val) { pid.target_val = temp_val; // 设置当前的目标值 }

设置目标值

bsp_pid.c-位置式PID算法实现¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 float PID_realize(float actual_val) { /*计算目标值与实际值的误差*/ pid.err = pid.target_val - actual_val; pid.integral += pid.err; /*PID算法实现*/ pid.actual_val = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * (pid.err - pid.err_last); /*误差传递*/ pid.err_last = pid.err; /*返回当前实际值*/ return pid.actual_val; }

这个函数主要实现了位置式PID算法,用传入的目标值减去实际值得到误差值得到比例项,在对误差值进行累加得到积分项, 用本次误差减去上次的误差得到微分项,然后通过前面章节介绍的位置式PID公式实现PID算法,并返回实际控制值。

bsp_pid.c-电机位置式PID算法实现¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void bldcm_pid_control(void) { int32_t speed_actual = get_motor_speed(); // 电机旋转的当前速度 if (bldcm_data.is_enable) { float cont_val = 0; // 当前控制值 cont_val = PID_realize(speed_actual); if (cont_val PWM_PERIOD_COUNT) ? PWM_PERIOD_COUNT : cont_val; // 上限处理 set_bldcm_speed(cont_val); #ifdef PID_ASSISTANT_EN set_computer_value(SEND_FACT_CMD, CURVES_CH1, &speed_actual, 1); // 给通道 1 发送实际值 #else printf("实际值:%d, 目标值:%.0f,控制值: %.0f\n", speed_actual, get_pid_target(), cont_val); #endif } }

该函数在定时器的中断里定时调用默认是50毫秒调用一次,如果改变了周期那么PID三个参数也需要做相应的调整, PID的控制周期与控制效果是息息相关的。 调用get_motor_speed()获取电机的旋转速度,单位是转每分钟。把实际速度带入PID_realize(speed_actual)进行运算, 根据运算结果的正负,设置电机的旋转方向。 最后对输出的结果做一个上限处理,最后用于PWM占空比的控制,最后将实际的速度值发送到上位机绘制变化的曲线。

protocol.c-串口数据解析¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 /** * @brief 接收的数据处理 * @param void * @return -1:没有找到一个正确的命令. */ int8_t receiving_process(void) { uint8_t frame_data[128]; // 要能放下最长的帧 uint16_t frame_len = 0; // 帧长度 uint8_t cmd_type = CMD_NONE; // 命令类型 while(1) { cmd_type = protocol_frame_parse(frame_data, &frame_len); switch (cmd_type) { case CMD_NONE: { return -1; } case SET_P_I_D_CMD: { uint32_t temp0 = COMPOUND_32BIT(&frame_data[13]); uint32_t temp1 = COMPOUND_32BIT(&frame_data[17]); uint32_t temp2 = COMPOUND_32BIT(&frame_data[21]); float p_temp, i_temp, d_temp; p_temp = *(float *)&temp0; i_temp = *(float *)&temp1; d_temp = *(float *)&temp2; set_p_i_d(p_temp, i_temp, d_temp); // 设置 P I D } break; case SET_TARGET_CMD: { int actual_temp = COMPOUND_32BIT(&frame_data[13]); // 得到数据 set_pid_target(actual_temp); // 设置目标值 } break; case START_CMD: { set_bldcm_enable(); // 启动电机 } break; case STOP_CMD: { set_bldcm_disable(); // 停止电机 } break; case RESET_CMD: { HAL_NVIC_SystemReset(); // 复位系统 } break; case SET_PERIOD_CMD: { uint32_t temp = COMPOUND_32BIT(&frame_data[13]); // 周期数 SET_BASIC_TIM_PERIOD(temp); // 设置定时器周期1~1000ms } break; default: return -1; } } }

这函数用于处理上位机发下的数据,在主函数中循环调用,可以使用上位机调整PID参数,使用上位机可以非常方便的调整PID参数, 这样可以不用每次修改PID参数时都要改代码、编译和下载代码;可以使用上位机设置目标速度;可以启动和停止电机; 可以使用上位机复位系统;可以使用上位机设置定时器的周期;具体功能的实现请参考配套工程代码。

main.c-主函数¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 int main(void) { int16_t target_speed = 1200; uint8_t i = 0; /* 初始化系统时钟为168MHz */ SystemClock_Config(); /* HAL 库初始化 */ HAL_Init(); /* 初始化按键GPIO */ Key_GPIO_Config(); /* LED 灯初始化 */ LED_GPIO_Config(); /* 协议初始化 */ protocol_init(); /* 调试串口初始化 */ DEBUG_USART_Config(); PID_param_init(); /* 周期控制定时器 50ms */ TIMx_Configuration(); /* 电机初始化 */ bldcm_init(); /* 设置目标速度 */ set_pid_target(target_speed); #if defined(PID_ASSISTANT_EN) set_computer_value(SEND_STOP_CMD, CURVES_CH1, NULL, 0); // 同步上位机的启动按钮状态 set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &target_speed, 1); // 给通道 1 发送目标值 #endif while(1) { /* 接收数据处理 */ receiving_process(); /* 扫描KEY1 */ if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON ) { /* 使能电机 */ set_bldcm_enable(); #if defined(PID_ASSISTANT_EN) set_computer_value(SEND_START_CMD, CURVES_CH1, NULL, 0); // 同步上位机的启动按钮状态 #endif } /* 扫描KEY2 */ if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON ) { /* 停止电机 */ set_bldcm_disable(); #if defined(PID_ASSISTANT_EN) set_computer_value(SEND_STOP_CMD, CURVES_CH1, NULL, 0); // 同步上位机的启动按钮状态 #endif } /* 扫描KEY3 */ if( Key_Scan(KEY3_GPIO_PORT,KEY3_PIN) == KEY_ON ) { /* 增大占空比 */ target_speed += 100; if(target_speed > 3000) target_speed = 3000; set_pid_target(target_speed); #if defined(PID_ASSISTANT_EN) set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &target_speed, 1); // 给通道 1 发送目标值 #endif } /* 扫描KEY4 */ if( Key_Scan(KEY4_GPIO_PORT,KEY4_PIN) == KEY_ON ) { target_speed -= 100; if(target_speed


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭