Use GPIO for binary input

General-purpose I/O (GPIO) pins can be configured to receive electrical signals as input. At its most basic level, this is useful for scenarios that detect the opening/closing of a circuit. Such circuits might include push buttons, toggle switches, reed switches, pressure switches, and other devices that represent binary (on/off) values by completing a circuit.

In this tutorial, you'll use .NET and your Raspberry Pi's GPIO pins to detect the opening and closing of a circuit.

Prerequisites

  • ARM-based (ARMv7 or greater) single-board computer (SBC)
  • Jumper wires
  • Breadboard (optional)
  • Raspberry Pi GPIO breakout board (optional)
  • .NET SDK 8 or later

Note

This tutorial is written assuming the target device is Raspberry Pi. However, this tutorial can be used for any Linux-based SBC that supports .NET, such as Orange Pi, ODROID, and more.

Ensure SSH is enabled on your device. For Raspberry Pi, refer to Setting up an SSH Server in the Raspberry Pi documentation.

Prepare the hardware

Use the hardware components to build the circuit as depicted in the following diagram:

A diagram showing a circuit that connects a ground pin to pin 21.

The image above depicts a direct connection between a ground pin and pin 21.

Tip

The diagram depicts a breadboard and GPIO breakout for illustrative purposes, but feel free to just connect a ground pin and pin 21 with a jumper wire on the Raspberry Pi.

Refer to the following pinout diagram as needed:

A diagram showing the pinout of the Raspberry Pi GPIO header. Image courtesy Raspberry Pi Foundation.
Image courtesy Raspberry Pi Foundation.

Create the app

Complete the following steps in your preferred development environment:

  1. Create a new .NET Console App using either the .NET CLI or Visual Studio. Name it InputTutorial.

    dotnet new console -o InputTutorial
    cd InputTutorial
    
  2. Add the System.Device.Gpio package to the project. Use either .NET CLI from the project directory or Visual Studio.

    dotnet add package System.Device.Gpio --version 3.2.0-*
    
  3. Replace the contents of Program.cs with the following code:

    using System.Device.Gpio;
    using System.Threading.Tasks;
    
    const int Pin = 21;
    const string Alert = "ALERT 🚨";
    const string Ready = "READY ✅";
    
    using var controller = new GpioController();
    controller.OpenPin(Pin, PinMode.InputPullUp);
    
    Console.WriteLine(
        $"Initial status ({DateTime.Now}): {(controller.Read(Pin) == PinValue.High ? Alert : Ready)}");
    
    controller.RegisterCallbackForPinValueChangedEvent(
        Pin,
        PinEventTypes.Falling | PinEventTypes.Rising,
        OnPinEvent);
    
    await Task.Delay(Timeout.Infinite);
    
    static void OnPinEvent(object sender, PinValueChangedEventArgs args)
    {     
        Console.WriteLine(
            $"({DateTime.Now}) {(args.ChangeType is PinEventTypes.Rising ? Alert : Ready)}");
    }
    

    In the preceding code:

    • A using declaration creates an instance of GpioController. The using declaration ensures the object is disposed and hardware resources are released properly.
      • GpioController is instantiated with no parameters, indicating that it should detect which hardware platform it's running on and use the logical pin numbering scheme.
    • GPIO pin 21 is opened with PinMode.InputPullUp.
      • This opens the pin with a PullUp resistor engaged. In this mode, when the pin is connected to ground, it will return PinValue.Low. When the pin is disconnected from ground and the circuit is open, the pin returns PinValue.High.
    • The initial status is written to a console using a ternary expression. The pin's current state is read with Read(). If it's PinValue.High, it writes the Alert string to the console. Otherwise, it writes the Ready string.
    • RegisterCallbackForPinValueChangedEvent() registers a callback function for both the PinEventTypes.Rising and PinEventTypes.Falling events on the pin. These events correspond to pin states of PinValue.High and PinValue.Low, respectively.
    • The callback function points to a method called OnPinEvent(). OnPinEvent() uses another ternary expression that also writes the corresponding Alert or Ready strings.
    • The main thread sleeps indefinitely while waiting for pin events.
  4. Build the app. If using the .NET CLI, run dotnet build. To build in Visual Studio, press Ctrl+Shift+B.

  5. Deploy the app to the SBC as a self-contained app. For instructions, see Deploy .NET apps to Raspberry Pi. Make sure to give the executable execute permission using chmod +x.

  6. Run the app on the Raspberry Pi by switching to the deployment directory and running the executable.

    ./InputTutorial
    

    The console displays text similar to the following:

    Initial status (05/10/2022 15:59:25): READY ✅
    
  7. Disconnect pin 21 from ground. The console displays text similar to the following:

    (05/10/2022 15:59:59) ALERT 🚨
    
  8. Reconnect pin 21 and ground. The console displays text similar to the following:

    (05/10/2022 16:00:25) READY ✅
    
  9. Terminate the program by pressing Ctrl+C.

Congratulations! You've used GPIO to detect input using the System.Device.Gpio NuGet package! There are many uses for this type of input. This example can be used with any scenario where a switch connects or breaks a circuit. Here's an example using it with a magnetic reed switch, which is often used to detect open doors or windows.

Animated GIF demonstrating a magnetic reed switch opening and closing. The switch is exposed to a magnet, and the app displays READY. The magnet is removed, and the app displays ALERT. The action is then repeated.

Laser tripwire

Extending the previous example concept a bit further, let's take a look at how this could be applied to creating a laser tripwire. Building a laser tripwire requires the following additional components:

  • KY-008 laser transmitter module
  • Laser receiver sensor module (see note below)
  • 2 10K Ω resistors

Note

Laser receiver sensor module is the generic name applied to a common module found at many internet retailers. The device may vary in name or manufacturer, but should resemble this image.

Image of a Laser Receiver Sensor Module

Connect laser tripwire hardware

Connect the components as detailed in the following diagram.

A diagram showing a circuit that gets input from a laser receiver sensor module.

Pay close attention to the 10K Ω resistors. These implement a voltage divider. This is because the laser receiver module outputs 5V to indicate the beam is broken. Raspberry Pi only supports up to 3.3V for GPIO input. Since sending the full 5V to the pin could damage the Raspberry Pi, the current from the receiver module is passed through a voltage divider to halve the voltage to 2.5V.

Apply source code updates

You can almost use the same code as earlier, with one exception. In the other examples, we used PinMode.InputPullUp so that when the pin is disconnected from ground and the circuit is open, the pin returns PinValue.High.

However, in the case of the laser receiver module, we're not detecting an open circuit. Instead, we want the pin to act as a sink for current coming from the laser receiver module. In this case, we'll open the pin with PinMode.InputPullDown. This way, the pin returns PinValue.Low when it's getting no current, and PinValue.High when it receives current from the laser receiver module.

controller.OpenPin(pin, PinMode.InputPullDown);

Important

Make sure the code deployed on your Raspberry Pi includes this change before testing a laser tripwire. The program does work without it, but using the wrong input mode risks damage to your Raspberry Pi!

Animated GIF showing a demonstration of the laser tripwire. The laser emitter lights the laser sensor module, and the app displays READY. The laser beam is broken, and the app displays ALERT. The action is then repeated.

Get the source code

The source for this tutorial is available on GitHub.

Next steps