We’re working on a control theory project using the px4 and we’re pretty stuck. We’re total newbies so I hope you’ll bear with us.
What we’re trying to do:
- building a thrust vector drone with 2 stacked propellers on the bottom of drone run with brushless motors. The motors are mounted on two orthagonal gimbals run by servos.
- Running the drone inside, fully autonomous with no RC and no GPS. (we have a GPS if we need it)
- control motors and servos directly with PWM.
The details:
- Our companion computer is a Raspberry Pi with Ubuntu 24.04, ROS2 Jazzy
- running Micro-xrce-dds-agent on Pi
- using px4_msgs 1.15
- We can control servos and motors through QGC
- We configured our EKF to use accelerometer, gyro, barometer, magnetometer
Problem:
The drone arms and then a few seconds later disarms.
I suspect we may have a number of things setup incorrectly. If anyone could direct us towards the correct resources to read or give us guidance it would be greatly appreciated.
Here’s our health status health_report
nsh> listener health_report
TOPIC: health_report
health_report
timestamp: 75107291 (57.853012 seconds ago)
can_arm_mode_flags: 80768 (0b1'0011'1011'1000'0000)
can_run_mode_flags: 80768 (0b1'0011'1011'1000'0000)
health_is_present_flags: 671223810 (0b10'1000'0000'0010'0001'0000'0000'0010)
health_warning_flags: 0
health_error_flags: 0
arming_check_warning_flags: 128 (0b1000'0000)
arming_check_error_flags: 1048576 (0b1'0000'0000'0000'0000'0000)
nsh>
I believe the offending bit in arming_check_error_flags
is the CPU/RAM error. We’ve had this error before and at one point had turned lots of things off, reducing the CPU load, but the error didn’t go away. For our current run we get the following CPU graph.
Here’s our Python code:
import rclpy
from rclpy.node import Node
from px4_msgs.msg import ActuatorMotors, OffboardControlMode, VehicleStatus, VehicleCommand
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy, QoSDurabilityPolicy
import time
class SimplePwmTest(Node):
def __init__(self):
super().__init__('simple_pwm_test')
# QoS profiles
qos_pub = QoSProfile(
reliability=QoSReliabilityPolicy.BEST_EFFORT,
durability=QoSDurabilityPolicy.TRANSIENT_LOCAL,
history=QoSHistoryPolicy.KEEP_LAST,
depth=1
)
qos_sub = QoSProfile(
reliability=QoSReliabilityPolicy.BEST_EFFORT,
durability=QoSDurabilityPolicy.VOLATILE,
history=QoSHistoryPolicy.KEEP_LAST,
depth=1
)
self.setpoint_cnt = 0
self.status_sub = self.create_subscription(VehicleStatus, '/fmu/out/vehicle_status', self.vehicle_status_callback, qos_sub)
self.nav_state = VehicleStatus.NAVIGATION_STATE_MAX
self.arming_state = VehicleStatus.ARMING_STATE_DISARMED
self.publisher_vehicle_command = self.create_publisher(VehicleCommand, '/fmu/in/vehicle_command', qos_pub)
self.publisher_offboard_mode = self.create_publisher(OffboardControlMode, '/fmu/in/offboard_control_mode', qos_pub)
self.publisher_motors = self.create_publisher(ActuatorMotors, '/fmu/in/actuator_motors', qos_pub)
self.timer = self.create_timer(0.1, self.send_pwm) # 10 Hz
def vehicle_status_callback(self, msg):
print("NAV_STATUS: ", msg.nav_state)
print(" - offboard status: ", VehicleStatus.NAVIGATION_STATE_OFFBOARD)
self.nav_state = msg.nav_state
self.arming_state = msg.arming_state
def publish_vehicle_command(self, command, p1=0., p2=0.):
msg = VehicleCommand()
msg.timestamp = self.get_clock().now().nanoseconds // 1000
msg.command = command
msg.param1, msg.param2 = float(p1), float(p2)
msg.target_system = 1
msg.target_component = 1
msg.source_system = 1
msg.source_component = 1
msg.from_external = True
self.publisher_vehicle_command.publish(msg)
def send_pwm(self):
# publish off board control
offboard_msg = OffboardControlMode()
offboard_msg.timestamp = int(self.get_clock().now().nanoseconds / 1000)
offboard_msg.direct_actuator=True
offboard_msg.position = False
offboard_msg.velocity = False
offboard_msg.acceleration = False
offboard_msg.attitude = False
offboard_msg.body_rate = False
self.publisher_offboard_mode.publish(offboard_msg)
# If we are armed and offboard publish pwm for motors
if (self.nav_state == VehicleStatus.NAVIGATION_STATE_OFFBOARD and self.arming_state == VehicleStatus.ARMING_STATE_ARMED):
msg = ActuatorMotors()
t = self.get_clock().now().nanoseconds // 1000
msg.timestamp_sample = t
msg.timestamp = t
msg.control = [0.05, 0.0, 0.0, 0.0] + [0.0] * 8 # 4 motors + 4 unused
self.publisher_motors.publish(msg)
self.get_logger().info('Sent PWM vector: ' + str(msg.control[:4]))
# Send mode‑change / arm once after 10 set‑points
if self.setpoint_cnt == 10:
# switch to Offboard
self.publish_vehicle_command(
VehicleCommand.VEHICLE_CMD_DO_SET_MODE, 1, 6)
# arm
self.publish_vehicle_command(
VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, 1.0)
self.setpoint_cnt += 1
def main(args=None):
rclpy.init(args=args)
node = SimplePwmTest()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
I exported our parameter file to include but it’s rather long. We can upload log files as well if that’s helpful.