From Swordfish Wiki

SwordfishUser: LCDMenuModule

A module for displaying a hierarchical menu system on a character LCD.

The module handles scrolling the menus and navigating up and down the menu levels. It triggers an event in the main program when the user selects a menu item which requires an action other then opening another menu. All the menu data is stored in Const arrays so the module uses very little RAM. It works with LCD displays from 1 to many lines and has various options to control how the menus are displayed.

To accompany the module is a basic utility - Menu Builder - which allows you to design the menu structure using a tree-view and then generates all the Const array declarations for you.

Downloads

Download module description and instructions doc »

Download Menu module & updated LCD module »

Download example code »

Download instructions for running example code »

Download Menu Builder Utility »

Download Menu Builder Utility instructions »

Menu Module Sample Code

{
********************************************************************************
*  Name    : LCD Menu Module Sample Program                                    *
*  Author  : AndyO                                                             *
*  Notice  : Copyright (c) 2008 AndyO                                          *
*          : All Rights Reserved                                               *
*  Date    : 5 October 2008                                                    *
*  Version : 1.0                                                               *
*  Notes   : See separate doc - "Running the Sample Program.pdf"               *
*          :                                                                   *
********************************************************************************
}

'===============================================================================
'Device, Clock and Config directives
'-------------------------------------------------------------------------------

Device = 18F2520
Clock = 20

'Disable MCLR on Pin 1 (RE3)
Config MCLRE = OFF


'===============================================================================
'Module options & Includes
'-------------------------------------------------------------------------------

'---Menu module options
#define MenuDataModule = "SampleDataModule.bas"
#option Menu_ShowMenuTitle = True
#option Menu_ShowScrollIndicators = True
#option Menu_ScrollWrapping = False
#option Menu_LCDLines = 4         

'---LCD module options
#option LCD_DATA = PORTA.0
#option LCD_RS = PORTA.4
#option LCD_EN = PORTA.5

'---Includes
Include "LCD.bas"
Include "Utils.bas"
Include "Menu.bas"                  'Menu Module             
Include "SampleDataModule.bas"      'Menu Data Module


'===============================================================================
'Variable and Const Declarations
'-------------------------------------------------------------------------------

Dim UpButton As PORTB.4                      
Dim DownButton As PORTB.5           
Dim SelectButton As PORTB.6         

Dim LED1 As PORTB.0                 
Dim LED2 As PORTB.1                 

Dim UpButtonCount As Byte           '}Counters used for button debounce - 
Dim DownButtonCount As Byte         '}Counters track how long each button has
Dim SelectButtonCount As Byte       '}been pressed and
Dim NoButtonCount As Byte           '}how long no button has been pressed 

Const DebounceCount As Byte = 2     'Default debounce delay in units of 5ms


Dim LED1Delay As Byte               '}Delay between on/off toggle of LEDs in
Dim LED2Delay As Byte               '}units of 5mS

Dim LED1Count As Byte               '}Counters used to track time to next on/off
Dim LED2Count As Byte               '}toggle of LEDs

Dim DoFlash As Boolean              'True = flash LEDs, False = don't flash LEDs


'===============================================================================
'Interrupts and Events
'-------------------------------------------------------------------------------

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      Event - MenuItemSelected
'Purpose:   Event called by menu module whenever 'action' menu item is selected
'           Executes item action and then displays root menu
'
'Notes:     Variable 'Menu.SelectedMenuItem' holds index number of menu item.
'
'           Aliases to menu items used in Select...Case structure are declared
'           in the Menu Data Module - "SampleDataModule.bas".
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Event MenuItemSelected()

    Select Menu.SelectedMenuItem

    	Case mnuMainMenu_StartFlashing

            DoFlash = True

    	Case mnuMainMenu_StopFlashing

            DoFlash = False

    	Case mnuConfirmReset_Yes

            LCD.Cls
            LCD.WriteAt(1,1,"Resetting...")
            DelayMS(500)
            ASM
                reset
            End ASM

    	Case mnuLed1Frequency_1hz

            LED1Delay = 100

    	Case mnuLed1Frequency_2hz

            LED1Delay = 50

    	Case mnuLed1Frequency_10hz

            LED1Delay = 10

    	Case mnuLed2Frequency_1hz

            LED2Delay = 100    	

    	Case mnuLed2Frequency_2hz

            LED2Delay = 50

    	Case mnuLed2Frequency_10hz

            LED2Delay = 10

    EndSelect

    Menu.ShowMenu                           'Show root menu

End Event


'===============================================================================
'Main Program
'-------------------------------------------------------------------------------

'Turn off analogue functions
SetAllDigital

'Initialise vars
LED1Delay = 100                             '}Both LEDs set to 1Hz flash
LED2Delay = 100                             '}frequency (500mS between toggles)

LED1Count = 0
LED2Count = 0

UpButtonCount = 0
DownButtonCount = 0
SelectButtonCount = 0 
NoButtonCount = 0        

DoFlash = True


DelayMS(100)                                'Let things settle

Menu.Initialise(MenuItemSelected)           'Initialise Menu module and let it
                                            'know the name of the event handler

Menu.ShowMenu                               'Show the root menu

Low(LED1)                                   '}Make both LED pins outputs and 
Low(LED2)                                   '}turn off


While True                                  'Endless loop which executes every
    DelayMS(5)                              '5mS


    'Flash the LEDs
    If DoFlash = True Then                  'If the LEDs should be flashing:

        Inc(LED1Count)                      '}Increase both LED counters by 1
        Inc(LED2Count)                      '}

        If LED1Count >= LED1Delay Then      '}If LED1 count has reached the delay
                                            '}value:
            Toggle(LED1)                    '}Toggle the LED and 
            LED1Count = 0                   '}Reset counter to 0
                                            '}
        EndIf                               '}

        If LED2Count >= LED2Delay Then      '}If LED2 count has reached the delay
                                            '}value:
            Toggle(LED2)                    '}Toggle the LED and
            LED2Count = 0                   '}Reset counter to 0
                                            '}
        EndIf                               '}

    EndIf


    'Check and debounce buttons
    If NoButtonCount = DebounceCount Then   '}If no button has been pressed for
                                            '}10mS:
                                            '}
        If UpButton = 1 Then                '}Check each button and if a button
                                            '}is pressed then increase that
            Inc(UpButtonCount)              '}button's counter by 1
                                            '}
        ElseIf DownButton = 1 Then          '}
                                            '}
            Inc(DownButtonCount)            '}
                                            '}
        ElseIf SelectButton = 1 Then        '}
                                            '}
            Inc(SelectButtonCount)          '}
                                            '}
        EndIf                               '}

    ElseIf (PORTB And %01110000) = 0 Then   '}If less than 10mS since the last
                                            '}button push and no button is being
        Inc(NoButtonCount)                  '}pressed then increase the
                                            '}No-button counter by 1
    EndIf                                   '}


    If UpButtonCount = DebounceCount Then   '}For each button, if the counter
                                            '}has reached 2 (i.e. the button has
        UpButtonCount = 0                   '}been pushed for 10mS) then:
        NoButtonCount = 0                   '}
        Menu.MoveUp                         '}Reset that button's counter,
                                            '}Reset the No-button counter, and 
    EndIf                                   '}Call the appropriate Menu sub -
                                            '}i.e. Up, Down or SelectItem
    If DownButtonCount = DebounceCount Then '}
                                            '}
        DownButtonCount = 0                 '}
        NoButtonCount = 0                   '}
        Menu.MoveDown                       '}
                                            '}
    EndIf                                   '}
                                            '}
    If SelectButtonCount = DebounceCount Then '}
                                            '}
        SelectButtonCount = 0               '}
        NoButtonCount = 0                   '}
        Menu.SelectItem                     '}
                                            '}
    EndIf                                   '}

Wend

Menu Module Sample Code Data

{
********************************************************************************
*  Name    : LCD Menu Sample Data Module                                      *
*  Author  : AndyO                                                             *
*  Notice  : Copyright (c) 2008 AndyO                                          *
*          : All Rights Reserved                                               *
*  Date    : 04/09/2008                                                        *
*  Version : 1.0                                                               *
*  Notes   : For use with LCD Menu Module sample program.                      *
*          :                                                                   *
*          : All Const declarations generated by Menu Builder utility.         *
*          :                                                                   *
********************************************************************************
}

Module SampleDataModule 


'===============================================================================
'Menu Header & Menu Item Data
'-------------------------------------------------------------------------------

Public Const mnuMenuName(5) As String = ("MAIN MENU", "FLASH SETTINGS", "CONFIRM RESET",
"LED1 Frequency", "LED2 Frequency")
Public Const mnuMenuItemStart(5) As Byte = (0, 4, 7, 9, 13)
Public Const mnuMenuItemEnd(5) As Byte = (3, 6, 8, 12, 16)
Public Const mnuParentMenuID(5) As Byte = (0, 0, 0, 1, 1)

Public Const mnuItemName(17) As String = ("Start Flashing",
"Stop Flashing", "Flash Settings", "Reset", "LED1 Frequency", "LED2 Frequency", "Back", "No",
"Yes", "1Hz", "2Hz", "10Hz", "Back", "1Hz", "2Hz", "10Hz", "Back")
Public Const mnuItemAction(17) As Byte = (255, 255, 1, 2, 3, 4, 0, 0, 255,
255, 255, 255, 1, 255, 255, 255, 1)


'===============================================================================
'Menu Item Aliases - used by Select Case structure in main program
'-------------------------------------------------------------------------------

Public Const mnuMainMenu_StartFlashing = 0
Public Const mnuMainMenu_StopFlashing = 1
Public Const mnuConfirmReset_Yes = 8
Public Const mnuLed1Frequency_1hz = 9
Public Const mnuLed1Frequency_2hz = 10
Public Const mnuLed1Frequency_10hz = 11
Public Const mnuLed2Frequency_1hz = 13
Public Const mnuLed2Frequency_2hz = 14
Public Const mnuLed2Frequency_10hz = 15

Menu Module

{
********************************************************************************
*  Name    : LCD Menu Module                                                   *
*  Author  : AndyO                                                             *
*  Notice  : Copyright (c) 2008 AndyO                                          *
*          : All Rights Reserved                                               *
*  Date    : 13/07/2008                                                        *
*  Version : 1.0                                                               *
*  Notes   : See doc "LCD Menu Module.pdf"                                     *
*          :                                                                   *
*          : Menu data stored in Const arrays which are declared in seperate   *
*          : module indicated by #define MenuDataModule = "filename.BAS" in    *
*          : main program                                                      *
*          :                                                                   *
*          : Requires modified LCD.bas module.  Following sub should be placed *
*          : after sub "WriteAt":                                              *
*                                                                              *
*   -------------------------------------------------------------------------  *
    Public Sub WriteConstStringAt(pY, pX As Byte, ByRefConst pText As String)  

        SetLocationY(pY)                                                       
        SetLocationX(pX)                                                       
        MoveTo()                                                               

        EECON1 = 0
        EECON1.7 = 1

        TABLEPTR = @pText

        ASM
            TBLRD*+
        End ASM

        While TABLAT <> 0

            WriteItem(TABLAT)

            ASM
                TBLRD*+
            End ASM

        Wend

    End Sub
*   -------------------------------------------------------------------------  *
*                                                                              *
*                                                                              *
********************************************************************************
}

Module Menu 

Include "LCD.bas"

#ifdef MenuDataModule
    Include MenuDataModule
#else
    #error "No MenuDataModule defined in main program"
#endif

'===============================================================================
'User definable options
'-------------------------------------------------------------------------------

#option Menu_LCDLines = 4                   'Number of lines on LCD display

#option Menu_ShowMenuTitle = True           'Display Menu Name at top of screen?
#option Menu_ShowScrollIndicators = True    'Show up and down scroll indicators?
#option Menu_ScrollWrapping = False         'Scroll wraps from top to bottom?

#option Menu_PointerCharacter = 126         'Character used as pointer


#if Menu_LCDLines = 1 And Menu_ShowMenuTitle = True

    #error "Cannot show menu titles on 1 line LCD"

#endif

'===============================================================================
'Variable and Const Declarations
'-------------------------------------------------------------------------------

'---Types

Type TEvent = Event()


'---Bring options into module

Private Const
    LCDLines As Byte = Menu_LCDLines,
    PointerCharacter As Byte = Menu_PointerCharacter


'---Custom Characters (only loaded if option Menu_ShowScrollIndicators = True)

#If Menu_ShowScrollIndicators = True

    'Character definitions for Up Arrow and Down Arrow scroll indicators
    Private Const ScrollIndicators(16) As Byte=($04,$0E,$1F,$00,$00,$00,$00,$00,
                                                $00,$00,$00,$00,$00,$1F,$0E,$04)

    Private Const UpArrow = 0
    Private Const DownArrow = 1

#EndIf


'---Private Variables

Private Dim FItemActionEvent As TEvent  'Holds pointer to event code in main
                                        'program which is run when an "action"
                                        'menu item is selected

Private Dim PointerPosition As Byte     'Position of pointer character (0 = 1st) 
Private Dim PointerLine As Byte         'Actual LCD line of pointer        
Private Dim ScrollPosition As Byte      'Number of lines the menu is scrolled by

Private Dim CurrentMenu As Byte         'Index number of current menu
Private Dim ParentID As Byte            'Index number of parent of current menu
Private Dim FirstItemNumber As Byte     'First item number for current menu
Private Dim LastItemNumber As Byte      'Last item number for current menu


'---Public Variables

Public Dim SelectedMenuItem As Byte     'Index number of selected menu item

'===============================================================================
'Helper Subs and Functions (Private - can't be called from main program)
'-------------------------------------------------------------------------------

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      DrawMenu(pMenuNumber as byte)      
'Purpose:   Draws current menu, scrolled by ScrollPosition amount
'                 
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub DrawMenu()

Dim LineNumber As Byte                      'Line number of LCD to write at
Dim ItemNumber As Byte                      'Index of menu item number to write

#If Menu_ScrollWrapping = True              '}
                                            '}If scroll wrapping enabled then
    Dim ItemsDrawn As Byte                  '}also need to keep track of number
                                            '}of menu items drawn on LCD
    ItemsDrawn = 0                          '}
                                            '}
#EndIf

    LCD.Cls

    LineNumber = 1
    ItemNumber = FirstItemNumber + ScrollPosition

    #If Menu_ShowMenuTitle = True           'If option enabled, show Menu Title

        LCD.WriteConstStringAt(1, 1, mnuMenuName(CurrentMenu))
        LineNumber = 2

    #Endif


    'Loop which keeps writing menu items until the following conditions exist -
    '
    '   Scroll Wrapping Enabled: write menu items (wrapping from last menu item
    '   back to first menu item as necessary) until either the number of LCD
    '   lines has been reached or all menu items have been written (ItemsDrawn =
    '   LastItemNumber - FirstItemNumber)
    '
    '   Scroll Wrapping Not Enabled: write items until the number of LCD lines
    '   has been reached or the last menu item has been written

    Repeat

        LCD.WriteConstStringAt(LineNumber, 2, mnuItemName(ItemNumber))

        Inc(LineNumber)
        Inc(ItemNumber)

    #If Menu_ScrollWrapping = True

        Inc(ItemsDrawn)

        If ItemNumber > LastItemNumber Then '}
                                            '}Scroll wrapping enabled so wrap
            ItemNumber = FirstItemNumber    '}from last menu item to the first
                                            '}menu item
        EndIf                               '}

    Until LineNumber > LCDLines Or ItemsDrawn > LastItemNumber - FirstItemNumber

    #Else

    Until LineNumber > LCDLines Or ItemNumber > LastItemNumber

    #EndIf


    'If Menu_ShowScrollIndicators option enabled then show scroll indicators
    'depending on the following conditions -
    '
    '   Scroll Wrapping Enabled: Show both Up and Down arrows if the number of
    '   menu items exceeds the number of available LCD lines
    '
    '   Scroll Wrapping Not Enabled: Show Up arrow if menu can be scrolled up,
    '   Down arrow if there are more menu items to display
    '
    'Note: Number of available LCD lines and position of Up arrow depends on
    '      whether Menu_ShowMenuTitle is enabled or not

    #If Menu_ShowScrollIndicators = True

        #If Menu_ScrollWrapping = True

            #If Menu_ShowMenuTitle = True

                If LastItemNumber - FirstItemNumber > LCDLines - 1 Then

                    LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(2, 1, UpArrow)

            #Else

                If LastItemNumber - FirstItemNumber > LCDLines Then

                    LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(1, 1, UpArrow)

            #EndIf

                EndIf

        #Else

            If ItemNumber - 1 < LastItemNumber Then

                LCD.WriteAt(LCDLines, 1, DownArrow)

            EndIf


            If ScrollPosition > 0 Then

                #If Menu_ShowMenuTitle = True

                    LCD.WriteAt(2, 1, UpArrow)

                #Else

                    LCD.WriteAt(1, 1, UpArrow)

                #EndIf

            EndIf

        #EndIf

    #EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      DrawPointer(pOldPosition as byte)      
'Purpose:   Deletes pointer at pOldPosition and draws pointer at PointerPosition
'
'Notes:     When pointer is at first menu item on screen, PointerPosition = 0
'
'           Pointer character determined by option "Menu_PointerCharacter",
'           default = 126 (right arrow)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub DrawPointer(pOldPosition As Byte)

    'Actual LCD line which pointer is drawn on depends on whether
    'Menu_ShowMenuTitle is enabled or not 

    #If Menu_ShowMenuTitle Then

        PointerLine = PointerPosition + 2

        LCD.WriteAt(pOldPosition + 2, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)

    #Else

        PointerLine = PointerPosition + 1

        LCD.WriteAt(pOldPosition + 1, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)

    #EndIf

End Sub

'===============================================================================
'Public Subs and Functions
'-------------------------------------------------------------------------------

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      ShowMenu(pMenuNumber as byte = 0)      
'Purpose:   Display specified menu on LCD
'         
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub ShowMenu(pMenuNumber As Byte = 0)

    'Reset scroll and pointer positions
    ScrollPosition = 0
    PointerPosition = 0

    'Set values for first item number, last item number & parentID for current
    'menu
    CurrentMenu = pMenuNumber
    ParentID = mnuParentMenuID(CurrentMenu)
    FirstItemNumber = mnuMenuItemStart(CurrentMenu)
    LastItemNumber = mnuMenuItemEnd(CurrentMenu)

    'Display menu & pointer
    DrawMenu
    DrawPointer(0)

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      MoveDown()      
'Purpose:   Move pointer to next menu item, scrolling the menu if necessary
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub MoveDown()

Dim CanScroll As Boolean

    'First, determine if the menu can be scrolled down and set CanScroll -
    '
    '   If Scroll Wrapping Not Enabled: Menu may only be scrolled down if the
    '   number of menu items left to display won't fit on the number of
    '   available LCD lines
    '
    '   Note: Number of available lines depends on whether Menu_ShowMenuTitle is
    '   enabled or not
    '
    '   If Scroll Wrapping Enabled: Can always scroll down

    #If Menu_ScrollWrapping = False

        CanScroll = False

        #If Menu_ShowMenuTitle = True Then

            If FirstItemNumber + ScrollPosition + LCDLines - 2 < LastItemNumber Then

                CanScroll = True

            EndIf

        #Else

            If FirstItemNumber + ScrollPosition + LCDLines - 1 < LastItemNumber Then

                CanScroll = True

            EndIf

        #EndIf

    #Else

        CanScroll = True

    #EndIf


    'Scroll the menu if the following conditions exist -
    '
    '   Menu can be scrolled down and pointer is on or below the second last
    '   line of the display
    '   OR
    '   Menu can be scrolled down and pointer is on the last menu item - this
    '   allows scroll wrapping

    If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And
       CanScroll = True Then

        Inc(ScrollPosition)

        #If Menu_ScrollWrapping = True

        'If scroll wrapping and we've scrolled to the bottom then start again at
        'the top

            If ScrollPosition > (LastItemNumber - FirstItemNumber) Then

                ScrollPosition = 0

            EndIf

        #EndIf

        DrawMenu()
        DrawPointer(PointerPosition)


    'If not scrolling the menu then move the pointer down if the following
    'conditions exist -
    '
    '   Pointer is not on the last line of the display
    '   AND
    '   Pointer isn't on the last menu item

    ElseIf PointerLine < LCDLines And
       PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then

        Inc(PointerPosition)

        'Fix to ensure Up Arrow scroll indicator is shown on smaller displays
        'when pointer moves down from top line 
        #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And
           Menu_ShowScrollIndicators = True

            DrawMenu()
            DrawPointer(PointerPosition)

        #Else

            DrawPointer(PointerPosition - 1)

        #EndIf

    End If    

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      MoveUp()      
'Purpose:   Move pointer to previous menu item, scrolling menu if necessary
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub MoveUp()

    'Routine decides whether to scroll the menu up or just move the pointer up
    'depending on the current state:
    '
    'If there is a free LCD line above the pointer (i.e. one that doesn't have
    'the menu title or a scroll indicator in it then move the pointer up;
    '
    'Otherwise, if the menu can be scrolled (either because scroll wrapping is
    'on or because the current scroll position > 0) then scroll the menu up;
    '
    'If the menu can't be scrolled then move the pointer up, overwriting any
    'scroll-up indicator.  (If necessary, re-draw the scroll-down indicator).

    #If Menu_ShowMenuTitle = True

        #If Menu_ShowScrollIndicators = True

            If PointerLine > 3 Then

        #Else

            If PointerLine > 2 Then

        #EndIf

                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)

        #If Menu_ScrollWrapping = True

            Else

                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf

                DrawMenu()
                DrawPointer(PointerPosition)

            EndIf

        #Else                

            ElseIf ScrollPosition > 0 Then

                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)


            ElseIf PointerLine > 2 Then

                Dec(PointerPosition)

                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True

                    DrawMenu()
                    DrawPointer(PointerPosition)

                #Else

                    DrawPointer(PointerPosition + 1)

                #EndIf

            EndIf

        #EndIf

    #Else

        #If Menu_ShowScrollIndicators = True

            If PointerLine > 2 Then

        #Else

            If PointerLine > 1 Then

        #EndIf

                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)


        #If Menu_ScrollWrapping = True

            Else

                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf

                DrawMenu()
                DrawPointer(PointerPosition)

            EndIf

        #Else                        

            ElseIf ScrollPosition > 0 Then

                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)


            ElseIf PointerLine > 1 Then

                Dec(PointerPosition)

                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True

                    DrawMenu()
                    DrawPointer(PointerPosition)

                #Else

                    DrawPointer(PointerPosition + 1)

                #EndIf

            EndIf

        #EndIf

    #EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      Back()      
'Purpose:   Show parent of current menu
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub Back()

    ShowMenu(ParentID)

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      SelectItem()      
'Purpose:   Carries out action of selected menu item.  If menu item opens
'           another menu then that menu is drawn.  If menu item is an 'action'
'           item then the event in the main program which handles the action is
'           called
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub SelectItem()

Dim ItemAction As Byte

    SelectedMenuItem = FirstItemNumber + ScrollPosition + PointerPosition

    #If Menu_ScrollWrapping = True

        If SelectedMenuItem > LastItemNumber Then

            SelectedMenuItem = SelectedMenuItem - (LastItemNumber - FirstItemNumber) - 1

        EndIf

    #EndIf

    ItemAction = mnuItemAction(SelectedMenuItem)


    If ItemAction = 255 Then

        ParentID = CurrentMenu 'Means 'back' command will return to correct menu
        FItemActionEvent()     'Call event handler in main program

    Else

        ShowMenu(ItemAction)    

    EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      Initialise(pItemActionEvent as TEvent)      
'Purpose:   Sets name of main program event handler and loads custom scroll
'           characters into LCD
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub Initialise(pItemActionEvent As TEvent)

    FItemActionEvent = pItemActionEvent

    #If Menu_ShowScrollIndicators = True Then

        LCD.Write(ScrollIndicators)

    #EndIf

End Sub

'===============================================================================
'Module Initialisation
'-------------------------------------------------------------------------------

Updated LCD Module

{
****************************************************************
*  Name    : LCD.BAS - Modified                                *
*  Author  : David John Barker - modified by AndyO             *
*  Notice  : Copyright (c) 2006 Mecanique                      *
*          : All Rights Reserved                               *
*  Date    : 17/07/2008                                        *
*  Version : 1.2 - Added "WriteConstStringAt" function for     *
*            sending String Const Array to LCD without using   *
*            RAM - uses ByRefConst method of parameter passing *
*          : 1.1 - Implements optional RW pin for testing the  *
*            device busy flag, rather than using fixed delays  *
*          : 1.0 - Release                                     *
*  Notes   : Supports Hitachi HD44780 LCD controller           *
****************************************************************
}
Module LCD

Include "system.bas"
Include "utils.bas"  

// by default, the module assumes RW is not used and tied to ground - if the
// user specifies a RW pin, then data read code is linked in...
#if IsOption(LCD_RW)
   #define _RW_USED
   #if Not IsValidPortPin(LCD_RW) 
      #error LCD_RW, "Invalid option. RW must be a valid port pin."
   #endif   
#endif

// user has specified a data port, check to see if it is 8
// bit or 4 bit interface...
#if IsOption(LCD_DATA)
   #ifndef LCD_DATA@
      #define _LCD_INTERFACE_08
   #endif   
#endif

// default module option - user options can override these values...
#option LCD_DATA = PORTB.4
#option LCD_RS = PORTB.3
#option LCD_EN = PORTB.2
#option LCD_COMMAND_US = 2000 
#option LCD_DATA_US = 50 
#option LCD_INIT_DELAY = 100

// validate data port...
#if IsOption(LCD_DATA) 
   #if Not IsValidPort(LCD_DATA)
      #error LCD_DATA, "Invalid option. DATA must be a valid port name."
   #endif

   // has the user specified a DATA port bit? If so, then
   // we need to validate and set for 4 bit interface...
   #ifdef LCD_DATA@ Then
      #if Not (LCD_DATA@ in (0,4))
         #error LCD_DATA@, "Invalid option. DATA pin must be 0 or 4."      
      #endif
      #if LCD_DATA@ = 4
         #define _LCD_UPPER
      #endif    
   #endif 
#endif  

// create TRIS, based on the DATA port option...
#option _LCD_DATA_TRIS = GetTRIS(LCD_DATA)     

// validate RS pin...
#if IsOption(LCD_RS) And Not IsValidPortPin(LCD_RS) 
   #error LCD_RS, "Invalid option. RS must be a valid port pin."
#endif

// validate EN pin...
#if IsOption(LCD_EN) And Not IsValidPortPin(LCD_EN) 
   #error LCD_EN, "Invalid option. EN must be a valid port pin."
#endif

// validate command delay...
#if IsOption(LCD_COMMAND_US) And Not (LCD_COMMAND_US in (1 to 65535))
   #error LCD_COMMAND_US, "Invalid option. Command delay must be between 1 and 65535 us."
#endif

// validate data delay...
#if IsOption(LCD_DATA_US) And Not (LCD_DATA_US in (1 to 255))
   #error LCD_DATA_US, "Invalid option. Data delay must be between 1 and 255 us."
#endif

// validate initialisation delay...
#if IsOption(LCD_INIT_DELAY)
   #if Not (LCD_INIT_DELAY in (0 to 1000))
      #error LCD_INIT_DELAY, "Invalid option. LCD initialize delay must be between 0 and 1000 (ms)."
   #endif
#endif 

// public command constants...
Public Const
   cmdCGRAM           = %01000000,
   cmdDDRAM           = %10000000,
   cmdClear           = %00000001,
   cmdHome            = %00000010,
   cmdCursorOff       = %00001100,
   cmdCursorOn        = %00001110,  
   cmdBlinkOn         = %00001101,
   cmdBlinkOff        = %00001100,
   cmdMoveLeft        = %00010000,
   cmdMoveRight       = %00010100

// bring delay options into the module...
Const
   DataUS = LCD_DATA_US,
   CommandUS = LCD_COMMAND_US,
   LCDDelay = LCD_INIT_DELAY  

// bring port and pin options into the module...
Dim
   Data As LCD_DATA,           // DATA port
   TRISData As _LCD_DATA_TRIS, // DATA port TRIS
   RS As LCD_RS.LCD_RS@,       // register select
   EN As LCD_EN.LCD_EN@,       // enable read
   FPosX, FPosY As Byte        // local x, y coordinates  

// RW specified...
#ifdef _RW_USED
Dim RW As LCD_RW.LCD_RW@
#endif
{
****************************************************************************
* Name    : StrobeEN (PRIVATE)                                             *
* Purpose : Strobe read enable                                             *
****************************************************************************
}
Inline Sub StrobeEN()
   EN = 1
ASM-
   Nop
End ASM
   EN = 0
End Sub
{
****************************************************************************
* Name    : MakeOutput (PRIVATE)                                           *
* Purpose : Make data pins output                                          *
****************************************************************************
}
#ifdef _RW_USED
Sub MakeOutput()
   #ifdef _LCD_INTERFACE_08
      TRISData = $00
   #else
      #ifdef _LCD_UPPER
      TRISData = TRISData And $0F
      #else
      TRISData = TRISData And $F0
      #endif
   #endif
End Sub
{
****************************************************************************
* Name    : MakeInput (PRIVATE)                                            *
* Purpose : Make data pins input                                           *
****************************************************************************
}
Sub MakeInput()
   #ifdef _LCD_INTERFACE_08
      TRISData = $FF
   #else
      #ifdef _LCD_UPPER
      TRISData = TRISData Or $F0
      #else
      TRISData = TRISData Or $0F
      #endif
   #endif
End Sub
{
****************************************************************************
* Name    : GetData (PRIVATE)                                              *
* Purpose : Read Data port value. For 4 bit interface, value is returned   *
*         : in LSNibble                                                    *
****************************************************************************
}
Function GetData() As Byte
   EN = 1
   ASM-
      bra $ + 2
      bra $ + 2
   End ASM
   result = Data
   #ifndef _LCD_INTERFACE_08
      #ifdef _LCD_UPPER 
      result = result >> 4
      #else
      result = result And $0F
      #endif
   #endif
   EN = 0 
End Function
{
****************************************************************************
* Name    : ReadByte (PRIVATE)                                             *
* Purpose : Read a data byte from the device                               *
****************************************************************************
}
Function ReadByte() As Byte
   MakeInput
   RS = 0
   RW = 1
   #ifdef _LCD_INTERFACE_08
      result = GetData
   #else   
      result = GetData << 4                 // high nibble
      result = result Or (GetData And $0F)  // low nibble
   #endif
   RW = 0
   MakeOutput
End Function
{
****************************************************************************
* Name    : WaitFor (PRIVATE)                                              *
* Purpose : Wait for device busy flag to clear                             *
****************************************************************************
}
Sub WaitFor()
   Repeat
   Until (ReadByte And $80) = 0
End Sub  
#endif // _RW_USED
{
****************************************************************************
* Name    : SetData (PRIVATE)                                              *
* Purpose : Sets DATA port value                                           *
****************************************************************************
}
Sub SetData(pData As Byte)
   #ifdef _LCD_INTERFACE_08
   Data = pData
   #else
      #ifdef _LCD_UPPER 
      Data = Data And $0F
      Data = Data Or (pData And $F0)
      #else
      Data = Data And $F0
      Data = Data Or (pData And $0F)
      #endif
   #endif
   StrobeEN
   ClrWDT
End Sub
{
****************************************************************************
* Name    : SetDDRAM (PRIVATE)                                             *
* Purpose : Set DDRAM address                                              *
****************************************************************************
}
Sub SetDDRAM(pAddr As Byte)
   #ifdef _RW_USED
   WaitFor
   #endif

   RS = 0
   #ifdef _LCD_INTERFACE_08 
   SetData(pAddr Or %10000000)
   #else
      #ifdef _LCD_UPPER 
      SetData(pAddr Or %10000000)
      SetData(pAddr << 4)
      #else
      SetData((pAddr Or %10000000) >> 4)
      SetData(pAddr)
      #endif
   #endif  

   #ifndef _RW_USED
   DelayUS(DataUS) 
   #endif
   ClrWDT
End Sub
{
****************************************************************************
* Name    : Command                                                        *
* Purpose : Write command to LCD                                           *
****************************************************************************
}
Public Sub Command(pCommand As Byte)
   #ifdef _RW_USED
   WaitFor
   #endif

   RS = 0
   #ifdef _LCD_INTERFACE_08 
   SetData(pCommand)
   #else
      #ifdef _LCD_UPPER 
      SetData(pCommand)
      SetData(pCommand << 4)
      #else
      SetData(pCommand >> 4)
      SetData(pCommand)
      #endif
   #endif   

   #ifndef _RW_USED
   DelayUS(CommandUS)
   #endif
   ClrWDT
End Sub
{
****************************************************************************
* Name    : MoveCursor                                                     *
* Purpose : Move the cursor to line and column                             *
****************************************************************************
}
Public Sub MoveCursor(pLine, pCol As Byte)
   Dec(pCol)
   Select pLine
      Case 1 : SetDDRAM(pCol)
      Case 2 : SetDDRAM($40 + pCol)
      Case 3 : SetDDRAM($14 + pCol)
      Case 4 : SetDDRAM($54 + pCol)
   End Select
End Sub
{
****************************************************************************
* Name    : WriteItem (OVERLOAD)                                           *
* Purpose : Write a single byte to the LCD                                 *
****************************************************************************
}
Sub WriteItem(pData As Byte)
   #ifdef _RW_USED
   WaitFor
   #endif

   RS = 1
   #ifdef _LCD_INTERFACE_08
   SetData(pData)
   #else
      #ifdef _LCD_UPPER 
      SetData(pData)
      SetData(pData << 4)
      #else
      SetData(pData >> 4)
      SetData(pData)
      #endif
   #endif   

   #ifndef _RW_USED
   DelayUS(DataUS) 
   #endif
   ClrWDT
End Sub
{
****************************************************************************
* Name    : WriteItem (OVERLOAD)                                           *
* Purpose : Write a string to the LCD                                      *
****************************************************************************
}
Sub WriteItem(pText As String)
   Dim TextPtr As POSTINC0
   Dim Text As INDF0
   FSR0 = AddressOf(pText)
   While Text <> 0
      WriteItem(TextPtr)
   Wend
End Sub
{
****************************************************************************
* Name    : WriteItem (OVERLOAD)                                           *
* Purpose : Initialise the CGRAM with constant data array. Eight           *
*         : programmable characters are available (0..7).                  *
****************************************************************************
}
Sub WriteItem(ByRefConst pBitmap() As Byte)
   Dim Index As Byte
   Command(cmdCGRAM)
   For Index = 0 To Bound(pBitmap)
      WriteItem(pBitmap(Index))
   Next
   Command(cmdDDRAM)
End Sub
{
****************************************************************************
* Name    : Write (COMPOUND)                                               *
* Purpose : Calls one or more WriteItem() subroutines                      *
****************************************************************************
}
Public Compound Sub Write(WriteItem)
{
****************************************************************************
* Name    : MoveTo (PRIVATE)                                               *
* Purpose : Moves cursor to module private x, y location                   *
****************************************************************************
}
Sub MoveTo()
   MoveCursor(FPosY, FPosX)
End Sub 
{
****************************************************************************
* Name    : SetLocationX (PRIVATE)                                         *
* Purpose : Set cursor x                                                   *
****************************************************************************
} 
Sub SetLocationX(pX As Byte)
   FPosX = pX
End Sub
{
****************************************************************************
* Name    : SetLocationY (PRIVATE)                                         *
* Purpose : Set cursor y                                                   *
****************************************************************************
} 
Sub SetLocationY(pY As Byte)
   FPosY = pY
End Sub
{
****************************************************************************
* Name    : WriteAt (COMPOUND)                                             *
* Purpose : Calls one or more WriteItem() subroutines at location x, y     *
****************************************************************************
}
Public Compound Sub WriteAt(SetLocationY, SetLocationX, MoveTo, WriteItem)  


{
****************************************************************************
* Name    : WriteConstStringAt(pY, pX As Byte,ByRefConst pText As String)  *
* Purpose : Writes a string stored in a Const Array                        *
****************************************************************************
}
Public Sub WriteConstStringAt(pY, pX As Byte, ByRefConst pText As String) 

    SetLocationY(pY) 
    SetLocationX(pX) 
    MoveTo() 

    EECON1 = 0
    EECON1.7 = 1

    TABLEPTR = @pText

    ASM
        TBLRD*+
    End ASM

    While TABLAT <> 0

        WriteItem(TABLAT)

        ASM
            TBLRD*+
        End ASM

    Wend

End Sub 


{
****************************************************************************
* Name    : Cls                                                            *
* Purpose : Clear the LCD screen                                           *
****************************************************************************
}
Public Sub Cls()
   Command(cmdClear)
   DelayMS(30)
End Sub
{
****************************************************************************
* Name    : DoInitSequence (PRIVATE)                                       *
* Purpose : Performs a initialization sequence                             *
****************************************************************************
}
Sub DoInitSequence(pValue As Byte)
   SetData(pValue)
   DelayMS(5)
   SetData(pValue)
   DelayUS(160)
   SetData(pValue)
   DelayUS(160)
End Sub 
{
****************************************************************************
* Name    : Initialize (PRIVATE)                                           *
* Purpose : Initialize the LCD                                             *
****************************************************************************
}
Sub Initialize()  
   // wait for LCD to stabilise...
   SetAllDigital
   DelayMS(LCDDelay)

   // read write...
   #ifdef _RW_USED
   Low(RW)
   #endif

   // set RS and EN to low output...
   Low(RS)
   Low(EN)
   DelayMS(15)  

   // initialise for 8 bit interface...
   #ifdef _LCD_INTERFACE_08 
      Data = $00
      TRISData = $00
      DoInitSequence($30)
      Command(%00111000) // DL = 1, N = 1, F = 0

   // initialise for 4 bit interface...
   #else
      #ifdef _LCD_UPPER
      Data = Data And $0F
      TRISData = TRISData And $0F
      DoInitSequence($30)
      SetData($20)
      DelayUS(160) 
      #else
      Data = Data And $F0
      TRISData = TRISData And $F0
      DoInitSequence($03)
      SetData($02) 
      DelayUS(160) 
      #endif
      Command(%00101000) // DL = 0, N = 1, F = 0
   #endif
   Command(%00001000)    // D = 0, C = 0, B = 0
   Command(%00000001)    // Clear
   Command(%00000110)    // ID = 1, S = 0
   Command(%00001100)    // D = 1, C = 0, B = 0
End Sub

// initialise the module...
Initialize
Retrieved from https://www.sfcompiler.co.uk/wiki/pmwiki.php?n=SwordfishUser.LCDMenuModule
Page last modified on October 11, 2008, at 06:46 PM