4.05. Генерация и измерение импульсов.md


Поздравляем! Вы освоили, пожалуй, самые сложные для понимания концепции — как микроконтроллер взаимодействует с аналоговым миром. Теперь, когда за плечами теория о ШИМ, АЦП и ЦАП, давайте немного отдохнём и займёмся чем-то более прикладным и весёлым. В этом параграфе мы научим наш Рудирон «петь», используя встроенные функции для генерации звука, а также измерять длительность сигналов, что очень полезно для работы с различными датчиками.

Музыка и физика - как Рудирон создаёт звук?

Вы наверняка слышали, как пищат электронные будильники или детские игрушки. Этот звук создаётся с помощью быстро вибрирующей мембраны в устройстве, которое называется пьезоизлучатель или зуммер (buzzer). Чтобы заставить мембрану вибрировать, на неё нужно подавать быстро переключающийся цифровой сигнал. Частота этих переключений определяет высоту звука - чем чаще сигнал переключается, тем выше тон.

Каждая музыкальная нота — это звук с определённой частотой. Например, нота «Ля» первой октавы, на которую настраивают музыкальные инструменты, имеет частоту 440 Гц. Это значит, что для её воспроизведения мембрана пьезоизлучателя должна совершать 440 колебаний в секунду.

Нота C (До) D (Ре) E (Ми) F (Фа) G (Соль) A (Ля) B (Си)
Частота 1-й октавы (Гц) 262 294 330 349 392 440 494

Ноты в других октавах имеют частоты, кратные этим значениям. Например, «Ля» второй октавы (A5) звучит на частоте 880 Гц (в 2 раза выше), а «Ля» малой октавы (A3) — на частоте 220 Гц (в 2 раза ниже).

Можно ли сгенерировать такой сигнал вручную с помощью digitalWrite()? Теоретически да, но это очень неудобно. Чтобы получить ноту «Ля» (440 Гц), вам пришлось бы написать код, который переключает пин с HIGH на LOW и обратно каждые \(1 / (440 \times 2) \approx 0.00113\) секунды, или 1136 микросекунд. Это сложно, неточно, и такой код полностью заблокирует микроконтроллер, не давая ему выполнять другие задачи.

К счастью, для этого есть специальная функция tone().

Генерация импульса — функция tone()

Функция tone() — это удобный инструмент, который берёт на себя всю сложную работу по генерации сигнала нужной частоты. Она использует аппаратные таймеры микроконтроллера для создания на пине сигнала ШИМ с коэффициентом заполнения 50% и точно заданной вами частотой. Это происходит в фоновом режиме, не мешая выполнению основного кода в loop().

Синтаксис функции:

tone(номерПина, частота);
tone(номерПина, частота, длительность);
  • номерПина. Любой GPIO пин, к которому подключён пьезоизлучатель.
  • частота. Частота звука в герцах (Гц).
  • длительность (необязательный параметр). Длительность звучания в миллисекундах. Если её не указать, звук будет генерироваться бесконечно, пока не будет остановлен функцией noTone().

Для прекращения генерации звука используется функция noTone():

noTone(номерПина);

Пример (проигрывание гаммы): Давайте научим Рудирон играть простую гамму «До-мажор».

const int buzzerPin = 8; // Пин для пьезоизлучателя

// Массив с частотами нот гаммы "До-мажор"
int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};

void setup() {
  // pinMode для tone() не требуется
}

void loop() {
  // Проигрываем мелодию последовательно из массива
  for (int i = 0; i < 8; i++) {
    tone(buzzerPin, melody[i], 300); // Каждая нота звучит 300 мс
    delay(400); // Пауза между нотами
  }
  
  delay(2000); // Пауза перед повторением
}

Измерение импульса — функция pulseIn()

Если tone() позволяет нам генерировать сигнал определённой частоты, то pulseIn() — это обратная ей функция, которая позволяет измерить длительность входящего сигнала. Это невероятно полезно при работе с датчиками, которые кодируют информацию во временных интервалах.

Представьте ультразвуковой дальномер: он отправляет короткий звуковой импульс и «слушает» эхо. Время, прошедшее между отправкой и приёмом, говорит о расстоянии до препятствия. Функция pulseIn() идеально подходит для измерения этого времени. Она блокирует выполнение программы и «засекает время», пока на указанном пине держится определённый уровень сигнала (HIGH или LOW).

Синтаксис функции:

unsigned long duration = pulseIn(номерПина, состояние, таймаут);
  • номерПина. Пин, на котором измеряется импульс.
  • состояние. Уровень сигнала, длительность которого мы измеряем (HIGH или LOW).
  • таймаут (необязательный параметр). Максимальное время ожидания импульса в микросекундах. По умолчанию — 1 секунда.

Функция возвращает длительность импульса в микросекундах (мкс).

Пример (измерение длительности нажатия кнопки): Этот код будет измерять, как долго вы удерживаете кнопку нажатой, и выводить результат в монитор порта.

const int buttonPin = 35; // Пин, к которому подключена кнопка

void setup() {
  pinMode(buttonPin, INPUT_PULLDOWN); 
  Serial.begin(9600);
  Serial.println("Нажмите и удерживайте кнопку для измерения...");
}

void loop() {
  // pulseIn() будет ждать, пока на пине появится высокий уровень (кнопка нажата),
  // измерит, как долго он продлится, и вернёт длительность.
  unsigned long pulseDuration = pulseIn(buttonPin, HIGH);

  // Если был зафиксирован импульс (то есть кнопка была нажата)
  if (pulseDuration > 0) {
    Serial.print("Кнопка была нажата: ");
    Serial.print(pulseDuration);
    Serial.println(" микросекунд.");
  }
}

Зачем это нужно?

Функции tone() и pulseIn() — это отличные инструменты для создания интерактивных проектов:

  • С помощью tone() можно добавлять в устройства звуковые оповещения, создавать простые музыкальные инструменты или сигнализации.
  • С помощью pulseIn() можно работать с широким спектром датчиков, которые передают информацию через длительность импульса, например, с ультразвуковыми дальномерами (HC-SR04), ИК-приёмниками или некоторыми видами энкодеров.

Мы рассмотрели, как заставить Рудирон издавать звуки и измерять временные интервалы. Эти простые, но мощные функции открывают дорогу к множеству увлекательных проектов. Если вы хотите закрепить полученные знания, переходите к лабораторным работам. А если вы готовы научиться делать так, чтобы микроконтроллер реагировал на события мгновенно, не дожидаясь своей очереди в цикле loop(), отправляйтесь в следующий параграф, где мы изучим аппаратные прерывания.