Playing video and other fun things on a Nokia 3310 LCD with Arduino

The Nokia 3310 LCD is well known in the electronics community. It is a monochrome graphics LCD with a resolution of 84×48 and was originally used in the Nokia 3110 and 5110 handsets, and is relatively old (1998), but in my opinion it is brilliant for its price.

I purchased mine on Dealextreme for only $5.60 (link to the product page here), which is a very good price considering what is possible with this display. It came in a nice breakout board so it can be used directly on a breadboard.

If you remove the casing on the breakout board, you will find out that the display looks just like a piece of glass, and has a PCD8544 controller chip embedded inside it.

This controller has an easy to use SPI protocol and handles all the hard work of driving this display (multiplexing, generating bias voltages, ect.).
Driving the display is relatively easy with any microcontroller. However, it runs on and uses 3.3V logic, so you would need level shifters to shift the voltage down.

However, after doing lots of research it seems that the PCD8544 has a maximum voltage of over 5 volts, and that people can drive it perfectly fine on 5V, although it could reduce the life span of the display. I was willing to take the risk.

I decided to connect the VCC (power) connector to my Arduino’s dedicated 3.3V supply, and all the other lines to my Arduino directly. It worked perfectly and still does, with absolutely no issues. However, I do not recommend this so don’t blame me if you end up damaging your display!

Adafruit has made a nice Arduino tutorial and library for using this display. It was perfect for my needs, as their library can draw text, lines, rectangles, circles and other shapes easily to the display, plus all these functions are built into the library. I will not explain how to wire this display up as their tutorial explains it well.

I soldered up a simple shield for driving the display, and a few buttons. However, these are not normal buttons but capacitive buttons! The reason why I made these was because I did not have any normal buttons, plus it is very cool to use! The piece of code I used for the buttons was found over here at the Arduino site, and is connected directly to the buttons. No pullup resistors were used in this case.
The buttons were connected to the following pins:

  • Select File – Pin 19 (analog A5)
  • Toggle FPS – Pin 18 (analog A4)
  • Play – Pin 17 (analog A3)

Here are some images of the shield I made (I apologise for the incredibly messy soldering, I needed this done in a hurry):

First of all I wanted to make a simple text scroller. This was easy, and the following code was used in the loop() routine to scroll some text, with display being the display object:

String text = "Test of scrolling text!";
word textWidth = text.length()*6+85;
for(int i=0; i display.clearDisplay();
display.setCursor((display.width()+2)-i, 0);

This worked very well, with smooth text being scrolled across the screen. However, I wanted more.
I had seen somewhere of getting text to scroll while each character ‘wobbles’.

This effect was easy to achieve.
Instead of printing out a whole string to the display, you print out each character of the string one by one, and change the Y axis of each one for the wobble effect.

To get the ‘wobble’, a sine wave was added to each character’s Y axis, each with a small offset to make the sinusoid slightly different for every letter. I then made a few lines at the bottom to follow the pattern of another sine wave at the bottom of the screen just for something to fill the extra space with.
The effects turned out brilliantly. (Arduino code for the Wobbly Text at the bottom of the page)

This was fun, but I still wanted more. Then I came accross this video. It has an ATMega88 getting video off an SD card and playing it on a monochrome LCD at a good frame rate, and states that the program was written in ASM.
The video looked like it was using some form of 1-bit dithering to be visible on a monochrome display, and was very recognisable. I thought that this was definitely possible, and I was going to give it a try in C++ first and try redoing it in ASM later on, and did not know what frame rate I would get, but I assumed the worst.

I had also purchased an Ethernet shield from Dealextreme. This has the Wiznet chip which is used in the Official Ethernet shields, and works well. It has a SD card slot which would work well for this project. Best of all, it is less than $14 which is almost one third the price of the official version.

First of all I came up with a format for saving my data. The encoded data file would consist of ASCII characters. If the ASCII character was from 0x00 to 0x3F in hex (the first 6 bits, or from B000000 to B111111) then it would be treated as data.
If I wanted the first 6 pixels of the first row on the frame buffer to appear in this binary pattern: 101010 then I would set the character to be 0x2A in hex, or B101010 in binary, and increases the X coordinate by 6. If I repeat this character, it would set the next 6 pixels to 101010, and so on. This is what is used to set the image of the display.
When this is done 14 times, I end up with 84 pixels which is exactly the width of one of the rows of the display. Perfect fit!

Now, if the ASCII character is over 0x3F it is a control character. This is a command that can trigger a certain function in the program.
Here are the control characters:

  • 0x7B – New line – Increments the Y-axis by one, use this when you have written all 14 data characters for the row to go on to the next one and carry on sending your data.
  • 0x7C – Reset the X and Y coordinates – Use this when you have filled up the whole display, and need to start at the beginning point for a new image.
  • 0x7D – Update display – This sends the data in the frame buffer to the display and displays it.

With these commands it is possible to display images or video.

I wrote a Processing script to render a simple 3D Cube and save each frame to a file in the sketch’s project folder using the protocol above (code at the bottom of the page).

I made it save the data to a file called data.dat.
Now I formatted my MicroSD card on Fat32 as the format, and copied the data.dat file onto it.

Writing the Arduino video player program was the most difficult part. I used the SD library included with Arduino to read the files of the SD card and wrote up a file chooser what uses the buttons on the shield, and a routine to read data from the chosen file off the SD card, draw the images and handle the commands.
Everything worked out OK in the end after a day of work, and I had a working product. However, I still needed to finish my goal of displaying video.

Next I did some research on Dithering. I then discovered Windell Oskay’s writeup on how dithering works and some code he wrote to use Dithering in Processing. The example converted real-time video from the webcam to 1-bit dithered images using the Atkinson Dithering algorithm. This was exactly what I needed. (thanks Windell!)

I then modified the program with a output resolution of 84×48, and gave it a video as the input file. (If you want to use a different file, make sure it is 84×48 with ‘high contrast’ for best results with the dithering, although any resolution should still work fine.
For the video, I decided to play the music video of Rick Astley’s “Never Gonna Give You Up”, primarily because of its ‘perfect contrast’ for 1-bit dithering, and also because it’s a fun internet meme 😉
After tackling many more bugs, crashes and other issues I finally had the program working and saving the data file. (yes, download at the bottom of the page)

All I then needed to do was then copy the file to the card and then watch the video!
The results were very impressive. After making the program more efficient I got it to run at a stable 15 frames per second!
I was simply amazed at how good the video looked and how it ran. I had used C++ to do this – No ASM, and it looked almost the same as the one I had seen previously in the other video. However, it should be noted that the other version had a higher resolution display and was running at half the clock speed (8Mhz compared to Arduino’s 16Mhz). There is definitely room for improvement that I will do at some stage. Most of the RAM on the Arduino is used up, but there is around 340 bytes available which should allow extra features. I am still using Adafruit’s library for the display, simply due to its inbuilt framebuffer and ease to draw pixels.
(video is at the top of the page)

In the end, it has been a good experience doing this project and I am impressed by my results. I hope these resources may be useful to others. There are definitely a lot of improvements that need to be made as there are many inefficiencies, but I will fix these at a later stage. Right now I am satisfied that it works perfectly.


Please note that the Processing sketches were written for Processing version 1.5.1. It will not work directly on the Processing 2.0+ betas, however it should be rather easy to port it over.
If you see any files with a .txt extension, just rename it to a .dat extension. There is no difference with the file content. I used it for debugging.

  • Arduino Wobbly Text sketch – Download
  • Processing 3D Cube + encoder project – Download
  • Processing video dithering + encoder project – Download
  • Arduino SD Card Video Player – Download
  • Example video file – “Never Gonna Give You Up” Video – Download
  • Example video file – Rotating Cube – Download

16 Thoughts on Playing video and other fun things on a Nokia 3310 LCD with Arduino

  1. This work is super! I tried with Nokia display and it runs nicely.
    Your code seams to be workable also with 1.8 tft display by adafruit.
    It is nice color display and I am just trying to figure out how it does. While playing Cube video file for istance the background is black and pixels of animation are bluish. Never the less refreshing is very slow. I am very new to arduino and I don’t know much about processing. Any of your comments it may help me to jump out of darkness!! Thanks.

    1. Thanks Pietro for the kind words.
      In my sketch, the Adafruit library is also used, and works with most of their displays.
      The reason the pixels look so black is because the colours on the Nokia display have 2 values: 0 – white, 1 – black.
      On a colour display this may be different, for example if it has 8 bit color (values from 0 to 255) then the 1 would be a very dim color.
      To fix it, replace these original lines:

      display.drawPixel(x, y, ch & B100000);
      display.drawPixel(x, y, ch & B010000);
      display.drawPixel(x, y, ch & B001000);
      display.drawPixel(x, y, ch & B000100);
      display.drawPixel(x, y, ch & B000010);
      display.drawPixel(x, y, ch & B000001);

      with the following:

      display.drawPixel(x, y, (ch & B100000 == 1) ? ST7735_BLACK : ST7735_WHITE);
      display.drawPixel(x, y, (ch & B010000 == 1) ? ST7735_BLACK : ST7735_WHITE);
      display.drawPixel(x, y, (ch & B001000 == 1) ? ST7735_BLACK : ST7735_WHITE);
      display.drawPixel(x, y, (ch & B000100 == 1) ? ST7735_BLACK : ST7735_WHITE);
      display.drawPixel(x, y, (ch & B000010 == 1) ? ST7735_BLACK : ST7735_WHITE);
      display.drawPixel(x, y, (ch & B000001 == 1) ? ST7735_BLACK : ST7735_WHITE);

      Basically this says if the pixel value is 1, fill a black pixel, if it’s 0 fill a white pixel. Have not tested it, but it should work.
      The reason it is slower is because it takes a much longer time to communicate and send the data to the display. There is nothing I can really do about that.


      1. auteur 8 avril 2011 La photo a été prise vers 9h du matin (juillet 2010) avec un 50D, et le grand angle Sigma 10-20mm. J’étais en 10mm, ouverture f8, vitesse 1/200, Iso 100J’ai tendance à corriger l&s8o17;exp2#ition (-2/3), et j’ai mesurée l’exposition sur le ciel afin de ne pas le cramer, mais de bien faire ressortir le bleu du ciel

  2. Thanks Joseph, It works with your lines then I can see now playing the 3D cube video in the right colors. It is also easy to make some color changes. I am facing some more issue that I am trying to fix. For istance the pixels of screen border supposed to be filled black . Both horizontal lines look like dot lines. This is a very inspiring project thank you!


  3. Hi Rodro,
    The issue is that the contrast is too high. Lower it down until the blurring goes away. You’ll know when you get it there.


  4. i made the sd card shield…initially that is working fine…
    i connected the nokia 5110 lcd on breadboard …

    first time sd card connected…

    then i done the program program up to processing 3d cube..
    that is also worked fine..

    but i used the sdhc card in FAT format…

    after 3d cube data.dat file you mentioned like “”i formated the sd card in FAT32″….
    i done that also…

    on arduino side i compiled the sd player program..then it is showing like insert sd card….

    can you pls help me solve this

  5. can you tell me how to convert the .mp4 (video.mp4) files to .dat (data.dat ) file format…

    when i am compiling the code i am getting the error like could not find quick time,pls reinstall quick time or later…

  6. Currently it sounds like WordPress is the preferred
    blogging platform available right now. (from what I’ve read) Is that what you’re using on your blog?

  7. Yes, I know this is a bit old, but…
    Joseph, this is simply amazing =)
    Thanks a lot for sharing the code and for your nice tutorial !
    Have a nice day Joseph !

      1. Thank you for your answer Joseph, you are very reactive =)
        Did you notice that :
        I have converted a video showing a simple timer counting seconds. Then I timed each seconds, as they display, with a real chronometer. Normally, each second should display one second, like this (say we measure 10 seconds, and 10 more)
        But here are the results :
        1.09, 2.03, 1.85, 1.49, 1.12, 0.91, 0.73, 0.67, 0.63, 0.60
        1.09, 2.03, 1.85, 1.49, 1.12, 0.91, 0.73, 0.67, 0.63, 0.60
        and so on.
        So it seems that there is a slow decrease (exept for the 1st count), and every 10 seconds, it is repeating the same.
        I added a frame count, but the instability is not coming during the display from the Arduino. I think it is coming when you record the video with your code using Processing, someting is slowing down the recording during 10 seconds. Strange.
        Have you ever noticed that ?
        Maybe you can guide me ? Thanks again Joseph =)

  8. Oh, by the way, I have adapted your code for the LCD sold by Ebay and Aliexpress, with another library (LCD5110_Graph).
    I had to do that because your LCD model is not available anymore, and the Adafruit library is not working with recent Nokia LCDs.
    I also got rid of the menu and the buttons, keeping only the dispay part.
    Here is my modded code, hope someone find it useful. All credits goes to Joseph.

    Project by Joseph Rautenbach, full writeup at:
    modded by Chris 7/2016

    LCD5110 myGLCD(3,4,5,6,7);


    File file, root; // file = the file of the video to be played; root = the file that lists all the other files 😛

    const int chipSelect = 10;

    String fileName; // name of the chosen video file
    byte x,y = 0; //
    boolean firstRun = true;
    byte fps = 0; // stores the current frame rate
    long startTime = 0; // used for calculating the FPS
    long frame = 0; // frame count

    extern uint8_t TinyFont[];

    void setup() {

    if (!SD.begin(chipSelect)) { // there’s no SD card detected or SD card error
    char msg1[] = “Insert SD Card”;
    asm(“jmp 0”); // ‘reboots’ the Arduino by jumping to 0x00. Can cause problems in some cases, but works fine here.

    root =“/”); // open the root of the SD card for listing fies

    while(1) {
    if(firstRun) { // Select file button pressed
    firstRun = false; // if program just started, run this function once (to select the first file and display something on the screen)
    while(1) { // look for valid files
    fileName = getFileName(root); // get current filename of what is being read
    String newFileName = “”;
    for(int i=0; i<fileName.length(); i++) {
    if(fileName[i] != '~') { // replace all '~'s with nothing, due to a bug with _'s in a filename appearing as _~'s.
    newFileName += fileName[i];
    fileName = newFileName;
    byte len = fileName.length(); // get length of filename
    if(fileName[len-3] == 'D' && fileName[len-2] == 'A' && fileName[len-1] == 'T') { // does the file have .dat extension?
    fileName.toLowerCase(); // make it lower case (files are listed upper case, looks ugly)
    break; // found a valid file (.dat extension) and carry on with rest of program
    break; // carry on with program

    void loop() {

    // converts file name String to char array, as can't use Strings.
    String dataFileName = fileName;
    char __dataFileName[sizeof(dataFileName)+3];
    dataFileName.toCharArray(__dataFileName, sizeof(__dataFileName)+3);

    file =;

    while(file.available()) {
    char ch =;
    switch(ch) {
    case B000000…B111111: // character contains graphics
    if (ch & B100000) {myGLCD.setPixel(x, y);} else {myGLCD.clrPixel(x, y);}
    if (ch & B010000) {myGLCD.setPixel(x, y);} else {myGLCD.clrPixel(x, y);}
    if (ch & B001000) {myGLCD.setPixel(x, y);} else {myGLCD.clrPixel(x, y);}
    if (ch & B000100) {myGLCD.setPixel(x, y);} else {myGLCD.clrPixel(x, y);}
    if (ch & B000010) {myGLCD.setPixel(x, y);} else {myGLCD.clrPixel(x, y);}
    if (ch & B000001) {myGLCD.setPixel(x, y);} else {myGLCD.clrPixel(x, y);}
    case 123: // new line command
    case 124: // reset character positions
    x = 0;
    y = 0;
    case 125: // send framebuffer image to display and display (displays the image, basically)
    // show fps
    fps = 1000/(millis()-startTime);
    startTime = millis();
    char msg[1];
    sprintf (msg,"%2u",fps);
    // show frame count
    char msg[1];
    sprintf (msg,"%3u",frame);
    file.close(); // finished the file, so we close it, then it starts playing again and again…

    String getFileName(File selector) { // function to get then name of the next file, for file list
    File selectorEntry = root.openNextFile();
    if(!selectorEntry) { // we've reached the last file on the card
    root.rewindDirectory(); // start reading from the beginning again
    selectorEntry = root.openNextFile();
    String selectorName =;
    selectorEntry.close(); // close the read file, otherwise it wastes LOADS of ram!
    return selectorName;

Leave a Comment