Introduction: Arduino Scientific Calculator

Picture of Arduino Scientific Calculator

UPDATED Section 4 on 20130301 to show Analog Pin Read (APR) code and updated Digital Pin Read (DPR) code.

I recently went Googling for examples of calculator programs (sketches) for my Arduino Nano.  I found a number of examples on how to interface an Arduino to the keyboard of an old calculator, but did not get any hits of pre-written sketches for turning an Arduino into a scientific calculator... you know, the ones with Sin, Cos, Tan, and etc. (Excepting one clever implementation of a full version of an HP 45 but it required a massive IBM PS/2 keyboard.)

Why would anyone want such a thing?  The answer to this question is probably why I could not find anything on the Internet!  But I had my reasons and so I was forced to write my own sketch.  I sat down with paper and pencil and wrote down (while looking at my old HP instruction manual from college) and came up with a list of things I wanted: the obvious +, -, *, and / (add, subtract, multiply, and divide) and the more involved ones: Sin, Cos, Tan, SQR, Log, and so on.  Then there were the nice to have power functions.  Then the arcSin, arcCos, and arcTan stuff.  The list got a bit long... too long to assign a single keyboard character to each operation.

Then there was the concern about how to get my input into the Arduino.  Generally, one types in something and presses the Enter Key to confirm that they are finished with that entry.  Then the next bit, and then the next bit... each followed with that Enter key.  I'm not particularly fond of the Enter key so I wanted to send everything to the Arduino at one time:  The operation to perform and one or two numbers as required.  All I wanted the Arduino software to do was spit back the answer!  After finishing the program, the bulk of the code is in the area of sending back things that I really did not want... for testing and such.  So, I created two ways to build the application, one with all the voluminous output and one with just the answer; I call this verbose mode and the obvious non-verbose mode.  Verbose mode is useful if you want the operator prompted for each value and for viewing settings such as Decimals and Degree-Radians.

Step 1: Arduino Scientific Calculator - Commands

Picture of Arduino Scientific Calculator - Commands
I implemented a number of commands once I got the hang of it... single variable commands look mostly the same and double variable commands look nearly the same... so, cut&paste makes for easy expansion.

Commands are not case-sensitive; so, div and DIV and diV all mean the same thing.  All numeric input less than 1 MUST HAVE A LEADING ZERO:  So, div 3.4 0.45 is valid and div 3.4 .45 IS NOT VALID

Currently the commands are;

"DIV", divide
"MUL", multiply
"ADD", add
"SUB", subtract
"Y^X", raise Y to X power
"LOG", log x
"NLG", natural log x
"10X", 10 raised to X power
"1/X", reciprocal
"e^X", Euler's number raised to X power
"SQR", Square Root
"X^2", X squared
"SIN", sin of X
"COS", cos of X
"TAN", tan of X
"ASN", arcsin of X
"ACS", arccos of X
"ATN", arctan of X
"DEG", mode = degrees (default)
"RAD", mode = radians
"DEC", display decimal places
"RTT", hypotenuse with x and y legs
"DPR", 328P digital pin# read (experimental)
"APR", 328P analog pin# read (not implemented)
"LTX", Last value displayed, 0 initially

Step 2: Arduino Scientific Calculator - Examples

Picture of Arduino Scientific Calculator - Examples

Examples: (all RS232 serial to PC over USB using Arduino terminal)

Compiled with verbose mode on (1):

Input:  sin 45 <sendbutton>
Enter Instruction: SIN Found at location 12
Prompting for X: 45.00 D-->R = 0.79 Sin(X) = 0.7071068

Input: add 2.2 3.0 sub 3.1 4.5 mul 113 3.1 div 355 113 <sendbutton>
Enter Instruction: ADD Found at location 2
Enter first number <delimiter> secoond number: 2.20 3.00
a + b = 5.1999998

Enter Instruction: SUB Found at location 3
Enter minuend <delimiter> subtrahend: 3.10 4.50
a - b = -1.3999998

Enter Instruction: MUL Found at location 1
Enter multiplicand <delimiter> multiplier: 113.00 3.10
a * b = 350.3000183

Enter Instruction: DIV Found at location 0
Enter dividend <delimiter> divisor: 355.00 113.00
a / b = 3.1415929

Compiled with verbose mode off (0):

Input: add 2.2 3.0 sub 3.1 4.5 mul 113 3.1 div 355 113 <sendbutton>

Input:  dec 3 add 2.2 3.0 sub 3.1 4.5 mul 113 3.1 div 355 113

Step 3: Arduino Scientific Calculator - Under the Hood

Picture of Arduino Scientific Calculator - Under the Hood
This is a bit of an unusual project for me as most of what I do is small hardware projects.  I am using commands that are not available in Arduino release below 1.0, so be sure to use the latest version to compile and upload your Arduino.  On the Arduino Nano 328P, I am using the following hardware resources:
// Binary sketch size: 11,606 bytes (of a 30,720 byte maximum) verbose  = false
// Binary sketch size: 14,722 bytes (of a 30,720 byte maximum) verbose  = true + FreeRam()
// Free RAM 1417 in verbose mode

Roughly, less than one-half of the program flash memory and about 30% of the available RAM.  Therefore, you can extend the sketch significantly before running out of resources.  At present, none of the eeprom resources are being used.

To ADD a command, do this:
  1. Create a unique command name that is 3 characters
  2. Place the command at the end of the sStack[ ] = {} array.
    1. Observe quotes
    2. Separate array elements with comma
  3. Increase the value of  #define operations by 1
  4. Add your code at the end of the switch / case #: construct.  Do not forget to set LastX
Depending on your new instruction, you may be able to reuse variables a and b.  If other variables are required, you must define them and scope them properly.

Step 4: Arduino Scientific Calculator - I/O Expansion?

Picture of Arduino Scientific Calculator - I/O Expansion?
I had worked on another project recently, where I was reading the IO state of the microcontroller and forwarding that over the USB to the PC.  I figured that since the Nano had all of its input-output pins unused (excepting the serial that connects to the FTDI chip) that I would craft an instruction to interrogate the state of any of the digital pins by pin#.

The experimental command DPR (Digital Pin Read) does just that.  For example:

Entering DPR 4 <Enter key> gives this verbose display:

Enter Instruction: DPR Found at location 22
Prompting for Digital Pin Number 2 - 13: 4 Logic State = 0

One could envision that a new command, DPW (Digital Pin Write) could be similarly constructed.

Because we are building a command-controlled software environment, a new command APR, Analog Pin Read, can also be envisioned.  Essentially, any upstream microcontroller could use serial commands to take full control of an ATmel chip such as on a BareBones Arduino and use the downstream chip for I/O expansion.  Yes, there is latency from the software decoding and the serial processing, but the concept is very cheap and may find a place where near-instantaneous signaling is not necessary.

Update 20130301:
Note: Addition of Analog Read Pin and modification (for error condition) for Digital Pin Read.  This is not in the attached ZIP which is unchanged.  Also note I did not put in the "verbose" statements, but you can, if desired.
byte DigPinNo;
byte AnaPinNo;  // added
case 22:    //DPR DigitalPin Read # valid 3, 6 - 12 (Nano)  Returns: 0, 1 for state or 2 for Error
            DigPinNo = Serial.parseInt();
            if (DigPinNo < 3 || DigPinNo > 12) {
                Serial << "Err\n"; break; }
            if (DigPinNo == 4 || DigPinNo == 5) {
                Serial << "Err\n"; break; }
            Serial << digitalRead(DigPinNo);

    case 23:    //APR Analog Pin Read - A0 through A7 (Nano)  Return 0 - 1023 value for 10-bit A/D or "Err" for Error
            AnaPinNo = Serial.parseInt();
            if (AnaPinNo < 0 || AnaPinNo > 7) {
                Serial << "Err\n"; break; }
            Serial << analogRead(AnaPinNo);

WHAT IF we wanted to read all 8 of the Analog values, A0 through A7?  We could create another instruction such as AP8 or we could create a data-centric input to control the condition.  For example, the only valid digits for APR are 0, 1, 2, 3, 4, 5, 6, 7.  Our error logic already tests for this condition.  BUT if we did one further test in the error routine for a special (specific) number, then we could use this as a key to perform a special operation.  Since we are inputting a byte value, let's use the value of 255 for our special condition.  Now the code would look like:

case 23:    //APR Analog Pin Read - A0 through A7 (Nano)  Return 0 - 1023 value for 10-bit A/D or "Err" for Error
            AnaPinNo = Serial.parseInt();
            if (AnaPinNo < 0 || AnaPinNo > 7) {
              if (AnaPinNo == 255) {
                for (byte q = 0; q < 8; q++) {
                  Serial << analogRead(q) << " " ; }
                  break; }
                  Serial << "Err\n"; break; }
            Serial << analogRead(AnaPinNo);

The corresponding output would be along these lines for APR 255<Enter.
291 288 284 287 287 283 283 287

Pretty cool?  What if I needed to average 10 samples?  Maybe the data stream is going to a PC or another uC.  Then the input stream to the Nano could look like this:
apr 255 apr 255 apr 255 apr 255 apr 255 apr 255 apr 255 apr 255 apr 255 apr 255

and the resulting output on a terminal would appear as:
517 449 402 371 335 303 348 322
385 392 380 371 348 325 332 316
351 367 372 373 358 339 331 314
314 320 332 340 340 334 334 322
316 316 322 323 325 328 333 326
319 317 316 311 312 316 320 316
316 315 309 303 305 307 309 305
309 307 300 296 299 299 298 296
300 297 291 292 294 291 290 291
291 288 284 287 287 283 283 287

For work with Spreadsheets such as Excel, I would probably elect to modify the code line:
Serial << analogRead(q) << " " ; }
to something like:
Serial << analogRead(q) << ", " ; }

So that I could easily input the text.  However, you could leave the code as-is and change the parse character in Excel, too.


Step 5: Arduino Scientific Calculator - Source Code

Ye Olde Disclaimer:
The software "sketch" attached with this article is released into the public domain as an example only.  No functions are guaranteed and no accuracy is stated.  In fact, there may be significant issues with any calculations performed.  The code is therefore solely for amusement and is of an instructional nature.

The Arduino IEEE Floating Point libraries linked to this code are standard Arduino environment 32-bit (4-byte) libraries.  Accuracy may be increased if 64-bit libraries were used; however none were identified and tested.  Users are therefore warned to investigate the limitation of Floating Point within the Arduino environment.

Other than the standard "I take absolutely no responsibility" and the "use at your own risk' clauses, have fun!

- Ray

Instructables does not seem to like <dot>INO files, so the code is in <dot>TXT ... after downloading, rename, and place in your Arduino home code directory.


goldbar2975 (author)2013-04-16

how many decimal of pi can you get it to calculate?

RayBurne (author)goldbar29752013-04-17

... In calculations, 355 DIV 113 comes in very close to Pi


RayBurne (author)goldbar29752013-04-17

Pi is a constant in the code:
float Pi = 3.141592654;

Decimals default to 7 places. You can override this in script to execute:
(but remember that floats in Arduino are only 4-byte IEEE numbers, so 10 digits really does not make a lots of sense.)

byte decimals = 7;
case 20: // DEC set decimal places
if (verbose) {Serial.print(F("Prompting for Decimal places 0-10: ")); }
b = Serial.parseInt();
if (b>10) {b = 10; }
decimals = b;
if (verbose) {Serial.print(F(" Decimal Places = "));
Serial.print(b); }
if (verbose) {Serial.println(); }

maewert (author)2013-02-12

I love it Ray. I'm thinking about changing verbose to a boolean and set/clear it via VM+ VM- commands. Also maybe storing LastX into a circular array or 255 values and being able to use past values via an ANS -N (or could use a STO to store it at a location). You may want to include functions to set the pins I or O and to enable the pull up resistors. A matrix solver could also be useful as well as unit conversions... the list goes on and on :-)
Best Wishes

RayBurne (author)maewert2013-02-12

Writing this was an educational exercise for me since I wanted to play with the new Arduino 1.0+ streaming commands. The for-loop serial parser really needs a new architecture if many more commands are needed to minimize the loop delay. Also, I should have used a compiler directive instead of the 'verbose' implementation used.

But, all in fun. Another idea would be to use eeprom to implement numerous physical constants instead of inline code.

The IO extension may actually be the most productive use of this concept because it could allow some remote intelligence to be added via RF RS232 comm. In such an implementation, I would op to create a response channel so that the main uC actually knows that the remote did its bidding.

Evolve and repost! Also a thread is open in the Arduino forum:
Arduino Forum ::
Community ::
Exhibition / Gallery ::
Don't Cross The Streams (FP scientific calculator serial co-processor)


About This Instructable




Bio: Ray Burne is my pseudonym, I sometimes write on various Blogs and Sites. Effective 12 June 2013, Ray has decided to no longer participate as ... More »
More by RayBurne:Minty Magic Morse - Arduino StylePICAXE Pitcher Perfect ThermometerA PICAXE Infrared "logging" Thermometer
Add instructable to: