How to construct a flexible menu from items in flash

Started by Peter Truman, Aug 17, 2025, 07:12 AM

Previous topic - Next topic

Peter Truman

Hi All

I'm trying to find the best way of building a flexible menu picker (18F2525 with a Newhaven 4 x 20 LCD) with a rotary encoder and momentary button
What I have is a database of upto 40 Menu items (each item is an 18chr string)

I want to call my menu procedure like this
B_Value=P_Menu("Screen Title",1,2,7,9,41,43) where the digits represent the menu items I want to choose from. My project will have many menu screens, each made up of 1 or more of the items in my list. I'm keeping my Items list in flash.

So for example
B_Value=P_Menu("Setup Menu",1,2,7,9,41) would look like:

 Setup Menu
[Power Fail Delay]
 Set Start Delay
 Set Stop Delay
 Stop Mode
 Menu Timeout
 BACK

I use the "[]" as my cursor and only the bottom 3 lines scroll up and down with my rotary encoder. There are never more than 9 items in any menu.

ChatGPT5 has been very very helpful but, its like having an endlessly patient, highly knowledgeable but quite dim firmware developer sitting next to me. If I ask it the 'right' sort of question it very quickly shows me how to proceed, but the trick is knowing the 'right' question. When I asked it to help me with this problem it came up with a solution (after a fair bit of too and fro) which did work but seems the most cumbersome and seemingly inefficient way of getting there! There is also a of converting from C to Positron!

I'd welcome any suggestions of how to do this in a clean and efficient manner. Many thanks in advance.

top204

Hello Peter

I am not sure if you are asking for a Menu system mechanism, or a system for accessing texts from a sequence held in Flash memory, that can be accessed by a menu's procedure.

If it is texts, I created a String Array mechanism quite a few years ago, that I have used several times. They can be a String held in RAM or a String held in Flash memory, and can be created with set texts for each dimension of the array. For example, the demo below shows it working:

'
'  /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\    \/\\\                                                /\\\          /\\\
'  \/\\\\\\\\\\\/        /\\\\\    /\\\\\\\\\\    /\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'    \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\    \/\\\        \/\\\        /\\\\\\\\\\
'      \/\\\    \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\    \/\\\ /\\  /\\\/////\\\
'      \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\  \//\\\\\\\\/\\
'        \///        \///    \/////    \//////////    \//////////      \/////        \/////    \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate RAM and Flash memory string array handling using the "String Array.inc" meta-macros
' For 18F devices only
'
' Written by Les Johnson for the Positron8 BASIC compiler
'
    Device = 18F25K20                                              ' Tell the compiler what device to compile for
    Declare Xtal = 16                                              ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Setup USART1
'
    Declare Hserial1_Baud = 9600
    Declare HRSOut1_Pin = PORTC.6

    Include "String Array.inc"                                      ' Load the String array macros into the program
'
' Create a 10 string flash memory array, with each String 24 characters in length (including the null)
' The flash memory string must be an even amount of bytes (including the null)
'
    Create_FlashStringArray(MyFlashString, 10, 24) = {"My Flash String Array 0", 0,
                                                      "My Flash String Array 1", 0,
                                                      "My Flash String Array 2", 0,
                                                      "My Flash String Array 3", 0,
                                                      "My Flash String Array 4", 0,
                                                      "My Flash String Array 5", 0,
                                                      "My Flash String Array 6", 0,
                                                      "My Flash String Array 7", 0,
                                                      "My Flash String Array 8", 0,
                                                      "My Flash String Array 9", 0}
'
' Create variables here
'
    Dim bIndex As Byte                                              ' Temporary byte used by the demo
    Dim TempString As String * 32 Heap                              ' Temporary string used by the demo
'
' Create a 10 string RAM array, with each string 20 characters in length (not including the null)
'
    Create_StringArray(MyString, 10, 20) = {"RAM String Array  0", 0,
                                            "RAM String Array  1", 0,
                                            "RAM String Array  2", 0,
                                            "RAM String Array  3", 0,
                                            "RAM String Array  4", 0,
                                            "RAM String Array  5", 0,
                                            "RAM String Array  6", 0,
                                            "RAM String Array  7", 0,
                                            "RAM String Array  8", 0,
                                            "RAM String Array  9", 0}

'--------------------------------------------------------------------------------------
' Test the string array meta-macros by loading then retrieving values
'
Main:
'
' Read the RAM String array's default content
'
    For bIndex = 0 To 9
        TempString = Read_StringArray MyString, [bIndex]            ' Retrieve the RAM string array
        HRSOutLn TempString                                        ' Transmit the string's text to a serial terminal
    Next
'
' Load and Read the RAM String array
'
    Clear MyString
    For bIndex = 0 To 9
        TempString = "MyString RAM Array " + Str$(Dec bIndex)      ' Fill the string to load and retrieve
        Write_StringArray MyString,[bIndex], TempString            ' Load the RAM string array
        TempString = Read_StringArray MyString, [bIndex]            ' Retrieve the RAM string array
        HRSOutLn TempString                                        ' Transmit the string's text to a serial terminal
    Next
'
' Read the Flash memory String array
'
    For bIndex = 0 To 9
        TempString = Read_FlashStringArray MyFlashString, [bIndex]  ' Retrieve a Flash memory string array
        HRSOutLn Dec bIndex, "...", TempString                      ' Transmit the string's text to a serial terminal
    Next

The above demonstration, will transmit the texts shown below:

RAM String Array  0
RAM String Array  1
RAM String Array  2
RAM String Array  3
RAM String Array  4
RAM String Array  5
RAM String Array  6
RAM String Array  7
RAM String Array  8
RAM String Array  9
MyString RAM Array 0
MyString RAM Array 1
MyString RAM Array 2
MyString RAM Array 3
MyString RAM Array 4
MyString RAM Array 5
MyString RAM Array 6
MyString RAM Array 7
MyString RAM Array 8
MyString RAM Array 9
0...My Flash String Array 0
1...My Flash String Array 1
2...My Flash String Array 2
3...My Flash String Array 3
4...My Flash String Array 4
5...My Flash String Array 5
6...My Flash String Array 6
7...My Flash String Array 7
8...My Flash String Array 8
9...My Flash String Array 9


I have attached the String Array include file and demo to this post. The "String Array" include file contains the routines to create and read Flash memory String arrays, and create, read, and write RAM String arrays.

To access the text within an array's dimension, just use its index value, that can be set by a procedure's parameter.

Best regards
Les

top204

Something else I have found very useful in Menu Systems I have created using alphanumeric or graphic LCDs, is a mechanism to display texts in the center of the LCD's line. Instead of having to jiggle texts around, and test, then jiggle again, then test, or create a string with the full line's text.

Below is a simple procedure that will calculate the center X position on an alphanumeric LCD, based upon the length of the text to display, and display it on the line required:

'------------------------------------------------------------------------------------------------
' Display text at the center X of the LCD
' Input     : pLine holds the line to display the text on
'           : pText holds the text to display (maximum 20 characters)
' Output    : None
' Notes     : cCharsOnLine is set for a 20 characters per line LCD
'
Proc LCD_PrintAtCenter(pLine As Byte, pText As String * 20)
    Symbol cCharsOnLine = 20                                            ' The amount of characters per line on the LCD
    Symbol cLineCntr = (cCharsOnLine / 2) + 1                           ' The center X position on a line
    Dim bChars As Byte                                                  ' Holds the amount of characters in the pText String
    Dim bXpos  As Byte                                                  ' Holds the center X position on a line

    bChars = Len(pText)                                                 ' Count the amount of characters in the text
    bChars = bChars / 2                                                 ' Divide it by 2, so it can be subtracted from the full text count
    bXpos = cLineCntr - bChars                                          ' Subtract the amount of chars from the center of the LCD's line amount
    Print At pLine, bXpos, pText                                        ' Display the text
    Clear pText                                                         ' Clear the parameter string before exiting
EndProc

If ASCII values are needed to be displayed, use the Str$ function in the procedure's parameter, when it is called.

I have also created PrintAtCenter procedures for graphic LCDs, but they take a bit more calculation, and other, 'helper', procedures because of the proportional fonts used in them. But it saves hours of time, that is needed to make sure a piece of text actually sits in the middle of the display.

The concept of a procedure for the center of a display, came about because I was so bored of having to move an X position, then re-programming to see if it was in the centre, then when text or a font was changed, having to go through the whole program again, and do the same for all the texts in it. It used to take a couple of hours 'or so', on a largish program, just to position texts.