GPIO Communication
GPIO (General Purpose Input/Output) communication using libgpiod for Linux systems, particularly Raspberry Pi and other single-board computers.
When to Use GPIO
- Raspberry Pi or Linux SBC digital I/O
- Relay control for devices (covers, heaters, dew heaters)
- Reading sensors (switches, buttons, limit switches)
- PWM control for motors, fans, or LEDs
- Custom hardware interfacing
- Direct hardware control without USB/Serial overhead
Important: libgpiod Version
Use libgpiod v2 for all new projects. The v2 API provides significant improvements in stability, performance, and features. If you’re working with existing code using libgpiod v1, plan to migrate to v2 as the older version is deprecated.
Key differences:
- v2: Modern C++ API with RAII, better error handling, and improved performance
- v1: Legacy API, deprecated, to be phased out
All examples in this documentation use libgpiod v2.
INDI Interface Classes
INDI provides specialized interface classes for GPIO devices:
INDI::InputInterface: For digital sensors and input devices (buttons, switches, sensors)INDI::OutputInterface: For digital outputs and control devices (relays, LEDs, actuators)
These interfaces provide standardized properties and methods for handling GPIO operations, making your driver consistent with other INDI GPIO drivers.
Required Headers
#include <gpiod.hpp>
Basic GPIO Setup
class INDIGPIO : public INDI::DefaultDevice,
public INDI::InputInterface,
public INDI::OutputInterface
{
public:
INDIGPIO()
{
setVersion(VERSION_MAJOR, VERSION_MINOR);
}
bool Connect() override
{
try
{
// Open GPIO chip (usually gpiochip0 on Raspberry Pi)
m_GPIO.reset(new gpiod::chip("gpiochip0"));
// Setup PWM if available
setupPWMProperties();
// Enumerate available GPIO lines
detectGPIOLines();
return true;
}
catch (const std::exception &e)
{
LOGF_ERROR("Failed to connect: %s", e.what());
return false;
}
}
private:
std::unique_ptr<gpiod::chip> m_GPIO;
std::vector<int> m_InputOffsets;
std::vector<int> m_OutputOffsets;
};
GPIO Input Example
Reading digital inputs (sensors, switches, buttons):
bool INDIGPIO::updateDigitalInputs()
{
try
{
for (size_t i = 0; i < m_InputOffsets.size(); i++)
{
auto oldState = DigitalInputsSP[i].findOnSwitchIndex();
// Request line for input
auto request = m_GPIO->prepare_request()
.set_consumer("indi-gpio")
.add_line_settings(
m_InputOffsets[i],
gpiod::line_settings().set_direction(
gpiod::line::direction::INPUT))
.do_request();
// Read value
auto newState = request.get_value(m_InputOffsets[i]) ==
gpiod::line::value::ACTIVE ? 1 : 0;
// Update property if changed
if (oldState != newState)
{
DigitalInputsSP[i].reset();
DigitalInputsSP[i][newState].setState(ISS_ON);
DigitalInputsSP[i].setState(IPS_OK);
DigitalInputsSP[i].apply();
}
}
return true;
}
catch (const std::exception &e)
{
LOGF_ERROR("Failed to update inputs: %s", e.what());
return false;
}
}
GPIO Output Example
Controlling digital outputs (relays, LEDs, actuators):
bool INDIGPIO::commandOutput(uint32_t index, OutputState command)
{
if (index >= m_OutputOffsets.size())
{
LOGF_ERROR("Invalid output index %d", index);
return false;
}
try
{
auto offset = m_OutputOffsets[index];
// Request line for output
auto request = m_GPIO->prepare_request()
.set_consumer("indi-gpio")
.add_line_settings(
offset,
gpiod::line_settings().set_direction(
gpiod::line::direction::OUTPUT))
.do_request();
// Set value
gpiod::line::value val = (command == OutputState::Off) ?
gpiod::line::value::INACTIVE :
gpiod::line::value::ACTIVE;
request.set_value(offset, val);
return true;
}
catch (const std::exception &e)
{
LOGF_ERROR("Failed to set output: %s", e.what());
return false;
}
}
PWM Control Example
For motor control, fans, or LED dimming:
bool INDIGPIO::setPWMDutyCycle(size_t index, int dutyCycle)
{
if (index >= m_PWMPins.size() || dutyCycle < 0 || dutyCycle > 100)
return false;
auto &pin = m_PWMPins[index];
// Calculate duty cycle in nanoseconds
uint64_t period_ns = 1000000000ULL / pin.frequency;
uint64_t duty_ns = (period_ns * dutyCycle) / 100;
// Write to sysfs PWM interface
std::string dutyPath = "/sys/class/pwm/" + pin.pwmChip +
"/pwm" + std::to_string(pin.channel) +
"/duty_cycle";
std::ofstream dutyFile(dutyPath);
if (!dutyFile.is_open())
{
LOGF_ERROR("Failed to open duty cycle file: %s", dutyPath.c_str());
return false;
}
dutyFile << duty_ns << std::endl;
dutyFile.close();
pin.dutyCycle = dutyCycle;
return true;
}
bool INDIGPIO::enablePWM(size_t index, bool enabled)
{
if (index >= m_PWMPins.size())
return false;
auto &pin = m_PWMPins[index];
std::string enablePath = "/sys/class/pwm/" + pin.pwmChip +
"/pwm" + std::to_string(pin.channel) +
"/enable";
std::ofstream enableFile(enablePath);
if (!enableFile.is_open())
{
LOGF_ERROR("Failed to open enable file: %s", enablePath.c_str());
return false;
}
enableFile << (enabled ? "1" : "0") << std::endl;
enableFile.close();
pin.active = enabled;
return true;
}
Raspberry Pi Pin Reference
Common GPIO pins on Raspberry Pi:
| GPIO Number | Physical Pin | Common Use |
|---|---|---|
| GPIO2 | Pin 3 | I2C SDA |
| GPIO3 | Pin 5 | I2C SCL |
| GPIO4 | Pin 7 | General Purpose |
| GPIO17 | Pin 11 | General Purpose |
| GPIO27 | Pin 13 | General Purpose |
| GPIO22 | Pin 15 | General Purpose |
| GPIO12 | Pin 32 | PWM0 |
| GPIO13 | Pin 33 | PWM1 |
Best Practices
- Chip Selection: Use
gpiochip0for Raspberry Pi, may vary on other boards - Line Direction: Always set direction (INPUT or OUTPUT) before use
- PWM Access: PWM requires sysfs interface (
/sys/class/pwm/) - Permissions: Add user to
gpiogroup or create udev rules - Resource Cleanup: Release GPIO lines when done
- Error Handling: Wrap GPIO operations in try-catch blocks
- Pull Resistors: Configure pull-up/pull-down for inputs as needed
Common Use Cases
Relay Control (Flat Panel, Heater, etc.)
// Turn on relay
commandOutput(RELAY_INDEX, OutputState::On);
// Turn off relay
commandOutput(RELAY_INDEX, OutputState::Off);
Limit Switch Monitoring
// Check if cover is fully open
void TimerHit()
{
updateDigitalInputs();
if (DigitalInputsSP[LIMIT_SWITCH_INDEX][1].getState() == ISS_ON)
{
// Limit switch triggered, cover fully open
CoverSP.setState(IPS_OK);
CoverSP.apply();
}
}
Fan Speed Control via PWM
// Set fan to 50% speed
setPWMDutyCycle(FAN_PWM_INDEX, 50);
enablePWM(FAN_PWM_INDEX, true);
// Stop fan
enablePWM(FAN_PWM_INDEX, false);
Troubleshooting
Cannot Open GPIO Chip
# Verify chip exists
ls /dev/gpiochip*
# Add user to gpio group
sudo usermod -a -G gpio $USER
# Log out and log back in
# Check libgpiod is installed
dpkg -l | grep libgpiod
PWM Not Working
# Check PWM is enabled in device tree
ls /sys/class/pwm/
# Verify permissions
ls -l /sys/class/pwm/pwmchip0/
# Check PWM overlay is enabled (Raspberry Pi)
grep dtoverlay /boot/config.txt
Permission Issues
# Create udev rule for PWM
sudo nano /etc/udev/rules.d/99-pwm.rules
# Add:
SUBSYSTEM=="pwm", ACTION=="add", RUN+="/bin/chgrp -R gpio /sys%p", RUN+="/bin/chmod -R g+w /sys%p"
# Reload rules
sudo udevadm control --reload-rules
sudo udevadm trigger
Example Driver
See the complete GPIO driver implementation:
indi-gpio/indi_gpio.cpp- Full-featured Raspberry Pi GPIO driver
Related Guides
- Best Practices
- System Setup - Detailed permission configuration
- Troubleshooting
- INDI API Documentation