
//  Author: avishorp@gmail.com
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

extern "C" {
    #include <stdlib.h>
    #include <string.h>
    #include <inttypes.h>
}

#include <TM1637Display.h>
#include <Arduino.h>


// COMMANDS ////////////////////////////////////////////////////////////////////
#define TM1637_COM_SET_DATA     B01000000 // 0x40 (1) Data set
#define TM1637_COM_SET_ADR      B11000000 // 0xC0 (2) Address command set
#define TM1637_COM_SET_DISPLAY  B10000000 // 0x80 (3) Display control command set

// Data set (1) (use logical OR to contruct complete command)
#define TM1637_SET_DATA_WRITE   B00000000 // Write data to the display register
#define TM1637_SET_DATA_READ    B00000010 // Read the key scan data
#define TM1637_SET_DATA_A_ADDR  B00000000 // Automatic address increment
#define TM1637_SET_DATA_F_ADDR  B00000100 // Fixed address
#define TM1637_SET_DATA_M_NORM  B00000000 // Normal mode
#define TM1637_SET_DATA_M_TEST  B00100000 // Test mode

// Address settings (2) (use logical OR to contruct complete command)
#define TM1637_SET_ADR_00H      B0000000  // addr 00
#define TM1637_SET_ADR_01H      B0000001  // addr 01
#define TM1637_SET_ADR_02H      B0000010  // addr 02
#define TM1637_SET_ADR_03H      B0000011  // addr 03
#define TM1637_SET_ADR_04H      B0000100  // addr 04 (only TM1637)
#define TM1637_SET_ADR_05H      B0000101  // addr 05 (only TM1637)
// The command is used to set the display register address; if the address is set to 0C4H or higher, the data is ignored, until the effective address is set; when the power is on, the default is set to 00H address.

// Display control command set (use logical OR to consruct complete command)
#define TM1637_SET_DISPLAY_1    B0000000  // Pulse width 1/16 (0.0625) (0)
#define TM1637_SET_DISPLAY_2    B0000001  // Pulse width 2/16 (0.0625) (1)
#define TM1637_SET_DISPLAY_4    B0000010  // Pulse width 4/16 (0.0625) (2)
#define TM1637_SET_DISPLAY_10   B0000011  // Pulse width 10/16 (0.0625) (3)
#define TM1637_SET_DISPLAY_11   B0000100  // Pulse width 11/16 (0.0625) (4)
#define TM1637_SET_DISPLAY_12   B0000101  // Pulse width 12/16 (0.0625) (5)
#define TM1637_SET_DISPLAY_13   B0000110  // Pulse width 13/16 (0.0625) (6)
#define TM1637_SET_DISPLAY_14   B0000111  // Pulse width 14/16 (0.0625) (7)
#define TM1637_SET_DISPLAY_OFF  B0000000  // OFF
#define TM1637_SET_DISPLAY_ON   B0001000  // ON
// there are a total of 8 brighness values, plus off

#define pinAsOutput(P)      pinMode(P, OUTPUT)
#define pinAsInput(P)       pinMode(P, INPUT)
#define pinAsInputPullUp(P) pinMode(P, INPUT_PULLUP)
#define digitalLow(P)       digitalWrite(P, LOW)
#define digitalHigh(P)      digitalWrite(P, HIGH)
#define isHigh(P)           (digitalRead(P) == 1)
#define isLow(P)            (digitalRead(P) == 0)
#define digitalState(P)     digitalRead(P)


//
//      A
//     ---
//  F |   | B
//     -G-
//  E |   | C
//     ---
//      D
const uint8_t digitToSegment[] = 
{
    // XGFEDCBA
    0b00111111,    // 0
    0b00000110,    // 1
    0b01011011,    // 2
    0b01001111,    // 3
    0b01100110,    // 4
    0b01101101,    // 5
    0b01111101,    // 6
    0b00000111,    // 7
    0b01111111,    // 8
    0b01101111,    // 9
    0b01110111,    // A
    0b01111100,    // b
    0b00111001,    // C
    0b01011110,    // d
    0b01111001,    // E
    0b01110001     // F
};

static const uint8_t minusSegments = 0b01000000;


TM1637Display::TM1637Display(uint8_t pinClk, uint8_t pinDIO, unsigned int bitDelay)
{
    // Copy the pin numbers
    m_pinClk = pinClk;
    m_pinDIO = pinDIO;
    m_bitDelay = bitDelay;

    pinMode(m_pinClk, OUTPUT);
    pinMode(m_pinDIO, OUTPUT);
    digitalLow(m_pinClk);
    digitalLow(m_pinDIO);
}

void TM1637Display::setBrightness(uint8_t brightness, bool on)
{
    m_brightness = (brightness & 0x7) | (on? 0x08 : 0x00);
    setBacklight(m_brightness) ;
}

void TM1637Display::showNumberDec(int num, bool leading_zero, uint8_t length, uint8_t pos)
{
    showNumberDecEx(num, 0, leading_zero, length, pos);
}

void TM1637Display::showNumberDecEx(int num, uint8_t dots, bool leading_zero,
                                    uint8_t length, uint8_t pos)
{
    showNumberBaseEx(num < 0? -10 : 10, num < 0? -num : num, dots, leading_zero, length, pos);
}

void TM1637Display::showNumberHexEx(uint16_t num, uint8_t dots, bool leading_zero,
                                    uint8_t length, uint8_t pos)
{
    showNumberBaseEx(16, num, dots, leading_zero, length, pos);
}

void TM1637Display::showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots, bool leading_zero,
                                    uint8_t length, uint8_t pos)
{
    bool negative = false;
    if (base < 0) 
    {
        base = -base;
        negative = true;
    }


    uint8_t digits[4];

    if (num == 0 && !leading_zero) 
    {
        // Singular case - take care separately
        for(uint8_t i = 0; i < (length-1); i++)
        {
            digits[i] = 0;
        }
        digits[length-1] = encodeDigit(0);
    }
    else
    {
        for(int i = length-1; i >= 0; --i)
        {
            uint8_t digit = num % base;

            if (digit == 0 && num == 0 && leading_zero == false)
            {
                // Leading zero is blank
                digits[i] = 0;
            }
            else
            {
                digits[i] = encodeDigit(digit);
            }

            if (digit == 0 && num == 0 && negative)
            {
                digits[i] = minusSegments;
                negative = false;
            }
            num /= base;
        }

        if(dots != 0)
        {
            showDots(dots, digits);
        }
    }
    setSegments(digits, length, pos);
}


void TM1637Display::showDots(uint8_t dots, uint8_t* digits)
{
    for(int i = 0; i < 4; ++i)
    {
        digits[i] |= (dots & 0x80);
        dots <<= 1;
    }
}

uint8_t TM1637Display::encodeDigit(uint8_t digit)
{
    return digitToSegment[digit & 0x0f];
}





void TM1637Display::setBacklight(uint8_t value) 
{
    command(TM1637_COM_SET_DISPLAY + (m_brightness & 0x0f));
}


///////////////////////////////////////////////////////////////////////////

void TM1637Display::setSegments(const uint8_t segments[], uint8_t length, uint8_t pos)
{
    printRaw(segments, length, pos);
    setBacklight(m_brightness);
}

void TM1637Display::clear()
{
    uint8_t rawBytes[4] = {0,0,0,0};
    printRaw(rawBytes);
}



///////////////////////////////////////////////////////////////////////////

void  TM1637Display::printRaw(uint8_t rawByte, uint8_t position)
{
    uint8_t cmd[2];
    cmd[0] = TM1637_COM_SET_ADR | position;
    cmd[1] = rawByte;
    command(cmd, 2);
}

void  TM1637Display::printRaw(const uint8_t* rawBytes, size_t length, uint8_t position)
{
    // if fits on display
    if ( (length + position) <= MAX_SEGMENTS_NUM) 
    {
        uint8_t cmd[5] = {0, };
        cmd[0] = TM1637_COM_SET_ADR | (position & B111);  // sets address
        memcpy(&cmd[1], rawBytes, length);       // copy bytes

        command(cmd, length+1);                           // send to display
    }
    else 
    {
        // First print 1-4 characters
        uint8_t numtoPrint = MAX_SEGMENTS_NUM - position;
        printRaw(rawBytes, numtoPrint, position);
        delay(300);

        // keep printing 4 characters till done
        uint8_t remaining = length - numtoPrint + 3;
        uint8_t i         = 1;
        while( remaining >= MAX_SEGMENTS_NUM) 
        {
            printRaw(&rawBytes[i], MAX_SEGMENTS_NUM, 0);
            delay(300);
            remaining--;
            i++;
        }
    }
}

// TM1637 LOW LEVEL
bool    TM1637Display::command(uint8_t cmd)
{
    start();
    writeByte(cmd);
    bool acknowledged = ack();
    stop();
    return acknowledged;
}


bool TM1637Display::command(const uint8_t* commands, uint8_t length) 
{
    bool acknowledged = true;
    start();
    for (uint8_t i=0; i < length;i++) 
    {
        writeByte(commands[i]);
        acknowledged &= ack();
    }
    stop();
    return acknowledged;
}

uint8_t TM1637Display::readByte(void)
{
    uint8_t readKey = 0;

    start();
    writeByte(TM1637_COM_SET_DATA | TM1637_SET_DATA_READ);
    ack();

    pinAsInput(m_pinDIO);
    digitalHigh(m_pinDIO);
    delayMicroseconds(5);

    for ( uint8_t i=0; i < 8; i++) 
    {
        readKey >>= 1;
        digitalLow(m_pinClk);
        delayMicroseconds(30);

        digitalHigh(m_pinClk);

        if ( isHigh(m_pinDIO) ) 
        {
            readKey = readKey | B10000000;
        }
        delayMicroseconds(30);
    }
    pinAsOutput(m_pinDIO);
    ack();
    stop();
    return readKey;
};

void    TM1637Display::writeByte(uint8_t command) 
{
    // CLK in bits
    for ( uint8_t i=0; i < 8; i++) 
    {
        digitalLow(m_pinClk);   // CLK LOW

        if ( command & B1) 
        {
            digitalHigh(m_pinDIO);// DIO HIGH
        } 
        else 
        {
            digitalLow(m_pinDIO); // DIO LOW
        }
        delayMicroseconds(m_bitDelay);

        command >>= 1;

        digitalHigh(m_pinClk);   // CLK HIGH
        delayMicroseconds(m_bitDelay);
    }
}

void    TM1637Display::start(void) 
{
    pinMode(m_pinClk, OUTPUT);
    pinMode(m_pinDIO, OUTPUT);

    digitalHigh(m_pinDIO);   // DIO HIGH
    digitalHigh(m_pinClk);   // CLK HIGH
    delayMicroseconds(m_bitDelay);

    digitalLow(m_pinDIO);    // DIO  LOW
}

void    TM1637Display::stop(void) 
{
    digitalLow(m_pinClk);   // CLK LOW
    delayMicroseconds(m_bitDelay);

    digitalLow(m_pinDIO);    // DIO LOW
    delayMicroseconds(m_bitDelay);

    digitalHigh(m_pinClk);   // CLK HIGH
    delayMicroseconds(m_bitDelay);

    digitalHigh(m_pinDIO);   // DIO HIGH
}


bool    TM1637Display::ack(void)
{
    bool acknowledged = false;

    digitalLow(m_pinClk);          // CLK  LOW
    pinAsInputPullUp(m_pinDIO);    // DIO INPUT PULLUP (state==HIGH)
    delayMicroseconds(m_bitDelay);

    acknowledged = isLow(m_pinDIO);// Ack should pull the pin low again

    digitalHigh(m_pinClk);         // CLK HIGH
    delayMicroseconds(m_bitDelay);

    digitalLow(m_pinClk);          // CLK  LOW
    pinAsOutput(m_pinDIO);

    return acknowledged;
}


