208а. Прибор от 24 В: промышленное питание, защита входа и цифровые входы
Раздел 208 провёл прибор через границу сети 230 В. Второе типичное питание изделия — шина 24 В постоянного тока в шкафу автоматики: блок питания на рейке, а от него — контроллеры, датчики, реле, клапаны. Трогать её безопасно, но жить на ней трудно. Шину делят с контакторами и соленоидами, провода к датчикам тянутся на десятки метров, клеммы подключают под напряжением и путают полярность. Здесь — что бывает на шине, как собрать вход питания прибора, как читать цифровой вход 24 В микроконтроллером на 3,3 В и как управлять катушкой с выхода, чтобы ключ пережил её отключение.
Опоры: расчёт оптрона из 54.2 (коэффициент передачи 50…600 %, старение
вдвое), защита из 173 (P-канальный в плюсовом проводе, ограничитель),
верхний ключ из 172.2, пороги выводов — даташиты STM32G474 (DS12288) и
STM32F103 (DocID13587) из паков Keil. Числа — модели ngspice в контейнере
stm-lab:1 (revpol24.cir, hotplug.cir, input.cir, coil.cir) и
расчёт calc208a.py; код собран armclang для обоих МК и прогнан тестом на
компьютере. Стандартов на промышленные источники и входы контроллеров на
диске нет: 18…30 В ниже — рамка расчёта, та же, что в 54.2, а не допуск
из стандарта. Параметры катушки, кабеля и ограничителя условные — это
типичные величины для расчёта, а не данные конкретной детали.
Как это называется в документации
Оригинал Перевод 24 V DC digital input, type 1 / type 3 дискретный вход 24 В и его типы по стандарту на контроллеры sinking input / sourcing output вход, принимающий ток датчика / выход, отдающий ток в нагрузку two-wire sensor, off-state leakage двухпроводной датчик и его ток в выключенном состоянии current-limited input вход с ограничителем тока hot plugging подключение под напряжением LC input filter входной фильтр из дросселя и конденсатора inductive load demagnetization гашение энергии катушки после выключения vertical counter вертикальный счётчик: биты счётчиков всех входов в соседних словах sensor supply monitoring контроль питания датчиков
208а.1 Шина 24 В: что на ней бывает
Напряжение гуляет. Блок питания выдаёт 24 В, но на клеммах прибора в дальнем конце шкафа, при просадке под нагрузкой и после подстройки бывает и меньше, и больше. Считать всё удобно в рамке 18…30 В — размах 12 В. Нижний край проверяют на пороги входов и запас стабилизатора, верхний — на нагрев резисторов (54.2) и предел затвора (ниже, 208а.2).
Соседи отключают катушки. Контактор или клапан на той же шине —
индуктивность с запасённой энергией. Условная катушка на 24 В, 48 Ом,
0,3 Гн берёт 0,5 А, её постоянная времени τ = L/R = 6,25 мс, а энергия
W = L·I²/2 = 37,5 мДж — в 833 раза больше, чем у реле из 51.2. Если сосед
разрывает её контактом без гасящей цепи, напряжение стремится к
U = I·√(L/C): при 1 нФ ёмкости обмотки и проводов это 8,7 кВ. Столько
не бывает — раньше пробивается воздух между контактами, и в шину уходит
та самая пачка наносекундных импульсов из 203.3. Гасящие цепи соседа —
не в нашей власти, поэтому вход прибора рассчитывают на то, что до него
дойдёт.
Клеммы подключают под напряжением. Провод от шины втыкают в работающий шкаф, и кабель с конденсатором на входе прибора звенит: на шине 30 В вход видит больше 50 В (208а.3).
208а.2 Предохранитель и переполюсовка на двух амперах
Первым от клеммы стоит предохранитель — по току самого прибора, а не шкафа: автомат в шкафу защищает провод, а плату защищает только свой. Здесь прибор берёт до 2 А — выходы и логика вместе. Самовосстанавливающийся удобен, но срабатывает за сотни миллисекунд (173.2); ключи выходов должны пережить ток короткого замыкания хотя бы это время или ограничивать его сами.
Схемы защиты от переполюсовки те же, что в 173.1, но ток вдвое больше, а шина — 24 В. Модель: нагрузка 12 Ом, 2 А.
| Схема | Падение | Потери | Ток при −24 В |
|---|---|---|---|
| кремниевый диод последовательно | 0,98 В | 1,88 Вт | 7 нА |
| диод Шоттки последовательно | 0,49 В | 0,95 Вт | 30 мкА |
| P-канальный в плюсовом проводе | 0,116 В | 0,23 Вт | 25 пА |
Потери диода растут как U·I, а у транзистора — как I²·R: канал в 58 мОм
на 2 А теряет 0,23 Вт, в восемь раз меньше кремниевого диода. На двух
амперах почти два ватта в одной детали — заметный нагрев, поэтому на таких
токах выбирают транзистор.
Затвор на 24 В без стабилитрона сгорит. Затвор подтянут к минусу, и напряжение затвор-исток равно напряжению шины: на верхнем краю — 30 В при типичном пределе ±20 В (172.2, 173.1). Стабилитрон 12 В между затвором и истоком с резистором 100 кОм держит в модели −11,9 В и на 24, и на 30 В; его ток — от 60 мкА на 18 В до 180 мкА на 30 В, мощность — не больше 2,2 мВт. Падение на канале при 30 В — 0,145 В: нагрузка 12 Ом берёт уже 2,5 А.
208а.3 Ограничитель, горячее подключение и фильтр перед DC-DC
Ограничитель выбирают по трём напряжениям. Рабочее — выше верха шины,
иначе он подтекает и греется всё время: для 30 В годится 33 В, запас 3 В.
Пробой — выше рабочего. А под импульсом на нём напряжение ограничения
Uогр = Uпроб + Rд·I (173.3). Условный ограничитель — рабочее 33 В,
пробой 37 В, динамическое сопротивление 1,5 Ом — даёт:
| Ток импульса | Напряжение ограничения |
|---|---|
| 7,9 А | 48,9 В |
| 10 А | 52,0 В |
| 20 А | 67,0 В |
Отсюда правило: всё за ограничителем — транзистор защиты, электролит, вход DC-DC — берут с запасом над напряжением ограничения, а не над 30 В шины. Реальные числа — строка «напряжение ограничения при импульсном токе» в документации выбранной детали; здесь их нет, числа условные.
Горячее подключение. Кабель — индуктивность (условно 3 мкГн и 0,1 Ом,
метр-три пары проводов), на входе прибора — керамика 10 мкФ. Вместе это
колебательный контур с волновым сопротивлением Z0 = √(L/C) = 0,55 Ом и
затуханием ζ = R/(2·Z0) = 0,091 — почти без потерь, резонанс 29,1 кГц.
Ступенька 30 В раскачивает его до U·(1 + e^(−πζ/√(1−ζ²))) = 52,5 В:
перерегулирование 75 %. Модель hotplug.cir:
| Что на входе | Пик на входе | Что ещё |
|---|---|---|
| только керамика 10 мкФ | 52,2 В | — |
| керамика и ограничитель | 49,3 В | 7,9 А и 3,3 мДж в ограничителе |
| керамика и электролит 47 мкФ, 0,5 Ом | 33,3 В | — |
Ограничитель почти не помогает: его пробой 37 В плюс 1,5 Ом · 7,9 А — те
же 49 В. Помогает затухание: электролит с внутренним сопротивлением
0,5 Ом — порядка Z0 — гасит звон до 33,3 В. Поэтому на входе 24 В рядом
с керамикой ставят электролит, и его «плохое» сопротивление здесь —
достоинство.
Фильтр перед DC-DC. Импульсный стабилизатор берёт ток рывками на
частоте ключа и возвращает их в шину — а по шине они дойдут до соседей.
Дроссель 10 мкГн и конденсатор 10 мкФ дают резонанс 15,9 кГц и Z0 = 1 Ом;
на частоте ключа 500 кГц такой фильтр ослабляет помеху в (f/f0)² = 987 раз,
на 60 дБ. Но это снова LC-контур: на своём резонансе он не ослабляет, а
раскачивает, и тот же электролит с внутренним сопротивлением нужен и здесь.
Разводка и выбор дросселя — раздел 58, фильтры питания — 168.
Почему DC-DC, а не линейный. Логике нужно 3,3 В при 100 мА. Линейный
от 24 В рассеивает (Uвх − Uвых)·I = 2,07 Вт, от 30 В — 2,67 Вт (тепловой
расчёт — 55.4). Импульсный с условным КПД 0,85 теряет 58 мВт и берёт от
шины 16,2 мА вместо 100 мА. Обычная цепочка: понижающий 24 → 5 В
(58.8), за ним LDO на 3,3 В или сразу понижающий на 3,3 В.
208а.4 Цифровой вход 24 В: пороги, ток и мощность
К входу подключают контакт реле, концевик или датчик, который выдаёт на клемму плюс шины. Стандарт на контроллеры делит такие входы на типы по порогам и току; тип 3 придуман ради двухпроводных датчиков, которые и в выключенном состоянии пропускают через себя ток. Таблицы стандарта на диске нет, поэтому пороги ниже выведены из оптрона и схемы, а не взяты из стандарта; перед сертификацией их сверяют с ним.
Критерии задаёт оптрон. На выходе — подтяжка 22 кОм к 3,3 В, это 0,15 мА. Коэффициент передачи считают по 54.2: минимум 50 % пополам на старение, максимум 600 %, и ещё запас 2 в обе стороны.
- «1»: светодиод должен получить не меньше
2 · 0,15 мА / 0,25= 1,2 мА; - «0»: не больше
0,15 мА / 2 / 6= 12,5 мкА.
Схема. Диод от переполюсовки, затем элемент, задающий ток, стабилитрон
9,1 В — он и есть порог: пока на входе меньше суммы падений на диоде,
стабилитроне и светодиоде, ток почти не идёт (при 5 В в модели — 6 пА), —
и светодиод оптрона, зашунтированный резистором 680 Ом. Ток задаёт либо
резистор 1,5 кОм, либо ограничитель на двух транзисторах: 47 кОм в базу,
датчик тока 200 Ом, около 0,65 В / 200 Ом. Модель input.cir, развёртка
входа от −31 до +31 В:
| Резистор 1,5 кОм | Ограничитель тока | |
|---|---|---|
| «1» гарантирована от | 14,9 В | 13,3 В |
| «0» гарантирован до | 12,3 В | 11,7 В |
| ток входа при 15 В | 2,78 мА | 2,93 мА |
| ток входа при 24 В | 8,60 мА | 3,33 мА |
| ток входа при 30 В | 12,5 мА | 3,51 мА |
| ток светодиода при 24 В | 6,91 мА | 1,78 мА |
| мощность при 24 В | 206 мВт | 80 мВт |
| мощность при 30 В | 376 мВт | 105 мВт |
| ток при −30 В | 2,5 нА | 2,5 нА |
Между двумя порогами — зона, где не гарантировано ничего: вход может показать и «0», и «1». Так и задумано: провод с напряжением посередине — неисправность, а не сигнал.
Резистор честен, но греется. У резистора ток растёт с напряжением: на 30 В схема рассеивает 376 мВт, из них 235 мВт в самом резисторе и 117 мВт в стабилитроне. Ограничитель держит ток почти постоянным, и на 30 В вся схема берёт 105 мВт, из них 65 мВт в транзисторе (18,5 В на нём). На шестнадцати входах это 1,7 Вт против 6,0 Вт — разница, которую видно по температуре платы в закрытом шкафу.
Шунт 680 Ом — от утечки датчика. Выключенный двухпроводной датчик пропускает ток, в модели — 1 мА. Он поднимает вход до 11,5 В (с резистором — до 11,9 В), но светодиод получает 1,0 мкА: весь миллиампер уходит в шунт и создаёт на нём 0,68 В, а при таком напряжении светодиод почти закрыт. Без шунта весь миллиампер после порога стабилитрона пошёл бы в светодиод — между критериями «0» и «1», то есть вход мог бы показать что угодно.
208а.5 Сторона МК: RC-фильтр и антидребезг в прошивке
Аппаратный фильтр — микросекунды. На выводе МК подтяжка 22 кОм и
конденсатор 2,2 нФ, τ = R·C = 48,4 мкс. Порог единицы у STM32G474 —
0,7·VDD, нуля — 0,3·VDD (DS12288); у STM32F103 — 0,65·VDD и 0,35·VDD
(DocID13587). Когда оптрон гаснет, вывод поднимается подтяжкой до
порога единицы за 58,3 мкс у G474 и 50,8 мкс у F103. Когда загорается,
транзистор оптрона тянет вниз не меньше 1,2 мА · 0,25 = 0,3 мА, из них
0,15 мА уходит в подтяжку, а 0,15 мА разряжает конденсатор: до порога
нуля — 33,9 мкс у G474 и 31,5 мкс у F103. Наносекундные пачки из 203.3 на
этом умирают, а дребезг контакта длится миллисекунды и проходит — его
убирает прошивка. Как считают RC на выводе — 62.7.
Программный фильтр — миллисекунды. Опрос раз в 1 мс, вход признаётся
новым уровнем после четырёх подряд одинаковых отсчётов: задержка 4 мс,
импульс короче 4 мс не пройдёт, предел частоты 1/(2 · 4 мс) = 125 Гц.
В 198в.6 такой фильтр сделан счётчиком на каждый вход; здесь — сразу на
шестнадцать входов порта, без цикла и без ветвлений. Для каждого входа
счётчик на два бита, но хранятся они «вертикально»: младшие биты всех
шестнадцати счётчиков — в одном слове cnt0, старшие — в cnt1. Одна
операция над словом — шаг счёта для всех входов разом:
/* Четыре подряд отсчёта, не совпавших с признанным состоянием, — и вход
* переключается. Все входы разом, без ветвлений. Возвращает переключившиеся. */
uint32_t di_debounce(di_t *d, uint32_t raw)
{
uint32_t delta = (raw ^ d->state) & DI_MASK; /* где отсчёт спорит с состоянием */
uint32_t toggle = delta & d->cnt0 & d->cnt1; /* счётчик был на 3 — это 4-й */
d->glitch |= (d->cnt0 | d->cnt1) & ~delta; /* счёт начался и оборвался */
d->cnt1 = (d->cnt1 ^ d->cnt0) & delta; /* +1, а при согласии — сброс */
d->cnt0 = ~d->cnt0 & delta;
d->state ^= toggle;
return toggle;
}
Где отсчёт совпал с состоянием, delta — ноль, и оба бита счётчика
обнуляются; где спорит — счётчик прибавляет единицу, а на четвёртом
несогласном отсчёте бит состояния переворачивается. armclang с -O2
для G474 превращает функцию в 20 команд без единого перехода: время
опроса не зависит от того, что творится на входах.
Диагностика. Сам по себе антидребезг скрывает неисправности: вход, который скачет раз в 10 мс, никогда не переключится и будет выглядеть «просто выключенным». Поэтому опрос считает сырые перепады и следит за питанием датчиков:
/* Опрос раз в 1 мс: port — чтение порта как есть, supply_ok — питание датчиков
* в норме (свой вход или АЦП). Без питания входы не признаются — замораживаются. */
uint32_t di_poll(di_t *d, uint32_t port, int supply_ok)
{
uint32_t raw = (port ^ DI_ACTIVE_LOW) & DI_MASK;
uint32_t toggle = 0u;
uint32_t e = raw ^ d->last_raw; /* сырые перепады — для диагностики */
d->last_raw = raw;
d->supply_ok = (uint8_t)(supply_ok != 0);
if (d->supply_ok) {
toggle = di_debounce(d, raw);
} else {
d->cnt0 = 0u; /* без питания нули — не сигнал */
d->cnt1 = 0u;
}
for (uint32_t i = 0u; e != 0u; i++, e >>= 1) {
if ((e & 1u) != 0u && d->edges[i] < UINT16_MAX) {
d->edges[i]++;
}
}
if (++d->window_ms >= DI_WINDOW_MS) { /* окно закрыто: итог по дребезгу */
uint32_t ch = 0u;
for (uint32_t i = 0u; i < DI_COUNT; i++) {
if (d->edges[i] > DI_CHATTER_MAX) {
ch |= 1u << i;
}
d->edges[i] = 0u;
}
d->chatter = ch;
d->window_ms = 0u;
}
return toggle;
}
Больше 20 перепадов за окно 1000 мс — флаг «дребезжит», и
di_valid_mask() исключает вход из годных. Флаг glitch поднимается,
когда счёт начался и оборвался, — была помеха короче четырёх отсчётов; он
липкий, прошивка сбрасывает его сама, прочитав. Если пропало питание
датчиков, все входы разом падают в ноль — это не сигнал, а авария:
состояние замораживается, годных входов нет. Пороги и размеры проверены
_Static_assert; порт читается одной командой — у G474 по адресу
0x4800 0410, у F103 по 0x4001 0C08 (регистр IDR у них на разных
смещениях, и это тоже проверено при сборке).
Тест di_test.c на компьютере:
| Опыт | Результат |
|---|---|
| чистая ступенька в 10 мс | признана в 13 мс — на четвёртом отсчёте |
| дребезг 10…15 мс, дальше стоит | одно переключение, в 18 мс; флаг помехи поднят |
| импульсы 1, 2, 3 мс | не прошли; импульс 4 мс прошёл |
| меандр 100 Гц (полупериод 5 мс) | 199 переключений за 1 с из 200 |
| меандр около 167 Гц (полупериод 3 мс) | ни одного переключения |
| вход скачет каждые 10 мс | флаг дребезга, годные входы 0xFFF7; через окно покоя снят |
| питание датчиков пропало на 50 мс | состояние 0x00F0 сохранено, годных 0x0000 |
| 16 ступенек в 10…25 мс | признаны в 13…28 мс, каждая вовремя |
208а.6 Цифровой выход: ключ в плюсе и катушка
В шкафу нагрузку обычно сажают одним концом на минус шины, а ключ ставят в плюс. Провод к нагрузке, замкнутый на корпус шкафа (он на минусе), тогда не включает её — а при ключе в минусе включил бы: катушка уже висит на плюсе. Ключ — P-канальный со сдвигом уровня на NPN из 172.2 и тем же стабилитроном 12 В в затворе, что в 208а.2. Нагрузка — условная катушка из 208а.1: 0,3 Гн, 48 Ом, 0,5 А.
При выключении ключа ток катушки продолжает течь и тянет клемму выхода
ниже минуса — ровно настолько, насколько нужно, чтобы нашёлся путь. Какой
путь ему дать — выбор между скоростью и напряжением на ключе. Модель
coil.cir (транзистор с пробоем 40 В, время — от момента выключения):
| Гасящая цепь | Клемма выхода | Спад до 10 % | Куда ушла энергия |
|---|---|---|---|
| нет | −16,7 В: ключ в пробое | 4,7 мс | 48,4 мДж в ключ |
| диод | −0,87 В | 12,8 мс | 2,3 мДж в диод, остальное в обмотку |
| диод и стабилитрон 12 В | −13,4 В | 5,5 мс | 17,8 мДж в гасящую цепь |
| диод и ограничитель 37 В | −16,7 В: ключ в пробое | 4,7 мс | 48,4 мДж в ключ |
Проверка по модели L/R: ток спадает с постоянной τ к «−Uогр/R», и до
10 % он доходит за τ · ln((I0 + Uогр/R) / (0,1·I0 + Uогр/R)) — 12,68 мс с
диодом и 5,39 мс с диодом и стабилитроном; до нуля во втором случае —
τ · ln(1 + I0·R/Uогр) = 6,42 мс (в модели 6,5 мс), энергия в гасящей
цепи — 17,9 мДж. С одним диодом клапан или реле отпускает вдвое с лишним
позже, а контакты расходятся медленнее (51.2).
Цена скорости — напряжение на ключе. Сток-исток видит шину плюс
напряжение гасящей цепи: 24 + 13,4 = 37,4 В — при пределе 40 В уже впритык,
а на верхнем краю шины 30 + 13,4 = 43,4 В — за пределом. Ключ на 40 В
годится только с более низким стабилитроном; иначе берут ключ на 60 В.
Ограничитель «с запасом» бесполезен. Ограничитель на 37 В последовательно с диодом не успевает открыться: ключ пробивается раньше, на 40 В. В ключ уходит 48,4 мДж — больше, чем было в катушке (37,5 мДж): пока ключ в пробое, шина продолжает подливать через него ток. Без гасящей цепи — то же самое. Переживёт ли ключ такое выключение тысячу раз в день, решает паспортная энергия лавинного пробоя, а её модель не знает.
208а.7 Грабли
Прибор выгорает, когда провод от шины втыкают «на ходу». Керамика
на входе и индуктивность кабеля звенят: шина 30 В даёт на входе 52 В.
Ограничитель спасает мало (49 В), электролит с внутренним сопротивлением
порядка Z0 — спасает (33 В). Входные детали — с запасом над пиком, а не
над 24 В.
Выход с «хорошим» ограничителем горит на каждом выключении. Ограничитель на 37 В вместе с шиной 24 В требует от ключа больше 60 В, а ключ пробивается на 40 В и берёт на себя 48 мДж. Напряжение гасящей цепи складывают с верхом шины и сравнивают с пределом ключа.
Клапан отпускает с опозданием. Один диод на катушке держит ток 12,8 мс вместо 5,5 мс с диодом и стабилитроном. Где важна скорость отпускания, диод дополняют стабилитроном — и пересчитывают напряжение на ключе.
Все входы разом «выключились». Пропало питание датчиков — и опрос честно видит нули. Без контроля питания прошивка примет аварию за команду; с ним состояние замораживается, а годных входов нет.
Вход «горит», хотя датчик выключен. Двухпроводной датчик в выключенном состоянии пропускает около миллиампера — без шунта у светодиода этот ток уходит в оптрон и попадает между «0» и «1». Шунт 680 Ом оставляет светодиоду 1 мкА.
Счётчик импульсов теряет импульсы. Антидребезг 4 мс режет всё короче 4 мс и выше 125 Гц — меандр 167 Гц не даёт ни одного переключения. Для счёта быстрых импульсов вход заводят на таймер (раздел 13), а антидребезг оставляют кнопкам и концевикам.
Линейный стабилизатор от 24 В раскалён. 100 мА до 3,3 В — это 2,07 Вт на 24 В и 2,67 Вт на 30 В. От шины 24 В логику питают через понижающий DC-DC.
208а.8 Итоги
- Шину считают в рамке 18…30 В; катушка соседа (0,3 Гн, 0,5 А) хранит 37,5 мДж — в 833 раза больше реле из 51.2 — и без гасящей цепи шлёт в шину пачки импульсов.
- Переполюсовка на 2 А: диод теряет 1,88 Вт, P-канальный — 0,23 Вт; затвору на 24…30 В нужен стабилитрон 12 В (ток 60…180 мкА).
- Ограничитель: рабочее напряжение выше 30 В, а всё за ним — выше напряжения ограничения (условные 48,9 В при 7,9 А). Горячее подключение даёт 52,2 В на керамике; электролит 47 мкФ / 0,5 Ом снижает до 33,3 В.
- Фильтр 10 мкГн / 10 мкФ ослабляет помеху DC-DC на 500 кГц на 60 дБ; линейный 24 → 3,3 В при 100 мА греется на 2,07 Вт против 58 мВт у импульсного.
- Цифровой вход: оптрон требует 1,2 мА для «1» и не больше 12,5 мкА для «0»; ограничитель тока даёт пороги 13,3 / 11,7 В и 105 мВт на 30 В против 376 мВт у резистора; шунт 680 Ом гасит утечку датчика 1 мА до 1 мкА в светодиоде.
- RC 22 кОм / 2,2 нФ фильтрует десятки микросекунд (58,3 мкс до порога у G474); дребезг снимает прошивка: 4 отсчёта по 1 мс, импульсы короче 4 мс отброшены, предел 125 Гц; 16 входов — 20 команд без переходов.
- Диагностика обязательна: дребезг (больше 20 перепадов за 1 с), отвергнутые помехи и питание датчиков — иначе авария выглядит как «вход выключен».
- Выход: ключ в плюсе; диод — спад 12,8 мс, диод и стабилитрон 12 В — 5,5 мс ценой 37,4 В на ключе при шине 24 В и 43,4 В при 30 В.
Полные исходники
p24v208a.c — опрос входов 24 В: антидребезг на вертикальных счётчиках и диагностика.
/* Раздел 208а: опрос дискретных входов 24 В — антидребезг на вертикальных
* счётчиках и диагностика (дребезг, отвергнутые помехи, питание датчиков).
* di_poll() вызывается раз в 1 мс; входы — биты 0…DI_COUNT-1 одного порта.
* Ядро переносимое; чтение порта — только при сборке под STM32G474 или STM32F103. */
#include <stdint.h>
#include <stddef.h>
#define DI_COUNT 16u /* входов на порту */
#define DI_MASK ((1u << DI_COUNT) - 1u)
#define DI_ACTIVE_LOW DI_MASK /* оптрон тянет вывод к нулю: активен нулём */
#define DI_WINDOW_MS 1000u /* окно подсчёта перепадов */
#define DI_CHATTER_MAX 20u /* больше перепадов за окно — «дребезжит» */
_Static_assert(DI_COUNT >= 1u && DI_COUNT <= 16u, "входы — на одном 16-битном порту");
_Static_assert((DI_ACTIVE_LOW & ~DI_MASK) == 0u, "маска инверсии шире числа входов");
_Static_assert(DI_CHATTER_MAX < DI_WINDOW_MS, "порог дребезга больше числа отсчётов в окне");
typedef struct {
uint32_t state; /* признанное состояние, 1 = вход активен */
uint32_t cnt0, cnt1; /* два бита счётчика на вход, «вертикально» */
uint32_t last_raw; /* сырой отсчёт прошлого опроса */
uint32_t chatter; /* флаги: за прошлое окно вход дребезжал */
uint32_t glitch; /* флаги: был импульс короче четырёх отсчётов */
uint16_t edges[DI_COUNT]; /* сырые перепады в текущем окне */
uint16_t window_ms;
uint8_t supply_ok; /* питание датчиков было в норме в этом опросе */
} di_t;
_Static_assert(DI_WINDOW_MS <= UINT16_MAX, "окно и счётчики перепадов — в uint16_t");
_Static_assert(offsetof(di_t, cnt0) == 4u && offsetof(di_t, cnt1) == 8u, "раскладка di_t");
/* Четыре подряд отсчёта, не совпавших с признанным состоянием, — и вход
* переключается. Все входы разом, без ветвлений. Возвращает переключившиеся. */
uint32_t di_debounce(di_t *d, uint32_t raw)
{
uint32_t delta = (raw ^ d->state) & DI_MASK; /* где отсчёт спорит с состоянием */
uint32_t toggle = delta & d->cnt0 & d->cnt1; /* счётчик был на 3 — это 4-й */
d->glitch |= (d->cnt0 | d->cnt1) & ~delta; /* счёт начался и оборвался */
d->cnt1 = (d->cnt1 ^ d->cnt0) & delta; /* +1, а при согласии — сброс */
d->cnt0 = ~d->cnt0 & delta;
d->state ^= toggle;
return toggle;
}
/* Опрос раз в 1 мс: port — чтение порта как есть, supply_ok — питание датчиков
* в норме (свой вход или АЦП). Без питания входы не признаются — замораживаются. */
uint32_t di_poll(di_t *d, uint32_t port, int supply_ok)
{
uint32_t raw = (port ^ DI_ACTIVE_LOW) & DI_MASK;
uint32_t toggle = 0u;
uint32_t e = raw ^ d->last_raw; /* сырые перепады — для диагностики */
d->last_raw = raw;
d->supply_ok = (uint8_t)(supply_ok != 0);
if (d->supply_ok) {
toggle = di_debounce(d, raw);
} else {
d->cnt0 = 0u; /* без питания нули — не сигнал */
d->cnt1 = 0u;
}
for (uint32_t i = 0u; e != 0u; i++, e >>= 1) {
if ((e & 1u) != 0u && d->edges[i] < UINT16_MAX) {
d->edges[i]++;
}
}
if (++d->window_ms >= DI_WINDOW_MS) { /* окно закрыто: итог по дребезгу */
uint32_t ch = 0u;
for (uint32_t i = 0u; i < DI_COUNT; i++) {
if (d->edges[i] > DI_CHATTER_MAX) {
ch |= 1u << i;
}
d->edges[i] = 0u;
}
d->chatter = ch;
d->window_ms = 0u;
}
return toggle;
}
/* Состояние можно отдавать наружу, только если датчики запитаны и вход не дребезжит. */
uint32_t di_valid_mask(const di_t *d)
{
return d->supply_ok ? (DI_MASK & ~d->chatter) : 0u;
}
#if defined(STM32G474xx) || defined(STM32F103xB)
/* Входы — GPIOB[15:0]. IDR у G4 на смещении 0x10, у F1 — 0x08 (CRL, CRH, IDR). */
#if defined(STM32G474xx)
_Static_assert(offsetof(GPIO_TypeDef, IDR) == 0x10u, "G4: IDR = база + 0x10");
_Static_assert(GPIOB_BASE == 0x48000400u, "G4: GPIOB на AHB2");
#else
_Static_assert(offsetof(GPIO_TypeDef, IDR) == 0x08u, "F1: IDR = база + 0x08");
_Static_assert(GPIOB_BASE == 0x40010C00u, "F1: GPIOB на APB2");
#endif
uint32_t di_read_port(void)
{
return GPIOB->IDR;
}
#endif
Дальше: раздел 208б «Нагрузка 230 В от микроконтроллера: реле, симистор, твердотельное реле» — от катушки на 24 В к нагрузке в сети: чем её коммутировать и как не пустить 230 В на плату.