Timing frequency modulation with micros results in wrong period duration for the first pulses

Hello,

for a project i need several (more than timers) pwm signals with different frequencies (principally frequency modulation) without blocking other stuff going on. The pulse-width doesn't matter for the purpose and the jitter isn't a problem either, so i decided to use "software pwm".
I implemented the timing with micros(). The problem is, that the frequency of the first pulses is significantly higher that it should (good to see with an oscilloscope). The amount of "wrong frequency pulses" vary with different timing parameters. I reduced the problem to a few lines of code with only one signal, where the problem also occurs. I am aware that the microseconds will overflow after a while, but i wonder what exacly is going on here, in general? And is there a better way of implementing it?

#define PIN 5

unsigned long pwmTime = 1000000;  // Time in us for pwm signal
unsigned long prevPulseTime = 0;  // Time in us when last pulse occured
unsigned long pulsePeriod = 200;  // pwm period duration in us

unsigned long prevPauseTime = 0;    // Time in us when last pause occured
unsigned long pauseTime = 1000000;  // pause time in us

bool state = 1;  // Enable or disable pwm output

void setup() {

  // Init IO pin
  pinMode(PIN, OUTPUT);
}

void loop() {

  // Some software pwm.
  if ((micros() - prevPulseTime) >= pulsePeriod && state == 1) {
    prevPulseTime += pulsePeriod;
    digitalWrite(PIN, 1);
    digitalWrite(PIN, 0);
  }

  // After a while take a short break from signal.
  if (micros() - prevPauseTime >= pwmTime && state == 1) {
    prevPauseTime += pwmTime;
    state = 0;
  }

  // Continue pwm output after break ends.
  if (micros() - prevPauseTime >= pauseTime && state == 0) {
    prevPauseTime += pauseTime;
    state = 1;
  }
}

I've used an Arduino Nano as well as an ESP8266-12F, both behaved almost identically. For programming i used the Arduino IDE 2.1.1 with all neccessary libraries up to date.

Hello philstern

Welcome to the worldbest Arduino forum ever.

Take the BlinkWithOutDelay example of the IDE and use this example to design your timer() function.

This timer() function might provide the following methodes:

start();
stop(),
event();
isRunning();

Have a nice day and enjoy coding in C++.

1 Like

You have designed and program an "incoherent" system. You must change the pulse length ONLY while the pin is high. You are doing it at any random time and are getting random pulses as a result.

The problem is that the timing doesn't start at 0. The micros clock starts running when the board starts up. But then there's time while init() and setup() run. So when you get to the first timing mark, you've already passed a few time points.

Adding to prevPausTime will cause the code to repeatedly trigger the timing here until it catches up. If you use

prevPauseTime = micros();

that will stop the bad pulses at the beginning, but leaves you with the possibility of short pulses later.

A second option would be to set prevPauseTime and prevPulseTime to micros right at the end of setup. That will result in your timing starting from that point instead of trying to catch up from a start at 0.

1 Like

Here's a thread that discusses (argues) the two approaches to how to advance the previous time. Further down the thread there are a number of examples in code that illustrate the difference. With one method you enforce a minimum pulse length and with the other style you enforce that the pulses will align properly, but you may end up with short pulses as it tried to catch up if it gets behind.

1 Like

Hello,

first of all, thank you all for your replies. @Delta_G describes what is happening here. Only that he problem is not just at the beginning, but inside the loop itself, where for:

  // Some soft pwm
  if ((micros() - prevPulseTime) >= pulsePeriod && state == 1) {
    prevPulseTime += pulsePeriod;
    digitalWrite(PIN, 1);
    digitalWrite(PIN, 0);
  }

obviously the counting of prevPulseTime stops, when the if-statement gets false. As soon as it becomes true again, there is a large time gap that triggers the if-statement in each loop until prevPulseTime catches up. Oddly, changing prevPulseTime += pulsePeriod; to prevPulseTime = micros(); results in strange frequency behavior when frequency is changed. But the solution is also quite simple: Just keep counting if the statement is false. Here is the full code which works just fine for me:

#define PIN 5

unsigned long pwmTime = 1000000;  // Time in us for pwm signal
unsigned long prevPulseTime = 0;  // Time in us when last pulse occured
unsigned long pulsePeriod = 200;  // pwm period duration in us

unsigned long prevPauseTime = 0;    // Time in us when last pause occured
unsigned long pauseTime = 1000000;  // pause time in us

bool state = 1;  // Enable or disable pwm output

void setup() {

  // Init IO pin
  pinMode(PIN, OUTPUT);
}

void loop() {

  // Some soft pwm
  if ((micros() - prevPulseTime) >= pulsePeriod && state == 1) {
    prevPulseTime += pulsePeriod;
    digitalWrite(PIN, 1);
    digitalWrite(PIN, 0);
  } else if (state == 0) {
    prevPulseTime = micros();
  }

  // After a while take a short break from signal.
  if (micros() - prevPauseTime >= pwmTime && state == 1) {
    prevPauseTime += pwmTime;
    state = 0;
  }

  // Continue pwm output after break
  if (micros() - prevPauseTime >= pauseTime && state == 0) {
    prevPauseTime += pauseTime;
    state = 1;
  }
}

It is also possible to get rid of state. Just use prevPulseTime == 0 and set it to zero when the pause starts and reset it after the pause. Thanks again for your help.

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