Accessing The Hardware PWM Peripheral on the Raspberry Pi in C++

In this Blog entry I will demonstrate how to access the single hardware pulse width modulation (PWM) channel available on the Raspberry Pi. The BCM2835 SOC on the Raspberry Pi has two PWM peripherals; PWM1 & PWM2. Only the PWM1 peripheral can be mapped onto a GPIO  pin (GPIO18) available on the RPi’s 26-pin header. It’s important to note that both the PWM1 & PWM2 peripherals are used by Raspbian to generate audio, so make sure that the RPI is not generating audio while accessing the PWM peripherals. Since Raspbian/ Linux already assigns the PWM peripherals for audio generation, there’s strictly no direct ‘proper’ way to access the PWM1 peripheral from userspace without accessing the hardware registers directly by ‘mmaping into /dev/mem’.

The rpiPWM1 Class

I developed C++ class ‘rpiPWM1′ that  maps the PWM1 peripheral to GPIO18 and is able to control the PWM frequency, resolution, duty cycle and mode with ease. The C++ class declaration for rpiPWM1 is provided below for reference:

#ifndef RPIPWM1_H
    #define PRIPWM1_H
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/***********************************************************************
 * Author: Hussam al-Hertani (Hertaville.com)
 * Others are free to modify and use this code as they see fit so long as they
 * give credit to the author.
 * 
 * The author is not liable for any kind of damage caused by this software. 
 * 
 * Acknowledgements: This 'C++' class is based on 'C' code available from :
 * - code from http://elinux.org/RPi_Low-level_peripherals 
 * - http://www.raspberrypi.org/phpBB3/viewtopic.php?t=8467&p=124620 for PWM initialization
 * - frank's code...http://www.frank-buss.de/raspberrypi/pwm.c
 * - Gertboard's C source code 
 * 
 * The rpiPWM1 class provides a direct memory mapped (register-based)
 * interface to the PWM1 hardware on the Raspberry Pi's BCM2835 SOC.
 * The BCM2835 SOC was two PWM subsystems, PWM1 & PWM2. This code will 
 * enable access to the PWM1 subsystem which outputs the PWM signal on
 * GPIO18 (ALT5). 
 * 
 * The class enables setting the Frequency (Max 19.2MHz), PWM resolution(4095),
 * DutyCycle and PWM Mode to be used. The Duty Cycle can be set as a 
 * percentage (setDutyCycle() or setDutyCycleForce()) or as a  function 
 * of the PWM Resoultion (setDutyCycleCount())
 * 
 * Two PWM modes exist: 
 *  - MSMODE - This is the traditional PWM Mode i.e. if PWM Resolution 
 *             (counts) is 1024 and the dutyCycle (Pulse on time) is 512 (in counts or 50%)
 *             then the waveform would look like:
 *             |||||||||||||||||_________________
 *             MSMODE is ideal for servos and other applications that 
 *             require classical PWM waveforms
 * - PWMMODE - Is a slightly modified version of the traditional PWM Mode 
 *             described above. The duty cycle or ON time is still unchanged
 *             within the period but is distributed across the entire period instead
 *             on being concentrated in the first part of the period..i.e.if PWM Resolution 
 *             (counts) is 1024 and the dutyCycle (Pulse on time) is 512 (in counts or 50%)
 *             then the waveform would look like:     
 *             |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_
 *             This mode is ideal if you want to pass the signal through 
 *             a low pass filter to obtain an analog signal equivalent to the . The even 
 *             distibution of the ON time through the entire period 
 *             significantly reduces ripple caused by using a simple RC
 *             low pass filter     
 *
 * When setting the frequency via the constructor or 'setFrequency' method, one is strictly
 * requesting a particular frequency. The code will do its best to get as close as possible to 
 * the requested frequency but it is likely to not create a PWM at the exact requested Frequency.
 * As an example say that we want to create a PWM waveform with a resolution of 256 counts (8-bit)
 * and a frequency of 8KHz....We get the divisor using the following algorithm
 *
 * Waveform  period = 1 / 8KHz = 0.125ms
 * Duration of a single count = period/256 = 0.125ms / 256 = 0.488us
 * Frequency of a single count = 1 / Duration of a single count = 1 / 0.488us = 2.048MHz
 * Divisor value = floor (PWM Clock Frequency / Frequency of a single count) = floor (19.2MHz / 2.048MHz) = floor(9.375) = 9
 *
 * With a divisor of 9, the actual Waveform Frequency = 1/((1/(19.2MHz/9))*256) = 8.333 KHz
 *
 * The actual Frequency will generally deviate further from the desired frequency as the count value (PWM resolution)
 * increases. For example, let's create a PWM waveform with a resolution of 1024 counts (10-bit)
 * and the same frequency as the above example:
 *
 * Waveform  period = 1 / 8KHz = 0.125ms
 * Duration of a single count = period/1024 = 0.125ms / 1024 = 122.070ns
 * Frequency of a single count = 1 / Duration of a single count = 1 / 122.070ns = 8.192MHz
 * Divisor value = floor (PWM Clock Frequency / Frequency of a single count) 
 *               = floor (19.2MHz / 8.192MHz) = floor(2.34) = 2
 *
 * With a Divisor of 2, the actual Waveform Frequency = 1/((1/(19.2MHz/2))*1024) = 9.375KHz
 * 
 * DIVISOR MUST BE AT LEAST 2....SO PICK YOUR COUNT AND DESIRED FREQUENCY VALUES CAREFULLY!!!!! 
 * i.e MAXIMUM FREQUENCY FOR 10-BIT RESOLUTION (COUNT=1024) IS 9.375KHz
 *  &  MAXIMUM FREQUENCY FOR 8-BIT RESOLUTION (COUNT=256) IS 37.5KHz
 *
 * WARNING:    The RPI uses the PWM1 subsystem to produce audio. As such
 *             please refrain from playing audio on the RPI while this code 
 *             is running.
 * *********************************************************************/
class rpiPWM1 {

public:
  rpiPWM1();
  // default constructor configures GPIO18 for PWM and Frequency 1000Hz,
  // PWM resolution (counts) 256, Duty Cycle 50% and PWM mode is 'PWMMODE'
  rpiPWM1(double Hz, unsigned int cnts, double duty,  int m);
  //overloaded constructor..allows user to set initial values for Frequency,
  //PWM resolution, Duty Cycle and PWM mode.
  ~rpiPWM1();
  // Destructor....safely releases all mapped memory and puts all used peripherals
  // (PWM clock, PWM peripheral and GPIO peripheral in their initial states
  unsigned int setFrequency(const double &hz);
  // Sets Frequency and reinitializes PWM peripheral
  unsigned int setCounts(const unsigned int &cnts);
  // Sets PWM resolution (counts) and reinitializes PWM peripheral
  unsigned int setDutyCycle(const double &duty);
  // Sets Duty Cycle as a Percentage (Fast)
  unsigned int setDutyCycleCount(const unsigned int &cnts );
  // Sets Duty Cycle as a count value (Fast) i.e. if counts is 1024
  // and 'duty' is set to 512, a 50% duty cycle is achieved
  unsigned int setDutyCycleForce(const double &duty, const  int &m);
  // disables PWM peripheral first,
  //Sets Duty Cycle as a Percentage and PWM mode...
  // then enables PWM peripheral
  unsigned int setMode(const  int &m);
  // sets PWM mode...calls 'setDutyCycleForce()'
  
  
  double getFrequency() const;
  // returns current Frequency of PWM waveform
  double getDutyCycle() const;
  // returns current DutyCycle (as a %) of PWM waveform
  int getCounts() const;
  // returns PWM resolution 
  int getDivisor() const;
  //returns Divisor value used to set the period per count
  //as a function of the default PWM clock Frequency of 19.2MHz
  int getMode() const;
  //returns (1) if current PWM mode is 'PWMMODE' or (2) if current PWM mode
  //is 'MSMODE'
  
  //Public constants
  static const int PWMMODE = 1;
  static const int MSMODE = 2;
  //Two PWM modes
  static const int ERRFREQ = 1;
  static const int ERRCOUNT = 2;
  static const int ERRDUTY = 3;
  static const int ERRMODE = 4;
  //Error Codes
  
private:
  //Private constants
  static const int BCM2708_PERI_BASE = 0x20000000;
  static const int PWM_BASE = (BCM2708_PERI_BASE + 0x20C000); /* PWM controller */
  static const int CLOCK_BASE = (BCM2708_PERI_BASE + 0x101000); /* Clock controller */
  static const int GPIO_BASE = (BCM2708_PERI_BASE + 0x200000); /* GPIO controller */
  //Base register addresses
  static const int PWM_CTL = 0;
  static const int PWM_RNG1 = 4;
  static const int PWM_DAT1 = 5;
  static const int PWMCLK_CNTL= 40;
  static const int PWMCLK_DIV = 41;
  // Register addresses offsets divided by 4 (register addresses are word (32-bit) aligned 
  static const int BLOCK_SIZE = 4096;
  // Block size.....every time mmap() is called a 4KB 
  //section of real physical memory is mapped into the memory of
  //the process

  
  volatile unsigned *mapRegAddr(unsigned long baseAddr);
  // this function is used to map physical memory 
  void configPWM1Pin();
  //this function sets GPIO18 to the alternat function 5 (ALT5)
  // to enable the pin to output the PWM waveforms generated by PWM1
  void configPWM1();
  //This function is responsible for the global configuration and initialixation
  //of the the PWM1 peripheral
  
  double frequency; // PWM frequency
  double dutyCycle; //PWM duty Cycle (%)
  unsigned int counts; // PWM resolution
  unsigned int divisor; // divisor value
  int mode;  // PWM mode
  volatile unsigned *clk, *pwm, *gpio; // pointers to the memory mapped sections 
  //of our process memory 
  
};
#endif

Here’s example code on how to use this class:

#include "rpiPWM1.h"


int main (void){
        
    rpiPWM1 pwm(1000.0, 256, 80.0, rpiPWM1::MSMODE);
    // initialize PWM1 output to 1KHz 8-bit resolution 80% Duty Cycle & PWM mode is MSMODE
    unsigned int dcyccount = 0; // reset Duty Cycle to Zero
    while(dcyccount != 256){
        pwm.setDutyCycleCount(dcyccount); // increase Duty Cycle by 16 counts every two seconds
        dcyccount += 16;// until we hit 256 counts or 100% duty cycle
        printf("Duty Cycle is %3.2lf \n",pwm.getDutyCycle());
        printf("Divisor is %d\n", pwm.getDivisor());
        usleep(2000000);
    }
    return 0;

In line 6 we merely instantiate a rpiPWM1 object and initialize the PWM peripheral to a 1KHz PWM frequency, 256 bits of resolution (8-bit), 80% duty cycle and set the pwm mode to rpi::MSMODE. We then use the ‘setDutyCycleCount()’ method to set the duty  cycle to 0% and then increase it by 16 counts from 0 all the way to 256 every 2 seconds. Note that there are two ways to control the duty cycle:

  • setDutyCycle() sets the duty cycle as a percentage;
  •  setDutyCycleCount() sets the duty cycle as a count value…..so if we choose a count value (PWM resolution) of 256 (8-bit), setting the duty cycle with this method to 16 will create a duty cycle of 16/256*100 = 6.25%.

The rpiPWM1 class implementation will not be listed here for brevity but it is reasonably well documented. It has various setters, getters and includes many ways of updating the various parameters associated with the PWM1 peripheral. Below are oscilloscope screen captures of PWM output of the PWM1 peripheral (via GPIO18) using the rpiPWM1 class:

PWM waveform  1000Hz, 25% duty cycle, 8-bit resolution & rpiPWM1::MSMODE mode

PWM waveform 1000Hz, 25% duty cycle, 8-bit resolution & rpiPWM1::MSMODE mode

 

PWM waveform  1000Hz, 50% duty cycle, 8-bit resolution & rpiPWM1::MSMODE mode

PWM waveform 1000Hz, 50% duty cycle, 8-bit resolution & rpiPWM1::MSMODE mode

 

PWM waveform  1000Hz, 75% duty cycle, 8-bit resolution & rpiPWM1::MSMODE mode

PWM waveform 1000Hz, 75% duty cycle, 8-bit resolution & rpiPWM1::MSMODE mode

Setting the PWM Frequency

When setting the frequency via the constructor or ‘setFrequency’ method, one is strictly requesting a particular frequency. The code will do its best to get as close as possible to the target frequency but the actual PWM frequency might deviate a bit from it. As an example say that we want to create a PWM waveform with a resolution of 256 counts (8-bit) and a frequency of 8KHz. We need to get the divisor value to prescale the PWM’s default 19.2MHz clock frequency using the following algorithm:

  • Waveform period = 1 / 8KHz = 0.125ms
  • Duration of a single count = period/256 = 0.125ms / 256 = 0.488us
  • Frequency of a single count = 1 / Duration of a single count = 1 / 0.488us = 2.048MHz
  • Divisor value = floor (PWM Clock Frequency / Frequency of a single count) = floor(19.2MHz / 2.048MHz) = floor(9.375) = 9

With a divisor of 9, the  actual waveform frequency = 1/((1/(19.2MHz/9))*256) = 8.333 KHz. The actual frequency will generally deviate further from the desired frequency as the count value (PWM resolution)  increases i.e. For example let’s create a PWM waveform with a resolution of 1024 counts (10-bit) and the same frequency as the above example:

  • Waveform period = 1 / 8KHz = 0.125ms
  • Duration of a single count = period/1024 = 0.125ms / 1024 = 122.070ns
  • Frequency of a single count = 1 / Duration of a single count = 1 / 122.070ns = 8.192MHz
  • Divisor value = floor (PWM Clock Frequency / Frequency of a single count) = floor (19.2MHz / 8.192MHz) = floor(2.34) = 2

With a divisor of 2, the actual waveform frequency = 1/((1/(19.2MHz/2))*1024) = 9.375KHz.

The divisor values needs to be at least 2 so pick your target PWM count (resolution) and frequency carefully. Based on the this piece of info:

  • Maximum PWM frequency for 10-bit resolution (count = 1024) is 9.375KHz
  • Maximum PWM frequency for 8-bit resolution (count = 256) is 37.5KHz

Setting the PWM Mode

The rpiPWM1 class can configure the RPI’s PWM1 peripherals in two PWM modes:

  • rpiPWM1::MSMODE – This is the traditional PWM Mode i.e. if PWM Resolution (counts) is 1024 and the dutyCycle (Pulse on time) is 512 (in counts or 50%) then the waveform would look like:

|||||||||||||||||_________________

  • rpiPWM1::PWMMODE – Is a slightly modified version of the traditional PWM Mode described above. The duty cycle or ON time is still unchanged within the period but is distributed across the entire period instead of being concentrated in the first part of the period..i.e.if PWM Resolution (counts) is 1024 and the dutyCycle (Pulse on time) is 512 (in counts or 50%) then the waveform would look like:

|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_

The rpiPWM1::MSMODE  mode, is ideal for servos and other applications that require classical PWM waveforms, whereas the rpiPWM1::PWMMODE mode is ideal if you want to pass the signal through a passive low pass filter to obtain an analog signal i.e. use the PWM1 channel as a DAC. The even distibution of the ON time across the entire period significantly reduces ripple caused by using a simple RC low pass filter.

The first three oscilloscope screenshots shown above display PWM waveforms with varying duty cycles in rpiPWM1:MSMODE mode. The following three screenshots display the PWM waveforms with the same frequency (1000Hz), duty cycles(25, 50 & 75 %) and resolution (8-bit) but in rpiPWM1:PWMMODE. Notice how the redistribution of the ‘ON’ time across the period changes the effective frequency measured by the scope in spite of it being actually 1KHz…but the duty cycle is pretty much the same.

PWM waveform  1000Hz, 25% duty cycle, 8-bit resolution & rpiPWM1::PWMMODE mode

PWM waveform 1000Hz, 25% duty cycle, 8-bit resolution & rpiPWM1::PWMMODE mode

PWM waveform  1000Hz, 50% duty cycle, 8-bit resolution & rpiPWM1::PWMMODE mode

PWM waveform 1000Hz, 50% duty cycle, 8-bit resolution & rpiPWM1::PWMMODE mode

PWM waveform  1000Hz, 75% duty cycle, 8-bit resolution & rpiPWM1::PWMMODE mode

PWM waveform 1000Hz, 75% duty cycle, 8-bit resolution & rpiPWM1::PWMMODE mode

 The rpiPWM1 C++ class can be downloaded from here. The code is well documented, relatively well tested and comes with a makefile. It is not however perfect. If you notice any mistakes or errors please let me know. And feel free to use the library in your projects.

This entry was posted in PWM, Raspberry Pi, Raspberry Pi Peripherals. Bookmark the permalink.

3 Responses to Accessing The Hardware PWM Peripheral on the Raspberry Pi in C++

  1. Pingback: Access PWM on the #RaspberryPi using C++ | Raspberry Pi Pod

  2. Pingback: Add Analog to Digital Conversion Capability to the Raspberry Pi without using an ADC chip | Hertaville

  3. Pingback: Drive a Servo Motor with The Raspberry Pi’s PWM1 Peripheral in C++ | Hertaville

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>