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;
    }
}
Committer:
frankvnk
Date:
Sun Dec 28 18:01:50 2014 +0000
Revision:
6:85236b955cf6
Parent:
4:b9e61a9022da
Dmax : Rescale level when DMAX changes.; only stop/start PulseWidth when running.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
frankvnk 0:ca2bbe1d3321 1 /******************************************************************************************************/
frankvnk 0:ca2bbe1d3321 2 /* Pulse Density Modulation driver. */
frankvnk 0:ca2bbe1d3321 3 /* FILE: PDM.h */
frankvnk 0:ca2bbe1d3321 4 /* */
frankvnk 0:ca2bbe1d3321 5 /* Code based on an article and source code released by Ken Wada from Aurium Technologies Inc. */
frankvnk 0:ca2bbe1d3321 6 /* http://www.embedded.com/electronics-blogs/embedded-round-table/4414405/Pulse-density-modulation */
frankvnk 0:ca2bbe1d3321 7 /* */
frankvnk 0:ca2bbe1d3321 8 /******************************************************************************************************/
frankvnk 0:ca2bbe1d3321 9
frankvnk 0:ca2bbe1d3321 10 #ifndef __PDM_BB_H
frankvnk 0:ca2bbe1d3321 11 #define __PDM_BB_H
frankvnk 0:ca2bbe1d3321 12
frankvnk 0:ca2bbe1d3321 13 #include "mbed.h"
frankvnk 0:ca2bbe1d3321 14
frankvnk 0:ca2bbe1d3321 15 /** Class to use software defined Pulse Density Modulation (PDM).
frankvnk 0:ca2bbe1d3321 16 *
frankvnk 0:ca2bbe1d3321 17 * 'pdm' pin can be any digital pin.
frankvnk 0:ca2bbe1d3321 18 *
frankvnk 0:ca2bbe1d3321 19 \code
frankvnk 0:ca2bbe1d3321 20 //Example
frankvnk 0:ca2bbe1d3321 21 //-------
frankvnk 0:ca2bbe1d3321 22 #include "mbed.h"
frankvnk 0:ca2bbe1d3321 23 #include "SoftPdmOut.h"
frankvnk 0:ca2bbe1d3321 24
frankvnk 0:ca2bbe1d3321 25 SoftPdmOut pdm(LED1);
frankvnk 0:ca2bbe1d3321 26
frankvnk 0:ca2bbe1d3321 27 int main()
frankvnk 0:ca2bbe1d3321 28 {
frankvnk 0:ca2bbe1d3321 29 float pdmSet = 0.0f;
frankvnk 0:ca2bbe1d3321 30 float pdmAdd = 0.01f;
frankvnk 0:ca2bbe1d3321 31
frankvnk 0:ca2bbe1d3321 32 // Continuously cycle the output
frankvnk 0:ca2bbe1d3321 33 while(1)
frankvnk 0:ca2bbe1d3321 34 {
frankvnk 0:ca2bbe1d3321 35 pdm = pdmSet;
frankvnk 0:ca2bbe1d3321 36 wait_ms(10);
frankvnk 0:ca2bbe1d3321 37 if(pdmSet >= 1.0f)
frankvnk 0:ca2bbe1d3321 38 pdmAdd = -0.01f;
frankvnk 0:ca2bbe1d3321 39 if(pdmSet <= 0.0f)
frankvnk 0:ca2bbe1d3321 40 pdmAdd = 0.01f;
frankvnk 0:ca2bbe1d3321 41 pdmSet += pdmAdd;
frankvnk 0:ca2bbe1d3321 42 }
frankvnk 0:ca2bbe1d3321 43 }
frankvnk 0:ca2bbe1d3321 44 \endcode
frankvnk 0:ca2bbe1d3321 45 */
frankvnk 0:ca2bbe1d3321 46
frankvnk 0:ca2bbe1d3321 47 class SoftPdmOut {
frankvnk 0:ca2bbe1d3321 48 public:
frankvnk 0:ca2bbe1d3321 49 /** Create a PDM object connected to a digital pin.
frankvnk 0:ca2bbe1d3321 50 *
frankvnk 0:ca2bbe1d3321 51 * @param pdm : Digital pin.
frankvnk 4:b9e61a9022da 52 * @param PulseWidth : Optional - The desired pulse width in microseconds (default = 500us).
frankvnk 0:ca2bbe1d3321 53 * @param Dmax : Optional - This is the total number of states in the DAC output (default = 64).
frankvnk 0:ca2bbe1d3321 54 * @param StartLevel : Optional - The DAC percentage (0.0 to 1.0) to be preprogrammed upon initialization (default = 0).
frankvnk 0:ca2bbe1d3321 55 * @return none
frankvnk 0:ca2bbe1d3321 56 */
frankvnk 4:b9e61a9022da 57 SoftPdmOut(PinName pdm, uint32_t PulseWidth = 500, uint32_t Dmax = 64, float StartLevel = 0);
frankvnk 0:ca2bbe1d3321 58
frankvnk 0:ca2bbe1d3321 59 /** Start the PDM.
frankvnk 0:ca2bbe1d3321 60 * @param none
frankvnk 0:ca2bbe1d3321 61 * @return none
frankvnk 0:ca2bbe1d3321 62 */
frankvnk 0:ca2bbe1d3321 63 void start(void);
frankvnk 0:ca2bbe1d3321 64
frankvnk 0:ca2bbe1d3321 65 /** Stop the PDM.
frankvnk 0:ca2bbe1d3321 66 * @param idleState (optional, allows the user to define the idle state - default = 0)
frankvnk 0:ca2bbe1d3321 67 * @return none
frankvnk 0:ca2bbe1d3321 68 */
frankvnk 4:b9e61a9022da 69 void stop(bool idleState = 0);
frankvnk 0:ca2bbe1d3321 70
frankvnk 4:b9e61a9022da 71 /** Change the pulse width.
frankvnk 4:b9e61a9022da 72 * @param level : The desired pulse width in microseconds.
frankvnk 4:b9e61a9022da 73 * @return none
frankvnk 4:b9e61a9022da 74 */
frankvnk 4:b9e61a9022da 75 void PulseWidth(uint32_t level);
frankvnk 4:b9e61a9022da 76
frankvnk 4:b9e61a9022da 77 /** Read the pulse width.
frankvnk 4:b9e61a9022da 78 * @param none.
frankvnk 1:a0c0b1b9e965 79 * @return current PulseWidth value
frankvnk 0:ca2bbe1d3321 80 */
frankvnk 4:b9e61a9022da 81 uint32_t getPulseWidth(void);
frankvnk 0:ca2bbe1d3321 82
frankvnk 4:b9e61a9022da 83 /** Change the total number of states in the DAC output (DMAX).
frankvnk 4:b9e61a9022da 84 * @param level : The desired max. level.
frankvnk 4:b9e61a9022da 85 * @return none.
frankvnk 4:b9e61a9022da 86 */
frankvnk 4:b9e61a9022da 87 void Dmax(uint32_t level);
frankvnk 4:b9e61a9022da 88
frankvnk 4:b9e61a9022da 89 /** Read the total number of states in the DAC output (DMAX).
frankvnk 4:b9e61a9022da 90 * @param none.
frankvnk 1:a0c0b1b9e965 91 * @return current DMAX value.
frankvnk 0:ca2bbe1d3321 92 */
frankvnk 4:b9e61a9022da 93 uint32_t getDmax(void);
frankvnk 0:ca2bbe1d3321 94
frankvnk 0:ca2bbe1d3321 95 /** Set the PDM level, specified as a percentage (float).
frankvnk 0:ca2bbe1d3321 96 * @param level
frankvnk 0:ca2bbe1d3321 97 * @return none
frankvnk 0:ca2bbe1d3321 98 */
frankvnk 0:ca2bbe1d3321 99 void write(float level);
frankvnk 0:ca2bbe1d3321 100
frankvnk 0:ca2bbe1d3321 101 /** Return the current PDM level as a percentage (float).
frankvnk 0:ca2bbe1d3321 102 * @param none
frankvnk 0:ca2bbe1d3321 103 * @return level
frankvnk 0:ca2bbe1d3321 104 */
frankvnk 0:ca2bbe1d3321 105 float read(void);
frankvnk 0:ca2bbe1d3321 106
frankvnk 0:ca2bbe1d3321 107 /**
frankvnk 0:ca2bbe1d3321 108 * An operator shorthand for read()
frankvnk 0:ca2bbe1d3321 109 */
frankvnk 0:ca2bbe1d3321 110 operator float();
frankvnk 0:ca2bbe1d3321 111
frankvnk 0:ca2bbe1d3321 112 /**
frankvnk 0:ca2bbe1d3321 113 * An operator shorthand for write()
frankvnk 0:ca2bbe1d3321 114 */
frankvnk 0:ca2bbe1d3321 115 SoftPdmOut& operator= (float value);
frankvnk 0:ca2bbe1d3321 116
frankvnk 0:ca2bbe1d3321 117 private:
frankvnk 0:ca2bbe1d3321 118 Ticker _pdmTicker;
frankvnk 0:ca2bbe1d3321 119 DigitalOut _pdm;
frankvnk 0:ca2bbe1d3321 120 uint32_t _PulseWidth;
frankvnk 0:ca2bbe1d3321 121 uint32_t _Level;
frankvnk 0:ca2bbe1d3321 122 uint32_t _Dmax;
frankvnk 0:ca2bbe1d3321 123 uint32_t _accumulator;
frankvnk 0:ca2bbe1d3321 124 bool _running;
frankvnk 0:ca2bbe1d3321 125 void pdmTick(void);
frankvnk 0:ca2bbe1d3321 126 };
frankvnk 0:ca2bbe1d3321 127 #endif