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:
5:408b68033540
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.cpp */
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 #include "SoftPdmOut.h"
frankvnk 0:ca2bbe1d3321 11
frankvnk 0:ca2bbe1d3321 12 SoftPdmOut::SoftPdmOut(PinName pdm, uint32_t PulseWidth, uint32_t Dmax, float StartLevel) : _pdm(pdm), _PulseWidth(PulseWidth), _Dmax(Dmax)
frankvnk 0:ca2bbe1d3321 13 {
frankvnk 0:ca2bbe1d3321 14 _pdm = 0;
frankvnk 0:ca2bbe1d3321 15 _running = 0;
frankvnk 0:ca2bbe1d3321 16 if(StartLevel >= 0.0f && StartLevel <= 1.0f) _Level = (uint32_t)(StartLevel * (float)_Dmax);
frankvnk 0:ca2bbe1d3321 17 else _Level = 0;
frankvnk 0:ca2bbe1d3321 18 start();
frankvnk 0:ca2bbe1d3321 19 }
frankvnk 0:ca2bbe1d3321 20
frankvnk 0:ca2bbe1d3321 21 void SoftPdmOut::start(void)
frankvnk 0:ca2bbe1d3321 22 {
frankvnk 0:ca2bbe1d3321 23 if (!_running) {
frankvnk 0:ca2bbe1d3321 24 // enable ONLY if state has changed
frankvnk 0:ca2bbe1d3321 25 _accumulator = 0; // zero out the error accumulator
frankvnk 0:ca2bbe1d3321 26 _running = 1; // NOW ... enable the controller
frankvnk 0:ca2bbe1d3321 27 _pdmTicker.attach_us(this,&SoftPdmOut::pdmTick,_PulseWidth); // Start the PDM.
frankvnk 5:408b68033540 28 wait_ms(5); // Wait for output to settle
frankvnk 0:ca2bbe1d3321 29 }
frankvnk 0:ca2bbe1d3321 30 }
frankvnk 0:ca2bbe1d3321 31
frankvnk 0:ca2bbe1d3321 32 void SoftPdmOut::stop(bool idleState)
frankvnk 0:ca2bbe1d3321 33 {
frankvnk 0:ca2bbe1d3321 34 if (_running) {
frankvnk 0:ca2bbe1d3321 35 // disable ONLY if state has changed
frankvnk 0:ca2bbe1d3321 36 _running = 0; // disable the controller
frankvnk 0:ca2bbe1d3321 37 _pdmTicker.detach(); // Stop the PDM.
frankvnk 4:b9e61a9022da 38 _pdm = idleState; // Set output (default = 0)
frankvnk 0:ca2bbe1d3321 39 }
frankvnk 0:ca2bbe1d3321 40 }
frankvnk 0:ca2bbe1d3321 41
frankvnk 4:b9e61a9022da 42 void SoftPdmOut::PulseWidth(uint32_t level)
frankvnk 0:ca2bbe1d3321 43 {
frankvnk 6:85236b955cf6 44 if(!_running) // When pdm is inactive, only change the pulse width and don't start the pdm
frankvnk 6:85236b955cf6 45 _PulseWidth = level;
frankvnk 6:85236b955cf6 46 else
frankvnk 6:85236b955cf6 47 {
frankvnk 6:85236b955cf6 48 stop();
frankvnk 6:85236b955cf6 49 _PulseWidth = level;
frankvnk 6:85236b955cf6 50 start();
frankvnk 6:85236b955cf6 51 }
frankvnk 4:b9e61a9022da 52 }
frankvnk 4:b9e61a9022da 53
frankvnk 4:b9e61a9022da 54 uint32_t SoftPdmOut::getPulseWidth(void)
frankvnk 4:b9e61a9022da 55 {
frankvnk 2:7ead6071d6d6 56 return _PulseWidth;
frankvnk 0:ca2bbe1d3321 57 }
frankvnk 0:ca2bbe1d3321 58
frankvnk 4:b9e61a9022da 59 void SoftPdmOut::Dmax(uint32_t level)
frankvnk 0:ca2bbe1d3321 60 {
frankvnk 6:85236b955cf6 61 _Level = (uint32_t)((float)_Level * ((float)level / (float)_Dmax)); // Rescale level : level = level * (new level / current DMAX)
frankvnk 4:b9e61a9022da 62 _Dmax = level;
frankvnk 6:85236b955cf6 63 _accumulator = 0; // zero out the error accumulator
frankvnk 4:b9e61a9022da 64 }
frankvnk 4:b9e61a9022da 65
frankvnk 4:b9e61a9022da 66 uint32_t SoftPdmOut::getDmax(void)
frankvnk 4:b9e61a9022da 67 {
frankvnk 2:7ead6071d6d6 68 return _Dmax;
frankvnk 0:ca2bbe1d3321 69 }
frankvnk 0:ca2bbe1d3321 70
frankvnk 0:ca2bbe1d3321 71 void SoftPdmOut::write(float level)
frankvnk 0:ca2bbe1d3321 72 {
frankvnk 0:ca2bbe1d3321 73 // Only change the level when the desired level is within range
frankvnk 0:ca2bbe1d3321 74 if(level <= 0.0f) _Level = 0;
frankvnk 0:ca2bbe1d3321 75 else if(level >= 1.0f) _Level = _Dmax;
frankvnk 0:ca2bbe1d3321 76 else if (level > 0.0f && level < 1.0f) _Level = (uint32_t)(level * (float)_Dmax);
frankvnk 0:ca2bbe1d3321 77 }
frankvnk 0:ca2bbe1d3321 78
frankvnk 0:ca2bbe1d3321 79 float SoftPdmOut::read(void)
frankvnk 0:ca2bbe1d3321 80 {
frankvnk 0:ca2bbe1d3321 81 return ((float)_Level / (float)_Dmax);
frankvnk 0:ca2bbe1d3321 82 }
frankvnk 0:ca2bbe1d3321 83
frankvnk 0:ca2bbe1d3321 84 SoftPdmOut::operator float()
frankvnk 0:ca2bbe1d3321 85 {
frankvnk 0:ca2bbe1d3321 86 return read();
frankvnk 0:ca2bbe1d3321 87 }
frankvnk 0:ca2bbe1d3321 88
frankvnk 0:ca2bbe1d3321 89 SoftPdmOut & SoftPdmOut::operator= (float value)
frankvnk 0:ca2bbe1d3321 90 {
frankvnk 0:ca2bbe1d3321 91 write(value);
frankvnk 0:ca2bbe1d3321 92 return(*this);
frankvnk 0:ca2bbe1d3321 93 }
frankvnk 0:ca2bbe1d3321 94
frankvnk 0:ca2bbe1d3321 95 void SoftPdmOut::pdmTick(void)
frankvnk 0:ca2bbe1d3321 96 {
frankvnk 0:ca2bbe1d3321 97 if(_Level == 0) {
frankvnk 0:ca2bbe1d3321 98 _pdm = 0;
frankvnk 0:ca2bbe1d3321 99 return;
frankvnk 0:ca2bbe1d3321 100 }
frankvnk 0:ca2bbe1d3321 101 if(_Level == _Dmax) {
frankvnk 0:ca2bbe1d3321 102 _pdm = 1;
frankvnk 0:ca2bbe1d3321 103 return;
frankvnk 0:ca2bbe1d3321 104 }
frankvnk 0:ca2bbe1d3321 105 _accumulator += _Level; // accumulate error integral
frankvnk 0:ca2bbe1d3321 106 if (_accumulator > _Dmax) {
frankvnk 0:ca2bbe1d3321 107 _accumulator -= _Dmax;
frankvnk 0:ca2bbe1d3321 108 _pdm = 1;
frankvnk 0:ca2bbe1d3321 109 } else
frankvnk 0:ca2bbe1d3321 110 _pdm = 0;
frankvnk 0:ca2bbe1d3321 111 }