Подсчет количества импульсов в секунду

Для моего приложения спидометра я хочу подсчитать количество импульсов, происходящих каждую секунду, и преобразовать его в скорость. Для этого мне нужно посчитать количество импульсов, возникающих на данном входном выводе за одну секунду. Этот счетчик используется для преобразования в скорость. Счет должен обнулиться и начинаться заново через каждую секунду. Но я не понимаю, как установить ограничение в одну секунду и начинать каждую секунду заново. Пока что я пришел к следующему:

int count = 0;
float lasttime = 0;
float currenttime = 0;
const int speedpin = 52;

void setup()
{
    Serial.begin(115200);
    pinMode(speedpin, INPUT);
}

void loop()
{
    currenttime = millis();
    if (currenttime - lasttime <= 1000)
    {
        if (digitalRead(speedpin == HIGH))
        {
            count = count + 1;
        }
        else
        {
            count = count;
        }
    }
    else
    {
        count = 0;
    }
    Serial.println(count);
}

, 👍3

Обсуждение

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

@chrisl Импульсы начинают пропадать гораздо быстрее, чем при использовании внешнего источника синхронизации для счетчика. Зависит от того, сколько циклов микроконтроллера требуется для обработки всего ISR., @KIIV

@KIIV: если импульсы не отскакивают (как это делают механические переключатели), AttachInterrupt() довольно удобен, а сам подсчет происходит очень быстро. Если ожидается менее 256 импульсов в секунду, обработка этого счетчика байтов в основном цикле также будет очень простой., @DataFiddler

Как вы планируете отображать выходные данные приложения спидометра? Будете ли вы использовать последовательный монитор, модуль 7-сегментного дисплея или ЖК-дисплей 16 x 2?, @VE7JRO

Прямо сейчас вижу на последовательном мониторе. Но в приложении я буду отображать это на ЖК-экране., @Jackie

На каком автомобиле вы будете использовать приложение спидометра?, @VE7JRO

Вообще-то это для проекта по электровелосипедам, @Jackie


4 ответа


0

Сначала выровняйте свой код:

int count = 0;
float lasttime = 0;
float currenttime = 0;
const int speedpin = 52;

void setup()
{
  Serial.begin(115200);
  // поместите сюда свой код установки для однократного запуска:
  pinMode(speedpin, INPUT);
}

void loop() {
  // поместите сюда свой основной код для многократного запуска:
  currenttime = millis();
  if(currenttime - lasttime <= 1000)
  {
    if (digitalRead(speedpin == HIGH))
    {
      count = count + 1;
    }
    else
    {
      count=count;
    }
  }
  else 
  {
    count=0;
  }
  Serial.println(count);
}

Тогда оператор count = count присваивает себе переменную, которая не нужна, и для приращения вы можете использовать count++, и вы получаете:

int count = 0;
float lasttime = 0;
float currenttime = 0;
const int speedpin = 52;

void setup()
{
  Serial.begin(115200);
  // поместите сюда свой код установки для однократного запуска:
  pinMode(speedpin, INPUT);
}

void loop() {
  // поместите сюда свой основной код для многократного запуска:
  currenttime = millis();
  if(currenttime - lasttime <= 1000)
  {
    if (digitalRead(speedpin == HIGH))
    {
      count++;
    }
  }
  else 
  {
    count = 0;
  }
  Serial.println(count);
}

В цикле вы не подсчитываете импульсы, а только измерения, когда на выводе ВЫСОКИЙ уровень, а когда на выводе НИЗКИЙ уровень где-либо в течение секунды, он устанавливается на 0.

Вместо этого вы хотите сделать следующее (при условии, что вы хотите подсчитать переходные процессы LOW -> HIGH: создать глобальную переменную с именем, например, previousState. Теперь, если previousState == LOW и текущее состояние GPIO равно HIGH, только чем увеличивать счетчик на единицу. Кроме того, внутри цикла каждый раз обновляйте предыдущее состояние текущим состоянием.

Реализацию оставляю на ваше усмотрение.

,

Но одну секунду мне приходится считать пульс. И после того, как эта секунда превысит счет, он должен обнулиться и начать отсчет снова. Таким образом, мне нужно количество импульсов за каждую секунду., @Jackie

Затем через одну секунду вы сохраняете количество измеренных импульсов (count) в отдельной переменной для обработки в следующую секунду (в то время как count уже измеряет новые импульсы за эту секунду)., @Michel Keijzers


0

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

Вот простой тестовый скетч, отображающий частоту входного сигнала.

Дополнение

На основании вашего комментария «...Я покажу это на ЖК-экране» я обновил скетч для работы с ЖК-экраном.

// Эта строка кода необходима для Arduino 1.0.6 IDE.
#define NOT_AN_INTERRUPT -1

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

const byte interruptPin = 2;
unsigned long previousMillis = 0;
unsigned long previousMillisLCD = 0;
unsigned long lcdRefreshRate = 500;
unsigned long resetDisplayTimer = 1500;
volatile byte pulseDetected = 0;
float frequency = 0.0;

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup(){
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), pulse, FALLING);
  lcd.init();
  lcd.backlight();
}

void loop(){

  unsigned long currentMillis = millis();

  if(pulseDetected == 1){
    frequency = 1.0 / ((currentMillis - previousMillis) / 1000.0);
    previousMillis = currentMillis;
    pulseDetected = 0;
  }

  // Пульс не обнаружен в течение 1,5 секунд, поэтому сбросьте значение дисплея на 0.
  if(currentMillis - previousMillis >= resetDisplayTimer){
    frequency = 0.0;
  }

  if(currentMillis - previousMillisLCD >= lcdRefreshRate){
    previousMillisLCD = currentMillis;
    lcd.setCursor(0, 0);
    lcd.print(frequency);
    lcd.print("                ");
  }
}

void pulse(){
  pulseDetected = 1;
}
,

Это синхронизация интервала между последовательными импульсами с разрешением millis() (2 мс). Если частота импульсов высокая, результат будет довольно шумным, и вы можете даже пропустить импульсы из-за того, что Serial.print() станет медленным при заполнении последовательного буфера., @Edgar Bonet

Хорошая мысль о том, что Serial.print() становится медленным. Я попросил ОП разъяснить, какой тип «дисплея» будет иметь их приложение спидометра. Возможно, использование «micros()» вместо «millis()» было бы лучше, ЕСЛИ частота импульсов высокая., @VE7JRO

ОП сообщает, что они будут использовать ЖК-экран для отображения данных, поэтому замедление работы Serial.print() не будет проблемой., @VE7JRO

@Эдгар Боне: Он может потерять отображение, поэтому он может отобразить 10, затем 12 и пропустить «отображение» 11, но он НЕ потеряет счет., @Peter

@Питер: Мой комментарий был о коде в этом ответе, а не о коде в вопросе., @Edgar Bonet


3

Как уже объяснялось в ответе Мишеля Кейзерса, вместо того, чтобы считать, как много раз вы находите входное значение HIGH, вам следует посчитать LOW → Переходы HIGH. Для этого необходимо сохранить предыдущее состояние ввода и подсчет перехода только тогда, когда входной сигнал имеет значение HIGH, в то время как он был LOW когда вы видели это в последний раз:

static int last_pin_state = LOW;
int pin_state = digitalRead(SPEED_PIN);
if (pin_state == HIGH && last_pin_state == LOW) {  // нарастающий фронт
    count++;
}
last_pin_state = pin_state;

Для отображения счетчика каждую секунду используйте метод, описанный в разделе Мигайте без задержки Учебное пособие по Arduino:

static uint32_t last_time = 0;
if (millis() - last_time >= PERIOD) {
    Serial.println(count);
    count = 0;
    last_time += PERIOD;
}

Обратите внимание, что вам придется сбрасывать счетчик каждый раз, когда вы его распечатываете.

VE7JRO предлагает использовать прерывания. Прерывания избавят вас от боли написание обнаружения края. С другой стороны, они также делают вещи немного сложнее, потому что теперь вам придется беспокоиться о атомарность, условия гонки, изменчивые переменные и тому подобное. Здесь нет необходимо использовать прерывания, если только импульсы не очень быстрые (или ваш loop() настолько медленный), что вы можете пропустить пульс. Если ваш loop() становится больше и медленнее, и вы рискуете пропустить импульсы, тогда прерывания действительно могут помочь. Но правильный способ их использования — подсчет импульсов в ISR. Если вы используйте ISR только для поднятия флага, вы теряете преимущество в скорости прерывания, в этом и состоит весь смысл этого подхода.

Процедура обработки прерываний будет такой:

volatile unsigned int count;

void count_pulse()
{
    count++;
}

Затем циклу нужно только напечатать и сбросить счетчик:

void loop()
{
    // Периодически печатаем и сбрасываем счетчик.
    static uint32_t last_time = 0;
    if (millis() - last_time >= PERIOD) {
        noInterrupts();
        unsigned int count_copy = count;
        count = 0;
        interrupts();
        Serial.println(count_copy);
        last_time += PERIOD;
    }
}

Обратите внимание, что переменная count используется совместно ISR и основным петля. Вот почему его необходимо объявить изменчивым. Также его доступ внутри loop() должен быть защищен в пределах критической секции, ограниченной с помощью пары noInterrupts()/interrupts(), иначе у вас будет состояние гонки. Также обратите внимание, что для сохранения критического раздела как короче, значение count копируется только внутри критический раздел, а фактическая печать откладывается после критический раздел завершен.

,

0

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

Вместо этого убедитесь, что длина счетчика соответствует максимальному количеству импульсов, которое вы когда-либо могли получить за один интервал. Затем через каждый интервал сохраняйте счетчик во временной папке, скажем, tempCount. Вычтите текущий «tempCount» из предыдущего счетчика «prevCount», в котором вы сохранили «count» в предыдущий раз. Разница будет заключаться в количестве импульсов, засчитанных в этом интервале. Используйте его или сохраните, или что-нибудь еще, что вам нужно с ним сделать. Затем сохраните tempCount в prevCount, и все готово.

Нет пропущенных импульсов, нет ошибок переполнения.

,

Если count поступает из ISR, вы можете безопасно прочитать его и очистить в том же критическом разделе. Если это не только 8 бит, вам все равно нужна эта критическая секция, чтобы ее прочитать. Если count исходит от аппаратного счетчика, то вы правы, его никогда не следует очищать., @Edgar Bonet

Да, если это сделано в критическом разделе. Но если фоновый код (ISR) собирает импульсы, а код переднего плана подсчитывает счетчики, вероятность ошибок все равно будет. Используя непрерывный счетчик (а его ширина всего 1 байт), можно избежать необходимости повторно отключать прерывания., @JRobert