摘要:有刷电机结构简单开发久技术成熟响应速度快,起动扭矩大运行平稳,起制动效果好控制精度高使用成本低,维修方便而无刷电机由于无电刷,具有低干扰噪音低运转顺畅寿命长低维护成本等优点。电机控制方式力矩控制指定电机提供设置大小的力矩。
电机作为一种能将电能转化为机械能的装置,其在制造、医疗、运动控制等等许多地方都起着重要的作用。想学习了解机器人的小伙伴,从电机了解起走也是一条不错(坎坷)的道路。
其实电机对于我来说是接触的比较多了的,记得小时候玩四驱车,就特意将“马达”拆开来看过想搞懂原理,也多带带将电机拿出来制作了一些小的diy,后来到了高中在学到了电磁学,算是了解了基础的原理了(在不停的刷题后),再后来到大学就是真正的使用了。第一次使用应该是在大一买的单片机,配了一个电机,有程序可以进行调速,但当时由于一些原因,没有再去使用。又到了大三,学习了电机拖动,对电机的认识又深了一点,也做了一些关于电机的实验。但是,令我难以忘记的是研究生开始调试电机的时候,真的是…一言难尽,之前也参考过许多大佬的博客,所以想把自己的这段难忘的经历做个总结,也给有需要的朋友一个参考。
按电源种类分为:直流电机和交流电机。我们常见常用的电机大多是直流电机,相比前者,交流电机不需要换向器和电刷转换电流方向,与直流电机相比它的结构更简单,功率更大,在工业领域被广泛应用
根据有无电刷分为:有刷电机和无刷电机。有刷电机结构简单、开发久技术成熟、响应速度快,起动扭矩大、运行平稳,起制动效果好、控制精度高、使用成本低,维修方便;而无刷电机由于无电刷,具有低干扰、噪音低、运转顺畅、寿命长、低维护成本等优点。于是我接触的以无刷电机为主。
根据有无反馈分为:步进电机和伺服电机。前者没有反馈信号,位置精度不够高,且转速远远小于后者。在需要精确的控制,伺服电机更加常用。
| |
由于位置是速度的积分,所以三种控制方式的控制框图是有要求的,下面是一种常见的控制结构图,当然,如果只针对某一两种控制模式,其控制方案将比这个更加简易。
为了方便用户的使用,市面上许多电机都是针对上面的控制方式进行了封装的,也就是我们常听说的——控制器。控制器的控制方案有许多,针对不同的控制环也有不同的控制方案,例如:对于电流环,有FOC矢量控制,速度、位置环有PID。当然,也有其他的控制算法,但这里我们就使用常用的就行了。现在我们也可以开始谈谈标题了。
PID 是一种传统且经典的控制算法,在工程中应用非常广泛,相比其他高大上甚至只存在于 paper 上的算法, PID 是非常接地气的。
PID ,即:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。顾名思义,PID 控制算法是结合比例、积分和微分的融合怪,其规律可以描述为:
u ( t ) = K p ( e ( t ) + 1 T t ∫ 0 t e ( t ) d t + T d d e ( t ) d t u(t)=K_p(e(t)+/frac{1}{T_t}/int_0^t e(t)dt+T_d/frac{de(t)}{dt} u(t)=Kp(e(t)+Tt1∫0te(t)dt+Tddtde(t)
其中 K p K_p Kp 是比例增益, T t T_t Tt 是积分时间常数, T d T_d Td 是微分时间常数, u ( t ) u(t) u(t) 是输入信号, e ( t ) e(t) e(t) 是误差。
三个环节在控制中也分别起着不同的控制作用。
比例环节 P:比例环节与稳态误差相关,比例环节越大,上升速度越快,且稳态误差越小,但无论怎样多大都会存在误差,不能消除误差,而且过大还会导致震荡,反而不稳定
积分环节 I:积分环节则可以消除误差,合适的积分环节可以很快的消除误差,但是设置较大会产生超调,并且过大也会导致震荡,从而不稳定
微分环节 D:微分环节具有预测作用,可以预测信号的变化方向,从而可以减小超调,提高响应速度,但过大会导致系统不稳定
matlab PID 的参考代码如下(上面的图是在下面代码基础上修改了一点,但是核心没有变):
%% 说明% 被控系统: 1/(0.1s+1)% 控制器: PID%%clc,clearts=0.001; %采样时间=0.001ssys=tf(1,[0.1,1]); %建立被控对象传递函数dsys=c2d(sys,ts,"z") % 离散化[num,den]=tfdata(dsys,"v"); % 得到差分方程系数 y(k) = -den[2]*y(k-1) + num[2]*u(k-1)e_last=0; %前一时刻的偏差 E_integ=0; %累积偏差u_last=0.0; %前一时刻的控制量y_last=0; %前一时刻的输出% PID参数kp=1; ki=0;kd=0;u=zeros(1,10000); %设置仿真长度time=zeros(1,10000); %时刻点(设定10000个)for k=1:1:10000 time(k)=k*ts; %时间 r(k)=100; %期望值 y(k)=-1*den(2)*y_last + num(2)*u_last; %系统响应输出序列 e(k)=r(k)-y(k); %误差信号 u(k)=kp*e(k)+ki*E_integ+kd*(e(k)-e_last); %系统PID控制器输出序列 E_integ=E_integ+e(k); %误差的累加和 u_last=u(k); %前一个的控制器输出值 y_last=y(k); %前一个的系统响应输出值 e_last=e(k); %前一个误差信号的值endp1=plot(time,r,"-.");xlim([0,1]);hold on; %指令信号的曲线(即期望输入)p2=plot(time,y,"--");xlim([0,1]); %不含积分分离的PID曲线hold on;
上面的 PID公示 是针对连续情况下的,而在生活中,我们常常使用的是离散型的变量,比如时间,于是我们需要将 PID 的公式进行离散化,根据离散化的方法不同,PID 控制的公式就有两种,即位置式 PID 和增量式 PID,
常见的调参方式是比较快乐的,直接在生产商写好的驱动下进行参数的设置以及测试,找到合适的参数,更有的还有调参软件,遍历参数寻找合适的参数,从而省去人工调试的复杂环节。
但这里想要分享的调参方法要多一点步骤,但是大体方向是不变的,这里以 Tmotor 的 AK10-9 与 大疆的 M2006 两款无刷直流电机为例子进行介绍。
Tmotor 的电机本来是有调参软件的,但是最开始的时候由于资料不完善,加上他的控制环不符合我们的应用要求,所以我们需要进行简单的修改。
Tmotor 的运动控制框图如下,可以看到他的电流环采用 FOC 矢量控制,我们不能修改,另外两个环,速度环和位置环,只有比例环节,不能达到无误差的目标,所以我们需要在他的电流环上进行编写封装。
我们需要的控制环应该如下:
下面我们先编写控制程序。
之前探讨过离散 PID 有位置式 PID 算法和增量式 PID 算法,下面是根据其公式编写的 PID 程序,在使用之前需要稍稍修改一下参数。
位置式 PID
********************位置式 PID***************************** 输入参数:电机电流速度位置等反馈值fed ***********************************************************typedef struct PID{ float target; //目标参考值 float deadband; //定义电机死区 float err_now; //定义当前误差 float err_last; //定义上一时刻误差 float kp; //比例环节系数 float ki; //积分环节系数 float kd; //微分环节系数,这里已将时间常数包含进去 float Pout; //比例环节输出 float Iout; //积分环节输出 float Dout; //微分环节输出 float IntegLimt; //设置积分限幅 float output; //输出量 float OutputLimt;//输出限幅}PID_PARM;//初始化PID参数的函数void PID_parm_Init(PID_PARM *PID_parm,float target,float kp,float ki,float kd,float IntegLimt,float OutputLimt){ PID_parm->target = target; PID_parm->err_now = 0; PID_parm->err_last = 0; PID_parm->kp = kp; PID_parm->ki = ki; PID_parm->kd = kd; PID_parm->Pout = 0; PID_parm->Iout = 0; PID_parm->Dout = 0; PID_parm->IntegLimt = IntegLimt; PID_parm->output = 0; PID_parm->OutputLimt = OutputLimt;}//计算PID的函数float PID_cal(PID_PARM *pid_parm,float feedback){ pid_parm->err_now = pid_parm->target - feedback; pid_parm->Pout = pid_parm->kp*pid_parm->err_now; pid_parm->Iout += pid_parm->ki*pid_parm->err_now; pid_parm->Dout = pid_parm->kd*(pid_parm->err_now-pid_parm->err_last); //积分限幅 if(pid_parm->Iout > pid_parm->IntegLimt) pid_parm->Iout = pid_parm->IntegLimt; else if(pid_parm->Iout < -pid_parm->IntegLimt) pid_parm->Iout = -pid_parm->IntegLimt; //输出限幅 pid_parm->output = pid_parm->Pout + pid_parm->Iout + pid_parm->Dout; if(pid_parm->output > pid_parm->OutputLimt) pid_parm->output = pid_parm->OutputLimt; else if(pid_parm->output < -pid_parm->OutputLimt) pid_parm->output = -pid_parm->OutputLimt; //数据更新 pid_parm->err_last = pid_parm->err_now; return pid_parm->output;}float u; PID_PARM pid_parm;PID_parm_Init(&pid_parm,10,1,0.1,,0.5,50,100); //这里的参数随机给的,具体参数需要调节u = PID_cal(&pid_parm,fed); //计算出来的下一次输入
增量式 PID
********************增量式 PID***************************** 输入参数:电机电流速度位置等反馈值fed ***********************************************************typedef struct PID{ float target; //目标参考值 float deadband; //定义电机死区 float err_now; //定义当前误差 float err_last; //定义上一时刻误差 float err_llast; //定义上上时刻误差 float kp; //比例环节系数 float ki; //积分环节系数 float kd; //微分环节系数,这里已将时间常数包含进去 float Pout; //比例环节输出 float Iout; //积分环节输出 float Dout; //微分环节输出 float output; //输出增量 float output_last; //上一次输出的增量 float OutputLimt; //输出限幅}PID_PARM;//初始化PID参数的函数void PID_parm_Init(PID_PARM *PID_parm,float target,float kp,float ki,float kd,float OutputLimt){ PID_parm->target = target; PID_parm->err_now = 0; PID_parm->err_last = 0; PID_parm->err_llast = 0; PID_parm->kp = kp; PID_parm->ki = ki; PID_parm->kd = kd; PID_parm->Pout = 0; fPID_parm->Iout = 0; PID_parm->Dout = 0; PID_parm->output = 0; PID_parm->output_last = 0; PID_parm->OutputLimt = OutputLimt;}//计算PID的函数float PID_cal(PID_PARM *pid_parm,float feedback){ pid_parm->err_now = pid_parm->target - feedback; pid_parm->Pout = pid_parm->kp*(pid_parm->err_now - pid_parm->err_last); pid_parm->Iout = pid_parm->ki*pid_parm->err_now; pid_parm->Dout = pid_parm->kd*(pid_parm->err_now - 2*pid_parm->err_last + pid_parm->err_llast); //输出限幅 pid_parm->output += pid_parm->Pout + pid_parm->Iout + pid_parm->Dout; if(pid_parm->output > pid_parm->OutputLimt) pid_parm->output = pid_parm->OutputLimt; else if(pid_parm->output < -pid_parm->OutputLimt) pid_parm->output = -pid_parm->OutputLimt; //数据更新 pid_parm->err_llast = pid_parm->err_last; pid_parm->err_last = pid_parm->err_now; pid_parm->output_last = pid_parm->output; return pid_parm->output;}float u; PID_PARM pid_parm;PID_parm_Init(&pid_parm,10,1,0.1,,0.5,100); //这里的参数随机给的,具体参数需要调节u = PID_cal(&pid_parm,fed); //计算出来的下一次输入
这里采用的 stm32 进行控制的,工程文件全部在下面:
key.h
#ifndef __KEY_H#define __KEY_H #include "sys.h" /*下面的方式是通过直接操作库函数方式读取IO*/#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) //PE3 #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) //PE2#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) //PA0#define KEY0_PRES 1#define KEY1_PRES 2#define KEY2_PRES 3#define WKUP_PRES 4void KEY_Init(void); //IO初始化u8 KEY_Scan(u8); //按键扫描函数 #endif
can.h
#ifndef __CAN_H#define __CAN_H #include "sys.h" void CAN1_Init(void);//CAN1初始化 u8 CAN1_Send_Msg(u8* msg); //发送数据 u8 CAN1_Receive_Msg(u8 *buf);#endif
pid.h
#ifndef __PID_H#define __PID_H #include "sys.h" #include "stdlib.h"typedef struct PID{ float target; //目标参考值 float deadband; //定义电机死区 float err_now; //定义当前误差 float err_last; //定义上一时刻误差 float err_llast; //定义上上时刻误差 float kp; //比例环节系数 float ki; //积分环节系数 float kd; //微分环节系数,这里已将时间常数包含进去 float Pout; //比例环节输出 float Iout; //积分环节输出 float Dout; //微分环节输出 float IntegLimt; //设置积分限幅 float output; //输出量 float output_last; //上一次输出的增量 float OutputLimt; //输出限幅}PID_PARM;void PID_parm_Init(PID_PARM *PID_parm,float target,float deadband,float kp,float ki,float kd,float IntegLimt,float OutputLimt);float Pos_PID_cal(PID_PARM *pid_parm,float feedback);float Inc_PID_cal(PID_PARM *pid_parm,float feedback);void PID_init(PID_PARM *Spd_PID,PID_PARM *Pos_PID);#endif
motor.h
#ifndef __MOTOR_H#define __MOTOR_H #include "sys.h" #include "pid.h"#define pi 3.1415926#define P_MAX 12.5#define P_MIN -12.5#define V_MAX 46.57#define V_MIN -46.57#define KP_MAX 500#define KP_MIN 0#define KD_MAX 5#define KD_MIN 0#define T_MAX 54#define T_MIN -54extern u8 Tmotor_Mod_Buf[3][8];typedef enum{ Tmotor_Open = 0xfc, Tmotor_Close = 0xfd, Tmotor_SetZero = 0xfe,}Tmotor_Mod;typedef struct{ u8 id; // id int16_t speed_rps; // rad/s int16_t real_torque; // 反馈力矩 uint16_t angle; // 绝对角度}Tmotor_measure_t;extern Tmotor_measure_t TmotorData; // 保存Tmotor电机的状态int float_to_uint(float x, float x_min, float x_max, int bits);float uint_to_float(int x_int, float x_min, float x_max, int bits);float limitf(float val, float min_val, float max_val);void Tmotor_mod(u8 mod);void get_Tmotor_measure(Tmotor_measure_t *ptr, CanRxMsg *Rxmsg);u8 set_Tmotor_torque(float torque);void Tmotor_Speed_Control(PID_PARM
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/125303.html
摘要:起因及介绍在处理原始对账文件的时候,我将数据归类后批量存入相应的表中。结论事务只能管着开启事务的线程,其他子线程出了问题都感知不到,所以在多线程环境操作要慎重。高频容易搞死服务器,低频会阻塞自身程序。重试次数和超时时间根据业务情况设置。 起因及介绍 在处理原始对账文件的时候,我将数据归类后批量存入相应的表中。在持久化的时候,用了parallelStream(),想着同时存入很多表这样可...
摘要:力矩控制模式电机在运行过程的电流,始终等于给定的值。设定电流为零,弹簧不被拉伸。比如机械臂从点运动到点,并限制挥舞过程中的最大速度和最大力矩。 目录 说明一、电机...
摘要:一硬件框架与模型设计机械臂最核心的部分应该就是关节部分的伺服电机了,针对与文稿中的设计思路,每个伺服电机都为一独立的控制系统,并通过总线的形式获取数据并控制。 ##...
摘要:每个控制周期需要做的内容包括获取陀螺仪和编码器两个传感器的数据,传入直立环和速度环算法中进行计算得到控制量,将控制量作用于直流电机上。 Ruff Lite Ruff Lite 是 Ruff 团队针对 MCU(MicroController Unit,微控制器)推出的 Ruff OS,具有高实时性,占用内存小等特点。目前官方支持的开发板为TI TM4C1294-LaunchPad ,R...
摘要:综合诸多考虑与相应调研,我们希望能够制作出一款宿舍升降机为同学们提供更方便安全的上下床方式。摘要本设计采用开发板作为主控,结合压力传感器红外避障传感器电机驱动模块实现了一个可以自动升降自动停止自动调速的宿舍升降机模型系统。 (第一次写博客,记录下自己大一时做的一个课设,如有不妥之处,还望多...
阅读 3733·2023-01-11 11:02
阅读 4243·2023-01-11 11:02
阅读 3049·2023-01-11 11:02
阅读 5179·2023-01-11 11:02
阅读 4733·2023-01-11 11:02
阅读 5532·2023-01-11 11:02
阅读 5312·2023-01-11 11:02
阅读 3985·2023-01-11 11:02