Плохие показания сигнала тахометра вентилятора 12 В pwm

У меня есть следующая схема:

(Питание Arduino осуществляется через USB)

Проблема в том, что показания тахометра вентилятора неверны,

Я попытался добавить два резистора в качестве делителя напряжения, но показания все равно неверны. Когда я проверяю напряжение сигнала тахометра мультиметром, за время подключения мультиметра показания несколько правильные (не 100%).

Вот фрагмент документации фаната:

(Кстати: когда я использовал другой 12-вольтовый ШИМ-вентилятор и не использовал делитель напряжения, показания были неправильными только в то время, когда я выключал питание 12 В до тех пор, пока вентилятор не перестал вращаться (после того, как он перестал вращаться, тахометр перестал посылать сигналы). поэтому скорость вращения была равна 0, что было правильно).)

Рабочий код (правильное значение сигнала тахометра):

const byte tachpin = 2;

unsigned int tacho = 0;
long int startTime = 0;
long int elapsed = 0;

void setup() {
  Serial.begin(9600);
   // Настройте Таймер 1 для ШИМ @ 25 кГц.
    TCCR1A = 0;           
    TCCR1B = 0;           
    TCNT1  = 0;           
    TCCR1A = _BV(COM1A1)  
           | _BV(COM1B1)  
           | _BV(WGM11);  
    TCCR1B = _BV(WGM13)   
           | _BV(CS10);   
    ICR1   = 320;         



    pinMode(tachpin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(tachpin), tachisr, RISING);
    startTime = millis();
}

void tachisr(){
  tacho++;
}

void loop() {
      elapsed = millis() - startTime;
      if(elapsed > 1000) {
        int tachcopy = 0;
        noInterrupts();
        tachcopy = tacho;
        tacho = 0;
        interrupts();
        rpm = (tachcopy / 2.0) * (60000 / elapsed);
        Serial.println(rpm,DEC);
      }
      startTime = millis();
}

Правильное чтение из рабочего кода

rpm = 0, tachcopy = 0
rpm = 1150, tachcopy = 39
rpm = 2360, tachcopy = 80
rpm = 2655, tachcopy = 90
rpm = 2773, tachcopy = 94
rpm = 2655, tachcopy = 90
rpm = 1268, tachcopy = 43

Не рабочий код (неверный сигнал тахометра):

const byte tachpin = 2;

unsigned int tacho = 0;
long int startTime = 0;
long int elapsed = 0;
long int rpm = 0;
unsigned int pwmValue = 0;
unsigned int outValue = 0;
unsigned int seconds_counter = 0;

void setup() {
  Serial.begin(9600);
   // Настройте Таймер 1 для ШИМ @ 25 кГц.
    TCCR1A = 0;           
    TCCR1B = 0;           
    TCNT1  = 0;          
    TCCR1A = _BV(COM1A1)  
           | _BV(COM1B1)  
           | _BV(WGM11);  
    TCCR1B = _BV(WGM13)   
           | _BV(CS10);  
    ICR1   = 320;        

    // Установите выводы ШИМ в качестве выходных.
    pinMode( 9, OUTPUT); //!!!ДОБАВЛЕНА СТРОКА!!!



    pinMode(tachpin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(tachpin), tachisr, RISING);
    startTime = millis();
}

void tachisr(){
  tacho++;
}

//!! ДОБАВЛЕНА ФУНКЦИЯ!!!
void analogWrite25k(int value) 
{

    OCR1A = value;  
    
}

void loop() {

      elapsed = millis() - startTime;
      if(elapsed > 1000) {
        int tachcopy = 0;
        noInterrupts();
        tachcopy = tacho;
        tacho = 0;
        interrupts();
        startTime = millis();
        rpm = (tachcopy / 2.0) * (60000 / elapsed);
        Serial.print("rpm = ");
        Serial.print(rpm,DEC);
        Serial.print(", tachcopy = ");
        Serial.println(tachcopy,DEC);

        seconds_counter++;
      }

      analogWrite25k(100); //!!!ДОБАВЛЕНА СТРОКА!!!

      
}

не работает вывод кода:

rpm = 737854, tachcopy = 25012
rpm = 738591, tachcopy = 25037
rpm = 737824, tachcopy = 25011
rpm = 738591, tachcopy = 25037
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737824, tachcopy = 25011
rpm = 738591, tachcopy = 25037
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012
rpm = 738562, tachcopy = 25036
rpm = 737854, tachcopy = 25012

Показания при использовании резистора 10 кОм в качестве подтягивающего (вместо делителя напряжения (10 кОм+4,7 кОм)) и максимизации аналоговой записи 25 кОм - analogWrite25 кОм(320);:

rpm = 6165
rpm = 6401
rpm = 6136
rpm = 6136
rpm = 6165
rpm = 6018
rpm = 6283
rpm = 5870
rpm = 6047
rpm = 6578
rpm = 6460
rpm = 6224
rpm = 5782
rpm = 6047
rpm = 6136
rpm = 6136
rpm = 6313
rpm = 5988
rpm = 6224
rpm = 6195
rpm = 6136
rpm = 5900

Что интересно, поскольку максимальная скорость вентилятора составляет ~3000 об/мин (поэтому показания об/мин примерно умножаются на 2 от максимальной скорости вращения вентилятора при максимальной скорости AnalogWrite25k)

Обновление:

Добавлено подавление дребезга + 10 кОм для Arduino 5V:

#define DEBOUNCE 10
// Обработчик прерываний. Сохраняет временные метки последних 2 прерываний и обрабатывает устранение дребезга.
unsigned long volatile ts1=0,ts2=0;
//Рассчитывает RPM на основе меток времени последних 2 прерываний. Можно позвонить в любое время.
unsigned int calcRPM(){
    unsigned long ts1_copy, ts2_copy;
    noInterrupts();
    ts1_copy = ts1;
    ts2_copy = ts2;
    interrupts();

    Serial.print(ts1_copy);
    Serial.print(" / ");
    Serial.print(ts2_copy);
    Serial.print(" / ");
    Serial.println(ts2_copy - ts1_copy);   
    return (60000000.0 / (ts2_copy - ts1_copy ) / 2.0);
}

void tachisr2() {
    unsigned long m=micros();
    if((m-ts2)>DEBOUNCE){
        ts1=ts2;
        ts2=m;
    }
}

Вывод после добавления устранения дребезга:

rpm = 2953.92
90923308 / 90923320 / 12
rpm = 2500000.00
91948328 / 91958496 / 10168
rpm = 2950.43
92994188 / 92994200 / 12
rpm = 2500000.00
94031032 / 94031040 / 8
rpm = 3750000.00
95047108 / 95057256 / 10148
rpm = 2956.25
96093308 / 96093320 / 12
rpm = 2500000.00
97119144 / 97129296 / 10152
rpm = 2955.08

Если значение равно ~2900, это правильное значение.

, 👍4

Обсуждение

Пожалуйста, покажите код, который дал вам неправильные значения, и объясните, как именно они неверны и как вы проверили правильные значения., @chrisl

@chrisl, добавил код, я знаю, что значения неверны, потому что я получаю> 10 000 в качестве оборотов в минуту, а иногда и отрицательные числа, @Takata

@jsotola это делитель напряжения, я также пробовал подтянуть 10K, подключенный к 3,3 В без делителя напряжения, и также получил неправильные показания, @Takata

@jsotola, проверьте мое обновление, я понял, что проблема возникает только тогда, когда я включил выходной контакт pwm., @Takata


1 ответ


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

1

1. Выход модуля Open Collector.

Питание для выхода с открытым коллектором должно поступать от MCU, а не (косвенно) от источника питания датчика. Это очень похоже на подключение кнопочного переключателя на стороне низкого напряжения. Обычно внутренний подтягивающий резистор вывода Arduino, который подключен к модулю, пропускает достаточную мощность, чтобы определить состояние этого выхода (как вы сделали). Этот внутренний подтягивающий резистор имеет значение около 30 кОм. Также можно добавить внешний подтягивающий резистор с меньшим значением, если это необходимо, например, из-за электрических помех, между шиной питания 5 В Arduino и контактом. Однако убедитесь, что ток находится в допустимых пределах для подключенного устройства.

Измерение напряжения ШИМ с помощью мультиметра, как вы обнаружили, не увенчается успехом. Они имеют тенденцию к интегрированию и показывают среднее напряжение. Скажем, 12 вольт при рабочем цикле 50% будут читаться как 6 вольт. Используйте осциллограф, чтобы увидеть напряжение на форме волны.

2. Электрические помехи.

Ваше число здесь tachcopy = 25012 указывает на то, что ваш сигнал 25 кГц просачивается и определяется как импульсы от датчика. Питание открытого коллектора через 5-вольтовую шину питания Nano (как указано выше) может устранить эту проблему. Возможно, также лучше развязать 12-вольтовый источник питания (скажем, добавить большой конденсатор).

3. Проблемы с кодом

В вашем коде есть ряд проблем, которые следует устранить. Некоторые могут появиться только через 25 дней. Некоторые могут появиться после следующей перекомпиляции, а некоторые влияют на точность результатов.

  • unsigned int tacho = 0; должно быть изменчивым.
  • int tachcopy = 0; должно быть целым числом без знака, чтобы соответствовать копируемой ленте данных.
  • Переменные, производные от millis (), должны быть длинными без знака (uint32_t), как здесь: elapsed = millis() - startTime;.
  • Этот (60000/прошло) должен быть принудительно разделен на число с плавающей запятой, скажем (60000.0/прошло)
  • Это может быть в setup() вместо loop(): analogWrite25k(100); //!!!ДОБАВЛЕНА СТРОКА!!!

4. "показания оборотов примерно в 2 раза больше максимальных оборотов вентилятора"

Я не могу этого объяснить. Возможно, необходимо устранить дребезг выходного сигнала датчика.

5. Пример процедуры устранения дребезга (принцип блокировки)

// Простая процедура устранения отказов. На основе исходного кода.
// После приема импульса дальнейшие импульсы блокируются на 3 мс
// Должно быть нормально для 100 Гц (период 10 мс)

void tachisr(){
  // ISR (внешнее прерывание)
  static uint32_t lastAcceptedPulseAtMs = 0 ; // статическая инициализация только один раз
  uint32_t ms = millis() ;
  if ( ms - lastAcceptedPulseAtMs >= 3 ) {  // 3 мс - настроить при необходимости
    lastAcceptedPulseAtMs = ms ; 
    tacho++;
  }
}

Примечание. ISR должен быстро выполняться и не должен содержать Serial.print() и т. д.

,

Да, разминка обязательна. Иногда он также имеет положения, которые «колеблются», даже если он не работает (4-контактные вентиляторы с 0% ШИМ), @KIIV

@KIIV Я быстро поискал, но не смог узнать, какая технология используется для тахометра. Некоторые из них очень склонны к подпрыгиванию, скажем, геркону, однако я сомневаюсь, что это продлится очень долго в таком приложении. Датчик Холла или что-то оптическое было бы менее рискованным. Может быть, ОП может сказать нам, что использует их модель., @6v6gt

@ 6v6gt Спасибо за ваш отличный ответ, я немного проверю его, однако я не понимаю, почему tacho должен быть изменчивым? (я обращаюсь к нему только тогда, когда прерывания отключены и один раз в ISR). Что касается вашего вопроса, у меня нет доступа к контроллеру FAN, но мой вентилятор Noctua NF-F12., @Takata

@Takata tacho должен быть изменчивым, потому что «источник правды» находится в памяти, а не в регистрах ЦП. Для «обычных» переменных компилятор может переключать источник истины между памятью и регистрами по своему усмотрению. Но в вашем случае, без volatile, регистр, содержащий значение tacho, находясь в loop(), назначается и заполняется из памяти в начале loop(), то есть до того, как вы вызываете noInterrupts() . volatile в основном добавляет машинные инструкции для ЗАГРУЗКИ значения из памяти в регистр в тот момент, когда вы читаете переменную в своем коде (и СОХРАНЯЕТСЯ сразу после записи), @orithena

@Takata Если volatile отсутствует, компилятор может оптимизировать использование регистров по своему усмотрению - и для этого он предполагает полностью линейную программу, что явно не относится к прерываниям. Компилятор может даже решить полностью сохранить переменную в регистрах, которые сохраняются в стеке при входе в функцию ISR и восстанавливаются при выходе из нее., @orithena

@ 6v6gt Добавлен код устранения дребезга, значение оборотов в некоторых случаях правильное, @Takata

Значение DEBOUNCE, равное 30, дает хорошие показания тахометра в 90% случаев, этого недостаточно., @Takata

@Takata Я предполагаю, что при сигнале с максимальной скоростью 3000 об / мин (100 Гц при 2 импульсах на оборот) вам нужна блокировка примерно на 3 мс, если вы делаете это в программном обеспечении. Однако, если сигнал очень шумный, можно использовать более короткие провода и/или усиление подтягивающего резистора. Вероятно, около 2k, подтянутое до 5 вольт, - это самое сильное, на что вы можете пойти. Вы знаете, на чем основан датчик тахометра? Эффект Холла, переключатель чтения или другое?, @6v6gt

@Takata Ваша логика устранения отказов выглядит довольно сложной. Я добавил простую версию в главу 5 моего ответа. Это просто расширение вашего исходного ISR и не требует дополнительного кода global или loop(). Он работает, принимая первый импульс, а затем игнорируя последующие в течение следующих 3 мс. Если общее сопротивление подтягивания составляет около 10 кОм, вы можете добиться того же эффекта, установив 2 или 3 конденсатора по 100 нФ между контактом 2 и заземлением., @6v6gt

@ 6v6gt Удивительно, большое спасибо!, @Takata

@Takata Я только что заметил, что триггер прерывания - это ВОСХОДЯЩИЙ край в вашем коде. Для выхода с открытым коллектором он действительно должен ПАДАТЬ. Задний фронт чище, потому что транзистор на выходе резко тянет вниз при включении. В выключенном состоянии выход плавает из-за подтягивающего резистора и любого емкостного эффекта датчика. Имеет ли это какое-то реальное значение в вашем случае, я не знаю., @6v6gt

@6v6gt На самом деле в моем случае RISING работает лучше. Я опубликовал обновление. Надеюсь, вы поможете мне разобраться. Еще раз спасибо!, @Takata

@Takata Опубликуйте обновленный код. Некоторые вещи, которые стоит попробовать (1) уменьшить сопротивление подтягивающего резистора до 5 В примерно до 2 кОм (не ниже 1 кОм) (2) увеличить скорость печати с 9600 до 115 200 бод (3) попробовать установить конденсатор емкостью 100 нф между нано-контактом D2 и GND (предполагается, что Подтягивающий резистор ~2 кОм, как предложено выше). (4) проверьте выходной сигнал с помощью осциллографа. Альтернативой может быть оптический метод с ИК-светодиодом и ИК-фототранзистором. Магнитный метод может создавать помехи существующему вентиляторному механизму., @6v6gt