PDM library (Pulse Density Modulation)

Dependents:   mbed-shiny

Software PDM

Software PDM (Pulse Density Modulation) is a viable alternative to software PWM where mcu resources are limited because we only need one timer to control the output (whereas PWM requires two timers).

The code presented here is based on an article and source code released by Ken Wada from Aurium Technologies.
Additional info on Pulse Density Modulation is available here.

Keep in mind that the dynamic range is limited when we bit-bang PDM as following limitations apply:

  • A very low pulse width value will inevitably break (and even stop) other code. This value depends on the complexity of the additional user code. A decent start value for a single instance PDM is 100us or more. Multiple PDM instances will require a higher value.
  • Keep the maximum level (DMAX) within reasonable limits. To get a higher precision, we would be tempted to set this as high as possible. As a result, to realize the highest and lowest states (near zero, and near maximum), we need to wait at least for the DAC's maxlevel number of updates in order for those states to manifest properly into the output.

Both the pulse width and DMAX values determine the highest frequency we can reproduce :

                         1
max. frequency = -----------------
                 PulseWidth x DMAX

Example
-------
pulse width = 10us
DMAX        = 256 counts

                       1
max. frequency = ------------- = 390.625Hz
                 0.00001 x 256

Applications

  • Low frequency analog output for mcu's without DA converters.
  • LED brightness control.
  • ...

Examples

Basic use

// mbed-shiny
#include "mbed.h"
#include "SoftPdmOut.h"

SoftPdmOut pdm(LED1);

int main()
{
    float pdmSet = 0.0f;
    float pdmAdd = 0.01f;

    // Continuously cycle the output
    while(1)
    {
        pdm = pdmSet;
        wait_ms(10);
        if(pdmSet >= 1.0f)
            pdmAdd = -0.01f;
        if(pdmSet <= 0.0f)
            pdmAdd = 0.01f;
        pdmSet += pdmAdd;
    }
}



Demonstrate library functions

#include "mbed.h"
#include "SoftPdmOut.h"

Serial pc(USBTX, USBRX);

// optional parameters, when omitted, their default library value will be used.
#define PULSEWIDTH 100
#define DMAX       64
#define STARTLEVEL 0.5f

// PinName and optional pulse width, dmax, start level
// When  SoftPdmOut pdm(LED1);  is used, all optional parameters revert to their default values (defined in the library).
SoftPdmOut pdm(LED1, PULSEWIDTH, DMAX, STARTLEVEL);

int main()
{
    pc.baud (38400);
    
    float pdmVal;
    
    printf("PDM test\r\n");
    pdmVal = 0.1f;

    // Stop the PDM with pin idle value = 0
    printf("Stop the PDM with pin idle value = 0\r\n");
    pdm.stop(0);
    wait(1);
    pdm.start();

    // Stop the PDM with pin idle value = 1
    printf("Stop the PDM with pin idle value = 1\r\n");
    pdm.stop(1);
    wait(1);
    pdm.start();

    // Set PDM level, read it back and print it, can be done in two ways :
    // using pdm.write(nn) and pdm.read(nn)  OR  pdm = nn and <variable> = pdm.
    printf("Set the current PDM level to %f\r\n", pdmVal);
    // Write the current PDM level using pdm.write()
    pdm.write(pdmVal);
    // Write the current PDM level using the shorthand notation instead of pdm.write()
    pdm = pdmVal;
    printf("Get the current PDM level : %f\r\n", pdm.read());
    // Read the current PDM level using the shorthand notation instead of pdm.read()
    float dummy = pdm;
    printf("Get the current PDM level : %f\r\n", dummy);
    wait(1);
    // Change the pulse width and DMAX values.
    pdm.PulseWidth(100);
    pdm.Dmax(128);

    float pdmSet = 0.0f;
    float pdmAdd = 0.01f;

    // Cycle the output
    while(1)
    {
        pdm = pdmSet;
        wait_ms(10);
        if(pdmSet >= 1.0f)
            pdmAdd = -0.01f;
        if(pdmSet <= 0.0f)
            pdmAdd = 0.01f;
        pdmSet += pdmAdd;
    }
}
Revision:
6:85236b955cf6
Parent:
5:408b68033540
--- a/SoftPdmOut.cpp	Sun Dec 28 15:57:53 2014 +0000
+++ b/SoftPdmOut.cpp	Sun Dec 28 18:01:50 2014 +0000
@@ -41,7 +41,14 @@
 
 void SoftPdmOut::PulseWidth(uint32_t level)
 {
-    _PulseWidth = level;
+    if(!_running)                // When pdm is inactive, only change the pulse width and don't start the pdm
+        _PulseWidth = level;
+    else
+    {
+        stop();
+        _PulseWidth = level;
+        start();
+    }
 }
 
 uint32_t SoftPdmOut::getPulseWidth(void)
@@ -51,8 +58,9 @@
 
 void SoftPdmOut::Dmax(uint32_t level)
 {
+    _Level = (uint32_t)((float)_Level * ((float)level / (float)_Dmax));  // Rescale level : level = level * (new level / current DMAX)
     _Dmax = level;
-    _accumulator  = 0;              // zero out the error accumulator
+    _accumulator  = 0;                  // zero out the error accumulator
 }
 
 uint32_t SoftPdmOut::getDmax(void)