GPIO
Microcontrollers expose pins that can be controlled from software as logical inputs or outputs — General Purpose Input/Output, or GPIO.
Two APIs
TinyCLR offers two GPIO APIs that coexist on the same hardware. Pick whichever fits your project — switching between them is free, and you can mix both in the same application if needed.
- .NET IoT API — Microsoft's standard
System.Device.Gpionamespace. Use this for new code and for portability with desktop .NET IoT applications. - TinyCLR API — the original
GHIElectronics.TinyCLR.Devices.Gpionamespace. Keeps v2 code working without changes.
Both API namespaces are provided by a single NuGet package — install one and you have both:
GHIElectronics.TinyCLR.Devices.Gpio— contains both theSystem.Device.Gpio(.NET IoT) andGHIElectronics.TinyCLR.Devices.Gpio(TinyCLR) namespaces.GHIElectronics.TinyCLR.Pins— pin name constants for SITCore boards. Shared across GPIO, SPI, I²C, UART, and other peripherals — not GPIO-specific.
Digital outputs
A digital output pin can be set high (~3.3V) or low (~0V).
Never connect two output pins together. If one is high and the other is low, the entire processor can be damaged.
Digital pins on microcontrollers are weak — typically enough to drive small LEDs or transistors, but not motors or other high-power loads. Use a transistor to switch high-power devices.
- .NET IoT API
- TinyCLR API
using System.Threading;
using System.Device.Gpio;
using GHIElectronics.TinyCLR.Pins;
using GpioController controller = new GpioController();
controller.OpenPin(FEZBit.GpioPin.Led, PinMode.Output);
while (true){
controller.Write(FEZBit.GpioPin.Led, PinValue.High);
Thread.Sleep(100);
controller.Write(FEZBit.GpioPin.Led, PinValue.Low);
Thread.Sleep(100);
}
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Pins;
var led = GpioController.GetDefault().OpenPin(FEZBit.GpioPin.Led);
led.SetDriveMode(GpioPinDriveMode.Output);
while (true){
led.Write(GpioPinValue.High);
Thread.Sleep(100);
led.Write(GpioPinValue.Low);
Thread.Sleep(100);
}
Digital inputs
Digital inputs read the state of a pin based on its voltage. Most processors run on 3.3V — input pins should stay between 0V and 3.3V. Negative voltages may damage the pin or processor.
Some 3.3V processors tolerate up to 5V on their inputs — SITCore is 5V-tolerant.
5V-tolerant doesn't mean the processor can be powered by 5V — only that input pins can tolerate 5V signals.
Unconnected input pins are called "floating." A pull-up or pull-down resistor anchors the pin to a known state. Modern processors include software-controlled internal pull-up and pull-down resistors.
The code below uses an internal pull-up on a button pin. When the button is pressed, the pin reads Low.
- .NET IoT API
- TinyCLR API
using System.Threading;
using System.Device.Gpio;
using GHIElectronics.TinyCLR.Pins;
using GpioController controller = new GpioController();
controller.OpenPin(FEZBit.GpioPin.ButtonA, PinMode.InputPullUp);
while (true){
if (controller.Read(FEZBit.GpioPin.ButtonA) == PinValue.Low){
// Button is pressed.
}
Thread.Sleep(10); // Always give the system time to think!
}
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Pins;
var gpio = GpioController.GetDefault();
var button = gpio.OpenPin(FEZBit.GpioPin.ButtonA);
button.SetDriveMode(GpioPinDriveMode.InputPullUp);
while (true){
if (button.Read() == GpioPinValue.Low){
// Button is pressed.
}
Thread.Sleep(10); // Always give the system time to think!
}
Digital input events
Polling an input in a loop is simple but inefficient. You can use events (interrupts) instead.
The example below registers a callback that fires on both falling edges (high-to-low transitions, e.g. a button being pressed) and rising edges (low-to-high transitions, e.g. a button being released).
- .NET IoT API
- TinyCLR API
using System.Threading;
using System.Device.Gpio;
using GHIElectronics.TinyCLR.Pins;
using GpioController controller = new GpioController();
controller.OpenPin(FEZBit.GpioPin.ButtonA, PinMode.InputPullUp);
controller.RegisterCallbackForPinValueChangedEvent(
FEZBit.GpioPin.ButtonA,
PinEventTypes.Falling | PinEventTypes.Rising,
Button_ValueChanged
);
// Do other tasks here ...
Thread.Sleep(Timeout.Infinite);
void Button_ValueChanged(object sender, PinValueChangedEventArgs args){
if (args.ChangeType == PinEventTypes.Falling){
// Pin went low — button pressed
} else{
// Pin went high — button released
}
}
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Pins;
var gpio = GpioController.GetDefault();
var button = gpio.OpenPin(FEZBit.GpioPin.ButtonA);
button.SetDriveMode(GpioPinDriveMode.InputPullUp);
button.ValueChangedEdge = GpioPinEdge.FallingEdge | GpioPinEdge.RisingEdge;
button.ValueChanged += Button_ValueChanged;
// Do other tasks here ...
Thread.Sleep(Timeout.Infinite);
void Button_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e){
if (e.Edge == GpioPinEdge.FallingEdge){
// Pin went low — button pressed
} else {
// Pin went high — button released
}
}
Debouncing
When a mechanical button is pressed, contacts physically bounce — producing multiple edges in quick succession. The TinyCLR API has built-in debounce filtering: any edges faster than the configured timeout are ignored. Default is 20 ms; override with:
button.DebounceTimeout = TimeSpan.FromMilliseconds(10);
When using the .NET IoT API, implement debounce in your callback by tracking the last edge time and ignoring events that arrive too quickly.
Interrupt limitation
GPIO events use the chip's interrupt lines. SITCore supports up to 16 simultaneous interrupts, and the pin number must be unique across all ports. For example, PA1 and PB1 can't both be interrupt pins at the same time — but PA1 and PB2, or PA1 and PA2, can.
Other internal system functions that use interrupts (WiFi, for example) reserve one of the 16 available slots.
Low-level access (advanced)
This advanced API moves a peripheral signal from its default pin to an alternate pin using the chipset's alternate-function multiplexer.
- Does not check whether the alternate function is valid.
- Does not reserve the destination pin.
This example moves the SPI2 MOSI signal from PB2 to PC7 using Alternate Function 6 (AF6):
// Start by creating SPI2 to initialize the feature on PB2,
// then transfer it to PC7.
var settings = new Settings {
mode = PortMode.AlternateFunction,
speed = OutputSpeed.VeryHigh,
driveDirection = PullDirection.None,
alternate = AlternateFunction.AF6,
type = OutputType.PushPull
};
LowLevelController.TransferFeature(SC20100.GpioPin.PB2, SC20100.GpioPin.PC7, settings);
// SC20100.GpioPin.PB2 → default state (input)
// SC20100.GpioPin.PC7 → AF6 (SPI2 MOSI)
Call this right before Enable() (or Open() for UART, Start() for PWM, etc. — depends on the peripheral) for it to work correctly.
API reference
| Namespace | Description |
|---|---|
| GHIElectronics.TinyCLR.Devices.Gpio | TinyCLR GPIO controller and pin classes |
| System.Device.Gpio | .NET IoT standard GPIO API (included in the same NuGet) |