/***********************************************************************
** lcd.c
**                
** 11.03.2009 li Umbau auf 10 Bit shift Register
** 12.03.2009 li Anpassung an 4 * 20 Display 
** 27.03.2009 li \n wird ausgewertet, dann neue Zeile 
** 04.04.2009 li 2 oder 4 Line-Displays werden unterstuetzt
** 07.09.2009 li Hintergrundbeleuchtung eigener Port
** 08.01.2010 li Schaltung mit HC4094 sollte gehen 
** 14.01.2010 li EA_DIP204_4 Electronic assembly display Erweiterungen 
** 13.03.2010 li \n Abfrage in lcd_data 
** 13.05.2012 li Auswertung der Definition LCD_LED_PWM, Helligkeit in 10 Stufen
** 13.05.2012 li LCD_PWM_OC1B: 9Bit Mode 6
** 09.06.2012 li LCD_PWM_TIM0: Timer0 in 5 Stufen PWM von 0 12,5% 25,0%  50,0%  99,9%             
*/

#include <stdint.h>
#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "basis.h"
#include "lcd.h"

#define CLEAR_DISPLAY 0x01
#define CURSOR_HOME   0x02

S_LCD_ANZ lcdanz_s; 

#ifdef LCD_PWM_TIM0          // Timer0
volatile unsigned char pwm_max = 0;      
volatile unsigned char pwm = 0;                  
#endif      // LCD_PWM_TIM0 


void lcd_command( unsigned char temp1 );
void load_LCD( unsigned char temp1 , unsigned char rs );
void lcd_WaitBusy(void);

#ifdef LCD_PWM_TIM0 
void init_pwm( unsigned char init );
#endif

#ifdef LCD_PWM_OC1B 
void init_pwm( void );
#endif



/********************************************************
** sendet ein Datenbyte an das LCD
*/ 
void lcd_data(unsigned char temp1)
{     
   if ( temp1 == '\n' )          // new line ??
   {
      while( lcdanz_s.xpos < LCD_CHARS )
      {
         lcdanz_s.xpos++;   
         load_LCD( ' ', 1 );       // RS auf 1
         lcd_WaitBusy();
      }  
   }
   else
   {
      if ( lcdanz_s.xpos > (LCD_CHARS-1) )
      {
         lcdanz_s.xpos = 0;
         lcdanz_s.zeile++;
         if ( lcdanz_s.zeile > LCD_LINES )
           lcdanz_s.zeile = 1;
         set_cursor( lcdanz_s.xpos, lcdanz_s.zeile);    
      }
      lcdanz_s.xpos++;   
      load_LCD( temp1 , 1 );       // RS auf 1
      lcd_WaitBusy();
   }
}


#if LCD_SHIFT == 10
/********************************************************
** 10 Bit Schift Register setzen
** danach Enable Puls
** von 10 Bits werden 9 genutzt  
*/  
void load_LCD( unsigned char temp1 , unsigned char rs )
{
   int n; 
   
   if ( rs )
      LCD_CNTL_PORT |= LCD_DA;     // RS auf 1 setzen = Data Mode
   else
      LCD_CNTL_PORT &= ~LCD_DA;    // RS auf 0 setzen = CMD Mode
   LCD_CNTL_PORT &= ~LCD_CP;       // CP auf 0 setzen
   _delay_us(2); 
   LCD_CNTL_PORT |= LCD_CP;        // CP auf 1 setzen
   _delay_us(2);
   
   for (n=0; n < 8; n++)           // 8 Datenbits
   {
      if ( temp1 & 0x80 )          // Bit high or low ?
         LCD_CNTL_PORT |= LCD_DA;  // DATA auf 1 
      else
         LCD_CNTL_PORT &= ~LCD_DA; // DATA auf 0
      temp1 <<= 1;                 // data shift left
      LCD_CNTL_PORT &= ~LCD_CP;    // CP auf 0 setzen
      _delay_us(2); 
      LCD_CNTL_PORT |= LCD_CP;     // CP auf 1 setzen
      _delay_us(2);
   }    
                                   // 10 Takt = Leertakt 
   LCD_CNTL_PORT &= ~LCD_DA;   
   LCD_CNTL_PORT &= ~LCD_CP;       // CP auf 0 setzen
   _delay_us(2); 
   LCD_CNTL_PORT |= LCD_CP;        // CP auf 1 setzen
   _delay_us(2);   
                                   // erzeugt den Enable-Puls
   LCD_CNTL_PORT |= LCD_EN;        // E auf 1
   _delay_us(2);                   // pulse Dauer
   LCD_CNTL_PORT &= ~LCD_EN;       // E auf 0   
}
#endif

#if LCD_SHIFT == 8
/********************************************************
** 8 Bit Schift Register setzen
** HC4094 schiebt wenn CP Flanke von 0 nach 1   
** danach Enable Puls mit RS
*/  
void load_LCD( unsigned char temp1 , unsigned char rs )
{
   int n; 
      
   for (n=0; n < 8; n++)            // 8 Datenbits
   {
      if ( temp1 & 0x80 )           // Bit high or low ?
         LCD_CNTL_PORT |= LCD_DA;  // DATA auf 1 
      else
         LCD_CNTL_PORT &= ~LCD_DA; // DATA auf 0
      temp1 <<= 1;                 // data shift left
      LCD_CNTL_PORT &= ~LCD_CP;    // CP auf 0 setzen
      _delay_us(2); 
      LCD_CNTL_PORT |= LCD_CP;     // CP auf 1 setzen
      _delay_us(2);
   }    
   
   if ( rs )
      LCD_CNTL_PORT |= LCD_DA;     // RS auf 1 setzen = Data Mode
   else
      LCD_CNTL_PORT &= ~LCD_DA;    // RS auf 0 setzen = CMD Mode
   
   _delay_us(2);                   // 31.05.2012 dazu 
                                   // erzeugt den Enable-Puls
   LCD_CNTL_PORT |= LCD_EN;        // E auf 1
   _delay_us(2);                   // pulse Dauer
   LCD_CNTL_PORT &= ~LCD_EN;       // E auf 0   
}
#endif


/********************************************************
** sendet einen Befehl an das LCD
*/  
void lcd_command(unsigned char temp1)
{
   load_LCD( temp1 , 0 );   // RS auf 0 Setzen   
   lcd_WaitBusy();
}
 
 

/********************************************************
** Warten 100 µ da read nicht moeglich
*/  
void lcd_WaitBusy(void)
{
   _delay_us(100);                
}



/********************************************************
** LCD Initialisierung
** fuer den frequ counter pwm erweiterung 
*/   
void lcd_init( unsigned char pwm )
{
#ifdef LCD_PWM_OC1B        // OC1B = TIMER1, Ausgang B
   LCD_CNTL_PORT |=  LCD_CP;   // CP auf 1 setzen
   LCD_CNTL_PORT &= ~LCD_DA;   // D  auf 0 setzen
   LCD_CNTL_PORT &= ~LCD_EN;   // E  auf 0 setzen
   
   LCD_PWM_PORT &= ~LCD_LED;                       // Beleuchtung aus
   lcdanz_s.lcd_backlight = 0;
   LCD_CNTL_DDR |= ( LCD_DA + LCD_EN + LCD_CP  );  // Ports auf OUTPUT
   LCD_PWM_DDR |= ( LCD_LED );                     // Ports auf OUTPUT    
   init_pwm(); 
#else
#ifdef LCD_PWM_TIM0            // TIM0 = TIMER0 
   init_pwm( pwm ); 
#endif
   LCD_CNTL_PORT |=  LCD_CP;   // CP auf 1 setzen
   LCD_CNTL_PORT &= ~LCD_DA;   // D  auf 0 setzen
   LCD_CNTL_PORT &= ~LCD_EN;   // E  auf 0 setzen
   // LCD_CNTL_PORT &= ~LCD_LED;  // Beleuchtung aus
   // lcdanz_s.lcd_backlight = 0;
   LCD_CNTL_DDR |= ( LCD_DA + LCD_EN + LCD_CP + LCD_LED );  // Ports auf OUTPUT 
#endif   
   _delay_ms(500);      // delay nach Power ON

#ifdef EA_DIP204_4      // Electronic assembly display  

   lcd_command(0x34);	// 8 Bit Modus einschalten, RE=1 
      
   lcd_command(0x09);	// 4 Zeilen Modus 
      
   lcd_command(0x30);	// 8 Bit Modus einschalten, RE=0 
      
   lcd_command(0x0C);	// Display ein, Cursor aus, Cursor blinken aus
      
   lcd_command(0x01);	// Clear Display 
   _delay_us(2000);     // wait 
   
   lcd_command(0x06);	// Cursor auto increment 
      
#else
                
   lcd_command(0x30);   // 8 Bit Modus einschalten     
   _delay_ms(20);       // wait 
                                
   lcd_command(0x38);   // 8 Bit Modus und mehr als 1 Zeile  
    _delay_us(200);     // wait
    
   lcd_command(0x38);   // 8 Bit Modus und mehr als 1 Zeile 
    _delay_us(200);     // wait     
          
   cursor_view( 0, 0 ); // Display ein / Cursor aus / kein Blinken
   
   lcd_command(0x06);   // inkrement / kein Scrollen
   
   lcd_clear();
#endif
  
   lcd_home();       
}


#ifdef LCD_PWM_TIM0     // TIMER0 auf ca. 1200 Hz einstellen
/*********************************************************** 
** PWM fuer die Beleuchtung definieren
** 13.05.2012 9Bit PWM, mode6, 187Hz output
** 02.07.2012 atmega8 kann nur timer0 overflow   
*/ 
void init_pwm( unsigned char init )
{
#if defined(__AVR_ATmega8__)
   if ( init )
   { 
#if F_CPU == 6144000   
      TCNT0  = 175;                          // 6,144 MHz 256-175 = 81 Takte bis Overflow
      TCCR0 |= (1 << CS01) + (1 << CS00);    // prescaler 64
#endif
#if F_CPU == 12000000   
      TCNT0  = 100;                          // 12 MHz    256-100 = 156 Takte bis Overflow
      TCCR0 |= (1 << CS01) + (1 << CS00);    // prescaler 64
#endif
      TIMSK |= (1 << TOIE0 );                // enable interrupt
   }
   else
   {
    //  TIMSK &= ~(1 << TOIE0 );               // disable interrupt
   }
#endif  // (__AVR_ATmega8__)

#if defined(__AVR_ATmega168__)
   if ( init )
   { 
      TCNT0  = 0;
 
#if F_CPU == 6144000
      TCCR0B |= (1<< CS01)+ (1<< CS00);               // prescaler 64
      OCR0A  = 79;               // compare match bei 79 + 1 = 80 Takte
#endif

#if F_CPU == 14745600
      TCCR0B |= (1<< CS01)+ (1<< CS00);               // prescaler 64
      OCR0A  = 190;              // compare match bei 190 + 1 = 191 Takte
#endif

#if F_CPU == 18432000
      TCCR0B |= (1<< CS01)+ (1<< CS00);               // prescaler 64
      OCR0A  = 239;              // compare match bei 239 + 1 = 240 Takte 
#endif

      TCCR0A |= (1<< WGM01);     // CTC
      TIMSK0 |= (1 << OCIE0A );  // enable compare match interrupt
   }
   else
   {
      TCNT0  = 0;
      TCCR0B = 0;
      OCR0A  = 0;
      TCCR0A &= ~(1<< WGM01);     // CTC clear
      TIMSK0 &= ~(1 << OCIE0A );  // disable compare match interrupt
   }
#endif  // (__AVR_ATmega168__)
}


/*****************************************************************************
*  Timer0 Beleuchtung, Software PWM 
** Werte von  0-5 = 6 Takte ergibt 1200 / 8 ca. 150Hz fuer den Hintergrund 
*/
#if defined(__AVR_ATmega8__)
ISR ( TIMER0_OVF_vect )
{
#if F_CPU == 6144000 
   TCNT0  = 175;
#endif

#if F_CPU == 12000000 
   TCNT0  = 100;
#endif

   if ( pwm == 0 && pwm_max )
   {
       LCD_CNTL_PORT |= LCD_LED;     // Beleuchtung ein
   }    
   else
   {
      if ( pwm >= pwm_max )
      {      
         LCD_CNTL_PORT &= ~LCD_LED;  // Beleuchtung aus
      }   
   }
   pwm++;
   pwm &= 0x07;    // 0 - 7 zulassen
}
#endif  // (__AVR_ATmega8__)

#if defined(__AVR_ATmega168__)
ISR ( TIMER0_COMPA_vect )
{                                 
   if ( pwm == 0 && pwm_max )        // 06.07.2012
       LCD_CNTL_PORT |= LCD_LED;     // Beleuchtung ein
   else
   {
      if ( pwm >= pwm_max )       
         LCD_CNTL_PORT &= ~LCD_LED;  // Beleuchtung aus
   }
   pwm++;
   pwm &= 0x07;    // 0 - 7 zulassen
}
#endif
#endif       // LCD_PWM_TIM0


#ifdef LCD_PWM_OC1B     // OC1B = TIMER1, Ausgang B  
/******************************************************** 
** PWM fuer die Beleuchtung definieren
** 13.05.2012 9Bit PWM, mode6, 187Hz output   
*/ 
void init_pwm( void )
{
#if defined(__AVR_ATmega168__)
                            // mode 6 = 9 Bit Aufloesung
  TCNT1  = 0;               //  
  OCR1B  = 8;               // init wert     
  TCCR1A |= (1 << COM1B1) + (1 << WGM11);
  TCCR1B |= (1 << WGM12) + (1 << CS11) + (1 << CS10);  // prescaler = 64
  TCCR1C |= (1 << FOC1B);   

#endif  // (__AVR_ATmega168__)
}
#endif

 
/******************************************************** 
** display ein und cursor sichtweise 
*/ 
void cursor_view( unsigned char on, unsigned char blinken )
{
   unsigned char cmd = 0x0C ;
   
   if ( on )
      cmd |= 0x02;
   if ( blinken )
      cmd |= 0x01;       
   lcd_command( cmd );   // Display ein / Cursor control / blink control
} 
 
 
 
/******************************************************** 
** Hintergrundbeleuchtung des Panels ein/aus
** wenn pwm dann on in 10 Stufen
*/ 
void lcd_beleuchtung( unsigned char on)
{
#ifdef LCD_LED_PWM

#ifdef LCD_PWM_TIM0          // Timer0
   unsigned char pwm_ent[5] = { 0, 1, 2, 4, 8 };

   if ( on > 4 )
      on = 4;
   if ( on == 0 )
      LCD_CNTL_PORT &= ~LCD_LED;  // Beleuchtung aus
   else
      LCD_CNTL_PORT |= LCD_LED;    // Beleuchtung ein   
   lcdanz_s.lcd_backlight = on;   // merker fuer menu 
   pwm_max = pwm_ent[ on ];      //            
#endif      // LCD_LED_PWM == TIM0 
   
#ifdef LCD_PWM_OC1B            // OC1B = TIMER1, Ausgang B
                        //  0 11% 22% 33% 44% 55% 66% 77%  88%  99%  
   unsigned int pwm[10] = { 0, 2,  4,  8, 16, 32, 64, 128, 256, 511 };
   
   if ( on > 10 )
      on = 10;   
   lcdanz_s.lcd_backlight = on;    // merker fuer menu 

    OCR1B = pwm[on];             // OCR1B setzen           
#endif  // LCD_LED_PWM == OC1B

#else   // LCD_LED_PWM
   if ( on )                       // einschalten ??
   {
      lcdanz_s.lcd_backlight = 1;  // merker fuer menu 
      LCD_CNTL_PORT |= LCD_LED;    // Beleuchtung ein
   }
   else
   {
      lcdanz_s.lcd_backlight = 0;  // merker fuer menu
      LCD_CNTL_PORT &= ~LCD_LED;   // Beleuchtung aus
   } 
#endif   // LCD_LED_PWM
} 


 
/******************************************************** 
** Loeschung des Displays
** Dauer 1,64ms 
*/ 
void lcd_clear(void)
{
   lcd_command(CLEAR_DISPLAY);      // 0x01
   _delay_ms(2);   
}
 
 
 
/********************************************************  
** Cursor auf Home Position setzen
** Dauer 1,64ms 
*/ 
void lcd_home(void)
{
   lcd_command(CURSOR_HOME);        // 0x02
   _delay_ms(2); 
   lcdanz_s.xpos  = 0;
   lcdanz_s.zeile = 1;
   lcdanz_s.lcd_backlight = 0;

#if LCD_CHARS == 16   
   lcdanz_s.chars_pl = 16;
#endif
#if LCD_CHARS == 20   
   lcdanz_s.chars_pl = 20;
#endif      
   
#if LCD_LINES == 4
   lcdanz_s.zeilen = 4;
#else
   lcdanz_s.zeilen = 2;   
#endif     
}



/********************************************************  
** setzt den Cursor in Zeile y (1..4) Spalte x (0..15)
*/ 
void set_cursor(uint8_t x, uint8_t y)
{
   register uint8_t tmp;

   lcdanz_s.xpos  = x;
   lcdanz_s.zeile = y;  
   
   tmp = 0x80;           // command

#ifdef EA_DIP204_4      // Electronic assembly display 

   if ( y == 1 )
      tmp += x;          // 1. Zeile     
   else if (y == 2) 
      tmp += 0x20 + x;   // 2. Zeile
   else if (y == 3)
      tmp += 0x40 + x;   // 3. Zeile
   else if ( y == 4) 
      tmp += 0x60 + x;   // 4. Zeile 

#else     

   if ( y == 1 )
      tmp += x;          // 1. Zeile     
   else if (y == 2) 
      tmp += 0x40 + x;   // 2. Zeile
#if LCD_LINES == 4
   else if (y == 3)
      tmp += 0x14 + x;   // 3. Zeile
   else if ( y == 4) 
      tmp += 0x54 + x;   // 4. Zeile
#endif

#endif                   // /EA_DIP204_4      Electronic assembly display  
   lcd_command( tmp );
}



/******************************************************** 
** String auf das LCD ausgeben, \n Auswertung in lcd_data
*/ 
void lcd_string(char *data)
{
   while( *data )
   {
      lcd_data( *data );
      data++;
   }
}
