Friday, March 22, 2013

Calculator

I had an LCD (LMB0820, available from Seeedstudio, datasheet link here) that I wanted to try out, and in conjunction with a keypad (96BB2-056-R Grayhill, available from Digikey, datasheet link here), I thought it would be interesting and easy to make a calculator.  It turned out to be a whole lot more challenging that I thought.


The electronics for this project was fairly straightforward.  The LCD has eleven pins to wire up, so in conjunction with the eight for the keypad, this project used up all but one of the I/O pins on a Arduino Duemilanove.  I also wired power through a switch on the front.  Incidentally, I found out not to use pin13 for the keypad.  Apparently the resistor required for the built-in LED messes you up!  Pin 13 worked fine for the LCD though.  This word document lists the Arduino pins used for the Keypad and for the LCD.

The hardest part of this project was most definitely the coding aspect.  


I started by reading in pressed keys into a character array, displaying each key on the top line of the LCD by simply moving the cursor to the right one time after each button was pressed.  The numbers are stored in an 9 element char array (last character is a null character) which limits the size of input numbers to 8 characters.  Numbers are added to the last element in the array, shifting every other element to the left first.  This is important, because if the input buffer has the following form: {'0','0','0','0','0','0','3','4',null}, it will convert easily to the number 34.  Conversely, if you were to read in characters from the left side, {'3','4','0','0','0','0','0','0',null}, you would obtain the value 34,000,000 when converting to a number. 

I later added support for negative numbers (which now limited the size of input numbers to 7 characters as the first character location on the top LCD line is reserved for the negative sign).  I also added a decimal button.  Fortunately, the built in function "atof" converts a char array of the form {'-','0','0','0','0','0','3','4',null} to -34, and a char array of the form {'0','0','0','2','.','0','3','4',null} to 2.034. 

I used two buttons on the keypad as up/down scroll keys, moving through a list of functions that I added.  Currently the calculator supports addition, subtraction, multiplication, division, powers, squaring, and tangent, cosine, sin.  But it would be pretty easy to add other functions in.  A mod function makes the scrolling effortless.
The approach that a calculator takes depends on whether the mathematical function desired takes one or two numbers.  The trig functions and squaring require only a single number, while the other functions require two numbers.   The first time the "equals" sign is pressed, the program stores the first number (using the built in function "atof" which maps a character array into a floating point variable) and the desired function.   The program prints an error message "FUNCT??" if the user does not have a function selected when the "equals" key is pressed.

If a single number function is selected, the code executes the function and prints the result.  Otherwise, the code clears the top line on the LCD and "zeros" the input buffer to make room for a second number.  The second time the "equals" sign is read, the code reads the second number, executes the function, and prints the response. 
There are several inherent difficulties with a calculator.  The first has to do with the limited size of an LCD display.  This is a problem for both inputs and outputs.  For inputs, I chose to simply limit the program to a maximum number of input digits.  Additional pressed buttons are just not registered.   Otherwise one would have to treat memory more dynamically, allocating arrays to fit the size of the input numbers.  This would be much more efficient, but I don't know how to use malloc at this time.  Also, the top line of the display would need to scroll to allow the user to see what had already been inputed as well as what they were inputing currently.  This was not worth the work.

In terms of outputs, we have two potential problems.  The first is if the result of the calculation is bigger than eight characters.  My code returns "too big" if the result exceeds 99999999 or is less than -9999999.  The second problem is if the number is too small.  Currently the code always prints 4 decimal places, so if the response is less than 0.0001 but greater than zero, then the code prints "too small".   Better programs would implement scientific notation of some type.

I worked to make the interface clean, although it still needs more work.  I added a function which parses the output to find the decimal place.  It reads the values after the decimal place and decides the number of decimals to print by discarding any trailing zeros.  Hence, the result of 25/5 is printed as 5 (not 5.0000), while 6/5 is printed as 1.2 (not 1.2000), 6.1/5 is printed as 1.22 (not 1.2200), and so forth. 
If the user wants to use a function which needs a second number, I added code which shows what has been input on the second line.  

I also added code which keeps a running string which includes the first number pressed, the function pressed, the second number pressed, and an equals sign (for functions which work with two numbers) OR the first number pressed and the function pressed plus an equals sign (for functions which work on a single number), then displays this string on the top line.  The top line will scroll to the left continuously if the string is too long to fit on the LCD line.

I thought that it would be fun to use the calculator as a "game".  So I added an initialization routine which asks the user to choose between calculator and game mode.  In game mode, when you enter a number and press "Enter", it executes a function on the number and returns the result.  The user has to guess the function by trying to figure out the rule being applied to the numbers.  See if you can figure out the rule from the movie below.

It would be easy to add more functionality to the game such that there were different levels with increasingly harder functions, or even functions which act on two input numbers.  

The code has some issues with precision in floating point arithmetic; when I compare the results from a TI-83 calculator to the results of the Arduino calculator, they sometimes differ in the last digit.  It is a little buggy; I've seen weird characters displayed on the LCD and it has frozen a couple of times on me.   Despite these issues, and the obnoxious usage of the "equals" button, the calculator works pretty well.  I'm sure I will clean up the bugs over time.

When I have time, I will add a new overlay for the keypad which shows more clearly how to use it (eg up and down arrows, an equals key, +/- key, and decimal key).