A collection of C++ ROS2 nodes for the DEXI drone project, providing GPIO control, servo management, and I2C device interfaces.
- Servo Controller: Controls servos via PCA9685 PWM controller
- TCA9555 Controller: Manages TCA9555 16-bit I2C I/O expander for GPIO control
- RGB Status LED Controller: Controls RGB status indicators
DEXI's expansion hardware is built around two I²C peripherals on the companion computer's I²C-1 bus. They serve different roles and are addressed independently, so they coexist on the same two wires (SDA/SCL).
Raspberry Pi (companion computer)
│
I²C-1 bus (/dev/i2c-1)
│
┌─────────────────┴─────────────────┐
│ │
┌───▼────────┐ ┌─────▼──────┐
│ PCA9685 │ │ TCA9555 │
│ 0x40 │ │ 0x20 │
│ 16-ch PWM │ │ 16-bit IO │
└───┬────────┘ └─────┬──────┘
│ PWM 0..15 │ GPIO 0–4, 14–15
▼ ▼
servos / LEDs digital inputs/outputs
/dev/i2c-1 is the standard user I²C bus (the one enabled by raspi-config → Interface Options → I2C and scanned by i2cdetect -y 1). It is wired to the 40-pin header as follows:
| Signal | BCM GPIO | Physical pin |
|---|---|---|
| SDA | GPIO 2 | Pin 3 |
| SCL | GPIO 3 | Pin 5 |
| GND | — | Pin 6 (or any GND) |
| 3.3 V | — | Pin 1 |
Both the PCA9685 (0x40) and TCA9555 (0x20) share these two wires; they are distinguished only by their I²C addresses.
- Chip: NXP PCA9685, 16-channel 12-bit PWM driver.
- Bus / address:
/dev/i2c-1,0x40. - Frequency: configured to 50 Hz (20 ms period) for hobby servos.
- Channels: 16 independent PWM outputs (0–15).
- Node:
servo_controller—src/servo_controller.cpp, headerinclude/dexi_cpp/servo_controller.hpp. - Interface: service
/dexi/servo_control(dexi_interfaces/srv/ServoControl). Request takes a channelpin(0–15), anangle(0–180°), and optionalmin_pw/max_pwpulse widths in microseconds. The node converts angle → pulse width → duty cycle and writes it to the channel. - Typical use: driving servos. The same channels can also dim LEDs or any device that wants a duty-cycled signal.
- Chip: TI TCA9555, 16-bit I²C I/O expander.
- Bus / address:
/dev/i2c-1,0x20(A0/A1/A2 tied to GND). - Pins exposed by the node: 0–4 and 14–15 (seven total). Each pin is individually configured as input or output via launch parameters.
- Node:
tca9555_controller—src/tca9555_controller.cpp, headerinclude/dexi_cpp/tca9555_controller.hpp. - Interface:
- Service
/dexi/gpio_writer_service/write_gpio(dexi_interfaces/srv/GPIOSend) — set an output pin HIGH/LOW. - Topics
~/gpio_<pin>_state(std_msgs/Bool) — input pins are polled (default 10 Hz) and their state published.
- Service
- Typical use: relays, status logic levels, switches, buttons — anything that's digital on/off rather than PWM.
| Need to drive… | Use |
|---|---|
| Servo (RC-style 1–2 ms pulse, 50 Hz) | PCA9685 |
| LED brightness / motor speed (PWM) | PCA9685 |
| Relay, MOSFET gate, digital signal | TCA9555 output |
| Read a button, switch, or logic input | TCA9555 input |
Both nodes can run simultaneously — they live on the same I²C bus but at different addresses, so there's no contention beyond standard I²C arbitration.
# I2C support (required for TCA9555 and PCA9685)
sudo apt-get update
sudo apt-get install i2c-tools
sudo usermod -a -G i2c $USER- Enable I2C in raspi-config:
sudo raspi-config
# Interface Options -> I2C -> Enable- Create udev rules for I2C access:
sudo bash -c 'echo "SUBSYSTEM==\"i2c-dev\", GROUP=\"i2c\", MODE=\"0660\"" > /etc/udev/rules.d/90-i2c.rules'
sudo udevadm control --reload-rules
sudo udevadm trigger- Verify I2C devices:
i2cdetect -y 1# Build the package
colcon build --packages-select dexi_cpp --symlink-install
# Source the workspace
source install/setup.bashControls servos via PCA9685 PWM controller with robust I2C communication and comprehensive error handling.
Hardware Setup:
- Connect PCA9685 to I2C (SDA: GPIO 2, SCL: GPIO 3)
- Connect servos to PWM channels 0-15 (all 16 channels supported)
- Power servos from 5V supply
- PCA9685 uses I2C address 0x40 by default
Features:
- Supports all 16 PCA9685 channels (0-15)
- Custom pulse width control per servo
- Input validation and error handling
- 50Hz PWM frequency optimized for servos
ros2 launch dexi_cpp servo_controller.launch.pyServices:
/dexi/servo_control(dexi_interfaces/srv/ServoControl) - Control servo position
Service Interface:
# Request fields:
int32 pin # Servo channel (0-15)
int32 angle # Target angle in degrees (0-180)
int32 min_pw # Optional: minimum pulse width in microseconds (default: 500)
int32 max_pw # Optional: maximum pulse width in microseconds (default: 2500)
---
# Response fields:
bool success # Operation success status
string message # Status or error message
Usage Examples:
# Move servo on channel 0 to 90 degrees (using defaults: 500-2500μs pulse width)
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: 0, angle: 90}"
# Move servo on channel 1 to 0 degrees
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: 1, angle: 0}"
# Move servo on channel 2 to 180 degrees
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: 2, angle: 180}"
# Use custom pulse width range for specific servo (e.g., 600-2400μs)
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: 0, angle: 90, min_pw: 600, max_pw: 2400}"
# Control servo on channel 5 (demonstrates 16-channel support)
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: 5, angle: 45}"Testing Multiple Servos:
# Sequential servo control - sweep through channels 0-4
for i in {0..4}; do
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: $i, angle: 90}"
sleep 0.5
done
# Center all servos
for i in {0..4}; do
ros2 service call /dexi/servo_control dexi_interfaces/srv/ServoControl "{pin: $i, angle: 90}"
doneManages TCA9555 16-bit I2C I/O expander for GPIO control with configurable pin modes.
Hardware Setup:
- Connect TCA9555 to I2C (SDA: GPIO 2, SCL: GPIO 3)
- Set address pins A0, A1, A2 to GND for address 0x20
- Power from 3.3V
- Note: Supports pins 0-4 and 14-15
ros2 launch dexi_cpp tca9555_controller.launch.pyConfiguration: The launch file configures pins 0-2 and 14-15 as outputs and pins 3-4 as inputs by default.
Services:
/dexi/gpio_writer_service/write_gpio(dexi_interfaces/srv/GPIOSend) - Set output pin state
Topics:
/tca9555_controller/gpio_<pin>_state(std_msgs/Bool) - Input pin states
Usage:
# Set output pin 0 high
ros2 service call /dexi/gpio_writer_service/write_gpio dexi_interfaces/srv/GPIOSend "{pin: 0, state: true}"
# Set output pin 0 low
ros2 service call /dexi/gpio_writer_service/write_gpio dexi_interfaces/srv/GPIOSend "{pin: 0, state: false}"
# Monitor input pin 3
ros2 topic echo /tca9555_controller/gpio_3_stateControls RGB status indicators.
ros2 launch dexi_cpp rgb_status_led_controller.launch.pyModify pin modes in launch/tca9555_controller.launch.py:
'pin_0_mode': True, # Pin 0: output
'pin_1_mode': True, # Pin 1: output
'pin_2_mode': True, # Pin 2: output
'pin_3_mode': False, # Pin 3: input
'pin_4_mode': False, # Pin 4: input
'pin_14_mode': True, # Pin 14: output
'pin_15_mode': True # Pin 15: outputOverride parameters at launch:
# Change I2C device and polling rate
ros2 launch dexi_cpp tca9555_controller.launch.py i2c_device:=/dev/i2c-0 input_polling_rate:=5.0
# Change servo parameters
ros2 launch dexi_cpp servo_controller.launch.py i2c_address:=64# Add user to i2c group (required for I2C devices)
sudo usermod -a -G i2c $USER
# Log out and back in for group changes to take effect# Check I2C devices
i2cdetect -y 1
# Check I2C permissions
ls -la /dev/i2c-*
# Test I2C communication
i2cget -y 1 0x20 0x00 # Read TCA9555 input port 0# Clean and rebuild
rm -rf build/dexi_cpp install/dexi_cpp log/latest_build/dexi_cpp
colcon build --packages-select dexi_cpp --symlink-install
source install/setup.bashA Python example demonstrating how to read TCA9555 input pins and control output pins:
# Make executable
chmod +x examples/example_pin_control.py
# Run the example (requires TCA9555 controller to be running)
python3 examples/example_pin_control.pyThis example subscribes to pin 4 input and mirrors its state to pin 0 output.
A simple example that toggles TCA9555 pin 14 on and off at 1-second intervals:
# Make executable
chmod +x examples/example_toggle_pin.py
# Run the example (requires TCA9555 controller to be running)
python3 examples/example_toggle_pin.py- ROS2 Humble or later
- rclcpp - ROS2 C++ client library
- std_msgs - Standard ROS2 message types
- dexi_interfaces - Custom service and message definitions
MIT License - see LICENSE file for details.