Switching a pin from analogWrite to digitalWrite on the fly for low frequency PWM

andnate

Member
Hi all,
I'm trying to write a program that takes in a frequency using FreqMeasure and pipes it back out on two pins, as well as piping back out a scaled version of that frequency (say 0.5x for example) on two other pins. analogWriteFrequency has been great for this, but has a lower limit of about 17 Hz on the Teensy 4.1 at default clock speed when I need to be able to get down to ~5 Hz. I was planning on switching to bit-banging at low speeds, but for some reason once a pin has been set using analogWriteFrequency a digitalWrite isn't kicking it out of that mode. That is to say, if I start in my low frequency mode it works fine, and transitions to high frequency mode as input freq rises, but it doesn't switch back, it just gets stuck. It seems like the code is executing properly, but the pin isn't being controlled by the digitalWrite.
Code:
/* FreqMeasure - Example with serial output
 * http://www.pjrc.com/teensy/td_libs_FreqMeasure.html
 *
 * This example code is in the public domain.
 */
#include <FreqMeasure.h>

float lastFreq;
unsigned long currentMillis;
unsigned long previousMillis;
bool pinState;

#define SIG1 6
#define SIG2 5
#define SIG3 4
#define SIG4 3
#define INPUT_PIN 22

#define FREQ_SCALE 0.5 // Scale of SIG3-4 output frequency vs SIG1-2
#define FREQEN 1

float frequency = 0;
 
void setup() {
  Serial.begin(57600);
  if(FREQEN){
    FreqMeasure.begin();
  }
 
  pinMode(SIG4, OUTPUT);
  pinMode(SIG3, OUTPUT);
  pinMode(SIG2, OUTPUT);
  pinMode(SIG1, OUTPUT);
  //analogWriteFrequency(SIG4, frequency);
  //analogWrite(SIG4, 128);
  //analogWriteFrequency(SIG3, frequency);
  //analogWrite(SIG3, 128);
  //analogWriteFrequency(SIG2, frequency);
  //analogWrite(SIG2, 128);
  //analogWriteFrequency(SIG1, frequency);
  //analogWrite(SIG1, 128);
}

double sum=0;
int count=0;

void loop() {
  if (FREQEN && FreqMeasure.available()) {
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 3) {
      frequency = FreqMeasure.countToFrequency(sum / count);
      sum = 0;
      count = 0;
    }
  }
  else if(!FREQEN){
    int analogVal = analogRead(INPUT_PIN);
    frequency = analogVal;
  }
  setFrequency(SIG4, frequency);
  //setFrequency(SIG3, frequency);
  //setFrequency(SIG2, frequency*FREQ_SCALE);
  //setFrequency(SIG1, frequency*FREQ_SCALE);
}

void setFrequency(int pin, float frequency){
  if(frequency > 24){ // Need to transition from fast to slow
    //Serial.print(F("Using PWM Freq = "));
    //Serial.println(frequency);
    analogWriteFrequency(pin, frequency);
    analogWrite(pin, 128);
  } else{
    currentMillis = millis();   // capture the latest value of millis()
    setFrequencySlow(pin, frequency, currentMillis);
  }
}
void setFrequencySlow(int pin, float frequency, unsigned long currentMillis){
  //Serial.print(F("Using SlowPWM Freq = "));
  //Serial.println(frequency);
  unsigned long interval = (float)(1/frequency)*500;
 
  //Serial.println(currentMillis - previousMillis);
    if(currentMillis - previousMillis >= interval){
      digitalWrite(pin, pinState);
      pinState = !pinState; // Separate from a digitalRead() to transition smoother
      
      Serial.println("Write OK!");  // debug
      Serial.println(pinState);     // debug
      previousMillis = currentMillis;
    }
}
 
The pin is most likely not in digital mode...
If you are transitioning between fast and slow, you probably need to change the IO MUX register to mode 5...
Easiest way is to call pinMode(pin, OUTPUT);

You can probably short cut some of the code with something like:
Code:
*(portConfigRegister(pin)) = 5 | 0x10;
 
Easiest way is to call pinMode(pin, OUTPUT);
That did it, thank you!
I'm also seeing a glitch in the output waveform when it transitions from "slow" to "fast". I'm planning on adding in some sort of hysteresis to stop it from fluttering around this point, but I'd still like to eliminate the glitch if possible.

Is there a way to tell the PWM timer to reset its period so it doesn't get switched to mid-period?
TeensyTransitionS-F.png
 
Back
Top