Чтение квадратурного энкодера в реальном времени с полным разрешением только с одним прерыванием на ATmega328
Я хочу прочитать квадратурные энкодеры с полным разрешением и одним прерыванием на Arduino Nano (ATmega328). Итак, я обнаружил, что мы можем использовать XOR для достижения полного разрешения:
Где контакт 3
является прерываемым, а 4
и 5
— нет.
Наши требования:
- Полное разрешение: например, решение, предложенное в этом ответе, работает только с половинным разрешением, не обнаруживая все края.
- В режиме реального времени: быть максимально быстрым до уровня, который позволяет это оборудование, и безопасным с точки зрения отсутствия пропуска ни одного из фронтов сигнала.
- используя только одно прерывание. Нам нужен другой для других целей.
На данный момент я разработал два разных кода, один из которых направлен на обеспечение безопасности, а другой — на производительность:
Преамбула:
#include "digitalWriteFast.h"
#define pinT 3 // триггерный вывод, исходящий от XOR
#define pinA 4 // канал A
#define pinB 5 // канал B
volatile long counter = 0;
long counter_ = counter;
где библиотеку digitalWriteFast
можно загрузить здесь.
Исполнитель:
volatile short int increment = 0;
volatile bool tmpA;
volatile bool tmpB;
volatile bool lastA;
ISR(trigger) {
tmpA = digitalReadFast(pinA);
tmpB = digitalReadFast(pinB);
counter += (1 - 2 * tmpA) * (1 - 2 * tmpB) * (1 - 2 * (lastA != tmpA));
lastA = tmpA;
}
void setup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinT), trigger, CHANGE);
lastA = digitalReadFast(pinA);
Serial.begin(9600);
}
void loop() {
if (counter != counter_) {
Serial.println(counter);
counter_ = counter;
}
}
Безопасно:
volatile unsigned long error = 0;
unsigned long error_ = 0;
volatile bool inputA;
volatile bool inputB;
volatile short state; // 100 110 101 111
volatile short state_;
void detectState() {
inputA = digitalReadFast(pinA);
inputB = digitalReadFast(pinB);
if (inputA) {
if (inputB) {
state = 111;
} else {
state = 110;
}
} else {
if (inputB) {
state = 101;
} else {
state = 100;
}
}
}
ISR(trigger) {
detectState();
if (state_ != state) {
switch (state_) {
case 100:
if (state == 110) {
counter++;
} else if (state == 101) {
counter--;
} else {
error++;
}
break;
case 110:
if (state == 100) {
counter--;
} else if (state == 111) {
counter++;
} else {
error++;
}
break;
case 101:
if (state == 100) {
counter++;
} else if (state == 111) {
counter--;
} else {
error++;
}
break;
case 111:
if (state == 110) {
counter--;
} else if (state == 101) {
counter++;
} else {
error++;
}
break;
default:
error++;
break;
}
state_ = state;
} else {
error++;
}
}
void setup() {
pinMode(pinT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinT), trigger, CHANGE);
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
detectState();
state_ = state;
Serial.begin(9600);
}
void loop() {
if (counter != counter_) {
Serial.print("counter: ");
Serial.println(counter);
counter_ = counter;
}
if (error != error_) {
Serial.print(error);
Serial.println(" errors");
error_ = error;
}
}
Теперь мои вопросы:
- Есть ли способ повысить скорость/безопасность производительного/безопасного кода? Приветствуется любой способ улучшить этот код.
- Возможно ли сочетание максимальной скорости и безопасности или компромисс между ними?
- Какую максимальную частоту мы можем получить при такой конфигурации?
Заранее спасибо за помощь.
@Foad, 👍0
Обсуждение1 ответ
Это всего лишь частичный, поспешный ответ.
Вы сможете получить значительное ускорение, если определите свой собственный ISR для обработки прерывания, например:
ISR(INT1_vect) { ... }
Вместо этого Arduino использует прерывания
void trigger() { ...}
void setup() {
attachInterrupt(int_number, trigger, CHANGE);
...
}
но это медленно, так как использует ISR, обеспечиваемый ядром Arduino,
который должен найти и вызвать ваш обработчик прерывания. Как ты написал
обработчик неверен: либо вы определяете обычную функцию без
ISR
и пойти по пути Arduino, или вы идете по простому пути AVR и
самостоятельно определите ISR, который должен называться INTERRUPT_NAME_vect
, в
в этом случае вы не используете attachInterrupt()
. То, как вы это сделали,
вероятно, самый медленный, который вы можете получить, так как компилятор без необходимости добавит
Пролог типа ISR и эпилог вашего обработчика.
Что касается максимальной частоты, то ее трудно оценить без тестирования, но вы можете получить некоторое представление, прочитав этот ответ о прерывании задержка.
Обновлено: как я уже говорил в своем первом комментарии к вопросу, смена контакта прерывание можно использовать вместо внешнего логического вентиля. Вот как сделай это.
Во-первых, нужно посмотреть на распиновку Arduino Uno и увидеть, что:
- цифровой 4 = PD4 = PCINT20
- цифровой 5 = PD5 = PCINT21
Оба вывода связаны с запросом прерывания 2 на смену вывода (PCINT2_vect).
Это прерывание можно настроить в setup()
следующим образом:
PCMSK2 = _BV(PCINT20) // разрешить прерывание на выводе PCINT20 = PD4
| _BV(PCINT21); // разрешить прерывание на выводе PCINT21 = PD5
PCIFR = _BV(PCIF2); // очистить флаг прерывания
PCICR = _BV(PCIE2); // разрешить запрос прерывания смены контакта 2
Теперь прерывание будет срабатывать при каждом изменении логических уровней входы 4 и 5.
Затем в ISR весь порт D может быть прочитан за один цикл с помощью
просто оценивая соответствующий входной регистр вывода, т.е. PIND
. С
некоторое смещение и маскирование, можно получить фазу между 0 и 3
(счетчик в коде Грея: 0, 1, 3, 2, 0...). Из предыдущего и
текущей фазе, можно вычислить изменение, которое необходимо применить к
счетчик:
// Обработка запроса прерывания смены контакта 2.
ISR(PCINT2_vect) {
static uint8_t previous_phase;
uint8_t phase = (PIND >> 4) & 0x03; // чтение входных контактов
counter += encoder_delta(previous_phase, phase);
previous_phase = phase;
}
Реализация encoder_delta()
оставлена в качестве упражнения для
читатель. ;-)
Спасибо. Я просмотрю новые правки и буду беспокоить вас, если у меня возникнут дополнительные вопросы., @Foad
- Правильное использование SPI с ISR
- Не удалось получить показания для "Двигателя с энкодером"
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
- Использование поворотных энкодеров с прерываниями смены контактов
- Выводы прерываний Arduino Mega 2560 и отображение портов с помощью поворотного энкодера
- Серийное прерывание
- Влияет ли `millis()` на длинные ISR?
- Как прервать функцию цикла и перезапустить ее?
Обратите внимание, что вместо вентиля XOR вы можете использовать одно _прерывание смены контакта_ на обоих каналах., @Edgar Bonet
@EdgarBonet Вы имеете в виду подключение двух каналов к двум прерываниям? что мы не можем сделать. Arduino Uno/Nano имеет только два прерывания, и нам нужно одно для других целей. Это то, что вы имели в виду?, @Foad
Нет, я имею в виду подключение двух каналов к одному прерыванию смены вывода_. Arduino имеет три прерывания по изменению контакта в дополнение к двум внешним прерываниям., @Edgar Bonet
Я не уверен, что такое «прерывание смены контакта», но если вы подключаете два канала к одному прерываемому контакту (т. е. 3 или 4), то это работает как «И»., @Foad
Нет, это не так. Выполните поиск в Интернете по запросу «_Прерывание смены контакта Arduino_» или «_Прерывание смены контакта AVR_»., @Edgar Bonet
@ ЭдгарБонет Хорошо. Читая [эту страницу](https://github.com/NicoHood/PinChangeInterrupt), я не думаю, что использование прерываний смены контакта/порта является лучшим решением. Если только я не понял вашу точку зрения, и вы можете уточнить., @Foad
MCU имеет прерывания по смене контактов, поэтому вам не нужно добавлять логический элемент XOR и использовать один из внешних контактов прерывания. Вам не нужна библиотека для их использования, и вам, вероятно, в любом случае не нужны накладные расходы на библиотеку. Прочитайте [Прерывания смены контакта Arduino](https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/)., @Edgar Bonet
@EdgarBonet, пожалуйста, учтите, что остальные контакты не бесплатны. есть чтение и запись других вещей. Итак, если я использую 3 доступных блока
PinChangeInterrupts
(0-7, 8-13, A0-A5). Я получаю много ненужных прерываний. XOR помогает считывать оба канала в «реальном времени» с помощью всего одного вывода прерывания и одного обычного. См. [это изображение](https://trello-attachments.s3.amazonaws.com/5d15eec00aa2638683ed41ed/600x173/f34f85ad8bd4d7c17a69661855122f79/eiczu.png)., @FoadНет, вы не получите ненужных прерываний, если не активируете PCINT на других выводах., @Edgar Bonet
@EdgarBonet, каков правильный синтаксис для чтения контактов 4 и 5 (которые не прерываются) с использованием прерывания смены контакта / порта? какие двоичные/шестнадцатеричные значения должны быть присвоены
PCICR
иPCMSK0/PCMSK1/PCMSK2
?, @Foad