Navigator Lib - PWM Outputs C++

Hello,

When using `navigator-lib’ in C++, I’m having issues consistently driving the fins with the PWM class.

  • I’ll run it once as sudo, and nothing will happen
  • I’ll run it a second time as sudo, and I here the chirping of the servos, however they are not being driven as expected. If I hit Ctrl+C to kill the process, the servos will then jump.
  • Video: https://photos.app.goo.gl/vxdNdtVUx1ow7Pq87 - you can hear the servo chirp, see the print out of my screen with the following code, and how the fins are not moving as expected
  • I have some quick test code below, after I’ve messed with things a bit and applied several changes. I initialize the navigator, enable pwm, set the frequency to 50 hz (20 millisecond period), and then have the servos cycle back and forth. I had tried using the set_pwm_channel_duty_cycle method, changing the rate in which I provide signal updates, and have tried both blocking and non-blocking applications. The second code block only changes the actual fin angles when I hit ctrl-C to exit the program, and then they move.
  • The fins start to move when the value is less than 550 or so, and between 0 to 550 they’ll sweep twice. Why is it that if I set either the prescale to 99 or the frequency to 50 I get this behavior for the 0 to 4095 range?
void signalHandler(int signum) {

    set_led(UserLed::Led1, false);
    set_pwm_enable(false);
    exit(signum);
    
}

int main() {

  signal(SIGINT, signalHandler);

  init();

  printf("Initialized navigator\n");
  set_pwm_freq_hz(50);
  set_pwm_enable(true);
  printf("PWM Chip State: %d\n", get_pwm_enable());
    
  double max = 4096;
  double center = max/2;
  double min = 0;
   
  PwmChannel fin_channels[3] = {PwmChannel::Ch1, PwmChannel::Ch2, PwmChannel::Ch3};
  for (auto i: fin_channels) {
    //set_pwm_channel_value(i, center);
    set_pwm_channel_value(PwmChannel::All,center);
    printf("Centered fin %d\n",i);
  }

  //Set to sweep about 10 degrees per second
  while(true) {
    for(int i = center; i <= max; i+=7) {
      for (auto j: fin_channels) {
        //set_pwm_channel_value(j, i);
	set_pwm_channel_value(PwmChannel::All,i);
	printf("Set fin %d to %d\n",j, i);
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
      }
    }

    for(int i = max; i >= min; i-=7) {
      for (auto j: fin_channels) {
        //set_pwm_channel_value(j, i);
	set_pwm_channel_value(PwmChannel::All,i);
	printf("Set fin %d to %d\n",j, i);
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
      }
    }

    for(int i = min; i < center; i+=7) {
      for (auto j: fin_channels) {
	set_pwm_channel_value(PwmChannel::All,i);
        //set_pwm_channel_value(j, i);
        printf("Set fin %d to %d\n",j, i);
	std::this_thread::sleep_for(std::chrono::milliseconds(50));
      }
    }
  }
}

and

using namespace std::chrono;

void signalHandler(int signum) {
    set_led(UserLed::Led1, false);
    set_pwm_enable(false);
    exit(signum);    
}

int main() {
    signal(SIGINT, signalHandler);
    init();
    printf("Initialized navigator\n");
    fflush(stdout);
    set_pwm_freq_hz(50);
    set_pwm_enable(true);
    printf("PWM Chip State: %d\n", get_pwm_enable());

    double max = 4096;
    double center = max / 2;
    double min = 0;

    PwmChannel fin_channels[3] = {PwmChannel::Ch1, PwmChannel::Ch2, PwmChannel::Ch3};
    for (auto i : fin_channels) {
        set_pwm_channel_value(PwmChannel::All, center);
        printf("Centered fin %d\n", i);
    	fflush(stdout);
    }

    auto next_time = steady_clock::now();
    int i = center;
    bool increasing = true; 

    while (true) {
        auto current_time = steady_clock::now();
        if (current_time >= next_time) {
            for (auto j : fin_channels) {
                set_pwm_channel_value(PwmChannel::All, i);
                printf("Set fin %d to %d\n", j, i);
            	fflush(stdout);
	    }
            
            if (increasing) {
                i += 7;
                if (i >= max) {
                    i = max; 
                    increasing = false; 
                }
            } else {
                i -= 7;
                if (i <= min) {
                    i = min;
                    increasing = true; 
                }
            }

            next_time = current_time + milliseconds(50); 
        }
    }
}

Thanks

Hey @rturrisi,

The provided code works here; I checked it on the scope, and the PWM signal behaves as expected.

I’ve used some 90g servos; but modified “double max = 4096/10;” to match the control window that is between 0~2ms.
Do you have 5V on the servo +?

Hey Raul,
Thanks for getting back to me. Per the library, and what I was trying originally, I thought if I set the frequency to 50 Hz (20 ms pulse width), I would be able to set the duty cycle such that if it was 50%, the servos should be centered? Also if I set the pwm frequency to 50 Hz, that if I set 4096 manually, it would correspond to 100% of the desired period? And yes, I have 5V servos - I’m using three Savox 1350 MTG’s.
Best,
Ray

By checking the technical specs here for Savox 1350:

Can you try with this values?

Probably they will not work properly if are the same from above specs,
Here is an example to setup the PWM channel values according to period values.
Python Library Docs

I made this spreadsheet Prescale Frequency/Steps calculator

desired_freq 333
prescale_value 17
final freq 333,3333333 period 3,00000E-3
resolution (1/4096) 732,42188E-9
OFF Counter Duty Cycle Pulse Width
Min 1366 33,35% 1,00049E-3
Neutral 2048 50,00% 1,50000E-3
Max 2730 66,65% 1,99951E-3

@rturrisi the max value accepted is 4095, the PCA is a bit more versatile and allows different combinations. For a full duty-cycle ( 1 ) I’ve made the duty_cycle method to handle this when receive this special case.

I believe @RaulTrombin was asking whether you have supplied 5V power to the servo’s power connector (which is generally done via the “AUX 5V” pins, to power anything connected to the servo rail), since the Navigator does not do that automatically.

Ahh - yes - I am indeed powering the servos. I’m using the BR 5V/6A power supply which is connected to AUX and the input power to the Pi. I got everything working once Raul provided the link to the Python Library Docs, and found the upper and lower bounds with the provided equation. The code which works as expected for these servos is:

#include "bindings.h"
#include <chrono>
#include <cmath>
#include <iostream>
#include <thread>
#include <csignal>

using namespace std::chrono;

void signalHandler(int signum) {
    set_pwm_enable(false);
    exit(signum);    
}

int main() {
    signal(SIGINT, signalHandler);
    init();
    set_pwm_freq_hz(50);
    set_pwm_enable(true);
    
    printf("PWM Chip State: %d\n", get_pwm_enable());

    //https://discuss.bluerobotics.com/t/navigator-lib-pwm-outputs-c/16378
    //https://docs.bluerobotics.com/navigator-lib/python/#bluerobotics_navigator.set_pwm_channel_value
    double max = 409.5; //at 50 Hz, corresponds to 2000 microseconds pulsewidth
    
    double min = 204.75; //at 50 Hz, corresponds to 1000 microseconds pulsewidth
    
    double increments = 300;

    double step_size = max/increments;
    double range = max-min;
    double center = min + range/2;
    
    PwmChannel fin_channels[3] = {PwmChannel::Ch1, PwmChannel::Ch2, PwmChannel::Ch3};

    int hold_for = 5; //seconds
    auto current_time = steady_clock::now();
    auto update_time = steady_clock::now();
    auto next_time = steady_clock::now();
    next_time = next_time + milliseconds(hold_for*1000);
    
    printf("Centering fins\n");
    for (auto i : fin_channels) {
        set_pwm_channel_value(i, center);
        fflush(stdout);
    }

    //Hold center for 5 seconds
    do {
        current_time = steady_clock::now();
        if(current_time >= update_time) {
            printf("T - %0.2f\n", duration_cast<duration<double>>((next_time.time_since_epoch() - current_time.time_since_epoch())).count());
            update_time = current_time + seconds(1);
        }
    } while(current_time <= next_time);

    double i = center;
    bool increasing = true; // Initial direction

    while (true) {
        current_time = steady_clock::now();
        if (current_time >= next_time) {
            for (auto j : fin_channels) {
                set_pwm_channel_value(j, i);
	    }
            
            // Increment or decrement the position based on direction
            if (increasing) {
                i += step_size;
                if (i >= max) {
                    i = max; 
                    increasing = false; 
                }
            } else {
                i -= step_size;
                if (i <= min) {
                    i = min; 
                    increasing = true; 
                }
            }

            next_time = current_time + milliseconds(50); // Schedule next update
        }
    }
}
1 Like

@rturrisi Nice!
You still can speedup the control using the set_pwm_freq_hz(333) and adjust with the previous values I sent.

Hey @RaulTrombin - Thanks!

I am aware of how we can change the frequency - however I need to use a standard frequency across all outputs - it isn’t just for servos. I tested with 333 Hz and also adjusted the values and it didn’t change the quality of performance, so I’m sticking with 50 Hz since it is more standard and works with the range of actuators/outputs in the system. I’m including the code/paper trail should it serve as a useful reference for some other user downstream.

I will have a central PWM output interface application from our middleware to the Navigator, so on our AUV I’ll have one MOOS application controlling the three control surfaces, the thruster, and some other small accessories (LEDs and such).

1 Like