Disable and enable hardware PWM for single channel

Here my sketch for STM32:

#define PWM_RC_FREQ        50
#define PIN_SERVO          PE9

typedef struct
{
  bool active;
  int pin;
  uint32_t channel;
  HardwareTimer *timer;
} Servo_t;

Servo_t servo;

float float_rc(float angle, float min, float max)
{
    float in_min = 0.0;
    float in_max = 180.0;
    float out_min = min / 20000 * 65535;
    float out_max = max / 20000 * 65535;
 
    return (angle - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;  
}

void servo_on()
{
  pinMode(servo.pin, OUTPUT);
  servo.timer->setMode(servo.channel, TIMER_OUTPUT_COMPARE_PWM1, servo.pin);
  servo.timer->setOverflow(PWM_RC_FREQ, HERTZ_FORMAT);
  servo.timer->setCaptureCompare(servo.channel, float_rc(90.0, 1966, 7864), RESOLUTION_16B_COMPARE_FORMAT);
  servo.timer->resume();
  servo.active = true;        
}

void servo_off()
{
  servo.timer->setMode(servo.channel, TIMER_DISABLED, servo.pin);
  servo.timer->pause(); // if I pause the timer also the other associated channels won't work
  pinMode(servo.pin, INPUT);
  servo.active = false;
}

void setup() 
{
  servo.active = false;  
  servo.pin = PIN_SERVO;
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(servo.pin), PinMap_PWM);
  servo.channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(servo.pin), PinMap_PWM));
  servo.timer = new HardwareTimer(Instance);
  servo.timer->setMode(servo.channel, TIMER_DISABLED, servo.pin);
  servo_on();
}

void loop() 
{
  static unsigned long millis_old = 0;

  if (millis() - millis_old > 2000)
  {
    if (servo.active) servo_off();
    else servo_on();

    millis_old = millis();
  }
}

It works but pausing the timer with servo.timer->pause(); inside servo_off() leads to pause also all the other channels of the timer.

Instead I want to disable the current channel only. Making the pin an input.

If I remove the pause() call, I'm not able to get the PWM again in servo_on(). It will stay at high level.

From '#define PWM_RC_FREQ 50' I assume you are generating a PWM signal for a servo or ESC with BLDC motor. Why not use a library? You didn't say exactly which microcontroller you were programming, but maybe this library

would help you.

1 Like

I'm using the Nucleo-144 F429ZI dev board.
I don't want to use third-part libraries because the actual code is very complex and should handle specific features.

I just want to be able to configure a pin as input or as output for hardware PWM like in the test code above.

Okay. At least you can look at the library code to see how it works. That might help you enough.

Thanks. That means you don't see an error in my code?

I don't see an error in your code but it doesn't matter for 2 reasons. First, obviously the code doesn't do what you want. Second, most of the code I'm not sure what it does because it's above my level of understanding. Someone else may be able to help you.

I gave a look at the library: 1. it was released the same day you sent the message (3 days ago). 2. it's a software generated PWM (even if under ISR).

Sorry, but I don't see how it can help me to understand how to reconfigure the timer to enable and disable the PWM for each channel. We're talking about two completely different stuff!

Try it, but not tested.

#define PWM_RC_FREQ        50
#define PIN_SERVO          PE9

typedef struct
{
  bool active;
  int pin;
  uint32_t channel;
  HardwareTimer *timer;
} Servo_t;

Servo_t servo;

float float_rc(float angle, float min, float max)
{
    float in_min = 0.0;
    float in_max = 180.0;
    float out_min = min / 20000 * 65535;
    float out_max = max / 20000 * 65535;
 
    return (angle - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;  
}

void servo_on()
{
  pinMode(servo.pin, OUTPUT);
  servo.timer->pause();
  servo.timer->setMode(servo.channel, TIMER_OUTPUT_COMPARE_PWM1, servo.pin);
  servo.timer->setOverflow(PWM_RC_FREQ, HERTZ_FORMAT);
  servo.timer->setCaptureCompare(servo.channel, float_rc(90.0, 1966, 7864), RESOLUTION_16B_COMPARE_FORMAT);
  servo.timer->resume();
  servo.active = true;        
}

void servo_off()
{
  servo.timer->pause();
  servo.timer->setMode(servo.channel, TIMER_DISABLED, servo.pin);
  servo.timer->resume();
  pinMode(servo.pin, INPUT);
  servo.active = false;
}

void setup() 
{
  servo.active = false;  
  servo.pin = PIN_SERVO;
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(servo.pin), PinMap_PWM);
  servo.channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(servo.pin), PinMap_PWM));
  servo.timer = new HardwareTimer(Instance);
  servo.timer->setMode(servo.channel, TIMER_DISABLED, servo.pin);
  servo_on();
}

void loop() 
{
  static unsigned long millis_old = 0;

  if (millis() - millis_old > 2000)
  {
    if (servo.active) servo_off();
    else servo_on();

    millis_old = millis();
  }
}

It has the same problem: when disabling one channel, also the other 3 channels related to the same timer will be disabled.

Apparently I solved in a very easy way:

void servo_off()
{
  pinMode(servo.pin, INPUT);
  servo.active = false;
}