MCP23017 и прерывания.

RxMaxx
Offline
Зарегистрирован: 10.04.2020

Помогите разобраться с прерываниями при работе с MCP23017.

Схема подключения такая к 5 - 11 входам MCP подключен энкодер и еще 4 тактовых кнопки, все они при нажатии замыкаются на землю. Нога INT_A подключена к ноге D2 Arduino NANO. Все прочие подключено в точности по даташиту (если сильно нужно – могу нарисовать). Используется библиотека Adafruit_MCP23017. В качестве теста использую немного переделанный (под несколько кнопок вместо двух) код из примера Interrupt:

#include <Adafruit_MCP23017.h>
Adafruit_MCP23017 mcp;
unsigned int count = 0;
byte arduinoIntPin = 2;
byte arduinoInterrupt = 0;
volatile boolean awakenByInterrupt = false;
void setup() {
  Serial.begin(9600);
  Serial.println("MCP23007 Interrupt Test");
  pinMode(arduinoIntPin, INPUT);
  mcp.begin();
  mcp.setupInterrupts(true, false, LOW);
  for (byte i = 5; i < 12; i++ )
  {
    mcp.pinMode(i, INPUT); mcp.pullUp(i, HIGH);
    mcp.setupInterruptPin(i, FALLING);
  }
}
void intCallBack() {
  awakenByInterrupt = true;
}
void handleInterrupt() {
  uint8_t pin = mcp.getLastInterruptPin();
  uint8_t val = mcp.getLastInterruptPinValue();
  count++;
  Serial.print(count); Serial.print(" Pin="); Serial.print(pin); Serial.print(" Val="); Serial.println(val);
  boolean A = false;
  while (!A)
  { A = true;
    for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);
  }
  cleanInterrupts();
}
void cleanInterrupts() {
  EIFR = 0x01;
  awakenByInterrupt = false;
}
void loop() {
  attachInterrupt(arduinoInterrupt, intCallBack, FALLING);
  while (!awakenByInterrupt);
  detachInterrupt(arduinoInterrupt);
  if (awakenByInterrupt) handleInterrupt();
}

Код работает, но виснет. Повиснуть он может практический в любой момент. В среднем у меня успевает счетчик нажатий дойти до 150-200, но может повиснуть и на первом десятке. Если уменьшить кол-во используемых кнопок, программа виснет позже, для одной кнопки (точнее энкодера) я смог дойти до 3500, для двух всего до 1100. Т.е. чем больше кнопок и чем быстрее я их нажимаю/кручу энкодер – тем раньше происходит зависание, но какой-то конкретной зависимости я не нашел. Оригинальный код из примера для двух кнопок также виснет где-то после тысячи нажатий.

В чем может быть проблема? Библиотека кривая? Пример кривой? Если я не использую прерывания, но использую ту же библиотеку и железо, то все работает стабильно очень долго (несколько дней как минимум). Еще пару лет назад пробовал использовать прерывания MCP23017 – тоже столкнулся с зависаниями (тогда использовал другое железо – MEGA2560, два энкодера, и экран LCD2004, писал что-то типа игры Змейка). Я, в принципе, могу обойтись без прерываний, но в этом случае энкодер периодически тупит, по часовой стрелке идет ровно, но против - может прыгать туда-сюда, с обычными кнопками проблем нет. Вешать энкодер на отдельные ноги ардуино нет возможности – все заняты, да и схему приделывать проблематично.

sadman41
Offline
Зарегистрирован: 19.10.2016

А вот этот бесконечный attachInterrupt() для чего?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Предполагаю, что виснет на цикле в строке №28. Для начала, нужно в этом убедиться. Для этого поставьте вывод чего-нибудь в сериал после строки №31. И скажите, прав ли я, что зависание происходит именно в этом цикле?

Bruzzer
Offline
Зарегистрирован: 17.03.2020

К зависанию это скорее всего не имеет отношения, но не рационально последовательно читать все пины

    for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);

Для сброса флагов прерывания в MCP23017 достаточно считать один раз GPIOA и GPIOB. (Или по одному пину из А и В.)

Bruzzer
Offline
Зарегистрирован: 17.03.2020

RxMaxx пишет:

Помогите разобраться с прерываниями при работе с MCP23017.

Сам с 23017 не работал, так что дальше только мысли.

Зависание может быть если на входе INT0 ардуины постоянно низкий уровень. Проверить легко - или тестером, или раз в несколько секунд выводить значение в Serial.

А постоянно низкий уровень может быть например если неудачно прошли отрезок

for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);

cleanInterrupts();

Когда дошли до последнего пина порта В, пин порта А уже снова стал 0, и снова выставил флаг прерывания.  INT0 снова стал 0, но флаг сбросится cleanInterrupts();

RxMaxx
Offline
Зарегистрирован: 10.04.2020

Bruzzer пишет:

К зависанию это скорее всего не имеет отношения, но не рационально последовательно читать все пины

    for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);

Для сброса флагов прерывания в MCP23017 достаточно считать один раз GPIOA и GPIOB. (Или по одному пину из А и В.)

Я плохо понимаю зачем делать опрос пинов, до тех пор пока хотя бы на одном из них не пропадет сигнал, но в исходном примере так и есть:

// Install the LowPower library for optional sleeping support.
// See loop() function comments for details on usage.
//#include <LowPower.h>
#include <Wire.h>
#include <Adafruit_MCP23017.h>
Adafruit_MCP23017 mcp;
byte ledPin=13;
// Interrupts from the MCP will be handled by this PIN
byte arduinoIntPin=3;
// ... and this interrupt vector
byte arduinoInterrupt=1;
volatile boolean awakenByInterrupt = false;
// Two pins at the MCP (Ports A/B where some buttons have been setup.)
// Buttons connect the pin to grond, and pins are pulled up.
byte mcpPinA=7;
byte mcpPinB=15;
void setup(){
  Serial.begin(9600);
  Serial.println("MCP23007 Interrupt Test");
  pinMode(arduinoIntPin,INPUT);
  mcp.begin();      // use default address 0
  // We mirror INTA and INTB, so that only one line is required between MCP and Arduino for int reporting
  // The INTA/B will not be Floating 
  // INTs will be signaled with a LOW
  mcp.setupInterrupts(true,false,LOW);
  // configuration for a button on port A
  // interrupt will triger when the pin is taken to ground by a pushbutton
  mcp.pinMode(mcpPinA, INPUT);
  mcp.pullUp(mcpPinA, HIGH);  // turn on a 100K pullup internally
  mcp.setupInterruptPin(mcpPinA,FALLING); 
  // similar, but on port B.
  mcp.pinMode(mcpPinB, INPUT);
  mcp.pullUp(mcpPinB, HIGH);  // turn on a 100K pullup internall
  mcp.setupInterruptPin(mcpPinB,FALLING);
  // We will setup a pin for flashing from the int routine
  pinMode(ledPin, OUTPUT);  // use the p13 LED as debugging
}
// The int handler will just signal that the int has happen
// we will do the work from the main loop.
void intCallBack(){
  awakenByInterrupt=true;
}
void handleInterrupt(){
  // Get more information from the MCP from the INT
  uint8_t pin=mcp.getLastInterruptPin();
  uint8_t val=mcp.getLastInterruptPinValue();
  // We will flash the led 1 or 2 times depending on the PIN that triggered the Interrupt
  // 3 and 4 flases are supposed to be impossible conditions... just for debugging.
  uint8_t flashes=4; 
  if(pin==mcpPinA) flashes=1;
  if(pin==mcpPinB) flashes=2;
  if(val!=LOW) flashes=3;
  // simulate some output associated to this
  for(int i=0;i<flashes;i++){  
    delay(100);
    digitalWrite(ledPin,HIGH);
    delay(100);
    digitalWrite(ledPin,LOW);
  }
  // we have to wait for the interrupt condition to finish
  // otherwise we might go to sleep with an ongoing condition and never wake up again.
  // as, an action is required to clear the INT flag, and allow it to trigger again.
  // see datasheet for datails.
  while( ! (mcp.digitalRead(mcpPinB) && mcp.digitalRead(mcpPinA) ));
  // and clean queued INT signal
  cleanInterrupts();
}
// handy for interrupts triggered by buttons
// normally signal a few due to bouncing issues
void cleanInterrupts(){
  EIFR=0x01;
  awakenByInterrupt=false;
}  
/**
 * main routine: sleep the arduino, and wake up on Interrups.
 * the LowPower library, or similar is required for sleeping, but sleep is simulated here.
 * It is actually posible to get the MCP to draw only 1uA while in standby as the datasheet claims,
 * however there is no stadndby mode. Its all down to seting up each pin in a way that current does not flow.
 * and you can wait for interrupts while waiting.
 */
void loop(){
   // enable interrupts before going to sleep/wait
  // And we setup a callback for the arduino INT handler.
  attachInterrupt(arduinoInterrupt,intCallBack,FALLING);
  // Simulate a deep sleep
  while(!awakenByInterrupt);
  // Or sleep the arduino, this lib is great, if you have it.
  //LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
  // disable interrupts while handling them.
  detachInterrupt(arduinoInterrupt);
  if(awakenByInterrupt) handleInterrupt();
}

Но даже когда я оставляю всего одну кнопку, и условие сводится к простому while( ! mcp.digitalRead(mcpPin)); то зависание рано или поздно происходит. Если бы это была обычная кнопка, то мало у кого хватило бы терпения сделать несколько тысяч нажатий, в случае энкодера докрутить до зависания становится возможным. Но когда кнопок несколько – зависание ловится почти сразу. В любом случае, мне не очень нравится этот пример. Хотя бы тем, что они там delay используют, я вывод заменил на serial.print, для большей наглядности, но где-то на форуме видел, что нежелательно его использовать при работе с прерываниями (не понял по чему).

Да и ждать отпускания кнопки, это как-то странно. А если я хочу удержание кнопки тоже как-то использовать? Получается, что так прерывания можно использовать только для энкодера?

 

Bruzzer
Offline
Зарегистрирован: 17.03.2020

Вы можете померить тестером, что на D2 в зависшем состоянии?

RxMaxx
Offline
Зарегистрирован: 10.04.2020

Bruzzer пишет:

Вы можете померить тестером, что на D2 в зависшем состоянии?

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

Bruzzer
Offline
Зарегистрирован: 17.03.2020

Тогда, для проверки, вставьте в loop отправку раз в несколько секунд (или десятков секунд) состояния D2.

 

RxMaxx
Offline
Зарегистрирован: 10.04.2020

Bruzzer пишет:

Тогда, для проверки, вставьте в loop отправку раз в несколько секунд (или десятков секунд) состояния D2.

Сделал вот так:

void loop() {
  mcp.digitalWrite(12, !digitalRead(2));
  attachInterrupt(arduinoInterrupt, intCallBack, FALLING);
  mcp.digitalWrite(13, !digitalRead(2));
  while (!awakenByInterrupt);
  mcp.digitalWrite(14, !digitalRead(2));
  detachInterrupt(arduinoInterrupt);
  mcp.digitalWrite(15, !digitalRead(2));
  if (awakenByInterrupt) handleInterrupt();
}

На  12-15 ногах у меня светодиоды висят. При первом же нажатии на любую кнопку загораются и остаются гореть 14 и 15 светодиоды, 12, 13 - не видно, но если быстро вращать энкодер, то 12 и 13 уже заметно мигают, а при зависании уже горят все четыре светодиода, т.е. на D2 в этот момент 0.

P.S. Если крутануть энкодер совсем быстро, то гарантированно зависает уже на втором счете.

P.P.S. Даже если использую всего ОДНУ ногу (while (!mcp.digitalRead(5));) - если энкодер крутить - виснет! I2C шина не успевает, или что?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

RxMaxx пишет:
виснет! I2C шина не успевает, или что?

Может, пора всё-таки понять где именно виснет? Ответ на мой вопрос из поста #2 будет? Или ну его нахрен?

RxMaxx
Offline
Зарегистрирован: 10.04.2020

ЕвгенийП пишет:

 Для этого поставьте вывод чего-нибудь в сериал после строки №31. И скажите, прав ли я, что зависание происходит именно в этом цикле?

Как ни странно, виснет не тут:

void handleInterrupt() {
  uint8_t pin = mcp.getLastInterruptPin();
  uint8_t val = mcp.getLastInterruptPinValue();
mcp.digitalWrite(14, HIGH);
  while (!mcp.digitalRead(5));
mcp.digitalWrite(15, HIGH); 
  cleanInterrupts();
mcp.digitalWrite(14, LOW); 
mcp.digitalWrite(15, LOW);
}

После зависания 14 и 15 не горят!  (Из loop'a я их убрал)

Bruzzer
Offline
Зарегистрирован: 17.03.2020

(Начал писать до ответа ЕвгенийП)

Предполагаемую причину я написал в #4

По хорошему, чтобы понять можно ли реализовать на MCP23017 требуемое вам разрешение по частоте нажатий, надо прочитать описание даташит. Или найти хорошее объяснение в инете (где искать не знаю).

Если предположения #4 верны, то как заплатку можно предложить два варианта для устранения зависаний. Но пропуски нажатий возможны.

1 вариант. Перенести cleanInterrupts(); вверх

cleanInterrupts();

while (!mcp.digitalRead(5));

2 Вариант. Перейти на прерывание D2 по уровню

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

RxMaxx пишет:

Как ни странно, виснет не тут:

Похоже, Вам помощь не нужна. Я Вам сказал поставьте ПОСЛЕ СТРОКИ №31 из кода, что в первом посте. А Вы чего сделали?

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

Делайте. Пока Вы не поймёте где виснет, все остальные телодвижения - в пользу бедных.

RxMaxx
Offline
Зарегистрирован: 10.04.2020

ЕвгенийП пишет:

Я Вам сказал поставьте ПОСЛЕ СТРОКИ №31 из кода, что в первом посте. А Вы чего сделали?

А я что сделал? Зажигаю светодиод до цикла, а гашу после – раз он моргает и тут же гаснет, значит цикл проходит, и виснет не в нем. Разве не так?

Bruzzer пишет:

1 вариант. Перенести cleanInterrupts(); вверх

А вот это, как раз помогло. Спасибо!

Рабочий вариант теперь выглядит так:

void handleInterrupt() {
  uint8_t pin = mcp.getLastInterruptPin();
 uint8_t val = mcp.getLastInterruptPinValue();
 if (pin!=255)
 {count++;
 Serial.print(count); Serial.print(" Pin="); Serial.print(pin); Serial.print(" Val="); Serial.println(val);
 }
cleanInterrupts();
 boolean A = false;
 while (!A)
 { A = true;
   for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);
} 
}

Теперь не виснет! Ну, или у меня не хватает терпения/скорости, чтоб вызвать зависание. Но приходится фильтровать значение pin=255, которые теперь постоянно появляются.

Вариант следить только за одним из пинов из группы GPIOA и GPIOB, в принципе работает, но например, если я пишу так: while (!(mcp.digitalRead(7)&&mcp.digitalRead(8))); то программа будет ждать отпускания кнопок 7 и 8, а все остальные будут повторятся без отпускания, что не очень хорошо. В любом случае, мне не подходит такой вариант опроса для кнопок, тут не получится реализовать обработку удержания кнопок. Попробую использовать прерывания только для энкодера, остальное и так нормально работает.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

RxMaxx пишет:

А я что сделал? Зажигаю светодиод до цикла, а гашу после – раз он моргает и тут же гаснет, значит цикл проходит, и виснет не в нем. Разве не так?

Не так. Я же Вам писал номера строк. Я говорю о цикле в строках №№28-31, а Вы о цикле в строке №40 (все номера по коду из первого поста. Нельзя быть таким невнимательным! 

 

RxMaxx
Offline
Зарегистрирован: 10.04.2020

http://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/mcp23017-i-preryvaniya#comment-533997

ЕвгенийП пишет:

 Нельзя быть таким невнимательным! 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Понятно. Ну, не хотите делать, что Вам говорят, трахайтесь со своими проблемами сами.

RxMaxx
Offline
Зарегистрирован: 10.04.2020

ЕвгенийП пишет:

Понятно. Ну, не хотите делать, что Вам говорят, трахайтесь со своими проблемами сами.

ДА, БЛИН (пардон!) у посмотрите на ссылку то! Я же в точности тот цикл и проверил, о котором Вы говорили! 

Только там опрос идет уже всего одной ноги, а нескольких (для нескольких результат тот же). 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Вам трудно было взять код из первого поста и сделать, что Вас просили? Это не моя проблема.

ЕвгенийП пишет:

трахайтесь со своими проблемами сами.

RxMaxx
Offline
Зарегистрирован: 10.04.2020

ЕвгенийП пишет:

Вам трудно было взять код из первого поста и сделать, что Вас просили? Это не моя проблема.

Еще раз, я проверил и этот код:

void handleInterrupt() {
  uint8_t pin = mcp.getLastInterruptPin();
  uint8_t val = mcp.getLastInterruptPinValue();
mcp.digitalWrite(14, HIGH);
  while (!mcp.digitalRead(5));
mcp.digitalWrite(15, HIGH); 
  cleanInterrupts();
mcp.digitalWrite(14, LOW); 
mcp.digitalWrite(15, LOW);
}

И этот:

void handleInterrupt() {
  uint8_t pin = mcp.getLastInterruptPin();
  uint8_t val = mcp.getLastInterruptPinValue();
  count++;
  Serial.print(count); Serial.print(" Pin="); Serial.print(pin); Serial.print(" Val="); Serial.println(val);
  boolean A = false;
mcp.digitalWrite(14, HIGH);
  while (!A)
  { A = true;
    for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);
  }
mcp.digitalWrite(15, HIGH); 
  cleanInterrupts();
mcp.digitalWrite(14, LOW); 
mcp.digitalWrite(15, LOW);
}

 Разница только в том, что в первом случае я работаю только с ОДНОЙ кнопкой, а во втором с НЕСКОЛЬИКИМИ сразу, но это один и тот же код, с одной и той же логикой, и я их ОБА проверил, результат был ОДИНАКОВЫЙ – программа рано или поздно висла, но не в этом месте, эта процедура отрабатывала до конца, светодиоды моргали и гасли. Я не понимаю причину Вашего «недовольства». Проблема оказалась все равно не в этом месте.

 

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

RxMaxx пишет:

Еще раз, я проверил и этот код:

Теперь это уже только Ваша проблема, мне можете ничего не писать. До свиданья.