Если ваш вопрос не влез ни в одну из вышеперечисленных тем, вам сюда.
Ответить

PID-регулятор на языке C.

Пт май 13, 2022 11:17:45

Здравствуйте. Прошу помощи в настройке PID-регулятора, реализованном на STM32F103. Устройство - паяльная станция. Функция PID-регулятора вызывается по прерыванию таймера один раз в 10 миллисекунд. Код самой функции на языке C прилагаю ниже. Проблема вот в чем: не могу настроить коэффициенты. Если подобрать их экспериментально (сначала Kp, затем Kd, и "добить" Ki), то все отлично работает в устоявшемся режиме. Но если остудить нагреватель до комнатной температуры и затем включить питание, то интегральный коэффициент начинает очень быстро расти до бесконечности, ускоряя нагрев. То есть P и I составляющие начинают работать вместе в одну сторону. Как результат - перелет уставки почти на 100%. Я понимаю, что дифференциальный коэффициент должен снижать влияние I в момент нагрева, но этого не происходит должным образом. Может, я что-то не так делаю? Подбор коэффициентов по методу Зиглера-Никольсона вообще приводит в болтанке температуры вокруг уставки в пределах 3-5 градусов. Либо же коэффициенты при нагреве и в режиме стабилизации параметра должны быть разные? Спасибо.

Код:
float Kp = 71.5;
float Ki = 5.7;
float Kd = 1.368;

volatile float  PreError = 0;
volatile float Integral = 0;
volatile float Derivative = 0;

float PID_Regulate(float input, float setpoint, float dt, float min, float max) {

float P = 0;
float I = 0;
float D = 0;
float out = 0;
float error = 0;

error = setpoint - input;
P = Kp*error;

Integral += error * dt;
I = Ki*Integral;

Derivative = (error - PreError)/dt;
D = Kd*Derivative;

out = P + I + D;

// Restrict to max/min
if( out > max )
out = max;
else if( out < min )
out = min;

PreError = error;

return out;

}

Re: PID-регулятор на языке C.

Пт май 13, 2022 11:54:07

введи ограничение на компонентту I, например нет смысла выращивать её больше, чем допускает out...

Re: PID-регулятор на языке C.

Пт май 13, 2022 12:02:55

введи ограничение на компонентту I, например нет смысла выращивать её больше, чем допускает out...


Думал про это. Но не повредит ли это неразрывности функции PID-регулятора? Я в матанализе дундук. Насколько я понял, переменные с точкой применяют как раз для обеспечения неразрывности.

Re: PID-регулятор на языке C.

Пт май 13, 2022 12:08:38

точнее "подрезать" надо не саму I, а переменную Integral... подезать до +max/Ki и -min/Ki

Добавлено after 5 minutes 8 seconds:
ну... неразрывность это точно не нарушит, да и не так важна она, если понимаешь что делаешь и чем рулишь...
если на выходе ШИМ, то ему и высокоточный флоат нафиг не нужен, в большинстве случаев хватает char для компонент и int для накопителей

для умножения тоже флоат не обязателен, достаточно математики с фиксированной (в уме программиста :) ) точкой

Re: PID-регулятор на языке C.

Пт май 13, 2022 12:31:37

если понимаешь что делаешь и чем рулишь..


С моим знанием матанализа я мало чего понимаю. То, что я сейчас наделал и написал - это результат курения мануалов, форумов и статей по ПИД-регуляторам в течение более чем месяца. Но что такое дифференциал и интеграл по dt я все равно не понимаю от слова совсем. Разбирая код классического ПИД-алгоритма, я более-менее въехал, что интегральная составляющая накапливает ошибку в себе, суммируя ошибки на интервалах периода дискретизации. Дифференциальная составляющая мне далась сложнее - я вижу, что она показывает, как сильно изменилась "разница" между текущей ошибкой и предыдущей за период дискретизации dt. То есть D - это "скоростная" характеристика PID-регулятора, она "замедляет" нагрев при приближении температуры к уставке, становясь отрицательной и вычитаясь из суммы P + I. В случае большой разницы между уставкой и текущим значением она, наоборот, становится положительной, ускоряя нагрев. Правильно же я понимаю?

Но тут может быть и другая проблема. В моей реализации детектора нуля для синуса (нагреватель питается синусом 24 вольта). Работает он так: есть детектор перехода синуса через ноль на оптроне. Он дает спадающий фронт в момент перехода через ноль синусоиды. Этот спадающий фронт подается на ногу входа таймера TIM2, который запускается при каждом спадающем фронте (Slave mode). К этому же таймеру подключен Output Compare в режиме PWM, который ждет, когда таймер досчитает до некоторого значения TIM2->CCR3 и дает импульс определенной длительности. То есть, по обнаружению каждого спадающего фронта с детектора нуля, контроллер выдает ответный импульс через задержку, которая задается TIM2->CCR3. Этот ответный импульс и подается на управляющий вход симистора. Чем больше задержка между точкой перехода синуса через ноль и ответным импульсом Output Compare, тем сильнее "режется" синус. Мой ПИД-регулятор выдает значение от 0 до 1000. И чтобы получить нужную "задержку" перед импульсом, я беру значение обратное, чем выдает ПИД-регулятор: то есть 1000 - PID. Например, ПИД решил, что нужна мощность 80% - это значение 800 (образно). Тогда задержка, которая записывается в TIM2->CCR3, будет 1000 - 800 = 200. Таймер TIM2 настроен так, чтобы при значении в CNT числа 1000, задержка была ровно 10 миллисекунд, то есть полупериод полностью обрезан симистором. Задержка 0 с момента перехода через ноль - синус не порезан, мощность максимальна. Задержка 1000 - синус полностью порезан.

К чему я все это рассказываю - возможно, у меня какие-то нестыковки с этим числом - 1000. То есть эта 1000 "долго" накапливается в ПИД-алгоритме и долго спадает. Может, здесь косяк?

Вот как это у меня реализовано (TIM4 срабатывает раз в 10 мс, это dt):

Код:
void TIM4_IRQHandler(void) {

uint8_t i;

TIM4->SR &=~ TIM_SR_UIF; // Clear TIM4 interrupt flag.

// Calculating average of 16 meansurements ******************************************
ReadedTemp = 0;

for (i = 0; i < 16; i++) {
   ReadedTemp += ((SPI_Get32bit() >> 18)/4);
}

ReadedTemp = ReadedTemp/16;
// **********************************************************************************

Power = (uint32_t)(1000 -(PID_Regulate(ReadedTemp, TIM1->CNT, 0.01, 0, 1000)));

TIM2->CCR3      = Power; // Задержка перед передним фронтом ответного импульса на симистор.
TIM2->ARR       = TIM2->CCR3 + 5; // Задний фронт = передний фронт + 5 тактов.

}

Re: PID-регулятор на языке C.

Пт май 13, 2022 13:12:19

ПИД не должен касаться метода реализации: ШИМ там ЦАП, или ФИМ - без разницы

из прочитанного я понял, что выход может принимать значения от 0 до 1000, соответственно и переменная Integral не должна выходить за диапазон [0..1000/Ki]

скорость нагрева и остывания паяльника разные, поэтому можно сделать разные скорости приращения Integral (в зависимости от знака и значения ошибки)
так получится не чистый ПИД, а его смесь с фуззилоджик, но такой регулятор обычно показывает лучшие характеристики...

Добавлено after 6 minutes 23 seconds:
симистор на 24 вольта? занятно... ИМХО полевик (или 2 встречных полевика) тут покажет гораздо лучшие результаты, т.к. падение на нем значительно меньше.

Добавлено after 2 minutes 23 seconds:
Lum1noFor писал(а):ибо же коэффициенты при нагреве и в режиме стабилизации параметра должны быть разные?
примерно это я и хотел сказать (по крайней мере про I)
но, чтобы обеспечить непрерывность регулирования - нужно коэффициент Ki сделать до накопителя, тогда накопителем станет сам I (т.е. от одной переменной можно будет избавиться)

Добавлено after 6 minutes 21 second:
Ещё вопрос - на сколько отсчетов АЦП изменяется input за время между измерениями? если на 0-3, то это очень мало - коэффициент Д будет дергаться... (нужно будет увеличивать шаг итерации, либо другим способом повышать точность определения скорости изменения характеристики)

Re: PID-регулятор на языке C.

Пт май 13, 2022 13:29:20

Огромное спасибо Вам за подробные ответы! В Ваших советах я отдаленно вижу то, что мельком читал на зарубежных форумах и в аппнотах ATMEL, но сходу понять не смог. Попытаюсь поиграть временем дискретизации - увеличу до 100 мсек. Благо все делается железкой, поправить предделитель таймера не проблема. Ибо за 10 мсек, действительно, температура будет меняться всего на 1-3 градуса. А возможно ли сделать так - запускать нагрев при Ki = 0, а включать его уже после "грубого" нагрева?

Re: PID-регулятор на языке C.

Пт май 13, 2022 13:48:06

Вот примерно накидал ограничение I малой кровью...

Lum1noFor писал(а):А возможно ли сделать так - запускать нагрев при Ki = 0, а включать его уже после "грубого" нагрева?
можно, можно просто при большой ошибке принудительно вычищать накопитель... но решение это несколько "угловатое" :)

Добавлено after 3 minutes 47 seconds:
Lum1noFor писал(а):за 10 мсек, действительно, температура будет меняться всего на 1-3 градуса.
не, надо не в градусах, а в единицах измерения... если у тебя температура измеряется с точностью 0.1 градуса, то градус - это уже 10 единиц... (тут не важно с какой точностью оно на экран выходит, а важно с какой точностью оно измеряется и в ПИД передается)

Добавлено after 2 minutes 35 seconds:
Ээх... одни флоаты... хорошо вам на стмах живётся... :))) :))) :))) жаль АЦП температуру в инт`ах меряет... :)))

Добавлено after 6 minutes 26 seconds:
П.С: код поправил перевозьми...

Re: PID-регулятор на языке C.

Пт май 13, 2022 13:49:57

не, надо не в градусах, а в единицах измерения... если у тебя температура измеряется с точностью 0.1 градуса, то градус - это уже 10 единиц... (тут не важно с какой точностью оно на экран выходит, а важно с какой точностью оно измеряется и в ПИД передается)


А вот это я не учел. Микросхема меряет с разрешением 0.25 градуса. А в ПИД я передаю это число, уже умноженное на 4, то есть 1 градус. Больше так делать не буду. Благодарю еще раз!

Re: PID-регулятор на языке C.

Пт май 13, 2022 13:51:57

если умножаешь после перевода во флоат, то не страшно, если до - то точность потеряется.

Re: PID-регулятор на языке C.

Пт май 13, 2022 14:04:07

Функция PID-регулятора вызывается по прерыванию таймера один раз в 10 миллисекунд.
Во-первых: Если функция вызывается периодически, с const периодом, то зачем dt? Чтобы ещё больше затормозить и без того крайне тормозной код? :dont_know:
Выкинуть dt отовсюду. За ненадобностью.
Integral += error * dt;
I = Ki*Integral;
Во-вторых: Здесь интегральную составляющую лучше считать так:
Integral += error * Ki;
Далее - ограничение. Это и будет I.

В-третьих: дифференциальная составляющая вам зачем? Думаете - возможны очень резкие изменения регулируемой величины??? Разве температура нагревателя в принципе может изменяться резко за .01 сек? :dont_know:
Выкинуть нафиг дифф.составляющую. Не загромождать и без того тормозной код ненужным хламом.

В-четвёртых: (как уже сказали выше) - ввести ограничение на интегральную составляющую (а не только на общий выход).

В-пятых: Использовать плавающую точку на CPU не имеющем FPU имеет смысл только там где это реально необходимо. И то - стоит 10 раз подумать, прежде чем использовать её в алгоритмах реального времени. В вашем случае нет совершенно никакой надобности в плавающей точке, всё легко считается на фиксированной точке.
И в целом - код написан очень топорно, задней пяткой левой ноги.

Re: PID-регулятор на языке C.

Пт май 13, 2022 14:16:07

ну, я не вижу, какой у Lum1noForа проц.... но раз так щедро флоаты применены, решил, что позволяет...
а разве это редкость? вроде стм32ф303... уже флоаты аппаратно могёт...
Последний раз редактировалось Ivanoff-iv Пт май 13, 2022 14:18:04, всего редактировалось 1 раз.

Re: PID-регулятор на языке C.

Пт май 13, 2022 14:18:01

ну, я не вижу, какой у Lum1noForа проц.... но раз та
Посмотрите 1-е сообщение.

Re: PID-регулятор на языке C.

Пт май 13, 2022 14:20:04

прошу пардону... увидел, да 103 вроде их не умеет...
но если мк больше ни чем не занят то 32х битное ядро с этим кодом сумеет справиться...

Re: PID-регулятор на языке C.

Пт май 13, 2022 14:45:36

прошу пардону... увидел, да 103 вроде их не умеет...
но если мк больше ни чем не занят то 32х битное ядро с этим кодом сумеет справиться...
С этим - да. Но ведь завтра окажется, что ШИМ или UART или ещё какой интерфейс "почему-то" теряет события (не успевает). А всё потому, что прерывание ПИД зачем то занимает тысячи тактов. Хотя в этом нет совершенно никакой надобности. И всё придётся переделывать сначала.... :dont_know:

Добавлено after 21 minute 48 seconds:
ТС-у для примера пример достаточно оптимального кода ПИ-регулятора на fixed-point:
Код:
  typedef unsigned long u32;
  typedef signed long s32;
  #define shift(a, b) (((b) >= 0) ? (a) << (b): (a) >> -(b))
  #define ASIGN32(x, y) (((s32)(x) ^ (s32)(y) >> 31) - ((s32)(y) >> 31)) //при y>=0 result == x; при y<0 result == -x
  #define roundU32(x) ((u32)((x) + .5))

  //ПИ-регулятор
  #define FOC_QPNT        25 //положение дес.точки для вычислений ПИ-регулятора
  #define FOC_QPNT_ACT 28 //положение дес.точки для входных значений ПИ-регулятора
  #define CONFIG_MAX_PID_kp 16.0 //макс.возможное значение Kp (невкл.)
  s32 focd.cfg.pid.id.kp; //коэфф.усиления пропорциональной составляющей; UQ1.31, нормированое к CONFIG_MAX_PID_kp
  s32 focd.cfg.pid.id.ki;  //коэфф.усиления интегральной составляющей; SQ1.31 * -1 (меньше 1)
  s32 ref;  //целевое значение
  s32 in;   //текущее входное значение
  s32 err, i, j;
  //вычисление ошибки
  err = ref - shift(in, FOC_QPNT - 28); //SQ.FOC_QPNT
  //вычисление I-составляющей
  j = __QDSUB(focd.pid.d.outi, __SMMUL(focd.cfg.pid.id.ki, err)); //IOut(k) = IOut(k-1) + Ki * err; SQ.FOC_QPNT 
  //ограничение I-составляющей (симметричное ограничение)
  i = focd.cfg.pid.id.limI;
  if ((u32)(j + i) > (u32)i * 2) j = ASIGN32(i, j);
  focd.pid.d.outi = j;  //SQ.FOC_QPNT;
  //вычисление полного результата
  j = __SMMLA(focd.cfg.pid.id.kp, err * (s32)roundU32(CONFIG_MAX_PID_kp), j); //Out(k) = Kp * err + IOut(k); SQ.(FOC_QPNT-1)
  //ограничение полного результата (симметричное ограничение)
  i = focd.cfg.pid.id.lim;
  if ((u32)(j + i) > (u32)i * 2) j = ASIGN32(i, j);
  j <<= 1; //SQ.FOC_QPNT
  //здесь в j = результат с точкой в позиции FOC_QPNT

Значения положения дес.точки (FOC_QPNT и FOC_QPNT_ACT) - подобрать наиболее подходящими для себя.
Компилится такое на Cortex-M4 всего в ~24 команды. Даже на не самом высоком уровне оптимизации.
Конечно этот код - для Cortex-M4. Но если заменить команды SMMUL, SMMLA, QDSUB (которых нет в Cortex-M3) на другие, то будет почти тот же результат, только чуть больше команд.
Последний раз редактировалось jcxz Пт май 13, 2022 14:52:57, всего редактировалось 1 раз.

Re: PID-регулятор на языке C.

Пт май 13, 2022 14:51:41

И в целом - код написан очень топорно, задней пяткой левой ноги.
Так человек и не хвастаться пришел... разберётся - перепишет на фиксированных дробях... а пока ему приколов с потерей точности да с переполнениями не хватало...

Re: PID-регулятор на языке C.

Пт май 13, 2022 15:00:53

разберётся - перепишет на фиксированных дробях... а пока ему приколов с потерей точности да с переполнениями не хватало...
Вряд-ли. Вангую - как только хоть как-то заработает, так и оставит как есть.

PS: Я в своё время начинал на fixed-point. Так как в те времена просто не было подходящих МК с аппаратными FPU. И считать надо было быстро. Сразу на fixed-point начинал. И ничего: не умер, разобрался с переполнениями и пр. Ничего в этом сложного нет. А если не ставить перед собой задач, требующих обучения и роста, то и никогда и не вырастешь.
И к тому же - в плавающей точке приколов тоже не меньше. И переполнения там имеются, и всякие особые значения (денормированные, "нечисла", +-бесконечности). Только начинающие о них никогда не задумываются. И даже когда уже получат какой-то результат - тоже так и не задумаются и не узнают. Так как "не вникают".

Re: PID-регулятор на языке C.

Пт май 13, 2022 15:08:52

А всё потому, что прерывание ПИД зачем то занимает тысячи тактов.

По-моему, float в прерывании - это уже за гранью. Конечно, в этом простом проекте ещё может и прокатить, но навык "индусско_кодо_писательства" закрепится, и перебороть себя будет нелегко.
----------

Re: PID-регулятор на языке C.

Пт май 13, 2022 15:46:46

Функция PID-регулятора вызывается по прерыванию таймера один раз в 10 миллисекунд.
Во-первых: Если функция вызывается периодически, с const периодом, то зачем dt? Чтобы ещё больше затормозить и без того крайне тормозной код? :dont_know:
Выкинуть dt отовсюду. За ненадобностью.
Integral += error * dt;
I = Ki*Integral;
Во-вторых: Здесь интегральную составляющую лучше считать так:
Integral += error * Ki;
Далее - ограничение. Это и будет I.

В-третьих: дифференциальная составляющая вам зачем? Думаете - возможны очень резкие изменения регулируемой величины??? Разве температура нагревателя в принципе может изменяться резко за .01 сек? :dont_know:
Выкинуть нафиг дифф.составляющую. Не загромождать и без того тормозной код ненужным хламом.

В-четвёртых: (как уже сказали выше) - ввести ограничение на интегральную составляющую (а не только на общий выход).

В-пятых: Использовать плавающую точку на CPU не имеющем FPU имеет смысл только там где это реально необходимо. И то - стоит 10 раз подумать, прежде чем использовать её в алгоритмах реального времени. В вашем случае нет совершенно никакой надобности в плавающей точке, всё легко считается на фиксированной точке.
И в целом - код написан очень топорно, задней пяткой левой ноги.


Спасибо за критику! Задача именно попытаться разобраться, как сделать правильно. По поводу float'ов не беспокойтесь, я знаю, что их лучше не использовать без FPU. А здесь я это сделал по двум причинам: 1. МК не делает больше НИЧЕГО, кроме расчета PID-алгоритма. Все остальное работает на железках - SPI, детектор нуля, энкодер. 2) Я НЕ ПОНИМАЮ ВЫСШУЮ МАТЕМАТИКУ! Хочу понять, что такое интеграл, дифференциал, но я уже не в том возрасте, чтобы до меня легко доходило. Ну не понимаю и все тут. Буду дальше пытаться понять. И именно из-за этого полного непонимания смысла дифференциала и интеграла я и налепил float'ов, чтобы где-нибудь не вылезла какая-нибудь неизвестная мне математическая ошибка, типа мистического "разрыва функции регулирования". Например, мне не понятно, почему Вы говорите, что можно просто взять и убрать dt. Если есть время и возможность - объясните, пожалуйста! Еще раз благодарю!


Во-вторых: Здесь интегральную составляющую лучше считать так:
Integral += error * Ki;
Далее - ограничение. Это и будет I.


А почему именно так? Из соображений производительности, или есть какие-то другие причины? Почему не "в лоб", вот так:

Код:
Integral += error * dt;

if (Integral > max/Ki) {
   Integral = max/Ki;
} else if (Integral < -max/Ki) {
   Integral = -max/Ki;
}

I = Ki*Integral;

Re: PID-регулятор на языке C.

Пт май 13, 2022 16:09:40

dt это время прошедшее между замерами - чтобы узнать скорость, нужно знать путь и время, можно измерять скорость за весь путь, а можно за короткий его участок, причём чем эти участки короче, тем "живей" (менее сглаженным) будет график скорости, математики хотят убавить этот участок до нуля, но на нулевом участке не измерить скорости, поэтому математики согласились просто сделкть его очень маленьким.... а инженеры (и программисты) оптимальным - чтобы и обсчитывать было проще и "задумчивости" не возникало

Добавлено after 3 minutes 21 second:
dt из программы выкинуть ни-как не получится! но, если dt всегда равен 0,01с (или другому значению, важно, что он постоянен) то его можно учесть в прикомпонентных коэффициентах вот и весь фокус...
Ответить