// ---------------------------------------------------------------------------
// Created by Francisco Malpartida on 7.3.2012.
// Copyright (C) - 2018
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    This program 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 General Public License for more details.
//
//    You should have received a copy of the GNU General Public License v3.0
//    along with this program.
//    If not, see <https://www.gnu.org/licenses/gpl-3.0.en.html>.
// 
// ---------------------------------------------------------------------------
//
// This software is furnished "as is", without technical support, and with no 
// warranty, express or implied, as to its usefulness for any purpose.
//
// Thread Safe: No
// Extendable: Yes
//
// @file LiquidCrystal_SRG.h
// This file implements a basic liquid crystal library that comes as standard
// in the Arduino SDK but using a generic SHIFT REGISTER extension board.
// 
// @brief 
// This is a basic implementation of the LiquidCrystal library of the
// Arduino SDK. The original library has been reworked in such a way that 
// this class implements the all methods to command an LCD based
// on the Hitachi HD44780 and compatible chipsets using a 3 wire latching
// shift register. While it has been tested with a 74HC595N shift register
// it should also work with other latching shift registers such as the MC14094
// and the HEF4094
//
// This particular driver has been created as generic as possible to enable
// users to configure and connect their LCDs using just 3 digital IOs from the
// AVR or Arduino, and connect the LCD to the outputs of the shiftregister
// in any configuration. The library is configured by passing the IO pins
// that control the strobe, data and clock of the shift register and a map
// of how the shiftregister is connected to the LCD.
// 
//
//   +--------------------------------------------+
//   |                 MCU                        |
//   |   IO1           IO2           IO3          |
//   +----+-------------+-------------+-----------+
//        |             |             |
//        |             |             |
//   +----+-------------+-------------+-----------+
//   |    Strobe        Data          Clock       |
//   |          8-bit shift/latch register        | 74HC595N
//   |    Qa0  Qb1  Qc2  Qd3  Qe4  Qf5  Qg6  Qh7  |
//   +----+----+----+----+----+----+----+----+----+
//        |    |    |    |    |    |    |    
//        |11  |12  |13  |14  |6   |5   |4   (LCD pins)
//   +----+----+----+----+----+----+----+----+----+
//   |    DB4  DB5  DB6  DB7  E    Rw   RS        |
//   |                 LCD Module                 |
//
// NOTE: Rw is not used by the driver so it can be connected to GND.
//
// The functionality provided by this class and its base class is identical
// to the original functionality of the Arduino LiquidCrystal library.
//
//
// History
// 2012.03.29 bperrybap - fixed constructors not properly using Rs
//                        Fixed incorrect use of 5x10 for default font 
//                        - now matches original LQ library.
//                        moved delay to send() so it is per cmd/write vs shiftout()
//                        NOTE: delay is on hairy edge of working when FAST_MODE is on.
//                        because of waitUsec().
//                        There is margin at 16Mhz AVR but might fail on 20Mhz AVRs.
//                        
// @author F. Malpartida - fmalpartida@gmail.com
// ---------------------------------------------------------------------------
// flags for backlight control
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

#if (ARDUINO <  100)
#include <WProgram.h>
#else
#include <Arduino.h>
#endif
#include "LiquidCrystal_SR3W.h"

#include "FastIO.h"

/*!
 @defined 
 @abstract   LCD_NOBACKLIGHT
 @discussion No BACKLIGHT MASK
 */
#define LCD_NOBACKLIGHT 0x00

/*!
 @defined 
 @abstract   LCD_BACKLIGHT
 @discussion BACKLIGHT MASK used when backlight is on
 */
#define LCD_BACKLIGHT   0xFF


// Default library configuration parameters used by class constructor with
// only the I2C address field.
// ---------------------------------------------------------------------------
/*!
 @defined 
 @abstract   Enable bit of the LCD
 @discussion Defines the IO of the expander connected to the LCD's Enable
 */
#define EN 4  // Enable bit

/*!
 @defined 
 @abstract   Read/Write bit of the LCD
 @discussion Defines the IO of the expander connected to the LCD's Rw pin
 */
#define RW 5  // Read/Write bit

/*!
 @defined 
 @abstract   Register bit of the LCD
 @discussion Defines the IO of the expander connected to the LCD's Register select pin
 */
#define RS 6  // Register select bit

/*!
 @defined 
 @abstract   LCD dataline allocation this library only supports 4 bit LCD control
 mode.
 @discussion D4, D5, D6, D7 LCD data lines pin mapping of the extender module
 */
#define D4 0
#define D5 1
#define D6 2
#define D7 3



LiquidCrystal_SR3W::LiquidCrystal_SR3W(uint8_t data, uint8_t clk, uint8_t strobe)
{
   init( data, clk, strobe, RS, RW, EN, D4, D5, D6, D7 );
}

LiquidCrystal_SR3W::LiquidCrystal_SR3W(uint8_t data, uint8_t clk, uint8_t strobe,
                                       uint8_t backlighPin, t_backlightPol pol)
{
   init( data, clk, strobe, RS, RW, EN, D4, D5, D6, D7 );
   setBacklightPin(backlighPin, pol);
}

LiquidCrystal_SR3W::LiquidCrystal_SR3W(uint8_t data, uint8_t clk, uint8_t strobe,
                                       uint8_t En, uint8_t Rw, uint8_t Rs, 
                                       uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7 )
{
   init( data, clk, strobe, Rs, Rw, En, d4, d5, d6, d7 );
}

LiquidCrystal_SR3W::LiquidCrystal_SR3W(uint8_t data, uint8_t clk, uint8_t strobe, 
                                       uint8_t En, uint8_t Rw, uint8_t Rs, 
                                       uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
                                       uint8_t backlighPin, t_backlightPol pol)
{
   init( data, clk, strobe, Rs, Rw, En, d4, d5, d6, d7 );
   setBacklightPin(backlighPin, pol);
}


void LiquidCrystal_SR3W::send(uint8_t value, uint8_t mode)
{
   
   if ( mode != FOUR_BITS )
   {
      write4bits( (value >> 4), mode ); // upper nibble
   }   
   write4bits( (value & 0x0F), mode); // lower nibble


#if (F_CPU <= 16000000)
   // No need to use the delay routines on AVR since the time taken to write
   // on AVR with SR pin mapping even with fio is longer than LCD command execution.
   waitUsec(37); //goes away on AVRs
#else
   delayMicroseconds ( 37 );      // commands & data writes need > 37us to complete
#endif

}


void LiquidCrystal_SR3W::setBacklightPin ( uint8_t value, t_backlightPol pol = POSITIVE )
{
   _backlightPinMask = ( 1 << value );
   _backlightStsMask = LCD_NOBACKLIGHT;
   _polarity = pol;
   setBacklight (BACKLIGHT_OFF);     // Set backlight to off as initial setup
}

void LiquidCrystal_SR3W::setBacklight ( uint8_t value )
{
   // Check if backlight is available
   // ----------------------------------------------------
   if ( _backlightPinMask != 0x0 )
   {
      // Check for polarity to configure mask accordingly
      // ----------------------------------------------------------
      if  (((_polarity == POSITIVE) && (value > 0)) || 
           ((_polarity == NEGATIVE ) && ( value == 0 )))
      {
         _backlightStsMask = _backlightPinMask & LCD_BACKLIGHT;
      }
      else 
      {
         _backlightStsMask = _backlightPinMask & LCD_NOBACKLIGHT;
      }
      loadSR( _backlightStsMask );
   }
}


// PRIVATE METHODS
// -----------------------------------------------------------------------------

int LiquidCrystal_SR3W::init(uint8_t data, uint8_t clk, uint8_t strobe, 
                             uint8_t Rs, uint8_t Rw, uint8_t En,
                             uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
   _data       = fio_pinToBit(data);
   _clk        = fio_pinToBit(clk);
   _strobe     = fio_pinToBit(strobe);
   _data_reg   = fio_pinToOutputRegister(data);
   _clk_reg    = fio_pinToOutputRegister(clk);
   _strobe_reg = fio_pinToOutputRegister(strobe);
   
   // LCD pin mapping
   _backlightPinMask = 0;
   _backlightStsMask = LCD_NOBACKLIGHT;
   _polarity = POSITIVE;
   
   _En = ( 1 << En );
   _Rw = ( 1 << Rw );
   _Rs = ( 1 << Rs );
   
   // Initialise pin mapping
   _data_pins[0] = ( 1 << d4 );
   _data_pins[1] = ( 1 << d5 );
   _data_pins[2] = ( 1 << d6 );
   _data_pins[3] = ( 1 << d7 );
   
   _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
   
   return (1);
}

void LiquidCrystal_SR3W::write4bits(uint8_t value, uint8_t mode)
{
   uint8_t pinMapValue = 0;
   
   // Map the value to LCD pin mapping
   // --------------------------------
   for ( uint8_t i = 0; i < 4; i++ )
   {
      if ( ( value & 0x1 ) == 1 )
      {
         pinMapValue |= _data_pins[i];
      }
      value = ( value >> 1 );
   }
   
   // Is it a command or data
   // -----------------------
   mode = ( mode == LCD_DATA ) ? _Rs : 0;
   
   pinMapValue |= mode | _backlightStsMask;
   loadSR ( pinMapValue | _En );  // Send with enable high
   loadSR ( pinMapValue); // Send with enable low
}


void LiquidCrystal_SR3W::loadSR(uint8_t value) 
{
   // Load the shift register with information
   fio_shiftOut(_data_reg, _data, _clk_reg, _clk, value, MSBFIRST);
   
   // Strobe the data into the latch
   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
      fio_digitalWrite_HIGH(_strobe_reg, _strobe);
      fio_digitalWrite_SWITCHTO(_strobe_reg, _strobe, LOW);
   }
}
