Непрерывное чтение двух последовательных периодов импульса

Я контролирую скорость двигателя/вентилятора постоянного тока с помощью ШИМ с помощью переменного аналогового входа. Использование Uno или Nano.

Цикл выглядит следующим образом:

void loop()
{    
  val = analogRead(analogPin0);  // прочитать пин аналогового входа
  analogWrite(pwmPin9, map(val, 0, 1024, 0 , 255));
}

Вышеуказанное значение AnalogPin0 изменяется на потенциометр.

Но я также хочу выводить скорость вращения на ЖК-дисплей. Для этого мне нужно измерить период между двумя последовательными импульсами, как показано ниже:

Как я могу непрерывно измерять "период между двумя последовательными импульсами"? Должен ли я использовать прерывание? У кого-нибудь есть опыт?

, 👍0

Обсуждение

О каком временном диапазоне здесь идет речь?, @chrisl

Длительность одного импульса от минимум 4 мс до 100 мс, @user1245

зачем нужно измерять сигнал? ... вы точно знаете значение параметра, который вы передаете в функцию AnalogWrite(), @jsotola

Я не знаю точных оборотов, только рабочий цикл, @user1245

вы не забыли рассказать нам о датчике оборотов? ...непонятно как вы рассчитываете определить обороты вентилятора по ширине импульса напряжения питания, @jsotola


2 ответа


Лучший ответ:

1

Я вижу два пути:

  1. Вы можете прикрепить к выводу простое прерывание по переднему фронту с помощью attachInterrupt() и подсчитать нарастающие фронты с помощью переменной. Если ощущается третий нарастающий фронт, у вас есть 1 период. Чтобы измерить время, вы можете сохранить время начала первого переднего фронта в переменной с помощью micros() (что дает вам время с момента запуска в микросекундах). По третьему переднему фронту сохраните разницу во времени между началом и концом в другую переменную (объявленную как volatile): micros()-start_time. Затем эту переменную можно отобразить в вашей функции loop(). Обязательно скопируйте переменную только в локальную переменную в этой части и отключите прерывания во время этой операции с помощью noInterrupts() (после этого повторно активируйте прерывания с помощью interrupts(). Также вы следует использовать атомарную переменную флага (атомарное значение, что запись или чтение этой переменной не может быть прервана. Например, если вы используете byte или любую другую 1-байтовую переменную), чтобы показать loop(), что есть новые данные для отображения. Только в этом случае код в функции loop() будет обрабатывать данные.

  2. Если вы хотите проявить фантазию или хотите измерять более высокие скорости (более низкие периоды), вы можете использовать оборудование Timer1 в качестве счетчика. Вы даете Timer1 свой сигнал в качестве тактового входа. На каждом фронте значение таймера увеличивается на 1. Таким образом, когда значение таймера равно 5, прошел один период. Вы можете использовать таймер в режиме сравнения совпадений, чтобы он генерировал прерывание, если в регистре значения таймера находится специальное значение (здесь 5). В этой ISR (подпрограмме обслуживания прерываний) вы должны записать текущее время с помощью micros(). При совпадении сравнения таймер генерирует прерывание, снова устанавливает значение на ноль (если настроено правильно) и снова начинает считать. Таким образом, период будет временем между двумя последовательными измерениями времени. Отображение в функции loop() можно обрабатывать по принципу, описанному выше.

Кроме того, было бы неплохо напрямую взять среднее значение за период (чтобы отфильтровать шум в периоде) путем подсчета, пока не пройдет несколько периодов. В первом случае вы могли сосчитать до 11, чтобы получить 5 периодов. Затем разделите разницу во времени на 5, чтобы получить 1 период. Во втором случае вы можете установить регистр соответствия Timer1 на 21, чтобы получить 5 периодов. Здесь также разделите время на 5, чтобы получить 1 период.

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

Первый способ может выглядеть примерно так:

const int pin = 2;

volatile byte counter = 0;
volatile unsigned long start_time;
volatile unsigned long period;
volatile byte data_flag = 0;

void speed_ISR(){
    counter++;
    if(counter==3){
        unsigned long now = micros();
        period = now - start_time;
        data_flag = 1;
        start_time = now;
        counter = 1;
    }
}

void setup(){
    pinMode(pin, INPUT);
    attachInterrupt(digitalPinToInterrupt(pin), speed_ISR, RISING);
}

void loop(){
    if(data_flag){
        noInterrupts();
        unsigned long local_period = period;
        data_flag = 0;
        interrupts();
        // Теперь отображаем период на ЖК-дисплее
    } else {
        // Вывести ноль на ЖК-дисплей
    }
}

Код дает вам возможность записывать ноль на ЖК-дисплей, когда нет импульсного входа (следовательно, нет данных --> data_flag должен быть равен нулю). Это может быть неудобно для чтения на низких частотах импульсов, если ЖК-дисплею не требуется больше времени для отображения результатов, поскольку будет измерен следующий импульс, поскольку он может отображать ноль и следующее измерение в быстрой последовательности. Вы можете либо использовать millis(), чтобы установить data_flag в ноль через указанное время (вместо того, чтобы сбрасывать его рядом с кодом ЖК-дисплея), либо вы можете ограничить скорость отображения, как мерцание выполняется в примере BlinkWithoutDelay, который поставляется с Arduino IDE.

Обратите внимание, что этот код не тестировался.

,

Я получаю сообщение об ошибке: ожидаемое первичное выражение перед токеном "==". это в строке if(byte==3){, @user1245

будет ли байт счетчиком?, @user1245

А также наличие «NoInterrupts» не было объявлено в этой области «, @user1245

Хорошо, опечатки. Да, оператор if в ISR должен быть со счетчиком. Также это noInterrupts(), а не notInterrupts. Извините, я изменил свой ответ, @chrisl

И в ISR не хватает 2 точек с запятой. Я надеюсь, что теперь это структурно правильно, @chrisl

Какие контакты вместо контакта 2 я могу использовать?, @user1245

Я изменил вызов attachInterrupt, чтобы он был действительно правильным, как в документации. Вы можете посмотреть [здесь][https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/] таблицу, какие выводы можно использовать. В документации сказано, что на Uno вы можете использовать контакты 2 или 3., @chrisl

micros() следует вызывать только один раз в ISR: unsigned long now = micros(); период = сейчас - время_начала; время_начала = сейчас; ... Обратите внимание, что только period и data_flag должны быть volatile. counter и start_time могут быть статическими локальными переменными для speed_ISR()., @Edgar Bonet

Насчет volatile переменных: Да, вы правы. О micros(): почему нельзя вызывать micros() несколько раз? Значение не изменится во время ISR, так как это зависит от прерываний., @chrisl

@chris Не могли бы вы обновить свой код? Будет ли эта часть находиться за пределами ISR unsigned long now = micros(); период = сейчас - время_начала; время_начала = сейчас; ?, @user1245

Вызов micros() занимает нетривиальное количество циклов. И его значение _будет_ изменяться во время ISR, так как [оно зависит от таймера](https://github.com/arduino/ArduinoCore-avr/blob/1.6.23/cores/arduino/wiring.c#L86)., @Edgar Bonet

Моя другая проблема: я не мог изменить программу для вывода нуля, когда нет импульсного входа., @user1245

if(!data_flag) сделает это, я думаю?, @user1245

@atmnt Я снова изменил код, так как Эдгар прав насчет micros(). В отличие от millis(), micros() также будет изменяться во время ISR, так как он считывается непосредственно из регистра таймера. Также я добавил запрошенный функционал, описание подводных камней и как их избежать., @chrisl


1

Прерывание одного из таймеров кажется самым простым способом. Чтобы получить точность измерения, скажем, 3% при 4 мс, означает выборку каждые (4000 мкс * 0,03) = 120 мкс (8,333 кГц). Если вашему коду больше нечего делать, вам может сойти с рук наблюдение за часами — micros() отсчитывает каждые 4 мкс, — но вам нужно будет сохранять все остальное, что вы делаете между чтениями micros () до менее чем 120 us, и половина этого может быть хорошим целевым значением.

В этом ответе я описал общую неблокирующую процедуру для своевременного управления несколькими задачами. Если вы выбрали такой дизайн, одна задача может управлять потенциометром и аналоговым выходом, другая — измерением оборотов, а третья — ЖК-дисплеем. ISR ничего не сделает, кроме как установит флаг.

Доход на тысячу показов & Задачи ЖК-дисплея могут выполняться с относительно низкой скоростью, поскольку они предназначены только для потребления человеком. Однако им, возможно, придется работать по частям, чтобы уложиться в ограничения по времени.

,