/* 
   DISPLAY.C drives three kinds of displays:


   1. LCD  is HD44780-based 2x16 character display LM-11 in 4 bit mode 
      connected to port P5

   2. ICM is ICM7218A 8 Digit LED Display Driver memory mapped at 0x8000
      with 8 LEDs for status display instead of D.P. segment.
      The display is broken into two 4 digit integer displays icm1 and icm2

   3. Static serial display with four 3 1/2 (-188.8) displays with sign 
      consists of twelve 74HC595 shift registers with each segment 
      connected via resistor to 595. Decimal point is fixed. 


   f_osc = 11.059 MHz -> 1 machine cycle  = 12/f_osc = 1.085us 

*/

#include <reg515.h>
#include <intrins.h>
#include "display.h"


/* LCD display pin definition */

sbit LCD_E  = 0xFB; /* P5.3 enable LCD */
sbit LCD_RW = 0xFA; /* P5.2 Read/Write LCD */
sbit LCD_RS = 0xF9; /* P5.1 Data/Instruction LCD */

sbit BUSY   = 0xFF; /* P5.7 LCD Busy bit 7 */ 
sfr  LCD_PORT=0xF8; /* P5 port high nibbles P5.4-P5.7 */
sbit IR7    = 0xFF; /* P5.7 */
sbit IR6    = 0xFE; /* P5.6 */
sbit IR5    = 0xFD; /* P5.5 */
sbit IR4    = 0xFC; /* P5.4 */

bit
lcd_busy()
{
  bit busy;

  IR7 = IR6 = IR5 = IR4 = 1;   /* Prepare for read */

  LCD_RS = 0;     /* Instruction */
  LCD_RW = 1;     /* Read DISPLAY */

  LCD_E = 1;      /* enable LCD */
  busy = BUSY;
  LCD_E = 0;
  LCD_E = 1;
  LCD_E = 0;
  return busy;
}



/* if RS == 0 then write instruction else write character to LCD */ 
void
lcd_send(bit RS, unsigned char c) 
{
  unsigned int count = 0;

  while (lcd_busy())
    {
      count++;
      if (count == 10000)
        {
          GATE = 1;
          icm_error(01); /* LCD failure - short circuit */ 
          while (1)
            {
              WDT = 1;       /* ensure WDT is reset */
              SWDT = 1;     
            }
        }
    }

  LCD_RS = RS;    /* Data/Instruction select */
  LCD_RW = 0;     /* set write */      

  LCD_PORT = (c & 0xF0) | (LCD_PORT & 0x0F);  /* high nibble to the LCD */
  LCD_E = 1;      /* enable LCD */
  LCD_E = 0;      /* write to LCD */


  LCD_PORT = (c << 4 & 0xF0) | (LCD_PORT &0x0F);
  LCD_E = 1;
  LCD_E = 0;
}   

void
lcd_cgram_caron_init()
{
  char i;

  char code *caron = 
    "\x0a\x04\x0e\x10\x10\x11\x0e\x00"   /* 1. c caron */
    "\x0a\x04\x0e\x10\x0e\x01\x1e\x00"   /* 2. s caron */
    "\x0a\x04\x1f\x02\x04\x08\x1f\x00"   /* 3. z caron */
    "\x0a\x0e\x11\x10\x10\x11\x0e\x00"   /* 4. C caron */
    "\x0a\x0e\x11\x0e\x01\x11\x0e\x00"   /* 5. S caron */
    "\x0a\x1f\x02\x04\x08\x10\x1f\x00"   /* 6. Z caron */
        "\x00\x0e\x15\x1f\x15\x0e\x00\x00"   /* 7. Selected */
        "\x00\x0e\x11\x11\x11\x0e\x00\x00"   /* 7. DeSelected */        
        ;
  
  lcd_send(0, 0x48); /* set  CGRAM start address */
  for (i = 0; i < 8*8; i++)
    lcd_send(1, caron[i]);

}

void
lcd_cgram_symbols_init()
{
  char i;
  char code *symbols = 
        "\x00\x00\x1f\x1d\x05\x07\x00\x00"   /* Burner */
        "\x00\x0e\x04\x1e\x1f\x03\x00\x02"   /* Water circulation */
        "\x1f\x01\x1d\x05\x1d\x15\x11\x1f"   /* Under floor heating */
    "\x04\x15\x0e\x1b\x0e\x15\x04\x00"   /* Sun */
        "\x0e\x0b\x0a\x0a\x0a\x1a\x0e\x00"   /* Solar Tank */
        "\x02\x1d\x02\x00\x02\x1f\x02\x00"   /* Preferences */
        ;
  lcd_send(0, 0x48); /* set  CGRAM start address */
  for (i = 0; i < 6*8; i++)
    lcd_send(1, symbols[i]);
}
  

/* Initialise LCD in 4 bit without cursor display */
void lcd_init(void)
{

  unsigned int i;

  LCD_E = 0;  /* disable LCD */
  LCD_RW = 0; /* we will Write */
  LCD_RS = 0; /* instructions */
  LCD_PORT = 0x30 | (LCD_PORT & 0x0f);

  for (i = 1800; i > 0; i--); /* wait 15 ms or more after poweron*/
                              /* i*8+i/256+4 cycles */

  LCD_E=1; LCD_E=0;
  for (i = 600; i > 0; i--); /* wait 4.1 ms or more */

  LCD_E=1; LCD_E=0;
  for (i = 19; i > 0; i--); /* wait 160 us or more */

  LCD_E=1; LCD_E=0;

  LCD_PORT = 0x20 | (LCD_PORT & 0x0f);
  LCD_E=1; LCD_E=0;

  lcd_send(0, 0x28); /* set interface length */
  lcd_send(0, 0x08); /* turn OFF */
  lcd_send(0, 0x01); /* clear display */
  lcd_send(0, 0x06); /* Enty mode set, Set cursor move direction */
  lcd_send(0, 0x0C); /* Enable display without cursor and blink*/


  lcd_cgram_symbols_init();

}



void 
lcd_print(char code *message)
{
  unsigned char i = 0;

  lcd_send(0, 0x01); /* clear display */

  while(message[i])
    {
      if (message[i] == '\n')
                {
                  lcd_send(0, 0x80+0x40); /* new line */
                  i++;
                  continue;
                }
      lcd_send(1, message[i++]);
    }
}



void 
lcd_princ(unsigned char position, char code *message)
{
  unsigned char i = 0;
  lcd_send(0, 0x80|position); /* go to the left position */
  while (message[i])
   {
      if (message[i] == '\n')
                {
                  lcd_send(0, 0x80+0x40); /* new line */
                  i++;
                  continue;
                }
      lcd_send(1, message[i++]);
   }
}


void
lcd_print_ulong(unsigned char position, unsigned long value)
{
  char i = 8;
  lcd_send(0, 0x80|position); /* go to rightmost digit*/
  lcd_send(0, 0x04); /* left move */
  while(i--)
    {
      lcd_send(1, (value % 10)+'0');
      value /= 10;
    }
  lcd_send(0, 0x06); /* back to right move */
}

lcd_print_hms(unsigned char position, unsigned long value)
{
  lcd_send(0, 0x80|position); /* go to rightmost digit*/
  lcd_send(0, 0x04); /* left move */
  lcd_send(1, (value % 10)+'0'); /* seconds */
  value /= 10;
  lcd_send(1, (value % 6)+'0');
  value /= 6;
  lcd_send(1, ':');
  lcd_send(1, (value % 10)+'0'); /* minutes */
  value /= 10;
  lcd_send(1, (value % 6)+'0');
  value /= 6;
  lcd_send(1, ':');
  do
    {
      lcd_send(1, (value % 10)+'0');
      value /= 10;
    }
  while(value);
        
  lcd_send(0, 0x06); /* back to right move */
} 

void
lcd_print_uchar(unsigned char position, unsigned long value)
{
  char i = 3;
  lcd_send(0, 0x80|position); /* go to rightmost digit*/
  lcd_send(0, 0x04); /* left move */
  while(i--)
    {
      lcd_send(1, (value % 10) + '0');
      value /= 10;
    }
  lcd_send(0, 0x06); /* back to right move */
}

void
lcd_print_temp(unsigned char position_right, int value)
{
  char i = 4;
  lcd_send(0, 0x80|position_right); /* go to rightmost digit*/
  lcd_send(0, 0x04); /* left move */

  lcd_send(1, 'C');
  lcd_send(1, 0xDF);

  while(i--)
    {
      lcd_send(1, (value % 10) + '0');
      if (i == 2)
        lcd_send(1, 0x2E);
      value /= 10;
    }
  lcd_send(0, 0x06); /* back to right move */
}

void
lcd_print_time_of_day(unsigned char time_of_day)
{
  if (time_of_day > 239)
        {
          lcd_princ(0x43, "Izklju\01eno");
        }
  else
        {
          unsigned char minutes = (time_of_day % 10) * 6;
          lcd_princ(0x43, "          ");
          lcd_send(0, 0xc8); /* go to rightmost digit*/
          lcd_send(0, 0x04); /* left move */
          lcd_send(1, (minutes % 10) + '0');
          minutes /= 10;
          lcd_send(1, minutes + '0');
          time_of_day /= 10;
          lcd_send(1, ':');
          lcd_send(1, (time_of_day % 10)+'0');
          time_of_day /= 10;
          lcd_send(1, time_of_day+'0');
          lcd_send(0, 0x06); /* back to right move */
        }
  
}


/*------------------------  ICM7218A section --------------------------*/

/* MODE is on A0 pin, ID is on DATA bus, WR\ in on A15\ pin */
#define icm_data  *((unsigned char volatile xdata *)0x8000)
#define icm_ctrl  *((unsigned char volatile xdata *)0x8001)
/* ICM mode control masks */
#define ICM_NORMAL           0x10
#define ICM_NO_DECODE        0x20
#define ICM_HEX              0x40
#define ICM_DATA_COMING      0x80

static unsigned char _leds; 

void 
icm1(unsigned int i) /* first line */
{
  char digit;
  lcd_print_temp(0x06, i);
  for (digit = 3; digit >=0 ; digit--)
    {
      icm_ctrl = ICM_NORMAL | digit;
      icm_data = i % 10 | ( _leds & 1 << digit ? 0x00 : 0x80);
      i /= 10;
    }
}

void 
icm2(unsigned int i) /* second line of the four digits */
{
  char digit;
  lcd_print_temp(0x46, i);
  
  for (digit = 7; digit >=4 ; digit--)
    {
      icm_ctrl = ICM_NORMAL | digit;
      icm_data = i % 10 | ( _leds & 1 << digit ? 0x00 : 0x80);
      i /= 10;
    }
}


         

/* Writes error number message to ICM display */
void
icm_error(unsigned char number)
{
#define SEG_A  0x40 /*  aaaa  */
#define SEG_B  0x20 /* f    b */
#define SEG_C  0x10 /* f    b */
#define SEG_D  0x01 /*  gggg  */
#define SEG_E  0x08 /* e    c */
#define SEG_F  0x02 /* e    c */
#define SEG_G  0x04 /*  dddd  */
#define LED(i) (leds & 1 << i ? 0x00 : 0x80)

  icm_ctrl = ICM_NORMAL | ICM_NO_DECODE | ICM_DATA_COMING;
  icm_data = SEG_A | SEG_D | SEG_E | SEG_F | SEG_G | LED(0); /* E */
  icm_data = SEG_E | SEG_G | LED(1);                         /* r */
  icm_data = SEG_C | SEG_D | SEG_E | SEG_G | LED(2);         /* o */
  icm_data = SEG_E | SEG_G | LED(3);                         /* r */

  icm_data = SEG_C | SEG_E | SEG_G | LED(4);                 /* n */
  icm_data = SEG_C | SEG_D | SEG_E | SEG_G | LED(5);         /* o */
  icm_ctrl = ICM_NORMAL | 6;
  icm_data = (number / 10 ? number / 10 : 0xFF) | LED(6);
  icm_ctrl = ICM_NORMAL | 7 ;
  icm_data = (number % 10) | LED(7);
}

void
icm_blank(void)
{
  icm_ctrl = ICM_NORMAL | ICM_NO_DECODE | ICM_DATA_COMING;
  icm_data = LED(0);
  icm_data = LED(1);
  icm_data = LED(2);
  icm_data = LED(3);
  icm_data = LED(4);
  icm_data = LED(5);
  icm_data = LED(6);
  icm_data = LED(7);
  icm_ctrl = ICM_NORMAL;
}


/* Call icm1() or icm2() for leds update! */
void
set_leds(unsigned char leds)
{
  _leds = leds;
}

/* Writes tests to ICM display */
void
icm_test(unsigned char number)
{
  switch (number)
        {
        case 0:
          icm_ctrl = ICM_NORMAL | ICM_NO_DECODE | ICM_DATA_COMING;
          icm_data = 0x00; icm_data = 0x00;
          icm_data = 0x00; icm_data = 0x00;
          icm_data = 0x00; icm_data = 0x00;
          icm_data = 0x00; icm_data = 0x00;
          icm_ctrl = ICM_NORMAL;
          break;
        case 1:
          icm_ctrl = ICM_NORMAL | ICM_NO_DECODE | ICM_DATA_COMING;
          icm_data = 0xFF; icm_data = 0xFF;
          icm_data = 0xFF; icm_data = 0xFF;
          icm_data = 0xFF; icm_data = 0xFF;
          icm_data = 0xFF; icm_data = 0xFF;
          icm_ctrl = ICM_NORMAL;
          break;
        default:
          icm_ctrl = ICM_NORMAL | ICM_NO_DECODE | ICM_DATA_COMING;
          icm_data = SEG_A | SEG_D | SEG_E | SEG_F | SEG_G | LED(0); /* E */
          icm_data = SEG_E | SEG_G | LED(1);                         /* r */
          icm_data = SEG_C | SEG_D | SEG_E | SEG_G | LED(2);         /* o */
          icm_data = SEG_E | SEG_G | LED(3);                         /* r */
          
          icm_data = SEG_C | SEG_E | SEG_G | LED(4);                 /* n */
          icm_data = SEG_C | SEG_D | SEG_E | SEG_G | LED(5);         /* o */
          icm_ctrl = ICM_NORMAL | 6;
          icm_data = (number / 10 ? number / 10 : 0xFF) | LED(6);
          icm_ctrl = ICM_NORMAL | 7 ;
          icm_data = (number % 10) | LED(7);
          icm_ctrl = ICM_NORMAL;
        }
  
}


/* ------------------ static serial display --------------------- */
sbit SI    = 0xEC; /* P4.4 negated serial input */
sbit SCK   = 0xED; /* P4.5 negated serial clock */
sbit RCK   = 0xEE; /* P4.6 negated reload clock */

static signed int idata display_data[4]; /* pomnozeno s 100 */

static void
send_digit(unsigned char segment_data)
{
  unsigned char i;
  for(i = 0; i < 8; i++)
    {
      SCK = 1;
      SI = segment_data & 0x80;
      SCK = 0;
      segment_data = segment_data << 1;
    }
}

/* Sends display_data via serial protocol to HC595 shits registers
   If blanking set then display is cleared! */
void 
update_serial_display(bit blanking)
{
  unsigned char code segment[10] = 
  { 0xEE, 0xC0, 0xB6, 0xF4, 0xD8,
    0x7C, 0x7E, 0xE0, 0xFE, 0xFC};

  unsigned char num;       /* row counter */
  unsigned char buffer[3]; /* segment buffer */
  int value;               /* current display_data */
  bit negative;            /* negative sign */

  RCK = 1;
  for ( num = 0; num < 4; num ++)
    {
          if (blanking)
                {
                  buffer[0] = buffer[1] = buffer[2] = 0;
                }
          else
                {
                  value = display_data[3 - num];
                  
                  if ( value < 0)
                        {
                          negative = 1;
                          value = -value;
                        }
                  else
                        negative = 0;
                  
                  value /= 10;
                  buffer[2] = segment[value % 10] | negative;
                  value /= 10;
                  buffer[1] = segment[value % 10];
                  value /= 10;
                  buffer[0] = segment[value % 10];
                  if ( value > 9 ) /* hundrets */
                        {
                          buffer[1] |= 1;
                        }
                  else
                        {
                          if (value == 0)
                        buffer[0] = negative; /* leading zero suppresion */
                        }
                }
      send_digit(buffer[0]);
      send_digit(buffer[1]);
      send_digit(buffer[2]);
    }
  RCK = 0;
}

/* The last digit is missing! To display a value multiply ii with 10
   e.g. -12345 will be shown as -123.4  (no rounding is performed!)
*/

void 
set_serial_display(unsigned char display_number, signed int value)
{
  display_data[display_number] = value;
}


#if 0
signed int 
get_serial_display(unsigned char display_number)
{
  return display_data[display_number];
}
#endif

/*  Generic part */

void 
halt(unsigned char errno, char code *message)
{

  GATE = 1; /* disable all output */

  icm_error(errno);

  if (errno != 0)
    {
      /* lcd_init(); */
      lcd_princ(0x00, message);
    }

  while(1)
    {
      unsigned int i;


      for ( i = 10000; i > 0; i--) /* must wait*/
        {
          WDT = 1;       /* ensure WDT is reset */
          SWDT = 1;     
        }
      leds++;
      icm_error(errno); /* for leds refresh */
    }
}