Is anyone working on a pwm driver for the 'bone?

The pwm hardware on the bone is completely different than what's on the beagleboard, so this code is probably moot. I was able to build a kernel with the modules for the beaglebone hardware, but had to leave town for work before I was able to get it working on my board.

Steve

So I understand if you build a new image with the 3.2 kernel, it will
have PWM. How do you access it in userspace? What files in the rfs do
I need to read/write to?

- Mike

Michael,

I was going to write a PWM driver for the kernel but in the process of setting up my development environment I discovered, to my pleasant surprise, that it has already been done. I have done some quick hacking around and have gotten PWM output working on the beaglebone. I am running short on time right now so I’ll just give a quick and dirty description on what I did to get PWM output. I’ll post some more detailed information when I have some available time tonight or tomorrow.

First of all, the errors that previous users have reported when doing things like, “echo 1 > run” in one of the PWM sysfs folders is caused by the clocks not being turned on for the PWM module. To turn on the system clocks I used the mmap /dev/mem trick. Just open /dev/mem then mmap that file into your address space. The register of interest for me was the CM_PER_EPWMSS1 register, for the PWM1 module. It is located at offset 0x44e000cc in the address space of the beaglebone’s processor. See page 1017 of the AM335x ARM Cortex-A8 reference manual and look down for that register. After writing 0x2 to this register the clock should be enabled for the PWM module allowing you to write to the files in sysfs.

The next thing I did was set up the pin mux to get the PWM outputs out to the pad. I did this with:

echo 6 > /sys/kernel/debug/omap_mux/gpmc_a2

Then to control the PWM module I did:

cd /sys/class/pwm/ehrpwm.1:0
echo 1 > run
echo 100 > period_freq
echo 10 > duty_percent

Then I was able to see a 1ms pulse with period of 10ms on my oscope coming out of the expected pin on the beaglebone. Can anyone say servo control? :slight_smile:

The step to enable the PWM clocks seems very hackish. Does anyone know if there is a way to do this through the sysfs instead of writing to /dev/mem directly?

I hope this helps you, and anyone else, get going. Again, since I’m running short on time I’ll post the code I used to enable the PWM clocks when I get time.

Dustin

Dustin, I suspected this was what was going on, but am still traveling and haven’t been able to confirm. Thanks for the post.

Steve

Dustin,

Congrats on figuring this out, and thanks for sharing. I wasn't able
to figure out the mmap thing, so I look forward to seeing your
instructions on that.

I guess enabling the PWM timer will also enable the eCAP PWM? Based
on the SRM, I think ecap.0 is Port 9 Pin 42, Mode 0. No idea where
ecap.1 is, though.

For those looking for details on how to access the PWM settings
through the file system, I believe that the instructions on this page
are correct for the BeagleBone: http://processors.wiki.ti.com/index.php/AM335x_PWM_Driver’s_Guide

For those looking to enable the 3.2 kernel, if you have a uImage-3.2.x
file in your /boot folder (updated via opkg, current latest version is
uImage-3.2.6+), then I believe Frank Meyer's instructions in this post
are correct
http://groups.google.com/group/beagleboard/browse_thread/thread/29006f9619c2cd79/9340f75616100f25?lnk=gst&q=uimage+beaglebone#9340f75616100f25

Dan.

Dan, here’s one way to set do the mmap bit in python (you only need the bold bits, but
leaving the rest there in case it’s useful reference):

from mmap import mmap
import struct

MMAP_OFFSET = 0x44c00000 # base address of registers
MMAP_SIZE = 0x48ffffff-MMAP_OFFSET # size of the register memory space

CM_PER_BASE = 0x44e00000 - MMAP_OFFSET
CM_PER_EPWMSS1_CLKCTRL = CM_PER_BASE + 0xcc
CM_PER_EPWMSS0_CLKCTRL = CM_PER_BASE + 0xd4
CM_PER_EPWMSS2_CLKCTRL = CM_PER_BASE + 0xd8

with open("/dev/mem", “r+b”) as f:
mem = mmap(f.fileno(), MMAP_SIZE, offset=MMAP_OFFSET)

def _andReg(address, mask):
“”" Sets 32-bit Register at address to its current value AND mask. “”"
_setReg(address, _getReg(address)&mask)

def _orReg(address, mask):
“”" Sets 32-bit Register at address to its current value OR mask. “”"
_setReg(address, _getReg(address)|mask)

def _xorReg(address, mask):
“”" Sets 32-bit Register at address to its current value XOR mask. “”"
_setReg(address, _getReg(address)^mask)

def _getReg(address):
“”" Returns unpacked 32 bit register value starting from address. “”"
return struct.unpack("<L", mem[address:address+4])[0]

def _setReg(address, new_value):
""" Sets 32 bits at given address to given value. “”"
mem[address:address+4] = struct.pack("<L", new_value)

_setReg(CM_PER_EPWMSS1_CLKCTRL, 0x2)

Steve

For those interested in a C solution for the mmap trick here is a program I wrote to enable/disable the PWM module clocks. This mmap method that both Steve and I have demonstrated can be used to directly access any register on the processor. Also, if anyone is wondering where Steve and I have gotten these magic offset values see the AM335x ARM Coretex-A8 Technical Reference Manual, page 207, which lists the memory map for all registers in the processor.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>

#define CM_PER_REG_START 0x44e00000
#define CM_PER_REG_LENGTH 1024
#define CM_PER_EPWMSS0_CLKCTRL_OFFSET 0xd4
#define CM_PER_EPWMSS1_CLKCTRL_OFFSET 0xcc
#define CM_PER_EPWMSS2_CLKCTRL_OFFSET 0xd8

#define PWM_CLOCK_ENABLE 0x2
#define PWM_CLOCK_DISABLE 0x0

#define PWM_LIST_MAX 3

int PWM_OFFSETS[PWM_LIST_MAX] = {
CM_PER_EPWMSS0_CLKCTRL_OFFSET / sizeof (uint32_t),
CM_PER_EPWMSS1_CLKCTRL_OFFSET / sizeof (uint32_t),
CM_PER_EPWMSS2_CLKCTRL_OFFSET / sizeof (uint32_t)
};

void print_usage (const char *message)
{
if (message)
printf ("%s\n", message);

printf (“pwm_clock <-e | -d> <PWM [PWM]>\n\n”);
}

int main (int argc, char **argv)
{
int i;
int *cur_list = NULL;
int *cur_list_index = NULL;
int enable_list[PWM_LIST_MAX];
int enable_list_index = 0;
int disable_list[PWM_LIST_MAX];
int disable_list_index = 0;
int dev_mem_fd;
volatile uint32_t *cm_per_regs;

for (i = 0; i < PWM_LIST_MAX; ++i) {
enable_list[i] = -1;
disable_list[i] = -1;
}

for (i = 1; i < argc; ++i) {
if (strncmp (argv[i], “-e”, 2) == 0) {
cur_list = enable_list;
cur_list_index = &enable_list_index;
}
else if (strncmp (argv[i], “-d”, 2) == 0) {
cur_list = disable_list;
cur_list_index = &disable_list_index;
}
else {
if (!cur_list) {
print_usage (0);
return 1;
}

if (*cur_list_index >= PWM_LIST_MAX) {
print_usage (“Too many PWM’s specified for an option”);
return 1;
}

cur_list[*cur_list_index] = atoi (argv[i]);
++*cur_list_index;
}
}

dev_mem_fd = open ("/dev/mem", O_RDWR);
if (dev_mem_fd == -1) {
perror (“open failed”);
return 1;
}

cm_per_regs = (volatile uint32_t *)mmap (NULL, CM_PER_REG_LENGTH,
PROT_READ | PROT_WRITE, MAP_SHARED, dev_mem_fd, CM_PER_REG_START);
if (cm_per_regs == (volatile uint32_t *)MAP_FAILED) {
perror (“mmap failed”);
close (dev_mem_fd);
return 1;
}

for (i = 0; i < PWM_LIST_MAX && enable_list[i] != -1; ++i) {
if (enable_list[i] < 0 || enable_list[i] >= PWM_LIST_MAX) {
printf (“Invalid PWM specified, %d\n”, enable_list[i]);
goto out;
}

printf (“Enabling PWM %d\n”, enable_list[i]);
cm_per_regs[PWM_OFFSETS[enable_list[i]]] = PWM_CLOCK_ENABLE;
}

for (i = 0; i < PWM_LIST_MAX && disable_list[i] != -1; ++i) {
if (disable_list[i] < 0 || disable_list[i] >= PWM_LIST_MAX) {
printf (“Invalid PWM specified, %d\n”, disable_list[i]);
goto out;
}

printf (“Disabling PWM %d\n”, disable_list[i]);
cm_per_regs[PWM_OFFSETS[disable_list[i]]] = PWM_CLOCK_DISABLE;
}

out:
munmap ((void *)cm_per_regs, CM_PER_REG_LENGTH);
close (dev_mem_fd);

return 0;
}

Dustin

Thanks so much you guys for all the work you’ve done. It would have taken me ages to figure this all out.

Steve, Python is complaining about there being no module named mmap. Did you use this with Python 2.7.2 on the BeagleBone?

  • Mike

Not at the bone right now, but I think I did an opkg install python-mmap (or something quite similar)

Steve

Dustin and Steve,

Thanks, guys!

For anyone who's having problems identifying the pins (hopefully I'm
not the only one), Port 9 Pin 14 is ehrpwm1:0 and Port 9 Pin 16 is
ehrpwm1:1. Both require mux setting to mode 6. Both output 3.3V, so
they work well with most LEDs too.

It looks like you can safely adjust the duty cycle on the fly by just
writing a new value to duty_percent. The value written to period_freq
is Hz.

Dan.

Thanks Steve, the python-mmap package worked for me.

I got a servo working! It’s nano-sized, so it worked just fine off the 3.3V output (although my multimeter measured 3.8V).

These settings were easier since the servo reference I was using described the pulse width control in terms of milliseconds

This one in particular needed a period of 20ms and a pulse width of 1.5ms to achieve the center position.

echo 2000000 > period_ns

echo 1500000 > duty_ns

echo 1 > run

  • Mike

Sweet, glad it's working! Can't wait to get home from this trip; I might have to take a quick break from my 'real' project for some r/c car shenanigans....

Steve

hey guys,

i just got this Rover 5: http://www.sparkfun.com/datasheets/Robotics/Rover%205%20Introduction.pdf

and I am wondering what is the frequency that i have to use in PWM
output?

thanks!

This doesn’t compare to the awesome work Steve and Dustin posted, but it does make use of it. It is a working example of controlling a servoe through PWM. Perhaps some of the other rookies can make use of it and some of the more seasoned developers can help improve it. To use it, run the Python code that was posted to perform the mapping. Then run this.

import os
import time

Enable clock for PWM module

MMAP code borrowed from https://groups.google.com/forum/?fromgroups#!topic/beagleboard/alKf67dwMHI

run the python version of mmap to enable the clock before running this

Put Port 9 Pin 14 into mode 6

omap_mux = ‘echo 6 > /sys/kernel/debug/omap_mux/gpmc_a2’
echo = ‘echo ’
path = ’ > /sys/class/pwm/ehrpwm.1:0/’
run = ‘run’
period_freq = ‘period_freq’
duty_percent = ‘duty_percent’

run = ‘echo 1 > /sys/class/pwm/ehrpwm.1:0/run’

set_period_freq = ‘echo 100 > /sys/class/pwm/ehrpwm.1:0/period_freq’

set_duty_percent = ‘echo 10 > /sys/class/pwm/ehrpwm.1:0/duty_percent’

count = 0;
os.system("%s%s%s%s" % (echo, str(1), path, run))
os.system("%s%s%s%s" % (echo, str(100), path, period_freq))
while (count < 50):
os.system("%s%s%s%s" % (echo, str(6), path, duty_percent))
time.sleep(2);
os.system("%s%s%s%s" % (echo, str(22), path, duty_percent))
time.sleep(2);
os.system("%s%s%s%s" % (echo, str(15), path, duty_percent))
time.sleep(2)
count = count + 1

#servo (s3004) - echo 100 > period_freq
#echo 6 > duty_percent 0 degrees
#echo 15 > duty_percent center
#echo 22 > duty_percent 180 degrees