'=============================================================================================
' TM1637 Library (Oshonsoft PIC Basic)
' Target MCU: PIC18F46K22 @ 64 MHz  (HL-K18 board)
' Toolchain:  Pic18 Simulator IDE V5.90
'
' Versions:   2025-01-25, 2024-11, 2024-04
' Author:     COS (dogflu66@yahoo.es)
'
' Description:
'   Driver library for TM1637-based 7-segment display modules:
'     - 4-digit display
'     - 6-digit display with keyboard (key scan)
'
'   Implementation is based on the timing and examples from the TM1637 datasheet,
'   with practical fixes for open-drain handling, ACK timeouts and module speed
'   variations (some boards require longer delays).
'
' Important IDE setting:
'   Options → "Initialize variables in declaration" → must be UNCHECKED.
'
' Notes:
'   • Interface uses two signals: CLK (push-pull) and DIO (open-drain).
'   • DIO must be implemented as open-drain (input to release / output-low to pull down).
'   • Not all TM1637 modules run at the same speed—tune delays if needed.
'=============================================================================================

'------------------------------------ TM1637 command set -------------------------------------
' Mode command (data write, auto address increment):
'   Const TM1637_MODE = 0x40   ' 0b0100_0000
'
' Address command (first digit base address):
'   Const TM1637_ADDRESS = 0xC0 ' 0b1100_0000
'
' Display control commands:
'   Const TM1637_CONTROL_OFF = 0x80 ' 0b1000_0000, display off
'   Const TM1637_CONTROL_ON  = 0x88 ' 0b1000_1000, display on at minimum brightness
'
' Brightness is set by OR-ing 0x88 with a brightness value (0x00..0x07).
'---------------------------------------------------------------------------------------------

'----------------------------------------- Pin map -------------------------------------------
' TM1637 pins (board labels in parentheses):
'   TM1637_CLK  -> LATB.0  (SCI / CLK)  : push-pull output
'   TM1637_DIO  -> LATB.1  (SDA / DIO)  : open-drain (drive low or release)
'   TM1637_DIO_IN -> PORTB.1            : input readback (keys / data)
'   TM1637_CLK_IO -> TRISB.0            : TRIS control for CLK
'   TM1637_DIO_IO -> TRISB.1            : TRIS control for DIO (open-drain emulation)
'
Symbol TM1637_CLK      = LATB.0     '(PCB label: SCI / CLK)
Symbol TM1637_DIO      = LATB.1     '(PCB label: SDA / DIO)
Symbol TM1637_DIO_IN   = PORTB.1    'Readback for DIO (keys / data)
Symbol TM1637_CLK_IO   = TRISB.0    'TRIS for CLK (0=output)
Symbol TM1637_DIO_IO   = TRISB.1    'TRIS for DIO (1=input=release, 0=output-low)

'-------------------------------------- Module geometry --------------------------------------
' Number of display digits (set to 4 or 6 depending on your hardware)
Const TM1637_DIGIT_NUMBER = 6

'------------------------------------------ Delays -------------------------------------------
' Timing guard bands (microseconds). Tune to your specific module if necessary.
' Example presets:
'   Fast modules:
'       Const TM1637_DELAY1 = 2
'       Const TM1637_DELAY2 = 1
'   Slow modules:
'       Const TM1637_DELAY1 = 30
'       Const TM1637_DELAY2 = 1
'   Very slow modules (fallback):
Const TM1637_DELAY1 = 100   ' µs
Const TM1637_DELAY2 = 100   ' µs

'----------------------------------- Open-drain helpers --------------------------------------
' Drive DIO high (release line): configure as input so pull-up on the module pulls it high.
Function TM1637_DIO_HIGH() As Bit
    TM1637_DIO_IO = 1  'Input (released). External pull-up on the module sets logic HIGH.
    ' Open-drain transitions are slower than push-pull: allow the line to settle.
    ' These NOPs act as a short, deterministic delay across fast/slow modules.
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
    ASM: NOP
End Function

' Drive DIO low: configure as output and pull the line down.
Function TM1637_DIO_LOW() As Bit
    TM1637_DIO_IO = 0  'Output
    TM1637_DIO    = 0  'Force logic LOW
End Function

'---------------------------------------- Brightness -----------------------------------------
' Predefined brightness levels (OR with base command 0x88)
Const TM1637_LOW_BRIGHT = 0x00
Const TM1637_MID_BRIGHT = 0x02
Const TM1637_MAX_BRIGHT = 0x07

'------------------------------------- Segment reference --------------------------------------
' Segment order for bit mapping:
'
'      a0
'     ----
' f5 | g6 | b1
'     ----
' e4 |    | c2
'     ----
'      d3

'Segment LED patterns.
'TM1637_Digit(0) = 0b00111111 ' 0
'TM1637_Digit(1) = 0b00000110 ' 1
'TM1637_Digit(2) = 0b01011011 ' 2
'TM1637_Digit(3) = 0b01001111 ' 3
'TM1637_Digit(4) = 0b01100110 ' 4
'TM1637_Digit(5) = 0b01101101 ' 5
'TM1637_Digit(6) = 0b01111101 ' 6
'TM1637_Digit(7) = 0b00000111 ' 7
'TM1637_Digit(8) = 0b01111111 ' 8
'TM1637_Digit(9) = 0b01101111 ' 9
'TM1637_Digit(10) = 0b01110111 'A
'TM1637_Digit(11) = 0b01111100 'b
'TM1637_Digit(12) = 0b00111001 'C
'TM1637_Digit(13) = 0b01011110 'd
'TM1637_Digit(14) = 0b01111001 'E
'TM1637_Digit(15) = 0b01110001 'F
'TM1637_Digit(16) = 0b01000000 '-
'TM1637_Digit(17) = 0b10000000 ':' , '.'
'TM1637_Digit(18) = 0b00000000 'Space
'-----------------------------------------------------------------------
'Select digit representation based on the segments to illuminate.
Function TM1637_Digit(index As Byte) As Byte
    Dim char As Byte
    char = LookUp(0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b01110111, 0b01111100, 0b00111001, 0b01011110, 0b01111001, 0b01110001, 0b01000000, 0b10000000, 0b00000000), index
    ReturnValue char
End Function
'------------------------------------- Bus primitives ----------------------------------------
' START condition: while CLK is high, pull DIO low to begin a transaction.
Function TM1637_Start() As Bit
    TM1637_CLK = 1
    TM1637_DIO_HIGH()           'Release DIO (open-drain)
    WaitUs TM1637_DELAY1
    TM1637_DIO_LOW()            'Start: drive DIO LOW
End Function

' ACK phase: sample device ACK after the 8th falling edge; includes timeout to avoid lockups.
' Returns: 0 = ACK received, 1 = no response (timeout).
Function TM1637_Ask() As Byte
    Dim counter As Word
    counter = 100
    TM1637_DIO_HIGH()           'Release DIO to let device drive ACK
    TM1637_CLK = 0
    WaitUs TM1637_DELAY2        'Datasheet: ~5 µs after the 8th falling edge before ACK check

    While TM1637_DIO = 1 And counter > 0   'Wait for device to pull DIO low
        WaitUs 1
        counter--
    Wend

    TM1637_CLK = 1
    WaitUs TM1637_DELAY2
    TM1637_CLK = 0

    If counter = 0 Then
        ReturnValue 1           'No ACK (device not responding)
    Else
        ReturnValue 0           'ACK received
    Endif
End Function

' STOP condition: while CLK is high, release DIO to end the transaction.
Function TM1637_Stop() As Bit
    TM1637_CLK = 0
    WaitUs TM1637_DELAY2
    TM1637_DIO_LOW()
    WaitUs TM1637_DELAY2
    TM1637_CLK = 1
    WaitUs TM1637_DELAY2
    TM1637_DIO_HIGH()           'Release DIO high (stop)
End Function

' Write one byte LSB-first on DIO; DIO=HIGH means release (logic 1), DIO=LOW drives a 0.
Function TM1637_WriteByte(oneByte As Byte) As Bit
    Dim i As Byte
    CFor (i = 0; i < 8; i++)
        TM1637_CLK = 0
        If (oneByte & 0x01) Then
            TM1637_DIO_HIGH()   'Bit=1 → release line
        Else
            TM1637_DIO_LOW()    'Bit=0 → drive low
        Endif
        WaitUs TM1637_DELAY1
        oneByte = oneByte >> 1
        TM1637_CLK = 1
        WaitUs TM1637_DELAY1
    CNext
    TM1637_CLK = 0              'Hold low before ACK phase
End Function

'--------------------------------------- Key scanning ----------------------------------------
' Read the 8-bit key status (module-dependent mapping). LSB is read first.
Function TM1637_ScanKey() As Byte
    Dim rkey, i As Byte
    rkey = 0

    TM1637_Start()
    TM1637_WriteByte(0x42)      'Read-keys command
    TM1637_Ask()

    CFor (i = 0; i < 8; i++)    'Read LSB first
        TM1637_CLK = 0
        rkey = rkey >> 1
        WaitUs TM1637_DELAY1
        TM1637_CLK = 1
        If (TM1637_DIO_IN) Then
            rkey.7 = 1          'Set MSB as we shift right each cycle
        Endif
        WaitUs TM1637_DELAY1
    CNext

    TM1637_Ask()
    TM1637_Stop()
    ReturnValue rkey
End Function

'------------------------------------ Display initialisation ---------------------------------
' Initialise display RAM and turn display on at maximum brightness.
' Example (main): TM1637_Init()
Function TM1637_Init() As Bit
    TM1637_CLK_IO = 0           'CLK as output
    TM1637_DIO_IO = 0           'DIO as output (will switch to input to release)

    TM1637_Start()
    TM1637_WriteByte(0x40)      '0x40: data write, auto-increment address
    TM1637_Ask()
    TM1637_Stop()

    TM1637_Start()
    TM1637_WriteByte(0xC0)      'Start at address 0
    TM1637_Ask()
    Dim i As Byte
    CFor (i = 0; i < TM1637_DIGIT_NUMBER; i++)
        TM1637_WriteByte(0xFF)  'Initial pattern (all segments ON) – adjust if you prefer blanking
        TM1637_Ask()
    CNext
    TM1637_Stop()

    TM1637_Start()
    TM1637_WriteByte(0x88 Or TM1637_MAX_BRIGHT)  'Display ON, max brightness
    TM1637_Ask()
    TM1637_Stop()
End Function

' Set global brightness (0x00..0x07). Value is OR-ed with 0x88 (display ON).
Function TM1637_Bright(Bright As Byte) As Bit
    TM1637_Start()
    TM1637_WriteByte(0x88 Or Bright)
    TM1637_Ask()
    TM1637_Stop()
End Function

'------------------------------------ Random-access write ------------------------------------
' Write a raw 7-seg pattern to a specific digit (0..7). MSB is the dot/colon bit.
Function TM1637_WriteChar(Dig As Byte, nDigit As Byte) As Bit
    TM1637_Start()
    TM1637_WriteByte(0x44)                      'Fixed address mode
    TM1637_Ask()
    TM1637_Stop()

    TM1637_Start()
    TM1637_WriteByte(0xC0 Or (nDigit And 0x07)) 'Digit address
    TM1637_Ask()
    TM1637_WriteByte(Dig)
    TM1637_Ask()
    TM1637_Stop()
End Function

'--------------------------------------- Bulk write (4) --------------------------------------
' Write four consecutive digits starting at address 0 (for 4-digit modules).
' Example:
'   TM1637_Write4Char( TM1637_Digit((n/1000)%10), TM1637_Digit((n/100)%10),
'                      TM1637_Digit((n/10)%10),   TM1637_Digit(n%10) )
Function TM1637_Write4Char(Dig0 As Byte, Dig1 As Byte, Dig2 As Byte, Dig3 As Byte) As Bit
    TM1637_Start()
    TM1637_WriteByte(0x40)      'Auto-increment mode
    TM1637_Ask()
    TM1637_Stop()

    TM1637_Start()
    TM1637_WriteByte(0xC0)      'Start at address 0
    TM1637_Ask()
    TM1637_WriteByte(Dig0)
    TM1637_Ask()
    TM1637_WriteByte(Dig1)
    TM1637_Ask()
    TM1637_WriteByte(Dig2)
    TM1637_Ask()
    TM1637_WriteByte(Dig3)
    TM1637_Ask()
    TM1637_Stop()
End Function

'--------------------------------------- Bulk write (6) --------------------------------------
' Write a signed/unsigned long as six digits (for 6-digit modules).
' Leftmost digits get the higher decades.
Function TM1637_WriteNumber(number As Long) As Bit
    TM1637_Start()
    TM1637_WriteByte(0x40)      'Auto-increment mode
    TM1637_Ask()
    TM1637_Stop()

    TM1637_Start()
    TM1637_WriteByte(0xC0)      'Start at address 0
    TM1637_Ask()
    TM1637_WriteByte(TM1637_Digit((number/100000)%10))
    TM1637_Ask()
    TM1637_WriteByte(TM1637_Digit((number/10000)%10))
    TM1637_Ask()
    TM1637_WriteByte(TM1637_Digit((number/1000)%10))
    TM1637_Ask()
    TM1637_WriteByte(TM1637_Digit((number/100)%10))
    TM1637_Ask()
    TM1637_WriteByte(TM1637_Digit((number/10)%10))
    TM1637_Ask()
    TM1637_WriteByte(TM1637_Digit(number%10))
    TM1637_Ask()
    TM1637_Stop()
End Function

'------------------------------------ String formatting --------------------------------------
' Render an ASCII string (up to 2 chars per digit to allow dot/colon) onto 4- or 6-digit modules.
' Supported chars: '0'..'9', 'A'..'F', '-', ' ', '.' and ':' (dot/colon sets the MSB of prior digit).
' Behaviour:
'   • Left-pads with '0' up to 2*digits to consistently pair dot/colon with the preceding char.
'   • If string is longer than 2*digits, it is truncated on the left.
Function TM1637_WriteString(_string[12] As String) As Bit
    Dim Dig(6) As Byte
    Dim lenString As Byte
    Dim i As Byte
    Dim x As Byte
    x = TM1637_DIGIT_NUMBER - 1

    lenString = Len(_string)
    If lenString > (TM1637_DIGIT_NUMBER * 2) Then lenString = TM1637_DIGIT_NUMBER * 2

    ' Left-pad to align pairs (char + optional dot/colon)
    For i = 1 To ((TM1637_DIGIT_NUMBER * 2) - lenString)
        _string = "0" + _string
    Next i

    ' Pack characters into digit patterns from right to left
    For i = (TM1637_DIGIT_NUMBER * 2 - 1) To 0 Step -1
        If _string(i) = "." Or _string(i) = ":" Then
            Dig(x) = 0x80
            If _string(i-1) <> "." And _string(i-1) <> ":" Then
                i--
                Dig(x) = Dig(x) Or TM1637_Digit(TM1637_GetCharIndex(_string(i)))
            Endif
        Else
            Dig(x) = TM1637_Digit(TM1637_GetCharIndex(_string(i)))
        Endif
        x--
        If x > TM1637_DIGIT_NUMBER - 1 Then Exit For
    Next i

    ' Burst write
    TM1637_Start()
    TM1637_WriteByte(0x40)      'Auto-increment mode
    TM1637_Ask()
    TM1637_Stop()

    TM1637_Start()
    TM1637_WriteByte(0xC0)      'Start at address 0
    TM1637_Ask()
    TM1637_WriteByte(Dig(0))
    TM1637_Ask()
    TM1637_WriteByte(Dig(1))
    TM1637_Ask()
    TM1637_WriteByte(Dig(2))
    TM1637_Ask()
    TM1637_WriteByte(Dig(3))
    TM1637_Ask()
    If TM1637_DIGIT_NUMBER = 4 Then
        TM1637_Stop()
        Exit
    Endif
    TM1637_WriteByte(Dig(4))
    TM1637_Ask()
    TM1637_WriteByte(Dig(5))
    TM1637_Ask()
    TM1637_Stop()
End Function

' Map ASCII to digit table index.
' Returns:
'   '0'..'9' -> 0..9
'   'A'..'F' -> 10..15
'   '-'     -> 16  (minus)
'   ' '     -> 18  (space)
'   default -> 17  (dot/colon as standalone or unknown char -> dot only)
Function TM1637_GetCharIndex(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
            Else
                If char = " " Then
                    ReturnValue 18
                Else
                    ReturnValue 17
                Endif
            Endif
        Endif
    Endif
End Function
