Beaglebone: Inputs and Outputs: Accessing GPIO off the PRU

Andrew B. Wright, S. M. ’88, Ph. D.


Previous Post: Beaglebone: All you do is talk-talk, talk-talk

Next Post: Beaglebone: Inputs: Analog Input, the Inverted Pendulum Potentiometer


The archive

Instructions:  Download the archive into a directory, such as /root/code.  Unpack the archive with tar -xzf gpio-date.tar.gz.  Change to the archive directory.  Compile with make gpio and make install.  Run with ./gpio.

You should see a list of registers displayed to the screen.

Once the code is running on the PRU, you can toggle the switch attached to P8.18 (see below) and you should see the output from P8.15 toggle up to five times.


R30 and R31 are tied to General Purpose Input Output (gpio) resources.  However, with the Beaglebone, many of the pins carrying these signals are multiplexed with other signals, in particular the HDMI.  For example, PRU1 has no signals brought out to the P8 or P9 connectors that can be accessed with the cape-universal default cape.  In order to get these pins, you have to unload the default cape and write a custom dts file.  These aren’t unachievable tasks, but they make it difficult to use the PRU1.

If you want to use a realistic number of digital signals that aren’t tied up by default (i.e. avoid unloading the eMMC or HDMI device tree overlays), then you may have to go “off-PRU” to get the signals.

There are many, many tutorials on how to use R30 to access digital outs and R31 to access digital ins.  Not so much on getting to the GPIO in general.

It’s actually extremely easy with all the work in the previous blogs.  But, that one last piece of info is buried in the Technical Reference Manual (TRM).

If you go “off-PRU” you may be able to use only P9 for a substantial project such as the Control And Sensor SYstem (CASSY).  In addition to avoiding those reserved signals, it makes the wiring of the CAPE much less convoluted.

ARM side access for GPIO

The Beaglebone gives the user access to most of the hardware resources from the linux command line.  This allows the user to debug the hardware using high level tools.  Some people write code to access the hardware using the sysfs interface.

In the current incarnation, the pin needs to be set up from the command line:

> config-pin -i P8.15

tells what signals are available (default gpio gpio_pu gpio_pd gpio_input qep pru_ecap pruin).

For the current purpose, choose gpio with pull down resistor:

> config-pin P8.15 gpio_pd

which will set the output to ground unless a signal is applied.

P8.15 is connected to gpio1_15, so, it will be mapped to the directory /sys/class/gpio/gpio47.

Set the direction with:

> config-pin P8.15 out

And toggle the value with:

> config-pin P8.15 1


> config-pin P8.15 0

You can use a voltmeter connected to P8. 15 and ground (P8.1).  You should see values of 0v and 3.3 v for low to high. (NOTE: I’m only seeing 2.2 V now on P8.15.)

The input pin will be P8.18, which is connected to gpio2[1].

Set the pin with

> config-pin P8.18 gpio_input

which will set the pad control registers to ??.

PRU side access

Interconnect Set Up

Access to the gpio control registers is a bit tricky.  The example, /usr/lib/ti/pru-software-support-package/examples/am335x/PRU_access_const_table, gives an idea of the syntax required to access registers off the PRU.

The Clock Module Peripheral (cm_per) registers control access to memory mapped registers across the L3 and L4 interconnects.  The memory locations are shown in Table 2-2 in the Technical Reference Manual (TRM) as 0x44E0 0000 to 0x44E0 3FFF.

Table 8-29 of the TRM shows the offsets from 0x44E0 0000 for the enabling logic. These offsets are CM_PER_GPIO1_CLKCTRL (0xAC), CM_PER_GPIO2_CLKCTRL (0xB0), and CM_PER_GPIO3_CLKCTRL (0xB4).

Table 8-58 of the TRM shows the definition of GPIO1’s control word.  In particular, bits 0 and 1 are the MODULEMODE, where 0x0 is disabled, 0x2 is enabled, and 0x1 and 0x3 are reserved.  Therefore, in order to access GPIO1 from the PRU, 0x2 must be written to 0x44E0 00AC.

In the latest Debian incarnation, these bits are enabled by default.  If you want to double-check to ensure that the system is always in the proper state when you start your PRU program, you might want to set them again.

typedef struct{


volatile unsigned x;

volatile struct{

unsigned MODULEMODE: 2;
unsigned rsrvd1: 14;
unsigned IDLEST: 2;
unsigned rsrvd2: 13;

} x_bits;



volatile CLKCTRL *ptr_cm_per_gpio1_clkctrl = (volatile CLKCTRL *)0x44E000AC;

ptr_cm_per_gpio1_clkctrl->x_bits.MODULEMODE = 0x2;

The first line defines a pointer and assigns it the value 0x44E000AC.  Using the volatile keyword keeps the compiler from optimizing it out of existence. (NOTE:  can I get away with this without the far keyword?)

The second line assigns the value 0x2 to the MODULEMODE subfield.

GPIO Setup and Multiplexor

The GPIO registers must be configured for input or output, pull-up or pull-down, the multiplexing (which function is assigned to the pin), and the slew rate (fast/slow).  This is done with the pad control registers (Table 9-1).

The universal cape and config-pin allow you to do this from the command line.

It appears that CONTROL_MODULE is readable by the PRU, but not writeable.  So, the pad control registers need to be set by the ARM side using config-pin.  Once your project is ready for production, there are a number of ways to execute config-pin.  It is a good idea to only have one method for setting these pins, so limiting the access to the operating system is a good idea; however, it might be a good idea to read and check these values from the PRU and set an error if they are not what you expect.

Finding the pesky pins!

The base address for the CONTROL MODULE is found in Table 2-2 of the TRM and is 0x44E1 0000 (Control Module).

The MUXMODE field (bits 0-2) sets the multiplexor mode (0-7).  The valid MUXMODE’s can be found in the AM3358 data sheet (Table 4-1).  The Signal Name is cross-referenced against mode for each pin.  This data is also available in the Beaglebone Black’s System Reference Manual (SRM) in Tables 12 and 13.  The SRM cross references against the P8 and P9 headers on the Beaglebone.

For instance, Table 12 in the SRM reveals that P8.15 carries signal name GPMC_AD15.  The modes for this pin are gpmc_ad15, lcd_data16, mmc1_dat7, mmc2_dat3, eQEP2_strong, pr1_pru0_pru_r31_15, gpio1[15].  Mode 7 (gpio1[15]) is the desired mode for this application.

Table 9-10 of the TRM reveals that the offset for GPMC_AD15 is 0x83C.  Table 9-60 gives the control module word’s layout, which can be assigned to a structure:

typedef struct {


volatile unsigned x;

volatile struct {

unsigned mmode: 3;

unsigned puden: 1;

unsigned putypesel: 1;

unsigned rxactive: 1;

unsigned slewctrl: 1;

unsigned rsrvd1: 13;

unsigned rsrvd2: 12;

} x_bits;



volatile CONTROL_MODULE *ptr_gpio1_15_ctrlmod = (volatile CONTROL_MODULE *)0x44E1083C;

If you look at 0x44E1083C with these settings, you will see a binary number: x010 0111b, where the x is a reserved bit that could be either 0 or 1.

For the input, P8.18 is used.  The SRM shows that this signal is gpmc_clk with offset, 0x88C.  The signals on the pin are gpmc_clk_mux0, lcd_memory_clk, gpmc_wait1, mmc2_clk, mcasp0_fsr, gpio2[1].  So, mode 7 (gpio2[1]) is the desired mode, disable pulp/pulldown, pulldown selected (doesn’t matter), receiver enabled, and fast.  Looking at 0x44E1088C shows the binary number x010 1111b.

GPIO Control

The GPIO registers are located at 0x44E0 7000 to 0x44E0 7FFF (GPIO0), 0x4804 C000 to 0x4804 CFFF (GPIO1), 0x481A C000 to 0x481A CFFF (GPIO2), and 0x481A E000 to 0x481A EFFF (GPIO3).

The gpio registers can be accessed by:

volatile pruGPIO *GPIO0 = (volatile pruGPIO *)(0x44e07000);

volatile pruGPIO *GPIO1 = (volatile pruGPIO *)(0x4804c000);

volatile pruGPIO *GPIO2 = (volatile pruGPIO *)(0x481ac000);

volatile pruGPIO *GPIO3 = (volatile pruGPIO *)(0x481ae000);

where the pruGPIO typedef is defined in pru_gpio.h, which implement the register definition from Table 25-5 in the TRM.

The Program for PRU0

To access the pin for this project, P8.15, use the config-pin utility (added to the Makefile):

config-pin P8.15 gpio

which is done in the Makefile when you “make install”.

The resource table is the same used in the previous examples.  The linker command file is the same used in previous examples, but renamed as gpio.cmd.

In previous versions of the OS, gpio2 and gpio3 were not enabled at boot.  The CM_PER register had to be adjusted to activate these modules.  However, in the current version, all four gpio modules are active by default.

With the remoteproc interface, passing data back and forth is much easier.

From the SRM, P8.15 uses GPIO1 [15].  In the SRM, 15 is in decimal; however, in pro_gpio.h, access requires hexadecimal, so GPIO1->SD_bit.SD_bitf and GPIO1->OE_bit.OE_bitf.  For whatever reason, enabling the output requires that the OE bit be set to zero, which seems a bit reversed from my way of thinking.

Hence, the initialization to use GPIO1 bit 15 looks like:

ptr_cm_per_gpio1_clkctrl->x_bits.MODULEMODE = 0x2; //turn on the clock, just in case

//set up gpio1[15] to pull resistor, mode ?? (gpio), fast slew – can do via config-pin

GPIO1->OE_bit.OE_bitf = 0; //enable output

GPIO1->CD_bit.CD_bitf = 1; //set the output to zero

use gpio as an output – voltmeter, oscope

use gpio as an input – rig up a bumper switch (see novel encoder interface blog for circuit)

For the CASSY project, the following signals will be allocated:

Pin signal CASSY I/O Address Mode
P9.22 EHPWM0A Right Motor Plus (6 ma)  0x154  0x3
P9.21 EHPWM0B Right Motor Minus (6 ma)  0x150  0x3
P9.12 GPIO1 [28] Right Motor Plus Direction (6 ma)  0x078  0x7
P9.15 GPIO1 [16] Right Motor Minus Direction (6 ma)  0x040 0x7
P9.14 EHPWM1A Left Motor Plus (6 ma)  0x048  0x6
P9.16 EHPWM1B Left Motor Minus (6 ma)  0x04c  0x6
P9.11 GPIO0 [30] Left Motor Plus Direction (6 ma)  0x070  0x7
P9.13 GPIO0 [31] Left Motor Minus Direction (6 ma)  0x074  0x7
P9.30 PRU0 R31 [2] Right Encoder Channel A  (6 ma)  0x198  0x26
P9.27 PRU0 R31 [5] Right Encoder Channel B (6 ma)  0x1a4  0x26
P9.24 PRU0 R31 [16] Left Encoder Channel A (4 ma)  0x184  0x26
P9.42 PRU0 R31 [4] Left Encoder Channel B (6 ma)  0x1a0  0x26
P9.39 Analog In0 (25 ma) Inverted Pendulum Potentiometer  0x20
P9.40 Analog In1 (25 ma) unused  0x20
P8.7 gpio2 [2] Left Front Bumper


P8.8  gpio2 [3] Right Front Bumper  0x094  0x27
P8.9 gpio2 [5] Left Rear Bumper
P8.10  gpio2 [4] Right Rear Bumper


P9.37 Analog In2 (25 ma) unused  0x20
P9.38 Analog In3 (25 ma) unused  0x20
P9.33 Analog In4 (25 ma) unused  0x20
P9.34 Analog Ground
P9.32 Analog Vdd (for pot)
P9.7,8 Sys 5V (for logic power, optoisolator, encoder)
P9.23 GPIO1 [17] Cape enable (mainly the motors)  0x044 0x7
P9.1, 2, 43, 44, 45, 46 Digital Ground
P9.17  gpio0 [5], i2c1 – scl  0x15C 0x72
P9.18  gpio0 [4], i2c1 – sda  0x158 0x72
P9.26 pru1 [16]
P9.36 AIN5


Pins in use by HDMI or other P9.25 P9.28 P9.29 P9.31 P9.41 P9.19 P9.20

This is a work in progress, so signals may be added, removed, or changed as the implementation continues.



Archived Post: Beaglebone: turning a motor

Archived Post: Beaglebone: To the Batcave! Wiring up an optoisolator/h-bridge CAPE

The following table lists pins on the headers that are not part of the CASSY project, but which have been tested.  It is a difficult process to get access to the pins, especially considering that many are in use by default.  So, when the task has been done, best to write it down for later consumption.

Pin signal CASSY I/O Address Mode
P8.20 GPMC_CSn2 carries GPIO1 [31] in use by BB-BONE-EMMC-2G  0x084  0x7
P8.19 GPMC_AD8 carriers gpio0 [22] speaker  0x020  0x7
P8.13 ??? speaker  0xxx  0xx
P8.xx ??? speaker  0xxx  0xx
P8.xx ??? speaker  0xxx  0xx
P8.xx ??? speaker  0xxx  0xx
P8.xx ??? speaker  0xxx  0xx
P8.03 GPMC_AD6 in use  0x018  0x7


Before you can access the pins, you also have to add them to your dts file:

exclusive-use =
"P9.11", //uart4_rxd ... gpio0[30] ... 0x7 ... gpmc_wait0
"P9.12", //GPIO1_28 ... 0x7 ... gpmc_ben1
"P9.13", //uart4_txd ... gpio0[31] ... 0x7 ... gpmc_wpn
"P9.15", //gpio1_16 ... 0x7 ... gpmc_a0
fragment@0 {
target = <&am33xx_pinmux>;
__overlay__ {
pruicss_cassy: pinmux_pruicss_cassy{
pinctrl-single,pins = < 0x070 0x7 //P9.11 0x078 0x7 //P9.12 0x074 0x7 //P9.13 0x040 0x7 //P9.15 >;

You almost certainly already have entries in your dts file.  The above entries would patch into the appropriate fragments.



Posted in: Robotics

Comments are closed.