В статье будет рассмотрен и описан простой процесс сборки полезного генератора импульсов для изучения катушек индуктивности и других экспериментов.
Отличие такого генератора от распространённых ШИМ-генераторов в том, что у данного генератора имеется возможность задавать отдельно длительность импульса и отдельно длительность паузы, а также количество последовательно идущих импульсов и пауз. Имеется также возможность получения полноценного регулируемого повторяющегося во времени сигнала.
Все перечисленные возможности реализованы через код. Для создания генератора понадобится Arduino Nano/Uno, две тактильные кнопки, LCD-дисплей и установленная на ПК программа Arduino IDE.
Схема подключения изображена на рисунке 1.

Для удобства подключения и использования Arduino Nano можно применить такую плату расширения (рис. 2). Очень удобно, есть рядом расположенные необходимые выводы для подключения LCD-дисплея. У каждого сигнального пина рядом есть отдельный пин GND и +5V.

Вначале необходимо скачать и установить Arduino IDE. Далее необходима библиотека LiquidCrystal I2C, автор Frank de Brabander, библиотека необходима для работы дисплея. Это можно сделать, зайдя в Tools - Manage Libraries…. (рис.3).

На данном этапе у вас должны быть в наличии: I2C LCD 2004 дисплей, две тактильные кнопки, Arduino Nano или Uno. И установлена на компьютер Arduino IDE, добавлена библиотека LiquidCrystal I2C.
Рассмотрим возможности генератора импульсов (рис.4) и (рис.1).

Кнопка 1. Длинное нажатие (0,4 сек) служит для выбора цифровой ячейки по горизонтали.
Кнопка 1. Короткое нажатие задаёт цифровое значение в выделенной ячейке.
Кнопка 2. По короткому нажатию идёт выбор строки. Выделение текущей ячейки опускается сверху в низ, дойдя до самого низа, выделение возвращается в верхнюю ячейку.
Кнопка 2. Длинное нажатие выдаёт сигнал или пачку сигналов в режиме Burst на вывод 9 Arduino.
Pulse. Задаётся длительность импульса в микросекундах. Рабочий диапазон от единиц микросекунд до 16000 микросекунд.
Pause. Задаётся длительность паузы в микросекундах. Рабочий диапазон от единиц микросекунд до 16000 микросекунд.
Mode. Здесь задаётся один из двух режимов. В данном примере это Burst, то есть режим одиночного импульса или последовательности из нескольких импульсов и пауз. Переключение режимов происходит по быстрому нажатию на кнопку 1.
Burst. В этой строчке задаётся количество последовательно идущих импульсов и пауз. По умолчанию стоит 1, это значит, что данный режим работы, — это генератор одиночного импульса. Если поставить 2, то мы получим два импульса и две паузы. Если 3, то три импульса и три паузы и т.д. При этом после последней паузы генерация выключается, а значит, отчётливой границы последней паузы не будет видно, пример: (рис.6,7).
Для переключения к настройкам повторяющегося сигнала необходимо опуститься в то место дисплея, где написано Mode, и далее короткое нажатие на кнопку 1. Режим Burst изменится на Continuous (рис.5).

В режиме Continuous имеется возможность задавать длительность импульсов и отдельно пауз. И выводить повторяющийся сигнал.
Режим Continuous работает в реальном времени. Без необходимости запускать повторяющийся сигнал и изменения кнопкой.
Данный генератор создавался с целью изучения катушек индуктивности. Он создавался и рассчитан не на прямое подключение к полевому транзистору, а на подключение через оптопару и драйвер затвора. В моём случае это 6N137 и TC4420. Поэтому логика работы генератора инвертированная: то есть когда MOSFET закрыт, на выводе 9 Arduino — 5 вольт.
Пример работы. Генератор выдаёт 1 импульс (Pulse) длительностью 100 микросекунд (рис6).

Пример работы. Генератор выдаёт 3 последовательных импульсов и пауз. Длительность импульса (Pulse) 100 микросекунд, длительность паузы (Pause) 50 микросекунд. При этом после последней паузы генерация выключается, поэтому последняя пауза всегда не разделяется (рис. 7).

Пример работы. Генератор в режиме Continuous. 200 микросекунд длительность импульса (Pulse) и 450 микросекунд длительность паузы (Pause), — это бесконечно повторяющийся режим (рис. 8).

Для завершения создания этого генератора импульсов достаточно загрузить предоставленный код в Arduino IDE. Для этого подключите Arduino Nano через USB-кабель к компьютеру, откройте Arduino IDE, выберите Arduino Nano/Uno, выберите COM-порт к которому подключена Arduino Nano/Uno. Необходимое выделено на (рис. 9). Загрузите код, нажмите вверху "Загрузка" (Upload).

Если после загрузки кода на дисплее не появилась информация, то в начале кода измените адрес на "0x3F" (без кавычек). Смотрите (рис. 10).

#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 20, 4); // Адрес I2C дисплея (подставьте свой при необходимости) // ---------- Пины ---------- const int button1Pin = 3; // Кнопка 1 (короткое: изменение цифры, длинное: переход на следующую цифру) const int button2Pin = 4; // Кнопка 2 (короткое: выбор раздела, длинное: запуск Burst) const int outputPin = 9; // Выход на оптопару (ШИМ/импульсы) // ---------- Параметры генерации ---------- unsigned long pulseWidth = 100; // Длительность импульса (мкс) unsigned long pauseWidth = 100; // Длительность паузы (мкс) unsigned long burstCount = 1; // Число импульсов в пачке (Burst) // Символьные представления для ввода (6 цифр) char pulseDigits[6] = {'0', '0', '0', '1', '0', '0'}; // "000100" -> 100 мкс char pauseDigits[6] = {'0', '0', '0', '1', '0', '0'}; char burstDigits[6] = {'0', '0', '0', '0', '0', '1'}; // "000001" -> 1 импульс // ---------- Интерфейс ---------- int currentSection = 0; // 0 = pulse, 1 = pause, 2 = mode, 3 = burst count (только в Burst-режиме) int currentDigit = 0; // Индекс редактируемой цифры (0-5) int mode = 1; // 0 = Continuous, 1 = Burst volatile bool generating = false; // Флаг активной генерации пачки // ---------- Дребезг кнопок ---------- int button1State = HIGH, lastButton1Reading = HIGH; unsigned long lastDebounceTime1 = 0, button1PressTime = 0; int button2State = HIGH, lastButton2Reading = HIGH; unsigned long lastDebounceTime2 = 0, button2PressTime = 0; const unsigned long debounceDelay = 50; // мс const unsigned long longPressThreshold = 400; // мс (удержание кнопки) // ---------- Вспомогательные функции ---------- unsigned long getValue(char* digits) { unsigned long val = 0; for (int i = 0; i < 6; i++) { val = val * 10 + (digits[i] - '0'); } return (val == 0) ? 1 : val; // 000000 интерпретируется как 1 мкс } // Настройка аппаратного ШИМ (режим Continuous) void configureTimer() { unsigned long period_us = pulseWidth + pauseWidth; if (period_us > 1000000) period_us = 1000000; // ограничение 1 сек uint16_t prescaler; uint8_t cs_bits; double tick_us; if (period_us <= 4096) { prescaler = 1; cs_bits = 0b001; tick_us = 0.0625; } else if (period_us <= 32768) { prescaler = 8; cs_bits = 0b010; tick_us = 0.5; } else if (period_us <= 262144) { prescaler = 64; cs_bits = 0b011; tick_us = 4.0; } else if (period_us <= 1048576) { prescaler = 256; cs_bits = 0b100; tick_us = 16.0; } else { prescaler = 1024; cs_bits = 0b101; tick_us = 64.0; } double clock_mhz = 16.0; double ticks_period = (period_us * clock_mhz) / prescaler; uint16_t icr = round(ticks_period) - 1; if (icr < 1) icr = 1; double ticks_high = (pulseWidth * clock_mhz) / prescaler; uint16_t ocr = round(ticks_high) - 1; if (ocr > icr) ocr = icr; if (ocr < 0) ocr = 0; noInterrupts(); TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; ICR1 = icr; OCR1A = ocr; // Инвертированный fast PWM (чтобы HIGH на выходе оптопары соответствовал открытому MOSFET) TCCR1A = (1 << COM1A1) | (1 << COM1A0) | (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | cs_bits; interrupts(); } // Обновление дисплея с мигающим курсором void updateDisplay() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Pulse: "); for (int i = 0; i < 6; i++) lcd.print(pulseDigits[i]); lcd.print(" us"); lcd.setCursor(0, 1); lcd.print("Pause: "); for (int i = 0; i < 6; i++) lcd.print(pauseDigits[i]); lcd.print(" us"); lcd.setCursor(0, 2); lcd.print("Mode: "); if (mode == 0) lcd.print("Continuous"); else lcd.print("Burst"); if (mode == 1) { lcd.setCursor(0, 3); lcd.print("Burst: "); for (int i = 0; i < 6; i++) lcd.print(burstDigits[i]); } // Мигающий курсор int row, col; if (currentSection == 0) { row = 0; col = 7 + currentDigit; } else if (currentSection == 1) { row = 1; col = 7 + currentDigit; } else if (currentSection == 2) { row = 2; col = 6; } else { row = 3; col = 7 + currentDigit; } lcd.setCursor(col, row); lcd.blink(); } // ---------- Инициализация ---------- void setup() { pinMode(button1Pin, INPUT_PULLUP); pinMode(button2Pin, INPUT_PULLUP); pinMode(outputPin, OUTPUT); // Начальное состояние: светодиод оптопары горит -> выход LOW -> MOSFET закрыт digitalWrite(outputPin, HIGH); lcd.init(); lcd.backlight(); pulseWidth = getValue(pulseDigits); pauseWidth = getValue(pauseDigits); burstCount = getValue(burstDigits); if (mode == 0) configureTimer(); // Если Continuous, запускаем таймер updateDisplay(); } // ---------- Основной цикл ---------- void loop() { // ? Кнопка 1 (изменение значений) int reading1 = digitalRead(button1Pin); if (reading1 != lastButton1Reading) { lastDebounceTime1 = millis(); } if ((millis() - lastDebounceTime1) > debounceDelay) { if (reading1 != button1State) { button1State = reading1; if (button1State == LOW) { button1PressTime = millis(); // нажали } else { // Отпустили кнопку unsigned long duration = millis() - button1PressTime; bool paramsChanged = false; if (currentSection == 0 || currentSection == 1 || (currentSection == 3 && mode == 1)) { char* digits = (currentSection == 0) ? pulseDigits : (currentSection == 1) ? pauseDigits : burstDigits; if (duration < longPressThreshold) { // Короткое нажатие: увеличить цифру digits[currentDigit]++; if (digits[currentDigit] > '9') digits[currentDigit] = '0'; paramsChanged = true; } else { // Длинное нажатие: переход к следующей цифре currentDigit = (currentDigit + 1) % 6; } } else if (currentSection == 2) { // Раздел Mode: короткое нажатие переключает Continuous/Burst if (duration < longPressThreshold) { mode = (mode + 1) % 2; paramsChanged = true; } } // Если параметры изменились, пересчитываем и сбрасываем генератор if (paramsChanged) { // Принудительно закрываем MOSFET digitalWrite(outputPin, HIGH); noInterrupts(); TCCR1A = 0; // остановка таймера TCCR1B = 0; interrupts(); generating = false; pulseWidth = getValue(pulseDigits); pauseWidth = getValue(pauseDigits); burstCount = getValue(burstDigits); if (mode == 0) configureTimer(); // перезапустить Continuous, если нужно } updateDisplay(); } } } lastButton1Reading = reading1; // ? Кнопка 2 (переключение разделов и запуск Burst) int reading2 = digitalRead(button2Pin); if (reading2 != lastButton2Reading) { lastDebounceTime2 = millis(); } if ((millis() - lastDebounceTime2) > debounceDelay) { if (reading2 != button2State) { button2State = reading2; if (button2State == LOW) { button2PressTime = millis(); } else { unsigned long duration = millis() - button2PressTime; if (duration < longPressThreshold) { // Короткое нажатие: выбор следующего раздела меню currentSection = (currentSection + 1) % (mode == 0 ? 3 : 4); currentDigit = 0; } else { // Длинное нажатие: запустить пачку (только в режиме Burst) if (mode == 1 && !generating && burstCount > 0) { generating = true; // Задаём длительности без коррекции (6N137 быстрая) unsigned long pulseEffective = pulseWidth; unsigned long pauseEffective = pauseWidth; for (unsigned long i = 0; i < burstCount; i++) { // Быстрый LOW: сбрасываем бит 1 порта B PORTB &= ~(1 << PB1); delayMicroseconds(pulseEffective); // Быстрый HIGH: устанавливаем бит 1 порта B PORTB |= (1 << PB1); if (i < burstCount - 1) { delayMicroseconds(pauseEffective); } } generating = false; } } updateDisplay(); } } } lastButton2Reading = reading2; // Если режим Continuous и таймер остановлен (например, был сброс параметров), // он автоматически не перезапустится, но в setup() и при изменении настроек мы его запускаем. }
Arhammon
Мне кажется что любой ИИ сейчас с вероятностью 90% создаст подобный код с первого раза. По этому в рамках статьи было бы не неплохо рассказать про аналоговую часть девайса, как например определять ток насыщения сердечника с помощью такого девайса...