LCDMenuModule

SwordfishUser.LCDMenuModule History

Hide minor edits - Show changes to markup

Changed lines 1-2 from:

http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png

to:

http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png 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.

Added lines 12-14:

Download module description and instructions doc »

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif

Deleted lines 17-19:

Download PDF instructions »

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif

Changed lines 21-22 from:

Download instructions for running sample code »

to:

Download instructions for running example code »

Added line 29:
Changed lines 1-2 from:

http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png

to:

http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png

Added lines 1-2:

http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png

Changed lines 6-20 from:
to:

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif Download PDF instructions »

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif Download example code »

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif Download instructions for running sample code »

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif Download Menu Builder Utility »

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif Download Menu Builder Utility instructions »

Added lines 1-6:

Downloads

http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif Download Menu module & updated LCD module »

Changed lines 696-697 from:
    If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And CanScroll = True Then
to:
    If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And
       CanScroll = True Then
Changed lines 282-283 from:

Public Const mnuMenuName(5) As String = ("MAIN MENU", "FLASH SETTINGS", "CONFIRM RESET", "LED1 Frequency", "LED2 Frequency")

to:

Public Const mnuMenuName(5) As String = ("MAIN MENU", "FLASH SETTINGS", "CONFIRM RESET", "LED1 Frequency", "LED2 Frequency")

Changed lines 289-293 from:

"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)

to:

"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)

Changed lines 287-289 from:

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")

to:

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")

Changed lines 722-723 from:
    ElseIf PointerLine < LCDLines And PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
to:
    ElseIf PointerLine < LCDLines And
       PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
Changed lines 729-730 from:
        #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And Menu_ShowScrollIndicators = True
to:
        #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And
           Menu_ShowScrollIndicators = True
Changed line 1 from:

Sample Code

to:

Menu Module Sample Code

Deleted lines 2-6:

=]

Menu Module

=code [=

Changed line 5 from:
  • Name : LCD Menu Module *
to:
  • Name : LCD Menu Module Sample Program *
Changed line 9 from:
  • Date : 13/07/2008 *
to:
  • Date : 5 October 2008 *
Changed line 11 from:
  • Notes : See doc "LCD Menu Module.pdf" *
to:
  • Notes : See separate doc - "Running the Sample Program.pdf" *
Deleted lines 12-48:
  • : 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
  • ------------------------------------------------------------------------- *
  • *
  • *
Deleted lines 15-24:

Module Menu

Include "LCD.bas"

  1. ifdef MenuDataModule
    Include MenuDataModule
  1. else
    #error "No MenuDataModule defined in main program"
  1. endif
Changed line 17 from:

'User definable options

to:

'Device, Clock and Config directives

Changed lines 20-34 from:
  1. option Menu_LCDLines = 4 'Number of lines on LCD display
  2. option Menu_ShowMenuTitle = True 'Display Menu Name at top of screen?
  3. option Menu_ShowScrollIndicators = True 'Show up and down scroll indicators?
  4. option Menu_ScrollWrapping = False 'Scroll wraps from top to bottom?
  5. option Menu_PointerCharacter = 126 'Character used as pointer
  6. if Menu_LCDLines = 1 And Menu_ShowMenuTitle = True
    #error "Cannot show menu titles on 1 line LCD"
  1. endif
to:

Device = 18F2520 Clock = 20

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

Changed line 28 from:

'Variable and Const Declarations

to:

'Module options & Includes

Changed lines 31-76 from:

'---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)

  1. 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
  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

to:

'---Menu module options

  1. define MenuDataModule = "SampleDataModule.bas"
  2. option Menu_ShowMenuTitle = True
  3. option Menu_ShowScrollIndicators = True
  4. option Menu_ScrollWrapping = False
  5. option Menu_LCDLines = 4

'---LCD module options

  1. option LCD_DATA = PORTA.0
  2. option LCD_RS = PORTA.4
  3. option LCD_EN = PORTA.5

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

Changed line 51 from:

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

to:

'Variable and Const Declarations

Added lines 54-81:

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 '-------------------------------------------------------------------------------

Changed lines 83-85 from:

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

to:

'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". '

Changed lines 94-107 from:

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

  1. 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                          '}
                                            '}
  1. EndIf
    LCD.Cls
to:

Event MenuItemSelected()

    Select Menu.SelectedMenuItem

    	Case mnuMainMenu_StartFlashing

            DoFlash = True
Changed lines 102-105 from:
    LineNumber = 1
    ItemNumber = FirstItemNumber + ScrollPosition

    #If Menu_ShowMenuTitle = True           'If option enabled, show Menu Title
to:
    	Case mnuMainMenu_StopFlashing

            DoFlash = False
Changed lines 106-109 from:
        LCD.WriteConstStringAt(1, 1, mnuMenuName(CurrentMenu))
        LineNumber = 2

    #Endif
to:
    	Case mnuConfirmReset_Yes

            LCD.Cls
            LCD.WriteAt(1,1,"Resetting...")
            DelayMS(500)
            ASM
                reset
            End ASM
Added lines 115-117:
    	Case mnuLed1Frequency_1hz

            LED1Delay = 100
Changed lines 119-127 from:
    '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
to:
    	Case mnuLed1Frequency_2hz

            LED1Delay = 50
Changed lines 123-132 from:
    Repeat

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

        Inc(LineNumber)
        Inc(ItemNumber)

    #If Menu_ScrollWrapping = True

        Inc(ItemsDrawn)
to:
    	Case mnuLed1Frequency_10hz
Changed lines 125-129 from:
        If ItemNumber > LastItemNumber Then '}
                                            '}Scroll wrapping enabled so wrap
            ItemNumber = FirstItemNumber    '}from last menu item to the first
                                            '}menu item
        EndIf                               '}
to:
            LED1Delay = 10
Changed lines 127-131 from:
    Until LineNumber > LCDLines Or ItemsDrawn > LastItemNumber - FirstItemNumber

    #Else

    Until LineNumber > LCDLines Or ItemNumber > LastItemNumber
to:
    	Case mnuLed2Frequency_1hz

            LED2Delay = 100    	
Changed lines 131-133 from:
    #EndIf
to:
    	Case mnuLed2Frequency_2hz

            LED2Delay = 50
Added lines 135-137:
    	Case mnuLed2Frequency_10hz

            LED2Delay = 10
Changed lines 139-149 from:
    '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
to:
    EndSelect
Changed lines 141-180 from:
    #If Menu_ShowScrollIndicators = True
to:
    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
Changed lines 182-194 from:
        #If Menu_ScrollWrapping = True
to:
    '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                               '}
Changed lines 196-341 from:
            #If Menu_ShowMenuTitle = True
to:
        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

=code [= {

  • 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
Changed lines 343-348 from:
                If LastItemNumber - FirstItemNumber > LCDLines - 1 Then

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

            #Else
to:
            WriteItem(TABLAT)
Changed lines 345-352 from:
                If LastItemNumber - FirstItemNumber > LCDLines Then

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

            #EndIf

                EndIf
to:
            ASM
                TBLRD*+
            End ASM
Changed line 349 from:
        #Else
to:
        Wend
Changed lines 351-388 from:
            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)

to:
    End Sub
  • ------------------------------------------------------------------------- *
  • *
  • *

}

Module Menu

Include "LCD.bas"

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

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

  1. option Menu_LCDLines = 4 'Number of lines on LCD display
  2. option Menu_ShowMenuTitle = True 'Display Menu Name at top of screen?
  3. option Menu_ShowScrollIndicators = True 'Show up and down scroll indicators?
  4. option Menu_ScrollWrapping = False 'Scroll wraps from top to bottom?
  5. option Menu_PointerCharacter = 126 'Character used as pointer
  6. if Menu_LCDLines = 1 And Menu_ShowMenuTitle = True
    #error "Cannot show menu titles on 1 line LCD"
Changed lines 385-405 from:
    '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

to:
  1. endif
Changed line 388 from:

'Public Subs and Functions

to:

'Variable and Const Declarations

Changed lines 391-401 from:

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '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
to:

'---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)

  1. 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)
Changed lines 411-416 from:
    'Set values for first item number, last item number & parentID for current
    'menu
    CurrentMenu = pMenuNumber
    ParentID = mnuParentMenuID(CurrentMenu)
    FirstItemNumber = mnuMenuItemStart(CurrentMenu)
    LastItemNumber = mnuMenuItemEnd(CurrentMenu)
to:
    Private Const UpArrow = 0
    Private Const DownArrow = 1
  1. EndIf

'---Private Variables

Changed lines 419-424 from:
    'Display menu & pointer
    DrawMenu
    DrawPointer(0)

End Sub

to:

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) '-------------------------------------------------------------------------------

Changed lines 442-444 from:

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

to:

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

Changed lines 447-451 from:

Public Sub MoveDown()

Dim CanScroll As Boolean

    'First, determine if the menu can be scrolled down and set CanScroll -
to:

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

  1. 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                          '}
                                            '}
  1. 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 -
Changed lines 475-477 from:
    '   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
to:
    '   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)
Changed lines 480-487 from:
    '   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
to:
    '   Scroll Wrapping Not Enabled: write items until the number of LCD lines
    '   has been reached or the last menu item has been written
Changed lines 483-485 from:
        #If Menu_ShowMenuTitle = True Then
to:
    Repeat

        LCD.WriteConstStringAt(LineNumber, 2, mnuItemName(ItemNumber))
Changed lines 487-493 from:
            If FirstItemNumber + ScrollPosition + LCDLines - 2 < LastItemNumber Then

                CanScroll = True

            EndIf

        #Else
to:
        Inc(LineNumber)
        Inc(ItemNumber)
Changed lines 490-496 from:
            If FirstItemNumber + ScrollPosition + LCDLines - 1 < LastItemNumber Then

                CanScroll = True

            EndIf

        #EndIf
to:
    #If Menu_ScrollWrapping = True
Changed line 492 from:
    #Else
to:
        Inc(ItemsDrawn)
Changed lines 494-502 from:
        CanScroll = True
to:
        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
Added lines 504-505:
    Until LineNumber > LCDLines Or ItemNumber > LastItemNumber
Changed lines 507-509 from:
    'Scroll the menu if the following conditions exist -
to:
    'If Menu_ShowScrollIndicators option enabled then show scroll indicators
    'depending on the following conditions -
Changed lines 512-518 from:
    '   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
to:
    '   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
Changed lines 521-523 from:
        Inc(ScrollPosition)
to:
    #If Menu_ShowScrollIndicators = True

        #If Menu_ScrollWrapping = True
Changed lines 525-530 from:
        #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
to:
            #If Menu_ShowMenuTitle = True
Changed line 527 from:
                ScrollPosition = 0
to:
                If LastItemNumber - FirstItemNumber > LCDLines - 1 Then
Changed lines 529-532 from:
            EndIf
to:
                    LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(2, 1, UpArrow)

            #Else
Changed lines 534-541 from:
        #EndIf
to:
                If LastItemNumber - FirstItemNumber > LCDLines Then

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

            #EndIf

                EndIf
Changed lines 543-545 from:
        DrawMenu()
        DrawPointer(PointerPosition)
to:
        #Else
Changed lines 545-561 from:
    '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)
to:
            If ItemNumber - 1 < LastItemNumber Then
Changed lines 547-549 from:
        #Else

            DrawPointer(PointerPosition - 1)
to:
                LCD.WriteAt(LCDLines, 1, DownArrow)
Added lines 549-565:
            EndIf


            If ScrollPosition > 0 Then

                #If Menu_ShowMenuTitle = True

                    LCD.WriteAt(2, 1, UpArrow)

                #Else

                    LCD.WriteAt(1, 1, UpArrow)

                #EndIf

            EndIf
Changed lines 567-569 from:
    End If    
to:
    #EndIf
Changed lines 573-574 from:

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

to:

'Name: DrawPointer(pOldPosition as byte) 'Purpose: Deletes pointer at pOldPosition and draws pointer at PointerPosition

Added lines 576-579:

'Notes: When pointer is at first menu item on screen, PointerPosition = 0 ' ' Pointer character determined by option "Menu_PointerCharacter", ' default = 126 (right arrow)

Changed lines 582-596 from:

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
to:

Private Sub DrawPointer(pOldPosition As Byte)

    'Actual LCD line which pointer is drawn on depends on whether
    'Menu_ShowMenuTitle is enabled or not 
Changed lines 587-592 from:
        #If Menu_ShowScrollIndicators = True
to:
    #If Menu_ShowMenuTitle Then

        PointerLine = PointerPosition + 2

        LCD.WriteAt(pOldPosition + 2, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
Changed lines 594-596 from:
            If PointerLine > 3 Then

        #Else
to:
    #Else

        PointerLine = PointerPosition + 1
Changed lines 598-599 from:
            If PointerLine > 2 Then
to:
        LCD.WriteAt(pOldPosition + 1, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
Changed lines 601-661 from:
        #EndIf
to:
    #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
Changed lines 663-664 from:
                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
to:
                CanScroll = True
Changed lines 665-667 from:
        #If Menu_ScrollWrapping = True
to:
            EndIf

        #Else
Changed lines 669-675 from:
            Else

                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
to:
            If FirstItemNumber + ScrollPosition + LCDLines - 1 < LastItemNumber Then

                CanScroll = True
Deleted lines 672-674:
                DrawMenu()
                DrawPointer(PointerPosition)
Changed lines 675-688 from:
        #Else                

            ElseIf ScrollPosition > 0 Then

                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)


            ElseIf PointerLine > 2 Then

                Dec(PointerPosition)

                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
to:
        #EndIf
Deleted lines 676-688:
                    DrawMenu()
                    DrawPointer(PointerPosition)

                #Else

                    DrawPointer(PointerPosition + 1)

                #EndIf

            EndIf

        #EndIf
Changed lines 678-679 from:
        #If Menu_ShowScrollIndicators = True
to:
        CanScroll = True
Changed lines 681-683 from:
            If PointerLine > 2 Then

        #Else
to:
    #EndIf
Changed lines 683-694 from:
            If PointerLine > 1 Then
to:
    '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)
Changed lines 696-699 from:
        #EndIf

                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
to:
        #If Menu_ScrollWrapping = True
Added lines 698-699:
        'If scroll wrapping and we've scrolled to the bottom then start again at
        'the top
Changed lines 701-707 from:
        #If Menu_ScrollWrapping = True
to:
            If ScrollPosition > (LastItemNumber - FirstItemNumber) Then

                ScrollPosition = 0

            EndIf

        #EndIf
Changed lines 709-735 from:
            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
to:
        DrawMenu()
        DrawPointer(PointerPosition)
Changed lines 712-723 from:
                    DrawMenu()
                    DrawPointer(PointerPosition)

                #Else

                    DrawPointer(PointerPosition + 1)

                #EndIf

            EndIf

        #EndIf
to:
    '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)
Changed lines 724-725 from:
    #EndIf
to:
        '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    
Changed lines 742-743 from:

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

to:

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

Changed lines 747-770 from:

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
to:

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
Changed lines 767-771 from:
            SelectedMenuItem = SelectedMenuItem - (LastItemNumber - FirstItemNumber) - 1
to:
        #Else

            If PointerLine > 2 Then

        #EndIf
Changed lines 773-783 from:
        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
to:
                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)

        #If Menu_ScrollWrapping = True
Changed lines 778-780 from:
    Else

        ShowMenu(ItemAction)    
to:
            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
Changed lines 806-807 from:
    EndIf
to:
                    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
Changed lines 883-885 from:

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

to:

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

Changed lines 888-897 from:

Public Sub Initialise(pItemActionEvent As TEvent)

    FItemActionEvent = pItemActionEvent

    #If Menu_ShowScrollIndicators = True Then

        LCD.Write(ScrollIndicators)

    #EndIf
to:

Public Sub Back()

    ShowMenu(ParentID)
Added lines 894-953:

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '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

Changed line 6 from:

Module

to:

Menu Module

Added lines 658-1193:

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
Added lines 1-657:

Sample Code


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
'-------------------------------------------------------------------------------