本文共 4728 字,大约阅读时间需要 15 分钟。
PID控制算法即比例、积分、微分控制算法。PID控制分为两种:模拟PID和数字PID,前者处理的是连续信号,后者处理的是离散信号。因为单片机只能处理离散信号,本文主要谈后者。对于后者,又分为两种常用的算法即增量式数字PID和位置式数字PID,其中增量式用的更多,所以本文以增量式数字PID为例。PID控制的数学原理比较复杂,限于作者水平,大家可以点击进一步了解其控制原理。另外,根据实际控制需求,PID控制算法不一定三个环节都要用,一般常用的还有PI控制、PD控制。
PID控制算法的设计过程中最重要的一个环节就是确定三个参数值的大小的确定,即PID参数整定。为了方便叙述,我们直接用上面的链接里的思路,即确定三个系数A、B、C的大小。其中 :
A = Kp + Ki + Kd,B = Kp + 2 * Kd,C = Kd。 ------- (1)
Kp,Ki,Kd分别是PID控制器的三个参数。
先上图看一下它长什么样子:
编码器:所谓编码,就是它可以把电机的角位移或者角速度信号转换为电脉冲信号,通过对脉冲信号的检测可以确定电机的转速。在上面的结构图中,编码器就是传感器,它将电机转速经测量变速传给单片机。
编码器的驱动:我们用stm32f10xxx系列的单片机作为主控芯片,stm32单片机的定时器外设资源丰富,并且它的通用定时器本身就带有控制编码电机的工作模式,简直就是“量身定做”! 直流电机的控制:直流电机的转速控制用PWM技术实现,调节PWM的占空比可以线性地改变电机两端的电压,从而控制电机的转速,控制结构图如下:
上面的电机驱动本质是一个功率放大器,将单片机产生的、一定占空比的PWM矩形波转为一定电压电流的模拟信号,进而驱动电机。
程序流程如下:
1. 初始化
初始化这一步主要完成一些全局变量、外设的赋值和配置工作。具体是:PID控制相关参数的初始化,定时器、串口等其他辅助性的外设的配置:
delay_init(); //延时函数初始化 TIM5_PWM_Init(1000,9); //PWM初始化 8KHz TIM3_ENC_Init(); //编码器初始化 TIM4_ENC_Init(); //编码器初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级配置 pid_init(); uart_init(115200); //初始化串口,波特率设置为115200
2. PID参数设置
do //下面的循环语句是为了调节PID参数,串口接收到'0',跳出循环 { switch(go) //注意大小写 { case 'P': mts.Kp += val; go = 0; break; case 'I': mts.Ki += val; go = 0; break; case 'D': mts.Kd += val; go = 0; break; case 'p': mts.Kp -= val; go = 0; break; case 'i': mts.Ki -= val; go = 0; break; case 'd': mts.Kd -= val; go = 0; break; default: break; } delay_ms(10); }while(go != '0');
这一步是为了后面程序复位后方便调整PID参数,即便于PID参数整定。
3. 循环体
while(1) { detPID_PWM_out();//调用PID函数,计算控制增量 if(sendFlag) //每刷新一次控制输出,往上位机发一次数据 { send_intData(mts.detPWM_out); sendFlag = 0; } TIM_SetCompare1(TIM5,BASE_SPEED - mts.detPWM_out); //注意电机的正反转,这里的4个通道可以控制两个电机正反转 TIM_SetCompare2(TIM5,BASE_SPEED + mts.detPWM_out); TIM_SetCompare3(TIM5,0); //通道3、4为0,此时假设两个电机刚好往同一个方向转,如果转向不一致,请对调硬件或再修改代码,这里只举例说明 TIM_SetCompare4(TIM5,0); }}
循环体里面计算控制输出,并根据控制输出调整PWM矩形波的占空比。
4. 中断
/*下面是中断服务函数,定时器中断的优先级高于串口中断*/void USART1_IRQHandler(void) //串口1中断服务程序 { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d(回车) 0x0a(换行)结尾) { if(sendFlag%2) { val = USART_ReceiveData(USART1); //读取接收到的数据 } else { go = USART_ReceiveData(USART1); //读取接收到的数据 } sendFlag = ++sendFlag >= 255?1:sendFlag; } } void TIM6_IRQHandler(void) //TIM6中断{ if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 { TIM_ClearITPendingBit(TIM6, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源 // (u_16) = TIM_GetCounter(TIM4);// (U_16) = TIM_GetCounter(TIM3); mts.Ek_2 = mts.Ek_1; mts.Ek_1 = mts.Ek; mts.Ek = TIM_GetCounter(TIM3) - TIM_GetCounter(TIM4); //计算偏差 sendFlag = 1; } }
程序中只用了两个中断,一个是串口接收中断,用于接收上位机的命令和数据,以便调试或者监控程序里面一些变量的变化;另一个是定时器中断,为了以50Hz的频率采集编码器反馈的速度信号,同时计算偏差。
本文只介绍了最简单的PID控制算法C语言实现,上面提到的那几种常用的PID改进算法每一个都可以单独拿出来讲一下如何实现,但限于篇幅且本文重点不是这个所以大家感兴趣的话可以先查查相关的文章,后面有机会我会再单独讲一下我个人对这些算法的理解和实际应用中的实现。本文的基于uKeil的C源码工程共享在公众号“24K纯学渣”上面,回复“PID电机控制”即可获取。另外,限于作者水平,如有描述错误或不妥之处,欢迎大家在下面评论指出。如果希望进一步的交流学习,可以在公众号上联系作者,任何学习上的交流本人都非常欢迎!
--------------------- 作者:24K纯学渣 来源:CSDN 原文:https://blog.csdn.net/qq_42144047/article/details/103871970?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control