USB driven LCD with the PIC 18F2455
Introduction
Here I will chronicle the design and implementation of a small hardware project I've undertaken in my spare time. The object is to build a USB device that consists of an LCD and possibly some small buttons for displaying text messages.I want to do this is so that I can look for wireless networks without taking my computer out. The LCD will just print information that will let me decide whether I want to stop and check out the network.
Update 2006-09-29: I finished this project several months ago. Since then I have had several requests for the source code, which is on Mark's site, but unfortunately is not open to anonymous access. So here it is (warning: no documentation): usblcd-0.1.tar.gzUpdates
2006-10-06 - Current application of the LCD The current application of this USB LCD is as a vocabulary helper while I learn French. Every minute a script updates the display with a random French word and provides the English translation from Google.
2006-03-26 - Finished After working out a few bugs and soldering together a prototype, the USB LCD device is finished. The project was a success. In hindsight, building a USB device is pretty easy, especially if you don't have to deal with writing the USB stack yourself. For this reason, the PIC 18F2455 was a good choice for the microcontroller. Currently the device is controlled via libusb in user space. There is a kernel driver in the Linux 2.4 and 2.6 trees called
usblcd.ko which supports the USB LCD devices from http://www.usblcd.de/. In order to support this driver, the firmware has to be modified to support two USB endpoints and the USB-LCD protocol. This won't be very difficult and I'll be working on that next.
The hardest part of this project was getting a PIC programmer to work. When I started I knew almost nothing about electronics. Because of this, building a programmer was almost as challenging as building the device itself. If I had bought a prebuilt programmer things would have been much easier. But I'm glad I didn't, because I learned a lot more.
The following pictures detail the fabrication of the device on a simple prototype board.
I soldered some pins taken from an old sound card onto the back of the LCD to make it easy to remove from the circuit.
The 18F2455-based circuit runs on a 4 MHz crystal with the LCD data bus and control lines mapped to a header from an old computer. This header is soldered close to the PCB to keep the LCD firmly connected. I took the small 4 MHz crystal from a defunct car clock. Wherever possible, I tried to make use of existing junk to build this device.
The small vice I'm using is actually a vice designed for tying flies for fly fishing. It fits a PCB very nicely!
The back of the 18F2455-based circuit. I'm getting a little better at soldering.
Here I am connecting the LCD to the header on the circuit board. One of the pins had to be bent out so it connected to the second row as I accidentally pulled one of the wires out completely while stripping it.
The LCD sits relatively firmly in the header. You can see the one pin that needed to be bent out to fit into the second row of the header.
The circuit board piggy backs the LCD nicely keeping the footprint of the device as small as possible.
The LCD control lines (grey) and data bus (blue). It turns out the data bus was wired backwards in this picture.
Adding VCC (red).
These garbage characters and unpredictable behaviour resulted from the backwards wiring of the data bus (see above).
It works much better with the data bus wired correctly.
I left the low-voltage in-system parallel port programmer soldered to the relevant PIC pins for easy re-flashing.
The C code used on the PC uses libusb to communicate with the device. The rudamentary code follows and was based from Lab4 on this site. The code looks through the list of connected USB devices searching for the correct vendor and product IDs. It then initialises/clears/sends text. For low-speed USB devices, the maximum packet size is 8 bytes. In order to print a string of text to the LCD, the text has to be broken up into 8 byte chunks and sent to the device using usb_control_msg(). Typically I think you'd want to add a separate USB endpoint to the device and call usb_bulk_write() for this, but I kept it simple by using the existing default endpoint.
The code is compiled using gcc lcdprint.c -o lcdprint -lusb. This code and the PIC 18F2455 firmware is available through Subversion at Mark's site (https://svn.digitalspace.ca/pic/).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <usb.h>
#define FALSE 0
#define TRUE 1
#define LCD_INIT 0x01
#define LCD_CLEAR 0x02
#define LCD_PRINT 0x03
#define LCD_GOTOXY 0x04
int min(int x, int y)
{
if(x < y) return x;
return y;
}
int main(int argc, char *argv[]) {
struct usb_bus *bus;
struct usb_device *dev;
usb_dev_handle *udev;
int dev_found, ret, i, len, n;
char *ptr, buffer[8];
char message[256];
int init = 0, clear = 1;
if (argc != 2) {
printf("Usage: %s message\n", argv[0]);
exit(-1);
}
// preprocess message to parse \n
n=0;
for(i=0; inext) {
for (dev = bus->devices; dev && !dev_found; dev = dev->next) {
if ((dev->descriptor.idVendor == 0x10D2) && (dev->descriptor.idProduct == 0x0001)) {
dev_found = TRUE;
udev = usb_open(dev);
}
}
}
if (!dev_found) {
printf("No matching device found...\n");
}
if (udev) {
if(init)
{
// Init
ret = usb_control_msg(udev, 0x40, LCD_INIT, 0, 0, 0, 0, 0);
if (ret < 0) {
printf("Unable to send init, ret = %d...\n", ret);
}
}
if(clear)
{
// Clear the LCD
ret = usb_control_msg(udev, 0x40, LCD_CLEAR, 0, 0, 0, 0, 0);
if (ret < 0) {
printf("Unable to send clear, ret = %d...\n", ret);
}
}
// Print the text
ptr = message;
while(message + strlen(message) - ptr > 0)
{
len = min(message + strlen(message) - ptr, 8);
memset(buffer,0,8);
strncpy(buffer, ptr, len);
printf("%d: %s\n",len,buffer);
ret = usb_control_msg(udev, 0x40, LCD_PRINT, 0, 0, buffer, len, 0);
if (ret < 0) {
printf("Unable to send print, ret = %d...\n", ret);
}
ptr += 8;
}
usb_close(udev);
}
}
Here is the finished device in use as an internet bandwidth metre.
2006-03-14 - LCD code works...mostly The problems with the programmer have been ironed out. I'm now using it in-circuit which is very handy. No need to unplug cables and move the chip around. Just hit program, the chip enters programming mode, and when the programming finishes the chip is reset and the USB device reconnects. After many hours debugging LCD code I discovered the code I got from PICList (PIC Micro Controller C Routine LCD Input / Output Library) doesn't really work very well with the LCD in 4-bit mode. I need 4-bit mode because there aren't enough 8-bit ports free. One 8-bit port is used for USB data and the other has one pin that needs to be tied to GND due as it's used for low-voltage programming. So I'm using PORTA which is a 6-bit port. Anyhow, I had to rewrite the initialisation routine as well as the function that prints individual characters to the LCD. I also had to rewrite the delay routines as they didn't make much sense. The function
delay_mS in delay.c takes a parameter which is never used! It's a very important parameter, too - the number of milliseconds to delay! The 18F2455 has 4 timers, so eventually I'll use them, but for now all I did was construct a loop of NOPs and modified the number of iterations until I got roughly the desired time.
Here's a picture of the first successful message printed to the LCD directly from the PIC. I'm still working out some issues. It seems like there is something funky going on with Microchip's C compiler...
Now I need to figure out how to make some schematics so I can post them here. In the mean time, this is how I've connected the LCD to the PIC in 4-bit mode:
LCD 18F2455 --- ------- E (6) RB0 (21) RW (5) RB1 (22) RS (4) RB2 (23) DB4 (11) RA0 (2) DB5 (12) RA1 (3) DB6 (13) RA2 (4) DB7 (14) RA3 (5)
2006-03-02 After a long hiatus due to my master's thesis, vacation, and electronics related hurdles, we've seen a resurgence in activity on this project. My friend Mark Lise and his friend Emre have started working on their own versions. This renewed my excitement for the project. There's a lot to recap. First I finally got MPLAB installed under wine. I used Winetools to get a base system installed and after that MPLAB and the C18 compiler installed easily. For the programmer, I discovered the Olimex style PIC-PG2C won't work with my laptop because the serial port isn't powerful enough. So I looked around for a low-voltage programmer that works with the 18F family of PIC chips. It was then that I found PICPgm which is a low-voltage, in-system parallel port programmer. The software, WinPICPgm, is simple and runs well under Wine. However, in order to get direct access to the parallel port, I had to insert the following lines into my
/root/.wine/user.reg:
[Software\\Wine\\VDM\\Ports] 1141205054 "read"="0x278-0x27f,0x378-0x37f" "write"="0x278-0x27f,0x378-0x37f"and then run Wine under the root account. Mark successfully built his programmer, and reported that his 18F2455 was detected by PICPgm over the parallel port. He mentioned that it was important to put the parallel port into ECP mode in the BIOS, and to make sure that RB5 on the 18F2455 was pulled to GND with a 1K resistor (as mentioned in the schematic on the PICPgm website). Soon after, I built the programmer and after resolving the direct access issue with Wine the 18F2455 was successfully detected. I decided to solder up a programmer with a PCB to free up my breadboard for prototyping.
Here is the finished programmer. It uses a hex inverter (74LS05N) to buffer the signals and (I'm told) save your parallel port from being accidentally fried. The in-circuit cable is useful such that you don't have to keep moving the PIC in and out of the programmer. For +5V I used a USB cable plugged into the USB port on my laptop.
A close up of the programmer circuit.
The 18F2455 goes into this socket, which is actually two 14 pin sockets in series.
This is the back of the programmer. My soldering is a bit of a mess.
I discovered that I left my soldering iron on all night! Dang. Well it was only from 4am-10am, but still, that can't be good for it. One problem I've noticed is that PICPgm fails to verify the PIC after programming if I have "Configuration bits" checked. I thought the config bits were important, but perhaps they're not needed or the chip is just using the defailts.
This is a picture of a simple USB circuit. It's actually LAB1 from this Olin College site that has been using the 18F2455 for class projects. When the 18F2455 is flashed with the LAB1 code, and the above USB device is attached to the PC, the LED will flash at 2 Hz. I was pretty stoked when I plugged it in and it worked! I'm using a 4 MHz crystal to run the PIC, and the rest of the circuit is pretty basic.
Well that's all for now. More later after we start building the circuit for the LCD and writing the driver code.
2005-06-14 Well the Zener diodes I was looking for turned out to be very common. The part numbers were just slightly different. I'm not sure why anyone I asked at the various electronics stores didn't figure that out, but I picked some up at the good old Dicksmith here in Palmerston North. So the programmer is complete, save testing. I think my laptop doesn't have enough power on the serial port to do the high voltalge programming, so I have to try it out on the PC in my office. But that won't be for some time, because I'm pretty busy with school. A while back I found this guy, who made a USB LCD for his Mac.
2005-05-04 I completed the programmer, save the two Zener diodes, which I am seeking replacements for. I took a couple of photos. I think I can probably fit it onto one breadboard to free the other up for prototyping the USB device.
Here's a zoomed in version using the magnifying glass. Look at that big green LED!
2005-05-01 I started building the programmer. It's the PIC-PG2C. Since this programmer supports many different chip sizes and I am only programming a 28 pin chip, I decided to skip a lot of the connections. Below are some pictures.
Above you can see my workspace. I reorganised a bit so it's easer to work. I hope Jen doesn't mind that I've taken over her desk entirely.
And this is what I've got wired up so far. I'll probably end up rewiring the whole thing because I'm new at this and this certainly can't be an optimal use of wires and breadboard holes.
This is my soldering stand. I made it using a metal coat hanger that I coiled by wrapping around a broom handle. Before I used to have the iron just resting on the desk, and it would always almost fall off because of the heavy, awkward power cord.
This is what the LCD looks like at the moment. It's just hardwired to the parallel port and gets its +5V from the USB port. Here I had it printing out the amount of time remaining on my laptop batteries.
And here's a picture of Miss Jen, all tucked into bed with her computer.
2005-04-27 Yesterday I picked up some components to build the programmer for my PICs. I got some 1.5K resistors, some 1N4004 diodes (although upon closer inspection they appear to be 1N4007 diodes...hmmm, apparently the only difference is these can handle a higher voltage), a 5mm LED, some 2N3904 transistors, and some 100uF 16V capacitors. The only thing I'm missing are two Zener diodes: BZV 55C 6V2 and BZV 55C 5V1. I bought a female serial port connector too, but discovered a male was needed and exchanged it.
I began connecting things up, but soon realised I need a lot more supplies. For starters, I need a bigger breadboard.
2005-04-18 My PIC18F2455 chips arrived today. Free chips!
Time to get working on the programmer for it. I'm going to build this programmer.
2005-03-31 Today I ordered 3 sample 18F2455 PICs from Microchip. Since I'm in New Zealand, I had to call the Australian distributor. The guy was helpful and recorded my address and said he'd send them out. Cool. The 18F2455 has built in USB support. Other projects have made their own USB support, which would be cool, but I want to save some trouble.
2005-03-29 Today I bought some more wires and some 10K resistors. Later I found out that the resistors weren't really needed. Maybe I'll need them later on though... Tonight I got some text displaying on the LCD through the printer port. I was stuck for a few days because I wasn't latching the data by tripping the Signal Enable line. Here are some pictures of the momentous event:
Here's the code I used:
/*
* lcdprint.c - initialises and prints characters to
* HD44780 compatible LCD (Z4172 from Dick Smiths NZ)
*
* Donn Morrison, March 2005, donn.morrison@gmail.com
*
* If argument is supplied, that string is printed.
* If no arguments, lcdprint enters curses mode and
* any characters typed are echoed to the LCD.
*
* Compile: gcc lcdprint.c -o lcdprint -lcurses
*
*/
#include <stdio.h>
#include <unistd.h>
#include <asm/io.h>
#include <curses.h>
#define BASEPORT 0x0378 /* lp0 */
int lcd_putc(char c);
int lcd_init();
int lcd_print(char *string);
int outb4(char c, int port);
int main(int argc, char *argv[])
{
/* curses stuff */
initscr();
cbreak();
/* Get access to the ports */
if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}
lcd_init();
if(argc == 2)
{
lcd_print(argv[1]);
}
else
{
char c;
while((c=getch()))
{
printf("0x%02x",c);
lcd_putc(c);
}
}
/* We don't need the ports anymore */
if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}
exit(0);
}
int lcd_init()
{
/* wait for voltage to rise */
usleep(30000);
/* function set */
outb(3, BASEPORT+2);
outb(56, BASEPORT); /* 0011 [10xx] */
outb(2, BASEPORT+2);
usleep(39);
/* display on */
outb(3, BASEPORT+2);
outb(13, BASEPORT); /* 0000 1[101] */
outb(2, BASEPORT+2);
usleep(39);
/* clear display */
outb(3, BASEPORT+2);
outb(1, BASEPORT); /* 0000 0001 */
outb(2, BASEPORT+2);
usleep(1530);
/* entry mode set */
outb(3, BASEPORT+2);
outb(6, BASEPORT); /* 0000 01[10] */
outb(2, BASEPORT+2);
usleep(39);
return 0;
}
int lcd_putc(char c)
{
if(c == 0x7f)
{
char addr = 0;
/* backspace */
/* put r/w high */
outb(1, BASEPORT+2);
/* signal enable (latch data) */
outb(2, BASEPORT+2);
outb(3, BASEPORT+2);
usleep(39);
addr = inb(BASEPORT);
printf("addr=0x%02x\n", addr);
/* move cursor back */
outb(3, BASEPORT+2);
outb(addr-1+128, BASEPORT);
outb(2, BASEPORT+2);
usleep(39);
}
/* printf("putting 0x%02xh %d %c\n",c,c,c);*/
outb(14, BASEPORT+2);
outb(c, BASEPORT);
outb(11, BASEPORT+2);
usleep(43);
return 0;
}
int lcd_print(char *string)
{
int i=0;
for(i=0;i<strlen(string);i++)
{
lcd_putc(string[i]);
}
return 0;
}

djv@exis.net @ 2006.10.05.16:40: Nice project! I was wondering if anyone had tried your control board with any other LCD software, particularly on the Win32 platform. The package I'm currently using for my parallel port LCD is LCDSmartie. (http://lcdsmartie.sf.net) I'd like to build something similar to your board so I can ditch the bulky parallel cable for data. Thanks in advance! -Dirk
donn @ 2006.10.06.05:47: the software i use here uses libusb, so you'd just need to compile it for windows if you wanted to build a similar device.
naveendavisv@gmail.com @ 2006.11.20.08:58: as part of our project,we are doing some usb interface to PC. We need the details of your work.We need the burning circuit for pic18f2455....can u help me.......
joerfrada@hotmail.com @ 2007.01.17.08:18: Hi..
It's interesting me, but I didn't find the diagram schematic drawing like LCD 16 pins to USB pins, please send me the diagram schematic picture like circuit. Can u help me. I'll hope you.
me2ubond@yahoo.co.in @ 2007.02.03.02:16: hi ! very good efforts . if u have any other such plans u can share me . I would like to work
anish.gectcr@gmail.com @ 2007.02.23.07:43: Its a really nice work.. I am trying to do similar projects using the PIC18f2455. I built the LVP programing circuit from schematics from the PicPgm site. The problem I am facing is that I can't test any programs. Can you provide some details about the running circuit? Should we set any config words while using the low voltage programing? Thanking You. Anish
scorpia @ 2007.04.17.01:42: Nice to see it works, you might also want to check for a similar project.
http://forums.bit-tech.net/showthread.php?t=115461
electronicstudent@gmail.com @ 2007.07.03.03:05: it is wonderfull! and you have explain any thing ! good project!
andyt12@optusnet.com.au @ 2007.12.23.11:16: The are many config bits in the 18f2455 and if not set correctly the chip wont run. If you set the code protect config bit then verify will fail. This is normal behavior.
post a comment (HTML stripped):