'*****************************************************************************
'*	VK16K33_I2C.bas -> requires the _I2CLib.bas library.
'*  VK16K33 I²C LED Display Driver Library for PIC18 Oshonsoft BASIC v6.10
'*  Compatible with PIC18 devices supporting I²C Master mode.
'*	By COS, dogflu66@yahoo.es
'*	AI assistant: ChatGPT.
'*	V2026/07/14
'*  --------------------------------------------------------------------------
'*  Driver for LED display modules based on the VinKa VK16K33 controller.
'*  The VK16K33 is compatible with the HT16K33 command set and provides
'*  an easy-to-use I²C interface for multiplexed LED displays.
'*	NOT TESTED WITH HT16K33.
'*
'* ------------------------------------------------------------------
'* I2C pins
'* ------------------
'* SCL  -> SCL (RC.3)
'* SDA  -> SDA (RC.4)
'*
'*  Typical module connections
'*  --------------------------
'*	Vi2c	-> +5 V
'*	VDD		-> +5 V
'*	GND		-> GND
'*	SDA		-> C.4
'*	SCL		-> C.3

'*  Features
'*  --------
'*  • Supports alphanumeric LED displays up to 8 digits.
'*  • 15-segment character generator with decimal point.
'*  • Internal 16-byte display RAM (frame buffer).
'*  • Adjustable brightness (16 PWM levels).
'*  • Hardware display blinking.
'*  • Automatic display RAM update.
'*  • Character, string and decimal point printing.
'*  • Circular right-to-left text scrolling.
'*  • Variable display size support (4, 6, 8 digits, etc.).
'*	  TESTED ONLY WITH 4-DIGIT DISPLAY.
'*
'*  Display Memory
'*  --------------
'*  The VK16K33 contains an internal display RAM organized as sixteen bytes.
'*  Each display digit occupies two consecutive bytes, allowing up to
'*  sixteen segment outputs.
'*  The module used consists of 4 digits with 14 segments plus the decimal point
'*
'*  Communication
'*  -------------
'*  Interface : I²C Master
'*  Default Address : 0x70
'*  Address Range   : 0x70...0x77
'*
'*  Display Buffer
'*  --------------
'*  All drawing operations are performed on the local framebuffer
'*  (VK16K33_RAM). The physical display is updated only when
'*  VK16K33_Update() is called.
'*
'*	I²C low-level functions
'*	-----------------------
'*  • VK16K33_Cmd(cmd)
'*  • VK16K33_Write_Byte(addr, dt)
'*  • VK16K33_Update()
'*
'*	Configuration, control and draw
'*	-------------------------------
'*  • VK16K33_Init()
'*  • VK16K33_Display_On()
'*  • VK16K33_Set_Blink(blink)
'*	• VK16K33_Set_Brightness(bright)
'*  • VK16K33_Test()
'*
'*  • VK16K33_Clear_RAM()
'*  • VK16K33_Clear()
'*
'*  • VK16K33_Cursor(pos)
'*  • VK16K33_CursorHome()
'*  • VK16K33_DPMode(dp)
'*  • VK16K33_PutC(char)
'*  • VK16K33_Print(string)
'*  • VK16K33_ScrollReset()
'*  • VK16K33_ScrollLeft(string)
'*  • VK16K33_GetEncode(ASCII)
'*  • VK16K33_PadLeft(char, ByRef string)
'*
'*  Text Scrolling
'*  --------------
'*  The library implements circular right-to-left scrolling.
'*  Each call to VK16K33_ScrollLeft() performs exactly one scrolling step.
'*  During the initial phase, characters enter progressively from the
'*  rightmost digit. Once the display is full, the text continues scrolling
'*  without inserting additional characters, wrapping seamlessly from the
'*  end of the string back to the beginning.
'*
'*  Example:
'*
'*        H
'*       HE
'*      HEL
'*     HELL
'*     ELLO
'*     LLOH
'*     LOHE
'*     OHEL
'*
'*****************************************************************************
'-----------------------------------------------------------------------------
' User configuration
'-----------------------------------------------------------------------------
' Scan I2C: device found at 7-bit address = 0x70 -> Address << 1 = 0xE0
Const VK16K33_W = 0xE0		' Device Write address.
Const VK16K33_Digits = 4	' Number of digits To control (0 to 3).
Const VK16K33_RAM_SIZE = 8	' VK16K33_Digits x 2
Dim VK16K33_RAM(8) As Byte	' VK16K33_Digits x 2 (frame buffer)

'-----------------------------------------------------------------------------
' Global variables required for the control of the Scroll function.
'-----------------------------------------------------------------------------
Dim VK16K33_ScrollIndex As Byte
Dim VK16K33_ScrollCount As Byte

'-----------------------------------------------------------------------------
' Low level I2C helpers
'-----------------------------------------------------------------------------

' Sends command to the display.
Function VK16K33_Cmd(cmd As Byte) As Byte
    START_I2C()
    WRITE_I2C(VK16K33_W)
    WRITE_I2C(cmd)
    STOP_I2C()
End Function

' Write data:
' address = 0 To VK16K33_RAM_SIZE - 1
' dt      = data to send.
Function VK16K33_Write_Byte(addr As Byte, dt As Byte) As Byte
    If addr >= VK16K33_RAM_SIZE Then Exit

    VK16K33_RAM(addr) = dt

    START_I2C()
    WRITE_I2C(VK16K33_W)
    WRITE_I2C(addr)
    WRITE_I2C(dt)
    STOP_I2C()
End Function

' Sends the contents of the frame buffer to the display.
Function VK16K33_Update() As Byte
    Dim i As Byte

    START_I2C()
    WRITE_I2C(VK16K33_W)
    WRITE_I2C(0x00)                 ' Start at display RAM address 0
    CFor (i = 0; i < VK16K33_RAM_SIZE; ++i)
        WRITE_I2C(VK16K33_RAM(i))
    CNext
    STOP_I2C()
End Function
' Delete the frame buffer.
Function VK16K33_Clear_RAM() As Byte
    Dim i As Byte

    CFor (i = 0; i < VK16K33_RAM_SIZE; ++i)
        VK16K33_RAM(i) = 0x00
    CNext
End Function

'Clear the display.
Function VK16K33_Clear() As Byte
    VK16K33_Clear_RAM()
    VK16K33_Update()
End Function

' Enables or disables decimal-point interpretation inside strings.
' dp = 0: "." is displayed as an independent character.
' dp = 1: "." is applied to the previously written character and does not
'         consume a display position.
Function VK16K33_DPMode(dp As Bit) As Bit
    ReturnValue dp
End Function

' Digit position where the next character is printed.
Function VK16K33_Cursor(pos As Byte) As Byte
	If pos >= VK16K33_Digits Then pos = 0
	ReturnValue pos
End Function

' Place the cursor on the first digit of the display.
Function VK16K33_CursorHome() As Byte
	VK16K33_Cursor = 0
End Function

' Prints one character at the current position.
' When decimal-point interpretation is enabled, "." modifies the previously
' written digit directly and does not advance the cursor.
Function VK16K33_PutC(char As Byte) As Byte
    Dim d_pos As Byte
    Dim mask As Word

    If VK16K33_Cursor >= VK16K33_Digits Then VK16K33_Cursor = 0

    If VK16K33_DPMode = 1 And char = "." Then
        If VK16K33_Cursor = 0 Then
            d_pos = (VK16K33_Digits - 1) * 2
        Else
            d_pos = (VK16K33_Cursor - 1) * 2
        Endif

        VK16K33_RAM(d_pos + 1).6 = 1
        Exit
    Endif

    d_pos = VK16K33_Cursor * 2
    mask = VK16K33_GetEncode(char)

    VK16K33_RAM(d_pos) = mask.LB
    VK16K33_RAM(d_pos + 1) = mask.HB

    ++VK16K33_Cursor
End Function

' Prints a string starting at the current digit position.
' Decimal-point handling is performed entirely by VK16K33_PutC().
Function VK16K33_Print(str[20] As String) As Byte
    Dim index As Byte
    index = 0

    While str(index) > 0
        VK16K33_PutC(str(index))
        ++index
    Wend
End Function

' Fill a string with a character specified from the left until
' the numBer of digits on the display is reached.
Function VK16K33_PadLeft(char As Byte, ByRef str As String) As Byte
	Dim i As Byte
	Dim Lenstr As Byte
	i = 0
	Lenstr = 0
	' Calculate the length of the input string by counting the "."
	' Or not, as indicated by TM1622_DPMode.
	While Pointer(str + i) > 0
		If VK16K33_DPMode = 1 Then
			If Pointer(str + i) <> "." Then ++Lenstr
		Else
			++Lenstr
		Endif
		++i
	Wend
	'Calculate the numBer of characters that need To be added.
	i = VK16K33_Digits - Lenstr
	' Add the chosen character the number of times i.
	While i > 0
		str = Chr(char) + str
		--i
	Wend
End Function

'-----------------------------------------------------------------------------
' Resets the circular right-to-left scrolling.
'
' Call this function before starting a new string.
'-----------------------------------------------------------------------------
Function VK16K33_ScrollReset() As Byte

    VK16K33_ScrollIndex = 0
    VK16K33_ScrollCount = 0

End Function
'-----------------------------------------------------------------------------
' Circular right-to-left string scrolling with decimal-point support.
'
' Each call performs exactly one scrolling step.
'
' When VK16K33_DPMode = 1:
'
'   - A decimal point does not occupy a display position.
'   - The decimal point is associated with the preceding character.
'   - The decimal-point bit is added directly to the encoded character.
'
' The first character enters through the rightmost digit. Subsequent calls
' progressively fill the display. Once all digits are occupied, scrolling
' continues circularly without adding characters that are not present in
' the original string.
'
' Example with four digits and "12.345":
'
'       1
'      12.
'     12.3
'    12.34
'    2.345
'    3451
'    4512.
'    512.3
'
' Each call performs only one step. The scrolling speed must be controlled
' by the calling program.
'-----------------------------------------------------------------------------
Function VK16K33_ScrollLeft(str[20] As String) As Byte
	Dim raw_len As Byte
	Dim text_len As Byte
	Dim raw_index As Byte
	Dim logical_index As Byte
	Dim target_index As Byte

	Dim visible As Byte
	Dim first_digit As Byte
	Dim char_index As Byte
	Dim digit_index As Byte
	Dim ram_pos As Byte
	Dim index As Byte

	Dim mask As Word
	Dim found As Bit

	'-------------------------------------------------------------------------
	' Determine the physical length of the string and the number of logical
	' display characters.
	'
	' When decimal-point interpretation is enabled, "." is not counted as
	' an independent character.
	'-------------------------------------------------------------------------
	raw_len = Len(str)
	text_len = 0

	CFor (raw_index = 0; raw_index < raw_len; ++raw_index)
		If VK16K33_DPMode = 0 Or str(raw_index) <> "." Then
			++text_len
		Endif
	CNext

	' Empty string, or a string containing only decimal points.
	If text_len = 0 Then
		VK16K33_Clear()
		Exit
	Endif

	'-------------------------------------------------------------------------
	' Initial entry phase.
	'
	' The occupied area increases by one digit on each call until the entire
	' display is filled.
	'-------------------------------------------------------------------------
	If VK16K33_ScrollCount < VK16K33_Digits Then
		++VK16K33_ScrollCount
	Endif

	visible = VK16K33_ScrollCount

	' Clear the local framebuffer.
	VK16K33_Clear_RAM()

	' Right-align the characters during the initial entry phase.
	first_digit = VK16K33_Digits - visible

	'-------------------------------------------------------------------------
	' Determine the first logical character of the circular visible window.
	'-------------------------------------------------------------------------
	char_index = VK16K33_ScrollIndex
	index = visible - 1

	While index > 0
		If char_index = 0 Then
			char_index = text_len - 1
		Else
			--char_index
		Endif
		--index
	Wend

	'-------------------------------------------------------------------------
	' Encode and write all visible logical characters.
	'-------------------------------------------------------------------------
	CFor (digit_index = 0; digit_index < visible; ++digit_index)
		target_index = char_index
		raw_index = 0
		logical_index = 0
		found = False
		mask = 0

		' Locate the requested logical character inside the original string.
		While raw_index < raw_len And found = False

			' When enabled, decimal points are modifiers and are skipped
			' while searching for a logical character.
			If VK16K33_DPMode = 1 And str(raw_index) = "." Then
				++raw_index
			Else
            If logical_index = target_index Then

                ' Encode only the actual character.
                mask = VK16K33_GetEncode(str(raw_index))

                ' If the next physical character is a decimal point,
                ' activate the decimal-point segment directly.
                If VK16K33_DPMode = 1 Then
                    If raw_index + 1 < raw_len Then
                        If str(raw_index + 1) = "." Then
                            mask.HB.6 = 1
                        Endif
                    Endif
                Endif
                found = True
				Else
					++logical_index
					++raw_index
				Endif
			Endif
		Wend

		' Safety fallback.
		If found = False Then
			mask = 0
		Endif

		ram_pos = (first_digit + digit_index) * 2
		VK16K33_RAM(ram_pos) = mask.LB
		VK16K33_RAM(ram_pos + 1) = mask.HB

		' Advance to the next logical character.
		++char_index
		If char_index >= text_len Then
			char_index = 0
		Endif
	CNext

	' Transfer the framebuffer to the VK16K33.
	VK16K33_Update()

	'-------------------------------------------------------------------------
	' Select the next logical character that will enter through the right.
	'-------------------------------------------------------------------------
	ReturnValue VK16K33_ScrollIndex
	++VK16K33_ScrollIndex

	If VK16K33_ScrollIndex >= text_len Then
		VK16K33_ScrollIndex = 0
	Endif
End Function

' Display ON, blink OFF
Function VK16K33_Display_On() As Byte
    VK16K33_Cmd(0x81)               ' Display ON, blink OFF
End Function

' blink:
'   0 = off
'   1 = 2 Hz
'   2 = 1 Hz
'   3 = 0.5 Hz
Function VK16K33_Set_Blink(blink As Byte) As Byte
    If blink > 3 Then blink = 3
    VK16K33_Cmd(0x81 Or (blink << 1))
End Function

' Adjustable brightness 0 to 15 PWM levels.
Function VK16K33_Set_Brightness(bright As Byte) As Byte
    If bright > 15 Then bright = 15
    VK16K33_Cmd(0xE0 Or bright)
End Function

' Initialize the module
Function VK16K33_Init() As Byte
	VK16K33_Cursor(0)
	VK16K33_DPMode(True) ' Enables decimal-point interpretation inside strings.

    WaitMs 10
    VK16K33_Cmd(0x21)               ' Internal oscillator ON
    VK16K33_Set_Blink(0)            ' Display ON, blink OFF
    VK16K33_Set_Brightness(15)      ' Max brightness
    VK16K33_Clear()

    VK16K33_ScrollReset()

End Function

' Test function: all segments of the digits to on.
Function VK16K33_Test() As Byte
	Dim index As Byte
	For index = 0 To ((VK16K33_Digits * 2) - 1)
		VK16K33_RAM(index) = 0xFF
	Next index
	VK16K33_Update()
End Function

'--------------------------------------------------------------------------------
' Font: 15-segments (two bytes)
' Only the numbers, capital letters and the main symbols have been defined.
'--------------------------------------------------------------------------------
' Physical order of the bits from Bit 15 To Bit 0:
'
'                        mask_HB                             mask_LB
'           ________________^_______________     _______________^_______________
' Bit:     / 15  14  13  12  11  10   9   8 \   / 7   6   5   4   3   2   1   0 \
' Segment:   nc  dp   l   m   n   j   h   g       k   p   f   e   d   c   b   a
' nc: It's not in use.
'---------------------------------------------------------------------------------
Function VK16K33_GetEncode(value As Byte) As Word
	Symbol mask = VK16K33_GetEncode
	Symbol mask_LB = VK16K33_GetEncode.LB
	Symbol mask_HB = VK16K33_GetEncode.HB

	Select Case value
		Case " "
            mask_LB = 0x00	' Space o blank
            mask_HB = 0x00
		Case "0"
            mask_LB = 0x3F  ' 0
            mask_HB = 0x0C
		Case "1"
            mask_LB = 0x06  ' 1
            mask_HB = 0x04
       Case "2"
            mask_LB = 0xDB  ' 2
            mask_HB = 0x00
       Case "3"
            mask_LB = 0xCF  ' 3
            mask_HB = 0x00
       Case "4"
            mask_LB = 0xE6  ' 4
            mask_HB = 0x00
       Case "5"
            mask_LB = 0x69  ' 5
            mask_HB = 0x20
       Case "6"
            mask_LB = 0xFD  ' 6
            mask_HB = 0x00
       Case "7"
            mask_LB = 0x07  ' 7
            mask_HB = 0x00
       Case "8"
            mask_LB = 0xFF  ' 8
            mask_HB = 0x00
       Case "9"
            mask_LB = 0xEF  ' 9
            mask_HB = 0x00
       Case "A"
            mask_LB = 0xF7  ' A
            mask_HB = 0x00
       Case "B"
            mask_LB = 0x8F  ' B
            mask_HB = 0x12
       Case "C"
            mask_LB = 0x39	' C
            mask_HB = 0x00
       Case "D"
            mask_LB = 0x0F	' d
            mask_HB = 0x12
       Case "E"
            mask_LB = 0x79	' E
            mask_HB = 0x00
       Case "F"
            mask_LB = 0x71	' F
            mask_HB = 0x00
       Case "G"
            mask_LB = 0xBD	' G
            mask_HB = 0x00
       Case "H"
            mask_LB = 0xF6	' H
            mask_HB = 0x00
       Case "I"
            mask_LB = 0x09	' I
            mask_HB = 0x12
        Case "J"
            mask_LB = 0x1E	' J
            mask_HB = 0x00
       Case "K"
            mask_LB = 0x70	' K
            mask_HB = 0x24
       Case "L"
            mask_LB = 0x38	' L
            mask_HB = 0x00
       Case "M"
            mask_LB = 0x36	' M
            mask_HB = 0x05
       Case "N"
            mask_LB = 0x36	' N
            mask_HB = 0x21
       Case "O"
            mask_LB = 0x3F	' O
            mask_HB = 0x00
       Case "P"
            mask_LB = 0xF3	' P
            mask_HB = 0x00
       Case "Q"
            mask_LB = 0x3F	' Q
            mask_HB = 0x20
       Case "R"
            mask_LB = 0xF3	' R
            mask_HB = 0x20
       Case "S"
            mask_LB = 0xED	' S
            mask_HB = 0x00
       Case "T"
            mask_LB = 0x01	' T
            mask_HB = 0x12
       Case "U"
            mask_LB = 0x3E	' U
            mask_HB = 0x00
       Case "V"
            mask_LB = 0x30	' V
            mask_HB = 0x0C
       Case "W"
            mask_LB = 0x36	' W
            mask_HB = 0x28
       Case "X"
            mask_LB = 0x00	' X
            mask_HB = 0x2D
       Case "Y"
            mask_LB = 0xEE	' Y
            mask_HB = 0x00
       Case "Z"
            mask_LB = 0x09	' Z
            mask_HB = 0x0C
       Case "Ñ"
            mask_LB = 0x55	' Ñ
            mask_HB = 0x20
       Case "+"
            mask_LB = 0xC0	' +
            mask_HB = 0x12
       Case "-"
            mask_LB = 0x80	' -
            mask_HB = 0x00

       Case "."
            mask_LB = 0x00	' .
            mask_HB = 0x40

       Case "*"
            mask_LB = 0xC0	'  *
            mask_HB = 0x3F

       Case "º"
            mask_LB = 0xE3	'  º
            mask_HB = 0x00

       Case Else
            mask_LB = 0x00	' blank
            mask_HB = 0x00

    EndSelect

    ReturnValue mask

End Function














