NSLU2-Linux
view · edit · print · history

HowTo.AddAnLcdDisplayViaTheI2C History

Hide minor edits - Show changes to markup

July 04, 2008, at 07:12 AM by stefan keller-tuberg --
Changed lines 816-817 from:
   PORTB=0x00;
to:
   PORTB=SWITCH_MASK;
Changed lines 917-918 from:
         if (txbuf[SWITCHES].SW1? != i.SW1?)  // Switch 1 changed
         {
to:
         if ((i.SW1? == 0) && (txbuf[SWITCHES].SW1? == 1))  // Switch 1 pressed
Changed lines 919-923 from:
           txbuf[SWITCH1?].F7 = i.SW1? ;
         }

         if (txbuf[SWITCHES].SW2? != i.SW2?)  // Switch 2 changed
         {
to:
         if ((i.SW2? == 0) && (txbuf[SWITCHES].SW2? == 1))  // Switch 2 pressed
Changed lines 922-926 from:
           txbuf[SWITCH2?].F7 = i.SW2? ;
         }

         if (txbuf[SWITCHES].SW3? != i.SW3?)  // Switch 3 changed
         {
to:
         if ((i.SW3? == 0) && (txbuf[SWITCHES].SW3? == 1))  // Switch 3 pressed
Changed lines 925-927 from:
           txbuf[SWITCH3?].F7 = i.SW3? ;
         }
to:
         txbuf[SWITCH1?].F7 = i.SW1? ;
         txbuf[SWITCH2?].F7 = i.SW2? ;
         txbuf[SWITCH3?].F7 = i.SW3? ;
Added line 971:
     Check_ADC();
June 28, 2008, at 08:17 PM by ByronT -- Removal of LCDs from wikiword
Changed lines 3-8 from:

I wanted to interface a generic HD44780 based LCD display to my NSLU2. There are many different displays of different dimensions available meeting this standard, and its relatively easy to programme. These LCDs? are relatively cheap, starting at a few dollars for a small device up to a few tens of dollars for the larger ones.

When thinking about connecting any hardware peripheral to the NSLU2, the problem is there are no general purpose I/O pins available. I've seen projects using the USB interface to connect LCDs?, but only hints of projects using the I2C? bus. In this article, I'm going to detail how I implemented an I2C? LCD interface.

Typical LCDs? don't come with I2C? interfaces built in. However cheap PIC microcontrollers can implement an I2C? bus for a couple of dollars. I chose to use a 16F88, an 18 pin DIP with two eight bit ports available. My design uses two of these 16 I/O pins for the I2C? interface, seven for the LCD interface and leaves seven for you to implement other cool stuff such as A/D inputs, keypad reading inputs or general purpose outputs. Using these other 16F88 I/O lines, I've added a battery voltage monitor and some general purpose switches accessible from the NSLU2 software through the I2C? bus.

to:

I wanted to interface a generic HD44780 based LCD display to my NSLU2. There are many different displays of different dimensions available meeting this standard, and its relatively easy to programme. These LCDs are relatively cheap, starting at a few dollars for a small device up to a few tens of dollars for the larger ones.

When thinking about connecting any hardware peripheral to the NSLU2, the problem is there are no general purpose I/O pins available. I've seen projects using the USB interface to connect LCDs, but only hints of projects using the I2C? bus. In this article, I'm going to detail how I implemented an I2C? LCD interface.

Typical LCDs don't come with I2C? interfaces built in. However cheap PIC microcontrollers can implement an I2C? bus for a couple of dollars. I chose to use a 16F88, an 18 pin DIP with two eight bit ports available. My design uses two of these 16 I/O pins for the I2C? interface, seven for the LCD interface and leaves seven for you to implement other cool stuff such as A/D inputs, keypad reading inputs or general purpose outputs. Using these other 16F88 I/O lines, I've added a battery voltage monitor and some general purpose switches accessible from the NSLU2 software through the I2C? bus.

June 27, 2008, at 07:18 AM by stefan keller-tuberg --
Changed line 28 from:
 Pin 7: PORT B bit 1 = SD on I2C?  (I added my own 22k pullup to 3.3V)
to:
 Pin 7: PORT B bit 1 = SD on I2C?  (I added my own 4k7 pullup to 3.3V)
Changed line 31 from:
 Pin 10: PORT B bit 4 = CK on I2C?  (I added my own 22k pullup to 3.3V)
to:
 Pin 10: PORT B bit 4 = CK on I2C?  (I added my own 4k7 pullup to 3.3V)
Changed line 223 from:
    printf("d: ADC1?=\%04x, ADC2?=\%04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
to:
    printf("\%d: ADC1?=\%04x, ADC2?=\%04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
Changed lines 229-230 from:
      printf(", %\02x", buf[n]);
to:
      printf(", \%02x", buf[n]);
Changed lines 982-983 from:

Other tips

to:

Big Tip: NSLU2 I2C? drivers can only drive short flying leads unless you pull them up externally

June 27, 2008, at 07:09 AM by stefan keller-tuberg --
Changed lines 78-79 from:

Software to access the I2C? on the NSLU2 (echo_lcd.c)

to:

Programme to write to an LCD connected via I2C? to the NSLU2 (echo_lcd.c)

Changed lines 173-176 from:

Secondly, here's an example of a programme to read a data structure off the I2C? bus. This is tailored to interpret the data structure returned by the PIC code below.

to:

Programme to read data from an I2C? device connected to the NSLU2 (read_i2c.c)

Here's an example of a programme to read a data structure off the I2C? bus. This is tailored to interpret the data structure returned by the PIC code below.

June 27, 2008, at 07:06 AM by stefan keller-tuberg --
Changed line 107 from:
    fprintf (stderr, "Usage: %s STRING [STRING] ...\n", argv[0]);
to:
    fprintf (stderr, "Usage: \s STRING [STRING] ...\n", argv[0]);
Changed line 164 from:
      printf("Write error! Returned d - %s\n", n, length, strerror(errno));
to:
      printf("Write error! Returned \%d instead of \%d - \%s\n", n, length, strerror(errno));
Changed lines 218-224 from:
      printf("d: Read error! Returned d instead of d\n", i, n, BUFSIZE);

    printf("d: ADC1?=04x, ADC2?=04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
    printf(", SW1?(=c count=d)", (buf[4] & 0x80)?'1':'0', buf[4]&0x7f);
    printf(", SW2?(=c count=d)", (buf[5] & 0x80)?'1':'0', buf[5]&0x7f);
    printf(", SW3?(=c count=d)", (buf[6] & 0x80)?'1':'0', buf[6]&0x7f);
to:
      printf("\%d: Read error! Returned \%d instead of \%d\n", i, n, BUFSIZE);

    printf("d: ADC1?=\%04x, ADC2?=\%04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
    printf(", SW1?(=\%c count=\%d)", (buf[4] & 0x80)?'1':'0', buf[4]&0x7f);
    printf(", SW2?(=\%c count=\%d)", (buf[5] & 0x80)?'1':'0', buf[5]&0x7f);
    printf(", SW3?(=\%c count=\%d)", (buf[6] & 0x80)?'1':'0', buf[6]&0x7f);
Changed lines 226-227 from:
      printf(", 02x", buf[n]);
to:
      printf(", %\02x", buf[n]);
June 27, 2008, at 07:05 AM by stefan keller-tuberg --
Changed lines 160-161 from:
    printf ("Writing s\n", length, argv[i]) ;
to:
    printf ("Writing \%d characters: \%s\n", length, argv[i]) ;
June 27, 2008, at 07:04 AM by stefan keller-tuberg --
Changed lines 160-161 from:
    printf ("Writing d characters: s\n", length, argv[i]) ;
to:
    printf ("Writing s\n", length, argv[i]) ;
Changed line 164 from:
      printf("Write error! Returned d instead of d - s\n", n, length, strerror(errno));
to:
      printf("Write error! Returned d - %s\n", n, length, strerror(errno));
June 27, 2008, at 07:03 AM by stefan keller-tuberg --
Changed line 107 from:
    fprintf (stderr, "Usage: %37s STRING [STRING] ...\n", argv[0]);
to:
    fprintf (stderr, "Usage: %s STRING [STRING] ...\n", argv[0]);
June 27, 2008, at 07:02 AM by stefan keller-tuberg --
Changed line 107 from:
    fprintf (stderr, "Usage: &#37s STRING [STRING] ...\n", argv[0]);
to:
    fprintf (stderr, "Usage: %37s STRING [STRING] ...\n", argv[0]);
June 27, 2008, at 07:02 AM by stefan keller-tuberg --
Changed line 107 from:
    fprintf (stderr, "Usage: s STRING [STRING] ...\n", argv[0]);
to:
    fprintf (stderr, "Usage: &#37s STRING [STRING] ...\n", argv[0]);
June 27, 2008, at 06:57 AM by stefan keller-tuberg --
Changed line 107 from:
    fprintf (stderr, "Usage: %s STRING [STRING] ...\n", argv[0]);
to:
    fprintf (stderr, "Usage: s STRING [STRING] ...\n", argv[0]);
Changed lines 160-161 from:
    printf ("Writing s\n", length, argv[i]) ;
to:
    printf ("Writing d characters: s\n", length, argv[i]) ;
Changed line 164 from:
      printf("Write error! Returned d - %s\n", n, length, strerror(errno));
to:
      printf("Write error! Returned d instead of d - s\n", n, length, strerror(errno));
Changed lines 218-224 from:
      printf("%d: Read error! Returned d\n", i, n, BUFSIZE);

    printf("%d: ADC1?=%04x, ADC2?=%04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
    printf(", SW1?(=d)", (buf[4] & 0x80)?'1':'0', buf[4]&0x7f);
    printf(", SW2?(=d)", (buf[5] & 0x80)?'1':'0', buf[5]&0x7f);
    printf(", SW3?(=d)", (buf[6] & 0x80)?'1':'0', buf[6]&0x7f);
to:
      printf("d: Read error! Returned d instead of d\n", i, n, BUFSIZE);

    printf("d: ADC1?=04x, ADC2?=04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
    printf(", SW1?(=c count=d)", (buf[4] & 0x80)?'1':'0', buf[4]&0x7f);
    printf(", SW2?(=c count=d)", (buf[5] & 0x80)?'1':'0', buf[5]&0x7f);
    printf(", SW3?(=c count=d)", (buf[6] & 0x80)?'1':'0', buf[6]&0x7f);
Changed lines 226-227 from:
      printf(", %02x", buf[n]);
to:
      printf(", 02x", buf[n]);
June 27, 2008, at 06:55 AM by stefan keller-tuberg -- update of software source code
Deleted line 82:
Changed line 92 from:
to:
Changed lines 99-170 from:
   int fd;
   int length ;
   int n, i;
   char *s ;
   unsigned char buf[BUFSIZE+1];

   if (argc < 2)
   {
     fprintf (stderr, "Usage: %s STRING [STRING] ...\n", argv[0]);
     exit (1) ;
   }

   if ((fd = open("/dev/i2c-0", O_RDWR)) < 0)
   {
     printf("Error opening i2c port!\n");
     exit (1);
   }

   if (ioctl(fd,I2C?_SLAVE,I2C?_ADDRESS) < 0)
   {
     printf("Error setting i2c address!\n");
     exit (1);
   }

   if (ioctl(fd,I2C?_RETRIES,2) < 0)
   {
     printf("Error setting i2c retries!\n");
     exit (1);
   }

   for (i=1 ; i < argc ; i++)
   {
     length = strlen (argv[i]) ;

     if (!length)
       continue ;

     for (s=argv[i],length=0 ; *s && (length < BUFSIZE) ;)
     {
       if (*s == '\\')
       {
         switch (*++s)
         {
           case '0': buf[length++]=0x00 ; s++ ; break ;
           case '1': buf[length++]=0x01 ; s++ ; break ;
           case '2': buf[length++]=0x02 ; s++ ; break ;
           case '3': buf[length++]=0x03 ; s++ ; break ;
           case '4': buf[length++]=0x04 ; s++ ; break ;
           case 'n': buf[length++]=0x0a ; s++ ; break ;
           case 'r': buf[length++]=0x0d ; s++ ; break ;
           default: ; break ; //Ignore the slash if following char is unknown
         }
       }
       else
         buf[length++]=*s++ ;
     }

     if ((i < argc) && (length < BUFSIZE))
       buf[length++]=' ' ;

     n = write(fd, buf, length);
     printf ("Writing s\n", length, argv[i]) ;

     if (n != length)
     {
       printf("Write error! Returned d - %s\n", n, length, strerror(errno));
       exit (1);
     }
   }

   close (fd);
   return 0 ;
to:
  int fd;
  int length ;
  int n, i;
  char *s ;
  unsigned char buf[BUFSIZE+1];

  if (argc < 2)
  {
    fprintf (stderr, "Usage: %s STRING [STRING] ...\n", argv[0]);
    exit (1) ;
  }

  if ((fd = open("/dev/i2c-0", O_RDWR)) < 0)
  {
    printf("Error opening i2c port!\n");
    exit (1);
  }

  if (ioctl(fd,I2C?_SLAVE,I2C?_ADDRESS) < 0)
  {
    printf("Error setting i2c address!\n");
    exit (1);
  }

  if (ioctl(fd,I2C?_RETRIES,2) < 0)
  {
    printf("Error setting i2c retries!\n");
    exit (1);
  }

  for (i=1 ; i < argc ; i++)
  {
    if (strlen(argv[i]) == 0)
      continue ;

    length = 0 ;

    if (i > 1))
      buf[length++]=' ' ;

    for (s=argv[i] ; *s && (length < BUFSIZE) ;)
    {
      if (*s == '\\')
      {
        switch (*++s)
        {
          case '0': buf[length++]=0x00 ; s++ ; break ;
          case '1': buf[length++]=0x01 ; s++ ; break ;
          case '2': buf[length++]=0x02 ; s++ ; break ;
          case '3': buf[length++]=0x03 ; s++ ; break ;
          case '4': buf[length++]=0x04 ; s++ ; break ;
          case 'n': buf[length++]=0x0a ; s++ ; break ;
          case 'r': buf[length++]=0x0d ; s++ ; break ;
          default: ; break ; //Ignore the slash if following char is unknown
        }
      }
      else
        buf[length++]=*s++ ;
    }

    n = write(fd, buf, length);
    printf ("Writing s\n", length, argv[i]) ;

    if (n != length)
    {
      printf("Write error! Returned d - %s\n", n, length, strerror(errno));
      exit (1);
    }
  }

  close (fd);
  return 0 ;
Added lines 173-234:

Secondly, here's an example of a programme to read a data structure off the I2C? bus. This is tailored to interpret the data structure returned by the PIC code below.

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 //include <linux/i2c-dev.h>
 #include "i2c-dev.h"

 #define I2C?_ADDRESS 0x32
 #define BUFSIZE 10

 int
 main (int argc, char **argv)
 {
  int fd;
  int n, i;
  unsigned char buf[BUFSIZE+1];

  if ((fd = open("/dev/i2c-0", O_RDWR)) < 0)
  {
    printf("Error opening i2c port!\n");
    exit (1);
  }

  if (ioctl(fd,I2C?_SLAVE,I2C?_ADDRESS) < 0)
  {
    printf("Error setting i2c address!\n");
    exit (1);
  }

  if (ioctl(fd,I2C?_RETRIES,2) < 0)
  {
    printf("Error setting i2c retries!\n");
    exit (1);
  }

  for (i=1 ; i < 8 ; i++)
  {
    sleep (1) ;
    n = read (fd, buf, BUFSIZE);

    if (n != BUFSIZE)
      printf("%d: Read error! Returned d\n", i, n, BUFSIZE);

    printf("%d: ADC1?=%04x, ADC2?=%04x", i, (buf[0]<<8 + buf[1]), (buf[2]<<8 + buf[3]));
    printf(", SW1?(=d)", (buf[4] & 0x80)?'1':'0', buf[4]&0x7f);
    printf(", SW2?(=d)", (buf[5] & 0x80)?'1':'0', buf[5]&0x7f);
    printf(", SW3?(=d)", (buf[6] & 0x80)?'1':'0', buf[6]&0x7f);

    for (n=7 ; n < BUFSIZE ; n++)
      printf(", %02x", buf[n]);

    printf ("\n");
  }

  close (fd);
  return 0 ;
 }
Added line 271:
        $(INSTALL_BIN) $(PKG_BUILD_DIR)/read_i2c $(1)/bin/
Added line 273:
        $(INSTALL_BIN) $(PKG_BUILD_DIR)/test_i2c $(1)/bin/
Changed line 284 from:
 BINS = echo_lcd
to:
 BINS = echo_lcd read_i2c
Changed lines 305-306 from:
to:
 read_i2c: read_i2c.o
Changed lines 325-328 from:

In order for these functions to work properly, you need to customise the C code with the dimensions of the LCD you are going to use. Use the #define statements near the top of the code to specify the number of lines and the number of characters per line.

You also need to choose an i2c address for the PIC and specify this in the #define near the top of the file too. If necessary, you can have several different I2C? devices connected to your NSLU2 at the same time, each with different addresses.

to:

In order for these functions to work properly, you need to customise the C code with the dimensions of the LCD you are going to use. Use the #define statements near the top of the code to specify the number of lines and the number of characters per line. Choose an i2c address for the PIC using the #define near the top of the file also. Its possible to have several different I2C? devices connected to your NSLU2 at the same time, each with different addresses.

Changed lines 331-335 from:
 // 16F88 programme to drive a hitachi compatible LCD display from the
 // i2C bus
 //
 // There are plenty of spare inputs and outputs for additional functions
 //////////////////////////////////////////////////////////////////////
to:
 // 16F88 programme to drive a hitachi compatible LCD display, read switches
 // and monitor some voltages..... Interface to NSLU2 or host micro via I2C? bus

 // Define the I2C? address for this device
Changed line 336 from:
to:
Changed lines 340-349 from:
 // This is the assignment of 16F88 pins I chose
 // PORT A bit 0 = D4 on LCD
 // PORT A bit 1 = D5 on LCD
 // PORT A bit 2 = D6 on LCD
 // PORT A bit 3 = D7 on LCD
 // PORT A bit 4 = NC
 // PORT A bit 5 = pulled high with 10k
 // PORT A bit 6 = NC
 // PORT A bit 7 = NC
to:
 //////////////////////////////////////////////////////////////////////
 // Copyright Stefan Keller-Tuberg (C) 2008
Added lines 343-365:
 //    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
 //    (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.
 //
 // Visit <http://www.gnu.org/licenses/> to view the terms of the license.
 //////////////////////////////////////////////////////////////////////
 // PIC 16F88 pin assignments as follows:
 //
 // PORT A bit 0 = D4 on LCD
 // PORT A bit 1 = D5 on LCD
 // PORT A bit 2 = D6 on LCD
 // PORT A bit 3 = Analogue Input - tied to 3.3V rail with 22k
 // PORT A bit 4 = Analogue Input - Battery Voltage via 1:2 voltage divider
 // PORT A bit 5 = pulled to VDD via 22k (This is normally reset input)
 // PORT A bit 6 = NC
 // PORT A bit 7 = D7 on LCD
 //
Changed line 367 from:
 // PORT B bit 1 = SD on I2C?
to:
 // PORT B bit 1 = SD on I2C?  (I added my own 4k7 pullup to 3.3V - didn't work without)
Changed lines 370-374 from:
 // PORT B bit 4 = CK on I2C?
 // PORT B bit 5 = NC
 // PORT B bit 6 = NC
 // PORT B bit 7 = NC
to:
 // PORT B bit 4 = CK on I2C?  (I added my own 4k7 pullup to 3.3V - didn't work without)
 // PORT B bit 5 = Switch3 input. Pulled to VDD via 22k. Switched low.
 // PORT B bit 6 = Switch2 input. Pulled to VDD via 22k. Switched low.
 // PORT B bit 7 = Switch1 input. Pulled to VDD via 22k. Switched low.
 //
 // VDD connected to +5V USB port supply via a 1N4001 diode - to drop voltage so
 // I2C? inputs will work OK when pulled to only 3.3V
 //////////////////////////////////////////////////////////////////////
Added lines 379-380:
 // Define PORTB bits used for LCD control
Changed lines 384-387 from:
 // counters for debugging purposes
 unsigned char crashed, crash_cause, info, last_i2c ;
 //////////////////////////////////////////////////////////////////////
to:
 // Define PORTA bits used for the four LCD data bits
 #define LCD4?   F0
 #define LCD5?   F1
 #define LCD6?   F2
 #define LCD7?   F7

 // Define PORTA bits used for Analogue inputs
 #define ANALOGUE0?  F3
 #define ANALOGUE1?  F4

 // Define PORTB bits used for Digital inputs
 #define SW1?    F5
 #define SW2?    F6
 #define SW3?    F7
 #define SWITCH_MASK 0xe0
Changed lines 404-409 from:
   TRISA.F0 = 0;      //Set Bits 3-0 as OUTPUTS
   TRISA.F1 = 0;
   TRISA.F2 = 0;
   TRISA.F3 = 0;
   PORTA = c ;        //Bits 3-0 = high order data nibble
to:
   TRISA.LCD4? = 0;
   TRISA.LCD5? = 0;
   TRISA.LCD6? = 0;
   TRISA.LCD7? = 0;
   PORTA.LCD4? = c.F0 ;
   PORTA.LCD5? = c.F1 ;
   PORTA.LCD6? = c.F2 ;
   PORTA.LCD7? = c.F3 ;
Changed line 414 from:
to:
Changed line 419 from:
to:
Changed line 425 from:
to:
Changed lines 428-429 from:
to:
 // Assumes Port A bits 7, 2-0 are the ONLY outputs (ie bits 6-3 are INPUTS)
Changed lines 434-439 from:
   TRISA.F0 = 1;       //Set Bits 3-0 as INPUTS
   TRISA.F1 = 1;
   TRISA.F2 = 1;
   TRISA.F3 = 1;
to:
   TRISA.LCD4? = 1;
   TRISA.LCD5? = 1;
   TRISA.LCD6? = 1;
   TRISA.LCD7? = 1;
Changed line 444 from:
to:
Changed line 447 from:
to:
Changed line 452 from:
     busy = PORTA ;      //Read the busy flag in bit 3 (bit 7 of LCD)
to:
     busy = PORTA ;      //Read the busy flag in bit 7 (bit 7 of LCD)
Changed lines 459-460 from:
   } while (busy.F3);
to:
   } while (busy.LCD7?);
Changed lines 463-464 from:
   write_nibble_to_lcd (c & 0x0f, RS) ;
to:
   write_nibble_to_lcd (c, RS) ;
Changed line 467 from:
to:
Changed line 474 from:
to:
Changed line 480 from:
to:
Changed line 482 from:
to:
Changed line 486 from:
to:
Changed line 491 from:
to:
Changed line 496 from:
to:
Changed line 499 from:
to:
Changed line 509 from:
to:
Changed lines 512-528 from:
to:
 // The following is a bogus structure definition....
 // the array is fed back to the NSLU2 when read off I2C?
 #define BATT_VOLT  0              // 2 byte ADC count
 #define REF_VOLT   (BATT_VOLT+2)  // 2 byte ADC count
 #define SWITCH1?    (REF_VOLT+2)   // 1 byte count of debounced presses
 #define SWITCH2?    (SWITCH1?+1)    // 1 byte count of debounced presses
 #define SWITCH3?    (SWITCH2?+1)    // 1 byte count of debounced presses
 #define SWITCHES   (SWITCH3?+1)    // 1 byte count current state
 #define STRUCT_LEN (SWITCHES+1)   // Should be total of 8

 unsigned char txbuf [STRUCT_LEN] ;
 unsigned char tx_index ;

 // counters for debugging purposes
 unsigned char crashed, crash_cause, info, last_i2c ;
Changed line 533 from:
to:
Changed line 540 from:
to:
Changed line 549 from:
to:
Added line 555:
       tx_index = 1 ;   // byte to be transmitted
Changed lines 557-558 from:
       do
to:
            do
Changed lines 560-561 from:
               SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0xaa ; // Just return AA for the time being
to:
              SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = txbuf[0] ; // Return first byte of array
Changed line 567 from:
to:
Changed line 571 from:
         SSPBUF = 0x55 ; // Just return 55 for the time being
to:
         SSPBUF = txbuf[tx_index] ; // Return next byte of array
Added lines 573-574:
       tx_index++ ; // point to next byte in the array next time
Changed line 587 from:
to:
Changed line 603 from:
to:
Changed line 605 from:
to:
Changed line 610 from:
to:
Changed line 619 from:
to:
Changed line 621 from:
to:
Changed line 624 from:
to:
Changed line 628 from:
to:
Changed line 643 from:
to:
Changed line 646 from:
to:
Changed line 649 from:
   else if (c < 5)
to:
   else if (c <= LINES)
Changed line 665 from:
to:
Changed line 668 from:
to:
Changed line 671 from:
to:
Changed line 677 from:
to:
Changed line 680 from:
 enqueue_string (const unsigned char *s)
to:
 enqueue_char (unsigned char c)
Changed lines 682-688 from:
   while (*s)
to:
   PIE1?.F3 = 0;  // Disable int F3=I2C? int
   rxbuf[rx_head++] = c ;
   rx_head &= BUFFER_MASK ;
   rx_len++ ;
   PIE1?.F3 = 1;  // Enable int F3=I2C? int

   if (rx_len > BUFFER_LEN)
Changed lines 690-700 from:
     PIE1?.F3 = 0;  // Disable int F3=I2C? int
     rxbuf[rx_head++] = *s++ ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;
     PIE1?.F3 = 1;  // Enable int F3=I2C? int

     if (rx_len > BUFFER_LEN)
     {
       crashed = 4 ;  //flag error
       return ;
     }
to:
     crashed = 4 ;  //flag error
     return ;
Changed line 694 from:
to:
Changed line 696 from:
 enqueue_number (const unsigned char c)
to:
 enqueue_string (const unsigned char *s)
Added lines 698-704:
   while (*s)
     enqueue_char (*s++) ;
 }

 void
 enqueue_number (const unsigned char c)
 {
Changed line 707 from:
to:
Changed line 709 from:
to:
Changed line 714 from:
to:
Changed line 718 from:
to:
Changed lines 723-728 from:
 //==============================================================================
 #define MAGIC       0x3d

 unsigned char num_boots ;
 unsigned char magic ; // Will hold a magic number so we know a cold boot
to:
Changed line 725 from:
 main()
to:
 flush_display_buffer()
Changed lines 727-757 from:
   char i ;

   // Osc = bits 6,5,4
   OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
   while (OSCCON.F2 == 0) ; // Wait for osc to stabilise

 restart:
   OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
   INTCON=0;        //Disable interrupts
   PIR1?=0;          //Clear all interrupt flags

   TRISA=0;         //Set Port A to all outputs
   TRISB=0x12;      //B4 and B1 are inputs (I2C?)
   PORTA=0x00;
   PORTB=0x00;

   WDTCON =0x17 ;   // Enable watchdog with max timeout value = approx 2s

   ANSEL=0; //0 = PortA? bit is used for digital I/O
   ADCON1?=0x07 ; // Disable A/D

   SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                   // F6=0 SSPOV Receive Overflow Indicator bit
                   // F5=1 Synchronous Serial Port Enable
                   // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                   // F3-0=0110: I2C? slave mode: 7 bits address
   SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
   SSPSTAT = 0 ;   // Clear all status bits
   PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int

   if (magic != MAGIC)
to:
   asm {
     CLRWDT  // clear the watchdog timer
   }

   if (rx_len)
Changed lines 733-735 from:
     crash_cause=0 ;  // used for debugging interrupt routine with print statements
     num_boots = 0 ;
     magic = MAGIC ;
to:
     printchar (rxbuf[rx_tail++]) ;  //THIS IS ONLY PLACE WE TOUCH rx_tail
     rx_tail &= BUFFER_MASK ;
     PIE1?.F3 = 0;  // Disable int F3=I2C? int
     rx_len-- ;
     PIE1?.F3 = 1;  // Enable int F3=I2C? int
Changed lines 739-754 from:
   else
     num_boots++ ;

   crashed = 0 ;   // We're not crashed at the moment!
   rx_head = rx_tail = rx_len = 0 ;

   INTCON.F6 = 1 ; // enable peripheral interrupts
   INTCON.F7 = 1 ; // enable all unmasked interrupts

   TXSTA = 0 ;     // Turn off transmit from UART
   RCSTA = 0 ;
   CMCON = 0x07 ;  // Disable comparators

   Delay_ms (100); // Provide a little time for the LCD to initialise
   asm {
     CLRWDT        // Clear the watchdog timer
to:
 }
 //==============================================================================
 unsigned char debounce ;

 void
 Check_ADC()
 {
   unsigned char i ;

   if (ADCON0?.F2 == 0) //zero means converstion finished
   {
     i = ADCON0? ;
     i &= 0x38 ;    // Only interested in the channel select bits

     if (i == 0x18)
     {
       txbuf [REF_VOLT]   = ADRESH ;
       txbuf [REF_VOLT+1] = ADRESL ;
       ADCON0?=0xa1 ;//  convert voltage on A4 next
     }
     else
     {
       txbuf [BATT_VOLT]   = ADRESH ;
       txbuf [BATT_VOLT+1] = ADRESL ;
       ADCON0?=0x99 ;//  else convert voltage on A3 next
     }

     Delay_us (100); // Wait a bit for analogue input to settle
     ADCON0?.F2 = 1; // Start the conversion
Changed lines 769-796 from:
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (5);               // Wait at least 4.1ms
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
   Delay_ms (1);               // Wait at least 100us

   write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
   write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
   write_byte_to_lcd (0x01, 0); //Clear display
   write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
   write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
   line = 1;
   column = 0 ;

   // Display the welcome message
   enqueue_number (LINES) ;
   enqueue_string ("x") ;
   enqueue_number (COLUMNS) ;
   enqueue_string (" lcd @addr ") ;
   enqueue_number (I2C?_ADDRESS) ;
   enqueue_string ("\nVersion 1");

   // Give some indication if there's been a crash
   if (num_boots)
to:
 }

 void
 Long_Delay (unsigned char time)
 {
   unsigned char i ;

   for (i=0 ; i < time ; i++)
Deleted lines 777-782:
     enqueue_string ("\rBoot count: ");
     enqueue_number (num_boots) ;
   }

   for (;;)
   {
Changed line 779 from:
       CLRWDT  // clear the watchdog timer
to:
       CLRWDT         // clear the watchdog timer
Changed lines 781-828 from:
     if (rx_len)
     {
       printchar (rxbuf[rx_tail++]) ;
       rx_tail &= BUFFER_MASK ;
       PIE1?.F3 = 0;  // Disable int F3=I2C? int
       rx_len-- ;
       PIE1?.F3 = 1;  // Enable int F3=I2C? int
     }

     if (crashed)
     {
       crash_cause = crashed ; // Remember why we crashed
       write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
       string_to_lcd ("Cause:") ;
       hexbyte_to_lcd (crash_cause);
       string_to_lcd (" last:") ;
       hexbyte_to_lcd (last_i2c) ;
       string_to_lcd (" info:") ;
       hexbyte_to_lcd (info);
       string_to_lcd (" boots:") ;
       hexbyte_to_lcd (num_boots);

       write_byte_to_lcd (SECOND_ROW, 0);
       string_to_lcd ("Buf len:") ;
       decbyte_to_lcd (rx_len);
       write_byte_to_lcd (' ', 1);

       for (i=0 ; i < 14 ; i++)
         hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);

       // The following kludge is to get around the non-reentrant version of C
       // If there's an error inside the Interrupt, this flag will be set
       // That lets us display something - and this loop will cause an infinte wait
 //      while (crashed) ; // Infinite loop if an error has been set in the ISR
                         // This will trip the watchdog timer some time later

       // Delay for 15 seconds
       for (i=0 ; i < 150 ; i++)
       {
         Delay_ms (100);
         asm {
           CLRWDT         // clear the watchdog timer
         }
       }

       goto restart ;
     }
to:
     Delay_ms (100);
Changed lines 785-789 from:

Example code for the NSLU2

to:
 //==============================================================================
 #define MAGIC       0x3d

 unsigned char num_boots ;
 unsigned char magic ; // Will hold a magic number so we know a cold boot

 void
 main()
 {
   char i, j ;

   OSCCON=0x7c; // Internal osc: 0x4c=1MHz, 0x5c=2MHz, 0x6c=4MHz, 0x7c=8MHz
   while (OSCCON.F2 == 0) ; // Wait for osc to stabilise

 restart:
   OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
   INTCON=0;     //Disable interrupts
   PIR1?=0;       //Clear all interrupt flags

   WDTCON =0x17 ;// Enable watchdog with max timeout value = approx 2s

   TRISA=0;      //Set Port A as outputs, except analogue inputs
   TRISA.ANALOGUE0?=1 ;
   TRISA.ANALOGUE1?=1 ;

   TRISB=0x12 | SWITCH_MASK;   //B4 and B1 are inputs (I2C?)

   PORTA=0x00;
   PORTB=0x00;

   ANSEL=0;      //Set Port A as outputs, except analogue inputs
   ANSEL.ANALOGUE0?=1 ;
   ANSEL.ANALOGUE1?=1 ;
   ADCON1?=0xc0 ; // Bit7=1=right justify, 6=1=clokc/2, 5,4=00=Vref
   ADCON0?=0x81 ; // Bit7,6=10 Osc/32, Bit5-3=Chan select, bit0=1 A-D enabled

   SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                   // F6=0 SSPOV Receive Overflow Indicator bit
                   // F5=1 Synchronous Serial Port Enable
                   // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                   // F3-0=0110: I2C? slave mode: 7 bits address
   SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
   SSPSTAT = 0 ;   // Clear all status bits
   PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int

   if (magic != MAGIC)
   {
     crash_cause=0 ;  // used for debugging interrupt routine with print statements
     num_boots = 0 ;
     magic = MAGIC ;
   }
   else
     num_boots++ ;

   crashed = 0 ;   // We're not crashed at the moment!
   rx_head = rx_tail = rx_len = 0 ;

   INTCON.F6 = 1 ; // enable peripheral interrupts
   INTCON.F7 = 1 ; // enable all unmasked interrupts

   TXSTA = 0 ;     // Turn off transmit from UART
   RCSTA = 0 ;
   CMCON = 0x07 ;  // Disable comparators

   Long_Delay (2) ;  // Wait 200ms seconds

   txbuf[SWITCHES] = PORTB & SWITCH_MASK ; // read the initial switch inputs
   txbuf[SWITCH1?] = 0 ;
   txbuf[SWITCH2?] = 0 ;
   txbuf[SWITCH3?] = 0 ;
   debounce=0 ; // Initialise debouncing algorithm OFF

   write_nibble_to_lcd (0x03,0);// Function set 8 bit mode
   Delay_ms (5);                // Wait at least 4.1ms
   write_nibble_to_lcd (0x03,0);// Function set 8 bit mode
   Delay_ms (1);                // Wait at least 100us
   write_nibble_to_lcd (0x03,0);// Function set 8 bit mode
   Delay_ms (1);                // Wait at least 100us
   write_nibble_to_lcd (0x02,0);// NOW FINALLY Function set 4 bit mode
   Delay_ms (1);                // Wait at least 100us

   write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
   write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
   write_byte_to_lcd (0x01, 0); //Clear display
   write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
   write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
   line = 1;
   column = 0 ;

   // Give some indication if there's been a crash
   j=1 ;
   enqueue_number (LINES) ;
   enqueue_char ('x') ;
   enqueue_number (COLUMNS) ;
   enqueue_string (" lcd @addr ") ;
   enqueue_number (I2C?_ADDRESS) ;

   if (num_boots)
   {
     enqueue_string ("\nBoot count: ");
     enqueue_number (num_boots) ;
     j=2 ;
   }

   for (; j < LINES ; j++) // visual test for data line wiring errors...
   {
     enqueue_char ('\n');

     for (i=0; i < COLUMNS ; i++)
       enqueue_char (' '+i+(j*COLUMNS));

     while (rx_len) // empty out what we've just queued
       flush_display_buffer();
   }

   Long_Delay (50) ;  // Wait 5 seconds
   enqueue_char ('\0') ;
   enqueue_string ("Booting Linux.......");

   for (;;)
   {
     if (debounce) // are we currently debouncing?
     {
       debounce-- ;

       if (debounce == 0)
       {
         i = PORTB & SWITCH_MASK ; // read the current switch inputs

         if (txbuf[SWITCHES].SW1? != i.SW1?)  // Switch 1 changed
         {
           txbuf[SWITCH1?]++ ;
           txbuf[SWITCH1?].F7 = i.SW1? ;
         }

         if (txbuf[SWITCHES].SW2? != i.SW2?)  // Switch 2 changed
         {
           txbuf[SWITCH2?]++ ;
           txbuf[SWITCH2?].F7 = i.SW2? ;
         }

         if (txbuf[SWITCHES].SW3? != i.SW3?)  // Switch 3 changed
         {
           txbuf[SWITCH3?]++ ;
           txbuf[SWITCH3?].F7 = i.SW3? ;
         }

         txbuf[SWITCHES] = i ; // Remember state and END of debounce
       }
     }
     else  // we are NOT currently debouncing - so read the switches
     {
       i = PORTB & SWITCH_MASK ; // read the current switch inputs

       if (txbuf[SWITCHES] != i)  // need to debounce??
         debounce-- ;
     }

     if (crashed)
     {
       crash_cause = crashed ; // Remember why we crashed
       write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
       string_to_lcd ("Cause:") ;
       hexbyte_to_lcd (crash_cause);
       string_to_lcd (" last:") ;
       hexbyte_to_lcd (last_i2c) ;
       string_to_lcd (" info:") ;
       hexbyte_to_lcd (info);
       string_to_lcd (" boots:") ;
       hexbyte_to_lcd (num_boots);

       write_byte_to_lcd (SECOND_ROW, 0);
       string_to_lcd ("Buf len:") ;
       decbyte_to_lcd (rx_len);
       write_byte_to_lcd (' ', 1);

       for (i=0 ; i < 14 ; i++)
         hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);

       // The following kludge is to get around the non-reentrant version of C
       // If there's an error inside the Interrupt, this flag will be set
       // That lets us display something - and this loop will cause an infinte wait
 //      while (crashed) ; // Infinite loop if an error has been set in the ISR
                         // This will trip the watchdog timer some time later

       Long_Delay (150) ; // Delay for 15 seconds
       goto restart ;
     }

     flush_display_buffer();  //Print any characters received on I2C?
   }
 }
Added lines 980-981:

I could not get the I2C? drivers on the NSLU2 to speak with anything (such as the PIC described above) on a cable longer than about 50mm (ie really short) unless I pulled the I2C? clock and data lines to 3.3V on my external board using a 4k7 resistors. I know that the NSLU2 has its own pull up resistors - but they appear to be insufficient when driving anything other than a trivially short I2C? cable.

June 27, 2008, at 06:21 AM by stefan keller-tuberg --
Changed lines 3-4 from:

I wanted to interface a generic HD44780? based LCD display to my NSLU2. There are many different displays of different dimensions available meeting this standard, and its relatively easy to programme. These LCDs? are relatively cheap, starting at a few dollars for a small device up to a few tens of dollars for the larger ones.

to:

I wanted to interface a generic HD44780 based LCD display to my NSLU2. There are many different displays of different dimensions available meeting this standard, and its relatively easy to programme. These LCDs? are relatively cheap, starting at a few dollars for a small device up to a few tens of dollars for the larger ones.

June 26, 2008, at 05:54 AM by stefan keller-tuberg --
Deleted lines 0-1:

This is Work In Progress. I'll complete during June 2008

How do you upload a photo to this wiki? I have a circuit diagram to upload

Changed lines 7-8 from:

Typical LCDs? don't come with I2C? interfaces built in. However cheap PIC microcontrollers can implement an I2C? bus for a couple of dollars. I chose to use a 16F88, an 18 pin DIP with two eight bit ports available. My design uses two of these 16 I/O pins for the I2C? interface, seven for the LCD interface and leaves seven for you to implement other cool stuff such as A/D inputs, keypad reading inputs or general purpose outputs.

to:

Typical LCDs? don't come with I2C? interfaces built in. However cheap PIC microcontrollers can implement an I2C? bus for a couple of dollars. I chose to use a 16F88, an 18 pin DIP with two eight bit ports available. My design uses two of these 16 I/O pins for the I2C? interface, seven for the LCD interface and leaves seven for you to implement other cool stuff such as A/D inputs, keypad reading inputs or general purpose outputs. Using these other 16F88 I/O lines, I've added a battery voltage monitor and some general purpose switches accessible from the NSLU2 software through the I2C? bus.

June 26, 2008, at 05:48 AM by stefan keller-tuberg --
Added lines 19-40:

In the interim, without a circuit diagram, here are the 16F88 pin assignments:

 Pin 17: PORT A bit 0 = D4 on LCD
 Pin 18: PORT A bit 1 = D5 on LCD
 Pin 1: PORT A bit 2 = D6 on LCD
 Pin 2: PORT A bit 3 = Analogue Input - tied to 3.3V rail with 22k
 Pin 3: PORT A bit 4 = Analogue Input - Battery Voltage via 1:2 voltage divider
 Pin 4: PORT A bit 5 = pulled to VDD via 22k (This is normally reset input)
 Pin 15: PORT A bit 6 = NC
 Pin 16: PORT A bit 7 = D7 on LCD

 Pin 6: PORT B bit 0 = E on LCD
 Pin 7: PORT B bit 1 = SD on I2C?  (I added my own 22k pullup to 3.3V)
 Pin 8: PORT B bit 2 = RW on LCD
 Pin 9: PORT B bit 3 = RS on LCD
 Pin 10: PORT B bit 4 = CK on I2C?  (I added my own 22k pullup to 3.3V)
 Pin 11: PORT B bit 5 = Switch3 input. Pulled to VDD via 22k. Switched low.
 Pin 12: PORT B bit 6 = Switch2 input. Pulled to VDD via 22k. Switched low.
 Pin 13: PORT B bit 7 = Switch1 input. Pulled to VDD via 22k. Switched low.

 Pin 14: VDD connected to NSLU2 +5V USB supply via a 1N4001 diode
 Pin 5: VSS connected to NSLU2 0 Volt supply
June 16, 2008, at 06:28 AM by stefan keller-tuberg --
Changed lines 75-76 from:
to:
 #define BUFSIZE 256
Changed lines 83-85 from:
to:
   char *s ;
   unsigned char buf[BUFSIZE+1];
Changed line 110 from:
   for (i=1 ; i < argc ;)
to:
   for (i=1 ; i < argc ; i++)
Changed line 113 from:
to:
Changed lines 116-121 from:
     n = write(fd, argv[i], length);

     printf ("Writing s\n", length, argv[i]) ;

     if (n != length)
to:
     for (s=argv[i],length=0 ; *s && (length < BUFSIZE) ;)
Changed lines 119-120 from:
       printf("Write error! Returned d - %s\n", n, length, strerror(errno));
       exit (1);
to:
       if (*s == '\\')
       {
         switch (*++s)
         {
           case '0': buf[length++]=0x00 ; s++ ; break ;
           case '1': buf[length++]=0x01 ; s++ ; break ;
           case '2': buf[length++]=0x02 ; s++ ; break ;
           case '3': buf[length++]=0x03 ; s++ ; break ;
           case '4': buf[length++]=0x04 ; s++ ; break ;
           case 'n': buf[length++]=0x0a ; s++ ; break ;
           case 'r': buf[length++]=0x0d ; s++ ; break ;
           default: ; break ; //Ignore the slash if following char is unknown
         }
       }
       else
         buf[length++]=*s++ ;
Added lines 136-148:
     if ((i < argc) && (length < BUFSIZE))
       buf[length++]=' ' ;

     n = write(fd, buf, length);
     printf ("Writing s\n", length, argv[i]) ;

     if (n != length)
     {
       printf("Write error! Returned d - %s\n", n, length, strerror(errno));
       exit (1);
     }
   }
Deleted lines 149-152:
     if (++i < argc)
       write(fd, " ", 1);
   }
Changed lines 168-170 from:
  SECTION:=utils
  CATEGORY:=Base system
  TITLE:=My i2c additions to the NSLU2
to:
        SECTION:=utils
        CATEGORY:=Base system
        TITLE:=My i2c additions to the NSLU2
Changed lines 187-188 from:
        $(INSTALL_DIR) $(1)/etc
        $(INSTALL_DATA) ./files/* $(1)/etc
to:
        $(INSTALL_DIR) $(1)/etc/rc.d
        $(INSTALL_BIN) ./files/S* $(1)/etc/rc.d
Added lines 195-196:

Note the section starting with define Package/my-additions/install. This is where you compiled and static files get copied into the openwrt image. The lines involving the /etc/rc.d directory copy startup scripts which will be run when the NSLU2 boots. The filenames should conform with the general format of the first three characters being Sxx or Kxx (xx being a number between 00 and 99, and S or K denoting that the script should be run at startup time, or Kill (shutdown) time).

Changed lines 219-220 from:
 install: $(INSTALL_BIN) $(BINS) $(DESTDIR)/sbin/
to:
 install: $(INSTALL_BIN) $(BINS) $(DESTDIR)/bin/
June 15, 2008, at 09:09 AM by stefan keller-tuberg --
Changed lines 15-16 from:

The schematic diagram is really simple. I socketed a 16F88 onto a small piece of matrix board, along with the resistors and diode. I've constructed several of these, some built directly onto the back of the LCD module itself, and others connected to the LCD (and NSLU2) using pluggable ribbon cables.

to:

The schematic diagram is really simple. Just the 16F88, a diode, capacitor and a few resistors. I assembled them and some sockets onto a small piece of matrix board and now constructed several of them for my slugs.

Changed lines 19-20 from:

Note that because the NSLU2 I2C? bus uses 3V3 levels and the 16F88 and LCD devices are 5V, you need to pull a trick to interface the two. Thankfully, the I2C? bus is open drain, and the 16F88 and LCD devices work well down to a little over 4V (at least that's what I've found on the ones I've built). So by picking 5V off the USB port on the NSLU2, dropping this by 0.6V through a diode, you can power the LCD and 16F88 at 4.4V. At this reduced voltage, the 16F88 will recognise the 3V3 I2C? signalling just fine. Nothing further needs to be done to translate levels.

to:

Note that because the NSLU2 I2C? bus uses 3V3 (i.e. 3.3V) levels and the 16F88 and LCD devices are 5V, you need to pull a trick to interface the two. Thankfully, the I2C? bus is open drain, and the 16F88 and LCD devices work well down to a little over 4V (at least that's what I've found on the ones I've built). So by picking 5V off the USB port on the NSLU2, dropping this by 0V6 through a diode, you can power the LCD and 16F88 at 4V4. At this reduced voltage, the 16F88 will recognise the 3V3 I2C? signalling just fine. Nothing further needs to be done to translate levels.

Changed lines 23-24 from:

As mentioned above, I chose the OpenWRT? distribution for my NSLU2. I had some trouble compiling a working kernel so I will make some notes here to get you going. I have still never been able to compile a working kernel with the svn source.

to:

As mentioned above, I chose the OpenWRT? distribution for my NSLU2. I had some trouble compiling a working kernel so I will make some notes here to get you going. I have still never been able to compile a working kernel with the svn source. If somebody has a working kernel .config file they'd like to post - please do so! (The downloaded openwrt-kamikaze comes with a working .config which is easy to tweak to your requirements)

June 15, 2008, at 09:05 AM by stefan keller-tuberg --
Changed lines 13-14 from:

There are other pages on this wiki that describe where the I2C? bus can be accessed on the NSLU2. I'm not going to repeat the information here. However there's no information yet on this wiki about implementing the I2C? interface in hardware. That's what I'm providing.

to:

There are other pages on this wiki that describe where the I2C? bus can be accessed on the NSLU2. I'm not going to repeat the information here. However there's no information yet on this wiki about implementing the I2C? interface in hardware. That's what I'm providing.

Changed lines 41-42 from:
  1. make (This step will take a LONG time. If the build works, the image file will be found in openwrt-kamikaze_7.09/bin - its the big file - and can be flashed to the NSLU2 using upslug)
to:
  1. make (This step will take a LONG time. If the build works, the image file will be found in openwrt-kamikaze_7.09/bin - its the big file - and can be flashed to the NSLU2 using upslug)
June 15, 2008, at 09:00 AM by stefan keller-tuberg --
Changed lines 27-42 from:
  1. tar zxf sources/openwrt-kamikaze_7.09.tgz
  2. ln -s packages/utils/i2c-tools/ openwrt-kamikaze_7.09/package/i2c-tools
  3. mkdir openwrt-kamikaze_7.09/dl
  4. cp IPL_ixp400NpeLibrary-2_4.zip openwrt-kamikaze_7.09/dl (You downloaded this from the Intel site, right? Refer to the instructions on this wiki.
  5. cd openwrt-kamikaze_7.09
  6. make menuconfig (Enable utilities->i2c-tools i.e. type 'y' so that it shows with an asterisk)
  7. make kernel_menuconfig (Need to enable i2c in the kernel)
  8. make (This step will take a LONG time. If the build works, the image file will be found in openwrt-kamikaze_7.09/bin - its the big file - and can be flashed to the NSLU2 using upslug)
to:
  1. tar zxf sources/openwrt-kamikaze_7.09.tgz
  2. ln -s packages/utils/i2c-tools/ openwrt-kamikaze_7.09/package/i2c-tools
  3. mkdir openwrt-kamikaze_7.09/dl
  4. cp IPL_ixp400NpeLibrary-2_4.zip openwrt-kamikaze_7.09/dl (You downloaded this from the Intel site, right? Refer to the instructions on this wiki.
  5. cd openwrt-kamikaze_7.09
  6. make menuconfig (Enable utilities->i2c-tools i.e. type 'y' so that it shows with an asterisk)
  7. make kernel_menuconfig (Need to enable i2c in the kernel)
  8. make (This step will take a LONG time. If the build works, the image file will be found in openwrt-kamikaze_7.09/bin - its the big file - and can be flashed to the NSLU2 using upslug)
Changed lines 44-57 from:
  1. cd .. (change to the level above the OpenWRT? tree you just compiled)
  2. mkdir my-additions my-additions/files my-additions/src (these are directories where you're going to install your I2C? source code. I create it in the level above the OpenWRT? tree and link it in so that I can blow away the OpenWRT? tree and start again without blowing away my LCD/I2C? code)
  3. cp top-Makefile my-additions (provided below)
  4. cp src-Makefile echo_lcd.c i2c-dev.h my-additions/src (The first two are provided below. The i2c-dev.h file can be copied from openwrt-kamikaze_7.09/build_armeb/i2c-tools-3.0.0/include/linux after you've compiled openwrt the first time)
  5. ln -s my-additions openwrt-kamikaze_7.09/package/my-additions (this links your code into the OpenWRT? structure so that it gets built)
  6. cd openwrt-kamikaze_7.09 && make menuconfig (enable base system -> my-additions i.e. type 'y' so that it shows with an asterisk)
  7. make (this time, the i2c code will be compiled into the image)
to:
  1. cd .. (change to the level above the OpenWRT? tree you just compiled)
  2. mkdir my-additions my-additions/files my-additions/src (these are directories where you're going to install your I2C? source code. I create it in the level above the OpenWRT? tree and link it in so that I can blow away the OpenWRT? tree and start again without blowing away my LCD/I2C? code)
  3. cp top-Makefile my-additions (provided below)
  4. cp src-Makefile echo_lcd.c i2c-dev.h my-additions/src (The first two are provided below. The i2c-dev.h file can be copied from openwrt-kamikaze_7.09/build_armeb/i2c-tools-3.0.0/include/linux after you've compiled openwrt the first time)
  5. ln -s my-additions openwrt-kamikaze_7.09/package/my-additions (this links your code into the OpenWRT? structure so that it gets built)
  6. cd openwrt-kamikaze_7.09 && make menuconfig (enable base system -> my-additions i.e. type 'y' so that it shows with an asterisk)
  7. make (this time, the i2c code will be compiled into the image)
June 14, 2008, at 12:27 PM by stefan keller-tuberg --
Changed lines 161-164 from:
        $(MAKE) -C $(PKG_BUILD_DIR)                 LINUX="$(LINUX_DIR)"                 CC="$(TARGET_CC)"                 STAGING_DIR="$(STAGING_DIR)"
to:
        $(MAKE) -C $(PKG_BUILD_DIR) LINUX="$(LINUX_DIR)" CC="$(TARGET_CC)" STAGING_DIR="$(STAGING_DIR)"
Changed lines 203-205 from:

Edit the dimensions of your LCD display into the #define statements near the top of the code. You need to choose an i2c address in the #define near the top also. In this way, you can have several I2C? devices connected to your NSLU2 at the same time.

to:

As written, the I2C? is configured into 7 bit addressing mode (the normal mode). The I2C? message consists of a leading address byte (containing the 7 bit address and 1 bit write command), followed by one or more ascii characters to display on the LCD.

There are several special characters:

  • Zero (0x00) will clear the LCD and move the cursor to position 1 on line 1.
  • One (0x01) through two (0x02) or four (0x04) will move the cursor to position 1 of the numbered line. All the characters on that line will be blanked.
  • Newline (0x0a) will move the cursor to position 1 on the next line and blank that line. If the cursor was already on the bottom line, it will wrap back to the first line.
  • Carriage Return (0x0d) will take the cursor back to the beginning of the current line and blank the current line.
  • If you attempt to print more characters onto a line than the width of the LCD supports, the cursor will be moved onto the beginning of the next line, the character will be printed in the 1st position on that line and the rest of that line will be blanked.

In order for these functions to work properly, you need to customise the C code with the dimensions of the LCD you are going to use. Use the #define statements near the top of the code to specify the number of lines and the number of characters per line.

You also need to choose an i2c address for the PIC and specify this in the #define near the top of the file too. If necessary, you can have several different I2C? devices connected to your NSLU2 at the same time, each with different addresses.

When you power-on the 16F88, it will initialise the LCD and display the size of LCD compiled into it, and it will also display the I2C? address it is listening to. As I have a range of different LCD display sizes, this helped me realise when I'd connected the wrong PIC to the wrong LCD!

Finally, I experimented with PIC clock speed to see if I could reduce power consumption by reducing the clock. The I2C? routines seem to work perfectly down to a clock speed of 2 MHz?. If you try to clock the PIC slower than this, characters start to be dropped because the I2C? interrupt routine isn't entered quickly enough or because of I2C? input buffer overflows (because the last character isn't emptied quickly enough).

June 14, 2008, at 11:13 AM by stefan keller-tuberg --
Added line 26:
Added line 28:
Added line 30:
Added line 32:
Added line 34:
Added line 36:
Added line 38:
Added line 40:
Changed lines 43-45 from:
  1. mkdir my-additions (this is the directory where you're going to install your I2C? source code)
  2. mkdir my-additions/files my-additions/src
to:

Now its time to add your LCD code and compile it into the build

  1. cd .. (change to the level above the OpenWRT? tree you just compiled)
  2. mkdir my-additions my-additions/files my-additions/src (these are directories where you're going to install your I2C? source code. I create it in the level above the OpenWRT? tree and link it in so that I can blow away the OpenWRT? tree and start again without blowing away my LCD/I2C? code)
Changed lines 49-56 from:
  1. cp src-Makefile my-additions/src (provided below)
  2. cp echo-lcd.c my-additions/src (provided below)
  3. cp i2c-dev.h my-additions/src (you'll copy this out of the openwrt-kamikaze_7.09/build_armeb/i2c-tools-3.0.0/include/linux directory after you've compiled openwrt the first time)
  4. ln -s my-additions openwrt-kamikaze_7.09/package/my-additions

Software to access the I2C? on the NSLU2

to:
  1. cp src-Makefile echo_lcd.c i2c-dev.h my-additions/src (The first two are provided below. The i2c-dev.h file can be copied from openwrt-kamikaze_7.09/build_armeb/i2c-tools-3.0.0/include/linux after you've compiled openwrt the first time)
  2. ln -s my-additions openwrt-kamikaze_7.09/package/my-additions (this links your code into the OpenWRT? structure so that it gets built)
  3. cd openwrt-kamikaze_7.09 && make menuconfig (enable base system -> my-additions i.e. type 'y' so that it shows with an asterisk)
  4. make (this time, the i2c code will be compiled into the image)

Software to access the I2C? on the NSLU2 (echo_lcd.c)

Changed lines 132-133 from:
to:

top-Makefile

This makefile goes in the top directory of your i2c application directory. Note that Makefiles require tabs and not space characters on the indented lines!!!

 include $(TOPDIR)/rules.mk

 PKG_NAME:=my-additions
 PKG_RELEASE:=1

 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)

 include $(INCLUDE_DIR)/package.mk

 define Package/my-additions
  SECTION:=utils
  CATEGORY:=Base system
  TITLE:=My i2c additions to the NSLU2
 endef

 define Package/my-additions/description
        This package contains all my i2c additions to the system
 endef

 define Build/Prepare
        mkdir -p $(PKG_BUILD_DIR)
        $(CP) ./src/* $(PKG_BUILD_DIR)/
 endef

 define Build/Compile
        $(MAKE) -C $(PKG_BUILD_DIR)                 LINUX="$(LINUX_DIR)"                 CC="$(TARGET_CC)"                 STAGING_DIR="$(STAGING_DIR)"
 endef

 define Package/my-additions/install
        $(INSTALL_DIR) $(1)/etc
        $(INSTALL_DATA) ./files/* $(1)/etc
        $(INSTALL_DIR) $(1)/bin
        $(INSTALL_BIN) $(PKG_BUILD_DIR)/echo_lcd $(1)/bin/
 endef

 $(eval $(call BuildPackage?,my-additions))

src-Makefile

This makefile goes in the src directory of your i2c application directory. Note that Makefiles require tabs and not space characters on the indented lines!!!

 BINS = echo_lcd
 DESTDIR =
 CFLAGS += -O2

 prefix := /usr/local
 incdir := $(prefix)/include

 INSTALL := install
 INSTALL_BIN := $(INSTALL) -c -m 755
 INSTALL_DATA := $(INSTALL) -c -m 644
 INSTALL_DIR := $(INSTALL) -m 755 -d

 RM := rm -rf

 all: $(BINS)

 clean: $(RM) $(wildcard *.o *.so *.a $(BINS)) .depend

 install: $(INSTALL_BIN) $(BINS) $(DESTDIR)/sbin/

 echo_lcd: echo_lcd.o
June 14, 2008, at 11:00 AM by stefan keller-tuberg --
Changed lines 26-27 from:
  1. I then executed the following...
 TREE="openwrt-kamikaze_7.09"
to:
  1. tar zxf sources/openwrt-kamikaze_7.09.tgz
  2. ln -s packages/utils/i2c-tools/ openwrt-kamikaze_7.09/package/i2c-tools
  3. mkdir openwrt-kamikaze_7.09/dl
  4. cp IPL_ixp400NpeLibrary-2_4.zip openwrt-kamikaze_7.09/dl (You downloaded this from the Intel site, right? Refer to the instructions on this wiki.
  5. cd openwrt-kamikaze_7.09
  6. make menuconfig (Enable utilities->i2c-tools i.e. type 'y' so that it shows with an asterisk)
  7. make kernel_menuconfig (Need to enable i2c in the kernel)
  8. make (This step will take a LONG time. If the build works, the image file will be found in openwrt-kamikaze_7.09/bin - its the big file - and can be flashed to the NSLU2 using upslug)
  9. mkdir my-additions (this is the directory where you're going to install your I2C? source code)
  10. mkdir my-additions/files my-additions/src
  11. cp top-Makefile my-additions (provided below)
  12. cp src-Makefile my-additions/src (provided below)
  13. cp echo-lcd.c my-additions/src (provided below)
  14. cp i2c-dev.h my-additions/src (you'll copy this out of the openwrt-kamikaze_7.09/build_armeb/i2c-tools-3.0.0/include/linux directory after you've compiled openwrt the first time)
  15. ln -s my-additions openwrt-kamikaze_7.09/package/my-additions

Software to access the I2C? on the NSLU2

I found several example C programmes both on this wiki as well as generally on the web, but none of them worked without hacking on my OpenWRT? based NSLU2. In any case, all the examples seemed to be for interfacing I2C? EEPROMs?, and I wanted to be able to echo strings to the I2C? bus rather like you'd echo strings to an RS-232 port.

Here's an example of a programme to echo strings to the I2C? bus, similar to echo in shell script.

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 //include <linux/i2c-dev.h>
 #include "i2c-dev.h"
Changed lines 61-64 from:
 if [ -d ${TREE} ] ; then
   echo "Wait while removing the old tree"
   rm -rf ${TREE}
 fi
to:
 #define I2C?_ADDRESS 0x32
Deleted lines 62-88:
 echo "Now wait while untaring the new tree"
 tar zxf sources/${TREE}.tgz
 ln -s packages/utils/i2c-tools/ ${TREE}/package/i2c-tools
  1. Here are my packages

ln -s ../../my-additions ${TREE}/package/my-additions

Software to access the I2C? on the NSLU2

I found several example C programmes both on this wiki as well as generally on the web, but none of them worked without hacking on my OpenWRT? based NSLU2. In any case, all the examples seemed to be for interfacing I2C? EEPROMs?, and I wanted to be able to echo strings to the I2C? bus rather like you'd echo strings to an RS-232 port.

Here's an example of a programme to echo strings to the I2C? bus, similar to echo in shell script.

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 //include <linux/i2c-dev.h>
 #include "i2c-dev.h"

 #define I2C?_ADDRESS 0x32
June 14, 2008, at 10:34 AM by stefan keller-tuberg --
Added lines 17-18:

<<INSERT PHOTO OF CIRCUIT DIAGRAM HERE - HOW DOES ONE UPLOAD A PHOTO??>>

Added lines 21-42:

Compiling an image with your I2C? code embedded

As mentioned above, I chose the OpenWRT? distribution for my NSLU2. I had some trouble compiling a working kernel so I will make some notes here to get you going. I have still never been able to compile a working kernel with the svn source.

  1. I started with the OpenWRT tarball and customised it.
  2. I then executed the following...
 TREE="openwrt-kamikaze_7.09"

 if [ -d ${TREE} ] ; then
   echo "Wait while removing the old tree"
   rm -rf ${TREE}
 fi

 echo "Now wait while untaring the new tree"
 tar zxf sources/${TREE}.tgz
 ln -s packages/utils/i2c-tools/ ${TREE}/package/i2c-tools
  1. Here are my packages

ln -s ../../my-additions ${TREE}/package/my-additions

June 14, 2008, at 10:24 AM by stefan keller-tuberg -- how do I upload a png file of a circuit diagram?
Changed line 2 from:
to:

How do you upload a photo to this wiki? I have a circuit diagram to upload

June 14, 2008, at 03:59 AM by stefan keller-tuberg --
Changed lines 1-2 from:

This is Work In Progress. I'll complete over the next few days

to:

This is Work In Progress. I'll complete during June 2008

June 14, 2008, at 03:59 AM by stefan keller-tuberg --
Added lines 1-2:

This is Work In Progress. I'll complete over the next few days

June 14, 2008, at 03:57 AM by stefan keller-tuberg --
Changed lines 9-10 from:

Interfacing the I2c to the NSLU2

to:

Interfacing the I2C? to the NSLU2

Changed lines 17-29 from:

Firmware for a 16F88 PIC

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at the mikroElektronika web site.

Edit the dimensions of your LCD display into the #define statements near the top of the code. You need to choose an i2c address in the #define near the top also. In this way, you can have several I2C? devices connected to your NSLU2 at the same time.

 // 16F88 programme to drive a hitachi compatible LCD display from the
 // i2C bus
 //
 // There are plenty of spare inputs and outputs for additional functions
 //////////////////////////////////////////////////////////////////////
to:

Software to access the I2C? on the NSLU2

I found several example C programmes both on this wiki as well as generally on the web, but none of them worked without hacking on my OpenWRT? based NSLU2. In any case, all the examples seemed to be for interfacing I2C? EEPROMs?, and I wanted to be able to echo strings to the I2C? bus rather like you'd echo strings to an RS-232 port.

Here's an example of a programme to echo strings to the I2C? bus, similar to echo in shell script.

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 //include <linux/i2c-dev.h>
 #include "i2c-dev.h"
Changed lines 35-37 from:
 // Define the number of lines and columns on the LCD display
 #define LINES 4
 #define COLUMNS 20
to:
 int
 main (int argc, char **argv)
 {
   int fd;
   int length ;
   int n, i;
Changed lines 42-59 from:
 // This is the assignment of 16F88 pins I chose
 // PORT A bit 0 = D4 on LCD
 // PORT A bit 1 = D5 on LCD
 // PORT A bit 2 = D6 on LCD
 // PORT A bit 3 = D7 on LCD
 // PORT A bit 4 = NC
 // PORT A bit 5 = pulled high with 10k
 // PORT A bit 6 = NC
 // PORT A bit 7 = NC
 //
 // PORT B bit 0 = E on LCD
 // PORT B bit 1 = SD on I2C?
 // PORT B bit 2 = RW on LCD
 // PORT B bit 3 = RS on LCD
 // PORT B bit 4 = CK on I2C?
 // PORT B bit 5 = NC
 // PORT B bit 6 = NC
 // PORT B bit 7 = NC
to:
   if (argc < 2)
   {
     fprintf (stderr, "Usage: %s STRING [STRING] ...\n", argv[0]);
     exit (1) ;
   }
Changed lines 48-51 from:
 // Fx is mikroC nomenclature for bit x
 #define RS_BIT F3
 #define WR_BIT F2
 #define E_BIT  F0
to:
   if ((fd = open("/dev/i2c-0", O_RDWR)) < 0)
   {
     printf("Error opening i2c port!\n");
     exit (1);
   }
Changed lines 54-64 from:
 // counters for debugging purposes
 unsigned char crashed, crash_cause, info, last_i2c ;
 //////////////////////////////////////////////////////////////////////
 void
 write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
 {
   TRISA.F0 = 0;      //Set Bits 3-0 as OUTPUTS
   TRISA.F1 = 0;
   TRISA.F2 = 0;
   TRISA.F3 = 0;
   PORTA = c ;        //Bits 3-0 = high order data nibble
to:
   if (ioctl(fd,I2C?_SLAVE,I2C?_ADDRESS) < 0)
   {
     printf("Error setting i2c address!\n");
     exit (1);
   }
Changed lines 60-64 from:
   PORTB.E_BIT = 0 ;
to:
   if (ioctl(fd,I2C?_RETRIES,2) < 0)
   {
     printf("Error setting i2c retries!\n");
     exit (1);
   }
Changed lines 66-69 from:
   if (RS)
     PORTB.RS_BIT = 1 ;
   else
     PORTB.RS_BIT = 0 ;
to:
   for (i=1 ; i < argc ;)
   {
     length = strlen (argv[i]) ;
Changed lines 70-74 from:
   PORTB.WR_BIT = 0 ;
   Delay_us (1);
   PORTB.E_BIT = 1 ;  //E High, WR low, RS set to selected value
   Delay_us (1);
   PORTB.E_BIT = 0 ;
to:
     if (!length)
       continue ;
Changed lines 73-74 from:
   // Exit with E Low, WR Low, RS set to selected value
 }
to:
     n = write(fd, argv[i], length);
Changed lines 75-78 from:
 void
 write_byte_to_lcd (const unsigned char c, const unsigned char RS)
 {
   unsigned char busy ;
to:
     printf ("Writing s\n", length, argv[i]) ;
Changed lines 77-80 from:
   TRISA.F0 = 1;       //Set Bits 3-0 as INPUTS
   TRISA.F1 = 1;
   TRISA.F2 = 1;
   TRISA.F3 = 1;
to:
     if (n != length)
     {
       printf("Write error! Returned d - %s\n", n, length, strerror(errno));
       exit (1);
     }
Changed lines 83-86 from:
   PORTB.E_BIT = 0 ;
   PORTB.RS_BIT = 0 ;
   PORTB.WR_BIT = 1 ;
   Delay_us(1);
to:
     if (++i < argc)
       write(fd, " ", 1);
   }
Changed lines 87-88 from:
   // Wait for LCD to become unbusy
   // BUSY FLAG polled on falling edge of E
to:
   close (fd);
   return 0 ;
 }

Firmware for a 16F88 PIC

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at the mikroElektronika web site.

Edit the dimensions of your LCD display into the #define statements near the top of the code. You need to choose an i2c address in the #define near the top also. In this way, you can have several I2C? devices connected to your NSLU2 at the same time.

 // 16F88 programme to drive a hitachi compatible LCD display from the
 // i2C bus
 //
 // There are plenty of spare inputs and outputs for additional functions
 //////////////////////////////////////////////////////////////////////
 #define I2C?_ADDRESS 0x32
Changed lines 107-118 from:
   do  // Need to set E high and low twice - two four bit reads
   {
     PORTB.E_BIT = 1;    //WR High=Read, E High
     Delay_us(1);
     busy = PORTA ;      //Read the busy flag in bit 3 (bit 7 of LCD)
     PORTB.E_BIT = 0 ;   //WR High, E Clocks low
     Delay_us(1);
     PORTB.E_BIT = 1;    //WR High=Read, E High
     Delay_us(1);
     PORTB.E_BIT = 0 ;   //WR High, E Clocks low
     Delay_us(1);
   } while (busy.F3);
to:
 // Define the number of lines and columns on the LCD display
 #define LINES 4
 #define COLUMNS 20
Changed lines 111-113 from:
   // Now the LCD is ready, write byte in two 4 bit nibbles
   write_nibble_to_lcd (c >> 4, RS) ;
   write_nibble_to_lcd (c & 0x0f, RS) ;
to:
 // This is the assignment of 16F88 pins I chose
 // PORT A bit 0 = D4 on LCD
 // PORT A bit 1 = D5 on LCD
 // PORT A bit 2 = D6 on LCD
 // PORT A bit 3 = D7 on LCD
 // PORT A bit 4 = NC
 // PORT A bit 5 = pulled high with 10k
 // PORT A bit 6 = NC
 // PORT A bit 7 = NC
 //
 // PORT B bit 0 = E on LCD
 // PORT B bit 1 = SD on I2C?
 // PORT B bit 2 = RW on LCD
 // PORT B bit 3 = RS on LCD
 // PORT B bit 4 = CK on I2C?
 // PORT B bit 5 = NC
 // PORT B bit 6 = NC
 // PORT B bit 7 = NC
Changed lines 130-131 from:
   // Exit with E Low, WR Low, RS set to selected value
 }
to:
 // Fx is mikroC nomenclature for bit x
 #define RS_BIT F3
 #define WR_BIT F2
 #define E_BIT  F0
Added lines 135-137:
 // counters for debugging purposes
 unsigned char crashed, crash_cause, info, last_i2c ;
 //////////////////////////////////////////////////////////////////////
Changed line 139 from:
 string_to_lcd (const unsigned char *s)
to:
 write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
Changed lines 141-143 from:
   while (*s)
     write_byte_to_lcd (*s++, 1) ;
 }
to:
   TRISA.F0 = 0;      //Set Bits 3-0 as OUTPUTS
   TRISA.F1 = 0;
   TRISA.F2 = 0;
   TRISA.F3 = 0;
   PORTA = c ;        //Bits 3-0 = high order data nibble
Changed lines 147-151 from:
 void
 decbyte_to_lcd (unsigned char c)
 {
   char *s ;
   unsigned char text[4] ;
to:
   PORTB.E_BIT = 0 ;
Changed lines 149-152 from:
   ByteToStr? (c, text);
to:
   if (RS)
     PORTB.RS_BIT = 1 ;
   else
     PORTB.RS_BIT = 0 ;
Changed lines 154-156 from:
   for (s=text; *s ;)
     write_byte_to_lcd (*s++, 1) ;
 }
to:
   PORTB.WR_BIT = 0 ;
   Delay_us (1);
   PORTB.E_BIT = 1 ;  //E High, WR low, RS set to selected value
   Delay_us (1);
   PORTB.E_BIT = 0 ;
Added lines 160-162:
   // Exit with E Low, WR Low, RS set to selected value
 }
Changed line 164 from:
 hexnibble_to_lcd (unsigned char c)
to:
 write_byte_to_lcd (const unsigned char c, const unsigned char RS)
Changed line 166 from:
   c &= 0x0f ;
to:
   unsigned char busy ;
Changed lines 168-171 from:
   if (c > 9)
       c += 'A' - 10 ;
   else
     c += '0' ;
to:
   TRISA.F0 = 1;       //Set Bits 3-0 as INPUTS
   TRISA.F1 = 1;
   TRISA.F2 = 1;
   TRISA.F3 = 1;
Changed lines 173-174 from:
   write_byte_to_lcd (c, 1) ;
 }
to:
   PORTB.E_BIT = 0 ;
   PORTB.RS_BIT = 0 ;
   PORTB.WR_BIT = 1 ;
   Delay_us(1);
Changed lines 178-186 from:
 void hexbyte_to_lcd (unsigned char c)
 {
    hexnibble_to_lcd (c >> 4) ;
    hexnibble_to_lcd (c) ;
 }
 //==============================================================================
 // BUFFER_POWER must be >= 2
 #define BUFFER_LEN 64
 #define BUFFER_MASK (BUFFER_LEN-1)
to:
   // Wait for LCD to become unbusy
   // BUSY FLAG polled on falling edge of E
Changed lines 181-189 from:
 unsigned char rxbuf [BUFFER_LEN] ;
 unsigned char rx_head, rx_tail, rx_len ;

 void
 interrupt ()
 {
   unsigned char stat;

   if (PIR1?.F3 == 1) // is this an i2c interrupt
to:
   do  // Need to set E high and low twice - two four bit reads
Changed lines 183-186 from:
     SSPCON.F4 = 0 ;         // Hold clock low - (ie stretch clock while we work)
     stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
                             // 5=(1=data,0=addr), 3=(1=start detected),
                             // 2=(1=read,0=write), 0=(1=sspbuf full)
to:
     PORTB.E_BIT = 1;    //WR High=Read, E High
     Delay_us(1);
     busy = PORTA ;      //Read the busy flag in bit 3 (bit 7 of LCD)
     PORTB.E_BIT = 0 ;   //WR High, E Clocks low
     Delay_us(1);
     PORTB.E_BIT = 1;    //WR High=Read, E High
     Delay_us(1);
     PORTB.E_BIT = 0 ;   //WR High, E Clocks low
     Delay_us(1);
   } while (busy.F3);
Changed lines 194-201 from:
     if (stat == 0x09) // I2C? Write: Byte received was an address
     {
       last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
     }
     else if (stat == 0x29) // I2C? Write: Byte received was data
     {
       rxbuf[rx_head++] = SSPBUF ;
       rx_head &= BUFFER_MASK ;
to:
   // Now the LCD is ready, write byte in two 4 bit nibbles
   write_nibble_to_lcd (c >> 4, RS) ;
   write_nibble_to_lcd (c & 0x0f, RS) ;
Changed lines 198-203 from:
       if (rx_len++ >= BUFFER_LEN)
         crashed = 1 ;  //flag error
     }
     else if (stat == 0x0c) // I2C? Read: Byte received was an address
     {
       while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
to:
   // Exit with E Low, WR Low, RS set to selected value
 }
Changed lines 201-209 from:
       do
       {
               SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0xaa ; // Just return AA for the time being
       } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
     }
     else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
     {
       while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
to:
 void
 string_to_lcd (const unsigned char *s)
 {
   while (*s)
     write_byte_to_lcd (*s++, 1) ;
 }
Changed lines 208-224 from:
       do
       {
         SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0x55 ; // Just return 55 for the time being
       } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
     }
     else  // NACK received 0x28 - or another condition - Do a reset
     {
       last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
       SSPCON.F5=0;    // Temporarily disable
       SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                       // F6=0 SSPOV Receive Overflow Indicator bit
                       // F5=1 Synchronous Serial Port Enable
                       // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                       // F3-0=0110: I2C? slave mode: 7 bits address
       SSPSTAT = 0 ;   // Clear all status bits
     }
to:
 void
 decbyte_to_lcd (unsigned char c)
 {
   char *s ;
   unsigned char text[4] ;
Changed lines 214-221 from:
     SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
     PIR1?.F3 = 0 ;   // Clear the interrupt
   }
   else // unknown interrupt!!
   {
     crashed = 3;
     info = PIR1? ;
   }
to:
   ByteToStr? (c, text);

   for (s=text; *s ;)
     write_byte_to_lcd (*s++, 1) ;
Deleted lines 218-223:
 //==============================================================================
 #define CLEAR_AND_FIRST_ROW 0x01
 #define FIRST_ROW           0x02
 #define SECOND_ROW          (0x80 | 0x40)
 #define THIRD_ROW           (0x80 | 0x14)
 #define FOURTH_ROW          (0x80 | 0x54)
Deleted lines 219-220:
 unsigned char line, column ;
Changed line 221 from:
 linefeed ()
to:
 hexnibble_to_lcd (unsigned char c)
Changed line 223 from:
   unsigned char cmd, i ;
to:
   c &= 0x0f ;
Changed lines 225-232 from:
   // Blank the appropriate line.
   switch (line)
   {
     case 1:  cmd = FIRST_ROW ; break ;
     case 2:  cmd = SECOND_ROW ; break ;
     case 3:  cmd = THIRD_ROW ; break ;
     default: cmd = FOURTH_ROW ; break ;
   }
to:
   if (c > 9)
       c += 'A' - 10 ;
   else
     c += '0' ;
Changed lines 230-231 from:
   write_byte_to_lcd (cmd, 0); // Go to start of line
to:
   write_byte_to_lcd (c, 1) ;
 }
Changed lines 233-237 from:
   for (i=0 ; i<COLUMNS ; i++)      // Blank the entire line
     write_byte_to_lcd (' ', 1);

   write_byte_to_lcd (cmd, 0); // Return to start of line
   column = 0 ;
to:
 void hexbyte_to_lcd (unsigned char c)
 {
    hexnibble_to_lcd (c >> 4) ;
    hexnibble_to_lcd (c) ;
Added lines 238-241:
 //==============================================================================
 // BUFFER_POWER must be >= 2
 #define BUFFER_LEN 64
 #define BUFFER_MASK (BUFFER_LEN-1)
Added lines 243-245:
 unsigned char rxbuf [BUFFER_LEN] ;
 unsigned char rx_head, rx_tail, rx_len ;
Changed line 247 from:
 printchar (const unsigned char c)
to:
 interrupt ()
Changed lines 249-251 from:
   if (c == 0)
to:
   unsigned char stat;

   if (PIR1?.F3 == 1) // is this an i2c interrupt
Changed lines 253-261 from:
     // Clear display entirely
     write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
     line = 1 ;
     column = 0 ;
   }
   else if (c == '\n')
   {
     // linefeed and wrap around to line 0
     line++ ;
to:
     SSPCON.F4 = 0 ;         // Hold clock low - (ie stretch clock while we work)
     stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
                             // 5=(1=data,0=addr), 3=(1=start detected),
                             // 2=(1=read,0=write), 0=(1=sspbuf full)
Changed lines 258-259 from:
     if (line > LINES)
       line = 1 ;
to:
     if (stat == 0x09) // I2C? Write: Byte received was an address
     {
       last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
     }
     else if (stat == 0x29) // I2C? Write: Byte received was data
     {
       rxbuf[rx_head++] = SSPBUF ;
       rx_head &= BUFFER_MASK ;
Changed lines 267-281 from:
     linefeed() ;
   }
   else if (c < 5)
   {
     // move to line 1..4
     line = c ;
     linefeed() ;
   }
   else if (c == '\r')
   {
     linefeed() ;
   }
   else
   {
     if (column >= COLUMNS)
to:
       if (rx_len++ >= BUFFER_LEN)
         crashed = 1 ;  //flag error
     }
     else if (stat == 0x0c) // I2C? Read: Byte received was an address
Changed lines 272-273 from:
       // need to force a linefeed
       line++ ;
to:
       while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
Changed lines 274-277 from:
       if (line > LINES)
         line = 1 ;

       linefeed() ;
to:
       do
       {
               SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0xaa ; // Just return AA for the time being
       } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
Added lines 280-282:
     else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
     {
       while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
Changed lines 284-288 from:
     // Display the numbered character
     write_byte_to_lcd (c, 1);
     column++ ;
   }
 }
to:
       do
       {
         SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0x55 ; // Just return 55 for the time being
       } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
     }
     else  // NACK received 0x28 - or another condition - Do a reset
     {
       last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
       SSPCON.F5=0;    // Temporarily disable
       SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                       // F6=0 SSPOV Receive Overflow Indicator bit
                       // F5=1 Synchronous Serial Port Enable
                       // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                       // F3-0=0110: I2C? slave mode: 7 bits address
       SSPSTAT = 0 ;   // Clear all status bits
     }
Changed lines 302-306 from:
 // Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
 void
 enqueue_string (const unsigned char *s)
 {
   while (*s)
to:
     SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
     PIR1?.F3 = 0 ;   // Clear the interrupt
   }
   else // unknown interrupt!!
Changed lines 307-317 from:
     PIE1?.F3 = 0;  // Disable int F3=I2C? int
     rxbuf[rx_head++] = *s++ ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;
     PIE1?.F3 = 1;  // Enable int F3=I2C? int

     if (rx_len > BUFFER_LEN)
     {
       crashed = 4 ;  //flag error
       return ;
     }
to:
     crashed = 3;
     info = PIR1? ;
Added lines 311-316:
 //==============================================================================
 #define CLEAR_AND_FIRST_ROW 0x01
 #define FIRST_ROW           0x02
 #define SECOND_ROW          (0x80 | 0x40)
 #define THIRD_ROW           (0x80 | 0x14)
 #define FOURTH_ROW          (0x80 | 0x54)
Added lines 318-319:
 unsigned char line, column ;
Changed line 321 from:
 enqueue_number (const unsigned char c)
to:
 linefeed ()
Changed lines 323-324 from:
   unsigned char text[4] ;
   unsigned char *s ;
to:
   unsigned char cmd, i ;
Changed lines 325-327 from:
   ByteToStr? (c, text);

   for (s=text ; *s ; s++)
to:
   // Blank the appropriate line.
   switch (line)
Changed lines 328-329 from:
     if (*s ==  ' ')
       continue ;
to:
     case 1:  cmd = FIRST_ROW ; break ;
     case 2:  cmd = SECOND_ROW ; break ;
     case 3:  cmd = THIRD_ROW ; break ;
     default: cmd = FOURTH_ROW ; break ;
   }
Changed lines 334-336 from:
     rxbuf[rx_head++] = *s ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;
to:
   write_byte_to_lcd (cmd, 0); // Go to start of line
Changed lines 336-341 from:
     if (rx_len > BUFFER_LEN)
       crashed = 5 ;  //flag error
   }
 }
 //==============================================================================
 #define MAGIC       0x3d
to:
   for (i=0 ; i<COLUMNS ; i++)      // Blank the entire line
     write_byte_to_lcd (' ', 1);
Changed lines 339-340 from:
 unsigned char num_boots ;
 unsigned char magic ; // Will hold a magic number so we know a cold boot
to:
   write_byte_to_lcd (cmd, 0); // Return to start of line
   column = 0 ;
 }
Changed line 344 from:
 main()
to:
 printchar (const unsigned char c)
Changed lines 346-356 from:
   char i ;
to:
   if (c == 0)
   {
     // Clear display entirely
     write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
     line = 1 ;
     column = 0 ;
   }
   else if (c == '\n')
   {
     // linefeed and wrap around to line 0
     line++ ;
Changed lines 358-360 from:
   // Osc = bits 6,5,4
   OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
   while (OSCCON.F2 == 0) ; // Wait for osc to stabilise
to:
     if (line > LINES)
       line = 1 ;
Changed lines 361-364 from:
 restart:
   OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
   INTCON=0;        //Disable interrupts
   PIR1?=0;          //Clear all interrupt flags
to:
     linefeed() ;
   }
   else if (c < 5)
   {
     // move to line 1..4
     line = c ;
     linefeed() ;
   }
   else if (c == '\r')
   {
     linefeed() ;
   }
   else
   {
     if (column >= COLUMNS)
     {
       // need to force a linefeed
       line++ ;
Changed lines 380-383 from:
   TRISA=0;         //Set Port A to all outputs
   TRISB=0x12;      //B4 and B1 are inputs (I2C?)
   PORTA=0x00;
   PORTB=0x00;
to:
       if (line > LINES)
         line = 1 ;
Changed lines 383-384 from:
   WDTCON =0x17 ;   // Enable watchdog with max timeout value = approx 2s
to:
       linefeed() ;
     }
Changed lines 386-387 from:
   ANSEL=0; //0 = PortA? bit is used for digital I/O
   ADCON1?=0x07 ; // Disable A/D
to:
     // Display the numbered character
     write_byte_to_lcd (c, 1);
     column++ ;
   }
 }
Changed lines 392-401 from:
   SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                   // F6=0 SSPOV Receive Overflow Indicator bit
                   // F5=1 Synchronous Serial Port Enable
                   // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                   // F3-0=0110: I2C? slave mode: 7 bits address
   SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
   SSPSTAT = 0 ;   // Clear all status bits
   PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int

   if (magic != MAGIC)
to:
 // Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
 void
 enqueue_string (const unsigned char *s)
 {
   while (*s)
Changed lines 398-403 from:
     crash_cause=0 ;  // used for debugging interrupt routine with print statements
     num_boots = 0 ;
     magic = MAGIC ;
   }
   else
     num_boots++ ;
to:
     PIE1?.F3 = 0;  // Disable int F3=I2C? int
     rxbuf[rx_head++] = *s++ ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;
     PIE1?.F3 = 1;  // Enable int F3=I2C? int
Changed lines 404-405 from:
   crashed = 0 ;   // We're not crashed at the moment!
   rx_head = rx_tail = rx_len = 0 ;
to:
     if (rx_len > BUFFER_LEN)
     {
       crashed = 4 ;  //flag error
       return ;
     }
   }
 }
Changed lines 412-413 from:
   INTCON.F6 = 1 ; // enable peripheral interrupts
   INTCON.F7 = 1 ; // enable all unmasked interrupts
to:
 void
 enqueue_number (const unsigned char c)
 {
   unsigned char text[4] ;
   unsigned char *s ;
Changed lines 418-420 from:
   TXSTA = 0 ;     // Turn off transmit from UART
   RCSTA = 0 ;
   CMCON = 0x07 ;  // Disable comparators
to:
   ByteToStr? (c, text);
Changed lines 420-423 from:
   Delay_ms (100); // Provide a little time for the LCD to initialise
   asm {
     CLRWDT        // Clear the watchdog timer
   }
to:
   for (s=text ; *s ; s++)
   {
     if (*s ==  ' ')
       continue ;
Changed lines 425-432 from:
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (5);               // Wait at least 4.1ms
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
   Delay_ms (1);               // Wait at least 100us
to:
     rxbuf[rx_head++] = *s ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;
Changed lines 429-435 from:
   write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
   write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
   write_byte_to_lcd (0x01, 0); //Clear display
   write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
   write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
   line = 1;
   column = 0 ;
to:
     if (rx_len > BUFFER_LEN)
       crashed = 5 ;  //flag error
   }
 }
 //==============================================================================
 #define MAGIC       0x3d
Changed lines 436-442 from:
   // Display the welcome message
   enqueue_number (LINES) ;
   enqueue_string ("x") ;
   enqueue_number (COLUMNS) ;
   enqueue_string (" lcd @addr ") ;
   enqueue_number (I2C?_ADDRESS) ;
   enqueue_string ("\nVersion 1");
to:
 unsigned char num_boots ;
 unsigned char magic ; // Will hold a magic number so we know a cold boot
Changed lines 439-444 from:
   // Give some indication if there's been a crash
   if (num_boots)
   {
     enqueue_string ("\rBoot count: ");
     enqueue_number (num_boots) ;
   }
to:
 void
 main()
 {
   char i ;
Changed lines 444-448 from:
   for (;;)
   {
     asm {
       CLRWDT  // clear the watchdog timer
     }
to:
   // Osc = bits 6,5,4
   OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
   while (OSCCON.F2 == 0) ; // Wait for osc to stabilise
Changed lines 448-455 from:
     if (rx_len)
     {
       printchar (rxbuf[rx_tail++]) ;
       rx_tail &= BUFFER_MASK ;
       PIE1?.F3 = 0;  // Disable int F3=I2C? int
       rx_len-- ;
       PIE1?.F3 = 1;  // Enable int F3=I2C? int
     }
to:
 restart:
   OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
   INTCON=0;        //Disable interrupts
   PIR1?=0;          //Clear all interrupt flags
Changed lines 453-464 from:
     if (crashed)
     {
       crash_cause = crashed ; // Remember why we crashed
       write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
       string_to_lcd ("Cause:") ;
       hexbyte_to_lcd (crash_cause);
       string_to_lcd (" last:") ;
       hexbyte_to_lcd (last_i2c) ;
       string_to_lcd (" info:") ;
       hexbyte_to_lcd (info);
       string_to_lcd (" boots:") ;
       hexbyte_to_lcd (num_boots);
to:
   TRISA=0;         //Set Port A to all outputs
   TRISB=0x12;      //B4 and B1 are inputs (I2C?)
   PORTA=0x00;
   PORTB=0x00;
Changed lines 458-461 from:
       write_byte_to_lcd (SECOND_ROW, 0);
       string_to_lcd ("Buf len:") ;
       decbyte_to_lcd (rx_len);
       write_byte_to_lcd (' ', 1);
to:
   WDTCON =0x17 ;   // Enable watchdog with max timeout value = approx 2s
Changed lines 460-461 from:
       for (i=0 ; i < 14 ; i++)
         hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);
to:
   ANSEL=0; //0 = PortA? bit is used for digital I/O
   ADCON1?=0x07 ; // Disable A/D
Changed lines 463-467 from:
       // The following kludge is to get around the non-reentrant version of C
       // If there's an error inside the Interrupt, this flag will be set
       // That lets us display something - and this loop will cause an infinte wait
 //      while (crashed) ; // Infinite loop if an error has been set in the ISR
                         // This will trip the watchdog timer some time later
to:
   SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                   // F6=0 SSPOV Receive Overflow Indicator bit
                   // F5=1 Synchronous Serial Port Enable
                   // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                   // F3-0=0110: I2C? slave mode: 7 bits address
   SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
   SSPSTAT = 0 ;   // Clear all status bits
   PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int
Changed lines 472-482 from:
       // Delay for 15 seconds
       for (i=0 ; i < 150 ; i++)
       {
         Delay_ms (100);
         asm {
           CLRWDT         // clear the watchdog timer
         }
       }

       goto restart ;
     }
to:
   if (magic != MAGIC)
   {
     crash_cause=0 ;  // used for debugging interrupt routine with print statements
     num_boots = 0 ;
     magic = MAGIC ;
Added lines 478-581:
   else
     num_boots++ ;

   crashed = 0 ;   // We're not crashed at the moment!
   rx_head = rx_tail = rx_len = 0 ;

   INTCON.F6 = 1 ; // enable peripheral interrupts
   INTCON.F7 = 1 ; // enable all unmasked interrupts

   TXSTA = 0 ;     // Turn off transmit from UART
   RCSTA = 0 ;
   CMCON = 0x07 ;  // Disable comparators

   Delay_ms (100); // Provide a little time for the LCD to initialise
   asm {
     CLRWDT        // Clear the watchdog timer
   }

   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (5);               // Wait at least 4.1ms
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
   Delay_ms (1);               // Wait at least 100us

   write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
   write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
   write_byte_to_lcd (0x01, 0); //Clear display
   write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
   write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
   line = 1;
   column = 0 ;

   // Display the welcome message
   enqueue_number (LINES) ;
   enqueue_string ("x") ;
   enqueue_number (COLUMNS) ;
   enqueue_string (" lcd @addr ") ;
   enqueue_number (I2C?_ADDRESS) ;
   enqueue_string ("\nVersion 1");

   // Give some indication if there's been a crash
   if (num_boots)
   {
     enqueue_string ("\rBoot count: ");
     enqueue_number (num_boots) ;
   }

   for (;;)
   {
     asm {
       CLRWDT  // clear the watchdog timer
     }

     if (rx_len)
     {
       printchar (rxbuf[rx_tail++]) ;
       rx_tail &= BUFFER_MASK ;
       PIE1?.F3 = 0;  // Disable int F3=I2C? int
       rx_len-- ;
       PIE1?.F3 = 1;  // Enable int F3=I2C? int
     }

     if (crashed)
     {
       crash_cause = crashed ; // Remember why we crashed
       write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
       string_to_lcd ("Cause:") ;
       hexbyte_to_lcd (crash_cause);
       string_to_lcd (" last:") ;
       hexbyte_to_lcd (last_i2c) ;
       string_to_lcd (" info:") ;
       hexbyte_to_lcd (info);
       string_to_lcd (" boots:") ;
       hexbyte_to_lcd (num_boots);

       write_byte_to_lcd (SECOND_ROW, 0);
       string_to_lcd ("Buf len:") ;
       decbyte_to_lcd (rx_len);
       write_byte_to_lcd (' ', 1);

       for (i=0 ; i < 14 ; i++)
         hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);

       // The following kludge is to get around the non-reentrant version of C
       // If there's an error inside the Interrupt, this flag will be set
       // That lets us display something - and this loop will cause an infinte wait
 //      while (crashed) ; // Infinite loop if an error has been set in the ISR
                         // This will trip the watchdog timer some time later

       // Delay for 15 seconds
       for (i=0 ; i < 150 ; i++)
       {
         Delay_ms (100);
         asm {
           CLRWDT         // clear the watchdog timer
         }
       }

       goto restart ;
     }
   }
June 14, 2008, at 03:52 AM by stefan keller-tuberg --
Added lines 9-17:

Interfacing the I2c to the NSLU2

There are other pages on this wiki that describe where the I2C? bus can be accessed on the NSLU2. I'm not going to repeat the information here. However there's no information yet on this wiki about implementing the I2C? interface in hardware. That's what I'm providing.

The schematic diagram is really simple. I socketed a 16F88 onto a small piece of matrix board, along with the resistors and diode. I've constructed several of these, some built directly onto the back of the LCD module itself, and others connected to the LCD (and NSLU2) using pluggable ribbon cables.

Note that because the NSLU2 I2C? bus uses 3V3 levels and the 16F88 and LCD devices are 5V, you need to pull a trick to interface the two. Thankfully, the I2C? bus is open drain, and the 16F88 and LCD devices work well down to a little over 4V (at least that's what I've found on the ones I've built). So by picking 5V off the USB port on the NSLU2, dropping this by 0.6V through a diode, you can power the LCD and 16F88 at 4.4V. At this reduced voltage, the 16F88 will recognise the 3V3 I2C? signalling just fine. Nothing further needs to be done to translate levels.

Changed lines 22-23 from:

You need to specify the dimensions of your LCD display and the i2c address you'd like to allocate using #defines near the top of the code.

to:

Edit the dimensions of your LCD display into the #define statements near the top of the code. You need to choose an i2c address in the #define near the top also. In this way, you can have several I2C? devices connected to your NSLU2 at the same time.

June 14, 2008, at 03:43 AM by stefan keller-tuberg --
Changed lines 11-12 from:

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at the mikroElektronika web site.

to:

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at the mikroElektronika web site.

June 14, 2008, at 03:42 AM by stefan keller-tuberg --
Changed lines 11-12 from:

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at [[http://www.mikroe.com/en/download]the mikroElektronika] web site.

to:

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at the mikroElektronika web site.

June 14, 2008, at 03:41 AM by stefan keller-tuberg --
Changed lines 15-503 from:
 this is a test
 this is antother test
      this is a third
// 16F88 programme to drive a hitachi compatible LCD display from the
// i2C bus
//
// There are plenty of spare inputs and outputs for additional functions
//////////////////////////////////////////////////////////////////////
#define I2C?_ADDRESS 0x32
// Define the number of lines and columns on the LCD display
#define LINES 4
#define COLUMNS 20
// This is the assignment of 16F88 pins I chose
// PORT A bit 0 = D4 on LCD
// PORT A bit 1 = D5 on LCD
// PORT A bit 2 = D6 on LCD
// PORT A bit 3 = D7 on LCD
// PORT A bit 4 = NC
// PORT A bit 5 = pulled high with 10k
// PORT A bit 6 = NC
// PORT A bit 7 = NC
//
// PORT B bit 0 = E on LCD
// PORT B bit 1 = SD on I2C?
// PORT B bit 2 = RW on LCD
// PORT B bit 3 = RS on LCD
// PORT B bit 4 = CK on I2C?
// PORT B bit 5 = NC
// PORT B bit 6 = NC
// PORT B bit 7 = NC
// Fx is mikroC nomenclature for bit x
#define RS_BIT F3
#define WR_BIT F2
#define E_BIT F0
// counters for debugging purposes
unsigned char crashed, crash_cause, info, last_i2c ;
//////////////////////////////////////////////////////////////////////
void
write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
{
TRISA.F0 = 0; //Set Bits 3-0 as OUTPUTS
TRISA.F1 = 0;
TRISA.F2 = 0;
TRISA.F3 = 0;
PORTA = c ; //Bits 3-0 = high order data nibble
PORTB.E_BIT = 0 ;
if (RS)
PORTB.RS_BIT = 1 ;
else
PORTB.RS_BIT = 0 ;
PORTB.WR_BIT = 0 ;
Delay_us (1);
PORTB.E_BIT = 1 ; //E High, WR low, RS set to selected value
Delay_us (1);
PORTB.E_BIT = 0 ;
// Exit with E Low, WR Low, RS set to selected value
}
void
write_byte_to_lcd (const unsigned char c, const unsigned char RS)
{
unsigned char busy ;
TRISA.F0 = 1; //Set Bits 3-0 as INPUTS
TRISA.F1 = 1;
TRISA.F2 = 1;
TRISA.F3 = 1;
PORTB.E_BIT = 0 ;
PORTB.RS_BIT = 0 ;
PORTB.WR_BIT = 1 ;
Delay_us(1);
// Wait for LCD to become unbusy
// BUSY FLAG polled on falling edge of E
do // Need to set E high and low twice - two four bit reads
{
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
busy = PORTA ; //Read the busy flag in bit 3 (bit 7 of LCD)
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
} while (busy.F3);
// Now the LCD is ready, write byte in two 4 bit nibbles
write_nibble_to_lcd (c >> 4, RS) ;
write_nibble_to_lcd (c & 0x0f, RS) ;
// Exit with E Low, WR Low, RS set to selected value
}
void
string_to_lcd (const unsigned char *s)
{
while (*s)
write_byte_to_lcd (*s++, 1) ;
}
void
decbyte_to_lcd (unsigned char c)
{
char *s ;
unsigned char text[4] ;
ByteToStr? (c, text);
for (s=text; *s ;)
write_byte_to_lcd (*s++, 1) ;
}
void
hexnibble_to_lcd (unsigned char c)
{
c &= 0x0f ;
if (c > 9)
c += 'A' - 10 ;
else
c += '0' ;
write_byte_to_lcd (c, 1) ;
}
void hexbyte_to_lcd (unsigned char c)
{
hexnibble_to_lcd (c >> 4) ;
hexnibble_to_lcd (c) ;
}
//==============================================================================
// BUFFER_POWER must be >= 2
#define BUFFER_LEN 64
#define BUFFER_MASK (BUFFER_LEN-1)
unsigned char rxbuf [BUFFER_LEN] ;
unsigned char rx_head, rx_tail, rx_len ;
void
interrupt ()
{
unsigned char stat;
if (PIR1?.F3 == 1) // is this an i2c interrupt
{
SSPCON.F4 = 0 ; // Hold clock low - (ie stretch clock while we work)
stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
// 5=(1=data,0=addr), 3=(1=start detected),
// 2=(1=read,0=write), 0=(1=sspbuf full)
if (stat == 0x09) // I2C? Write: Byte received was an address
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
}
else if (stat == 0x29) // I2C? Write: Byte received was data
{
rxbuf[rx_head++] = SSPBUF ;
rx_head &= BUFFER_MASK ;
if (rx_len++ >= BUFFER_LEN)
crashed = 1 ; //flag error
}
else if (stat == 0x0c) // I2C? Read: Byte received was an address
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0xaa ; // Just return AA for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0x55 ; // Just return 55 for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else // NACK received 0x28 - or another condition - Do a reset
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
SSPCON.F5=0; // Temporarily disable
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPSTAT = 0 ; // Clear all status bits
}
SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
PIR1?.F3 = 0 ; // Clear the interrupt
}
else // unknown interrupt!!
{
crashed = 3;
info = PIR1? ;
}
}
//==============================================================================
#define CLEAR_AND_FIRST_ROW 0x01
#define FIRST_ROW 0x02
#define SECOND_ROW (0x80 | 0x40)
#define THIRD_ROW (0x80 | 0x14)
#define FOURTH_ROW (0x80 | 0x54)
unsigned char line, column ;
void
linefeed ()
{
unsigned char cmd, i ;
// Blank the appropriate line.
switch (line)
{
case 1: cmd = FIRST_ROW ; break ;
case 2: cmd = SECOND_ROW ; break ;
case 3: cmd = THIRD_ROW ; break ;
default: cmd = FOURTH_ROW ; break ;
}
write_byte_to_lcd (cmd, 0); // Go to start of line
for (i=0 ; i<COLUMNS ; i++) // Blank the entire line
write_byte_to_lcd (' ', 1);
write_byte_to_lcd (cmd, 0); // Return to start of line
column = 0 ;
}
void
printchar (const unsigned char c)
{
if (c == 0)
{
// Clear display entirely
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1 ;
column = 0 ;
}
else if (c == '\n')
{
// linefeed and wrap around to line 0
line++ ;
if (line > LINES)
line = 1 ;
linefeed() ;
}
else if (c < 5)
{
// move to line 1..4
line = c ;
linefeed() ;
}
else if (c == '\r')
{
linefeed() ;
}
else
{
if (column >= COLUMNS)
{
// need to force a linefeed
line++ ;
if (line > LINES)
line = 1 ;
linefeed() ;
}
// Display the numbered character
write_byte_to_lcd (c, 1);
column++ ;
}
}
// Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
void
enqueue_string (const unsigned char *s)
{
while (*s)
{
PIE1?.F3 = 0; // Disable int F3=I2C? int
rxbuf[rx_head++] = *s++ ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
if (rx_len > BUFFER_LEN)
{
crashed = 4 ; //flag error
return ;
}
}
}
void
enqueue_number (const unsigned char c)
{
unsigned char text[4] ;
unsigned char *s ;
ByteToStr? (c, text);
for (s=text ; *s ; s++)
{
if (*s == ' ')
continue ;
rxbuf[rx_head++] = *s ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
if (rx_len > BUFFER_LEN)
crashed = 5 ; //flag error
}
}
//==============================================================================
#define MAGIC 0x3d
unsigned char num_boots ;
unsigned char magic ; // Will hold a magic number so we know a cold boot
void
main()
{
char i ;
// Osc = bits 6,5,4
OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
while (OSCCON.F2 == 0) ; // Wait for osc to stabilise
restart:
OPTION_REG=0x03; //0x03=Prescaler=1:256 0x80=low..pullup enabled
INTCON=0; //Disable interrupts
PIR1?=0; //Clear all interrupt flags
TRISA=0; //Set Port A to all outputs
TRISB=0x12; //B4 and B1 are inputs (I2C?)
PORTA=0x00;
PORTB=0x00;
WDTCON =0x17 ; // Enable watchdog with max timeout value = approx 2s
ANSEL=0; //0 = PortA? bit is used for digital I/O
ADCON1?=0x07 ; // Disable A/D
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
SSPSTAT = 0 ; // Clear all status bits
PIE1?.F3 = 1; // F6=ADC int, F3=I2C? int
if (magic != MAGIC)
{
crash_cause=0 ; // used for debugging interrupt routine with print statements
num_boots = 0 ;
magic = MAGIC ;
}
else
num_boots++ ;
crashed = 0 ; // We're not crashed at the moment!
rx_head = rx_tail = rx_len = 0 ;
INTCON.F6 = 1 ; // enable peripheral interrupts
INTCON.F7 = 1 ; // enable all unmasked interrupts
TXSTA = 0 ; // Turn off transmit from UART
RCSTA = 0 ;
CMCON = 0x07 ; // Disable comparators
Delay_ms (100); // Provide a little time for the LCD to initialise
asm {
CLRWDT // Clear the watchdog timer
}
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (5); // Wait at least 4.1ms
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
Delay_ms (1); // Wait at least 100us
write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
write_byte_to_lcd (0x01, 0); //Clear display
write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1;
column = 0 ;
// Display the welcome message
enqueue_number (LINES) ;
enqueue_string ("x") ;
enqueue_number (COLUMNS) ;
enqueue_string (" lcd @addr ") ;
enqueue_number (I2C?_ADDRESS) ;
enqueue_string ("\nVersion 1");
// Give some indication if there's been a crash
if (num_boots)
{
enqueue_string ("\rBoot count: ");
enqueue_number (num_boots) ;
}
for (;;)
{
asm {
CLRWDT // clear the watchdog timer
}
if (rx_len)
{
printchar (rxbuf[rx_tail++]) ;
rx_tail &= BUFFER_MASK ;
PIE1?.F3 = 0; // Disable int F3=I2C? int
rx_len-- ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
}
if (crashed)
{
crash_cause = crashed ; // Remember why we crashed
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
string_to_lcd ("Cause:") ;
hexbyte_to_lcd (crash_cause);
string_to_lcd (" last:") ;
hexbyte_to_lcd (last_i2c) ;
string_to_lcd (" info:") ;
hexbyte_to_lcd (info);
string_to_lcd (" boots:") ;
hexbyte_to_lcd (num_boots);
write_byte_to_lcd (SECOND_ROW, 0);
string_to_lcd ("Buf len:") ;
decbyte_to_lcd (rx_len);
write_byte_to_lcd (' ', 1);
for (i=0 ; i < 14 ; i++)
hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);
// The following kludge is to get around the non-reentrant version of C
// If there's an error inside the Interrupt, this flag will be set
// That lets us display something - and this loop will cause an infinte wait
// while (crashed) ; // Infinite loop if an error has been set in the ISR
// This will trip the watchdog timer some time later
// Delay for 15 seconds
for (i=0 ; i < 150 ; i++)
{
Delay_ms (100);
asm {
CLRWDT // clear the watchdog timer
}
}
goto restart ;
}
}
}
to:
 // 16F88 programme to drive a hitachi compatible LCD display from the
 // i2C bus
 //
 // There are plenty of spare inputs and outputs for additional functions
 //////////////////////////////////////////////////////////////////////
 #define I2C?_ADDRESS 0x32

 // Define the number of lines and columns on the LCD display
 #define LINES 4
 #define COLUMNS 20

 // This is the assignment of 16F88 pins I chose
 // PORT A bit 0 = D4 on LCD
 // PORT A bit 1 = D5 on LCD
 // PORT A bit 2 = D6 on LCD
 // PORT A bit 3 = D7 on LCD
 // PORT A bit 4 = NC
 // PORT A bit 5 = pulled high with 10k
 // PORT A bit 6 = NC
 // PORT A bit 7 = NC
 //
 // PORT B bit 0 = E on LCD
 // PORT B bit 1 = SD on I2C?
 // PORT B bit 2 = RW on LCD
 // PORT B bit 3 = RS on LCD
 // PORT B bit 4 = CK on I2C?
 // PORT B bit 5 = NC
 // PORT B bit 6 = NC
 // PORT B bit 7 = NC

 // Fx is mikroC nomenclature for bit x
 #define RS_BIT F3
 #define WR_BIT F2
 #define E_BIT  F0

 // counters for debugging purposes
 unsigned char crashed, crash_cause, info, last_i2c ;
 //////////////////////////////////////////////////////////////////////
 void
 write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
 {
   TRISA.F0 = 0;      //Set Bits 3-0 as OUTPUTS
   TRISA.F1 = 0;
   TRISA.F2 = 0;
   TRISA.F3 = 0;
   PORTA = c ;        //Bits 3-0 = high order data nibble

   PORTB.E_BIT = 0 ;

   if (RS)
     PORTB.RS_BIT = 1 ;
   else
     PORTB.RS_BIT = 0 ;

   PORTB.WR_BIT = 0 ;
   Delay_us (1);
   PORTB.E_BIT = 1 ;  //E High, WR low, RS set to selected value
   Delay_us (1);
   PORTB.E_BIT = 0 ;

   // Exit with E Low, WR Low, RS set to selected value
 }

 void
 write_byte_to_lcd (const unsigned char c, const unsigned char RS)
 {
   unsigned char busy ;

   TRISA.F0 = 1;       //Set Bits 3-0 as INPUTS
   TRISA.F1 = 1;
   TRISA.F2 = 1;
   TRISA.F3 = 1;

   PORTB.E_BIT = 0 ;
   PORTB.RS_BIT = 0 ;
   PORTB.WR_BIT = 1 ;
   Delay_us(1);

   // Wait for LCD to become unbusy
   // BUSY FLAG polled on falling edge of E

   do  // Need to set E high and low twice - two four bit reads
   {
     PORTB.E_BIT = 1;    //WR High=Read, E High
     Delay_us(1);
     busy = PORTA ;      //Read the busy flag in bit 3 (bit 7 of LCD)
     PORTB.E_BIT = 0 ;   //WR High, E Clocks low
     Delay_us(1);
     PORTB.E_BIT = 1;    //WR High=Read, E High
     Delay_us(1);
     PORTB.E_BIT = 0 ;   //WR High, E Clocks low
     Delay_us(1);
   } while (busy.F3);

   // Now the LCD is ready, write byte in two 4 bit nibbles
   write_nibble_to_lcd (c >> 4, RS) ;
   write_nibble_to_lcd (c & 0x0f, RS) ;

   // Exit with E Low, WR Low, RS set to selected value
 }

 void
 string_to_lcd (const unsigned char *s)
 {
   while (*s)
     write_byte_to_lcd (*s++, 1) ;
 }

 void
 decbyte_to_lcd (unsigned char c)
 {
   char *s ;
   unsigned char text[4] ;

   ByteToStr? (c, text);

   for (s=text; *s ;)
     write_byte_to_lcd (*s++, 1) ;
 }

 void
 hexnibble_to_lcd (unsigned char c)
 {
   c &= 0x0f ;

   if (c > 9)
       c += 'A' - 10 ;
   else
     c += '0' ;

   write_byte_to_lcd (c, 1) ;
 }

 void hexbyte_to_lcd (unsigned char c)
 {
    hexnibble_to_lcd (c >> 4) ;
    hexnibble_to_lcd (c) ;
 }
 //==============================================================================
 // BUFFER_POWER must be >= 2
 #define BUFFER_LEN 64
 #define BUFFER_MASK (BUFFER_LEN-1)

 unsigned char rxbuf [BUFFER_LEN] ;
 unsigned char rx_head, rx_tail, rx_len ;

 void
 interrupt ()
 {
   unsigned char stat;

   if (PIR1?.F3 == 1) // is this an i2c interrupt
   {
     SSPCON.F4 = 0 ;         // Hold clock low - (ie stretch clock while we work)
     stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
                             // 5=(1=data,0=addr), 3=(1=start detected),
                             // 2=(1=read,0=write), 0=(1=sspbuf full)

     if (stat == 0x09) // I2C? Write: Byte received was an address
     {
       last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
     }
     else if (stat == 0x29) // I2C? Write: Byte received was data
     {
       rxbuf[rx_head++] = SSPBUF ;
       rx_head &= BUFFER_MASK ;

       if (rx_len++ >= BUFFER_LEN)
         crashed = 1 ;  //flag error
     }
     else if (stat == 0x0c) // I2C? Read: Byte received was an address
     {
       while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full

       do
       {
               SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0xaa ; // Just return AA for the time being
       } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
     }
     else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
     {
       while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full

       do
       {
         SSPCON.F7 = 0 ; // Clear the write collision flag
         SSPBUF = 0x55 ; // Just return 55 for the time being
       } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
     }
     else  // NACK received 0x28 - or another condition - Do a reset
     {
       last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
       SSPCON.F5=0;    // Temporarily disable
       SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                       // F6=0 SSPOV Receive Overflow Indicator bit
                       // F5=1 Synchronous Serial Port Enable
                       // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                       // F3-0=0110: I2C? slave mode: 7 bits address
       SSPSTAT = 0 ;   // Clear all status bits
     }

     SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
     PIR1?.F3 = 0 ;   // Clear the interrupt
   }
   else // unknown interrupt!!
   {
     crashed = 3;
     info = PIR1? ;
   }
 }
 //==============================================================================
 #define CLEAR_AND_FIRST_ROW 0x01
 #define FIRST_ROW           0x02
 #define SECOND_ROW          (0x80 | 0x40)
 #define THIRD_ROW           (0x80 | 0x14)
 #define FOURTH_ROW          (0x80 | 0x54)

 unsigned char line, column ;

 void
 linefeed ()
 {
   unsigned char cmd, i ;

   // Blank the appropriate line.
   switch (line)
   {
     case 1:  cmd = FIRST_ROW ; break ;
     case 2:  cmd = SECOND_ROW ; break ;
     case 3:  cmd = THIRD_ROW ; break ;
     default: cmd = FOURTH_ROW ; break ;
   }

   write_byte_to_lcd (cmd, 0); // Go to start of line

   for (i=0 ; i<COLUMNS ; i++)      // Blank the entire line
     write_byte_to_lcd (' ', 1);

   write_byte_to_lcd (cmd, 0); // Return to start of line
   column = 0 ;
 }

 void
 printchar (const unsigned char c)
 {
   if (c == 0)
   {
     // Clear display entirely
     write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
     line = 1 ;
     column = 0 ;
   }
   else if (c == '\n')
   {
     // linefeed and wrap around to line 0
     line++ ;

     if (line > LINES)
       line = 1 ;

     linefeed() ;
   }
   else if (c < 5)
   {
     // move to line 1..4
     line = c ;
     linefeed() ;
   }
   else if (c == '\r')
   {
     linefeed() ;
   }
   else
   {
     if (column >= COLUMNS)
     {
       // need to force a linefeed
       line++ ;

       if (line > LINES)
         line = 1 ;

       linefeed() ;
     }

     // Display the numbered character
     write_byte_to_lcd (c, 1);
     column++ ;
   }
 }

 // Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
 void
 enqueue_string (const unsigned char *s)
 {
   while (*s)
   {
     PIE1?.F3 = 0;  // Disable int F3=I2C? int
     rxbuf[rx_head++] = *s++ ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;
     PIE1?.F3 = 1;  // Enable int F3=I2C? int

     if (rx_len > BUFFER_LEN)
     {
       crashed = 4 ;  //flag error
       return ;
     }
   }
 }

 void
 enqueue_number (const unsigned char c)
 {
   unsigned char text[4] ;
   unsigned char *s ;

   ByteToStr? (c, text);

   for (s=text ; *s ; s++)
   {
     if (*s ==  ' ')
       continue ;

     rxbuf[rx_head++] = *s ;
     rx_head &= BUFFER_MASK ;
     rx_len++ ;

     if (rx_len > BUFFER_LEN)
       crashed = 5 ;  //flag error
   }
 }
 //==============================================================================
 #define MAGIC       0x3d

 unsigned char num_boots ;
 unsigned char magic ; // Will hold a magic number so we know a cold boot

 void
 main()
 {
   char i ;

   // Osc = bits 6,5,4
   OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
   while (OSCCON.F2 == 0) ; // Wait for osc to stabilise

 restart:
   OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
   INTCON=0;        //Disable interrupts
   PIR1?=0;          //Clear all interrupt flags

   TRISA=0;         //Set Port A to all outputs
   TRISB=0x12;      //B4 and B1 are inputs (I2C?)
   PORTA=0x00;
   PORTB=0x00;

   WDTCON =0x17 ;   // Enable watchdog with max timeout value = approx 2s

   ANSEL=0; //0 = PortA? bit is used for digital I/O
   ADCON1?=0x07 ; // Disable A/D

   SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                   // F6=0 SSPOV Receive Overflow Indicator bit
                   // F5=1 Synchronous Serial Port Enable
                   // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                   // F3-0=0110: I2C? slave mode: 7 bits address
   SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
   SSPSTAT = 0 ;   // Clear all status bits
   PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int

   if (magic != MAGIC)
   {
     crash_cause=0 ;  // used for debugging interrupt routine with print statements
     num_boots = 0 ;
     magic = MAGIC ;
   }
   else
     num_boots++ ;

   crashed = 0 ;   // We're not crashed at the moment!
   rx_head = rx_tail = rx_len = 0 ;

   INTCON.F6 = 1 ; // enable peripheral interrupts
   INTCON.F7 = 1 ; // enable all unmasked interrupts

   TXSTA = 0 ;     // Turn off transmit from UART
   RCSTA = 0 ;
   CMCON = 0x07 ;  // Disable comparators

   Delay_ms (100); // Provide a little time for the LCD to initialise
   asm {
     CLRWDT        // Clear the watchdog timer
   }

   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (5);               // Wait at least 4.1ms
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
   Delay_ms (1);               // Wait at least 100us
   write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
   Delay_ms (1);               // Wait at least 100us

   write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
   write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
   write_byte_to_lcd (0x01, 0); //Clear display
   write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
   write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
   line = 1;
   column = 0 ;

   // Display the welcome message
   enqueue_number (LINES) ;
   enqueue_string ("x") ;
   enqueue_number (COLUMNS) ;
   enqueue_string (" lcd @addr ") ;
   enqueue_number (I2C?_ADDRESS) ;
   enqueue_string ("\nVersion 1");

   // Give some indication if there's been a crash
   if (num_boots)
   {
     enqueue_string ("\rBoot count: ");
     enqueue_number (num_boots) ;
   }

   for (;;)
   {
     asm {
       CLRWDT  // clear the watchdog timer
     }

     if (rx_len)
     {
       printchar (rxbuf[rx_tail++]) ;
       rx_tail &= BUFFER_MASK ;
       PIE1?.F3 = 0;  // Disable int F3=I2C? int
       rx_len-- ;
       PIE1?.F3 = 1;  // Enable int F3=I2C? int
     }

     if (crashed)
     {
       crash_cause = crashed ; // Remember why we crashed
       write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
       string_to_lcd ("Cause:") ;
       hexbyte_to_lcd (crash_cause);
       string_to_lcd (" last:") ;
       hexbyte_to_lcd (last_i2c) ;
       string_to_lcd (" info:") ;
       hexbyte_to_lcd (info);
       string_to_lcd (" boots:") ;
       hexbyte_to_lcd (num_boots);

       write_byte_to_lcd (SECOND_ROW, 0);
       string_to_lcd ("Buf len:") ;
       decbyte_to_lcd (rx_len);
       write_byte_to_lcd (' ', 1);

       for (i=0 ; i < 14 ; i++)
         hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);

       // The following kludge is to get around the non-reentrant version of C
       // If there's an error inside the Interrupt, this flag will be set
       // That lets us display something - and this loop will cause an infinte wait
 //      while (crashed) ; // Infinite loop if an error has been set in the ISR
                         // This will trip the watchdog timer some time later

       // Delay for 15 seconds
       for (i=0 ; i < 150 ; i++)
       {
         Delay_ms (100);
         asm {
           CLRWDT         // clear the watchdog timer
         }
       }

       goto restart ;
     }
   }
 }
June 14, 2008, at 03:39 AM by stefan keller-tuberg --
Added lines 17-18:
      this is a third
June 14, 2008, at 03:39 AM by stefan keller-tuberg --
Added lines 15-16:
 this is a test
 this is antother test
June 14, 2008, at 03:36 AM by stefan keller-tuberg --
Changed lines 13-14 from:

You need to specify the dimensions of your LCD display and the i2c address you'd like to allocate using #defines near the top of the code.

to:

You need to specify the dimensions of your LCD display and the i2c address you'd like to allocate using #defines near the top of the code.

Changed lines 16-20 from:
// i2C bus
//
// There are plenty of spare inputs and outputs for additional functions
//////////////////////////////////////////////////////////////////////
#define I2C?_ADDRESS 0x32
to:
// i2C bus
//
// There are plenty of spare inputs and outputs for additional functions
//////////////////////////////////////////////////////////////////////
#define I2C?_ADDRESS 0x32
Changed lines 22-24 from:
// Define the number of lines and columns on the LCD display
#define LINES 4
#define COLUMNS 20
to:
// Define the number of lines and columns on the LCD display
#define LINES 4
#define COLUMNS 20
Changed lines 26-43 from:
// This is the assignment of 16F88 pins I chose
// PORT A bit 0 = D4 on LCD
// PORT A bit 1 = D5 on LCD
// PORT A bit 2 = D6 on LCD
// PORT A bit 3 = D7 on LCD
// PORT A bit 4 = NC
// PORT A bit 5 = pulled high with 10k
// PORT A bit 6 = NC
// PORT A bit 7 = NC
//
// PORT B bit 0 = E on LCD
// PORT B bit 1 = SD on I2C?
// PORT B bit 2 = RW on LCD
// PORT B bit 3 = RS on LCD
// PORT B bit 4 = CK on I2C?
// PORT B bit 5 = NC
// PORT B bit 6 = NC
// PORT B bit 7 = NC
to:
// This is the assignment of 16F88 pins I chose
// PORT A bit 0 = D4 on LCD
// PORT A bit 1 = D5 on LCD
// PORT A bit 2 = D6 on LCD
// PORT A bit 3 = D7 on LCD
// PORT A bit 4 = NC
// PORT A bit 5 = pulled high with 10k
// PORT A bit 6 = NC
// PORT A bit 7 = NC
//
// PORT B bit 0 = E on LCD
// PORT B bit 1 = SD on I2C?
// PORT B bit 2 = RW on LCD
// PORT B bit 3 = RS on LCD
// PORT B bit 4 = CK on I2C?
// PORT B bit 5 = NC
// PORT B bit 6 = NC
// PORT B bit 7 = NC
Changed lines 45-48 from:
// Fx is mikroC nomenclature for bit x
#define RS_BIT F3
#define WR_BIT F2
#define E_BIT F0
to:
// Fx is mikroC nomenclature for bit x
#define RS_BIT F3
#define WR_BIT F2
#define E_BIT F0
Changed lines 50-60 from:
// counters for debugging purposes
unsigned char crashed, crash_cause, info, last_i2c ;
//////////////////////////////////////////////////////////////////////
void
write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
{
TRISA.F0 = 0; //Set Bits 3-0 as OUTPUTS
TRISA.F1 = 0;
TRISA.F2 = 0;
TRISA.F3 = 0;
PORTA = c ; //Bits 3-0 = high order data nibble
to:
// counters for debugging purposes
unsigned char crashed, crash_cause, info, last_i2c ;
//////////////////////////////////////////////////////////////////////
void
write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
{
TRISA.F0 = 0; //Set Bits 3-0 as OUTPUTS
TRISA.F1 = 0;
TRISA.F2 = 0;
TRISA.F3 = 0;
PORTA = c ; //Bits 3-0 = high order data nibble
Changed line 62 from:
PORTB.E_BIT = 0 ;
to:
PORTB.E_BIT = 0 ;
Changed lines 64-67 from:
if (RS)
PORTB.RS_BIT = 1 ;
else
PORTB.RS_BIT = 0 ;
to:
if (RS)
PORTB.RS_BIT = 1 ;
else
PORTB.RS_BIT = 0 ;
Changed lines 69-73 from:
PORTB.WR_BIT = 0 ;
Delay_us (1);
PORTB.E_BIT = 1 ; //E High, WR low, RS set to selected value
Delay_us (1);
PORTB.E_BIT = 0 ;
to:
PORTB.WR_BIT = 0 ;
Delay_us (1);
PORTB.E_BIT = 1 ; //E High, WR low, RS set to selected value
Delay_us (1);
PORTB.E_BIT = 0 ;
Changed lines 75-76 from:
// Exit with E Low, WR Low, RS set to selected value
}
to:
// Exit with E Low, WR Low, RS set to selected value
}
Changed lines 78-81 from:
void
write_byte_to_lcd (const unsigned char c, const unsigned char RS)
{
unsigned char busy ;
to:
void
write_byte_to_lcd (const unsigned char c, const unsigned char RS)
{
unsigned char busy ;
Changed lines 83-86 from:
TRISA.F0 = 1; //Set Bits 3-0 as INPUTS
TRISA.F1 = 1;
TRISA.F2 = 1;
TRISA.F3 = 1;
to:
TRISA.F0 = 1; //Set Bits 3-0 as INPUTS
TRISA.F1 = 1;
TRISA.F2 = 1;
TRISA.F3 = 1;
Changed lines 88-91 from:
PORTB.E_BIT = 0 ;
PORTB.RS_BIT = 0 ;
PORTB.WR_BIT = 1 ;
Delay_us(1);
to:
PORTB.E_BIT = 0 ;
PORTB.RS_BIT = 0 ;
PORTB.WR_BIT = 1 ;
Delay_us(1);
Changed lines 93-94 from:
// Wait for LCD to become unbusy
// BUSY FLAG polled on falling edge of E
to:
// Wait for LCD to become unbusy
// BUSY FLAG polled on falling edge of E
Changed lines 96-107 from:
do // Need to set E high and low twice - two four bit reads
{
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
busy = PORTA ; //Read the busy flag in bit 3 (bit 7 of LCD)
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
} while (busy.F3);
to:
do // Need to set E high and low twice - two four bit reads
{
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
busy = PORTA ; //Read the busy flag in bit 3 (bit 7 of LCD)
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
} while (busy.F3);
Changed lines 109-111 from:
// Now the LCD is ready, write byte in two 4 bit nibbles
write_nibble_to_lcd (c >> 4, RS) ;
write_nibble_to_lcd (c & 0x0f, RS) ;
to:
// Now the LCD is ready, write byte in two 4 bit nibbles
write_nibble_to_lcd (c >> 4, RS) ;
write_nibble_to_lcd (c & 0x0f, RS) ;
Changed lines 113-114 from:
// Exit with E Low, WR Low, RS set to selected value
}
to:
// Exit with E Low, WR Low, RS set to selected value
}
Changed lines 116-121 from:
void
string_to_lcd (const unsigned char *s)
{
while (*s)
write_byte_to_lcd (*s++, 1) ;
}
to:
void
string_to_lcd (const unsigned char *s)
{
while (*s)
write_byte_to_lcd (*s++, 1) ;
}
Changed lines 123-127 from:
void
decbyte_to_lcd (unsigned char c)
{
char *s ;
unsigned char text[4] ;
to:
void
decbyte_to_lcd (unsigned char c)
{
char *s ;
unsigned char text[4] ;
Changed line 129 from:
ByteToStr? (c, text);
to:
ByteToStr? (c, text);
Changed lines 131-133 from:
for (s=text; *s ;)
write_byte_to_lcd (*s++, 1) ;
}
to:
for (s=text; *s ;)
write_byte_to_lcd (*s++, 1) ;
}
Changed lines 135-138 from:
void
hexnibble_to_lcd (unsigned char c)
{
c &= 0x0f ;
to:
void
hexnibble_to_lcd (unsigned char c)
{
c &= 0x0f ;
Changed lines 140-143 from:
if (c > 9)
c += 'A' - 10 ;
else
c += '0' ;
to:
if (c > 9)
c += 'A' - 10 ;
else
c += '0' ;
Changed lines 145-146 from:
write_byte_to_lcd (c, 1) ;
}
to:
write_byte_to_lcd (c, 1) ;
}
Changed lines 148-156 from:
void hexbyte_to_lcd (unsigned char c)
{
hexnibble_to_lcd (c >> 4) ;
hexnibble_to_lcd (c) ;
}
//==============================================================================
// BUFFER_POWER must be >= 2
#define BUFFER_LEN 64
#define BUFFER_MASK (BUFFER_LEN-1)
to:
void hexbyte_to_lcd (unsigned char c)
{
hexnibble_to_lcd (c >> 4) ;
hexnibble_to_lcd (c) ;
}
//==============================================================================
// BUFFER_POWER must be >= 2
#define BUFFER_LEN 64
#define BUFFER_MASK (BUFFER_LEN-1)
Changed lines 158-159 from:
unsigned char rxbuf [BUFFER_LEN] ;
unsigned char rx_head, rx_tail, rx_len ;
to:
unsigned char rxbuf [BUFFER_LEN] ;
unsigned char rx_head, rx_tail, rx_len ;
Changed lines 161-164 from:
void
interrupt ()
{
unsigned char stat;
to:
void
interrupt ()
{
unsigned char stat;
Changed lines 166-171 from:
if (PIR1?.F3 == 1) // is this an i2c interrupt
{
SSPCON.F4 = 0 ; // Hold clock low - (ie stretch clock while we work)
stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
// 5=(1=data,0=addr), 3=(1=start detected),
// 2=(1=read,0=write), 0=(1=sspbuf full)
to:
if (PIR1?.F3 == 1) // is this an i2c interrupt
{
SSPCON.F4 = 0 ; // Hold clock low - (ie stretch clock while we work)
stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
// 5=(1=data,0=addr), 3=(1=start detected),
// 2=(1=read,0=write), 0=(1=sspbuf full)
Changed lines 173-180 from:
if (stat == 0x09) // I2C? Write: Byte received was an address
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
}
else if (stat == 0x29) // I2C? Write: Byte received was data
{
rxbuf[rx_head++] = SSPBUF ;
rx_head &= BUFFER_MASK ;
to:
if (stat == 0x09) // I2C? Write: Byte received was an address
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
}
else if (stat == 0x29) // I2C? Write: Byte received was data
{
rxbuf[rx_head++] = SSPBUF ;
rx_head &= BUFFER_MASK ;
Changed lines 182-187 from:
if (rx_len++ >= BUFFER_LEN)
crashed = 1 ; //flag error
}
else if (stat == 0x0c) // I2C? Read: Byte received was an address
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
to:
if (rx_len++ >= BUFFER_LEN)
crashed = 1 ; //flag error
}
else if (stat == 0x0c) // I2C? Read: Byte received was an address
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
Changed lines 189-197 from:
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0xaa ; // Just return AA for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
to:
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0xaa ; // Just return AA for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
Changed lines 199-215 from:
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0x55 ; // Just return 55 for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else // NACK received 0x28 - or another condition - Do a reset
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
SSPCON.F5=0; // Temporarily disable
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPSTAT = 0 ; // Clear all status bits
}
to:
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0x55 ; // Just return 55 for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else // NACK received 0x28 - or another condition - Do a reset
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
SSPCON.F5=0; // Temporarily disable
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPSTAT = 0 ; // Clear all status bits
}
Changed lines 217-231 from:
SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
PIR1?.F3 = 0 ; // Clear the interrupt
}
else // unknown interrupt!!
{
crashed = 3;
info = PIR1? ;
}
}
//==============================================================================
#define CLEAR_AND_FIRST_ROW 0x01
#define FIRST_ROW 0x02
#define SECOND_ROW (0x80 | 0x40)
#define THIRD_ROW (0x80 | 0x14)
#define FOURTH_ROW (0x80 | 0x54)
to:
SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
PIR1?.F3 = 0 ; // Clear the interrupt
}
else // unknown interrupt!!
{
crashed = 3;
info = PIR1? ;
}
}
//==============================================================================
#define CLEAR_AND_FIRST_ROW 0x01
#define FIRST_ROW 0x02
#define SECOND_ROW (0x80 | 0x40)
#define THIRD_ROW (0x80 | 0x14)
#define FOURTH_ROW (0x80 | 0x54)
Changed line 233 from:
unsigned char line, column ;
to:
unsigned char line, column ;
Changed lines 235-238 from:
void
linefeed ()
{
unsigned char cmd, i ;
to:
void
linefeed ()
{
unsigned char cmd, i ;
Changed lines 240-247 from:
// Blank the appropriate line.
switch (line)
{
case 1: cmd = FIRST_ROW ; break ;
case 2: cmd = SECOND_ROW ; break ;
case 3: cmd = THIRD_ROW ; break ;
default: cmd = FOURTH_ROW ; break ;
}
to:
// Blank the appropriate line.
switch (line)
{
case 1: cmd = FIRST_ROW ; break ;
case 2: cmd = SECOND_ROW ; break ;
case 3: cmd = THIRD_ROW ; break ;
default: cmd = FOURTH_ROW ; break ;
}
Changed line 249 from:
write_byte_to_lcd (cmd, 0); // Go to start of line
to:
write_byte_to_lcd (cmd, 0); // Go to start of line
Changed lines 251-252 from:
for (i=0 ; i<COLUMNS ; i++) // Blank the entire line
write_byte_to_lcd (' ', 1);
to:
for (i=0 ; i<COLUMNS ; i++) // Blank the entire line
write_byte_to_lcd (' ', 1);
Changed lines 254-256 from:
write_byte_to_lcd (cmd, 0); // Return to start of line
column = 0 ;
}
to:
write_byte_to_lcd (cmd, 0); // Return to start of line
column = 0 ;
}
Changed lines 258-271 from:
void
printchar (const unsigned char c)
{
if (c == 0)
{
// Clear display entirely
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1 ;
column = 0 ;
}
else if (c == '\n')
{
// linefeed and wrap around to line 0
line++ ;
to:
void
printchar (const unsigned char c)
{
if (c == 0)
{
// Clear display entirely
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1 ;
column = 0 ;
}
else if (c == '\n')
{
// linefeed and wrap around to line 0
line++ ;
Changed lines 273-274 from:
if (line > LINES)
line = 1 ;
to:
if (line > LINES)
line = 1 ;
Changed lines 276-293 from:
linefeed() ;
}
else if (c < 5)
{
// move to line 1..4
line = c ;
linefeed() ;
}
else if (c == '\r')
{
linefeed() ;
}
else
{
if (column >= COLUMNS)
{
// need to force a linefeed
line++ ;
to:
linefeed() ;
}
else if (c < 5)
{
// move to line 1..4
line = c ;
linefeed() ;
}
else if (c == '\r')
{
linefeed() ;
}
else
{
if (column >= COLUMNS)
{
// need to force a linefeed
line++ ;
Changed lines 295-296 from:
if (line > LINES)
line = 1 ;
to:
if (line > LINES)
line = 1 ;
Changed lines 298-299 from:
linefeed() ;
}
to:
linefeed() ;
}
Changed lines 301-305 from:
// Display the numbered character
write_byte_to_lcd (c, 1);
column++ ;
}
}
to:
// Display the numbered character
write_byte_to_lcd (c, 1);
column++ ;
}
}
Changed lines 307-317 from:
// Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
void
enqueue_string (const unsigned char *s)
{
while (*s)
{
PIE1?.F3 = 0; // Disable int F3=I2C? int
rxbuf[rx_head++] = *s++ ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
to:
// Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
void
enqueue_string (const unsigned char *s)
{
while (*s)
{
PIE1?.F3 = 0; // Disable int F3=I2C? int
rxbuf[rx_head++] = *s++ ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
Changed lines 319-325 from:
if (rx_len > BUFFER_LEN)
{
crashed = 4 ; //flag error
return ;
}
}
}
to:
if (rx_len > BUFFER_LEN)
{
crashed = 4 ; //flag error
return ;
}
}
}
Changed lines 327-331 from:
void
enqueue_number (const unsigned char c)
{
unsigned char text[4] ;
unsigned char *s ;
to:
void
enqueue_number (const unsigned char c)
{
unsigned char text[4] ;
unsigned char *s ;
Changed line 333 from:
ByteToStr? (c, text);
to:
ByteToStr? (c, text);
Changed lines 335-338 from:
for (s=text ; *s ; s++)
{
if (*s == ' ')
continue ;
to:
for (s=text ; *s ; s++)
{
if (*s == ' ')
continue ;
Changed lines 340-342 from:
rxbuf[rx_head++] = *s ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
to:
rxbuf[rx_head++] = *s ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
Changed lines 344-349 from:
if (rx_len > BUFFER_LEN)
crashed = 5 ; //flag error
}
}
//==============================================================================
#define MAGIC 0x3d
to:
if (rx_len > BUFFER_LEN)
crashed = 5 ; //flag error
}
}
//==============================================================================
#define MAGIC 0x3d
Changed lines 351-352 from:
unsigned char num_boots ;
unsigned char magic ; // Will hold a magic number so we know a cold boot
to:
unsigned char num_boots ;
unsigned char magic ; // Will hold a magic number so we know a cold boot
Changed lines 354-357 from:
void
main()
{
char i ;
to:
void
main()
{
char i ;
Changed lines 359-361 from:
// Osc = bits 6,5,4
OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
while (OSCCON.F2 == 0) ; // Wait for osc to stabilise
to:
// Osc = bits 6,5,4
OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
while (OSCCON.F2 == 0) ; // Wait for osc to stabilise
Changed lines 363-366 from:
restart:
OPTION_REG=0x03; //0x03=Prescaler=1:256 0x80=low..pullup enabled
INTCON=0; //Disable interrupts
PIR1?=0; //Clear all interrupt flags
to:
restart:
OPTION_REG=0x03; //0x03=Prescaler=1:256 0x80=low..pullup enabled
INTCON=0; //Disable interrupts
PIR1?=0; //Clear all interrupt flags
Changed lines 368-371 from:
TRISA=0; //Set Port A to all outputs
TRISB=0x12; //B4 and B1 are inputs (I2C?)
PORTA=0x00;
PORTB=0x00;
to:
TRISA=0; //Set Port A to all outputs
TRISB=0x12; //B4 and B1 are inputs (I2C?)
PORTA=0x00;
PORTB=0x00;
Changed line 373 from:
WDTCON =0x17 ; // Enable watchdog with max timeout value = approx 2s
to:
WDTCON =0x17 ; // Enable watchdog with max timeout value = approx 2s
Changed lines 375-376 from:
ANSEL=0; //0 = PortA? bit is used for digital I/O
ADCON1?=0x07 ; // Disable A/D
to:
ANSEL=0; //0 = PortA? bit is used for digital I/O
ADCON1?=0x07 ; // Disable A/D
Changed lines 378-385 from:
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
SSPSTAT = 0 ; // Clear all status bits
PIE1?.F3 = 1; // F6=ADC int, F3=I2C? int
to:
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
SSPSTAT = 0 ; // Clear all status bits
PIE1?.F3 = 1; // F6=ADC int, F3=I2C? int
Changed lines 387-394 from:
if (magic != MAGIC)
{
crash_cause=0 ; // used for debugging interrupt routine with print statements
num_boots = 0 ;
magic = MAGIC ;
}
else
num_boots++ ;
to:
if (magic != MAGIC)
{
crash_cause=0 ; // used for debugging interrupt routine with print statements
num_boots = 0 ;
magic = MAGIC ;
}
else
num_boots++ ;
Changed lines 396-397 from:
crashed = 0 ; // We're not crashed at the moment!
rx_head = rx_tail = rx_len = 0 ;
to:
crashed = 0 ; // We're not crashed at the moment!
rx_head = rx_tail = rx_len = 0 ;
Changed lines 399-400 from:
INTCON.F6 = 1 ; // enable peripheral interrupts
INTCON.F7 = 1 ; // enable all unmasked interrupts
to:
INTCON.F6 = 1 ; // enable peripheral interrupts
INTCON.F7 = 1 ; // enable all unmasked interrupts
Changed lines 402-404 from:
TXSTA = 0 ; // Turn off transmit from UART
RCSTA = 0 ;
CMCON = 0x07 ; // Disable comparators
to:
TXSTA = 0 ; // Turn off transmit from UART
RCSTA = 0 ;
CMCON = 0x07 ; // Disable comparators
Changed lines 406-409 from:
Delay_ms (100); // Provide a little time for the LCD to initialise
asm {
CLRWDT // Clear the watchdog timer
}
to:
Delay_ms (100); // Provide a little time for the LCD to initialise
asm {
CLRWDT // Clear the watchdog timer
}
Changed lines 411-418 from:
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (5); // Wait at least 4.1ms
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
Delay_ms (1); // Wait at least 100us
to:
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (5); // Wait at least 4.1ms
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
Delay_ms (1); // Wait at least 100us
Changed lines 420-426 from:
write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
write_byte_to_lcd (0x01, 0); //Clear display
write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1;
column = 0 ;
to:
write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
write_byte_to_lcd (0x01, 0); //Clear display
write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1;
column = 0 ;
Changed lines 428-434 from:
// Display the welcome message
enqueue_number (LINES) ;
enqueue_string ("x") ;
enqueue_number (COLUMNS) ;
enqueue_string (" lcd @addr ") ;
enqueue_number (I2C?_ADDRESS) ;
enqueue_string ("\nVersion 1. SKT");
to:
// Display the welcome message
enqueue_number (LINES) ;
enqueue_string ("x") ;
enqueue_number (COLUMNS) ;
enqueue_string (" lcd @addr ") ;
enqueue_number (I2C?_ADDRESS) ;
enqueue_string ("\nVersion 1");
Changed lines 436-441 from:
// Give some indication if there's been a crash
if (num_boots)
{
enqueue_string ("\rBoot count: ");
enqueue_number (num_boots) ;
}
to:
// Give some indication if there's been a crash
if (num_boots)
{
enqueue_string ("\rBoot count: ");
enqueue_number (num_boots) ;
}
Changed lines 443-447 from:
for (;;)
{
asm {
CLRWDT // clear the watchdog timer
}
to:
for (;;)
{
asm {
CLRWDT // clear the watchdog timer
}
Changed lines 449-456 from:
if (rx_len)
{
printchar (rxbuf[rx_tail++]) ;
rx_tail &= BUFFER_MASK ;
PIE1?.F3 = 0; // Disable int F3=I2C? int
rx_len-- ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
}
to:
if (rx_len)
{
printchar (rxbuf[rx_tail++]) ;
rx_tail &= BUFFER_MASK ;
PIE1?.F3 = 0; // Disable int F3=I2C? int
rx_len-- ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
}
Changed lines 458-469 from:
if (crashed)
{
crash_cause = crashed ; // Remember why we crashed
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
string_to_lcd ("Cause:") ;
hexbyte_to_lcd (crash_cause);
string_to_lcd (" last:") ;
hexbyte_to_lcd (last_i2c) ;
string_to_lcd (" info:") ;
hexbyte_to_lcd (info);
string_to_lcd (" boots:") ;
hexbyte_to_lcd (num_boots);
to:
if (crashed)
{
crash_cause = crashed ; // Remember why we crashed
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
string_to_lcd ("Cause:") ;
hexbyte_to_lcd (crash_cause);
string_to_lcd (" last:") ;
hexbyte_to_lcd (last_i2c) ;
string_to_lcd (" info:") ;
hexbyte_to_lcd (info);
string_to_lcd (" boots:") ;
hexbyte_to_lcd (num_boots);
Changed lines 471-474 from:
write_byte_to_lcd (SECOND_ROW, 0);
string_to_lcd ("Buf len:") ;
decbyte_to_lcd (rx_len);
write_byte_to_lcd (' ', 1);
to:
write_byte_to_lcd (SECOND_ROW, 0);
string_to_lcd ("Buf len:") ;
decbyte_to_lcd (rx_len);
write_byte_to_lcd (' ', 1);
Changed lines 476-477 from:
for (i=0 ; i < 14 ; i++)
hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);
to:
for (i=0 ; i < 14 ; i++)
hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);
Changed lines 479-483 from:
// The following kludge is to get around the non-reentrant version of C
// If there's an error inside the Interrupt, this flag will be set
// That lets us display something - and this loop will cause an infinte wait
// while (crashed) ; // Infinite loop if an error has been set in the ISR
// This will trip the watchdog timer some time later
to:
// The following kludge is to get around the non-reentrant version of C
// If there's an error inside the Interrupt, this flag will be set
// That lets us display something - and this loop will cause an infinte wait
// while (crashed) ; // Infinite loop if an error has been set in the ISR
// This will trip the watchdog timer some time later
Changed lines 485-492 from:
// Delay for 15 seconds
for (i=0 ; i < 150 ; i++)
{
Delay_ms (100);
asm {
CLRWDT // clear the watchdog timer
}
}
to:
// Delay for 15 seconds
for (i=0 ; i < 150 ; i++)
{
Delay_ms (100);
asm {
CLRWDT // clear the watchdog timer
}
}
Changed lines 494-499 from:
goto restart ;
}
}
}
to:
goto restart ;
}
}
}
Changed line 503 from:

Other tips

to:

Other tips

June 14, 2008, at 03:27 AM by stefan keller-tuberg --
Changed line 15 from:
// 16F88 programme to drive a hitachi compatible LCD display from the
to:
// 16F88 programme to drive a hitachi compatible LCD display from the
June 14, 2008, at 03:26 AM by stefan keller-tuberg --
Added lines 20-26:
#define I2C?_ADDRESS 0x32
// Define the number of lines and columns on the LCD display
#define LINES 4
#define COLUMNS 20
// This is the assignment of 16F88 pins I chose
Deleted lines 49-52:
// Define the number of lines and columns on the LCD display
#define LINES 4
#define COLUMNS 20
Deleted line 349:
#define I2C?_ADDRESS 0x32
Changed lines 360-368 from:
//000 = 31.25 kHz
//001 = 125 kHz
//010 = 250 kHz
//011 = 500 kHz
//100 = 1 MHz?
//101 = 2 MHz?
//110 = 4 MHz?
//111 = 8 MHz?
OSCCON=0x7c; // Internal osc: 0x2c=0.250MHz, 0x4c=1MHz, 0x5c=2MHz, 0x7c=8MHz
to:
OSCCON=0x7c; // Internal osc: 0x5c=2MHz, 0x7c=8MHz
June 14, 2008, at 03:22 AM by stefan keller-tuberg --
Changed lines 15-505 from:
@@// 16F88 programme to drive a hitachi compatible LCD display from the
@@// i2C bus

// // There are plenty of spare inputs and outputs for additional functions ////////////////////////////////////////////////////////////////////// // PORT A bit 0 = D4 on LCD // PORT A bit 1 = D5 on LCD // PORT A bit 2 = D6 on LCD // PORT A bit 3 = D7 on LCD // PORT A bit 4 = NC // PORT A bit 5 = pulled high with 10k // PORT A bit 6 = NC // PORT A bit 7 = NC // // PORT B bit 0 = E on LCD // PORT B bit 1 = SD on I2C? // PORT B bit 2 = RW on LCD // PORT B bit 3 = RS on LCD // PORT B bit 4 = CK on I2C? // PORT B bit 5 = NC // PORT B bit 6 = NC // PORT B bit 7 = NC ] // Fx is mikroC nomenclature for bit x

  1. define RS_BIT F3
  2. define WR_BIT F2
  3. define E_BIT F0

// Define the number of lines and columns on the LCD display

  1. define LINES 4
  2. define COLUMNS 20

// counters for debugging purposes unsigned char crashed, crash_cause, info, last_i2c ; ////////////////////////////////////////////////////////////////////// void write_nibble_to_lcd (const unsigned char c, const unsigned char RS) {

  TRISA.F0 = 0;      //Set Bits 3-0 as OUTPUTS
  TRISA.F1 = 0;
  TRISA.F2 = 0;
  TRISA.F3 = 0;
  PORTA = c ;        //Bits 3-0 = high order data nibble

  PORTB.E_BIT = 0 ;

  if (RS)
    PORTB.RS_BIT = 1 ;
  else
    PORTB.RS_BIT = 0 ;

  PORTB.WR_BIT = 0 ;
  Delay_us (1);
  PORTB.E_BIT = 1 ;  //E High, WR low, RS set to selected value
  Delay_us (1);
  PORTB.E_BIT = 0 ;

  // Exit with E Low, WR Low, RS set to selected value

}

void write_byte_to_lcd (const unsigned char c, const unsigned char RS) {

  unsigned char busy ;

  TRISA.F0 = 1;       //Set Bits 3-0 as INPUTS
  TRISA.F1 = 1;
  TRISA.F2 = 1;
  TRISA.F3 = 1;

  PORTB.E_BIT = 0 ;
  PORTB.RS_BIT = 0 ;
  PORTB.WR_BIT = 1 ;
  Delay_us(1);

  // Wait for LCD to become unbusy
  // BUSY FLAG polled on falling edge of E

  do  // Need to set E high and low twice - two four bit reads
  {
    PORTB.E_BIT = 1;    //WR High=Read, E High
    Delay_us(1);
    busy = PORTA ;      //Read the busy flag in bit 3 (bit 7 of LCD)
    PORTB.E_BIT = 0 ;   //WR High, E Clocks low
    Delay_us(1);
    PORTB.E_BIT = 1;    //WR High=Read, E High
    Delay_us(1);
    PORTB.E_BIT = 0 ;   //WR High, E Clocks low
    Delay_us(1);
  } while (busy.F3);

  // Now the LCD is ready, write byte in two 4 bit nibbles
  write_nibble_to_lcd (c >> 4, RS) ;
  write_nibble_to_lcd (c & 0x0f, RS) ;

  // Exit with E Low, WR Low, RS set to selected value

}

void string_to_lcd (const unsigned char *s) {

  while (*s)
    write_byte_to_lcd (*s++, 1) ;

}

void decbyte_to_lcd (unsigned char c) {

  char *s ;
  unsigned char text[4] ;

  ByteToStr? (c, text);

  for (s=text; *s ;)
    write_byte_to_lcd (*s++, 1) ;

}

void hexnibble_to_lcd (unsigned char c) {

  c &= 0x0f ;

  if (c > 9)
      c += 'A' - 10 ;
  else
    c += '0' ;

  write_byte_to_lcd (c, 1) ;

}

void hexbyte_to_lcd (unsigned char c) {

   hexnibble_to_lcd (c >> 4) ;
   hexnibble_to_lcd (c) ;

} //============================================================================== // BUFFER_POWER must be >= 2

  1. define BUFFER_LEN 64
  2. define BUFFER_MASK (BUFFER_LEN-1)

unsigned char rxbuf [BUFFER_LEN] ; unsigned char rx_head, rx_tail, rx_len ;

void interrupt () {

  unsigned char stat;

  if (PIR1?.F3 == 1) // is this an i2c interrupt
  {
    SSPCON.F4 = 0 ;         // Hold clock low - (ie stretch clock while we work)
    stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
                            // 5=(1=data,0=addr), 3=(1=start detected),
                            // 2=(1=read,0=write), 0=(1=sspbuf full)

    if (stat == 0x09) // I2C? Write: Byte received was an address
    {
      last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
    }
    else if (stat == 0x29) // I2C? Write: Byte received was data
    {
      rxbuf[rx_head++] = SSPBUF ;
      rx_head &= BUFFER_MASK ;

      if (rx_len++ >= BUFFER_LEN)
        crashed = 1 ;  //flag error
    }
    else if (stat == 0x0c) // I2C? Read: Byte received was an address
    {
      while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full

            do
      {
              SSPCON.F7 = 0 ; // Clear the write collision flag
        SSPBUF = 0xaa ; // Just return AA for the time being
      } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
    }
    else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
    {
      while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full

      do
      {
        SSPCON.F7 = 0 ; // Clear the write collision flag
        SSPBUF = 0x55 ; // Just return 55 for the time being
      } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
    }
    else  // NACK received 0x28 - or another condition - Do a reset
    {
      last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
      SSPCON.F5=0;    // Temporarily disable
      SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                      // F6=0 SSPOV Receive Overflow Indicator bit
                      // F5=1 Synchronous Serial Port Enable
                      // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                      // F3-0=0110: I2C? slave mode: 7 bits address
      SSPSTAT = 0 ;   // Clear all status bits
    }

    SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
    PIR1?.F3 = 0 ;   // Clear the interrupt
  }
  else // unknown interrupt!!
  {
    crashed = 3;
    info = PIR1? ;
  }

} //==============================================================================

  1. define CLEAR_AND_FIRST_ROW 0x01
  2. define FIRST_ROW 0x02
  3. define SECOND_ROW (0x80 | 0x40)
  4. define THIRD_ROW (0x80 | 0x14)
  5. define FOURTH_ROW (0x80 | 0x54)

unsigned char line, column ;

void linefeed () {

  unsigned char cmd, i ;

  // Blank the appropriate line.
  switch (line)
  {
    case 1:  cmd = FIRST_ROW ; break ;
    case 2:  cmd = SECOND_ROW ; break ;
    case 3:  cmd = THIRD_ROW ; break ;
    default: cmd = FOURTH_ROW ; break ;
  }

  write_byte_to_lcd (cmd, 0); // Go to start of line

  for (i=0 ; i<COLUMNS ; i++)      // Blank the entire line
    write_byte_to_lcd (' ', 1);

  write_byte_to_lcd (cmd, 0); // Return to start of line
  column = 0 ;

}

void printchar (const unsigned char c) {

  if (c == 0)
  {
    // Clear display entirely
    write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
    line = 1 ;
    column = 0 ;
  }
  else if (c == '\n')
  {
    // linefeed and wrap around to line 0
    line++ ;

    if (line > LINES)
      line = 1 ;

    linefeed() ;
  }
  else if (c < 5)
  {
    // move to line 1..4
    line = c ;
    linefeed() ;
  }
  else if (c == '\r')
  {
    linefeed() ;
  }
  else
  {
    if (column >= COLUMNS)
    {
      // need to force a linefeed
      line++ ;

      if (line > LINES)
        line = 1 ;

      linefeed() ;
    }

    // Display the numbered character
    write_byte_to_lcd (c, 1);
    column++ ;
  }

}

// Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!! void enqueue_string (const unsigned char *s) {

  while (*s)
  {
    PIE1?.F3 = 0;  // Disable int F3=I2C? int
    rxbuf[rx_head++] = *s++ ;
    rx_head &= BUFFER_MASK ;
    rx_len++ ;
    PIE1?.F3 = 1;  // Enable int F3=I2C? int

    if (rx_len > BUFFER_LEN)
    {
      crashed = 4 ;  //flag error
      return ;
    }
  }

}

void enqueue_number (const unsigned char c) {

  unsigned char text[4] ;
  unsigned char *s ;

  ByteToStr? (c, text);

  for (s=text ; *s ; s++)
  {
    if (*s ==  ' ')
      continue ;

    rxbuf[rx_head++] = *s ;
    rx_head &= BUFFER_MASK ;
    rx_len++ ;

    if (rx_len > BUFFER_LEN)
      crashed = 5 ;  //flag error
  }

} //==============================================================================

  1. define MAGIC 0x3d
  2. define I2C?_ADDRESS 0x32

unsigned char num_boots ; unsigned char magic ; // Will hold a magic number so we know a cold boot

void main() {

  char i ;

  // Osc = bits 6,5,4
  //000 = 31.25 kHz
  //001 = 125 kHz
  //010 = 250 kHz
  //011 = 500 kHz
  //100 = 1 MHz?
  //101 = 2 MHz?
  //110 = 4 MHz?
  //111 = 8 MHz?
  OSCCON=0x7c; // Internal osc: 0x2c=0.250MHz, 0x4c=1MHz, 0x5c=2MHz, 0x7c=8MHz
  while (OSCCON.F2 == 0) ; // Wait for osc to stabilise

restart:

  OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
  INTCON=0;        //Disable interrupts
  PIR1?=0;          //Clear all interrupt flags

  TRISA=0;         //Set Port A to all outputs
  TRISB=0x12;      //B4 and B1 are inputs (I2C?)
  PORTA=0x00;
  PORTB=0x00;

  WDTCON =0x17 ;   // Enable watchdog with max timeout value = approx 2s

  ANSEL=0; //0 = PortA? bit is used for digital I/O
  ADCON1?=0x07 ; // Disable A/D

  SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                  // F6=0 SSPOV Receive Overflow Indicator bit
                  // F5=1 Synchronous Serial Port Enable
                  // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                  // F3-0=0110: I2C? slave mode: 7 bits address
  SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
  SSPSTAT = 0 ;   // Clear all status bits
  PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int

  if (magic != MAGIC)
  {
    crash_cause=0 ;  // used for debugging interrupt routine with print statements
    num_boots = 0 ;
    magic = MAGIC ;
  }
  else
    num_boots++ ;

  crashed = 0 ;   // We're not crashed at the moment!
  rx_head = rx_tail = rx_len = 0 ;

  INTCON.F6 = 1 ; // enable peripheral interrupts
  INTCON.F7 = 1 ; // enable all unmasked interrupts

  TXSTA = 0 ;     // Turn off transmit from UART
  RCSTA = 0 ;
  CMCON = 0x07 ;  // Disable comparators

  Delay_ms (100); // Provide a little time for the LCD to initialise
  asm {
    CLRWDT        // Clear the watchdog timer
  }

  write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
  Delay_ms (5);               // Wait at least 4.1ms
  write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
  Delay_ms (1);               // Wait at least 100us
  write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
  Delay_ms (1);               // Wait at least 100us
  write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
  Delay_ms (1);               // Wait at least 100us

  write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
  write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
  write_byte_to_lcd (0x01, 0); //Clear display
  write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
  write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
  line = 1;
  column = 0 ;

  // Display the welcome message
  enqueue_number (LINES) ;
  enqueue_string ("x") ;
  enqueue_number (COLUMNS) ;
  enqueue_string (" lcd @addr ") ;
  enqueue_number (I2C?_ADDRESS) ;
  enqueue_string ("\nVersion 1. SKT");

  // Give some indication if there's been a crash
  if (num_boots)
  {
    enqueue_string ("\rBoot count: ");
    enqueue_number (num_boots) ;
  }

  for (;;)
  {
    asm {
      CLRWDT  // clear the watchdog timer
    }

    if (rx_len)
    {
      printchar (rxbuf[rx_tail++]) ;
      rx_tail &= BUFFER_MASK ;
      PIE1?.F3 = 0;  // Disable int F3=I2C? int
      rx_len-- ;
      PIE1?.F3 = 1;  // Enable int F3=I2C? int
    }

    if (crashed)
    {
      crash_cause = crashed ; // Remember why we crashed
      write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
      string_to_lcd ("Cause:") ;
      hexbyte_to_lcd (crash_cause);
      string_to_lcd (" last:") ;
      hexbyte_to_lcd (last_i2c) ;
      string_to_lcd (" info:") ;
      hexbyte_to_lcd (info);
      string_to_lcd (" boots:") ;
      hexbyte_to_lcd (num_boots);

      write_byte_to_lcd (SECOND_ROW, 0);
      string_to_lcd ("Buf len:") ;
      decbyte_to_lcd (rx_len);
      write_byte_to_lcd (' ', 1);

      for (i=0 ; i < 14 ; i++)
        hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);

      // The following kludge is to get around the non-reentrant version of C
      // If there's an error inside the Interrupt, this flag will be set
      // That lets us display something - and this loop will cause an infinte wait

// while (crashed) ; // Infinite loop if an error has been set in the ISR

                        // This will trip the watchdog timer some time later

      // Delay for 15 seconds
      for (i=0 ; i < 150 ; i++)
      {
        Delay_ms (100);
        asm {
          CLRWDT         // clear the watchdog timer
        }
      }

      goto restart ;
    }
  }

}]

to:
// 16F88 programme to drive a hitachi compatible LCD display from the
// i2C bus
//
// There are plenty of spare inputs and outputs for additional functions
//////////////////////////////////////////////////////////////////////
// PORT A bit 0 = D4 on LCD
// PORT A bit 1 = D5 on LCD
// PORT A bit 2 = D6 on LCD
// PORT A bit 3 = D7 on LCD
// PORT A bit 4 = NC
// PORT A bit 5 = pulled high with 10k
// PORT A bit 6 = NC
// PORT A bit 7 = NC
//
// PORT B bit 0 = E on LCD
// PORT B bit 1 = SD on I2C?
// PORT B bit 2 = RW on LCD
// PORT B bit 3 = RS on LCD
// PORT B bit 4 = CK on I2C?
// PORT B bit 5 = NC
// PORT B bit 6 = NC
// PORT B bit 7 = NC
// Fx is mikroC nomenclature for bit x
#define RS_BIT F3
#define WR_BIT F2
#define E_BIT F0
// Define the number of lines and columns on the LCD display
#define LINES 4
#define COLUMNS 20
// counters for debugging purposes
unsigned char crashed, crash_cause, info, last_i2c ;
//////////////////////////////////////////////////////////////////////
void
write_nibble_to_lcd (const unsigned char c, const unsigned char RS)
{
TRISA.F0 = 0; //Set Bits 3-0 as OUTPUTS
TRISA.F1 = 0;
TRISA.F2 = 0;
TRISA.F3 = 0;
PORTA = c ; //Bits 3-0 = high order data nibble
PORTB.E_BIT = 0 ;
if (RS)
PORTB.RS_BIT = 1 ;
else
PORTB.RS_BIT = 0 ;
PORTB.WR_BIT = 0 ;
Delay_us (1);
PORTB.E_BIT = 1 ; //E High, WR low, RS set to selected value
Delay_us (1);
PORTB.E_BIT = 0 ;
// Exit with E Low, WR Low, RS set to selected value
}
void
write_byte_to_lcd (const unsigned char c, const unsigned char RS)
{
unsigned char busy ;
TRISA.F0 = 1; //Set Bits 3-0 as INPUTS
TRISA.F1 = 1;
TRISA.F2 = 1;
TRISA.F3 = 1;
PORTB.E_BIT = 0 ;
PORTB.RS_BIT = 0 ;
PORTB.WR_BIT = 1 ;
Delay_us(1);
// Wait for LCD to become unbusy
// BUSY FLAG polled on falling edge of E
do // Need to set E high and low twice - two four bit reads
{
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
busy = PORTA ; //Read the busy flag in bit 3 (bit 7 of LCD)
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
PORTB.E_BIT = 1; //WR High=Read, E High
Delay_us(1);
PORTB.E_BIT = 0 ; //WR High, E Clocks low
Delay_us(1);
} while (busy.F3);
// Now the LCD is ready, write byte in two 4 bit nibbles
write_nibble_to_lcd (c >> 4, RS) ;
write_nibble_to_lcd (c & 0x0f, RS) ;
// Exit with E Low, WR Low, RS set to selected value
}
void
string_to_lcd (const unsigned char *s)
{
while (*s)
write_byte_to_lcd (*s++, 1) ;
}
void
decbyte_to_lcd (unsigned char c)
{
char *s ;
unsigned char text[4] ;
ByteToStr? (c, text);
for (s=text; *s ;)
write_byte_to_lcd (*s++, 1) ;
}
void
hexnibble_to_lcd (unsigned char c)
{
c &= 0x0f ;
if (c > 9)
c += 'A' - 10 ;
else
c += '0' ;
write_byte_to_lcd (c, 1) ;
}
void hexbyte_to_lcd (unsigned char c)
{
hexnibble_to_lcd (c >> 4) ;
hexnibble_to_lcd (c) ;
}
//==============================================================================
// BUFFER_POWER must be >= 2
#define BUFFER_LEN 64
#define BUFFER_MASK (BUFFER_LEN-1)
unsigned char rxbuf [BUFFER_LEN] ;
unsigned char rx_head, rx_tail, rx_len ;
void
interrupt ()
{
unsigned char stat;
if (PIR1?.F3 == 1) // is this an i2c interrupt
{
SSPCON.F4 = 0 ; // Hold clock low - (ie stretch clock while we work)
stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
// 5=(1=data,0=addr), 3=(1=start detected),
// 2=(1=read,0=write), 0=(1=sspbuf full)
if (stat == 0x09) // I2C? Write: Byte received was an address
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
}
else if (stat == 0x29) // I2C? Write: Byte received was data
{
rxbuf[rx_head++] = SSPBUF ;
rx_head &= BUFFER_MASK ;
if (rx_len++ >= BUFFER_LEN)
crashed = 1 ; //flag error
}
else if (stat == 0x0c) // I2C? Read: Byte received was an address
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0xaa ; // Just return AA for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
{
while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full
do
{
SSPCON.F7 = 0 ; // Clear the write collision flag
SSPBUF = 0x55 ; // Just return 55 for the time being
} while (SSPCON.F7 == 1) ; // Loop if there was a write collision
}
else // NACK received 0x28 - or another condition - Do a reset
{
last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
SSPCON.F5=0; // Temporarily disable
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPSTAT = 0 ; // Clear all status bits
}
SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
PIR1?.F3 = 0 ; // Clear the interrupt
}
else // unknown interrupt!!
{
crashed = 3;
info = PIR1? ;
}
}
//==============================================================================
#define CLEAR_AND_FIRST_ROW 0x01
#define FIRST_ROW 0x02
#define SECOND_ROW (0x80 | 0x40)
#define THIRD_ROW (0x80 | 0x14)
#define FOURTH_ROW (0x80 | 0x54)
unsigned char line, column ;
void
linefeed ()
{
unsigned char cmd, i ;
// Blank the appropriate line.
switch (line)
{
case 1: cmd = FIRST_ROW ; break ;
case 2: cmd = SECOND_ROW ; break ;
case 3: cmd = THIRD_ROW ; break ;
default: cmd = FOURTH_ROW ; break ;
}
write_byte_to_lcd (cmd, 0); // Go to start of line
for (i=0 ; i<COLUMNS ; i++) // Blank the entire line
write_byte_to_lcd (' ', 1);
write_byte_to_lcd (cmd, 0); // Return to start of line
column = 0 ;
}
void
printchar (const unsigned char c)
{
if (c == 0)
{
// Clear display entirely
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1 ;
column = 0 ;
}
else if (c == '\n')
{
// linefeed and wrap around to line 0
line++ ;
if (line > LINES)
line = 1 ;
linefeed() ;
}
else if (c < 5)
{
// move to line 1..4
line = c ;
linefeed() ;
}
else if (c == '\r')
{
linefeed() ;
}
else
{
if (column >= COLUMNS)
{
// need to force a linefeed
line++ ;
if (line > LINES)
line = 1 ;
linefeed() ;
}
// Display the numbered character
write_byte_to_lcd (c, 1);
column++ ;
}
}
// Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!!
void
enqueue_string (const unsigned char *s)
{
while (*s)
{
PIE1?.F3 = 0; // Disable int F3=I2C? int
rxbuf[rx_head++] = *s++ ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
if (rx_len > BUFFER_LEN)
{
crashed = 4 ; //flag error
return ;
}
}
}
void
enqueue_number (const unsigned char c)
{
unsigned char text[4] ;
unsigned char *s ;
ByteToStr? (c, text);
for (s=text ; *s ; s++)
{
if (*s == ' ')
continue ;
rxbuf[rx_head++] = *s ;
rx_head &= BUFFER_MASK ;
rx_len++ ;
if (rx_len > BUFFER_LEN)
crashed = 5 ; //flag error
}
}
//==============================================================================
#define MAGIC 0x3d
#define I2C?_ADDRESS 0x32
unsigned char num_boots ;
unsigned char magic ; // Will hold a magic number so we know a cold boot
void
main()
{
char i ;
// Osc = bits 6,5,4
//000 = 31.25 kHz
//001 = 125 kHz
//010 = 250 kHz
//011 = 500 kHz
//100 = 1 MHz?
//101 = 2 MHz?
//110 = 4 MHz?
//111 = 8 MHz?
OSCCON=0x7c; // Internal osc: 0x2c=0.250MHz, 0x4c=1MHz, 0x5c=2MHz, 0x7c=8MHz
while (OSCCON.F2 == 0) ; // Wait for osc to stabilise
restart:
OPTION_REG=0x03; //0x03=Prescaler=1:256 0x80=low..pullup enabled
INTCON=0; //Disable interrupts
PIR1?=0; //Clear all interrupt flags
TRISA=0; //Set Port A to all outputs
TRISB=0x12; //B4 and B1 are inputs (I2C?)
PORTA=0x00;
PORTB=0x00;
WDTCON =0x17 ; // Enable watchdog with max timeout value = approx 2s
ANSEL=0; //0 = PortA? bit is used for digital I/O
ADCON1?=0x07 ; // Disable A/D
SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
// F6=0 SSPOV Receive Overflow Indicator bit
// F5=1 Synchronous Serial Port Enable
// F4=1 When set at 0, I2C? clock pin held low to stretch clock
// F3-0=0110: I2C? slave mode: 7 bits address
SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
SSPSTAT = 0 ; // Clear all status bits
PIE1?.F3 = 1; // F6=ADC int, F3=I2C? int
if (magic != MAGIC)
{
crash_cause=0 ; // used for debugging interrupt routine with print statements
num_boots = 0 ;
magic = MAGIC ;
}
else
num_boots++ ;
crashed = 0 ; // We're not crashed at the moment!
rx_head = rx_tail = rx_len = 0 ;
INTCON.F6 = 1 ; // enable peripheral interrupts
INTCON.F7 = 1 ; // enable all unmasked interrupts
TXSTA = 0 ; // Turn off transmit from UART
RCSTA = 0 ;
CMCON = 0x07 ; // Disable comparators
Delay_ms (100); // Provide a little time for the LCD to initialise
asm {
CLRWDT // Clear the watchdog timer
}
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (5); // Wait at least 4.1ms
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
Delay_ms (1); // Wait at least 100us
write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
Delay_ms (1); // Wait at least 100us
write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
write_byte_to_lcd (0x01, 0); //Clear display
write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
line = 1;
column = 0 ;
// Display the welcome message
enqueue_number (LINES) ;
enqueue_string ("x") ;
enqueue_number (COLUMNS) ;
enqueue_string (" lcd @addr ") ;
enqueue_number (I2C?_ADDRESS) ;
enqueue_string ("\nVersion 1. SKT");
// Give some indication if there's been a crash
if (num_boots)
{
enqueue_string ("\rBoot count: ");
enqueue_number (num_boots) ;
}
for (;;)
{
asm {
CLRWDT // clear the watchdog timer
}
if (rx_len)
{
printchar (rxbuf[rx_tail++]) ;
rx_tail &= BUFFER_MASK ;
PIE1?.F3 = 0; // Disable int F3=I2C? int
rx_len-- ;
PIE1?.F3 = 1; // Enable int F3=I2C? int
}
if (crashed)
{
crash_cause = crashed ; // Remember why we crashed
write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
string_to_lcd ("Cause:") ;
hexbyte_to_lcd (crash_cause);
string_to_lcd (" last:") ;
hexbyte_to_lcd (last_i2c) ;
string_to_lcd (" info:") ;
hexbyte_to_lcd (info);
string_to_lcd (" boots:") ;
hexbyte_to_lcd (num_boots);
write_byte_to_lcd (SECOND_ROW, 0);
string_to_lcd ("Buf len:") ;
decbyte_to_lcd (rx_len);
write_byte_to_lcd (' ', 1);
for (i=0 ; i < 14 ; i++)
hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);
// The following kludge is to get around the non-reentrant version of C
// If there's an error inside the Interrupt, this flag will be set
// That lets us display something - and this loop will cause an infinte wait
// while (crashed) ; // Infinite loop if an error has been set in the ISR
// This will trip the watchdog timer some time later
// Delay for 15 seconds
for (i=0 ; i < 150 ; i++)
{
Delay_ms (100);
asm {
CLRWDT // clear the watchdog timer
}
}
goto restart ;
}
}
}
June 14, 2008, at 03:13 AM by stefan keller-tuberg --
Changed lines 13-14 from:

You need to specify the dimensions of your LCD display and the i2c address you'd like to allocate using #defines near the top of the code.

to:

You need to specify the dimensions of your LCD display and the i2c address you'd like to allocate using #defines near the top of the code.

June 14, 2008, at 03:12 AM by stefan keller-tuberg --
Changed lines 15-16 from:
// 16F88 programme to drive a hitachi compatible LCD display from the
// i2C bus
to:
@@// 16F88 programme to drive a hitachi compatible LCD display from the
@@// i2C bus
June 14, 2008, at 03:11 AM by stefan keller-tuberg --
Changed lines 13-15 from:

[ // 16F88 programme to drive a hitachi compatible LCD display from the // i2C bus

to:

You need to specify the dimensions of your LCD display and the i2c address you'd like to allocate using #defines near the top of the code.

// 16F88 programme to drive a hitachi compatible LCD display from the
// i2C bus
June 14, 2008, at 03:09 AM by stefan keller-tuberg --
Changed line 13 from:

[@

to:

[

Changed line 36 from:
to:

]

June 14, 2008, at 03:08 AM by stefan keller-tuberg --
Changed line 13 from:

[@echo off

to:

[@

June 14, 2008, at 03:08 AM by stefan keller-tuberg --
Changed lines 13-14 from:

[@echo off// 16F88 programme to drive a hitachi compatible LCD display from the

to:

[@echo off // 16F88 programme to drive a hitachi compatible LCD display from the

June 14, 2008, at 03:07 AM by stefan keller-tuberg --
Changed lines 11-503 from:
to:

I've written assembler code for the 16F88 before and frankly, its a pain to debug. This time I decided that I'd have a crack at writing the LCD interface in C. There's several freely downloadable C compilers for the PIC available and I chose to use one called MikroC?, available at [[http://www.mikroe.com/en/download]the mikroElektronika] web site.

[@echo off// 16F88 programme to drive a hitachi compatible LCD display from the // i2C bus // // There are plenty of spare inputs and outputs for additional functions ////////////////////////////////////////////////////////////////////// // PORT A bit 0 = D4 on LCD // PORT A bit 1 = D5 on LCD // PORT A bit 2 = D6 on LCD // PORT A bit 3 = D7 on LCD // PORT A bit 4 = NC // PORT A bit 5 = pulled high with 10k // PORT A bit 6 = NC // PORT A bit 7 = NC // // PORT B bit 0 = E on LCD // PORT B bit 1 = SD on I2C? // PORT B bit 2 = RW on LCD // PORT B bit 3 = RS on LCD // PORT B bit 4 = CK on I2C? // PORT B bit 5 = NC // PORT B bit 6 = NC // PORT B bit 7 = NC

// Fx is mikroC nomenclature for bit x

  1. define RS_BIT F3
  2. define WR_BIT F2
  3. define E_BIT F0

// Define the number of lines and columns on the LCD display

  1. define LINES 4
  2. define COLUMNS 20

// counters for debugging purposes unsigned char crashed, crash_cause, info, last_i2c ; ////////////////////////////////////////////////////////////////////// void write_nibble_to_lcd (const unsigned char c, const unsigned char RS) {

  TRISA.F0 = 0;      //Set Bits 3-0 as OUTPUTS
  TRISA.F1 = 0;
  TRISA.F2 = 0;
  TRISA.F3 = 0;
  PORTA = c ;        //Bits 3-0 = high order data nibble

  PORTB.E_BIT = 0 ;

  if (RS)
    PORTB.RS_BIT = 1 ;
  else
    PORTB.RS_BIT = 0 ;

  PORTB.WR_BIT = 0 ;
  Delay_us (1);
  PORTB.E_BIT = 1 ;  //E High, WR low, RS set to selected value
  Delay_us (1);
  PORTB.E_BIT = 0 ;

  // Exit with E Low, WR Low, RS set to selected value

}

void write_byte_to_lcd (const unsigned char c, const unsigned char RS) {

  unsigned char busy ;

  TRISA.F0 = 1;       //Set Bits 3-0 as INPUTS
  TRISA.F1 = 1;
  TRISA.F2 = 1;
  TRISA.F3 = 1;

  PORTB.E_BIT = 0 ;
  PORTB.RS_BIT = 0 ;
  PORTB.WR_BIT = 1 ;
  Delay_us(1);

  // Wait for LCD to become unbusy
  // BUSY FLAG polled on falling edge of E

  do  // Need to set E high and low twice - two four bit reads
  {
    PORTB.E_BIT = 1;    //WR High=Read, E High
    Delay_us(1);
    busy = PORTA ;      //Read the busy flag in bit 3 (bit 7 of LCD)
    PORTB.E_BIT = 0 ;   //WR High, E Clocks low
    Delay_us(1);
    PORTB.E_BIT = 1;    //WR High=Read, E High
    Delay_us(1);
    PORTB.E_BIT = 0 ;   //WR High, E Clocks low
    Delay_us(1);
  } while (busy.F3);

  // Now the LCD is ready, write byte in two 4 bit nibbles
  write_nibble_to_lcd (c >> 4, RS) ;
  write_nibble_to_lcd (c & 0x0f, RS) ;

  // Exit with E Low, WR Low, RS set to selected value

}

void string_to_lcd (const unsigned char *s) {

  while (*s)
    write_byte_to_lcd (*s++, 1) ;

}

void decbyte_to_lcd (unsigned char c) {

  char *s ;
  unsigned char text[4] ;

  ByteToStr? (c, text);

  for (s=text; *s ;)
    write_byte_to_lcd (*s++, 1) ;

}

void hexnibble_to_lcd (unsigned char c) {

  c &= 0x0f ;

  if (c > 9)
      c += 'A' - 10 ;
  else
    c += '0' ;

  write_byte_to_lcd (c, 1) ;

}

void hexbyte_to_lcd (unsigned char c) {

   hexnibble_to_lcd (c >> 4) ;
   hexnibble_to_lcd (c) ;

} //============================================================================== // BUFFER_POWER must be >= 2

  1. define BUFFER_LEN 64
  2. define BUFFER_MASK (BUFFER_LEN-1)

unsigned char rxbuf [BUFFER_LEN] ; unsigned char rx_head, rx_tail, rx_len ;

void interrupt () {

  unsigned char stat;

  if (PIR1?.F3 == 1) // is this an i2c interrupt
  {
    SSPCON.F4 = 0 ;         // Hold clock low - (ie stretch clock while we work)
    stat = SSPSTAT & 0x2d ; // mask out bits we're not interestted in
                            // 5=(1=data,0=addr), 3=(1=start detected),
                            // 2=(1=read,0=write), 0=(1=sspbuf full)

    if (stat == 0x09) // I2C? Write: Byte received was an address
    {
      last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
    }
    else if (stat == 0x29) // I2C? Write: Byte received was data
    {
      rxbuf[rx_head++] = SSPBUF ;
      rx_head &= BUFFER_MASK ;

      if (rx_len++ >= BUFFER_LEN)
        crashed = 1 ;  //flag error
    }
    else if (stat == 0x0c) // I2C? Read: Byte received was an address
    {
      while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full

            do
      {
              SSPCON.F7 = 0 ; // Clear the write collision flag
        SSPBUF = 0xaa ; // Just return AA for the time being
      } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
    }
    else if (stat == 0x2c) // I2C? Read: Byte to be transmitted will be data
    {
      while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full

      do
      {
        SSPCON.F7 = 0 ; // Clear the write collision flag
        SSPBUF = 0x55 ; // Just return 55 for the time being
      } while (SSPCON.F7 == 1) ; // Loop if there was a write collision
    }
    else  // NACK received 0x28 - or another condition - Do a reset
    {
      last_i2c = SSPBUF ; // Do a dummy read of the sspbuf
      SSPCON.F5=0;    // Temporarily disable
      SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                      // F6=0 SSPOV Receive Overflow Indicator bit
                      // F5=1 Synchronous Serial Port Enable
                      // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                      // F3-0=0110: I2C? slave mode: 7 bits address
      SSPSTAT = 0 ;   // Clear all status bits
    }

    SSPCON.F4 = 1 ; // Release the clock (finish stretching it)
    PIR1?.F3 = 0 ;   // Clear the interrupt
  }
  else // unknown interrupt!!
  {
    crashed = 3;
    info = PIR1? ;
  }

} //==============================================================================

  1. define CLEAR_AND_FIRST_ROW 0x01
  2. define FIRST_ROW 0x02
  3. define SECOND_ROW (0x80 | 0x40)
  4. define THIRD_ROW (0x80 | 0x14)
  5. define FOURTH_ROW (0x80 | 0x54)

unsigned char line, column ;

void linefeed () {

  unsigned char cmd, i ;

  // Blank the appropriate line.
  switch (line)
  {
    case 1:  cmd = FIRST_ROW ; break ;
    case 2:  cmd = SECOND_ROW ; break ;
    case 3:  cmd = THIRD_ROW ; break ;
    default: cmd = FOURTH_ROW ; break ;
  }

  write_byte_to_lcd (cmd, 0); // Go to start of line

  for (i=0 ; i<COLUMNS ; i++)      // Blank the entire line
    write_byte_to_lcd (' ', 1);

  write_byte_to_lcd (cmd, 0); // Return to start of line
  column = 0 ;

}

void printchar (const unsigned char c) {

  if (c == 0)
  {
    // Clear display entirely
    write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
    line = 1 ;
    column = 0 ;
  }
  else if (c == '\n')
  {
    // linefeed and wrap around to line 0
    line++ ;

    if (line > LINES)
      line = 1 ;

    linefeed() ;
  }
  else if (c < 5)
  {
    // move to line 1..4
    line = c ;
    linefeed() ;
  }
  else if (c == '\r')
  {
    linefeed() ;
  }
  else
  {
    if (column >= COLUMNS)
    {
      // need to force a linefeed
      line++ ;

      if (line > LINES)
        line = 1 ;

      linefeed() ;
    }

    // Display the numbered character
    write_byte_to_lcd (c, 1);
    column++ ;
  }

}

// Enqueue_string will print anything BUT NOT the \000 CLEAR command!!!! void enqueue_string (const unsigned char *s) {

  while (*s)
  {
    PIE1?.F3 = 0;  // Disable int F3=I2C? int
    rxbuf[rx_head++] = *s++ ;
    rx_head &= BUFFER_MASK ;
    rx_len++ ;
    PIE1?.F3 = 1;  // Enable int F3=I2C? int

    if (rx_len > BUFFER_LEN)
    {
      crashed = 4 ;  //flag error
      return ;
    }
  }

}

void enqueue_number (const unsigned char c) {

  unsigned char text[4] ;
  unsigned char *s ;

  ByteToStr? (c, text);

  for (s=text ; *s ; s++)
  {
    if (*s ==  ' ')
      continue ;

    rxbuf[rx_head++] = *s ;
    rx_head &= BUFFER_MASK ;
    rx_len++ ;

    if (rx_len > BUFFER_LEN)
      crashed = 5 ;  //flag error
  }

} //==============================================================================

  1. define MAGIC 0x3d
  2. define I2C?_ADDRESS 0x32

unsigned char num_boots ; unsigned char magic ; // Will hold a magic number so we know a cold boot

void main() {

  char i ;

  // Osc = bits 6,5,4
  //000 = 31.25 kHz
  //001 = 125 kHz
  //010 = 250 kHz
  //011 = 500 kHz
  //100 = 1 MHz?
  //101 = 2 MHz?
  //110 = 4 MHz?
  //111 = 8 MHz?
  OSCCON=0x7c; // Internal osc: 0x2c=0.250MHz, 0x4c=1MHz, 0x5c=2MHz, 0x7c=8MHz
  while (OSCCON.F2 == 0) ; // Wait for osc to stabilise

restart:

  OPTION_REG=0x03; //0x03=Prescaler=1:256   0x80=low..pullup enabled
  INTCON=0;        //Disable interrupts
  PIR1?=0;          //Clear all interrupt flags

  TRISA=0;         //Set Port A to all outputs
  TRISB=0x12;      //B4 and B1 are inputs (I2C?)
  PORTA=0x00;
  PORTB=0x00;

  WDTCON =0x17 ;   // Enable watchdog with max timeout value = approx 2s

  ANSEL=0; //0 = PortA? bit is used for digital I/O
  ADCON1?=0x07 ; // Disable A/D

  SSPCON = 0x36 ; // F7=0 WCOL Write Collision Detect bit
                  // F6=0 SSPOV Receive Overflow Indicator bit
                  // F5=1 Synchronous Serial Port Enable
                  // F4=1 When set at 0, I2C? clock pin held low to stretch clock
                  // F3-0=0110: I2C? slave mode: 7 bits address
  SSPADD = I2C?_ADDRESS << 1 ; // 7 bit address in bits 7--1
  SSPSTAT = 0 ;   // Clear all status bits
  PIE1?.F3 = 1;    // F6=ADC int, F3=I2C? int

  if (magic != MAGIC)
  {
    crash_cause=0 ;  // used for debugging interrupt routine with print statements
    num_boots = 0 ;
    magic = MAGIC ;
  }
  else
    num_boots++ ;

  crashed = 0 ;   // We're not crashed at the moment!
  rx_head = rx_tail = rx_len = 0 ;

  INTCON.F6 = 1 ; // enable peripheral interrupts
  INTCON.F7 = 1 ; // enable all unmasked interrupts

  TXSTA = 0 ;     // Turn off transmit from UART
  RCSTA = 0 ;
  CMCON = 0x07 ;  // Disable comparators

  Delay_ms (100); // Provide a little time for the LCD to initialise
  asm {
    CLRWDT        // Clear the watchdog timer
  }

  write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
  Delay_ms (5);               // Wait at least 4.1ms
  write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
  Delay_ms (1);               // Wait at least 100us
  write_nibble_to_lcd (0x03,0); // Function set 8 bit mode
  Delay_ms (1);               // Wait at least 100us
  write_nibble_to_lcd (0x02,0); // NOW FINALLY Function set 4 bit mode
  Delay_ms (1);               // Wait at least 100us

  write_byte_to_lcd (0x28, 0); //And repeat, doing full 8 bits of function cmd
  write_byte_to_lcd (0x0c, 0); //Display ON (04), Curson ON(02), Blinking(01)
  write_byte_to_lcd (0x01, 0); //Clear display
  write_byte_to_lcd (0x06, 0); //Entry mode Increment(02) Noshift(01)
  write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
  line = 1;
  column = 0 ;

  // Display the welcome message
  enqueue_number (LINES) ;
  enqueue_string ("x") ;
  enqueue_number (COLUMNS) ;
  enqueue_string (" lcd @addr ") ;
  enqueue_number (I2C?_ADDRESS) ;
  enqueue_string ("\nVersion 1. SKT");

  // Give some indication if there's been a crash
  if (num_boots)
  {
    enqueue_string ("\rBoot count: ");
    enqueue_number (num_boots) ;
  }

  for (;;)
  {
    asm {
      CLRWDT  // clear the watchdog timer
    }

    if (rx_len)
    {
      printchar (rxbuf[rx_tail++]) ;
      rx_tail &= BUFFER_MASK ;
      PIE1?.F3 = 0;  // Disable int F3=I2C? int
      rx_len-- ;
      PIE1?.F3 = 1;  // Enable int F3=I2C? int
    }

    if (crashed)
    {
      crash_cause = crashed ; // Remember why we crashed
      write_byte_to_lcd (CLEAR_AND_FIRST_ROW, 0);
      string_to_lcd ("Cause:") ;
      hexbyte_to_lcd (crash_cause);
      string_to_lcd (" last:") ;
      hexbyte_to_lcd (last_i2c) ;
      string_to_lcd (" info:") ;
      hexbyte_to_lcd (info);
      string_to_lcd (" boots:") ;
      hexbyte_to_lcd (num_boots);

      write_byte_to_lcd (SECOND_ROW, 0);
      string_to_lcd ("Buf len:") ;
      decbyte_to_lcd (rx_len);
      write_byte_to_lcd (' ', 1);

      for (i=0 ; i < 14 ; i++)
        hexbyte_to_lcd (rxbuf[(rx_tail+i)&BUFFER_MASK]);

      // The following kludge is to get around the non-reentrant version of C
      // If there's an error inside the Interrupt, this flag will be set
      // That lets us display something - and this loop will cause an infinte wait

// while (crashed) ; // Infinite loop if an error has been set in the ISR

                        // This will trip the watchdog timer some time later

      // Delay for 15 seconds
      for (i=0 ; i < 150 ; i++)
      {
        Delay_ms (100);
        asm {
          CLRWDT         // clear the watchdog timer
        }
      }

      goto restart ;
    }
  }

}]

June 14, 2008, at 03:01 AM by stefan keller-tuberg --
Added lines 1-15:

Interfacing to the LCD display

I wanted to interface a generic HD44780? based LCD display to my NSLU2. There are many different displays of different dimensions available meeting this standard, and its relatively easy to programme. These LCDs? are relatively cheap, starting at a few dollars for a small device up to a few tens of dollars for the larger ones.

When thinking about connecting any hardware peripheral to the NSLU2, the problem is there are no general purpose I/O pins available. I've seen projects using the USB interface to connect LCDs?, but only hints of projects using the I2C? bus. In this article, I'm going to detail how I implemented an I2C? LCD interface.

Typical LCDs? don't come with I2C? interfaces built in. However cheap PIC microcontrollers can implement an I2C? bus for a couple of dollars. I chose to use a 16F88, an 18 pin DIP with two eight bit ports available. My design uses two of these 16 I/O pins for the I2C? interface, seven for the LCD interface and leaves seven for you to implement other cool stuff such as A/D inputs, keypad reading inputs or general purpose outputs.

Firmware for a 16F88 PIC

Example code for the NSLU2

Other tips

view · edit · print · history · Last edited by stefan keller-tuberg.
Based on work by ByronT and stefan keller-tuberg.
Originally by stefan keller-tuberg.
Page last modified on July 04, 2008, at 07:12 AM