В статье будет рассмотрен и описан простой процесс сборки полезного генератора импульсов для изучения катушек индуктивности и других экспериментов.

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

рис.1
рис.1

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

рис.2
рис.2

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

рис.3
рис.3

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

Рассмотрим возможности генератора импульсов (рис.4) и (рис.1).

рис.4
рис.4

Кнопка 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).

рис.5
рис.5

В режиме Continuous имеется возможность задавать длительность импульсов и отдельно пауз. И выводить повторяющийся сигнал.
Режим Continuous работает в реальном времени. Без необходимости запускать повторяющийся сигнал и изменения кнопкой.

Данный генератор создавался с целью изучения катушек индуктивности. Он создавался и рассчитан не на прямое подключение к полевому транзистору, а на подключение через оптопару и драйвер затвора. В моём случае это 6N137 и TC4420. Поэтому логика работы генератора инвертированная: то есть когда MOSFET закрыт, на выводе 9 Arduino — 5 вольт.

Пример работы. Генератор выдаёт 1 импульс (Pulse) длительностью 100 микросекунд (рис6).

рис.6
рис.6

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

рис.7
рис.7

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

рис.8
рис.8

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

рис.9
рис.9

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

рис.10
рис.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() и при изменении настроек мы его запускаем.
}

Комментарии (2)


  1. Arhammon
    30.07.2026 03:00

    Мне кажется что любой ИИ сейчас с вероятностью 90% создаст подобный код с первого раза. По этому в рамках статьи было бы не неплохо рассказать про аналоговую часть девайса, как например определять ток насыщения сердечника с помощью такого девайса...