For some time I wanted to build a clock using a Nokia 3310 LCD but never had the time to create the digital font big enough to be readable at a distance and nice looking. After finding the Arduino code published by Pawel Kadluczka, on his blog Code, the Universe and everything, I converted it to use on a PIC 16F88.
There's only two hardware requirements for my version of the code to run on a PIC:
All code is in a single file to make it easy to understand.
The circuit runs from 5V and has its own 3.3V regulator. You can remove the 3.3V regulator and power it directly from 3.3V but the back light will be dimmer.
The schematic shows a PIC 16F88 with 2 crystals X1 and X2 but only X2 is really required because this PIC has an internal oscillator that you can use instead of X1. You can safely remove X1, C7 and C8 as long as you change the FUSES line in the code to setup the internal RC oscillator.
None, although the prototype was tested in a PCB from another project.
Mount it on a breadboard.
/*
* Clock for LCD 3310
* Adapted from arduino code by moozzyk
* Blog: http://blog.3d-logic.com/2012/08/26/digital-clock-on-arduino-uno-with-nokia-lcd-display/
* Source: https://github.com/moozzyk/ArduinoDigitalClock/blob/master/src/DigitalClock.pde
*
* Conversion made on 2015/06 Joao Figueiredo (http://www.enide.net)
*/
#include <htc.h>
// fuses for external crystal X1
__CONFIG(MCLREN & PWRTEN & BORDIS & LVPDIS & WDTDIS & HS);
// fuses for the internal oscillator, without X1, C7 and C8
//__CONFIG(MCLREN & PWRTEN & BOREN & LVPDIS & WDTDIS & INTIO);
#define LCDDATACOMM RB3
#define LCDSCE RB0
#define LCDRESET RB5
#define LCD_C 0
#define LCD_D 1
#define BACKLIGHT RA1
#define BACKLIGHTTRIS TRISA1
// --- Informative (not really used) -------------------------------
#define SDO RB2 // Serial Data Out
#define SDI RB1 // Serial Data In
#define SCK RB4 // Serial Clock
// ----------------------------------------------------------------------------
// SPI interface
// ----------------------------------------------------------------------------
void spiInit(void)
{
TRISB2 = 0; // SDO
TRISB1 = 1; // SDI
TRISB4 = 0; // SCK
SSPIE = 0; // Disable MSSP interrupt
SSPSTAT = 0xC0; // 0b1100 0000 Sampled at end and data transmited at rising edge of clock
SSPCON = 0x21; // 0b0010 0001 Enable CKP=0 SPI Master Fosc/16
}
void spiTxChar(unsigned char c)
{
SSPBUF = c; // Set char in SSPBUF to start transmission
while(!BF); // wait for data exchange to occur (polling)
c=SSPBUF; // Removes the garbage char to complete the transmission
}
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Real time clock using a LP 32768Hz crystal
// Timer 1 @ 1Hz
// ----------------------------------------------------------------------------
static unsigned char hour = 0, mins = 0, secs = 0;
static bit time_changed;
void rtcInit(void)
{
time_changed = 1; // compiler error: static initialization of bit types is illegal
ANS5 = 0; // for pin 12 RB6/T1OSO
ANS6 = 0; // for pin 13 RB7/T1OSI
TMR1H = 0x80;
TMR1L = 0;
TMR1IF = 0;
T1CON = 0x0F; // 16 bit timer with external clock
TMR1IE = 1; // enable TMR1 interrupt to fetch a new sample in the pwm buffer
PEIE = 1;
}
void rtcTick(void)
{
// retrigger timer 1 (check datasheet example 7.3, page 77)
TMR1H |= 0x80;
TMR1IF = 0;
++secs;
if (secs > 59)
{
secs = 0;
++mins;
if (mins > 59)
{
mins = 0;
++hour;
if (hour > 23)
{
hour = 0;
}
}
}
time_changed = 1;
}
bit rtcGetTime(unsigned char *h, unsigned char *m, unsigned char *s)
{
*h = hour;
*m = mins;
*s = secs;
if(time_changed)
{
time_changed = 0;
return 1;
}
else
{
return 0;
}
}
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Clock by moozzyk
// ----------------------------------------------------------------------------
const unsigned char Digits[][4][18] =
{
{
{ 0xE0, 0xF0, 0xF8, 0xF4, 0xEE, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xEE, 0xF4, 0xF8, 0xF0, 0xE0 },
{ 0x1F, 0x3F, 0x7F, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x3F, 0x1F },
{ 0xFC, 0xFE, 0xFF, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x03, 0x07, 0x0F, 0x17, 0x3B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3B, 0x17, 0x0F, 0x07, 0x03 },
},
{
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF8, 0xF0, 0xE0 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x3F, 0x1F },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0F, 0x07, 0x03 },
},
{
{ 0x00, 0x00, 0x00, 0x04, 0x0E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xEE, 0xF4, 0xF8, 0xF0, 0xE0 },
{ 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xDF, 0xBF, 0x7F, 0x3F, 0x1F },
{ 0xFC, 0xFE, 0xFF, 0xFE, 0xFD, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00 },
{ 0x03, 0x07, 0x0F, 0x17, 0x3B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00 },
},
{
{ 0x00, 0x00, 0x00, 0x04, 0x0E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xEE, 0xF4, 0xF8, 0xF0, 0xE0 },
{ 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xDF, 0xBF, 0x7F, 0x3F, 0x1F },
{ 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFD, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3B, 0x17, 0x0F, 0x07, 0x03 },
},
{
{ 0xE0, 0xF0, 0xF8, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF8, 0xF0, 0xE0 },
{ 0x1F, 0x3F, 0x7F, 0xBF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xDF, 0xBF, 0x7F, 0x3F, 0x1F },
{ 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFD, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0F, 0x07, 0x03 },
},
{
{ 0xE0, 0xF0, 0xF8, 0xF4, 0xEE, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x04, 0x00, 0x00, 0x00 },
{ 0x1F, 0x3F, 0x7F, 0xBF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFD, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3B, 0x17, 0x0F, 0x07, 0x03 },
},
{
{ 0xE0, 0xF0, 0xF8, 0xF4, 0xEE, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x04, 0x00, 0x00, 0x00 },
{ 0x1F, 0x3F, 0x7F, 0xBF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00 },
{ 0xFC, 0xFE, 0xFF, 0xFE, 0xFD, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFD, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x03, 0x07, 0x0F, 0x17, 0x3B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3B, 0x17, 0x0F, 0x07, 0x03 },
},
{
{ 0x00, 0x00, 0x00, 0x04, 0x0E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xEE, 0xF4, 0xF8, 0xF0, 0xE0 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x3F, 0x1F },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0F, 0x07, 0x03 },
},
{
{ 0xE0, 0xF0, 0xF8, 0xF4, 0xEE, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xEE, 0xF4, 0xF8, 0xF0, 0xE0 },
{ 0x1F, 0x3F, 0x7F, 0xBF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xDF, 0xBF, 0x7F, 0x3F, 0x1F },
{ 0xFC, 0xFE, 0xFF, 0xFE, 0xFD, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFD, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x03, 0x07, 0x0F, 0x17, 0x3B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3B, 0x17, 0x0F, 0x07, 0x03 },
},
{
{ 0xE0, 0xF0, 0xF8, 0xF4, 0xEE, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0xEE, 0xF4, 0xF8, 0xF0, 0xE0 },
{ 0x1F, 0x3F, 0x7F, 0xBF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xDF, 0xBF, 0x7F, 0x3F, 0x1F },
{ 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFD, 0xFE, 0xFF, 0xFE, 0xFC },
{ 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3B, 0x17, 0x0F, 0x07, 0x03 },
}
};
const unsigned char SecondIndicator[4] =
{
0x00, 0x07, 0x70, 0x00
};
void LcdInitialise(void)
{
PORTB = 0; // nah... just in case let's start with every pin at 0
TRISB3 = 0; // D/_C
TRISB0 = 0; // _SCE
TRISB5 = 0; // _RES
LCDRESET = 1; // RESET pin high
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop"); // 1us delay with a 20MHz XTAL
LCDRESET = 0; // !!!!RESET!!!!
LCDSCE = 1; // disable LCD CE
LCDDATACOMM = 0;// change to command mode
// minimum reset time is 100ns!
LCDRESET = 1; // RESET over
spiInit();
// inititalization routine from the moozzyk (author of the clock)
LCDSCE = 0;
LCDDATACOMM = 0;
spiTxChar( 0x21 ); // LCD Extended Commands.
spiTxChar( 0x80 | 0x0A /*0xC8*/ ); // Set LCD Vop (Contrast)
spiTxChar( 0x06 ); // Set Temp coefficent
spiTxChar( 0x14 ); // LCD bias mode 1:48
spiTxChar( 0x20 ); // LCD Standard Commands.
spiTxChar( 0x0C ); // LCD in normal mode. 0x0d for inverse
LCDSCE = 0;
}
void LcdWrite(unsigned char dc, unsigned char data)
{
LCDSCE = 0; // enable LCD
LCDDATACOMM = dc; // COMMAND/DATA mode depending on argument
spiTxChar( data );
LCDSCE = 1; // disable LCD
}
void LcdClear(void)
{
unsigned int i;
LCDSCE = 0; // enable LCD
LCDDATACOMM = 0; // COMMAND mode and send GotoXY
spiTxChar( 0x80 | 0x00 ); // x=0
spiTxChar( 0x40 | 0x00 ); // y=0
LCDDATACOMM = 1; // DATA mode
for(i=0; i<504; i++)
spiTxChar(0); // fill entire screen with 0
LCDSCE = 1; // disable LCD
}
void Spacer()
{
LcdWrite(LCD_D, 0x00);
LcdWrite(LCD_D, 0x00);
}
void DrawSecondsBar(unsigned char seconds)
{
// Position the pointer
LcdWrite(LCD_C, 0x80 | 0x0b);
LcdWrite(LCD_C, 0x44);
// Draw the left side of the progress bar box
LcdWrite(LCD_D, 0xF0);
for(unsigned char i = 0; i < 59; i++)
{
if(i < seconds)
{
LcdWrite(LCD_D, 0xF0);
}
else
{
LcdWrite(LCD_D, 0x90);
}
}
// Draw the right side of the progress bar box
LcdWrite(LCD_D, 0xF0);
}
void DisplaySecondIndicator(unsigned char row, unsigned char show)
{
signed char secondIndicatorSegment;
for(secondIndicatorSegment = 0; secondIndicatorSegment < 3; secondIndicatorSegment++)
{
if(show)
{
LcdWrite(LCD_D, SecondIndicator[row]);
}
else // clear
{
LcdWrite(LCD_D, 0x00);
}
}
Spacer();
}
void DisplayTime(unsigned char hour, unsigned char minutes, unsigned char seconds)
{
unsigned char row;
unsigned char col;
unsigned char digit;
unsigned char components[4];
components[0] = (unsigned char)(hour / 10);
components[1] = (unsigned char)(hour % 10);
components[2] = (unsigned char)(minutes / 10);
components[3] = (unsigned char)(minutes % 10);
for(row = 0; row < 4; row++)
{
LcdWrite(LCD_C, 0x80 | 0);
LcdWrite(LCD_C, 0x40 | row);
for(digit = 0; digit < 4; digit++)
{
for(col = 0; col < 18; col++)
{
LcdWrite(LCD_D, Digits[components[digit]][row][col]);
}
Spacer();
// Display second indicator after the second digit
if(digit == 1)
{
DisplaySecondIndicator(row, seconds & 0x01);
}
}
}
DrawSecondsBar(seconds);
}
void InitializeDisplay()
{
LcdInitialise();
LcdClear();
}
// ----------------------------------------------------------------------------
void setup(void)
{
BACKLIGHTTRIS = 0;
BACKLIGHT = 1; // backlight on
CMCON = 0x07; // disable the analog comparator
CVRCON = 0x00; // disable the voltage reference module
ANSEL = 0; // disable ADC
rtcInit();
InitializeDisplay();
GIE = 1;
}
void interrupt isr(void)
{
if (TMR1IF)
{
rtcTick();
}
}
void loop(void)
{
unsigned char h, m, s;
rtcGetTime(&h, &m, &s);
DisplayTime(h, m, s);
asm("sleep");
}
void main(void)
{
setup();
while(1)
loop();
}
The prototype in the pictures below runs on the same PCB of Baby Night Light & LCD project because it was faster to test the code using that PCB instead of using a breadboard.
First picture of the clock running after power on. The bar under the digits represents the seconds and fills until they reach 59.
List of files from the project
Published on Friday 2015/06/26, last modified on Monday 2015/07/13