11

I would like to increase the PWM bit resolution of the Arduino Uno. At this moment it is 8-bit which I consider too low. Is this possible without losing the ability of interrupts and delays?

Koen

EDIT This setup delivers a 16-bit resultion

void setupPWM16() {
    DDRB |= _BV(PB1) | _BV(PB2);        /* set pins as outputs */
    TCCR1A = _BV(COM1A1) | _BV(COM1B1)  /* non-inverting PWM */
        | _BV(WGM11);                   /* mode 14: fast PWM, TOP=ICR1 */
    TCCR1B = _BV(WGM13) | _BV(WGM12)
        | _BV(CS11);                    /* prescaler: clock / 8 */
    ICR1 = 0xffff;                      /* TOP counter value (freeing OCR1A*/
}
/* Comments about the setup
Changing ICR1 will effect the amount of bits of resolution.
ICR1 = 0xffff; (65535) 16-bit resolution
ICR1 = 0x7FFF; (32767) 15-bit resolution
ICR1 = 0x3FFF; (16383) 14-bit resolution etc....

Changing the prescaler will effect the frequency of the PWM signal.
Frequency[Hz}=CPU/(ICR1+1) where in this case CPU=16 MHz
16-bit PWM will be>>> (16000000/8)/(65535+1)=30.5175Hz
*/

/* 16-bit version of analogWrite(). Works only on pins 9 and 10. */
void analogWrite16(uint8_t pin, uint16_t val)
{
    switch (pin) {
        case  9: OCR1A = val; break;
        case 10: OCR1B = val; break;
    }
}
0

2 Answers 2

16

The Arduino Uno is based on an ATmega382P microcontroller. This chip has two 8-bit timers, driving two PWM channels each, and one 16-bit timer, driving the last two channels.

You cannot increase the resolution of the 8-bit timers. You can, however, put the 16-bit timer in 16-bit mode, instead of the 8-bit mode used by the Arduino core library. This will give you two 16-bit PWM channels, with a reduced frequency of 244 Hz (maximum). You will probably have to configure the timer yourself, and will not benefit from the easy to use analogWrite() function. For details, see the section on Timer 1 in in the ATmega328P datasheet.

Update: Here is an implementation of a 16-bit analogWrite(). It only works on pins 9 and 10, as these are the only pins connected to the 16-bit timer.

/* Configure digital pins 9 and 10 as 16-bit PWM outputs. */
void setupPWM16() {
    DDRB |= _BV(PB1) | _BV(PB2);        /* set pins as outputs */
    TCCR1A = _BV(COM1A1) | _BV(COM1B1)  /* non-inverting PWM */
        | _BV(WGM11);                   /* mode 14: fast PWM, TOP=ICR1 */
    TCCR1B = _BV(WGM13) | _BV(WGM12)
        | _BV(CS10);                    /* no prescaling */
    ICR1 = 0xffff;                      /* TOP counter value */
}

/* 16-bit version of analogWrite(). Works only on pins 9 and 10. */
void analogWrite16(uint8_t pin, uint16_t val)
{
    switch (pin) {
        case  9: OCR1A = val; break;
        case 10: OCR1B = val; break;
    }
}

You may notice that the top of the counter sequence is configured explicitly. You can change this to a smaller value to make the PWM faster, at the cost of decreased resolution.

And here is an example sketch illustrating its use:

void setup() {
    setupPWM16();
}

/* Test: send very slow sawtooth waves. */
void loop() {
    static uint16_t i;
    analogWrite16(9, i);
    analogWrite16(10, 0xffff - i);
    i++;
    delay(1);
}
12
  • Wow thank you very much, this is exactly what I need. I want my PWM resultion to be the same as my Sensor Resolution. If I change your code to <look at my edit> would it be a 13-bit resultion? If so, what would the frequency be? I will be driving a DC motor with it so 244Hz will be a bit less I guess
    – KoenR
    Commented Jun 18, 2015 at 6:51
  • @KoenR: No, the prescaler has no effect on the resolution, it's purpose is to slow down the counting. Setting the prescaler to 8 will give you a PWM frequency of 30.5 Hz. If you want 13 bit resolution, set ICR1 to 0x1fff, then your frequency will be 1953 Hz (F_CPU/(TOP+1)) with a prescaler at 1. Commented Jun 18, 2015 at 7:44
  • 1
    @Edgar Bonet This is great, however I can't seem to fully turn off an LED. I'm using ICR1 = 0x03FF and at 0 I'm seeing a tiny pulse on the scope enough to light the led. Any ideas?
    – davivid
    Commented Mar 7, 2017 at 17:03
  • 1
    @davivid: Yes, you cannot have a zero duty cycle. analogWrite16(pin, val) gives a duty cycle of (val+1)/ICR1. As a workaround, Arduino's analogWrite() does if (val == 0) digitalWrite(pin, LOW); else if (val == 255) digitalWrite(pin, HIGH);. But then you cannot get a duty cycle of 1/ICR1... Commented Mar 7, 2017 at 17:34
  • 1
    @Dima: Probably, but that won't be simple. Commented Aug 1, 2022 at 7:20
4

With some calibration you could sum the outputs of two PWM channels with differing weighting resistors. At the extreme you could notionally use one output to provide 8 bits of resolution and scale the other to 1/256th the level and add them so the 2nd channel covers one bit of range and you (again notionally) get 16 bits of resolution. Without immense care and adjustment all you'd get would be a mess.
However by dividing the 2nd channel by 16 or 32 you could add several extra bits of PWM resolution. Just by adding 2 PWM channels analog filtered outputs you add an extra bit (as potential range is doubled for unchanged mV/bit).
Notionally (again) for every additional divide by 2 you get an extra bit of resolution, but this can only be carried out for maybe 4 or 5 or 6 extra bits, with increasing accuracy requirements of scaling resistors and more difficult calibration and proneness to errors.

Brief example.
If one PWM is scaled down to give say 0 - 255 mV in 1 mV step, then summing two PWMs with equal amplitude would give a 0 - 510 mV range in 1 mV steps.
If one PWM is scaled down by a factor of 32 then instead of adding 255 mV to the initial PWMs range it would add only 8 mV to the top end (0.256.32 = 8 mV, but resolution would be in 0.03125 (1/32nd) mV steps.

While this could perhaps be achieved purely with resistor summing and RC filtering, use of an op amp summer would greatly improve results.

Also PWM ripple could be filtered with a simple RC filter but use of one opamp as a buffer (or even just a single transistor as an emitter follower) would give you 3 or 5 poles of low pass filtering and much better chance of achieving extra PWM resolution. I have not inspected the "phase coherence" of the PWM outputs but expect that they move in relative lockstep so you don't get the smoothing advantage of adding two uncorrelated waveforms.

More comment can be made if needed. Ask if interested.

5
  • This is clever! Seems that the Mozzi sound synthesis library uses this trick for it's so called “HIFI” mode. Commented Jun 17, 2015 at 19:40
  • That is a great us of the PWM. But wouldn't this smooth out the waveform? I am asking this since you are using a RC filter. Haven't mentioned this in my question but I'm driving a DC motor with it <feeling ashamed>. Thank you for the input!
    – KoenR
    Commented Jun 18, 2015 at 6:14
  • @KoenR (fwiw: I don't see anything to be ashamed of.) I don't know what frequency response/rate of change you want in your ADC output. Or why you want N bits or how big is enough. Motors will usually not be usefully controlled by more than 8 bits - depends on how precision an application you have. Motor acts as part of a smoothing filter due to inductance. You need to say what sort of motor and how driven. And a circuit diagram is about essential. Unless motor is tiny you have a driver. A brushed motor fed PWM must have a a catch diode to pass motor current when PWM is off. Adding two ... Commented Jun 18, 2015 at 9:01
  • ... PWMs here is entiely doable but circuit details need to be known. Commented Jun 18, 2015 at 9:01
  • Beware! In some cases, smoothing out PWM with a low pass RC is not desirable. E.g., if you plug the Arduino output into the gate of a MOSFET, the MOSFET will stay cold as long as it's driven by clean PWM. But if you smooth it out, the MOSFET will start dissipating much more heat. Sometimes that's not a good thing. Commented Jul 13, 2015 at 6:46

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