0

I'm building a programmable resin pump for my resin printer. The goal is to enable the ability to set a print volume and print time provided by the printer. The pump would then pump at full speed to cover the build plate, then continue at a lower speed to continue filling until the print is complete. Other features would be added as needed.

The problem that I'm having is that I can control everything by typing the commands on the keyboard, but the interrupt is not working as expected. Only the Left and Right commands work, but Up and Enter do nothing, and Down freezes the Arduino. The other buttons aren't connected yet.

When commands are issued via keyboard or button successfully, the command is printed to the serial terminal, e.g. #CMD# UP.

When using the buttons, Up and Enter (center) do nothing. Left and Right function properly, but Down begins to return the command and freezes the Arduino at #C so that even the keyboard commands do nothing.

I've tried changing the byte values of Up, Down, Left, Right, and Enter to a number of different values, but no change was noted.

What is going wrong?

Sketch

/*
 Sub Menu

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

*/

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

// #define is an alternate way of declaring a const #define LCD_ROWS 4 #define LCD_COLS 20

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

// Define commands as #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 .

// Assigned based on the hardware directional buttons 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 // Charset used for entering values char charset[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; // Active index of the charset int8_t charsetPosition = -1;

// Declare the call back functions void setVolumeCallback(char* value); void setTimeCallback(char* value);

// Define the main menu 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)); // Auto-Fill SUB_MENU(autofillMenu, mainMenu, ITEM_BASIC(" Auto Fill"), ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback), // defalt to 200 ml (create a variable) ITEM_INPUT("Set Time", "hh:mm", setTimeCallback), // default to 0's (create a variable) ITEM_BASIC("Start") // Execute the command to fill the vat over the specified time span. ); // Semi-Auto Fill SUB_MENU(semiautofillMenu, mainMenu, ITEM_BASIC(" Semi-Auto Fill"), ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback), // defalt to 200 ml (create a variable) ITEM_BASIC("Start") // Execute the command to fill the vat immediately. ); // Manual Control SUB_MENU(manualfillMenu, mainMenu, ITEM_BASIC(" Manual Control"), ITEM_BASIC("Fill (out)"), // Execute the command to pump out, ITEM_BASIC("Empty (in)"), // Execute the command to pump in, ITEM_BASIC("Stop") // Execute the command to stop pumping. ); // Self-Clean SUB_MENU(selfcleanMenu, mainMenu, ITEM_BASIC(" Self-Clean"), ITEM_BASIC("Start")); // Settings 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: Remove check for serial and Serial.read, these will be replaced // by physical buttons and an interrupt. 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 is share with both Auto Fill and Semi-Auto Fill

void setVolumeCallback(char* value) { // Do stuff with value Serial.print(F("# ")); Serial.println(value); }

void setTimeCallback(char* value) { // Do stuff with value Serial.print(F("# ")); Serial.println(value); }

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

void pressInterrupt() { // ISR if (millis() - lastFire < 200) { // Debounce return; } lastFire = millis();

configureDistinct(); // Setup pins for testing individual buttons

for (int i = 0; i < sizeof(BUTTONPINS) / sizeof(int); i++) { // Test each button for press if (!digitalRead(BUTTONPINS[i])) { execCommand(i); } } configureCommon(); // Return to original state }

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 schematic

ocrdu
  • 1,795
  • 3
  • 12
  • 24
TheCodeGeek
  • 111
  • 5

2 Answers2

2

You are probably doing too much in your interrupt routine (ISR). See my answer here about using interrupts.

I also have material on my web site which may be helpful.

A comment you linked to https://www.digikey.com/en/maker/tutorials/2022/the-dos-and-donts-of-using-arduino-interrupts which sensibly suggests merely setting a flag in the interrupt routine, and then doing something in your main loop if the flag is set.

Alternatively, as another comment suggested, just detect button presses in the main loop. You will iterate through it fast enough that this should be sufficient.

Nick Gammon
  • 38,901
  • 13
  • 69
  • 125
1

I resolved my problem by following the guidance that I linked to from digikey. My solution:

/*
 Sub Menu

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

*/

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

// #define is an alternate way of declaring a const #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}; // Used to indicate if the interrupt was triggered // If not, set to -1. int8_t interruptTrigger = -1;

// Define commands as #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 .

// Assigned based on the hardware directional buttons 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 // Charset used for entering values char charset[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; // Active index of the charset int8_t charsetPosition = -1;

// Declare the call back functions void setVolumeCallback(char* value); void setTimeCallback(char* value);

// Define the main menu 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)); // Auto-Fill SUB_MENU(autofillMenu, mainMenu, ITEM_BASIC(" Auto Fill"), ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback), // defalt to 200 ml (create a variable) ITEM_INPUT("Set Time", "hh:mm", setTimeCallback), // default to 0's (create a variable) ITEM_BASIC("Start") // Execute the command to fill the vat over the specified time span. ); // Semi-Auto Fill SUB_MENU(semiautofillMenu, mainMenu, ITEM_BASIC(" Semi-Auto Fill"), ITEM_INPUT("Set Vol. (ml)", "200", setVolumeCallback), // defalt to 200 ml (create a variable) ITEM_BASIC("Start") // Execute the command to fill the vat immediately. ); // Manual Control SUB_MENU(manualfillMenu, mainMenu, ITEM_BASIC(" Manual Control"), ITEM_BASIC("Fill (out)"), // Execute the command to pump out, ITEM_BASIC("Empty (in)"), // Execute the command to pump in, ITEM_BASIC("Stop") // Execute the command to stop pumping. ); // Self-Clean SUB_MENU(selfcleanMenu, mainMenu, ITEM_BASIC(" Self-Clean"), ITEM_BASIC("Start")); // Settings 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: Remove check for serial and Serial.read, these will be replaced // by physical buttons and an interrupt. //if (!Serial.available()) return; //char command = Serial.read();

// If the trigger was set, ignore the serial command and redefine command to use the button command if (interruptTrigger > -1) { char command = commandOrder[interruptTrigger]; processMenuCommand(menu, command, charsetPosition, charset, CHARSET_SIZE, UP, DOWN, ENTER, BACK, CLEAR, BACKSPACE, LEFT, RIGHT); interruptTrigger = -1; // Now that the trigger has been read, reset the trigger }

} /**

  • Define callbacks

/ // setVolume is share with both Auto Fill and Semi-Auto Fill void setVolumeCallback(char value) { // Do stuff with value Serial.print(F("# ")); Serial.println(value); } void setTimeCallback(char* value) { // Do stuff with value Serial.print(F("# ")); Serial.println(value); }

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

void pressInterrupt() { // ISR if (millis() - lastFire < DELAY) { // Debounce return; } lastFire = millis();

configureDistinct(); // Setup pins for testing individual buttons for (int i = 0; i < sizeof(BUTTONPINS); i++) { // Test each button for press if (!digitalRead(BUTTONPINS[i])) { interruptTrigger = i; // Set the interrupt trigger (reset elsewhere) /* NOT PICKING UP 4 (ENTER) */ } } configureCommon(); // Return to original state }

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); } }

I also had to adjust my loops which were only allowing me to check 4 inputs. by excluding / sizeof(int) I was able to detect all of the button presses. I'm sharing in case someone else runs into a similar situation.

TheCodeGeek
  • 111
  • 5