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;
    }
}

SoftPdmOut.h

Committer:
frankvnk
Date:
2014-12-28
Revision:
6:85236b955cf6
Parent:
4:b9e61a9022da

File content as of revision 6:85236b955cf6:

/******************************************************************************************************/
/*  Pulse Density Modulation driver.                                                                  */
/*  FILE: PDM.h                                                                                       */
/*                                                                                                    */
/*  Code based on an article and source code released by Ken Wada from Aurium Technologies Inc.       */
/*  http://www.embedded.com/electronics-blogs/embedded-round-table/4414405/Pulse-density-modulation   */
/*                                                                                                    */
/******************************************************************************************************/

#ifndef   __PDM_BB_H
#define   __PDM_BB_H

#include "mbed.h"

/** Class to use software defined Pulse Density Modulation (PDM).
*
* 'pdm' pin can be any digital pin.
* 
\code
//Example
//-------
#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;
    }
}
\endcode
*/ 

class SoftPdmOut {
public:
    /** Create a PDM object connected to a digital pin.
    *
    * @param pdm        : Digital pin.
    * @param PulseWidth : Optional - The desired pulse width in microseconds (default = 500us).
    * @param Dmax       : Optional - This is the total number of states in the DAC output (default = 64).
    * @param StartLevel : Optional - The DAC percentage (0.0 to 1.0) to be preprogrammed upon initialization (default = 0).
    * @return none
    */
    SoftPdmOut(PinName pdm, uint32_t PulseWidth = 500, uint32_t Dmax = 64, float StartLevel = 0);

    /** Start the PDM.
    * @param none
    * @return none
    */
    void start(void);

    /** Stop the PDM.
    * @param idleState (optional, allows the user to define the idle state - default = 0)
    * @return none
    */
    void stop(bool idleState = 0);

    /** Change the pulse width.
    * @param level : The desired pulse width in microseconds.
    * @return none
    */
    void PulseWidth(uint32_t level);

    /** Read the pulse width.
    * @param none.
    * @return current PulseWidth value
    */
    uint32_t getPulseWidth(void);

    /** Change the total number of states in the DAC output (DMAX).
    * @param level : The desired max. level.
    * @return none.
    */
    void Dmax(uint32_t level);

    /** Read the total number of states in the DAC output (DMAX).
    * @param none.
    * @return current DMAX value.
    */
    uint32_t getDmax(void);

    /** Set the PDM level, specified as a percentage (float).
    * @param level
    * @return none
    */
    void write(float level);

    /** Return the current PDM level as a percentage (float).
    * @param none
    * @return level
    */
    float read(void);

    /**
    * An operator shorthand for read()
    */
    operator float();

    /**
    * An operator shorthand for write()
    */
    SoftPdmOut& operator= (float value);

private:
    Ticker _pdmTicker;
    DigitalOut _pdm;
    uint32_t _PulseWidth;
    uint32_t _Level;
    uint32_t _Dmax;
    uint32_t _accumulator;
    bool _running;
    void pdmTick(void);
};
#endif