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