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

5
  • 1
    Welcome to SE/Arduino! Please take the tour to learn how this site works, and you might want to read "How to Ask" (again). You forgot to ask an answerable specific question... Please edit your post to add that, the schematic of your design, and what you already tried to debug. You might want to reduce your code to a minimal, complete and reproducible source, too. My next step would be to use some output pin as debug aid and toggle it in the ISR, to verify the assumptions. Commented Jan 22 at 7:41
  • 1
    What does processMenuCommand do? I suspect it does too much to be handled in an interrupt.
    – Nick Gammon
    Commented Jan 22 at 7:56
  • Human input is slow. Trying to use an interrupt to catch human input is adding unnecessary complication to your project. It would be better to ditch the interrupts and just read the buttons normally.
    – Delta_G
    Commented Jan 22 at 17:03
  • @Nick Gammon I was unaware of limitations in this sense. I did some brief research in responding to your comment and found the following which may be helpful to me. digikey.com/en/maker/tutorials/2022/… Commented Jan 22 at 20:31
  • The BUTTONPINS contains bytes but in the setup loop you are dividing it by sizeof(int). So half of the buttons are left floating and catching radio... Btw you can use for (byte pin : BUTTONPINS) {
    – KIIV
    Commented Jan 24 at 15:35

2 Answers 2

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.

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.

2
  • interruptTrigger should be declared volatile. Otherwise the compiler may optimize away any changes to it.
    – Nick Gammon
    Commented Jan 23 at 4:38
  • Thank you! I missed this! Commented Jan 23 at 6:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.