Как назначить прерывание на нажатие кнопки с помощью ATtiny? (прерывание не срабатывает с моим кодом)

(Вот, наконец, решение).

Я использую следующий код на ATtiny45, чтобы назначить прерывание на нажатие кнопки (контакт № 7, PB2, INT0). Однако светодиод не мигает при нажатии кнопки, как будто прерывание не работает. Почему?

Примечание. Я хотел бы использовать наименьшую мощность, когда не происходит прерывания, чтобы иметь ток порядка ~ 1 мкА для работы от батарей. лет.

#include <avr/sleep.h>

void setup() 
{
  pinMode(4, OUTPUT);  // ВЕЛ
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);      // контакт № 7 = PB2
  digitalWrite(4, HIGH); delay(100); digitalWrite(4, LOW);  // Светодиод мигает, здесь работает
}

void wake()
{
  sleep_disable(); 
  detachInterrupt(0); 
  digitalWrite(4, HIGH);  // светодиод здесь не работает при нажатии на кнопку
} 

void loop()
{
  sleep_enable();
  ADCSRA = 0;  
  attachInterrupt(0, wake, CHANGE);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  
  sleep_cpu(); 
}

Примечание:

  • Я использую Arduino IDE с attiny Damellis, я также пытался использовать ATtinyCore Спенса Конде, но это то же самое

  • В моем реальном коде у меня есть 3 кнопки на контактах № 5, № 6 и № 7, поэтому в конечном итоге я хотел бы иметь прерывания для всех этих трех контактов.

  • Я прочитал https://gammon.com.au/forum/?id=11497, но использование noInterrupts() / interrupts() в моем случае ничего не изменило.

  • Я попробовал "Sketch J" с https://gammon.com.au/forum/ ?id=11497, но EIFR = бит (INTF0); выдает ошибку: 'EIFR' не был объявлен в этой области (собираю из Arduino ИДЕ); возможно, это работает на ATmega, но не на ATtiny.


Обновление: Вот рабочий код глубокого сна с прерыванием при изменении контакта № 5 (PB0)...

#include <avr/interrupt.h>
#include <avr/sleep.h>

ISR(PCINT0_vect) {
  if (digitalRead(0) == LOW)
    digitalWrite(4, HIGH);
  else
    digitalWrite(4, LOW);
}

void setup() {  
  pinMode(4,OUTPUT); // ВЕЛ
  pinMode(0,INPUT_PULLUP);
  pinMode(1,INPUT_PULLUP);
  pinMode(2,INPUT_PULLUP);
  ADCSRA = 0; // АЦП отключен
  GIMSK = 0b00100000;  // Общий регистр маски прерывания, / Бит 5 — PCIE: Разрешение прерывания по смене контакта / Когда бит PCIE установлен (один) и I-бит в регистре состояния (SREG) установлен (один), прерывание по смене контакта разрешено . Любое изменение на любом включенном выводе PCINT[5:0] вызовет прерывание. Соответствующее прерывание запроса прерывания смены контакта выполняется из вектора прерывания PCI. Выводы PCINT[5:0] активируются индивидуально регистром PCMSK0. / см. https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf
  PCMSK = 0b00000111;  // Прерывание смены контакта для PB0, PB1, PB2
} 

void loop() {
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  
  sleep_cpu(); 
}

...но по неизвестной причине, если я добавлю этот код:

ISR(PCINT1_vect) {
  if (digitalRead(1) == LOW)
    digitalWrite(4, HIGH);
  else
    digitalWrite(4, LOW);
}

прерывание смены контакта для PB0 по-прежнему работает (PCINT0), но не прерывание для PB1 (PCINT1). Почему?

, 👍1

Обсуждение

причина в плохом коде... не используйте задержки в ISR... установите только флаговую переменную... сделайте мерцание внутри блока loop(), @jsotola

Не имея под рукой Arduino: не нужно ли вообще разрешать прерывания? Например, вызов sei() или около того?, @the busybee

@Basj - Извините, я не могу вам помочь. У меня нет ATtiny для тестирования., @VE7JRO

Возможный дубликат [Как перевести ATtiny/ATmega в режим глубокого сна (чтобы годами работать от батарей), но при этом обнаруживать нажатие кнопки?](https://arduinoprosto.ru/q/70284/how-to-deep-sleep- attiny-atmega-to-go-go-years-with-batteries-но-все еще-dete), @Basj


1 ответ


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

3

Посмотрите техническое описание ATtiny45. В разделе «Спящие режимы» есть таблица, в которой перечислены источники пробуждения, доступные для каждого сна режим. Для режима «отключение питания» INT0 указан как возможное пробуждение. источник, но есть небольшая сноска:

Для INT0 только прерывание уровня.

Это означает, что режим CHANGE, который вы пытаетесь использовать, не активируется. MCU из режима отключения питания. Вместо этого вы должны

attachInterrupt(0, wake, LOW);

В моем реальном коде у меня есть 3 кнопки на контактах № 5, № 6, № 7, поэтому в конечном итоге Я бы хотел иметь прерывания для всех этих трех контактов.

Возможно, вам придется изучить прерывание «смена контакта». Это что-то отличное от INT0, которое вы используете здесь, и не обычно поддерживается ядром Arduino. Вам придется либо получить какой-нибудь библиотекой прерываний смены контактов или работать без библиотеки, используя информация из даташита (не так сложно). Прерывание смены контакта также источник пробуждения для отключения питания.


Обновлено: ответы на дополнительные вопросы из комментариев.

Откуда вы узнали, что "прерывание только по уровню" означает только "НИЗКИЙ"?

В документации Arduino указано, что режим параметр attachInterrupt() может быть либо LOW, CHANGE, RISING или ПАДЕНИЕ. Некоторые платы также поддерживают HIGH, но ATtinies не поддерживаются. среди них. «Уровень» может быть либо LOW, либо HIGH, тогда как CHANGE, ВОСХОД и ПАДЕНИЕ — это «ребра».

Это подтверждается документом Техническое описание ATtiny45: «Программа INT0 прерывания могут быть вызваны падающим или нарастающим фронтом или низким уровнем».

Является ли прерывание смены контакта PCINT?

Да.

как вы думаете, почему это не поддерживается ядром Arduino?

API attachInterrupt() имеет смысл только для INT0, INT1, и т.д... не для PCINT. Вы также можете просмотреть исходный код.

Разве стандартом не является прерывание при изменении состояния контакта?

При желании вы можете настроить прерывание INT0 по переднему фронту, спадающий фронт или любое логическое изменение. Но это использует обнаружение края логика, которая опирается на основные часы и поэтому не работает в режим отключения питания.

Для получения дополнительной информации о INT0 и PCINT, их различиях и способах использования их, пожалуйста, обратитесь к техническому описанию, в частности к «Внешнему раздел «Прерывания», на который я ссылался выше.


Дополнение: Дополнительный вопрос:

почему это не работает [с ISR(PCINT1_vect) {...}]?

Gcc сообщает мне: «предупреждение: 'PCINT1_vect', по-видимому, содержит ошибку обработчик сигнала». И действительно, такого вектора прерывания на ATtiny45: есть только PCINT0_vect, и это прерывание используется все контакты, которые могут генерировать прерывание смены контакта. Если вы хотите знать, какой вывод вызвал прерывание, вам придется это выяснить в самом обработчике прерывания.

Это единственное, что усложняет работу с PCINT. чем INT0 и другие: каждое прерывание PCINT используется группой контактов. На ATtiny45, есть только одна такая группа, и PCINT0_vect — это только обработчик прерывания. Большие AVR имеют больше групп (3 на Uno/ATmega328P), и PCINT1_vect на них имеет смысл.

Также остерегайтесь несколько запутанной схемы именования. Avr-libc определяет некоторые макросы PCINTn, где n — число. Эти макросы расширяются до бит числа и предназначены для использования в таком коде:

PCMSK = _BV(PCINT0)   // воспринимает изменения на выводе PCINT0 = PB0...
      | _BV(PCINT1)   // и PCINT1 = PB1...
      | _BV(PCINT2);  // и PCINT2 = PB2

В этих макросах число n определяет отдельный вывод. AVR-libc также определяет макросы PCINTm_vect для определения обработчиков прерываний. В этих макросах число m определяет группу контактов.

,

Спасибо за ответ и за обновление!, @Basj

Я отредактировал свой вопрос и добавил рабочий код прерывания для PCINT0. Вы знаете, почему это не работает для PCINT1?, @Basj