'=============================================================================================
' TM1638 / QYF-TM1638 Driver Library (Oshonsoft PIC Basic)
' Target MCU : PIC18F26K22 @ 64 MHz
' Hardware   : TM1638-based: digit modules with LEDs and keys
' Variants   : LED&KEY TM1638, QYF-TM1638 (different key map / memory layout)
' Toolchain  : Pic18 Simulator IDE V5.91 / Amicus18
'
' Author     : COS (dogflu66@yahoo.es)
' Versions   : 2024-12-01, 2024-09, 2024-09
'
' Description:
'   Full-featured driver for TM1638 modules. Provides:
'     • Display initialization and brightness control
'     • Raw segment write per digit
'     • Raw LED control per position
'     • Formatted string rendering (digits, A–F, '-', space, '.' / ':')
'     • Key scan reading (single or simultaneous)
'     • Runtime selection between LED&KEY and QYF-TM1638 boards
'
' Electrical / protocol notes:
'   • 3-wire proprietary bus (NOT I²C / NOT SPI): STB (strobe), CLK (clock), DIO (data)
'   • DIO line behaves as open-drain: drive LOW for logic 0, release (input) for logic 1
'   • Timing can be very fast on typical boards; DELAY1_TM1638 is provided for guard time
'
' IDE requirement:
'   Options → "Initialize variables in declaration" → MUST be UNCHECKED.
'=============================================================================================

'-------------------------------------- Command set ------------------------------------------
' Mode (data write):
'   0x40  Write, auto-increment address
'   0x44  Write, fixed address
'
' Base address:
'   0xC0  First digit/LED address
'
' Display control:
'   0x80  Display OFF
'   0x88  Display ON, minimum brightness
'   0x8F  Display ON, maximum brightness
'---------------------------------------------------------------------------------------------

'----------------------------------- Variant & RAM buffers -----------------------------------
' QYF board selector (0 = LED&KEY board, 1 = QYF-TM1638 board)
Dim QYF_TM1638 As Byte

' Per-digit shadow buffer for QYF layout (bit-per-digit packing)
Dim TM1638_RamDigit(8) As Byte

'---------------------------------------- Pin mapping ----------------------------------------
' Assign MCU pins (update if your wiring changes)
Symbol STB_TM1638     = LATB.0  ' STB (strobe / latch)
Symbol CLK_TM1638     = LATB.1  ' CLK (clock, push-pull)
Symbol DIO_TM1638     = LATB.2  ' DIO (data, open-drain emulation)
Symbol DIO_TM1638_IN  = PORTB.2 ' DIO readback (keys/data)

' TRIS control for direction changes
Symbol STB_TM1638_IO  = TRISB.0 ' 0=output
Symbol CLK_TM1638_IO  = TRISB.1 ' 0=output
Symbol DIO_TM1638_IO  = TRISB.2 ' 1=input (release), 0=output (drive low)

'-------------------------------------- Display geometry -------------------------------------
Const DigitsTM1638_NUMBER = 8     ' Number of display digits (TM1638 = 8)

'------------------------------------------- Timing ------------------------------------------
' Guard delay (in microseconds). Typical boards work with very small values.
Const DELAY1_TM1638 = 1           ' µs; increase if you see bus glitches

'------------------------------------- Board selection API -----------------------------------
' Select QYF-TM1638 board behaviour (different memory/key mapping)
Proc Set_QYF_TM1638()
    QYF_TM1638 = 1
End Proc

' Select standard LED&KEY TM1638 board behaviour
Proc Set_LEDKEY_TM1638()
    QYF_TM1638 = 0
End Proc

'--------------------------------- Open-drain helpers (DIO) ----------------------------------
' Release DIO line (logic 1 via external pull-up on the module)
Function DIO_TM1638_HIGH() As Bit
    DIO_TM1638_IO = 1  ' Input mode → line floats high via pull-up
    ' Short settling delay — keeps behaviour consistent across boards
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
End Function

' Drive DIO line low (logic 0)
Function DIO_TM1638_LOW() As Bit
    DIO_TM1638_IO = 0  ' Output
    DIO_TM1638    = 0  ' Force LOW
End Function

'--------------------------------------- Brightness levels -----------------------------------
' OR these values with 0x88 (display ON) when setting brightness
Const LOW_BRIGHT = 0x00
Const MID_BRIGHT = 0x02
Const MAX_BRIGHT = 0x07

'------------------------------------- Segment reference -------------------------------------
' Segment map (bit positions):
'
'          a(0)
'         ------
'    f(5)| g(6) |b(1)
'         ------
'    e(4)|      |c(2)
'         ------
'          d(3)    . (dot)
'
' Mask bit order in this implementation: dot g6 f5 e4 d3 c2 b1 a0
'
' Character table (0..9, A..F, '-', dot/colon, space)
Function DigitsTM1638(index As Byte) As Byte
    Dim mask As Byte
    ' Map ASCII to table indices for convenience
    If index = "-" Then
        index = 16
    Else
        If index = "." Or index = ":" Then
            index = 17
        Else
            If index = " " Then
                index = 18
            Endif
        Endif
    Endif

    mask = LookUp(0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b01110111, 0b01111100, 0b00111001, 0b01011110, 0b01111001, 0b01110001, 0b01000000, 0b10000000, 0b00000000), index
    ReturnValue mask
End Function

'----------------------------------------- Bus control ---------------------------------------
' START: latch low while CLK high, DIO released beforehand
Function StartTM1638() As Bit
    STB_TM1638 = 1
    CLK_TM1638 = 1
    DIO_TM1638_HIGH()           ' Release DIO (logic 1)
    STB_TM1638 = 0              ' Begin frame
End Function

' ASK (placeholder): basic inter-command guard using STB pulse
' Note: This routine does NOT sample an ACK bit (TM1638 write cycles don't expose it).
Function AskTM1638() As Byte
    CLK_TM1638 = 1
    STB_TM1638 = 1
    DIO_TM1638_HIGH()
    WaitUs DELAY1_TM1638
    STB_TM1638 = 0
    WaitUs DELAY1_TM1638
End Function

' STOP: release latch
Function StopTM1638() As Bit
    STB_TM1638 = 1
End Function

' Write one byte on DIO, LSB first
Function WriteByte(oneByte As Byte) As Bit
    Dim i As Byte
    CFor (i = 0; i < 8; i++)
        CLK_TM1638 = 0
        WaitUs DELAY1_TM1638

        If (oneByte.0) Then     ' LSB → 1? release line
            DIO_TM1638_HIGH()
        Else                    ' LSB → 0? drive low
            DIO_TM1638_LOW()
        Endif

        WaitUs DELAY1_TM1638
        oneByte = oneByte >> 1
        CLK_TM1638 = 1
        WaitUs DELAY1_TM1638
    CNext
End Function

'------------------------------------------- Key scan ----------------------------------------
' Returns 16-bit key bitmap. Mapping depends on board variant (LED&KEY vs QYF).
Function ScanKeyTM1638() As Word
    Symbol outKey = ScanKeyTM1638
    Dim rKey(4) As Byte
    Dim i, x, y As Byte

    rKey(0) = 0
    rKey(1) = 0
    rKey(2) = 0
    rKey(3) = 0
    x = 0
    outKey = 0

    StartTM1638()
    WriteByte(0x42)             ' Read-keys command
    DIO_TM1638_HIGH()

    For y = 0 To 3
        WaitUs DELAY1_TM1638
        CFor (i = 0; i < 8; i++) ' Read LSB first
            CLK_TM1638 = 0
            WaitUs DELAY1_TM1638
            CLK_TM1638 = 1
            WaitUs DELAY1_TM1638

            rKey(x) = rKey(x) >> 1
            If (DIO_TM1638_IN) Then
                rKey(x).7 = 1
            Else
                rKey(x).7 = 0
            Endif
        CNext
        x++
    Next y

    StopTM1638()
    DIO_TM1638_LOW()

    ' Assemble 16-bit key result from the 4 bytes according to board type
    If QYF_TM1638 = 0 Then      ' LED&KEY mapping
        outKey.0 = rKey(0).0
        outKey.1 = rKey(1).0
        outKey.2 = rKey(2).0
        outKey.3 = rKey(3).0
        outKey.4 = rKey(0).4
        outKey.5 = rKey(1).4
        outKey.6 = rKey(2).4
        outKey.7 = rKey(3).4
    Else                        ' QYF mapping
        outKey.0  = rKey(0).2
        outKey.1  = rKey(0).6
        outKey.2  = rKey(1).2
        outKey.3  = rKey(1).6
        outKey.4  = rKey(2).2
        outKey.5  = rKey(2).6
        outKey.6  = rKey(3).2
        outKey.7  = rKey(3).6

        outKey.8  = rKey(0).1
        outKey.9  = rKey(0).5
        outKey.10 = rKey(1).1
        outKey.11 = rKey(1).5
        outKey.12 = rKey(2).1
        outKey.13 = rKey(2).5
        outKey.14 = rKey(3).1
        outKey.15 = rKey(3).5
    Endif
End Function

'-------------------------------------- Initialization ----------------------------------------
' Initialize display RAM and turn display ON at max brightness (LED&KEY by default)
' Example (main): SmgDisplayTM1638()
Function SmgDisplayTM1638() As Bit
    QYF_TM1638 = 0              ' Default to LED&KEY

    CLK_TM1638_IO = 0           ' Outputs
    DIO_TM1638_IO = 0
    STB_TM1638_IO = 0

    Dim i As Byte
    StartTM1638()
    WriteByte(0x40)             ' Auto-increment mode
    AskTM1638()
    WriteByte(0xC0)             ' Start at 0xC0
    CFor (i = 0; i < 16; i++)   ' 16 bytes: interleaved DIG/LED
        WriteByte(0xFF)         ' All digits and LEDs ON (power-on test)
    CNext
    AskTM1638()
    WriteByte(0x8F)             ' Display ON, max brightness
    StopTM1638()

    ' Clear QYF shadow RAM
    For i = 0 To 7
        TM1638_RamDigit(i) = 0
    Next i
End Function

' Set global brightness (0..7). OR'ed internally with 0x88 (display ON).
Function BrightTM1638(Bright As Byte) As Bit
    StartTM1638()
    WriteByte(0x88 Or Bright)
    StopTM1638()
End Function

'----------------------------------- Random-access writes ------------------------------------
' Write raw segment mask to a given digit (0..7).
' For LED&KEY: digits live at even addresses (C0, C2, ..., CE).
' For QYF:     segments are packed bitwise into TM1638_RamDigit[] and then burst-written.
Function writeRawDigitTM1638(segment As Byte, nDigit As Byte) As Bit
    If QYF_TM1638 = 0 Then      ' LED&KEY
        nDigit = nDigit * 2     ' Even addresses are digits
        StartTM1638()
        WriteByte(0x44)         ' Fixed address mode
        AskTM1638()
        WriteByte(0xC0 Or (nDigit And 0x0F))
        WriteByte(segment)
        StopTM1638()
    Else                        ' QYF
        TM1638_RamDigit(0).nDigit = segment.0
        TM1638_RamDigit(1).nDigit = segment.1
        TM1638_RamDigit(2).nDigit = segment.2
        TM1638_RamDigit(3).nDigit = segment.3
        TM1638_RamDigit(4).nDigit = segment.4
        TM1638_RamDigit(5).nDigit = segment.5
        TM1638_RamDigit(6).nDigit = segment.6
        TM1638_RamDigit(7).nDigit = segment.7

        StartTM1638()
        WriteByte(0x40)         ' Auto-increment
        AskTM1638()
        WriteByte(0xC0)         ' Start at 0xC0
        WriteByte(TM1638_RamDigit(0))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(1))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(2))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(3))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(4))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(5))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(6))
        WriteByte(0)
        WriteByte(TM1638_RamDigit(7))
        WriteByte(0)
        StopTM1638()
    Endif
End Function

' Write a single LED state at index nLed (0..7). For LED&KEY, LEDs are at odd addresses.
Function writeRawLedTM1638(stateLed As Byte, nLed As Byte) As Bit
    If stateLed > 1 Then stateLed = 1
    nLed = (nLed * 2) + 1       ' Odd addresses are LEDs
    StartTM1638()
    WriteByte(0x44)             ' Fixed address
    AskTM1638()
    WriteByte(0xC0 Or (nLed And 0x0F))
    WriteByte(stateLed)
    StopTM1638()
End Function

'-------------------------------------- Burst operations -------------------------------------
' Write all 8 digit segment masks (left->right arguments map to addresses C0..CE).
' For QYF, digits are mirrored (right-to-left) via writeRawDigitTM1638 calls.
Function displayValuesTM1638(segment0 As Byte, segment1 As Byte, segment2 As Byte, segment3 As Byte, segment4 As Byte, segment5 As Byte, segment6 As Byte, segment7 As Byte) As Bit

    If QYF_TM1638 = 0 Then      ' LED&KEY
        StartTM1638()
        WriteByte(0x44)         ' Fixed address
        AskTM1638()
		WriteByte(0xC0)
		WriteByte(segment0)
		AskTM1638()
		WriteByte(0xC2)
		WriteByte(segment1)
		AskTM1638()
		WriteByte(0xC4)
		WriteByte(segment2)
		AskTM1638()
		WriteByte(0xC6)
		WriteByte(segment3)
		AskTM1638()
		WriteByte(0xC8)
		WriteByte(segment4)
		AskTM1638()
		WriteByte(0xCA)
		WriteByte(segment5)
		AskTM1638()
		WriteByte(0xCC)
		WriteByte(segment6)
		AskTM1638()
		WriteByte(0xCE)
		WriteByte(segment7)
		StopTM1638()

    Else							 'QYF

		writeRawDigitTM1638(segment0, 7)
		writeRawDigitTM1638(segment1, 6)
		writeRawDigitTM1638(segment2, 5)
		writeRawDigitTM1638(segment3, 4)
		writeRawDigitTM1638(segment4, 3)
		writeRawDigitTM1638(segment5, 2)
		writeRawDigitTM1638(segment6, 1)
		writeRawDigitTM1638(segment7, 0)

	Endif
End Function

'--------------------------------------- String rendering ------------------------------------
' Render a formatted string to the 8-digit display.
' Accepts digits 0–9, A–F, '-', ' ', '.' and ':' (dot/colon sets MSB on preceding digit).
' Pads on the left with '0' to align char + dot pairs across the field.
Function DisplayStringTM1638(_string[16] As String) As Bit
    Dim segment(8) As Byte
    Dim lenString As Byte
    Dim i As Byte
    Dim x As Byte

    x = DigitsTM1638_NUMBER - 1     ' Rightmost digit index

    lenString = Len(_string)
    If lenString > (DigitsTM1638_NUMBER * 2) Then
        lenString = DigitsTM1638_NUMBER * 2
    Endif

    ' Left-pad with '0' so that pairs (char + optional dot) line up
    For i = 1 To ((DigitsTM1638_NUMBER * 2) - lenString)
        _string = "0" + _string
    Next i

    ' Pack characters right-to-left, overlaying dot/colon on the same digit
    For i = (DigitsTM1638_NUMBER * 2 - 1) To 0 Step -1
        If _string(i) = "." Or _string(i) = ":" Then
            segment(x) = 0x80
            If _string(i-1) <> "." And _string(i-1) <> ":" Then
                i--
                segment(x) = segment(x) Or DigitsTM1638(GetSegmentIndexTM1638(_string(i)))
            Endif
        Else
            segment(x) = DigitsTM1638(GetSegmentIndexTM1638(_string(i)))
        Endif
        x--
        If x > DigitsTM1638_NUMBER - 1 Then Exit For
    Next i

    ' Send data to the device
    If QYF_TM1638 = 0 Then      ' LED&KEY
        StartTM1638()
        WriteByte(0x44)         ' Fixed address
        AskTM1638()

        WriteByte(0xC0)
        WriteByte(segment(0))
        AskTM1638()
        WriteByte(0xC2)
        WriteByte(segment(1))
        AskTM1638()
        WriteByte(0xC4)
        WriteByte(segment(2))
        AskTM1638()
        WriteByte(0xC6)
        WriteByte(segment(3))
        AskTM1638()
        WriteByte(0xC8)
        WriteByte(segment(4))
        AskTM1638()
        WriteByte(0xCA)
        WriteByte(segment(5))
        AskTM1638()
        WriteByte(0xCC)
        WriteByte(segment(6))
        AskTM1638()
        WriteByte(0xCE)
        WriteByte(segment(7))
        StopTM1638()
    Else                        ' QYF
        Dim idx As Byte
        x = 7
        For idx = 0 To 7
            writeRawDigitTM1638(segment(idx), x)
            x--
        Next idx
    Endif
End Function

' Translate ASCII char to segment table index used by DigitsTM1638()
Function GetSegmentIndexTM1638(char As Byte) As Byte
    If char >= "0" And char <= "9" Then
        ReturnValue char - "0"
    Else
        If char >= "A" And char <= "F" Then
            ReturnValue char - "A" + 10
        Else
            If char = "-" Then
                ReturnValue 16        ' minus
            Else
                If char = " " Then
                    ReturnValue 18    ' space
                Else
                    ReturnValue 17    ' dot/colon or unknown → dot MSB only
                Endif
            Endif
        Endif
    Endif
End Function
