Calculator with PIC microcontroller (PIC16F690)

Let’s create simple calculator using microcontroller.
I will be using Microchip PIC16F690, LCD Display (1602A-1), and “4×4 Universal 16 Key Keypad For Arduino”.

We will start with creating simulation with Proteus and then create real device.

But let’s start with creating new project in MPLAB X IDE.
– Start MPLAB X IDE, go to File -> New Project.
– Microchip Embedded; Standalone Project
– Family: Mid-Range 8-bit MCUs (PIC10/12/16/MCP); Device: PIC16F690
– Supported Debug Header: None
– Select Tools: Proteus VSM Viewer
– Select Compiler: XC8 (v…)
– Project name: CalcPIC690;
– Finish
– Right click on Source Files in Project Tree (on the left side) -> New -> Select main.c; Name: main; click Finish
– Go to Production menu and click on Set Configuration Bits

– Select FOSC: HS (HS oscillator) and WDTE: OFF (watch dog timer)
– Click Generate Source Code to Output
– Copy/Paste configuration into main.c
– Go to Production menu and click on Build Main Project

Let’s create simulation with Proteus now.
– Start Proteus, go to File -> New Project.
– Name: CalcPIC690.pdsprj, Path: where MPLAB X IDE project is located
– Schematic design: DEFAULT
– Do not create PCB layout
– No Firmware Project
– Finish

– Press P (or go to Library menu -> Pick parts from libraries; or click on P on the left)

– Type pic16f690 in Keywords, select found item, click OK
– Type keypad in Keywords, select KEYPAD-SMALLCALC, click OK
– Type LM016L in Keywords, select found item, click OK
– Type CRYSTAL in Keywords, select CRYSTAL, click OK
– Type CAP in Keywords, select CAP, click OK

– Select Component mode

– Select a component from the list and place it on the design

– Connect them together:
Keypad -> microcontroller
Row A -> RC0
Row B -> RC1
Row C -> RC2
Row D -> RC3
Col 1 -> RC4
Col 2 -> RC5
Col 3 -> RC6
Col 4 -> RC7
Display -> microcontroller
RS -> RA0
E -> RA1
D4 -> RB4
D5 -> RB5
D6 -> RB6
D7 -> RB7
Crystal -> microcontroller
to RA4 and RA5

– Click on PIC microcontroler in the design; edit dialog should appear: set Processor Clock Frequency: 8MHz
– Select Temmanal mode, select GROUND
– Place ground near capasitors and one near display
– Select POWER
– Place power near display

– Connect them together:

– Go to Design menu -> Configure Power Rails
– Verify that there are no unconnected power nets (VSS and GND -> GND; VDD and VCC -> VCC/VDD)
– Save project and close Proteus.

Now we are going back to MPLAB X IDE
– Right click on Project name on the left -> Properties.
– Select Proteus VSM Viewer
– Click on Design file name and select Proteus project file we just created (CalcPIC690.pdsprj)

– Go to Debug menu and click Debug Main Project
A Proteus simulation should appear, but since we didn’t write any code it would do nothing.
– Close Proteus.

Let’s write some code now.
When I start learning programming I didn’t have access to any computer. So I was learning on a programmable calculator (and ever that one was borrowed). It was very interesting calculator, it used reverse Polish notation: it’s when operation follows operand(s); e.g. to calculate sum of two numbers on a regular calculator you press |2| |+| |2| |=|, in reverse notation calculator you press |2| |push| |2| |+|, where |push| was a special key to move first operand to another register. That calculator supported for registers, so for example to calculate 8 * (3 + 2 + 1) you can do this:
|8| |push| |3| |push| |2| |push| |1| |+| |+| |*|.
And that’s how I would like my calculator to work. So I need to create a stack where I would push all the values:


const int MAX_SIZE = 4;
double stack[4];
int stored_items = 0;
//note that there is no check for capacity, if fifth number is pushed, first one would be lost
void push(double x)
{
    stack[3] = stack[2];
    stack[2] = stack[1];
    stack[1] = stack[0];
    stack[0] = x;
    stored_items++;
    if(stored_items > 4) stored_items = 4;
}
// returns 0 if stack is empty, 1 if value is available
// if x is not empty x is set with last stacked value and last stacked value is removed from stack
// if x is null then stack is untouched (use this to check if stack is empty)
int pop(double* x)
{
    if(stored_items <= 0) 
        return 0;
    
    if(!x) return 1;
    
    *x = stack[0];
    stack[0] = stack[1];
    stack[1] = stack[2];
    stack[2] = stack[3];
    stack[3] = 0.0;
    stored_items--;
    if(stored_items < 0) stored_items = 0;
    return 1;
}
// returns 0 if stack is empty, 1 if value is available
// if x is not empty x is set with last stacked value and stack is untouched 
// if x is null then stack is untouched (use this to check if stack is empty)
int get(double* x)
{
    if(stored_items <= 0) 
        return 0;
    
    if(x)*x = stack[0];
    
    return 1;
}

One problem I have is for real project I am using "4x4 Universal 16 Key Keypad For Arduino", but Proteus doesn't have it and I use KEYPAD-SMALLCALC instead. And it has it's number reversed - my keypad:
|1|2|3|A|
|4|5|6|B|
|7|8|9|C|
|*|0|#|D|

Proteus calculator:
|7|8|9|/|
|4|5|6|x|
|1|2|3|-|
|C|0|=|+|

So our code should be flexible and accommodate both keypad. I do it using #define PROTEUS and mapping key differently:


#ifdef PROTEUS        
        if (COL1 == HIGH) return 0x7;		// Key '1' is pressed (Proteus: 7)
        if (COL2 == HIGH) return 0x8;		// Key '2' is pressed (Proteus: 8)
        if (COL3 == HIGH) return 0x9;		// Key '3' is pressed (Proteus: 9)
#else
        if (COL1 == HIGH) return 0x1;		// Key '1' is pressed (Proteus: 7)
        if (COL2 == HIGH) return 0x2;		// Key '2' is pressed (Proteus: 8)
        if (COL3 == HIGH) return 0x3;		// Key '3' is pressed (Proteus: 9)
#endif

And here is the final code:


/*
 * File:   main.c
 * Author: akirik
 *
 * Created on February 16, 2018, 12:52 PM
 * 
 * Calculator
 * Using LCD Display (1602A-1) in 4-bit mode
 * and "4x4 Universal 16 Key Keypad For Arduino"
 * 
 *  Board connection (PICKit 2 Low Count Demo; PIC16F690):
 *   PIN                	Module                         				  
 * -------------------------------------------                        
 *    RC7              C4 (pin 1)
 *    RC6              C3 (pin 2)
 *    RC5              C2 (pin 3)
 *    RC4              C1 (pin 4)
 *    RC3              R4 (pin 5)
 *    RC2              R3 (pin 6)
 *    RC1              R2 (pin 7)
 *    RC0              R1 (pin 8)
 *
 *    RA1              RS (register select: 0 = instruction; 1 = character)
 *    RA0              E  (enable signal: 0 = access disabled; 1 = access enabled)
 *    RB4              DB4
 *    RB5              DB5
 *    RB6              DB6
 *    RB7              DB7
 *
 */

#define PROTEUS // use this for Proteus similation, as keypad numbers are reversed (7-8-9 instead of 1-2-3)

#define RS RA1
#define EN RA0
#define D4 RB4
#define D5 RB5
#define D6 RB6
#define D7 RB7

//4x4 Keypad
#define ROW1			PORTCbits.RC0		// Row 1
#define ROW2			PORTCbits.RC1		// Row 2
#define ROW3			PORTCbits.RC2		// Row 3
#define ROW4			PORTCbits.RC3		// Row 4
#define COL1			PORTCbits.RC4		// Column 1
#define COL2			PORTCbits.RC5		// Column 2
#define COL3			PORTCbits.RC6		// Column 3
#define COL4			PORTCbits.RC7		// Column 4

#define HIGH 1
#define LOW 0

/* The __delay_ms() function is provided by XC8. 
It requires you define _XTAL_FREQ as the frequency of your system clock. 
The compiler then uses that value to calculate how many cycles are required to give the requested delay. 
There is also __delay_us() for microseconds and _delay() to delay for a specific number of clock cycles. 
Note that __delay_ms() and __delay_us() begin with a double underscore whereas _delay() 
begins with a single underscore.
*/
#define _XTAL_FREQ 8000000

// CONFIG
// PIC16F690 Configuration Bit Settings
#pragma config FOSC     = HS        // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN)
#pragma config WDTE     = OFF       // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE    = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE    = ON        // MCLR Pin Function Select bit (MCLR pin function is MCLR)
#pragma config CP       = OFF       // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD      = OFF       // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN    = ON        // Brown-out Reset Selection bits (BOR enabled)
#pragma config IESO     = ON        // Internal External Switchover bit (Internal External Switchover mode is enabled)
#pragma config FCMEN    = ON        // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)

#include 
#include 
//#include  // for itoa
#include  // for printf, please not that XC8 does not support %f, $g, %e (for me at least)

void LCDSetData(char x)
{
	D4 = x & 1 ? 1 : 0;
	D5 = x & 2 ? 1 : 0;
	D6 = x & 4 ? 1 : 0;
	D7 = x & 8 ? 1 : 0;
}

/*
    Code (Hex)  lcd command
    1           Clear display screen
    2           Return home
    4           Decrement cursor (shift cursor to left)
    6           Increment cursor (shift cursor to right)
    5           Shift display right
    7           Shift display left
    8           Display off, cursor off
    A           Display off, cursor on
    C           Display on, cursor off
    E           Display on, cursor blinking
    F           Display on, cursor blinking
    10          Shift cursor position to left
    14          Shift cursor position to right
    18          Shift the entire display to the left
    1C          Shift the entire display to the right
    80          Force cursor to beginning to 1st line
    C0          Force cursor to beginning to 2nd line
    28          2 lines and 5×7 matrix (4-bit mode)
    38          2 lines and 5×7 matrix (8-bit mode)
*/
void LCDSendCommand(char cmd)
{
	LCDSetData(cmd);
	RS = 0;              
	EN  = 1;             
    __delay_ms(4);
    EN  = 0;             
}

void LCDSendData(char n)
{
   uint8_t firstNibble = n & 0xF0;
   uint8_t secondNibble = n & 0x0F;
   RS = 1;             
   LCDSetData(firstNibble >> 4); 
   EN = 1;
   __delay_us(40);
   EN = 0;
   LCDSetData(secondNibble);
   EN = 1;
   __delay_us(40);
   EN = 0;
}

void LCDCleanScreen()
{
	LCDSendCommand(0x00);
	LCDSendCommand(0x01);//Clear display screen
}

void LCDSetCursor(char row, char column)
{
    char offset = 1 == row ? 0x80 : 0xC0;//address of first character/first row - 0x80, address of first character/second row - 0xC0
    char temp = offset + column - 1;
	LCDSendCommand(temp >> 4);
	LCDSendCommand(temp & 0x0F);
}

void LCDInitialize()
{
  LCDSetData(0x00);
   __delay_ms(20);
  LCDSendCommand(0x03);//send reset
	__delay_ms(5);
  LCDSendCommand(0x03);//NOTE this is not copy/paste error, we have to do it three times
	__delay_ms(11);
  LCDSendCommand(0x03);//NOTE this is not copy/paste error, we have to do it three times
  
  LCDSendCommand(0x02);
  LCDSendCommand(0x02);
  LCDSendCommand(0x08);
  LCDSendCommand(0x00);
  LCDSendCommand(0x0C);
  LCDSendCommand(0x00);
  LCDSendCommand(0x06);
}

void LCDPrint(char *pstr)
{
    for(; *pstr; ++pstr)
	   LCDSendData(*pstr);
}

void LCDPrintD(double d)
{
	char str[17];

	char *sign = (d < 0) ? "-" : "";
	double absd = (d < 0) ? -d : d;

	int interger = absd;						// Get the integer part
	double fraction = absd - interger;			// Get fraction part
	int interger_fraction = fraction * 1000;	// Turn fraction part into integer

	if(interger_fraction > 0)
		sprintf(str, "%s%d.%03d", sign, interger, interger_fraction);
	else
		sprintf(str, "%s%d", sign, interger);
    
	LCDPrint(str);
}

unsigned int keypad_readkey(void)
{
    // scan 1st row: 1, 2, 3, A
        ROW1 = HIGH;		
        ROW2 = LOW;
        ROW3 = LOW;
        ROW4 = LOW;
        __delay_us(30);
#ifdef PROTEUS        
        if (COL1 == HIGH) return 0x7;		// Key '1' is pressed (Proteus: 7)
        if (COL2 == HIGH) return 0x8;		// Key '2' is pressed (Proteus: 8)
        if (COL3 == HIGH) return 0x9;		// Key '3' is pressed (Proteus: 9)
#else
        if (COL1 == HIGH) return 0x1;		// Key '1' is pressed (Proteus: 7)
        if (COL2 == HIGH) return 0x2;		// Key '2' is pressed (Proteus: 8)
        if (COL3 == HIGH) return 0x3;		// Key '3' is pressed (Proteus: 9)
#endif
        if (COL4 == HIGH) return 0xA;		// Key 'A' is pressed, we will store as 10 (Proteus: /)
    
    // scan 2nd row: 4, 5, 6, B
        ROW1 = LOW;		
        ROW2 = HIGH;
        ROW3 = LOW;
        ROW4 = LOW;
        __delay_us(30);
        if (COL1 == HIGH) return 0x4;		// Key '4' is pressed
        if (COL2 == HIGH) return 0x5;		// Key '5' is pressed
        if (COL3 == HIGH) return 0x6;		// Key '6' is pressed
        if (COL4 == HIGH) return 0xB;		// Key 'B' is pressed, we will store as 11 (Proteus: x)

	// scan 3rd row: 7, 8, 9, C
        ROW1 = LOW;		
        ROW2 = LOW;
        ROW3 = HIGH;
        ROW4 = LOW;
        __delay_us(30);
#ifdef PROTEUS        
        if (COL1 == HIGH) return 0x1;		// Key '7' is pressed (Proteus: 1)
        if (COL2 == HIGH) return 0x2;		// Key '8' is pressed (Proteus: 2)
        if (COL3 == HIGH) return 0x3;		// Key '9' is pressed (Proteus: 3)
#else
        if (COL1 == HIGH) return 0x7;		// Key '7' is pressed (Proteus: 1)
        if (COL2 == HIGH) return 0x8;		// Key '8' is pressed (Proteus: 2)
        if (COL3 == HIGH) return 0x9;		// Key '9' is pressed (Proteus: 3)
#endif
        if (COL4 == HIGH) return 0xC;		// Key 'C' is pressed, we will store as 12 (Proteus: -)

	// scan 4th row: *, 0, #, D
        ROW1 = LOW;		
        ROW2 = LOW;
        ROW3 = LOW;
        ROW4 = HIGH;
        __delay_us(30);
        if (COL1 == HIGH) return 0xE;		// Key '*' is pressed, we will store as 14 (Proteus: ON/C)
        if (COL2 == HIGH) return 0x0;		// Key '0' is pressed
        if (COL3 == HIGH) return 0xF;		// Key '#' is pressed, we will store as 15 (Proteus: =) works as PUSH
        if (COL4 == HIGH) return 0xD;		// Key 'D' is pressed, we will store as 13 (Proteus: +)
    
    ROW1 = LOW;		
	ROW2 = LOW;
	ROW3 = LOW;
	ROW4 = LOW;

	return 0xFF;					// if no key press, the register is 0xFF
}

unsigned int keypad_getkey(void)
{
	unsigned int key = 0xFF;	
	
	// wait until any key is pressed
	while(key == 0xFF)
		key = keypad_readkey();
	
	// wait until that key is released
	while(keypad_readkey() != 0xFF);
	
	return key;
}	

void system_init()
{
    OSCCON=0x70;          // Select 8 Mhz internal clock

    // ANSELx registers
	// ANSEL and ANSELH control the mode of AN0 through AN11:
	// 0 sets the pin to digital mode and 1 sets the pin to analog mode.
        ANSEL = 0x00;         // Set PORT ANS0 to ANS7  as Digital I/O
        ANSELH = 0x00;        // Set PORT ANS8 to ANS11 as Digital I/O
  
    // TRISx registers
	// This register specifies the data direction of each pin:
	// O - output, 1 - input
        TRISA = 0x00;         // Set All on PORTA as Output    
        TRISB = 0x00;         // Set All on PORTB as Output    
        TRISC = 0b11110000;   // Set PORTC 0-3 Output, 4-7 Input (for keypad columns)
    
    // PORT registers 
	// Hold the current digital state of the digital I/O
    // If you read these registers, you can determine which pins are currently HIGH or LOW
    // Writing to the PORTX registers will set the digital output latches. 
    // Writing to a pin that is currently an input will have no effect on the pin because the output latch will be disabled.
        PORTA = 0x00;         // Set PORTA all 0
        PORTB = 0x00;         // Set PORTB all 0
        PORTC = 0x00;         // Set PORTC all 0    
}

const int MAX_SIZE = 4;
double stack[4];
int stored_items = 0;
//note that there is no check for capacity, if fifth number is pushed, first one would be lost
void push(double x)
{
    stack[3] = stack[2];
    stack[2] = stack[1];
    stack[1] = stack[0];
    stack[0] = x;
    stored_items++;
    if(stored_items > 4) stored_items = 4;
}
// returns 0 if stack is empty, 1 if value is available
// if x is not empty x is set with last stacked value and last stacked value is removed from stack
// if x is null then stack is untouched (use this to check if stack is empty)
int pop(double* x)
{
    if(stored_items <= 0) 
        return 0;
    
    if(!x) return 1;
    
    *x = stack[0];
    stack[0] = stack[1];
    stack[1] = stack[2];
    stack[2] = stack[3];
    stack[3] = 0.0;
    stored_items--;
    if(stored_items < 0) stored_items = 0;
    return 1;
}
// returns 0 if stack is empty, 1 if value is available
// if x is not empty x is set with last stacked value and stack is untouched 
// if x is null then stack is untouched (use this to check if stack is empty)
int get(double* x)
{
    if(stored_items <= 0) 
        return 0;
    
    if(x)*x = stack[0];
    
    return 1;
}

void main(void) 
{
    system_init();
    
    // LCD Display setup
    LCDInitialize();
    
    double nCurrentValue = 0;
    double nPushedValue = 0;
    int bAutoPush = 0;
    while(1)
    {
        LCDCleanScreen();
        LCDSetCursor(2,1);
        LCDPrintD(nCurrentValue);
        
        if(get(&nPushedValue) > 0)  {
            LCDSetCursor(1 ,1);
            LCDPrintD(nPushedValue);
        }
        __delay_ms(10);
        
        unsigned int key = keypad_getkey(); // get pressed button (wait)
        if(key > 16) key = 16;              // safeguard
        
        if(key < 10)  { // number
            if(1 == bAutoPush)  {
                bAutoPush = 0;
                push(nCurrentValue);
                nCurrentValue = key;
            }
            else
                nCurrentValue = nCurrentValue * 10 + key;
        }
        else
        if(key == 0xE)  { //ON/C
            nCurrentValue = 0;
            pop(0);
            pop(0);
            pop(0);
            pop(0);
        }
        else
        if(key == 0xF)   { // push
            push(nCurrentValue);
            nCurrentValue = 0;
        }
        else
        if(key == 0xD)   { // +
            double upvalue = 0;
            if(1 == pop(&upvalue))  {
                nCurrentValue += upvalue;
                bAutoPush = 1;
            }
        }
        else
        if(key == 0xA)   { // /
            if(0 == nCurrentValue)  {
                LCDCleanScreen();
                LCDSetCursor(2,1);
                LCDPrint("ERROR");
                __delay_ms(1000);
            }
            else
            {
                double upvalue = 0;
                if(1 == pop(&upvalue))  {
                    nCurrentValue = upvalue / nCurrentValue;
                    bAutoPush = 1;
                }
            }
        }
        else
        if(key == 0xB)   { // x
            double upvalue = 0;
            if(1 == pop(&upvalue))  {
                nCurrentValue *= upvalue;
                bAutoPush = 1;
            }
        }
        else
        if(key == 0xC)   { // -
            double upvalue = 0;
            if(1 == pop(&upvalue))  {
                nCurrentValue = upvalue - nCurrentValue;
                bAutoPush = 1;
            }
        }
        
        __delay_ms(10);
    }
    
	return;
}

Leave a Reply