LCD menu for 16F628A in Asm

Thread Starter

TCOP

Joined Apr 27, 2011
94
So my project requires a menu with submenus. I have used a shift register to serially communicate with HD44780 16x2 and everything works fine. So, only 3 pins are involved for writting bytes to the LCD.
From the software side, I've made two look up tables. One holding the text to be written in the lcd and an other one holding info such as address of text, action on enter pressed etc. So far I have built the menu and responds properly as it was supposed to. Menu is like this:
------------------------
Set Time <-
Set Date
------------------------
Set Delay 1
Set Delay 2
Line 5
Line 6
...
Line 10

I am striving on finding a nice solution on how to proceed after the user presses enter and proceeds.
The problem is that for each choice, the buttons functions whould be different. In some cases the up/down buttons should move Left/Right, in other cases should react again as Up/down and the data that the user should insert have different restrictions e.g. number ranging 1-10, or 1-59 or Letters etc.
I need some tips about handling this.
 

osx-addict

Joined Feb 9, 2012
122
one thing comes to mind perhaps is a state machine.. Basically when you move into a particular 'state', only certain button pushes apply and once a valid button push is found you move to the next state which would have a different set of available actions.. Using something like this, you could probably have a table for each state indicating the available actions/button presses that apply and what they map to functionality wise.. HTH!
 

ErnieM

Joined Apr 24, 2011
8,377
You don't say how many buttons you have. I like to use 5 buttons (along with an LCD this is only 7 or 8 I/O lines) arranges for up/down/left/right/enter. For three buttons I would call them plus/minus/enter.

You don't say what parameters you want to set besides Time & Date. For these you could just present the time (or date) with a carrot underneath to indicate which is being changed.

Example:

Rich (BB code):
1234567890123456  (just the character counter)
Time 12:12:12 AM
Set     ^
With 5 buttons it's simple to "drive" this menu. With but 3 you have more work to do, but I would just make enter advance to the next digit.
 

Thread Starter

TCOP

Joined Apr 27, 2011
94
Thank you both for your rapid replies.

@osx-addict
I think I see your point. I also did some googling about this. I had no idea there was a math model about "state machine". Please, add some more explicable comments about how to implement it.

@ErnieM
My design is using 3 buttons and I'd like to stick with this because I have saved only 2 more i/o pins on the μC for future need. I didn't thought that it would be so difficult using three buttons.
your example is what I was thinking too but I was ready to design an upward arrow cause i hadn't notice the 'carrot' in the char set. You saved me time already :)
Suppose the user presses enter on "set time". A new routine should be executed in order to present the current time and allow the user to change it. What I've done, is to save the segment and the offset (PCLATH/PCL) of the routine in the look up table i mentioned earlier and redirect the code execution to there.
Well this works but I haven't seen it any where else and I am thinking if there is an other optimal solution.

What do you suggest?
 
Last edited:

osx-addict

Joined Feb 9, 2012
122
This is the game I'm currently playing -- the old game of which processor is best for my task at hand -- while cheap is a good way to go, it may require more programming work in the end.. As for more info on using state machines, you can read up about them -- they're used all over the place -- here's one sample reading for your amusement.. I'm not suggesting it here as the be-all-end-all solution but just one of many...
 

takao21203

Joined Apr 28, 2012
3,702
So my project requires a menu with submenus. I have used a shift register to serially communicate with HD44780 16x2 and everything works fine. So, only 3 pins are involved for writting bytes to the LCD.
From the software side, I've made two look up tables. One holding the text to be written in the lcd and an other one holding info such as address of text, action on enter pressed etc. So far I have built the menu and responds properly as it was supposed to. Menu is like this:
------------------------
Set Time <-
Set Date
------------------------
Set Delay 1
Set Delay 2
Line 5
Line 6
...
Line 10

I am striving on finding a nice solution on how to proceed after the user presses enter and proceeds.
The problem is that for each choice, the buttons functions whould be different. In some cases the up/down buttons should move Left/Right, in other cases should react again as Up/down and the data that the user should insert have different restrictions e.g. number ranging 1-10, or 1-59 or Letters etc.
I need some tips about handling this.
I have done LCD menu in assembler a while ago.
Basically in a table I hold information which variable is to be modified, and which action is to be taken.

It was also neccessary to run a parser over the table and build up a table with index entries into all the menu topics. Even sub menu is possible.

All this results in many many pages assembler and is quite a pain. I made it working within two weeks or so.

If you use C, one day to make it working, another to clean it up and optimize it.

Actually I used a 1K PIC, some data was moved into an EEPROM.

Take a 18f24j10, use 4-bit mode, and C language. You could make a much comfortable menu + it is far more easy to code it.
 

BMorse

Joined Sep 26, 2009
2,675
I am currently working on a project involving a 16x2 LCD display with 3 user buttons (set up all 3 in a row), when under "normal" operation, the left and right buttons don't do anything, the middle button toggles back light on (times out in an interrupt routine, and goes off after system is idle for 1 minute) when the backlight is on, if the middle button is pressed it goes into the "Menu Mode", I use the top row of the LCD as the User display portion, and the bottom serves as button use indicator since the buttons are right under the LCD, example:

first line : ***MAIN MENU***
second : < Exit >

So in this state if the user presses the center switch it will exit menu mode,
if the user uses the left or right switch, it will scroll to the next/previous menu option, but then the "button use" indicator changes to reflect the new option:

first line : Set <DATE><TIME>
second : < Select >

now in this state, if the center switch is pressed, it will run a separate function that is used so the user can set the Date and Time... and of course once in that function, the buttons should change accordingly...

first line : 19:25 08/06/12
second : - Set +

I use the underline cursor to indicate which one is currently selected, and when the user presses the center switch it will advance to the next char, and the left will decrement the count, the right will increment the count/value

etc....

so each one of these "button use indicator" is predefined and I will just display the appropriate one when needed....
 

Thread Starter

TCOP

Joined Apr 27, 2011
94
Thanks everyone! Lots of great ideas!
I cant switch to other controller now as I have done half the job + the pcb. My only other option is to upgrade to 16F648A in case i'll need more memory which i have on hand and needs no modifications in the code.
@Bmorse. I like your idea. I have set my LCD presenting data in both lines and the scrolling is vertical. I'll think a little of your set up and see of a way to marry both.

I'll post the results in next few days.
Any other suggestions are still welcomed.
 

ErnieM

Joined Apr 24, 2011
8,377
...My only other option is to upgrade to 16F648A in case i'll need more memory which i have on hand and needs no modifications in the code...
I prefer to start a project with the largest PIC that fits in pin count and other modules. That way when it is done I *may* be able to switch to a cheaper device.

On one project I actually did switch to another device once.

Once.
 

Thread Starter

TCOP

Joined Apr 27, 2011
94
You are right.
I did the same. As i told you before, I saved 2 pins for future use :)
In case I go with your solution and use 4 push buttons, I'll still have one pin free.


By the way I found a better way of saving the sub routine address on the table. Instead of the obscure way I used for pushing pclath and pcl in stack and then indirectly calling the saved address, I placed at the and of the table a call and a return command:
e.g.
Tbl1 dt byte1,byte2,byte3...byte6
call SubTbl1, return
Tbl2 dt byte1,byte2,byte3...byte6
call SubTbl2, return

I get a retlw only for the first 6 bytes and then i perform a call on the 7th byte
 

MMcLaren

Joined Feb 14, 2010
861
Why do a "call" followed by a "return" when a "goto" would accomplish the same thing while using one less stack level and one less word of program memory?

Can you tell us how many pins you're using now with your shift register based solution? Depending on how you've designed it, you may not need an extra pin for the additional push buttons.

Finally, have you considered storing strings inline with your code. Program flow may seem more intuitive for many programmers and the method may save some program memory. Please let me know if you'd like details...

Rich (BB code):
        lcdtab  line1+1         ; line 1, tab 2
        lcdstr  "AOS: "         ;
        lcdtab  line2+1         ; line 2, tab 2
        lcdstr  "LOS: "         ;
Regards, Mike
 

Attachments

Last edited:

Thread Starter

TCOP

Joined Apr 27, 2011
94
Sure I can,
look at the attached file. This works but i will reverse the order of LCD_07 to 04. So LCD_07 should be connected to Q3, LCD_06 to Q2 and so on.
It leads to less code this way.
I don't undestand what you say about 'goto'? and yes, i need more details about string manipulation
 

Attachments

Thread Starter

TCOP

Joined Apr 27, 2011
94
Ok...
I've finished with the menus and submenus.
A small problem occured. My text is larger than 0x100 bytes. Any ideas how to configure pclath on runtime in order to set PCL correctly?
I use this:
org 600
Text
addwf PCL, F
Text1 dt "xxx",0
Text2 dt "xxx",0
....
Text30 dt "xxx",0 -> this goes on segment 700 and I get an error when I set the PCL here, since the PCLATH was set to point on 600: addwf PCL, F
 

JohnInTX

Joined Jun 26, 2012
4,787
You'll have to make your index (offset) 16 bits. Calculate the effective target first using a base of 600h (or whatever). Then write the upper result to PCLATH, and the lower to PCL. Watch out for crossing the ROM bank limits and if you cross them, be sure to save/reset PCLATH to the current bank when you are done fetching from the table. The only other solution in midrange is to break the tables into <256 byte pages. Don't forget that if your data spans page ends, you'll have to propagate that into PCLATH before the move to PCL.

I prefer to start a project with the largest PIC that fits in pin count and other modules. That way when it is done I *may* be able to switch to a cheaper device.
My advice exactly but might I add, if you are just starting out, consider the 18F. Way easier to do this kind of thing.

On one project I actually did switch to another device once.
To date, I've never had a client that wanted to make the downsize. They didn't want to lock themselves out of updates.

I once did a 16xx table processor that used the full 16 bits and could span ROM banks at run-time. Now I use the 18. But if your problem persists, I can see if I can find the code.

But.. if this is a new project, port it. Now. I already know where you are going and you won't like it.
 
Last edited:

Thread Starter

TCOP

Joined Apr 27, 2011
94
I dont get it.
I do compute the Pclath and I set it on 6.
Then everything goes ok until a text exceeds the 256bytes boundary. My question is how to set correctly the Pclath. My problem is that Text1 and text 30 have the same offset = 0x01 but the first is on PCLATH=6 and the other on 7. I cant think of a neat way to calculate it.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
Text1 and text 30 have the same offset = 0x01
You are missing the point. They have the same offset if the offset is only 8 bits. You need to maintain and calculate a 16 bit table address and maintain PCLATH as well. Here's something I whipped up for you to give you the idea. It assembles and works in MPSIM. The main point is that you calculate a FULL ROM address and jam it onto the program counter (which hopefully points to a retlw!) You should take steps to see that your calculated address is valid etc etc...

Rich (BB code):
;************************** TABLE 16 EXAMPLE **************************
    ; Untested but you get the idea - not exactly optimized either..

    INCLUDE "p16F648a.inc"

    cblock (20h)
        TablAddrL: 1    ; declare 2 byte table address
        TablAddrH: 1
        savePCLATH:1    ; saves PCLATH during table ops
        TargetBuf: .32    ; somewhere to put it
    endc


    ORG 0

    ;---------------------------
    ; typical code flow. Could automate this setup with a macro..
    ;...
    movlw    TargetBuf            ; Set target buffer
    movwf    FSR                    ; FSR-> where data goes
    bcf        STATUS,IRP

    movlw    low (Text30)        ; set ROM address of data to get
    movwf    TablAddrL
    movlw    high (Text30)    
    movwf    TablAddrH

    call    GetData_FSR            ; gets string from ROM to *FSR
    ;*
    goto    0                    

    ;-------------------------------------------
    ; GetData_FSR Copies RETLW xx ROM data from
    ; TablAddrH:L to *IRP:FSR 
GetData_FSR:
    movf    PCLATH,W            ; save PCLATH of current page
    movwf    savePCLATH

    call    _GetLoop            ; fetches data from TablPtr until \0

    movf    savePCLATH,W        ; restore PCLATH 
    movwf    PCLATH
    return

    ;----------------------------------------------
    ; Gets data from ROM at *TablAddrH:L ->*FSR until
    ; data is 00h. Doesn't store 00h

_GetLoop:
    call    lookup16_W            ; fetch one byte via RETLW xx
    iorlw    0                    ; set flags on W (look for \0 to terminate string)
    skpnz
    return                        ; done on data==Z

    movwf    INDF                ; else, got one byte, save it at *FSR
    incf    FSR,F                ; FSR++

    incf    TablAddrL,F            ; bump 'table' pointer to next RETLW xx
    skpnz
    incf    TablAddrH,F
    goto    _GetLoop

    ;-------------------------------------------------
    ; Jams TablAddrH:L onto PCLATH:PCL to force a jump

lookup16_W:
    movf    TablAddrH,W            ; forced goto to TablAddrH:L
    movwf    PCLATH
    movf    TablAddrL,W            ; returns through RETLW xx
    movwf    PCL



    ORG    600h        ; your table, page 1 (could be anywhere)
TableStart:
Text1    dt    "This is text 1",0
Text2    dt    "This is text 2",0
    ;.. .blah blah

    ; Table runs past 600h into page 700h.. but ORG is not needed

    ; blah blah
    ORG 700h
Text30    dt    "Text 30 is in 7xx",0

    END
 

MMcLaren

Joined Feb 14, 2010
861
... i need more details about string manipulation
Inline strings for 16F devices (usage example, macro, and driver):

Strings can straddle a 256 word memory boundary or be longer than 256 words without problems. Overhead is 13 words of program memory for the driver subroutine plus 6 words for each inline string.

Rich (BB code):
;
;  usage example
;
        lcdtab  line1+3         ; line 1 tab 4
        lcdstr  "23:59:59"      ;
Rich (BB code):
;
;  lcdstr - print in-line character string
;
lcdstr  macro   str
        local   String, Print
        movlw   low String      ;
        movwf   ptrl            ;
        movlw   high String     ;
        movwf   ptrh            ;
        goto    Print           ;
String  dt      str,0
Print   call    PutStr          ; print string
        endm
Rich (BB code):
;
;  PutStr sends a character string to the LCD
;
;  Entry: setup ptrl and ptrh to string address before entry
;	  string must be terminated with a 00 byte
;
PutStr
        call    GetTable        ; get a table character           |B0
        andlw   b'11111111'     ;                                 |B0
        skpnz                   ; 00 byte, last character?        |B0
        return                  ; yes, return                     |B0
;       pagesel putdat          ;                                 |B0
        call    putdat          ; else, output character          |B0
        incf    ptrl,F          ; increment pointer               |B0
        skpnz                   ;                                 |B0
        incf    ptrh,F          ;                                 |B0
;       pagesel PutStr          ;                                 |B0
        goto    PutStr          ;                                 |B0
GetTable
        movf    ptrh,W          ;                                 |B0
        movwf   PCLATH          ;                                 |B0
        movf    ptrl,W          ;                                 |B0
        movwf   PCL             ;                                 |B0
Inline strings for 18F devices (macro and driver):

Usage example is the same. Storing two characters per word of program memory is more efficient, as is pulling the string address from the stack. Overhead is 17 words of program memory for the driver subroutine and one word for each inline string.

Rich (BB code):
;
;  lcdstr macro for 18F devices
;
lcdstr  macro   str             ; print in-line string macro
        call    PutStr          ;
        db      str,0
        end
Rich (BB code):
;
;  PutStr - print in-line string via Stack and TBLPTR
;
;  string must be terminated with a 00 byte and does not need
;  to be word aligned
;
PutStr
        movff   TOSL,TBLPTRL    ; copy return address into TBLPTR
        movff   TOSH,TBLPTRH    ;
        clrf	TBLPTRU		; assume PIC with < 64-KB
PutNext
        tblrd   *+              ; get in-line string character
        movf    TABLAT,W        ; last character (00)?
        bz      PutExit         ; yes, exit, else
        rcall   putdat          ; send character to LCD
        bra     PutNext         ; and do another
PutExit
        btfsc   TBLPTRL,0       ; odd address?
        tblrd   *+              ; yes, make it even (fix PC)
        movf    TBLPTRH,W       ; setup new return address
        movwf   TOSH            ; 
        movf    TBLPTRL,W       ;
        movwf   TOSL            ;
        return                  ;

Inline strings for 16F "enhanced mid-range" devices (macro and driver):

Usage example is the same. This method is similar to the 18F example since "enhanced mid-range" parts provide access to the stack. Overhead is 18 words of program memory for the driver subroutine and one word for each inline string.

Rich (BB code):
;
;  lcdstr macro for 16F "enhanced mid-range" devices
;
lcdstr  macro   str             ; print in-line string macro
        call    putstr          ;
        dt      str,0
        end
Rich (BB code):
;
;  putstr subroutine - pull in-line string address from top of
;  stack, print string up to the null terminator, then return
;  to the address immediately following the in-line string.
;
putstr
        banksel TOSL            ; bank 31                         |B31
        movf    TOSL,W          ; top of stack lo                 |B31
        movwf   FSR1L           ;                                 |B31
        movf    TOSH,W          ; top of stack hi                 |B31
        movwf   FSR1H           ;                                 |B31
        bsf     FSR1H,7         ; set FSR1.15 for rom access      |B31
getchar
        moviw   INDF1++         ; null terminator?                |B?
        bz      putexit         ; yes, exit, else                 |B?
        call    putdat          ; send char to LCD                |B?
        bra     getchar         ; loop                            |B?
putexit
        banksel TOSL            ; bank 31                         |B31
        movf    FSR1L,W         ; adjust return address           |B31
        movwf   TOSL            ;                                 |B31
        movf    FSR1H,W         ;                                 |B31
        movwf   TOSH            ;                                 |B31
        movlb   0               ; bank 0                          |B0
        return                  ;                                 |B0
Good luck with your project.

Regards, Mike
 
Last edited:
Top