Original 8051 calculator showing a division result, AT89C51 calculator, 4x4 keypad input, Proteus LCD output

Design a Simple Calculator with 8051 Microcontroller

2.5K Views
40
700
60
25
60
PCBWay

Hello friends! Today, we are going to design a simple calculator with an 8051 microcontroller in Proteus. We will combine a 4×4 keypad, a 20×4 LCD and an AT89C51 so that you can enter an arithmetic operation and see its result on the display.

We have already covered LCD interfacing and keypad interfacing with the 8051. Those lessons provide the input and output functions. Here, we will add the application logic that decides which key is valid and when a calculation should take place.

This remains a single-digit calculator, like the original project. It accepts two operands from zero through nine and supports addition, subtraction, multiplication and integer division. We will also handle negative results, invalid input and division by zero explicitly.

Original 8051 calculator showing a division result, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Original 8051 calculator showing a division result.

What Can This Calculator Do?

Each calculation uses four keypresses: a digit, an operator, another digit and the equals key. For example, enter 2 + 3 = to obtain 5. The four-key sequence is part of the user interface, not merely a suggestion about how to type.

OperationExampleExpected result
Addition3 + 5 =8
Subtraction3 - 9 =-6
Multiplication9 x 9 =81
Integer division6 / 3 =2
Integer division with a remainder7 / 2 =3
Invalid division8 / 0 =Divide by zero message

The clear key resets the calculator's input state and clears the working display area. It does not need to reset the entire microcontroller. After a successful result, entering another digit begins a new calculation.

We do not support multi-digit operands, decimal points, expression precedence or chained operations in this version. Therefore, 12 + 13 is not a valid input sequence. Keeping the scope small lets us understand the relationship between input validation and arithmetic before building a more complicated parser.

Hardware and Proteus Circuit

The hardware is the same as our keypad demonstration. The keypad occupies Port 1, the LCD uses Port 2 for its eight-bit data bus, and P3.7 and P3.6 control the display. The AT89C51 runs with an 11.0592 MHz clock for the delay functions used in the preceding lessons.

AT89C51 calculator circuit with keypad and 20x4 LCD, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: AT89C51 calculator circuit with keypad and 20x4 LCD.

Part or signalConnection or purpose
AT89C51Executes the calculator application
Keypad rows A through DP1.0 through P1.3
Keypad columns 1 through 4P1.4 through P1.7
LCD D0 through D7P2.0 through P2.7
LCD RS and EP3.7 and P3.6 respectively
LCD R/WGround for write-only operation
Oscillator and reset networkProvide the processor's clock and startup conditions

The old drawing's manual-reset button must be corrected to assert the active-high reset input, as explained in the LCD article. For physical hardware, also check contrast adjustment, power decoupling and the exact microcontroller's electrical requirements.

A working keypad-only demonstration is a useful starting point. If pressing 7 displays 4 before the calculator logic is added, fix that row-mapping problem first. Arithmetic code cannot correct a scanner that reports the wrong character.

From a Key Character to a Number

The keypad scanner returns characters such as '3' and '+'. A character is not automatically the numerical value we want to calculate with. After checking that a key lies between '0' and '9', subtract '0' to obtain its digit value.

For example, '7' - '0' gives 7 because digit characters occupy consecutive positions in the character encoding used by this program. The range check is essential: applying the same subtraction to an operator does not produce a meaningful operand.

Keep the numeric operands separate from the text displayed on the LCD. The display may contain 3-9=, but the calculation uses integer variables containing 3 and 9 plus a variable identifying subtraction.

Why We Need an Input State

The meaning of a key depends on what has already been entered. A digit is valid at the start, but an operator is expected after that first digit. The equals key becomes valid only after both operands and the operator have been accepted.

We can represent those expectations with a small state machine. Each accepted key moves the program to the next state. An invalid key produces a clear error instead of allowing partially initialized values to reach the arithmetic function.

StateExpected inputAction
First operandDigit 0 through 9Store the first value
Operator+, -, x or /Store the selected operation
Second operandDigit 0 through 9Store the second value
Equals=Validate and calculate
ResultNew digit or CBegin a new entry or clear
ErrorCClear the error and restart entry

The C key is accepted in every state. Checking it before the state-specific logic avoids duplicating clear handling throughout the program. It also makes the interface predictable when the user wants to abandon an incomplete operation.

Integer Arithmetic and Its Limits

With operands between zero and nine, the largest sum is 18 and the largest product is 81. Subtraction ranges from -9 to 9. These small bounds make it easy to choose a display routine and reason about whether the result fits.

Use a signed integer for the result so that subtraction can produce a negative number. An unsigned result variable cannot represent -6 in the intended way; it would instead hold a value produced by unsigned arithmetic conversion.

Division in this program is integer division. For positive operands, the fractional part is discarded. Thus, 7 / 2 produces 3, with a remainder of 1. It does not produce 3.5, and adding a decimal point to the displayed integer would not recover the missing fraction.

The separate remainder operation is useful if you later want to display 3 remainder 1. Decimal output would require a deliberate fixed-point or floating-point design, including a precision and rounding policy. Keil's C51 arithmetic helper documentation provides toolchain context for the larger arithmetic operations a program may require.

Check the divisor before dividing

Always test the second operand before evaluating a division. If it is zero, display an error and return without executing the expression. Do not attempt the division first and hope that the result can be checked afterward.

This rule applies to 0 / 0 as well as 8 / 0. The calculator should explain that the operation is invalid rather than showing a fabricated numeric answer, an old result or a processor-dependent value.

Preparing the Program

Use the revised LCD driver from the LCD lesson and the scan_keypad and keypad_event functions from the keypad lesson. Keep the keypad row patterns, character map and debounce state. The LCD control declarations must remain P3.7 for RS and P3.6 for E.

Remove the keypad demonstration's main function and its input-row display application. Add the calculator application below instead. This code deliberately builds on those preceding functions; it is not a standalone replacement for the LCD driver and scanner.

For a single-file project, place the driver functions first, then the scanner, and finally this application. Alternatively, separate them into source files and provide matching declarations in headers. In either case, include only one main function and one definition of each helper.

Calculator Application Code

#define FIRST_DIGIT  0
#define OPERATOR     1
#define SECOND_DIGIT 2
#define EXPECT_EQUAL 3
#define SHOW_RESULT  4
#define SHOW_ERROR   5

unsigned char stage = FIRST_DIGIT;
int first_value = 0;
int second_value = 0;
char operation = 0;

bit is_digit(char key)
{
    return key >= '0' && key <= '9';
}

void calculator_clear(void)
{
    lcd_text(0, 0, "                    ");
    lcd_text(1, 0, "                    ");
    first_value = 0;
    second_value = 0;
    operation = 0;
    stage = FIRST_DIGIT;
}

void calculator_error(const char code *message)
{
    lcd_text(1, 0, "                    ");
    lcd_text(1, 0, message);
    stage = SHOW_ERROR;
}

void display_small_result(int value)
{
    lcd_text(1, 0, "                    ");
    lcd_position(1, 0);
    if (value < 0) {
        lcd_data('-');
        value = -value;
    }
    if (value >= 10) lcd_data('0' + value / 10);
    lcd_data('0' + value % 10);
}

void calculate(void)
{
    int result;

    switch (operation) {
        case '+': result = first_value + second_value; break;
        case '-': result = first_value - second_value; break;
        case 'x': result = first_value * second_value; break;
        case '/':
            if (second_value == 0) {
                calculator_error("Divide by zero: C");
                return;
            }
            result = first_value / second_value;
            break;
        default:
            calculator_error("Wrong operator: C");
            return;
    }
    display_small_result(result);
    stage = SHOW_RESULT;
}

void calculator_key(char key)
{
    if (key == 'C') {
        calculator_clear();
        return;
    }
    if (stage == SHOW_ERROR) return;
    if (stage == SHOW_RESULT) calculator_clear();

    switch (stage) {
        case FIRST_DIGIT:
            if (!is_digit(key)) {
                calculator_error("Enter one digit: C");
                return;
            }
            first_value = key - '0';
            lcd_position(0, 0);
            lcd_data(key);
            stage = OPERATOR;
            break;
        case OPERATOR:
            if (key != '+' && key != '-' && key != 'x' && key != '/') {
                calculator_error("Wrong operator: C");
                return;
            }
            operation = key;
            lcd_position(0, 1);
            lcd_data(key);
            stage = SECOND_DIGIT;
            break;
        case SECOND_DIGIT:
            if (!is_digit(key)) {
                calculator_error("Enter one digit: C");
                return;
            }
            second_value = key - '0';
            lcd_position(0, 2);
            lcd_data(key);
            stage = EXPECT_EQUAL;
            break;
        case EXPECT_EQUAL:
            if (key != '=') {
                calculator_error("Expected equals: C");
                return;
            }
            lcd_position(0, 3);
            lcd_data('=');
            calculate();
            break;
        default:
            calculator_error("State error: C");
            break;
    }
}

void main(void)
{
    char key;
    P1 = 0xFF;
    lcd_init();
    lcd_text(2, 1, "www.TheEngineering");
    lcd_text(3, 4, "Projects.com");
    calculator_clear();
    while (1) {
        key = keypad_event();
        if (key != 0) calculator_key(key);
        delay_ms(5);
    }
}

Why display_small_result has a limited range

The result function is intentionally small because the allowed arithmetic produces values from -9 through 81. It prints a minus sign when needed, then a tens digit if the magnitude is at least ten, followed by the units digit.

It is not a general integer-to-string function. If you later accept larger operands, you must update both the arithmetic range checks and the number formatting. Otherwise, a result such as 123 would not be displayed correctly by a routine designed for at most two magnitude digits.

The negation of a negative value is safe within the limited range of this application. A general signed-integer formatter must also consider the most negative representable value, whose positive magnitude may not fit in the same signed type.

How the display layout differs from the original

The expression appears on the first row and its result or error appears on the second. The lower two rows retain the website text. Keeping the result on its own row makes negative values and error messages easier to read.

Before writing a new result, the program replaces the complete result row with spaces. That prevents the last digits of a longer previous result from remaining visible when the next result is shorter.

Building and Running the Calculator

  1. Confirm that the LCD and keypad examples work with the same pin map.
  2. Combine their driver functions with the calculator application.
  3. Build the AT89C51 target in Keil and generate its HEX file.
  4. Load that file into the Proteus microcontroller and set the clock to 11.0592 MHz.
  5. Run the simulation and enter a four-key expression.
  6. Use C to clear an unfinished expression or recover from an error.

Calculator startup display in the original Proteus project, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Calculator startup display in the original Proteus project.

The original startup screenshot shows the project branding. In the revised application, the upper rows are ready for your expression while the footer remains visible. Do not use the hardware reset button as the normal calculator clear command.

Testing the Four Arithmetic Operations

Addition

Press 3, plus, 5 and equals. The operands are stored as integers, the addition produces 8, and the display function writes that result. This is also a useful first check that the operator and equals keys are mapped correctly.

Addition example on the 8051 calculator, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Addition example on the 8051 calculator.

Subtraction

Press 3, minus, 9 and equals. The result is -6. If the screen instead shows a large positive number, inspect the signedness of the variables and the formatting routine rather than changing the subtraction order.

Negative subtraction result on the calculator LCD, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Negative subtraction result on the calculator LCD.

Multiplication

Press 9, x, 9 and equals. The result is 81, the largest product supported by the single-digit operands. The keypad returns the character x; the C calculation itself uses the multiplication operator *.

Multiplication example producing 81, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Multiplication example producing 81.

Division

Press 6, division, 3 and equals. The result is 2. Then try 7 divided by 2 to see integer truncation, and 8 divided by zero to confirm that the error path prevents the division from being executed.

Integer division example producing 2, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Integer division example producing 2.

These original images illustrate the four operations. The refreshed application's result placement and error text may differ because it uses the clearer two-row layout described above.

Handling Incorrect Input

Pressing equals before entering a digit is invalid. Pressing two digits at the start is also invalid because this version expects an operator after the first digit. Rather than silently discarding the unexpected key, the application displays an explanation and waits for C.

Original calculator message for invalid input, AT89C51 calculator, 4x4 keypad input, Proteus LCD output
Figure: Original calculator message for invalid input.

Waiting for an explicit clear after an error prevents a leftover key sequence from accidentally becoming another calculation. For example, if a user types 12+3=, rejecting the second digit should not cause the later characters to be interpreted as an unrelated valid expression.

After a successful result, a new digit starts another expression. An operator entered immediately after a result is not treated as a chained operation. That behavior keeps the program consistent with its stated single-expression scope.

A Useful Test Table

Input sequenceExpected behavior
0 + 0 =Display 0
9 + 9 =Display 18
0 - 9 =Display -9
9 x 9 =Display 81
0 / 7 =Display 0
7 / 2 =Display 3
0 / 0 =Reject division and request clear
= at startupRequest a digit and clear
1 followed by 2Reject the second digit as an operator
C during any entryClear operands, operator and working rows

Also hold a key and confirm that the scanner creates only one event. A calculator can appear to have arithmetic errors when the actual problem is duplicated key input. Test each layer separately before combining symptoms into one diagnosis.

Download the Original Simulation and Source

The original project archive is preserved below. Its older source uses a different control flow and does not contain all the checks described in this revised application. Keep it as a reference rather than assuming its HEX file implements the updated behavior.

Download Proteus Simulation and Code

Build the revised source into its own HEX file and select that file in Proteus. If the simulator still shows the older behavior, check the program-file path before rewriting the arithmetic.

Extending the Calculator Carefully

To accept multi-digit numbers, accumulate each new digit with value = value × 10 + digit. Before doing so, check that the existing value is small enough for the chosen maximum. Otherwise, overflow can occur before the program has a chance to report an error.

A larger calculator also needs a defined policy for negative input, decimal places, repeated equals, operator replacement and expression precedence. These are user-interface and parsing decisions, not just extra cases in the arithmetic switch.

For fixed-point decimal results, choose a scale such as one hundred units per displayed unit and account for intermediate multiplication overflow. For floating point, consider program size and formatting support on the selected compiler. Neither approach should be added merely by changing the LCD text.

Project Review

Tracing one calculation through the program

For 3+5=, the first accepted event stores 3 and advances to the operator state. The plus key stores the operation and advances to the second-operand state. The 5 key stores the second value, and only the equals event finally calls the calculation function.

This sequence means the program never needs to guess whether an operand has already been entered. The state records that information explicitly. If the operator key is missing, the second digit is rejected at the operator stage instead of being used with an old operation from a previous expression.

Diagnosing common failures

If the expression shown on the LCD differs from the keys you pressed, investigate scanning, mapping and debouncing first. If the expression is correct but the result is wrong, inspect conversion, arithmetic and signedness. If the internal result is correct but the screen contains extra digits, inspect field clearing and formatting.

Keep Timer0 reserved for the shared delay function and avoid adding another routine that changes its mode or preload unexpectedly. Likewise, Port 1 belongs to the keypad and Port 2 belongs to the LCD. Reusing those resources for an unrelated output can create failures that look like calculator logic errors.

When debugging in Keil, observe the stage, first operand, second operand and operation after each accepted key. Those four values describe the application's progress much more clearly than repeatedly inspecting a large collection of unrelated registers. Return to the smallest failing input sequence before adding extra features or changing the hardware.

This project turns separate keypad and LCD exercises into a complete interaction. The most useful lesson is the separation between detecting a key, validating its meaning, performing arithmetic and formatting a result.

The single-digit limit keeps the arithmetic bounded and the interface easy to follow. Once those boundaries are understood, you can extend the design deliberately without losing control over invalid inputs, numeric ranges or display behavior.

Frequently Asked Questions

Why does 7 divided by 2 show 3?

The program uses integer division. Fractional output needs a different numeric and formatting design.

Can the calculator display negative results?

Yes. Subtraction can produce values down to -9, and the result function prints the minus sign separately.

Does C restart the microcontroller?

No. It resets the calculator's variables and working display area. The processor continues running normally.

Why are two-digit operands rejected?

The interface intentionally accepts one digit before the operator and one afterward. Multi-digit input requires a different entry state and range checks.

Can I use the archived HEX file with the new explanation?

The archive preserves the older implementation. Compile the revised application if you want the new state handling and divide-by-zero protection.


Comments

2

Join the conversation

Reply1

How to convert it into a 3 digit calculator? what change in the c code should we make? One other thing is that it is totally an integer calculator, what if it is to be converted into a floating point number calculator?