Почему мои прерывания mcp23s17 больше не работают?

Я использую teensy 4.0 с двумя расширителями gpio mcp23s17, где к первому подключено 16 кнопок, а к второму — 4 кнопки и шесть поворотных энкодеров. Для запуска всего я использую библиотеку majenkos mcp23s17. Недавно я провел рефакторинг своего кода и вдруг не могу настроить mcp для корректной работы прерываний.

Я попытался сузить проблему, закомментировав код, и оказалось, что как только я вызываю gpioExpander.enableInterrupt(pin, CHANGE); в gpioSetup() возникает проблема. В частности, loop() работает нормально, но как только я нажимаю кнопку, тинси зависает. Кроме того, чтобы восстановить тинси, мне нужно загрузить код с вызовом gpioExpander.enableInterrupt(pin, CHANGE);, закомментированным, чтобы тинси снова заработала.

Я смотрел на код вслепую и подозреваю, что упускаю что-то очевидное. Мы ценим вашу помощь.

//main.cpp
#include <Arduino.h>

#include "GPIO.h"
#include "MCP23S17.h"

extern MCP23S17 gpioExpander1;
extern MCP23S17 gpioExpander2;

void setup() {
    Serial.begin(115200);
    gpioSetup();
}

void loop() {
    GPIOtest(gpioExpander1, gpioExpander2);
}

//GPIO.h
#ifndef GPIO_H
#define GPIO_H

#include <Arduino.h>

#include "MCP23S17.h"

constexpr uint8_t address = 0;
constexpr uint8_t interruptPin1 = 8;
constexpr uint8_t interruptPin2 = 22;
constexpr uint8_t chipSelectPin1 = 7;
constexpr uint8_t chipSelectPin2 = 21;

void setGPIO1Pins(MCP23S17 &gpioExpander);
void setGPIO2Pins(MCP23S17 &gpioExpander);
void gpioSetup();
void GPIO1interrupt();
void GPIO2interrupt();
void GPIOtest(MCP23S17 &gpioExpander1, MCP23S17 &gpioExpander2);

#endif

//GPIO.cpp
#include "GPIO.h"

#include <Arduino.h>
#include <MCP23S17.h>

volatile bool interruptFlag1 = false;
volatile bool interruptFlag2 = false;

MCP23S17 gpioExpander1(&SPI, chipSelectPin1, address);
MCP23S17 gpioExpander2(&SPI, chipSelectPin2, address);

// Установить все выводы расширителя портов в желаемый режим INPUT_PULLUP
// Установить прерывания всех входных контактов расширителя портов
void setGPIOPins(MCP23S17& gpioExpander) {
    for (uint8_t pin = 0; pin <= 15; ++pin) {
        gpioExpander.pinMode(pin, INPUT_PULLUP);
        gpioExpander.enableInterrupt(pin, CHANGE); 
    }

    // установка конфигурации прерывания расширителя портов
    gpioExpander.setMirror(true);
    gpioExpander.setInterruptOD(false);
    gpioExpander.setInterruptLevel(LOW);
}

void gpioSetup() {
    gpioExpander1.begin();
    gpioExpander2.begin();
    digitalWrite(chipSelectPin1, HIGH);
    digitalWrite(chipSelectPin2, HIGH);

    pinMode(interruptPin1, INPUT_PULLUP);
    pinMode(interruptPin2, INPUT_PULLUP);

    attachInterrupt(digitalPinToInterrupt(interruptPin1), GPIO1interrupt, LOW);
    attachInterrupt(digitalPinToInterrupt(interruptPin2), GPIO2interrupt, LOW);

    setGPIOPins(gpioExpander1);
    setGPIOPins(gpioExpander2);

    // регистры GPIO считываются для очистки
    gpioExpander1.getInterruptValue();
    gpioExpander2.getInterruptValue();
}

void GPIO1interrupt() {
    interruptFlag1 = true;
}

void GPIO2interrupt() {
    interruptFlag2 = true;
}

void GPIOtest(MCP23S17& gpioExpander1, MCP23S17& gpioExpander2) {
    if (interruptFlag1 == true) {
        Serial.println("interrupt 1 = true");
        interruptFlag1 = false;  
    }

    if (interruptFlag2 == true) {
        Serial.println("interrupt 2 = true");
        interruptFlag2 = false;
    }
}

, 👍0


1 ответ


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

2

Короткая версия, попробуйте:

attachInterrupt(digitalPinToInterrupt(interruptPin1), GPIO1interrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(interruptPin2), GPIO2interrupt, FALLING);

FALLING (вместо LOW) является активный ингредиент. Я предполагаю, что FALLING поддерживается. Если нет, вы можете попробовать синтезировать это с помощью CHANGE и прочитать порт, чтобы увидеть, является ли он в настоящее время LOW.

Затем вызовите .getInterruptValue();, чтобы сбросить условие прерывания MCP23S17, где вы сбрасываете глобальные изменчивые флаги:

void GPIOtest(MCP23S17& gpioExpander1, MCP23S17& gpioExpander2) {
    if (interruptFlag1 == true) {
        Serial.println("interrupt 1 = true");
        interruptFlag1 = false;
        gpioExpander1.getInterruptValue(); // <------
    }

    if (interruptFlag2 == true) {
        Serial.println("interrupt 2 = true");
        interruptFlag2 = false;
        gpioExpander2.getInterruptValue(); // <------
    }
}

Полная версия:

Это предположение, так как я не могу проверить. Но это, возможно, неплохое предположение:

Вы настроили устройство MCP23S17 на установку активного низкого уровня при/во время состояния прерывания MCP23S17:

    gpioExpander.setMirror(true);
    gpioExpander.setInterruptOD(false);
    gpioExpander.setInterruptLevel(LOW);

Итак, при возникновении прерывания MCP23S17 (gpioExpander.enableInterrupt(pin, CHANGE);) будет установлен флаг прерывания внутри MCP23S17, и INTA/INTB MCP23S17 станет активным. низкий.

В техническом описании MCP23S17 говорится:

Условие прерывания сбрасывается после того, как младший разряд данных синхронизация во время команды чтения GPIO или INTCAP

Вы настроили контакты прерывания для прерывания НИЗКОГО уровня прерывания:

attachInterrupt(digitalPinToInterrupt(interruptPin1), GPIO1interrupt, LOW);

Итак, если я правильно понимаю, ваш Teensy постоянно повторно входит в функции GPIO#interrupt() до тех пор, пока что-то прочитанное не приведет к сбросу условия прерывания. Вы не очищаете условие прерывания внутри ISR, вы устанавливаете флаг, чтобы сделать это позже в основной строке выполнения, что само по себе разумно. Я могу предположить, что если бы раньше у вас было что-то, чтобы сбросить условие прерывания внутри прерывания, то теперь оно вне прерывания, а именно ваш вызов .getInterruptValue(), который считывает регистр INTCAP, упомянутый выше. Или, возможно, вы будете использовать одну из функций, которая читает контакт/порт, который очистил бы условие прерывания. Очистка глобальной переменной сама по себе не очистит условие прерывания, поэтому вы сразу же вернетесь в ISR, который переназначит глобальную переменную.

AVR выполнит одну инструкцию за пределами ISR перед повторным входом в ISR. Таким образом, пребывание в состоянии прерывания приводит к тому, что ваш код становится медленным, потенциально очень медленным. Я не знаю навскидку, что в этом случае будет делать ядро Teensy 4.x. В любом случае он будет заблокирован или, возможно, выглядит заблокированным.

Если бы вы читали/получали значение прерывания внутри ISR, оно бы очистило его и поэтому повторно не входило бы в ISR. Вернее, он не стал бы этого делать определенно (или просто надолго). Возможно, Teensy настолько быстрее, что может повторно войти несколько раз, пока MCP23S17 не подтвердит, что условие прерывания было устранено. Я не пропагандирую это, поэтому на этом остановлюсь.

Я считаю, что в конечном итоге вам нужно установить прерывание FALLING от фронта:

attachInterrupt(digitalPinToInterrupt(interruptPin1), GPIO1interrupt, FALLING);

В любом случае, с FALLING начальный задний фронт линии INTA/INTB устанавливает условие прерывания в Teensy (не MCP23S17), но оно сбрасывается при выполнении вашего ISR. Таким образом, вы не будете продолжать повторно входить в ISR. Таким образом, ваш ISR введет один раз, чтобы установить глобальный флаг volatile. Позже, когда вы получите установленное значение вашего глобального флага(ов) volatile, вам нужно будет вызвать .getInterruptValue() (или иным образом прочитать вывод), чтобы очистить условие прерывания в вашем MCP23S17 (не Teensy), которое снимет/поднимет INTA/INTB, и в этот момент ISR в Teensy будет готов к срабатыванию другого спадающего фронта INTA/INTB.

Вероятно, это также возможно сделать с помощью прерывания уровня LOW, но вам придется продолжать отменять их настройку в ISR и перенастраивать их после сброса состояния MCP23S17. Не уверен, что это будет рекомендовано. Но я думаю, что это возможно.

,

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