IB металошукач на ARDUINO DUE (проект, 20 кГц)

Конструкції та прграмування ARDUINO DUE
Відповісти
Аватар користувача
radioman
Site Admin
Повідомлень: 133
З нами з: 28 вересня 2020, 12:01
Звідки: Тернопіль
Дякував (ла): 8 разів
Подякували: 6 разів

IB металошукач на ARDUINO DUE (проект, 20 кГц)

Повідомлення radioman »

В мережі є не один проект металошукача на ARDUINO UNO.
Мікроконтролер ARDUINO DUE має набагато більші можливості; як мінімум - 12-розрядний АЦП, котрий може працювати у повному диференційному режимі.
Передаюча частина зроблена по схемі Фішер CZ-5; катушка використана від Тесоро Cibola з переробленим кабелем; дане рішення має безліч недоліків (низький струм і, відповідно, поле; значний рівень 3 та 5 гармонік), тому в подальшому планую використати стандартну катушку від Фішер F75 з розкачкою за його схемою (в моделі - ідеально).
Прийомна частина теж зроблена по спрощеній схемі Фішер CZ-5 з додатковим фільтром НЧ на вході (бажано ще встановити "фільтр-пробку" на 3 гармоніку як у F75):
Аналогова частина (Rx), 20кГц
Аналогова частина (Rx), 20кГц
Вихід аналогової частини дає змогу підключити її до АЦП в диференційному режимі, що при встановленні підсилення у 1 дасть нам додатковий біт інформації (?). Дане схемотехнічне рішення не є ідеальним, так як вихідний сигнал не є повністю протифазним (є кращі варіанти; найбільш оптимальний, але і найдорожчий - використання спеціалізованих ОУ). Для корекції фази використовується багатооборотний резистор, хоча більш оптимальним є програмний поворот вектора, так як це дає змогу уникнути впливу шуму від переключення ключа Тх на результат вимірювання.
Так як спочатку вимірювання проводились не в диференційному режимі, було прийнято рішення проводити заміри не 2 рази за період, а 4 з відніманням результатів; реалізувавши таким чином псевдодиференційний вхід та нівелювавши дрейф нуля.
Відповідно, частота переривань у два рази вища (40076 Гц для частоти 20038 Гц):

Код: Виділити все

TC0->TC_CHANNEL[0].TC_RC = 524;                           // Frequency = (Mck/2)/TC_RC/2 = 20038Hz
В обробнику переривань проходить формування меандру на 2 виході ARDUINO DUE, вимірювання квадратурного сигналу 4 рази за період, медіанна фільтрація вхідних даних:

Код: Виділити все

void ADC_Handler(void)
{
  intResultDif = ADC->ADC_CDR[6];                    // отримання даних з АЦП
  sensorValues[intSs][intCs] = intResultDif;      // внесення даних в двомірний масив

  //Формування меандру
  if ( interruptCtr++ >= 1 ) // вимірювання кожні 90 градусів - 2 такти за період
  {
    interruptCtr = 0;
    REG_PIOB_ODSR ^= 1 << 25; //переключити рівень 2 PWM виходу
  }
  if (intCs == 2) bolTact = true;             // завершення періоду
  if (intCs++ >= 3)
  {
    intCs = 0;
    if (intSs++ >= 2)
    {
      intSs = 0;
    }
  }

  // Дані для фільтру пікового шуму (медіанний фільтр на 3 значення)
  ....
  
Для того, що "віалізувати" синхронізацію, внесемо в скетч деякі правки:
- назначимо 13 контакт (порт В27) виходом

Код: Виділити все

PIOB->PIO_OER = 1 << 27;                            // Встановлюємо 2 контакт Arduino DUE як вихід (тільки для тесту синхронізації)

- в обробнику переривань після вимірювання тимчасово додамо команду на зміну рівня на 13 контакті

Код: Виділити все

void ADC_Handler(void)
{
  intResultDif = ADC->ADC_CDR[6];                    // отримання даних з АЦП
  REG_PIOB_ODSR ^= 1 << 27;                          // переключити рівень 13 виходу (тільки для тесту синхронізації)
  sensorValues[intSs][intCs] = intResultDif;         // внесення даних в двомірний масив
Підключивши двоканальний осциллограф до одного з виходів аналогової частини та 13 контакту ARDUINO DUE, отримаємо таку картинку:
Синхронізація Tx та Rx
Синхронізація Tx та Rx
Зверніть увагу на "лохматість" синусоїди та завади від переключення ключів Тх (біля тестової платки працює ноутбук, цифрова приставка-осциллограф); всі ці шуми потім відобразяться у вигляді похибки вимірювання (можна спробувати використати їх для оверсемплінгу).
А ось завади від переключення ключів можуть бути "захоплені" АЦП; тому, більш доцільним, на мою думку, буде зсунути фазу для вимірювання на 45 градусів і "поставити її на місце" помноживши отриманий результат на матрицю повороту. Вимірювання "Х" відбувається у точках "0" та "2", "У" - "1" та "3", котрі відповідають індексам у масиві.
Отримані результати поміщаються у двомірний масив і обробляються медіанним фільтром на 3 значення (краще на 5 або 6, але я не зайшов багатопотокової реалізації такого, і сам написати не можу :( ) та 4 потоки. Відфільтровані значення поміщаються у одномірний масив і уже в "тілі" програми розраховується значення Х та У:

Код: Виділити все

lngSX = intMiddle[0] - intMiddle[2] + lngSX;
lngSY = intMiddle[3] - intMiddle[1] + lngSY;
if (intS++ >= 511)
та додатково фільтруються простим середнім, але без ділення ! На закінчення, розраховуємо кут за допомогою наближеної формули Джорджа Пейна (велика точність тут нам не потрібна, а швидкість має значення) та виводимо значення на монітор:

Код: Виділити все

Serial.println(90.00000 * lngSY / (lngSY + lngSX));

PS. В архіві скетч проекту та його лістинг в форматі doc.
Вкладення
test_DUE_201205.7z
Скетч проекту
(9.08 Кіб) Завантажено 284 разів
Аватар користувача
radioman
Site Admin
Повідомлень: 133
З нами з: 28 вересня 2020, 12:01
Звідки: Тернопіль
Дякував (ла): 8 разів
Подякували: 6 разів

Re: IB металошукач на ARDUINO DUE (проект, 20 кГц)

Повідомлення radioman »

Це була присказка, а тепер почнеться казка (с)
Запускаємо скетч, виставляємо "У" в нуль і підносимо ціль (використав невиликий кусок катанки), рухаючи її над котушкою вверх-вниз. І ось що ми отримаємо:
Зміна амплітуди в каналі &quot;У&quot; залежно від відстані
Зміна амплітуди в каналі "У" залежно від відстані
подібну картинку дасть нам і розрахунок VDI:
Зміна VDI залежно від відстані
Зміна VDI залежно від відстані
Чому так відбувається ?
Сигнал на приймальній катушці, в даному випадку, складається із суми сигналу "розбалансу" та сигналу від цілі (в реальних умовах додасться ще грунт); амплітуда розбалансу може значно перевищувати амплітуду сигналу від цілі і вносити значну похибку в розрахунок VDI (не забуваємо про грунт).
Для нівелювання цього впливу внесемо в скетч деякі зміни:
- зробимо "детектор цілі":

Код: Виділити все

 if (lngSY + lngSX / 512 >= 0)                         // якщо появилася ціль (чсло 512 потрібно підбирати, при збільшенні - збільшується чутливість
- визначимо "чисті" значення "Х" та "У" відгуку цілі:

Код: Виділити все

 lngSX = lngSX - lngSXib;                            // визначаємо "Х" без розбалансу
 lngSY = lngSY - lngSYib;                            // визначаємо "У" без розбалансу
- для того, щоб скетч працював, не забуваємо оголосити нові змінні:

Код: Виділити все

long lngSXib;                                              // сума даних по X "розбалансу"
long lngSYib;                                              // сума даних по Y "розбалансу"

// розрахунок VDI
int intVDI;                                                // розраховане VDI
int intVDIold;                                             // якщо VDI помилкове, використовуємо старе значення
в результаті отримаємо наступний код "тіла" програми:

Код: Виділити все

void loop()
{
  if (bolTact)                                              // якщо пройшов повний цикл (360 градусів)
  {
    bolTact = false;
    lngSX = intMiddle[0] - intMiddle[2] + lngSX;            // розрахунок Х та фільтрація за допомогою простого середнього на 512 значень (без ділення)
    lngSY = intMiddle[3] - intMiddle[1] + lngSY;            // розрахунок У та фільтрація за допомогою простого середнього на 512 значень (без ділення)

    if (intS++ >= 511)                                      // сумуємо 512 вимірювань
    {
      if (lngSY + lngSX / 512 >= 0)                         // якщо появилася ціль (чсло 512 потрібно підбирати, при збільшенні - збільшується чутливість
      {
        lngSX = lngSX - lngSXib;                            // визначаємо "Х" без розбалансу
        lngSY = lngSY - lngSYib;                            // визначаємо "У" без розбалансу
        intVDIold = intVDI;                                 // запам'ятаємо попереднє значення VDI

        if (lngSX > 0)                                      // якщо "Х" позитивний ("кольорова" ціль)
        {
          intVDI = 90 - 90 * lngSY / (lngSY + lngSX);       // розрахунок VDI за формулою Джорджа Пейна
          if (intVDI >= 90) intVDI = intVDIold;             // якщо VDI > 90 (помилка), використовуємо старе значення (можна також зробити "маску гарячих каменів")
        }
        else                                                // якщо "Х" негативний ("чорна" ціль)
        {
          intVDI = 90 * lngSY / (lngSY - lngSX) - 90;       // розрахунок VDI за формулою Джорджа Пейна
          if (intVDI <= -90) intVDI = intVDIold;            // якщо VDI < -90 (помилка), використовуємо старе значення
        }
        Serial.println(intVDI);                             // виводимо значення VDI на монітор
      }
      else                                                  // якщо ціль не виявлено
      {
        lngSXib = lngSX;                                    // запам'ятовуємо значення "Х"
        lngSYib = lngSY;                                    // запам'ятовуємо значення "У"
        Serial.println(0);                                  // на монітор виводимо "0" (або не виводимо нічого)
      }
      //Serial.println(lngSY / 2048.00000 * 3300 / 4096);     // виведення сигналу "У" на монітор, використовується для встановлення в "0" початкової фази

      // обнуляємо змінні
      intS = 0;
      lngSX = 0;
      lngSY = 0;
      intVDI = 0;
    }
  }
}
І що ми отримаємо в результаті ?
&quot;Чиста&quot; амплітуда &quot;У&quot; залежно від відстані
"Чиста" амплітуда "У" залежно від відстані
Як бачимо, "У" далі "танцює. Чому так? А він і так дорівнював нулю ;)
А ось "поведінка" VDI зовсім інша:
&quot;Чистий&quot; VDI залежно від відстані
"Чистий" VDI залежно від відстані
Як бачимо, викликані зміною амплітуди, коливання VDI вже зовсім незначні; тепер уже можна використовувати наш металодетектор для "повітряних" тестів. 8-)

PS. Практично, даний алгоритм використовується для векторного віднімання грунту. Для нівелювання сигналу розбалансу можна використати апаратний метод: подати на вхід сигнал в протифазі до сигналу розбалансу (так зроблено в "Квазарі"). Програмним методом цього можна не робити - одразу віднімати від комплексного сигналу суму розбаланс+грунт (але тоді не зможемо визначити фазу грунту).
PPS. В архіві модернізований скетч та його лістинг у форматі doc
test_DUE_201209.7z
Скетч та лістинг
(10.62 Кіб) Завантажено 322 разів
Аватар користувача
radioman
Site Admin
Повідомлень: 133
З нами з: 28 вересня 2020, 12:01
Звідки: Тернопіль
Дякував (ла): 8 разів
Подякували: 6 разів

Re: IB металошукач на ARDUINO DUE (проект, 20 кГц)

Повідомлення radioman »

В скетч з поворотом вектора додамо змінні для віднімання сигналу розбалансу:

Код: Виділити все

long lngSXold;                                              // сума даних по X без цілі (в майбутньому - баланс грунту)
long lngSYold;                                              // сума даних по Y без цілі (в майбутньому - баланс грунту)
int Z1;                                                     // наявність цілі
і в "тіло" програми додамо обчислення "чистих" (без значення розбалансу, а в майбутньому - грунту) значень "Х" та "У":

Код: Виділити все

void loop()
{
  if (bolTact)                                              // якщо пройшов повний цикл (360 градусів)
  {
    bolTact = false;                                        // позначаємо новий цикл
    lngSX = intMiddle[0] - intMiddle[2] + lngSX;            // розрахунок Х та фільтрація за допомогою простого середнього на 512 значень (без ділення)
    lngSY = intMiddle[3] - intMiddle[1] + lngSY;            // розрахунок У та фільтрація за допомогою простого середнього на 512 значень (без ділення)

    if (intS++ >= 511)                                      // якщо просумували 512 значень (початок - 0)
    {
      if (bolAir)                                           // якщо натиснута кнопка "балансування" (ще не реалізовано, виконується при старті)
      {
        fAir (lngSX, lngSY);                                // проводимо корекцію "Х" та "У"
      }
      
      lngSX = lngSX >> 2;                                       // зсув вправо на 2 розряди (ділимо на 4) для уникнення перевищення озміру змінної
      lngSY = lngSY >> 2;                                       // зсув вправо на 2 розряди (ділимо на 4) для уникнення перевищення озміру змінної
      lngSXrev = (lngSX * intCos + lngSY * intSin) / 16384;     // здійснюємо поворот вектора для отримання коригованого значення "Х"
      lngSYrev = (lngSY * intCos - lngSX * intSin) / 16384;     // здійснюємо поворот вектора для отримання коригованого значення "Х"
      
      Z1 = lngSYrev + lngSXrev / 128;                           // виявляємо наявність цілі векторним методом
      if (Z1 >= 0)                                              // якщо ціль виявлено
      {
        //lngSXrev = lngSXrev - lngSXold;                       // знаходимо значення "Х" без впливу розбалансу 
        //lngSYrev = lngSYrev - lngSYold;                       // знаходимо значення "У" без впливу розбалансу 
      }
      else                                                      // якщо ціль відсутня
      {
        lngSXold = lngSXrev;                                    // оновлюємо значення розбалансу по "Х"             
        lngSYold = lngSYrev;                                    // оновлюємо значення розбалансу по "У"
      }
      
      /*для контролю*/
      
      lngSXrev = lngSXrev - lngSXold;
      lngSYrev = lngSYrev - lngSYold;


      Serial.print("    lngSYrev = ");                          // виведення значення "У" на монітор в мілівольтах
      Serial.print(lngSYrev * 3300.00000 / 4096 / 511);
      Serial.print(" mv.    lngSXrev = ");                      // виведення значення "Х" на монітор в мілівольтах
      Serial.println(lngSXrev * 3300.00000 / 4096 / 511);

      // обнуляємо змінні
      intS = 0;
      lngSX = 0;
      lngSY = 0;
    }
  }
}
На моніторі спостерігаємо відхилення значень "Х" та "У" протифазно для "чорної" цілі (кусочок стальної катанки):
Протифазне відхилення &quot;Х&quot; та &quot;У&quot; (статика)
Протифазне відхилення "Х" та "У" (статика)
і синфазне відхилення значень "Х" та "У" для "кольорової цілі" цілі (50 копійок, Україна):
Синфазне відхилення &quot;Х&quot; та &quot;У&quot; (статика)
Синфазне відхилення "Х" та "У" (статика)

PS. В архіві скетч "настільного" IB металошукача на ARDUINO DUE та його лістинг у форматі doc
MD_DUE_210101.7z
Металошукач на ARDUINO DUE
(17.07 Кіб) Завантажено 336 разів
Відповісти