Git Product home page Git Product logo

dma_pwm's Introduction

Direct Memory Access (DMA) PWM for the Raspberry Pi

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.

o_scope

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:

  1. C source and header files that can be compiled along with your program
  2. C shared library

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.

Getting Started

These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.

Installing

Obtain the Project

First, clone this repository.

$ git clone https://github.com/besp9510/dma_pwm.git

Alternatively, download the repository from Git.

Configure

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.

Make

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.

Uninstall

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.

Limitations

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.

Running the Test

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:

  1. Configuring dma_pwm.c
  2. Requesting a PWM channel
  3. Setting a PWM signal
  4. Enabling the PWM signal
  5. Updating the PWM signal
  6. Get signal properties
  7. Disabling the PWM signal
  8. Freeing the PWM channel

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.

dma_pwm_test

Video of dma_pwm_test.c

Documentation

How it Works

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

  1. What DMA is and what DMA controllers are
  2. Using the DMA controller on the Pi
  3. PWM and the DMA controller
  4. DMA controller to create PWM
  5. Implementation in C

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:

  • Raspberry Pi 1 & Zero (BCM2835) : 0x20000000
  • Raspberry Pi 4 (BCM2711) : 0xFE000000

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).

Functions

You must run dma_pwm.c as root or with root privileges.

Configure PWM

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:

  1. DEFAULT_PULSE_WIDTH : 100 Hz - 20 kHz
  2. SERVO_PULSE_WIDTH : 50 Hz - 10 kHz
  3. MOTOR_PULSE_WIDTH : 5 kHz - 1 Mhz
  4. LED_PULSE_WIDTH : 0.05 Hz - 10 Hz

Note 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.

Return 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 PWM Channel

Request a DMA channel to create a PWM signal.

int request_pwm();
Return Value

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 PWM Signal

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.

Return Value

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 PWM Signal

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().

Return Value

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 PWM Signal

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.

Return Value

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 PWM Channel

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.

Return Value

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

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.

Return Value

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 Pulse Width

Get the pulse width in microseconds of all PWM signals.

float get_pulse_width()
Return Value

get_pulse_width() always returns the pulse width in microseconds.

Contributing

Follow the "fork-and-pull" Git workflow.

  1. Fork the repo on GitHub
  2. Clone the project to your own machine
  3. Commit changes to your own branch
  4. Push your work back up to your fork
  5. Submit a Pull request so that your changes can be reviewed

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.

Authors

Benjamin Spencer

License

This project is licensed under the MIT License - see the LICENSE.md file for details

Acknowledgments

dma_pwm's People

Contributors

besp9510 avatar golammott avatar iomz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

dma_pwm's Issues

Conflict with ws281x

Hello,
I tried to run PWM on GPIO 13 using your library. It worked well.
But wen I start LED rendering using rpi_ws281x (https://github.com/jgarff/rpi_ws281x) on GPIO12, LED is interfered by your PWM.
Could you please help me?
I assume, both libraries are using some same resources.

Here is debug output of my test program using dma_pwm:

Configuring dma_pwm.c
Setting pulse width to 0.400 us
Setting number of allocated pages to 16
Configured dma_pwm.c
dma_pwm.c configured
DEBUG logs enabled for dma_pwm.c!
Initializing dma_pwm.c
Signal 1 registered with signal handler
Signal 3 registered with signal handler
Signal 2 registered with signal handler
Signal 15 registered with signal handler
Setting PI board version as 4
BCM peripheral base physical address = 0xFE000000
BCM peripheral base bus address = 0x7E000000
GPSET0 bus address = 0x7E20001C
GPCLR0 bus address = 0x7E200028
PWMFIF1 bus address = 0x7E200028
Setting pulse width to 0.400 us
Mapped peripherals into virtual memory:
GPIO base virtual address = 0xb6fda000
DMA CTL base virtual address = 0xb6fd9000
PWM CTL base virtual address = 0xb6fd8000
PWM CLK base virtual address = 0xb6fd7000
PWM CTL and CM initialized:
PWM CM Register: PWMDIV = 0x00002000
PWM CM Register: PWMCTL = 0x00000096
PWM CTL Register: CTL = 0x00000021
PWM CTL Register: DMAC = 0x80000F0F
PWM CTL Register: RNG1 = 0x00000064
Initialized dma_pwm.c
Initializing channel 0
Setting page size to 4096 bytes
Setting channel 0 selected CB buffer to 1
Channel 0 initialized with 65536 bytes allocated (x2)
Channel 0 requested
PWM signal to be set on channel 0
Selecting CB buffer 0 for channel 0
Setting GPIO masks
GPIO set mask = 0x00002000
GPIO clear mask = 0x00002000
Setting PWM signal and CB sequence properties:
Pulse width = 0.4000 us
Subcycle = 10 us
Actual frequency = 104166.6640625 Hz
Duty cycle resolution = 8.3333311%
Actual duty cycle = 74.9999771%
CB sequence "set" number = 9
CB sequence "clear" number = 3
CB sequence total number = 14
Building CB sequence for channel 0 on buffer 0
Built CB sequence for channel 0 on buffer 0
Channel 0 PWM signal set
Channel 0 PWM signal set
PWM signal frequency: 104166.664 Hz
PWM signal duty cycle: 75.000%
Channel 0 to be enabled
Loading CB sequence from buffer 0
DMA Channel 0 Register: CONBLK_AD = 0xBEBB7000
DMA Channel 0 Register: CS = 0x10770009
Channel 0 enabled
Channel 0 enabled
Channel 0 to be disabled
GPIO BCM pin 13 cleared
Channel 0 disabled
Channel 0 disabled
Channel 0 to be freed
Channel 0 to be disabled
GPIO BCM pin 13 cleared
Channel 0 disabled

testing with raspberry pi 3

Test code starts fine, and I am able to see pulse with oscilloscope.

  • but when adjusting the Sleep value bigger and changing frequency, to for example 100 Hz, and Duty % to 50%
    recompiling the code,
    the pulse is not showing up anymore.
  • then if I try to reduce the frequency, for example to 30 Hz, recompile again.
    pulse is again seen, but it is not 30Hz, but 16,66 Hz at the Oscilloscope.

Any ideas, why is this happening? How to test furthermore, to make Frequencies from 1 to 1000 Hz possible,
and could it be possible to change code, so that could set pulse length as us values?

Just looking for a solution, to make for example Four independent PWM pulse outputs,
so that could setup the Hz and Lenght values, as variables.

pi 4B with Ubuntu Desktop 22.04.1 LTS

Thanks for you example. I am using Pi 4b in ubuntu desktip 22.04.1 os. I run the executable dma_pwm_test under /bin ,but the result showed that could not request channel.

pi@pi-desktop:~/Downloads/dma_pwm-master/test/bin$ ./dma_pwm_test
dma_pwm.c configured
Could not request channel

I have add the snd-blacklist.conf file with the content blacklist snd_bcm2835 , but get the same result.
Hope to get your suggestion, thanks!

Raspi 4

Thanks for this example!
I'm running your code on a Rasperry Pi4 with 64-bit RpiOs. GCC sets the uintptr_t type to a unsigned long int which in the automatic conversion to uint32_t crashed the program. Changing all declarations of uintptr_t to uint32_t solved the issue.

dma_pwm for other than raspberry pi ?

I am a complete beginner in the DMA domain, that's why I'm allowing myself to ask for some directions here:

  • Would it be possible to use this library with another processor?
  • If yes, how hard would it be?

Here is what I'm trying to achieve:
I have a Cubietruck (aka. Cubieboard 3) with PWM capabilities (processor is an AllWinner A20) and I'm looking to drive a WS2812b led strip with it in user-mode. I found some libraries that achieved it on a raspberry pi already, but they all seem to be hardcoded for the pi's hardware sadly.

I ended up finding this repo that seemed very promising and I don't mind having to re-implement the led strip's protocol.
Thank you very much!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.