1. Тема дня
Дискретные входы через оптопары: что реально происходит между внешним сигналом 220 В / 24 В / 12 В и GPIO ESP32/STM32, почему на выходе могут быть пульсации, дребезг, задержки, ложные фронты. Главная мысль: Оптопара не превращает внешний сигнал в идеальный логический уровень. Она превращает его в ток через светодиод, затем в ток фототранзистора, а уже потом схема подтяжки и фильтрации делает уровень для MCU.
2. Зачем это нужно
Входы фаз светофора могут быть:
220 В AC
24 В DC
12 В DC
промышленная линия
длинный кабель с помехами
сигнал через реле/оптопару/модульА MCU видит только GPIO 0/1. Между ними:
- AC после оптопары может пульсировать 50/100 Гц;
- фототранзистор медленно выходит из насыщения;
- CTR оптопары гуляет;
- длинный кабель ловит помехи;
- слабая подтяжка даёт медленные фронты;
- GPIO-прерывания могут ловить ложные события.
Поэтому нельзя просто:
if (gpio_get_level(INPUT_RED)) {
phase = RED;
}3. Базовая схема
Внешний сигнал
-> токоограничивающий резистор
-> LED внутри оптопары
-> световая связь
-> фототранзистор
-> pull-up / pull-down
-> GPIO ESP32/STM32Типовая схема:
3.3 В
│
Rpullup
│
├── GPIO
│
collector оптопары
emitter
│
GNDЧасто есть инверсия:
внешний сигнал есть -> GPIO = 0
внешнего сигнала нет -> GPIO = 1В коде это надо задавать явно:
#define INPUT_ACTIVE_LEVEL 04. DC проще, чем AC
Для 24 В DC:
24 В есть -> LED оптопары светит постоянно
24 В нет -> LED не светитДля 220 В AC:
+ полуволна
0
- полуволна
0Если схема проводит только одну полуволну, выход будет импульсным 50 Гц. Если обе - 100 Гц. Поэтому наличие 220 В AC часто нужно детектировать как:
за последние 20-50 мс были импульсы?а не как:
какой уровень GPIO прямо сейчас?5. Программный AC detector
#define AC_PRESENT_TIMEOUT_MS 40
typedef struct {
int64_t last_active_us;
bool present;
} ac_input_detector_t;Логика:
если увидели активный уровень:
last_active_us = now
если now - last_active_us < 40 мс:
AC присутствует
иначе:
AC отсутствуетДля сети 50 Гц период 20 мс. Окно 30-50 мс обычно перекрывает несколько полупериодов.
6. Pull-up
Внутренняя подтяжка MCU часто слабая и с большим разбросом. Для оптопарного входа лучше внешний pull-up:
10 kΩ - экономичнее, фронты медленнее
4.7 kΩ - быстрее и устойчивее
2.2 kΩ - ещё жёстче, но больше ток через фототранзистор7. CTR оптопары
CTR = ток фототранзистора / ток LED. Он зависит от:
- экземпляра;
- температуры;
- старения;
- тока LED;
- партии;
- режима насыщения.
Надо проверять худший случай:
минимальный ток LED
минимальный CTR
максимальная температура
старение
самая жёсткая подтяжка8. RC-фильтр
RC-фильтр сглаживает помехи, но добавляет задержку.
τ = R × CПример:
R = 10 kΩ
C = 100 nF
τ = 1 мсА вот 100 кΩ и 1 мкФ дадут 100 мс - это может слишком сильно задерживать события.
9. Schmitt trigger
Если фронты медленные или шумные, полезен буфер с гистерезисом:
внешний сигнал
-> защита
-> токоограничение
-> оптопара
-> pull-up
-> RC / фильтр
-> Schmitt trigger / компаратор
-> GPIO MCUГистерезис убирает дрожание около порога.
10. GPIO interrupt или polling
GPIO interrupt:
- быстрый timestamp;
- хорошо для чистых фронтов;
- плохо для грязного AC после оптопары.
Polling:
- проще фильтровать;
- меньше проблем с шумом;
- удобно для HIL;
- timestamp имеет погрешность до периода опроса.
Для светофора лучше начинать с polling 1-5 мс.
11. Детектор DC-входа
Для DC можно использовать integrator:
typedef struct {
uint8_t acc;
bool stable;
} dc_input_filter_t;
#define FILTER_MAX 10
#define FILTER_ON_THRESHOLD 8
#define FILTER_OFF_THRESHOLD 2
static void dc_input_update(dc_input_filter_t *f, bool raw_active)
{
if (raw_active) {
if (f->acc < FILTER_MAX) f->acc++;
} else {
if (f->acc > 0) f->acc--;
}
if (f->acc >= FILTER_ON_THRESHOLD) {
f->stable = true;
} else if (f->acc <= FILTER_OFF_THRESHOLD) {
f->stable = false;
}
}12. Связь с архитектурой
input_driver
читает raw GPIO
input_filter
превращает raw в stable_present
phase_detector
определяет RED/YELLOW/GREEN/OFF/CONFLICT
phase_task
создаёт phase_event_t с timestamp
transport_task
отправляет событие13. Конфликты фаз
RED = 1, YELLOW = 0, GREEN = 0 -> RED
RED = 0, YELLOW = 1, GREEN = 0 -> YELLOW
RED = 0, YELLOW = 0, GREEN = 1 -> GREEN
RED = 0, YELLOW = 0, GREEN = 0 -> OFF/UNKNOWN
RED = 1, YELLOW = 1, GREEN = 0 -> RED_YELLOW или TRANSITION
RED = 1, YELLOW = 0, GREEN = 1 -> CONFLICTНевозможные комбинации нужно явно логировать.
14. Типичные ошибки
- читать AC-вход как обычный уровень;
- использовать один фильтр для AC и DC;
- ставить слишком длинный debounce;
- не ставить внешнюю подтяжку;
- забыть про инверсию;
- делать тяжёлую обработку в GPIO ISR;
- не измерить реальный сигнал осциллографом.
15. Практическое задание
Создай INPUT_FILTER_POLICY.md:
# Input filter policy
## Input types
- AC optocoupler input:
Detect presence of pulses within a time window.
Do not use instantaneous GPIO level as final state.
- DC optocoupler input:
Use integrator/debounce filter.
## Timing
- Polling period: 1-5 ms
- AC present timeout: start with 40 ms for 50 Hz mains
- Stable confirm time: 20-50 ms
- Timestamp policy:
Capture raw sample timestamp immediately.
## Signal inversion
All input channels must define active level explicitly.
## Diagnostics
Track:
- raw pulse count
- stable transitions
- glitches
- time since last pulse
- impossible phase combinations16. Короткий итог
Оптопара не даёт идеальный логический уровень. Для AC-входа правильно думать:
сигнал активен,
если за последние 30-50 мс были импульсыа не:
сигнал активен,
если gpio_get_level() прямо сейчас равен active_levelЗадание
По τ=R×C вычислите nominal time constant для R=10 kΩ, C=100 nF. Чего это значение не доказывает для полного input detector?
Критерии самопроверки: Получите 1 ms и отличите RC time constant от полного timing detector или hardware validation.
Показать ответ автора
τ=10000×100×10^-9 s=0.001 s=1 ms. Это RC time constant, не полная latency phase detection и не validated threshold. Поведение оптопары, форма сигнала, polling и confirmation policy также влияют на detector; source examples требуют измерений конкретной схемы.