Shop OBEX P1 Docs P2 Docs Learn Events
Menu system in Spin with 3 buttons and minimum / maximum range. — Parallax Forums

Menu system in Spin with 3 buttons and minimum / maximum range.

eagletalontimeagletalontim Posts: 1,399
edited 2012-12-16 12:19 in Propeller 1
And now on to another part of my coding.... I have a simple menu system setup I wrote using arrays to pre-define the menu item and another array that stores the value for each menu item.

Example of what I have now (Not all Code is here) :
VAR
   LONG Menu[255]
   BYTE Stored[255]

PUB Main
   Menu[0] := string("Menu Item 1")
   Menu[1] := string("Menu Item 2")
   Menu[2] := string("Menu Item 3")
   Menu[3] := string("Menu Item 4")

  StartLCD

PUB StartLCD
  StopLCD
  cogon := (cog := cognew(LCDupdate, @LCDstack)) > 0

PUB StopLCD
  if cogon~
    cogstop(cog)

PUB StartMenu
  StopLCD
  cogon := (cog := cognew(EnterMenu, @LCDstack)) > 0
  
PUB EnterMenu | btnpresstime, startid, userExit
  startid := 0
  userExit := 0
  lcd.init(24, 9600, 2)
  lcd.displayOn
  lcd.backLight(TRUE)
  waitcnt(1_000_000 + cnt)

  lcd.gotoxy(0,0)
  lcd.str(string("Programming Menu"))
  lcd.gotoxy(0,1)
  lcd.str(string("Loading....     "))
  waitcnt(100_000_000 + cnt)
  
  repeat
    DisplayMenu(startid)
    UpdateMenuValue(startid, 0)
    userExit := 0
    repeat until userExit == 1
      btnpresstime := 0
      repeat while ina[downbutton] == 1 or ina[upbutton] == 1 or ina[menubutton] == 1
        if(ina[downbutton] == 1)
          if(btnpresstime == 0)
            UpdateMenuValue(startid, -1)
        if(ina[upbutton] == 1)
          if(btnpresstime == 0)
            UpdateMenuValue(startid, 1)
        if ina[menubutton] == 1
          if btnpresstime == 0
            saveval(Stored[startid], startid)
            userExit := 1
          btnpresstime++
        waitcnt(20_000_000 + cnt)
        if(btnpresstime == 10)
          restartMain := 1
    startid++
    if(startid > menuItems)
      startid := 0

PUB DisplayMenu(id)
    lcd.gotoxy(0,0)
    lcd.str(Menu[id])
    lcd.gotoxy(0, 1)
    lcd.str(String("Value : "))

PUB UpdateMenuValue(id, amount)
  lcd.gotoxy(8, 1)
  Stored[id] := Stored[id] + amount
  lcd.str(string("       "))
  lcd.gotoxy(8, 1)
  lcd.str(num.dec(Stored[id]))

What I have works just fine, but I don't like the navigation of the menu system since I am only using 3 buttons (Up, Down, Menu/Enter). I want to convert the entire menu to be much more accessible without having to go through each menu item till I get to the one I need to change.

Currently, I am working with the Parallax 2x16 Serial display so my space is limited on what I can put on the screen. The first initial screen is the "working" screen which shows updated variables as the program runs and reads sensors. This screen must be cleared and the cog stopped to prevent other writing on the screen while in the menu system. Once the cog is stopped, another is started for the Menu system to take over. If there is a better way to do this, please let me know.

My idea is to stay with the 3 buttons but have the menu button instantly access the menu instead of holding it down for 2 or 3 seconds. Then once the menu is loaded, each menu item will be listed on the screen 2 items at a time. When I press the down button, the menu items will shift up so I can "scroll" through the menu. The selected line will be show with an ">" and will stay on the first line of the screen. Once the menu item I want to edit is in the selected line, I can then press the Menu/Enter button to edit its value. I can more than likely get to this point with the changes but here is where I am not sure what to do to make this easier in programming terms. I am only a little knowledgeable in Spin and cannot grasp PASM at all.

Each menu item's value (the Stored[] variable) will be a numerical value stored in the EEPROM which is updated by adjusting the value in the menu. Each menu item's value will need to have a pre-defined range (Minimum and Maximum value). Some values will be 1, 2, or 3, some will be 15 through 100. This means that when a specific menu item is being changed, the value must not go higher or lower than that specific menu item's pre-defined range. What would be the best way to accomplish this in SPIN? Using Arrays seems to be easiest for me currently since I can use the Array pointer as the multiplier for where to save the value in the EEPROM.

Comments

  • mojorizingmojorizing Posts: 249
    edited 2012-12-09 17:40
    I'm not sure of your intent for the min and max, and a 2 line LCD is small to convey info, but I've got an example of a 4x20 LCD with scrolling menu o this post

    http://forums.parallax.com/showthread.php?141846-Menus-on-the-Parallax-4X20-Serial-LCD-Screen.&p=1118421&viewfull=1#post1118421
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-09 17:47
    The min and max range is to prevent user error when programming the device. If the value is set too high, it could cause major undesirable results.

    I am sure I can do the scrolling menu which is the easier part of my question :p My biggest issue is the minimum and maximum values for each individual menu item.
  • Mike GMike G Posts: 2,702
    edited 2012-12-09 20:05
    As I explained in a previous thread... keep track of the menu items (state) in a dedicated object. This type of design can also encapsulate validation, like min/max limits, so the top level object is not doing all the work.
    CON
      _clkmode = xtal1 + pll16x     
      _xinfreq = 5_000_000
    
      MAX_MOVIE_MENU_ITMES          = 5
    VAR
    
    DAT
      miSel   byte  $FF
      mi1     byte  "Star Wars",0
      mi2     byte  "The Hangover",0
      mi3     byte  "Christmas Vacation",0
      mi4     byte  "One Flew Over the Cuckoo's Nest",0
      mi5     byte  "The Good, the Bad and the Ugly",0
      mis     long  @mi1, @mi2, @mi3, @mi4, @mi5
    
      null  long  $00
    
    OBJ
      pst           : "Parallax Serial Terminal"
    
      
    PUB Main | i, ptr
    
      pst.Start(115_200)
      pause(500)
    
      repeat i from 0 to MAX_MOVIE_MENU_ITMES-1
        pst.str(SelectMenuByIndex(i))
        pst.str(string(" | Selected Index = "))
        pst.dec(GetMenuItemSelected)
        pst.char(13)
    
      'Detect index out of bounds
      ptr :=  SelectMenuByIndex(10)
      if(ptr == @null)
        pst.str(string(13, "Index is out of bounds", 13))
    
      'Last slected index
      pst.str(string("Last Selected Index = "))
      pst.dec(GetMenuItemSelected)
      pst.char(13)
    
    
    PUB SelectMenuByIndex(idx)
      if(idx > MAX_MOVIE_MENU_ITMES-1)
        return @null
        
      miSel := idx  
      return @@mis[idx]
    
    PUB GetMenuItemSelected
      return miSel
    
    PRI pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return
    

    BTW, this is not good design due to scope.
    PUB Main
       Menu[0] := string("Menu Item 1")
       Menu[1] := string("Menu Item 2")
       Menu[2] := string("Menu Item 3")
       Menu[3] := string("Menu Item 4")
    
  • BRBR Posts: 92
    edited 2012-12-10 11:17
    This object may give some ideas as to one possible approach:
    http://obex.parallax.com/objects/696/

    I have an (unpublished) 3-button version I can post if it would help...though extending the 1-button method to 3 is pretty straightforward.
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-12-10 11:57
    For the min and max part:
    You already have an array for storing the values I guess. So, what you need is simply add 2 more arrays one named maxval and one called minval.
    DAT
      minval  word     0,   15,  100, ...
      maxval   word    30,  100, 1000, ...
    

    This does not look like an array, but you can use it in the same way. In the code where you increment or decrement the value you's simply write:

    ' increment
    value[ actual_selection ] := ++value[ actual_selection ] <# maxval[ actual_selection ]

    ' decrement
    value[ actual_selection ] := --value[ actual_selection ] #> minval[ actual_selection ]
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-16 11:28
    I am trying to work from Mike G's example and have no idea how to use his example in my project. I have 2 different display screens (software, not actual displays), one is the main screen which shows the program running and updating variables based on sensor inputs, the other is the actual Programming menu which a user will have access to many programming points. What I can't seem to figure out is how to go about showing one specified display screen at one time, then exiting out of it and starting the other. Basically to simplify things, the program will start in the "main screen". When the user presses the menu button, the main screen will go away and the "programming menu" will appear. Once programming is completed the user can select "EXIT" in the programming menu to close the programming menu and go back to the main screen. What is the best way to accomplish this? Does this have to be done in 1 cog? Is a Cog even needed if I read button inputs from the main program and call the screen program by using LCDmenu : "LCD_Menu". I am sooo lost now.
    BTW, this is not good design due to scope.
    PUB Main
    Menu[0] := string("Menu Item 1")
    Menu[1] := string("Menu Item 2")
    Menu[2] := string("Menu Item 3")
    Menu[3] := string("Menu Item 4")

    I have no idea what scope means and can't figure out how it is different from this :
    DAT
    minval word 0, 15, 100, ...
    maxval word 30, 100, 1000, ...
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-12-16 12:19
    You should dedicate one COG to user IO. Meaning that this COG reads the buttons and has the code to display both screens.

    It would be as easy as
    con
     #0,MODE_SCREEN1, MODE_SCREEN2
    var
      long mode
    
    pub userIO
      mode:=MODE_SCREEN1
    
      repeat
        check_buttons
        case mode
           MODE_SCREEN1:
             update_screen1
           MODE_SCREEN2:
             update_screen2
    

    check_buttons, update_screen1 and update_screen2 being functions.

    check_buttons reads the buttons and according to the mode it is doing different things:
    if mode == MODE_SCREEN1 it switches into the other mode if the right button is pushed (switch to menu).
    if mode == MODE_SCREEN2 it switches into the other mode if the right button is pushed (going back to main screen).
    and it updates values if increase decrease buttons are pushed
    and it updates print_from variable if buttons for scrolling in the menu are pushed.

    update_screen1 simply print values that are updated by other code running in n other COGs

    update_screen2 prints the actual part of the menu (print_from variable) and the editable values
Sign In or Register to comment.