Подсчет количества импульсов в секунду
Для моего приложения спидометра я хочу подсчитать количество импульсов, происходящих каждую секунду, и преобразовать его в скорость. Для этого мне нужно посчитать количество импульсов, возникающих на данном входном выводе за одну секунду. Этот счетчик используется для преобразования в скорость. Счет должен обнулиться и начинаться заново через каждую секунду. Но я не понимаю, как установить ограничение в одну секунду и начинать каждую секунду заново. Пока что я пришел к следующему:
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);
}
@Jackie, 👍3
Обсуждение4 ответа
Сначала выровняйте свой код:
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
Используя 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
Как уже объяснялось в ответе Мишеля Кейзерса, вместо того, чтобы считать, как
много раз вы находите входное значение 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
копируется только внутри
критический раздел, а фактическая печать откладывается после
критический раздел завершен.
Если счетчик поступает из счетчика или регистра устройства или сохраняется процедурой обслуживания прерываний, вы никогда не захотите обнулять счетчик. Если импульс поступает между моментом чтения переменной и моментом ее очистки, вы не смогли посчитать этот импульс.
Вместо этого убедитесь, что длина счетчика соответствует максимальному количеству импульсов, которое вы когда-либо могли получить за один интервал. Затем через каждый интервал сохраняйте счетчик во временной папке, скажем, tempCount. Вычтите текущий «tempCount» из предыдущего счетчика «prevCount», в котором вы сохранили «count» в предыдущий раз. Разница будет заключаться в количестве импульсов, засчитанных в этом интервале. Используйте его или сохраните, или что-нибудь еще, что вам нужно с ним сделать. Затем сохраните tempCount в prevCount, и все готово.
Нет пропущенных импульсов, нет ошибок переполнения.
Если count
поступает из ISR, вы можете безопасно прочитать его и очистить в том же критическом разделе. Если это не только 8 бит, вам все равно нужна эта критическая секция, чтобы ее прочитать. Если count
исходит от аппаратного счетчика, то вы правы, его никогда не следует очищать., @Edgar Bonet
Да, если это сделано в критическом разделе. Но если фоновый код (ISR) собирает импульсы, а код переднего плана подсчитывает счетчики, вероятность ошибок все равно будет. Используя непрерывный счетчик (а его ширина всего 1 байт), можно избежать необходимости повторно отключать прерывания., @JRobert
- C++ против языка Arduino?
- avrdude ser_open() can't set com-state
- Как читать и записывать EEPROM в ESP8266
- Float печатается только 2 десятичных знака после запятой
- устаревшее преобразование из строковой константы в 'char*'
- Запрограммировать ATMega328P и использовать его без платы Arduino.
- Разница между print() и println()
- Как исправить: Invalid conversion from 'const char*' to 'char*' [-fpermissive]
Возможно, вам захочется взглянуть на
attachInterrupt()
, который часто используется для подобных вещей, потому что с его помощью вы не пропустите ни одного импульса. Затем вам также следует больше прочитать о прерываниях, чтобы понять, что вы с их помощью кодируете., @chrisl@chrisl Импульсы начинают пропадать гораздо быстрее, чем при использовании внешнего источника синхронизации для счетчика. Зависит от того, сколько циклов микроконтроллера требуется для обработки всего ISR., @KIIV
@KIIV: если импульсы не отскакивают (как это делают механические переключатели), AttachInterrupt() довольно удобен, а сам подсчет происходит очень быстро. Если ожидается менее 256 импульсов в секунду, обработка этого счетчика байтов в основном цикле также будет очень простой., @DataFiddler
Как вы планируете отображать выходные данные приложения спидометра? Будете ли вы использовать последовательный монитор, модуль 7-сегментного дисплея или ЖК-дисплей 16 x 2?, @VE7JRO
Прямо сейчас вижу на последовательном мониторе. Но в приложении я буду отображать это на ЖК-экране., @Jackie
На каком автомобиле вы будете использовать приложение спидометра?, @VE7JRO
Вообще-то это для проекта по электровелосипедам, @Jackie