LCD menu for 16F628A in Asm

Discussion in 'Embedded Systems and Microcontrollers' started by TCOP, Aug 6, 2012.

  1. TCOP

    Thread Starter Member

    Apr 27, 2011
    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.
  2. osx-addict


    Feb 9, 2012
    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!
  3. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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.


    Code ( (Unknown Language)):
    1. 1234567890123456  (just the character counter)
    2. Time 12:12:12 AM
    3. 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.
  4. TCOP

    Thread Starter Member

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

    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.

    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: Aug 6, 2012
  5. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    Well... since I don't write anything but the simplest app in assembly any more...

    I'd start with suggesting to use a PIC with more pins and code it in C. :D
  6. TCOP

    Thread Starter Member

    Apr 27, 2011
    that was a kick bellow the belt... :)
  7. osx-addict


    Feb 9, 2012
    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...
    TCOP likes this.
  8. takao21203

    Distinguished Member

    Apr 28, 2012
    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.
  9. BMorse

    Senior Member

    Sep 26, 2009
    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


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

    Thread Starter Member

    Apr 27, 2011
    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.
  11. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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.

  12. TCOP

    Thread Starter Member

    Apr 27, 2011
    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:
    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
  13. MMcLaren

    Well-Known Member

    Feb 14, 2010
    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...

    Code ( (Unknown Language)):
    2.         lcdtab  line1+1         ; line 1, tab 2
    3.         lcdstr  "AOS: "         ;
    4.         lcdtab  line2+1         ; line 2, tab 2
    5.         lcdstr  "LOS: "         ;
    Regards, Mike
    Last edited: Aug 6, 2012
  14. TCOP

    Thread Starter Member

    Apr 27, 2011
    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
  15. TCOP

    Thread Starter Member

    Apr 27, 2011
    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
    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
  16. JohnInTX


    Jun 26, 2012
    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.

    My advice exactly but might I add, if you are just starting out, consider the 18F. Way easier to do this kind of thing.

    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: Aug 8, 2012
  17. TCOP

    Thread Starter Member

    Apr 27, 2011
    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: Aug 9, 2012
  18. JohnInTX


    Jun 26, 2012
    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...

    Code ( (Unknown Language)):
    2. ;************************** TABLE 16 EXAMPLE **************************
    3.     ; Untested but you get the idea - not exactly optimized either..
    5.     INCLUDE ""
    7.     cblock (20h)
    8.         TablAddrL: 1    ; declare 2 byte table address
    9.         TablAddrH: 1
    10.         savePCLATH:1    ; saves PCLATH during table ops
    11.         TargetBuf: .32    ; somewhere to put it
    12.     endc
    15.     ORG 0
    17.     ;---------------------------
    18.     ; typical code flow. Could automate this setup with a macro..
    19.     ;...
    20.     movlw    TargetBuf            ; Set target buffer
    21.     movwf    FSR                    ; FSR-> where data goes
    22.     bcf        STATUS,IRP
    24.     movlw    low (Text30)        ; set ROM address of data to get
    25.     movwf    TablAddrL
    26.     movlw    high (Text30)    
    27.     movwf    TablAddrH
    29.     call    GetData_FSR            ; gets string from ROM to *FSR
    30.     ;*
    31.     goto    0                    
    33.     ;-------------------------------------------
    34.     ; GetData_FSR Copies RETLW xx ROM data from
    35.     ; TablAddrH:L to *IRP:FSR
    36. GetData_FSR:
    37.     movf    PCLATH,W            ; save PCLATH of current page
    38.     movwf    savePCLATH
    40.     call    _GetLoop            ; fetches data from TablPtr until \0
    42.     movf    savePCLATH,W        ; restore PCLATH
    43.     movwf    PCLATH
    44.     return
    46.     ;----------------------------------------------
    47.     ; Gets data from ROM at *TablAddrH:L ->*FSR until
    48.     ; data is 00h. Doesn't store 00h
    50. _GetLoop:
    51.     call    lookup16_W            ; fetch one byte via RETLW xx
    52.     iorlw    0                    ; set flags on W (look for \0 to terminate string)
    53.     skpnz
    54.     return                        ; done on data==Z
    56.     movwf    INDF                ; else, got one byte, save it at *FSR
    57.     incf    FSR,F                ; FSR++
    59.     incf    TablAddrL,F            ; bump 'table' pointer to next RETLW xx
    60.     skpnz
    61.     incf    TablAddrH,F
    62.     goto    _GetLoop
    64.     ;-------------------------------------------------
    65.     ; Jams TablAddrH:L onto PCLATH:PCL to force a jump
    67. lookup16_W:
    68.     movf    TablAddrH,W            ; forced goto to TablAddrH:L
    69.     movwf    PCLATH
    70.     movf    TablAddrL,W            ; returns through RETLW xx
    71.     movwf    PCL
    75.     ORG    600h        ; your table, page 1 (could be anywhere)
    76. TableStart:
    77. Text1    dt    "This is text 1",0
    78. Text2    dt    "This is text 2",0
    79.     ;.. .blah blah
    81.     ; Table runs past 600h into page 700h.. but ORG is not needed
    83.     ; blah blah
    84.     ORG 700h
    85. Text30    dt    "Text 30 is in 7xx",0
    87.     END
    Eric007 and TCOP like this.
  19. TCOP

    Thread Starter Member

    Apr 27, 2011
    thanks. I get it.
    I'll switch to your technique.
  20. MMcLaren

    Well-Known Member

    Feb 14, 2010
    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.

    Code ( (Unknown Language)):
    1. ;
    2. ;  usage example
    3. ;
    4.         lcdtab  line1+3         ; line 1 tab 4
    5.         lcdstr  "23:59:59"      ;
    Code ( (Unknown Language)):
    1. ;
    2. ;  lcdstr - print in-line character string
    3. ;
    4. lcdstr  macro   str
    5.         local   String, Print
    6.         movlw   low String      ;
    7.         movwf   ptrl            ;
    8.         movlw   high String     ;
    9.         movwf   ptrh            ;
    10.         goto    Print           ;
    11. String  dt      str,0
    12. Print   call    PutStr          ; print string
    13.         endm
    Code ( (Unknown Language)):
    3. ;
    4. ;  PutStr sends a character string to the LCD
    5. ;
    6. ;  Entry: setup ptrl and ptrh to string address before entry
    7. ;     string must be terminated with a 00 byte
    8. ;
    9. PutStr
    10.         call    GetTable        ; get a table character           |B0
    11.         andlw   b'11111111'     ;                                 |B0
    12.         skpnz                   ; 00 byte, last character?        |B0
    13.         return                  ; yes, return                     |B0
    14. ;       pagesel putdat          ;                                 |B0
    15.         call    putdat          ; else, output character          |B0
    16.         incf    ptrl,F          ; increment pointer               |B0
    17.         skpnz                   ;                                 |B0
    18.         incf    ptrh,F          ;                                 |B0
    19. ;       pagesel PutStr          ;                                 |B0
    20.         goto    PutStr          ;                                 |B0
    21. GetTable
    22.         movf    ptrh,W          ;                                 |B0
    23.         movwf   PCLATH          ;                                 |B0
    24.         movf    ptrl,W          ;                                 |B0
    25.         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.

    Code ( (Unknown Language)):
    1. ;
    2. ;  lcdstr macro for 18F devices
    3. ;
    4. lcdstr  macro   str             ; print in-line string macro
    5.         call    PutStr          ;
    6.         db      str,0
    7.         end
    Code ( (Unknown Language)):
    1. ;
    2. ;  PutStr - print in-line string via Stack and TBLPTR
    3. ;
    4. ;  string must be terminated with a 00 byte and does not need
    5. ;  to be word aligned
    6. ;
    7. PutStr
    8.         movff   TOSL,TBLPTRL    ; copy return address into TBLPTR
    9.         movff   TOSH,TBLPTRH    ;
    10.         clrf    TBLPTRU     ; assume PIC with < 64-KB
    11. PutNext
    12.         tblrd   *+              ; get in-line string character
    13.         movf    TABLAT,W        ; last character (00)?
    14.         bz      PutExit         ; yes, exit, else
    15.         rcall   putdat          ; send character to LCD
    16.         bra     PutNext         ; and do another
    17. PutExit
    18.         btfsc   TBLPTRL,0       ; odd address?
    19.         tblrd   *+              ; yes, make it even (fix PC)
    20.         movf    TBLPTRH,W       ; setup new return address
    21.         movwf   TOSH            ;
    22.         movf    TBLPTRL,W       ;
    23.         movwf   TOSL            ;
    24.         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.

    Code ( (Unknown Language)):
    1. ;
    2. ;  lcdstr macro for 16F "enhanced mid-range" devices
    3. ;
    4. lcdstr  macro   str             ; print in-line string macro
    5.         call    putstr          ;
    6.         dt      str,0
    7.         end
    Code ( (Unknown Language)):
    2. ;
    3. ;  putstr subroutine - pull in-line string address from top of
    4. ;  stack, print string up to the null terminator, then return
    5. ;  to the address immediately following the in-line string.
    6. ;
    7. putstr
    8.         banksel TOSL            ; bank 31                         |B31
    9.         movf    TOSL,W          ; top of stack lo                 |B31
    10.         movwf   FSR1L           ;                                 |B31
    11.         movf    TOSH,W          ; top of stack hi                 |B31
    12.         movwf   FSR1H           ;                                 |B31
    13.         bsf     FSR1H,7         ; set FSR1.15 for rom access      |B31
    14. getchar
    15.         moviw   INDF1++         ; null terminator?                |B?
    16.         bz      putexit         ; yes, exit, else                 |B?
    17.         call    putdat          ; send char to LCD                |B?
    18.         bra     getchar         ; loop                            |B?
    19. putexit
    20.         banksel TOSL            ; bank 31                         |B31
    21.         movf    FSR1L,W         ; adjust return address           |B31
    22.         movwf   TOSL            ;                                 |B31
    23.         movf    FSR1H,W         ;                                 |B31
    24.         movwf   TOSH            ;                                 |B31
    25.         movlb   0               ; bank 0                          |B0
    26.         return                  ;                                 |B0
    Good luck with your project.

    Regards, Mike
    Last edited: Aug 11, 2012
    Eric007 likes this.