Skip to main content

Аналоговые датчики и ШИМ

ЗАЧЕМ ЭТО НУЖНО? 🎯

Суть проблемы: мир не черно-белый! Нам нужно измерять не только “есть/нет”, но и “сколько”.

Ключевые концепции:

  • Аналоговый сигнал - непрерывное изменение величины (освещенность, температура, расстояние)
  • АЦП (Аналого-Цифровой Преобразователь) - перевод аналогового сигнала в цифровое значение (0-1023)
  • ШИМ (Широтно-Импульсная Модуляция) - имитация аналогового выхода с помощью цифровых импульсов

Олимпиадная ценность: 70% заданий используют аналоговые датчики и ШИМ - от потенциометров для управления до регулировки яркости светодиодов! Ученики понимают разницу между цифровым и аналоговым сигналом, могут преобразовывать и использовать непрерывные величины!


КАК ИЗУЧАЕМ? 🚀

Без кода - только логика:

Фоторезистор: 
Светло → сопротивление ↓ → значение АЦП ↑ (ближе к 1023)
Темно → сопротивление ↑ → значение АЦП ↓ (ближе к 0)

ШИМ:
Яркость 0% → всегда 0В
Яркость 50% → половину времени 5В, половину 0В  
Яркость 100% → всегда 5В

Методика обучения:

  1. “Шкала значений” - строим графики зависимости показаний от реальных условий
  2. Эксперименты с map() - как преобразовать диапазон 0-1023 в полезные значения?
  3. Визуализация ШИМ - подключаем светодиод и меняем яркость “вживую”
  4. Калибровка под условия - настройка под конкретное освещение в классе

Основные принципы:

// Чтение аналогового датчика
int sensorValue = analogRead(A0); // 0-1023

// Запись ШИМ (только на пины с ~)
analogWrite(9, brightness); // 0-255

АНАЛОГОВЫЕ И ЦИФРОВЫЕ СИГНАЛЫ: ГЛУБОКОЕ ПОГРУЖЕНИЕ

АНАЛОГОВЫЙ vs ЦИФРОВОЙ - НЕ ВОЛЬТЫ И АМПЕРЫ!

Аналоговый сигнал:

🌊 НЕПРЕРЫВНЫЙ сигнал - может принимать ЛЮБОЕ значение в диапазоне
Пример: температура 23.7°C, напряжение 3.1415V, расстояние 15.83 см

Характеристики:
- Бесконечное количество значений
- Чувствителен к помехам
- Плавное изменение

Цифровой сигнал:

🔢 ДИСКРЕТНЫЙ сигнал - может принимать только ОПРЕДЕЛЕННЫЕ значения  
Пример: есть/нет (1/0), включено/выключено, 5V/0V

Характеристики:
- Конечное количество значений
- Устойчив к помехам
- Скачкообразное изменение

ВОЛЬТЫ и АМПЕРЫ - это разные физические величины:

  • Напряжение (Вольты) - “давление” электричества
  • Ток (Амперы) - “поток” электричества

И аналоговые и цифровые сигналы могут быть в Вольтах!


ПОЧЕМУ ИМЕННО 1023? 🔢

АЦП (Analog-to-Digital Converter) Arduino:

Arduino Uno имеет 10-битный АЦП

Что такое "10-битный"?:
1 бит = 2 значения (0 или 1)
2 бита = 4 значения (00, 01, 10, 11)  
...
10 бит = 2¹⁰ = 1024 значения (от 0 до 1023)

Почему от 0 до 1023, а не до 1024?
Потому что счет начинается с 0:
0, 1, 2, 3, ..., 1023 = всего 1024 значения

Как это работает на практике:

// Arduino измеряет напряжение от 0V до 5V
// и преобразует его в число от 0 до 1023

0V     0
2.5V   511
5V     1023

// Разрешение: 5V / 1024 = 0.0049V на шаг
// Это значит Arduino может различить изменение напряжения на 4.9mV!

Пример с фоторезистором:

Фоторезистор + резистор 10кОм создают делитель напряжения:

Светло → низкое сопротивление → высокое напряжение → значение ~900
Темно → высокое сопротивление → низкое напряжение → значение ~50

ПОЧЕМУ ИМЕННО 255? 🎛️

ШИМ (Pulse Width Modulation):

Arduino использует 8-битный ШИМ на пинах с ~

8 бит = 2⁸ = 256 значений (от 0 до 255)

Почему от 0 до 255?
0   → всегда 0V (0% времени 5V)
127 → 2.5V (50% времени 5V)  
255 → 5V (100% времени 5V)

Как работает ШИМ:

ШИМ НЕ создает настоящее аналоговое напряжение!
Он быстро включает/выключает 5V:

Яркость 50%: ████░░░░████░░░░ (50% времени 5V, 50% времени 0V)
Яркость 25%: ██░░░░░░██░░░░░░ (25% времени 5V, 75% времени 0V)
Яркость 75%: ██████░░██████░░ (75% времени 5V, 25% времени 0V)

Частота ~490 Гц - глаз не замечает мерцание, видит среднюю яркость!

ПРАКТИЧЕСКАЯ РАЗНИЦА 🔧

Аналоговый вход (чтение):

int sensorValue = analogRead(A0); // 0-1023
float voltage = sensorValue * (5.0 / 1023.0); // преобразуем в Вольты

Цифровой вход (чтение):

int buttonState = digitalRead(2); // только 0 или 1

Аналоговый выход (ШИМ):

analogWrite(9, 128); // 50% яркости (128/255)

Цифровой выход:

digitalWrite(13, HIGH); // только ВКЛ или ВЫКЛ

ПОЧЕМУ ТАКИЕ ЧИСЛА В ОЛИМПИАДАХ? 🏆

Преобразование между диапазонами:

// Частая задача: преобразовать 0-1023 в 0-255
int adcValue = analogRead(A0); // 0-1023
int pwmValue = map(adcValue, 0, 1023, 0, 255); // 0-255
pwmValue = constrain(pwmValue, 0, 255); // ограничиваем диапазон

// Или наоборот - из 0-255 в углы/расстояния/время

Математика для любознательных:

1023 = 2¹⁰ - 1
255  = 2⁸ - 1

Эти числа появляются из-за битовой организации памяти:
- 10 бит для АЦП (достаточно для большинства измерений)
- 8 бит для ШИМ (достаточно для плавного управления)

МЕТОДИЧЕСКИЕ ЭКСПЕРИМЕНТЫ 🧪

Эксперимент 1: “Увидеть ШИМ”

// Подключите светодиод к пину 9
for (int i = 0; i <= 255; i++) {
  analogWrite(9, i);
  delay(10);
}
// Наблюдайте плавное нарастание яркости!

Эксперимент 2: “Измерь все!”

// Подключите потенциометр к A0
void loop() {
  int val = analogRead(A0);
  Serial.print("Цифровое: "); Serial.print(val);
  Serial.print(" -> Напряжение: "); Serial.println(val * (5.0 / 1023.0));
}

Эксперимент 3: “Пороги срабатывания”

// Поиграйте с разными порогами
if (analogRead(A0) > 512) { // 512 = примерно 2.5V
  // срабатывание при половине диапазона
}

ТИПИЧНЫЕ ОШИБКИ ПРИ ОБУЧЕНИИ

Ошибка 1: “ШИМ = аналоговое напряжение”

Неправильно: “ШИМ создает 2.5V” Правильно: “ШИМ создает импульсы, которые в среднем дают эффект как 2.5V”

Ошибка 2: “1023 = 5V”

Неправильно: “1023 это 5V” Правильно: “1023 соответствует измеренному напряжению 5V”

Ошибка 3: “map() без constrain()”

// ОПАСНО - может выйти за пределы
int brightness = map(analogRead(A0), 0, 1023, 0, 255);

// БЕЗОПАСНО - всегда в диапазоне
int brightness = constrain(map(analogRead(A0), 0, 1023, 0, 255), 0, 255);

РЕАЛЬНЫЕ ПРИМЕРЫ ИЗ ОЛИМПИАД 🎯

Задача с потенциометром:

// Потенциометр на A0 → битовые сдвиги
int potValue = analogRead(A0); // 0-1023
int shiftAmount = map(potValue, 0, 1023, -6, 6); // преобразуем в -6..+6

Задача с управлением яркостью:

// Кнопка переключает уровни яркости 33%-66%-100%
// 33% от 255 = 84, 66% = 168, 100% = 255
analogWrite(ledPin, currentBrightness);

РАЗЛИЧИЯ АЦП В РАЗНЫХ КОНТРОЛЛЕРАХ 🔌

ARDUINO UNO 🎯

Разрешение: 10 бит (0-1023)
Напряжение: 0-5V
Каналов: 6 (A0-A5)
Частота: ~10 кГц
Точность: ±2 LSB

ESP32 🚀

Разрешение: 12 бит (0-4095)
Напряжение: 0-3.3V
Каналов: 18 (но некоторые ограничения)
Частота: до 100 кГц
Точность: зависит от версии, могут быть проблемы с линейностью

// В коде:
analogReadResolution(12); // устанавливаем 12 бит
int value = analogRead(A0); // 0-4095

RASPBERRY PI PICO (RP2040) 🍓

Разрешение: 12 бит (0-4095)
Напряжение: 0-3.3V  
Каналов: 5 (3 аналоговых + 2 с АЦП)
Частота: до 500 кГц
Точность: хорошая линейность

RASPBERRY PI (полноценная) 🥧

Внимание! У обычных Raspberry Pi НЕТ встроенного АЦП!
Нужно использовать внешние модули:
- MCP3008 (10 бит, 8 каналов)
- ADS1115 (16 бит, 4 канала)

ПРАКТИЧЕСКИЕ РАЗЛИЧИЯ ДЛЯ ОЛИМПИАД 🏆

Arduino Uno (основной для олимпиад):

// Стандартные диапазоны:
int sensorValue = analogRead(A0);     // 0-1023
analogWrite(pin, brightness);         // 0-255

// Преобразование в вольты:
float voltage = sensorValue * (5.0 / 1023.0);

ESP32 (если используется):

// Настройка и чтение:
analogReadResolution(12);             // устанавливаем 12 бит
int sensorValue = analogRead(A0);     // 0-4095

// ШИМ на ESP32:
ledcSetup(0, 5000, 8);               // канал 0, 5кГц, 8 бит
ledcAttachPin(ledPin, 0);            // привязываем пин
ledcWrite(0, brightness);            // 0-255

// Преобразование в вольты:
float voltage = sensorValue * (3.3 / 4095.0);

СРАВНИТЕЛЬНАЯ ТАБЛИЦА 📊

ПараметрArduino UnoESP32RPi Pico
Разрешение АЦП10 бит12 бит12 бит
Диапазон значений0-10230-40950-4095
Напряжение0-5V0-3.3V0-3.3V
Шаг напряжения~4.9mV~0.8mV~0.8mV
ШИМ разрешение8 битдо 16 бит16 бит
Стоимость₽₽

КАК ЭТО ВЛИЯЕТ НА ОЛИМПИАДНЫЕ ЗАДАНИЯ? 🎯

Для Arduino Uno (основной стандарт):

// Все задания рассчитаны на эти диапазоны:

// Потенциометр для битовых сдвигов:
int potValue = analogRead(A0); // 0-1023
int bits = map(potValue, 0, 1023, 0, 6); // преобразуем в 0-6 бит

// Управление яркостью:
analogWrite(ledPin, 128); // 50% яркости (128/255)

Если бы использовали ESP32:

// Пришлось бы адаптировать:

// Потенциометр для битовых сдвигов:
int potValue = analogRead(A0); // 0-4095
int bits = map(potValue, 0, 4095, 0, 6); // тот же результат!

// Управление яркостью:
ledcWrite(0, 128); // 50% яркости (128/255) - тот же диапазон!

ПОЧЕМУ В ОЛИМПИАДАХ ИСПОЛЬЗУЮТ ARDUINO UNO? 🤔

Преимущества для образования:

  • Простота - единый стандарт для всех участников
  • Предсказуемость - одинаковое поведение на всех устройствах
  • Документация - огромное количество учебных материалов
  • Стоимость - доступность для школ
  • Надежность - проверенная временем платформа

Единые условия для всех:

// Каждый участник работает в одинаковых условиях:
- Тот же диапазон АЦП: 0-1023
- Тот же ШИМ: 0-255  
- Тот же вольтаж: 5V
- Те же пины и распиновка

ЧТО ДЕЛАТЬ, ЕСЛИ ПОПАЛСЯ ДРУГОЙ КОНТРОЛЛЕР? 🔧

Универсальный подход:

// Вместо "магических чисел" используем константы:

#define ADC_MAX 1023    // для Arduino Uno
// #define ADC_MAX 4095 // для ESP32
// #define ADC_MAX 4095 // для Pico

#define PWM_MAX 255     // для всех 8-битных ШИМ

// Тогда код становится универсальным:
int sensorValue = analogRead(A0);
int brightness = map(sensorValue, 0, ADC_MAX, 0, PWM_MAX);

Адаптация под разные платформы:

// В начале программы определяем платформу:
#ifdef ARDUINO_AVR_UNO
  #define ADC_MAX 1023
  #define VOLTAGE_MAX 5.0
#elif defined(ESP32)
  #define ADC_MAX 4095  
  #define VOLTAGE_MAX 3.3
#endif

// Далее используем эти константы:
float voltage = analogRead(A0) * (VOLTAGE_MAX / ADC_MAX);

ВЫВОД ДЛЯ ОЛИМПИАДНЫХ ЗАДАНИЙ 🎓

Главное правило:

“Олимпиадные задания написаны для Arduino Uno с его 10-битным АЦП (0-1023) и 8-битным ШИМ (0-255)”

Что нужно запомнить:

  1. Arduino Uno = 0-1023 для аналогового чтения
  2. Arduino Uno = 0-255 для ШИМ
  3. 5V - рабочее напряжение
  4. Пины с ~ поддерживают ШИМ

Если встретите другие контроллеры:

  • Читайте документацию к конкретной плате
  • Проверяйте разрешение АЦП и диапазон напряжений
  • Адаптируйте код используя map() и константы