Уроки технологии
Технология Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Arduino Pulsein Interrupts

В этом уроке я покажу вам, как сделать функцию PulseIn() в Arduino неблокирующей, используя прерывания. На самом деле мы попытаемся воспроизвести поведениеpulsIn(), не используя его. Я не буду здесь объяснять, что такое PulseIn() и как он работает, для этого проверьте это руководство по пульсации() сначала.

Оглавление

Проблема с PulseIn()

Давайте сначала разберемся, зачем мы вообще это делаем, ведь оптимизация ради оптимизации не стоит. Проблема с pulseIn() заключается в том, что он заблокирует всю вашу программу на определенное время. Это количество времени может каждый раз быть разным, а также может быть довольно большим. Чтобы убедиться в этом, давайте воспользуемся базовой схемой и базовым кодом. Эта схема содержит кнопку с резистором 10 кОм, который будет действовать как понижающий резистор. На контакте 3 мы сможем прочитать LOW, когда кнопка не нажата, и HIGH, когда кнопка нажата. Вы учитесь использовать Arduino для создания собственных проектов? Ознакомьтесь с Arduino для начинающих и учитесь шаг за шагом. С помощью этого кода мы будем измерьте длительность функцииpulsIn() с таймаутом 10 секунд (10 миллионов микросекунд):

#define BUTTON_PIN 3
void setup()
{
    Serial.begin(9600);
    pinMode(BUTTON_PIN, INPUT);
}
void loop()
{
    unsigned long timeBegin = micros();
    unsigned long pulseResult = pulseIn(BUTTON_PIN, HIGH, 10000000);
    unsigned long timeEnd = micros();
    unsigned long duration = timeEnd - timeBegin;
    Serial.println(duration);
}

Когда вы запустите этот код с предыдущей схемой и в зависимости от того, что вы делаете, вы получите разные результаты:

  • если вы нажмете и отпустите кнопку, вы увидите, как долго вы ее нажимали.
  • если функцияpulseIn() достигнет таймаута, вы увидите таймаут (примечание: возможно, таймаутpulsIn() не очень точен). Таким образом, по сути, с тайм-аутом в 10 секунд вы можете ожидать, что функция PulseIn() будет блокироваться от 0 до 10 секунд. И это определенно то, чего вам следует избегать в своих программах.

Что мы собираемся делать с прерываниями

Есть два времени ожидания, от которых мы хотим избавиться: время ожидания между вызовом PulseIn() и началом импульса, а затем длительность импульса. Итак, вместо использования PulseIn() мы подключим прерывание к контакту 3, чтобы мы могли теперь видеть, когда импульс начинается и когда импульс заканчивается. Примечание: очень важно, чтобы вы выбрали контакт, совместимый с прерываниями! Для Arduino Uno у вас есть цифровые контакты 2 и 3. И вот что будет происходить в коде в 3 шага:

  • При запуске импульса: сработает прерывание. Получаем и сохраняем текущее время (время начала).
  • Когда импульс закончится: также сработает прерывание. Мы получаем и сохраняем текущее время (время окончания), а также устанавливаем флаг (логический) — В цикле void() все, что нам нужно сделать, это проверить наличие флага. Когда флаг установлен, мы вычисляем разницу между временем начала и окончания. Это дает нам длительность импульса без необходимости блокировать программу.

The “pulseIn()” code with interrupts

#define BUTTON_PIN 3
volatile unsigned long pulseInTimeBegin = micros();
volatile unsigned long pulseInTimeEnd = micros();
volatile bool newPulseDurationAvailable = false;
void buttonPinInterrupt()
{
    if (digitalRead(BUTTON_PIN) == HIGH)
    {
        // start measuring
        pulseInTimeBegin = micros();
    }
    else
    { // stop measuring
        pulseInTimeEnd = micros();
        newPulseDurationAvailable = true;
    }
}
void setup()
{
    Serial.begin(9600);
    pinMode(BUTTON_PIN, INPUT);
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonPinInterrupt, CHANGE);
}
void loop()
{
    if (newPulseDurationAvailable)
    {
        newPulseDurationAvailable = false;
        unsigned long pulseDuration = pulseInTimeEnd - pulseInTimeBegin;
        Serial.println(pulseDuration);
    }
    // do your other stuff here
}

Объяснение кода

Несколько глобальных переменных

#define BUTTON_PIN 3
volatile unsigned long pulseInTimeBegin = micros();
volatile unsigned long pulseInTimeEnd = micros();
volatile bool newPulseDurationAvailable = false;

Начнем с создания трех глобальных переменных: -pulseInTimeBegin: будет обновлено с учетом текущего времени начала импульса. -pulseInTimeEnd: будет обновлено с учетом текущего времени, когда импульс закончится.

  • newPulseDurationAvailable: простая логическая переменная (которую я здесь называю «флагом»), позволяющая узнать, когда импульс закончился, чтобы мы могли вычислить длительность. Одно важное замечание: поскольку мы собираемся изменять эти переменные в функции прерывания, рекомендуется добавлять перед ними ключевое слово «летучий».

Функция прерывания – обнаружение пульса

void buttonPinInterrupt()
{
    if (digitalRead(BUTTON_PIN) == HIGH)
    { // start measuring
        pulseInTimeBegin = micros();
    }
    else
    { // stop measuring
        pulseInTimeEnd = micros();
        newPulseDurationAvailable = true;
    }
}

Когда мы попадаем в прерывание, чтобы узнать, где мы находимся, мы сначала проверяем, находится ли текущий сигнал на выводе кнопки в HIGH или LOW. Если он ВЫСОКИЙ, мы знаем, что импульс только начался (сигнал изменился с НИЗКОГО на ВЫСОКИЙ), и мы сохраняем текущее время. Если он НИЗКИЙ, мы знаем, что импульс только что закончился (сигнал изменился с ВЫСОКОГО на НИЗКИЙ). В этом случае мы делаем 2 вещи: сохраняем текущее время и устанавливаем флаг.

void setup() – присоединить прерывание

void setup() 
{ 
    Serial.begin(9600); 
    pinMode(BUTTON_PIN, INPUT); 
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonPinInterrupt, CHANGE); 
}

В void setup() мы прикрепляем функцию прерывания к выводу кнопки. Здесь мы используем режим CHANGE, который представляет собой комбинацию ПОВЫШЕНИЯ (переход сигнала от НИЗКОГО к ВЫСОКЕМУ) и ПАДЕНИЯ (переход сигнала от ВЫСОКОГО к НИЗКОМу). Итак, для обоих событий будет вызвано прерывание — и нам нужно будет снова проверить состояние при вызове функции прерывания.

voidloop() – проверка флага

void loop()
{
    if (newPulseDurationAvailable)
    {
        newPulseDurationAvailable = false;
        unsigned long pulseDuration = pulseInTimeEnd - pulseInTimeBegin;
        Serial.println(pulseDuration);
    } // do your other stuff here }

Цикл void() теперь очень прост: вам просто нужно проверить наличие флага. Если флаг установлен, вы можете вычислить продолжительность с помощью двух имеющихся у вас переменных времени. Кроме того, не забудьте снять флаг (установить логическое значение false). После этого очень маленького (и быстрого) блока кода вы можете выполнить любое другое действие в цикле void(). Больше ничего не застревает.

Что делать, чтобы измерить пульс в режиме LOW

Все, что мы сделали в этом уроке, предназначено для случаев, когда сигнал по умолчанию НИЗКИЙ, а импульс происходит, когда сигнал ВЫСОКИЙ. Однако если у вас есть подтягивающий резистор для вашей кнопки, импульс будет инвертирован: сигнал ВЫСОКИЙ по умолчанию и НИЗКИЙ во время импульса. И хорошая новость здесь в том, что код будет практически таким же, вам останется только изменить одну строчку.

if (digitalRead(BUTTON_PIN) == HIGH) {
becomes
if (digitalRead(BUTTON_PIN) == LOW) {

Вот и все! Вы просто измеряете начало импульса, когда сигнал переходит от ВЫСОКОГО к НИЗКОМу, и измеряете конец импульса, когда сигнал переходит от НИЗКОГО к ВЫСОКЕМУ.

Вывод –pulsIn() с прерываниями

В этом уроке вы увидели, как воспроизвести поведение функции ArduinopulsIn() с использованием прерываний. Этот метод может показаться немного более сложным, но как только вы его поймете, вы сможете очень быстро его воспроизвести. Помимо основного преимущества отсутствия блокировки вашего кода, это также более надежный способ измерения пульса. Какой бы ни была длительность импульса (очень короткая или очень длинная), вы сможете ее измерить. Кроме того, функция PulseIn() может быть не очень точной из-за таймаута и более длинных импульсов. Никаких проблем при использовании прерываний. Единственное ограничение здесь заключается в том, что вам придется использовать контакты прерывания в вашей схеме. В Arduino Uno у вас есть только 2 таких контакта, поэтому, если вы уже зависите от прерываний для других функций вашей схемы, вам может не хватать контактов, и вам придется использовать плату с большим количеством прерываний (например, Arduino Mega).