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