How do you build a menu for data input?
I've been working on a telescope tracking system and I've got most of my motor control and tracking calculations worked out but now it is time to start tying them all together. Part of the system will be a hand controller with a 12 key number pad, a directional pad with up,down,left,right and center buttons, and a small joystick for slewing the telescope manually.
When I first start up the telescope tracker I need to input information such as latitude, longitude, time, etc... I thought making the menu and getting the data would be the easiest part of the coding but it is turning out to be more difficult than expected and I'm not sure how to proceed and structure it. Here is where I have started:
Here is what I get when I run this:
http://youtu.be/kNgQ94qL5Do
I have the * and # buttons on the keypad set to move the cursor left and right. I still need to make the d pad. What I'm really trying to figure out is how to take the numbers I put on the screen and put them into the variables they represent in my code.
In the second line there are 4 variables: longitude degrees, longitude minutes, latitude degrees, latitude minutes. I'd like to be able to enter the values and be able to go back to fix mistakes before going to the next screen. I'm just not sure how to take the values I put up on the screen. If I could I would just have the lcd tell me what values are in certain positions on the screen when I ask it to but I don't think I can do that. I don't think I there was anything useful in the obex for making menus, if anyone has any ideas I would appreciate it.
When I first start up the telescope tracker I need to input information such as latitude, longitude, time, etc... I thought making the menu and getting the data would be the easiest part of the coding but it is turning out to be more difficult than expected and I'm not sure how to proceed and structure it. Here is where I have started:
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
lcdpin = 0 'serial lcd pin assignment
k1 = 9 'keypad pin assignments
k2 = 10
k3 = 11
k4 = 12
k5 = 13
k6 = 14
k7 = 15
OBJ
LCD : "SparkFun_Serial_LCD"
PUB Main
LCD.init(lcdpin,9_600,2,16) 'initialize LCD
waitcnt(clkfreq/10 + cnt) 'wait for LCD to initialize
LCD.cls 'clear lcd
lcd.cursor(2) 'set cursor type
lcd.str(string("ENTER LOCATION:")) 'display first page of menu:
lcd.gotoxy(0,1)
lcd.str(string("000")) 'ENTER LOCATION:
lcd.putc(223) '000d00'W 00d00'N 'where d is the actual degree symbol
lcd.str(string("00'W 00"))
lcd.putc(223)
lcd.str(string("00'N"))
lcd.gotoxy(0,1)
repeat
lcd.dec(keypad)
waitcnt(clkfreq/4+cnt)
PRI keypad
'keypad sets the columns as outputs and the rows as inputs. It sweeps each column indidually and looks for a keypress on a row. When a keypress is detected it returns the associated value
dira[k1] := dira[k3] := dira[k5] := 1
dira[k2] := dira[k4] := dira[k6] := dira[k7] := 0
repeat
outa[k1] := 1 'set column one high then check rows for key press
if ina[k2] == 1
return 2
elseif ina[k4] == 1
return 0
elseif ina[k6] == 1
return 8
elseif ina[k7] == 1
return 5
outa[k1] := 0
outa[k3] := 1 'set column three high then check rows for key press
if ina[k2] == 1
return 1
elseif ina[k4] == 1
lcd.cursorleft '* button
waitcnt(clkfreq/4+cnt)
elseif ina[k6] == 1
return 7
elseif ina[k7] == 1
return 4
outa[k3] := 0
outa[k5] := 1 'set column five high then check rows for key press
if ina[k2] == 1
return 3
elseif ina[k4] == 1
lcd.cursorright '# button
waitcnt(clkfreq/4+cnt)
elseif ina[k6] == 1
return 9
elseif ina[k7] == 1
return 6
outa[k5] := 0
Here is what I get when I run this:
http://youtu.be/kNgQ94qL5Do
I have the * and # buttons on the keypad set to move the cursor left and right. I still need to make the d pad. What I'm really trying to figure out is how to take the numbers I put on the screen and put them into the variables they represent in my code.
In the second line there are 4 variables: longitude degrees, longitude minutes, latitude degrees, latitude minutes. I'd like to be able to enter the values and be able to go back to fix mistakes before going to the next screen. I'm just not sure how to take the values I put up on the screen. If I could I would just have the lcd tell me what values are in certain positions on the screen when I ask it to but I don't think I can do that. I don't think I there was anything useful in the obex for making menus, if anyone has any ideas I would appreciate it.

Comments
In my projects I tend to define LCD menu screens in DAT tables which allows me change screens quickly with a method call (the screen displayed is based on the main-menu state). Then I call a handler for that screen which knows where the fields are and how to manipulate them.
Till this point, your input method sounds great.
At last, there are only two lines of text, and you wisely decided to use the first one, to explain the meaning of the second one.
As long as you keep using defined templates to gather data input, you won't have to worry about "Insert" or "Delete" alike keys, just the ones you have in fact.
IMHO, if you decide not using extra buttons, in a separate keypad, to input an "Enter" alike key stroke and other suitable function keys, you could use the detection of a simultaneous pressing of both * and #, to create into your decision tree, suitable branches to deal with them.
As for storing gathered data, you can do it in a multitude of ways, its only a matter of each programmer's personal preferences.
Testing various options will help you decide which "style" best fits yours.
In cases like the one you has shown us at the video, my trend is to point a base register, to value place holders at local memory, and an index to the leftmost (or rightmost) item, whichever best fits; It's related to the place where the cursor was left, at the very beggining of data input screen editing routine; Then you can be confident of the following operator movements.
Then, as the operator progresses, doing left or right movements using the "cursor positioning" keys, the index variable is incremented or decremented accordingly.
You just have to set upper and lower limits to the index value, to don't mess with neighboring memory areas.
For each valid keystroke, you will have only two options:
- store the new value at its memory position, and decide if the chosen input method determines a left or right cursor movement, or not;
- if you choose to update cursor's position, adjust the index variable accordingly. If you reached leftmost or rightmost display line position, then you must choose staying there, or executing some line wrapping code, but your current application doesn't appear to benefit of such an strategy.
To be true, short-key-counts keypad projects, are a sure head ache to deal with, when it comes to wrapping data or text input. But its your decision, and operator's convenience, that will prevail at the end.
Since Propellers are, by nature, multitasking processors, you can act wisely, keeping the original value, the one displayed at the beggining of data input routine, at Hub ram, and use a copy to deal with, during data input editing.
After the input and edit process exits, thru the detection of a "Enter" keystoke, you can forward the fresh new value to Hub ram, and sign another waiting Cog's routine, to deal with the mechanical positioning system.
As long as you are entering some new coordinates, if you chose to dinamically update its contents, in parallel with the motor steering mechanics controller, then, IMHO, I'm about to suggest you to update it from right to left, minute units first, sparing your system from doing large movements at first.
But its just my point of view, your own choices will dictate project's final behavior.
Good luck!:thumb:
I hope it can help a bit
Yanomani
Tested using my 4x20 parallel LCD, which I've commented out. Also shows the value on the serial terminal.
{{ }} CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 'LCD pins RS_pin = 23 ' RS - low for commands, high for text RW_pin = 22 ' R/W - low for write, high for read E_pin = 21 ' E - clocks the data lines D4_pin = 16 ' D4 through D7 must be connected to consecutive pins 'D5 = 17 ' with D4 on the low pin 'D6 = 18 'D7 = 19 OBJ FDS : "FullDuplexSerial" ' LCD : "LCD SPIN driver - 4x20" PUB Main | number ' LCD.start(E_pin,RW_pin,RS_pin,D4_pin) FDS.start(31,30,0,115_200) waitcnt(cnt + clkfreq) ' LCD.send(%0000_1111) ' Turn LCD on, cursor on, blink on number := 00000 repeat FDS.tx($0D) number := update_number(number) FDS.tx($0D) FDS.str(string("Number stored as: ")) FDS.dec(number) PRI update_number (number) | key, i, temp, cursor { This method creates a 5-digit number representing degrees and minutes To extract: degrees := number / 100 minutes := number // 100 Limits maximum degrees to 359 and minutes to 59 } cursor := 1 i := 10000 repeat until key == $0D ' LCD.move(1,cursor) ' Update cursor position to show currently selected digit key := FDS.rx case key "0".."9": temp := (number - (number / i // 10 * i)) + ((key - "0") * i) ' Subtract the old number and add the new one if temp / 100 < 360 and temp // 100 < 60 ' Only update if degrees < 360 and minutes < 60 number := temp i := i / 10 #> 1 ' Update the divisor, limit minimum to 1 if ++cursor == 4 ' Skip over the degree sign cursor := 5 cursor <#= 6 $08: ' $08 is backspace i := i * 10 <# 10000 ' Update the divisor, limit maximum to 100_000 if --cursor == 4 ' Skip over the degree sign cursor := 3 cursor #>= 1 display_number(number) return number PRI display_number(number) ' LCD.clear FDS.tx($00) ' clear screen dec(number / 100,3) ' display degrees ' LCD.send("°") FDS.tx("°") dec(number // 100,2) ' display minutes PUB Dec(value,digits) | i, x ' Taken from FullDuplexSerial and modified to display fixed-width fields i := 1 repeat digits - 1 i *= 10 ' Initialize divisor repeat digits FDS.Tx(value / i + "0") ' Display most-significant digit ' LCD.send(value / i + "0") value //= i ' Move remainder into value i /= 10 ' Update divisorPUB EnterLocation 'ENTER LOCATION: '000d00'W 00d00'N column := 0 row := 1 direction := 2 bytefill(@data,0,16) 'fill data array with zeros data[0] := longdeg/100 ' breakdown longdeg into hundred tens and ones data[1] := (longdeg - data[0]*100) / 10 data[2] := longdeg - data[0]*100 - data[1]*10 data[4] := longmin/10 'breakdown longmin into tens and ones data[5] := longmin - data[4]*10 data[9] := latdeg/10 'breakdown latdeg into tens and ones data[10] := latdeg - data[9]*10 data[12] := latmin/10 'breakdown latmin into tens and ones data[13] := latmin -data[12]*10 lcd.home 'build initial display lcd.str(string("ENTER LOCATION:")) lcd.gotoxy(0,1) lcd.dec(data[0]) 'display longdeg lcd.dec(data[1]) lcd.dec(data[2]) lcd.gotoxy(3,1) lcd.putc(223) 'display longmin lcd.dec(data[4]) lcd.dec(data[5]) lcd.gotoxy(6,1) lcd.str(String("'W ")) lcd.dec(data[9]) 'display latdeg lcd.dec(data[10]) lcd.gotoxy(11,1) lcd.putc(223) lcd.dec(data[12]) 'display latmin lcd.dec(data[13]) lcd.gotoxy(14,1) lcd.str(string("'N")) lcd.gotoxy(column,row) repeat 'call for keypad entry, waits until keypress is detected then moves on if keypad => 0 and kpdout =< 9 lcd.dec(kpdout) data[column-1]:= kpdout 'enter keypad entry into data array longdeg := data[0]*100 + data[1]*10 + data[2] 'combine appropriate data positions into longdeg longmin latdeg and latmin longmin := data[4]*10 + data[5] latdeg := data[9]*10 + data[10] latmin := data[12]*10 + data[13] if column == 3 and direction == 2 column := 4 elseif column == 3 and direction == 1 column := 2 elseif column == 6 and direction == 2 column := 7 elseif column == 6 and direction == 1 column := 5 elseif column == 8 and direction == 2 column := 9 elseif column == 8 and direction == 1 column := 7 elseif column == 11 and direction == 2 column := 12 elseif column == 11 and direction == 1 column := 10 elseif column == 14 and direction == 2 column := 15 elseif column == 14 and direction == 1 column := 13 elseif kpdout == 10 if column == 3 and direction == 2 column := 4 elseif column == 3 and direction == 1 column := 2 elseif column == 6 and direction == 2 column := 7 elseif column == 6 and direction == 1 column := 5 elseif column == 8 and direction == 2 column := 9 elseif column == 8 and direction == 1 column := 7 elseif column == 11 and direction == 2 column := 12 elseif column == 11 and direction == 1 column := 10 elseif column == 14 and direction == 2 column := 15 elseif column == 14 and direction == 1 column := 13 lcd.gotoxy(column,row) waitcnt(clkfreq / 4 + cnt)The button used is a the Parallax 5 position switch. When the method is activated it cycles through the list using lookup(). The up /dwn button inc and dec the value to control the display and selected option.
pub setupScreen |menuOption, buttonValue ''Method for user interface to select menu option menuOption := 1 lcd.cmd(lcd#cls) repeat lcd.move_to(1,1) 'Display Date to LCD, start at col/line lcd.str(string(" Configuration Menu")) lcd.move_to(5,3) lcd.str(lookup(menuOption:string("[ Stats ]"), string("[ Display ]"),string("[ Setup ]"))) buttonValue := button.read 'scan button pins for user input case buttonValue 4: 'dwn button menuOption ++ 2: 'Up button menuOption -- 3,5: 'left or right buttons do nothing 1: 'center button quit repeat, choice selected return(menuOption) 'return selected menu item number menuOption := 1 #> menuOption <# 3 'limit menuOption values to keep valid choicesPUB read | buttonValue ''Method to read the input from the 5-position switch ''read switch, return button pressed buttonValue := 0 if ina[24] == 0 buttonValue := 5 'left if ina[25] == 0 buttonValue := 4 'down if ina[26] == 0 buttonValue := 3 'right if ina[22] == 0 buttonValue := 1 'center if ina[23] == 0 buttonValue := 2 'up pause(150) 'debounce return(buttonValue)