1

I have a system which requires me to obtain different duty cycles at different frequencies. But using the analogWrite() does not allow me to obtain a value lower than 1/255 duty cycle. I need to obtain lower duty cycle values for 1Hz

  • 1
    Is it OK if, at 1 Hz, you are restricted to pulse widths which are multiples of 16 µs? E.g. you would have 496 µs instead of 500. If so, you can configure Timer 1 to generate the PWM signal on pins 9 or 10. A signal generated this way would have more consistent timing than a similar signal generated by software (e.g. using micros()). – Edgar Bonet Oct 11 '17 at 15:51
  • @EdgarBonet - could you explain where are you getting the 16us from? Several prescalar and count value possibilities will yield 500us, but I'm not immediately thinking of any that yield your numbers. – Chris Stratton Oct 11 '17 at 16:11
  • Prescaler = 256 → resolution = 16 us and maximum period = 1.048576 s. – Edgar Bonet Oct 11 '17 at 16:14
  • Ok, the maximum interval is a point - but that doesn't have to come from the same timer. – Chris Stratton Oct 11 '17 at 16:16
3

Expanding from my comment. This is an attempt to generate that signal with Timer 1. No time to test it now... Edit: tested, works as advertised.

/*
 * Configure Timer 1 to generate a slow, low duty cycle PWM signal.
 * Output pin = digital 9 = PB1 = OC1A.
 */

const float PWM_PERIOD  = 1.0;     // 1 s
const float PULSE_WIDTH = 500e-6;  // 500 us

/*
 * With a 16 MHz clock and this prescaler, the resolution is 16 us and
 * the maximum period is 1.048576 s.
 */
const uint16_t TIMER_PRESCALER = 256;
const float F_TIMER = F_CPU / TIMER_PRESCALER;

void setup()
{
    // Configure Timer 1.
    DDRB  |= _BV(PB1);    // set PB1 = OC1A as output
    ICR1   = round(PWM_PERIOD * F_TIMER) - 1;
    OCR1A  = round(PULSE_WIDTH * F_TIMER) - 1;
    TCCR1A = _BV(COM1A1)  // non-inverting PWM on OC1A
           | _BV(WGM11);  // fast PWM mode, TOP = ICR1
    TCCR1B = _BV(WGM12)   // ditto
           | _BV(WGM13)   // ditto
           | _BV(CS12);   // clock at F_CPU/256 = 62.5 kHz
}

void loop() {}
  • Thank you for this, It works great but I would like to get the same thing for 2 different pins and I have no idea about registers. Can you tell me how can I make this code applicable for pin 10 and pin 8? – allta Oct 17 '17 at 22:21
  • @allta: You can't. Hardware-based PWM only works on pins OCnA and OCnB, where n = 0, 1, or 2 is the index of the timer you use. The small duty cycle you want can only be achieved with a 16-bit timer, and you have only one such timer on the Uno: Timer 1. Thus you can only use pins 9 (OC1A) and 10 (OC1B). C.f. the Arduino Uno pinout. See also the tutorial Timers and counters by Nick Gammon. – Edgar Bonet Oct 18 '17 at 7:52
  • Thank you again It's ok we can use the pin10 we won't use the pin 9 and 10 simultaneously. Will changing OC1A with OC1B would work for pin 10? How can I modify this code for pin 10? – allta Oct 18 '17 at 14:12
  • @allta: Yes. OCR1B controls pin 10 in the same way as OCR1A controls pin 9. – Edgar Bonet Oct 18 '17 at 15:44
  • void setup() { // Configure Timer 1. DDRB |= _BV(PB2); // set PB1 = OC1A as output ICR1 = round(PWM_PERIOD * F_TIMER) - 1; OCR1B = round(PULSE_WIDTH * F_TIMER) - 1; TCCR1A = _BV(COM1B1) // non-inverting PWM on OC1B | _BV(WGM11); // fast PWM mode, TOP = ICR1 TCCR1B = _BV(WGM12) // ditto | _BV(WGM13) // ditto | _BV(CS12); // clock at F_CPU/256 = 62.5 kHz } didn't work I don't get how can I modify the last part correctly – allta Oct 18 '17 at 16:56
2

Sure - if you program it to.

  1. Turn on an output.
  2. Wait for 500µs less the amount of time it took to turn on the output
  3. Turn off the output
  4. Wait for 0.9995 seconds minus the amount of time it took to turn off the output
  5. Go to 1.

You would probably find using the micros() function easiest to get the timing right - in exactly the same way as the BlinkWithoutDelay example in the IDE.

Also direct port manipulation may be of benefit to make things as fast as possible.

  • You must also account for the time spent outside loop. There is some conditional code executed out there (plus calls, returns and interrupts), so execution time is variable from pass to pass. That's the problem with delay: you don't know how much time will pass before the next execution of loop. Better use micros. – user31481 Oct 11 '17 at 17:04
  • Yes, hence my instruction to use micros. – Majenko Oct 11 '17 at 17:04
  • Sorry. I was aware of your original micro recomendation. I was just adding reasons for it. I didn't express myself correctly. – user31481 Oct 11 '17 at 17:10
0

Sure. Two easy way to do it.

  1. 0,05 percent is 1:2000. So modify timer settings to produce a pen of the desired frequency and duty cycle.

  2. Use timer interrupt: either the overflow or compare match interrupt. In the first case, load the timer with -duty cycle and -(period - duty) to produce a wave form of duty cycle / period.

The same can be done using compare match as well: just advance the match point alternately.

edit: to show how it can be done with compare match, here is what I have:

#define PR_PWM          (F_CPU / 256 / 10)      //period for the pwm waveform
#define PR_ON           (PR_PWM * 25 / 100)         //prescaler at 256:1, 25% duty cycle
#define PR_OFF          (PR_PWM - PR_ON)            //pin off period, to achieve 1Hz
//end hardware configuration

//global defines

//global variables

//timer1 ch a compare match isr
ISR(TIMER1_COMPA_vect) {
    static char pr_index=0;     //0->PR_ON, 1->PR_OFF

    //clear the flag  -  done automatically
    //advance to the next match point
    if ((pr_index++ & 0x01) == 0) {
        OCR1A += PR_ON;
        IO_SET(PWM_PORT, PWM);  //set the pin on
    } else {
        OCR1A += PR_OFF;
        IO_CLR(PWM_PORT, PWM);  //clear the pin
    }

}

for this demo, we tried to generate a 25% duty cycle pwm with a period of 100ms. Here is the output of that code.

enter image description here

The follow changes the duty cycle to 0.05%:

#define PR_ON           (PR_PWM * 0.05 / 100)           //prescaler at 256:1, 0.05% duty cycle

and the corresponding output is:

enter image description here

Now, there are other ways to do it, for example, with higher resolution, or more precision, ..., but at the cost of more coding.

But the basic concept is the same.

hope it helps.

edit2: for those brave souls, the isr can be simplified to:

OCR1A += (pr_index++&0x01)?PR_OFF:PR_ON;  //advance the match point
IO_FLP(PWM_PORT, PWM);  //flip the pwm pin
  • 1
    A pen? It must be a typo – user31481 Oct 11 '17 at 17:13
  • @LookAlterno Unless he means to draw the waveform on a piece of paper...?! – Majenko Oct 11 '17 at 22:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.