Project tutorial
Etch-A-Sketch

Etch-A-Sketch © GPL3+

An Arduino powered Etch-A-Sketch with advancements such as line, rectangle and circle modes.

  • 499 views
  • 0 comments
  • 2 respects

Components and supplies

Ard nano
Arduino Nano R3
×1
Nokia 5110 LCD display
×1
74HC14 Hex Schmitt Trigger (SOIC 14)
×2
Resistor 10K 0805 SMD
×7
Resistor 2K2 0805 SMD
×6
Capacitor 22nF 0805 SMD
×6
Bourns pec11r 4215f s0024 image 75px
Rotary Encoder with Push-Button
with suitable knobs
×2
Raised Tactile Switch
×1

Necessary tools and machines

3drag
3D Printer (generic)

Apps and online services

About this project


Demonstration of my Nokia 5110 electronic Etch-A-Sketch

Perusing Thingiverse, I came across an Electronic Etch-a-sketch by liteul. It uses potentiometers for its X and Y drawing controls. While probably OK for sketching, they don't offer precise positioning like a rotary encoder can. So with this build I decided to replace the potentiometers with rotary encoders. Because the rotary encoders also incorporate push switches, this meant I could add more functionality.

Noise, Noisier, Noisest

The problem with any form of mechanical switch is that the contacts "bounce" when contact is made or broken. This is referred to as noise. Usually all switches have it in some form and a rotary encoder, being a pair of switches, is no different.

One way to handle bounce is through software via a debounce routine. When you detect a switch state change, you wait around 10mS and test the state of the switch again. If they don't match, the original state was caused by a contact bounce.

Hardware debouncing uses a RC (Resistor-Capacitor) network to suppress the contact bounces. The resistor and capacitor values are chosen to produce a 10mS delay. By adding a Schmitt trigger to the RC network, the result will be accurate and precise state changes.

Assembling the electronic components

I designed a simple circuit board that holds the electronic components, rotary encoders and the tactile switch used to clear the drawing canvas. The Eagle files have been included should you wish to have the board commercially made or do as I did and make it yourself. I used the Toner method.

The PCB is fixed to the top via the rotary encoders. I had to use a couple of washers between the rotary encoder and top so that the push button had sufficient room.

Wiring up the Nokia 5110 LCD display

SCLK to D13
DIN to D12
DC to D11
CS to D9
RST to D10
LIGHT to A0 via a 220 ohm resistor

Wiring up the Encoder board

Left encoder A pin to D3
Left encoder B pin to D2
Left encoder Switch pin to D4
Right encoder A pin to D8
Right encoder B pin to D7
Right encoder Switch pin to D6
Reset switch to D5

Wiring up the battery and switch

Positive battery terminal to RAW
Negative battery terminal to GND via ON/OFF switch

Software

The code uses a modified LCD5110_Graphic library from Rinky-Dink Electronics. I added an extra screen buffer and a couple of functions to control it.

void updateFromCanvas();      //Transfers canvas buffer to screen buffer
void setCanvasMode(bool on); //On - writes to screen buffer also written to canvas

The rotary encoder and switches are all interrupt driven using the pin change interrupt. The following variables are automatically updated by the interrupt handers:

int horzPosition;
int vertPosition;
bool leftEncoderPressed;
bool rightEncoderPressed;
bool switchPressed;

Because the interrupt handlers can update these variables at any time, any non-interrupt handler code that uses these variables needs to disable interrupts while processing them. Beware, the delay() function relies on interrupts and will block if interrupts are disabled when you invoke it.

Using the Etch-A-Sketch

The Etch-A-Sketch has 4 drawing modes set by pressing the left knob:

DRAW mode

In Draw mode, the left knob will move the cursor left and right and the right knob will move the cursor up and down. By pressing the right knob, you can cycle between PEN DOWN, PEN UP or ERASE pen modes.

LINE mode

In Line mode, the left and right knobs will move the cursor left/right and up/down respectively and a flashing line is drawn between the start position and the current cursor position. Pressing the right button will permanently lock the line in place and start a new line. You can exit the current line by changing the drawing mode.

RECTANGLE mode

In Rectangle mode, the left and right knobs will move the cursor left/right and up/down respectively and a flashing rectangle is drawn between the start position and the current cursor position. Pressing the right button will permanently lock the rectangle in place and return you to DRAW mode.

CIRCLE mode

In Circle mode, the left and right knobs will move the cursor left/right and up/down respectively and a flashing circle is drawn centered on the start position with a radius to the current cursor position. Pressing the right button will permanently lock the circle in place and return you to DRAW mode. Note: the Nokia 5110 LCD doesn't have a 1:1 pixel width/height ratio. The circle won't be round.

Centre button

The centre button will clear the canvas.

Final thoughts

There is plenty of Flash and RAM to extend the code. This project was a nice diversion from the boredom of home isolation during this pandemic.

Code

Etch_A_Sketch_V1.inoC/C++
// Etch-A-Sketch V1
// by John Bradnam (jbrad2089@gmail.com)

//Modified LCD5110_Graph to have extra buffer for canvas and the ability to read from screen buffer
//New functions:
//    void updateFromCanvas();                //Transfers canvas buffer to screen buffer
//    void setCanvasMode(bool on);            //On - writes to screen buffer also written to canvas
//    bool getPixel(uint16_t x, uint16_t y);  //Returns true if pixel at x,y is on in the screen buffer
#include "LCD5110_Graph_Ex.h"

//Nokia 5110
#define SCLK 13
#define DIN 12
#define DC 11
#define CS_LCD 9
#define RST 10
#define LIGHT A0

//Left encoder
#define E1S 4
#define E1A 3
#define E1B 2

//Right encoder
#define E2S 6
#define E2A 8
#define E2B 7

//Switch - Active LOW
#define SWITCH 5

#define SCREEN_WIDTH 84
#define SCREEN_HEIGHT 48
#define DRAW_AREA_MAX_WIDTH 79
#define DRAW_AREA_MAX_HEIGHT 43

bool lastE1A = false;
bool lastE2A = false;
bool lastSWITCH = false;

int horzPosition = SCREEN_WIDTH / 2;
int vertPosition = SCREEN_HEIGHT / 2;
int lastHorzPosition = -1;
int lastVertPosition = -1;
uint16_t switchDebounceTimeout = 0;
bool leftEncoderPressed = false;
bool rightEncoderPressed = false;
bool switchPressed = false;

int lineHorzPosition = 0;   //Start coordinates of line in line mode
int lineVertPosition = 0;
#define LINE_FLASH_DELAY 100;
uint16_t lineFlashTimeout = 0;
bool lineVisible = false;

enum DRAW_MODE { DRAW, LINE, RECT, CIRCLE };
DRAW_MODE drawMode = DRAW;
enum PEN_MODE { PEN_DOWN, PEN_UP, ERASER };
PEN_MODE penMode = PEN_DOWN;

LCD5110 myGLCD(SCLK,DIN,DC,RST,CS_LCD);

extern uint8_t SmallFont[];
//extern unsigned char TinyFont[];

struct point {
  uint8_t x;
  uint8_t y;
};
point cursorCordinates[8] = {{0,2},{1,2},{3,2},{4,2},{2,0},{2,1},{2,3},{2,4}};

void pciSetup(byte pin)
{
    *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
    PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
    PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}

void setup()
{
  Serial.begin(115200);  
  
  pinMode(E1A,INPUT);
  pinMode(E1B,INPUT);
  pinMode(E1S,INPUT);

  pinMode(E2A,INPUT);
  pinMode(E2B,INPUT);
  pinMode(E2S,INPUT);

  pinMode(SWITCH,INPUT);
  
  pinMode(LIGHT, OUTPUT);
  digitalWrite(LIGHT, HIGH);

  myGLCD.InitLCD();
  myGLCD.setContrast(65);   //Default 70
  myGLCD.setFont(SmallFont);
  randomSeed(analogRead(7));

  pciSetup(E1A);
  pciSetup(E1S);
  pciSetup(E2A);
  pciSetup(E2S);
  pciSetup(SWITCH);
  interrupts();

  myGLCD.clrScr();
  myGLCD.drawLine(0,1,83,1);
  myGLCD.print("ETCH-A-SKETCH", CENTER, 4);
  myGLCD.drawLine(0,14,83,14);
  myGLCD.print("Design by", CENTER, 25);
  myGLCD.print("John Bradnam", CENTER, 35);
  myGLCD.update();
  delay(3000);

  //Start out in draw mode
  resetCanvas();
}

//Main program loop
void loop()
{
  switch(drawMode)
  {
    case DRAW: drawLoop(); break;
    case LINE: lineLoop(); break;
    case RECT: rectLoop(); break;
    case CIRCLE: circleLoop(); break;
  }
  
  //Reset button
  if (switchPressed)
  {
    switchPressed = false;
    resetCanvas();
  }
  
  //Test if user wants menu
  if (leftEncoderPressed)
  {
    leftEncoderPressed = false;
    //Change draw status
    rightEncoderPressed = false;
    drawMode = (drawMode == CIRCLE) ? DRAW : (DRAW_MODE)((int)drawMode + 1);
    flashDrawStatus();
    //Initialise incase of line mode
    lineHorzPosition = horzPosition;
    lineVertPosition = vertPosition;
    lineFlashTimeout = millis() + LINE_FLASH_DELAY;
  }
}

//------------------------------------------------------------------------------------
// Interrupt handlers
//------------------------------------------------------------------------------------

ISR (PCINT0_vect) // handle pin change interrupt for D8 to D13 here
{    
  if (!digitalRead(E2A) && lastE2A)
  {
    vertPosition = min(max(vertPosition + ((digitalRead(E2B)) ? 1 : -1), 0), DRAW_AREA_MAX_HEIGHT);
  }
  lastE2A = digitalRead(E2A);
}
 
ISR (PCINT2_vect) // handle pin change interrupt for D0 to D7 here
{
  if (!digitalRead(E1A) && lastE1A)
  {
    horzPosition = min(max(horzPosition + ((digitalRead(E1B)) ? 1 : -1), 0), DRAW_AREA_MAX_WIDTH);
  }
  else if (digitalRead(E1S))
  {
    leftEncoderPressed = true;
  }
  else if (digitalRead(E2S))
  {
    rightEncoderPressed = true;
  }
  else //if (digitalRead(SWITCH) != lastSWITCH)
  {
    /*
    if (digitalRead(SWITCH) == LOW)
    {
      switchDebounceTimeout = millis() + 10;
    }
    else if (switchDebounceTimeout > 0 && millis() > switchDebounceTimeout)
    {
      switchDebounceTimeout = 0;
      switchPressed = true;
    }
    */
    if (digitalRead(SWITCH) == LOW)
    {
      if (switchDebounceTimeout > 0 && millis() > switchDebounceTimeout)
      {
        switchDebounceTimeout = 0;
        switchPressed = true;
      }
      else
      {
        switchDebounceTimeout = millis() + 10;
      }
    }
    
  }
  lastE1A = digitalRead(E1A);
  lastSWITCH = digitalRead(SWITCH);
}

//------------------------------------------------------------------------------------
// Reset button handler
//------------------------------------------------------------------------------------

//Clear screen and draw border
void resetCanvas()
{
  noInterrupts();
  lastHorzPosition = -1;
  lastVertPosition = -1;
  myGLCD.setCanvasMode(true);
  myGLCD.clrScr();
  myGLCD.drawRect(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);
  myGLCD.setCanvasMode(false);
  interrupts();
  myGLCD.update();
  drawMode = DRAW;
}

//Clears the white space aroud the boarder
void clearBorderSpace()
{
  myGLCD.clrRect(1, 1, SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2);
}


//------------------------------------------------------------------------------------
// draw mode loop
//------------------------------------------------------------------------------------

void drawLoop()
{
  if (lastHorzPosition != horzPosition || lastVertPosition != vertPosition)
  {
    noInterrupts();
    if (lastHorzPosition != -1)
    {
      drawCursor(lastHorzPosition, lastVertPosition, false);
    }
    drawCursor(horzPosition, vertPosition, true);
    if (penMode != PEN_UP && lastHorzPosition != -1)
    {
      //Serial.println("X1,Y1,X2,Y2: " + String(lastHorzPosition + 2) + ", " + String(lastVertPosition + 2) + ", " + String(horzPosition + 2) + ", " + String(vertPosition + 2));
      myGLCD.setCanvasMode(true);
      switch (penMode)
      {
        case PEN_DOWN:
          myGLCD.setPixel(lastHorzPosition + 2, lastVertPosition + 2);
          myGLCD.drawLine(lastHorzPosition + 2, lastVertPosition + 2, horzPosition + 2,vertPosition + 2);
          myGLCD.setPixel(horzPosition + 2,vertPosition + 2);
          break;

        case ERASER:
          myGLCD.clrPixel(lastHorzPosition + 2, lastVertPosition + 2);
          myGLCD.clrLine(lastHorzPosition + 2, lastVertPosition + 2, horzPosition + 2,vertPosition + 2);
          myGLCD.clrPixel(horzPosition + 2, vertPosition + 2);
          break;
      }
      myGLCD.setCanvasMode(false);
    }
    lastHorzPosition = horzPosition;
    lastVertPosition = vertPosition;
    interrupts();
    myGLCD.update();
  }

  if (rightEncoderPressed)
  {
    //Change pen status
    rightEncoderPressed = false;
    penMode = (penMode == ERASER) ? PEN_DOWN : (PEN_MODE)((int)penMode + 1);
    flashPenStatus();
  }
  delay(100);
}

//Draws a cursor at x, y
void drawCursor(uint8_t x, uint8_t y, bool on)
{
  myGLCD.updateFromCanvas();
  if (on)
  {
    for (int i = 0; i < 8; i++)
    {
      myGLCD.setPixel(cursorCordinates[i].x + x, cursorCordinates[i].y + y);
    }
  }
}

//flash the pen status
void flashPenStatus()
{
  switch (penMode)
  {
    case PEN_DOWN: myGLCD.print(" PEN DOWN ", CENTER, 20); break;
    case PEN_UP: myGLCD.print(" PEN UP ", CENTER, 20); break;
    case ERASER: myGLCD.print(" ERASER ", CENTER, 20); break;
  }
  myGLCD.update();
  delay(250);
  noInterrupts();
  myGLCD.updateFromCanvas();
  lastHorzPosition = -1;
  lastVertPosition = -1;
  interrupts();
}

//flash the draw status
void flashDrawStatus()
{
  switch (drawMode)
  {
    case DRAW: myGLCD.print(" DRAW ", CENTER, 20); break;
    case LINE: myGLCD.print(" LINE ", CENTER, 20); break;
    case RECT: myGLCD.print(" RECTANGLE ", CENTER, 20); break;
    case CIRCLE: myGLCD.print(" CIRCLE ", CENTER, 20); break;
  }
  myGLCD.update();
  delay(250);
  noInterrupts();
  myGLCD.updateFromCanvas();
  lastHorzPosition = -1;
  lastVertPosition = -1;
  interrupts();
}

//------------------------------------------------------------------------------------
// line mode loop
//------------------------------------------------------------------------------------

void lineLoop()
{
  if (lastHorzPosition != horzPosition || lastVertPosition != vertPosition || millis() > lineFlashTimeout)
  {
    lineVisible = !lineVisible;
    noInterrupts();
    if (lastHorzPosition != -1)
    {
      drawCursor(lastHorzPosition, lastVertPosition, false);
    }
    if (lineVisible)
    {
      drawCursor(horzPosition, vertPosition, true);
      myGLCD.drawLine(lineHorzPosition + 2, lineVertPosition + 2, horzPosition + 2,vertPosition + 2);
      myGLCD.setPixel(horzPosition + 2,vertPosition + 2);
      lastHorzPosition = horzPosition;
      lastVertPosition = vertPosition;
    }
    interrupts();
    myGLCD.update();
    lineFlashTimeout = millis() + LINE_FLASH_DELAY;
  }

  if (rightEncoderPressed)
  {
    //Draw line on canvas
    rightEncoderPressed = false;
    noInterrupts();
    myGLCD.setCanvasMode(true);
    myGLCD.setPixel(lineHorzPosition + 2, lineVertPosition + 2);
    myGLCD.drawLine(lineHorzPosition + 2, lineVertPosition + 2, horzPosition + 2,vertPosition + 2);
    myGLCD.setPixel(horzPosition + 2,vertPosition + 2);
    myGLCD.setCanvasMode(false);
    lineHorzPosition = horzPosition;
    lineVertPosition = vertPosition;
    interrupts();
    myGLCD.update();
  }
  delay(100);
}

//------------------------------------------------------------------------------------
// rectangle loop
//------------------------------------------------------------------------------------

void rectLoop()
{
  if (lastHorzPosition != horzPosition || lastVertPosition != vertPosition || millis() > lineFlashTimeout)
  {
    lineVisible = !lineVisible;
    noInterrupts();
    if (lastHorzPosition != -1)
    {
      drawCursor(lastHorzPosition, lastVertPosition, false);
    }
    if (lineVisible)
    {
      drawCursor(horzPosition, vertPosition, true);
      myGLCD.drawRect(lineHorzPosition + 2, lineVertPosition + 2, horzPosition + 2,vertPosition + 2);
      myGLCD.setPixel(horzPosition + 2,vertPosition + 2);
      lastHorzPosition = horzPosition;
      lastVertPosition = vertPosition;
    }
    interrupts();
    myGLCD.update();
    lineFlashTimeout = millis() + LINE_FLASH_DELAY;
  }

  if (rightEncoderPressed)
  {
    //Draw line on canvas
    rightEncoderPressed = false;
    noInterrupts();
    myGLCD.setCanvasMode(true);
    myGLCD.setPixel(lineHorzPosition + 2, lineVertPosition + 2);
    myGLCD.drawRect(lineHorzPosition + 2, lineVertPosition + 2, horzPosition + 2,vertPosition + 2);
    myGLCD.setPixel(horzPosition + 2,vertPosition + 2);
    myGLCD.setCanvasMode(false);
    interrupts();
    myGLCD.update();

    //Switch to draw mode
    drawMode = DRAW;
    flashDrawStatus();
  }
  delay(100);
}

//------------------------------------------------------------------------------------
// circle loop
//------------------------------------------------------------------------------------

void circleLoop()
{
  if (lastHorzPosition != horzPosition || lastVertPosition != vertPosition || millis() > lineFlashTimeout)
  {
    lineVisible = !lineVisible;
    noInterrupts();
    if (lastHorzPosition != -1)
    {
      drawCursor(lastHorzPosition, lastVertPosition, false);
    }
    if (lineVisible)
    {
      drawCursor(horzPosition, vertPosition, true);
      int r = round(sqrt((horzPosition - lineHorzPosition) * (horzPosition - lineHorzPosition) + (vertPosition - lineVertPosition) * (vertPosition - lineVertPosition)));
      myGLCD.drawCircle(lineHorzPosition + 2, lineVertPosition + 2, r);
      clearBorderSpace();
      lastHorzPosition = horzPosition;
      lastVertPosition = vertPosition;
    }
    interrupts();
    myGLCD.update();
    lineFlashTimeout = millis() + LINE_FLASH_DELAY;
  }

  if (rightEncoderPressed)
  {
    //Draw line on canvas
    rightEncoderPressed = false;
    noInterrupts();
    myGLCD.setCanvasMode(true);
    int r = round(sqrt((horzPosition - lineHorzPosition) * (horzPosition - lineHorzPosition) + (vertPosition - lineVertPosition) * (vertPosition - lineVertPosition)));
    myGLCD.drawCircle(lineHorzPosition + 2, lineVertPosition + 2, r);
    clearBorderSpace();
    myGLCD.setCanvasMode(false);
    interrupts();
    myGLCD.update();

    //Switch to draw mode
    drawMode = DRAW;
    flashDrawStatus();
  }
  delay(100);
}
LCD5110_Graph_Ex.hC Header File
/*
  LCD5110_Graph.h - Arduino/chipKit library support for Nokia 5110 compatible LCDs
  Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
  
  Basic functionality of this library are based on the demo-code provided by
  ITead studio. You can find the latest version of the library at
  http://www.RinkyDinkElectronics.com/

  This library has been made to make it easy to use the Nokia 5110 LCD module 
  as a graphics display on an Arduino or a chipKit.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the CC BY-NC-SA 3.0 license.
  Please see the included documents for further information.

  Commercial use of this library requires you to buy a license that
  will allow commercial use. This includes using the library,
  modified or not, as a tool to sell products.

  The license applies to all part of the library including the 
  examples and tools supplied with the library.
*/

#ifndef LCD5110_Graph_Ex_h
#define LCD5110_Graph_Ex_h

#define LEFT 0
#define RIGHT 9999
#define CENTER 9998

#define LCD_COMMAND 0
#define LCD_DATA 1

// PCD8544 Commandset
// ------------------
// General commands
#define PCD8544_POWERDOWN			0x04
#define PCD8544_ENTRYMODE			0x02
#define PCD8544_EXTENDEDINSTRUCTION	0x01
#define PCD8544_DISPLAYBLANK		0x00
#define PCD8544_DISPLAYNORMAL		0x04
#define PCD8544_DISPLAYALLON		0x01
#define PCD8544_DISPLAYINVERTED		0x05
// Normal instruction set
#define PCD8544_FUNCTIONSET			0x20
#define PCD8544_DISPLAYCONTROL		0x08
#define PCD8544_SETYADDR			0x40
#define PCD8544_SETXADDR			0x80
// Extended instruction set
#define PCD8544_SETTEMP				0x04
#define PCD8544_SETBIAS				0x10
#define PCD8544_SETVOP				0x80
// Display presets
#define LCD_BIAS					0x03	// Range: 0-7 (0x00-0x07)
#define LCD_TEMP					0x02	// Range: 0-3 (0x00-0x03)
#define LCD_CONTRAST				0x46	// Range: 0-127 (0x00-0x7F)

#include "Arduino.h"
#include "HW_AVR_defines.h"

struct _current_font
{
	uint8_t* font;
	uint8_t x_size;
	uint8_t y_size;
	uint8_t offset;
	uint8_t numchars;
	uint8_t inverted;
};

class LCD5110
{
	public:
		LCD5110(int SCK, int MOSI, int DC, int RST, int CS);
		void InitLCD(int contrast=LCD_CONTRAST);
		void setContrast(int contrast);
		void enableSleep();
		void disableSleep();
		void update();
		void updateFromCanvas();
		void setCanvasMode(bool on);
		void clrScr();
		void fillScr();
		void invert(bool mode);
		bool getPixel(uint16_t x, uint16_t y);
		void setPixel(uint16_t x, uint16_t y);
		void clrPixel(uint16_t x, uint16_t y);
		void invPixel(uint16_t x, uint16_t y);
		void invertText(bool mode);
		void print(char *st, int x, int y);
		void print(String st, int x, int y);
		void printNumI(long num, int x, int y, int length=0, char filler=' ');
		void printNumF(double num, byte dec, int x, int y, char divider='.', int length=0, char filler=' ');
		void setFont(uint8_t* font);
		void drawBitmap(int x, int y, uint8_t* bitmap, int sx, int sy);
		void drawLine(int x1, int y1, int x2, int y2);
		void clrLine(int x1, int y1, int x2, int y2);
		void drawRect(int x1, int y1, int x2, int y2);
		void clrRect(int x1, int y1, int x2, int y2);
		void drawRoundRect(int x1, int y1, int x2, int y2);
		void clrRoundRect(int x1, int y1, int x2, int y2);
		void drawCircle(int x, int y, int radius);
		void clrCircle(int x, int y, int radius);

	protected:
		regtype			*P_SCK, *P_MOSI, *P_DC, *P_RST, *P_CS;
		regsize			B_SCK, B_MOSI, B_DC, B_RST, B_CS;
		uint8_t			SCK_Pin, RST_Pin;			// Needed for for faster MCUs
		_current_font	cfont;
		uint8_t			scrbuf[504];
		uint8_t         canvas[504];
		boolean			_sleep;
		int				_contrast;
		boolean			_drawToCanvas;

		void _LCD_Write(unsigned char data, unsigned char mode);
		void _print_char(unsigned char c, int x, int row);
		void _convert_float(char *buf, double num, int width, byte prec);
		void drawHLine(int x, int y, int l);
		void clrHLine(int x, int y, int l);
		void drawVLine(int x, int y, int l);
		void clrVLine(int x, int y, int l);
};

#endif
LCD5110_Graph_Ex.cppC/C++
/*
  LCD5110_Graph.cpp - Arduino/chipKit library support for Nokia 5110 compatible LCDs
  Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
  
  Basic functionality of this library are based on the demo-code provided by
  ITead studio. You can find the latest version of the library at
  http://www.RinkyDinkElectronics.com/

  This library has been made to make it easy to use the Nokia 5110 LCD module 
  as a graphics display on an Arduino or a chipKit.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the CC BY-NC-SA 3.0 license.
  Please see the included documents for further information.

  Commercial use of this library requires you to buy a license that
  will allow commercial use. This includes using the library,
  modified or not, as a tool to sell products.

  The license applies to all part of the library including the 
  examples and tools supplied with the library.
*/

#include "LCD5110_Graph_Ex.h"

LCD5110::LCD5110(int SCK, int MOSI, int DC, int RST, int CS)
{ 
	P_SCK	= portOutputRegister(digitalPinToPort(SCK));
	B_SCK	= digitalPinToBitMask(SCK);
	P_MOSI	= portOutputRegister(digitalPinToPort(MOSI));
	B_MOSI	= digitalPinToBitMask(MOSI);
	P_DC	= portOutputRegister(digitalPinToPort(DC));
	B_DC	= digitalPinToBitMask(DC);
	P_RST	= portOutputRegister(digitalPinToPort(RST));
	B_RST	= digitalPinToBitMask(RST);
	P_CS	= portOutputRegister(digitalPinToPort(CS));
	B_CS	= digitalPinToBitMask(CS);
	pinMode(SCK,OUTPUT);
	pinMode(MOSI,OUTPUT);
	pinMode(DC,OUTPUT);
	pinMode(RST,OUTPUT);
	pinMode(CS,OUTPUT);
	SCK_Pin=SCK;
	RST_Pin=RST;
}

void LCD5110::_LCD_Write(unsigned char data, unsigned char mode)
{   
    cbi(P_CS, B_CS);

	if (mode==LCD_COMMAND)
		cbi(P_DC, B_DC);
	else
		sbi(P_DC, B_DC);

	for (unsigned char c=0; c<8; c++)
	{
		if (data & 0x80)
			sbi(P_MOSI, B_MOSI);
		else
			cbi(P_MOSI, B_MOSI);
		data = data<<1;
		pulseClock;
	}

	sbi(P_CS, B_CS);
}

void LCD5110::InitLCD(int contrast)
{
	if (contrast>0x7F)
		contrast=0x7F;
	if (contrast<0)
		contrast=0;

	resetLCD;

	_LCD_Write(PCD8544_FUNCTIONSET | PCD8544_EXTENDEDINSTRUCTION, LCD_COMMAND);
	_LCD_Write(PCD8544_SETVOP | contrast, LCD_COMMAND);
	_LCD_Write(PCD8544_SETTEMP | LCD_TEMP, LCD_COMMAND);
	_LCD_Write(PCD8544_SETBIAS | LCD_BIAS, LCD_COMMAND);
	_LCD_Write(PCD8544_FUNCTIONSET, LCD_COMMAND);
	_LCD_Write(PCD8544_SETYADDR, LCD_COMMAND);
	_LCD_Write(PCD8544_SETXADDR, LCD_COMMAND);
	_LCD_Write(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYNORMAL, LCD_COMMAND);

	clrScr();
	update();
	cfont.font=0;
	_sleep=false;
	_contrast=contrast;
}

void LCD5110::setContrast(int contrast)
{
	if (contrast>0x7F)
		contrast=0x7F;
	if (contrast<0)
		contrast=0;
	_LCD_Write(PCD8544_FUNCTIONSET | PCD8544_EXTENDEDINSTRUCTION, LCD_COMMAND);
	_LCD_Write(PCD8544_SETVOP | contrast, LCD_COMMAND);
	_LCD_Write(PCD8544_FUNCTIONSET, LCD_COMMAND);
	_contrast=contrast;
}

void LCD5110::enableSleep()
{
	_sleep = true;
	_LCD_Write(PCD8544_SETYADDR, LCD_COMMAND);
	_LCD_Write(PCD8544_SETXADDR, LCD_COMMAND);
	for (int b=0; b<504; b++)
		_LCD_Write(0, LCD_DATA);
	_LCD_Write(PCD8544_FUNCTIONSET | PCD8544_POWERDOWN, LCD_COMMAND);
}

void LCD5110::disableSleep()
{
	_sleep = false;
	_LCD_Write(PCD8544_FUNCTIONSET | PCD8544_EXTENDEDINSTRUCTION, LCD_COMMAND);
	_LCD_Write(PCD8544_SETVOP | _contrast, LCD_COMMAND);
	_LCD_Write(PCD8544_SETTEMP | LCD_TEMP, LCD_COMMAND);
	_LCD_Write(PCD8544_SETBIAS | LCD_BIAS, LCD_COMMAND);
	_LCD_Write(PCD8544_FUNCTIONSET, LCD_COMMAND);
	_LCD_Write(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYNORMAL, LCD_COMMAND);
	update();
}

void LCD5110::update()
{
	if (_sleep==false)
	{
		_LCD_Write(PCD8544_SETYADDR, LCD_COMMAND);
		_LCD_Write(PCD8544_SETXADDR, LCD_COMMAND);
		for (int b=0; b<504; b++)
			_LCD_Write(scrbuf[b], LCD_DATA);
	}
}

void LCD5110::updateFromCanvas()
{
	for (int c=0; c<504; c++)
		scrbuf[c]=canvas[c];
}

void LCD5110::setCanvasMode(bool on)
{
	_drawToCanvas = on;
}


void LCD5110::clrScr()
{
	for (int c=0; c<504; c++)
	{
		scrbuf[c]=0;
		if (_drawToCanvas)
		{
			canvas[c]=0;
		}
	}
}

void LCD5110::fillScr()
{
	for (int c=0; c<504; c++)
	{
		scrbuf[c]=255;
		if (_drawToCanvas)
		{
			canvas[c]=255;
		}
	}
}

void LCD5110::invert(bool mode)
{
	if (mode==true)
		_LCD_Write(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYINVERTED, LCD_COMMAND);
	else
		_LCD_Write(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYNORMAL, LCD_COMMAND);
}

bool LCD5110::getPixel(uint16_t x, uint16_t y)
{
	int by, bi;
	bool on = false;

	if ((x>=0) && (x<84) && (y>=0) && (y<48))
	{
		by=((y/8)*84)+x;
		bi=y % 8;

		on=(scrbuf[by] & (1<<bi));
	}
	return on;
}

void LCD5110::setPixel(uint16_t x, uint16_t y)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		by=((y/8)*84)+x;
		bi=y % 8;

		scrbuf[by]=scrbuf[by] | (1<<bi);
		if (_drawToCanvas)
		{
			canvas[by]=canvas[by] | (1<<bi);
		}
	}
}

void LCD5110::clrPixel(uint16_t x, uint16_t y)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		by=((y/8)*84)+x;
		bi=y % 8;

		scrbuf[by]=scrbuf[by] & ~(1<<bi);
		if (_drawToCanvas)
		{
      canvas[by]=canvas[by] & ~(1<<bi);
		}
	}
}

void LCD5110::invPixel(uint16_t x, uint16_t y)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		by=((y/8)*84)+x;
		bi=y % 8;

  	scrbuf[by]=scrbuf[by] ^ (1<<bi);
		if (_drawToCanvas)
		{
			canvas[by]=canvas[by] ^ (1<<bi);
		}
	}
}

void LCD5110::invertText(bool mode)
{
	if (mode==true)
		cfont.inverted=1;
	else
		cfont.inverted=0;
}

void LCD5110::print(char *st, int x, int y)
{
	unsigned char ch;
	int stl;

	stl = strlen(st);
	if (x == RIGHT)
		x = 84-(stl*cfont.x_size);
	if (x == CENTER)
		x = (84-(stl*cfont.x_size))/2;

	for (int cnt=0; cnt<stl; cnt++)
			_print_char(*st++, x + (cnt*(cfont.x_size)), y);
}

void LCD5110::print(String st, int x, int y)
{
	char buf[st.length()+1];

	st.toCharArray(buf, st.length()+1);
	print(buf, x, y);
}

void LCD5110::printNumI(long num, int x, int y, int length, char filler)
{
	char buf[25];
	char st[27];
	boolean neg=false;
	int c=0, f=0;
  
	if (num==0)
	{
		if (length!=0)
		{
			for (c=0; c<(length-1); c++)
				st[c]=filler;
			st[c]=48;
			st[c+1]=0;
		}
		else
		{
			st[0]=48;
			st[1]=0;
		}
	}
	else
	{
		if (num<0)
		{
			neg=true;
			num=-num;
		}
	  
		while (num>0)
		{
			buf[c]=48+(num % 10);
			c++;
			num=(num-(num % 10))/10;
		}
		buf[c]=0;
	  
		if (neg)
		{
			st[0]=45;
		}
	  
		if (length>(c+neg))
		{
			for (int i=0; i<(length-c-neg); i++)
			{
				st[i+neg]=filler;
				f++;
			}
		}

		for (int i=0; i<c; i++)
		{
			st[i+neg+f]=buf[c-i-1];
		}
		st[c+neg+f]=0;

	}

	print(st,x,y);
}

void LCD5110::printNumF(double num, byte dec, int x, int y, char divider, int length, char filler)
{
	char st[27];
	boolean neg=false;

	if (num<0)
		neg = true;

	_convert_float(st, num, length, dec);

	if (divider != '.')
	{
		for (int i=0; i<sizeof(st); i++)
			if (st[i]=='.')
				st[i]=divider;
	}

	if (filler != ' ')
	{
		if (neg)
		{
			st[0]='-';
			for (int i=1; i<sizeof(st); i++)
				if ((st[i]==' ') || (st[i]=='-'))
					st[i]=filler;
		}
		else
		{
			for (int i=0; i<sizeof(st); i++)
				if (st[i]==' ')
					st[i]=filler;
		}
	}

	print(st,x,y);
}

void LCD5110::_print_char(unsigned char c, int x, int y)
{
	if ((cfont.y_size % 8) == 0)
	{
		int font_idx = ((c - cfont.offset)*(cfont.x_size*(cfont.y_size/8)))+4;
		for (int rowcnt=0; rowcnt<(cfont.y_size/8); rowcnt++)
		{
			for(int cnt=0; cnt<cfont.x_size; cnt++)
			{
				for (int b=0; b<8; b++)
					if ((fontbyte(font_idx+cnt+(rowcnt*cfont.x_size)) & (1<<b))!=0)
						if (cfont.inverted==0)
							setPixel(x+cnt, y+(rowcnt*8)+b);
						else
							clrPixel(x+cnt, y+(rowcnt*8)+b);
					else
						if (cfont.inverted==0)
							clrPixel(x+cnt, y+(rowcnt*8)+b);
						else
							setPixel(x+cnt, y+(rowcnt*8)+b);
			}
		}
	}
	else
	{
		int font_idx = ((c - cfont.offset)*((cfont.x_size*cfont.y_size/8)))+4;
		int cbyte=fontbyte(font_idx);
		int cbit=7;
		for (int cx=0; cx<cfont.x_size; cx++)
		{
			for (int cy=0; cy<cfont.y_size; cy++)
			{
				if ((cbyte & (1<<cbit)) != 0)
					if (cfont.inverted==0)
						setPixel(x+cx, y+cy);
					else
						clrPixel(x+cx, y+cy);
				else
					if (cfont.inverted==0)
						clrPixel(x+cx, y+cy);
					else
						setPixel(x+cx, y+cy);
				cbit--;
				if (cbit<0)
				{
					cbit=7;
					font_idx++;
					cbyte=fontbyte(font_idx);
				}
			}
		}
	}
}

void LCD5110::setFont(uint8_t* font)
{
	cfont.font=font;
	cfont.x_size=fontbyte(0);
	cfont.y_size=fontbyte(1);
	cfont.offset=fontbyte(2);
	cfont.numchars=fontbyte(3);
	cfont.inverted=0;
}

void LCD5110::drawHLine(int x, int y, int l)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		for (int cx=0; cx<l; cx++)
		{
			by=((y/8)*84)+x;
			bi=y % 8;

			scrbuf[by+cx] |= (1<<bi);
			if (_drawToCanvas)
			{
				canvas[by+cx] |= (1<<bi);
			}
		}
	}
}

void LCD5110::clrHLine(int x, int y, int l)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		for (int cx=0; cx<l; cx++)
		{
			by=((y/8)*84)+x;
			bi=y % 8;

			scrbuf[by+cx] &= ~(1<<bi);
			if (_drawToCanvas)
			{
				canvas[by+cx] &= ~(1<<bi);
			}
		}
	}
}

void LCD5110::drawVLine(int x, int y, int l)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		for (int cy=0; cy<l; cy++)
		{
			setPixel(x, y+cy);
		}
	}
}

void LCD5110::clrVLine(int x, int y, int l)
{
	int by, bi;

	if ((x>=0) and (x<84) and (y>=0) and (y<48))
	{
		for (int cy=0; cy<l; cy++)
		{
			clrPixel(x, y+cy);
		}
	}
}

void LCD5110::drawLine(int x1, int y1, int x2, int y2)
{
	int tmp;
	double delta, tx, ty;
	double m, b, dx, dy;
	
	if (((x2-x1)<0))
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
		tmp=y1;
		y1=y2;
		y2=tmp;
	}
    if (((y2-y1)<0))
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
		tmp=y1;
		y1=y2;
		y2=tmp;
	}

	if (y1==y2)
	{
		if (x1>x2)
		{
			tmp=x1;
			x1=x2;
			x2=tmp;
		}
		drawHLine(x1, y1, x2-x1);
	}
	else if (x1==x2)
	{
		if (y1>y2)
		{
			tmp=y1;
			y1=y2;
			y2=tmp;
		}
		drawVLine(x1, y1, y2-y1);
	}
	else if (abs(x2-x1)>abs(y2-y1))
	{
		delta=(double(y2-y1)/double(x2-x1));
		ty=double(y1);
		if (x1>x2)
		{
			for (int i=x1; i>=x2; i--)
			{
				setPixel(i, int(ty+0.5));
        		ty=ty-delta;
			}
		}
		else
		{
			for (int i=x1; i<=x2; i++)
			{
				setPixel(i, int(ty+0.5));
        		ty=ty+delta;
			}
		}
	}
	else
	{
		delta=(float(x2-x1)/float(y2-y1));
		tx=float(x1);
        if (y1>y2)
        {
			for (int i=y2+1; i>y1; i--)
			{
		 		setPixel(int(tx+0.5), i);
        		tx=tx+delta;
			}
        }
        else
        {
			for (int i=y1; i<y2+1; i++)
			{
		 		setPixel(int(tx+0.5), i);
        		tx=tx+delta;
			}
        }
	}

}

void LCD5110::clrLine(int x1, int y1, int x2, int y2)
{
	int tmp;
	double delta, tx, ty;
	double m, b, dx, dy;
	
	if (((x2-x1)<0))
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
		tmp=y1;
		y1=y2;
		y2=tmp;
	}
    if (((y2-y1)<0))
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
		tmp=y1;
		y1=y2;
		y2=tmp;
	}

	if (y1==y2)
	{
		if (x1>x2)
		{
			tmp=x1;
			x1=x2;
			x2=tmp;
		}
		clrHLine(x1, y1, x2-x1);
	}
	else if (x1==x2)
	{
		if (y1>y2)
		{
			tmp=y1;
			y1=y2;
			y2=tmp;
		}
		clrVLine(x1, y1, y2-y1);
	}
	else if (abs(x2-x1)>abs(y2-y1))
	{
		delta=(double(y2-y1)/double(x2-x1));
		ty=double(y1);
		if (x1>x2)
		{
			for (int i=x1; i>=x2; i--)
			{
				clrPixel(i, int(ty+0.5));
        		ty=ty-delta;
			}
		}
		else
		{
			for (int i=x1; i<=x2; i++)
			{
				clrPixel(i, int(ty+0.5));
        		ty=ty+delta;
			}
		}
	}
	else
	{
		delta=(float(x2-x1)/float(y2-y1));
		tx=float(x1);
        if (y1>y2)
        {
			for (int i=y2+1; i>y1; i--)
			{
		 		clrPixel(int(tx+0.5), i);
        		tx=tx+delta;
			}
        }
        else
        {
			for (int i=y1; i<y2+1; i++)
			{
		 		clrPixel(int(tx+0.5), i);
        		tx=tx+delta;
			}
        }
	}

}

void LCD5110::drawRect(int x1, int y1, int x2, int y2)
{
	int tmp;

	if (x1>x2)
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
	}
	if (y1>y2)
	{
		tmp=y1;
		y1=y2;
		y2=tmp;
	}

	drawHLine(x1, y1, x2-x1);
	drawHLine(x1, y2, x2-x1);
	drawVLine(x1, y1, y2-y1);
	drawVLine(x2, y1, y2-y1+1);
}

void LCD5110::clrRect(int x1, int y1, int x2, int y2)
{
	int tmp;

	if (x1>x2)
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
	}
	if (y1>y2)
	{
		tmp=y1;
		y1=y2;
		y2=tmp;
	}

	clrHLine(x1, y1, x2-x1);
	clrHLine(x1, y2, x2-x1);
	clrVLine(x1, y1, y2-y1);
	clrVLine(x2, y1, y2-y1+1);
}

void LCD5110::drawRoundRect(int x1, int y1, int x2, int y2)
{
	int tmp;

	if (x1>x2)
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
	}
	if (y1>y2)
	{
		tmp=y1;
		y1=y2;
		y2=tmp;
	}
	if ((x2-x1)>4 && (y2-y1)>4)
	{
		setPixel(x1+1,y1+1);
		setPixel(x2-1,y1+1);
		setPixel(x1+1,y2-1);
		setPixel(x2-1,y2-1);
		drawHLine(x1+2, y1, x2-x1-3);
		drawHLine(x1+2, y2, x2-x1-3);
		drawVLine(x1, y1+2, y2-y1-3);
		drawVLine(x2, y1+2, y2-y1-3);
	}
}

void LCD5110::clrRoundRect(int x1, int y1, int x2, int y2)
{
	int tmp;

	if (x1>x2)
	{
		tmp=x1;
		x1=x2;
		x2=tmp;
	}
	if (y1>y2)
	{
		tmp=y1;
		y1=y2;
		y2=tmp;
	}
	if ((x2-x1)>4 && (y2-y1)>4)
	{
		clrPixel(x1+1,y1+1);
		clrPixel(x2-1,y1+1);
		clrPixel(x1+1,y2-1);
		clrPixel(x2-1,y2-1);
		clrHLine(x1+2, y1, x2-x1-3);
		clrHLine(x1+2, y2, x2-x1-3);
		clrVLine(x1, y1+2, y2-y1-3);
		clrVLine(x2, y1+2, y2-y1-3);
	}
}

void LCD5110::drawCircle(int x, int y, int radius)
{
	int f = 1 - radius;
	int ddF_x = 1;
	int ddF_y = -2 * radius;
	int x1 = 0;
	int y1 = radius;
	char ch, cl;
	
	setPixel(x, y + radius);
	setPixel(x, y - radius);
	setPixel(x + radius, y);
	setPixel(x - radius, y);
 
	while(x1 < y1)
	{
		if(f >= 0) 
		{
			y1--;
			ddF_y += 2;
			f += ddF_y;
		}
		x1++;
		ddF_x += 2;
		f += ddF_x;    
		setPixel(x + x1, y + y1);
		setPixel(x - x1, y + y1);
		setPixel(x + x1, y - y1);
		setPixel(x - x1, y - y1);
		setPixel(x + y1, y + x1);
		setPixel(x - y1, y + x1);
		setPixel(x + y1, y - x1);
		setPixel(x - y1, y - x1);
	}
}

void LCD5110::clrCircle(int x, int y, int radius)
{
	int f = 1 - radius;
	int ddF_x = 1;
	int ddF_y = -2 * radius;
	int x1 = 0;
	int y1 = radius;
	char ch, cl;
	
	clrPixel(x, y + radius);
	clrPixel(x, y - radius);
	clrPixel(x + radius, y);
	clrPixel(x - radius, y);
 
	while(x1 < y1)
	{
		if(f >= 0) 
		{
			y1--;
			ddF_y += 2;
			f += ddF_y;
		}
		x1++;
		ddF_x += 2;
		f += ddF_x;    
		clrPixel(x + x1, y + y1);
		clrPixel(x - x1, y + y1);
		clrPixel(x + x1, y - y1);
		clrPixel(x - x1, y - y1);
		clrPixel(x + y1, y + x1);
		clrPixel(x - y1, y + x1);
		clrPixel(x + y1, y - x1);
		clrPixel(x - y1, y - x1);
	}
}

void LCD5110::drawBitmap(int x, int y, uint8_t* bitmap, int sx, int sy)
{
	int bit;
	byte data;

	for (int cy=0; cy<sy; cy++)
	{
		bit= cy % 8;
		for(int cx=0; cx<sx; cx++)
		{
			data=bitmapbyte(cx+((cy/8)*sx));
			if ((data & (1<<bit))>0)
				setPixel(x+cx, y+cy);
			else
				clrPixel(x+cx, y+cy);
		}
	}      
}
DefaultFonts.cC/C++
#if defined(__AVR__)
	#include <avr/pgmspace.h>
	#define fontdatatype const uint8_t
#elif defined(__PIC32MX__)
	#define PROGMEM
	#define fontdatatype const unsigned char
#elif defined(__arm__)
	#define PROGMEM
	#define fontdatatype const unsigned char
#endif

fontdatatype SmallFont[] PROGMEM =
{
0x06, 0x08, 0x20, 0x5f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // sp
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,   // !
0x00, 0x00, 0x07, 0x00, 0x07, 0x00,   // "
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14,   // #
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12,   // $
0x00, 0x23, 0x13, 0x08, 0x64, 0x62,   // %
0x00, 0x36, 0x49, 0x55, 0x22, 0x50,   // &
0x00, 0x00, 0x05, 0x03, 0x00, 0x00,   // '
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00,   // (
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00,   // )
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14,   // *
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08,   // +
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00,   // ,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08,   // -
0x00, 0x00, 0x60, 0x60, 0x00, 0x00,   // .
0x00, 0x20, 0x10, 0x08, 0x04, 0x02,   // /

0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E,   // 0
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00,   // 1
0x00, 0x42, 0x61, 0x51, 0x49, 0x46,   // 2
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31,   // 3
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10,   // 4
0x00, 0x27, 0x45, 0x45, 0x45, 0x39,   // 5
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30,   // 6
0x00, 0x01, 0x71, 0x09, 0x05, 0x03,   // 7
0x00, 0x36, 0x49, 0x49, 0x49, 0x36,   // 8
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E,   // 9
0x00, 0x00, 0x36, 0x36, 0x00, 0x00,   // :
0x00, 0x00, 0x56, 0x36, 0x00, 0x00,   // ;
0x00, 0x08, 0x14, 0x22, 0x41, 0x00,   // <
0x00, 0x14, 0x14, 0x14, 0x14, 0x14,   // =
0x00, 0x00, 0x41, 0x22, 0x14, 0x08,   // >
0x00, 0x02, 0x01, 0x51, 0x09, 0x06,   // ?

0x00, 0x32, 0x49, 0x59, 0x51, 0x3E,   // @
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C,   // A
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36,   // B
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22,   // C
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C,   // D
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41,   // E
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01,   // F
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A,   // G
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F,   // H
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00,   // I
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01,   // J
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41,   // K
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40,   // L
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F,   // M
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F,   // N
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E,   // O

0x00, 0x7F, 0x09, 0x09, 0x09, 0x06,   // P
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E,   // Q
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46,   // R
0x00, 0x46, 0x49, 0x49, 0x49, 0x31,   // S
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01,   // T
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F,   // U
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F,   // V
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F,   // W
0x00, 0x63, 0x14, 0x08, 0x14, 0x63,   // X
0x00, 0x07, 0x08, 0x70, 0x08, 0x07,   // Y
0x00, 0x61, 0x51, 0x49, 0x45, 0x43,   // Z
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00,   // [
0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55,   // Backslash (Checker pattern)
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00,   // ]
0x00, 0x04, 0x02, 0x01, 0x02, 0x04,   // ^
0x00, 0x40, 0x40, 0x40, 0x40, 0x40,   // _

0x00, 0x00, 0x03, 0x05, 0x00, 0x00,   // `
0x00, 0x20, 0x54, 0x54, 0x54, 0x78,   // a
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38,   // b
0x00, 0x38, 0x44, 0x44, 0x44, 0x20,   // c
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F,   // d
0x00, 0x38, 0x54, 0x54, 0x54, 0x18,   // e
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02,   // f
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C,   // g
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78,   // h
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00,   // i
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00,   // j
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,   // k
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00,   // l
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78,   // m
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78,   // n
0x00, 0x38, 0x44, 0x44, 0x44, 0x38,   // o

0x00, 0xFC, 0x24, 0x24, 0x24, 0x18,   // p
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC,   // q
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08,   // r
0x00, 0x48, 0x54, 0x54, 0x54, 0x20,   // s
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20,   // t
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C,   // u
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C,   // v
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C,   // w
0x00, 0x44, 0x28, 0x10, 0x28, 0x44,   // x
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,   // y
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44,   // z
0x00, 0x00, 0x10, 0x7C, 0x82, 0x00,   // {
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,   // |
0x00, 0x00, 0x82, 0x7C, 0x10, 0x00,   // }
0x00, 0x00, 0x06, 0x09, 0x09, 0x06    // ~ (Degrees)
};

fontdatatype MediumNumbers[] PROGMEM =
{
0x0c, 0x10, 0x2d, 0x0d,
0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00,   // -
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,   // .
0x00, 0x00, 0x02, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x02, 0x00, 0x00, 0x00, 0x00, 0x81, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x81, 0x00, 0x00,   // /
0x00, 0xfc, 0x7a, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x7a, 0xfc, 0x00, 0x00, 0x7e, 0xbc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbc, 0x7e, 0x00,   // 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x7e, 0x00,   // 1
0x00, 0x00, 0x02, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x7a, 0xfc, 0x00, 0x00, 0x7e, 0xbd, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x81, 0x00, 0x00,   // 2
0x00, 0x00, 0x02, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x7a, 0xfc, 0x00, 0x00, 0x00, 0x81, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xbd, 0x7e, 0x00,   // 3
0x00, 0xfc, 0x78, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x78, 0xfc, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x3d, 0x7e, 0x00,   // 4
0x00, 0xfc, 0x7a, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x02, 0x00, 0x00, 0x00, 0x00, 0x81, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xbd, 0x7e, 0x00,   // 5
0x00, 0xfc, 0x7a, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x02, 0x00, 0x00, 0x00, 0x7e, 0xbd, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xbd, 0x7e, 0x00,   // 6
0x00, 0x00, 0x02, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x7a, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x7e, 0x00,   // 7
0x00, 0xfc, 0x7a, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x7a, 0xfc, 0x00, 0x00, 0x7e, 0xbd, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xbd, 0x7e, 0x00,   // 8
0x00, 0xfc, 0x7a, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x7a, 0xfc, 0x00, 0x00, 0x00, 0x81, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xbd, 0x7e, 0x00,   // 9
};

fontdatatype BigNumbers[] PROGMEM =
{
0x0e, 0x18, 0x2d, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // -
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xe0, 0xe0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,   // .
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0x80, 0x00, 0x00,   // /
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0xef, 0xc7, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xc7, 0xef, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf8, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xc7, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x00,   // 1
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0xe0, 0xd0, 0xb8, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x3b, 0x17, 0x0f, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0x80, 0x00, 0x00,   // 2
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 3
0x00, 0xfc, 0xf8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf8, 0xfc, 0x00, 0x00, 0x0f, 0x17, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x00,   // 4
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x17, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xb8, 0xd0, 0xe0, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 5
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0xef, 0xd7, 0xbb, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xb8, 0xd0, 0xe0, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 6
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xc7, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x00,   // 7
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0xef, 0xd7, 0xbb, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 8
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0x0f, 0x17, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 9
};

fontdatatype TinyFont[] PROGMEM =
{
0x04, 0x06, 0x20, 0x5f,
0x00, 0x00, 0x00, 0x03, 0xa0, 0x00, 0xc0, 0x0c, 0x00, 0xf9, 0x4f, 0x80, 0x6b, 0xeb, 0x00, 0x98, 0x8c, 0x80, 0x52, 0xa5, 0x80, 0x03, 0x00, 0x00,  // Space, !"#$%&'
0x01, 0xc8, 0x80, 0x89, 0xc0, 0x00, 0x50, 0x85, 0x00, 0x21, 0xc2, 0x00, 0x08, 0x40, 0x00, 0x20, 0x82, 0x00, 0x00, 0x20, 0x00, 0x18, 0x8c, 0x00,  // ()*+,-./
0xfa, 0x2f, 0x80, 0x4b, 0xe0, 0x80, 0x5a, 0x66, 0x80, 0x8a, 0xa5, 0x00, 0xe0, 0x8f, 0x80, 0xea, 0xab, 0x00, 0x72, 0xa9, 0x00, 0x9a, 0x8c, 0x00,  // 01234567
0xfa, 0xaf, 0x80, 0x4a, 0xa7, 0x00, 0x01, 0x40, 0x00, 0x09, 0x40, 0x00, 0x21, 0x48, 0x80, 0x51, 0x45, 0x00, 0x89, 0x42, 0x00, 0x42, 0x66, 0x00,  // 89:;<=>?
0x72, 0xa6, 0x80, 0x7a, 0x87, 0x80, 0xfa, 0xa5, 0x00, 0x72, 0x25, 0x00, 0xfa, 0x27, 0x00, 0xfa, 0xa8, 0x80, 0xfa, 0x88, 0x00, 0x72, 0x2b, 0x00,  // @ABCDEFG
0xf8, 0x8f, 0x80, 0x8b, 0xe8, 0x80, 0x8b, 0xe8, 0x00, 0xf8, 0x8d, 0x80, 0xf8, 0x20, 0x80, 0xf9, 0x0f, 0x80, 0xf9, 0xcf, 0x80, 0x72, 0x27, 0x00,  // HIJKLMNO
0xfa, 0x84, 0x00, 0x72, 0x27, 0x40, 0xfa, 0x85, 0x80, 0x4a, 0xa9, 0x00, 0x83, 0xe8, 0x00, 0xf0, 0x2f, 0x00, 0xe0, 0x6e, 0x00, 0xf0, 0xef, 0x00,  // PQRSTUVW
0xd8, 0x8d, 0x80, 0xc0, 0xec, 0x00, 0x9a, 0xac, 0x80, 0x03, 0xe8, 0x80, 0xc0, 0x81, 0x80, 0x8b, 0xe0, 0x00, 0x42, 0x04, 0x00, 0x08, 0x20, 0x80,  // XYZ[\]^_
0x02, 0x04, 0x00, 0x31, 0x23, 0x80, 0xf9, 0x23, 0x00, 0x31, 0x24, 0x80, 0x31, 0x2f, 0x80, 0x31, 0x62, 0x80, 0x23, 0xea, 0x00, 0x25, 0x53, 0x80,  // `abcdefg
0xf9, 0x03, 0x80, 0x02, 0xe0, 0x00, 0x06, 0xe0, 0x00, 0xf8, 0x42, 0x80, 0x03, 0xe0, 0x00, 0x79, 0x87, 0x80, 0x39, 0x03, 0x80, 0x31, 0x23, 0x00,  // hijklmno
0x7d, 0x23, 0x00, 0x31, 0x27, 0xc0, 0x78, 0x84, 0x00, 0x29, 0x40, 0x00, 0x43, 0xe4, 0x00, 0x70, 0x27, 0x00, 0x60, 0x66, 0x00, 0x70, 0x67, 0x00,  // pqrstuvw
0x48, 0xc4, 0x80, 0x74, 0x57, 0x80, 0x59, 0xe6, 0x80, 0x23, 0xe8, 0x80, 0x03, 0x60, 0x00, 0x8b, 0xe2, 0x00, 0x61, 0x0c, 0x00                     // zyx{|}~
};
HW_AVR.hC Header File
void LCD5110::_convert_float(char *buf, double num, int width, byte prec)
{
	dtostrf(num, width, prec, buf);
}
HW_AVR_defines.hC Header File
// *** Hardwarespecific defines ***
#define cbi(reg, bitmask) *reg &= ~bitmask
#define sbi(reg, bitmask) *reg |= bitmask
#define pulseClock cbi(P_SCK, B_SCK); asm ("nop"); sbi(P_SCK, B_SCK)
#define resetLCD sbi(P_DC, B_DC); sbi(P_MOSI, B_MOSI); sbi(P_SCK, B_SCK); sbi(P_CS, B_CS); cbi(P_RST, B_RST); delay(10); sbi(P_RST, B_RST)

#define fontbyte(x) pgm_read_byte(&cfont.font[x])  
#define bitmapbyte(x) pgm_read_byte(&bitmap[x])  

#define regtype volatile uint8_t
#define regsize volatile uint8_t
#define bitmapdatatype uint8_t*

Custom parts and enclosures

Case - Top
Case - Bottom

Schematics

Eagle Files
Schematic and PCB in Eagle format
eagle_files_ZswYe3VcUN.zip
Schematic
Schematic pbjvfzeumm

Comments

Similar projects you might like

Retro Gamer Clock

Project tutorial by John Bradnam

  • 932 views
  • 1 comment
  • 6 respects

ATtiny85 Mini Arcade: Snake

Project tutorial by Arduino “having11” Guy

  • 3,643 views
  • 7 comments
  • 22 respects

WW2 Tank Laser Tag Sherman & Panther

Project tutorial by Arduino “having11” Guy

  • 19,808 views
  • 1 comment
  • 60 respects

Marduino Party 1

Project tutorial by Arduino “having11” Guy

  • 1,980 views
  • 0 comments
  • 2 respects

Tacoyaki (Lights Out) Game

Project tutorial by John Bradnam

  • 991 views
  • 0 comments
  • 4 respects
Add projectSign up / Login