/* lcd.c Routines for sending data to a serial LCD
 *
 *  Copyright (C) 1998  Mark Crosbie  mark@mastincrosbie.com
 *
 *  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 2 of the License, or
 *  (at your option) 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
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Provides the following functions:
 *
 *   void lcd_putc(char c)    send the byte c to the LCD
 *   void lcd_clear(void)     clears the LCD screen
 *   void lcd_home(void)      homes the LCD cursor
 *   void lcd_puts()          print a string **UNIMPLEMENTED**
 *   void lcd_printhex(char c) prints byte c as a 2 digit hex character
 *   void lcd_printdec(char c) prints byte c as a decimal digit string
 *   void lcd_printbin(char c) prints byte c as a binary string
 *   void lcd_scroll_left(char n) scrolls LCD screen left n positions
 *   void lcd_scroll_right(char n) scrolls LCD screen right n positions
 *   void lcd_goto(char row, char col) goto a position
 * Constants used:
 *    LCDPORT        port on the PIC the LCD is connected to
 *    LCDPIN         what PIN the LCD serial input is connected to
 *
 * Libraries used:
 *    delay_ms() from the delays.lib library.
 *
 */

/* Change these definitions if you move the LCD */
#define LCDPORT PORTA
#define LCDPIN 1

#define LCDCMD 254
#define LCDCLS 1
#define LCDHOME 2
#define LCDLINE1BASE 128
#define LCDLINEINCR 64
#define LCDSCRLRIGHT 28
#define LCDSCRLLEFT 24

/* defined in delays.c */
extern void delay_ms(char);

/* Print a single byte to the LCD screen
 *
 * Inverts the bits as the serial LCD expects inverse polarity.
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: character c: 0 <= c <= 255
 * Return Value: none
 *
 * Uses Dwayne Reid's TXBYTE routine. Thanks Dwayne!
 * Also uses code from Peter H. Anderson
 */
void lcd_putc(char c) {
  char sertime;
  /* This code adapted from:
   * TXBYTE: send 1 byte @ 9600 baud: 1 RAM, 15 (16) ROM, 1043 cycles
   * Copyright (C) 1995 Dwayne Reid. May be freely used so long as this
   * copyright notice is retained.
   *
   * Other parts taken from code:
   * Copyright, Peter H. Anderson, Morgan State University, June 14, '97
   *
   * This code must invert the sense of the bits because the Serial
   * LCD backpack expects it this way. This includes inverting the sense
   * of the START and STOP bits.
   */

  clear_bit(LCDPORT, LCDPIN); /* set to stop bit */
  
  sertime = 255;			    
  while(sertime-- != 0);  /* let stop bit be set for a while */

  /* now we loop through 9 bits and set the output line. After each
     bit, pause for 104uS. This represents 9600 baud
  */
asm movlw 9		; 8 data + 1 start 
asm clrc		; start bit

lcdtxloop:
asm  skpnc	        ; bit time = 104.167 uSec
  clear_bit(LCDPORT, LCDPIN);
asm  skpc
  set_bit(LCDPORT, LCDPIN);
lcddloop:       	
asm                     ; 95 clk cycle delay
asm goto $+1            ; 2-cycle NOP in 1 instruction!
asm  addlw b'00010000'	; increment upper nibble
asm  skpc		; delay = 6n -1 (1 less when falls thru)
			    /* A relative jump backwards */
asm  goto $-3   	; goto lcddloop loop sixteen times
asm  addlw -1		; dec w, valid z, c=1 if w=0 after decrement
asm  rrf param00_lcd_putc,F	; carry will be set - shifted in as stop bit
asm  skpz
			    /* A relative jump back */
asm  goto $-0xb        ; goto lcdtxloop txloop is 104 clk cycles

  clear_bit(LCDPORT, LCDPIN); /* send stop bit */
  delay_ms(10);  /* a pacing delay of 10ms between characters */
}

/*
 * clear the LCD screen
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: none
 * Return Value: none
 */
void lcd_clear(void) {
  lcd_putc(LCDCMD);
  lcd_putc(LCDCLS);
}

/* 
 * Home the LCD cursor
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: none
 * Return Value: none
 */
void lcd_home(void) {
  lcd_putc(LCDCMD);
  lcd_putc(LCDHOME);
}


/* Print a string to the LCD screen
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: s: address of a string of bytes in DATA memory
 *               string must be NULL terminated
 * Returns: number of bytes written (number of chars printed)
 */
char lcd_puts(char s) {

}

/* Print a value in decimal to the LCD screen
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Note: output is always 3 digits: 001 not 1, 025, not 25
 * Arguments: x: value to print 0 <= x <= 255
 * Returns: none
 */
void lcd_printdec(char x) {

  char count;
  
  /* how many hundreds? */
  count=0;
  while(x > 99) {
    x = x - 100;
    count++;
  }
  lcd_putc('0' + count);

  /* how many tens? */
  count=0;
  while(x > 9) {
    x = x - 10;
    count++;
  }
  lcd_putc('0' + count);

  /* and finally units */
  lcd_putc('0' + x);

}

/* Print a value in hex to the LCD screen
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: x: value to print 0 <= x <= 255
 * Returns: none
 */
void lcd_printhex(char x) {
  char nibble;

  /* extract high nibble */
  nibble = x & 0xf0;
  /* swap the high and low nibbles */
asm swapf _nibble_lcd_printhex, f;
  if(nibble < 10) {
    lcd_putc('0' + nibble);
  } else {
    lcd_putc(nibble - 10 + 'A');
  }

  /* do the same on the low nibble */
  nibble = x & 0x0f;

  if(nibble < 10) {
    lcd_putc('0' + nibble);
  } else {
    lcd_putc(nibble - 10 + 'A');
  }

}

/* Print a value to the LCD screen as binary
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: x: value to print 0 <= x <= 255
 * Returns: none
 */
void lcd_printbin(char x) {

  char bits[] = {128, 64, 32, 16, 8, 4, 2, 1};
  char i = 0;
  while(i < 8) {
    if(x & bits[i]) {
      lcd_putc('1');
    } else {
      lcd_putc('0');
    }
    i++;
  }
}

/* Move the cursor to the specified position
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: row: row on LCD: 0 <= row <= 1
 *            col: column on LCD: 0 <= col <= 16
 *
 *** Remember: columns are indexed from the left starting at col 0.
 * If you print off the screen, it is not displayed, but is stored in
 * the LCD's RAM. Use the lcd_scroll_left and lcd_scroll_right commands
 * to access the data
 *
 * Returns: nothing
 */
void lcd_goto(char row, char col) {

  lcd_putc(LCDCMD);

  lcd_putc(LCDLINE1BASE + (LCDLINEINCR * row) + col);

}

/* Scroll the display N characters to the left
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: N: number of characters to scroll left. N > 0
 * There is a 10ms delay between each scroll action
 * Returns: nothing
 */
void lcd_scroll_left(char n) {

  while(n) {
    lcd_putc(LCDCMD);
    lcd_putc(LCDSCRLLEFT);
    delay_ms(10);
    n--;
  }
}

/* Scroll the display N characters to the right
 *
 *** Assumes 9600 8N1 communication
 *** The constant LCDPIN defines which pin the LCD is on
 *** The constant LCDPORT defines which port the LCD in on
 *
 * Arguments: N: number of characters to scroll right. N > 0
 * There is a 10ms delay between each scroll action
 * Returns: nothing
 */
void lcd_scroll_right(char n) {

  while(n) {
    lcd_putc(LCDCMD);
    lcd_putc(LCDSCRLRIGHT);
    delay_ms(10);
    n--;
  }
}
