Switch and LED on the same pin, suggestions FYI

There have been requests asking about increasing available i/o pins for projects.
Wrote up a sketch using a pin for both LED o/p and switch i/p.

Suggestions to improve this example welcomed.

Attached is a schematic.

/*
  Version YY/MM/DD
  1.00    16/01/01   Running code

*/

//**********************************************************************
//G l o b a l   v a r i a b l e s
//**********************************************************************
//inputs
const byte heartLED  = 13;
const byte secondLED = 12;
const byte SwitchLED = 2; //switch (and LED) is on Arduino pin D02, pushed = LOW

//outputs

//SRAM variables
byte state = 1;
byte lastSwitchState = 1;

//EEPROM locations

//======================================================================
struct timer
{
  //lastMillis = the time this "timer" was (re)started
  //waitMillis = delay time (ms) we are looking for
  //restart    = do we start "this timer" again and again
  //enableFlag = is "this timer" enabled/allowed to be accessed
  //timeType   = true = millis(), false = micros()
  //**********************
  //For each timer object you need:
  //Example:
  //   timer myTimer = //give the timer a name "myTimer"
  //   {
  //     0, 200UL, true, true, true  //lastMillis, waitMillis, restart, enableFlag, timeType
  //   };
  // You have access to:
  // myTimer.lastMillis, myTimer.waitMillis, myTimer.restart, myTimer.enableFlag, myTimer.timeType, myTimer.CheckTime()
  //**********************

  unsigned long lastMillis;
  unsigned long waitMillis;
  bool          restart;
  bool          enableFlag;
  bool          timeType;

  unsigned long currentTime;

  bool CheckTime() //Delay time expired function "CheckTime()"
  {
    if (timeType == true)
    {
      currentTime = millis();
    }
    else
    {
      currentTime = micros();
    }

    //is the time up for this task?
    if (enableFlag == true && currentTime - lastMillis >= waitMillis)
      {
      //should this start again?
      if (restart)
      {
        //get ready for the next iteration
        lastMillis = currentTime;
      }
      //time was reached
      return true;
    }
    //time was not reached
    return false;

  } //END of CheckTime()

}; //END of structure timer
//======================================================================

//**********************************************************************
//                Create timer objects and initialize
//**********************************************************************
timer heartBeatLED =          //create timer pin13
{
  0, 250UL, true, true, true //lastMillis, waitMillis, restart, enableFlag, true=millis/false=micros
};

timer pin2LED =
{
  0, 1000UL, true, true, true  //lastMillis, waitMillis, restart, enableFlag, true=millis/false=micros
};

timer checkSwitches =
{
  0, 50UL, true, true, true  //lastMillis, waitMillis, restart, enableFlag, true=millis/false=micros
};



//                         s e t u p ( )
//**********************************************************************
void setup()
{
  //**************************************
  digitalWrite(heartLED, HIGH);  //LED  LOW = ON
  pinMode(heartLED, OUTPUT);

  digitalWrite(secondLED, HIGH); //LED  LOW = ON
  pinMode(secondLED, OUTPUT);

  pinMode(SwitchLED, OUTPUT);    //normal state of this pin
  digitalWrite(SwitchLED, HIGH); //LED  LOW = ON

} //  >>>>>>>>>>>>>>>>>> E N D  O F  s e t u p ( ) <<<<<<<<<<<<<<<<<<<<<


//                             l o o p ( )
//**********************************************************************
void loop()
{
  //***************************
  //HeartBeat LED
  if (heartBeatLED.CheckTime())
  {
    //Toggle heartLED
    digitalWrite(heartLED, !digitalRead(heartLED));
  }

  //***************************
  if (pin2LED.CheckTime())
  {
    //Toggle the LED on the SwitchLED pin
    digitalWrite(SwitchLED, !state);
    state = !state;
  }


  //***************************

  //None blocking code goes here

  //***************************
  //time to check the Switch(s)?
  if (checkSwitches.CheckTime())
  {
    handleSwitchPresses();
  }

} //  >>>>>>>>>>>>>>>>>>> E N D  O F  l o o p ( ) <<<<<<<<<<<<<<<<<<<<<<


//======================================================================
//                         F U N C T I O N S
//======================================================================

//                h a n d l e S w i t c h P r e s s e s( )
//**********************************************************************

void handleSwitchPresses()
{
  //prepare to read the switch connected to this pin
  pinMode(SwitchLED, INPUT);

  //has the SwitchLED changed state?
  if (digitalRead(SwitchLED) != lastSwitchState)
  {
    lastSwitchState = !lastSwitchState;

    if (lastSwitchState == LOW) //do LOW stuff
    {
      //toggle secondLED
      digitalWrite(secondLED, !digitalRead(secondLED));
    }

    else                       //do HIGH stuff
    {
    }

  } //END of SwitchLED changed state

  //return pin to normal, i.e. OUTPUT
  pinMode(SwitchLED, OUTPUT);
  //update the pin's state
  digitalWrite(SwitchLED, state);

  //Other Switches

} //      E n d   o f   h a n d l e S w i t c h P r e s s e s ( )

//**********************************************************************


//======================================================================
//                        E N D  O F  C O D E
//======================================================================

2016-12-04_18-48-21.png

Vely crever. :) K++

Hi, Yes, nice, I like it...

Tom... :)

LarryD: Suggestions to improve this example welcomed.

Hi Larry. Unfortunately your circuit misses the worst case logic low level voltage by quite a long way. TBH if it correctly reads LOW when the switch is pressed then it's as much good luck as anything else.

The datasheet worst case V_in_low is 0.3Vcc, so 1.5 volts in this case. This is the highest input voltage that is guaranteed to read as logic low.

In your circuit, the worst case (lowest) voltage across the LED is about 1.6volts (could be higher depending on the exact LED type and colour, but we need to be conservative in design), so that leaves about 3.4 volts (worst case) at the LED cathode. After the voltage divider this gives about 3.4/1.22 = 2.8 volts (worst case).

So you see that you potentially miss the design target for a valid logic low by up to about 1.3 volts. To improve your design you need to either significantly reduce the value of the pull down resistor or increase the value of the upper LED series resistor (currently 220)

EDIT: The worst case (lowest) voltage assumption for the LED of 1.6V is probably a little low there. 1.8 volts would be realistic. But anyway, that just gives you a bit of noise margin.

Let me give some suggestions for the resistor values.

Firstly you have to keep in mind that there are two conflicting requirements here. If you lower the R42 too much then it will work fine as an input, but the pin might have trouble driving it high as an output (and hence the LED might not go completely off). So you really want to limit the R42 current to about 20mA at about 4 volts, so this gives about 200 ohms minimum for R42.

Unfortunately even at R42 = 200 ohms you still miss (but only very slightly) the worst case logic low design target, so you may need to slightly increase R41 as well. Of course there is another compromise there, as increasing R41 decreases your LED output brightness. If you take it up just a little to 270 ohm however, then you can meet the V_IL specs without effecting the LED brightness very much.

So in summary. I'd use R41=270 ohms and R42=200. (or R41:R42=300:220 or even 330:240 as possible alternatives)

Thank you for the comments.

@stuart0 suggestions put into practice gives the following results.
With Red LED Vf=1.7v, R2=220 and VCC=5V. See schematic.

Note: very dim light comes from the LED during switch reading.

2016-12-05_10-37-56.png

Edit:
Logic analyzer view
2016-12-05_11-58-42.png

Minor changes:

/*
  Version YY/MM/DD
  1.00    16/01/01   Running code

*/

//**********************************************************************
//G l o b a l   v a r i a b l e s
//**********************************************************************
//inputs/outputs
const byte heartLED  = 13;
const byte secondLED = 12;
const byte SwitchLED = 2; //switch (and LED) is on Arduino pin D02, pushed = LOW

//SRAM variables
byte state = 1;
byte lastSwitchState = 1;

//EEPROM locations

//======================================================================
struct timer
{
  //lastMillis = the time this "timer" was (re)started
  //waitMillis = delay time (mS) we are looking for
  //restart    = do we start "this timer" again and again
  //enableFlag = is "this timer" enabled/allowed to be accessed
  //timeType   = true = millis(), false = micros()
  //**********************
  //For each timer object you need:
  //Example:
  //   timer myTimer = //give the timer a name "myTimer"
  //   {
  //     0, 200UL, true, true, true  //lastMillis, waitMillis, restart, enableFlag, timeType
  //   };
  // You have access to:
  // myTimer.lastMillis, myTimer.waitMillis, myTimer.restart, myTimer.enableFlag, myTimer.timeType, myTimer.CheckTime()
  //**********************

  unsigned long lastMillis;
  unsigned long waitMillis;
  bool          restart;
  bool          enableFlag;
  bool          timeType;

  unsigned long currentTime;

  bool CheckTime() //Delay time expired function "CheckTime()"
  {
    if (timeType == true)
    {
      currentTime = millis();
    }
    else
    {
      currentTime = micros();
    }

    //is the time up for this task?
    if (enableFlag == true && currentTime - lastMillis >= waitMillis)
      {
      //should this start again?
      if (restart)
      {
        //get ready for the next iteration
        lastMillis = currentTime;
      }
      //time was reached
      return true;
    }
    //time was not reached
    return false;

  } //END of CheckTime()

}; //END of structure timer
//======================================================================

//**********************************************************************
//                Create timer objects and initialize
//**********************************************************************
timer heartBeatLED =          //create timer pin13
{
  0, 250UL, true, true, true	//lastMillis, waitMillis, restart, enableFlag, true=millis/false=micros
};

timer pin2LED =
{
  0, 100UL, true, true, true  //lastMillis, waitMillis, restart, enableFlag, true=millis/false=micros
};

timer checkSwitches =
{
  0, 50UL, true, true, true  //lastMillis, waitMillis, restart, enableFlag, true=millis/false=micros
};



//                         s e t u p ( )
//**********************************************************************
void setup()
{
  //**************************************
  digitalWrite(heartLED, HIGH);  //LED  LOW = ON
  pinMode(heartLED, OUTPUT);

  digitalWrite(secondLED, HIGH); //LED  LOW = ON
  pinMode(secondLED, OUTPUT);

  pinMode(SwitchLED, OUTPUT);    //normal state of this pin
  digitalWrite(SwitchLED, HIGH); //LED  LOW = ON

} //  >>>>>>>>>>>>>>>>>> E N D  O F  s e t u p ( ) <<<<<<<<<<<<<<<<<<<<<


//                             l o o p ( )
//**********************************************************************
void loop()
{
  //***************************
  //HeartBeat LED
  if (heartBeatLED.CheckTime())
  {
    //Toggle heartLED
    digitalWrite(heartLED, !digitalRead(heartLED));
  }

  //***************************
  if (pin2LED.CheckTime())
  {
    //Toggle the LED on the SwitchLED pin
    digitalWrite(SwitchLED, !state);
    state = !state;
  }


  //***************************

  //None blocking code goes here

  //***************************
  //time to check the Switch(s)?
  if (checkSwitches.CheckTime())
  {
    handleSwitchPresses();
  }

} //  >>>>>>>>>>>>>>>>>>> E N D  O F  l o o p ( ) <<<<<<<<<<<<<<<<<<<<<<


//======================================================================
//                         F U N C T I O N S
//======================================================================

//                h a n d l e S w i t c h P r e s s e s( )
//**********************************************************************

void handleSwitchPresses()
{
  //store the condition of the output pin
  byte PinState = digitalRead(SwitchLED);
  
  //read the switch connected to this pin
  pinMode(SwitchLED, INPUT_PULLUP);
  byte switchPosition = digitalRead(SwitchLED);

  //return the pin to OUTPUT
  pinMode(SwitchLED, OUTPUT);
  //restore the pin state
  digitalWrite(SwitchLED, PinState);

  //has the SwitchLED changed state?
  if ( switchPosition != lastSwitchState)
  {
    lastSwitchState = !lastSwitchState;

    if (lastSwitchState == LOW) //do LOW stuff
    {
      //toggle secondLED
      digitalWrite(secondLED, !digitalRead(secondLED));
    }

    else                       //do HIGH stuff
    {
    }

  } //END of SwitchLED changed state


  //Other Switches

} //      E n d   o f   h a n d l e S w i t c h P r e s s e s ( )

//**********************************************************************


//======================================================================
//                        E N D  O F  C O D E
//======================================================================

.

Scope vs logic analyzer:
Scope.png

Simplified Sketch:

/*
  Demonstration how to use a pin as a output for a LED 
  and as an input for a switch.

  Note:
  The LED 'must' be connected to +5V through a 470 ohm (or larger) resistor.
  The switch is connected to GND through a 220 ohm resistor.
  
  Version YY/MM/DD
  1.00    16/12/11   Running code
  
*/

//**********************************************************************
//G l o b a l   v a r i a b l e s
//**********************************************************************

const byte heartLED  = 13; //Heart beat LED, toggles on and off showing if there is blocking code
const byte Pin12LED  = 12; //Push ON push OFF, controlled by a switch on pin 6
const byte SwitchLED =  6; //Switch and LED connected this pin, switch pushed = LOW
                           //LED is toggled showing the dual action of this pin

//SRAM variables
byte state = 1;
byte lastSwitchState = 1;

unsigned long Pin13Millis;
unsigned long Pin6Millis;
unsigned long SwitchMillis;


//                         s e t u p ( )
//**********************************************************************
void setup()
{
  //**************************************
  digitalWrite(heartLED, HIGH);  //LOW = ON
  pinMode(heartLED, OUTPUT);

  digitalWrite(Pin12LED, HIGH);  //LOW = ON
  pinMode(Pin12LED, OUTPUT);

  digitalWrite(SwitchLED, HIGH); //LOW = ON
  pinMode(SwitchLED, OUTPUT);    //normal state of this pin

} //  >>>>>>>>>>>>>>>>>> E N D  O F  s e t u p ( ) <<<<<<<<<<<<<<<<<<<<<


//                             l o o p ( )
//**********************************************************************
void loop()
{
  //***************************
  //HeartBeat LED
  if (millis() - Pin13Millis >= 100UL)
  {
    Pin13Millis = millis();
    //Toggle heartLED
    digitalWrite(heartLED, !digitalRead(heartLED));
  }

  //***************************
  if (millis() - Pin6Millis >= 500ul)
  {
    Pin6Millis = millis();
    //Toggle the LED on the SwitchLED pin
    digitalWrite(SwitchLED, !state);
    state = !state;
  }


  //***************************

  //None blocking code goes here

  //***************************
  //time to check the Switch(s)?
  if (millis() - SwitchMillis >= 50ul)
  {
    SwitchMillis = millis();
    handleSwitchPresses();
  }

} //  >>>>>>>>>>>>>>>>>>> E N D  O F  l o o p ( ) <<<<<<<<<<<<<<<<<<<<<<


//======================================================================
//                         F U N C T I O N S
//======================================================================

//                h a n d l e S w i t c h P r e s s e s( )
//**********************************************************************

void handleSwitchPresses()
{
  //**** R e a d i n g   t h e  s w i t c h   s h a r e d   w i t h   L E D ****
  //Reading the switch takes ~14us
  //save the condition of the output pin
  byte PinState = digitalRead(SwitchLED);
  //read the switch connected to this pin
  pinMode(SwitchLED, INPUT_PULLUP);
  byte switchPosition = digitalRead(SwitchLED);

  //return the pin to OUTPUT
  pinMode(SwitchLED, OUTPUT);
  //restore the pin state
  digitalWrite(SwitchLED, PinState);
  //**** R e a d i n g  t h e  s w i t c h   s h a r e d   w i t h   L E D ****

  //has the SwitchLED changed state?
  if (switchPosition != lastSwitchState)
  {
    lastSwitchState = switchPosition;

    if (switchPosition == LOW) //do LOW stuff
    {
      //toggle Pin12LED
      digitalWrite(Pin12LED, !digitalRead(Pin12LED));
    }

    else                       //do HIGH stuff
    {
    }

  } //END of SwitchLED changed state


  //Other Switches

} //      E n d   o f   h a n d l e S w i t c h P r e s s e s ( )

//**********************************************************************


//======================================================================
//                        E N D  O F  C O D E
//======================================================================

See this post for more information:

Very cool Larry! I am encouraged because I have been working on something similar: putting a switch on the reset pin of a tiny85. I'm voltage dividing it similarly and analogRead()ing the pin, comparing the input value to something that is higher than the voltage that would cause a reset.

I like the cleanliness of your circuit, which has pushed mine 'back to the drawing board'. Thanks for posting your work, I'm paying close attention!

LarryD: Thank you for the comments.

@stuart0 suggestions put into practice gives the following results. With Red LED Vf=1.7v, R2=220 and VCC=5V. See schematic.

Note: very dim light comes from the LED during switch reading.

Hi Larry. I meant to reply to this earlier but then forgot about it.

Yeah, the dim illumination of off state LEDs when reading the switch is inevitable (with the present circuit). The output pin can hold the LED off when it's in the high state, but when you temporarily make it an input to read the switch then LED current will flow.

There is however a simple workaround for this. Just add a small capacitor (10 to 100 nF should do) in parallel with each LED. As long as the read time is sufficiently short then this will keep the LED fully off.

BTW, you're doing the right thing in that code to keep the duty cycle of the read events as low as possible. That is, to keep it in the input state for as few instruction cycles as possible and not to read it unnecessarily often. This will minimize the unwanted illumination and make it easy for the added capacitor to eliminate it completely.

One thing you might consider adding is a cli() just before and sti() just after the read event. This is just to make sure it doesn't get stuck in the input state while servicing an interrupt. Disabling interrupts while reading the switch will only block interrupts for about 3 or 4 clock cycles, so there should be absolutely no harm in doing so.