Home » BeagleBone Black » Beaglebone Black PWM on Ubuntu 16.04 Using Device Tree Overlay

Beaglebone Black PWM on Ubuntu 16.04 Using Device Tree Overlay

Now we'll use the Beaglebone Black PWM in controlling a servo motor. PWM, which is short for pulse width modulation, is widely used for controlling motors (dc and servomotors).  PWM is also used in controlling power delivered to a load or device without using "power-eating" rheostat.

Beaglebone Black PWM on Ubuntu

While there are a lot of tutorials out there about using the BBB's PWM module, this tutorial is slightly different since I am using Ubuntu 16.04 LTS with kernel 4.4.9. There are a few changes in the way you can use the pwm using this kernel as compared to earlier ones like 3.8, etc. If you're not sure which version you are using, type cat /etc/issue for the distro name and version and uname -r for the kernel version in your BBB's ssh terminal. This is what mine looks like:

Instructions on how to get Ubuntu to your BBB is found here.

You also need to know what device tree overlays are before pushing through this tutorial. Basically, a device tree is a way to access the hardware components of a device such as a BBB. A device tree overlay is a file that we can use to modify the hardware without recompiling the device's kernel.  I would suggest you read this tutorial by Adafruit if to get you acquainted with device tree overlays.

Installing the Device Tree Overlay

Preliminaries

First, login as root user so that you wouldn't need to use sudo on all commands (you'd have to enter your user password):

Next is to install the device tree overlay. I have scourged the web looking for the correct device tree overlay for my device and my kernel version. The one that worked for me can be found here: https://github.com/beagleboard/bb.org-overlays/tree/master/src/arm. You can clone this repository to your BBB.

The repository has three pwm device tree overlays. The one that you'll be using depends on which pwm output you will use. The BBB has six pwm outputs spread out to 12 pins (two pins per pwm output):

PWM Module Pin
EHRPWM0A P9_22, P9_31
EHRPWM0B P9_21, P9_29
EHRPWM1A P9_14, P8_36
EHRPWM1B P9_16, P8_34
EHRPWM2A P8_19, P8_45
EHRPWM2B P8_13, P8_46

For this tutorial, I'll be using EHRPWM1A at pin P9_14. That means I'll be using the device tree overlay BB-PWM1-00A0.dts. Here is its contents:

Pinmux on BBB

The above overlay is for both EHRPWM1A and EHRPWM1B. Notice these lines under fragment0:

Each pin has its own 32 bit address which starts with 44e10, followed by three unique numbers. The whole address of P9_14 is 44e10848 so it's offset by 848. You can see the addresses of the pin yourself:

The number with the pin address is the pin mode. P9_14's current mode is 27h . A reference table from AM335X technical documentation provided by TI will explain the mode number:

Bit Field Reset Description
6 conf_<module>_<pin>_slewctrl X Slew Control. Slew Rate: Fast is 0, Slow is 1
5 conf_<module>_<pin>_rxactive 1h Receiver Active. Input Enable: Receiver Disable 0, Receiver Enable 1
4 conf_<module>_<pin>_putypesel X Pad Pullup/Pulldown Type. Pulldown is 0, Pullup is 1
3 conf_<module>_<pin>_puden X Pad Pullup/Pulldown enable. Enabled is 0, Disabled is 1
2-0 conf_<module>_<pin>_mmode X Mode. Pad functional mux select. A number between 0 and 7 i.e. 000 and 111. This depends on which mode we require.

27 hex (00100111 binary) means that P9_14 is currently set to Receiver Active. Input Enable: Receiver Disable 0, Receiver Enable 1 and in Mode 7. According to this image here (click to enlarge):

Mode 7 for P9_14 is GPIO mode. This means we can't use it to multiplex pwm yet unless we change the mode. The device tree overlay does the mode changing!.

Look back at the dts file, there is a line that says:

The device tree overlay sets the mode of P9_14 (0x48) and P9_16 (0x4c) to mode 6 (0x06). Mode 6 is PWM mode!

Compiling the Device Tree Overlay

In order for the BBB to understand what the .dts file is for, we need to compile the .dts file to the binary object .dtbo. Here's how to do that:

Note that my .dts file is at the root directory. After that, the .dtbo file will now have been created. The next step is to copy that .dtbo file to /lib/firmware

Once the dtbo is in the /lib/firmware folder, it's now ready to be loaded to the cape manager. To do that, just do:

Next, check if it's loaded:

If everything is successful, doing this:

Should show that P9_14 will now have 6 as its pin mode. Now we are ready to use PWM!

Using the Beaglebone Black PWM Module

The PWM Chips

Go to /sys/class/pwm 

You can see the pwmchips inside this folder.

EHRPWM1 is under PWMSS1 which has address 0x48302000 and can be seen under pwmchip0. Hence we should go to this folder and echo 0 to export (0 for EHRPWM1A, 1 for EHRPWM1B).

Once 0 is echoed to export, pwm0 will now become part of folder pwmchip0. Go to this newly created directory to find:

Echo, Echo

You can give values (in nano seconds) to period and duty_cycle. Consequently, give 1 or 0 to enable to turn the Beaglebone Black PWM on or off respectively. For example, if a 100 Hz pulse with a duty_cycle of 80% is desired, do:

Connecting a Servo Motor

Now that we know how to use the Beaglebone Black PWM module, it's time to control a servo motor.

To start, wire the circuit as shown:

beaglebone black pwm connection to servo

The direction of the servo motor's arm can be controlled by changing the duty cycle of the Beaglebone Black PWM signal applied to the pulse (yellow) line.  In theory, the minimum pulse width should be 1 ms while the maximum pulse width is 2 ms. A 1.5 ms pulse width will turn the arm to the center position. But not all servo motors follow the exact same pulse widths. Some will have center positions at less than or greater than 1.5 ms. Thus you need to experiment with your own servo meter to know the exact pulse width for the center position. As for me, I'll stick with the standard 1.5 ms pulse width for the center position.

To begin with, we need to setup the Beaglebone Black PWM's period. The period should be larger than the max pulse width of 2 ms. We also need to set the polarity to a positive pulse. I wanted to set it to 20 ms so here's what I did:

I sent a 1 ms pulse to turn it to -90° (left most position when facing the arm):

To turn it to 90° (all the way to the right), I sent a 2 ms pulse:

To turn it to the center position, I sent a 1.5 ms pulse:

TL, DR:

  • To begin with beaglebone black pwm, download/copy/create a suitable device tree overlay
  • Then compile the device tree overlay
  • Then move the compiled device tree overlay to cape manager
  • Next, identify the correct pwm chip
  • Finally, echo values to polarity, period, duty cycle and enable!

Was this post useful? Drop a commment below!

7 comments

  1. Could i get a little clarification on how you knew to do echo pwm0 > export.. I'm a little confused on that. Also how do i can only input the value for the period that you gave. If i try any other numbers i get invalid argument.. what am i doing wrong?

    • Hi,

      Page 182 of the AM335x Technical Reference Manual gives the address of PWMSS1 (where EHRPWM1 belongs) as 0x4830_2000. Once the device tree overlay is installed and that pwmchips are accessible, you just need to find that address. You can see above that it's on pwmchip0 (in the line: pwmchip0 -> ../../devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/pwmchip0) . So I just changed my current directory to pwmchip0. Then to choose between EHRPWM1A or EHRPWM1B, you need to echo 0 > export (for 1A) or echo 1 >export (for 1B). Only then will pwm0 subfolder will appear.

    • Can you show me the error that appeared when you put values into the period?

      • Hey! thanks for the reply.. It kept saying "Invalid argument" but I rebooted my BB and tried again and it worked great!

        I have been trying to write a C++ program that:
        1. Loads the cape overlay
        2. configures the pins
        3. Changes the period, duty cycle, and enables/disables

        I have managed to accomplish everything but I can't get the part where you have to echo 0 > export.. I am getting a segmentation fault.

        Do you have any idea of how to get around this?
        I am trying to use:
        configureP9_14 = fopen("/sys/class/pwm/pwmchip0/export", "w");
        fwrite(pwm0,1,sizeof(pwm0), configureP9_14);
        fclose(configureP9_14);
        and its no good.

        Also, do you know of any good IDE's to develop c++ code for the BeagleBone?

        • Hi,

          The segmentation fault is due to the pwm0 not created yet. I'm not sure if pwm0 in your code refers to the string "pwm0" or the char "0". In any case, you should export "0" and not "pwm0" like this:

          char str[] = "0"
          configureP9_14 = fopen("/sys/class/pwm/pwmchip0/export", "w");
          fwrite(str,1,sizeof(str), configureP9_14);
          fclose(configureP9_14);

          Only then will the pwm0 folder appear. You can then do this:

          char str2[] = "20000000";
          setPeriod = fopen ("/sys/class/pwm/pwmchip0/pwm0/period", "w");
          fwrite (str2, 1, sizeof(str2), setPeriod);
          fclose (setPeriod);

          to set the period. Setting the duty_cycle and enabling it follows the same approach.

          As for the IDE, you can use any Linux C++ IDE if you have a monitor connected to your BBB. I suggest Eclipse CDT, although I really don't use it. Linux itself is an IDE 🙂

  2. #include
    #include
    using namespace std;

    int main() {
    FILE *loadOverlay = NULL;
    FILE *configureP9_14 = NULL;
    FILE *configureP9_16 = NULL;
    FILE *period_file = NULL;
    FILE *dutyCycle_file = NULL;
    FILE *enable_file = NULL;

    char overlay[] = "BB-PWM1";
    char pwm0[] = "0";

    char period[] = "100000000";
    char dutyCycle[] = "5000000";
    char enable[] = "1";
    char disable[] = "0";

    /* Load the DTO to enable PWM */
    loadOverlay = fopen("/sys/devices/platform/bone_capemgr/slots", "w");
    fwrite(overlay,1,sizeof(overlay), loadOverlay);
    fclose(loadOverlay);

    /*We are using pins P9_14 and and P9_16 pins for this PWM */
    configureP9_14 = fopen("/sys/class/pwm/pwmchip0/export", "w");
    fwrite(pwm0,1,sizeof(pwm0),configureP9_14);
    fclose(configureP9_14);

    period_file = fopen("/sys/class/pwm/pwmchip0/pwm0/period", "w");
    fwrite(period,1,sizeof(period),period_file);
    fclose(period_file);

    dutyCycle_file = fopen("/sys/class/pwm/pwmchip0/pwm0/duty_cycle", "w");
    fwrite(period,1,sizeof(dutyCycle),dutyCycle_file);
    fclose(dutyCycle_file);

    enable_file = fopen("/sys/class/pwm/pwmchip0/pwm0/enable", "w");
    fwrite(enable,1,sizeof(enable),enable_file);
    fclose(enable_file);

    usleep(10000000);

    enable_file = fopen("/sys/class/pwm/pwmchip0/pwm0/enable", "w");
    fwrite(disable,1,sizeof(disable),enable_file);
    fclose(enable_file);
    }

    You are no able to write to the /sys/class/pwm/pwmchip0/export file.. That is what give you the segmentation fault...

    If you see any error in my code please let me know. I also tried to set config_P9 = /sys/class/pwm/pwmchip0 while pwm0 = echo 0 > export.

    I still received the segmentation fault in both cases

    • You're code works on mine! I suggest you switch to super user first (sudo su) then run the same code then see what happens.

Leave a Reply

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