Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
|
I was in high school when I first used a Hewlett Packard RPN (reverse Polish notation) calculator, the HP 15C. I preferred this format for solving math problems, because you enter the 2 operands first and then the math operation. This removes the necessity of parenthesis. For example, 4 + 5 = would be 4 ENTER 5 + (both use 4 keypresses, but RPN removes the need to press the = key). Today, to get an RPN scientific calculator, you either need to buy a used one on eBay for $100 or more, or buy a modern day "clone", for $180 to $300..
At a Maker Faire, I saw the M5Stack Cardputer selling for $30 and thought it would be the ideal platform to build a RPN calculator on, such as the HP 10C. After using Microsoft Word to make an overlay and coding in Arduino (about 2, 000 lines of code), I was able to make a working model. Which led to the birth of the 10LC (LC stands for low cost). There are some limitations, such as the small size, and the firmware is at a beta stage (for a complete list of features/issues and overlay template see the 10LC Github webpage at https://github.com/riker2072/10LC), but it's easy to update the firmware using a standard USB-C cable and a Windows PC or Mac computer.
It is fast - it can do 100, 000 iterations of a loop in about 10 seconds. I added some features, such as 2 font sizes, 3 font color options and audible keyboard beep option. Also, 99 program steps are available with all 10 registers. It is fairly easy to make a calculator program that gives answers to calculations, but adding programability that allows the users to enter their own programs and run them on the calculator adds a whole new level of complexity.
But I didn't stop there. The HP 32S calculator has an alpha numeric display and can solve equations, do integrals (for calculus) and some complex number functions. So I added that functionality to create the 32LC. This increased the code size to about 3, 000 lines and is also at a beta stage. See https://www.hackster.io/ron-t/low-cost-32lc-rpn-calculator-3eff39
One of my favorite calculators to use is the HP 42S. This calculator adds more complex number support, matrix functions and the ability to graph functions (with the use of a user written program). The increased functionality of the 42LC increased the code size to about 7, 000 lines of code. Because it has so many functions to implement, it is currently at an alpha stage, but a lot of the basic functionality is there. See https://www.hackster.io/ron-t/low-cost-42lc-rpn-calculator-7e7ccb
10LC Arduino code
C/C++To setup Arduino to work with the M5Stack Cardputer, the first step is to follow the Arduino board management guidelines at:
https://docs.m5stack.com/en/arduino/arduino_board
The next step is to add the cardputer library. See https://docs.m5stack.com/en/arduino/m5cardputer/program
You can try uploading the display example before creating a new project that uses the source code below. Make sure you have the correct board and port settings in the Tools pulldown menu.
/** 10LC calculator
copyright R. Tanikawa 2025
v. 0.90
*/
#include "M5Cardputer.h"
#include "math.h"
#include "SD.h"
#include "Complex.h"
int tones=0;
double mults=0;
int colr=0;
int fontz=0;
int keys;
int expp=0;
double num=0;
double num1=0;
int shifts=0;
int ans=0;
double stos[12]={0,0,0,0,0,0,0,0,0,0,0,0};
int pc=0;
int beeps=0;
int pgm[102];
int pgm1[102];
int mode=0;
int decm=0;
int gotos=0;
int scientif=0;
int scidigs=0;
int signs=1;
int idles=0;
int codes1=0;
double slope=0;
double intc=0;
int angles=0;
int gotosflag=0;
double lastx=0;
int stoper=0;
int secs=0;
int dispFlag=0;
//int zz=c_sqrt(3);
// 1=normal shift, 2=sto 3=rcl 5=exp 6=exp2 7=gto1 8=gto2 9=fix 10=sci 11=eng
double stacks[4]={0,0,0,0};
char strs[100];
//char keyz[42]="234567890-wertyuiop[asdfghjkl;@zxcvbnm,. ";
char keyz[46]="-234567890[wertyuiop;asdfgbjkl.@zxcvhnm,=/]' ";
int fixs=5;
void clearPgm() {
int i;
for (i=1; i<100; ++i)
pgm[i]=2200;
pgm[0]=0;
}
void clearDsp() {
M5Cardputer.Display.fillRect(0,0,240,135,BLACK);
}
double tohms(double a) {
int i;
double fp,temp;
long a1;
int minutes,seconds;
int divs;
Serial.println(a*3600);
// temp=fmod(fp,fp);
seconds=(int)(3600.0*modf(a,&temp)+0.5);
// divs=(int)a;
//seconds=(int)((a*3600.0)-(3600.0*divs)+0.5);
// (int)(a*3600.0)(int)(a*3600) % 3600;
Serial.println(seconds);
// a1=(int)a;
// fp=a-a1;
//Serial.println("%f",fp);
// seconds=fp*3600;
minutes=(int)(seconds/60);
seconds=(int)(seconds) % 60;
// seconds=(int)(60*((60*fp)-(int)(60*fp)));
return (int)a+(minutes/100.0)+(seconds/10000.0);
}
double tohours(double a) {
int minutes,seconds;
long a1;
double fp;
a1=10000*a;
seconds=(int)a1 % 100;
a1=a1/100;
minutes=(int)a1 % 100;
/* fp=a-a1;
minutes=(int)(fp*100);
fp=fp*100;
seconds=(int)((fp-minutes)*100); */
Serial.println(minutes);
Serial.println(seconds);
return (int)a+(minutes/60.0)+(seconds/3600.0);
}
double facts(float a) {
double prod=1.0;
int i;
for (i=(int)a; i>1; --i)
prod=prod*i;
return prod;
}
void rolldown(){
double temp;
temp=num;
stacks[0]=stacks[1];
stacks[1]=stacks[2];
stacks[2]=stacks[3];
stacks[3]=temp;
dispNum(stacks[0]);
num=stacks[0];
ans=1;
}
void pushx(double val){
stacks[3]=stacks[2];
stacks[2]=stacks[1];
stacks[1]=val;
stacks[0]=val;
}
void stacklift(double val){
stacks[3]=stacks[2];
stacks[2]=stacks[1];
stacks[1]=stacks[0];
stacks[0]=val;
}
void popx(){
stacks[1]=stacks[2];
stacks[2]=stacks[3];
}
void clearReg(){
int i;
for (i=0; i<10; ++i)
stos[i]=0;
for (i=0; i<4; ++i)
stacks[i]=0;
num=0;
dispNum(0);
ans=1;
}
void setup() {
int i;
char strs1[]=". ";
char xx;
Serial.begin(9600);
i=M5Cardputer.Power.getBatteryLevel();
Serial.println(i);
i=M5Cardputer.Power.getBatteryVoltage();
Serial.println(i);
for (i=0; i<100; ++i)
pgm[i]=2200;
auto cfg = M5.config();
M5Cardputer.begin(cfg, true);
M5Cardputer.Display.setRotation(1);
M5Cardputer.Display.setTextColor(GREEN);
M5Cardputer.Display.setTextDatum(middle_left);
M5Cardputer.Display.setTextFont(&fonts::FreeSerifBold18pt7b);
M5Cardputer.Display.setTextSize(1);
M5Cardputer.Display.drawString("10LC calculator",
0,
M5Cardputer.Display.height() / 2);
M5Cardputer.Display.setTextFont(&fonts::FreeSerif9pt7b);
M5Cardputer.Display.drawString("ver. 0.9",100,90);
fileReads();
pgm[0]=0;
fileReads1();
num=stacks[0];
dispNum(num);
ans=1;
}
void fileWrites() {
int i;
char strs1[]=" ";
SD.begin();
File files=SD.open("/test9.txt",FILE_WRITE);
if (files==NULL) {
SD.end();
return;
}
for (i=0; i<100; ++i) {
sprintf(strs1,"%d",pgm[i]);
files.println(strs1);
delay(5); }
files.close();
SD.end();
}
void fileWrites1() {
int i;
char strs1[]=" ";
SD.begin();
File files=SD.open("/test8.txt",FILE_WRITE);
if (files==NULL) {
SD.end();
return;
}
for (i=0; i<10; ++i) {
sprintf(strs1,"%e",stos[i]);
files.println(strs1); }
for (i=0; i<4; ++i) {
sprintf(strs1,"%e",stacks[i]);
files.println(strs1); }
sprintf(strs1,"%d",fixs);
files.println(strs1);
sprintf(strs1,"%d",scientif);
files.println(strs1);
sprintf(strs1,"%d",scidigs);
files.println(strs1);
sprintf(strs1,"%d",fontz);
files.println(strs1);
sprintf(strs1,"%d",beeps);
files.println(strs1);
sprintf(strs1,"%d",colr);
files.println(strs1);
files.close();
SD.end();
//fixs,scientif,scidigs,fontz,beeps,colr
}
void fileReads() {
int xx;
int i,j,k;
char strs[]=" ";
j=0;
k=0;
SD.begin();
File files=SD.open("/test9.txt",FILE_READ);
if (files==NULL)
return;
for (i=0; i<1000; ++i) {
xx=files.read();
// Serial.println(xx);
if (xx==-1)
break;
if (xx!=13) {
strs[j]=xx;
j+=1;
}else{
xx=files.read();
// Serial.println(xx);
strs[j]=0;
pgm[k]=atoi(strs);
// Serial.println(strs);
k+=1;
if (k>99)
break;
j=0;
}
delay(1);
}
files.close();
SD.end();
}
void fileReads1() {
int xx;
int i,j,k;
char strs[]=" ";
int stackmode=0;
j=0;
k=0;
SD.begin();
File files=SD.open("/test8.txt",FILE_READ);
if (files==NULL)
return;
for (i=0; i<200; ++i) {
xx=files.read();
// Serial.println(xx);
if (xx==-1)
break;
if (xx!=13) {
strs[j]=xx;
j+=1;
}else{
xx=files.read();
// Serial.println(xx);
strs[j]=0;
if (stackmode==0)
sscanf(strs,"%lf",&stos[k]);
else if (stackmode==1)
if (k<4)
sscanf(strs,"%lf",&stacks[k]);
else {
switch (k) {
case 4:
sscanf(strs,"%d",&fixs);
break;
case 5:
sscanf(strs,"%d",&scientif);
break;
case 6:
sscanf(strs,"%d",&scidigs);
break;
case 7:
sscanf(strs,"%d",&fontz);
break;
case 8:
sscanf(strs,"%d",&beeps);
break;
case 9:
sscanf(strs,"%d",&colr);
if (colr==0)
M5Cardputer.Display.setTextColor(GREEN);
else if (colr==1)
M5Cardputer.Display.setTextColor(RED);
else if (colr==2)
M5Cardputer.Display.setTextColor(WHITE);
break;
default:
break;
}
}
// stos[k]=atof(strs);
Serial.println(strs);
k+=1;
if (k==10) {
stackmode=1;
k=0;
}
j=0;
}
delay(1);
}
files.close();
SD.end();
}
int proKeys() {
int i;
char strs1[]=" ";
for (i=0; i<44; ++i)
if (M5Cardputer.Keyboard.isKeyPressed(keyz[i]))
return i;
return -1;
}
int parseNum(int keys) {
switch (keys) {
case 7:
return 7;
case 8:
return 8;
case 9:
return 9;
case 17:
return 4;
case 18:
return 5;
case 19:
return 6;
case 27:
return 1;
case 28:
return 2;
case 29:
return 3;
case 37:
return 0;
default:
return -1;
}
return -1;
}
int parseKey(int keys) {
double temp;
int i;
char strs[]=" ";
switch (keys) {
case 7:
return 7;
case 8:
return 8;
case 9:
return 9;
case 17:
return 4;
case 18:
return 5;
case 19:
return 6;
case 27:
return 1;
case 28:
return 2;
case 29:
return 3;
case 37:
return 0;
//DECIMAL POINT
case 38:
decm=2;
mults=0.1;
return 48;
//ADD
case 30:
lastx=num;
num=num+stacks[1];
popx();
stacks[0]=num;
dispNum(num);
ans=1;
return 40;
//ENTER
case 26:
// lastx=num;
pushx(num);
num1=num;
// num=0;
dispNum(num);
decm=0;
ans=2;
shifts=0;
return 36;
case 1:
if (num<0)
errors(0);
else {
lastx=num;
num=sqrt(num);
stacks[0]=num;
dispNum(num);
ans=1;
}
return 11;
case 2:
lastx=num;
num=exp(num);
stacks[0]=num;
dispNum(num);
ans=1;
return 12;
case 3:
lastx=num;
num=pow(10,num);
stacks[0]=num;
dispNum(num);
ans=1;
return 13;
case 4:
if ((stacks[1]==0) && (num<=0))
errors(0);
else if ((stacks[1]<0) && ((num-int(num))!=0))
errors(0);
else {
lastx=num;
num=pow(stacks[1],num);
popx();
stacks[0]=num;
dispNum(num);
ans=1;
}
return 14;
case 5:
if (num==0)
errors(0);
else {
lastx=num;
num=1/num;
stacks[0]=num;
dispNum(num);
ans=1;
}
return 15;
case 6:
num=-num;
stacks[0]=num;
dispNum(num);
ans=1;
return 16;
case 0:
if (num==0)
errors(0);
else {
lastx=num;
num=stacks[1]/num;
popx();
stacks[0]=num;
dispNum(num);
ans=1;
}
return 109;
//percent - STACKLIFT?
case 11:
lastx=num;
num=num*0.01*stacks[1];
dispNum(num);
ans=1;
return 21;
//GOTO
case 12:
shifts=7;
M5Cardputer.Display.setTextFont(&fonts::FreeSerif9pt7b);
M5Cardputer.Display.drawString("gto",90,120);
M5Cardputer.Display.setTextFont(&fonts::FreeSerif18pt7b);
return 22;
case 13:
switch (angles) {
case 0:
num=sin(num*3.14159265359/180.0);
break;
case 1:
num=sin(num);
break;
case 2:
num=sin(num*3.14159265359/200.0);
break;
}
stacks[0]=num;
dispNum(num);
ans=1;
return 23;
case 14:
lastx=num;
switch (angles) {
case 0:
num=cos(num*3.14159265359/180.0);
break;
case 1:
num=cos(num);
break;
case 2:
num=cos(num*3.14159265359/200.0);
break;
}
stacks[0]=num;
dispNum(num);
ans=1;
return 24;
case 15:
lastx=num;
switch (angles) {
case 0:
num=tan(num*3.14159265359/180.0);
break;
case 1:
num=tan(num);
break;
case 2:
num=tan(num*3.14159265359/200.0);
break;
}
stacks[0]=num;
dispNum(num);
ans=1;
return 25;
case 10:
lastx=num;
num=stacks[1]*num;
popx();
stacks[0]=num;
dispNum(num);
ans=1;
return 20;
case 20:
lastx=num;
num=stacks[1]-num;
popx();
stacks[0]=num;
dispNum(num);
ans=1;
return 30;
// RUN
case 21:
if (mode!=1) {
mode=2;
// stacks[0]=num;
ans=1;
}
return 31;
// SST
case 22:
if (pc==0)
pc=1;
showProgram(pgm[pc]);
delay(1000);
doProgLine(pgm[pc]);
return 132;
// rolldown
case 23:
rolldown();
return 133;
// x<>y
case 24:
temp=num;
stacks[0]=stacks[1];
stacks[1]=temp;
num=stacks[0];
dispNum(num);
ans=2;
return 134;
//clx
case 25:
num=0;
stacks[0]=0;
dispNum(num);
ans=2;
return 135;
case 36:
num=lastx;
stacks[0]=num;
dispNum(num);
ans=2;
return 36;
//Exponent
case 16:
shifts=5;
Serial.println("expon");
return 26;
// PROG
case 33:
mode=1;
//M5Cardputer.Display.clear();
clearDsp();
sprintf(strs,"%d-%d",pc,pgm[pc]);
M5Cardputer.Display.drawString(strs,15,90);
return 43;
//STO
case 34:
shifts=2;
stoper=0;
return 44;
//RCL
case 35:
shifts=3;
return 45;
//SHIFT
case 32:
shifts=1;
M5Cardputer.Display.setTextFont(&fonts::FreeSerif9pt7b);
M5Cardputer.Display.drawString("f",10,120);
M5Cardputer.Display.setTextFont(&fonts::FreeSerif18pt7b);
return 42;
// sum +
case 39:
lastx=num;
stos[0]+=1;
stos[1]+=num;
stos[2]+=num*num;
stos[3]+=stacks[1];
stos[4]+=stacks[1]*stacks[1];
stos[5]+=num*stacks[1];
dispNum(stos[0]);
num=stos[0];
stacks[0]=num;
ans=2;
return 149;
case 40:
fontz=(fontz+1) % 2;
dispNum(num);
return 150;
case 41:
stacks[0]=num;
fileWrites();
fileWrites1();
M5Cardputer.Power.powerOff();
return 151;
case 42:
colr=(colr+1) % 3;
if (colr==0)
M5Cardputer.Display.setTextColor(GREEN);
else if (colr==1)
M5Cardputer.Display.setTextColor(RED);
else if (colr==2)
M5Cardputer.Display.setTextColor(WHITE);
dispNum(num);
return 152;
case 43:
beeps=(beeps+1) % 2;
return 153;
default:
return -1;
}
return -1;
}
// non working tangent algorithm
double tan3(double a) {
return 0;
}
int parseShift(int keys) {
double temp,temp1;
double i;
Serial.println("shift");
switch (keys) {
case 1:
lastx=num;
num=num*num;
stacks[0]=num;
dispNum(num);
ans=1;
shifts=0;
return 111;
case 2:
if (num<=0)
errors(0);
else {
lastx=num;
num=log(num);
stacks[0]=num;
dispNum(num);
ans=1;
}
shifts=0;
return 112;
case 3:
if (num<=0)
errors(0);
else {
lastx=num;
num=log10(num);
stacks[0]=num;
dispNum(num);
ans=1;
}
shifts=0;
return 113;
case 4:
lastx=num;
num=tohms(num);
stacks[0]=num;
dispNum(num);
ans=1;
shifts=0;
return 114;
case 5:
lastx=num;
num=tohours(num);
stacks[0]=num;
dispNum(num);
ans=1;
shifts=0;
return 115;
case 6:
stacks[0]=num;
// stacklift(num);
num=3.14159265359;
stacklift(num);
// stacks[0]=num;
dispNum(num);
ans=1;
shifts=0;
return 116;
case 7:
Serial.println("FIX");
shifts=9;
scientif=0;
return 117;
case 8:
shifts=10;
scientif=1;
return 118;
// p->r
case 11:
lastx=num;
switch(angles) {
case 0:
stacks[1]=stacks[1]*3.14159265359/180.0;
break;
case 2:
stacks[1]=stacks[1]*3.14159265359/200.0;
break;
}
temp=num*sin(stacks[1]);
temp1=num*cos(stacks[1]);
stacks[1]=temp;
stacks[0]=temp1;
num=temp1;
dispNum(num);
ans=1;
shifts=0;
return 121;
// R->P, quad 2,3,4 case
case 12:
lastx=num;
Serial.println("r-p");
temp=sqrt(num*num+stacks[1]*stacks[1]);
if (num==0) {
if (stacks[1]>0)
stacks[1]=3.14159265359/2.0;
else
stacks[1]=-3.14159265359/2.0;
}
else {
//Quadrant 1
if ((stacks[1]>=0) && (num>0))
stacks[1]=atan(stacks[1]/num);
// Quadrant 3
else if ((stacks[1]<=0) && (num<0))
stacks[1]=atan(stacks[1]/num)-3.14159265359;
// Quadrant 4
else if ((stacks[1]<=0) && (num>0))
stacks[1]=atan(stacks[1]/num);
else
// Quadrant 2
stacks[1]=atan(stacks[1]/num)+3.14159265359;
}
num=temp;
switch (angles) {
case 0:
stacks[1]=stacks[1]*180/3.14159265359;
break;
case 2:
stacks[1]=stacks[1]*200/3.14159265359;
break;
}
dispNum(num);
ans=1;
shifts=0;
return 121;
case 13:
if (abs(num)>1)
errors(0);
else {
lastx=num;
num=asin(num);
switch (angles) {
case 0:
num=num*180/3.14159265359;
break;
case 2:
num=num*200/3.14159265359;
break;
}
stacks[0]=num;
dispNum(num);
ans=1;
}
shifts=0;
return 123;
case 14:
if (abs(num)>1)
errors(0);
else {
lastx=num;
num=acos(num);
switch (angles) {
case 0:
num=num*180/3.14159265359;
break;
case 2:
num=num*200/3.14159265359;
break;
}
stacks[0]=num;
dispNum(num);
ans=1;
}
shifts=0;
return 124;
case 15:
lastx=num;
num=atan(num);
switch (angles) {
case 0:
num=num*180/3.14159265359;
break;
case 2:
num=num*200/3.14159265359;
break;
}
stacks[0]=num;
dispNum(num);
ans=1;
shifts=0;
return 125;
case 16:
if ((num<0) || (modf(num,&i)!=0))
errors(0);
else {
lastx=num;
num=facts(num);
stacks[0]=num;
dispNum(num);
ans=1;
}
shifts=0;
return 126;
case 17:
angles=0;
shifts=0;
dispNum(num);
return 127;
case 18:
angles=1;
shifts=0;
dispNum(num);
return 128;
case 19:
angles=2;
shifts=0;
dispNum(num);
return 129;
// to radians
case 20:
lastx=num;
num=num*3.14159265359/180.0;
stacks[0]=num;
ans=1;
shifts=0;
dispNum(num);
return 130;
// Pause
case 21:
shifts=0;
dispNum(num);
return 131;
case 22:
shifts=0;
dispNum(num);
return 132;
case 23:
clearPgm();
pc=0;
dispNum(num);
shifts=0;
return 133;
case 24:
clearReg();
dispNum(num);
shifts=0;
return 134;
//clear prefix
case 25:
dispNum(num);
shifts=0;
return 135;
case 27:
if ((stos[0]<=1) || (slope==0) || ((stos[0]*stos[2]-(stos[1]*stos[1]))==0) || ((stos[0]*stos[4]-(stos[3]*stos[3]))==0) )
errors(1);
else {
lastx=num;
// y=mx+b, x=(y-b)/m
num=(num-intc)/slope;
stacks[1]=(stos[0]*stos[5]-(stos[1]*stos[3]))/sqrt((stos[0]*stos[2]-(stos[1]*stos[1]))*(stos[0]*stos[4]-(stos[3]*stos[3])));
dispNum(num);
ans=1;
}
shifts=0;
return 137;
case 28:
if ((stos[0]<=1) || ((stos[0]*stos[2]-(stos[1]*stos[1]))==0) || ((stos[0]*stos[4]-(stos[3]*stos[3]))==0))
errors(1);
else {
...
This file has been truncated, please download it to see its full contents.
Comments