MCP23017 и прерывания.
- Войдите на сайт для отправки комментариев
Помогите разобраться с прерываниями при работе с 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, писал что-то типа игры Змейка). Я, в принципе, могу обойтись без прерываний, но в этом случае энкодер периодически тупит, по часовой стрелке идет ровно, но против - может прыгать туда-сюда, с обычными кнопками проблем нет. Вешать энкодер на отдельные ноги ардуино нет возможности – все заняты, да и схему приделывать проблематично.
А вот этот бесконечный attachInterrupt() для чего?
Предполагаю, что виснет на цикле в строке №28. Для начала, нужно в этом убедиться. Для этого поставьте вывод чего-нибудь в сериал после строки №31. И скажите, прав ли я, что зависание происходит именно в этом цикле?
К зависанию это скорее всего не имеет отношения, но не рационально последовательно читать все пины
for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);
Для сброса флагов прерывания в MCP23017 достаточно считать один раз GPIOA и GPIOB. (Или по одному пину из А и В.)
Помогите разобраться с прерываниями при работе с MCP23017.
Сам с 23017 не работал, так что дальше только мысли.
Зависание может быть если на входе INT0 ардуины постоянно низкий уровень. Проверить легко - или тестером, или раз в несколько секунд выводить значение в Serial.
А постоянно низкий уровень может быть например если неудачно прошли отрезок
for (byte i = 5; i < 12; i++ ) A = A && mcp.digitalRead(i);
cleanInterrupts();
Когда дошли до последнего пина порта В, пин порта А уже снова стал 0, и снова выставил флаг прерывания. INT0 снова стал 0, но флаг сбросится cleanInterrupts();
К зависанию это скорее всего не имеет отношения, но не рационально последовательно читать все пины
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, для большей наглядности, но где-то на форуме видел, что нежелательно его использовать при работе с прерываниями (не понял по чему).
Да и ждать отпускания кнопки, это как-то странно. А если я хочу удержание кнопки тоже как-то использовать? Получается, что так прерывания можно использовать только для энкодера?
Вы можете померить тестером, что на D2 в зависшем состоянии?
Вы можете померить тестером, что на D2 в зависшем состоянии?
Сейчас это проблематично, у меня на рука только собранное устройство, которое даже вскрывать лишний раз не хочется. К другому железу в текущий момент доступа нет.
Тогда, для проверки, вставьте в loop отправку раз в несколько секунд (или десятков секунд) состояния D2.
Тогда, для проверки, вставьте в 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 шина не успевает, или что?
Может, пора всё-таки понять где именно виснет? Ответ на мой вопрос из поста #2 будет? Или ну его нахрен?
Для этого поставьте вывод чего-нибудь в сериал после строки №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 я их убрал)
(Начал писать до ответа ЕвгенийП)
Предполагаемую причину я написал в #4
По хорошему, чтобы понять можно ли реализовать на MCP23017 требуемое вам разрешение по частоте нажатий, надо прочитать описание даташит. Или найти хорошее объяснение в инете (где искать не знаю).
Если предположения #4 верны, то как заплатку можно предложить два варианта для устранения зависаний. Но пропуски нажатий возможны.
1 вариант. Перенести cleanInterrupts(); вверх
2 Вариант. Перейти на прерывание D2 по уровню
Как ни странно, виснет не тут:
Похоже, Вам помощь не нужна. Я Вам сказал поставьте ПОСЛЕ СТРОКИ №31 из кода, что в первом посте. А Вы чего сделали?
Делайте, что говорят, поставьте туда, куда я сказал и скажите, что происходит. Только выложите при этом код как он получился, т.к. доверия к тому. что Вы вставите правильно у меня нет.
Делайте. Пока Вы не поймёте где виснет, все остальные телодвижения - в пользу бедных.
Я Вам сказал поставьте ПОСЛЕ СТРОКИ №31 из кода, что в первом посте. А Вы чего сделали?
А я что сделал? Зажигаю светодиод до цикла, а гашу после – раз он моргает и тут же гаснет, значит цикл проходит, и виснет не в нем. Разве не так?
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, а все остальные будут повторятся без отпускания, что не очень хорошо. В любом случае, мне не подходит такой вариант опроса для кнопок, тут не получится реализовать обработку удержания кнопок. Попробую использовать прерывания только для энкодера, остальное и так нормально работает.
А я что сделал? Зажигаю светодиод до цикла, а гашу после – раз он моргает и тут же гаснет, значит цикл проходит, и виснет не в нем. Разве не так?
Не так. Я же Вам писал номера строк. Я говорю о цикле в строках №№28-31, а Вы о цикле в строке №40 (все номера по коду из первого поста. Нельзя быть таким невнимательным!
http://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/mcp23017-i-preryvaniya#comment-533997
Нельзя быть таким невнимательным!
Понятно. Ну, не хотите делать, что Вам говорят, трахайтесь со своими проблемами сами.
Понятно. Ну, не хотите делать, что Вам говорят, трахайтесь со своими проблемами сами.
ДА, БЛИН (пардон!) у посмотрите на ссылку то! Я же в точности тот цикл и проверил, о котором Вы говорили!
Только там опрос идет уже всего одной ноги, а нескольких (для нескольких результат тот же).
Вам трудно было взять код из первого поста и сделать, что Вас просили? Это не моя проблема.
трахайтесь со своими проблемами сами.
Вам трудно было взять код из первого поста и сделать, что Вас просили? Это не моя проблема.
Еще раз, я проверил и этот код:
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); }Разница только в том, что в первом случае я работаю только с ОДНОЙ кнопкой, а во втором с НЕСКОЛЬИКИМИ сразу, но это один и тот же код, с одной и той же логикой, и я их ОБА проверил, результат был ОДИНАКОВЫЙ – программа рано или поздно висла, но не в этом месте, эта процедура отрабатывала до конца, светодиоды моргали и гасли. Я не понимаю причину Вашего «недовольства». Проблема оказалась все равно не в этом месте.
Еще раз, я проверил и этот код:
Теперь это уже только Ваша проблема, мне можете ничего не писать. До свиданья.