Плавный старт электродвигателя

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Всем привет. Ребят, хотелось бы получить помощь в реализации плавного старта электродвигателя при пид регулировании. Чтобы после нажатия кнопки пуск, двигатель плавно набрал минимальные или установленные обороты, например в течение секунды-двух, а после разгона далее можно было вручную регулировать обороты. Не могу сообразить, как данный алгоритм реализовать в программе.

#define obMin 500         //  минимальные обороты двигателя, меньше не але
#define obMax 6000      //  максимальные обороты двигателя
#define kImp 8          //  количество импульсов на 1 оборот таходатчикa
#define PWMpin 3        //  пин выхода ШИМ D3
int16_t poten;         	//  переменная регулятора скорости (потенциометра)
int16_t rpmzad;     //  показания регулятора скорости (потенциометра)
int16_t rpm;        //  переменная оборотов
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
volatile uint16_t int_tic;
volatile uint32_t tic;
bool flag = false;

void setup() {
  lcd.init(); // Turn on the blacklight and print a message.
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0); lcd.print("ob/min:");

  pinMode(2, INPUT_PULLUP);
  pinMode(3, OUTPUT);	// настройка D3 на выход
  pinMode(8, INPUT);  // вход сигнала ICP( D8 only для atmega328)

  //настройка 16 бит таймера-счётчика 1
  TCCR1B = 0;
  TCCR1A = 0;
  TCNT1 = 0;
  TIMSK1 = (1 << ICIE1) | (1 << TOIE1); //создавать прерывание от сигнала на пине ICP1
  TCCR1B = (1 << ICNC1) | (1 << ICES1) | (1 << CS10); //div 1
}

ISR (TIMER1_CAPT_vect) { //прерывание захвата сигнала на входе ICP1
  TCNT1 = 0;
  if (TIFR1 & (1 << TOV1)) {
    TIFR1 |= 1 << TOV1;
    if (ICR1 < 100) {
      int_tic++;
    }
  }
  tic = ((uint32_t)int_tic << 16) | ICR1 ; //подсчёт тиков
  int_tic = 0;
}

ISR (TIMER1_OVF_vect) { //прерывание для счёта по переполнению uint
  int_tic++; //считать переполнения через 65536 тактов
  if (int_tic > 244) //если на входе пусто более секунды
  {
    tic = 0;  //то обнулить счётчики
    int_tic = 0;
  }
}

void loop() {
  lcd_print_screen();
  rpm = (((float)F_CPU / tic) * 60) / kImp;
  /* tic-количество тактов процессора, совершенных за 1 период входного сигнала.
    если tic*(1/F_CPU) то будет время периода в секундах.
    если F_CPU/tic - будет частота в герцах. Как пример: Hz = (float)F_CPU / tic;
  */

  poten = analogRead(A0);                         //  потенциометр на пине А0
  rpmzad = map(poten, 0, 1023, obMin, obMax);     //  Приводим показания регулятора к минимальным и максимальным оборотам

  bool btnState = !digitalRead(3);
  if (btnState && !flag) {  // обработчик нажатия
    flag = true;
    //Serial.println("press");
  }
  if (!btnState && flag) {  // обработчик отпускания
    flag = false;
    //Serial.println("release");
  }
  if (!btnState && flag == true) {
    // (вход, установка, п, и, д, период в секундах, мин.выход, макс. выход)
    analogWrite(3, computePID(rpm, rpmzad, 0.03, 0.01, 0.03, 0.03, 0, 255));
  }
}

// функция пид
int computePID(float input, float setpoint, float kp, float ki, float kd, float dt, int minOut, int maxOut) {
  float err = setpoint - input;
  static float integral = 0, prevErr = 0;
  integral = constrain(integral + (float)err * dt * ki, minOut, maxOut);
  float D = (err - prevErr) / dt;
  prevErr = err;
  return constrain(err * kp + integral + D * kd, minOut, maxOut);
}

void lcd_print_screen() { // вывод переменных на lcd_2004
  static uint32_t lcdTimer;
  if (millis() - lcdTimer > 350) {
    lcdTimer = millis();
    lcd.setCursor(7, 0);
    lcd.print(rpm);
    lcd.print("   ");
  }
}

 

rkit
Offline
Зарегистрирован: 23.11.2016

Смог сделать пид-регулятор, но не можешь плавный старт? Смешная шутка.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

пид не мой. я просто его пристроил, скажем так. не могу понять как с пид-ом плавно стартануть с места.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

О каком двигателе речь? Можно подробно? Они настолько разные бывают. Коллекторный постоянного тока? Переменка "от розетки"? Трёхфазный? Или какой?

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

От стиральной машины. Питаю выпрямленным напряжением с конденсатором

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Так, давайте толком. В стиральной машине несколько двигателей. Основной - обычно на переменное напряжение, зачем там что-то выпрямлять? Скажите толком что за двигатель?

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Однофазный коллекторный

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Поговаривают, что на постоянке они работают получше, поэтому выпрямил. Регулирую igbt транзистором

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Тогда Вам лучше взять специализированную микросхему для управления таким двигателем (их в природе и в гугле 100500+, например ILA1185AN или U211B2/B3 или ... легион им имя). 

Это Вам даст и плавный пуск, и стабилизацию количества оборотов под нагрузкой и без, и защиту от перегрузок двигателя. 

Подумайте.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Дело уже сделано. Работа устраивает. Хотелось бы добавить плавный пуск для удобства. 

Kakmyc
Offline
Зарегистрирован: 15.01.2018

При включении сначала с нуля(или что то около того), поднимаешь до значения задания, а потом уже включаешь пид

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Как я понимаю решение данной задачи. Нужна функция, которая по нажатию кнопки, будет поднимать в течении двух секунд обороты до установленных, а только затем ручное управление с пид регулированием. И будет ли нужно при завершении набора оборотов сравнение оборотов текущих и заданных или достаточно чтобы функция просто отработала две секунды?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Vlad1m1r пишет:

Дело уже сделано. Работа устраивает. Хотелось бы добавить плавный пуск для удобства. 

Если "устраивает" - зачем задаете вопросы на форуме?

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

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Не отрицаю, что Евгений Петрович рекомендует дело. В моем случае на контроллер возложены дополнительные задачи, кроме управления двигателем. Поэтому попросил помощи на форуме. Не прошу написать код, просто предложить алгоритм решения конкретной ситуации по коду

b707
Offline
Зарегистрирован: 26.05.2017

Vlad1m1r пишет:
Не прошу написать код, просто предложить алгоритм решения конкретной ситуации по коду

вы же его сами сформулировали в #11

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Vlad1m1r пишет:
Не отрицаю, что Евгений Петрович рекомендует дело. В моем случае на контроллер возложены дополнительные задачи, кроме управления двигателем. Поэтому попросил помощи на форуме. Не прошу написать код, просто предложить алгоритм решения конкретной ситуации по коду

Указанная Вами задача решается аппаратными средствами, а не программными.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Аппаратно она решается только применением специальных драйверов?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Нет. В первом приближении достаточно обычного генератора тока.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Как то так:

  const uint32_t accelTime=5000;       // *** время разгона
  
  {
  //...
  bool btnState = !digitalRead(3);
  static uint32_t accelTimer=0;        // *** таймер разгона
  if (btnState && !flag) {  // обработчик нажатия
    flag = true;
    accelTimer=millis();               // *** при нажатии засекаем время старта разгона
    //Serial.println("press");
  }
  if (!btnState && flag) {  // обработчик отпускания
    flag = false;
    //Serial.println("release");
  }
  if (!btnState && flag == true) {
    uint32_t delta=millis()-accelTimer; // ***
    if(delta<accelTime){                // *** пока время разгона
       rpmzad=delta*rpmzad/accelTime;   // *** изменяем скорость исходя из прошедшего времени и установки резистора 
    }
    // (вход, установка, п, и, д, период в секундах, мин.выход, макс. выход)
    analogWrite(3, computePID(rpm, rpmzad, 0.03, 0.01, 0.03, 0.03, 0, 255));
  }
}

Набросал, но не проверял. Мог и накосячить.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Спасибо за помощь. Проверить получится только завтра. Что-то похожее я встречал здесь на форуме в посте по проектированию медогонки

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Я не понял что встречал.... Если меня , то я тут везде гажу потихоньку....

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Я про алгоритм разгона)

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Ну и почему же гадить. Очень ценная помощь

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Да какой там алгоритм. Математика. Просто ее нужно воткнуть в правильное место.

==тут была фигня написана==

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Тем не менее, спасибо за потраченное время

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

brokly, ваш алгоритм заработал с ходу. В процессе возникла другая проблема. Повторным нажатием кнопки у меня не получается остановить двигатель. Пробовал различные условия и флаги. самое лучшее, что из этого вышло - это однократный цикл по одиночному нажатию кнопки start-work-stop. полагаю, что такая задача решается каким-то другим способом или я во флагах делаю какую-то логическую ошибку?

#define obMin 500         //  минимальные обороты двигателя, меньше не але
#define obMax 6000      //  максимальные обороты двигателя
#define kImp 8          //  количество импульсов на 1 оборот таходатчикa
const uint32_t accelTime = 5000;     // *** время разгона
#define PWMpin 3        //  пин выхода ШИМ D3
int16_t poten;           //  переменная регулятора скорости (потенциометра)
int16_t rpmzad;     //  показания регулятора скорости (потенциометра)
int16_t rpm;        //  переменная оборотов
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
volatile uint16_t int_tic;
volatile uint32_t tic;
bool flag = false;
bool stop_it = false;

void setup() {
  Serial.begin(9600);
  lcd.init(); // Turn on the blacklight and print a message.
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0); lcd.print("ob/min:");

  pinMode(2, INPUT_PULLUP);
  pinMode(3, OUTPUT); // настройка D3 на выход
  pinMode(8, INPUT);  // вход сигнала ICP( D8 only для atmega328)

  //настройка 16 бит таймера-счётчика 1
  TCCR1B = 0;
  TCCR1A = 0;
  TCNT1 = 0;
  TIMSK1 = (1 << ICIE1) | (1 << TOIE1); //создавать прерывание от сигнала на пине ICP1
  TCCR1B = (1 << ICNC1) | (1 << ICES1) | (1 << CS10); //div 1
}

ISR (TIMER1_CAPT_vect) { //прерывание захвата сигнала на входе ICP1
  TCNT1 = 0;
  if (TIFR1 & (1 << TOV1)) {
    TIFR1 |= 1 << TOV1;
    if (ICR1 < 100) {
      int_tic++;
    }
  }
  tic = ((uint32_t)int_tic << 16) | ICR1 ; //подсчёт тиков
  int_tic = 0;
}

ISR (TIMER1_OVF_vect) { //прерывание для счёта по переполнению uint
  int_tic++; //считать переполнения через 65536 тактов
  if (int_tic > 244) //если на входе пусто более секунды
  {
    tic = 0;  //то обнулить счётчики
    int_tic = 0;
  }
}

void loop() {
  lcd_print_screen();
  rpm = (((float)F_CPU / tic) * 60) / kImp;
  /* tic-количество тактов процессора, совершенных за 1 период входного сигнала.
    если tic*(1/F_CPU) то будет время периода в секундах.
    если F_CPU/tic - будет частота в герцах. Как пример: Hz = (float)F_CPU / tic;
  */

  poten = analogRead(A0);                         //  потенциометр на пине А0
  rpmzad = map(poten, 0, 1023, obMin, obMax);     //  Приводим показания регулятора к минимальным и максимальным оборотам

  bool btnState = !digitalRead(2);
  static uint32_t accelTimer = 0;      // *** таймер разгона
  if (btnState && !flag) {  // обработчик нажатия
    flag = true;
    accelTimer = millis();             // *** при нажатии засекаем время старта разгона
    Serial.println("start");
  }
  if (!btnState && flag == true) {
    uint32_t delta = millis() - accelTimer; // ***
    if (delta < accelTime) {            // *** пока время разгона
      rpmzad = delta * rpmzad / accelTime; // *** изменяем скорость исходя из прошедшего времени и установки резистора
    }
    // (вход, установка, п, и, д, период в секундах, мин.выход, макс. выход)
    analogWrite(3, computePID(rpm, rpmzad, 0.03, 0.01, 0.03, 0.03, 0, 255));
    Serial.println("work");
  }
  if (btnState && flag == true) stop_it = true;
  if (!btnState && flag == true && stop_it == true) {
    flag = false;
    stop_it = false;
    Serial.println("stop");
  }
}

// функция пид
int computePID(float input, float setpoint, float kp, float ki, float kd, float dt, int minOut, int maxOut) {
  float err = setpoint - input;
  static float integral = 0, prevErr = 0;
  integral = constrain(integral + (float)err * dt * ki, minOut, maxOut);
  float D = (err - prevErr) / dt;
  prevErr = err;
  return constrain(err * kp + integral + D * kd, minOut, maxOut);
}

void lcd_print_screen() { // вывод переменных на lcd_2004
  static uint32_t lcdTimer;
  if (millis() - lcdTimer > 350) {
    lcdTimer = millis();
    lcd.setCursor(7, 0);
    lcd.print(rpm);
    lcd.print("   ");
  }
}

 

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

А раньше то это работало ?

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Раньше это не использовал. Только сейчас применил

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Если не применять алгоритмы остановки, то все работает хорошо. То есть по нажатию плавно разгоняется, потом регулируется.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

А вот остановить повторным нажатием кнопки никак не выходит

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Ну так попробуйте старый скетч, без разгона. Остановится у вас мотор или нет ? Вообще я вижу, что мотор крутится пока кнопка нажата. Отпустил - мотор встает. Точнее встает PID, а мотор возможно продолжает крутиться. Не хочется глубоко погружаться в логику вашего скетча.

Я так понимаю, что к 8 ноге у вас подключен датчик оборотов ?

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Всё также. Start-work-stop однократно

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Vlad1m1r пишет:
Всё также. Start-work-stop однократно

Я не понял этой фразы. Можно более развернуто?

При остановленном моторе на выходе шим сигнал какого уровня должен быть ? (Фраза Йода стаил получилась:))

 

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

В чужом коде разбираться это муторно и долго. я предпологал логику такую:

1. первый раз нажал - отпустил - работаем.

2. второй раз нажал - остановились.

и так по кругу

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

по второму нажатию шим к нулю опустить

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

по скетчу я по сути пытаюсь скинуть флаг, чтобы сломать условие работы пид, но никак не получается

b707
Offline
Зарегистрирован: 26.05.2017

Vlad1m1r пишет:

по скетчу я по сути пытаюсь скинуть флаг, чтобы сломать условие работы пид, но никак не получается

флаг-то вы сбрасываете - но мотор не останавливаете, вместо этого просто перестаете регулировать обороты. Но если ШИМ не трогать, он остается на уровне "полный газ" и мотор будет крутится, пока батарейки хватит

Варианта два

 - простой - по команде стоп резко убрать ШИМ до нуля

- сложный - встроить плавную остановку по типу плавного пуска Брокли, но в обратную сторону

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

мотор по второму нажатию останавливается, но в процессе не регулируется. сразу раскручивается до скольки может

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Так , объясните мне  вот код:

  bool btnState = !digitalRead(3);
  if (btnState && !flag) {  // обработчик нажатия
    flag = true;
    //Serial.println("press");
  }
  if (!btnState && flag) {  // обработчик отпускания
    flag = false;
    //Serial.println("release");
  }
  if (!btnState && flag == true) { // НИЖЕ РАБОТАЕТ ПИД, ТУДА ПОПАДАЕМ ЕСЛИ КНОПКА НАЖАТА и fjag==true, по сути ТОЛЬКО ЕСЛИ НАЖАЛИ КНОПКУ И ПОДЕРЖАЛИ ЕЕ ДВА ЦИКЛА LOOP
    // (вход, установка, п, и, д, период в секундах, мин.выход, макс. выход)
    analogWrite(3, computePID(rpm, rpmzad, 0.03, 0.01, 0.03, 0.03, 0, 255));
  } else {
    // сюда воткнуть обслуживание остановки мотора   
  }

 

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

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

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

кнопку нажали - установили флаг

кнопку отпустили и если нажимали до этого - опускаем флаг

если кнопка не нажата и флаг установлен - регулируем пид

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

только регулирование не будет выполняться, мне кажется

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

извиняюсь, в первом посте выложил не ту версию кода

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014
  bool btnState = !digitalRead(3);
  static bool runState = false;
  if (btnState && !flag) {  // обработчик нажатия
    flag = true;
    //Serial.println("press");
  }
  if (!btnState && flag) {  // обработчик отпускания
    flag = false;
    //Serial.println("release");
  }
  if (!btnState && flag == true){
     runState=!runState;    
  }
  if (runState) {
     //... 
  } else {
     analogWrite(3,0); 
  }

Вот чего проще, а ?

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

код с которым сейчас работаю в сообщении #25. Он задуман как по первому нажатию выйти плавно на заданные обороты, а по повторному нажатию двигатель остановить

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Vlad1m1r пишет:

извиняюсь, в первом посте выложил не ту версию кода

Вапче, тут за это, даже в песочнице шлепают.....

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Vlad1m1r пишет:

код с которым сейчас работаю в сообщении #25. Он задуман как по первому нажатию выйти плавно на заданные обороты, а по повторному нажатию двигатель остановить

Мой последний пример с кодом из #1 и с плавным стартом сами скрестите ?

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

я не преднамеренно, честное слово.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

скрещу конечно

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

brokly пишет:

При остановленном моторе на выходе шим сигнал какого уровня должен быть ? (Фраза Йода стаил получилась:))

Поскольку на этот вопрос не ответили, то делаю как хочу. Но бяка может быть дымная.

Vlad1m1r
Vlad1m1r аватар
Offline
Зарегистрирован: 08.06.2019

Vlad1m1r пишет:

по второму нажатию шим к нулю опустить

повыше писал, может не так поняли.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Где ?