Bit-banging pulse density modulation - Embedded.com

Bit-banging pulse density modulation

In the last article, I described how to implement a bit-bang pulse-width modulator (PWM) using a single microcontroller port pin, a timer interrupt, and some firmware. One of the issues that came about was we needed a lot of microprocessor MIPS (millions of instructions per second) in order to have any reasonable bandwidth for our PWM controller. We either needed more processor power (to sample and do the calculations more often), or we just had to resign ourselves that a cheap single-bit digital-to-analog converter (DAC) will always be relegated to the slow speed near DC domain.

I also hinted that there really is a better method for implementing a cheap single-bit DAC. This “better” method is the pulse density modulation (PDM) .

As I mentioned in the last article, the PWM is a method where a pulse width is varied for a fixed PWM frequency to attain an analog output ranging from 0 to 100 percent. In the PDM case, we keep the pulse width fixed and vary, in time, the number of pulses sent out by the controller. Figure 1 shows how a pulse density modulator maintains a fixed pulse width and modulates the density in order to affect an analog output.


Click on image to enlarge.

Figure 1: using pulse density modulation to affect an analog output.

Hardware consideration
In the last article, I briefly described the PWM, and the idea behind the PWM, and dived straight into demonstrating how to implement the PWM in code. We did an analysis of how to do the filter component selection for a simple RC filter. One of the things that arose from the analysis was either the RC time constant needed to be very large, or we needed to drastically increase the sampling frequency in order to get an acceptable voltage ripple from our single-bit D-to-A converter.

For DC applications, it seems as if the single-bit PWM DAC will probably be suitable. However, what if we wish to have some better response? One solution to this problem is to increase the sampling frequency. The other solutions involve adding more poles to the low-pass filter (thus defeating reduced component cost) or to reduce the RC time constant (thus living with a higher amount of noise induced by the increased ripple).

Approaching this in a typical engineer-problem style, I'm going to describe the problem first and then propose a solution, instead of describing the PDM and jumping into code. Let's go back and review some hardware considerations.

Single-pole low-pass filter
The simple RC filter is a single-pole low-pass filter. The Bode plot ofthe filter, along with the critical frequencies is shown in Figure 2 .


Click on image to enlarge.
Figure 2: A single-pole RC filter for the pulse density modulator.

The critical parameters to note are the two frequencies FPDM and FFILT . These are the PDM half-density frequency and the low-pass filter cut-off frequency respectively.

FPDM
The PDM half-density frequency is the equivalent to the frequency ofthe pulse density output at a 50%. This frequency is related to thesampling frequency FS as shown in Equation 1 :


Equation 1: The relationship between the pulse density half-duty frequency and the sampling frequency.

Again, we need to bring back the cut-off frequency for the single-pole low-pass filter as we described in the last article. Equation 2 shows this inequality:


Equation 2: Inequality relation between low-pass cut-off frequency and the filter time constant

We also need to constrain the 1% voltage ripple between the low pass filter cut-off and the PDM half-density frequency. Equation 3 shows this relationship:


Equation 3: Relationship between the cut-off frequency and the PDM half-duty frequency .

Combining Equation 2 and Equation 3 will yield the inequality which relates the RC filter time constant to the PDM half-duty frequency. This is shown as Equation 4 :


Equation 4: RC filter time constant relationship with the PDM half-duty frequency.

So, let us plug in some numbers here. If we use a sampling frequency of 10,240 Hertz, and plug this into Equation 1 , we get Equation 5 for FPDM :


Equation 5: Calculation of the PDM half-duty frequency .

We can immediately see the much higher frequency of the half-densityPDM as compared with the full-cycle PWM. This is a 128-fold improvement.

Substituting the result from Equation 5 into Equation 4 will yield the minimum RC time constant for our simple single-pole low-pass filter. This is shown in Equation 6 .Clearly, this shows the 128-fold improvement in the RC time constant.This will also make the step response for the D/A output much quickertoo.


Equation 6. PDM low-pass filter time constant .

Design and implementation
OK, now that you can see that the PDM controller is something you'dwant, how do you implement it? You can go to the Internet and type in aGoogle search, or maybe go to Wikipedia. These two options will give onea pretty decent idea of how a PDM controller ought to work.

However, the articles and references are pretty sketchy when it comes to details.

The basic idea of how to generate a density of PDM pulses comesfrom the idea of the sigma-delta analog to digital converter. The ideais to keep an integrator, which integrates an error signal. We feed thiserror signal back and keep adjusting (adding or subtracting from theintegrator), until we get a minimum error signal.

This idea is difficult to envision because we are talking aboutfeedback here. Maybe it is better to represent this idea as some kind ofpretty picture. Figure 3 shows the input (DAC VALUE),the output (pulses), the main control unit (error integrator, summingjunction + comparator), and the DAC MAX level switch (DMAX switch).


Click on image to enlarge.

Figure 3: Pulse density modulator control block diagram.

The DMAX value is the number of total DAC states on the converter. Thiswill be 256 for an 8-bit converter. The DMAX value for a 10-bitconverter will be 1,024 states. One interesting side effect of the DMAXvalue is that we can now support DAC states that are not 2N as in a typical D-A converter.

Objects and interfaces
Here again, I introduce the header file, which describes our PDM controller. The header file is shown in Listing 1 .

#ifndef   __PDM_BB_H   #define   __PDM_BB_H /****************************************************************************/ /*  FILE: pdm_bb.h                                                          */ /*                                                                          */ /*  These files contain the attributes and methods for managing the         */ /*  Pulse Density Modulation driver module                                  */ /*                                                                          */ /*    BY: Ken Wada                                                          */ /*        Aurium Technologies Inc.                                          */ /*        Mar. 15, 2013                                                     */ /*                                                                          */ /****************************************************************************/     struct pdm_bbS;  /* A forward declaration here */      typedef void (*VBIT_ENABLEFN)(struct pdm_bbS *, const int);      typedef struct  pdm_bb_dcbS     {       int m_id;                               /* some unique identifier     */       int idle_state;                         /* PIN state when disabled    */       int preset;                             /* preset initial level       */       int max_level;                          /* MAX DAC level              */       VBIT_ENABLEFN vbit_enableFn;            /* set or clear hardware bit  */     } pdm_bb_dcbS;      /************************************************************************/     /*  The following is the Pulse Density Modulator object. This is the    */     /*  object which will manage a single instance of the pulse density     */     /*  modulator.                                                          */     /************************************************************************/     typedef struct  pdm_bbS     {       pdm_bb_dcbS dcb;                        /* PDM device control block   */       int enable;                             /* 1 == enable PDM            */       int level;                              /* current DAC level setting  */       int accumulator;                        /* the error accumulator      */     } pdm_bbS;    #ifdef __cplusplus     extern "C"  {   #endif     void  pdm_bbS_init      (pdm_bbS *_this, const pdm_bb_dcbS *dcb);     void  pdm_bbS_enable    (pdm_bbS *_this, const int enable);     void  pdm_bbS_levelSet  (pdm_bbS *_this, const int level);     void  pdm_bbS_idleSet   (pdm_bbS *_this, const int idleState);      int   pdm_bbS_levelGet  (const pdm_bbS *_this);     int   pdm_bbS_idleGet   (const pdm_bbS *_this);     void  pdm_bbS_bitEnable (pdm_bbS *_this, const int enable);      /************************************************************************/     /*  The following is the main pulse density modulation update function  */     /*  This function should be called periodically ... preferably in a    */     /*  timer interrupt service routine.                                    */     /************************************************************************/     void  pdm_bbS_update    (pdm_bbS *_this);    #ifdef __cplusplus     }   #endif #endif 

Listing 1: The objects and interfaces required to implement the PDM controller.

PDM device control block
As in the previous article, I introduced the PDM Device Control Block(DCB). Again, this record is used to properly configure and initialize aPDM module.

m_id
This is the module controller ID. This is some unique number which maybe used as an identifier for a specific PDM controller module. Ofcourse, in a single instance PDM application, this identifier may be setto zero and never used throughout the life of the application. However,for applications that require multiple instances, the m_id can and usually proves to be invaluable.

idle_state
This is the desired state of the output when the PDM controller isturned off. I include this here just in case we wish the PDM output portpin to be in another state other than the typical zero (0).

preset
Often you'll wish for the DAC level to be preprogrammed upon initialization. This level is known as the preset level .The preset level sets the DAC level during initialization. In this way,the DAC will immediately go to this level when 1st enabled. We canoverride this behavior by simply setting the DAC level before enablingthe DAC.

max_level
This is the total number of states in the DAC output. As mentionedpreviously, we can program this to be a number other than the 2N states in a typical DAC; where N = the DAC word length.

PDM controller object
The main PDM controller object consists of the device control block andthree state variables. These are all the fields required to properlymaintain and control the PDM module.

DCB
This is the PDM device control block. The fields within the DCB are described as above.

enable
A one(1) for this field will enable the PDM controller. A zero(0) for this field will disable the controller.

level
This is the DAC input level. In other words, this is similar to the DACdigital input word setting used by digital-to-analog converters.

accumulator
This is the error accumulator. The DAC input level is accumulated intothis field. The resultant output will drive a comparator and will eithersubtract the max_level or do no modification in order to maintain a constant error level.

Interfaces
Again, I include a set of interfaces for the PDM controller. In essence,these interfaces allow you to setup, monitor, and control the PDMmodule. Table 1 shows a brief description of the supported interfaces for the PDM controller module.


Click on image to enlarge.
Table 1: PDM interface description.

Instead of presenting every listing within this article, I shallpresent the listing of the most important functions along with a briefdescription of the internal workings of the code.

Main update engine
Listing 2 shows the main update engine (pdm_bbS_update ).As you can see, there is very little code here. The main update engineessentially implements the control block diagram as shown in Figure 3 .

#define   __PDM_BBS_UPDATE_C /****************************************************************************/ /*  FILE: pdm_bbS_update.c                                                  */ /*                                                                          */ /*  These files contain the attributes and methods for managing the         */ /*  Pulse Density Modulation driver module                                  */ /*                                                                          */ /*    BY: Ken Wada                                                          */ /*        Aurium Technologies Inc.                                          */ /*        Mar. 15, 2013                                                     */ /*                                                                          */ /****************************************************************************/ #include  "pdm_bb.h"  /****************************************************************************/ /*                           CODE STARTS HERE                               */ /****************************************************************************/   /****************************************************************************/ /*  Function:                                                               */ /*    void pdm_bbS_update (pdm_bbS *_this);                                 */ /*                                                                          */ /*  This is the main pulse density modulation function. This function       */ /*  should be called periodically, for example by a hard timer interrupt    */ /*  service routine.                                                        */ /****************************************************************************/ void pdm_bbS_update(pdm_bbS *_this) {   int my_bitOutput;   _this->accumulator  += _this->level;        /* accumulate error integral  */   if (_this->accumulator > _this->dcb.max_level)   {     _this->accumulator  = _this->accumulator - _this->dcb.max_level;     my_bitOutput = 1;   }   else   {     my_bitOutput = 0;   }   pdm_bbS_bitEnable (_this, my_bitOutput); } 

Listing 2: The main update engine for the PDM controller.

Hardware encapsulation
Listing 3 shows how the hardware is effectivelyencapsulated in the main PDM object. We do this to allow the maximumflexibility with microcontroller architecture and hardware variation. Ineffect, doing this type of encapsulation will allow you to reuse thisPDM module in many different processor architectures and with manydifferent hardware implementations. You can even use the hardwareencapsulation in order to test the PDM controller in a Microsoft VisualStudio or Linux desktop environment!

#define   __PDM_BBS_BITENABLE_C /****************************************************************************/ /*  FILE: pdm_bbS_bitEnable.c                                               */ /*                                                                          */ /*  These files contain the attributes and methods for managing the         */ /*  Pulse Density Modulation driver module                                  */ /*                                                                          */ /*    BY: Ken Wada                                                          */ /*        Aurium Technologies Inc.                                          */ /*        Mar. 15, 2013                                                     */ /*                                                                          */ /****************************************************************************/ #include  "pdm_bb.h"  /****************************************************************************/ /*                           CODE STARTS HERE                               */ /****************************************************************************/ /****************************************************************************/ /*  Function:                                                               */ /*    void pdm_bbS_bitEnable (pdm_bbS *_this, const int enable);            */ /*                                                                          */ /*  This function is a wrapper used to set or clear the hardware I/O bit    */ /*  for the pdm controller.                                                 */ /*                                                                          */ /****************************************************************************/ void  pdm_bbS_bitEnable (pdm_bbS *_this, const int enable) {   if (_this->dcb.vbit_enableFn)   {     (* _this->dcb.vbit_enableFn)(_this, enable);   } } 

Listing 3: Using a function pointer member variable to encapsulate the hardware details .

Example implementation and test
Listings 4 through 7 shows a “C/CPP” consoleapplication running under Microsoft Windows. The test environment usedhere is Microsoft Visual Studio. The idea here is to do the following:

1. Configure and setup a 4-bit DAC (Listing 4 ).

2. Set a DAC level (Listing 5 ).

3. Run the update through 32 states (Listing 6 and 7 ).

4. Display the results graphically using Excel.

Of course, the actual exercise to implement this module on a real live piece of hardware is up to you, the reader!

 #define   __PDM_CINIT_C /****************************************************************************/ /*  FILE: pdm_cinit.c                                                       */ /*                                                                          */ /*  These files contain the attributes and methods for managing the         */ /*  Pulse Density Modulator application example                             */ /*                                                                          */ /*    BY: Ken Wada                                                          */ /*        Aurium Technologies Inc.                                          */ /*        Mar. 17, 2013                                                     */ /*                                                                          */ /****************************************************************************/ #include    #include  "pdm_bb.h"                  /* pulse density modulation bit-bang  */ #include  "pdm_ctest.h"               /* PDM 'C' test interface             */  static int my_index   = 0; static int my_lastBit = 0;  /****************************************************************************/ /*                           CODE STARTS HERE                               */ /****************************************************************************/ /****************************************************************************/ /*  The following 'bit' I/O output is used to demonstrate the states for    */ /*  the PDM as part of a Microsoft Windows or any console application.      */ /*                                                                          */ /*  We define the DAC as a 4-bit converter, and will spin the update        */ /*  through 32 states. This should be enough to demonstrate the operation   */ /*  of the PDM controller.                                                  */ /*                                                                          */ /*  The output printout is done in such a way to allow capture as an Excel  */ /*  CSV (comma separated value) file.                                       */ /*                                                                          */ /****************************************************************************/ void pdm_vbitEnable (struct pdm_bbS *_this, const int enable) {   int my_enable = (0!=enable);    if (my_enable != my_lastBit)   {     printf("n%d, %d", my_index, my_lastBit);   }   printf("n%d, %d", my_index, my_enable);   ++my_index;   my_lastBit = my_enable; }  pdm_bb_dcbS my_dcb  = {   0,                          /* m_id = 0, only single instance */   0,                          /* idle_state = 0                 */   0,                          /* preset = 0                     */   16,                         /* 4-bit DAC                      */   pdm_vbitEnable              /* use the printf diagnostic      */ };  pdm_bbS pdm_ctl;              /* instance a PDM controller object           */  void pdm_cinit (void) {   my_index = 0;   pdm_bbS_init (&pdm_ctl, &my_dcb); }  void pdm_clearIndex (void) {   my_index = 0; } 

Listing 4: The PDM setup and initialization

We also include the instance of the actual PDM controller within the initialization process. The pdm_clearIndex() function is an interface that allows the main application to reset the index count for the graphic display on Excel.

#define   __PDM_CSETLEVEL_C /****************************************************************************/ /*  FILE: pdm_csetLevel.c                                                   */ /*                                                                          */ /*  These files contain the attributes and methods for managing the         */ /*  Pulse Density Modulator application example                             */ /*                                                                          */ /*    BY: Ken Wada                                                          */ /*        Aurium Technologies Inc.                                          */ /*        Mar. 17, 2013                                                     */ /*                                                                          */ /****************************************************************************/ #include    #include  "pdm_bb.h"                  /* pulse density modulation bit-bang  */ #include  "pdm_ctest.h"               /* PDM 'C' test interface             */  extern pdm_bbS pdm_ctl; extern void pdm_clearIndex (void);  /****************************************************************************/ /*                           CODE STARTS HERE                               */ /****************************************************************************/ void pdm_csetLevel (const int var) {   pdm_clearIndex   ();   pdm_bbS_levelSet (&pdm_ctl, var); } 

Listing 5: Application example of setting, or changing a PDM DAC output level.

#define   __PDM_CUPDATE_C /****************************************************************************/ /*  FILE: pdm_cupdate.c                                                     */ /*                                                                          */ /*  These files contain the attributes and methods for managing the         */ /*  Pulse Density Modulator application example                             */ /*                                                                          */ /*    BY: Ken Wada                                                          */ /*        Aurium Technologies Inc.                                          */ /*        Mar. 17, 2013                                                     */ /*                                                                          */ /****************************************************************************/ #include   #include  "pdm_bb.h"                  /* pulse density modulation bit-bang  */ #include  "pdm_ctest.h"               /* PDM 'C' test interface             */   extern pdm_bbS pdm_ctl; /****************************************************************************/ /*                           CODE STARTS HERE                               */ /****************************************************************************/ void pdm_cupdate (void) {   pdm_bbS_update (&pdm_ctl); }

Listing 6: The instance of the update function in our application.

Next, we show the actual application as an output of density statesrunning as a Windows console application. The actual tested DAC levelsare shown in Table 2 . Since we are using Visual Studio, I chose to create the main application using C++.


Click on image to enlarge.

Table 2: PDM controller tested DAC levels for a 4-bit DAC.

// pdm_test.cpp : Defines the entry point for the console application. //  #include "stdafx.h"  #include  #include "pdm_ctest.h"  int my_level[] =  {   1,   4,   8,   12 };  int _tmain(int argc, char** argv) {   pdm_cinit();   for (int i=0;i<4;i++)   {     std::cout << std::endl << std::endl <<"LEVEL =" << my_level[i];     pdm_csetLevel (my_level[i]);     for (int j=0;j<32;j++)     {       pdm_cupdate();     }   }   return 0;  } 

Listing 7. Demonstration of the PDM controller as a state-based console application.

Test results
The test results of the 4-bit DAC are shown in Figure 4 . As you can see, the PDM controller does distribute the pulses uniformly depending on the DAC level and maximum DAC states.


Click on image to enlarge.
Figure 4: Pulse density controller's output showing density of pulses with respect to DAC setting for a 4-bit DAC .

Better use of your time
I've shown that the PDMcontroller is much more time-efficient than the classical PWMcontroller. The code and resource requirements for the PDM method arevery similar to that used by the PWM.

Of course, there are things that you need to watch out for whenusing the PDM control method. One of the most important is the DAC maxlevel. One may be tempted to set this max level to a very large numberto get a very high precision. But alas, nothing is for free in the realworld. To realize the highest and lowest states (near zero, and nearmaximum), you'll need to wait at for least the DAC's max-level number ofupdates in order for those states to manifest properly into the output.

So, in a nutshell, there are some practical limitations on how big a DACmax level you can set. In other words: No free lunch. But there neveris, is there?

Ken Wada is president and owner of Aurium Technologies, an independent product design and consulting firm in California's Silicon Valley. Ken has over 25 years of experience architecting and designing high-tech products and systems, including the FASTRAK vehicle-sensing system for toll roads and bridges. His expertise includes industrial automation, biotechnology, and high-speed optical networks. Ken holds four patents. You may reach him at .

Endnote :
All the listings are included as two parts. The 1st part is thelibrary itself. The 2nd part is included as the test application. Idecided to not rename the files as LISTxx_csource.c The source files are there, but without the LISTxx addition in the filename.  Click here for code.   You must login to download the code)

--Ken Wada

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.