Expandable/Modular (sort-of) keypad

First off, before I get started, I wanted to say thank you to everyone who has posted a question or answer on this forum. I have used it many times to help me get started with my arduino projects and it has been beyond helpful, and now I get to share and get input on a project that I could not find in this wealth of information (thats not to say its not here).

Now, on to the project:

I wanted a keypad system that was easy to expand, I could build with easy to get (or already owned) parts and used minimal pins on my arduino boards. This is what I came up with and I wanted to run it by you guys to make sure it is safe for my megas (it should be) and to see if you guys see any problems I might run into with this setup. Hopefully if it works as expected, it will help someone else's projects as well.

The basic idea is to make a voltage divider and use a switch to send each of these different voltages to an analog input on the arduino, which it recognizes as different key presses and performs the appropriate action.

The thought process:
A basic voltage divider (with resisters) didnt really seem like a good way to go, so my next thought was zeners in series. But zeners are only available down to about 1v or so (4 buttons after the additional circuitry), so that wouldn't really work well either. Then I realized I had a TON of 4148 signal diodes, and knew their forward voltage was lower, and could be manipulated down to about 400-450mv by restricting current (win win, low standby operating current). The low current probably needs to be amplified a bit, which is where the transistor comes in, the resistor (R2) at the emitter keeps the current from being too high.

Laying it out:
On paper it looks good, in simulation, I needed a resistor to pull down the transistor when no buttons were closed, which is what R3 is for. Otherwise I got about 900mv (slightly more than the second lowest button voltage) on the output. I plan to keep this resistor in my first prototype, but I'll remove it to see if its actually necessary when I finish the system. As expected on paper, the simulation showed that it will not be a completely linear climb in voltage as you work down the ladder, but thats not a problem, since the numbers dont get close enough to prevent a healthy buffer between button voltages.

The Program:
The programming will simply watch for a non-zero (with some tolerance) voltage at the appropriate analog input(s) and use a case select to determine which button is being pressed, then perform the appropriate action.

The Checklist:

As shown in the attachment, it only uses 3 pins, and for each additional 10 buttons, it adds 1 more. So, check on minimal pins, as long as you dont need a fully functional computer keyboard.

Simply add another switch circuit (the switches, transistor, R3, and R2) in parallel to the existing switch circuit and add another connection to a different analog input pin (another 'J1' in the attachment) and you have room for 10 more buttons, this is limited only by the number of inputs on your board that you can use. Expandable? CHECK!!!! (a big, bright red one 5x the size of the box)

It consumes very little current. I'm not sure if everyone else will see this as a good thing or bad thing, but since I'm limited to using as few pins as possible, I'm calling this a win (until someone shows me a better way).

I can build it with components I have on hand (and make use of the 100+ micro buttons I have, like the reset buttons), and all the parts are really common.

So yeah, that's what I was thinking. What do you guys think? I know I'm re-inventing the wheel here, but I wanted a system to call my own for my projects, and this rather simple approach fit all my criteria (assuming it works).

-Elryk

UPDATE: The system is working and now in library form with an example that outputs to serial. It can be downloaded in post #16.

Maybe you could expand this by at least ten more keys by just stringing up ten more on a line like you have, but with a voltage offset, so that the voltage between each zener in the new string is, say, half way between the voltages in the first string. You could keep that up with more key strings until you got to where the resolution of the analogReads was no longer reliable to separate the inputs. Probably could get at least to 50 keys. I'd be interested in hearing how this works out.

Good luck.

jrdoner:
Maybe you could expand this by at least ten more keys by just stringing up ten more on a line like you have, but with a voltage offset, so that the voltage between each zener in the new string is, say, half way between the voltages in the first string. You could keep that up with more key strings until you got to where the resolution of the analogReads was no longer reliable to separate the inputs. Probably could get at least to 50 keys. I'd be interested in hearing how this works out.

Good luck.

I had thought of that, but I decided for now it was better to keep the system simple with the trade off of having less buttons per pin. The obstacle that made it not simple is the offset voltage. Because the voltage drop of the 4148s changes slightly with current, the voltage change between adjacent buttons on the ladder is not linear. The buttons at the end (S9 and S10) only have an output voltage difference of about 230mv, meaning the reference voltage for the offset would need to be 115mv (to give the largest margin). Thats not a problem for the mechanics of the system, but I found it very difficult to find a stable, simple and cheap 100mv reference.

When you consider that, and the number of analog inputs on the Mega, its hard to justify the complication/expense. If I ever need a small project with a bunch of buttons, I'll defiantly re-consider this though.

Thanks for the input!

Elryk:
First off, before I get started, I wanted to say thank you to everyone who has posted a question or answer on this forum. I have used it many times to help me get started with my arduino projects and it has been beyond helpful, and now I get to share and get input on a project that I could not find in this wealth of information (thats not to say its not here).

Now, on to the project:

I wanted a keypad system that was easy to expand, I could build with easy to get (or already owned) parts and used minimal pins on my arduino boards. This is what I came up with and I wanted to run it by you guys to make sure it is safe for my megas (it should be) and to see if you guys see any problems I might run into with this setup. Hopefully if it works as expected, it will help someone else's projects as well.

The basic idea is to make a voltage divider and use a switch to send each of these different voltages to an analog input on the arduino, which it recognizes as different key presses and performs the appropriate action.

The thought process:
A basic voltage divider (with resisters) didnt really seem like a good way to go, so my next thought was zeners in series. But zeners are only available down to about 1v or so (4 buttons after the additional circuitry), so that wouldn't really work well either. Then I realized I had a TON of 4148 signal diodes, and knew their forward voltage was lower, and could be manipulated down to about 400-450mv by restricting current (win win, low standby operating current). The low current probably needs to be amplified a bit, which is where the transistor comes in, the resistor (R2) at the emitter keeps the current from being too high.

Laying it out:
On paper it looks good, in simulation, I needed a resistor to pull down the transistor when no buttons were closed, which is what R3 is for. Otherwise I got about 900mv (slightly more than the second lowest button voltage) on the output. I plan to keep this resistor in my first prototype, but I'll remove it to see if its actually necessary when I finish the system. As expected on paper, the simulation showed that it will not be a completely linear climb in voltage as you work down the ladder, but thats not a problem, since the numbers dont get close enough to prevent a healthy buffer between button voltages.

The Program:
The programming will simply watch for a non-zero (with some tolerance) voltage at the appropriate analog input(s) and use a case select to determine which button is being pressed, then perform the appropriate action.

The Checklist:

As shown in the attachment, it only uses 3 pins, and for each additional 10 buttons, it adds 1 more. So, check on minimal pins, as long as you dont need a fully functional computer keyboard.

Simply add another switch circuit (the switches, transistor, R3, and R2) in parallel to the existing switch circuit and add another connection to a different analog input pin (another 'J1' in the attachment) and you have room for 10 more buttons, this is limited only by the number of inputs on your board that you can use. Expandable? CHECK!!!! (a big, bright red one 5x the size of the box)

It consumes very little current. I'm not sure if everyone else will see this as a good thing or bad thing, but since I'm limited to using as few pins as possible, I'm calling this a win (until someone shows me a better way).

I can build it with components I have on hand (and make use of the 100+ micro buttons I have, like the reset buttons), and all the parts are really common.

So yeah, that's what I was thinking. What do you guys think? I know I'm re-inventing the wheel here, but I wanted a system to call my own for my projects, and this rather simple approach fit all my criteria (assuming it works).

-Elryk

Depends on the actual gain(Hfe) of the transistor, and where in the gain curve your currents lie.

build it and measure the voltages, The you are going to need at least a repeatable reading within 0.05V between each value.

good luck.

I just ran a model on LTSpice. Too many Diodes. With 1n4148 and 2n3906 the voltage crashes after 7 switches.

Here is a screen grab of the simulation

so, seven Switches would work.

Chuck.

Isn't there a shift register chip, perhaps I2C based, that creates an interrupt when an input changes?

Another way that may be more responsive is this - daisy chain as many 74HC165 shift registers as you need buttons for, 8 per shift register. Use SPI.transfer to read their data in - scan them periodically, react to any that have changed. I'd pull the inputs high with pullup resistors, use the button press to create a low input.
You can scan as quickly as you can, or you can add diodes as well to create an interrupt when a button is pushed, capture the state of the inputs, then read them in via SPI and act on the low input(s).

chucktodd:
Depends on the actual gain(Hfe) of the transistor, and where in the gain curve your currents lie.

build it and measure the voltages, The you are going to need at least a repeatable reading within 0.05V between each value.

good luck.

I just ran a model on LTSpice. Too many Diodes. With 1n4148 and 2n3906 the voltage crashes after 7 switches.

Here is a screen grab of the simulation

so, seven Switches would work.

Chuck.

You are getting different simulation results than I was, I like yours better. Although your results lead to less buttons, the output is much cleaner. Mine had an almost logarithmic increase (going from s10 and working up) in change in voltage, going from about 230mv between s9 and s10 up to about 450mv between s1 and s2. It also seems like my highest voltage is higher than yours by about 1v (mine went up to about 4.3v). Do you know why we are getting such different results (~30%)? I simulated with Multisim.

CrossRoads:
Isn't there a shift register chip, perhaps I2C based, that creates an interrupt when an input changes?

Another way that may be more responsive is this - daisy chain as many 74HC165 shift registers as you need buttons for, 8 per shift register. Use SPI.transfer to read their data in - scan them periodically, react to any that have changed. I'd pull the inputs high with pullup resistors, use the button press to create a low input.
You can scan as quickly as you can, or you can add diodes as well to create an interrupt when a button is pushed, capture the state of the inputs, then read them in via SPI and act on the low input(s).

An interesting idea here. I dont have any 165s around, but they are fairly cheap. How would you interpret the serial output on an arduino? You mention SPI, but doesnt that need addresses for each device?

Elryk:
You are getting different simulation results than I was, I like yours better. Although your results lead to less buttons, the output is much cleaner. Mine had an almost logarithmic increase (going from s10 and working up) in change in voltage, going from about 230mv between s9 and s10 up to about 450mv between s1 and s2. It also seems like my highest voltage is higher than yours by about 1v (mine went up to about 4.3v). Do you know why we are getting such different results (~30%)? I simulated with Multisim.

An interesting idea here. I dont have any 165s around, but they are fairly cheap. How would you interpret the serial output on an arduino? You mention SPI, but doesnt that need addresses for each device?

the biggest difference is probably the actual transistor models.

.model 2N3904 NPN(IS=1E-14 VAF=100 Bf=300 IKF=0.4 XTB=1.5 BR=4 CJC=4E-12 CJE=8E-12 RB=20 RC=0.1 RE=0.1 TR=250E-9 TF=350E-12 ITF=1 VTF=2 XTF=3 Vceo=40 Icrating=200m mfg=Philips)
.model 1N4148 D(Is=2.52n Rs=.568 N=1.752 Cjo=4p M=.4 tt=20n Iave=200m Vpk=75 mfg=OnSemi type=silicon)

Chuck.

I have attached a netlist and the LtSpice schematic file. You'll have to rename them. This forum only allows specific file names

diodeswitch.asc.txt (5.18 KB)

diodeswitch.net.txt (1.61 KB)

I got a chance to build this circuit and write some code for it today. I was waiting on some new breadboards, 5eBoards (really cool item btw). As it turns out, I didnt need the transistor section at all, and infact, as chuck's simulation showed, it limited the buttons to 7 before it stopped working properly.

So, to modify the circuit: remove the transistor, R2, D11 (it was there to help with the transistors voltage drop), and connect the analog input to where the base of the transistor was connected. Also, it turns out that R3 (the pull down resistor) is required, otherwise, the software will see no button press as a press on either button 3 or 4.

Without the transistor, the voltage change between each adjacent button is nearly linear at a little over 90 steps, or ~440mv. I was able to keep all 10 switches. The code handles different input pins and continues the same variable for all pins in the For loop.

Here is my test code. The for loop must be adjusted for the number of inputs. any un-used or un-pulled-down inputs will show a button press even though no buttons are even attached.

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int checktime;  //variable for determing button check timer
int curtime;  //current time (for button check timer)
int button;  //Current button recieved on input pin.
int b2curs[] = {2, 0};  //Cursor location for bottom 2 lines of screen (key press log).
int lastbutton;  //Last button that was pressed.
int lastbuttonon;  //Time last button was pressed.
int lastbuttonoff;  //Time last button was released. 0 while a button is pressed.

void setup() {
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("Button Test:");
  lcd.setCursor(0, 1);
  lcd.print("Button Pushed:");
}

void loop() {
  curtime = (int)millis(); 
  checktime = (int)(curtime / 50) * 50; //Set the check interval, 50ms
  if (checktime == curtime) {  //If it is time to check (every 50ms)...
    for (int i = 0; i < 5; i++) {  //Checking inputs 0 to 4.
      if (analogRead(i) > 45) {  //Do the check
        button = (analogRead(i) / 90) - 1 + (10 * i);  //Assign the button variable with the button number.
        lcd.setCursor(14, 1);
        lcd.print(button);
      } else {
        button = -1;  //Make button = -1 if no button is being pressed.
        if (lastbuttonoff == 0) lastbuttonoff = curtime; 
        lcd.setCursor(14, 1);
        lcd.print("    ");
      }
    }
    if (button > -1) { //If a button is currently pressed. 
      if (button != lastbutton || curtime - lastbuttonon == (int) ((curtime - lastbuttonon) / 1000) * 1000) {  //New button or repeat hold.
        if (button != lastbutton) { //New button
          lastbutton = button; 
          lastbuttonon = curtime;
          lastbuttonoff = 0;
        }
        lcd.setCursor(b2curs[1], b2curs[0]);
        lcd.print(button);  //Display button press in the bottom 2 lines of the LCD and increment that cursor variable.
        b2curs[1] = b2curs[1] + 1;
        if (b2curs[1] == 20) {
          b2curs[1] = 0;
          b2curs[0] = b2curs[0] + 1;
          if (b2curs[0]==4) b2curs[0] = 2;
        }
      }
      if (button == lastbutton && lastbuttonoff > 0) { //If its the same button, but released and repressed.
        lastbuttonon = curtime; //Make it the last button as if it is new (lastbutton is already assigned)
        lastbuttonoff = 0;
        lcd.setCursor(b2curs[1], b2curs[0]);
        lcd.print(button);
        b2curs[1] = b2curs[1] + 1;
        if (b2curs[1] == 20) {
          b2curs[1] = 0;
          b2curs[0] = b2curs[0] + 1;
          if (b2curs[0]==4) b2curs[0] = 2;
        }
      }
    }
  }
}

I am still having some trouble with the for loop as mentioned. I would like to setup the final code so it doesn't need to be changed based on what inputs are connected. Any suggestions?

Is there anything there that might cause a problem when I start to throw other actions into the mix? (say, monitoring sensors and activating relays for instance)

My next step is to take the circuit off the 5eBoard and put it on a standard protoboard with all 5 input pins in the code connected (50 button keypad!). I dont really need it, but it will be a good way to test full functionality. Then I'll re-organize the code so it just reports the button info to variables (no action, just button discovery) then a separate section after that to handle the actions.

Thanks alot Chuck, your simulation pointed me EXACTLY to the problem when I actually built it and started troubleshooting.

I'll try to keep this thread updated as I progress to the final 50 key prototype.

-Elryk

I sort of resolved the not-connected input problem. The code below checks all the analog pins for low voltage (meaning pulled down with the 1M resistor), then when it finds one, it counts consecutive connected inputs until it doesn't find one Its not pretty and wont work if any buttons are pressed on startup (or reset), so I'm still looking for a better option, but this will do for now:

int Astart;  //First analog input to check.
int NoA = 0;  //Number of analog inputs used.

void setup() {
  for (int i = 0; i < 16; i++) {  //Check all the inputs for connection
    if (analogRead(i) < 75) {  //This input is connected
      Astart = i;  //Assign the current input as the first analog input
      for (int j = i; j < 16; j++) {  //check each input after the current one
        if (analogRead(j) < 75) {  //this input is connected
          NoA = NoA + 1; //Add 1 to the number of inputs
        } else {  //this input is not connected
          i = 16; //set i and j to 16 to skip the rest of the for loops
          j = 16;
        }
      }
    }
  }
}

For some reason I had to change the "no button" voltage up to 75 (from 45 in the actual button check section). Anything lower and it would frequently pickup inputs that were not connected.

I have started to build the 50 button prototype, which turned out to be 40 buttons, since I ordered protoboards that were a touch to small, anyways....

I got 2 sets (10 per set) of buttons wired up and decided to give it a test run. Got my updated code uploaded and started to push buttons when I noticed something very strange: The first 3 (0-2) buttons would not work and the other 7 buttons reported as the last button (9) on that set, the second set of buttons, 10-19 worked fine. I swapped the button outputs around on various analog inputs and found that which ever button set was plugged into the lowest numbered analog input would not work correctly, however, the second would work fine. Also, when either button set is plugged in and the other is not, it works fine, no matter what analog input is used.

Clearly my wiring is correct (each set works individually), so it must be something with the arduino, but I have tried 2 different ones (both megas) with identical results. Why would the buttons for each set work when that set is the only one plugged in, but when both are plugged in, only the higher number output works? Anyone have any ideas?

-Elryk

Elryk:
I have started to build the 50 button prototype, which turned out to be 40 buttons, since I ordered protoboards that were a touch to small, anyways....

I got 2 sets (10 per set) of buttons wired up and decided to give it a test run. Got my updated code uploaded and started to push buttons when I noticed something very strange: The first 3 (0-2) buttons would not work and the other 7 buttons reported as the last button (9) on that set, the second set of buttons, 10-19 worked fine. I swapped the button outputs around on various analog inputs and found that which ever button set was plugged into the lowest numbered analog input would not work correctly, however, the second would work fine. Also, when either button set is plugged in and the other is not, it works fine, no matter what analog input is used.

Clearly my wiring is correct (each set works individually), so it must be something with the arduino, but I have tried 2 different ones (both megas) with identical results. Why would the buttons for each set work when that set is the only one plugged in, but when both are plugged in, only the higher number output works? Anyone have any ideas?

-Elryk

In your code are you reading each analog input twice, throwing away the first reading?

The Arduinos have ONE ADC + a multi channel MUX when you switch between inputs the sample capacitor is not discharged. So it transfers charge between MUX channels.

// minimum
uint16_t reading1 = analogRead(A0); // throw this one away
reading1 = analogRead(A0); // good reading

// better, average of two reading
analogRead(A0); // garbage value because of possible MUX channel change
reading1 = analogRead(A0); // first reading
reading1 += analogRead(A0); // add the second good reading
reading1 = reading1/2; // average them out

// even better Average of 10 readings
analogRead(A0); // switch MUX Channel, discard reading
reading1 = 0;
for(uint8_t i=0;i<10;i++){ reading1 += analogRead(A0);
reading1 = reading1 / 10;

uint16_t reading2= analogRead(A1); // throw this one away
reading2 = analogRead(A1);// good reading

Chuck.

I was not checking the analog inputs twice and discarding the first, but after changing it so that I was, the problem got worse. Now, the set of buttons connected to the lowest numbered input will not read any button presses, however, the second set still works fine.

If either set is connected and the other is disconnected, the connected one works, reguardless of which set it is or which input it is connected to.

If both are connected, the first set to a0 and the second set to a1, only the second set (a1) will register button presses

If both are connected, the first to a15, the second to a14, only the first set (a15) will register button presses.

Here is the code I current have running:

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int checktime;  //variable for determing button check timer
int curtime;  //current time (for button check timer)
int button;  //Current button recieved on input pin.
int b2curs[] = {2, 0};  //Cursor location for bottom 2 lines of screen (key press log).
int lastbutton;  //Last button that was pressed.
int lastbuttonon;  //Time last button was pressed.
int lastbuttonoff;  //Time last button was released. 0 while a button is pressed.
int Astart;  //First analog input to check.
int NoA = 0;  //Number of analog inputs used.

void setup() {

  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("Button Test:");
  lcd.setCursor(0, 1);
  lcd.print("Button Pushed:");
  
  for (int i = 0; i < 16; i++) {  //Check all the inputs for connection
    analogRead(i);
    if (analogRead(i) < 75) {  //This input is connected
      Astart = i;  //Assign the current input as the first analog input
      for (int j = i; j < 16; j++) {  //check each input after the current one
        analogRead(j);
        if (analogRead(j) < 75) {  //this input is connected
          NoA = NoA + 1; //Add 1 to the number of inputs
        } else {  //this input is not connected
          i = 16; //set i and j to 16 to skip the rest of the for loops
          j = 16;
        }
      }
    }
  }
}

void loop() {
  curtime = (int)millis(); 
  checktime = (int)(curtime / 50) * 50; //Set the check interval, 50ms
  if (checktime == curtime) {  //If it is time to check (every 50ms)...
    for (int i = Astart; i < Astart + NoA; i++) {  //Checking inputs Change initialization values to change inputs.
      analogRead(i);
      if (analogRead(i) > 60) {  //Do the check
        button = (analogRead(i) / 90) - 1 + (10 * i);  //Assign the button variable with the button number.
        lcd.setCursor(14, 1);
        lcd.print(button);
        i = Astart + NoA; //**********ADDED THIS LINE************
      } else {
        button = -1;  //Make button = -1 if no button is being pressed.
        if (lastbuttonoff == 0) lastbuttonoff = curtime; 
        lcd.setCursor(14, 1);
        lcd.print("    ");
      }
    }
    if (button > -1) { //If a button is currently pressed. 
      if (button != lastbutton || curtime - lastbuttonon == (int) ((curtime - lastbuttonon) / 1000) * 1000) {  //New button or repeat hold.
        if (button != lastbutton) { //New button
          lastbutton = button; 
          lastbuttonon = curtime;
          lastbuttonoff = 0;
        }
        lcd.setCursor(b2curs[1], b2curs[0]);
        lcd.print(button);  //Display button press in the bottom 2 lines of the LCD and increment that cursor variable.
        b2curs[1] = b2curs[1] + 2;
        if (b2curs[1] == 20) {
          b2curs[1] = 0;
          b2curs[0] = b2curs[0] + 1;
          if (b2curs[0]==4) b2curs[0] = 2;
        }
      }
      if (button == lastbutton && lastbuttonoff > 0) { //If its the same button, but released and repressed.
        lastbuttonon = curtime; //Make it the last button as if it is new (lastbutton is already assigned)
        lastbuttonoff = 0;
        lcd.setCursor(b2curs[1], b2curs[0]);
        lcd.print(button);
        b2curs[1] = b2curs[1] + 2;
        if (b2curs[1] == 20) {
          b2curs[1] = 0;
          b2curs[0] = b2curs[0] + 1;
          if (b2curs[0]==4) b2curs[0] = 2;
        }
      }
    }
  }
}

EDIT: While trying to debug a seemingly unrelated problem (detecting a hold, rather than detecting a new keypress for every check), I seem to have fixed the problem, but it makes no sense as to why this fixes it. I added the following to try to break the for loop when a keypress is detected (to prevent button from being reset to -1 on the next iteration):

i = Astart + NoA; //ADDED THIS LINE*

Oh, btw, this didnt fix my problem, it still thinks a hold is a bunch of repetitive presses.

What in the Hello World is going on with this project?!?! Very confused....

Elryk:
I was not checking the analog inputs twice and discarding the first, but after changing it so that I was, the problem got worse. Now, the set of buttons connected to the lowest numbered input will not read any button presses, however, the second set still works fine.

If either set is connected and the other is disconnected, the connected one works, reguardless of which set it is or which input it is connected to.

If both are connected, the first set to a0 and the second set to a1, only the second set (a1) will register button presses

If both are connected, the first to a15, the second to a14, only the first set (a15) will register button presses.

Here is the code I current have running:

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int checktime;  //variable for determing button check timer
int curtime;  //current time (for button check timer)
int button;  //Current button recieved on input pin.
int b2curs[] = {2, 0};  //Cursor location for bottom 2 lines of screen (key press log).
int lastbutton;  //Last button that was pressed.
int lastbuttonon;  //Time last button was pressed.
int lastbuttonoff;  //Time last button was released. 0 while a button is pressed.
int Astart;  //First analog input to check.
int NoA = 0;  //Number of analog inputs used.

void setup() {

pinMode(13, OUTPUT);
 digitalWrite(13, LOW);
 lcd.begin(20, 4);
 lcd.setCursor(0, 0);
 lcd.print("Button Test:");
 lcd.setCursor(0, 1);
 lcd.print("Button Pushed:");
 
 for (int i = 0; i < 16; i++) {  //Check all the inputs for connection
   analogRead(i);
   if (analogRead(i) < 75) {  //This input is connected
     Astart = i;  //Assign the current input as the first analog input
     for (int j = i; j < 16; j++) {  //check each input after the current one
       analogRead(j);
       if (analogRead(j) < 75) {  //this input is connected
         NoA = NoA + 1; //Add 1 to the number of inputs
       } else {  //this input is not connected
         i = 16; //set i and j to 16 to skip the rest of the for loops
         j = 16;
       }
     }
   }
 }
}

void loop() {
 curtime = (int)millis();
 checktime = (int)(curtime / 50) * 50; //Set the check interval, 50ms
 if (checktime == curtime) {  //If it is time to check (every 50ms)...
   for (int i = Astart; i < Astart + NoA; i++) {  //Checking inputs Change initialization values to change inputs.
     analogRead(i);
     if (analogRead(i) > 60) {  //Do the check
       button = (analogRead(i) / 90) - 1 + (10 * i);  //Assign the button variable with the button number.
       lcd.setCursor(14, 1);
       lcd.print(button);
       i = Astart + NoA; //ADDED THIS LINE**
     } else {
       button = -1;  //Make button = -1 if no button is being pressed.
       if (lastbuttonoff == 0) lastbuttonoff = curtime;
       lcd.setCursor(14, 1);
       lcd.print("    ");
     }
   }
   if (button > -1) { //If a button is currently pressed.
     if (button != lastbutton || curtime - lastbuttonon == (int) ((curtime - lastbuttonon) / 1000) * 1000) {  //New button or repeat hold.
       if (button != lastbutton) { //New button
         lastbutton = button;
         lastbuttonon = curtime;
         lastbuttonoff = 0;
       }
       lcd.setCursor(b2curs[1], b2curs[0]);
       lcd.print(button);  //Display button press in the bottom 2 lines of the LCD and increment that cursor variable.
       b2curs[1] = b2curs[1] + 2;
       if (b2curs[1] == 20) {
         b2curs[1] = 0;
         b2curs[0] = b2curs[0] + 1;
         if (b2curs[0]==4) b2curs[0] = 2;
       }
     }
     if (button == lastbutton && lastbuttonoff > 0) { //If its the same button, but released and repressed.
       lastbuttonon = curtime; //Make it the last button as if it is new (lastbutton is already assigned)
       lastbuttonoff = 0;
       lcd.setCursor(b2curs[1], b2curs[0]);
       lcd.print(button);
       b2curs[1] = b2curs[1] + 2;
       if (b2curs[1] == 20) {
         b2curs[1] = 0;
         b2curs[0] = b2curs[0] + 1;
         if (b2curs[0]==4) b2curs[0] = 2;
       }
     }
   }
 }
}




EDIT: While trying to debug a seemingly unrelated problem (detecting a hold, rather than detecting a new keypress for every check), I seem to have fixed the problem, but it makes no sense as to why this fixes it. I added the following to try to break the for loop when a keypress is detected (to prevent button from being reset to -1 on the next iteration):

i = Astart + NoA; //********ADDED THIS LINE*********

Oh, btw, this didnt fix my problem, it still thinks a hold is a bunch of repetitive presses.

What in the Hello World is going on with this project?!?! Very confused....

couple of questions:

  1. Are you using a MEGA2560?
  2. you do realize that A1 does not equate to the value 1?

A1 is an Arduino define for Analog input #1

this code

  for (int i = 0; i < 16; i++) {  //Check all the inputs for connection
    analogRead(i);

does not do what you want.

if you want to reference the Analog input pins as integer values

A0 (analog pin 0) is 54
A1 (analog pin 1) is 55

See the attached PNG file.

The AIN0..AIN15 are A0..A15 those Blue D54..D69 are integer values for the pins.

analog pins can be used as digital but digital cannot be used as analog.

Chuck.

I am using a Mega2560 (the one in your picture). I'm not sure I understand what your second question means. A1 does in fact correspond to analogRead(1), If it didn't, none of my buttons would have been working at all. I'm guessing that's not what you meant though.

That for-loop is there to see what pins are connected to ground (through the 1M resistor), if its not, the pins will see a floating value of around 2v (my observations). the next few lines of code after that do the checking. That part works (it correctly finds all the pins its supposed to), but id like to find a better way to do it, since its using an unpredictable variable and wont work if a button is being pressed on startup/reset. The analogRead(i); you have there is just a discard check, like you suggested, the actual check

As for the digital pin thing you mentioned. If I understand that right, your saying that I should access those pins using a digital reference? Like digitalRead(54) instead of analogRead(0)? Is that because I am using an integer variable or some other reason?

Elryk:
I am using a Mega2560 (the one in your picture). I'm not sure I understand what your second question means. A1 does in fact correspond to analogRead(1), If it didn't, none of my buttons would have been working at all. I'm guessing that's not what you meant though.

That for-loop is there to see what pins are connected to ground (through the 1M resistor), if its not, the pins will see a floating value of around 2v (my observations). the next few lines of code after that do the checking. That part works (it correctly finds all the pins its supposed to), but id like to find a better way to do it, since its using an unpredictable variable and wont work if a button is being pressed on startup/reset. The analogRead(i); you have there is just a discard check, like you suggested, the actual check

As for the digital pin thing you mentioned. If I understand that right, your saying that I should access those pins using a digital reference? Like digitalRead(54) instead of analogRead(0)? Is that because I am using an integer variable or some other reason?

Try this sketch, Tell me what you see?

void setup(){
Serial.begin(9600);
#ifdef __AVR_ATmega2560__
Serial.println(" Arduino MEGA2560 Analog pins A0..A15");
for(uint8_t i=A0; i<=A15;i++){
#elif defined(__AVR_ATmega328P__)
Serial.println(" Arduino UNO Analog pins A0..A5");
for(uint8_t i=A0; i<=A5;i++){
#else
Serial.println(" Unknown Arduino, analog pins A0..A5");
for(uint8_t i=A0; i<=A5;i++){
#endif
  Serial.println(i,DEC);
  }
}
void loop(){}

inside analogRead() it is expected A0 .. A15 for Mega2560, A0..A5 for UNO

chuck.

While analogRead(1) does read pin A1, it's safer and more consistent to always use the names of the pins that are labelled on the board. Some of the Arduinos have pins like SDA, MISO or TXLED labelled on the board which are useable with those names. You don't need to memorise the numbers which sit behind those definitions and the numbers of those named pins do change when you switch to a different Arduino board.

Pin labeling etiquette aside, I found my problem. It was a simple code problem where a variable got changed at a bad time. Since that fix, I have been able take my project a step further and turn it into a library. As far as I can tell, it is working as expected, but there are still a few things that need polishing (they are noted in the Readme). The example removes the LCD code I was using and outputs the results to serial for simplicity.

Can some of you guys give it a shot and toss me some feedback for potential issues or optimizations?

Now back to the pin thing.

I am currently accessing analog pin 0 with (the number of the analog input):
analogRead(0)

Chuck seems to be saying to use either the pin number or label:
analogRead(54) or analogRead(D54)
Not sure which

MorganS is saying its better to use the board component label (printed pin label):
analogRead(A1)
I think..

These are all for the mega2560, I havn't actually tried any of these except the one I am using, but if I am understanding correctly, chucks method is quicker (more direct), morgans method is more convenient/compatible, and mine is just the simple and dirty way...right? Which would be the commonly accepted "proper" way?

Thanks,
Elryk

Keypad.zip (28.2 KB)

Have a look at the code executed by all variants, it does not really matter IMHO.

Some work on more platforms.

int analogRead(uint8_t pin)
{
	uint8_t low, high;

#if defined(analogPinToChannel)
#if defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#endif
	pin = analogPinToChannel(pin);
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

#if defined(ADCSRB) && defined(MUX5)
	// the MUX5 bit of ADCSRB selects whether we're reading from channels
	// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  
	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
	// to 0 (the default).
#if defined(ADMUX)
	ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif

	// without a delay, we seem to read from the wrong channel
	//delay(1);

#if defined(ADCSRA) && defined(ADCL)
	// start the conversion
	sbi(ADCSRA, ADSC);

	// ADSC is cleared when the conversion finishes
	while (bit_is_set(ADCSRA, ADSC));

	// we have to read ADCL first; doing so locks both ADCL
	// and ADCH until ADCH is read.  reading ADCL second would
	// cause the results of each conversion to be discarded,
	// as ADCL and ADCH would be locked when it completed.
	low  = ADCL;
	high = ADCH;
#else
	// we dont have an ADC, return 0
	low  = 0;
	high = 0;
#endif

	// combine the two bytes
	return (high << 8) | low;
}

The "proper" way is usually the more portable way. I'm working on code now that I spent a year writing for the Arduino Due. Since some other things have happened in that year, I now have to make it work on a Teensy. Now I'm extremely glad that I didn't try to "optimise" it with direct port writes or something else non-portable.

The Due is special because the SPI pins SDA and SCL aren't numbered in any of the documentation. You can find numbers for them if you dig into those files like Whandall showed us but why go to that effort when they have names?

All of the approaches above take exactly the same amount of time on the processor. The optimising compiler is staggeringly good at working out fast ways to do what you want. For constants like these, which are defined deep, deep down, it finds the right binary value that the actual processor needs and that is what it loads onto the board. Those if() statements don't make it into the final binary because the optimiser is able to calculate them before compiling.

The methode used to make pin-numbers work, is more a quick hack,
no rangecheck, just a subtract if the value is possibly valid.

What makes me really feel bad, is to look at these lines:

  // without a delay, we seem to read from the wrong channel
  //delay(1);

So the first read from a different channel seems to read from the wrong channel?

Alternating between two channels will magically exchange the values read?

Always?

If not it could be used to create random numbers...