Skip to main content
3 of 4
added 13 characters in body
Spook
  • 195
  • 1
  • 8

Arduino UNO hangs without visible reason

I'm having trouble with Arduino UNO. I bought a cheap clone (what obviously may be the reason) along with display+keypad shield (site in Polish). For my project I wrote a menu library, which (along with simple demo project) may be downloaded or cloned from my Bitbucket repository.

My problem is, that when I push "up" or "down" button and hold it, program behaves as it should for couple of seconds and then board freezes. This is definitely not a case of program hang, because when I used serial debugging, and when such freeze happened, message sent from board was cut in half and no new messages were comming. After such hang I can only reset the board.

I investigated my code a lot, but couldn't find a problem. My questions are:

  • In what circumstances board may freeze in such way?
  • If someone owns similar hardware (display, 5-button keypad) and original UNO, could you try running my program and check, if the hang happens as well?

Edit: Complete sources of the project, as requested (now fixed):

Arduinomenu.ino:

#include "Display.h"
#include "Keypad.h"
#include "Menu.h"

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
Display display(lcd, 16, 2);
Keypad keypad;

BaseMenuItem * subItems[] =
{
  new ActionMenuItem("Subaction 1", 4),
  new ActionMenuItem("Subaction 2", 5),
  new ActionMenuItem("Subaction 3", 6),
  new IntMenuItem("Int item", 0, 10, 5),
  new BoolMenuItem("Bool item", true)
};

BaseMenuItem * mainItems[] = 
{
  new ActionMenuItem("Action 1", 1),
  new ActionMenuItem("Action 2", 2),
  new ActionMenuItem("Action 3", 3),
  new SubMenuItem("Submenu", subItems, sizeof(subItems) / sizeof(BaseMenuItem *))
};

MainMenu mainMenu(display, mainItems, sizeof(mainItems) / sizeof(BaseMenuItem *));

void setup()
{
  Serial.begin(9600);
  while (!Serial);

  mainMenu.Show();
}
 
void loop()
{
  int key = keypad.ReadKey();
  int result = mainMenu.Action(key);
}

Display.h:

#ifndef DISPLAY_H__
#define DISPLAY_H__

#include <LiquidCrystal.h>

struct Display
{
  LiquidCrystal & lcd;
  unsigned char cols, rows;

  Display(LiquidCrystal & lcd, unsigned char cols, unsigned char rows);
  void Print(unsigned char col, unsigned char row, char * text);
};

#endif

Display.cpp:

#include "Display.h"

Display::Display(LiquidCrystal & lcd, unsigned char cols, unsigned char rows)
  : lcd(lcd)
{
  this->cols = cols;
  this->rows = rows;
  lcd.begin(cols, rows);
}

void Display::Print(unsigned char col, unsigned char row, char * text)
{
  this->lcd.setCursor(col, row);
  this->lcd.print(text);
}

Keypad.h:

#ifndef KEYPAD_H__
#define KEYPAD_H__

#define USE_KEYPAD

#define KEY_RIGHT  0
#define KEY_UP     1
#define KEY_DOWN   2
#define KEY_LEFT   3
#define KEY_SELECT 4
#define KEY_NONE   5

#define LAST_READ_DELAY 250

#define keypadPort 0

class Keypad 
{
private:
  unsigned long lastRead;
  unsigned char lastKey;
public:
  Keypad();
  unsigned char ReadKey();
};

#endif

Keypad.cpp:

#include "Keypad.h"
#include "Arduino.h"

Keypad::Keypad() 
{
  this->lastRead = millis();
  this->lastKey = KEY_NONE;
}

// Button values (approximate):
// Right    0
// Up       144
// Down     329
// Left     504
// Select   741
unsigned char Keypad::ReadKey() 
{
  unsigned long now = millis();
  if (this->lastKey != KEY_NONE && (now - this->lastRead < LAST_READ_DELAY) && (now > this->lastRead))
  {
    return KEY_NONE;
  }
  
  int key = analogRead(keypadPort);      
  
  if (key > 1000)
    this->lastKey = KEY_NONE;
  else if (key < 50)
    this->lastKey = KEY_RIGHT;
  else if (key < 250)
    this->lastKey = KEY_UP;
  else if (key < 450)
    this->lastKey = KEY_DOWN;
  else if (key < 650)
    this->lastKey = KEY_LEFT;
  else if (key < 850)
    this->lastKey = KEY_SELECT;
  else
    this->lastKey = KEY_NONE;
  
  this->lastRead = now;

  return this->lastKey;
}

Menu.h:

#ifndef MENU_H__
#define MENU_H__

#include <stdio.h>
#include <stdlib.h>
#include "Display.h"
#include "Arduino.h"

#ifdef USE_KEYPAD
  #define MENU_ACTION_RIGHT KEY_RIGHT
  #define MENU_ACTION_UP KEY_UP
  #define MENU_ACTION_DOWN KEY_DOWN
  #define MENU_ACTION_LEFT KEY_LEFT
  #define MENU_ACTION_SELECT KEY_SELECT
#else
  #define MENU_ACTION_RIGHT 0
  #define MENU_ACTION_UP 1
  #define MENU_ACTION_DOWN 2
  #define MENU_ACTION_LEFT 3
  #define MENU_ACTION_SELECT 4
#endif

#define TEXT_BACK "Back"
#define TEXT_BACK_LEN 4

#ifndef SELECTION_CHAR
#define SELECTION_CHAR '>'
#endif

#define MENU_TYPE_INT 0
#define MENU_TYPE_BOOL 1
#define MENU_TYPE_SUB 2
#define MENU_TYPE_ACTION 3

#ifndef MAX_MENU_LEVELS
#define MAX_MENU_LEVELS 5
#endif

// Forward declarations -------------------------------------------------------

class MainMenu;

// BaseMenuItem ---------------------------------------------------------------

class BaseMenuItem
{
protected:
  const char * name;
  unsigned char len;

public:
  BaseMenuItem(const char * name);
  void ShowListItem(Display & display, int row, bool selected);
  const char * GetName();
  virtual int GetType() = 0;
};

// MenuItem -------------------------------------------------------------------

class MenuItem : public BaseMenuItem
{
friend class MainMenu;
  
private:
  void ShowEditMode(Display & display);
  
protected:
  void ShowEditHeader(Display & display);
  virtual void ShowEditValue(Display & display) = 0;
  virtual void HandleAction(int key) = 0;  

public:
  MenuItem(const char * name);  
};

// IntMenuItem ----------------------------------------------------------------

class IntMenuItem : public MenuItem
{
friend class MainMenu;
  
private:
  int value;
  int min;
  int max;
  
protected:
  void ShowEditValue(Display & display);
  void HandleAction(int key);

public:
  IntMenuItem(const char * name, int min, int max, int defaultValue);
  int GetType();
};

// BoolMenuItem ---------------------------------------------------------------

class BoolMenuItem : public MenuItem
{
friend class MainMenu;
  
private:
  bool value;
  
protected:
  void ShowEditValue(Display & display);
  void HandleAction(int key);
  
public:
  BoolMenuItem(const char * name, bool defaultValue);
  int GetType();
};

// SubMenuItem ----------------------------------------------------------------

class SubMenuItem : public BaseMenuItem
{
friend class MainMenu;
  
private:
  BaseMenuItem ** items;
  int count;

public:
  SubMenuItem(const char * name, BaseMenuItem ** items, int count);
  int GetCount();
  BaseMenuItem * GetItem(int index);
  int GetType();
};

// ActionMenuItem -------------------------------------------------------------

class ActionMenuItem : public BaseMenuItem
{
private: 
  int actionId;

public:
  ActionMenuItem(const char * name, int actionId);
  int GetActionId();
  int GetType();
};

// MainMenu -------------------------------------------------------------------

class MainMenu : public SubMenuItem
{
private:
  SubMenuItem ** itemStack; 
  short * positionStack;
  unsigned char stackTop;
  short selectedItem;
  short firstItem;
  Display & display;
  MenuItem * editedItem;

  void ShowBack(int row, bool isSelected);
  
public:
  MainMenu(Display & display, BaseMenuItem ** items, int count);
  ~MainMenu();
  int Action(int keyAction);
  void Show();
};

#endif

Menu.cpp:

#include "Menu.h"

char * NewEmptyLine(int width)
{
  char * line = new char[width + 1];
    
  memset(line, 32, width);
  line[width] = 0;
  return line;  
}

void DeleteLine(char * line)
{
  delete line;
}

// BaseMenuItem ---------------------------------------------------------------

void BaseMenuItem::ShowListItem(Display & display, int row, bool selected)
{
  char * line = NewEmptyLine(display.cols);
  
  if (selected)
    line[0] = SELECTION_CHAR;

  strncpy(line + 2, this->name, min(display.cols - 2, this->len));
  display.Print(0, row, line);

  DeleteLine(line);
}

BaseMenuItem::BaseMenuItem(const char * name)
{
  this->name = name;
  this->len = strlen(this->name);
}

const char * BaseMenuItem::GetName()
{
  return this->name;
}

// MenuItem -------------------------------------------------------------------

void MenuItem::ShowEditHeader(Display & display)
{
  char * line = NewEmptyLine(display.cols);

  strncpy(line, this->name, min(display.cols, this->len));
  display.Print(0, 0, line);  

  DeleteLine(line);
}

void MenuItem::ShowEditMode(Display & display)
{
  this->ShowEditHeader(display);
  this->ShowEditValue(display);
}

MenuItem::MenuItem(const char * name) : BaseMenuItem(name)
{  

}

// IntMenuItem ----------------------------------------------------------------

IntMenuItem::IntMenuItem(const char * name, int min, int max, int defaultValue)
  : MenuItem(name)
{
  this->min = min;
  this->max = max;
  this->value = defaultValue < this->min ? this->min : (defaultValue > this->max ? this->max : defaultValue);
}

void IntMenuItem::ShowEditValue(Display & display)
{
  char * line = NewEmptyLine(display.cols);
  
  line[0] = 127;
  line[display.cols - 1] = 126;

  int i = display.cols - 3;
  int temp = abs(this->value);
  do
  {
    line[i] = temp % 10 + 48;
    temp /= 10;
    i--;
  }
  while (temp > 0);

  if (this->value < 0)
    line[i] = '-';

  display.Print(0, 1, line);

  DeleteLine(line);
}

int IntMenuItem::GetType() 
{
  return MENU_TYPE_INT;  
}

void IntMenuItem::HandleAction(int key)
{
  if (key == MENU_ACTION_LEFT)
  {
    if (this->value - 1 >= this->min)
      this->value--;
  }
  else if (key == MENU_ACTION_RIGHT)
  {
    if (this->value + 1 <= this->max)
      this->value++;
  }
}

// BoolMenuItem ---------------------------------------------------------------

BoolMenuItem::BoolMenuItem(const char * name, bool defaultValue)
  : MenuItem(name)
{
  value = defaultValue;    
}

void BoolMenuItem::ShowEditValue(Display & display)
{
  char * line = NewEmptyLine(display.cols);
  
  line[0] = 127;
  line[display.cols - 1] = 126;

  if (this->value)
  {
    strncpy(line + display.cols - 4, "On", 2);    
  }
  else
  {
    strncpy(line + display.cols - 5, "Off", 3);
  }

  display.Print(0, 1, line);

  DeleteLine(line);
}

int BoolMenuItem::GetType() 
{
  return MENU_TYPE_BOOL;
}

void BoolMenuItem::HandleAction(int key)
{
  if (key == MENU_ACTION_LEFT || key == MENU_ACTION_RIGHT) 
  {
    value = !value;
  }
}

// SubMenuItem ----------------------------------------------------------------

SubMenuItem::SubMenuItem(const char * name, BaseMenuItem ** items, int count) : BaseMenuItem(name)
{
  this->items = items;
  this->count = count;
}

int SubMenuItem::GetCount()
{
  return this->count;
}

BaseMenuItem * SubMenuItem::GetItem(int index)
{
  return this->items[index];
}

int SubMenuItem::GetType() 
{
  return MENU_TYPE_SUB;
}

// ActionMenuItem -------------------------------------------------------------

ActionMenuItem::ActionMenuItem(const char * name, int actionId) : BaseMenuItem(name)
{
  this->actionId = actionId;
}

int ActionMenuItem::GetActionId()
{
  return this->actionId;
}

int ActionMenuItem::GetType() 
{
  return MENU_TYPE_ACTION;  
}

// MainMenu -------------------------------------------------------------------

void MainMenu::ShowBack(int row, bool isSelected)
{
  char * line = NewEmptyLine(display.cols);
  if (isSelected)
  {
    line[0] = SELECTION_CHAR;  
  }
  strncpy(line + 2, TEXT_BACK, TEXT_BACK_LEN);
  display.Print(0, row, line);

  DeleteLine(line);
}

void MainMenu::Show()
{  
  if (editedItem == NULL)
  {
    bool isSubMenu = stackTop > 0;

    for (int i = 0; i < display.rows; i++)
    {
      if (isSubMenu && i + firstItem == itemStack[stackTop]->GetCount())
      {
        ShowBack(i, i + firstItem == selectedItem);
      }
      else
      {
        itemStack[stackTop]->GetItem(i + firstItem)->ShowListItem(display, i, i + firstItem == selectedItem);
      }
    }
  }
  else
  {
    editedItem->ShowEditMode(display);
  }
}

MainMenu::MainMenu(Display & display, BaseMenuItem ** items, int count) : 
  SubMenuItem(NULL, items, count),
  display(display)
{
  // Assuming initially 5 levels of menu max
  itemStack = new SubMenuItem * [MAX_MENU_LEVELS];
  positionStack = new short[MAX_MENU_LEVELS * 2];
  stackTop = 0;
  itemStack[stackTop] = this;
}

MainMenu::~MainMenu()
{
  delete itemStack;
  delete positionStack;
}

int MainMenu::Action(int keyAction)
{  
  int lastItemId = stackTop > 0 ? itemStack[stackTop]->GetCount() : itemStack[stackTop]->GetCount() - 1;

  if (editedItem == NULL)
  {
    if (keyAction == MENU_ACTION_UP || keyAction == MENU_ACTION_DOWN) 
    {      
      if (keyAction == MENU_ACTION_UP)
        this->selectedItem--;
      else
        this->selectedItem++;
          
      if (selectedItem < 0)
        selectedItem = lastItemId;      
      if (selectedItem > lastItemId)
        selectedItem = 0;    
  
      // Adjust items in view
      
      while (firstItem > selectedItem)
        firstItem--;
      while (firstItem + display.rows - 1 < selectedItem)
        firstItem++;
  
      Show();
      return -1;
    }
    else if (keyAction == MENU_ACTION_SELECT) 
    {
      if (selectedItem == lastItemId && stackTop > 0) 
      {
        // Chosen Back
        
        stackTop--;
        selectedItem = positionStack[stackTop * 2];
        firstItem = positionStack[stackTop * 2 + 1];
  
        Serial.print("Restored sel/first: ");
        Serial.print(selectedItem);
        Serial.print("/");
        Serial.print(firstItem);
        Serial.print("\n");
  
        Show();
      }
      else
      {
        int type = itemStack[stackTop]->GetItem(selectedItem)->GetType();
        switch (type)
        {
          case MENU_TYPE_INT:
          {
            editedItem = (MenuItem *)itemStack[stackTop]->GetItem(selectedItem);
            Show();
            break;
          }
          case MENU_TYPE_BOOL:
          {
            editedItem = (MenuItem *)itemStack[stackTop]->GetItem(selectedItem);
            Show();
            break;
          }
          case MENU_TYPE_SUB:
          {
            Serial.print("Entering ");
            Serial.print(((SubMenuItem *)(itemStack[stackTop]->GetItem(selectedItem)))->GetName());
            Serial.print("\n");
            
            if (stackTop >= MAX_MENU_LEVELS - 1)
              return -1;
    
            Serial.print("Storing sel/first: ");
            Serial.print(selectedItem);
            Serial.print("/");
            Serial.print(firstItem);
            Serial.print("\n");
  
            positionStack[stackTop * 2] = selectedItem;
            positionStack[stackTop * 2 + 1] = firstItem; 
  
            itemStack[stackTop + 1] = ((SubMenuItem *)(itemStack[stackTop]->GetItem(selectedItem)));
            stackTop++;
            selectedItem = 0;
            firstItem = 0;
  
            Show();
            
            break;
          }
          case MENU_TYPE_ACTION:
          {
            int actionId = ((ActionMenuItem *)(itemStack[stackTop]->GetItem(selectedItem)))->GetActionId();
            return actionId;
          }
        }
      }
    }
  }
  else
  {
    if (keyAction == MENU_ACTION_LEFT || keyAction == MENU_ACTION_RIGHT || keyAction == MENU_ACTION_UP || keyAction == MENU_ACTION_DOWN) 
    {
      Serial.print("Item action\n");
      editedItem->HandleAction(keyAction);  
      Show();
    }
    else if (keyAction == MENU_ACTION_SELECT)
    {
      editedItem = NULL;
      Show();
    }
  }
  return -1;
}
Spook
  • 195
  • 1
  • 8