ATmega. Счетчик импульсов ШИМ
На базе ATmega 328P реализовать счетчик импульсов для проверки ШИМ 25 кГц, точность измерений до импульса не нужна, но порядок нужно знать.
Логика решения проста, отслеживаем импульсы, по которым инкрементируем глобальную переменную в течении секунды. Накопленное значение и будет частотой входящего сигнала.
Для считывания импульсов воспользуемся внешними прерываниями, они описаны на страницах 87-96 документации от производителя. В Atmega 328P есть два входа, которыми мы можем отслеживать внешние прерывания INT0(PD2) и INT1(PD3), для решения задачи воспользуемся INT0.
Настройка внешних прерываний
Первым делом необходимо настроить порт D как вход, а для избежания наводок подключу подтягивающий резистор.
Для определения по каким событиям будет вызываться обработчик прерывания нужно настроить регистр ERICA. Биты ISC00 и ISC01 отвечают за INT0, а ISC10 и ISC11 за INT1. Настройка отслеживаемых событий идентична, за разницей в битах:
00 — Низкий уровень сигнала;
01 — Любое логическое изменение сигнала;
10 — Нисходящий фронт сигнала;
11 — Восходящий фронт сигнала.
Для непосредственного включения входов прерываний служит регистр EIMSK, биты INT0 и INT1 отвечают за одноименные выходы. По вышеизложенному пишем код
Обработка внешних прерываний
Прерывания настроил, теперь надо их обработать. Для этого существует функция обработки прерывания ISR(), которой необходимо указать тип прерывания, в моем случае INT0_vect. В функции будем делать инкремент переменной Tic_Count:
Вывод результата
Для облегчения вывода результата, дабы не прикручивать дисплей воспользовался не чистой ATmega 328P, а Arduino UNO и Arduino NANO, на борту которых тот же МК.
Как писал выше точность измерений не столь важна, потому таймеров настраивать не буду, а просто в основном цикле один раз в секунду выведу накопленное значение переменной Tic_Count и обнулю ее. На время этих действий прекращаю обработку прерываний.
Ниже полный код решения задачи с комментариями:
Теперь остается подключить сигнал ШИМ к ножке PD2, и открыть монитор последовательного порта. Так же можно сформировать и проверить сигнал на одном МК.
Выводимые показания примерно равны ранее рассчитанной частоте, небольшие отличия ожидаемы из-за реализации. Для точного измерения наверное правильнее считать время между импульсами и от этого вычислять частоту.
ШИМ сигнал
Аппаратный
Для генерации ШИМ сигнала с заданным заполнением есть стандартная функция analogWrite(pin, duty) , подробнее обсуждали в уроке про ШИМ сигнал, а частоту можно изменить перенастройкой таймера, как в уроке об увеличении частоты ШИМ. На самом деле таймеры позволяют настроить ШИМ сигнал с более точной или более высокой частотой и другими диапазонами заполнения (до 10 бит), но в ядре Arduino это не предусмотрено. Если такое будет нужно, можно воспользоваться библиотекой GyverPWM. Пример:
Программный ШИМ
Программная генерация ШИМ сигнала может пригодиться, если не хватает лишнего таймера или частота ШИМ низкая и не повлияет на остальной код, а он на неё. ШИМ сигнал на “миллисе” можно организовать вот таким образом, переключая выход по двум периодам:
Функцию PWMgen(заполнение) в данной реализации нужно вызывать как можно чаще в основном цикле программы:
Здесь мы на каждом вызове считаем новый период переключения, тратя на это какое-то время. Можно считать период в отдельной функции, а сам ШИМ генерировать отдельно. Реализацию можно посмотреть в библиотеке PWMrelay.
Полуаппаратный ШИМ
Можно снизить нагрузку на процессор, отдав счёт времени аппаратному таймеру. Примеры на базе GyverTimers (для ATmega328, 2560):
Как известно, digitalWrite() является очень тяжёлой и долгой функцией, и для генерации софт ШИМ рекомендуется заменить её чем-то более быстрым, например прямым обращением к регистру или вот такой конструкцией (для ATmega328p):
Если не хватает количества стандартных ШИМ-выходов, можно поднять полуаппаратный ШИМ на таймере на несколько пинов сразу:
Этот алгоритм является не самым оптимальным, более интересный можно посмотреть в GyverHacks.
Примечание: во всех трёх алгоритмах используется проверка совпадения со счётчиком counter == pwm_duty . Это сильно снижает использование процессорного времени в прерывании, но при резком уменьшении заполнения может приводить к одиночным “вспышкам” заполнения до максимума, так как условие не выполнится. Для более плавной работы можно сделать counter >= pwm_duty , тогда условие будет каждый раз “подстраиваться” под новое значение заполнения, но установка пина будет осуществляться на каждом тике!
Можно ввести буферизацию заполнения ШИМ и брать новое значение только при нулевом значении счётчика, это решит проблему:
Можно применить буферизацию и к остальным алгоритмам.
Библиотека Servo
Как известно, RC сервоприводы управляются при помощи ШИМ сигнала с частотой
50 Гц и длительностью импульса от
2500 микросекунд. В стандартной библиотеке Servo.h реализована генерация полуаппаратного ШИМ сигнала, причём количество пинов можно менять во время работы. Библиотеку можно использовать как генерацию ШИМ, если его параметры подходят для использования.
2. Светящийся куб на 512 светодиодов
Сложность: 3/5.
Время: 3/5.
Красивая штука, которая может светиться в такт музыке как трёхмерный эквалайзер и показывать 3D-анимацию. А ещё это может работать как необычный ночник.
Для сборки понадобится деревянное шасси с отверстиями, чтобы каждый ярус был таким же по размеру и форме, что и остальные. Число светодиодов в каждой грани выбрано не случайно: 8 ламп = 8-битная логика, самая простая в программировании и управлении через контроллер.
Примеры использования attachInterrupt
Давайте приступим к практике и рассмотрим простейший пример использования прерываний. В примере мы определяем функцию-обработчик, которая при изменении сигнала на 2 пине Arduino Uno переключит состояние пина 13, к которому мы традиционно подключим светодиод.
Давайте рассмотрим несколько примеров более сложных прерываний и их обработчиков: для таймера и кнопок.
Прерывания по нажатию кнопки с антидребезгом
При прерывании по нажатию кнопки возникает проблема дребезга – перед тем, как контакты плотно соприкоснутся при нажатии кнопки, они будут колебаться, порождая несколько срабатываний. Бороться с дребезгом можно двумя способами – аппаратно, то есть, припаивая к кнопке конденсатора, и программно.
Избавиться от дребезга можно при помощи функции millis – она позволяет засечь время, прошедшее от первого срабатывания кнопки.
Этот код позволяет удалить дребезг и не блокирует исполнение программы, как в случае с функцией delay, которая недопустима в прерываниях.
Прерывания по таймеру
Таймером называется счетчик, который производит счет с некоторой частотой, получаемой из процессорных 16 МГц. Можно произвести конфигурацию делителя частоты для получения нужного режима счета. Также можно настроить счетчик для генерации прерываний при достижении заданного значения.
Таймер и прерывание по таймеру позволяет выполнять прерывание один раз в миллисекунду. В Ардуино имеется 3 таймера – Timer0, Timer1 и Timer2. Timer0 используется для генерации прерываний один раз в миллисекунду, при этом происходит обновление счетчика, который передается в функцию millis (). Этот таймер является восьмибитным и считает от 0 до 255. Прерывание генерируется при достижении значения 255. По умолчанию используется тактовый делитель на 65, чтобы получить частоту, близкую к 1 кГц.
Для сравнения состояния на таймере и сохраненных данных используются регистры сравнения. В данном примере код будет генерировать прерывание при достижении значения 0xAF на счетчике.
Требуется определить обработчик прерывания для вектора прерывания по таймеру. Вектором прерывания называется указатель на адрес расположения команды, которая будет выполняться при вызове прерывания. Несколько векторов прерывания объединяются в таблицу векторов прерываний. Таймер в данном случае будет иметь название TIMER0_COMPA_vect. В этом обработчике будут производиться те же действия, что и в loop ().
Подведение итогов
Прерывание в Ардуино – довольно сложная тема, потому что приходится думать сразу обо всей архитектуре проекта, представлять как выполняется код, какие возможны события, что происходит, когда основной код прерывается. Мы не ставили задачу раскрыть все особенности работы с этой конструкцией языка, главная цель была познакомить с основными вариантами использования. В следующих статьях мы продолжим разговор о прерываниях более подробне.
Снятие данных с импульсного выхода водосчетчика
Геркон, по сути — это механическая кнопка, управляемая магнитом. Поэтому может быть использована схема коммутации для обычной кнопки. Это медленное механическое устройство. Бытовой счетчик воды после коммутации геркона в течении нескольких секунд «держит» геркон в замкнутом состоянии.
В некоторых схемах в Интернет для подключения кнопки/геркона используют дополнительный «подвешивающий» резистор. Смысла в этом нет, поскольку в самом чипе есть резистр соединенный с +5V. Он активируется кодом: pinMode(PULSE_PIN, INPUT_PULLUP).
Однако геркон нужно подключить через резистор на 1 кОм к входу GPIO. Он нужен, чтобы не «спалить» порт микроконтроллера если прошивка случайно выставит единицу на пине, а геркон в этот момент закоротит эту линию на землю. При наличии резистора через микроконтроллер потечет ток I = 3.3В/1000 Ом = 3.3 мА при том, что ток выхода из строя GPIO ESP8266 — 12 мА, а ESP32 — 20 мА.
При коммутации механических контактов возникает дребезг, который может приводить к ложному срабатыванию при включении и отключении. Как устранять дребезг контактов подробно описано в статье.
Поскольку устройство очень медленное, нет необходимости использовать аппаратные решения, вроде триггера Шмидта или RC цепочки, для подавления дребезга контактов. Достаточно подождать пока переходные процессы завершатся и после этого считывать состояние геркона.
Код Arduino для подсчета расхода воды:
Код был оттестирован на счетчике расхода воды Valtec VLF-U-I. Схема импульсного выхода водосчетчика:
- PIN 2 Arduino подключен к 1 выходу водосчетчика.
- GND Arduino подключен к 4 выходу водосчетчика.
- В случае с герконом контакты 1 и 4 можно менять местами. 🙂
Другой вариант реализации кода — с использованием прерываний.
Если сравнивать два варианта кода, то с прерываниями выглядит более технологичным, но и более капризным. Для варианта с прерыванием я рекомендую использовать аппаратное подавление дребезга RC цепочкой или триггером Шмитта (Шмидта).
При программном устранении дребезга контактов при использовании прерываний нужно очень аккуратно подбирать значение времени игнорирования. Если его взять слишком большим, то можно проскочить последнее прерывание перед завершением переходных процессов и тогда будет пропуск в подсчете расхода потребления. Поэтому лучше использовать аппаратное устранение дребезга с соответствующей корректировкой кода.
В случае с опросом состояния в цикле, чем больше значение защиты от «дребезга» взять (в пределах разумного), тем надежнее будет избавление от дребезга и никаких проблем с пропуском, поскольку импульс от геркона продолжительный, длится секунды — это позволяет гарантированно отследить момент завершения переходных процессов.
Схема проекта
Схема подключения датчика расхода воды к плате Arduino Uno представлена на следующем рисунке.
Соединения между платой Arduino, ЖК дисплеем 16×2 и датчиком расхода воды представлены в следующих таблицах ниже. Потенциометр подключен к контактам 5V и GND, а его средний контакт подключен к контакту V0 ЖК дисплея.
Датчик расхода воды | Плата Arduino |
красный провод | 5V |
черный провод | GND |
желтый провод | 2 |
ЖК дисплей | Плата Arduino |
Vss | GND (ground rail of breadboard) |
VDD | 5V (Positive rail of the breadboard) |
V0 | к потенциометру |
RS | 12 |
RW | GND |
E | 11 |
D7 | 9 |
D6 to D3 | 3 to 5 |
После сборки проекта на макетной плате у нас получилась конструкция следующего вида: