PDA

View Full Version : Interrupt Menu System Example and Help Needed



Tom Gonser
- 27th March 2005, 17:34
I have a working Interrupt Menu on an 18F2525, but need some help improving it. My program is constantly listening for data coming in on serial line via RB2, converting the binary data to usable information, and then either displaying that data, or writing the raw data to EEPROM. Since the data could come in as fast as every 100ms, I want to use an interrupt to manage the menu selection vs just polling for a button push. (but am open to simpler ways, certainly!)

The menu/interrupt needs to allow the user to change what is happening with as little disruption to the data read - store routine as possible. I don't need to get too fancy, because a few lost packets is OK, but I do want to allow user to select:

1. View 1 of data - eg long/lat
2. View 2 of data - eg speed/hdg
3. View 3 of data - eg volt/temp

AND,

4. Start recording data to EEPROM
5. Stop recording data to EEPROM
6. Play recorded data from EEPROM out the Serial port (Dump)
7. Erase EEPROM

Ideally, I'd use 2-3 buttons - 1/2 would scroll(up/down) throgh menus displayed on the LCD, the other would 'Select' the current view so the user can skip past those not wanted, and would then display that menu info on the LCD.

The routine I use now is a simple Interrupt with T1CON. I don't think this will do it for the more complex menu that I need. Here is what I have setup for the 18F2525:

@ __CONFIG _CONFIG1H, _OSC_INTIO67_1H
@ __CONFIG _CONFIG2H, _WDT_ON_2H & _WDTPS_128_2H
@ __CONFIG _CONFIG3H, _PBADEN_OFF_3H & _MCLRE_OFF_3H
@ __CONFIG _CONFIG4L, _LVP_OFF_4L & 0bfh ;_XINST_OFF_4L
'
' -----[ Includes/Defines ]---------------------------------------------------------
OSCCON=%01111000
DEFINE OSC 8
While OSCCON.2=0:Wend

include "modedefs.bas" 'include serout defines
'define loader_used 1 Used for bootloader only

ADCON0 = %00110000 ' turn off - select port AN12 (nothing)
ADCON1 = %00001111 ' turn portA to digital I/O (same as dec 15)

CMCON = $07 ' turn off
HLVDCON = %00000000 ' turn off
CVRCON = $00000000 ' turn off

SSPCON1 = %11011100 ' supposed to be turning on I2C
SSPCON2 = %01111000 ' supposed to be turning on I2C

INTCON = %11110000 ' TG guess at 2525 interrups for all INT pins
INTCON2= %01110101 ' rising edge of INT0,1,2 RB0,1,2
RCON = %10000000 ' no priority interrups

T1CON = %11000000 'Timer1 1:1 prescale.

...
variables, etc,
...

' ----- starting here ---------------------------------------------------
On Interrupt Goto ProcedureSwitcher

Bmenu = 1

ReStart:
TMR1H = 0 ' Clear time counts before Timer1 re-start
TMR1L = 0 ' Clear time counts before Timer1 re-start
PIR1.0 = 0 ' CLear over-flow flag before enable
T1CON.0 = 1 ' Turn Timer1 back on before entry into MainProcedure

Main:' ****** [Main Program Loop] ************************************************** ***************
wp=wp+1
gosub getdata ' go get data from radio
if stflag = 1 then
gosub storedata ' store ss data to EEROM if flag is on
endif
gosub calcvalue ' calculate values
if wp > 15 then
gosub waypoint ' ALWAYS put out a waypoint signal on the line (4800b)
endif

Select Case Bmenu ' display and act accordingly - driven from interrupts
case 1
gosub dis1 ' display information
Case 2
gosub dis2 ' display lat/long
Case 3
gosub dis3 ' display speed volts
Case 4
gosub dis4 ' Display Alt
Case 5
gosub dis5 ' RSSI - radio strenght
Case 6
gosub dis6 ' output memory - 9600b - same line as GPS is on
Case 7
gosub dis7 ' erase memory, reset address to 0
Case 8
gosub dis8 ' Start Recorder - set stflag to 1
Case 9
gosub dis9 ' Stop Recorder - set stflag to 0, write last address to EEPROM on 18F
End Select

' --------- Interrupt Handler ------------------------------------------------------------

IF PIR1.0 THEN ' IF Timer1 has over-flowed then
Timer = Timer + 1 ' Increment Timer variable
PIR1.0 = 0 ' Clear over-flow/int flag
' @20MHz 200nS * 65,536 * 8 = 0.1048576 seconds per over-flow
' 0.1048576 * 48 = ~5.033 seconds before jump to NextStage
IF Timer >= 48 THEN NextStage
ENDIF


goto Main

return

NextStage: ' Dont really know what you need here. Just a visual test
T1CON.0 = 0 ' Turn off Timer1 if you need to here
Timer = 0 ' Clear Timer var on entry here
TMR1H = 0
TMR1L = 0 ' CLear timer count registers as required

GOTO ReStart ' When you're ready to start all over

' Interrupt handler stuff here
'
Disable ' Disable interrupts in handler
ProcedureSwitcher:
Bmenu = Bmenu + 1 ' Changing tasks only
' I need to be able to do more here... A 'select' button?

If Bmenu = 10 Then Bmenu=1
serout2 ALCDout, LCDbd, [I,CLR]
serout2 ALCDout, LCDbd,[I,L1_C1]
serout2 ALCDout, LCDbd, ["Using Menu #: ",#Bmenu]
pause 500

Here:
While SWITCH = 0 ' waiting until
wend ' push-button is release
pause 100 ' debounce time
If switch = 0 then here
PIR1.0 = 0 ' Clear Timer1 over-flow flag
Timer = 0 ' Clear Timer counts before return
INTCON.1=0 ' reset RB0 interrupt flag
Resume ' Return to main program

Enable ' Enable interrupts after
' handler

The 18F2525 supports multiple interrupts, and even priorities of interupt (which I think I turned off) - EG there is INT on RB0, RB1, RB2... If necessary, I can move the incoming serial data from RB1 to RB5 or so to make space for multiple interrupts.

Anyone have any really good Interrupt/menu experience for such an application? I am sure this is a common type of activity - scroll through menus, then select the one you want, hopefully while something is still happening in the background....

Thanks,

Tom

cupajoe
- 27th March 2005, 19:50
That's right my secrets out. I have done a few projects that involve user interfaces and I like to use a pot to scroll through menu items. I also use a single button and assign it different funtions based on how long its held down. You see I like to use a timer to debounce my switch. I set a timer to run free with a 1 mS period. Then when I service its interrupt I check to see if the button is pressed and if it is I increment a counter. Back in my main loop I check the value of the counter and if it is > then say 1000 I toggle an output, if it is > then 3000 I enter the menu screens. If the button is not pressed I zero the counter.

Hope this helps,

Joe Kupcha.

P.S. Consider using a harware UART so that you won't need to wait and you won't miss any characters.

Edit: If you want to use buttons for navigation you can save I/O by using an analog port and setting up a resistor network with the switches shorting out indivdual resistors.

Tom Gonser
- 27th March 2005, 21:09
So, a few questions:

1. What a 10k pot or something? Then setup a scale from 1-?? and convert this to X number of menu items? Hmmm..

2. My menu system is broken with my current interrupt system - for example, if I want to go PAST menu 5 to menu 6, I can't do it because menu 5 is already happening.. It will show me that I selected 'Menu 6', but then goes back to Menu 5 --

I need a way to SKIP menus I don't want to use and only dive in on the ones I want.. I think they way I have it set up, I HAVE TO go to each one.. no good..

Do I have a SECOND button which does the selecting? I've never made a menu system like this before that chooses basically Gosubs...

Tom

NavMicroSystems
- 27th March 2005, 22:28
Tom,

the trick with menu systems is to structure them as far as possible.

i.e.
if you are in a menu to set the clock you don't want to see an item "set LCD contrast", do you ?

A good example are mobile phones

You might remember the early motorola phones:
it was almost impossible to change anything without having the manual next to you.

Today (with almost any phone) you can do everything with only two or three buttons without having had a look at the manual at all.

What I'm trying to say is:
The work starts ways before you start writing your first line of code.

Tom Gonser
- 28th March 2005, 00:25
I agree the hard part is keeping it usable!

My problem is that this is my first 'LCD Navigation' project with buttons. What I want and what I can do are not in alignment...

I WANT the program to behave like this:

Initialize:
- warm up LCD, etc.
- Ready interrupt buttons on RB0 and RB1 (menu & select)
- set counters to zero

main:
while no buttons are pressed
- go get data
- If recordflag=1 then
gosub recorddata
endif
- covert data to info
- show data view on LCD
If Menu=1: Gosub view1
If Menu=2: Gosub View2
If Menu=3: Gosub view3
Goto main

If main interrupt menu (B0) is pressed cycle through 3 options:
- Main Menu View - Explans to press B0 to move to next menu B1 to select
- pressing B1 here does nothing. Pressing B0 moves to next view

- Change View (BO/B1)
- If I press the B1 button while looking at this view, it selects it
- Select Menu1 - If I press B1, it selects it and returns to main
- If I press B0, it shows the next menu ->
- Select Menu2 - If I press B1, it selects it and retunrs to main
- If I press B0, it shows the next menu ->
- Select Menu3 - If I press B1, it selects it and retunrs to main
- If I press B0, it shows the next menu ->
- Select Exit - If I press B1, it selects it and goes to change view
- If I press B0, it shows Menu1 ->

- Start Recording(or stop recording if it is already on) (B0/B1)
- If I press the B1 button while looking at this view
- set recordflag to (on/off) depending on what current state is
- If I press the B0 button while looking at this view, it moves next view

- Playback recorded data(B0/B1)
- If I press the B1 button while looking at this view, it selects it
- takes me to a view that shows a warning that data will be dumped
- if I press BO/B1 it will cancel this and return to top
- If I press the B0 button while looking at this view, it moves next view

- Exit (go back to where I was)
- If I press the B1 button while looking at this view, it selects it
- If I press the B0 button while looking at this view, it moves first view

DURING this menu 'viewing', I want the processor to continue to take data in on the serial port.... If I DON'T click any BO/B1, after a certain amount of time, it should just COME BACK to the last menu# it was on...

My problem is that I can only seem to trap for B0, then cycle through one menu at a time, and ALWAYS select that one (can't skip it). Pressing B1 is not doing anything, and I am not sure how to turn it on as an interrupt.. (18F2525 supporst multiple INT, and I THOUGHT I turned them on..)

Any hints?

Thanks,

Tom

mister_e
- 28th March 2005, 18:02
My suggestion, use the interrupt on PORTB change. Have a look to the datasheet.

Once you jump to the interrupt routine, check the state of your push-buttons and do the according stuff or set some specific flags and do your stuff in the MAINLOOP.

Tom Gonser
- 28th March 2005, 18:12
OK - Once I am in the interrupt routine I need it to WAIT for a few seconds for the user to either click the BO again (in which case I'd do one thing), or click the B1, in which case I'd do something different.

My problem is I can't find a way to WAIT... Is it some sort of loop?

while RB0 <> pressed then do (do main routine stuff - read from port/convert)
* time out after 5 seconds and return
If RB1 is pressed then do (something)
if RB0 is pressed then do (something else)

wend

??

Tom Gonser
- 29th March 2005, 14:41
With the 18F2525, there is an INT setting for external interrupts on RB0-2. From the sheet:
-----------------------------------------------
9.6 INTn Pin Interrupts
External interrupts on the RB0/INT0, RB1/INT1 and
RB2/INT2 pins are edge-triggered. If the corresponding
INTEDGx bit in the INTCON2 register is set (= 1), the
interrupt is triggered by a rising edge; if the bit is clear,
the trigger is on the falling edge. When a valid edge
appears on the RBx/INTx pin, the corresponding flag
bit, INTxF, is set. This interrupt can be disabled by
clearing the corresponding enable bit, INTxE. Flag bit,
INTxF, must be cleared in software in the Interrupt
Service Routine before re-enabling the interrupt.
All external interrupts (INT0, INT1 and INT2) can wakeup
the processor from Idle or Sleep modes if bit INTxE
was set prior to going into those modes. If the Global
Interrupt Enable bit, GIE, is set, the processor will
branch to the interrupt vector following wake-up.
Interrupt priority for INT1 and INT2 is determined by the
value contained in the interrupt priority bits, INT1IP
(INTCON3<6>) and INT2IP (INTCON3<7>). There is
no priority bit associated with INT0. It is always a high
priority interrupt source.

----------------------------------------------------
I have set (I think) the appropriate bits for B0 and B1 to be interrupts. How can I tell the difference in an interrupt routine tho?? Since I am not watching for B1 to go high.... ????

Tom

cupajoe
- 1st April 2005, 17:05
Tom,

The value of the pot doesn't really matter as long as its over say 5k. You see you are going to set up a voltage divider across Vcc and ground with the wiper going to the A/D pin. Then I simply read the value of the A/D (0 -1023) and I divide it to give me the number of selections I need. Here is an example. Note the tmr1cnt variable. TMR1 is free running with a 1mS period and tmr1cnt is a variable that is incremented every 1mS. This is compared to mud(menu update) to provide the timing to smoothly update the display. The other timing is provided by the variable "I". It is updated every 1mS if a button is pressed. It is cleared if the button is released. This variable is compared to MBP(menu button press) to debounce the button and ensure that it was held in for a bit.

read_pot:
adcin 3, adval3
y = adval3 / 204
return

SETUP_MENU:
if tmr1cnt > mud then
tmr1cnt = 0
gosub read_pot
x = x + 1
endif
select case y
case 0
lcdout $FE,$C0,"->Reset Counters"
case 1
lcdout $FE,$C0,"->Set Rej. Dwell"
case 2
lcdout $FE,$C0,"->Set Threshold "
case 3
lcdout $FE,$C0,"->Prog. Scanner "
case 4,5
lcdout $FE,$C0,"->Exit Setup "
end select
if i > MBP then
select case y
case 0
tmr1cnt = 0
i = 0
gosub reset_counters
case 1
tmr1cnt = 0
i = 0
gosub set_dwell
case 2
tmr1cnt = 0
i = 0
gosub set_thrsh
case 3
tmr1cnt = 0
i = 0
gosub prog_scan
case 4,5
@ BSF 0x06,1 ;SCANNER Off
gosub SETUP_LCD
goto main
end select
endif
goto SETUP_MENU


Hope this helps

Joe