Introduction: PWM (Pulse-Width Modulation) for Digital Audio Conversion
Pulse width modulation (PWM) technique has been widely used for converting digital audio stream into audio signal since it is very simple and easy to implement. Many low-cost micro controllers include dedicated PWM controllers as their standards, and many engineers use it for many purposes. In microcontroller, the PWM controller is implemented as timer or counter that runs continuously to increment the counter at every clock tick and reset every time the counter reach a certain configurable value, the PWM period. Each time the counter value is incremented, it will be compared with the PWM active factor value to get the output. If the counter value is less than this active factor value then the output will be “high”, and will be “low” otherwise. This result in a controllable pulse width (by the active factor value) that spans during the counter increment from zero to the active period (when the counter value is less than the active factor). This active factor, which is simply called as PWM value, is then can be regularly updated with audio stream data to produce audio PWM output that can be further filtered to reproduce the audio signal by removing the PWM frequency component (1/PWM period) which is much higher than the audio frequency components. Most microcontroller’s PWM module implementation can be represented by block diagram shown in the Figure 1.
The clock source is normally configurable to select the clock from many sresources, and the clock prescaler is configurable to provide more frequency option by dividing the clock source by the prescaler values. The reload/reset value is provided to automatically reset the counter when a counting up mode is selected, when the counter value reach the reset/reload value. If counting down mode is selected, then it will auto-reload the counter with the reset/reload value if the counter overflows from decrementing the zero value, so the counting down will restart from the reloaded value. In other terms, this reset/reload value will determine the PWM period when the system is configured in PWM mode (for your information, the same system is also configurable to provide other function than producing PWM output). Please note that the PWM period put a limit on the PWM resolution, more solution means lower PWM frequency, and lower resolution means higher PWM frequency. For example an STM32F103 microcontroller has 16 bit counter for PWM generation, and the clock source can be configured to provide maximum of 72MHz to increment the counter. If we want a 10-bit resolution, then we can set an auto-reload/reset value of 1024 for the PWM period, so we get a PWM frequency of (72,000,000/1024) Hz or 70.312 KHz. We can increase the resolution to 11-bit and we will get 35.156 KHz PWM frequency. Increasing for more resolution is not acceptable for audio application since the PWM frequency should be much higher (at least twice) than the sampled audio signal’s frequency. In some applications, it is acceptable to limit the audio frequency range to around 15 KHz (half of the PWM frequency) with 11-bit resolution, but it won’t be acceptable in professional audio or recording studio standard.
Dual 8-bit PWM for Converting 16-bit Audio Data
To improve the PWM frequency and the resolution with the limited capability of the hardware, we can use two 8-bit resolution PWM generators in parallel configuration. The most significant byte is sent to high byte PWM and the least significant byte is sent to le lower byte PWM. The output of the lower byte PWM is attenuated by 1/256 factor and then mixed with the high byte PWM output. The block diagram of this system is shown in the Figure 2, which I first saw this configuration in audio software codec for STM8S application note.
Auto-Calibration for Dual PWM Audio Output for STM32F103 Microcontroller
The dual PWM circuit shown in the Figure 2 is very simple, but attenuating the low byte PWM output with such small factor of 1/256 require high precision resistors (R1, R2). The resistor value proportion of R1 and R2 should be exactly 1:256, and we just couldn’t find a single resistor with such exact value, and even it exists then it always has some deviation within a range of toleration. To allow calibration of the mismatched resistors within the software, a lower resistor value (220k) is intentionally chosen for the low byte PWM ouput attenuation resistor R2, as shown in the Figure 3. The circuit is designed for STM32F103 microcontroller that has 3.3V operating voltage, which uses the same voltage level for PWM output and ADC conversion reference. The amplifier and filtering circuit is implemented using LM324, that works with 5V voltage supply and capable of swinging its output down to (almost) zero and up to 1.5 Volts below its supply voltage.
The calibration procedure should be implemented to adjust the auto reload/reset value to lengthen the PWM period, so the full bits switch of the low byte PWM value from 0x00 to 0xFF would produce about the same increase by smallest increment (increment by 1) of the high byte PWM. With 12 bit ADC resolution microcontroller such as STM32F103, the chosen gain (16x) for U4 amplifier converts the lowest 12 bits of the combined 16 bit PWM into the full range of the ADC input, enabling the calibration of the low byte PWM period to get the best result in the range of 0x0000 to 0x0fff. Above the 0x0fff, the output of U4 amplifier would be higher than 3.3V and the Zener diode D1 would start conducting to protect the microcontroller chip. It is assumed that after the low byte PWM period has been calibrated within the first 12-bit range then the similar performance would be achieved when the high byte PWM goes above that range.
Missing-Steps and Over-Steps Error
A gain mismatch of the low byte PWM period would result in either missing-steps or over-steps errors, such as illustrated in the Figure 4. The figure illustrate the error in a simplified way, since the low byte steps is reduced to 8 steps (3 bits) where the actual hardware produces 256 steps (8 bit). Because of the lower resistance value is chosen for the R2, then an over-steps error (Figure 4. B) would be produced if the low byte PWM period is not calibrated since the low byte gain is higher than the expected. The opposite effect, modifying the low byte PWM period to the higher period than expected would result in missing-steps error (Figure 4. A). A calibration method should be implemented to minimize this error by adjusting the low byte PWM period to get the proper value.
To minimize the missing-steps and over-steps errors, we have to do calibration routine that adjust the low byte PWM period to the optimum value. With the amplifier circuit providing 16x gain (U4), the step-size of the high byte steps can be measured for all of the first 4 bit steps. There would be 16 levels of high byte increment for optimizing the calibration. The overall low byte steps can be measured by flipping the low byte PWM period from 0x00 to 0xFF and vice versa for each levels. It is possible that not all 16 levels can be used for calibration since the 16x amplifier is not calibrated, so some top levels might be useless and we have to detect if the 12-bit ADC reading gets the saturated value (4095).
UPDATE: Final Circuit After Testing (10/10/2018)
After constructed and tested, D1 Zener diode has significant current leakage before reaching its breakdown voltage (3.3V), and it should be omitted. Fortunately, the application note [reference 1] of STM32 series would accept positive current injection (up to some level) without affecting its conversion precision. The 10k resistor R7 is sufficient to limit the current while maintaining the input series resistance low enough to enable fast sampling time (fast charging of the hold-capacitor inside the analog multiplexer in the microcontroller). The final note, is that the -Vcc of the operational amplifier should be sourced below the ground. Although with a single supply the LM324 operational amplifier can swing almost down to the ground, but practically the current source and sink capability would be very low and it would introduce significant error around. Here is the final circuit:
 AN2834 Application Note: How to get the best ADC accuracy in STM32 microcontrollers, STMicroelectronics
 Audio Software Codec for STM8S, STMicroelectronics Application Note.