Кулер в самодельной зарядной полке завыл ночью, и я понял, что не знаю о нём ничего: не «крутится ли», а сколько именно оборотов он выдаёт прямо сейчас и когда пора бить тревогу. На жёлтом проводе трёхпинового вентилятора живёт тахо-выход — два импульса на каждый оборот, открытый коллектор, подтяжка нужна. Подключил к пину 2, завёл счётчик из урока про прерывания — и получил красивое число, которое ни о чём не говорит: «импульсов всего: 43 812». Накопленный итог не отвечает на вопрос «сколько сейчас».
Скорость из итога не вытащить: нужна граница времени. Частотомер — это тот же счётчик, но окно: копим импульсы секунду, снимаем слепок, переводим в герцы, окно начинается заново. Для вентилятора арифметика предельно честная: два импульса на оборот дают об/мин = герцы × 30. Полторы тысячи оборотов — это 50 Гц, и тревога «меньше 800 об/мин» превращается в сравнение с 27 Гц.
В этом уроке собираю схему целиком: volatile-счётчик и окно на millis-гейте из урока про планировщик, атомарное чтение 32-битного счётчика под noInterrupts(), перевод в герцы и об/мин, выбор RISING против CHANGE и точность, которую даёт окно в секунду против двух. Формулы подойдут и датчику потока, и щелевой оптопаре на валу — таблица внизу.
Окно, слепок и замок на 32 бита
Счётчик в обработчике — одна строка, по всем правилам ISR: volatile unsigned long, никаких Serial. В loop() живёт окно: millis-гейт «раз в 1000 мс», внутри — слепок. Здесь главная грабля всей темы: счётчик 32-битный, а процессор 8-битный, и чтение такой переменной из loop раскладывается на четыре байтовые операции. Прерывание может вклиниться между ними — и слепок соберётся из старых и новых байт вперемешку. Замок из урока про volatile стоит копейки: noInterrupts(), копия, обнуление, interrupts() — четыре строки, и слепок всегда цельный. Туда же, под замок, прячется и обнуление: иначе между чтением и записью нуля обработчик успеет накинуть лишний импульс, и он растворится.
Точность окну задаёт длина. За секунду счёт 50 Гц приходит с шагом в единицу — плюс-минус один импульс, то есть ±30 об/мин, два процента от 1500. Для тревоги «подшипник умирает» хватает за глаза; для плавного регулирования оборотов — нет. Окно 2 с режет разброс вдвое, но отклик тревоги ползёт; усреднение трёх последних замеров в переменной даёт ровную цифру без потери скорости — выбор зависит от того, что важнее: скорость реакции или спокойные числа в журнале. Общий итог, если он нужен, копи отдельным счётчиком и помни про беззнаковую арифметику из урока про переполнение 49,7 суток — разности она прощает, метки «на потом» нет.
// Частотомер: тахо-выход вентилятора (2 импульса на оборот) на пин 2.// Окно 1 с на millis-гейте; чтение и обнуление счётчика - под замком.volatile unsigned long imp = 0; void naTik() { // ISR: только счёт imp = imp + 1;} void setup() { Serial.begin(9600); pinMode(2, INPUT_PULLUP); // открытый коллектор: подтяжка обязательна attachInterrupt(digitalPinToInterrupt(2), naTik, FALLING);} void loop() { static unsigned long tOkno = 0; if (millis() - tOkno >= 1000) { tOkno = millis(); noInterrupts(); // 32-битное чтение - под замок unsigned long n = imp; imp = 0; interrupts(); Serial.print(n); Serial.print(" Гц = "); Serial.print((n * 60) / 2); Serial.println(" об/мин"); }}Разбор по строкам. INPUT_PULLUP — не украшение: тахо-выход вентилятора не тянет линию в плюс, он её подминает к земле, и без подтяжки пин висит в воздухе, ловя помехи вместо импульсов. FALLING выбран под ту же механику: событие — момент, когда транзистор замкнул линию на землю. Формула (n × 60) / 2 — перевод в об/мин для двух импульсов на оборот: импульсы в минуту делим на импульсов на оборот; для щелевой оптопары с одной прорезью на вал делитель станет единицей, для энкодера — числом щелчков, и вся правка сведётся к одной константе.
Две классические ловушки числа оборотов. Первая: прерывание вешают на CHANGE «чтобы ловить надёжнее» — счёт удваивается, об/мин взлетают вдвое, и формула начинает врать стройно и уверенно; CHANGE годится только вместе с делителем на два, я предпочитаю не плодить сущности. Вторая: механический контакт вместо честного датчика — геркон или щелчок дают дребезг, и один оборот считается за пять; обработчик с фильтром времени разобран в уроке про антидребезг в прерывании, но у фильтра есть потолок — он режет и максимальную частоту, поэтому для быстрых валов ставят оптопару или датчик Холла. Считающий пин всегда 2 или 3: других внешних прерываний на Uno нет — планируй макетку с этого.
- вентилятор ПК 3-пин: 2 импульса на оборот, об/мин = Гц × 30; при 1500 об/мин тахо шепчет 50 Гц
- щелевая оптопара с одной прорезью на вал: 1 импульс на оборот, об/мин = Гц × 60
- энкодер на валу: об/мин = Гц × 60 / число импульсов на оборот — константа в одном месте
- датчик потока YF-S201: не обороты, а литры — 450 импульсов на литр, Гц / 7,5 = л/мин
- окно 1 с даёт ±1 импульс (±30 об/мин у вентилятора); окно 2 с или среднее из трёх замеров — если нужна ровность
- 1. Вентилятор выдаёт 2 импульса на оборот, за окно в секунду насчитано 50 импульсов. Сколько это об/мин?
- 2. Почему счётчик копируют под noInterrupts(), а не читают напрямую из loop?
- частотомер = счётчик из ISR плюс окно на millis-гейте: за окно — слепок, перевод, обнуление
- чтение и обнуление 32-битного счётчика прячь под noInterrupts()/interrupts() — иначе слепок из разных байт
- об/мин = Гц × 60 / импульсов на оборот; вентилятор ПК — 2 импульса на оборот, тахо открытый коллектор с подтяжкой
- CHANGE удваивает счёт, дребезг умножает — RISING/FALLING с честным датчиком и окно в 1-2 с дают точность ±1 импульс
Частые вопросы по теме урока
- Вентилятор выдаёт 2 импульса на оборот, за окно в секунду насчитано 50 импульсов. Сколько это об/мин?
- 50 импульсов в секунду = 50 Гц. Оборотов в секунду вдвое меньше — 25, умножаем на 60: 1500 об/мин. Формула об/мин = Гц × 30 работает для любого двухимпульсного тахо. 1500 об/мин
- Почему счётчик копируют под noInterrupts(), а не читают напрямую из loop?
- 8-битный процессор читает 32-битную переменную за четыре операции; прерывание посреди чтения соберёт слепок из байт разных эпох. noInterrupts() на пару строк делает слепок и обнуление атомарными. unsigned long читается четырьмя байтами, и ISR может вклиниться между ними