Помощь с ускорением ПИД-двигателя постоянного тока

Я работаю над проектом, который использует Arduino UNO и моторизованный фейдер для отправки и приема MIDI - данных с моего компьютера/музыкальной клавиатуры. Я пытаюсь настроить свой двигатель с помощью PID, чтобы контролировать положение и скорость двигателя - у меня были некоторые трудности, но после просмотра нескольких учебных пособий я думаю, что начинаю понимать эту идею.

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

#include <SoftwareSerial.h>
#define rxPin 2
#define txPin 1 

SoftwareSerial midiSerial (rxPin, txPin);

const byte wiper = 0; //Положение фейдера относительно GND (аналог 0)

const byte motorUp   = 4;
const byte motorDown = 5;
const byte motorPWM  = 6;

double faderMax = 0;
double faderMin = 0;

byte motorSpeed = 150;  // Raise if the fader is too slow (0-255)
byte tolerance  = 10;  // Поднять, если фейдер слишком шаткий (0-1023)

int incomingCommand;
int incomingNote;
int incomingVelocity;
    
int currentPosition;
unsigned targetPosition;
unsigned long distanceToTarget;
unsigned PIDspeed;

void setup() {
  midiSerial.begin(31250); 
  Serial.begin(250000);
  analogWrite(motorPWM, motorSpeed);
  calibrateFader();
}

void calibrateFader() {
  digitalWrite(motorUp, HIGH);
  analogWrite(motorPWM, motorSpeed);
  delay(300);
  digitalWrite(motorUp, LOW);
  faderMax = analogRead(wiper) - tolerance;
  digitalWrite(motorDown, HIGH);
  analogWrite(motorPWM, motorSpeed);
  delay(300);
  digitalWrite(motorDown, LOW);
  faderMin = analogRead(wiper) + tolerance;
}

void loop() {

  while ( midiSerial.available() > 0) {
    incomingCommand = midiSerial.read();
    incomingNote = midiSerial.read();
    incomingVelocity = midiSerial.read();
    midiSerial.write( incomingCommand);
    midiSerial.write( incomingNote);
    midiSerial.write( incomingVelocity);
    targetPosition = incomingVelocity*8.05511811024; 
    currentPosition = analogRead(A0);
    distanceToTarget = targetPosition-currentPosition;
  }
  PIDspeed = abs(distanceToTarget)/6;
     
  if (distanceToTarget >=0) {
    analogWrite(motorPWM, PIDspeed); 
    digitalWrite(motorUp, HIGH);
    digitalWrite(motorDown, LOW);
  }
  
  if (distanceToTarget <=0) {
    analogWrite(motorPWM, PIDspeed); 
    digitalWrite(motorDown, HIGH);
    digitalWrite(motorUp, LOW);
  }
}

Обновленный код:

#include <SoftwareSerial.h>
#define rxPin 2
#define txPin 1

SoftwareSerial midiSerial(rxPin, txPin);

const byte wiper = 0; //Положение фейдера относительно GND (аналог 0)

const byte motorUp = 4;
const byte motorDown = 5;
const byte motorPWM = 6;

double faderMax = 1023;
double faderMin = 0;

byte motorSpeed = 150;  // Raise if the fader is too slow (0-255)
byte tolerance = 10;  // Поднять, если фейдер слишком шаткий (0-1023)
byte Proportional = 12;

unsigned int incomingCommand;
unsigned int incomingNote;
unsigned int incomingVelocity;

unsigned int PIDspeed;
int currentPosition;
int targetPosition;
int distanceToTarget;

void setup()
{
    midiSerial.begin(31250);
    Serial.begin(250000);
    calibrateFader();
}

void calibrateFader()
{
    digitalWrite(motorUp, HIGH);
    analogWrite(motorPWM, motorSpeed);
    delay(300);
    digitalWrite(motorUp, LOW);
    faderMax = analogRead(wiper) - tolerance;
    digitalWrite(motorDown, HIGH);
    analogWrite(motorPWM, motorSpeed);
    delay(300);
    digitalWrite(motorDown, LOW);
    faderMin = analogRead(wiper) + tolerance;
}

void loop()
{
    while (midiSerial.available() >= 3)
    {
        incomingCommand = midiSerial.read();
        incomingNote = midiSerial.read();
        incomingVelocity = midiSerial.read();
        if (targetPosition <= 1023)
        {
            midiSerial.write(incomingCommand);
            midiSerial.write(incomingNote);
            midiSerial.write(incomingVelocity);
        }
        targetPosition = incomingVelocity * 8.05511811024;
    }
    currentPosition = analogRead(A0);
    distanceToTarget = (targetPosition - currentPosition);
    PIDspeed = (abs(distanceToTarget) / Proportional) + 90;
    analogWrite(motorPWM, PIDspeed);
    if (targetPosition <= 1023)
    {
        if (distanceToTarget > 60)
        {
            digitalWrite(motorUp, HIGH);
        }
        if (distanceToTarget < 60)
        {
            digitalWrite(motorDown, HIGH);
        }
        if ((distanceToTarget >= -60) && (distanceToTarget <= 60))
        {
            digitalWrite(motorDown, LOW);
            digitalWrite(motorUp, LOW);
        }
    }
}

В этой версии двигатель на самом деле менее отзывчив, чем раньше. Я думаю, что это должно быть связано с данными, поступающими от моей DAW, которые не всегда являются частью данных команды, заметки или скорости. Например, если я выберу другую дорожку в своей DAW, она отправит данные в мой Arduino. Когда я читаю что-либо большее, чем 0 байт, данные все еще поступают, но показания заметок, команд и скоростей расположены более спорадически. Другой предложил мне попробовать использовать MIDI-парсер, но это уже другая тема, я полагаю.

, 👍2

Обсуждение

чего вы ожидали? ... что же произошло вместо этого?, @jsotola

Я надеялся добавить пропорциональный регулятор скорости, который позволил бы двигателю ускоряться и замедляться до определенной "целевой позиции", которая подается в мой Arduino через MIDI-кабель. Случилось так, что мой мотор начал вести себя очень вяло при получении каких-либо данных. Чтобы заставить двигатель вообще двигаться, мне пришлось бы постоянно вводить в него данные, @zRockafellow

я бы начал с самого начала ... напишите **минимальный** код, необходимый для поворота двигателя в одном направлении на медленной скорости ... затем отредактируйте до минимального кода, чтобы повернуть медленно в течение 5 секунд и изменить скорость на среднюю и запустить в течение 5 секунд, повторите ... из этого вы узнаете, что требуется, чтобы мотор был счастлив, @jsotola

Вы не делаете никаких вычислений PID. Это действительно то, чего ты хочешь? Или вы хотите симметрично ускорять и замедлять свой двигатель?, @chrisl

Все, что я слышал от предыдущих пользователей этих моторизованных фейдеров, клянусь, что для того, чтобы иметь плавное движение, вы должны использовать PID, а не PWM. Я думал, что "Пропорциональное" значение будет ускорять мой двигатель пропорционально медленнее, когда он доберется до целевого положения? Разве это не так?, @zRockafellow

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

IMO это больше похоже на проблему интерполяции (например, библиотека Ramp, о которой я упоминал на днях): перейдите от " x " к " y " после этой кривой ускорения/etc., @Dave Newton


2 ответа


1

Я вижу 2 основные проблемы с вашим кодом, которые заставили бы его вести себя странно:

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

      currentPosition = analogRead(A0);
      distanceToTarget = targetPosition-currentPosition;
    

    из цикла while(midiSerial.available() > 0). Просто поставьте потом сразу после него.

  • В начале вашего midi-кода вы проверяете, доступно ли из midiSerial более нуля байтов, но затем вы читаете всего 3 байта, не зная, есть ли на самом деле 3 байта для чтения. SoftwareSerial.read() не будет ждать поступления байта, он просто вернет -1. Так что вполне может быть, что при выполнении midi - кода уже получен только один байт. Остальное будет получено позже. Так что остальные переменные-это просто мусор/недопустимость. Кроме того, это означает, что отправитель и получатель теперь смещены. Остальные 2 байта (которые будут получены позже) будут обработаны midi-кодом в следующий раз, как если бы они были байтами 1 и 2 (а не 2 и 3). Таким образом, следующие переходы также будут мусорными/недействительными, пока они случайно не выровняются снова.

    Чтобы решить эту проблему, вы должны проверить, доступно ли по крайней мере 3 байта (так как вы хотите прочитать 3 байта):

      while ( midiSerial.available() >= 3 ) {
    

Примечание: Судя по вашим комментариям, вы, похоже, неправильно поняли, что такое PID и PWM на самом деле.

  • ШИМ-это просто способ управлять двигателем с переменной скоростью, очень быстро включая и выключая штифт с различным соотношением времени включения и выключения. При высоком отношении к времени включения двигатель будет вращаться быстрее, чем при низком отношении к времени включения. Это соотношение переводится в диапазон от 0 до 255 на Arduino. Таким образом, вам нужен ШИМ для управления двигателем с переменной скоростью на Arduino. Вы не можете избавиться от этого.

  • ПИД-это замкнутый контур управления с обратной связью. Он обозначает способ вычисления нового выходного значения (значения ШИМ-двигателя) на основе измеренного входного значения (измеренного текущего положения). PID расшифровывается как Пропорциональный интегральный дифференциал, то есть для разных членов в расчетах. С помощью ПИД сначала вычисляется отклонение между измеряемым входом и целевым/заданным значением, затем оценивается функция ПИД (с ее пропорциональной, интегральной и дифференциальной частью отклонения). При этом вы получаете выходное значение, которое можете подать на свое выходное устройство (двигатель здесь).

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

На самом деле с вашим текущим кодом (при исправлении, как описано выше) уже выполняется пропорциональная часть PID (так как вы вычисляете новую скорость по расстоянию до цели, то есть по отклонению от целевой позиции). Чтобы получить полный PID, вам также нужно будет реализовать все остальное. Я бы предложил использовать библиотеку Arduino PID, которая уже обрабатывает вычисления для вас. Вам просто нужно правильно вызвать его (обратитесь к примерам библиотеки).

,

Большое спасибо за подробности! Я попробую это как можно скорее, @zRockafellow

Я определенно все еще пытаюсь понять концепцию PID. Я рад, что, по крайней мере, правильно понял Пропорциональное сечение (спасибо Майенко в предыдущем посте). Я опробую эту библиотеку и посмотрю, получится ли у меня что-нибудь получиться., @zRockafellow


0

Существует фундаментальная проблема с тем, как вы читаете последовательные данные. Прочитайте эту превосходную статью @Majenko: https://majenko.co.uk/blog/reading-serial-arduino

Следующий код будет считывать последние 3 байта. Если, например, доступно 9 байтов, он проигнорирует первые 6 байтов:

while (midiSerial.available() >= 3)
{
    incomingCommand = midiSerial.read();
    incomingNote = midiSerial.read();
    incomingVelocity = midiSerial.read();
. . .
}

Это будет читать следующие 3 байта:

if (midiSerial.available() >= 3)
{
    incomingCommand = midiSerial.read();
    incomingNote = midiSerial.read();
    incomingVelocity = midiSerial.read();
. . .
}

Вот настроенный цикл (), который:

  • использует if (midiSerial.available() >= 3) для чтения следующих 3 байтов.
  • обрезает целевую позицию, если она выходит за пределы (от 0 до 1023).
  • обрезает PIDspeed, если он находится вне диапазона (от 0 до 255).
  • выключает другой сигнал управления двигателем перед включением текущего на случай, если targetPosition пересечет зоны между итерациями цикла ().
const int QUIESCENT_BOUNDARY = 60;
const float INCOMING_VELOCITY_SCALE = 1023.0 / 255.0;

byte incomingCommand;
byte incomingNote;
byte incomingVelocity;

void loop()
{
    if (midiSerial.available() >= 3)                // Используйте `if`, чтобы получить следующие 3 доступных байта.
    {
        incomingCommand = midiSerial.read();
        incomingNote = midiSerial.read();
        incomingVelocity = midiSerial.read();
        midiSerial.write(incomingCommand);
        midiSerial.write(incomingNote);
        midiSerial.write(incomingVelocity);
        targetPosition = incomingVelocity * INCOMING_VELOCITY_SCALE;
        if (targetPosition > 1023)                  // Clip if out of bounds.
        {
            targetPosition = 1023;
        }
    }
    currentPosition = analogRead(A0);
    distanceToTarget = targetPosition - currentPosition;
    PIDspeed = abs(distanceToTarget) / 6;           // TODO: Tune the PID equation.
    if (PIDspeed > 255)                             // Clip if out of range.
    {
        PIDspeed = 255;
    }
    analogWrite(motorPWM, PIDspeed);
    if (distanceToTarget > QUIESCENT_BOUNDARY)      // Выше положительной границы зоны покоя.
    {
        digitalWrite(motorDown, LOW);               // Выключите регулятор down перед включением регулятора up.
        digitalWrite(motorUp, HIGH);
    }
    else if (distanceToTarget < -QUIESCENT_BOUNDARY)// Ниже отрицательной границы зоны покоя.
    {
        digitalWrite(motorUp, LOW);                 // Выключите регулятор up перед включением регулятора down.
        digitalWrite(motorDown, HIGH);
    }
    else                                            // В зоне покоя.
    {
        digitalWrite(motorDown, LOW);
        digitalWrite(motorUp, LOW);
    }
}

Это должно помочь вам настроить уравнение ПИД.

,