Introduction: Connecting Nokia 3310 LCD to USB Using AVR

What do you do with an old phone, a microcontroller and lots of time?
You hook the old phone's LCD screen to the computer USB of course!


In this project we're going to communicate with a Nokia 3310 LCD display over USB! How are we going to do that? We're going to connect the LCD display to an Atmel ATmega8 micocontroller and talk to it using the SPI, then connect the ATmega to a PC using V-USB.

The Nokia 3310 LCD display is easy to find, and has a very well documented interface, so it's perfect for hobby use! Not only that, but we're going to use V-USB (Formerly AVR-USB) as our USB driver on the AVR chip. This makes the project very easy to pull off the ground.

V-USB is very slow, so you cannot do any fancy animations this way, but is perfect for updating the display with text! Also, when using USB, you can use this on pretty much any computer. It does require libusb though, but libusb is cross platfrom, so that shouldn't be a problem. Or, if you're really hardcore, you can write your own driver for this project (This is way out of the scope of this instructable)!

What uses does thing have?
As I mentioned, V-USB is pretty slow, so it's best for sending text. Even through this is a little drawback, there is still a lot of stuff you can do with it!
Display RSS feed, Twitter updates, weather, temperature, free disk space, unread e-mails.
You are not limited to just display text though. If you're feeling really fancy, you can make a 1-bit picture slideshow!
Only your imagination (And the slow speed of V-USB) limits you :)

What features does the code have?
With the code you get from this Instructable, you get:
* Built-in font and support for writing ASCII characters
* Character wrapping (Prevents characters from being printed over 2 lines if end of display is reached)
* USB connected
and powered
* Easy to expand

The code is open source (Both firmware and host software), so you're free to do whatever you want with it. (This only includes the software I have written. See the license for V-USB for further use of that, same goes for libusb. Both licenses are included in the source files.)

This project is loosely based on Raphnet's Multiuse PCB, V-USB's PowerSwitch and DharmaniTech's Nokia 3310 LCD routines library.

If you have problems downloading the attached files, I have mirrored them here: Atmega8_LCD.rar and LCD_Screen.rar

Step 1: This Instructable

In this instructable, our main focus is the programming of the decive and host software.

What we'll cover in this instructable:
* Circuit and components
* Processing the data using V-USB on the AVR
* How to communicate with the LCD screen, and what you can tell it
* Transferring data through USB using libusb on the host
* Make a custom font for the display


We will not cover setting up V-USB or libusb, as these are included in the sources!

This instructable is a little advanced, but I assume you know the following:
* Can read datasheets and schematics
* Can solder decently
* Is familiar with programming in C/C++
* Uses Visual C++ 2008 (For the host software, no platform depended code used, so can easily be ported)
* Knows how to burn AVR chips and the programs needed to do so
* Knows the basics of AVR programming
* You use Windows (This can be done on any OS, but I currently only have Windows at hand)


If you have little or no programming experience and is very new electronics, this is not a good way to start. Have you tried making a LED blinker yet? Yes? How about a music box with a piezo element? That might keep you busy for quite some time :)

The concepts in this instructable might be very confusing to one who have little programming experience, and I do not go into too deep details in the concepts.

If you absolutely want to do this instructable even though you are a beginner, I have commented the source code as good as I can. If something still is unclear, drop a comment and I'll try to help you.

Step 2: What You Need

For this project, you need some components, this include:
* IC1 - Atmel ATMega8
* LCD1 - PCD8544 (Nokia 3310 LCD display)
* ZD1, ZD2 - 3,6v Zender dioes
* C1 - 10uF capacitor
* C2, C3 - 20pF capacitor

* R1 - 1,5K ohm resistor
* R2, R3 - 68ohm resistor
* Q1 - 12MHz crystal
* D1 - LED diode (Optional)


You also need an USB cable, your standard soldering tools and thin wires. (As thin as possible. I used IDE cable wires, works pretty well.)

I did not have 3,6v zener diodes or 68ohm resistors available, but I had 3,2v zener diodes and 33 ohm resistors, so I used those instead. However, I highly recommend using the listed components! If you gamble like I did, the device might not be correctly recognized and might show up as "Unknown Device".

The LED diode is only needed if you want to know when there is activity and for debugging purposes, but it doesn't serve any other purpose and can be omitted if you like.

If you have trouble finding parts, may I suggest old broken electronic devices? Not only can you find most parts you need in old electronic devices, but you're also being "green" by recycling! All the parts I have used in this project comes from recycled components (Except for the ATMega8).

Datasheet for Atmel ATMega8 can be found here.

Datasheet for PCD8544 LCD display can be found here.

Step 3: Soldering It Together

This should be pretty straight forward.
Just follow the schematic, and everything should work just fine.


If your soldering skills are a bit rusty, of you're a beginner, there are a lot of good How-To-Solder guides here on Instructables! Be sure to check them out if you're unsure about soldering. Don't let bad soldering ruin this (awesome) project for you.

When you're soldering, be sure to note the D+ (Green) and D- (White) on the USB cable. It's very important not to mix those!
Here's the pinouts for various USB connectors.

Also, if you do I like I did and solder the wires directly onto the LCD display, be very careful to not make any bridges between the connections! The connections are very small, and trust me, it's not fun when you realize the only problem with your circuit is a bridged connection on the display.

Here's a bigger version of the schematic.
Again, here is the datasheet for ATmega8.

Step 4: Programming the ATmega

If you have followed the schematic, this should be a real quickie.

Download the source code or hex files attached to this instructable, compile and upload to your chip. If you're not sure how this works, please check out this very good instructable on how to get started with AVR. It should cover everything you need in regards to programs and SPI interface through parallel connection.

Once you've uploaded the firmware to the microcontroller, detach the programming cable. If you've done everything correctly, the display should now say "Display Initialized" and your computer should notify you that an USB device has been plugged in.

If you have trouble burning the chip:
*
Is the AVR powered? You can use power from the USB port.
* Check for any bridges in your soldering.
* See if you have mixed the MISO and MOSI wires. It's pretty quick to mix those.
* Is the crystal correctly soldered?


If the display does not work:
* Is the screen powered (It powers directly from the USB, just like the AVR)
* Make sure you have not flipped the pinout on the display. It's pretty quick to get it backwards.
* Have you mixed any of the SPI wires?
* Make sure the VOUT on the display is connected to a grounded capacitor.
* Make sure the RESET on the display is properly connected and to the correct pin on the AVR.


If your computer does not find the device over USB:
* Have you mixed the D+ and D- wires?
* Are the zener diodes correctly soldered?
* Does the D+ and D- signal go to the correct pin on the AVR?


If you still have problems, describe your problem in a comment and I'll try to help you out.


Step 5: Understanding the USB Code

This is where the fun begins!

If you've come this far, you should have a display that says "Display Initialized" when you power up the device.

If you haven't done it already, download the source code attached to this instructable, and we'll have a closer look on how the AVR communicates with the display and the computer.

If you've never worked with V-USB before, it might be very confusing at first. At least it was for me. Scroll down to the usbFunctionSetup function and we'll have a closer look on how it actually works. This is about the only function you need to care about editing besides the main function.

usbFunctionSetup is the function where you process all the data that i sent to the microcontroller over USB. If you notice the IF statements, they all check for a specific number at the rq->bRequest variable. This i where the request codes are stored. You can think of request codes as commands. On the host software, we have a function like this: SendData(int request, in data), the value you put in request parameter will be transferred to the microcontroller and stored in the rq->bRequest variable, and you use this request code to do various things in the usbFunctionSetup.

I have only added 6 request codes, but if you want to make a 7th request code simply type this in somewhere in the usbFunctionSetup function:

// If the request is 7
if(rq->bRequest == 7){ // Command 7 - SEND_MYCOMMAND
   // Do my code
}


If you now call the SendData function like this: SendData(7, 0);
The IF statement you just created will run.

The 2nd parameter in the SendData function is the actual data you send to the microcontroller. This number will be stored in rq->wValue struct. This is a WORD (2 bytes), but if you only want to use 1 of the 2 bytes stored here, you can do that by using the bytes array, like this: rq->wValue.bytes[0]. If you'd like to use the entire word, you can access it like this: rq->wValue.word. Note that you should use an unsigned int if you want to use the entire word.

You should put advanced functions that take a long time to do OUTSIDE of the usbFunctionSetup function, and instead let the main loop execute them. If you have them in the usbFunctionSetup and they use a long time to finish (50ms), you might lose the USB connection to the computer. I chose to have them in the usbFunctionSetup because writing to the display is MUCH faster than the USB (In fact, we write a stable 1,33Mbit/s to the display, and the display supports up to 4Mbits/s ) so I do not risk losing the connection by taking too long.

The rest of the code should be commented enough for you to understand. If something is unclear, let me know and I'll try to clear things up for you.

Step 6: Understanding the LCD

The Nokia 3310 display works in a pretty strange way, so we will cover how it works in this step.

The Nokia 3310 display addresses it's 84x42 pixels with 0-83 on the X axis, and 0-5 on the Y axis. The display has 6 pixels "Banks" on the Y axis, and these banks are 8 pixels tall and 84 pixels wide. By doing this, we can represent 8 pixels on the Y axis in just 1 byte! As you know, 1 byte is made up of 8 bits. 11111111 is translated to 8 solid pixels on the Y axis. The only downside is that you HAVE to write 8 pixels at a time and overwrite the existing data at the location.

Each time you draw pixels to the display, the display will automatically move to the next byte on the X axis. You will find this very convenient, as you do not have to move manually every time you draw something. If you're on the last byte of a bank, the display will instead send you to the 1st byte on the next bank, and if it is on the bottom of the display, you will continue from the top.

Note that the 1st bit in the byte you draw is the upper pixel, and the 8th bit is the lower pixel!
 
To write pixels to the display, you use the function LCD_writeData( data ) on the microcontroller. This will draw 8 pixels on the current XY location, overwriting the existing pixels at that location.

You can also send commands to the display, by using the LCD_writeCommand( data ) function. There aren't too many commands you can send to the display that are of interest other than initializing, however, the commands you might want to use are:

* 0b00001000 - Display blank screen ( Does not clear the display )
* 0b00001100 - Normal mode ( Disables invert mode and blank/fill modes )
* 0b00001001 - Display filled screen ( Does not clear the display )
* 0b00001101 - Invert mode ( Inverts the screen )


To move to XY locations, you also use commands, however, we have a function that does it for us: LCD_gotoXY( X, Y ), but in case you need them, they are:

* 0b01000YYY - Moves to 0bYYY on the Y axis (Replace Y with desired binary value)
* 0b1XXXXXXX - Movies to 0bXXXXXXX on the X axis (Replace X with desired value)

Step 7: Setting Up the Host Software

The host software step will be brief, as this is where you should add your code.

The first thing you should do is download libusb-win32 from sourceforge, and install it (libusb-win32-filter-bin). This is the driver we use in the host software, you cannot communicate through USB without this. (You don't have to download anything else than the libusb-win32-filter-bin, it's included in the attached files. )

The next thing to do is to install the driver for our USB device. In the source code attached to this step, you will find the drivers for our device included in the folder named "Drivers". Right-click the "LCD_Display.ini" file and chose install. This will install the libusb drivers for a device named "LCD_Display".

When everything is installed, download and open the LCD_Screen project file in Visual Studio!
Compile the project and run the program. If everything was done correctly, the display should now show your current local time! If the program automatically closes as you run it, it cannot find the USB device, try to reconnect the device or reboot your computer.

When everything is working, scroll down to the main function of the LCD_Screen project. The first thing we do in the main function is to find the USB device. When it is found, we can start having fun! Add your code below the USB init lines. This is where your work starts, and mine ends. You may write anything you'd like to the display now!

To get a better understanding on how you talk to the device on the host software, check out next step.

Step 8: Writing to the Display From Host Software

Writing to the display is very easy, and explained here.

All communication with the device is done with the function SendData( int request, int data ). This function is actually a shorted version of a function named usb_control_msg. We use the shorted version because the usb_control_msg is really long and messy. (The SendData function is defined in the USBFunctions.cpp file.)

I have defined all the request codes I have made with names, but you may use numbers if you like.

If you want to write pixels to the screen, use the SendData( SEND_DATA, data ) function, and replace the "data" with a 1 byte int. This will write 8 pixels on the current XY location. Keep in mind that it taks a long time to write a lot of pixels to the screen using this method. If you want to draw many pixels really fast, you should embed that code on the chip itself.

To send a command, use the SendData( SEND_COMMAND, data ) function, and replace the "data" with a command
(See the LCD dataseet for commands, page 14)

To clear the screen, use the SendData( SEND_CLEAR, 0 ) function. This will make the AVR draw 0 to all pixels on the screen. It is much faster to let the AVR do this, than doing it manually via host software. This obviously needs no extra data then the request itself.

If you want to specify an XY location to write to, use SendData( SEND_XY, ( y << 8 ) + x ). Replace the Y with a number between 0 and 5, and the X with a number between 0 and 83. This sends 2 bytes to the AVR, and that's the reason we shift the Y by 8 (So the Y value is at the 2nd byte).

To send an ASCII character to the screen, use SendData( SEND_CHAR, data ). Note that characters must be in single quotes, e.g.: SendData( SEND_CHAR, 'A' ).

If you'd like to write a lot of characters, you can store the string in an array and use a loop, like this:

unsigned char buffer[] = {"MY STRING12341!!\n"};
for(int j = 0;j < sizeof(buffer)-1;++j){
     SendData(SEND_CHAR, buffer[j]);
}


Note that you can use the newline( \n ) to jump to the next line on the Y axis. It will save you a lot of trouble.


Step 9: Making a Custom Font

You don't like my font?
Well, in the case, why not make your own? It's really simple.

Each character is made up of 5 bytes, meaning they will show up as 8 x 5 pixels on the display. This is enough room for most characters. The font is embedded on the AVR for fast access (It would take ages to send through USB), in an array named "font" located in Atmega8_LCD.h in the sources.

The easiest thing to do is overwrite the font I already have made in the font array, as then you don't have to mess with any other code. Simply remove everything inside the brackets of this array:

static const unsigned char font[] = {
    //Delete everything inside here
}


Then it's just so make your own font, starting at *space* (The 33rd ASCII character)!
If you want a ASCII lookup table, to see what characters you need to make, this is a good reference. Remember that you need to add 5 bytes for every character you make, and you cannot skip any characters!
Here's a blueprint you can copy:

0b00000000, // Character *NOTHING*
0b00000000,
0b00000000,
0b00000000,
0b00000000,

If you for some reason do not wish yo use all 5 bytes, you can use the special code for "skipping":

0b10000000

This will simply not draw anything, and will not move the X location. By doing this, you can make the characters have a single pixel spacing between them no matter how big or small the character is (Instead of a lot of empty space between the characters). The only bad thing with this is that you cannot have a character that uses that last pixel. I didn't worry about that, because my font has 1 pixel spacing there, so the font looks nicer.

If it becomes a problem for your font, you can always change it to something else in the LCD_writeChar function.

Here's an example character for you, it's the capital A from my font:

static const unsigned char font[] = {
....
0b01111000, // Character A
0b00100100,
0b00100100,
0b01111000,
0b10000000, // Skip last pixel
....
}


(The rules of writing pixels to the LCD still count here, 1st bit = upper, 8th bit = lower, read right to left)

Note that 1 pixel of spacing between characters are automatically added!