2014年2月10日 星期一

Arduino LCD 功能表(菜單)

Arduino 在軟體上的開發真的很方便,尤其是對於 C/C++ 開發者而言,更是如魚得水。在 Arduino 的世果裡,你可利用 Library方式來將一些功能進行封裝再進行發佈,使得其他開發者方便地使用。最近,我有一個 project 是要用到在 LCD 顯示器中,要有提供多個項目選項給使用者選取。但在範例和網上也找不到一個比較好和完善的Library,廠商給的 Library 祇提供最簡單的標準函式。 於是我便自己寫了這個 Library,希望可以幫到其他遇到同樣問題的開發者。

首先我註明以下的源碼百分之一百原著,如果想轉載,請列明出處。敬請尊重知識產權,多謝。

由於我購買了的 LCD 是用 I2C方式連接的,所以以下我假設大家的 LCD 也是用 I2C,大小為:4行 x 20字。整個 MENU Library是由四個 class 組成:LCD_MENU、MENU_ITEM、CONTENT 和 LINETEXT。我的簡單構想是每個 LCD_MENU功能表類都有一串 linked list的MENU_ITEM選項類物件,並且給 LCD_MENU的Items指著。而每個 MENU_ITEM物件的Main_Menu指著其所屬的功能表物件;Sub_Menu 指著其子功能表物件;Prev_Menu則指著上一層功能表物件(用以返回上一層功能表單);MENU_ITEM物件中的Selected就是使用者指著目前所選的項目;而MENU_ITEM物件中的Content_Page,就是指著該選項被 Enter 選取後,所顯示的內容物件( CONTENT )。內容物件裡有一串 linked list的LINETEXT字行類物件,並給CONTENT 物件中的p_Head指著開始位置。

//------------------------------------------------------------------------------
//檔案: LCD_MENU.h
//
//作者:Anthony Lee (Dephi.Ktop: skcc)
//
//日期:二零一四年二月一日
//
//功能:完整功能表(菜單)類封裝,提供卷頁瀏覽功能表。
//------------------------------------------------------------------------------
#ifndef LCD_MENU_h
#define LCD_MENU_h

#include "LiquidCrystal_I2C.h"

//如果你的LCD 是20字,請將 MAX_SCREEN_CHAR  設成 19 及 MAX_CHAR_BUFFER 設成 20

#define MAX_SCREEN_CHAR  19
#define MAX_CHAR_BUFFER  20

//如果你的LCD 是 4行,請將 MAX_ROW 設成 4
#define MAX_ROW 4

class LINETEXT
{
public:
int Line_Num;
char Line_Text[ MAX_CHAR_BUFFER ];
LINETEXT *p_Previous;
LINETEXT *p_Next;

public:
LINETEXT();
};

class CONTENT
{
public:
LINETEXT *p_Head;
  int LineCount;
 
  int n_select;
  LINETEXT *Selected;
 
public:
  CONTENT();
  void Add_Line( char *str_text );
  int Update_Line( int line_num, char * str_text );
  int Delete_Line( int line_num );
  void Clear_All();
 
  void init();
};

class MENU_ITEM
{
public:
  int Item_ID;
  char Item_Name[ MAX_CHAR_BUFFER ];

  void *Main_Menu;
  void *Prev_Menu;
  void *Sub_Menu;

  MENU_ITEM *Prev_Item;
  MENU_ITEM *Next_Item;
 
  CONTENT *Content_Page;
 
public:
  MENU_ITEM( int a_item_id, char *a_item_name, void* a_main_menu, void* a_prev_menu, void* a_sub_menu, CONTENT* a_contentpage );
 
}; //End of class MENU_ITEM

class LCD_MENU
{
public:
  char MenuTitle[ MAX_CHAR_BUFFER ];
  int ItemCount;
  MENU_ITEM *Items;
 
  MENU_ITEM *Selected;
  int n_select;
 
  CONTENT *Content_Page;

public:
  LCD_MENU( char *a_title  );
 
  void Add_Item( MENU_ITEM *a_item );
  void init();
 

  //----------- User reactions -------
  void Action_Up();
 
  void Action_Down();

  void *Action_Enter();

  void *Action_Back();

}; //End of class LCD_MENU

void CopyString(char *dest, char *src, int buffersize );
void LCD_Show( LiquidCrystal_I2C *screen, LCD_MENU *p_menu );
void Display_Menu( LiquidCrystal_I2C *screen, LCD_MENU *p_menu );
void Display_Content( LiquidCrystal_I2C *screen, CONTENT *p_content );
#endif


//------------------------------------------------------------------------------
//檔案: LCD_MENU.cpp
//
//作者:Anthony Lee (Dephi.Ktop: skcc)
//
//日期:二零一四年二月一日
//
//
// 如需轉載請列明出處,請尊重知識產權。
//
//------------------------------------------------------------------------------

#include "LCD_MENU.h"
 
//------------------------------------------------------------------------------
//------- String Tools -------
void CopyString(char *dest, char *src, int buffersize )
{
memset( dest, 0, buffersize);
int str_l = strlen( src );
if( str_l > buffersize )
memcpy( dest, src, buffersize-1 );
else
memcpy( dest, src, str_l );
}


//------------------------------------------------------------------------------
//------- Display Entry Function --------
void LCD_Show( LiquidCrystal_I2C *screen, LCD_MENU *p_menu )
{
screen->clear();

  if( p_menu->Content_Page != NULL )
  {
    if( p_menu->Content_Page->LineCount > 0 )
    {
  Display_Content( screen, p_menu->Content_Page );
  }
  else
   Display_Menu( screen, p_menu );
  }
else
 Display_Menu( screen, p_menu );
}

//------------------------------------------------------------------------------
//------- Display Menu Items --------
void Display_Menu( LiquidCrystal_I2C *screen, LCD_MENU *p_menu )
{
  char buffer[ MAX_CHAR_BUFFER ];

  screen->setCursor(0,0);
  memset( buffer, 0, MAX_CHAR_BUFFER);
  sprintf( buffer,"[ %s ]", p_menu->MenuTitle );
  screen->print( buffer );

  int n_end;
  int n_start;
  int n_page;  
  int row = 0;
 
  //Found the start item ans end item...
  MENU_ITEM *p_start_item;
  MENU_ITEM *p_end_item;
  p_start_item = p_menu->Items;
  p_end_item = p_menu->Items;
 
  if( p_menu->n_select < (MAX_ROW - 1) )
  {
    n_start = 0;
  }
  else
  {
    n_page =  p_menu->n_select / (MAX_ROW - 1);
    n_start = n_page * (MAX_ROW - 1);
  }

  if( (n_start + MAX_ROW - 1) > p_menu->ItemCount )
    n_end = p_menu->ItemCount;
  else
    n_end = n_start + MAX_ROW - 1;

  for( int i=0; i    p_start_item = p_start_item->Next_Item;
 
  for( int i=0; i    p_end_item = p_end_item->Next_Item;
 
  MENU_ITEM *p_item = p_start_item;
  while( p_item != p_end_item )
  {
    row = row + 1;
    if( p_menu->Selected == p_item )
    {
      screen->setCursor( 0, row );
      screen->print( ">" );          
    }
    screen->setCursor( 1, row );
    screen->print( p_item->Item_Name );
    p_item = p_item->Next_Item;
  }
 
}

//------------------------------------------------------------------------------
//------- Display Conent --------

void Display_Content( LiquidCrystal_I2C *screen, CONTENT *p_content )
{
  char buffer[ MAX_CHAR_BUFFER ];
  int n_end;
  int n_start;
  int n_page;
  int row = -1;
 
  if( p_content->n_select < (MAX_ROW) )
  {
    n_start = 0;
  }
  else
  {
    n_page =  p_content->n_select / (MAX_ROW);
    n_start = n_page * (MAX_ROW);
  }

  if( (n_start + MAX_ROW) > p_content->LineCount )
    n_end = p_content->LineCount;
  else
    n_end = n_start + MAX_ROW;
   
  LINETEXT *p_start_line;
  LINETEXT *p_end_line;
  p_start_line = p_content->p_Head;
  p_end_line = p_content->p_Head;
     
  for( int i=0; i    p_start_line = p_start_line->p_Next;
 
  for( int i=0; i    p_end_line = p_end_line->p_Next;  
   
  LINETEXT *p_line = p_start_line;
  while( p_line != p_end_line )
  {
    row = row + 1;
    if( p_content->Selected == p_line )
    {
      screen->setCursor( 0, row );
      screen->print( ">" );          
    }
    screen->setCursor( 1, row );
    screen->print( p_line->Line_Text );
    p_line = p_line->p_Next;
  }  
   
}

//------------------------------------------------------------------------------
LINETEXT::LINETEXT()
{
  memset( Line_Text, 0, MAX_CHAR_BUFFER);
  Line_Num = 0;
  p_Previous = NULL;
  p_Next = NULL;
}

//------------------------------------------------------------------------------
CONTENT::CONTENT()
{
  LineCount = 0;
  p_Head = NULL;
}

//------------------------------------------------------------------------------
void CONTENT::init()
{
  n_select = 0;
  Selected = p_Head;
}

//------------------------------------------------------------------------------
void CONTENT::Add_Line( char *str_text  )
{
  //Prepare the LINETEXT...
  LINETEXT *a_line = new LINETEXT();
  CopyString( a_line->Line_Text, str_text, MAX_CHAR_BUFFER - 1 );
 
  LINETEXT *p_Line = p_Head;
 
  if( p_Line != NULL )
  {
    while( p_Line->p_Next != NULL )
    {
      p_Line = p_Line->p_Next;
    }
    p_Line->p_Next = a_line;
    a_line->p_Previous = p_Line;
    a_line->p_Next = NULL;
  }
  else
  {
    p_Head = a_line;
    p_Head->p_Previous = NULL;
    p_Head->p_Next = NULL;
  }
   
  a_line->Line_Num = LineCount;
  LineCount++;
}

//------------------------------------------------------------------------------
int CONTENT::Update_Line( int line_num, char * a_text )
{
  //Locate the line....
  LINETEXT *p_Line = p_Head;
  while( p_Line != NULL )
  {
    if( p_Line->Line_Num == line_num )
    {
    CopyString( p_Line->Line_Text, a_text, MAX_CHAR_BUFFER );
    return( 1 );
    }
    p_Line = p_Line->p_Next;
  }
  return( -1 );
}
//------------------------------------------------------------------------------
int CONTENT::Delete_Line( int line_num )
{
  //Locate the line....
  LINETEXT *p_Line = p_Head;
  LINETEXT *p_PrevNode, *p_NextNode;
  int ln = 0;
 
  if( p_Line == NULL )
  {
 LineCount = 0;
 p_Head = NULL;
 init();    
  return(-1);
  }
   
  while( p_Line != NULL )
  {
    if( p_Line->Line_Num == line_num )
    {
      p_PrevNode = p_Line->p_Previous;
      p_NextNode = p_Line->p_Next;
    delete p_Line;
    p_PrevNode->p_Next = p_NextNode;
    p_NextNode->p_Previous = p_PrevNode;
   
    //Re-arrange the line num...
    p_Line = p_Head;
    while( p_Line != NULL )
    {
    p_Line->Line_Num = ln;
    ln++;
p_Line = p_Line->p_Next;
    }
    LineCount = ln;
    init();
    return( 1 );
   
    }
    p_Line = p_Line->p_Next;
  }
  return( -1 );
}

//------------------------------------------------------------------------------
void CONTENT::Clear_All()
{
  LINETEXT *p_PrevNode;
 
  //Get the rear line node...
  LINETEXT *p_RearLine = p_Head;
 
  if( p_RearLine == NULL )
  {
 LineCount = 0;
 p_Head = NULL;
 init();    
  return;
  }
 
  while( p_RearLine->p_Next != NULL )
  {
    p_RearLine = p_RearLine->p_Next;
  }
 
  while( p_RearLine->p_Previous != NULL )
  {
    p_PrevNode = p_RearLine->p_Previous;
    delete p_RearLine;
    p_PrevNode->p_Next = NULL;
    p_RearLine = p_PrevNode;
  }
  LineCount = 0;
  p_Head = NULL;
  init();  
}

//------------------------------------------------------------------------------
MENU_ITEM::MENU_ITEM( int a_item_id, char *a_item_name, void* a_main_menu, void* a_prev_menu, void* a_sub_menu, CONTENT* a_contentpage )
{
  Item_ID = a_item_id;
  CopyString( Item_Name, a_item_name, MAX_CHAR_BUFFER );
  Main_Menu = a_main_menu;
  Prev_Menu = a_prev_menu;
  Sub_Menu = a_sub_menu;

  Content_Page = a_contentpage;
 
  Prev_Item = NULL;
  Next_Item = NULL;
}

//------------------------------------------------------------------------------
LCD_MENU::LCD_MENU( char *a_title  )
{
  CopyString( MenuTitle, a_title, MAX_CHAR_BUFFER );
  Items = NULL;
  ItemCount = 0;
}

//------------------------------------------------------------------------------
void LCD_MENU::Add_Item( MENU_ITEM *a_item )
{
  MENU_ITEM *p_Item = Items;
 
  if( p_Item != NULL )
  {
    while( p_Item->Next_Item != NULL )
    {
      p_Item = p_Item->Next_Item;
    }
    p_Item->Next_Item = a_item;
    a_item->Prev_Item = p_Item;
  }
  else
  {
    Items = a_item;
    Items->Prev_Item = NULL;
  }
   
  a_item->Next_Item = NULL;
  ItemCount++;
}

//------------------------------------------------------------------------------
void LCD_MENU::init()
{
  n_select = 0;
  Selected = Items;
  Content_Page = NULL;
}

//------------------------------------------------------------------------------
  void LCD_MENU::Action_Up()
{
  MENU_ITEM *p_item = Items;
  if( Content_Page == NULL )
  {
 if( n_select > 0 )
 {
   n_select = n_select - 1;
   Selected = Selected->Prev_Item;
 }
  }
  else
  {
 if( Content_Page->n_select > 0 )
 {
   Content_Page->n_select = Content_Page->n_select - 1;
   Content_Page->Selected = Content_Page->Selected->p_Previous;
 }
  }
}

//------------------------------------------------------------------------------
void LCD_MENU::Action_Down()
{
  MENU_ITEM *p_item = Items;
  if( Content_Page == NULL )
  {
 if( n_select < (ItemCount - 1) )
 {
   n_select = n_select + 1;
   Selected = Selected->Next_Item;
 }
  }
  else
  {
 if( Content_Page->n_select < (Content_Page->LineCount - 1))
 {
   Content_Page->n_select = Content_Page->n_select + 1;
   Content_Page->Selected = Content_Page->Selected->p_Next;
 }
  }
}

//------------------------------------------------------------------------------
void *LCD_MENU::Action_Enter()
{
if( Selected->Content_Page != NULL )
{
 Content_Page = Selected->Content_Page;
 return( this );
}
  return( Selected->Sub_Menu );
}

//------------------------------------------------------------------------------
void *LCD_MENU::Action_Back()
{
void* ret_menu;
if( Content_Page != NULL )
{
 Content_Page->n_select = 0;
 Content_Page->Selected = Content_Page->p_Head;
Content_Page = NULL;
return( this );
}
else
{
    ret_menu = Selected->Prev_Menu;
   
    if( ret_menu == NULL )
    return( this );
    else
    {
   Selected = Items; //Reset the pointer at the first item;
   n_select = 0;
   return( ret_menu);
    }
  }
}
//------------------------------------------------------------------------------


以上就是我的這個 LCD_MENU 物件的源始碼…下回將講述如何使用…密切留意。

沒有留言:

張貼留言