'-----------------------------------------------------------------------
' Lcd1602_I2CLib.bas With PCF8574 and _I2CLib.bas
' Test Pic18F46K22 And HL-K18 board
' By COS, 2025/08/07, 2025/04, 2025/01, 2025/01, Pic18 Basic Oshonsoft v5.73
' dogflu66@yahoo.es
' Data bus mode: only 4 bits
' RW = 0
' PCF8574 address = 0x4E (device address in write mode)
'------------------------------------------------------------------------
' LCD1602 AND LCD20X4
'------------------------------------------------------------------------
' LCD configuration constants
Const LCD_I2C_ADDRESS = 0x4E ' Jumpers PCF8574 address to off + write mode
Const LCD_WAIT_DATAUS = 50   ' Timeout in microseconds between data writes
' Bit definitions for LCD control
Dim PCF_DB As Byte
Symbol PCF_E = PCF_DB.2        ' Enable pin
Symbol PCF_RS = PCF_DB.0       ' Register Select pin
Symbol PCF_BackLight = PCF_DB.3

Const LCD_TYPE = 2         ' LCD type: 0=5x7, 1=5x10, 2=2 lines.
Const LCD_LINE_2 = 0x40    ' Address of the second line in LCD RAM.
Const LCD_LINE_3 = 0x14    ' Address of the third line in LCD RAM.
Const LCD_LINE_4 = 0x54    ' Address of the fourth line in LCD RAM.
'**************************************************************************
'--------------------------------------------------------------------------
' LCD configuration number of characters per line and number of lines.
Const LCD_LEN = 20 '16	   ' Number of characters per line.
Const LCD_LINE = 4 '2      ' Number of lines.
'--------------------------------------------------------------------------
'**************************************************************************
Dim _LCD_MODE As Byte
' LCD initialization configuration sequence
Function LCD_Config(index As Byte) As Byte
    Dim dat(4) As Byte
	_LCD_MODE = %1100 '0x0C			'Display On, cursor OFF
    dat(0) = 0x20 | (LCD_TYPE << 2) ' 4-bit mode, 2 lines, 5x8 dots
    dat(1) = _LCD_MODE '0x0C        ' Display ON, cursor OFF
    dat(2) = 0x01                   ' Clear display
    dat(3) = 0x06                   ' Increment cursor position
    ReturnValue dat(index)
End Function
'-------------------------------------------------------------------
' Function:    WriteDevice_I2C
' Description:
' Initiates a write transaction with an I2C slave device.
' Sends the slave address and then a data byte.
' Arguments:
' - address (Byte): Slave device address.
' - data (Byte): Data to write to the slave.
'-------------------------------------------------------------------
Function WriteDevice_I2C(ByVal address As Byte, ByVal data As Byte) As Byte
    START_I2C() ' Start condition
	WRITE_I2C(address)
    WRITE_I2C(data) ' Send data
    Call STOP_I2C() ' End transaction
End Function

' Send data to the LCD data bus (4-bit mode)
Function LCD_Send_DB(dat As Byte) As Bit
	'Data bits
	PCF_DB.4 = dat.0
	PCF_DB.5 = dat.1
	PCF_DB.6 = dat.2
	PCF_DB.7 = dat.3

	'Control bits
	High PCF_E 'E
	WriteDevice_I2C(LCD_I2C_ADDRESS, PCF_DB)
	Low PCF_E 'E
	WriteDevice_I2C(LCD_I2C_ADDRESS, PCF_DB)
End Function

' Send a byte to the LCD (command or data)
' address = 0: command, address = 1: data
Function LCD_Send_Byte(address As Byte, dat As Byte) As Bit
    WaitUs LCD_WAIT_DATAUS  ' Wait for LCD to process previous command
    If address Then
        High PCF_RS ' Select data register
    Else
        Low PCF_RS  ' Select command register
    Endif
	Low PCF_E ' Ensure Enable pin is low
	WriteDevice_I2C(LCD_I2C_ADDRESS, PCF_DB)
	LCD_Send_DB(dat >> 4)  ' Send higher nibble
	LCD_Send_DB(dat & 0xF) ' Send lower nibble
End Function

' Initialize the LCD
Function LCD_Init() As Bit

	PCF_DB = 0 ' Initializes data and control bus
	WriteDevice_I2C(LCD_I2C_ADDRESS, PCF_DB)
	WaitMs 15 ' Wait For LCD To Power up

	' Initialization sequence for 4-bit mode
    LCD_Send_DB(0x03)
    WaitMs 5
    LCD_Send_DB(0x03)
    WaitUs 5
    LCD_Send_DB(0x03)
    WaitMs 100
    LCD_Send_DB(0x02)

    ' Send configuration commands
	Dim i As Byte
	CFor (i = 0; i < 4; i++)
        LCD_Send_Byte(0, LCD_Config(i))
    CNext
    WaitMs 5 ' Allow LCD To stabilize

	'User-defined characters (big number).
	LCD_DefChar(0, %11111, %11111, %11111, 0, 0, 0, 0, 0)  'Top horizontal bar.
    LCD_DefChar(1, 0, 0, 0, 0, 0, %11111, %11111, %11111)  'Bottom horizontal bar.
    LCD_DefChar(2, %11111, %11111, %11111, 0, 0, 0, %11111, %11111)  'Top and bottom horizontal bar.
    LCD_DefChar(3, %11100, %11110, %11110, %11110, %11110, %11110, %11110, %11100)  'Left column.
    LCD_DefChar(4, %00111, %01111, %01111, %01111, %01111, %01111, %01111, %00111)  'Right column.
    LCD_DefChar(5, 0, 0, 0, 0, 0, %00011, %00111, %01111)  'Bottom Right arrow.
    LCD_DefChar(6, %00011, %00111, %01111, 0, 0, 0, 0, 0)  'Top Right arrow.
    LCD_DefChar(7, 0, 0, 0, 0, 0, %11000, %11100, %11110)  'Bottom Left arrow.

	WaitMs 5
    LCD_BackLight(True)
    WaitMs 5
End Function

' Creates a custom character (0 to 7) and stores it in the LCD CGRAM (5x8 dot matrix).
' This function allows defining up to 8 custom characters, which can later be displayed on the LCD.
'
' Parameters:
'   CharIndex   - Byte: The index (0-7) where the character will be stored in CGRAM.
'   CharMask_7  - Byte: Row 0 of the custom character (top row).
'   CharMask_6  - Byte: Row 1 of the custom character.
'   CharMask_5  - Byte: Row 2 of the custom character.
'   CharMask_4  - Byte: Row 3 of the custom character.
'   CharMask_3  - Byte: Row 4 of the custom character.
'   CharMask_2  - Byte: Row 5 of the custom character.
'   CharMask_1  - Byte: Row 6 of the custom character.
'   CharMask_0  - Byte: Row 7 of the custom character (bottom row).
'
Function LCD_DefChar(CharIndex As Byte, CharMask_7 As Byte, CharMask_6 As Byte, CharMask_5 As Byte, CharMask_4 As Byte, CharMask_3 As Byte, CharMask_2 As Byte, CharMask_1 As Byte, CharMask_0 As Byte) As Bit

    ' Array to store the custom character row data
    Dim charMask(8) As Byte
    charMask(7) = CharMask_0  ' Assign bottom row of the character
    charMask(6) = CharMask_1
    charMask(5) = CharMask_2
    charMask(4) = CharMask_3
    charMask(3) = CharMask_4
    charMask(2) = CharMask_5
    charMask(1) = CharMask_6
    charMask(0) = CharMask_7  ' Assign top row of the character

    ' Ensure that the character index is within the valid range (0-7)
    If CharIndex > 7 Then CharIndex = 7

    ' Calculate the starting address in CGRAM (0x40 + (CharIndex * 8))
    LCD_Send_Byte(0, 0x40 + (CharIndex * 8))  ' Send LCD command to set CGRAM address

    ' Write the 8 bytes that define the custom character pattern
    Dim i As Byte
    For i = 0 To 7
        LCD_Send_Byte(1, charMask(i))  ' Send LCD data byte by byte
    Next i
End Function

' Set cursor position to specified row and column
Function LCD_PosXY(Xpos As Byte, Line As Byte) As Bit
    Dim address As Byte
	If Line > LCD_LINE Then Line = LCD_LINE
	If Line = 0 Then Line = 1

    If Line = 2 Then
		address = LCD_LINE_2
    Else
		If Line = 3 Then
			address = LCD_LINE_3
	Else
		If Line = 4 Then
			address = LCD_LINE_4
    Else
		address = 0x00 'LCD LINE 1
    Endif
    Endif
    Endif

    address += Xpos - 1
    LCD_Send_Byte(0, 0x80 | address)
End Function

' Display a character on the LCD
Function LCD_PutC(char As Byte) As Bit
    LCD_Send_Byte(1, char)
End Function
'LCD_CurBlink(statusBit)
Function LCD_CursorBlink(sBit As Bit) As Bit
	_LCD_MODE.0 = sBit
	LCD_Send_Byte(0, _LCD_MODE)
	WaitMs 5
End Function
' Lcdinit Lcd_Cursor(True/False)
Function LCD_Cursor(sBit As Bit) As Bit
	_LCD_MODE.1 = sBit
	LCD_Send_Byte(0, _LCD_MODE)
	WaitMs 5
End Function

' Lcdinit Lcd_BackLihgt(True/False)
Function LCD_BackLight(sBit As Bit) As Bit
	Dim dat As Byte
	dat = 0
	dat.3 = sBit
	PCF_BackLight = sBit
	WriteDevice_I2C(LCD_I2C_ADDRESS, dat)
	WaitMs 5
End Function

' Display a string starting at a specified line and position
Function LCD_Out(Line As Byte, Xpos As Byte, char[20] As String) As Bit
	If Line > LCD_LINE Then Line = LCD_LINE
	If Line = 0 Then Line = 1

	LCD_PosXY(Xpos, Line)

    Dim xStr As Byte
    Dim lenStr As Byte
    lenStr = Len(char) - 1
    For xStr = 0 To lenStr
        LCD_Send_Byte(1, char(xStr))
    Next xStr
End Function

' Move cursor to the beginning of the first line
Function LCD_Line1Home() As Bit
    LCD_PosXY(1, 1)
End Function

' Move cursor to the beginning of the second line
Function LCD_Line2Home() As Bit
    LCD_PosXY(1, 2)
End Function

' Move cursor to the beginning of the third line
Function LCD_Line3Home() As Bit
    LCD_PosXY(1, 3)
End Function

' Move cursor to the beginning of the fourth line
Function LCD_Line4Home() As Bit
    LCD_PosXY(1, 4)
End Function
' Set cursor to a specific position on the first line
Function LCD_Line1Pos(Xpos As Byte) As Bit
    LCD_PosXY(Xpos, 1)
End Function

' Set cursor to a specific position on the second line
Function LCD_Line2Pos(Xpos As Byte) As Bit
    LCD_PosXY(Xpos, 2)
End Function
' Set cursor to a specific position on the third line
Function LCD_Line3Pos(Xpos As Byte) As Bit
    LCD_PosXY(Xpos, 3)
End Function

' Set cursor to a specific position on the fourth line
Function LCD_Line4Pos(Xpos As Byte) As Bit
    LCD_PosXY(Xpos, 4)
End Function
' Clear the first line of the LCD
Function LCD_Line1Clear() As Bit
    Dim Xpos As Byte
    CFor (Xpos = 1; Xpos <= LCD_LEN; Xpos++)
        LCD_PosXY(Xpos, 1)
        LCD_PutC(" ")
    CNext
    LCD_Line1Home()
End Function

' Clear the second line of the LCD
Function LCD_Line2Clear() As Bit
    Dim Xpos As Byte
    CFor (Xpos = 1; Xpos <= LCD_LEN; Xpos++)
        LCD_PosXY(Xpos, 2)
        LCD_PutC(" ")
    CNext
    LCD_Line2Home()
End Function
' Clear the third line of the LCD
Function LCD_Line3Clear() As Bit
	If LCD_LINE < 3 Then Exit
    Dim Xpos As Byte
    CFor (Xpos = 1; Xpos <= LCD_LEN; Xpos++)
        LCD_PosXY(Xpos, 3)
        LCD_PutC(" ")
    CNext
    LCD_Line3Home()
End Function

' Clear the fourth line of the LCD
Function LCD_Line4Clear() As Bit
	If LCD_LINE < 4 Then Exit
    Dim Xpos As Byte
    CFor (Xpos = 1; Xpos <= LCD_LEN; Xpos++)
        LCD_PosXY(Xpos, 4)
        LCD_PutC(" ")
    CNext
    LCD_Line4Home()
End Function
' Clear the entire LCD
Function LCD_Clear() As Bit
    Dim Xpos As Byte
    CFor (Xpos = 1; Xpos <= LCD_LEN; Xpos++)
        LCD_PosXY(Xpos, 1)
        LCD_PutC(" ")

		If LCD_LINE >= 2 Then
			LCD_PosXY(Xpos, 2)
			LCD_PutC(" ")
		Endif

		If LCD_LINE >= 3 Then
			LCD_PosXY(Xpos, 3)
			LCD_PutC(" ")
		Endif

		If LCD_LINE = 4 Then
			LCD_PosXY(Xpos, 4)
			LCD_PutC(" ")
		Endif

    CNext
    LCD_Line1Home()
End Function

' **********************************************************************
' Function that enlarges the numeric characters of a string
' Limited string length, written for 2-line
' Line: LCD line.
' Xpos: Starting position to print on the LCD
' strChar: numeric string to write.
' Returns the next position to print (low line).
' *********************************************************************
Function LCD_OutBigNum(Line As Byte, Xpos As Byte, strChar[20] As String) As Byte
    Dim data As Byte	' Mask
    Dim index As Byte  	' Index mask.
    Dim xStr As Byte  		' Character position of then string.
    xStr = 0
	While strChar(xStr) > 0
        LCD_PosXY(Xpos, Line)	' Lcd cursor position.
		For index = 0 To 5  	' Upper and lower mask print control loop.

			Select Case strChar(xStr)
				' Numeric characters.
				Case "0"
						data = LookUp(4, 0, 3, 4, 1, 3), index			'0
				Case "1"
						data = LookUp(6, 255, " ", 5, 255, 7), index	'1
				Case "2"
						data = LookUp(6, 2, 3, 4, 1, 7), index			'2
				Case "3"
						data = LookUp(6, 2, 3, 5, 1, 3), index			'3
				Case "4"
						data = LookUp(4, 1, 255, " ", " ", 255), index	'4
				Case "5"
						data = LookUp(4, 2, 0, 5, 1, 3), index			'5
				Case "6"
						data = LookUp(4, 2, " ", 4, 1, 3), index		'6
				Case "7"
						data = LookUp(6, 0, 3, " ", 255, " "), index	'7
				Case "8"
						data = LookUp(4, 2, 3, 4, 1, 3), index			'8
				Case "9"
						data = LookUp(4, 2, 3, " ", " ", 255), index	'9

				' Special characters
				Case "º"
						LCD_PutC("o")									'º
						LCD_PosXY(Xpos, Line +1)
						LCD_PutC(" ")
						Xpos++
						Exit For
				Case "-"
						LCD_PutC("_")									'-
						LCD_PosXY(Xpos, Line +1)
						LCD_PutC(" ")
						Xpos++
						Exit For
				Case Else
						LCD_PutC(" ")
						LCD_PosXY(Xpos, Line +1)
						LCD_PutC(strChar(xStr))
						Xpos++
						Exit For
			EndSelect

			'Write the numeric characters.
            LCD_PutC(data)  'Print the current mask.
            If index = 2 Then
                LCD_PosXY(Xpos, Line +1)  ''Line change.
                Xpos = Xpos + 3  'Position of the new digit.
            Endif

		Next index

		xStr++  'Next digit to print.
		ReturnValue Xpos 'Return to the next position.
	Wend
End Function