PWM control via QGroundControl

I am using the BlueROV main electronics in a drop camera set up. I am currently using QGroundControl to capture the video stream. I do not have a controller as part of the system as there are no thrusters. I have the Lumen lights hooked up to the navigator board using PWM channels 13 and 14. Mission Planner software allows me to change the PWM value via the software and I have light control, but the video capture in Mission Planner is pretty terrible in terms of quality. Is there a way in QGroundControl to similarly alter the PWM frequency like Mission Planner provides? I have searched the forums and the internet for solutions to no avail – hopefully I didn’t miss the obvious.

my opinion.
I don’t know about a simple pwm control that can be use in QGC - like a slider or any switches in the software or using a keyboard.
But QGC will support a usb joystick connected to your laptop and will let you assign the PWM channel to it. Any game controller should do.

I’m trying to avoid any peripherals and keep the deck gear to a minimum.

Another options would be to use BlueOS, or even a simple python/bash script that is user-run on the internal raspberry pi, to change the PWM frequency, is this possible? I haven’t found where it would be.

I can’t think of a nice way to do this through QGC without running autopilot firmware (which you could do just without bothering to send it any motion control signals) or a custom QGC version.

If you don’t want to go the autopilot approach then a BlueOS extension could be useful, and wouldn’t even require a Navigator:

That said, if you already have (and want to make use of) a Navigator onboard then another option would be to make an extension using the new Navigator libraries. Hopefully we’ll have an example available soonish that does that anyway, since it’s useful functionality to show to people.

Thanks @EliotBR . The navigator libraries look like the way to go for my project. An example would definitely be handy, but hopefully I can make sense of things.

Having a look, and playing around, with the navigator libraries I become confused. I am primarily working with the python hooks, but I sifted through the rust implementation for help.

I am confused as to the interplay between set_pwm_channel_value() – which is granular in the per channel sense – and the set_pwm_freq_hz() – which seems to be for all pwm channels. Like in the example Lumen Light Installation and Example Code I expected a function that took a number that was somehow referenced to the duty cycle of the signal on that PWM channel. Using only set_pwm_channel_value() doesn’t seem to have any affect and I’m concerned about using set_pwm_freq_hz() as it will impact the other PWM channels that I don’t want to control at the moment.

I’m sure I’m missing something here, if anybody can point me in the right direction it would be appreciated.

Am I correct in thinking you’re not calling navigator.pwm_enable(True) in your code? There’s a global enable/disable pin for the whole chip that’s controlled by the pwm_enable function.

The Navigator’s PWM driver chip requires that all outputs use the same cycle frequency, which is what that function controls. 50Hz is generally a good value for controlling servo motors, but it’s not super important as long as the cycles are long enough for the pulses to be distinguishable.

Per-channel control of the pulse-durations is set by the set_pwm_channel(s)_value(s) functions. Having just looked at that documentation it’s quite confusing, so I’ve made some recommended improvements.

Each channel is controlled by a count from 0-4095, where the specified “value” determines at what point in that count the output turns off (they are currently all configured to turn on at count 0). If you want to control the duty-cycle you can do so as a fraction of 4095 (e.g. 20% ON would have a value of 819).

Lumen lights are controlled by the PWM pulse-durations though, not the duty cycle, so you’ll need to factor in the frequency that you set and then choose an appropriate count to achieve the pulse-duration you want.[1]

As an example, Lumen lights are controlled with pulses with durations between 1100 to 1900 µs, so with a 50 Hz frequency we have a minimum duty cycle of \frac{1100\cdot 10^{-6}}{1/50} = 5.5\% and a maximum duty cycle of 9.5\%, which works out to counts between 225 to 389.

There should also be functions for setting individual output pins continuously HIGH or LOW (like is done with ArduPilot’s relay functionalities), but they don’t seem to be implemented yet, so I’ll bring that up internally and get them added.


  1. This kind of low level control can be useful for some applications, but it can also be tedious - I’ve brought this up internally and recommended that we add some higher level more intuitive functions that allow you to just set the desired ON time in microseconds. ↩︎

I am using pwm_enable(), I think I was not fully understanding the interplay between set_pwm_freq() and set_pwm_channel_value(). What @EliotBR posted above has clarified things and I have a mostly working script.

A couple of issues have popped up, and perhaps this should be discussed in a different location, but keeping it here for now.

  1. I regularly get an error related to the magnetometer DataNotReady during a self-test that must run as part of either set_pwm_channel_value or pwm_enable(True). I am reasonably sure it is in pwm_enable().
  2. Not every time, but it is common, when I run my simple script to change the light intensity with the pwm control the camera servo changes its position – which is undesirable. I assume that this is related to the set_pwm_freq_hz call, but it doesn’t happen every time, which is little confusing. My code only changes pwm values for the 2 pwm channels I have lumen lights connected to – happens to be Ch13/14.
  3. Turns out that when using the code (like the pseudo code below) that only one of the lights are affected by the value specified and the other is affected in strange ways. If I set both channels to be 200 (for off with a 50Hz freq) both lights go off temporarily, but one comes back on. Likewise when setting the lights to be 300 (or similar) sometimes only one comes on.

My code is simple, pseudo code follows:

init()
pwm_enable(False)
PwmChannel to get the channels into a variable
set_pwm_freq_hz(50)
set_pwm_channel_value(PWM.Ch13, 300)
set_pwm_channel_value(PWM.Ch14, 300)
sleep(0.3)
pwm_enable(True)

It would be nice to only alter the lights and leave any other items connected alone.

Hi @oceanhoser,

The init() method will reset the PWM IC settings and startup with pwm_enabled = False. So it will rewrite ANY previous settings made by another driver instances (or ardupilot), every time it’s called, so our code has complete control of IC’s parameters.

If you are currently controlling the PWM IC from another source, you can try to use just the channel_value function, which will write the values on the I2C buffer. But, if you are running ardupilot, for example, it will not work properly, as ardupilot keeps rewriting all the channels values.

I ran some tests using a BlueOS experimental extension for navigator+python.

import bluerobotics_navigator as navigator
from bluerobotics_navigator import PwmChannel
navigator.set_pwm_channel_value(PwmChannel.Ch13, X)

So, if you are running it like a script, you don’t need to use init() on every call, just check first if there is no other drivers setting it.

2 Likes

Just noting that it’s possible to stop running an autopilot firmware on the flight controller by switching the current autopilot to SITL in BlueOS.

1 Like