How to Read a Switch


So far we've covered how to blink an LED on and off and how to flicker an LED using an analogue output value, it's time to start reading and reacting to inputs to the controller. This means adding some additional hardware to read from, but today that can be a simple as a single wire on a prototyping breadboard. One end of the wire is connected to the D1 input and the other end is swapped between the 3V3 pin and the GND pin to create High/Low input signals. The initial code to read this is impressively simple, we're also going to leave some LED code in the demo so that we can easily see what value is being read by the controller.

// Define the input pins
const uint8_t btnPin = D1;
// Define the output pins
const uint8_t ledPin = LED_BUILTIN;
uint8_t btnValue;
void setup() {
pinMode(btnPin, INPUT); // Initialise the btn pin as an input
pinMode(ledPin, OUTPUT); // Initialise the LED pin as an output
}
void loop() {
btnValue = digitalRead(btnPin);
digitalWrite(ledPin, btnValue);
}

When you load this example you should see that the LED turns on when the wire is connected to GND (remember the LED_BUILTIN is active low) and the LED turns off when the wire is connected to 3V3. This system works but has a downside, the input pin only changes state when the wire is connected to the new pin. When the wire is disconnected and left 'floating' you're unable to tell if the wire has actually been disconnected or not. 

A floating voltage like this can be removed/detected by using a pull up resistor. When the wire is disconnected from GND, the pull up resistor brings the voltage immediately up to 3V3 which gives a much more definitive response to the wire being disconnected/button is released. Thankfully most arduino devices have these resistors built into the controller and they can be configured by using 'INPUT_PULLUP' in the pinMode function. The other bonus is that you only need to run two wires to your external switch, the data line and GND.

By changing the pin mode to use the input pullup, in the above example, we can see that the LED turns on when the wire is connected and turns off again as soon as the wire is disconnected. Playing with this arrangement a bit should illustrate our next issue. The wire may not always connect and disconnect smoothly as metal scrapes against metal, this may cause the LED to appear to flicker slightly as the wire is inserted. There may be some 'electrical noise' on the input pin and if we attempt to read the pin during this noise the input may appear to be off when it should be on.

The common solution to this problem of electrical noise is to 'Debounce' the input pin. The process is to read the pin once, wait a small period of time and then read the pin again to make sure that the result is the same. If the results match, then do something with the new value. It's a good job our previous LED examples have been using code that loops every 25 milliseconds, it's almost as if that was deliberate.

EVERY_N_MILLISECONDS(INTERVAL) {
btnOldValue = btnCurValue;
btnCurValue = digitalRead(btnPin);
if (btnOldValue == btnCurValue) {
digitalWrite(ledPin, btnCurValue);
}
}
One big reason I chose to create all the previous examples with a 25ms loop is due to the human perception of 'response time'. In a perfect world there would be zero response time between pushing a button and our device reacting to it, but our microcontroller does not contain a super fast gigahertz processor, input debouncing must take some time and at some point in the future we're going to expect it to process some puzzles while it's doing everything else. I chose 25ms because that's about as slow as a response time can be, if the system takes longer than that to process the input it feels unresponsive. As long as we're getting everything done in the time then 25ms feels acceptable.

The way I chose to handle this debounce is to actually monitor the difference between the current state of the pin and the previous state of the pin. This has the added bonus of telling me when the button is pressed and when the button is released. If the button is pressed, I also start to increment a counter that tells me how long the button has been pressed for, when the button is released I reset the counter. This system allows me to track several different kinds of inputs from a single button. For example you could increment a value with a single button push or auto increment the value with a button hold.

void loop() {
EVERY_N_MILLISECONDS(INTERVAL) {
btnOldValue = btnCurValue;
btnCurValue = digitalRead(btnPin);
if (btnUp()) {
// The button has just been released. Reset the counter
btnCount = 0;
} else if (!btnCurValue) {
// Add to the count for each loop the button is held down
// Prevent the counter from overflowing by limiting to 250
if (btnCount < 250) {
btnCount++;
}
}
}
}
bool btnUp() {
return !btnOldValue & btnCurValue;
}
bool btnDown() {
return btnOldValue & !btnCurValue;
}
I created two little helper functions at this point to keep track of the button down and button up events. They just check to see the state of the value now and previous and return true if the button was pushed/released in the last loop.

That just about covers a simple system for reading a digital input into the device and interpreting the input values. Next time I'm going to expand all of the previous examples to use multiple inputs and outputs and smooshall the existing code together to have the LED react to the inputs. The full code for this and all the other tutorials in this series can be found in my github repository.
https://github.com/msraynsford/Puzzling/

Popular posts from this blog

Wiring the Ruida Controller

Laser Cut Cryptex

Leetro to Ruida Settings