摘要:
本文圍繞STM32中的PID控制展開。介紹了PID的比例、積分、微分三部分作用,以及積分限幅和輸出值限幅。闡述了位置式、增量式和串級PID的原理與代碼。還講述了Kp、Ki、Kd調參要點,并介紹了野火多功能調試助手的調參方法及通信代碼。
一、知識點
1. PID是什么?
在PID控制中,P、I、D分別代表比例(Proportional)、積分(Integral)、微分(Derivative)三個部分。它們是PID控制器中的三個調節參數,用于調節控制系統的輸出,以使系統的反饋與期望值更加接近。
P(比例)部分:根據當前偏差的大小來調節輸出。當偏差較大時,P部分的作用就越強烈,輸出的變化也就越大。P控制項對應于系統的當前狀態,它的作用是減小系統對設定值的超調和穩定時間。
I(積分)部分:對偏差的積累進行調節。它的作用是消除穩態誤差,使系統更快地達到穩定狀態。I控制項對應于系統過去的行為,它的作用是減小系統對外部干擾的影響。
D(微分)部分:根據偏差變化的速度來調節輸出。它的作用是預測系統未來的行為,以減小系統的振蕩和過沖現象,提高系統的響應速度和穩定性。
綜合來說,PID控制器通過比例、積分、微分三個部分的組合來調節系統的輸出,以實現對系統的精確控制。
2. 積分限幅–用于限制無限累加的積分項
因為積分系數的Ki是與累計誤差相乘的,所以效果是累加,隨著時間的推移,積分項的值會升到很高,積分本來的作用是用來減小靜態誤差,但積分項過大會引起過大的震蕩,所以我們可以加一個判斷函數if,當積分項的值達到一定值后,就讓積分項保持這個值,避免引起更大的震蕩。積分限幅的最大值,要根據經驗實際多調試調試。
//為了防止積分項過度累積,引入積分項的限幅是一種常見的做法。
//限制積分項的幅值可以防止積分項過度增加,從而限制了系統的累積誤差。這樣可以避免系統過度響應或者不穩定。
float abs_limit(float value, float ABS_MAX) //積分限幅,設置最大值。
{
if(value > ABS_MAX)
value = ABS_MAX;
if(value< -ABS_MAX)
value = -ABS_MAX;
return value;
}
3. 輸出值限幅–用于任何 pid 的輸出
這個需要查看產生 pwm 的定時器的計數周期初值設定。如Motor_PWM_Init(7200-1,0);
,則 outputmax 就不能大于 7200。
//限制輸出最大值,防止出現突發意外。輸出outputmax的最大值
if(pid->output > pid->outputmax ) pid->output = pid->outputmax;
if(pid->output < - pid->outputmax ) pid->output = -pid->outputmax
4. PID 工程
(1)定時器 1(產生 pwm)tim1.c
#include "tim1.h"
void Motor_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外設時鐘使能
//設置該引腳為復用輸出功能,輸出TIM1 CH1 CH4的PWM脈沖波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //TIM_CH1 //TIM_CH4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值 不分頻
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //選擇定時器模式:TIM脈沖寬度調制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能
TIM_OCInitStructure.TIM_Pulse = 0; //設置待裝入捕獲比較寄存器的脈沖值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高
TIM_OC4Init(TIM1, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的參數初始化外設TIMx
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主輸出使能
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH4預裝載使能
TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的預裝載寄存器
TIM_Cmd(TIM1, ENABLE); //使能TIM1
}
tim1.h
#ifndef __TIM1_H
#define __TIM1_H
#include
#define PWMB TIM1->CCR4 //PA11
void Motor_PWM_Init(u16 arr,u16 psc);
#endif
(2)定時器 2(定時)
#include "tim2.h"
#include "led.h"
#include "usart.h"
#include "sys.h"
void MotorControl(void)
{
Encoder_Posion = Read_Position();//1.獲取定時器3的編碼器數值
Speed=PosionPID_realize(&PosionPID,Encoder_Posion);//2.輸入位置式PID計算
Set_Pwm(Speed); //3.PWM輸出給電機
//指令/通道/發送數據/個數
set_computer_value(SEND_FACT_CMD, CURVES_CH2, &Encoder_Posion, 1); /*4.給上位機通道2發送實際的電機速度值,詳情看下面內容*/
}
void Time2_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = arr; //電機PWM頻率要和定時器采樣頻率一致
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
MotorControl();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
(3)定時器 4(編碼器)
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
/*TI1和TI2都計數,上升沿計數*/
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_Cmd(TIM4, ENABLE);
}
int16_t Read_Position(void)
{
int16_t Temp;
Temp = TIM_GetCounter(TIM4); //獲取定時器計數值
TIM_SetCounter(TIM4, 0);
return Temp;
}
(4)串口 1 usart.c
#include "sys.h"
#include "usart.h"
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
#if 1
#pragma import(__use_no_semihosting)
//標準庫需要的支持函數
struct __FILE
{
int handle;
};
FILE __stdout;
//定義_sys_exit()以避免使用半主機模式
void _sys_exit(int x)
{
x = x;
}
//重定義fputc函數
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循環發送,直到發送完畢
USART1->DR = (u8) ch;
return ch;
}
#endif
//串口1中斷服務程序
//注意,讀取USARTx->SR能避免莫名其妙的錯誤
u8 USART_RX_BUF[USART_REC_LEN]; //接收緩沖,最大USART_REC_LEN個字節.
//接收狀態
//bit15, 接收完成標志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字節數目
u16 USART_RX_STA=0; //接收狀態標記
void uart_init(u32 bound){
//GPIO端口設置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA時鐘
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶占優先級3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
//USART 初始化設置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟串口接受中斷
USART_Cmd(USART1, ENABLE); //使能串口1
}
void USART1_IRQHandler(void)//串口中斷服務函數
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE)== SET ) //產生了接收中斷
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除接收中斷標志位
Res=USART_ReceiveData(USART1);
protocol_data_recv(&Res,1);
}
}
void usart1_send(u8*data, u8 len) //發送數據函數
{
u8 i;
for(i=0;i
usart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "sys.h"
#define USART_REC_LEN 200 //定義最大接收字節數 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收緩沖,最大USART_REC_LEN個字節.末字節為換行符
extern u16 USART_RX_STA; //接收狀態標記
void uart_init(u32 bound);
void usart1_send(u8*data, u8 len);
#endif