Не могу заставить прерывание контролировать мой проект

Я создаю программируемый насос смолы для своего принтера смолы. Цель состоит в том, чтобы обеспечить возможность установки объема печати и времени печати, предоставляемых принтером. Затем насос будет качать на полной скорости, чтобы покрыть рабочую пластину, а затем продолжит работу на более низкой скорости, чтобы продолжить заполнение до завершения печати. Другие функции будут добавляться по мере необходимости.

Проблема в том, что я могу всем управлять, вводя команды на клавиатуре, но прерывание не работает должным образом. Работают только команды «Влево» и «Вправо», но «Вверх» и «Ввод» ничего не делают, а «Вниз» зависает Arduino. Остальные кнопки еще не подключены.

При успешной подаче команд с клавиатуры или кнопки команда выводится на последовательный терминал, например #CMD# UP.

При использовании кнопок «Вверх» и «Ввод» (в центре) ничего не происходит. Влево и Вправо работают правильно, но Вниз начинает возвращать команду и останавливает Arduino на #C, так что даже команды с клавиатуры ничего не делают.

Я пробовал менять значения байтов «Вверх», «Вниз», «Влево», «Вправо» и «Ввод» на несколько разных значений, но никаких изменений не наблюдалось.

Что происходит не так?

Эскиз

/*
 Sub Menu

 https://lcdmenu.forntoh.dev/examples/submenu

*/

#include <ItemSubMenu.h>
#include <ItemInput.h>
#include <LcdMenu.h>
#include <utils/commandProccesors.h>

// #define — альтернативный способ объявления константы
#define LCD_ROWS 4
#define LCD_COLS 20

#define COMMONPIN 2
const byte BUTTONPINS[] = {4,5,6,7,8,9,10,11};

// Определить команды как
#define UP 56        // NUMPAD 8
#define DOWN 50      // NUMPAD 2
#define LEFT 52      // NUMPAD 4
#define RIGHT 54     // NUMPAD 6
#define ENTER 53     // NUMPAD 5
#define BACK 55      // NUMPAD 7
#define BACKSPACE 8  // BACKSPACE
#define CLEAR 46     // NUMPAD .

// Назначается на основе аппаратных кнопок управления
const byte commandOrder[8] = {UP,DOWN,LEFT,RIGHT,ENTER,BACK,BACKSPACE,CLEAR};

unsigned long lastFire = 0;

extern MenuItem* autofillMenu[];
extern MenuItem* semiautofillMenu[];
extern MenuItem* manualfillMenu[];
extern MenuItem* selfcleanMenu[];
extern MenuItem* settingsMenu[];

#define CHARSET_SIZE 10
// Кодировка, используемая для ввода значений
char charset[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
// Активный индекс набора символов
int8_t charsetPosition = -1;

// Объявляем функции обратного вызова
void setVolumeCallback(char* value);
void setTimeCallback(char* value);

// Определяем главное меню
MAIN_MENU(
  ITEM_BASIC("   *Resin Pump*"),
  ITEM_SUBMENU("Auto Fill", autofillMenu),
  ITEM_SUBMENU("Semi-Auto Fill", semiautofillMenu),
  ITEM_SUBMENU("Manual Pump", manualfillMenu),
  ITEM_SUBMENU("Self-Clean", selfcleanMenu),
  ITEM_SUBMENU("Settings", settingsMenu));
// Автозаполнение
SUB_MENU(autofillMenu, mainMenu,
         ITEM_BASIC("   *Auto Fill*"),
         ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback),  // значение по умолчанию равно 200 мл (создать переменную)
         ITEM_INPUT("Set Time", "hh:mm", setTimeCallback),       // по умолчанию 0 (создать переменную)
         ITEM_BASIC("Start")                                     // Выполняем команду для заполнения чана в течение указанного промежутка времени.
);
// Полуавтоматическое заполнение
SUB_MENU(semiautofillMenu, mainMenu,
         ITEM_BASIC(" *Semi-Auto Fill*"),
         ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback),  // значение по умолчанию равно 200 мл (создать переменную)
         ITEM_BASIC("Start")                                     // Выполняем команду для немедленного заполнения чана.
);
// Ручное управление
SUB_MENU(manualfillMenu, mainMenu,
         ITEM_BASIC(" *Manual Control*"),
         ITEM_BASIC("Fill (out)"),  // Выполняем команду на откачку,
         ITEM_BASIC("Empty (in)"),  // Выполняем команду для закачки,
         ITEM_BASIC("Stop")         // Выполняем команду для остановки перекачки.
);
// Самоочистка
SUB_MENU(selfcleanMenu, mainMenu,
         ITEM_BASIC("   *Self-Clean*"),
         ITEM_BASIC("Start"));
// Настройки
SUB_MENU(settingsMenu, mainMenu,
         ITEM_BASIC("    *Settings*"),
         ITEM_BASIC("Max Volume of Vat"),
         ITEM_BASIC("Default Volume"),
         ITEM_BASIC("Resin Amount"),
         ITEM_BASIC("Resin Overhead"),
         ITEM_BASIC("Pump Flow Rate"),
         ITEM_BASIC("Default Pump Speed"),
         ITEM_BASIC("Print Start Delay"));

LcdMenu menu(LCD_ROWS, LCD_COLS);

void setup() {
  Serial.begin(9600);
  configureCommon();
  
  menu.setupLcdWithMenu(0x27, mainMenu);
  attachInterrupt(digitalPinToInterrupt(COMMONPIN), pressInterrupt,  FALLING);
}

void loop() {
  // TODO: удалить проверку на серийный номер и Serial.read, они будут заменены
  // физическими кнопками и прерыванием.
  if (!Serial.available()) 
    return;
  char command = Serial.read();
  processMenuCommand(menu, command, charsetPosition, charset, CHARSET_SIZE, UP, DOWN, ENTER, BACK, CLEAR, BACKSPACE, LEFT, RIGHT);
}
/**
 * Define callbacks
 */
// setVolume используется как для автоматического заполнения, так и для полуавтоматического заполнения

void setVolumeCallback(char* value) {
  // Делаем что-то со значением
  Serial.print(F("# "));
  Serial.println(value);
}

void setTimeCallback(char* value) {
  // Делаем что-то со значением
  Serial.print(F("# "));
  Serial.println(value);
}

/*** Begin Button Interrupt Watching ***/

void pressInterrupt() { // ISR
  if (millis() - lastFire < 200) { // Устранение дребезга
    return;
  }
  lastFire = millis();

  configureDistinct(); // Настройка пинов для тестирования отдельных кнопок

  for (int i = 0; i < sizeof(BUTTONPINS) / sizeof(int); i++) { // Проверяем каждую кнопку на нажатие
    if (!digitalRead(BUTTONPINS[i])) {
      execCommand(i);
    }
  }
  configureCommon(); // Возвращаемся в исходное состояние
}

void configureCommon() {
  pinMode(COMMONPIN, INPUT_PULLUP);
  for (int i = 0; i < sizeof(BUTTONPINS) / sizeof(int); i++) {
    pinMode(BUTTONPINS[i], OUTPUT);
    digitalWrite(BUTTONPINS[i], LOW);
  }
}

void configureDistinct() {
  pinMode(COMMONPIN, OUTPUT);
  digitalWrite(COMMONPIN, LOW);
  for (int i = 0; i < sizeof(BUTTONPINS) / sizeof(int); i++) {
    pinMode(BUTTONPINS[i], INPUT_PULLUP);
  }
}

void execCommand(int buttonIndex) {
  byte command =  commandOrder[buttonIndex];
  
  processMenuCommand(menu, command, charsetPosition, charset, CHARSET_SIZE, UP, DOWN, ENTER, BACK, CLEAR, BACKSPACE, LEFT, RIGHT);
}

Схема schematic

, 👍0

Обсуждение

Добро пожаловать в SE/Arduino! Пожалуйста, пройдите [тур], чтобы узнать, как работает этот сайт, и, возможно, вам захочется прочитать «[спросить]» (снова). Вы забыли задать конкретный вопрос, на который можно ответить... Пожалуйста, [отредактируйте] свой пост, добавив сюда схему вашего проекта и то, что вы уже пытались отладить. Возможно, вы также захотите сократить свой код до минимального, полного и воспроизводимого исходного кода. _Мой_ следующий шаг — использовать какой-нибудь выходной контакт в качестве средства отладки и переключить его в ISR, чтобы проверить предположения., @the busybee

Что делает processMenuCommand? Я подозреваю, что это слишком много, чтобы его можно было обработать в прерывании., @Nick Gammon

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

@Ник Гаммон Я не знал об ограничениях в этом смысле. Я провел небольшое исследование, отвечая на ваш комментарий, и нашел следующее, что может быть для меня полезным. https://www.digikey.com/en/maker/tutorials/2022/the-dos-and-donts-of-using-arduino-interrupts, @TheCodeGeek

BUTTONPINS содержит байты, но в цикле настройки вы делите их на sizeof(int). Таким образом, половина кнопок остается плавающей и ловит радио... Кстати, вы можете использовать for (byte pin: BUTTONPINS) {, @KIIV


2 ответа


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

1

Я решил свою проблему, следуя инструкциям, на которые я дал ссылку на digikey. Мое решение:

/*
 Sub Menu

 https://lcdmenu.forntoh.dev/examples/submenu

*/

#include <ItemSubMenu.h>
#include <ItemInput.h>
#include <LcdMenu.h>
#include <utils/commandProccesors.h>

// #define — альтернативный способ объявления константы
#define LCD_ROWS 4
#define LCD_COLS 20
#define DELAY 100
#define COMMONPIN 2
const byte BUTTONPINS[] = {4,5,6,7,8,9,10,11};
// Используется для указания, было ли вызвано прерывание
// Если нет, установите значение -1.
int8_t interruptTrigger = -1;

// Определить команды как
#define UP 56        // NUMPAD 8
#define DOWN 50      // NUMPAD 2
#define LEFT 52      // NUMPAD 4
#define RIGHT 54     // NUMPAD 6
#define ENTER 53     // NUMPAD 5
#define BACK 55      // NUMPAD 7
#define BACKSPACE 8  // BACKSPACE
#define CLEAR 46     // NUMPAD .

// Назначается на основе аппаратных кнопок управления
const byte commandOrder[] = {UP,DOWN,LEFT,RIGHT,ENTER,BACK,CLEAR,BACKSPACE};

unsigned long lastFire = 0;

extern MenuItem* autofillMenu[];
extern MenuItem* semiautofillMenu[];
extern MenuItem* manualfillMenu[];
extern MenuItem* selfcleanMenu[];
extern MenuItem* settingsMenu[];

#define CHARSET_SIZE 10
// Кодировка, используемая для ввода значений
char charset[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
// Активный индекс набора символов
int8_t charsetPosition = -1;

// Объявляем функции обратного вызова
void setVolumeCallback(char* value);
void setTimeCallback(char* value);

// Определяем главное меню
MAIN_MENU(
  ITEM_BASIC("   *Resin Pump*"),
  ITEM_SUBMENU("Auto Fill", autofillMenu),
  ITEM_SUBMENU("Semi-Auto Fill", semiautofillMenu),
  ITEM_SUBMENU("Manual Pump", manualfillMenu),
  ITEM_SUBMENU("Self-Clean", selfcleanMenu),
  ITEM_SUBMENU("Settings", settingsMenu));
// Автозаполнение
SUB_MENU(autofillMenu, mainMenu,
         ITEM_BASIC("   *Auto Fill*"),
         ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback),  // значение по умолчанию равно 200 мл (создать переменную)
         ITEM_INPUT("Set Time", "hh:mm", setTimeCallback),       // по умолчанию 0 (создать переменную)
         ITEM_BASIC("Start")                                     // Выполняем команду для заполнения чана в течение указанного промежутка времени.
);
// Полуавтоматическое заполнение
SUB_MENU(semiautofillMenu, mainMenu,
         ITEM_BASIC(" *Semi-Auto Fill*"),
         ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback),  // значение по умолчанию равно 200 мл (создать переменную)
         ITEM_BASIC("Start")                                     // Выполняем команду для немедленного заполнения чана.
);
// Ручное управление
SUB_MENU(manualfillMenu, mainMenu,
         ITEM_BASIC(" *Manual Control*"),
         ITEM_BASIC("Fill (out)"),  // Выполняем команду на откачку,
         ITEM_BASIC("Empty (in)"),  // Выполняем команду для закачки,
         ITEM_BASIC("Stop")         // Выполняем команду для остановки перекачки.
);
// Самоочистка
SUB_MENU(selfcleanMenu, mainMenu,
         ITEM_BASIC("   *Self-Clean*"),
         ITEM_BASIC("Start"));
// Настройки
SUB_MENU(settingsMenu, mainMenu,
         ITEM_BASIC("    *Settings*"),
         ITEM_BASIC("Max Volume of Vat"),
         ITEM_BASIC("Default Volume"),
         ITEM_BASIC("Resin Amount"),
         ITEM_BASIC("Resin Overhead"),
         ITEM_BASIC("Pump Flow Rate"),
         ITEM_BASIC("Default Pump Speed"),
         ITEM_BASIC("Print Start Delay"));

LcdMenu menu(LCD_ROWS, LCD_COLS);

void setup() {
  Serial.begin(9600);
  configureCommon();
  
  menu.setupLcdWithMenu(0x27, mainMenu);
  attachInterrupt(digitalPinToInterrupt(COMMONPIN), pressInterrupt,  FALLING);
}

void loop() {
  // TODO: удалить проверку на серийный номер и Serial.read, они будут заменены
  // физическими кнопками и прерыванием.
  //если (!Serial.available()) return;
  //команда символа = Serial.read();

  // Если триггер был установлен, игнорируем последовательную команду и переопределяем команду, чтобы использовать команду кнопки
  if (interruptTrigger > -1) {
    char command = commandOrder[interruptTrigger];
    processMenuCommand(menu, command, charsetPosition, charset, CHARSET_SIZE, 
                      UP, DOWN, ENTER, BACK, CLEAR, BACKSPACE, LEFT, RIGHT);
    interruptTrigger = -1;  // Теперь, когда триггер прочитан, сбрасываем триггер
  }
  
}
/**
 * Define callbacks
 */
// setVolume используется как для автоматического заполнения, так и для полуавтоматического заполнения
void setVolumeCallback(char* value) {
  // Делаем что-то со значением
  Serial.print(F("# "));
  Serial.println(value);
}
void setTimeCallback(char* value) {
  // Делаем что-то со значением
  Serial.print(F("# "));
  Serial.println(value);
}

/*** Begin Button Interrupt Watching ***/


void pressInterrupt()
{ // ИСР
  if (millis() - lastFire < DELAY) { // Устранение дребезга
    return;
  }
  lastFire = millis();

  configureDistinct(); // Настройка пинов для тестирования отдельных кнопок
  for (int i = 0; i < sizeof(BUTTONPINS); i++)
  { // Проверка каждой кнопки на нажатие
    if (!digitalRead(BUTTONPINS[i]))
    {
      interruptTrigger = i; // Устанавливаем триггер прерывания (сбрасываем в другом месте)
      /* NOT PICKING UP 4 (ENTER) */
    }
  }
  configureCommon(); // Возвращаемся в исходное состояние
}

void configureCommon()
{
  pinMode(COMMONPIN, INPUT_PULLUP);
  for (int i = 0; i < sizeof(BUTTONPINS); i++)
  {
    pinMode(BUTTONPINS[i], OUTPUT);
    digitalWrite(BUTTONPINS[i], LOW);
  }
}

void configureDistinct()
{
  pinMode(COMMONPIN, OUTPUT);
  digitalWrite(COMMONPIN, LOW);
  for (int i = 0; i < sizeof(BUTTONPINS); i++)
  {
    pinMode(BUTTONPINS[i], INPUT_PULLUP);
  }
}

Мне также пришлось настроить свои петли, которые позволяли мне проверять только 4 входа. исключив /sizeof(int), я смог обнаружить все нажатия кнопок. Поделюсь на случай, если кто-то еще попадет в подобную ситуацию.

,

InterruptTrigger должен быть объявлен как изменчивый. В противном случае компилятор может оптимизировать любые изменения в нем., @Nick Gammon

Спасибо! Я пропустил это!, @TheCodeGeek


2

Возможно, вы слишком много делаете в своей подпрограмме обработки прерываний (ISR). См. мой ответ здесь об использовании прерываний.

У меня также есть материалы на моем веб-сайте, которые могут оказаться полезными.

Комментарий, ссылка на который вы дали https: //www.digikey.com/en/maker/tutorials/2022/the-dos-and-donts-of-using-arduino-interrupts, который разумно предлагает просто установить флаг в процедуре прерывания, а затем выполнить что-то в вашем основном цикле, если флаг установлен.

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

,