14

I currently can set four PWM pins to around 31 kHz with the following code:

void setup()
{
    TCCR1B = TCCR1B & B11111000 | B00000001; // Set PWM frequency for D9 & D10:
    pinMode(pwmPin9, OUTPUT); // Sets the pin as output
    pinMode(pwmPin10, OUTPUT); // Sets the pin as output


    TCCR2B = TCCR2B & B11111000 | B00000001; // Set PWM for D3 & D11
    pinMode(pwmPin3, OUTPUT); // Sets the pin as output
    pinMode(pwmPin11, OUTPUT); // Sets the pin as output
}

I found this setup somewhere, but I don't know how I can set these four PWM pins to around 25 kHz instead. How is that possible?

gre_gor
  • 1,669
  • 4
  • 18
  • 28
user16307
  • 227
  • 2
  • 4
  • 14
  • 3
    Do you understand how the AVR timers work? – Ignacio Vazquez-Abrams Jun 24 '16 at 01:04
  • 3
    See [my page about timers](http://www.gammon.com.au/timers). – Nick Gammon Jun 24 '16 at 01:06
  • 1
    @IgnacioVazquez-Abrams Im not familiar and I need to set those four pins to around 25kHz at the beginning . I have hurry to finish a project and I would be glad any help. The code I have sets to 31kHz. Can I modify it to 25kHz? DC motors require that freq. – user16307 Jun 24 '16 at 01:08
  • 1
    @NickGammon Thanks but I really dont have enough time to study these at the moment. Could you provide me the code part to setup 25kHz. Im lost – user16307 Jun 24 '16 at 01:12
  • Why four pins? Are they all going to have different duty cycles? Why not one pin and send it to four different places? – Nick Gammon Jun 24 '16 at 02:24
  • `I currently can set four pwm pins to around 31kHz with the following code` - I just tried your code and it did not output anything. – Nick Gammon Jun 24 '16 at 06:06
  • 2
    I need to tune their exact rpm so their duty cycles will be slightly different. How about is it possible to set 2 pins only to 25kHz? – user16307 Jun 24 '16 at 07:26
  • This code can be easily adapted to set the frequency to 31400, 3920, 490, 123 or 30.6 Hz. Other frequencies would not be so simple, and will most likely involve a change in the resolution of the PWM output. Can you live with a resolution of only 41 steps? Or 80 steps but loosing the “phase correct” feature? Or 321 steps but only on pins 9 and 10? – Edgar Bonet Jun 24 '16 at 09:06
  • I think the last option would be great then I use two Ard. boards. Can you help me with that part? Im not familiar – user16307 Jun 24 '16 at 10:51
  • While discussing with Gerben, I just thought about a fourth possibility: You could slow down the main system clock to 8 MHz and use the three timers. You would then have 4 PWM channels at 25 kHz with 161 steps on a single Arduino Uno. The cost would be loosing the timing functions (`millis()`, `delay()` and co.), having to adjust the serial baud rates, and having the whole program run half as fast. Would that be an interesting option? – Edgar Bonet Jun 24 '16 at 19:22
  • @EdgarBonet Thanks for your detailed great answers. In my current code I can control 31kHz four pwm pins. At the same same time I can use LCD screen(I use delay() here to prevent flickering) and baud-rate is 9600. So if I use your 8MHz suggestion, which baudrate should I use? – user16307 Jun 25 '16 at 10:53
  • With the CPU @ 8 MHz, if you `Serial.begin(19200)`, the actual baud rate will be 9600 bps. `delay()` may kind of work, but in units of 39.0625 µs. – Edgar Bonet Jun 25 '16 at 11:11
  • 1
    The best material explaining PWM with the AVR timers I have found so far is the YouTube video *"[8. Arduino Timers and Counters](https://www.youtube.com/watch?v=faCVhp7gm-g)*" (1 h 00 m 08 s). – Peter Mortensen Feb 13 '18 at 00:32
  • searched this out, it's right what I need. How can modify it to output about 14 KHz? in fact between 11 - 19 khz OK. Thanks – laoadam Jan 16 '19 at 00:07

2 Answers2

12

You can configure Timer 1 to cycle at 25 kHz in phase correct PWM mode, and use it's two outputs on pins 9 and 10 like so:

// PWM output @ 25 kHz, only on pins 9 and 10.
// Output value should be between 0 and 320, inclusive.
void analogWrite25k(int pin, int value)
{
    switch (pin) {
        case 9:
            OCR1A = value;
            break;
        case 10:
            OCR1B = value;
            break;
        default:
            // no other pin will work
            break;
    }
}

void setup()
{
    // Configure Timer 1 for PWM @ 25 kHz.
    TCCR1A = 0;           // undo the configuration done by...
    TCCR1B = 0;           // ...the Arduino core library
    TCNT1  = 0;           // reset timer
    TCCR1A = _BV(COM1A1)  // non-inverted PWM on ch. A
           | _BV(COM1B1)  // same on ch; B
           | _BV(WGM11);  // mode 10: ph. correct PWM, TOP = ICR1
    TCCR1B = _BV(WGM13)   // ditto
           | _BV(CS10);   // prescaler = 1
    ICR1   = 320;         // TOP = 320

    // Set the PWM pins as output.
    pinMode( 9, OUTPUT);
    pinMode(10, OUTPUT);
}

void loop()
{
    // Just an example:
    analogWrite25k( 9, 110);
    analogWrite25k(10, 210);
    for (;;) ;  // infinite loop
}

Writing a value of 0 with analogWrite25k() means the pin will be always LOW, whereas 320 means always HIGH. The regular analogWrite() should almost work, but it will interpret 255 the same as 320 (i.e. always HIGH).

This code assumes an Arduino Uno or similar board (ATmega168 or 328 @ 16 MHz). The method used here requires a 16-bit timer, and thus it uses Timer 1 as it's the only one available on the Uno; that's why only two outputs are available. The method could be adapted to other AVR-based boards with a 16-bit timer. As Gerben noted, that timer should have a corresponding ICRx register. There are 4 such timers on the Arduino Mega, each with 3 outputs.

Edgar Bonet
  • 39,449
  • 4
  • 36
  • 72
  • 1
    It might be useful to explain that this method only works for timer1, as the other timers don't have a `ICRx` register. At most, you can only have one PWM pin per timer, for timers 0 and 2. – Gerben Jun 24 '16 at 13:04
  • 1
    @Gerben: Don't all the 16-bit timers have that register? At least on the Mega they do. – Edgar Bonet Jun 24 '16 at 13:09
  • 1
    Yes, but only timer1 is 16-bit on the ATMega328. The rest are 8-bit. And the OP wants 4 PWM output, and your solution only provides 2. Or am I mistaken? – Gerben Jun 24 '16 at 15:12
  • 1
    @Gerben: No, you are right. I just say that requiring ICRx seems to be redundant with requiring the timer to be 16-bits. For the Uno and Mega at least, not sure about other AVR-based Arduinos. The OP understand this only provides 2 PWM channels: see my comment on his question and his answer. – Edgar Bonet Jun 24 '16 at 15:48
  • On the ATMega328 (which the OP is using) only timer1 has the `ICRx` register. For the others you'd have to use `OCRxA` as TOP, which will make it impossible to use PWM on the `OCxA` pin. The OP said he/she was okay with only two PWM pins, but then says he/she would be using 2 Arduinos. It should still be possible to do with only 1 Uno, if the OP's willing to loose `millis` and `delay`. – Gerben Jun 24 '16 at 16:16
  • @Gerben: Only one Uno? Are you talking about the other two options I proposed (i.e. setting the prescalers to 8)? Oh, one could also change the frequency of the main clock and have 161 steps, I hadn't though about that one. Is that what you have in mind? – Edgar Bonet Jun 24 '16 at 17:24
  • I wasn't really following the main comment thread. I was indeed talking about your 41 step alternative. (I'm not sure if the OP really needs more thatn 41 steps for RPM control.) – Gerben Jun 24 '16 at 18:36
  • @Edgar Bonet I am trying to follow this and generate the same 25khz on pin 8 of arduino mega which is controlled by TIMER 4; I dont see any success; could you please help? – techniche Sep 14 '17 at 19:08
  • 2
    @techniche: 1. Works for me. Maybe you forgot to set `COM4C1` in `TCCR4A`? 2. If that's not the problem, then read [How do I ask a good question?](https://arduino.stackexchange.com/help/how-to-ask), then update [your question](https://arduino.stackexchange.com/questions/44750) by including your full source code and clearly stating what you expect the program to do and what it does instead (“I dont see any success” is not considered a valid problem statement). – Edgar Bonet Sep 15 '17 at 08:43
  • @EdgarBonet - thanks a lot for the response and help; I am able to generate the pwm signal and someone else verified it with an alpine 64 gt fan and it seems to work; its not working with the fan I have https://www.amazon.in/StorinTM-CPU-Cooler-Cooling-Connector/dp/B06XJV1SH8/ It is a standard CPU fan with 4 wires and its infact used a lot; could it be an issue with the fan and the pwm wire is just for show off? – techniche Sep 18 '17 at 07:39
12

I am posting this second answer since I realized it is possible to have 4 PWM channels at 25 kHz with 161 steps on a single Arduino Uno. This involves changing the main clock frequency to 8 MHz, which has some side effects since the whole program will run half as fast. It also involves reconfiguring the three timers, which means loosing the Arduino timing functions (millis(), micros(), delay() and delayMicroseconds()). If these trade-offs are acceptable, here is how it goes:

void setup()
{
    // Set the main system clock to 8 MHz.
    noInterrupts();
    CLKPR = _BV(CLKPCE);  // enable change of the clock prescaler
    CLKPR = _BV(CLKPS0);  // divide frequency by 2
    interrupts();

    // Configure Timer 0 for phase correct PWM @ 25 kHz.
    TCCR0A = 0;           // undo the configuration done by...
    TCCR0B = 0;           // ...the Arduino core library
    TCNT0  = 0;           // reset timer
    TCCR0A = _BV(COM0B1)  // non-inverted PWM on ch. B
        | _BV(WGM00);  // mode 5: ph. correct PWM, TOP = OCR0A
    TCCR0B = _BV(WGM02)   // ditto
        | _BV(CS00);   // prescaler = 1
    OCR0A  = 160;         // TOP = 160

    // Same for Timer 1.
    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1  = 0;
    TCCR1A = _BV(COM1A1)  // non-inverted PWM on ch. A
        | _BV(COM1B1)  // same on ch. B
        | _BV(WGM11);  // mode 10: ph. correct PWM, TOP = ICR1
    TCCR1B = _BV(WGM13)   // ditto
        | _BV(CS10);   // prescaler = 1
    ICR1   = 160;

    // Same for Timer 2.
    TCCR2A = 0;
    TCCR2B = 0;
    TCNT2  = 0;
    TCCR2A = _BV(COM2B1)  // non-inverted PWM on ch. B
        | _BV(WGM20);  // mode 5: ph. correct PWM, TOP = OCR2A
    TCCR2B = _BV(WGM22)   // ditto
        | _BV(CS20);   // prescaler = 1
    OCR2A  = 160;
}

void loop()
{
    analogWrite( 3,   1);  // duty cycle = 1/160
    analogWrite( 5,  53);  // ~ 1/3
    analogWrite( 9, 107);  // ~ 2/3
    analogWrite(10, 159);  // 159/160
}

Unlike the other answer, this does not need a modified version of analogWrite(): the standard one will work fine. Only care should be taken that:

  1. The value written should be between 0 (meaning always LOW) and 160 (always HIGH), inclusive.
  2. Only pins 3, 5, 9 and 10 are available. Attempting to analogWrite() to pins 6 or 11 will not only fail to deliver a PWM output, it will also change the frequency on pin 5 or 3 respectively.
Edgar Bonet
  • 39,449
  • 4
  • 36
  • 72
  • Been very long time and now I'm stuck with the same thing with Arduino Due which uses another processor. I would be glad if you hae any input here https://arduino.stackexchange.com/questions/67053/trouble-with-setting-the-pwm-frequency-for-arduino-due – user16307 Jul 14 '19 at 16:34