In the previous tutorial, we discussed how to use a digital output on Raspberry Pi. We designed a GUI-controlled LED driver, demonstrating how single-board computer features — like high-level language (HLL) such as Python — can control embedded applications.
Microcontroller-based embedded applications are typically programmed in low-level languages, such as in Assembly Language or embedded C. These generally lack a sophisticated software backend.
It’s challenging to design a sophisticated embedded application using microcontrollers and low-level programming because of the limited hardware resources and overall complexity. Single-board computers, such as Raspberry Pi (RPi), offer a solution where sophisticated user interfaces and features of HLL can be used in embedded applications.
This is similar to the previous tutorial, we where used graphic programming and multi-threading for designing a GUI-controlled LED driver.
Any controller or processor used in embedded applications interface and interact with the embedded electronics in five ways:
1. Digital output
2. Digital input
3. Analog input
4. Analog output
5. Serial communication
We have already covered using digital output from Raspberry in this series. Remember, RPi’s general-purpose input/output (GPIO) are 3.3V-compatible.
In this tutorial, we will discuss how to use digital input on Raspberry Pi. To do so, we’ll interface a push button to RPi’s GPIO and detect the digital input from it. We’ll also monitor this digital input on Python’s integrated development and learning environment (IDLE) console.
Digital input & RPi
Digital or logical input is required on controllers/processors to:
- Input data from peripherals
- Interact with interfaced external devices
- Design human-computer interfaces
After all, from an electronics perspective, any computer (be it a general-purpose processor, a dedicated processor, or a controller) is a digital electronic circuit that can input data, process inputs, and output data or electronic signals — according to the input. Given the “input-process-output,” the computer is able to accomplish designated tasks or operations.
Raspberry Pi, as a single-board computer, is also capable of output and input. Its GPIO pins are 3.3V TTL voltage-tolerant, meaning they can output and input logical signals, accordingly. The input signals on RPi’s GPIO can be used for data input via the momentary type switches (as we discussed when covering how to interface push buttons using Arduino).
RPi’s GPIO
As mentioned in the previous tutorial, Raspberry Pi comes with a 40-pin header, which is what makes it an embedded computer. This is because it’s capable of interfacing with embedded electronics and communicating through digital input/output, PWM, and various serial communication protocols (including UART/USART, I2C, and SPI).
In the 40-pin header, there are 26 pins that can output digital signals and accept digital input. These digital input/output pins (GPIO) are 3.3V TTL logic signal compatible.
The pins can be configured to use built-in pull-up or pull-down resistors, except GPIO2 (board pin number 3) and GPIO3 (board pin number 5), which have fixed pull-up resistors. The GPIO pins output 3.3 V for logical HIGH and 0V for logical LOW. They read 3.3V as logical HIGH and 0V as logical LOW.
Pull-up/pull-down resistors
If the GPIO pins are not configured to use built-in pull-up or pull-down resistors (in the user-program), this must be done externally or they will remain in a floating condition. The GPIO is extremely sensitive and may be affected by slight changes. For example, it can pick up a stray capacitance from a user’s finger, the air, or the breadboard connection. Therefore, it must be permanently tied to the VCC (or 3.3V in the case of RPi) or to the ground.
If a pin has to be configured to detect a LOW signal as the digital/logical input, it must be pulled to HIGH. To do so, one end of the switch must be connected to the ground while the other end must be hard-wired to the VCC (3.3V) via a pull-up resistor. In this configuration, the pin (by default) will receive a HIGH logical signal. When the button or switch is pressed, it will receive a logical LOW.
If the pin has to be configured to detect a HIGH signal as the digital/logical input, it must be pulled to LOW. To do so, one end of the switch must be connected to the VCC (3.3V) while its other end must be hard-wired to the ground via a pull-down resistor. In this configuration, the pin (by default) receives a LOW logical signal. When the button or switch is pressed, it receives a logical HIGH.
In both of these cases, the pull-up or pull-down resistor protects the channel (GPIO pin) from any voltage surge or stray capacitance that could potentially damage the pin.
Push buttons
Push buttons are the simplest momentary type of switches. These can be two or four-terminal switches.
In four-terminal switches, the terminals on each side are short. This means two circuit branches can be switched simultaneously. A two-terminal push button, however, can only switch a single branch of the circuit.
Interfacing push buttons
Push buttons can be easily interfaced with the GPIO pins of Raspberry Pi. All of the GPIO pins can be configured to use the internal pull-up or pull-down, with the exceptions of GPIO2 (board pin number 3) and GPIO3 (board pin number 5)
As mentioned above, if this is not the case, the pins must be configured with external pull-up or pull-down resistors. A current-limiting resistor can also be connected with a switch in the series for additional safety.
If the user-program is designed to read logical HIGH, the internal or external pull-down must occur. If the user-program is designed to read logical LOW, then an external pull-up must be done. The “read logical level” can be interpreted as a data value or a control signal, according to the user-program.
Sensing the digital input
There are several ways controllers/processors sense digital input. These ways are implemented in the user-programs.
The most basic method of sensing logical input is to check the input value at a point in time. This is called “polling.” However, in this method, the controller/processor can miss reading the input if the user program reads the value at the wrong time. For polling, the state of the switch is checked by “if conditional” and checked in a loop. But note, this is a processor-intensive operation.
Another way to do so is simply to sense the input using interrupts or edge-detection. In this method, the user-program waits for the HIGH-to-LOW transition (falling edge) or the LOW-to-HIGH transition (rising edge) on the GPIO pin.
Using Python to set up the GPIO pin as input
It’s fairly simple to set up a GPIO pin as input using Python. However, to control Raspberry Pi’s GPIO pins from Python, you’ll first need to import the RPi.GPIO module as follows:
import RPi.GPIO as GPIO
The module references the IO pins in two ways: as board numbers and as BCM numbers. The board numbers are the pin numbers that are on the RPI’s P1 header. These numbers remain the same for all RPi models.
The BCM numbers refer to the channel numbers on the Broadcom SoC. RPi models may have channel numbers wired to the RPi board numbers in different ways. So, if you use BCM numbers, you’ll need to work with the schematic of your particular board. If the BCM numbers are used, your script may work on one model and break on another.
The numbering system of the pins can be set using the setmode() method of the GPIO module. These are valid examples of setting the numbering system of the IO pins in the Python script:
GPIO.setmode(GPIO.BOARD)
or
GPIO.setmode(GPIO.BCM)
To detect the numbering system of the IO pins, the getmode() method of the GPIO module can be called upon as follows:
mode = GPIO.getmode()
or
print(GPIO.getmode())
The method returns GPIO.BOARD, GPIO.BCM, or NONE (if the numbering system is not set) as a string. So it’s recommended to use the board numbering system to ensure your Python script can run on all models without breaking.
Additionally, it’s possible to run more than one script in a session. If one script is already using an IO pin and another script is run that also uses the same IO pin, a warning will prompt the user that the channel is already in use.
To ignore this warnings and continue using the IO pin from the current script, the warnings can be set to “False.” To do so, use the setwarnings() method of the GPIO module like this:
GPIO.setwarnings(False)
To configure the pin as digital input, use this code:
GPIO.setup(channel, GPIO.IN)
However, if the pin has to be configured to use the internal pull-up, it must be set up as follows:
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
If the pin has to be configured to use internal pull-down, it must be set up like this:
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
Using Python to sense digital input by polling
The state of a switch can be tested at a GPIO pin by using the “if conditional” as follows:
if (GPIO.input(channel)):
print(“HIGH”)
else:
print(“LOW”)
A while loop can also be used to test the status of a switch at RPi’s GPIO:
while(GPIO.input(channel) == GPIO.LOW): print(“LOW”)
or
while(GPIO.input(channel) == GPIO.HIGH): print(“HIGH”)
The “if conditional” executes a block of code once when the state of the switch is read and while the loop keeps executing a block of code (at least until the state of the switch is changed). If the state of the switch has to be monitored throughout the life cycle of the user-program, the user-program must iterate itself.
In the previous tutorial, we used multi-threading to iterate the user program since we were using a GUI to control the LED driver. We also devised a trick to kill the threads. In this tutorial, we will iterate the user program using the try-exception method since we are not using a GUI.
Event-driven GPIO input
A more efficient way of sensing the digital input at a GPIO pin is known as edge detection. The change of a logical signal from LOW-to-HIGH (rising edge) or HIGH-to-LOW (falling edge) is considered an event (hardware interrupt) at the channel (pin).
Python provides a function, wait_for_edge(), which blocks the execution of the code until a rising or falling edge is detected.
The function has this syntax:
GPIO.wait_for_edge(channel, GPIO.RISING)
<Code to execute after detection of rising edge>
or
GPIO.wait_for_edge(channel, GPIO.FALLING)}
<Code to execute after detection of falling edge>
or
GPIO.wait_for_edge(channel, GPIO.BOTH)
<Code to execute after detection of rising or falling edge>
The function also accepts a timeout argument so the CPU can be forced to wait for a certain period of time to detect the edge at the GPIO pin. The timeout is specified in milliseconds.
Here’s an example:
channel = GPIO.wait_for_edge(channel, GPIO_RISING, timeout=5000)
if channel is None:}
<Code to execute if no edge is detected>
else:
<Code to execute if edge is detected>
The advantage of using the wait_for_edge() function is that the CPU will not miss the detection of the logical signal at the GPIO pin. However, the function temporarily interrupts the normal flow of program and stops the user-program. To avoid this, the event-driven edge detection can be done at the GPIO.
Python allows the addition of hardware events when using the function, add_event_detect(), which has this syntax:
GPIO.add_event_detect(channel, GPIO.RISING)
<Code to execute normally in the user-program>
if GPIO.event_detected(channel):
<Code to execute after detection of edge at GPIO>
Again, the edge can be GPIO.RISING, GPIO.FALLING, or GPIO.BOTH. Adding the event listener allows the execution of a follow-up action on detection of the logical signal (edge) of RPi’s channel — and without interrupting the normal flow of user-program.
The event of the edge detection can also be bind with one or more callback functions by using the add_event_callback() function. It has this syntax:
GPIO.add_event_detect(channel, GPIO.RISING)
GPIO.add_event_callback(channel, callback_function_one)
GPIO.add_event_callback(channel, callback_function_two)
If more than one callback function binds to the edge detection, these functions are executed sequentially, not concurrently. The callback functions are executed in the order that they’re called in the user-program. This is because, only one callback function can be called in using the add_event_callback() function. But the use of the callback functions will make your code (the user-program) more modular and manageable.
At times, the callback functions are executed more than once due to a switch bounce. This can be corrected by connecting a capacitor of 0.1uF across the switch or using software debouncing. For software debouncing, bouncetime parameter can be included as an argument in the add_event_callback() function.
The bouncetime is specified in milliseconds. Here’s an example:
GPIO.add_event_detect(channel, GPIO.RISING, callback=callback_func, bouncetime=250)
Both methods — connecting the capacitor across the switch and software debouncing — can also be applied for better results.
Iterating user-code infinitely using try-exception
In the previous tutorial, we executed a user-program as an infinite loop by using multi-threading. We also used a trick to kill threads whenever needed.
Multi-threading was necessary because that application was using GUI. Without applying multi-threading, the GUI would have froze.
If your Python application does not include a GUI to manage embedded electronics and it runs as a background script, controlling electronics, there’s a solution, A microcontroller-like infinite code execution can be implemented by a simple try-exception statement.
For example:
if __name__ == ‘__main__’:
setup()
try:
loop()
except KeyboardInterrupt:
endprogram()
In this example, the setup() is a user-defined function that executes once (like the setup() function in the Arduino programming). The loop() is a function that’s executed by a try statement and keeps iterating until a keyboard interrupt is received.
The endprogram() is a function for exception handling that’s called when a keyboard interrupt is received by RPi. This function can be used for the clean-up of resources and to securely close the Python script when the user-program terminates.
Recipe: Interfacing push button with RPi
In this recipe, we will interface a push button to Raspberry Pi’s GPIO and detect the press of a button on Python IIDLE’s console.
Components required
1. Raspberry Pi 3/4 Model B x1
2. Push button x1
3. 10K Ohms resistor x1
4. Breadboard x1
5. Male-to-female jumper wires
Circuit connections
- First connect the GPIO21 (board pin number 40) of RPi with one terminal of the push button.
- Then, connect a resistor of 10K on that same terminal and attach the resistor’s other end to the 3.3V.
- Connect the other terminal of the push button to ground. The push button should now be connected in a pull-high configuration.
- The DC supply voltage and the ground can be supplied to the circuit assembled on the breadboard, from pin number 1 and pin number 6 of the RPi board, respectively.
Python script
Working the project
A push button is interfaced at the GPIO21 (board pin number 40) with an external pull-up. By default, pin 40 will receive a HIGH logic (3.3V). When the push button is pressed, a LOW logic signal (ground) is applied to the pin.
The user-program is designed to detect the logical LOW at pin 40 and print this message to the console: “Button Pressed….” The user-program is written on Python IDLE and the message received when pressing the button is monitored on the console of the IDLE.
Programming guide
The script starts by importing the GPIO and the time libraries. A global variable is defined to denote pin 40, where the push button is connected with external pull-up.
import RPi.GPIO as GPIO
import time
button = 40
A user-defined function setup() is set in which the pin numbering system is set to BOARD and pin 40 is set as input. This function is only executed once.
def setup():
GPIO.setmode(GPIO.BOARD)
GPIO.setup(button, GPIO.IN)
A user-defined function loop() is also set in which the state of the switch is monitored in “if conditional.” If the switch is pressed and a logical LOW is read at the pin 40, the “Button Pressed…” message is printed on the console.
If the button remains pressed, the user-program is forced to wait for 0.2 seconds using the time.sleep() function. This function is repeated infinitely, continuously monitoring the state of the switch.
def loop():
while True:
button_state = GPIO.input(button)
if button_state == False:
print(“Button Pressed…”)
while GPIO.input(button) == False:
time.sleep(0.2)
Then, a user-defined function endprogram() executes when the script terminates. This function is set to clean up the GPIO.
def endprogram():
GPIO.cleanup()
A try-exception statement is used to execute the setup() function once, the loop() function infinitely after the setup() function, and the endprogram() function for terminating the script on the receipt of a keyboard interrupt.
if __name__ == ‘__main__’:
setup()
try:
loop()
except KeyboardInterrupt:
print(“keyboard interrupt detected”)
endprogram()
In the next tutorial, we will discuss how to generate PWM signals (analog output) from Raspberry Pi.
Demonstration video 15R-DV of this project
Filed Under: Raspberry pi
Questions related to this article?
👉Ask and discuss on Electro-Tech-Online.com and EDAboard.com forums.
Tell Us What You Think!!
You must be logged in to post a comment.