Детектор перешкод на Arduino

Простий приклад практичного використання ШІМ (PWM) та АЦП (ADC) мікроконтролера

Привіт! Сьогодні складатимемо простий прототип детектора перешкод. "Мозком" системи буде плата Arduino Nano, до АЦП входу якого буде підключено датчик CNY70. В якості індикатора я налаштую RGB-світлодіод через ШІМ так, щоб плавно змінювати колір світіння, в залежності від даних, прийнятих з датчика.

Для початку зберемо схему індикації. Для цього візьмемо RGB-світлодіод, такий світлодіод має в одному корпусі 3 діоди різних кольорів: червоний (R), зелений (G), синій (B). Вони мають 4 виводи - один з них загальний катод (самий довгий), інші - аноди кожного окремого діода.

RGB-детектор

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

Отже, встановлюю світлодіод на плату таким чином щоб катод був на "-" шині макетної плати, а аноди окремо один від одного. Одразу підключаю "-" шину до виводу GND Arduino-плати:

Від кожного аноду вивожу по резистору: від зеленого та синього 100 Ом, від червоного 220 Ом (крайній зі сторони плати):

У моєму випадку, напруга червоного діода менша за напруги інших, тому резистор з більшим опором, але пристрої можуть відрізнятись - дивіться документацію конкретного приладу.

Тепер нам потрібно всі три виводи діодів підключити до плати, але не до звичайних цифрових виводів, а до тих що підтримують ШІМ, щоб ми могли плавно змінювати навантаження на кожен колір. Нижче представлено скріншот розпіновки плати Arduino Nano з офіційного сайту документації:

Arduino Nano розпіновка

Нас цікавлять виводи D3, D5, D6. Позначка "~" перед назвою піна означає що ці виводи підтримують ШІМ, а отже підійдуть для наших цілей. Під'єднаємо аноди світлодіодів до них:

Наша схема індикації зібрана, і тепер напишемо код для тестування її роботи. Запускаємо Visual Studio Code, переходимо у вкладку PlatformIO і створюємо новий проєкт з такими налаштуваннями:

Вікно створення нового проєкту PlatformIO

Відкриємо файл main.cpp і замінимо код на такий:

#include <Arduino.h>

void setup() {
//встановлюємо відповідні піни в режим виводу

  pinMode(PD3, OUTPUT);
  pinMode(PD5, OUTPUT);
  pinMode(PD6, OUTPUT);

//встановлюємо значення потужності 50 для червоного (максимальне значення - 255)
  analogWrite(PD3, 50);
//очікуємо 2 секунди
  delay(2000);

 //встановлюємо значення потужності 50 для зеленого
  analogWrite(PD5, 50);
 //затримка 2 секунди
  delay(2000);

 //50 для синього
  analogWrite(PD6, 50);
 //2 секунди
  delay(2000);

}

void loop() {

}

Як видно, спочатку код в методі setup() встановлює режим виводу для пінів D3, D5, D6, потім почергово подає живлення на світлодіоди. Значення 50 обрано навмання, для нашого тесту можна використати будь яке значення до 255 включно. Головне щоб не нуль, бо колір не засвітиться.

Після компіляції коду (кнопка Build) і завантаження на плату маємо такий результат:

Чудово! Наша індикація працює, але в нас ще є робота. Тепер треба підключити датчик перешкод, який при наближенні до поверхні що відбиває світло передає інформацію про відстань до цього об'єкта на мікроконтролер. Для цього підійде CNY70.

CNY70 — це датчик, який складається з інфрачервоного випромінювача та фототранзистора в одному корпусі. Він використовується для виявлення перешкод, ліній, дисків тощо шляхом вимірювання відбитого світла, і має транзисторний вихід.

Його принцип роботи досить простий: випромінювач випускає невидиме інфрачервоне світло. Якщо на поверхні є об'єкт, світло відбивається, потрапляє на фототранзистор, і той пропускає струм. Якщо поверхня темна або відсутня, відбитого світла менше, і фототранзистор генерує слабший сигнал.

Розпіновка приладу і схема підключення представлена на малюнку нижче:

Розпіновка і схема підключення датчика CNY70

На корпусі датчику не вказані виводи, тому треба уважно продивитись де який вивід перед підключенням. Орієнтуйтесь по стороні з написом. Як видно зі схеми, інфрачервоний діод підключається як звичайний, через резистор 220 Омнапруга живлення - 5 Вольт. Світлотранзистор також живиться від 5В, через резистор 10 кОм, між його колектором і резистором вихід сигналу, який підключається до аналогового входу плати. На Arduino Nano аналогові входи називаються на "А": A0, A1, A2 і т.д. (див. розпіновку на малюнку плати вище).

Отже, нам потрібні 5 вольт для живлення датчику, виведемо їх на шину "+" макету від піну 5V плати мікроконтролера:

Тепер до пінів датчика приєднаємо дроти, щоб можна було підключити до схеми, при цьому залишити можливість рухати датчиком. Я підключив червоний і чорний дроти до пінів діода, відповідно червоний - до аноду, чорний - до катоду. Коричневий (емітер) і білий (колектор) з'єднані з виводами транзистора:

CNY70 з дротами

Підключаємо до плати: емітер і анод до "+" шини, а катод і колектор на окремі шинки для підключення резисторів:

Підключення CNY70 до макетної плати

Тепер, згідно схеми, через відповідні резистори під'єднаємо катод і колектор до шини "-" (зліва 220 Ом, справа - 10к):

Тепер потрібно під'єднати вихід фототранзистора з ADC входом плати мікроконтролера. Згідно схеми виводимо дріт між резистором 10к і білим проводом (колектор транзистора), і підводимо його до найближчого (або найзручнішого) аналоговому піну плати (в моєму випадку це A6):

На цьому збір схеми завершений, тож можна переходити до нашого коду. Я використаю лише два кольори діода-індикатора так, що коли датчик не бачить перешкоди горить зелений, і по мірі наближення до перешкоди зелений затухає, червоний починає світитись. Тож, замінимо код файла main.cpp на такий:

#include <Arduino.h>

#define RED_LED PD3
#define GREEN_LED PD5

#define DETECTOR A6


//об'являємо допоміжну функцію
int getLEDLightnessFrom(int sensorValue);

void setup() {
  //встановимо піни PD3 (червоний), PD5(зелений) в режим виводу
pinMode(RED_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);

  //пін А6 - в режим вводу
pinMode(DETECTOR, INPUT);

  //встановимо початкові значення для кольорів
analogWrite(GREEN_LED, 0);
analogWrite(RED_LED, 255);
}

void loop() {
  //зчитуємо значення що передає датчик з піну А6
int sensorVal = analogRead(DETECTOR);

  //записуємо нові значення у піни світлодіодів
  //для цього використовуємо допоміжну функцію
  int redLightness = getLEDLightnessFrom(sensorVal);
  int greenLightness = getLEDLightnessFrom(25 - sensorVal);
analogWrite(GREEN_LED, redLightness);
analogWrite(RED_LED, greenLightness);
}

//допоміжна функція для розрахунку рівня 
//світіння світлодіода в залежності 
//від значення, отриманого з датчика
int getLEDLightnessFrom(int sensorValue) {
if(sensorValue < 2)
    return 0;

  return sensorValue * 10;
}

Сенсор CNY70 видає значення від 0 до 25 (я попередньо дізнався ці значення експериментальним шляхом, через UART-консоль). Рівень яскравості встановлюється функцією analogWrite(pin, value), яка очікує що аргумент value матиме значення від 0 до 255. Допоміжна функція getLEDLightnessFrom(sensorValue) повертає 0 якщо значення сенсору менше 2, інакше повертає значення сенсору помножений на 10. Таким чином якщо сенсор видає 25, то функція видасть яскравість 250 (майже максимум), і 0 якщо сенсор ледь уловлює перешкоди. Зверніть увагу що для зеленого значення обернене (25 - sensorVal) так, що чим більше видає сенсор, тим меншу значення передається допоміжній функції. Як результат - чим ближче до сенсора перешкода, тим яскравіше світиться червоний світлодіод, і навпаки - чим далі перешкода, тим яскравіше зелений:

Згладжування значень детектора

На відео видно, що коли я наближую датчик до предмету, то індикатор змінює колір не рівномірно, а стрибками. Це тому що сам сенсор видає рівень нестабільно. Це звичайна справа для сенсорів, але я хотів трошки покращити результат, тож напишемо простий фільтр для стабілізації індикатора:

#include <Arduino.h>

#define RED_LED    PD3
#define GREEN_LED  PD5

#define DETECTOR   A6

int getLEDLightnessFrom(int sensorValue);
//об'являємо функцію сгладжування
int smooth(int rawValue);

void setup() {
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);

  pinMode(DETECTOR, INPUT);

  analogWrite(GREEN_LED, 0);
  analogWrite(RED_LED, 255);
}

void loop() {
  int sensorVal = analogRead(DETECTOR);
//згладжуємо вхідний сигнал
sensorVal = smooth(sensorVal);

  int redLightness = getLEDLightnessFrom(sensorVal);
  int greenLightness = getLEDLightnessFrom(25 - sensorVal);
  analogWrite(GREEN_LED, redLightness);
  analogWrite(RED_LED, greenLightness);
}

int getLEDLightnessFrom(int sensorValue) {
  if(sensorValue < 2)
    return 0;

  return sensorValue * 10;
}

bool initialized = false;
int lastVal = 0;
double koef = 0.5;


int smooth(int rawValue) {
  if(!initialized) {
    initialized = true;
    lastVal = rawValue;
  } else {
  lastVal = lastVal + koef * (rawValue - lastVal);
  }

  return lastVal;
}

Тут додано функцію smooth(rawValue), яка бере значення, і просто знижує різницю між попереднім і даним в два рази. Змінюючи коефіцієнт згладжування koef можна налаштовувати реакцію на зміни. Ось такий результат отримуємо: