PWM with 50% duty cycle at varying frequency without using tone()

Hello,

I am wondering if there is any method of outputting a PWM signal with a 50% duty cycle with a frequency that I can change without using the tone() command.

I cannot use the tone() function as it takes up too much processing time and I require the code to execute as fast as possible (in less than 90 micro seconds).

I have timed my code with the tone() command in it and it takes upwards of 200 micro seconds.

Thank you for your help, I can answer any further questions as best I can.

You can set up Timer1 to do PWM yourself, just like tone() does. Read Nick's great write-up on the subject
timers

What frequency range do you need? For certain ranges and specific output pins you can change frequency by writing one register (less than a microsecond to change frequency). For wider frequency range you may need to change the clock prescale bits in addition to the register containing the TOP value.

Thanks blh64 I’ll have a read through that page and try use it in the code.

In terms of the frequency range John, I need frequencies in the range of around 20hz to 300hz.

Essentially my code will calculate the frequency of an incoming analog signal and then output a PWM signal with the same frequency. This frequency can fluctuate and thus the frequency of the PWM signal will need to fluctuate along with the incoming analog signal within the aforementioned frequency range. Is this possible using the method you mentioned?

I will send a copy of my code tomorrow to make this clearer, apologies for not doing this sooner.

If you use Timer1 and set the 'prescale' to 8 you can easily cover the 20 Hz to 300 Hz range:

16 MHz / 8 = 2 MHz / 20 Hz = 100,000 clocks per cycle = 50,000 clocks per half cycle
16 MHz / 8 = 2 MHz / 300 Hz = 6666 clocks per cycle = 3,333 clocks per half cycle
That gives you about 47000 steps across the 20 Hz to 300 Hz range.

Hi,

I am having some difficulty with the timer1, I have set it up in the manner you mentioned earlier (I'm not sure if I have done this correctly).

This has increased the cycle time of my code from 8 microseconds to roughly 140 micro seconds which would make my code function improperly. When I first open the serial monitor to display the "duration" it starts at around 70-80 microseconds then jumps up to 140 microseconds.

Is this caused by timer1 making the micros(); functions erroneous and if so is there a way to reduce/solve this. Alternatively is there any method of generating the PWM using timer1 without increasing my cycle time so dramatically. Here is my code:

unsigned long totalLength; 
unsigned long spaceLength;
unsigned long markLength;

int start1;
int i;

unsigned long endTime;
unsigned long lowTime;
unsigned long midTime;

unsigned short int x;

int val;

int frequency;
float period;

void setup() {

  Serial.begin(2000000);
  ADCSRA = 0;             // clear ADCSRA register
  ADCSRB = 0;             // clear ADCSRB register
  ADMUX |= (0 & 0x07);    // set A0 analog input pin
  ADMUX |= (1 << REFS0);  // set reference voltage
  ADMUX |= (1 << ADLAR);  // left align ADC value to 8 bits from ADCH register

  // sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
  // for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
  ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz
  //ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz
  //ADCSRA |= (1 << ADPS1) | (1 << ADPS0);    // 8 prescaler for 153.8 KHz

  ADCSRA |= (1 << ADATE); // enable auto trigger
  ADCSRA |= (1 << ADIE);  // enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN);  // enable ADC
  ADCSRA |= (1 << ADSC);  // start ADC measurements

  
  pinMode(3, OUTPUT);
  TCNT1H = _BV(COM1A1) | _BV(CS11) | _BV(WGM13) | _BV(WGM11) | _BV(WGM10);  // this is the section of code used to generate the PWM using timer1 & is based off this https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
  OCR1A = val; 

  start1=0;

}

ISR(ADC_vect)
{
  x = ADCH;  // read 8 bit value from ADC
   
  
}

void loop() {

    unsigned long start = micros();
          
       if (x < 230 && start1==0)    //detects first tooth and starts to read the output signal from the sensor
       {
        
        start1=1;
  
       }

       if (x > 235  && start1==1)    //reads that the sensor has gone low and records the time 
       {
   
        start1=2;
       lowTime = micros();
        
      }

        if (x < 230  && start1==2)    //reads that the sensor has gone high and records the time 
       {
        
        start1=3;
        midTime = micros();

       }
        if (x > 235  && start1==3)    //reads that the sensor has gone high and records the time 
       {
        
        start1=1;
        endTime = micros();
        spaceLength = endTime - midTime;
        totalLength = endTime - lowTime;
        markLength = totalLength - spaceLength;   

       }

       period = totalLength/1000000.0; //to get to micro seconds
       frequency = 1 / (period * 2);  // multiply by two to sort rounding error 

       
       val = 500000/ (frequency * 4); //this calculation is to get val for the PWM generation using timer1

       unsigned long finish = micros();
       unsigned long duration = finish - start;


       
       Serial.print(duration);
       Serial.println();

       

}

I don't think you're using the timer correctly. This example compiles (I don't have a 2560 on hand to test it unfortunately)... It's supposed to generate a squarewave on pin 11 varying between 20Hz and 300Hz each second. If it works, maybe you can incorporate elements into your code:

#define PWM_OC_MAX      50000
#define PWM_OC_MIN      3333

#define FCLKIO          16000000ul
#define PRESCALER       8ul

const byte pinPWM = 11;

void setup( void )
{    
    pinMode( pinPWM, OUTPUT );
    digitalWrite( pinPWM, LOW );
    //TIMER1 setup
    //toggle OC1A pin on compare (COM1A1..0 == 01)
    //WGM CTC mode (WMG13..0 == 0100)
    //Prescaler clkio/8 (CS12..0 == 010)
    //  each tick is 500nS
    //      20  Hz = 50mS = 100,000 ticks; toggle each half cycle or 50000
    //      300 Hz = 3.333mS = 6666 ticks; toggle each half cycle or 3333
    TCCR1A = (1<<COM1A0);
    TCCR1B = (1<<WGM12) | (1<<CS11);
    
    OCR1A = PWM_OC_MAX;     //default PWM is 20Hz
    
}//setup

void loop( void )
{
    static bool
        bPWM = 1;
    static unsigned long
        timeSw=0;

    //should toggle between 20 and 300Hz at 1-second intervals
    if( millis() - timeSw >= 1000ul )
    {
        timeSw = millis();
        if( bPWM == false )
        {
            SetPWM( 300 );    
            bPWM = true;
            
        }//if
        else
        {
            SetPWM( 20 );
            bPWM = false;
            
        }//else
        
    }//if
    
}//loop

void SetPWM( unsigned long ulFreq )
{
    unsigned long
        ulOC;

    ulOC = (unsigned long)((1.0/(float)ulFreq) / (1.0/(float)((FCLKIO/PRESCALER)))) / 2;

    if( ulOC > PWM_OC_MAX )
        OCR1A = PWM_OC_MAX;
    else if( ulOC < PWM_OC_MIN )
        OCR1A = PWM_OC_MIN;
    else
        OCR1A = (unsigned int)ulOC;
    
}//SetPWM

This did not work :slightly_frowning_face: , I presume because I am using an arduino uno and this code was designed for the 2560.

Try changing

const byte pinPWM = 11;

to

const byte pinPWM = 9;