Beaglebone: Outputs: Accessing GPIO off the PRU

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

6/22/2017

Previous Post: Beaglebone: “Time … it’s ticking away with my sanity”

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

The archive

Introduction

R30 and R31 are tied to 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 as many 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 took a while to ferret out of the TRM.

My main reason for going “off-PRU” came from wanting to use only P9 for CASSY.  In addition to avoiding those reserved signals, it makes the wiring of the CAPE much less convoluted.

For this 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

 0x090

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

 0x098

 0x27
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
unused
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

In order to get to the GPIO registers, define a header file, pru_gpio.h,

In a real application, bits wouldn't be set or cleared all the same. If you wanted to set only bit 16 on gpio1, the command would be GPIO1.SETDATAOUT = 1<<16; .

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

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 definition:

#define CM_PER_BASE ((volatile uint8_t *)(0x44E00000))

volatile uint8_t *ptr_cm;

ptr_cm = CM_PER_BASE;

ptr_cm[SPI0_CLKCTRL] = ON;

gets you to the register base.

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.

The Program

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

config-pin P8.7 gpio

The ARM side program has been modified to use PRU1 instead of PRU0.  This will allow the gpio pin to be toggled in real-time.  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.

The communication file, pru_comm.c, has seen some reorganization.  The change in logic allows the main code to call just one subroutine, rather than three.

The state machine to process actions is updated to use an enum with the enumerated states.

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.  As an aside, here is a routine that allows registers to be read and passed to the arm. (coming soon)

gpio.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#define CASSY_DEV "/dev/rpmsg_pru31"
#define SIZE 100

int main(void)
{
 int retval,CASSY,flags,k,j;
 unsigned char buf[SIZE];
 unsigned char *pbuf;

 printf("starting up\n");
 
 if((CASSY = open(CASSY_DEV, O_RDWR)) < 0){ printf("cannot open file\n"); exit (0);}
 flags = fcntl(CASSY, F_GETFL, 0);
 fcntl(CASSY, F_SETFL, flags | O_NONBLOCK);

 retval = write(CASSY, "afS", 3);
 printf("write retval = %d\n",retval);
 while((retval = read(CASSY, buf, SIZE)) > 0){
 printf("retval = %d\n",retval);
 }
 for(k=0;k<10000000;k++);
 retval = write(CASSY, "afs", 3);

 close(CASSY);
 return 0;
}

 

main.c

#include <stdint.h>
#include <string.h>
#include <pru_cfg.h>
#include <pru_ctrl.h>
#include "pru_gpio.h"

volatile register unsigned __R30;
volatile register unsigned __R31;

unsigned short send(char *buf);
void update_action_state(unsigned char *action_state);

#define LOOP 10000 //5 ns * 10000 = .05 msec cycle time


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

enum run_states {IDLE,INIT,START,STOP,EXPERIMENT};
unsigned char CASSY_STATE=IDLE;
unsigned short shadow_bit = 0;

void act_on_command(unsigned char action_state){
 switch(action_state){
 case 'S':
 CASSY_STATE = INIT;
 break;
 case 's':
 CASSY_STATE = STOP;
 break;
 }
 switch(CASSY_STATE){
 case INIT:
 #ifdef PRU1
 GPIO1->OE_bit.OE_bit11 = 0;
 GPIO1->CD_bit.CD_bit11 = 1;
 #endif
 CASSY_STATE = START;
 break;
 case START:
 #ifdef PRU1
 if(shadow_bit){
 GPIO1->SD_bit.SD_bit11 = 1;
 shadow_bit = 0;
 }else{
 GPIO1->CD_bit.CD_bit11 = 1;
 shadow_bit = 1;
 }
 #endif
 break;
 case STOP:
 #ifdef PRU1
 GPIO1->CD_bit.CD_bit11 = 1;
 GPIO1->OE_bit.OE_bit11 = 1;
 #endif
 CASSY_STATE = IDLE;
 break;
 case IDLE:
 break;
 }

}

void main(void)
{
 unsigned char action_state=0;

 CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
 
 #ifdef PRU0
 PRU0_CTRL.CTRL_bit.CTR_EN = 1; //turn on cycle counter
 #endif
 #ifdef PRU1
 PRU1_CTRL.CTRL_bit.CTR_EN = 1; //turn on cycle counter
 #endif
 while (1) {
 //here's where you insert real-time code
 update_action_state(&action_state);
 act_on_command(action_state);
 #ifdef PRU0
 while(PRU0_CTRL.CYCLE < LOOP);
 PRU0_CTRL.CYCLE = 0; //reset the cycle counter/stall counter
 #endif
 #ifdef PRU1
 while(PRU1_CTRL.CYCLE < LOOP);
 PRU1_CTRL.CYCLE = 0; //reset the cycle counter/stall counter
 #endif
 }
}

 

pru_comm.c

#include <stdint.h>
#include <string.h>
#include <pru_cfg.h>
#include <pru_intc.h>
#include <pru_rpmsg.h>
#include "resource_table.h"

register volatile unsigned __R31;
#define VIRTIO_CONFIG_S_DRIVER_OK 4

struct pru_rpmsg_transport transport;
unsigned short rpmsg_src,rpmsg_dst;
unsigned char buffer[RPMSG_BUF_SIZE], *pbuffer;
unsigned short buffer_state=0,plen=0;

unsigned short rpmsg_init(void){
 volatile unsigned char *status;
 static unsigned short rpmsg_state=0;

 switch (rpmsg_state){
 case 0:
 CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;
 rpmsg_state = 1;
 break;
 case 1:
 status = &resourceTable.rpmsg_vdev.status;
 if((*status & VIRTIO_CONFIG_S_DRIVER_OK)){ 

 pru_rpmsg_init(&transport, &resourceTable.rpmsg_vring0, &resourceTable.rpmsg_vring1, TO_ARM_HOST, FROM_ARM_HOST);
 rpmsg_state = 2;
 }
 break;
 case 2:
 if (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) == PRU_RPMSG_SUCCESS){ rpmsg_state = 3;
 }
 break;
 }
 return(rpmsg_state);
}

void listen(void){
 if(rpmsg_init() != 3){ buffer_state = 0; return;}
 if(buffer_state == 1) return;
 if(! (__R31 & HOST_INT)) return;
 if (pru_rpmsg_receive(&transport, &rpmsg_src, &rpmsg_dst, buffer, &plen) == PRU_RPMSG_SUCCESS){
 buffer_state = 1;
 pbuffer = buffer; 
 return;
 }
 CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST; 
 return;
}

void parse_the_message(unsigned char *action_state){
 static unsigned short command_state=0;
 *action_state = 0;
 if(buffer_state == 0) return;
 for(;plen > 0;plen--,pbuffer++){
 switch(command_state){
 case 0:
 if(*pbuffer == 'a') command_state = 1;
 break;
 case 1:
 if(*pbuffer == 'f') command_state = 2;
 break;
 case 2:
 *action_state = *pbuffer;
 command_state = 0;
 break;
 default:
 command_state = 0;
 break;

 }
 }
 buffer_state = 0;
}

void update_action_state(unsigned char *action_state){
 listen();
 parse_the_message(action_state);
}

unsigned short send(char *buf){
 return(pru_rpmsg_send(&transport,rpmsg_dst, rpmsg_src, buf,strlen(buf)));

}

 

Makefile

gpio: gpio.c
 gcc gpio.c -o gpio

CODE_ROOT:=/root/gpio/
LINKER_CMD_FILE:= gpio.cmd

PRU_RPMSG_ROOT:= /usr/lib/ti/pru-software-support-package/
PRU_INCLUDE:= --include_path=/usr/include/arm-linux-gnueabihf/ --include_path=$(PRU_RPMSG_ROOT)include/ --include_path=$(PRU_RPMSG_ROOT)include/am335x/
PRU0_ROOT:= $(CODE_ROOT)pru0/
PRU1_ROOT:= $(CODE_ROOT)pru1/
PRU_TOOLS:=/usr/bin/

CFLAGS=-v3 -O2 --endian=little --hardware_mac=on

LDFLAGS+= -L.


$(PRU0_ROOT)pru_comm0.object: $(CODE_ROOT)pru_comm.c
 $(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe $(PRU0_ROOT)pru_comm0.object $(CODE_ROOT)pru_comm.c -D PRU0

$(PRU0_ROOT)main0.object: $(CODE_ROOT)main.c
 $(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe $(PRU0_ROOT)main0.object $(CODE_ROOT)main.c -D PRU0

am335x-pru0-fw: $(PRU0_ROOT)main0.object $(PRU0_ROOT)pru_comm0.object
 $(PRU_TOOLS)clpru -z $(LINKER_CMD_FILE) -o $(PRU0_ROOT)am335x-pru0-fw $(PRU0_ROOT)main0.object $(PRU0_ROOT)pru_comm0.object -l$(PRU_RPMSG_ROOT)lib/rpmsg_lib.lib

$(PRU1_ROOT)pru_comm1.object: $(CODE_ROOT)pru_comm.c
 $(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe $(PRU1_ROOT)pru_comm1.object $(CODE_ROOT)pru_comm.c -D PRU1

$(PRU1_ROOT)main1.object: $(CODE_ROOT)main.c
 $(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe $(PRU1_ROOT)main1.object $(CODE_ROOT)main.c -D PRU1

am335x-pru1-fw: $(PRU1_ROOT)main1.object $(PRU1_ROOT)pru_comm1.object
 $(PRU_TOOLS)clpru -z $(LINKER_CMD_FILE) -o $(PRU1_ROOT)am335x-pru1-fw $(PRU1_ROOT)main1.object $(PRU1_ROOT)pru_comm1.object -l$(PRU_RPMSG_ROOT)lib/rpmsg_lib.lib

install: am335x-pru0-fw am335x-pru1-fw
 dmesg --clear
 cp $(PRU0_ROOT)/am335x-pru0-fw /lib/firmware
 cp $(PRU1_ROOT)/am335x-pru1-fw /lib/firmware
 rmmod -f pru_rproc
 modprobe pru_rproc
 config-pin P8.12 pruout
 config-pin P9.23 gpio
clean:
 rm $(PRU0_ROOT)/am335x-pru0-fw
 rm $(PRU1_ROOT)/am335x-pru1-fw
 rm $(PRU0_ROOT)/*.object
 rm $(PRU1_ROOT)/*.object
 rm *.pp
 config-pin P8.12 default
 config-pin P9.23 default

 

 


NOTES

Archived Post: Beaglebone: turning a motor

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

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.