Справочници, схемотехника, теория > Програмиране на микроконтролери, програматори, цифрови проекти
GPS-автопилот - алгоритъм
juliang:
Копирал съм кода от работещ контролер, в който съм правил доста промени.
1. Такъв е синтаксиса на този тип контролери. А и на езика Си, на който се пишат програмите. Struct-а е... как да го кажа ... обект, нещо. Това нещо си носи както променливите, така и логиката. в една програм може да има много "неща", много "обекта". Самия контролер при захранваането последователно извиква функциите Init на всички обекти в прогамата, и после циклично извиква последователно всички Main методи на обектите. Така функционира... не ме питай защо, вероятно щото голяма част от кода се генерира автоматично и е по-лесно.
2. Enable е мой пропуск - трябва да го допиша в логиката. Всеки един "блок", или "обект" трябва да има тази променилва и ако тя е False, това означава че контролера е спрян. Тогава почти всички блокове трябва да спрат да работят, остават само тези които следят сензорите или бутоните, с които контролера да се пусне отново. Не че е грешка, но е признак на недоглждане :) Просто в началото на Main метода трябва да допиша "if !Enable then return;" и блока няма да изпълнява нищо.
3. Да, тази функция се изпълнява еднократно при захранването на контролера.
4. Тук ... исках да се подсигуря. Td и Ti са променливи, които са записани в енергонезависимата памет на контролера - те се задават от потребителя чрез менюто и се запомянт дори и тока да спре. Не съм сигурен дали програмно мога да правя нещо друго освен да ги прочитам, не знам дали мога да ги ползвам в изчисленията, така че просто копирам стойността им в integralTime и derivativeTime и вече работя с тях.
5. В началото всички елементи на масива са 0. При всяко изпълнение на Main-а аз обхождам масива, като в 99-та клетка слагам стойността на 98-а, в 98-а стойността на 97-а и т.н. т.е. "придвижвам" масива с една клетка надясно. Накрая поставям новата стйност в клетка нула. Малко бавно става, но процесите които управлявам са още по-бавни. Един цикъл на цялата програма - не само контролера, а целия проект - се изпълнва за около 0.15 секунди, което е много-много по-бързо отколкото ми трябва.
6. В масива се съхраняват отклоненията на входа от зададената стойност. Трябват ми за да мога да анализирам "накъде вървят нещата", т.е. колко бързо се променя входа или колко дълго време съм бил под или над исканаат стойност.
Изхода е само един - Output. Тъй като тоя контролер работи изключително тежко с числа с плаваща запетая, работя с цели числа и чак на края ги деля на 10. По-голяма точност не ми трябва - специално тои контролер го ползвам за управление на температури на вода, така че стойностите са от 0 до 100 градуса макс. Изхода на контролера е от 0 до 1000, т.е. имам разделителна способност от 0.1 градуса, което е повече от достатъчно.
Като цяло съм доста спънат от самия контролер и другите блокове в него, които идват от производителя и трябва да спазвам "добрия тон" като се пъхам между тях. Почти всички заводски блокове работят със стойности от 0 до 1000 и е безмислено да работя с отрицателни числа или с числа по-големи от 1000 - ианче ще трябва да ги конветирам в нещо разбираемо за другите блокчета. Дори входовете и изходите ми са мащабирани за стойности от 0 до 1000, което при вх/изх 0-10 волта е 0.01 волт - повече от достатъчно като точност. Помпата с 3 000 об/мин я упрявлявам с точност 3 об/мин, което е безполезно точно, една моторна задвижка с вход 0-10 волта и обхват 0-90 градуса да искам от нея точност 0.1 ъглови градуса положение също е несериозно.
EDM electronics:
Сега разбрах какво правиш, но не всичко. Записваш през определено време изменението на входа САМО в нулевия елемент на масива, като с цикъла преди това местиш данните от предходния елемент в един назад. Така придвижваш записа в нулевия елемент по целия масив с всяко изпълнение на цикъла. Обаче:
Не мога да разбера този запис, мисля, че имаш някаква грешка
for (i = 99; i > 0; i--)
{
errors = errors[i - 1]
}
Мисля, че не можеш така да представиш масива само с името му errors, без скобите и броя на елементи му или пък само един елемент записан вътре в него. Или вместо броя поне променлива, примерно i.
Така както си го записал няма да ти мести стойността, да не би записа да е правилно така:
errors [ i ] = errors[i - 1]
Тук виждам същото при четенето на масива:
for (i = 0; i < integralTime; i++)
{
integral = integral + errors * Ki;
}
Не трябва ли да е така:
integral = integral + errors [ i ] * Ki;
juliang:
Проблема не е в "моя телевизор"... във форума е :)
Като искаш да напишеш нещо наклонено (италик) във форума ползваш следните тагове:
--- Код: ---[i] italic text [/i]
--- Край на кода ---
и ... кат съм пейстнал кода, форума е изял това в скобите ...
EDM electronics:
--- Цитат на: juliang в Април 17, 2020, 10:41:26 pm ---
struct ClassicPID
{
// public
BOOL Enable;
INT Setpoint;
INT Input;
INT Kp;
INT Kd;
INT Ki;
INT Td;
INT Ti;
INT Output;
// private
INT errors[100];
DINT integral;
INT integralTime;
INT derivative;
INT derivativeTime;
INT i;
DINT DIntOutput;
void Init()
{
DIntOutput = 0;
integralTime = 0;
derivativeTime = 0;
}
void Main()
{
for (i = 99; i > 0; i--)
{
errors = errors[i - 1]
}
errors[0] = Setpoint - Input;
integralTime = Ti;
integral = 0;
for (i = 0; i < integralTime; i++)
{
integral = integral + errors * Ki;
}
integral = integral / integralTime;
derivativeTime = Td;
derivative = (errors[0] - errors[derivativeTime]) * Kd / derivativeTime;
DIntOutput = DIntOutput + errors[0] * Kp + integral + derivative * 10; // * 10 може да се махне ако ПИД-а е много нервен
if (DIntOutput < 0)
{
Output = 0;
DIntOutput = 0;
}
else if (DIntOutput > 10000)
{
Output = 1000;
DIntOutput = 10000;
}
else
{
Output = DIntOutput / 10;
}
}
};
--- Край на цитат ---
Юлиане, да те похваля,
обясненията ти за разбиране работата на ПИД в по-преден пост са отлични и другото отличаващо се в твоя код е използването на масив за запис на повече от една предходна стойност, което дава по добра оценка на процеса в по-дълъг период от време. В повечето ПИД-функции които съм гледал се взема само предходното значение за сравнение и ако има само пик, а не някаква тенденция, то тоя регулатор няма да работи добре при всички условия. Има обаче нещо липсващо в кода ти - това е времето /честотата на дискретизация/. Дал си integralTime, но това е само някаква променлива - число, не е време. После, като четеш интегралната съставна е неразбираемо, защо integralTime го пъхаш в цикъла за четене. Според мен трябва да четеш броя на масива - да поставиш 99.
for (i = 0; i < integralTime; i++)
{
integral = integral + errors * Ki;
}
Според мен трябва грешката /разликата между зададената и измерената стойност errors[0] = Setpoint - Input/ да се извършва с някакъв период в зависимост от инертността на системата. В твоя код не виждам таймер и така, както си го написал означава, че ще се прави запис при всяка итерация на основния уайл?
Аз бих ползвал софтуерен таймер, който няма блокираща функция. При мен системата е изключително инертна. За да отчета някакво изменение на позицията от дадена GPS-точка, измервам разстоянието от тази запомнена точка до новата, на която ще ме отнесе течението. В случая ще регулирам оборотите на двигателя, който ще ме връща в запаметената точка в зависимост от вятъра и течението. Колкото по-малко течение, толкова по-ниски обороти и обратно. Затова трябват поне 10 сек за всяко следващо измерване, иначе няма смисъл, защото за по-малко време стойностите ще са много близки по значение. Примерно 10 записа са ми достатъчни през 10 сек или за 100 сек ще имам пълна картина на условията - има ли постоянно течение или само пикове-пориви и колко силно е то.
Какво ще кажеш за времето на дискретизацията?
Къде в твоя код определяш времето за всяко измерване на сетпойнта?
juliang:
Контролера на Данфос има една функция, която ми показва за колко време се върти един Main цикъл. В моя случай е някъде около 150 милисекунди, или около 7 пъти в секундата. Затова и се боричкам в края на цикъла да деля на 100 или на 1 000, щото и при мен системите в повечето случаи са инертни.
Най-лесниото решение е в началото на Main-а да имаш един брояч, който да изхвърля логиката в 9 от 10 преминавания, или там колкото ти трябват за да се постигне желаната инертност.
И имай предвид че това което съм постнал е само библиотечката за ПИД-а. програмата върши още една камара неща, поне при мен - рисува по екрана, чете сензори и бутони... става доста досадно когато трябва да задържиш бутона за спиране в продължение на една-две секунди за да може loop-а да мине през проверката за натиснат бутон...
Да, интегралното време е бройка преминавания през дадената библиотека, а не време като секунди. Същото е и с деривативното време - и то е брой цикли, а не секунди. Но когато знаеш за какво време програмата прави един loop, можеш да си сметнеш секундите... не че ти трябват точно секунди.
Защо не чета 99 стойности назад? Ами... ако пробваш алгоритъма ще разбереш. Понякога 90 от тия 99 записа сочат, че си бил под исканата стойност, а само последните 10 ти указват, че вече си я надхвърлил. А ти си я надхвърлил отдавна, и с много... а интегралната тегли още и още нагоре. В тоя случай интегралната компонента прави системата ужасно нестабилна. Особено болезнен е цирка когато системата стартира и разликата между реалната и зададената стойност е много голяма.
Но пък имам и случаи, в който трябва с помпа да поддържам налягане в една тръбна система, пълна с вода. Система без разширителен съд. И тогава интегралната компонента на практика ми е абсолютно ненужна, щото промяната в налягането на системата става с добавянето буквално на 2 шепи вода :) Чист пропорционален алгоритъм, без никакви предвиждания и истории на процеса.
На практика интегралната компонента е много рядко приложима в инертни системи, които не изискват голяма точност. Там голяма роля играе деривативната - на теб ти е много важно да знаеш накъде се е запътил процеса, а не къде е бил, за да можеш да вземеш превантивни мерки преди да си се олял в корекцията.
Навигация
[0] Списък на темите
Премини на пълна версия