В общем-то ПИД регулятор применяю впервые.
Для начала решил стабилизировать обороты самого обычного кулера. Потом, естественно, доберусь и до температуры.
С кулером удобнее и нагляднее экспериментировать.
Ну, задача известная: поддерживать заданную частоту вращения ротора безколлекторного двигателя постоянного тока.
После внимательного изучения всей учёной части я таки впал в искушение и не стал кодить сам, а скачал атмеловскую аппноту
AVR221Код регулятора:
Код
#define SCALING_FACTOR 128
//Needed to avoid sign/overflow problems
// Maximum value of variables
#define MAX_INT INT16_MAX
#define MAX_LONG INT32_MAX
#define MAX_I_TERM (MAX_LONG / 2)
typedef struct PID_DATA{
//! Last process value, used to find derivative of process value.
int16_t lastProcessValue;
//! Summation of errors, used for integrate calculations
int32_t sumError;
//! The Proportional tuning constant, multiplied with SCALING_FACTOR
int16_t P_Factor;
//! The Integral tuning constant, multiplied with SCALING_FACTOR
int16_t I_Factor;
//! The Derivative tuning constant, multiplied with SCALING_FACTOR
int16_t D_Factor;
//! Maximum allowed error, avoid overflow
int16_t maxError;
//! Maximum allowed sumerror, avoid overflow
int32_t maxSumError;
} pidData_t;
//===========================================================
void pid_Init(int16_t p_factor, int16_t i_factor, int16_t d_factor, struct PID_DATA *pid)
// Set up PID controller parameters
{
// Start values for PID controller
pid->sumError = 0;
pid->lastProcessValue = 0;
// Tuning constants for PID loop
pid->P_Factor = p_factor;
pid->I_Factor = i_factor;
pid->D_Factor = d_factor;
// Limits to avoid overflow
pid->maxError = MAX_INT / (pid->P_Factor + 1);
pid->maxSumError = MAX_I_TERM / (pid->I_Factor + 1);
}
//===========================================================
int16_t pid_Controller(int16_t setPoint, int16_t processValue, struct PID_DATA *pid_st)
{
int16_t error, p_term, d_term;
int32_t i_term, ret, temp;
error = setPoint - processValue;
// Calculate Pterm and limit error overflow
if (error > pid_st->maxError){
p_term = MAX_INT;
}
else if (error < -pid_st->maxError){
p_term = -MAX_INT;
}
else{
p_term = pid_st->P_Factor * error;
}
// Calculate Iterm and limit integral runaway
temp = pid_st->sumError + error;
if(temp > pid_st->maxSumError){
i_term = MAX_I_TERM;
pid_st->sumError = pid_st->maxSumError;
}
else if(temp < -pid_st->maxSumError){
i_term = -MAX_I_TERM;
pid_st->sumError = -pid_st->maxSumError;
}
else{
pid_st->sumError = temp;
i_term = pid_st->I_Factor * pid_st->sumError;
}
// Calculate Dterm
d_term = pid_st->D_Factor * (pid_st->lastProcessValue - processValue);
pid_st->lastProcessValue = processValue;
ret = (p_term + i_term + d_term) / SCALING_FACTOR;
if(ret > MAX_INT){
ret = MAX_INT;
}
else if(ret < -MAX_INT){
ret = -MAX_INT;
}
return((int16_t)ret);
}
ШИМ у меня 8бит.
каждые 250ms делаю так
Код
int16_t regOut;
cooler = coolers.collection[i];
regOut = pid_Controller(cooler->rpm_goal, cooler_get_rpm(cooler->id), &cooler->pid );
regOut += cooler->current_pwm_duty;
if( regOut > 255 )
regOut = 255;
if( regOut < 0 )
regOut = 0;
cooler->current_pwm_duty = regOut;
cooler->setPWM( cooler->current_pwm_duty );
Регулятор в целом работает, но скверно. Его невозможно настроить.
передаваемые в pid_Init() p_factor i_factor d_factor имеют слишком большой эффект.
p_factor 1 работает, но реакция медленная. А уже при 2 колеблется достаточно сильно. При 3 - 5 уже полный абзац.
i_factor вообще лучше не трогать. Там даже 1 всё ломает. )
d_factor туда-сюда +/-5 - 10 ещё терпимо, но толку это не даёт
Также, заметил очень большой "зазор". Регулятор считает, что всё хорошо, хотя на самом деле обороты от заданных могут отличаться на 200 - 300(при 1100 заданных).
Мне не нравится что здесь всё как то уж больно целочисленно делается.
Есть идея переписать это дело под фиксированную точку, чтобы коэффициенты можно было задавать с шагом хотя-бы 0.1 вместо нынешней единицы.
Однако чувствую, что просто чего-то ещё не понял и того-же эффекта можно достичь другими методами.
Может быть мне стоит разделить входные setPoint и processValue на 2(а может и на 4), тем самым уменьшу ошибку и диапазон коэффициентов расширится?
Ещё вопрос как быть с частотой регулирования: как выбрать её оптимальной?
А ещё есть функция pid_Reset_Integrator(), которая сбрасывает сумму ошибок в 0. Когда её вызывать?
The truth is out there...