Мега: присоединение Interrupt на выводе 18/19/20/21 не работает

Задача: Я пытаюсь создать управление вентилятором с помощью arduino mega. Я генерирую ШИМ-сигнал на контактах 6 и 7 и хочу использовать прерывания для измерения скорости вращения вентиляторов.

Проблема: На контактах 2 и 3 все работает нормально, но я не могу заставить его работать ни на одном из контактов 18-21. Цифры прыгают дико. НО: цифры верны для 0% и 100% нагрузки!

Что я уже пробовал:

  • Изменил мой таймер с 1 на 4 (сейчас 4)
  • Заменен дисплей, чтобы избавиться от U8g2lib.
  • Изменение порядка подключения сигнальных кабелей (и, следовательно, подключенного вентилятора).
  • Проверьте сигнал с помощью цифрового осциллографа (все порты выглядят нормально)
  • Установите для глобальных переменных ISR значения volatile и byte, чтобы предотвратить опасность обновления.
  • Бейся головой об стол.

Ничто не решило мою проблему. Что я упускаю?

    #include <Arduino.h>
    #include <U8g2lib.h>
    #include <Bounce2.h>
    
    #ifdef U8X8_HAVE_HW_SPI
    #include <SPI.h>
    #endif
    #ifdef U8X8_HAVE_HW_I2C
    #include <Wire.h>
    #endif
    
    U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
    
    #define PIN_CONTROL_S 6
    #define PIN_CONTROL_L 7
    #define PIN_SENSE_1 18
    #define PIN_SENSE_2 19
    #define PIN_SENSE_3 3
    #define PIN_SENSE_4 2
    #define PIN_BUTTON 22
    
    #define UPDATE_CYCLE 1000 // Интервал обновления отображения/измерения.
    #define SIGNAL_PER_RND 2  // 2 сигнала на оборот
    #define DUTY_INC 10       // Увеличение пошлины в % за нажатие кнопки
    #define PWM_MAX 320       // режим ШИМ отсчитывает на 320 вверх, затем на 320 вниз (25 кГц)
    #define lineOffset 10     // Смещение (y) для каждой строки на дисплее
    
    word duty = 0;  // 0-100 рабочий цикл
    word dutyS = 0; // 0-320 = рабочий цикл 0-100% для небольших вентиляторов
    word dutyL = 0; // 0-320 = рабочий цикл 0-100% для больших вентиляторов
    
    volatile byte counterFan1 = 0;
    volatile byte counterFan2 = 0;
    volatile byte counterFan3 = 0;
    volatile byte counterFan4 = 0;
    
    unsigned long lastDraw = 0;
    
    Bounce btnBouncer = Bounce(PIN_BUTTON, 50);
    
    void setupPwm() {
        // Штойерунг
        pinMode(PIN_CONTROL_S, OUTPUT);
        pinMode(PIN_CONTROL_L, OUTPUT);
    
        // Сенсорен
        pinMode(PIN_SENSE_1, INPUT_PULLUP);
        pinMode(PIN_SENSE_2, INPUT_PULLUP);
        pinMode(PIN_SENSE_3, INPUT_PULLUP);
        pinMode(PIN_SENSE_4, INPUT_PULLUP);
    
        // Очистить регистр таймера
        TCCR4A = 0;
        TCCR4B = 0;
        TCCR4C = 0;
        TCNT4 = 0;
    
        TCCR4A |= _BV(WGM41);  // Установите режим ШИМ, правильная фаза. ТОР - это ICR1. (Режим 10)
        TCCR4B |= _BV(WGM43);  // Начать BOTTOM, TOP от OCR1x до OCR1x.
    
        TCCR4B |= _BV(CS40);   // Пределитель 1:1
    
        TCCR4A |= _BV(COM4A1); // Высокий выходной сигнал OC1A на compareMatch при прямом счете / низкий при обратном счете
        TCCR4A |= _BV(COM4B1); // Высокий выходной сигнал OC1B на compareMatch при прямом счете / низкий при обратном счете
    
        OCR4A = dutyS;         // устанавливаем начальную пошлину
        OCR4B = dutyL;         // устанавливаем начальную пошлину
    
        ICR4 = PWM_MAX; // ТОР для TCNTx. 320 => @ ЦП 16 МГц -> 25 кГц ШИМ
    }
    
    void setupDisplay() {
        u8g2.begin();
        u8g2.setFont(u8g2_font_6x10_tf);
        u8g2.setFontRefHeightExtendedText();
        u8g2.setDrawColor(1);
        u8g2.setFontPosTop();
        u8g2.setFontDirection(0);
    }
    
    void attachInterrups() {
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_1), isrFan1, RISING);
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_2), isrFan2, RISING);
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_3), isrFan3, RISING);
        attachInterrupt(digitalPinToInterrupt(PIN_SENSE_4), isrFan4, RISING);
    }
    
    void detachInterrups() {
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_1));
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_2));
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_3));
        detachInterrupt(digitalPinToInterrupt(PIN_SENSE_4));
    }
    
    void setup() {
        setupDisplay();
        setupPwm();
        attachInterrups();
    
        pinMode(PIN_BUTTON, INPUT_PULLUP);
    }
    
    void draw(void) {
        char output[22]; // В одной строке дисплея может отображаться 21 символ.
        float uPerSekS1 = counterFan1; // * 60000/measureDuration/SIGNAL_PER_RND;
        float uPerSekS2 = counterFan2; // * 60000/measureDuration/SIGNAL_PER_RND;
        float uPerSekL1 = counterFan3; // * 60000/measureDuration/SIGNAL_PER_RND;
        float uPerSekL2 = counterFan4; // * 60000/measureDuration/SIGNAL_PER_RND;
        int y = 0;
    
        sprintf(output, "Duty    :    %3d", duty);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec S1: %6d", (int) uPerSekS1);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec S2: %6d", (int) uPerSekS2);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec L1: %6d", (int) uPerSekL1);
        u8g2.drawStr(0, y, output);
    
        y += lineOffset;
        sprintf(output, "U/sec L2: %6d", (int) uPerSekL2);
        u8g2.drawStr(0, y, output);
    }
    
    void measure() {
        unsigned long measureDuration = millis() - lastDraw;
        if (measureDuration >= UPDATE_CYCLE) {
            // Деактивировать прерывание, пока мы вычисляем
            detachInterrups();
    
            u8g2.clearBuffer();
            draw();
            u8g2.sendBuffer();
    
            // сбросить состояние
            counterFan1 = 0;
            counterFan2 = 0;
            counterFan3 = 0;
            counterFan4 = 0;
            lastDraw = millis();
    
            // Повторно активировать прерывание
            attachInterrups();
        }
    }
    
    void loop() {
        measure();
        btnBouncer.update();
        if (btnBouncer.fell()) {
            // Кнопка была нажата. Повысить пошлину.
            if (duty > 90) {
                duty = 0;
            } else {
                duty += DUTY_INC;
            }
    
            dutyS = duty * PWM_MAX / 100;
            dutyL = duty * PWM_MAX / 100;
    
            OCR4A = dutyS;
            OCR4B = dutyL;
        }
    }
    
    void isrFan1() {
        counterFan1++;
    }
    
    void isrFan2() {
        counterFan2++;
    }
    
    void isrFan3() {
        counterFan3++;
    }
    
    void isrFan4() {
        counterFan4++;
    }

ОБНОВЛЕНО: Я изо всех сил старался создать удобочитаемую принципиальную схему. 4-контактные разъемы представляют собой вентиляторы.

Схема управления 4-контактным вентилятором

, 👍2

Обсуждение

Хорошо, может проблема в электронике? Я только что перепроверил свои утверждения и одновременно подключил осциллограф к ардуино, и оказалось, что осци стабилизирует результаты... Я использую подтягивающее сопротивление 5 кОм. Это нормально?, @Tarkil

Я не могу понять, какое отношение рабочий цикл имеет к чтению импульсов. Что будет, если отключить 6 и 7 и повернуть вентиляторы рукой? Вы уверены, что 8-битные счетчики не переполняются при быстром вращении вентилятора?, @Edgar Bonet

@EdgarBonet Если я отключу 6/7, вентиляторы будут работать на 100%. Я забыл упомянуть, что использую 4-контактные вентиляторы для ПК. Они подключены к 12 В и регулируются ШИМ 25 кГц, который я генерирую на третьем выводе. Четвертый контакт является сигнальным контактом с двумя сигналами на раунд., @Tarkil

@EdgarBonet ... и 8 бит не переполняются. Интервал измерения в настоящее время составляет 1 секунду, и я получаю до 50 сигналов за цикл измерения., @Tarkil

что подключено к прерыванию контактов и как?, @Juraj


1 ответ


1

Хорошо, я не решил проблему с контактами 18-21, но нашел другое решение: используя PinChangeInterrupts и библиотеку PinChangeInterrupt

Я ничего не менял в настройке оборудования, за исключением переноса проводов с контактов 18/19 на A8/A9. Я изменил свой код, чтобы использовать PinChangeInterrups для двух вентиляторов, возможно, я изменю все 4 вентилятора.

Вот новый код:

#include <Arduino.h>
#include <U8g2lib.h>
#include <Bounce2.h>
#include <PinChangeInterrupt.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

#define PIN_CONTROL_S 6
#define PIN_CONTROL_L 7

//#определить PIN_SENSE_1 18
//#определить PIN_SENSE_2 19
#define PIN_SENSE_1 A8
#define PIN_SENSE_2 A9

#define PIN_SENSE_3 3
#define PIN_SENSE_4 2
#define PIN_BUTTON 22

#define UPDATE_CYCLE 1000 // Интервал обновления отображения/измерения.
#define SIGNAL_PER_RND 2  // 2 сигнала на оборот
#define DUTY_INC 10       // Увеличение пошлины в % за нажатие кнопки
#define PWM_MAX 320       // режим ШИМ отсчитывает на 320 вверх, затем на 320 вниз (25 кГц)
#define lineOffset 10     // Смещение (y) для каждой строки на дисплее

word duty = 0;  // 0-100 рабочий цикл
word dutyS = 0; // 0-320 = рабочий цикл 0-100% для небольших вентиляторов
word dutyL = 0; // 0-320 = рабочий цикл 0-100% для больших вентиляторов

volatile byte counterFan1 = 0;
volatile byte counterFan2 = 0;
volatile byte counterFan3 = 0;
volatile byte counterFan4 = 0;

unsigned long measureDuration;
unsigned long lastDraw = 0;

Bounce btnBouncer = Bounce(PIN_BUTTON, 50);

void setupPwm() {
    // Штойерунг
    pinMode(PIN_CONTROL_S, OUTPUT);
    pinMode(PIN_CONTROL_L, OUTPUT);

    // Сенсорен
    pinMode(PIN_SENSE_1, INPUT_PULLUP);
    pinMode(PIN_SENSE_2, INPUT_PULLUP);
    pinMode(PIN_SENSE_3, INPUT_PULLUP);
    pinMode(PIN_SENSE_4, INPUT_PULLUP);

    // Очистить регистр таймера
    TCCR4A = 0;
    TCCR4B = 0;
    TCCR4C = 0;
    TCNT4 = 0;

    TCCR4A |= _BV(WGM41);  // Установите режим ШИМ, правильная фаза. ТОР - это ICR1. (Режим 10)
    TCCR4B |= _BV(WGM43);  // Начать BOTTOM, TOP от OCR1x до OCR1x.

    TCCR4B |= _BV(CS40);   // Пределитель 1:1

    TCCR4A |= _BV(COM4A1); // Высокий выходной сигнал OC1A на compareMatch при прямом счете / низкий при обратном счете
    TCCR4A |= _BV(COM4B1); // Высокий выходной сигнал OC1B на compareMatch при прямом счете / низкий при обратном счете

    OCR4A = dutyS;         // устанавливаем начальную пошлину
    OCR4B = dutyL;         // устанавливаем начальную пошлину

    ICR4 = PWM_MAX; // ТОР для TCNTx. 320 => @ ЦП 16 МГц -> 25 кГц ШИМ
}

void setupDisplay() {
    u8g2.begin();
    u8g2.setFont(u8g2_font_6x10_tf);
    u8g2.setFontRefHeightExtendedText();
    u8g2.setDrawColor(1);
    u8g2.setFontPosTop();
    u8g2.setFontDirection(0);
}

void attachInterrups() {
// attachInterrupt(digitalPinToInterrupt(PIN_SENSE_1), isrFan1, RISING);
    attachPCINT(digitalPinToPCINT(PIN_SENSE_1), isrFan1, RISING);

// attachInterrupt(digitalPinToInterrupt(PIN_SENSE_2), isrFan2, RISING);
    attachPCINT(digitalPinToPCINT(PIN_SENSE_2), isrFan2, RISING);

    attachInterrupt(digitalPinToInterrupt(PIN_SENSE_3), isrFan3, RISING);
    attachInterrupt(digitalPinToInterrupt(PIN_SENSE_4), isrFan4, RISING);
}

void detachInterrups() {
// detachInterrupt(digitalPinToInterrupt(PIN_SENSE_1));
    detachPinChangeInterrupt(digitalPinToPCINT(PIN_SENSE_1));

// detachInterrupt(digitalPinToInterrupt(PIN_SENSE_2));
    detachPinChangeInterrupt(digitalPinToPCINT(PIN_SENSE_2));

    detachInterrupt(digitalPinToInterrupt(PIN_SENSE_3));
    detachInterrupt(digitalPinToInterrupt(PIN_SENSE_4));
}

void setup() {
// кли();
    setupDisplay();
    setupPwm();
    attachInterrups();

    pinMode(PIN_BUTTON, INPUT_PULLUP);
// sei();
}

void draw() {
    char output[22]; // В одной строке дисплея может отображаться 21 символ.
    float uPerSekS1 = counterFan1 * 60000 / measureDuration / SIGNAL_PER_RND;
    float uPerSekS2 = counterFan2 * 60000 / measureDuration / SIGNAL_PER_RND;
    float uPerSekL1 = counterFan3 * 60000 / measureDuration / SIGNAL_PER_RND;
    float uPerSekL2 = counterFan4 * 60000 / measureDuration / SIGNAL_PER_RND;
    int y = 0;

    sprintf(output, "Duty    :    %3d", duty);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec S1: %6d", (int) uPerSekS1);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec S2: %6d", (int) uPerSekS2);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec L1: %6d", (int) uPerSekL1);
    u8g2.drawStr(0, y, output);

    y += lineOffset;
    sprintf(output, "U/sec L2: %6d", (int) uPerSekL2);
    u8g2.drawStr(0, y, output);
}

void measure() {
    measureDuration = millis() - lastDraw;
    if (measureDuration >= UPDATE_CYCLE) {
        // Деактивировать прерывание, пока мы вычисляем
        detachInterrups();

        u8g2.clearBuffer();
        draw();
        u8g2.sendBuffer();

        // сбросить состояние
        counterFan1 = 0;
        counterFan2 = 0;
        counterFan3 = 0;
        counterFan4 = 0;
        lastDraw = millis();

        // Повторно активировать прерывание
        attachInterrups();
    }
}

void loop() {
    measure();
    btnBouncer.update();
    if (btnBouncer.fell()) {
        // Кнопка была нажата. Повысить пошлину.
        if (duty > 90) {
            duty = 0;
        } else {
            duty += DUTY_INC;
        }

        dutyS = duty * PWM_MAX / 100;
        dutyL = duty * PWM_MAX / 100;

        OCR4A = dutyS;
        OCR4B = dutyL;
    }
}

void isrFan1() {
    counterFan1++;
}

void isrFan2() {
    counterFan2++;
}

void isrFan3() {
    counterFan3++;
}

void isrFan4() {
    counterFan4++;
}

,