//===============================================================================
// miniDragon+ - LCD Routines
// Connected to portM (PTM)
//  Initialization:
// - A list of control condes must be sent.
//
//  The FIRST Line on the LCD is ** 1 ** not ** 0 **
//
//  LCD routines use 4-bit transfer via port M
//  PM3 ------- RS ( register select, 0 = register transfer, 1 = data transfer).
//  PM2 ------- Enable ( write pulse )
//  PM4 ------- Data Bit 4 of LCD
//  PM5 ------- Data Bit 5 of LCD
//  PM6 ------- Data Bit 6 of LCD
//  PM7 ------- Data Bit 7 of LCD
//
//===============================================================================
#include "main.h"
#include "lcd.h"


#define LCD_ENABLE     4        // Strobe data into the LCD module
#define LCD_WRITE_DATA 8        // Select command/data
// Data is sent 4-bits high nibble first.
#define WRITE_CONTROL(c)\
  LCDWriteNibble(c);  c <<= 4; LCDWriteNibble(c);

#define LCD_SET_ADDRESS   0x80 // Write address command
#define LCD_CLEAR         0x10 // Write address command
#define SPACE_CHAR        0x20
// Adjust delay timers to set scroll rates.
// These are default, use functions to set.
#define LCD_START_COUNTS    0x2000
#define LCD_CHAR_COUNTS     0x900
#define LCD_WIDTH           20
#define NO_SCROLL           255
#define LCD_WRITE_DELAY     250
//===============================================================================
// This is a list of init commands that are sent to the LCD.
// We only have 4 bits - so for the first 4 commands we only send a nibble.
// Then we send two nibble, hi byte first.
//===============================================================================
byte _lcd_init[]={
  0x30,	// 1st reset code, must delay 4.1ms after sending
  0x30,	// 2nd reset code, must delay 100us after sending
 // all following 10 nibbles must be delay 40us each after sending
  0x30, // 3rd reset code,
  0x20,	// 4th reset code - we are now in 4 bit mode - start 2 pass transfers.
  0x20,	// 4 bit mode, 2 line, 5X7 dot
  0x80,	// 4 bit mode, 2 line, 5X7 dot
  0x00, // cursor increment, disable display shift
  0x60,	// cursor increment, disable display shift
  0x00,	// display on, cursor off, no blinking
  0xC0,	// display on, cursor off, no blinking
  0x00,	// clear display memory, set cursor to home pos
  0x10,	// clear display memory, set cursor to home pos
  0x80,  // set line1
  0x00,  // Set line1
};

//===============================================================================
// Delay
//===============================================================================
void delay(byte ms)
{
  int i,j;

  for( i = 0 ; i < ms ; ++i )
  {
     for( j = 0 ; j < LCD_WRITE_DELAY ; ++j)
       asm("nop; nop; nop;"); // This foils optimization
  }

}

//===============================================================================
// Send the upper nibble of the byte passed in
//===============================================================================
void LCDWriteNibble(byte n)
{
  n &= 0xf0;
  PTM &= 0xf;             // clear upper nibble of port.
  PTM |= n;               // or in the data byte
  delay(1);               // let it settle
  PTM |= LCD_ENABLE;      // Raise the strobe line.
  PTM &= ~LCD_ENABLE;     // lower the strobe line.
  delay(1);
}

void LCDWriteChar( byte d )
{
  PTM = LCD_WRITE_DATA;   // Set the command data line to data.
  LCDWriteNibble(d);      // Write the upper nibble.
  d <<= 4;                // Shift the lower nibble up to the upper.
  LCDWriteNibble(d);      // Write the upper nibble.
}
//===============================================================================
// Initalize the LCD Controller - Send a list of commands.
//===============================================================================
void LCD_Init()
{
  byte i = 0;
  PTM = 0;
  delay(100);            // Wait 15ms for first command.
  PTM = _lcd_init[i++];
  delay(15);
  PTM |= LCD_ENABLE;
  PTM &= ~LCD_ENABLE;
  delay(50);

  for( ; i < sizeof(_lcd_init) ; ++i )
  {
    LCDWriteNibble(_lcd_init[i]);
    delay(50);
  }
}
//===============================================================================
// These commands set the LCD controller to a given line.
// From the spec.
//===============================================================================
byte _line_control[]={  0x00,0x40, 0x14, 0x54 };


// The length of a string not counting trailing spaces.
int _trimlen(char * s)
{
   int i = _strlen(s) - 1;
   s += i;
   while(*(s--) == 0x20 )
     i--;
   return i;
}



ScrollData _sd[]=
{
{LCD_START_COUNTS,LCD_CHAR_COUNTS,0,LCD_WIDTH,-1,0,0},
{LCD_START_COUNTS,LCD_CHAR_COUNTS,0,LCD_WIDTH,-1,0,0},
//#ifdef LDC_4LINES
{LCD_START_COUNTS,LCD_CHAR_COUNTS,0,LCD_WIDTH,-1,0,0},
{LCD_START_COUNTS,LCD_CHAR_COUNTS,0,LCD_WIDTH,-1,0,0},
//#endif
{0}};

ScrollData* LCDSetStartDelay( int which, word delay)
{
   _sd[which-1].LCDstartDelay = delay;
   return &_sd[which-1];
}
ScrollData* LCDSetCharDelay( int which, word delay)
{
   _sd[which-1].LCDcharDelay = delay;
   return &_sd[which-1];
}


//===============================================================================
//  LCDWriteLine - Which line and a string.
//===============================================================================
void LCDWriteLine(byte line, char* d)
{
  byte c = 0;
  PTM = 0;

  c = _line_control[line-1] | LCD_SET_ADDRESS;

  WRITE_CONTROL(c);

  c = 0;
  while( *d && (c < LCD_WIDTH) )
  {
    if(*d == '\r' )
      break;
    LCDWriteChar(*(d++));
    c++;
  }

  // Clear to the end of the line....
  while( c++ < LCD_WIDTH )
    LCDWriteChar(SPACE_CHAR);

}
//===============================================================================
// Write a line to the LCD to be scrolled.
// if d  = 0 then stop scolling that line.
//===============================================================================
void LCDScrollLine( byte line, char* d )
{
  if( 0 == d )  // No more scrolling please.
  {
    _sd[line-1].LCDstate = NO_SCROLL;
    return;
  }

  LCDWriteLine(line, d );

  // Init the scoll timers.
  _sd[line-1].LCDindex = 0;
  _sd[line-1].LCDdelayCounter =  0;
  _sd[line-1].LCDscrollData = d;

  // If it is less than the width, then don't scoll.
  if( _trimlen(d) <  LCD_WIDTH )
     _sd[line-1].LCDstate = NO_SCROLL;
  else
     _sd[line-1].LCDstate = 0;
}

//===============================================================================
// LCDUpdateScroll MUST be called if scrolling is to work.
// The time constants depend on how often it is called.
//===============================================================================
void _LCDUpdateScroll(byte line);
void LCDUpdateScroll()
{
  byte i;

  // For each line update the scoll states.
  for( i = 0 ;  ; ++i)
  {
     if( 0 == _sd[i].LCDstartDelay )
       break;

     _LCDUpdateScroll(i);
  }
}
void _LCDUpdateScroll(byte index)
{
  if( _sd[index].LCDstate == NO_SCROLL )  // Means no scrolling.
    return;

  if( _sd[index].LCDstate == 0 )  // First write delay.
  {
     if(_sd[index].LCDdelayCounter >= _sd[index].LCDstartDelay)
     {
        _sd[index].LCDstate = 1;
        _sd[index].LCDindex = 1;
        _sd[index].LCDdelayCounter =  0;
     }
  }
  else  if( _sd[index].LCDstate == 1 ) // Each character delay.
  {

     if( _sd[index].LCDdelayCounter >= _sd[index].LCDcharDelay )
     {
       if(_sd[index].LCDscrollData[_sd[index].LCDindex] == 0 )
       {
          // hit the end, start over.
          _sd[index].LCDindex = 0;
          _sd[index].LCDstate = 0;
       }
       else
       { // Move to the next char.
         ++_sd[index].LCDindex;
       }
       // Update the lline on the LCD, shifted.
       LCDWriteLine(index+1, &_sd[index].LCDscrollData[_sd[index].LCDindex]);
       _sd[index].LCDdelayCounter =0;
     }
  }

  ++_sd[index].LCDdelayCounter;
}
