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 32S. After using Microsoft Word to make an overlay and coding in Arduino (over 3, 000 lines of code), I was able to make a working model. Which led to the birth of the 32LC (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 32LC Github webpage at https://github.com/riker2072/32LC ), but it's easy to update the firmware using a standard USB-C cable and a Windows PC or Mac computer.
Regarding the video below, the integration part could be better, and the video will be updated in the future, as time permits.
There is also the 10LC for those who want a slightly simpler RPN calculator. See https://www.hackster.io/ron-t/low-cost-10lc-rpn-calculator-64d03c
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
32LC 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.
/** 32LC calculator
copyright R. Tanikawa 2025
v. 0.92
*/
#include "M5Cardputer.h"
#include "math.h"
#include "SD.h"
#include "Complex.h"
char menuz[125][20]={"a:IP","b:FP","c:RN","d:ABS","","a:DG","b:RD","c:GR","","",
"a:FX","b:SC","c:ALL","","","a:cX","b:cVARS","c:cPrg","d:cSum","",
"a:rec->pol","b:pol->rec","","","","a:->HR","b:->HMS","","","",
"a:->DEG","b:->RAD","","","","a:DEC","b:HX","c:OC","d:BN","",
"a:LBL","b:RTN","c:PSE","","","a:DSE","b:ISG","","","",
"a:SF","b:CF","c:FS?","","","a:x?y","b:x?0","","","",
"a:x!=y","b:x<y","c:x>y","d:x=y","","a:x!=0","b:x<0","c:x>0","d:x=0","",
"a:FN","b:SOLVE","c:Intgrt","","","a:sum","b:mnX,Y","c:s","d:LR","",
"a:sx","b:sy","","","","a:predX","b:predY","c:r","d:m","e:b",
"a:Cn,r","b:Pn,r","c:x!","d:RANDOM","e:SEED","a:RANDOM","b:SEED","","","",
"a:VAR","b:PGM","","","","a:mnX","b:mnY","c:mnXw","","",
"a:n","b:x","c:y","d:x^2","e:y^2","f:xy"};
char cmds[100][20]={"SQRT","e^x","ln","y^x","1/x","sum+","7","8","9","/",
"STO","RCL","RLDN","SIN","COS","TAN","4","5","6","x",
"SST","XEQ","x<>y","CHS","E","BSP","1","2","3","-",
"","C","shift","","","ENTER","0",".","R/S","+",
"X^2","10^X","LOG","%","%CHG","sum-","","","","",
"CMPX","PI","HYP","ASIN","ACOS","ATAN","","","","",
"BST","GTO","","","","","","","","",
"","OFF","","","","LASTx","INPUT","SHOW","PRGM","VIEW",
"SINH","COSH","TANH","ASINH","ACOSH","ATANH"};
//char alphas[35]="234567WERTYUSDFG890IOPJKLNM,.;[-";
int alphas[35]={0,1,2,3,4,5,10,11,12,13,14,15,21,22,23,24,6,7,8,16,17,18,26,27,28,36,37,38,39,29,19,9};
int sizes[24]={4,3,3,4,2,2,2,4,3,2,
3,2,4,4,3,4,2,5,5,2, 2,3,6};
double stats[6];
int cmdd=0;
int flags;
int intsolv=0;
int menunum=0;
int tones=0;
double mults=0;
double rnums=0.3857162904;
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[28]={0,0,0,0,0,0,0,0,0,0,0,0};
int pc=0;
int beeps=0;
int pgm[1500];
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 solvemode=0;
int fnum=0;
int solvar=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[asdfghjkl;@zxcvbnm,. ";
int fixs=5;
void clearPgm() {
int i;
for (i=0; i<1500; ++i)
pgm[i]=38;
for (i=0; i<26; ++i)
pgm[100+50*i]=4000+i;
pgm[0]=38;
}
void clearDsp() {
M5Cardputer.Display.fillRect(0,0,240,135,BLACK);
}
double tohms(double a) {
int i;
double fp,temp;
long a1;
int minutes;
double seconds;
int divs;
Serial.println(a*3600);
// temp=fmod(fp,fp);
seconds=3600.0*modf(a,&temp);
// 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=fmod(seconds,60);
// seconds=(int)(60*((60*fp)-(int)(60*fp)));
return temp+(minutes/100.0)+(seconds/10000.0);
}
double tohours(double a) {
int minutes;
double seconds,temp;
long a1;
double fp;
fp=a*100.0;
seconds=modf(fp,&temp)/36.0;
// a1=10000*a;
// seconds=(int)a1 % 100;
// a1=a1/100;
minutes=fmod(temp,100.0);
/* fp=a-a1;
minutes=(int)(fp*100);
fp=fp*100;
seconds=(int)((fp-minutes)*100); */
Serial.println(minutes);
Serial.println(seconds);
modf(a,&temp);
return temp+(minutes/60.0)+(seconds);
}
void solves() { //secant solver method
double x0=-8;
double x1=0;
double e=0.000000001;
int N=20;
int step=0;
double x=0;
double f0;
double f1;
double x2;
double f2;
int count=0;
x0=stacks[0];
x1=stacks[1];
intsolv=1;
if (x0==x1) {
x1+=15.0;
x0-=30.0;
}
solvemode=1;
// mode=2;
while (count<50) {
count++;
x=x0;
pc=100+fnum*50;
stos[solvar]=x;
// mode=2;
runProgram();
f0=num;
x=x1;
pc=100+fnum*50;
stos[solvar]=x;
// mode=2;
runProgram();
f1=num;
if (f0==f1) {
intsolv=0;
//M5Cardputer.Display.clear();
clearDsp();
M5Cardputer.Display.setTextFont(&fonts::FreeSerif18pt7b);
M5Cardputer.Display.drawString("Error: g1=g2",15,90);
// Serial.println("Math error");
mode=0;
return;
}
x2=(x0*f1-x1*f0)/(f1-f0);
x0=x1;
x1=x2;
stos[solvar]=x2;
pc=100+fnum*50;
// mode=2;
runProgram();
f2=num;
//Serial.println(f0);
step = step + 1;
if (step > N) {
intsolv=0;
//M5Cardputer.Display.clear();
clearDsp();
M5Cardputer.Display.setTextFont(&fonts::FreeSerif18pt7b);
M5Cardputer.Display.drawString("not convergent",15,90);
// Serial.println("no convergence");
solvemode=0;
mode=0;
return;
}
if (fabs(f2)<=e)
break;
}
// Serial.println(x2);
intsolv=0;
if (count==50) {
//M5Cardputer.Display.clear();
clearDsp();
M5Cardputer.Display.setTextFont(&fonts::FreeSerif18pt7b);
M5Cardputer.Display.drawString("solution not found",15,90);
} else {
num=x2;
stacks[0]=num;
ans=1;
dispNum(num);
}
mode=0;
solvemode=0;
return;
}
void integrate(){
long int n,i;
double a,b,h,x,sum=0,integ;
double fa,fb;
char msg[20];
intsolv=1;
if (scientif)
n=int(pow(10,(scidigs+3)/2));
else
n=int(pow(10,(fixs+3)/2));
if ((n % 2)!=0)
n+=1;
//n=100;
a=stacks[1];
b=stacks[0];
h=abs(b-a)/n;
for(i=1;i<n;i++){
if ((i%10000)==0) {
sprintf(msg,"integrating %d",i/10000);
//M5Cardputer.Display.clear();
clearDsp();
M5Cardputer.Display.drawString(msg,15,40);
}
M5Cardputer.update();
if(M5Cardputer.Keyboard.isChange()) {
mode=0;
ans=1;
stacks[0]=num;
break;
}
x=a+i*h;
if(i%2==0){
pc=100+fnum*50;
stos[solvar]=x;
// mode=2;
runProgram();
sum=sum+2*num;
}
else{
pc=100+fnum*50;
stos[solvar]=x;
// mode=2;
runProgram();
sum=sum+4*num;
}
}
pc=100+fnum*50;
stos[solvar]=a;
// mode=2;
runProgram();
fa=num;
pc=100+fnum*50;
stos[solvar]=b;
// mode=2;
runProgram();
intsolv=0;
fb=num;
integ=(h/3)*(fa+fb+sum);
/*Print the answer */
Serial.println(integ);
num=integ;
dispNum(num);
// text(toString(integral),5,5);
}
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<27; ++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;
// double xxx;
Serial.begin(9600);
/* if (isinf(xxx))
Serial.println("infinite"); */
i=M5Cardputer.Power.getBatteryLevel();
Serial.println(i);
i=M5Cardputer.Power.getBatteryVoltage();
Serial.println(i);
for (i=0; i<100; ++i)
pgm[i]=38;
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("32LC calculator",
0,
M5Cardputer.Display.height() / 2);
M5Cardputer.Display.setTextFont(&fonts::FreeSerif9pt7b);
M5Cardputer.Display.drawString("ver. 0.92",100,90);
fileReads();
pgm[0]=38;
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<1500; ++i) {
sprintf(strs1,"%d",pgm[i]);
files.println(strs1);
delay(1); }
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<26; ++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<8000; ++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>1499)
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<2600; ++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==26) {
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 parseAlpha(int keys) {
int i;
for (i=0; i<32; ++i)
if (keys==alphas[i])
return i;
return -1;
}
int parseNum(int keys) {
switch (keys) {
case 6:
return 7;
case 7:
return 8;
case 8:
return 9;
case 16:
return 4;
case 17:
return 5;
case 18:
return 6;
case 26:
return 1;
case 27:
return 2;
case 28:
return 3;
case 36:
return 0;
default:
return -1;
}
return -1;
}
// for PROG mode, this is not used
int parseKey(int keys) {
double temp,temp1;
int i;
char strs[]=" ";
switch (keys) {
case 6:
return 7;
case 7:
return 8;
case 8:
return 9;
case 16:
return 4;
case 17:
return 5;
case 18:
return 6;
case 26:
return 1;
case 27:
return 2;
case 28:
return 3;
case 36:
return 0;
//DECIMAL POINT
case 37:
decm=2;
mults=0.1;
return 48;
//ADD
case 39:
lastx=num;
num=num+stacks[1];
popx();
stacks[0]=num;
dispNum(num);
ans=1;
return 40;
//ENTER
case 35:
// lastx=num;
pushx(num);
num1=num;
// num=0;
dispNum(num);
decm=0;
ans=2;
shifts=0;
return 36;
case 0:
if (num<0)
errors(0);
else {
lastx=num;
num=sqrt(num);
stacks[0]=num;
dispNum(num);
ans=1;
}
return 11;
case 1:
lastx=num;
num=exp(num);
stacks[0]=num;
dispNum(num);
ans=1;
return 12;
case 2:
if (num<=0)
errors(0);
else {
lastx=num;
num=log(num);
stacks[0]=num;
dispNum(num);
ans=1;
}
return 112;
case 3:
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 4:
if (num==0)
errors(0);
else {
lastx=num;
num=1/num;
stacks[0]=num;
dispNum(num);
ans=1;
}
return 15;
case 23:
lastx=num;
num=-num;
stacks[0]=num;
dispNum(num);
ans=1;
return 16;
case 9:
if (num==0)
errors(0);
else {
lastx=num;
num=stacks[1]/num;
popx();
stacks[0]=num;
dispNum(num);
ans=1;
}
return 109;
case 13:
lastx=num;
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 19:
lastx=num;
num=stacks[1]*num;
popx();
stacks[0]=num;
dispNum(num);
ans=1;
return 20;
case 29:
lastx=num;
num=stacks[1]-num;
popx();
stacks[0]=num;
dispNum(num);
ans=1;
return 30;
// RUN
case 38:
if (mode!=1) {
mode=2;
// stacks[0]=num;
ans=1;
}
return 31;
// SST
case 20:
if (pc==0)
pc=1;
showProgram(pgm[pc]);
delay(1000);
Serial.println(pgm[pc]);
doProgLine(pgm[pc]);
return 132;
// rolldown
case 12:
rolldown();
return 133;
case 21: //XEQ
dispCmd(21);
shifts=12;
return 121;
// x<>y
case 22:
temp=num;
stacks[0]=stacks[1];
stacks[1]=temp;
num=stacks[0];
dispNum(num);
ans=1;
return 134;
//bksp
case 25:
if (decm) {
if (mults>=0.1)
return -1;
temp=modf(num/(mults*100),&temp1);
num=temp1*(mults*100);
mults=mults*10;
dispNum(num);
// ans=2;
}
else {
temp=modf(num/10,&temp1);
num=temp1;
dispNum(num);
// ans=2;
}
return 135;
case 97:
num=lastx;
stacks[0]=num;
dispNum(num);
ans=2;
return 36;
//Exponent
case 24:
shifts=5;
doexponent();
/* shifts=5;
Serial.println("expon"); */
return 26;
// PROG
/* case 33:
mode=1;
M5Cardputer.Display.clear();
sprintf(strs,"%d-%d",pc,pgm[pc]);
M5Cardputer.Display.drawString(strs,15,90);
return 43; */
//STO
case 10:
Serial.println("sto");
dispCmd(10);
shifts=2;
stoper=0;
dosto();
return 44;
//RCL
case 11:
dispCmd(11);
shifts=3;
dorcl();
return 45;
case 31:
num=0;
stacks[0]=0;
ans=1;
dispNum(num);
return 31;
//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 5:
lastx=num;
stats[0]+=1;
stats[1]+=num;
stats[3]+=num*num;
stats[2]+=stacks[1];
stats[4]+=stacks[1]*stacks[1];
stats[5]+=num*stacks[1];
dispNum(stats[0]);
num=stats[0];
stacks[0]=num;
ans=2;
return 149;
case 33:
fontz=(fontz+1) % 2;
dispNum(num);
return 150;
case 34:
beeps=(beeps+1) % 2;
return 153;
default:
return -1;
}
return -1;
}
int parseShift(int keys) {
double temp,temp1;
double i;
Serial.println("shift");
switch (keys) {
case 0:
shifts=0;
if (mode==1) {
cmdd=40;
return 40;
}
lastx=num;
num=num*num;
stacks[0]=num;
dispNum(num);
ans=1;
cmdd=40;
return 111;
case 1:
shifts=0;
if (mode==1) {
cmdd=41;
...
This file has been truncated, please download it to see its full contents.
Comments