News:

;) This forum is the property of Proton software developers

Main Menu

Displaying on 4 by 4 LCD error?

Started by John Drew, Aug 23, 2024, 05:49 AM

Previous topic - Next topic

John Drew

Greetings all,
I've been experimenting with Les's code for a 4*40 display. I used it successfully with earlier compiler versions but with the latest compiler and maybe a version or two before I'm getting a wrong response. What should display at the beginning of line 2 is displaced inward. See attached image and file. The code is as Les wrote but I've added a simple print to line 2 at each half of the program to illustrate the problem. It seems as if the cursor is not being reset?
I'm also experiencing maths errors in another program when compiling in the latest(?) compiler versions, but one step at a time.

Help! What have I done wrong?
John

PS Forgot to mention latest compiler and using ISIS at present with a different PIC (18F4685) than the one used by Les
Just tried 18f25K20 and 18F452 - same problem

top204

#1
The code is still functioning OK with the very latest compiler version, which is version 4.0.4.5.

I re-compiled the original code I created a few years ago and also created a proteus simulation with two alphanumeric LCDs, and it runs OK.

The original demo code for the multiple LCDs controlled by a single microcontroller is listed below:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Interface to two Hitachi alphanumeric LCDs independently with an 18F device.
'
' The listing's __print_ subroutine replaces the compiler's Print command's library subroutine
' But allows two EN lines to be established, so two LCDs are interfaced seperately
'
' Written for the Positron8 compiler by Les Johnson
'
    Device = 18F25K20                                   ' Tell the compiler what device to compile for
    Declare Xtal = 16                                   ' Tell the compiler the frequency the device is operating at (in MHz)
    #Disable Print                                      ' Disable the compiler's print library routine
'
' Setup the alphanumeric LCD
'
    Declare LCD_DTPin = PORTB.4                         ' LCD lines D4 to D7 connect to the PIC's PORTB.4 to PORTB,.7 pins
    Declare LCD_RSPin = PORTB.3                         ' Connects to the LCD RS lines
    Declare LCD_Interface = 4                           ' Using a 4-bit interface
    Declare LCD_Lines = 2                               ' Using a 2 line LCD
    Declare LCD_Type = Alpha                            ' Using an Hitachi Alphanumeric LCD
    Declare LCD_CommandUs = 2000                        ' The delay after commands sent to the LCD
    Declare LCD_DataUs = 50                             ' The delay after data is sent to the LCD
'
' Setup the pins to use for the LCD EN lines
'
    Symbol LCD_EN1_Pin = PORTB.1                        ' Connects to LCD1's EN line
    Symbol LCD_EN2_Pin = PORTB.2                        ' Connects to LCD2's EN line
'
' Create variables for the __print_ routine
'
    Dim PP0               As Byte System
    Dim PP0H              As Byte System
    Dim BPF               As Byte System
    Dim PP3               As Byte System
    Dim PP3H              As Byte System
    Dim LCD_bFlags        As Byte Access = 0            ' Flags used by the LCD routine
    Dim LCD_tInitialised  As LCD_bFlags.0               ' Holds 1 if an LCD is initialised
    Dim LCD1_tInitialised As LCD_bFlags.1               ' Holds 1 if LCD1 is initialised
    Dim LCD2_tInitialised As LCD_bFlags.2               ' Holds 1 if LCD2 is initialised
    Dim LCD_tNumber       As LCD_bFlags.3               ' Holds 0 for LCD1, 1 for LCD2
'
' Create some aliases to make the code's operation clearer
'
    Dim LCD_bByteOut      As PP3
    Dim LCD_bWREGStore    As PP3H
    Dim LCD_tComOrData    As BPF.0
'
' Create two variables for the demo
'
    Dim Demo_wCounter1 As Word
    Dim Demo_wCounter2 As Word

'------------------------------------------------------------------------------------------------
' Clear an LCD
' Input     : pLCD is 1 for LCD1, 2 for LCD2
' Output    : None
' Notes     : None
'
$define LCD_Cls(pLCD) '
    $if pLCD = 1      '
    LCD_tNumber = 0   '
    Cls               '
    $elseif pLCD = 2  '
    LCD_tNumber = 1   '
    Cls               '
    $else             '
        $error "Incorrect LCD number. 1 or 2 only" '
    $endif

'------------------------------------------------------------------------------------------------
' Choose an LCD
' Input     : pLCD is 1 for LCD1, 2 for LCD2
' Output    : None
' Notes     : None
'
$define LCD_Number(pLCD) '
    $if pLCD = 1         '
    LCD_tNumber = 0      '
    $elseif pLCD = 2     '
    LCD_tNumber = 1      '
    $else                '
        $error "Incorrect LCD number. 1 or 2 only" '
    $endif

'------------------------------------------------------------------------------------------------
' The main program starts here
' Interface to two seperate LCDs and display a count on them
'
Main:
    LCD_Cls(1)                                                      ' Clear the display of LCD1
    LCD_Cls(2)                                                      ' Clear the display of LCD2

    Do                                                              ' Create a loop
        LCD_Number(1)                                               ' Choose LCD1 for the Print command
        Print At 1, 1, "LCD1: ", Dec Demo_wCounter1, "     ",       ' Display Demo_wCounter1 on LCD1
              At 2, 1, "LCD1: ", Dec Demo_wCounter1 + 1, "     "    ' Display Demo_wCounter1 on LCD1

        LCD_Number(2)                                               ' Choose LCD2 for the Print command
        Print At 1, 1, "LCD2: ", Dec Demo_wCounter2, "     ",       ' Display Demo_wCounter2 on LCD2
              At 2, 1, "LCD2: ", Dec Demo_wCounter2 + 1, "     "    ' Display Demo_wCounter2 on LCD2

        Demo_wCounter1 = Demo_wCounter1 + 1                         ' Increment Demo_wCounter1
        Demo_wCounter2 = Demo_wCounter2 + 10                        ' Increment Demo_wCounter2 differently
        DelayMS 100                                                 ' A delay so we can see the counts happening
    Loop                                                            ' Do it forever

'------------------------------------------------------------------------------------------------
' Replacement library routine for the Print command that allows two LCDs to be interfaced independently
' Input     : WREG holds the byte to send to the LCD
'           : LCD_tNumber holds the LCD to access. 0 is LCD1, 1 is LCD2
'           : LCD1_tInitialised is 1 if LCD1 has been initialised, else 0
'           : LCD2_tInitialised is 1 if LCD2 has been initialised, else 0
' Output    : WREG still holds the byte that was sent
' Notes     : Uses two Symbols for the two seperate LCD EN pins: LCD_EN1_Pin and LCD_EN2_Pin
'           : The subroutine is for a 4-bit interface on a standard 18F device, and uses the compiler's LCD Declares
'
    GoTo _LCD_Over_                                     ' Jump over the compiler's replacement library subroutine
__print_:
    LCD_bWREGStore = WREG                               ' Save the byte sent
    If LCD_tNumber = 0 Then                             ' Is it LCD1 that is being accessed?
        PinLow LCD_EN1_Pin                              ' Yes. So pull the EN pin to output low
    Else                                                ' Otherwise... It is LCD2 that is being accessed
        PinLow LCD_EN2_Pin                              ' So. Pull the EN pin to output low
    EndIf
    PinLow __LCD_RSPORT.__LCD_RSPIN                     ' Pull the RS pin to output low
    #if(__LCD_DTPIN == 0)
        WREG = $F0
    #else
        WREG = $0F
    #endif
    Andwf (__LCD_DTPORT + _TRIS_offset)                 ' Set the correct half of the port to output on a standard 18F device
    If LCD_tNumber = 0 Then                             ' Is it LCD1 that is being accessed?
        LCD_tInitialised = LCD1_tInitialised            ' Yes. So transfer LCD1_tInitialised to LCD_tInitialised
    Else                                                ' Otherwise... It is LCD2 that is being accessed
        LCD_tInitialised = LCD2_tInitialised            ' So. Transfer LCD2_tInitialised to LCD_tInitialised
    EndIf
    WREG = LCD_bWREGStore                               ' Get back the byte that is to be sent to the LCD
    If LCD_tInitialised = 0 Then                        ' Has the LCD been initialised?
        DelayUS 15000                                   ' No. So wait 15ms
        LCD_bByteOut = $33                              ' Initialise the LCD
        GoSub _LCD_NibbleSend_                          ' Send init
        DelayUS 5000                                    ' Wait 5ms
        GoSub _LCD_NibbleSend_                          ' Send init again
        DelayUS 100                                     ' Wait 100us
        GoSub _LCD_NibbleSend_                          ' Send init one more time
        DelayUS 100                                     ' Wait 100us
        LCD_bByteOut = $22                              ' Set 4-bit mode
        GoSub _LCD_NibbleSend_                          ' Set the interface to 4-bit mode
        #ifdef __LCD_LINES                              ' Has the LCD_Lines declare been used in the main program?
            #if(__LCD_LINES == 1)                       ' Yes. So...
                WREG = $20                              ' Set for 4-bit mode, 1 line, 5x7 font
            #else                                       ' Otherwise...
                WREG = $28                              ' Set for 4-bit mode, 2+ lines, 5x7 font
            #endif
        #else                                           ' Otherwise... Default to 2 lines
            WREG = $28                                  ' Set for 4-bit mode, 2+ lines, 5x7 font
        #endif
        GoSub _LCD1_SetForCommand_                      ' Send a command
        WREG = $0C                                      ' Set for display on, no cursor, no blink
        GoSub _LCD1_SetForCommand_
        WREG = $06                                      ' LCD entry mode set, increment, no shift
        GoSub _LCD1_SetForCommand_
        If LCD_tNumber = 0 Then                         ' Is it LCD1 that is being accessed?
            LCD1_tInitialised = 1                       ' Yes. So indicate that LCD1 is initialised
        Else                                            ' Otherwise... It is LCD2 that is being accessed
            LCD2_tInitialised = 1                       ' So. Indicate that LCD2 is initialised
        EndIf
        WREG = LCD_bWREGStore                           ' Get the saved byte back
        GoTo _LCD_Send_
_LCD1_SetForCommand_:
        LCD_tComOrData = 1                              ' Set for command
    EndIf

_LCD_Send_:
    LCD_bByteOut = WREG                                 ' Get the byte to send from WREG
    If LCD_tComOrData = 1 Then                          ' Is it a command this time?
        PinClear __LCD_RSPORT.__LCD_RSPIN               ' Yes. So set command register select
        WREG = 3 - WREG
        Bnc _LCD_SetForData_                            ' Short delay
        GoSub _LCD_SetForData_                          ' Long delay
        DelayUS __LCD_COMMANDUS                         ' Wait for the command to complete
        Return
    EndIf
    LCD_tComOrData = 1                                  ' Indicate first nibble, or command next time
    WREG = $FE - WREG                                   ' Command next time?
    Bz _LCD_Exit_
    PinSet __LCD_RSPORT.__LCD_RSPIN                     ' Set the RS pin high

_LCD_SetForData_:
    #if(__LCD_DTPIN == 0)
        Swap LCD_bByteOut, LCD_bByteOut                 ' Swap top and bottom nibbles
    #endif
    Btfss LCD_tComOrData                                ' First time through only
_LCD_NibbleSend_:
    LCD_tComOrData = 0                                  ' Indicate second nibble, and data next time
    If LCD_tNumber = 0 Then                             ' Is it LCD1 that is being accessed?
        PinSet LCD_EN1_Pin                              ' Yes. So enable LCD1
    Else                                                ' Otherwise... It is LCD2 that is being accessed
        PinSet LCD_EN2_Pin                              ' So. Enable LCD2
    EndIf
    #if(__LCD_DTPIN == 0)
        WREG = $F0                                      ' Clear the bits of the LCD data port
    #else
        WREG = $0F                                      ' Clear the bits of the LCD data port
    #endif
    __LCD_DTPORT = __LCD_DTPORT & WREG                  ' \
    WREG = LCD_bByteOut                                 ' / Isolate the correct nibble of the byte
    #if(__LCD_DTPIN == 0)
        WREG = WREG & $0F
    #else
        WREG = WREG & $F0
    #endif
    __LCD_DTPORT = __LCD_DTPORT | WREG                  ' Write the byte to the data port
    DelayCS 6
    If LCD_tNumber = 0 Then                             ' Is it LCD1 that is being accessed?
        PinClear LCD_EN1_Pin                            ' Yes. So disable LCD1
    Else                                                ' Otherwise... It is LCD2 that is being accessed
        PinClear LCD_EN2_Pin                            ' So. Disable LCD2
    EndIf                                          
    Swap LCD_bByteOut, LCD_bByteOut                     ' Move to the other nibble
    DelayCS 6
    Btfsc LCD_tComOrData
    GoTo _LCD_NibbleSend_                               ' Send the lower 4-bits to the LCD
    DelayUS __LCD_DATAUS                                ' Wait for the data operation to complete
_LCD_Exit_:
    WREG = LCD_bWREGStore                               ' Return the original byte in WREG
    Return
_LCD_Over_:


Make sure the proteus multi-LCD in a single package model is operating exactly the same as two independent LCDs, with the RAM at the same positions for the screen display. If it is the character positions that are incorrect, this means the RAM map within the LCD is different to standard 2x16 or 4x20 LCDs.

A screenshot of the above program running within the proteus simulator is shown below, and the complete code listing and the proteus simulation design file is attached below:

LCDx2 Screenshot.jpg

top204

#2
Running some tests to see the DRAM (Display RAM) layout within the MDLS40466 simulator model, looks like it is not simulating correctly, because the LCD's internal DRAM is out of line.

The simulator model shows that each 2x40 LCD is sitting on top of each other within the same package, so LCD1 is lines 1 and 2, and LCD2 is lines 3 and 4, but their internal DRAM map does not match anything I have ever seen in any alphanumeric LCD!

For example, try something like:

    LCD_Number(1)                                               ' Choose LCD1 for the Print command
    Cursor 1, 1
    Do
        Print "*"
        DelayMS 10  
    Loop

And you will see the asterisk moves across line 1 OK, but then moves from past the middle of line 2?, where it should overflow and start at the beginning of line 2 when the DRAM address is 64 onwards, because there is an offset of 64 bytes between lines on an alphanumeric LCD:

MDLS40466_Lines1-2.jpg

The same with the code below:

    LCD_Number(2)                                               ' Choose LCD2 for the Print command
    Cursor 1, 1
    Do
        Print "*"
        DelayMS 10  
    Loop

And you will see the asterisk moves across line 3 OK, but then moves from past the middle of line 4?, where it should overflow and start at the beginning of line 4:

MDLS40466_Lines3-4.jpg

So there is no linear pattern to the DRAM within the LCD and its display, and alphanumeric LCDs display what is in their DRAM at a specific screen position based upon the DRAM's map, hence the name; 'Display RAM'.

If the simulator is changed to actually use two 2x40 LCDs, they operate as they should:

LCDsx2x40 Screenshot.jpg

John Drew

Thanks Les, I appreciate you getting back so quickly and a thorough investigation as always.

I've been chasing another problem in a very complex piece of software and thought the first step was to get the display working in ISIS, then I can add (or delete) code to find the problem. That was when I spotted the Isis issue.

I'll add another LCD and continue with my explorations. It's good to keep the brain active 😁

Les, I'm proud to be one of your friends.
John

top204

#4
Many thanks for your kind words John, and I am very proud to have you and many others from this forum as friends, and some of us have been friends for around 20 years now!

Where does the time go?

I no longer have full trust in the proteus simulator, especially with the newer device models. Some of the newer microcontroller models do not even simulate some assembler mnemonics correctly, and do not set/reset STATUS flags as they should depending on a mnemonic's operation, and the quality and testing of the simulator's models seems to be getting worse and worse over time. However, because of its popularity, it is still trusted 100% by people !!

It used to be such a good simulator, but as history of other past items and software shows: Popularity means quality does not matter as much because it will be bought anyway and regardless!!!

It still has its uses for simple demos etc, but certainly not in a professional capacity!

Oskar-svr

#5
Hello Less, greetings, a question, I am reviewing the program that you posted and I have the following detail:
First of all, I am working with a 4x40 LCD and I program the instructions to print on the corresponding lines in LINE 1 and LINE 3, there is no problem, it respects the positions, but in LINE 2 and LINE 4 it places them in a position other than the start position. Could you help me where I should modify so that the LCD starts in the correct position for lines 2 and 4? Thank you.

The program I use is lcd4x40.bas

Main:
    LCD_Cls(1)                                                      ' Clear the display of LCD1
    LCD_Cls(2)                                                      ' Clear the display of LCD2

    LCD_Number(1) 
    Print At 1,1,"LINE 1  Oscar Eduarodo Rodriguez Gomez"
    Print At 2,1,"Line position N.-2  XXX abcdefghijklmno"
   
    LCD_Number(2) 
    Print At 1,1,"LINE 3  Oscar Eduarodo Rodriguez Gomez"
    Print At 2,1,"LINE 4  line position N.- 2  XXX-******"

    Stop

 

John Drew

Hello Oscar,
If you  read Les's answer to my original post you will see that it is a problem with ISIS, not the code. Just use it on a real PIC and it will work.
John

Oskar-svr

Hi Juan, thank you very much for your kind response. I will assemble it and check it and upload the images so that everyone knows that it works. Thank you Less for everything. You are a genius. Greetings to all.

Pepe

#8
Replacing the 4x40 ldc with 2 of 2x40 simulates it correctly

Oskar-svr

If it is correct PEPE it works very well, thank you very much for taking the time and dispelling my doubt, greetings to all