![]() |
Interfacing to the LCD displayI 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. Interfacing the I2C? to the NSLU2There 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. 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. <<INSERT PHOTO OF CIRCUIT DIAGRAM HERE - HOW DOES ONE UPLOAD A PHOTO??>> 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 4k7 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 4k7 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 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. Compiling an image with your I2C? code embeddedAs 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
Now its time to add your LCD code and compile it into the build
Programme to write to an LCD connected via I2C? to the NSLU2 (echo_lcd.c)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 #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 256 int main (int argc, char **argv) { 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 \%d characters: \%s\n", length, argv[i]) ; if (n != length) { printf("Write error! Returned \%d instead of \%d - \%s\n", n, length, strerror(errno)); exit (1); } } close (fd); return 0 ; } 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. #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 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); for (n=7 ; n < BUFSIZE ; n++) printf(", \%02x", buf[n]); printf ("\n"); } close (fd); return 0 ; } top-MakefileThis 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/rc.d
$(INSTALL_BIN) ./files/S* $(1)/etc/rc.d
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/read_i2c $(1)/bin/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/echo_lcd $(1)/bin/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/test_i2c $(1)/bin/
endef
$(eval $(call BuildPackage?,my-additions))
Note the section starting with src-MakefileThis 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 read_i2c 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)/bin/ echo_lcd: echo_lcd.o read_i2c: read_i2c.o Firmware for a 16F88 PICI'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(approve sites) web site. 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:
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 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). // 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 #define I2C?_ADDRESS 0x32 // Define the number of lines and columns on the LCD display #define LINES 4 #define COLUMNS 20 ////////////////////////////////////////////////////////////////////// // Copyright Stefan Keller-Tuberg (C) 2008 // // 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 // // PORT B bit 0 = E on LCD // PORT B bit 1 = SD on I2C? (I added my own 4k7 pullup to 3.3V - didn't work without) // PORT B bit 2 = RW on LCD // PORT B bit 3 = RS on LCD // 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 ////////////////////////////////////////////////////////////////////// // Fx is mikroC nomenclature for bit x // Define PORTB bits used for LCD control #define RS_BIT F3 #define WR_BIT F2 #define E_BIT F0 // 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 void write_nibble_to_lcd (const unsigned char c, const unsigned char RS) { 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 ; 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 } // Assumes Port A bits 7, 2-0 are the ONLY outputs (ie bits 6-3 are INPUTS) void write_byte_to_lcd (const unsigned char c, const unsigned char RS) { unsigned char busy ; TRISA.LCD4? = 1; TRISA.LCD5? = 1; TRISA.LCD6? = 1; TRISA.LCD7? = 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 7 (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.LCD7?); // Now the LCD is ready, write byte in two 4 bit nibbles write_nibble_to_lcd (c >> 4, RS) ; write_nibble_to_lcd (c, 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 ; // 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 ; 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 { tx_index = 1 ; // byte to be transmitted while (SSPSTAT.F0 == 1) ; // keep waiting while buffer full do { SSPCON.F7 = 0 ; // Clear the write collision flag SSPBUF = txbuf[0] ; // Return first byte of array } 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 = txbuf[tx_index] ; // Return next byte of array } while (SSPCON.F7 == 1) ; // Loop if there was a write collision tx_index++ ; // point to next byte in the array next time } 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 <= LINES) { // 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_char (unsigned char c) { 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) { crashed = 4 ; //flag error return ; } } void enqueue_string (const unsigned char *s) { while (*s) enqueue_char (*s++) ; } 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 } } void flush_display_buffer() { asm { CLRWDT // clear the watchdog timer } if (rx_len) { 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 } } //============================================================================== 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 } } void Long_Delay (unsigned char time) { unsigned char i ; for (i=0 ; i < time ; i++) { asm { CLRWDT // clear the watchdog timer } Delay_ms (100); } } //============================================================================== #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=SWITCH_MASK; 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 ((i.SW1? == 0) && (txbuf[SWITCHES].SW1? == 1)) // Switch 1 pressed txbuf[SWITCH1?]++ ; if ((i.SW2? == 0) && (txbuf[SWITCHES].SW2? == 1)) // Switch 2 pressed txbuf[SWITCH2?]++ ; if ((i.SW3? == 0) && (txbuf[SWITCHES].SW3? == 1)) // Switch 3 pressed txbuf[SWITCH3?]++ ; txbuf[SWITCH1?].F7 = i.SW1? ; txbuf[SWITCH2?].F7 = i.SW2? ; 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 ; } Check_ADC(); flush_display_buffer(); //Print any characters received on I2C? } } Big Tip: NSLU2 I2C? drivers can only drive short flying leads unless you pull them up externallyI 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.
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
|