Проект 10: ИК-пульт управления

ИК-пульт и приёмник VS1838B

Введение

В этом проекте мы научимся принимать команды с ИК-пульта для дистанционного управления роботом Фобо (дальность до 5-8 метров).

Теория: как работает ИК-пульт

Основной принцип

Инфракрасное (ИК) излучение — это невидимый для глаза свет с длиной волны 940 нм.

Как работает передача:

  1. Пульт (передатчик):

    • Внутри есть ИК-светодиод (похож на обычный, но излучает инфракрасный свет)

    • При нажатии кнопки светодиод мигает с частотой 38 кГц (38 000 раз в секунду!)

    • Последовательность вспышек кодирует информацию (какая кнопка нажата)

  2. Приёмник VS1838B:

    • Внутри есть фототранзистор, чувствительный к ИК-свету

    • Демодулятор на 38 кГц — фильтрует помехи (солнечный свет, лампы)

    • Преобразует последовательность вспышек в электрический сигнал

    • Выдаёт на выводе OUT цифровой сигнал (HIGH/LOW)

Принцип работы ИК-связи

Протокол NEC

Наш пульт использует протокол NEC — один из самых популярных:

  • Стартовый импульс: 9 мс LOW + 4.5 мс HIGH (сигнал начала передачи)

  • 32 бита данных:

    • 8 бит — адрес устройства (например, 0x00 для нашего пульта)

    • 8 бит — инверсия адреса (для проверки ошибок)

    • 8 бит — команда (код кнопки)

    • 8 бит — инверсия команды

  • Логический 0: 562 мкс LOW + 562 мкс HIGH (короткая пауза)

  • Логическая 1: 562 мкс LOW + 1687 мкс HIGH (длинная пауза)

Пример декодирования кнопки «1»

Давайте разберём, как из последовательности импульсов получается код 0xFFA25D.

Шаг 1: Приёмник получает 32 бита данных

При нажатии кнопки «1» пульт передаёт 32 бита (каждый бит = короткая или длинная пауза):

11111111 10100010 01011101 10100010
\_____/  \______/ \______/ \______/
  Адрес   ~Адрес  Команда  ~Команда

Расшифровка:

  • Байт 1 (адрес): 11111111 = 0xFF (255 в десятичной) — адрес нашего пульта

  • Байт 2 (инверсия адреса): 00000000 = 0x00 — для проверки (должен быть инверсией байта 1)

  • Байт 3 (команда): 10100010 = 0xA2 (162 в десятичной) — код кнопки «1»

  • Байт 4 (инверсия команды): 01011101 = 0x5D — инверсия команды для проверки ошибок

Шаг 2: Собираем 32-битное число

Теперь эти 4 байта объединяются в одно 32-битное число:

0xFF   0xA2   0x5D   (пропускаем байт 2, т.к. он инверсия)

Но! В протоколе NEC все 4 байта передаются:

Байт 1  Байт 2  Байт 3  Байт 4
0xFF    0x00    0xA2    0x5D

Объединяем (слева направо):
0xFF 00 A2 5D → но обычно записывают: 0xFFA25D (без лидирующих нулей инверсии)

Почему именно так?

В реальности библиотека получает все 32 бита, но часто код записывают без байта инверсии адреса, если адрес стандартный (0xFF → 0x00). Поэтому:

  • Полный код: 0xFF00A25D (32 бита)

  • Сокращённый: 0xFFA25D (24 бита, без 0x00)

Наглядный пример:

Кнопка "1" на пульте
        ↓
ИК-светодиод мигает по схеме NEC
        ↓
VS1838B принимает импульсы
        ↓
Демодулятор преобразует в биты: 11111111 10100010 01011101 10100010
        ↓
Библиотека AlashIRControlRX декодирует
        ↓
Получаем код: 0xFFA25D

Важно

В шестнадцатеричной системе (HEX):

  • 0xA2 = 10 × 16¹ + 2 × 16⁰ = 160 + 2 = 162 (в десятичной)

  • 0x5D = 5 × 16¹ + 13 × 16⁰ = 80 + 13 = 93 (в десятичной)

  • 0xFF = 15 × 16¹ + 15 × 16⁰ = 240 + 15 = 255 (в десятичной)

Полный код 0xFFA25D = 16 753 245 в десятичной системе!

Примечание

Библиотека AlashIRControlRX делает всю сложную работу за нас! Мы просто получаем готовый код кнопки.

VS1838B: конструкция приёмника

Внутри VS1838B:

  • Фототранзистор (чувствителен к 940 нм)

  • Усилитель сигнала

  • Полосовой фильтр на 38 кГц (отсекает помехи от ламп, солнца)

  • Демодулятор (преобразует 38 кГц в цифровой сигнал)

Распиновка VS1838B

Библиотека AlashIRControlRX

Установка: см. подробную инструкцию в Уроке 1. Скачайте ZIP с GitHub: https://github.com/Alash-electronics/AlashIRControl

Основные возможности библиотеки:

  • AlashIRControlRX irReceiver(pin) — создаёт объект приёмника на указанном пине

  • irReceiver.begin() — инициализация приёмника (в setup)

  • irReceiver.check() — проверка, пришёл ли новый сигнал (в loop)

  • irReceiver.data — код нажатой кнопки (в формате HEX)

Пример использования:

#include <AlashIRControlRX.h>
AlashIRControlRX irReceiver(A3);  // Приёмник на пине A3

void setup() {
  Serial.begin(9600);
  irReceiver.begin();  // Запускаем приёмник
}

void loop() {
  if (irReceiver.check()) {  // Если кнопка нажата
    Serial.println(irReceiver.data, HEX);  // Выводим код
  }
}

Код программы

Эксперимент 1: Считывание кодов кнопок

Цель: Научиться принимать сигналы с пульта и узнать коды всех кнопок в разных форматах (HEX и десятичный).

Скопируйте этот код в Arduino IDE:

 1/*
 2 * Проект 10: ИК-пульт управления
 3 * Эксперимент 1: Считывание кодов кнопок
 4 *
 5 * Что делает программа:
 6 * - Принимает сигналы с ИК-пульта
 7 * - Выводит код каждой нажатой кнопки в Serial Monitor
 8 * - Показывает код в двух форматах: HEX и десятичный
 9 *
10 * Подключение:
11 * Модуль IR → Sensor Shield линия A3 (- → G, + → V, S → S)
12 */
13
14#include <AlashIRControlRX.h>              // Подключаем библиотеку для ИК-приёмника
15
16// НАСТРОЙКИ ПРИЁМНИКА
17const int IR_PIN = A3;                    // Пин ИК-приёмника (A3 на Sensor Shield)
18
19AlashIRControlRX irReceiver(IR_PIN);       // Создаём объект приёмника
20
21void setup() {
22  Serial.begin(9600);                      // Запускаем Serial Monitor на скорости 9600
23  irReceiver.begin();                      // Инициализируем ИК-приёмник
24
25  Serial.println("=============================================");
26  Serial.println("   Проект 10: ИК-пульт управления");
27  Serial.println("   Эксперимент 1: Считывание кодов кнопок");
28  Serial.println("=============================================");
29  Serial.println();
30  Serial.println("Направьте пульт на приёмник и нажмите любую кнопку.");
31  Serial.println("Расстояние: 5-50 см для надёжного приёма.");
32  Serial.println();
33  Serial.println("Формат вывода:");
34  Serial.println("  HEX-код - для использования в программе");
35  Serial.println("  Десятичный - для понимания числового значения");
36  Serial.println();
37}
38
39void loop() {
40  // Проверяем, пришёл ли сигнал с пульта
41  if (irReceiver.check()) {
42    unsigned long code = irReceiver.data;  // Сохраняем код в переменную
43
44    // Игнорируем код повтора (когда кнопка удерживается)
45    if (code == 0xFFFFFFFF) {
46      Serial.println("  ↻ [удерживается кнопка]");
47      return;
48    }
49
50    // Выводим код в двух форматах
51    Serial.println("┌─────────────────────────────────┐");
52
53    // HEX-формат (для программы)
54    Serial.print("│ HEX-код:      0x");
55    if (code < 0x10000000) Serial.print("0");  // Добавляем ведущий 0 если нужно
56    Serial.print(code, HEX);
57    Serial.println(" │");
58
59    // Десятичный формат (для понимания)
60    Serial.print("│ Десятичный:   ");
61    Serial.print(code);
62    // Выравнивание
63    if (code < 10000000) Serial.print(" ");
64    if (code < 1000000) Serial.print(" ");
65    Serial.println(" │");
66
67    Serial.println("└─────────────────────────────────┘");
68    Serial.println();
69  }
70}

Как работает код:

  1. Строка 16: Подключаем библиотеку AlashIRControlRX.h

  2. Строка 19: Определяем пин приёмника (A3)

  3. Строка 21: Создаём объект irReceiver для работы с приёмником

  4. Строка 25: Инициализируем Serial Monitor (9600 бод)

  5. Строка 26: Запускаем приёмник функцией begin()

  6. Строка 43: Функция check() возвращает true, если пришёл новый сигнал

  7. Строка 44: Сохраняем код кнопки в переменную code (тип unsigned long)

  8. Строки 47-50: Игнорируем код повтора 0xFFFFFFFF (когда кнопка удерживается)

  9. Строки 53-69: Выводим код в двух форматах с красивым оформлением

Почему два формата?

  • HEX (шестнадцатеричный): Традиционный формат для ИК-кодов, используется в программах. Компактный: 0xFFA25D вместо 16753245

  • Десятичный: Обычное число, которое легче понять человеку. Показывает реальное числовое значение кода.

Совет

В коде программы всегда используйте HEX-формат — он короче и нагляднее. Десятичный формат нужен только для понимания.

Загрузка и тестирование

Примечание

Процесс загрузки кода: см. подробную инструкцию в Уроке 1. Откройте Serial Monitor (9600 baud) после загрузки.

Что вы увидите в Serial Monitor

После загрузки программы:

==================================
  Проект 10: ИК-пульт управления
  Эксперимент 1: Считывание кодов
=================================

Направьте пульт на приёмник и нажмите любую кнопку.
Расстояние: 5-50 см для надёжного приёма.

Теперь возьмите пульт, направьте на VS1838B (расстояние 10-30 см) и нажмите любую кнопку.

Пример вывода:

Код кнопки: 0xFFA25D    ← Нажата кнопка "1"
Код кнопки: 0xFF629D    ← Нажата кнопка "2"
Код кнопки: 0xFF18E7    ← Нажата кнопка "▲" (вверх)
Код кнопки: 0xFF38C7    ← Нажата кнопка "OK"

Совет

Запишите коды всех кнопок! Они понадобятся для следующих экспериментов. У разных производителей пультов коды могут отличаться.

Таблица кодов стандартного пульта

Вот коды для типового пульта из набора Alash:

Кнопка

HEX-код

Десятичный код

0

0xFF9867

16753767

1

0xFFA25D

16753245

2

0xFF629D

16736925

3

0xFFE21D

16761405

4

0xFF22DD

16720605

5

0xFF02FD

16712445

6

0xFFC23D

16757565

7

0xFFE01F

16761375

8

0xFFA857

16754775

9

0xFF906F

16748655

*

0xFF6897

16738455

#

0xFFB04F

16756815

▲ (UP)

0xFF18E7

16718055

▼ (DOWN)

0xFF4AB5

16730805

◄ (LEFT)

0xFF10EF

16716527

► (RIGHT)

0xFF5AA5

16734885

OK

0xFF38C7

16726215

Предупреждение

Ваш пульт может иметь другие коды! Всегда проверяйте коды с помощью Эксперимента 1 перед использованием.

Использование других пультов (TV, кондиционер, телефон)

Можно ли использовать пульт от телевизора или кондиционера?

Да! VS1838B работает с любым пультом, использующим протокол NEC (большинство бытовых пультов). Вы не ограничены только пультом из набора!

Варианты пультов для управления роботом:

  1. 📺 Пульт от телевизора

    • Большинство телевизоров (Samsung, LG, Sony) используют протокол NEC

    • У вас уже есть дома, не нужно покупать новый

    • Большие кнопки, удобно нажимать

    • Недостаток: много лишних кнопок (можно использовать только нужные)

  2. ❄️ Пульт от кондиционера

    • Обычно также работают по протоколу NEC

    • Крупные кнопки с чёткими символами

    • Недостаток: некоторые кондиционеры используют другие протоколы

  3. 📱 Телефон с ИК-передатчиком

    • Многие телефоны имеют встроенный ИК-передатчик (Xiaomi, Huawei, Samsung Galaxy S-серия)

    • Можно установить приложение-пульт (например, Mi Remote, AnyMote, SURE Universal Remote)

    • Можно создать свой интерфейс с нужными кнопками!

    • Преимущество: всегда при себе, не нужен отдельный пульт

    • Проверка: посмотрите спецификацию телефона или найдите «ИК-порт» (обычно сверху рядом с разъёмом для наушников)

  4. 🎮 Универсальный пульт

    • Можно купить в магазине электроники (~$3-5)

    • Программируется на разные устройства

Как использовать свой пульт:

Шаг 1: Запустите Эксперимент 1 (считывание кодов)

Шаг 2: Направьте свой пульт на VS1838B и нажмите нужные кнопки

Шаг 3: Запишите коды в таблицу:

Пример для пульта от телевизора Samsung:

┌─────────────────────────────────┐
│ HEX-код:      0xE0E0D02F         │  ← Кнопка VOL+ (громкость +)
│ Десятичный:   3772793903         │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ HEX-код:      0xE0E0E01F         │  ← Кнопка VOL- (громкость -)
│ Десятичный:   3772789791         │
└─────────────────────────────────┘

Шаг 4: Замените коды в программе:

// Пример: использование кнопок телевизора Samsung
const unsigned long BTN_UP    = 0xE0E006F9;  // CH+ (канал вверх)
const unsigned long BTN_DOWN  = 0xE0E08679;  // CH- (канал вниз)
const unsigned long BTN_LEFT  = 0xE0E0A659;  // ◄ (влево)
const unsigned long BTN_RIGHT = 0xE0E046B9;  // ► (вправо)
const unsigned long BTN_OK    = 0xE0E016E9;  // OK (центр)
const unsigned long BTN_1     = 0xE0E020DF;  // Цифра 1
const unsigned long BTN_2     = 0xE0E0A05F;  // Цифра 2

Важные замечания:

Примечание

  • Не все пульты используют NEC! Если пульт не работает, попробуйте другой.

  • Коды у разных производителей отличаются — даже для одной кнопки «VOL+».

  • Некоторые пульты передают длинные последовательности — они могут не работать корректно.

Совет

Для телефонов с IR: Приложения типа Mi Remote позволяют:

  • Создать кастомный пульт с только нужными кнопками

  • Назвать кнопки по-своему (FORWARD, LEFT, RIGHT, STOP)

  • Программировать макросы (одна кнопка = несколько команд)

Проверка совместимости:

Если пульт не работает (Serial Monitor показывает 0x00000000 или вообще ничего):

  1. Попробуйте другие кнопки — некоторые могут использовать другой протокол

  2. Проверьте батарейку в пульте (направьте на камеру телефона — должна видна вспышка)

  3. Попробуйте другой пульт — ваш может использовать RC5, RC6 или Sony SIRC (несовместимы с NEC)

Эксперимент 2: Распознавание кнопок

Цель: Научиться определять, какая именно кнопка нажата, и выводить её название.

 1/*
 2 * Проект 10: ИК-пульт управления
 3 * Эксперимент 2: Распознавание кнопок
 4 *
 5 * Что делает программа:
 6 * - Принимает сигналы с ИК-пульта
 7 * - Определяет, какая кнопка нажата
 8 * - Выводит название кнопки в Serial Monitor
 9 *
10 * ВАЖНО: Замените коды кнопок на свои (из Эксперимента 1)!
11 */
12
13#include <AlashIRControlRX.h>
14
15const int IR_PIN = A3;
16AlashIRControlRX irReceiver(IR_PIN);
17
18// КОДЫ КНОПОК (замените на свои!)
19const unsigned long BTN_1     = 0xFFA25D;
20const unsigned long BTN_2     = 0xFF629D;
21const unsigned long BTN_3     = 0xFFE21D;
22const unsigned long BTN_4     = 0xFF22DD;
23const unsigned long BTN_5     = 0xFF02FD;
24const unsigned long BTN_6     = 0xFFC23D;
25const unsigned long BTN_7     = 0xFFE01F;
26const unsigned long BTN_8     = 0xFFA857;
27const unsigned long BTN_9     = 0xFF906F;
28const unsigned long BTN_0     = 0xFF9867;
29const unsigned long BTN_STAR  = 0xFF6897;
30const unsigned long BTN_HASH  = 0xFFB04F;
31const unsigned long BTN_UP    = 0xFF18E7;
32const unsigned long BTN_DOWN  = 0xFF4AB5;
33const unsigned long BTN_LEFT  = 0xFF10EF;
34const unsigned long BTN_RIGHT = 0xFF5AA5;
35const unsigned long BTN_OK    = 0xFF38C7;
36
37void setup() {
38  Serial.begin(9600);
39  irReceiver.begin();
40
41  Serial.println("========================================");
42  Serial.println("  Проект 10: Эксперимент 2");
43  Serial.println("  Распознавание кнопок пульта");
44  Serial.println("========================================");
45  Serial.println();
46  Serial.println("Нажимайте кнопки — я скажу их названия!");
47  Serial.println();
48}
49
50void loop() {
51  if (irReceiver.check()) {
52    unsigned long code = irReceiver.data;  // Сохраняем код для проверки
53
54    // Определяем, какая кнопка нажата
55    Serial.print("Нажата кнопка: ");
56
57    if (code == BTN_0)          Serial.println("0");
58    else if (code == BTN_1)     Serial.println("1");
59    else if (code == BTN_2)     Serial.println("2");
60    else if (code == BTN_3)     Serial.println("3");
61    else if (code == BTN_4)     Serial.println("4");
62    else if (code == BTN_5)     Serial.println("5");
63    else if (code == BTN_6)     Serial.println("6");
64    else if (code == BTN_7)     Serial.println("7");
65    else if (code == BTN_8)     Serial.println("8");
66    else if (code == BTN_9)     Serial.println("9");
67    else if (code == BTN_STAR)  Serial.println("* (звёздочка)");
68    else if (code == BTN_HASH)  Serial.println("# (решётка)");
69    else if (code == BTN_UP)    Serial.println("▲ ВВЕРХ");
70    else if (code == BTN_DOWN)  Serial.println("▼ ВНИЗ");
71    else if (code == BTN_LEFT)  Serial.println("◄ ВЛЕВО");
72    else if (code == BTN_RIGHT) Serial.println("► ВПРАВО");
73    else if (code == BTN_OK)    Serial.println("OK");
74    else {
75      Serial.print("НЕИЗВЕСТНАЯ (код: 0x");
76      Serial.print(code, HEX);
77      Serial.println(")");
78    }
79  }
80}

Что нового:

  1. Строки 18-35: Определяем константы для кодов кнопок (удобнее, чем писать 0xFFA25D каждый раз)

  2. Строка 52: Сохраняем код в переменную code (тип unsigned long — беззнаковое целое 32 бита)

  3. Строки 57-73: Цепочка if-else if проверяет, какой кнопке соответствует код

  4. Строки 74-78: Если код не совпал ни с одной кнопкой → «НЕИЗВЕСТНАЯ»

Совет

Константы вместо «магических чисел»: Код if (code == BTN_UP) гораздо понятнее, чем if (code == 0xFF18E7).

Пример вывода:

Нажата кнопка: 1
Нажата кнопка: ▲ ВВЕРХ
Нажата кнопка: OK
Нажата кнопка: * (звёздочка)
Нажата кнопка: НЕИЗВЕСТНАЯ (код: 0xFFFFFFFF)

Эксперимент 3: Управление с пульта (светодиод + сервопривод)

Робот Фобо с ИК-пультом управления

Цель: Использовать кнопки пульта для одновременного управления встроенным светодиодом и сервоприводом.

Функционал:

  • Кнопка 1 → Включить светодиод

  • Кнопка 2 → Выключить светодиод

  • Кнопки ◄/► → Поворот сервопривода влево/вправо (шаг 15°)

  • Кнопка OK → Вернуть сервопривод в центр (90°)

  1/*
  2 * Проект 10: ИК-пульт управления
  3 * Эксперимент 3: Управление светодиодом и сервоприводом
  4 *
  5 * Управление:
  6 * 1 = включить светодиод
  7 * 2 = выключить светодиод
  8 * ◄ = сервопривод влево (шаг 15°)
  9 * ► = сервопривод вправо (шаг 15°)
 10 * OK = сервопривод в центр (90°)
 11 *
 12 * Подключение:
 13 * Модуль IR → Sensor Shield линия A3 (- → G, + → V, S → S)
 14 * Сервопривод → Sensor Shield линия D9 (см. Проект 4)
 15 *
 16 * ВАЖНО: Используем servo.detach() для устранения помех!
 17 */
 18
 19#include <AlashIRControlRX.h>
 20#include <Servo.h>
 21
 22const int IR_PIN = A3;
 23const int LED_PIN = 13;                    // Встроенный светодиод Arduino
 24const int SERVO_PIN = 9;                   // Сервопривод на D9
 25const int SERVO_CENTER = 90;   // Калибровка центра серво (90 = стандарт, настройте под ваш серво)
 26
 27AlashIRControlRX irReceiver(IR_PIN);
 28Servo myServo;
 29
 30// Коды кнопок (замените на свои!)
 31const unsigned long BTN_1     = 0xFFA25D;
 32const unsigned long BTN_2     = 0xFF629D;
 33const unsigned long BTN_LEFT  = 0xFF10EF;
 34const unsigned long BTN_RIGHT = 0xFF5AA5;
 35const unsigned long BTN_OK    = 0xFF38C7;
 36
 37int servoAngle = SERVO_CENTER;                       // Текущий угол сервопривода (0-180°)
 38
 39// Функция для безопасного управления сервоприводом
 40void moveServo(int angle) {
 41  myServo.attach(SERVO_PIN);               // Подключаем только на время движения
 42  delay(15);                               // Даём время на стабилизацию
 43  myServo.write(angle);                    // Устанавливаем позицию
 44  delay(300);                              // Ждём завершения движения
 45  myServo.detach();                        // ВАЖНО: Отключаем для устранения помех!
 46}
 47
 48void setup() {
 49  Serial.begin(9600);
 50  irReceiver.begin();
 51  pinMode(LED_PIN, OUTPUT);
 52
 53  Serial.println("========================================");
 54  Serial.println("  Проект 10: Эксперимент 3");
 55  Serial.println("  Управление с ИК-пульта");
 56  Serial.println("========================================");
 57  Serial.println();
 58  Serial.println("Управление:");
 59  Serial.println("  [1] = Светодиод ВКЛ");
 60  Serial.println("  [2] = Светодиод ВЫКЛ");
 61  Serial.println("  [◄] = Сервопривод влево");
 62  Serial.println("  [►] = Сервопривод вправо");
 63  Serial.println("  [OK] = Сервопривод в центр");
 64  Serial.println();
 65
 66  // Устанавливаем начальную позицию сервопривода
 67  moveServo(servoAngle);
 68  Serial.print("Сервопривод в центре: ");
 69  Serial.print(servoAngle);
 70  Serial.println("°");
 71}
 72
 73void loop() {
 74  if (irReceiver.check()) {
 75    unsigned long code = irReceiver.data;
 76
 77    // Игнорируем код повтора
 78    if (code == 0xFFFFFFFF) return;
 79
 80    // Обрабатываем команды для светодиода
 81    if (code == BTN_1) {
 82      digitalWrite(LED_PIN, HIGH);
 83      Serial.println("✓ Светодиод ВКЛЮЧЕН");
 84
 85    } else if (code == BTN_2) {
 86      digitalWrite(LED_PIN, LOW);
 87      Serial.println("✗ Светодиод ВЫКЛЮЧЕН");
 88
 89    // Обрабатываем команды для сервопривода
 90    } else if (code == BTN_LEFT) {
 91      // Поворот влево (увеличиваем угол)
 92      servoAngle += 15;                        // Шаг 15°
 93      if (servoAngle > 180) servoAngle = 180;  // Ограничение: максимум 180°
 94      moveServo(servoAngle);                   // Используем функцию с detach()
 95      Serial.print("◄ Сервопривод: ");
 96      Serial.print(servoAngle);
 97      Serial.println("°");
 98
 99    } else if (code == BTN_RIGHT) {
100      // Поворот вправо (уменьшаем угол)
101      servoAngle -= 15;
102      if (servoAngle < 0) servoAngle = 0;  // Ограничение: минимум 0°
103      moveServo(servoAngle);               // Используем функцию с detach()
104      Serial.print("► Сервопривод: ");
105      Serial.print(servoAngle);
106      Serial.println("°");
107
108    } else if (code == BTN_OK) {
109      // Возврат в центр
110      servoAngle = SERVO_CENTER;
111      moveServo(servoAngle);               // Используем функцию с detach()
112      Serial.print("⊙ Сервопривод в центре (");
113      Serial.print(SERVO_CENTER);
114      Serial.println("°)");
115    }
116  }
117}

Что нового:

  1. Строка 19: Подключаем библиотеку Servo.h для управления сервоприводом

  2. Строки 23-24: Определяем пины для светодиода (D13) и сервопривода (D11)

  3. Строка 36: Переменная servoAngle хранит текущий угол (0-180°)

  4. Строки 39-45: Функция moveServo()ключевое решение проблемы помех!

    • attach() — подключаем сервопривод только на время движения

    • delay(15) — даём время на стабилизацию

    • write() — устанавливаем нужный угол

    • delay(300) — ждём завершения движения

    • detach()отключаем сервопривод для устранения помех!

  5. Строка 59: В setup() используем moveServo() вместо прямого attach()

  6. Строки 76-79: Кнопки 1/2 включают/выключают светодиод (простое ON/OFF)

  7. Строки 84-104: Кнопки ◄/► изменяют угол сервопривода с шагом 15° через moveServo()

  8. Строки 90, 99: Кнопка OK возвращает сервопривод в центр (90°) через moveServo()

  9. Ограничения: if (servoAngle < 0) и if (servoAngle > 180) предотвращают выход за пределы

Важно

Почему detach() критически важен:

Без detach() сервопривод постоянно получает ШИМ-сигналы (50 Гц), создавая электромагнитные помехи (EMI), которые улавливаются чувствительным IR-приёмником. Вы заметите:

  • Сервопривод жужжит, пытаясь удерживать позицию

  • Индикатор IR-модуля мигает в такт шуму сервопривода

  • IR-приёмник «зависает» и не реагирует на пульт

Функция moveServo() решает проблему: подключаем сервопривод только на 300 мс для движения, затем отключаем. Сервопривод держит позицию механически, а IR-приёмник работает без помех!

Предупреждение

Важно про пин 13: Встроенный светодиод Arduino на пине D13 НЕ поддерживает ШИМ (плавное изменение яркости). В этом эксперименте мы используем простое включение/выключение (digitalWrite).

Примечание

Пины с ШИМ на Arduino Uno: Если вы хотите управлять яркостью светодиода, подключите внешний светодиод к одному из ШИМ-пинов: D3, D5, D6, D9, D10, D11. Эти пины помечены символом ~ (тильда) на плате Arduino.

Пример вывода:

Сервопривод в центре: 90°
✓ Светодиод ВКЛЮЧЕН
► Сервопривод: 105°
► Сервопривод: 120°
◄ Сервопривод: 105°
⊙ Сервопривод в центре (90°)
✗ Светодиод ВЫКЛЮЧЕН

Эксперимент (для самостоятельной работы):

Если вы хотите управлять яркостью светодиода с помощью ШИМ:

  1. Подключите внешний светодиод (с резистором 220 Ом) к ШИМ-пину, например D9:

    • Длинная ножка (анод) → резистор 220 Ом → D9

    • Короткая ножка (катод) → GND

  2. Измените код:

const int LED_PIN = 9;                     // Внешний светодиод на D9 (ШИМ-пин)
int brightness = 0;                        // Текущая яркость (0-255)

// В setup():
pinMode(LED_PIN, OUTPUT);

// Добавьте новые кнопки для яркости:
const unsigned long BTN_UP   = 0xFF18E7;  // Кнопка ▲
const unsigned long BTN_DOWN = 0xFF4AB5;  // Кнопка ▼

// В loop() добавьте обработку:
} else if (code == BTN_UP) {
  brightness += 25;
  if (brightness > 255) brightness = 255;
  analogWrite(LED_PIN, brightness);
  Serial.print("▲ Яркость: ");
  Serial.println(brightness);

} else if (code == BTN_DOWN) {
  brightness -= 25;
  if (brightness < 0) brightness = 0;
  analogWrite(LED_PIN, brightness);
  Serial.print("▼ Яркость: ");
  Serial.println(brightness);
}

Совет

Как определить ШИМ-пины? Посмотрите на плату Arduino — пины с поддержкой ШИМ помечены символом ~ (тильда) перед номером. Или проверьте распиновку вашей платы в документации.

Поиск неисправностей

Проблема: Serial Monitor не показывает коды при нажатии кнопок

Возможные причины:

  1. Перепутана распиновка VS1838B

    • Проверьте порядок выводов OUT-GND-VCC

    • У разных производителей порядок может отличаться!

    • Попробуйте поменять местами провода VCC и GND (если приёмник не греется)

  2. Неправильный пин в коде

    • Убедитесь: const int IR_PIN = A3;

    • Приёмник подключён к линии A3 на Sensor Shield

  3. Батарейка в пульте разряжена

    • Проверьте: направьте пульт на камеру телефона и нажмите кнопку

    • Должна быть видна вспышка ИК-светодиода (на экране телефона)

  4. Пульт слишком далеко или не направлен на приёмник

    • Попробуйте расстояние 10-20 см

    • Направляйте пульт точно на полусферу VS1838B

  5. Библиотека установлена неправильно

    • Переустановите AlashIRControl (процесс см. Урок 1)

Проблема: Выводятся странные коды (например, 0xFFFFFFFF)

Причина: Это код повтора (repeat code) — пульт отправляет его, когда кнопка удерживается нажатой.

Решение:

  • Нажимайте кнопки коротко (не удерживайте)

  • Или игнорируйте код 0xFFFFFFFF в программе:

if (irReceiver.check()) {
  unsigned long code = irReceiver.data;
  if (code != 0xFFFFFFFF) {  // Игнорируем код повтора
    // Обрабатываем команду
  }
}

Проблема: Коды кнопок не совпадают с таблицей

Причина: Разные производители пультов используют разные коды.

Решение:

  • Всегда запускайте Эксперимент 1 для определения кодов вашего пульта

  • Замените константы BTN_1, BTN_2 и т.д. на свои коды

Проблема: Приёмник работает только на близком расстоянии (<10 см)

Возможные причины:

  1. Яркое освещение (солнце, лампы дневного света)

    • VS1838B фильтрует помехи, но очень яркий свет может мешать

    • Попробуйте работать в помещении с обычным освещением

  2. Слабые контакты

    • Проверьте надёжность подключения проводов к VS1838B

    • Попробуйте другие провода Female-Female

  3. Повреждён приёмник

    • Проверьте визуально: нет ли трещин на корпусе

    • Попробуйте другой VS1838B (если есть)

Проблема: При работе с сервоприводом IR-приёмник «зависает» или пропускает команды

Симптомы:

  • Сервопривод издаёт шум (тихое жужжание), пытаясь удерживать позицию

  • ИК-приёмник перестаёт реагировать на пульт или сильно задерживается

  • Программа «зависает» после команды сервоприводу

Причина: Электромагнитные помехи (EMI) от работающего сервопривода влияют на чувствительный IR-приёмник. Сервопривод постоянно корректирует позицию, создавая электрические импульсы, которые улавливаются IR-приёмником как ложные сигналы.

Решение 1: Отключение сервопривода после движения

Используйте servo.detach() после того, как сервопривод достиг нужной позиции:

void moveServo(int angle) {
  myServo.attach(SERVO_PIN);    // Подключаем только на время движения
  delay(15);                     // Даём время на стабилизацию
  myServo.write(angle);          // Устанавливаем позицию
  delay(300);                    // Ждём завершения движения
  myServo.detach();              // ВАЖНО: Отключаем сервопривод!
}

void loop() {
  if (irReceiver.check()) {
    unsigned long code = irReceiver.data;

    if (code == BTN_LEFT) {
      servoAngle += 15;
      servoAngle = constrain(servoAngle, 0, 180);
      moveServo(servoAngle);     // Используем функцию с detach()
    }
  }
}

Решение 2: Фильтрация помех в коде

Добавьте проверки длины сигнала и антидребезг:

unsigned long lastCode = 0;
unsigned long lastTime = 0;

void loop() {
  if (irReceiver.check(true)) {  // true = проверять длину сигнала
    unsigned long code = irReceiver.data;

    // Фильтр 1: Игнорируем слишком короткие/длинные сигналы (помехи)
    if (irReceiver.length < 10 || irReceiver.length > 40) return;

    // Фильтр 2: Антидребезг (игнорируем сигналы чаще 80 мс)
    if (millis() - lastTime < 80) return;

    // Фильтр 3: Обработка повторов
    if (code == 0xFFFFFFFF) {
      if (millis() - lastTime < 200) {
        code = lastCode;  // Используем предыдущий код для удержания
      } else {
        return;
      }
    }

    lastCode = code;
    lastTime = millis();

    // Теперь обрабатываем команду
    if (code == BTN_LEFT) {
      servoAngle += 15;
      servoAngle = constrain(servoAngle, 0, 180);
      moveServo(servoAngle);
    }
  }
}

Решение 3: Разнесение компонентов

  • Расположите IR-приёмник как можно дальше от сервопривода (минимум 10 см)

  • Используйте более длинные провода Female-Female для IR-приёмника

  • На роботе Фобо: IR-приёмник на верхнем ярусе, сервопривод на переднем крае

Почему detach() помогает?

  • myServo.attach() — включает ШИМ-сигнал (50 Гц), сервопривод постоянно получает импульсы

  • myServo.detach() — отключает ШИМ, сервопривод перестаёт корректировать позицию и не создаёт помех

  • Сервопривод держит позицию механически (если нет большой нагрузки)

Совет

Для робота Фобо: Всегда используйте servo.detach() после сканирования ультразвуковым датчиком. Подключайте сервопривод только на время поворота головы робота!

Расширенная информация

Другие протоколы ИК-связи

Кроме NEC, существуют и другие популярные протоколы:

  • RC5 (Philips): 14 бит, манчестерское кодирование, используется в европейской технике

  • RC6 (Philips): 20+ бит, улучшенная версия RC5

  • Sony SIRC: 12/15/20 бит, используется в продукции Sony

  • Samsung: 32 бита, похож на NEC, но другая частота повтора

Библиотека AlashIRControlRX поддерживает только протокол NEC — самый распространённый.

Дальность передачи ИК-сигнала

Факторы, влияющие на дальность:

  • Мощность ИК-светодиода в пульте: обычно 20-100 мВт

  • Чувствительность приёмника: VS1838B рассчитан на 5-8 метров

  • Угол приёма: VS1838B имеет угол ±45° (всего 90° конус)

  • Преграды: ИК-свет не проходит через стены, мебель, руки

  • Освещение: прямой солнечный свет сильно снижает дальность

Для робота Фобо достаточно 2-3 метра — этого хватит для управления в классе.

Частота модуляции 38 кГц

Почему именно 38 кГц?

  • Это компромисс между энергопотреблением и помехоустойчивостью

  • Низкая частота (напр. 10 кГц): может совпадать с частотой ламп (50/60 Гц и их гармоники)

  • Высокая частота (напр. 100 кГц): требует более быстрой электроники в приёмнике

  • 38 кГц: стандарт с 1980-х годов, поддерживается большинством приёмников

Существуют также приёмники на 36 кГц, 40 кГц, 56 кГц — они несовместимы между собой!

Энергопотребление VS1838B

Типовые параметры:

  • Напряжение питания: 2.7-5.5V (оптимально 5V)

  • Ток потребления: 0.4-1.5 мА (в зависимости от уровня сигнала)

  • Режим ожидания: ~0.4 мА

Для сравнения: Arduino Uno потребляет ~45 мА, светодиод ~20 мА. VS1838B очень экономичен!

Что дальше?

Поздравляем! Вы научились работать с ИК-пультом — важнейшим инструментом дистанционного управления роботом.

Что мы узнали:

✅ Как работает ИК-связь (передатчик, приёмник, протокол NEC)

✅ Как подключить VS1838B к Sensor Shield

✅ Как установить и использовать библиотеку AlashIRControlRX

✅ Как считывать коды кнопок пульта

✅ Как распознавать кнопки и управлять устройствами

В следующих проектах:

  • Проект 11: Управление роботом через ИК-пульт — переключаем автономные режимы кнопками пульта

  • Проект 12: Bluetooth-модуль HM-10 — учимся работать с Bluetooth (Android и iOS!)

  • Проект 13: Управление роботом через Bluetooth — ручное управление моторами с телефона

  • Проект 14: Мастер-режим — полная интеграция: все автономные режимы + Bluetooth-управление!

Как ИК-пульт будет использоваться в роботе:

  • Кнопки ▲▼◄► — ручное управление движением (Проект 11)

  • Кнопки 1, 2, 3 — переключение автономных режимов (Проект 11)

  • Кнопка OK — старт/стоп программы (Проект 11)

До встречи в Проекте 11 — управление роботом через ИК-пульт! 🚀