Не могу заставить прерывание контролировать мой проект
Я создаю программируемый насос смолы для своего принтера смолы. Цель состоит в том, чтобы обеспечить возможность установки объема печати и времени печати, предоставляемых принтером. Затем насос будет качать на полной скорости, чтобы покрыть рабочую пластину, а затем продолжит работу на более низкой скорости, чтобы продолжить заполнение до завершения печати. Другие функции будут добавляться по мере необходимости.
Проблема в том, что я могу всем управлять, вводя команды на клавиатуре, но прерывание не работает должным образом. Работают только команды «Влево» и «Вправо», но «Вверх» и «Ввод» ничего не делают, а «Вниз» зависает 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);
}
Схема
@TheCodeGeek, 👍0
Обсуждение2 ответа
Лучший ответ:
Я решил свою проблему, следуя инструкциям, на которые я дал ссылку на 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
Возможно, вы слишком много делаете в своей подпрограмме обработки прерываний (ISR). См. мой ответ здесь об использовании прерываний.
У меня также есть материалы на моем веб-сайте, которые могут оказаться полезными.
Комментарий, ссылка на который вы дали https: //www.digikey.com/en/maker/tutorials/2022/the-dos-and-donts-of-using-arduino-interrupts, который разумно предлагает просто установить флаг в процедуре прерывания, а затем выполнить что-то в вашем основном цикле, если флаг установлен.
В качестве альтернативы, как было предложено в другом комментарии, просто обнаруживайте нажатия кнопок в основном цикле. Вы будете проходить через него достаточно быстро, и этого будет достаточно.
- Как сгенерировать аппаратное прерывание в mpu6050 для пробуждения Arduino из режима SLEEP_MODE_PWR_DOWN?
- Arduino непрерывно считывает значение АЦП с помощью прерывания
- Как правильно использовать volatile переменные в Arduino?
- Как прервать функцию цикла и перезапустить ее?
- 4-битный счетчик вверх и вниз
- Включить и отключить отдельные прерывания
- Управление функцией включения на драйвере микрошагового устройства
- Захват прерывания на обоих фронтах, когда он установлен на RISING или FALLING
Добро пожаловать в 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