Difficulty arming if offboard mode

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.

I’m adding my flight review. It now has a bunch of Yaw estimate errors.Flight Review

I downloaded the log file and ran it through an online analysis tool I use called Flight Assistant. It did a great job of parsing the log and immediately highlighted the root cause. It flagged the CPU/RAM overload from the vehicle_status data and showed that your RAM usage was consistently high, which is why you were failing the pre-arm check.
if you want to analyse your log files via natural language you can use it for free here
FlightAssistant
Of course, no tool is a replacement for digging into the raw data yourself, but I find it’s incredibly useful for getting a quick, accurate summary of what went wrong instead of spending hours searching for the right data fields.

You can see the full analysis it generated from your .ulg file here: FlightAssistant

Anyway, thought this might help you get it sorted faster. Good luck with the thrust-vectoring project!

Thank you! We cleared our CPU/RAM overload flag but of course we have more errors. I just tried the Flight Assistant and it’s super helpful!

So there were a bunch of things wrong, we were using both the internal and external magnetometers, we were running two Kalman Filters when we really just needed the one, we did have a CPU load issue, but the thing that was really causing the drone to disarm was COM_DISARM_PRFLT = 10. The drone would disarm after 10 seconds because we’re just testing, we aren’t taking off. The good news is we fixed a lot of things that would have probably been issues later and we are less newbies now. The FlightAssistant is much better than plain ChatGPT for debugging these kinds of issues. Thanks to Jithin.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.