Изменить прерывания на ATTiny 88

Я работаю над проектом, в котором требуется несколько контактов ввода-вывода для различных целей. Я выбрал ATTiny88, потому что у него много контактов GPIO и он относительно недорог.

Вот обновленный код для timemage, большое спасибо!

#include <Arduino.h>
#include <TM1637Display.h>
#include "PinChangeInterrupt.h"

int counter = 0;
bool Update = false;
int currentStatePhase_B;
int lastStatePhase_B;

// Выход отключения
#define SW 11

// Входы поворотного энкодера
#define Phase_A 13
#define Phase_B 12
#define DIO 3
#define CLK 4

TM1637Display display(CLK, DIO);

void setup() {

  // Установить выводы энкодера в качестве входных данных
  pinMode(Phase_A,INPUT);
  pinMode(Phase_B,INPUT);
  pinMode(SW, OUTPUT);

    // Читаем начальное состояние Phase_B
    lastStatePhase_B = digitalRead(Phase_B);
    
    // Вызываем updateEncoder() при изменении максимума/минимума
    // по прерыванию 0 (вывод 2) или прерыванию 1 (вывод 3)
    attachPCINT(digitalPinToPCINT(Phase_A), updateEncoder, CHANGE);
    attachPCINT(digitalPinToPCINT(Phase_B), updateEncoder, CHANGE);

  display.setBrightness(0x0f);
  display.clear();
}

void loop() {
  delay(100);
    if (Update){
        // Показать десятичные числа с ведущими нулями или без них
    display.showNumberDec(counter, false);
    // Ожидать: ___0
        Update = false;
    }
}

void updateEncoder(){
  delay (4);
    // Читаем текущее состояние Phase_B
    currentStatePhase_B = digitalRead(Phase_B);

    // Если последнее и текущее состояние Phase_B различаются, то произошел импульс
    // Реагировать только на 1 изменение состояния, чтобы избежать двойного счета
    if (currentStatePhase_B != lastStatePhase_B  && currentStatePhase_B == 1){

        // Если состояние Phase_A отличается от состояния Phase_B, то
        // энкодер вращается против часовой стрелки, поэтому уменьшаем
        if (digitalRead(Phase_A) != currentStatePhase_B) {
            counter --;
        } else {
            // Энкодер вращается по часовой стрелке, поэтому увеличивается
            counter ++;
        }

    if (counter > 1023){
      counter = 1023;
    }
    if (counter < 0){
      counter = 0;
    }
    delay(15);
    }

    // Запоминаем последнее состояние CLK
    lastStatePhase_B = currentStatePhase_B;
    Update = true;
}

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

Вот что кажется хорошей ссылкой: 8-разрядный микроконтроллер ATtiny88

, 👍2

Обсуждение

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

вы слишком много думаете о программе энкодера... триггер только по одному каналу... состояние другого канала указывает направление вращения, @jsotola

«Вот то, что кажется лучшей ссылкой» ... нет, веб-сайт производителя является источником лучшей ссылки, а не какой-то сторонний сайт ... посмотрите на страницу 53 в https://ww1.microchip.com/downloads /en/DeviceDoc/doc8008.pdf, @jsotola

дополнительная информация https://onlinedocs.microchip.com/pr/GUID-19759098-FF73-4B52-AA3D-E651C849337D-en-US-2/index.html?GUID-EC4CAB0F-FADE-433B-8381-0866ED41CF64, @jsotola

1. По поводу «_обновленного кода по Эдгару Бонету_»: вы имеете в виду «по временному изображению». 2. Так как updateEncoder() заботится только об изменениях в Phase\_B, нет смысла вызывать его при изменении Phase\_A. 3. Никогда не используйте delay() в контексте прерывания! 4. Если вы сделаете счетчик беззнаковым, вам не нужно будет зажимать его значение, и оно всегда будет правильным по модулю UINT_MAX., @Edgar Bonet

2′. Так как updateEncoder() заботится только о нарастающих фронтах Phase\_B (т.е. когда он изменяется с 0 на 1), нет смысла вызывать его на падающих фронтах. Прикрепите прерывание к событиям RISING и удалите тест для currentStatePhase_B и lastStatePhase_B (а также удалите эти переменные)., @Edgar Bonet

Я, конечно, понимаю, что задержки в процедурах ISR — не лучшая идея, и это была не моя идея. В одной из ссылок предлагалось установить короткую задержку для устранения дребезга кодировщика. Если я вызову updateEncoder() только по переднему фронту B, тогда код пропустит поворот против часовой стрелки, верно, потому что B уже высокий и остается высоким? Я собираюсь начать новую тему, потому что исходный вопрос эффективно ответил, @Leslie Rhorer

Я собираюсь использовать UINT8_T. Для этой цели достаточно 8-битного пространства данных. Я просто удвою значение счетчика при выводе напряжения., @Leslie Rhorer

Подождите, это не сработает. Использование чего-то вроде UINT8_T или чего-то еще, что переворачивается, не сработает. Счетчик должен не только ограничиваться положительными целыми числами от 0 до 1024, он должен игнорировать любые входные данные, которые заставят счетчик перевернуться на 0 или 1024. Вместо этого он должен просто прекратить считать в этом направлении., @Leslie Rhorer

Если вам нужен пример того, что я сказал (и довольно нечестно со стороны мода), обратите внимание, что сообщения исчезли., @timemage


1 ответ


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

3

Типы прерываний

Один источник говорит, что есть только два вывода прерывания...

Есть два "внешних прерывания" вывода; это термин, который вы найдете в техническом описании чипа. Это термин, который относится к типу контактов, которые использует attachInterupt. Это те, что в распиновке обозначены INT0, INT1, INTn. "внешний" термин может сбивать с толку по нескольким причинам. Множество внешних вещей вызывают прерывания, но это название применяется, когда вы не говорите о событиях, связанных с конкретными периферийными устройствами, такими как USART (последовательное периферийное устройство), получающее символ. Специальная функция «внешнего прерывания» предназначен для отправки прерываний на основе сигналов на этих выводах INT0/INT1/INT#. Для каждого из этих сигналов есть один вектор прерывания (два в вашем случае). Как вы, вероятно, видели в документации, они могут быть настроены на срабатывание по нарастающим или спадающим фронтам, обоим фронтам и низким уровням. Итак, они правы в том, что говорят о «внешнем прерывании». вывода.

"внешний" имя также сбивает с толку из-за того, что еще вы нашли:

Другой источник сообщает, что булавок для прерывания смены довольно много

Это тоже верно. Это "PCINT#" названия сигналов указаны в распиновке чипа. Эти "прерывания изменения контакта" новее; не новый, а новыйэр. Существуют (редактор) AVR, которые имели/имеют «внешние прерывания» но нет «прерываний смены контакта». Хочется думать, что если бы они были созданы одновременно, то не называли бы прежних просто «внешними», потому что эти не менее внешние, хотя и менее посвященные. Для каждой группы из 8 контактов PCINT имеется только один ISR/прерывание. И в отличие от других, они только естественным образом обнаруживают, что вывод изменился, и ничего не говорят о характере этого изменения (рост или падение). Но многие AVR поддерживают их, а те, у которых их действительно много. В случае вашего ATTiny88, все контакты GPIO (Arduino «цифровые») способны отправлять прерывание смены контакта. Однако ядро Arduino AVR не предоставляет функции для их использования.

Изменение библиотеки PinChangeInterrupt от NicoHood

Одним из людей, которые раньше были в моем кругу, был NicoHood, который создал эту библиотеку PinChangeInterrupt. У меня смутные воспоминания о том, что я помогал кое-чем.

Он смоделировал его по образцу обычной функции attachInterrupt. Таким образом, вы можете использовать его почти так, как если бы у вас было RISING/FALLING/CHANGE "внешнее прерывание" возможность на всех ваших выводах PCINT#; в вашем случае все ваши контакты. Под ним отслеживается состояние каждого вывода порта, чтобы определить, что изменилось и в каком направлении. Другими словами, использование прерывания, которое срабатывает при изменении любого из 8 контактов, вместе с переменными для синтеза обратных вызовов для каждого контакта не только для изменения, но также для повышения и понижения, если вы хотите. Это не идеально, можно потерять короткоживущие события, и для отправки вашей функции обработчика требуется больше времени. Но, вероятно, это нормально для того, что вы делаете.

Он не поддерживает ваш чип из коробки. Однако вы можете изменить для поддержки ATTiny88; частично, если вы не привередливы. Вы должны найти его в менеджере библиотек как "PinChangeInterrupt" от NicoHood, в настоящее время версия 1.2.9. Если вы установите его, он должен появиться в вашем каталоге Sketchbook/libraries. Вам нужно будет отредактировать файл по пути:

<sketchbook-directory>/libraries/PinChangeInterrupt/src/PinChangeInterruptBoards.h

Это ваша локальная копия этого файла.

Ваша ATTiny88 обрабатывает PCINT почти так же, как ATMega328P в UNO. Или, скорее, почти надмножество этого. Чипы в некотором роде тесно связаны. Однако у вас есть дополнительные контакты с поддержкой PCINT на ATTiny88, которые недоступны на ATMega328P. Таким образом, вы можете грубо взломать строку в этом разделе, чтобы прочитать:

#if defined(__AVR_ATmega328__) || defined(__AVR_ATmega328A__) || defined(__AVR_ATmega328PA__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328PB__) \
   || defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PB__) \
   || defined(__AVR_ATmega88__) || defined(__AVR_ATmega88A__) || defined(__AVR_ATmega88PA__) || defined(__AVR_ATmega88P__) || defined(__AVR_ATmega88PB__) \
   || defined(__AVR_ATtiny88__) \
   || defined(__AVR_ATmega48__) || defined(__AVR_ATmega48A__) || defined(__AVR_ATmega48PA__) || defined(__AVR_ATmega48P__) || defined(__AVR_ATmega48PB__)

Добавление этой предпоследней строки выше должно заставить библиотеку обрабатывать имеющийся у вас ATTiny88, как если бы это был ATMega328P. Это компилируется под ATTinyCore. Я не настроен тестировать его на реальном ATTiny88. Но я ожидаю, что это сработает, если вы попробуете несколько примеров.

После этого библиотека должна работать на любом из выводов с меткой от PCINT0 до PCINT23. Если вы хотите, чтобы он работал с PCINT24-PCINT27, это можно сделать, но требует дополнительной работы. Но, учитывая то, что вы сказали, вам, вероятно, не нужен один из этих четырех, и они недоступны в пакете DIP, который, как я полагаю, у вас все равно есть.

Обновление кода

Вы должны #include "PinChangeInterrupt.h" вверху вашего кода, а затем ваш текущий код:

    attachInterrupt(digitalPinToInterrupt(Phase_A), updateEncoder, CHANGE);
    attachInterrupt(digitalPinToInterrupt(Phase_B), updateEncoder, CHANGE);

затем становится:

    attachPCINT(digitalPinToPCINT(Phase_A), updateEncoder, CHANGE);
    attachPCINT(digitalPinToPCINT(Phase_B), updateEncoder, CHANGE);

Если повезет, это должно сделать это при условии, что Phase_A и Phase_B под вашим ядром (например, ATTinyCore) сопоставлены с GPIO/Arduino "digital" контакты, передающие сигнал PCINT#, где число меньше 24.

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

Проведение PCINT самостоятельно

Вы также можете использовать более прямой подход, прочитав техническое описание и выяснив, как сделать то, что библиотека делает за вас, только вручную. Один из примеров NicoHood, поставляемый с библиотекой, на самом деле не является примером использования библиотеки, а скорее объяснение того, что библиотека делает в форме кода. Обратите внимание, что на самом деле он не включает библиотеку; он будет работать автономно. Так что, если вы хотите сами разобраться в внутренностях, следуя техническому описанию ATtiny88, начните с него.

Одна из причин, по которой вы, возможно, захотите сделать это самостоятельно, заключается в том, что ваши кодировщики работают быстро, до такой степени, что вы не можете позволить себе некоторое дополнительное эффективное время отправки, связанное с тем, что делает библиотека PinChangeInterrupt при сортировке. пин менялся и как. Если вы обрабатываете PCINT самостоятельно, вы можете поместить два контакта энкодера в совершенно разные группы по 8, чтобы был только один источник прерывания смены контакта для каждого ISR, и поэтому нет необходимости проверять, какой вывод вызвал прерывание. и перенаправить это в другую функцию.

,

+1, очень хороший ответ. Небольшое исправление: «_они могут быть настроены на срабатывание по восходящим или падающим фронтам или по высоким и низким уровням_». Их можно настроить на срабатывание при изменении (например, PCINT), росте, падении или низком уровне, но не на высоком уровне., @Edgar Bonet

Ты прав. Мозг на автопилоте, наверное. Обновлю его либо при следующем естественном редактировании, либо через пару дней, если у меня нет другой причины., @timemage

Ну, я думаю, я собираюсь интегрировать ваше изменение сейчас, потому что они не дали указаний на изменение, и это, вероятно, будет в моем последнем посте., @timemage

Извините, я не совсем слежу за разговором между вами двумя., @Leslie Rhorer

Между тем, код близок к рабочему. По крайней мере, прерывания сейчас происходят. Однако их часто пропускают, и они подсчитываются беспорядочно. Вместо того, чтобы считать вверх, когда энкодер поворачивается по часовой стрелке, и вниз, когда он поворачивается против часовой стрелки, чаще всего iot считает вверх, независимо от того, в какую сторону повернут энкодер, а иногда он ведет обратный отсчет, независимо от того, в какую сторону повернут энкодер. Я не уверен, что вызывает такое поведение., @Leslie Rhorer

Это не важно для вашего варианта использования. Как вы поняли, я разложил объяснение по уровням, чтобы вы (или кто-либо другой) могли получить ответ на любом уровне, который им нужен. Эдгар Боне указал на мысль, которую я сделал, признавая/описывая ваши выводы о том, что разное количество выводов описывается как способное к прерываниям. Для тех, кто заботится и собирается следовать этой части, имеет смысл только то, что она также верна. Так и поправили, @timemage