'*****************************************************************************
'* VK16K33_I2C_MATRIX_16x8.bas -> requires the _I2CLib.bas library.
'* VK16K33 I2C driver for two 8x8 LED matrices (16x8 pixels).
'* PIC18 Oshonsoft BASIC Compiler v6.10
'* By COS, dogflu66@yahoo.es
'* AI assistant: ChatGPT
'* V2026/10/06
'*
'* ---------------------------------------------------------------------------
'* Driver for a VK16K33 module fitted with two 8x8 LED matrices.
'*
'* Physical orientation used by this library:
'* The module is viewed from the front with the silkscreen text readable.
'*
'* Logical display coordinates:
'*
'*        x = 0 ........................................ 15
'* y = 0  +-----------------------------------------------+
'*        |       LEFT MATRIX       |     RIGHT MATRIX    |
'*        |          8x8            |          8x8        |
'* y = 7  +-----------------------------------------------+
'*
'* Origin: upper-left corner.
'* X increases from left to right.
'* Y increases from top to bottom.
'*
'* Display RAM mapping found experimentally:
'*
'*   Row 0 : RAM[0]  = left matrix,  RAM[1]  = right matrix
'*   Row 1 : RAM[2]  = left matrix,  RAM[3]  = right matrix
'*   Row 2 : RAM[4]  = left matrix,  RAM[5]  = right matrix
'*   ...
'*   Row 7 : RAM[14] = left matrix,  RAM[15] = right matrix
'*
'* In every byte:
'*   bit 0 = leftmost LED
'*   bit 7 = rightmost LED
'*
'* Display Buffer
'* --------------
'* Drawing operations modify the local framebuffer VK16K33_RAM().
'* The physical display is updated only when VK16K33_Update() is called.
'*
'* Rotation
'* --------
'* Software rotation of 0 or 180 degrees is supported.
'* Rotation is performed inside VK16K33_Pixel(), therefore functions that use
'* Pixel() automatically inherit the selected orientation.
'*
'* Main functions
'* --------------
'* VK16K33_Init()
'* VK16K33_Cmd(cmd)
'* VK16K33_Write_Byte(addr, dt)
'* VK16K33_Update()
'* VK16K33_Clear_RAM()
'* VK16K33_Clear()
'* VK16K33_Display_On()
'* VK16K33_Display_Off()
'* VK16K33_Set_Blink(blink)
'* VK16K33_Set_Brightness(bright)
'* VK16K33_SetRotation(rotation)
'* VK16K33_Pixel(x, y, state)
'* VK16K33_Row(y, value)
'* VK16K33_Column(x, value)
'* VK16K33_Fill(state)
'* VK16K33_Invert()
'* VK16K33_Test()
'*****************************************************************************

' -----------------------------------------------------------------------------
' User configuration
' -----------------------------------------------------------------------------
' I2C scan:
' 7-bit address 0x70 -> Address << 1 = 0xE0
Const VK16K33_W = 0xE0

Const VK16K33_WIDTH = 16
Const VK16K33_HEIGHT = 8
Const VK16K33_RAM_SIZE = 16

Const VK16K33_ROTATION_0 = 0
Const VK16K33_ROTATION_180 = 1

Dim VK16K33_RAM(16) As Byte
Dim VK16K33_Rotation As Bit

'-----------------------------------------------------------------------------
' Low-level I2C helpers
'-----------------------------------------------------------------------------

' Sends a command to the VK16K33.
Function VK16K33_Cmd(cmd As Byte) As Byte
    START_I2C()
    WRITE_I2C(VK16K33_W)
    WRITE_I2C(cmd)
    STOP_I2C()
End Function

' Writes one byte to the local framebuffer and immediately to display RAM.
' addr = 0...15
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 complete framebuffer 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

'-----------------------------------------------------------------------------
' Display control
'-----------------------------------------------------------------------------

' Display ON, blink OFF.
Function VK16K33_Display_On() As Byte
    VK16K33_Cmd(0x81)
End Function

' Display OFF.
Function VK16K33_Display_Off() As Byte
    VK16K33_Cmd(0x80)
End Function

' Hardware blink control:
'   0 = blink OFF
'   1 = 0.5 Hz
'   2 = 1 Hz
'   3 = 2 Hz
Function VK16K33_Set_Blink(blink As Byte) As Byte
    If blink > 3 Then blink = 3
    VK16K33_Cmd(0x81 Or (blink << 1))
End Function

' Brightness: 0...15.
Function VK16K33_Set_Brightness(bright As Byte) As Byte
    If bright > 15 Then bright = 15
    VK16K33_Cmd(0xE0 Or bright)
End Function

' Selects display orientation.
' rotation = VK16K33_ROTATION_0
' rotation = VK16K33_ROTATION_180
Function VK16K33_SetRotation(rotation As Bit) As Byte
    VK16K33_Rotation = rotation
End Function

' Initializes the VK16K33.
Function VK16K33_Init() As Byte
    WaitMs 10
    VK16K33_Cmd(0x21)                   ' Internal oscillator ON.
    VK16K33_Set_Blink(0)                ' Display ON, blink OFF.
    VK16K33_Set_Brightness(15)          ' Maximum brightness.
    VK16K33_SetRotation(VK16K33_ROTATION_0)
    VK16K33_Clear()
End Function

' -----------------------------------------------------------------------------
' Framebuffer control
' -----------------------------------------------------------------------------

' Clears only the local framebuffer.
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

' Clears the framebuffer and updates the physical display.
Function VK16K33_Clear() As Byte
    VK16K33_Clear_RAM()
    VK16K33_Update()
End Function

' Fills or clears the complete local framebuffer.
' state = 0 -> all pixels OFF
' state = 1 -> all pixels ON
' VK16K33_Update() must be called afterwards.
Function VK16K33_Fill(state As Bit) As Byte
    Dim i As Byte
    Dim value As Byte

    If state = 1 Then
        value = 0xff
    Else
        value = 0x00
    Endif

    CFor (i = 0; i < VK16K33_RAM_SIZE; ++i)
        VK16K33_RAM(i) = value
    CNext
End Function

' Inverts every pixel in the local framebuffer.
' VK16K33_Update() must be called afterwards.
Function VK16K33_Invert() As Byte
    Dim i As Byte

    CFor (i = 0; i < VK16K33_RAM_SIZE; ++i)
        VK16K33_RAM(i) = Not VK16K33_RAM(i)
    CNext
End Function

'-----------------------------------------------------------------------------
' Graphics
'-----------------------------------------------------------------------------

' Sets or clears one pixel.
'
' Coordinates:
'   x = 0...15
'   y = 0...7
'
' state:
'   0 = pixel OFF
'   1 = pixel ON
'
' This function modifies only the local framebuffer.
' Call VK16K33_Update() to refresh the physical display.
Function VK16K33_Pixel(x As Byte, y As Byte, state As Bit) As Byte
    Dim addr As Byte
    Dim bit_pos As Byte

    If x >= VK16K33_WIDTH Then Exit
    If y >= VK16K33_HEIGHT Then Exit

    ' Software rotation by 180 degrees.
    If VK16K33_Rotation = VK16K33_ROTATION_180 Then
        x = 15 - x
        y = 7 - y
    Endif

    ' Two consecutive RAM bytes form one 16-pixel row.
    addr = y * 2

    If x < 8 Then
        bit_pos = x
    Else
        ++addr
        bit_pos = x - 8
    Endif

    If state = 1 Then
        VK16K33_RAM(addr) = VK16K33_RAM(addr) Or (1 << bit_pos)
    Else
        VK16K33_RAM(addr) = VK16K33_RAM(addr) And Not (1 << bit_pos)
    Endif
End Function

' Writes a complete logical row using a 16-bit value.
'
' y = 0...7
'
' Bit mapping in normal orientation:
'   value.0  -> x = 0
'   value.7  -> x = 7
'   value.8  -> x = 8
'   value.15 -> x = 15
'
' The function uses Pixel(), therefore rotation is automatically applied.
' VK16K33_Update() must be called afterwards.
Function VK16K33_Row(y As Byte, value As Word) As Byte
    Dim x As Byte

    If y >= VK16K33_HEIGHT Then Exit

    CFor (x = 0; x < VK16K33_WIDTH; ++x)
        If value.x = 1 Then
            VK16K33_Pixel(x, y, 1)
        Else
            VK16K33_Pixel(x, y, 0)
        Endif
    CNext
End Function

' Writes a complete logical column using an 8-bit value.
'
' x = 0...15
'
' Bit mapping in normal orientation:
'   value.0 -> y = 0
'   value.7 -> y = 7
'
' The function uses Pixel(), therefore rotation is automatically applied.
' VK16K33_Update() must be called afterwards.
Function VK16K33_Column(x As Byte, value As Byte) As Byte
    Dim y As Byte

    If x >= VK16K33_WIDTH Then Exit

    CFor (y = 0; y < VK16K33_HEIGHT; ++y)
        If value.y = 1 Then
            VK16K33_Pixel(x, y, 1)
        Else
            VK16K33_Pixel(x, y, 0)
        Endif
    CNext
End Function

' Turns ON all pixels.
' Useful for checking the complete display.
Function VK16K33_Test() As Byte
    VK16K33_Fill(1)
    VK16K33_Update()
End Function

'-----------------------------------------------------------------------------
' Text support - requires Include "Font_01.bas" in the main program.
' Each character uses 5 columns plus one blank spacing column.
' Font_01 column bit 0 corresponds to the top pixel.
' Draws clipped characters and supports rotation through Pixel().
'-----------------------------------------------------------------------------
Function VK16K33_PutC(char As Byte, x As Byte, y As Byte) As Byte
    Dim col As Byte
    Dim row As Byte
    Dim mask As Byte
    Dim px As Byte
    Dim py As Byte

    CFor (col = 0; col < 6; ++col)
        If col < 5 Then
            mask = Font_01(char, col)
        Else
            mask = 0
        Endif
        CFor (row = 0; row < 7; ++row)
            px = x + col
            py = y + row
            If px < 16 Then
                If py < 8 Then
                    If (mask And (1 << row)) <> 0 Then
                        VK16K33_Pixel(px, py, 1)
                    Else
                        VK16K33_Pixel(px, py, 0)
                    Endif
                Endif
            Endif
        CNext
    CNext
End Function

' Prints text from x,y, clipping at the right edge of the display.
' Use ScrollLeft later for text longer than 2 characters.
Function VK16K33_Print(str As String, x As Byte, y As Byte) As Byte
    Dim i As Byte
    Dim px As Byte
    Dim ch As Byte

    px = x
    CFor (i = 1; i <= Len(str); ++i)
        If px >= 16 Then Exit
        ch = Asc(MidStr(str, i, 1))
        VK16K33_PutC(ch, px, y)
        px = px + 6
    CNext
End Function

'-----------------------------------------------------------------------------
' Horizontal text scrolling: one pixel to the left per call.
' Requires Font_01.bas. Each character uses 5 columns + 1 blank column.
' Text enters from the right, exits completely to the left and repeats.
' Call VK16K33_ScrollReset() when changing the text or restarting.
' No delays are used. The main program controls the scrolling speed.
' The complete framebuffer is redrawn; call VK16K33_Update() afterwards.
' Rotation is inherited from VK16K33_Pixel().
'-----------------------------------------------------------------------------
Dim VK16K33_ScrollPos As Word

Function VK16K33_ScrollReset() As Byte
    VK16K33_ScrollPos = 0
End Function

Function VK16K33_ScrollLeft(str[40] As String) As Byte
    Dim text_len As Byte
    Dim x As Byte
    Dim y As Byte
    Dim char_index As Byte
    Dim font_col As Byte
    'Dim ch As Byte
    Dim mask As Byte
    Dim src As Word
    Dim total As Word

    text_len = Len(str)
    VK16K33_Clear_RAM()

    If text_len = 0 Then
        VK16K33_ScrollPos = 0
        Exit
    Endif

    ' Circular sequence: 5 font columns + 1 blank column per character.
    total = CWord(text_len) * 6

    CFor (x = 0; x < 16; ++x)
        src = VK16K33_ScrollPos + x
        If src >= total Then src = src Mod total

        char_index = src / 6
        font_col = src Mod 6

        If font_col < 5 Then
            'ch = Asc(MidStr(str, char_index + 1, 1))
            'mask = Font_01(ch, font_col)
            mask = Font_01(str(char_index), font_col)
            CFor (y = 0; y < 7; ++y)
                If (mask And (1 << y)) <> 0 Then
                    VK16K33_Pixel(x, y, 1)
                Endif
            CNext
        Endif
    CNext

    ++VK16K33_ScrollPos
    If VK16K33_ScrollPos >= total Then VK16K33_ScrollPos = 0
End Function

'-----------------------------------------------------------------------------
' Circular shift of the entire 16x8 framebuffer one pixel to the left.
' Pixels leaving the left edge reappear at the right edge of the same row.
' Works with any content: text, graphics, bitmaps, etc.
' Call VK16K33_Update() to refresh the display.
'-----------------------------------------------------------------------------
Function VK16K33_RotateLeft() As Byte
    Dim y As Byte
    Dim addr As Byte
    Dim leftByte As Byte
    Dim rightByte As Byte
    Dim carryBit As Byte

    CFor (y = 0; y < 8; ++y)
        addr = y * 2

        leftByte = VK16K33_RAM(addr)
        rightByte = VK16K33_RAM(addr + 1)

        ' Save the pixel leaving the left edge.
        carryBit = leftByte And 0x01

        ' Shift the left matrix and transfer one pixel
        ' from the right matrix.
        VK16K33_RAM(addr) = (leftByte >> 1) Or ((rightByte And 0x01) << 7)

        ' Shift the right matrix and restore the pixel
        ' leaving the left edge.
        VK16K33_RAM(addr + 1) = (rightByte >> 1) Or (carryBit << 7)
    CNext
End Function

'-----------------------------------------------------------------------------
' Circular shift of the entire 16x8 framebuffer one pixel to the right.
' Pixels leaving the right edge reappear at the left edge of the same row.
' Works with any content: text, graphics, bitmaps, etc.
' Call VK16K33_Update() to refresh the display.
'-----------------------------------------------------------------------------
Function VK16K33_RotateRight() As Byte
    Dim y As Byte
    Dim addr As Byte
    Dim leftByte As Byte
    Dim rightByte As Byte
    Dim carryBit As Byte

    CFor (y = 0; y < 8; ++y)
        addr = y * 2

        leftByte = VK16K33_RAM(addr)
        rightByte = VK16K33_RAM(addr + 1)

        ' Save the pixel leaving the right edge.
        carryBit = (rightByte >> 7) And 0x01

        ' Shift the right matrix and transfer one pixel
        ' from the left matrix.
        VK16K33_RAM(addr + 1) = (rightByte << 1) Or ((leftByte >> 7) And 0x01)

        ' Shift the left matrix and restore the pixel
        ' leaving the right edge.
        VK16K33_RAM(addr) = (leftByte << 1) Or carryBit
    CNext
End Function




