# 📘 Учебное пособие: мигающий светодиод на ESP32

Это самый простой компонент репозитория — стартовая точка перед тем, как переходить
к датчикам и классам. Пособие проведёт тебя от "зачем вообще разбивать код на файлы"
до "могу объяснить каждую строчку `led.cpp`".

## Чему ты научишься

- Зачем код делят на заголовочный файл (`.h`) и файл реализации (`.cpp`).
- Разницу между `const` и `constexpr` и почему для номера пина нужен именно `constexpr`.
- Как одна и та же переменная (`LED_PIN`) используется в трёх разных файлах проекта.
- Что такое блокирующий код и почему `delay()` — это упрощение, у которого есть цена.

---

## 1. Зачем делить код на файлы

Всю программу можно было бы написать в одном файле `main.cpp`. Для мигания одним
светодиодом это работает. Но в реальных прошивках счёт строк идёт на тысячи, и один
файл становится нечитаемым.

Поэтому код разбит на два файла с разными ролями:

- [`include/led.h`](include/led.h) — **что** умеет делать модуль (объявления: имя пина,
  имена функций). Другие файлы подключают этот файл, чтобы узнать, что доступно.
- [`src/led.cpp`](src/led.cpp) — **как** это делается (реализация функций).
- [`src/main.cpp`](src/main.cpp) — точка входа: вызывает функции из `led.h`, не зная,
  как именно они устроены внутри.

Это даёт две практические выгоды: компилятор может собирать `led.cpp` отдельно от
`main.cpp` (что ускоряет пересборку больших проектов), а другой человек может
пользоваться `setupLED()`/`toggleLED()`, вообще не открывая `led.cpp`.

```cpp
#ifndef LED_H
#define LED_H
...
#endif // LED_H
```

Строки [1, 2 и 27 в led.h](include/led.h#L1-L2) — **include guard**, защита от
повторного включения. Если `led.h` случайно попадёт в один файл дважды (например,
через цепочку из других `#include`), компилятор не станет читать его содержимое
второй раз — это защищает от ошибки "переопределение".

## 2. `const` против `constexpr`

Номер пина задан так:

```cpp
constexpr uint8_t LED_PIN = 2;
```

[include/led.h:14](include/led.h#L14)

Можно было бы написать `const uint8_t LED_PIN = 2;` — код скомпилируется и будет
работать точно так же. Разница — в том, **что происходит при компиляции**:

- `const` в C++ гарантирует только "нельзя изменить после инициализации". Компилятор
  вправе (а для глобальных `const` в разных единицах трансляции — почти всегда обязан)
  зарезервировать под неё реальную ячейку памяти.
- `constexpr` — более сильное требование: "значение известно уже на этапе компиляции".
  Компилятор не резервирует память под переменную, а подставляет число `2` напрямую
  в машинный код везде, где встречается `LED_PIN`. Для микроконтроллера с килобайтами
  RAM это не абстрактная экономия — лишняя переменная на каждую константу в проекте
  из тысяч строк складывается в заметный расход памяти.

Практическое правило: если значение константы известно заранее и не меняется —
используй `constexpr`, а не `const`.

## 3. Как устроено мигание

### 3.1 Инициализация — `setupLED()`

```cpp
void setupLED() {
    pinMode(LED_PIN, OUTPUT);
}
```

[src/led.cpp:3-5](src/led.cpp#L3-L5)

По умолчанию все пины ESP32 находятся в режиме входа (ожидают сигнал извне).
`pinMode(LED_PIN, OUTPUT)` переключает `GPIO2` в режим выхода — теперь пин может сам
подавать напряжение. Эта функция вызывается один раз из `setup()` в
[src/main.cpp:5](src/main.cpp#L5) — настройка не должна повторяться на каждой
итерации `loop()`.

### 3.2 Мигание — `toggleLED()`

```cpp
void toggleLED(uint32_t delayMs) {
    digitalWrite(LED_PIN, HIGH); // Подаём напряжение — светодиод загорается
    delay(delayMs);
    digitalWrite(LED_PIN, LOW);  // Снимаем напряжение — светодиод гаснет
    delay(delayMs);
}
```

[src/led.cpp:7-12](src/led.cpp#L7-L12)

`digitalWrite(LED_PIN, HIGH)` подаёт на пин напряжение (3.3 В) — через резистор и
светодиод начинает течь ток, диод загорается (схема — [WIRING.md](WIRING.md)).
`delay(delayMs)` останавливает выполнение на `delayMs` миллисекунд. `LOW` снимает
напряжение — диод гаснет. Один вызов `toggleLED(500)` — это полный цикл "моргнуть
один раз" длиной в секунду (500 мс горит + 500 мс не горит).

Параметр `delayMs` — `uint32_t` (беззнаковое 32-битное число), а не `int`, потому что
время в миллисекундах не бывает отрицательным, а `int` (обычно 16 или 32 бита в
зависимости от платформы) может не хватить для очень больших пауз.

### 3.3 Главный цикл — `main.cpp`

```cpp
void setup() {
    setupLED();
}

void loop() {
    toggleLED(500);
}
```

[src/main.cpp:4-10](src/main.cpp#L4-L10)

`setup()` вызывается один раз при включении платы. `loop()` — это тело бесконечного
цикла, который фреймворк Arduino запускает после `setup()`; каждый вызов `toggleLED(500)`
здесь и есть одно "моргание".

## 4. Блокирующий код — и его цена

`delay()` не просто "ждёт" — на время ожидания **весь процессор простаивает** и не
может делать ничего другого: ни читать датчики, ни отвечать по Wi-Fi/Bluetooth, ни
проверять кнопку. Пока в проекте один светодиод и больше ничего не происходит, это
не проблема. Но как только нужно, например, одновременно мигать светодиодом и следить
за показаниями датчика — блокирующий `delay()` начинает мешать: пока он "спит",
датчик может пропустить событие.

Решение — неблокирующая логика на счётчике `millis()` вместо `delay()`: вместо "спать
500 мс" код каждую итерацию `loop()` спрашивает "прошло ли уже 500 мс с последнего
переключения" и, если да, переключает пин, не останавливая исполнение. Этот паттерн,
класс-драйвер для группы светодиодов и эффект "бегущего огня" разобраны в следующем
компоненте — [`Components/2. LED_middle`](../LED_middle/README.md).

---

## 5. Проверь себя

Постарайся ответить, не подглядывая в код, потом сверься.

1. Что изменится в скомпилированной прошивке, если заменить `constexpr uint8_t LED_PIN`
   на `const uint8_t LED_PIN`?
   <details><summary>Ответ</summary>Код продолжит работать так же, но для <code>LED_PIN</code> может быть зарезервирована реальная ячейка памяти вместо подстановки числа <code>2</code> прямо в машинный код — то есть чуть больше расхода RAM без какой-либо пользы, так как значение и так известно заранее.</details>

2. Если вызвать `toggleLED(0)`, светодиод погаснет или будет мигать бесконечно быстро?
   <details><summary>Ответ</summary><code>delay(0)</code> не блокирует выполнение — <code>digitalWrite</code> в HIGH и LOW отработают практически мгновенно один за другим. Диод физически не успеет заметно моргнуть глазом и будет казаться постоянно тускло горящим (либо погашенным) — это не деление на ноль и не отказ, просто вырожденный, но валидный случай.</details>

3. Почему `setupLED()` вызывается из `setup()`, а не из `loop()`?
   <details><summary>Ответ</summary><code>pinMode()</code> — это одноразовая настройка режима пина. Если вызывать её из <code>loop()</code>, она будет повторяться на каждой итерации без всякой пользы и просто тратить время процессора.</details>

4. Почему в этом компоненте нет неблокирующего мигания на `millis()`, как в `2. LED_middle`?
   <details><summary>Ответ</summary>Это осознанное упрощение: <code>3. LED_simple</code> — вводный шаг, который показывает модульность (<code>.h</code>/<code>.cpp</code>) и базовый GPIO без класса и без <code>millis()</code>. Неблокирующая логика на классе-драйвере — следующий шаг, разобранный в <code>2. LED_middle</code>.</details>

## Куда дальше

Разобрался с базовым миганием? Следующий шаг — неблокирующее мигание, группы
светодиодов и класс-драйвер: [`Components/2. LED_middle`](../LED_middle/README.md).
