После публикации видео о регуляторе оборотов с
обратной связью на ардуино, пришло много откликов. Всем кто мне писал, давал советы, идеи, и рекомендации я очень благодарен. С вашей
помощью у меня получилось намного улучшить результат, достигнутый ранее,
установив ПИД-регулятор.
Но возникли другие вопросы.
Это порядок подбора нужных пид-коэффициентов. И здесь мне снова нужна ваша поддержка.
Методом перебора были выведены некие приемлемые
результаты, но от идеала они ещё очень далеки.
Какие изменения произошли в
самой схеме. Изменён порядок подключения экрана. Это сделано для того, чтоб в
дальнейшем нам было удобно спаять нашу ардуинку с экраном.
А вот и сама программа. Если нужно будет прокомментировать какую-нибудь строчку, спрашивайте.
Скачать можно здесь: https://drive.google.com/drive/folders/0B6x0JsiBUva0TTlESGdfTm11NWc?usp=sharing
#include
//
библиотека экрана
LiquidCrystal lcd( 9,10,4, 5,
6, 7); // пины экрана
#include
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,0.01,0.2,0.00005,
DIRECT);
int AC_LOAD = 3; // пин управления симистором
volatile int dimming =
130; // время задержки от нуля 7 = максимально, 128 = минимально
volatile unsigned long time;
// время в микросекундах срабатывания датчика нуля
unsigned long tims; // переменная показаний времени
unsigned long
currentTime; //временные переменные
для таймера экрана
unsigned long loopTime;
int obMax = 6000; //ввести максимальные обороты
int obMin = 200; //ввести минимальные обороты
float kImp = 120; //ввести кол-во импульсов на 10
оборотов
int ogrmin = 70 ;
// ограничение симистора на минимальных оборотах.
int minzn = 115; //
минимальное значение симмистора на котором начинается вращение.
unsigned long makim;
unsigned long minim;
volatile int holl = 0; //переменная срабатывания датчика
volatile long prOb = 0; //предвар реальн обороты
volatile long rOb = 0; // реальные обороты
volatile int sp = 0; //переменная суммы срабатываний
датчика
volatile unsigned int
int_tic; //переменные для подсчёта
времени между импульсами.
volatile unsigned long tic;
volatile int t = 0;
//минимальное время импульсов +1
void setup()
{
myPID.SetMode(AUTOMATIC);
makim = obMax * (kImp/10);
minim = obMin * (kImp/10);
t = (15000/minim)+3;
//высчитываем минимальное время импульсов +1
pinMode(AC_LOAD, OUTPUT); // назначаем выходом
attachInterrupt(0, zero_crosss_int, RISING); // прерывание по пину 2
lcd.begin(16, 2);
//дисплей 16символов 2строчки
lcd.setCursor(0,0);
lcd.write("R:"); //в верхней строке выводим время задержки
lcd.setCursor(0,1);
lcd.write("t:"); // В нижней выводим показания датчика
lcd.setCursor(7,0);
lcd.write("S:"); //в верхней строке будем выводить требуемые
обороты
lcd.setCursor(7,1);
lcd.write("S:"); // В нижней выводим фактические обороты
pinMode (8,INPUT); // вход
сигнала ICP( №8 only для atmega328)
//настройка 16 бит таймера-счётчика
1
TCCR1B = 0; TCCR1A = 0; TCNT1
= 0;
TIMSK1 =
(1<
TCCR1B = (1<
}
ISR (TIMER1_CAPT_vect) {
//прерывание захвата сигнала на входе ICP1
tic= ((uint32_t)int_tic<<16 span="">подсчёт тиков
ICR1=0; int_tic=0; TCNT1=0;
sp = sp +1 ; // для подсчёта оборотов в минуту.
holl =holl+ 1;} // после каждого срабатывания датчика холл+1
ISR (TIMER1_OVF_vect) {
//прерывание для счёта по переполнению uint
int_tic++; //считать
переполнения через 65536 тактов
if (int_tic > t) {tic=0;
int_tic=0;} //если на входе пусто более минимального времени то обнулить
счётчики
if (int_tic > 500)
{dimming =130;} // если стоим 2
секунды, то сбрасываем напряжение.
}
// the interrupt function must take no parameters and return nothing
void zero_crosss_int() //
function to be fired at the zero crossing to dim the light
{
time = micros();
}
void loop() {
int val = analogRead(A0);
Setpoint =map(val, 0, 1023, minim, makim); //Приводим показания
регулятора к минимальным и максимальным оборотам
int ogr =map(val, 0, 1023, ogrmin, 7);
prOb = 60000000/(tic * 0.0625 ); //Высчитываем частоту в минуту по показаниям
датчика
if( prOb > 0){ //проверяем на соответствие.
rOb = prOb ; //если нормально, записываем в
переменную
}
else {}
if (val > 0){ // если регулятор больше 0
if ( holl>=1){ // если сработал датчик
Output = constrain ( Output , 0, 250);
Input = rOb;
myPID.Compute();
dimming = map(Output, 0, 250, minzn, ogr);
holl=0;
}
else {}
if
(tic ==0){ // если двигатель
не вращается
dimming = 80 ; // время задержки = 100 подбираем опытным путём
}
else {}
}
else {
dimming =130; //Если регулятор на 0 то время задержки
130
if (tic ==0) rOb = 0;
}
dimming = constrain(dimming,7,130) ;
// Следим чтоб время задержки было не
меньше 7 и не больше 130
int dimtime = (75*dimming); // For 60Hz =>65
tims = micros();
// считываем время,
прошедшее с момента запуска программы
if(tims >= (time + dimtime)){ //если время больше или равно времени
срабатывания нуля + время задержки
digitalWrite(AC_LOAD, HIGH); // открываем симистор
delayMicroseconds(10); // задержка 10 микросекунд (для 60Hz = 8.33)
digitalWrite(AC_LOAD, LOW); // выключаем сигнал на симистор.
}
else {}
// Для вывода значений на дисплей 2 раз в
секунду
currentTime = millis(); // считываем время, прошедшее с момента
запуска программы
if(currentTime >= (loopTime + 500)){ // сравниваем текущий таймер с
переменной loopTime + 0,5 секунд
// выводим показания датчика
lcd.setCursor(2,0);
lcd.print(dimming);
lcd.print(" ");
// выводим время задержки на экран.
lcd.setCursor(2,1);
lcd.print(val);
lcd.print(" ");
lcd.setCursor(9,0);
lcd.print(map(val, 0, 1023,
obMin, obMax));
lcd.print("
"); // выводим нужные
обороты на экран.
lcd.setCursor(9,1);
lcd.print (int
(sp*(1200/kImp))); // выводим средние
обороты на экран.
lcd.print(" ");
sp = 0;
loopTime = currentTime; // в loopTime
записываем новое значение
}
}
Этот комментарий был удален автором.
ОтветитьУдалитьБлог эту строку не принимает. В файле всё есть.
УдалитьНе могу прошить. Пишет Ошибка компиляции(прошивка ПИД). Остальные прошивки работают без проблем.
ОтветитьУдалитьВозможно не установлена библиотека ПИД
УдалитьУстановил. Может не та.
УдалитьРазобрался. Заработало.
ОтветитьУдалитьЗдравствуйте Александр. Большое спасибо вам за проделанную работу .
ОтветитьУдалитьу меня появился вопрос .
в чем же отличие PID-регулятора от предыдущего регулятора? (немного запутался)
в обоих вариантах датчик хола используются ?
я так понимаю это отделный датчик? или на двигателе установлен датчик хола , а не тахогенератор?)
буду очень благодарен за пояснение !
Здравствуйте. Дождитесь моего видео, на этой неделе будет. Там я всё поясняю.
УдалитьPID регулятор - это так называемый пропорционально-интегрально-дифференциальный регулятор. Он вырабатывает управляющий сигнал на основе измерения сигнала ошибки (в данном случае это величина отклонения оборотов двигателя от заданных, то есть разность между заданными оборотами двигателя и фактически измеренными оборотами). ПИД регулятор принимает во внимание пропорциональную составляющую (абсолютное значение ошибки), дифференциальную составляющую (скорость изменения ошибки) и интегральную составляющую (историческое усредненное значение ошибки). Коэффициенты определяют вклад всех этих компонент в упрабвляющий сигнал и параметры дифференцирования и интегрирования сигнала ошибки. Настройка ПИД регулятора дело мутное и требует понимания алгоритма. Можно ознакомиться с основами настройки ПИД регуляторов вот здесь: http://roboforum.ru/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4_%D1%81%D1%82%D0%B0%D1%82%D1%8C%D0%B8_%22%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%BE_%D0%BE_%D0%9F%D0%98%D0%94-%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0%D1%85%22
ОтветитьУдалитьСпасибо, но уже доработал свой алгоритм. Обороты держит отлично.
УдалитьКакую именное библиотеку пид вы используете?
ОтветитьУдалитьобъясните как работает этот код?: tic = ((uint32_t)int_tic<<16)|TCNT1
Спасибо.
Честно говоря, ПИД не использую. Доработал свой алгоритм. Намного проще и понятней.
УдалитьДоброго дня. Я сам не гуд програміст але бачу що minim = obMin * (kImp/10); краще порахує якщо перше помножити: minim = (obMin * kImp)/10; а потім ділити, так як "minim" у вас не float і все що після коми інт захаває. з інтами краще по можливості всі підрахунки множити а потім ділити. Попробуйте так точніші дані виходять
ОтветитьУдалитьЗдравствуйте.
ОтветитьУдалитьАлександр под заказ возьметесь сделать небольшую программку пи-регулятор ?
вход - энкодер (сигналы А В) , задатчик - резистор, выход - передача значения в УАРТ (для моего МК который управляет тиристором) т.е. по сути мне нужен ПИ-регулятор для двигателя.
Пока силёнок маловато. поищите желающего в сообществах ардуинщиков.
УдалитьАлександр, крайне Вам признателен за то, что Вы этим делом занимаетесь. Реально полезной информации относительно управления коллекторным двигателем от СМА при помощи Ардуино без особой дополнительной обвязки информации фактически ноль. Посмотрел, как это сделали Вы, но решил не повторять, а сделать по-своему. Понравилась Ваша идея использовать таймер для работы таходатчика. Собственно, разбирая Ваш код, я и разобрался, как конфигурировать таймеры Ардуино.
ОтветитьУдалитьДумаю, Вы знаете, что у Atmega328 таймеров целых 3. Системный не трогаем. Конфигурируем таймер 1 на захват сигнала с 8-ого пина, создаем прерывание по переполнению (мотор не крутится), по внешнему сигналу (сигнал с таходатчика). Потом конфигурируем таймер 2 (он без захвата сигнала), там пишем прерывание по совпадению с A (в этот момент открываем симистр) и по совпадению с B (закрываем симистор), настраиваем внешнее прерывание INT0 на 2-м пине (будем ловить ноль). Это внешнее прерывание обнуляет таймер 2 (и запрещает само себя - мера борьбы с помехами). Таймер тикает до совпадения с A, прерывание A включает симистр и запрещает само себя. Затем наступает прерывание по совпадению с B (через 10 микросекунд примерно), которое выключает симистр и снимает запрет на прерывание INT0 и прерывания по совпадению. В регистр A записываем время задержки после нуля, в B - на 10 микросекунд больше. Если умело раскидать расчеты необходимого времени задержки и обработки данных таходатчика по обработчикам перечисленных прерываний, то в итоге можно получить абсолютно пустой цикл loop, в котором делать все, что душе угодно, хоть написать 10 delay по 30 минут каждый. Управление двигателем будет выполняться. Для отключения или пуска двигателя достаточно запретить прерывания по совпадению у 2-ого таймера.
Надеюсь, эта информация Вам пригодится.
С уважением,
Денис