dma_pwm.c provides flexible hardware pulse width modulation (PWM) for the Raspberry Pi via the direct memory access (DMA) controller. Providing PWM via DMA frees the CPU thus allowing low processor usages to programs driving DC motors, servos, LEDs, etc. using the general purpose input/output (GPIO) pins. Unlike the Pi's built-in PWM controller, any number of GPIO pins can be driven over a total of 10 individual channels. This software requires no dependencies other than Raspbian running on any version of the Raspberry Pi.
Other examples of DMA PWM, and ones that this project is based on, are Chris Hager's RPIO and Richard Hirst's ServoBlaster. Unfortunately, these projects are no longer actively maintained so dma_pwm.c serves to bridge the gap and continue to provide an up-to-date and easy-to-use library of functions to achieve flexible hardware PWM on the Pi. This project also emphasizes documenting how PWM via DMA is achieved to allow anyone a better understanding of the Raspberry Pi and low-level programming in general.
dma_pwm.c is provided two ways for flexibility:
To better understand the source code and theory behind DMA PWM on the Pi, see raspberry_pi_dma_pwm.pdf for a complete breakdown on how and why this works.
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
First, clone this repository.
$ git clone https://github.com/besp9510/dma_pwm.git
Alternatively, download the repository from Git.
Once obtaining a copy on your local machine, navigate to the top directory and run the configure script to generate a Makefile.
$ ./configure
By default, files will be installed under /usr/local/
. Note that passing the option --help
will display available configuration options such as installation directory prefix, debug symbols, and debug logs.
Compile dma_pwm.c into a shared library.
$ make
Then install files to the installation directory. You must run the following either as root or with root privileges.
$ sudo make install
To use dma_pwm.c in your project, simply include the header file dma_pwm.h
and link to the shared library -ldmapwm
.
At anytime, to uninstall dma_pwm.c, use the same Makefile used for compiling or a Makefile generated using the configuration script with the same options as root or with root privileges.
$ sudo make uninstall
A reboot is required for this change to take effect
Some distributions use audio by default, even if nothing is being played. If audio is needed, you can use a USB audio device instead.
Since this library and the onboard Raspberry Pi audio both use the PWM, they cannot be used together. You will need to blacklist the Broadcom audio kernel module by creating a file /etc/modprobe.d/snd-blacklist.conf
with
blacklist snd_bcm2835
If the audio device is still loading after blacklisting, you may also need to comment it out in the /etc/modules file.
On headless systems you may also need to force audio through hdmi Edit config.txt and add:
hdmi_force_hotplug=1
hdmi_force_edid_audio=1
A reboot is required for this change to take effect.
Some distributions use audio by default, even if nothing is being played. If audio is needed, you can use a USB audio device instead.
dma_pwm_test.c is a test script to check and see PWM via DMA works on your Raspberry Pi and provide examples on how to use dma_pwm.c. The outline of this test script:
To compile the test script, first navigate to the test directory test/
. Next, run the configure script to generate the Makefile:
$ ./configure
By default, the dma_pwm.c shared library will be looked for under the standard directories (e.g. /usr/local/
). If this is not the case, pass the option --help
to learn how to specify a directory to be searched. Additionally, --help
will also display available configuration options such as debug symbols and debug logs.
Next, compile the test script
$ make
This will create an executable called dma_pwm_test
under bin/
. While running the test script, either monitor the output of the selected GPIO pin using a LED, oscilloscope, ect.
Video of dma_pwm_test.c
To better understand the source code and theory behind DMA PWM on the PI, see raspberry_pi_dma_pwm.pdf for a complete breakdown on how and why this works. This reference documents
Note that this reference has register addresses specific to the BCM2836/BCM2837 (Raspberry Pi 2 & 3) processor. The peripheral base physical address for the other PI versions are:
Additionally, an Excel spreadsheet "dma_pwm_pulse_width_calculator.xlsx" is provided to allow easy calculation of custom pulse widths (more discussion below on this).
You must run dma_pwm.c as root or with root privileges.
Configure amount of memory pages allocated, pulse width in microseconds of the PWM signal, and Pi version away from defaults. This function call is not required to request a channel and enable PWM, but is required prior to requesting a channel for any configuration change to take effect. This function call will then fail if any channel has been requested. Note that these configurations effect all channels (e.g, and and all PWM signals regardless of channel will have the same pulse width).
float config_pwm(int pages, float pulse_width);
The amount of pages allocated int pages
describes the amount of uncached memory allocated by each PWM channel. Note that a "ping-pong" buffer is used to minimize signal interruption when set_pwm()
updates an already enabled signal so multiply int pages
by 2 to get the total amount of allocated memory. This defaults to DEFAULT_PAGES
or 16 pages (65,536 bytes for 4096-byte page systems).
Pulse width in microseconds of the PWM signal float pulse_width
is the length of time in which a GPIO pin remains set or cleared. Determining an appropriate pulse width is a function of the allocated memory pages, desired frequency range, and desired duty cycle resolution. See raspberry_pi_dma_pwm.pdf and "dma_pwm_pulse_width_calculator.xlsx" for a discussion on how to calculate this for yourself, but several presets are available targeting an appropriate pulse width for servos SERVO_PULSE_WIDTH
, D.C motors MOTOR_PULSE_WIDTH
, and LEDs LED_PULSE_WIDTH
. This defaults to DEFAULT_PULSE_WIDTH
or 5 us. Use the following recommendations for frequency ranges for the above presets to achieve a duty cycle within 10% of desired at the default allocated memory:
DEFAULT_PULSE_WIDTH
: 100 Hz - 20 kHzSERVO_PULSE_WIDTH
: 50 Hz - 10 kHzMOTOR_PULSE_WIDTH
: 5 kHz - 1 MhzLED_PULSE_WIDTH
: 0.05 Hz - 10 HzNote that these ranges are suggestions but feel free to calculate a pulse width that will be suite your application. Note that acceptable pulse widths lie above 0.5 us. Strange behavior was seen for any pulse width lower than this value.
config_pwm()
return 0 upon success. On error, an error number is returned.
Error numbers:
ECHNLREQ
: At least one channel has been requested; release all requested channels prior to function call.EINVPW
: Invalid pulse width; pulse width must be between 0.002 us and 35,175,782,146 us.Request a DMA channel to create a PWM signal.
int request_pwm();
request_pwm()
returns the channel number upon success. On error, an error number is returned.
Error numbers:
ENOFREECHNL
: No free DMA channels available to be requested.ENOPIVER
: Could not get Pi board revision.EMAPFAIL
: Peripheral memory mapping failed.ESIGHDNFAIL
: Signal handler failed to setup.Set a PWM signal on a requested channel for selected GPIOs at a desired frequency in Hz and duty cycle in percent (%). This function call is required prior to enabling (outputting) PWM on a requested channel. If a PWM signal is already set and enabled for a requested channel, this function serves as a method to update the PWM signal. The signal is updated immeadiately and does not require any additional function calls in this case. Note, a "ping-pong" buffer exists internally within dma_pwm.c that minimizes the interruption time for PWM signal updates to allow near-continuous output of a signal.
int set_pwm(int channel, int* gpio, size_t num_gpio, float freq, float duty_cycle);
The channel number int channel
is a previously requested channel obtained by a request_pwm()
call.
A vector of GPIO pins int* gpio
, and the number of pins (size of said vector) size_t num_gpio
, describes which GPIO pins will output the desired PWM signal. This is a vector of non-zero length containing any BCM GPIO pin numbers. Note that there is no check within pwm_dma.c to which GPIO pins are used to produce a PWM signal meaning it is your responsibility to choose pins wisely. See Raspberry Pi documentation on GPIOs for more information on this.
PWM signal properties float freq
and float duty_cycle
are desired frequency in Hz and duty cycle in percent (%) of the PWM signal. Note that desired frequency and duty cycle may not be the actual frequency and duty cycle of an output PWM signal; this is caused by dma_pwm.c's current configuration (config_pwm()
), what the desired frequency and duty cycle values are, and the inherit limitations of PWM via DMA. See raspberry_pi_dma_pwm.pdf for a complete discussion on why this is. If using default or the preset pulse widths, and are following the recommended frequency ranges, then actual signal properties will closely match desired. If unsatisfactory, you can configure pulse width to better suite your application and achieve actual signal properties closer to what is desired.
set_pwm()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EINVCHNL
: Invalid or non-requested channel; channel must be requested from request_pwm()
.EINVDUTY
: Invalid duty cycle; duty cycle must be between 0% and 100%.EINVGPIO
: Invalid GPIO pin; acceptable pins are between 0 and 31 inclusive.EFREQNOTMET
: Desired frequency cannot be met; the frequency is too high for the configured pulse width. Change the pulse width using configure_pwm()
.ENOMEM
: Amount of memory required to produce the PWM signal is greater than what is allocated; increase the amount of pages allocated or change the pulse width using config_pwm()
.Enable (output) an already set PWM signal on a requested channel. The PWM signal will output to the selected GPIO pins immediately upon function call. A function call to set_pwm()
is required prior to enabling.
int enable_pwm(int channel);
The channel number int channel
is a previously requested channel obtained by from request_pwm()
.
enable_pwm()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EINVCHNL
: Invalid or non-requested channel; channel must be requested from request_pwm()
.EPWMNOTSET
: PWM signal on requested channel has not been set; PWM must be set using set_pwm()
.Disable (stop output) a PWM signal on a requested channel. The PWM signal will immeadiately stop outputting and the selected GPIO pins will be cleared.
int disable_pwm(int channel);
The channel number int channel
is a previously requested channel obtained by a request_pwm()
call.
disable_pwm()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EINVCHNL
: Invalid or non-requested channel; channel must be requested from request_pwm()
.Free a requested channel by freeing allocated memory and clearing GPIO pins. This function call should always be used prior to program exit as the allocated memory associated with the channel will not automatically be freed after program exit. This is because non-cached memory is allocated via the VideoCore interface. If memory is not freed after use, expect dma_pwm.c to break; resolve this issue by power cycling the Pi. Note that in the case of unexpected program termination, dma_pwm.c has signal handlers that will call free_pwm()
for cases of SIGHUP
, SIGQUIT
, SIGINT
, and SIGTERM
signals to ensure allocated memory is freed.
int free_pwm(int channel);
The channel number int channel
is a previously requested channel obtained by a request_pwm()
call.
free_pwm()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EINVCHNL
: Invalid or non-requested channel; channel must be requested from request_pwm()
.Get PWM signal properties frequency and duty cycle. The properties returned are the actual properties of the signal outputed to selected GPIO pins and may not match the desired frequency and duty cycle passed into set_pwm()
.
float get_duty_cycle_pwm(int channel);
float get_freq_pwm(int channel);
The channel number int channel
is a previously requested channel obtained by a request_pwm()
call.
get_duty_cycle_pwm()
and get_freq_pwm()
returns duty cycle in percent (%) and frequency in Hz upon success. On error, an error number is returned.
Error number:
EINVCHNL
: Invalid or non-requested channel; channel must be requested from request_pwm()
.Get the pulse width in microseconds of all PWM signals.
float get_pulse_width()
get_pulse_width()
always returns the pulse width in microseconds.
Follow the "fork-and-pull" Git workflow.
Be sure to merge the latest from "upstream" before making a pull request!
Feel free to email at the email address under my account name if you have any questions.
Benjamin Spencer
This project is licensed under the MIT License - see the LICENSE.md file for details