6

I will be controlling a robot with more than 10 motors which means I'll need 2 buttons each to control moving forward and backward. My controller is an Arduino mega. Is 1 pin = 1 button the best solution here? Or should I use another IC for this?

1
  • 2
    "best solution" depends on your preferences. As the Arduino Mega has enough I/O pins, you might use them without additional hardware. If you want not to rely on the board used (e.g., switch to other/faster MCU in the future), you might use I/O multiplexers like MCP23017, which gives you 16 additional I/Os via I²C, and you can use up to 8 of them on one I²C line. We actually use four of them for 61 touch switches. There are also breakout boards with these chips.
    – ridgy
    Aug 17 at 11:43
14

For 20 buttons you can use a Keypad Matrix arrangement (plenty of examples online) where you would use only 9 pins (5 columns of 4 rows is 5+4 pins). It's slightly more involved that simple 1:1 buttons to pins, but thankfully there is a Keypad.h library that takes the hard work out of it for you.

If you want to be able to press multiple buttons at once you need to include diodes in your matrix to prevent "ghosting".

Nick Gammon has a great writeup on it here.

4
  • Upvoted. Nice alternative ... can I assume the benefits of yours are less ICs, simultaneous input, and the benefit of mine is less input pins? Aug 17 at 11:05
  • 1
    Yup. 3/4 as opposed to 9.
    – Majenko
    Aug 17 at 11:06
  • Yes this is actually one of my solutions in mind. Maybe apply multiple matrix no?
    – Julius
    Aug 17 at 11:22
  • 1
    The more buttons you have in your matrix the more efficient it gets on pins.
    – Majenko
    Aug 17 at 11:46
4

The example project below can control 16 switches. If you have a membrane keypad with 5 x 4 or 5 x 5 then you can meet your requirement.

enter image description here

I will shortly add an example of using shift registers to have multiple inputs (the main use case of shift registers is the same)

Link


Below is the example using shift registers. You need three shift registers as @Michael mentioned in his answer. Here is one example where you can control 32 switches maximum. You are only using 3 data pins and two power pins. This is the main application of the shift register. You don't have to do any selection, whenever you read the serial input data, you will automatically get the status of all the 32 switches in one Go.

enter image description here

Here is the code

// File : Cascade165.ino
//
// Version 1, 5 August 2021, by Koepel
//     Initial version.
// Version 2, 5 August 2021, by Koepel
//     Layout of the wiring made better.
// Version 3, 13 August 2021, by Koepel
//     Changed 'SCK' to 'clockPin'.
//
// Cascade of four 74HC165 shift-in registers.
// Only three pins are used on the Arduino board, to read 32 switches.
//
// Using the 74HC165 is safe, because a pulse to the Latch pin 
// ('PL' on the 74HC165) will make a new start every time. 
// In case of an error or a wrong clock pulse by noise, 
// it synchronizes the data when inputs are read the next time.
//
// Based on:
//   (1)
//     Demo sketch to read from a 74HC165 input shift register
//     by Nick Gammon, https://www.gammon.com.au/forum/?id=11979
//   (2)
//     74HC165 Shift register input example
//     by Uri Shaked, https://wokwi.com/arduino/projects/306031380875182657
//
//

const byte latchPin = 9;        // to latch the inputs into the registers
const byte clockPin = 13;       // I choose the SCK pin
const byte dataPin = 12;        // I choose the MISO pin
uint32_t oldOptionSwitch = 0;   // previous state of all the inputs

const int pulseWidth = 10;      // pulse width in microseconds

void setup ()
{
  Serial.begin( 115200);
  Serial.println( "Turn on and off the switches");
  Serial.println( "Top row is switch 0 (right) to switch 7 (left)");
  Serial.println( "Second row is 8 to 15, and so on");

  pinMode( clockPin, OUTPUT);   // clock signal, idle LOW
  pinMode( latchPin, OUTPUT);   // latch (copy input into registers), idle HIGH
  digitalWrite( latchPin, HIGH);
}

void loop ()
{
  // Give a pulse to the parallel load latch of all 74HC165
  digitalWrite( latchPin, LOW);    
  delayMicroseconds( pulseWidth);
  digitalWrite( latchPin, HIGH);

  // Reading one 74HC165 at a time and combining them into a 32 bit variable
  // The last 74HC165 is at the bottom, but the switches start numbering
  // at the top. So the first byte has to be shifted into the highest place.
  uint32_t optionSwitch = 0;
  for( int i=24; i>=0; i-=8)
  {
    optionSwitch |= ((uint32_t) ReadOne165()) << i;
  }

  for( int i = 0; i<32; i++)
  {
    if( bitRead( optionSwitch, i) != bitRead( oldOptionSwitch,i))
    {
      Serial.print( "Switch ");
      if( i < 10)
        Serial.print( " ");
      Serial.print( i);
      Serial.print( " is now ");
      Serial.println( bitRead( optionSwitch, i) == 0 ? "down ↓" : "up   ↑");
    }
  }
  
  oldOptionSwitch = optionSwitch;
  delay( 25);      // slow down the sketch to avoid switch bounce
}

// The ReadOne165() function reads only 8 bits,
// because of the similar functions shiftIn() and SPI.transfer() 
// which both use 8 bits.
//
// The shiftIn() can not be used here, because the clock is set idle low
// and the shiftIn() makes the clock high to read a bit.
// The 74HC165 require to read the bit first and then give a clock pulse.
//
byte ReadOne165()
{
  byte ret = 0x00;

  // The first one that is read is the highest bit (input D7 of the 74HC165).
  for( int i=7; i>=0; i--)
  {
    if( digitalRead( dataPin) == HIGH)
      bitSet( ret, i);

    digitalWrite( clockPin, HIGH);
    delayMicroseconds( pulseWidth);
    digitalWrite( clockPin, LOW);
  }

  return( ret);
}

Link to the shift register project: https://wokwi.com/arduino/projects/306024460940476993

2

you can use a so called shift register. One very common one is a 74HC165 shift register (https://playground.arduino.cc/Code/ShiftRegSN74HC165N/).

With one of such IC you can connect 8 buttons, however you can daisy chain max 4 to get 4 * 8 = 32 button inputs, only using 4 digital pins of the Arduino. In your case you would need 3 ICs to get 24 (thus 20) button inputs.

5
  • 2
    A shift register is not a multiplexer. A shift register shifts out all inputs, and a multiplexer selects one-of-N inputs to pass through.
    – Majenko
    Aug 17 at 11:00
  • @Majenko yes you are right, I fixed it (thanks) Aug 17 at 11:03
  • Thats the problem i have with multiplexer. I need it able to function independently without needing to be "selected"
    – Julius
    Aug 17 at 11:21
  • With this IC you don't need to 'select'', or at least you get all inputs. Aug 17 at 13:44
  • @Julius With a multiplexer you program the Arduino so that it selects each button and then checks whether it is pressed, in turn. Because the Arduino is much faster than the human finger, it still sees all the button presses.
    – user253751
    Aug 17 at 19:09
0

Max buttons for min pins (via electrical engineering and busy wait in interupt)

Hook up each button to an interrupt and an analog pin. You can use the same interrupt and analog pins for all buttons, if you use resistors to discern between the buttons.

Use the interrupt pin to get notified, when a button is pushed. Use the resistors to have a different target voltage for each button in a voltage divider. Then in the interrupt handler use the analog pin to measure the voltage.
The main problem is that the voltage will take some time to stabilize after the interrupt already fired. I'd check for at least 3 consecutive identical readings (~30ms delay after the voltage stabilized).

Make sure you have some tolerance, e.g. by shifting at least the two least significant bits out (value = AnalogRead(PIN) >> TOLERANCE_BIT_COUNT;). With the analog pins' 10 bit resolution (1024 values, AFAIR), 2 bits lost for tolerance and an additional bit lost for difference between HI and LO (needed to trigger the interrupt), there are at least 7 bits left, which could suffice for up to 127 buttons.

Yes, you can hook up a keyboard with 2 pins, if you implement part of the logic in hardware (127 carefully designed voltage dividers) and part in software (detecting stabilized voltage). However the identification which button is pressed can be susceptible to deviations in voltage as small as 4.8 mV (a difference of 1 in the value of AnalogRead, which might carry over the tolerance). Also you can not detect multiple buttons being pushed at the same time. However if the electrical design has been carefully engineered, no failure detection may be needed for multiple buttons being pushed, because the interrupt would not fire, depending on how the target voltages stack up.

Electrical engineering problems to be solved:

  • Designing the voltage dividers is left to the reader, because you can crank up the tolerance, if you need less buttons. This may mitigate the susceptibility to small deviations in voltage. My proposal would be to get X identical resistors of R Ohm for X buttons. Connect the resistors in series and fork the voltage between the resistors via the buttons. Connect all forks after their respective button to the analog pin and another resistor (of X times R Ohm) to either ground or Vcc (depending on the choice of interrupt HI vs. LO). Add a pull-up/down resistor to the end of the resistors in series as necessary.
  • You might need to make sure not to lose too many bits too early, because otherwise there might be consecutive "identical" values without the voltage having stabilized.
  • Check, if the interrupt should fire for HI->LO or LO->HI, as one might offer more distinct target voltages than the other. I just assumed that the first AnalogRead will return a value of about 512 either way.
  • I remember that you can remap the values of AnalogRead to regain the 1 bit lost to HI-LO vs. LO-HI transition. This is not recommended as it might interfere with the behaviour of other analog pins. However in a pinch this may either double the number of buttons possible or add another bit for tolerance.
  • A small capacitor might help to filter bumping out of the buttons' switching action, so you don't get multiple interrupts for a single button push. However this may increase the time needed for the voltage divider to stabilize at the target voltage. So choose the smallest capacitor and resistors, you can get away with.
0

A different take

Use an app to make on-screen virtual controls instead of using physical buttons, and send press events over bluetooth or wifi or ESPNOW.

Advantages

  • no parts needed if you have a phone/laptop already
  • no soldering
  • no flaky parts wearing out
  • complete control over button layout
  • easy to upgrade/modify control layout based on field experience
  • one less part to carry
  • trivial to have extra backup remotes
  • opens the door to macros (eg. auto-land), test routines, etc
  • lets you log data from the robot for debugging
  • EASILY add cool control hardware like gamepads, voice, or camera
  • much easier to code buttons in, eg. HTML+JS than C++
  • separates programming concerns and allows detached evolution
  • allows possibility of internet/remote control
  • can be developed on laptop, moved to portable raspPi or phone
  • can control more than one (or a new) robot from the remote control

Disdvantages

  • needs code in two languages
  • extra part (eg. ESP8266), though buttons are expensive too
  • slightly increased latency (but buttons should be debounced anyway)
  • might not be as satisfying as a 80+ hour hand-built controller
  • requires a bit of security consideration (maybe)

All in all, I think the advantages FAR outweigh the drawbacks. If you need soldering practice and human-held hardware design experience, a physical remote is a decent project, but you can always do that later once you get the robot working with abstract commands; you don't have to build and debug both at the same time, which is huge to me...

If you NEED a physical remote

You can still get many of the above benefits in a physical remote if you put an MCU/SBC in the remote. Then talk to the robot with uart/wifi/bt/etc. Uart lets you run just 4 wires to the remote instead of dozens. There's lots of existing 4+wire connectors (USB-B, RJ-45, XLR, DIN, etc) that you can use for this; thin, light, cheap, replaceable. Such a config allows serial debugging, logging, etc. It frees the robot CPU from having to deal with interrupts and library footprints.

All in all, it will make making faster for the small cost of a cheap MCU like a nano/bluepill/esp32...

Not the answer you're looking for? Browse other questions tagged or ask your own question.