Generating voltages using PWM with an Arduino microcontroller.
Get better results from using PWM as a DAC
PWM Modes: Choose the right mode of PWM
Limitations of RC low pass filter
Higher frequency PWM
How to change the PWM frequency
Use a better filter
Testing PWM with an active filter
Sketch to experiment with PWM ADC
Mode 0 - Phase correct PWM
In PC PWM the timer counts from 0 to 255 and back down. This is suitable for most purposes
In one cycle the count goes 0, 1, 2, ... 253, 254, 255, 254, 253, ... 2, 1 - then repeats
Note the 0 and 255 values only occur once in each cycle, while all the other values occur twice;
so there are NOT 256 counts in a cycle, but 510.
For a value of n ( n between 0 and 255) the duty cycle is D = n / 255
Mode 1 - Fast PWM
Fast PWM simply counts from nMin to nMax (typically 0 to 255) repetitively;
in one cycle the count goes from 0, 1, 2 ... 253, 254, 255; then repeats - so there are 256 possible.
For a value of n ( n between 1 and 255) the duty cycle is D = n + 1/ 256
(NB The timer sets D = 0 if n = 0, so there is a discontinuity between 0 and 1)
Because the total count is only 256 the available frequencies are twice that for PC PWM
Limitations of RC low pass filter
We saw on the previous page that
1: the simple RC filter doesnt do a very good job of removing the PWM frequency from the dc output.
An easy way to improve that is to
- use a higher frequency PWM signal, or
- use a better filter
2: if the output is loaded the voltage, frequency and step responses will change.
It can easily be buffered using an "op amp" as shown here: However if we are going to use an op amp we can at the same time get better filtering.
Higher frequency PWM
A PWM frequency of only 500 - 1000 per second imposes a major limitation on the rate at which the filtered output can change.
We call that the "SLEW RATE".
We can improve on this by increasing the PWM rate, by changing the frequency divider for the appropriate timer.
- Timer0 - An 8 bit timer used by Arduino functions delay(), millis() and micros(). Pins 5 & 6
- Timer1 - A 16 bit timer used by the Servo() library. Pins 9 & 10
- Timer2 - An 8 bit timer used by the Tone() library. Pins 3 & 11
We generally avoid changing Timer0 because of the problems it raises with the timing functions.
I've chosen to use pin 9 (Timer1) to avoid this.
The base frequency for Timer1 is 31250 Hz, and divisors of 1, 8, 64, 256, and 1024 are available;
these give PC PWM frequencies of 31,250Hz; 3906Hz; 488Hz; 122Hz; 30.5 Hz; (488Hz is the default)
Fast PWM freqencies are double those above.
So lets look at using a higher frequency - 3.9kHz
How to change the PWM frequency
We can change the base PWM frequency to a higher value to make filtering easier and achieve faster slew rates. Its done (on the UNO and similar Arduinos) by
changing the value in the appropriate timer control register, as shown here.
void setPwmFrequency(int pin, int divisor) { //"pin" is the number of the PWM pin being used
byte mode;
if (pin == 5 || pin == 6 || pin == 9 || pin == 10) {
switch (divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 64: mode = 0x03; break;
case 256: mode = 0x04; break;
case 1024: mode = 0x05; break;
default: return;
}
if (pin == 5 || pin == 6) {
TCCR0B = TCCR0B & 0b11111000 | mode; // set PWM rate for Timer 0
}
else { // pin is 9 or 10
TCCR1B = TCCR1B & 0b11111000 | mode; // set PWM rate for Timer 1
}
} else if (pin == 3 || pin == 11) {
switch (divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 32: mode = 0x03; break;
case 64: mode = 0x04; break;
case 128: mode = 0x05; break;
case 256: mode = 0x06; break;
case 1024: mode = 0x07; break;
default: return;
}
TCCR2B = TCCR2B & 0b11111000 | mode; // set PWM rate for Timer 2
}
}
https://docs.arduino.cc/tutorials/generic/secrets-of-arduino-pwm/
Use a better filter
The simple RC filter has several disadvantages.
1: If you load it the output voltage and frequency response will be affected.
2: the high frequencies are only attenuated by a factor of 0.1 for each decade:
so you need a big difference between the PWM frequency and the highest frequency you want to generate.
Use a "second order" low pass filter
This simple circuit uses a single "rail-rail" op amp with two resistors and two capacitors.
1: The op amp buffers the voltage so ( within reason) loading will not change the voltage at the output.
2: high frequencies are attenuated by a much better factor of 0.01 per decade
Component values:
For a PWM rate of 3.9 kHz or higher this filter with a 100 Hz "corner frequency" will give good results.
So you need R1 = R2 = 22k; C1= 0.1uF; C2=0.047uF
A suitable op amp is the MCP6002 with supplies of +Vcc and 0V (Vcc = 2.0V to 6.0V)
Testing PWM with an active filter
Here the ADS1115 is used to provide an accurate measurement of Vout; however you could also just connect Vout to one of the Analog inputs and use the "DEFAULT" Reference as this is a ratiometric measurement (ie the actual PWM voltage and analog reference are both equally dependent on Vcc)
Get the sketch
In the sketch you can choose divider frequency, PWM mode, Wave shape (static, step or ramp) and duration.
The output on the serial monitor shows the time in msec, expected and measured voltages.