Inaccurate PWM Signal Frequency and Timing

Status
Not open for further replies.

canberk

New member
Hello all,

I have a Teensy 3.2 and I am using the Arduino IDE to program it. I am trying to generate 8 pulses with the frequency of 40 kHz. I have written 3 different codes and none of them is working quite the way I wanted. The duty cycle of the pulses are not always constant, indicating an inaccuracy with the delay in the code. There seems to be an issue with delays not being consistent and changing within the 8 pulses and between the 8 pulses. This inaccuracy is different between the 3 different codes, but exists in all of them. Changing the processor speed between 16 MHz and 120 MHz does not appear to change the problem (except in code 3 where NOP is dependent on it). Is there a way to make the cycles more consistent at 40 kHz? Do I need to use an external timer or oscillator? The two images below display the two problems. The first image shows the differences in time delays and output frequency. The second image shows a recurring issue where the square wave is not in fact a square, and doesn't seem to be caused by software problems. I couldn't figure out how to display them so I had to link to where I uploaded them.

Image 1:
https://ibb.co/jggDMd

Image 2:
https://ibb.co/jqxngd


Code 1:
Code:
const uint8_t pin1 = 5;
const uint8_t pin2 = 6;

void setup() {
  analogWriteFrequency(pin1, 40000);
  analogWriteFrequency(pin2, 40000);
}

void loop() {
  analogWrite(pin1,128); // for a 50:50 square wave
  delayMicroseconds(188);
  analogWrite(pin1,0);
  delay(1000);
}
Code 2:
Code:
const uint8_t pin1 = 5;
const uint8_t pin2 = 6;
int count;
void setup() {
  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
}

void loop() {
  PWMSignal();
  delay(1000);
}

void PWMSignal(){
  for(int i =0; i<=8; i++){
      analogWrite(pin1, 255);
      analogWrite(pin2, 0);
      delayMicroseconds(10);
      analogWrite(pin1, 0);
      analogWrite(pin2, 255);
      delayMicroseconds(10);
      analogWrite(pin2, 0);
  }
}

Code 3:
Code:
const uint8_t pin1 = 5;
const uint8_t pin2 = 6;
const int N = 50;
#define NOP __asm__ __volatile__ ("nop\n\t")

void setup() {
  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
}

void loop() {
  PWMSignal();
  delay(100);
}

void PWMSignal(){
  for(int i =0; i<=8; i++){
      analogWrite(pin1, 255);
      analogWrite(pin2, 0);
      delayNOP();
      analogWrite(pin1, 0);
      analogWrite(pin2, 255);
      delayNOP();
      analogWrite(pin2, 0);
  }
}

void delayNOP(){
  for(int j =0; j<=N; j++){
    NOP;
  }
}
 
Can‘t understand why you mess everything up with your delays and nops, and with changing the duty cycle quicker than the PWM frequency itself... Obviously, you have not understood how PWM works on the Teensy.

Set the analogWriteFrequency to 40000Hz for pin5 once In setup(). Since pins 6,9,10,20,21,22,23 are part of the same Flextimer, they will operate at the same PWM frequency. Afterwards, set the resolution analogWriteResolution(8) for a system wide 8bit PWM resolution.

Finally, set the duty cycle with analogWrite(pin,0) to 0%, or analogWrite(pin,128) to 50%, or analogWrite(pin,255) to 99.9%, or any other intermediate value. From that moment on, the PWM Signal will be there with the selected duty cycle, without rewriting it again and again in loop(). You only need to use analogWrite() again, when you want to change the duty cycle. The corresponding FTM channel will end its current cycle (25us with your 40kHz) though, before starting the new cycle with a new value. So, doing more than one analogWrite per channel and per 25us makes absolutely no sense and will result in the timer channel to adopt only the last value which was written during the current cycle.

The analogWriteFrequency function is there to make abstraction of the BUS clock which clocks the timer, and which is different from the CPU clock. It will take the actual F_BUS and calculate the appropriate timer mod and prescaler values automatically. Thus, changing the CPU clock does not affect the PWM frequency, you have full control over the latter.
 
Example code:

Code:
  const uint8_t pin1=5;
  const uint8_t pin2=6;
  uint8_t duty1, duty2;

void setup() {
  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
  analogWriteResolution(8);
  analogWriteFrequency(pin1,40000);  //pins 6,9,10,20,21,22,23 follow automatically
}

void loop() {
  for(uint8_t i =0; i < 8; i ++) {
    duty1 = 30 * i + 15;  // will consecutively generate increasing duty cycles
    duty2 = 255 - duty1;  // will consecutively generate decreasing duty cycles
    analogWrite(pin1, duty1); // start/change PWM with duty cycle duty1 on pin1
    analogWrite(pin2, duty2); // start/change PWM with duty cycle duty2 on pin2
    delay(1000); // enjoy stable PWM during one second before the next change occurs
  }
}
 
I believe you're seeing the effects of two separate issues.

First, the code in analogWrite() handles the cases of 0 and 256 as special cases. It immediately turns off the PWM hardware and drives the pin low or high. That's not ideal and probably should be improved. However, some hardware has limits about producing a 100% low or high output from the PWM hardware, so most implementations of analogWrite() handle the min and max as special cases. Teensy's analogWrite does this too, only because it's the norm for all Arduino boards, even though it's probably not the best way (if the PWM hardware can indeed produce a glitch-free continuous high & low output).

All the other cases are handled by writing to the PWM hardware, which is double buffered. So when you set the PWM to a new value (1 to 255), the change takes effect at the beginning of the next cycle.

The second problem is lack of synchronization between the code's timing and the actual PWM cycle. Even if you use delays that add up to the PWM cycle time, the non-delay code and occasional interrupts will cause the rest of the code's timing to vary slightly. To really make this sort of thing work, you're going to need to somehow sync up to the actual PWM cycle, either by polling the timer registers or configuring an interrupt, or maybe even by hardware triggered DMA. There are many possible ways, but the bottom line is you need to be in sync with the phase of the PWM cycle to reliably manipulate the hardware for a fixed number of pulses.
 
Why did you make it gradually increase and decrease the duty cycle? I didn't mention this in my question. What I wanted to generate was eight pulses of 40 kHz frequency and 50% duty cycle that is repeated in every second. I solved the issues by disabling the interrupts during the generation of burst of pulses and then re-enabling them afterwards. The image below (or https://ibb.co/ejK9wd) shows the final output. It is exactly what I have described in my question. If you have a better solution feel free to share it.

TEK0003.JPG
 
That was a misunderstanding... I had understood that you wanted to generate 8 different PWM signals. Please accept my apologies for not being a native English speaker. Most forum questions here are about varying the PWM duty cycle, i.e. for motor or servo control. If I look at your picture, it's rather about a stable 50:50 pulse burst. That's a different challenge...
 
Status
Not open for further replies.
Back
Top