цифровая электроника
вычислительная техника
встраиваемые системы

 

Магнитная левитация с помощью Arduino своими руками

Автор: Mike(admin) от 27-10-2017, 15:55

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


Магнитная левитация с помощью Arduino своими руками

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


Принцип работы самодельного левитрона довольно прост. Используя датчик Холла U3503, Arduino Uno непрерывно измеряет магнитное поле, создаваемое постоянным магнитом (или несколькими неодимовыми магнитами, как в данном случае). Затем он вычисляет ошибку между показанием и уставкой и корректирует число в уравнении ПИД-регулятора, который затем корректирует выход ШИМ. ШИМ управляет включением полевого транзистора (MOSFET).


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


Магнитная левитация с помощью Arduino своими руками

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


Магнитная левитация с помощью Arduino своими руками

Схема подключения приведена ниже. Полевой транзистор управляется выводом 10 платы Arduino, который генерирует сигнал ШИМ. Выход датчика Холла подключается к аналоговому входу A0.


Схема устройства магнитной левитации на основе Arduino

Ниже приведен код программы реализации принципа магнитной левитации с помощью Arduino.



//#define QUIETMODE // Quietmode замедляет время, необходимое для обновления полного рабочего цикла PWM

#define MIN_PWM_VALUE         0          // Минимальная скважность ШИМ
#define MAX_PWM_VALUE         255        // Максимальная скважность ШИМ
#define IDLE_TIMEOUT_PERIOD   3000       // Миллисекунды. Должно быть меньше максимального значения gIdleTime
#define MIN_MAG_LIMIT         400        // Триггерная точка для режима ожидания / активного режима
#define PID_UPDATE_INTERVAL   1          // Интервал обновления ШИМ, в миллисекундах. 0 = как можно быстрее
#define DEFAULT_TARGET_VALUE  300       // Показания датчика эффекта Холла по умолчанию
#define DEFAULT_KP            0.7        // Kp по умолчанию, пропорциональный коэффициент усиления
#ifndef QUIETMODE
  #define DEFAULT_KD          1.7
  // Kd по умолчанию, дифференциальный коэффициент усиления
  #else //not QUIETMODE
  #define DEFAULT_KD            23.7
#endif //not QUIETMODE

#define DEFAULT_KI            0.0002     // Ki по умолчанию, интегральный коэффициент усиления
#define DEFAULT_MAX_INTEGRAL  5000      // Максимальная интегральная составляющая
#define KP_INCREMENT          0.1        // Инкремент, используемый для последовательных команд (gKp)
#define KD_INCREMENT          0.1        // Инкремент, используемый для последовательных команд (gKd)
#define KI_INCREMENT          0.0001     // Инкремент, используемый для последовательных команд (gKi)
#define VALUE_INCREMENT       1          // Инкремент, используемый для последовательных команд (gTargetValue)

#define FILTERFACTOR 3                   // Весовой коэффициент для считывания показаний датчика Холла
int roundValue(float value)
{
  return (int)(value + 0.5);
}

const int coilPin = 10; // Таймер 1B на Uno (ATmega328), таймер 2A на Mega (ATmega2560)
const int hallSensorPin = 0;
const int redLedPin = 12;
const int blueLedPin = 13; // светодиод на плате Arduino
const int gMidpoint = roundValue((MAX_PWM_VALUE - MIN_PWM_VALUE) / 2); // Средняя точка диапазона ШИМ

boolean gIdle = false; // Используется для отслеживания, находимся ли мы в режиме ожидания
signed int gIdleTime = 0; // Удерживается в следующий раз, когда мы можем перейти в режим ожидания
signed int gNextPIDCycle = 0; /// Удерживается в следующий раз, когда нам нужно пересчитать выходы ПИД-регулятора

int gCurrentDutyCycle = 0; // Текущая скважность ШИМ для катушки
int gLastSensorReadout = 0; // Последнее считывание датчика для расчета производной
int gNextSensorReadout = 0; // «Следующее» значение датчика

int gTargetValue = DEFAULT_TARGET_VALUE;
float gKp = DEFAULT_KP;
float gKd = DEFAULT_KD;
float gKi = DEFAULT_KI;
int gIntegralError = 0;  // Вычисляет текущую ошибку во времени

void writeCoilPWM(int value)
{
    OCR1B = value;  
}


void setup()
{  
    // Сначала мы настраиваем синий светодиод, чтобы мы могли использовать его для сигнализации статуса загрузки
    pinMode(blueLedPin, OUTPUT);
    digitalWrite(blueLedPin, HIGH);
    
    // Настройка таймера 1 как неинвертированный ШИМ с коррекцией фазы, 31372.55 Гц
    pinMode(9, OUTPUT);
    pinMode(10, OUTPUT);
    TCCR1A = 0;
    TCCR1A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
    TCCR1B = 0;
    TCCR1B = _BV(CS20);
    
    pinMode(redLedPin, OUTPUT);
    pinMode(hallSensorPin, INPUT);
    
    Serial.begin(9600);
    
    // Загрузка завершена, активация синего светодиода
    digitalWrite(blueLedPin, LOW);
}

// Используется в режиме ожидания
void idleLoop()
{
    digitalWrite(redLedPin, HIGH);
    
    // Отключить магнит
    if(0 != gCurrentDutyCycle)
    {
      gCurrentDutyCycle = 0;
      writeCoilPWM(gCurrentDutyCycle);
    }
    
    int sensorReadout = analogRead(hallSensorPin);
    
    // Переход в активный режим, если в диапазоне обнаружения есть магнит
    if(MIN_MAG_LIMIT > sensorReadout)
    {
      gIdle = false;
      gNextSensorReadout = sensorReadout; // Коррекция показаний датчика таким образом, чтобы они не были слишком устаревшими (более высокие коэффициенты фильтрации приведут к увеличению времени стабилизации)
      gLastSensorReadout = sensorReadout; // Сокращение производного коэффициента до 0
      digitalWrite(redLedPin, LOW);
      
      // Используется для обработки переполнения в выводе millis () при понижении. Если он переполняется в режиме ожидания, он будет придерживаться фильтра, ожидая, пока millis вернется к последней расчетной точке      
gNextPIDCycle = millis();
      gIdleTime = gNextPIDCycle + IDLE_TIMEOUT_PERIOD;
    }
}

// Используется в активном режиме
void controlLoop()
{
  // Понижение millis к типу хранимого аргумента цикла, затем вычислить разницу в качестве знакового числа. Если это отрицательно, время еще не прошло. Это позволяет нам выполнять арифметику с переполнением
  if(0 <= ((typeof(gNextPIDCycle))millis() - gNextPIDCycle))
  {
       gNextPIDCycle = millis() + PID_UPDATE_INTERVAL;
    
    // Считать датчик хотя бы один раз в цикле обновления
    gNextSensorReadout = roundValue(((gNextSensorReadout * (FILTERFACTOR - 1)) + analogRead(hallSensorPin)) / FILTERFACTOR);

    
    if(MIN_MAG_LIMIT <= gNextSensorReadout) // Прямо сейчас мы не видим постоянного магнита
    {
      if(0 <= ((typeof(gIdleTime))millis() - gIdleTime)) // Мы не видели постоянный магнит в период IDLE_TIMEOUT_PERIOD. Переполнение безопасно по той же причине, что и приведенный выше расчет для gNextPWMCycle     
 {
        gIdle = true;
        return; // Ранний выход в режим ожидания
      }
    }
    else // Во время этого обновления имеется постоянный магнит
    {
           gIdleTime = millis() + IDLE_TIMEOUT_PERIOD; 
    }
    
    int error = gTargetValue - gNextSensorReadout; // Разница между текущими и ожидаемыми значениями
 
    // Наклон характеристики входного сигнала во времени (для производной составляющей)
    int dError = gNextSensorReadout - gLastSensorReadout; 
    
    gIntegralError = constrain(gIntegralError + error, -DEFAULT_MAX_INTEGRAL, DEFAULT_MAX_INTEGRAL); // Грубая постоянная ошибка во времени (для интегральной составляющей)
    

#ifdef QUIETMODE
    // Это замедляет изменение поля электромагнита, что делает устройство значительно более тихим, но требуется намного больше времени для стабилизации (порядка 30 секунд) 
    int gNextDutyCycle = gMidpoint - roundValue((gKp*error) - (gKd*dError) + (gKi*gIntegralError));
    gCurrentDutyCycle = roundValue(((gCurrentDutyCycle * 2) + gNextDutyCycle) / 3);
#else // не QUIETMODE
    gCurrentDutyCycle = gMidpoint - roundValue((gKp*error) - (gKd*dError) + (gKi*gIntegralError));
#endif // не QUIETMODE
    // возможно превышение, поэтому ограничиваем между нашими максимальными и минимальными значениями
    gCurrentDutyCycle = constrain(gCurrentDutyCycle, MIN_PWM_VALUE, MAX_PWM_VALUE);
    
    writeCoilPWM(gCurrentDutyCycle);
    
    // Хранить для следующего расчета ошибки dError
    gLastSensorReadout = gNextSensorReadout;
  }
  else // Мы ждем нашего следующего цикла обновления ПИД-регулятора, просто считаем датчик Холла для нашей процедуры фильтрации и возврата  {
    // Это средневзвешенная функция. Она принимает выборки FILTERFACTOR, заменяет их текущим значением датчика холла и усредняет по числу входных данных
    // Чем выше FILTERFACTOR, тем медленнее отклик (и менее важные данные ошибки)
    gNextSensorReadout = roundValue(((gNextSensorReadout * (FILTERFACTOR - 1)) + analogRead(hallSensorPin)) / FILTERFACTOR);
  }
}

void serialCommand(char command)
{
  char output[255];
  
  switch(command)
  {
    case 'P':
      gKp += KP_INCREMENT;
      break;
    case 'p':
      gKp -= KP_INCREMENT;
      if(0 > gKp) gKp = 0;
      break;
      
    case 'D':
      gKd += KD_INCREMENT;
      break;
    case 'd':
      gKd -= KD_INCREMENT;
      if(0 > gKd) gKd = 0;
      break;
    
    case 'I':
      gKi += KI_INCREMENT;
      break;
    case 'i':
      gKi -= KI_INCREMENT;
      if(0 > gKi) gKi = 0;
      break;
      
    case 'T':
      gTargetValue += VALUE_INCREMENT;
      break;
    case 't':
      gTargetValue -= VALUE_INCREMENT;
      if(0 > gTargetValue) gTargetValue = 0;
      break;
    
    // выводим текущие настройки
    case 'V':
    case 'v':
      break;
    
    // Игнорировать нераспознанные символы
    default:
      return;
  }
  
  // Почему так сложно? Arduino не включает поддержку %f по умолчанию и требует дополнительной библиотеки, поэтому мы пишем ее вручную
  // В этой строке используется 3026 байт ПЗУ, почти половина размера скетча. Поэтому, если у вас закончилось свободное место, отключите это (или упростите)
  sprintf(output, "Target Value: [%3d] Current PWM duty cycle [%3d] Current sensor value [%4d] Kp [%2d.%02d] Kd [%2d.%02d] Ki,Integral Error [.%04d,%d] Idle timeout [%d]\n",
    gTargetValue, 
    gCurrentDutyCycle, 
    gNextSensorReadout, 
    (int)(gKp+0.0001),
    roundValue(gKp*100)%100, 
    (int)(gKd+0.0001), 
    roundValue(gKd*100)%100, 
    roundValue(gKi*10000)%10000, 
    gIntegralError, 
    gIdleTime);
   
  Serial.print(output);
}

void loop()
{
    //User commands waiting
    if(0 < Serial.available())
    {
      //Process one character at a time
      serialCommand(Serial.read());
    }
    
    if(gIdle)
      idleLoop();  
    else
      controlLoop();      
}



Теги: Arduino, магнитная левитация




Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Комментарии:

Оставить комментарий