Making a calculator using an Arduino

Here the updated code. I will appreaciated that you guys look at it and tell me what you think.

/*
   size : 2734 bytes
   
   Calculator Version 1.5
   
   Just add a number enter by the keypad and display it.
   With 2 function : Add / Equal ===> * button 
                     Clear ==>  # button
   It will use a MAX7219 for display the data ( for the shiftOut() ).
   and a 4021 for the keypad input data with the use of 
   an NOT gate 74HC14. ( for the shiftin() )
   See the keypad example in the tread for details.
  
   The interrupt use a 72LS20 from the row pins, any LOW signal will
   produce a HIGH pulse to be use has an interrupt signal.

   By Serge J Desjardins
   aka techone / tech37
   Toronto, Ontario, Canada

   Compile and Tested. Still Issues. Maybe it it the Keypad circuit. Not so clean   
*/

// Keypad Serial pins
const byte latchkeypin = 10;
const byte datakeypin = 12;
const byte clockkeypin = 11;

// Display Serial pins
const byte datadisplaypin = 9;
const byte clockdisplaypin = 8;
const byte latchdisplaypin = 7;

// Anykey and interrupt pin
const byte anykeypin = 2;

// MAX7419 init data
word initdata[5] = { 0x0C01, 0x0F00, 0x0A0B, 0x0B07, 0x09FF };

// Fill the Display array with a blank code
word display_data[8] = { 0x0100, 0x020F, 0x030F, 0x040F, 0x050F, 0x060F, 0x070F, 0x080F };  

// The digit array
byte digit_number[8] = {0,0,0,0,0,0,0,0};

// The second array - The total number
byte add_number[9] = {0,0,0,0,0,0,0,0,0};

// Key press keypad flag 
volatile boolean keystate;

// Overflow flag
boolean over_flow;
// add button flag
boolean add_flag;
// Clear button flag
boolean clear_flag;

// The keypad data 
byte keydata;
byte keynumber;

// The choice
byte choise;

void setup() 
{
  //start serial
  //Serial.begin(9600);

  //define the pins modes
  pinMode(latchkeypin, OUTPUT);
  pinMode(clockkeypin, OUTPUT); 
  pinMode(datakeypin, INPUT);
  pinMode(datadisplaypin, OUTPUT);
  pinMode(clockdisplaypin, OUTPUT);
  pinMode(latchdisplaypin, OUTPUT);
  pinMode(anykeypin, INPUT);
  // setup the interrupt pin
  attachInterrupt(0,anykeypress,RISING);
  
  /* Init the MAX7419 : Normal Mode
                        Normal Operation
                        Setting the Intensity
                        Numbers of Digits being display
                        Set to Decode Mode
  */
 
  for (int i=0;i<5;i++)
  {  
     digitalWrite(latchdisplaypin, LOW);
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, highByte(initdata[i]));
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, lowByte(initdata[i]) );
     digitalWrite(latchdisplaypin, HIGH);
     delay(5);
  }
  // Blank the display
  display_the_numbers();
  // init the flags and the calculation variable
  keystate = 0;  
  add_flag = 1;
  over_flow = 0;
  clear_flag = 1;  
}  

void loop()
{  
  while ( keystate == 0) 
  { 
     // wait for a key press 
  }
  read_keypad();
  convert_the_keypad_data();
  // The choise 1 = numbers , 2 = add , 3 = clear
  switch (choise)
  {
     case 1 :
             entry_of_numbers();
             break;
     case 2 :
             add_section();
             break;
     case 3 :
             clear_section();
             break;     
  }  
}

// Choise 1
void entry_of_numbers()
{  
  // Enter the numbers routine  
  if ( over_flow == 0 )
  {  
     if ( ( add_flag == 1) && ( clear_flag = 1 ) )
     {
       for (int i=0;i<8;i++)
       {
         digit_number[i] = 0;      
         display_data[i] = word ( ( i+1 ), 0x0F );
       }      
     }    
     // Enter the key number and move the arrays data.     
     for (int i=7;i>0;i--)
     {
       digit_number[i] = digit_number[i-1];
       display_data[i]=display_data[i-1];
       display_data[i] = word ( (i+1) , lowByte( display_data[i] ) );
     }
     digit_number[0] = keynumber;     
     display_data[0] = word ( 1, keynumber );     
     display_the_numbers();
     add_flag = 0;
     clear_flag = 0;
     keystate = 0;    
  }
}
// Choise 2 - the add section
void add_section()
{
    // Check for the "Add / Equal" button
    if ( over_flow == 0 )
    {      
      for (int i=0;i<8;i++)
      {
         add_number[i] = add_number[i+1] + add_number[i] + digit_number[i];
         if ((add_number[i] > 0x09) && (add_number[i] < 0x14) )
         {
           add_number[i+1] = 1;
           display_data[i] = word ( ( i + 1 ), ( add_number[i] - 10 )); 
         }
         else 
         {
            display_data[i] = word ( ( i + 1 ) , add_number[i]);
         }        
      }  
      
      // check for over flow
      if ( add_number[8] != 0)
      {      
        over_flow = 1;
        digit_number[0] = 0;        
        display_data[0] = 0x010B;
        for ( int i=1;i<8;i++)
        {
           digit_number[i] = 0;
           display_data[i] = word( (i+1), 0x0F );               
        }      
        keystate = 0;
        over_flow = 1;
        add_flag = 1;      
        display_the_numbers();           
      }
      else 
      {
        keystate = 0;
        over_flow = 0;
        add_flag = 1;
        display_the_numbers();
      }      
    }
}

// Choise 3 - The clear section
void clear_section()
{
    // Check for the "Clear" button 
      digit_number[0] = 0;
      add_number[0] = 0;
      display_data[0] = 0x0100;
      for ( int i=1;i<8;i++)
      {
        digit_number[i] = 0;
        add_number[i] = 0;
        display_data[i] = word( (i+1), 0x0F );               
      }
      add_number[8] = 0;
      keystate = 0;
      over_flow = 0;
      add_flag = 1;
      clear_flag = 1;
      display_the_numbers();     
      
}   
  //For troubleshooting the program using Serial Monitor
 /*  
    Serial.print(keydata, BIN);
    Serial.print("  ");
    Serial.print(keydata, HEX);
    Serial.print("  ");
    Serial.print(keynumber, HEX);
    Serial.print("  ");
    Serial.println(keydata, DEC);
    delay(1000);
*/

// Read keypad data via a 74HC14 - Inverter from the 4021
void read_keypad()
{
  digitalWrite(latchkeypin,HIGH);
  delayMicroseconds(20);
  digitalWrite(latchkeypin,LOW);
  keydata = shiftIn(datakeypin, clockkeypin,LSBFIRST);  
} 

// Convert the keypad data into BCD
// And a code for *, 0, # button
void convert_the_keypad_data()
{
  byte what_row;
  
  what_row = ( keydata >> 3) & 0x0F;
  switch ( what_row)
  {
    case 1:
           keynumber = ( ( keydata >> 1 ) & 0x03 ) + 1;
           choise = 1;
           break;
    case 2:
           keynumber = (( keydata >> 1 ) & 0x03 ) + 4;
           choise = 1;
           break;
    case 4:
           keynumber = (( keydata >> 1 ) & 0x03 ) + 7;
           choise = 1;
           break;
    case 8:
           special_key();
           break;    
  }  
  
}  

// the display the data routine to the MAX7219 
void display_the_numbers()
{
   for (int i=0;i<8;i++)
   {
     digitalWrite(latchdisplaypin, LOW);
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, highByte(display_data[i]) );
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, lowByte(display_data[i]) );
     digitalWrite(latchdisplaypin, HIGH);
     delay(5);
   }  
}

// A special code for 0, *, # button
void special_key()
{
   byte special_case;
   
   special_case = ( ( keydata >> 1) & 0x03 ) + 1;
   switch ( special_case )
   {
     case 1:
            keynumber = 0x0E;
            choise = 3;
            break;
     case 2:
            keynumber = 0x00;
            choise = 1;
            break;
     case 3:
            keynumber = 0x0B;
            choise = 2;
            break;
   }        
}

// Interrupt Routibe
void anykeypress()
{
  keystate = 1; 
}

Hi Mr.Serje , had a look on the code,

What about the KeyPad , is it working fine with amp now? i recall you had a problem with that! , May be its having debouncing effect?

One thing notice is that there's no debouncing with the keypad reading. That may cause unreliable input.


Rob

I agree with both of you. I may add a debounce, but I wonder how and where to place the tiny delay ? The key data came from the 4021, after a shiftIn() read. And the interrupt signal came from a 4 input NAND gate - 74LS20. So any bouncing by a key, also created a double pulse at the interrupt pin, activate the interrupt request. A 555 in monostable mode will take care of that. I will re-design the keypad circuit and is not done yet. I may add a latch in between the inverters and the 4021. And beside the bouncing problem, the close contact is not a close contact after all, more than intermittent, due to a non - proper LOW level at the input gates. The LM339 comparator will take care of that. I will set the comparator V ref at 2.5 V <-- mid point. Still two state.

I will keep you guys posted.

I happen to have a PS/2 keypad - see picture. I will re-code using the PS/2 library and re-do a circuit to use the PS/2 keypad and using the same 7 segments display. I will do that later. I will keep you posted on that one too. Still keep the same tread anyway.

Hi Mr.Serje i hope things are fine and the code is hopefully getting done ,following i add my suggestions:

also created a double pulse at the interrupt pin, activate the interrupt request.

I think what here can be done is that as the interrupt is being fired by the NAND gate You can set a counter in here to note the number of times the interrupt routine is being fired and put in a debugging statements on the Serial (I debugged my code in the same way when I was working with interrupts that however came from a mechanical part that is a Reed Switch) this way the real culprit can be traced back, if and only if the debouncing may be happening.

also. Mr.Serje you told once that amplifying(if i recall it right from my memory) the keypad buttons may make then susceptible to ultra sensitivity and thus the same can result in debounce.

I have a keypad/LCD screen that I put both debounce code into it for the usual reasons. I also put a millis count so that I could not double enter the same button.

Pseudo code:

Check for button press twice for debounce
          if good button press, use it,
  if a new button press check millis
 if (millis - last button press) ==  long enough time
  record new button press

The downside is you can push a button too quickly and have it ignored depending how you set the "long enough time variable.

@cyclegadget

Thank for you tips. Using the millis - time to check if a double press is being done... OK I think I get you. To impliment in my code, that I will figure this out. It look like it is a good idea. I will impliment something like that.

Let see :Loop 1 : read key Loop 1 ---> I mean : void loop()
measured time

Loop 2 : readkey
measured time
check the time difference
if to small - don't take the key
if big enough - take the key

Something like that. But with a 555 in monostable - output of the 555 going to the interrupt pin will solve the debouncing problem.
And I will do a digitalWrite( latchkeypin, HIGH ) in the interrupt routine to latch the keypad data.

@NI$HANT

To monitor the interrupt routine using Serial Monitor is a good idea.

also. Mr.Serje you told once that amplifying(if i recall it right from my memory) the keypad buttons may make then susceptible to ultra sensitivity and thus the same can result in debounce

I agree. That the 555 in monostable come in. I just set the output of the 555 about .. let say 10 mS to 20 mS. The input can multi pulse for 1 ms to 8 mS until it is stable, and the 555 chip won't even care about it. It is after the 555 finish it's pulse that the 555 chip care. ( the mono stable circuit, that is )

Updated :

The new keypad circuit is still no done. Today, when I was finish my afternoon run, going home, and in the kitchen, I was thinking about my code, and it occur to me that I made a executable bug in the ADD routine. I was installing the carry into the next array box, that may explain some garbage data in my display. No add right , screw up numbers, etc. Instead to place the carry into the add_number[] array, I just simply use a "temp" variable. Use the / and % to isolate the unit number.

Example : temp = add_number / 10; <-- the carry calculation

  • temp = 12 / 10 --> 1 or 8 / 10 --> 0 *
    Extract Unit : add_number = add_number % 10;
    add_number = 12 % 10 --- > 2 or 8 % 10 ---> 8 <-- the unit to be display.
    Add also, I fix the "cosmetic" bug. Was display : 00001234 Now : 1234
    I change the interrupt routine : I just add : digitalWrite(latchkeypin, HIGH); And remove that line from the read_keypad routine.
    *Next post is the new and final code. <-- I think *
    Here the old code :
    *_</em></em> <em><em><em>*if ( over_flow == 0)     {            for (int i=0;i<8;i++)       {         add_number[i] = add_number[i+1] + add_number[i] + digit_number[i];         if (add_number[i] > 0x09 )         {           add_number[i+1] = 1;           display_data[i] = word ( ( i + 1 ), ( add_number[i] - 10 ));         }         else         {             display_data[i] = word ( ( i + 1 ) , add_number[i]);         }              }              // check for over flow       if ( add_number[8] > 0)       {              over_flow = 1;         digit_number[0] = 0;                display_data[0] = 0x010B;         for ( int i=1;i<8;i++)         {           digit_number[i] = 0;           display_data[i] = word( (i+1), 0x0F );                      }              keystate = 0;         over_flow = 1;         add_flag = 1;              display_the_numbers();                }       else       {         keystate = 0;         over_flow = 0;         add_flag = 1;         display_the_numbers();       }          }*</em></em></em> <em><em>_*

The new Add section
*_</em></em> <em><em><em>*// Choise 2 - the add section void add_section() {     byte temp;       if ( over_flow == 0 )     {       // The Add logic        temp = 0; // the carry variable       for (int i=0;i<8;i++)       {         add_number[i] = temp + add_number[i] + digit_number[i];         temp = add_number[i] / 10;         add_number[i] = add_number[i] % 10;         display_data[i] = word ( ( i + 1 ) , add_number[i] );       }              // check for over flow       if ( temp != 0)       {              over_flow = 1;         digit_number[0] = 0;                display_data[0] = 0x010B;         for ( int i=1;i<8;i++)         {           digit_number[i] = 0;           display_data[i] = word( (i+1), 0x0F );                      }              keystate = 0;         over_flow = 1;         add_flag = 1;              display_the_numbers();                }       else       {         /*         For a cleaner display by removing         the zero's on the left side of the number         */         for (int i=7;i>0;i--)         {           if (add_number[i] == 0)           {             display_data[i] = word ( ( i + 1 ) , 0x0F );           }           else           {             break;           }                  }                  keystate = 0;         over_flow = 0;         add_flag = 1;         display_the_numbers();       }          } }*</em></em></em> <em><em>_*

Here is the updated and maybe the final code. It is working nice, except the issue with the keypad - double entry and miss key.

/*
   size : 2794 bytes
   
   Calculator Version 2.0
   
   Just add a number enter by the keypad and display it.
   With 2 function : Add / Equal ===> * button 
                     Clear ==>  # button
   It will use a MAX7219 for display the data ( for the shiftOut() ).
   and a 4021 for the keypad input data with the use of 
   an NOT gate 74HC14. ( for the shiftin() )
   See the keypad example in the tread for details.
  
   The interrupt use a 72LS20 from the row pins, any LOW signal will
   produce a HIGH pulse to be use has an interrupt signal.

   By Serge J Desjardins
   aka techone / tech37
   Toronto, Ontario, Canada

   Compile and Tested.   
*/

// Keypad Serial pins
const byte latchkeypin = 10;
const byte datakeypin = 12;
const byte clockkeypin = 11;

// Display Serial pins
const byte datadisplaypin = 9;
const byte clockdisplaypin = 8;
const byte latchdisplaypin = 7;

// Anykey and interrupt pin
const byte anykeypin = 2;

// MAX7419 init data
word initdata[5] = { 0x0C01, 0x0F00, 0x0A0B, 0x0B07, 0x09FF };

// Fill the Display array with a blank code
word display_data[8] = { 0x0100, 0x020F, 0x030F, 0x040F, 0x050F, 0x060F, 0x070F, 0x080F };  

// The digit array
byte digit_number[8] = {0,0,0,0,0,0,0,0};

// The second array - The total number
byte add_number[8] = {0,0,0,0,0,0,0,0};

// Key press keypad flag 
volatile boolean keystate;

// Overflow flag
boolean over_flow;
// add button flag
boolean add_flag;
// Clear button flag
boolean clear_flag;

// The keypad data 
byte keydata;
byte keynumber;

// The choice
byte choise;

void setup() 
{
  //define the pins modes
  pinMode(latchkeypin, OUTPUT);
  pinMode(clockkeypin, OUTPUT); 
  pinMode(datakeypin, INPUT);
  pinMode(datadisplaypin, OUTPUT);
  pinMode(clockdisplaypin, OUTPUT);
  pinMode(latchdisplaypin, OUTPUT);
  pinMode(anykeypin, INPUT);
  // setup the interrupt pin
  attachInterrupt(0,anykeypress,RISING);
  
  /* Init the MAX7419 : Normal Mode
                        Normal Operation
                        Setting the Intensity
                        Numbers of Digits being display
                        Set to Decode Mode
  */
 
  for (int i=0;i<5;i++)
  {  
     digitalWrite(latchdisplaypin, LOW);
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, highByte(initdata[i]));
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, lowByte(initdata[i]) );
     digitalWrite(latchdisplaypin, HIGH);
     delay(5);
  }
  // Blank the display
  display_the_numbers();
  
  // Set the latch key LOW
  digitalWrite(latchkeypin, LOW);
  // init the flags and the calculation variable
  keystate = 0;  
  add_flag = 1;
  over_flow = 0;
  clear_flag = 1;   
}  

void loop()
{  
  while ( keystate == 0) 
  { 
     // wait for a key press 
  }
  read_keypad();
  convert_the_keypad_data();
  // The choise 1 = numbers , 2 = add , 3 = clear
  switch (choise)
  {
     case 1 :
             entry_of_numbers();
             break;
     case 2 :
             add_section();
             break;
     case 3 :
             clear_section();
             break;     
  }  
}

// Choise 1
void entry_of_numbers()
{  
  // Enter the numbers routine  
  if ( over_flow == 0 )
  {  
     if ( ( add_flag == 1) && ( clear_flag = 1 ) )
     {
       for (int i=0;i<8;i++)
       {
         digit_number[i] = 0;      
         display_data[i] = word ( ( i+1 ), 0x0F );
       }      
     }    
     // Enter the key number and move the arrays data.     
     for (int i=7;i>0;i--)
     {
       digit_number[i] = digit_number[i-1];
       display_data[i]=display_data[i-1];
       display_data[i] = word ( (i+1) , lowByte( display_data[i] ) );
     }
     digit_number[0] = keynumber;     
     display_data[0] = word ( 1, keynumber );     
     display_the_numbers();
     add_flag = 0;
     clear_flag = 0;
     keystate = 0;    
  }
}
// Choise 2 - the add section
void add_section()
{
    byte temp;
  
    if ( over_flow == 0 )
    { 
      // The Add logic   
      temp = 0; // the carry variable 
      for (int i=0;i<8;i++)
      {
         add_number[i] = temp + add_number[i] + digit_number[i];
         temp = add_number[i] / 10;
         add_number[i] = add_number[i] % 10;
         display_data[i] = word ( ( i + 1 ) , add_number[i] );
      }  
      
      // check for over flow
      if ( temp != 0)
      {      
        over_flow = 1;
        digit_number[0] = 0;        
        display_data[0] = 0x010B;
        for ( int i=1;i<8;i++)
        {
           digit_number[i] = 0;
           display_data[i] = word( (i+1), 0x0F );               
        }      
        keystate = 0;
        over_flow = 1;
        add_flag = 1;      
        display_the_numbers();           
      }
      else 
      {
        /*
        For a cleaner display by removing
        the zero's on the left side of the number
        */
        for (int i=7;i>0;i--)
        {
          if (add_number[i] == 0)
          {
            display_data[i] = word ( ( i + 1 ) , 0x0F );
          }
          else 
          {
            break;
          }          
        }          
        keystate = 0;
        over_flow = 0;
        add_flag = 1;
        display_the_numbers();
      }      
    }
}

// Choise 3 - The clear section
void clear_section()
{
    // Check for the "Clear" button 
      digit_number[0] = 0;
      add_number[0] = 0;
      display_data[0] = 0x0100;
      for ( int i=1;i<8;i++)
      {
        digit_number[i] = 0;
        add_number[i] = 0;
        display_data[i] = word( (i+1), 0x0F );               
      }      
      keystate = 0;
      over_flow = 0;
      add_flag = 1;
      clear_flag = 1;
      display_the_numbers();     
      
}   

// Read keypad data via a 74HC14 - Inverter from the 4021
void read_keypad()
{
 
  delayMicroseconds(10);
  digitalWrite(latchkeypin,LOW);
  keydata = shiftIn(datakeypin, clockkeypin,LSBFIRST);  
} 

// Convert the keypad data into BCD
// And a code for *, 0, # button
void convert_the_keypad_data()
{
  byte what_row;
  
  what_row = ( keydata >> 3) & 0x0F;
  switch ( what_row)
  {
    case 1:
           keynumber = ( ( keydata >> 1 ) & 0x03 ) + 1;
           choise = 1;
           break;
    case 2:
           keynumber = (( keydata >> 1 ) & 0x03 ) + 4;
           choise = 1;
           break;
    case 4:
           keynumber = (( keydata >> 1 ) & 0x03 ) + 7;
           choise = 1;
           break;
    case 8:
           special_key();
           break;    
  }  
  
}  

// the display the data routine to the MAX7219 
void display_the_numbers()
{
   for (int i=0;i<8;i++)
   {
     digitalWrite(latchdisplaypin, LOW);
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, highByte(display_data[i]) );
     shiftOut(datadisplaypin, clockdisplaypin, MSBFIRST, lowByte(display_data[i]) );
     digitalWrite(latchdisplaypin, HIGH);
     delay(5);
   }  
}

// A special code for 0, *, # button
void special_key()
{
   byte special_case;
   
   special_case = ( ( keydata >> 1) & 0x03 ) + 1;
   switch ( special_case )
   {
     case 1:
            keynumber = 0x0E;
            choise = 3;
            break;
     case 2:
            keynumber = 0x00;
            choise = 1;
            break;
     case 3:
            keynumber = 0x0B;
            choise = 2;
            break;
   }        
}

// Interrupt Routibe
void anykeypress()
{
  digitalWrite(latchkeypin, HIGH);
  keystate = 1; 
}

double entry and miss key

okay so it seems Mr.Serje that everything now is fine , i think we guys are done and the code is good the problem arising if any is because of that keypad that is getting beyond our control.

Thanks NI$HANT for your support.

because of that keypad that is getting beyond our control.

I will tame that keypad, don't you worry about it :grin:

I will keep you guys update about my progress. The PS/2 keypad will be use for the same idea. But a more complete project by adding the function : Add, Minus, Multiplication and Division.

Mention not! Mr Serje, I'm always here for you, now lets see how things work with ps 2 keyboard.

I want to do something very similar to this, but I want preset buttons (15, 16, 17, 18, 19, 20, 25) and have those numbers be added when each of the 7 buttons would correlate with the addition of each of the numbers listed. Any advice as to how i would go about this coding?