Difference between revisions of "BeagleBoardPWM"

From eLinux.org
Jump to: navigation, search
m
m (Moved to ECE597Spring2010)
 
(26 intermediate revisions by 4 users not shown)
Line 1: Line 1:
There are three pins capable of [http://en.wikipedia.org/wiki/Pulse-width_modulation PWM (pulse-width modulation)] exposed on the C3/C4 BeagleBoard expansion header.  PWM is useful for control of a number of devices, from LEDs (which can be faded smoothly with PWM) to DC motors.  For robotics, this means that three hobby servos can easily be controlled by the Beagle given nothing more than a simple level-shifting circuit.
+
[[Category:ECE597Spring2010 |P]]
 +
 
 +
There are three pins capable of [http://en.wikipedia.org/wiki/Pulse-width_modulation PWM (pulse-width modulation)] exposed on the C3/C4 BeagleBoard expansion header.  PWM is useful for control of a number of devices, from LEDs (which can be faded smoothly with PWM) to [[ECE597 Project Sumo Robot | DC motors]].  For robotics, this means that three hobby servos can easily be controlled by the Beagle given nothing more than a simple [[BeagleBoard_Hardware_Interfacing#Level_Shifting | level-shifting]] circuit, with no CPU usage to speak of.
 +
 
 +
Alternative approaches are possible.  [http://github.com/tallakt/servodrive servodrive] is a kernel module that emits servo control PWM using straight GPIO (this page also claims that straight 1.8 V from the Beagle is sufficient to control servos).  [http://thoughtshubham.blogspot.com/2010/04/pwm-generation-in-beagleboard.html This page] shows how to use threading and GPIO to accomplish PWM in userspace.  The rest of this page focuses on use of the OMAP's hardware PWM capabilities.
  
 
== OMAP Mux Configuration ==
 
== OMAP Mux Configuration ==
  
Because the PWM pins are not set as such by default, the OMAP's mux must be configured to expose them before they can be used.  See [[BeagleBoardPinMux]] for more details on this procedure.  The short version is to add the following lines to the definition of <code>board_mux[]</code> in arch/arm/mach-omap2/board-omap3beagle.c (this has been tested with the 2.5.32 OMAP branch of the kernel).
+
Because the PWM pins are not set as such by default, the OMAP's mux must be configured to expose them before they can be used.  See [[BeagleBoardPinMux]] for more details on this procedure.  The short version is to add the following lines to the definition of <code>board_mux[]</code> in arch/arm/mach-omap2/board-omap3beagle.c (this has been tested with the 2.6.33 OMAP branch of the kernel).
  
 
  OMAP3_MUX(DSS_DATA15, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GPT9_PWMEVT, ball AB26, ex pin 4 */
 
  OMAP3_MUX(DSS_DATA15, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GPT9_PWMEVT, ball AB26, ex pin 4 */
Line 9: Line 13:
 
  OMAP3_MUX(UART2_RTS, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GTP10_PWMEVT, ball AB25, ex pin 10 */
 
  OMAP3_MUX(UART2_RTS, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GTP10_PWMEVT, ball AB25, ex pin 10 */
  
Obviously these lines should precede the line terminating the array.
+
Obviously these lines should precede the line terminating the array. This will likely be in a block conditional on CONFIG_OMAP_MUX, so you need to have the CONFIG_OMAP_MUX option set in your kernel config.
 +
 
 +
Note: Setting the kernel config option CONFIG_OMAP_RESET_CLOCKS to yes [[http://github.com/scottellis/omap3-pwm/blob/master/README | may cause problems]].
 +
 
 +
=== The 2.6.32 kernel ===
 +
 
 +
I found the above didn't work with the 2.6.32 kernel, rather the MUXing had to be done in u-boot.  [[BeagleBoardPinMux#Setting_Mux_Through_u-boot | Here]] is a nice description of how it is done.
  
 
== Activating PWM via Timer Registers ==
 
== Activating PWM via Timer Registers ==
  
PWM output on the BeagleBoard is done via the OMAP processor's general-purpose timer mechanism, described in the OMAP35x TRM in section 16.2.4 (page 2546).  To briefly summarize this (and simplify significantly), the general-purpose timer is a continuously-incrementing counter that can be configured to toggle the PWM output high when a certain value is reached, and low when it overflows.  By adjusting the first number the duty cycle can be set.  Setting the value the counter starts at can be used to set the frequency of the PWM.
+
PWM output on the BeagleBoard is done via the OMAP processor's general-purpose timer mechanism, described in the OMAP35x TRM in section 16.2.4 (page 2546, or page 2698 of the DM3730 TRM).  To briefly summarize this (and simplify significantly), the general-purpose timer is a continuously-incrementing counter that can be configured to toggle the PWM output high when a certain value is reached, and low when it overflows.  By adjusting the first number the duty cycle can be set.  Setting the value the counter starts at can be used to set the frequency of the PWM.
  
[[File:OMAP3-GPTimer-PWM.svg|thumb|right|300 px|alt="The registers TCRR, TLDR, and TMAR are illustrated as boxes partially-filled from the right.  Lines at the value of TLDR, the value of TMAR, and the end are labeled 'Start', 'Match', and 'Overflow' respectively.]]
+
[[File:OMAP3-GPTimer-PWM.svg|thumb|right|300 px|alt="The registers TCRR, TLDR, and TMAR are illustrated as boxes partially-filled from the right.  Lines at the value of TLDR, the value of TMAR, and the end are labeled 'Start', 'Match', and 'Overflow' respectively."]]
  
 
[[File:OMAP3-GPTimer-PWM-Waveform.svg|frameless|500 px|alt="A square waveform starting at 0, with the beginning labeled as 'Start'.  Where the waveform goes to 1 it is labeled 'Match', and when it returns to zero it is labeled 'Overflow, Start'.  It then repeats."]]
 
[[File:OMAP3-GPTimer-PWM-Waveform.svg|frameless|500 px|alt="A square waveform starting at 0, with the beginning labeled as 'Start'.  Where the waveform goes to 1 it is labeled 'Match', and when it returns to zero it is labeled 'Overflow, Start'.  It then repeats."]]
  
Each GP timer has a 4K block for memory-mapped registers (see TRM Table 16-12).  The start addresses of these blocks for the timers on the BeagleBoard are listed below.
+
Each GP timer has a 4K block for memory-mapped registers (see TRM Table 16-12, page 2558 OMAP3530 or page 2710 DM3730).  The start addresses of these blocks for the timers on the BeagleBoard are listed below.
  
 
{|border=1
 
{|border=1
|+ BeagleBoard C4 GP Timer Base Addresses
+
|+ BeagleBoard C3/C4 GP Timer Base Addresses
 
|-
 
|-
 
| '''Timer''' || '''Base address''' || '''Expansion header pin'''
 
| '''Timer''' || '''Base address''' || '''Expansion header pin'''
Line 36: Line 46:
  
 
{|border=1
 
{|border=1
|+ BeagleBoard C4 GP Timer Registers
+
|+ BeagleBoard C3/C4 GP Timer Registers
 
|-
 
|-
 
| '''Name''' || '''TRM section''' || '''Offset'''
 
| '''Name''' || '''TRM section''' || '''Offset'''
 
| '''Description'''
 
| '''Description'''
 
|-
 
|-
| TCLR || 16.3.2.6 (p.&nbsp;2568) || 0x024
+
| TCLR || 16.3.2.6 (p.&nbsp;2568/2719) || 0x024
 
| Control register<!-- (see below) -->.
 
| Control register<!-- (see below) -->.
 
|-
 
|-
| TCRR || 16.3.2.7 (p.&nbsp;2570) || 0x028
+
| TCRR || 16.3.2.7 (p.&nbsp;2570/2721) || 0x028
 
| The counter.  Increments with the clock when the timer is running.
 
| The counter.  Increments with the clock when the timer is running.
 
|-
 
|-
| TLDR || 16.3.2.8 (p.&nbsp;2571) || 0x02c
+
| TLDR || 16.3.2.8 (p.&nbsp;2571/2722) || 0x02c
 
| Timer load register.  Holds the value assumed by TCRR when it overflows.
 
| Timer load register.  Holds the value assumed by TCRR when it overflows.
 
|-
 
|-
| TMAR || 16.3.2.11 (p.&nbsp;2575) || 0x038
+
| TMAR || 16.3.2.11 (p.&nbsp;2575/2725) || 0x038
 
| Value to be compared with the counter.
 
| Value to be compared with the counter.
 
|}
 
|}
Line 56: Line 66:
 
<!--The relevant bits of the control register TCLR are as follows:-->
 
<!--The relevant bits of the control register TCLR are as follows:-->
  
<!--TODO: show bits of TCLR, talk about mmap() on /dev/mem, add some figures -->
+
<!--TODO: show bits of TCLR -->
 +
 
 +
== Interacting with Timer Registers in Linux ==
 +
 
 +
The best way to interact with the timer registers is to use the [[BeagleBoard/GSoC/2010_Projects/Pulse_Width_Modulation | kernel module]] in development as a Google Summer of Code project (this driver is currently, as of July 2010, in development).
 +
 
 +
Historically interaction with the registers could be done via the special device <code>/dev/mem</code>.  This file contains a live view of the contents of physical memory --- meaning that reading and writing to the physical address of a timer register as an offset in <code>/dev/mem</code> reflects the actual thing.
 +
 
 +
There is one complication, though, in that reads and writes to the OMAP registers cannot be done with byte-oriented I/O (such as the <code>write()</code> system call); however, this can be worked around by using the <code>mmap()</code> syscall.  This means that a pointer to a register can be cast to <code>volatile uint32_t*</code> and function correctly.
 +
 
 +
== OMAP3530 PWM library ==
 +
 
 +
There is a small library available to simplify manipulating the timer registers via <code>/dev/mem</code>.  It is made available under the LGPL 2.1 or MIT license.
 +
 
 +
Download:
 +
* [http://www.rose-hulman.edu/~mosttw/omap3530-pwm-1.1.tar.gz omap3530-pwm-1.1.tar.gz] (LGPL/MIT, md5 b33232531321778eadc022fef9cf7bac)
 +
* [http://www.rose-hulman.edu/~mosttw/omap3530-pwm-1.0.tar.gz omap3530-pwm-1.0.tar.gz] (LGPL only, md5 ff77617cf07450be1444019809c75a0c)
 +
 
 +
(These links appear to be broken now.  You can get to a version of the PWM code by following the instructions [[EBC Exercise 01a Getting Exercise Support Materials | here]]. It describes how to load code from [https://github.com/MarkAYoder/BeagleBoard-exercises git@github.com:MarkAYoder/BeagleBoard-exercises.git] which contains the pwm code.)
 +
 
 +
Information regarding compilation of this program is not included in any README file so to ease compilation problems use the following.  Keep in mind this has been only tested when compiled on the beagleboard and not cross-compiled though it should still shed light if you have problems.
 +
 
 +
<pre>
 +
root@beagleboard:~# gcc *.c -o pwm-demo -lglib-2.0 -I/usr/include/glib-2.0
 +
-I/usr/lib/glib-2.0/include
 +
</pre>
 +
 
 +
When you run your code you may encounter the following error:
 +
 
 +
<pre>
 +
root@beagleboard:~# ./pwm-demo
 +
0
 +
Bus error
 +
root@beagleboard:~# dmesg | tail -1
 +
[  174.893035] Unhandled fault: external abort on non-linefetch (0x1818) at 0x4001e024
 +
</pre>
 +
 
 +
This is caused because the library does not enable the PWM clocks and you are not allowed to access them until this is done.  In order to fix this you need to recompile your kernel with the option CONFIG_OMAP_RESET_CLOCKS disabled.  More information can be found in this thread:
 +
http://groups.google.com/group/beagleboard/browse_thread/thread/ee42e5c59edf83cd/67be50968de6a9f2?lnk=gst&q=guint8#67be50968de6a9f2
 +
 
 +
or see the note above at: http://elinux.org/BeagleBoardPWM#OMAP_Mux_Configuration
 +
 +
 
 +
=== Example usage ===
 +
 
 +
<pre>
 +
#include <glib.h>
 +
#include <errno.h>
 +
#include "omap3530-pwm.h"
 +
 
 +
int main(int argc, char **argv) {
 +
    int mem_fd = pwm_open_devmem();
 +
    if (mem_fd == -1) {
 +
        g_error("Unable to open /dev/mem, are you root?: %s", g_strerror(errno));
 +
    }
 +
    // Set instances 10 and 11 to use the 13 Mhz clock
 +
    pwm_config_clock(mem_fd, TRUE, TRUE);
 +
    guint8 *gpt10 = pwm_mmap_instance(mem_fd, 10);
 +
    // Get the resolution for 20 kHz PWM
 +
    guint32 resolution = pwm_calc_resolution(20000, PWM_FREQUENCY_13MHZ);
 +
    // Set to half duty cycle
 +
    pwm_config_timer(gpt10, resolution, 0.5);
 +
    pwm_munmap_instance(gpt10);
 +
    pwm_close_devmem(mem_fd);
 +
}
 +
</pre>
 +
 
 +
== Links and References ==
  
[[Category:BeagleBoard]]
+
* [http://www.jumpnowtek.com/index.php?option=com_content&view=article&id=56&Itemid=63 PWM from an OMAP3 Linux System], Scott Ellis ([http://github.com/scottellis/omap3-pwm kernel module])
[[Category:ECE597]]
+
* [[BeagleBoard/GSoC/2010_Projects/Pulse_Width_Modulation | 2010 GSoC Project: PWM driver]]
[[Category:Robotics]]
+
* <code>man 2 mmap</code>
 +
* <code>man 2 open</code>
 +
* [http://focus.ti.com/docs/prod/folders/print/omap3530.html OMAP 3530 Technical Reference Manaul]
 +
* [http://beagleboard.org/static/BBSRM_latest.pdf BeagleBoard System Reference Manaul]
 +
* [[Media:I2C_PWM_Hardware.pdf|Presentation on I2C, PWM and Hardware interfacing with the BeagleBoard]]
 +
* [[ECE597 Project Sumo Robot | Project: Sumo Robot]] -- uses PWM to control drive motors via L298 motor drivers.

Latest revision as of 16:35, 21 July 2014


There are three pins capable of PWM (pulse-width modulation) exposed on the C3/C4 BeagleBoard expansion header. PWM is useful for control of a number of devices, from LEDs (which can be faded smoothly with PWM) to DC motors. For robotics, this means that three hobby servos can easily be controlled by the Beagle given nothing more than a simple level-shifting circuit, with no CPU usage to speak of.

Alternative approaches are possible. servodrive is a kernel module that emits servo control PWM using straight GPIO (this page also claims that straight 1.8 V from the Beagle is sufficient to control servos). This page shows how to use threading and GPIO to accomplish PWM in userspace. The rest of this page focuses on use of the OMAP's hardware PWM capabilities.

OMAP Mux Configuration

Because the PWM pins are not set as such by default, the OMAP's mux must be configured to expose them before they can be used. See BeagleBoardPinMux for more details on this procedure. The short version is to add the following lines to the definition of board_mux[] in arch/arm/mach-omap2/board-omap3beagle.c (this has been tested with the 2.6.33 OMAP branch of the kernel).

OMAP3_MUX(DSS_DATA15, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GPT9_PWMEVT, ball AB26, ex pin 4 */
OMAP3_MUX(UART2_TX, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GPT11_PWMEVT, ball AA25, ex pin 6 */
OMAP3_MUX(UART2_RTS, OMAP_MUX_MODE2|OMAP_PIN_OUTPUT), /* GTP10_PWMEVT, ball AB25, ex pin 10 */

Obviously these lines should precede the line terminating the array. This will likely be in a block conditional on CONFIG_OMAP_MUX, so you need to have the CONFIG_OMAP_MUX option set in your kernel config.

Note: Setting the kernel config option CONFIG_OMAP_RESET_CLOCKS to yes [| may cause problems].

The 2.6.32 kernel

I found the above didn't work with the 2.6.32 kernel, rather the MUXing had to be done in u-boot. Here is a nice description of how it is done.

Activating PWM via Timer Registers

PWM output on the BeagleBoard is done via the OMAP processor's general-purpose timer mechanism, described in the OMAP35x TRM in section 16.2.4 (page 2546, or page 2698 of the DM3730 TRM). To briefly summarize this (and simplify significantly), the general-purpose timer is a continuously-incrementing counter that can be configured to toggle the PWM output high when a certain value is reached, and low when it overflows. By adjusting the first number the duty cycle can be set. Setting the value the counter starts at can be used to set the frequency of the PWM.

"The registers TCRR, TLDR, and TMAR are illustrated as boxes partially-filled from the right.  Lines at the value of TLDR, the value of TMAR, and the end are labeled 'Start', 'Match', and 'Overflow' respectively."

"A square waveform starting at 0, with the beginning labeled as 'Start'.  Where the waveform goes to 1 it is labeled 'Match', and when it returns to zero it is labeled 'Overflow, Start'.  It then repeats."

Each GP timer has a 4K block for memory-mapped registers (see TRM Table 16-12, page 2558 OMAP3530 or page 2710 DM3730). The start addresses of these blocks for the timers on the BeagleBoard are listed below.

BeagleBoard C3/C4 GP Timer Base Addresses
Timer Base address Expansion header pin
GPTIMER9 0x4904 0000 4
GPTIMER10 0x4808 6000 6
GPTIMER11 0x4808 8000 10

These are the registers relevant to our purpose:

BeagleBoard C3/C4 GP Timer Registers
Name TRM section Offset Description
TCLR 16.3.2.6 (p. 2568/2719) 0x024 Control register.
TCRR 16.3.2.7 (p. 2570/2721) 0x028 The counter. Increments with the clock when the timer is running.
TLDR 16.3.2.8 (p. 2571/2722) 0x02c Timer load register. Holds the value assumed by TCRR when it overflows.
TMAR 16.3.2.11 (p. 2575/2725) 0x038 Value to be compared with the counter.


Interacting with Timer Registers in Linux

The best way to interact with the timer registers is to use the kernel module in development as a Google Summer of Code project (this driver is currently, as of July 2010, in development).

Historically interaction with the registers could be done via the special device /dev/mem. This file contains a live view of the contents of physical memory --- meaning that reading and writing to the physical address of a timer register as an offset in /dev/mem reflects the actual thing.

There is one complication, though, in that reads and writes to the OMAP registers cannot be done with byte-oriented I/O (such as the write() system call); however, this can be worked around by using the mmap() syscall. This means that a pointer to a register can be cast to volatile uint32_t* and function correctly.

OMAP3530 PWM library

There is a small library available to simplify manipulating the timer registers via /dev/mem. It is made available under the LGPL 2.1 or MIT license.

Download:

(These links appear to be broken now. You can get to a version of the PWM code by following the instructions here. It describes how to load code from git@github.com:MarkAYoder/BeagleBoard-exercises.git which contains the pwm code.)

Information regarding compilation of this program is not included in any README file so to ease compilation problems use the following. Keep in mind this has been only tested when compiled on the beagleboard and not cross-compiled though it should still shed light if you have problems.

root@beagleboard:~# gcc *.c -o pwm-demo -lglib-2.0 -I/usr/include/glib-2.0 
-I/usr/lib/glib-2.0/include

When you run your code you may encounter the following error:

root@beagleboard:~# ./pwm-demo
0
Bus error
root@beagleboard:~# dmesg | tail -1
[  174.893035] Unhandled fault: external abort on non-linefetch (0x1818) at 0x4001e024

This is caused because the library does not enable the PWM clocks and you are not allowed to access them until this is done. In order to fix this you need to recompile your kernel with the option CONFIG_OMAP_RESET_CLOCKS disabled. More information can be found in this thread: http://groups.google.com/group/beagleboard/browse_thread/thread/ee42e5c59edf83cd/67be50968de6a9f2?lnk=gst&q=guint8#67be50968de6a9f2

or see the note above at: http://elinux.org/BeagleBoardPWM#OMAP_Mux_Configuration


Example usage

#include <glib.h>
#include <errno.h>
#include "omap3530-pwm.h"

int main(int argc, char **argv) {
    int mem_fd = pwm_open_devmem();
    if (mem_fd == -1) {
        g_error("Unable to open /dev/mem, are you root?: %s", g_strerror(errno));
    }
    // Set instances 10 and 11 to use the 13 Mhz clock
    pwm_config_clock(mem_fd, TRUE, TRUE);
    guint8 *gpt10 = pwm_mmap_instance(mem_fd, 10);
    // Get the resolution for 20 kHz PWM
    guint32 resolution = pwm_calc_resolution(20000, PWM_FREQUENCY_13MHZ);
    // Set to half duty cycle
    pwm_config_timer(gpt10, resolution, 0.5);
    pwm_munmap_instance(gpt10);
    pwm_close_devmem(mem_fd);
}

Links and References