PID- регулятор оборотов двигателя.




После публикации видео о регуляторе оборотов с обратной связью на ардуино, пришло много откликов.  Всем кто мне писал, давал советы, идеи,  и рекомендации я очень благодарен. С вашей помощью у меня получилось намного улучшить результат, достигнутый ранее, установив ПИД-регулятор.
Но возникли другие вопросы. Это порядок подбора нужных пид-коэффициентов.  И здесь мне снова нужна ваша поддержка. Методом перебора были выведены некие приемлемые  результаты, но от идеала они ещё очень далеки.


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




А вот и сама программа. Если нужно будет прокомментировать какую-нибудь строчку, спрашивайте.



#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 записываем новое значение
  }

}



16 комментариев:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
    Ответы
    1. Блог эту строку не принимает. В файле всё есть.

      Удалить
  2. Не могу прошить. Пишет Ошибка компиляции(прошивка ПИД). Остальные прошивки работают без проблем.

    ОтветитьУдалить
  3. Здравствуйте Александр. Большое спасибо вам за проделанную работу .
    у меня появился вопрос .
    в чем же отличие PID-регулятора от предыдущего регулятора? (немного запутался)
    в обоих вариантах датчик хола используются ?
    я так понимаю это отделный датчик? или на двигателе установлен датчик хола , а не тахогенератор?)
    буду очень благодарен за пояснение !

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. Дождитесь моего видео, на этой неделе будет. Там я всё поясняю.

      Удалить
  4. 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

    ОтветитьУдалить
    Ответы
    1. Спасибо, но уже доработал свой алгоритм. Обороты держит отлично.

      Удалить
  5. Какую именное библиотеку пид вы используете?
    объясните как работает этот код?: tic = ((uint32_t)int_tic<<16)|TCNT1
    Спасибо.

    ОтветитьУдалить
    Ответы
    1. Честно говоря, ПИД не использую. Доработал свой алгоритм. Намного проще и понятней.

      Удалить
  6. Доброго дня. Я сам не гуд програміст але бачу що minim = obMin * (kImp/10); краще порахує якщо перше помножити: minim = (obMin * kImp)/10; а потім ділити, так як "minim" у вас не float і все що після коми інт захаває. з інтами краще по можливості всі підрахунки множити а потім ділити. Попробуйте так точніші дані виходять

    ОтветитьУдалить
  7. Здравствуйте.
    Александр под заказ возьметесь сделать небольшую программку пи-регулятор ?
    вход - энкодер (сигналы А В) , задатчик - резистор, выход - передача значения в УАРТ (для моего МК который управляет тиристором) т.е. по сути мне нужен ПИ-регулятор для двигателя.

    ОтветитьУдалить
    Ответы
    1. Пока силёнок маловато. поищите желающего в сообществах ардуинщиков.

      Удалить
  8. Александр, крайне Вам признателен за то, что Вы этим делом занимаетесь. Реально полезной информации относительно управления коллекторным двигателем от СМА при помощи Ардуино без особой дополнительной обвязки информации фактически ноль. Посмотрел, как это сделали Вы, но решил не повторять, а сделать по-своему. Понравилась Ваша идея использовать таймер для работы таходатчика. Собственно, разбирая Ваш код, я и разобрался, как конфигурировать таймеры Ардуино.
    Думаю, Вы знаете, что у Atmega328 таймеров целых 3. Системный не трогаем. Конфигурируем таймер 1 на захват сигнала с 8-ого пина, создаем прерывание по переполнению (мотор не крутится), по внешнему сигналу (сигнал с таходатчика). Потом конфигурируем таймер 2 (он без захвата сигнала), там пишем прерывание по совпадению с A (в этот момент открываем симистр) и по совпадению с B (закрываем симистор), настраиваем внешнее прерывание INT0 на 2-м пине (будем ловить ноль). Это внешнее прерывание обнуляет таймер 2 (и запрещает само себя - мера борьбы с помехами). Таймер тикает до совпадения с A, прерывание A включает симистр и запрещает само себя. Затем наступает прерывание по совпадению с B (через 10 микросекунд примерно), которое выключает симистр и снимает запрет на прерывание INT0 и прерывания по совпадению. В регистр A записываем время задержки после нуля, в B - на 10 микросекунд больше. Если умело раскидать расчеты необходимого времени задержки и обработки данных таходатчика по обработчикам перечисленных прерываний, то в итоге можно получить абсолютно пустой цикл loop, в котором делать все, что душе угодно, хоть написать 10 delay по 30 минут каждый. Управление двигателем будет выполняться. Для отключения или пуска двигателя достаточно запретить прерывания по совпадению у 2-ого таймера.
    Надеюсь, эта информация Вам пригодится.
    С уважением,
    Денис

    ОтветитьУдалить