Beaglebone: Outputs: Toggling a bit with the PRU (remoteproc version)

 

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

Previous Post: Beaglebone: remoteproc “Hello, world!”

Next Post: Beaglebone: Time …. it’s ticking away with my sanity.

In “Hello, world!” the first major debugging tool for the PRU was developed.  Having communication through the remoteproc driver allows the host and the PRU to pass information back and forth.  This is useful to provide debugging information and basic flow control of the PRU.

In embedded applications, the next debugging tool is to toggle a digital output.  The presence of a toggled bit allows the user to see timing information, especially when using real-time loops.

Step One:

The 4.1+ kernel has seen significant changes in how the cape manager system works.  A universal cape device tree (cape-universaln) is loaded on boot time in preference to individually designed devices trees.  It uses the config-pin utility to choose amongst the options defined in that overlay. You can see what cape is loaded by examining the file:

cat /sys/devices/platform/bone_capemgr/slots

You should see “Override Board Name,00A0,Override Manuf,cape-universaln” in one of the slots.

Determine what the pin can do with

config-pin -i P8.12

There should be these lines:

Function if cape loaded: default gpio gpio_pu gpio_pd pruout qep

Function information: gpio1_12 default gpio1_12 gpio1_12 gpio1_12 pr1_pru0_pru_r30_14 eQEP2A_in

If you want to test the pin with the arm, you should configure it to use the function gpio

config-pin P8.12 gpio

The gpio ID is computable from the gpio1_12 line.  Take the 1, multiply by 32 and add to 12.  The correct gpio will be 44.

Determine specific information for the pin with

config-pin -q P8.12

You should see the response:

P8_12 Mode: gpio Direction: in Value: 0

Look for the entry in the file system:

ls /sys/class/gpio

There should be a directory gpio44.

Change the direction by typing

echo “out” > /sys/class/gpio/gpio4/direction

and check the direction with

config-pin -q P8.12

You should see the response:

P8_12 Mode: gpio Direction: out Value: 0

Plug a voltmeter into GND and into P8-12 on the Beaglebone with a range around 5 v.

Type

echo “1” > /sys/class/gpio/gpio44/value

and the voltmeter value should change to about 3.3 v.

Type

echo “0” > /sys/class/gpio/gpio44/value

and the voltmeter should return to 0 V.


Step Two:

To gain PRU access to P8.12, type

config-pin P8.12 pruout

and check with

config-pin -q P8.12

The lower 16 bits of __R30 are connected to general purpose output (gpo).  Toggling bit 14 of __R30 will toggle P8-12 on the Beaglebone connector.

Archive of code for this blog

The linker command file will be the same as hello.cmd from this blog.  However, it’s been renamed toggle.cmd in the Makefile.  The resource_table.h is identical to the one in this blog

The code to perform communications with the remoteproc system has been split into a separate file, pru_comm.c.  This file will be used for every project after this.  It’s good practice to put the function prototypes in a header file, which will be done in a subsequent project.

The remoteproc communication flow was redesigned to work with a real-time loop.  In the previous example, there are a number of steps where the program waits for the system to do something.  These kinds of steps can lead to an infinite wait if something is not quite right.  Should your PRU be connected to a motor, the motor might be configured in an undesirable state, and this wait could lead to difficult consequences.

The redesigned logic uses a state machine to step through the various tasks in order.  Once the final task has been accomplished, the state is used to bypass the initialization routine.

A second state machine is used to parse the commands from the remoteproc driver.  By using a standard format of required the characters ‘a’ followed by ‘f’ followed by the actual command, spurious inputs from the system can be rejected.  As an aside, the reason for ‘a’ and ‘f’ are a throwback to the use of the hexidecimal number 0xa and 0xf as the markers.  Going with ASCII commands allows the PRU to be managed from the command line using the echo/cat interface.

In this simple state machine, “afS” starts PRU0 and “afs” stops PRU0.  In later exercises, additional commands will be coded.

Create a directory under /root called toggle (/root/toggle).  Create subdirectories pru0 and pru1.

Run make install to compile and install the code.

Run ./toggle to check that the program runs correctly.

The revised Makefile.

toggle: toggle.c
 gcc toggle.c -o toggle

CODE_ROOT:=/root/toggle/
LINKER_CMD_FILE:= toggle.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.

test1.object: test1.c
 $(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe test1.object test1.c
test2.object: test2.c
 $(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe test2.object test2.c
test: test1.object test2.object
 $(PRU_TOOLS)clpru -z $(LINKER_CMD_FILE) -o test test1.object test2.object -l$(PRU_RPMSG_ROOT)lib/rpmsg_lib.lib

$(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
# dmesg
# echo 'separator'
# echo 'separator'
# dmesg --clear
 modprobe pru_rproc
# dmesg
 config-pin P8.12 pruout
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

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(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;
 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;
}

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

}

main.c

#include <stdint.h>
#include <pru_cfg.h>

volatile register unsigned __R30;
volatile register unsigned __R31;

unsigned short rpmsg_init(void);
void parse_the_message(unsigned char *action_state);
unsigned short send(char *buf);
void listen(void);

#ifdef PRU0
#define SET_MASK (1<<14)
#define CLEAR_MASK ~(1<<14)
#define MESSAGE "PRU0 responding"
#endif
#ifdef PRU1
#define SET_MASK 0
#define CLEAR_MASK ~0
#define MESSAGE "PRU1 responding"
#endif

void main(void)
{
 unsigned short toggle=0;
 unsigned char action_state=0;
 CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
 
 while (1) { //this will be replaced with the real-time loop.
  //here's where you insert real-time code
  if(rpmsg_init() == 3){
   listen();
   parse_the_message(&action_state);
   switch(action_state){
    case 'S':
     __R30 ^= SET_MASK;
     if(toggle == 0){
      toggle = 1;
      send(MESSAGE);
     }
    break;
    case 's':
     __R30 &= CLEAR_MASK;
     action_state = 0;
    break;
   }
  }
 }
}

toggle.c

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

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

int main(void)
{
 struct pollfd CASSY;
 int retval,CASSY,flags,k;
 unsigned char buf[SIZE];

 if((CASSY = open(CASSY_DEV, O_RDWR)) < 0) exit (0);
 flags = fcntl(CASSY, F_GETFL, 0);
 fcntl(CASSY, F_SETFL, flags | O_NONBLOCK);

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

 close(CASSY);
 return 0;
}

Connect to an oscilloscope, run toggle, and view  cycles of a square wave.  This will not necessarily be a perfect square wave if there are paths through the code that have a different number of instructions.  But, it will be pretty solid.

In the next blog, a real-time loop will replace the while loop.  This will allow precise real-time control, along with control over the sample time of the loop.


NOTES

In another blog, I started this work and then abandoned it.  I need to bring that stuff over here as appropriate.  Then, I need to repurpose that blog.

Candidate pins to get PRU0 to output a PWM are P9-29 and P9-31.  On these pins, R30:0 and R30: 1 are mapped, allowing the pin to be toggled as a digital output, to test that the PRU can access the pin through the gpio interface, and to test the DTS overlay. Once the pin has been confirmed, the mode can be switched to PWM and the PWM registers can be modified to set up the desired functionality.  Alas, those pins are allocated to the HDMI interface.  If you want to use them for testing, you have to remove the HDMI overlay and add your overlay.  Again, more work than a test that’s supposed to make life easier is worth.

With each iteration of the operating system, I revisit this issue.  There have been significant improvements.  But, the easy-to-use interface still does not allow the HDMI-allocated pins to be configured with config-pin, even if the HDMI cape has been removed.  The boot interface to get another cape installed has not worked the way that it is documented (or I haven’t invested the time to trouble-shoot).

Since this blog is a primer to get folks started, making changes to the boot environment is beyond the scope at this point.

My current thinking is that the cape-universaln should be replaced with the cape-universalh and a custom device tree overlay installed to give the desired functionality.  I have done this kind of thing in later blogs.  So, for now, we’ll leave PRU1 alone and focus on PRU0.

The HDMI cape, which is part of cape-universaln, sucks down all the good pins.  So, in order to use the PRU1 and toggle a bit, the HDMI cape has to go.  The cape-universalh will allow the use of those pins, while retaining the EMMC pins.

Since cape-universaln is loaded at boot, the file /boot/uEnv.txt has to be edited to load cape-univeralh at boot instead.

Edit /boot/uEnv.txt to change:

cmdline=coherent_pool=1M quiet cape_universal=enable

to

cmdline=coherent_pool=1M quiet

In other words, drop the cape_universal=enable from the cmdline.

Reboot for this change to take effect.

Check to see that you do not have a cape now by typing “cat /sys/devices/platform/bone_capemgr/slots”  There should be no cape-universaln.

NOTE: the stuff that follows is supposed to work according to the internet, but I have not been able to get the R31 PRU pins to work with config-pin. It should be possible to develop a custom cape to get them working.

Manually load the cape using

config-pin overlay cape-universalh

This allows you to test the cape before loading it on boot.  If this works properly, “cat /sys/devices/platform/bone_capemgr/slots” will show you “Override Board Name,00A0,Override Manuf,cape-universalh”

Open /boot/uEnv.txt again and add the line (towards the bottom of the file, after the cmdline statement):

cape_enable=bone_capemgr.enable_partno=cape_universalh,<any other cape>

You might want the <any other cape> to get things such as a2d converters, i2c devices, etc. to work.  Perhaps in those cases, cape_universalh will get ‘r done.  Time will tell.  If you develop a custom cape, this would be the place for your overlay.

Reboot the beaglebone for these changes to take effect.

Check cat /sys/devices/platform/bone_capemgr/slots to verify that you have loaded the cape-universalh.

 


Aside:  TI provides example code.  A description of the various modules is located on this page. In particular, the example pru_access_const_table provides syntax that allows the PRU to access registers that are peripheral to the PRU memory space.

Relevant lines in the c-code:

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

volatile uint8_t *ptr_cm;

ptr_cm = CM_PER_BASE;

ptr_cm[SPI0_CLKCTRL] = ON;

The #define creates a pointer to memory location 0x44E00000.  A quick jog to Table 2-1 of the TRM, it can be seen that 0x44C0_0000 0x44FF_FFFF is where the L4 wake up peripheral configuration registers reside.

Table 2-2 contains the specifics of the L$_WKUP registers.  CM_PER is mapped to 0x44E0_0000 to 0x44E0_3FFF.

Specifics of these registers can be found in Chapter 8 on clock management.  Section 8.1.12.1 contains the details of the CM_PER registers.

Although the example in the PRU software uses an array and offsets to access, it seems that every peripheral uses a different entry in the CM_PER, so it is advisable to create a header file after the fashion of pru_cfg.h.  When I get this done, I will place it in my header files repository.

The memory locations should be mapped in the linker command file and accessed using a definition, either in the header file or in the c-code itself.  Since this location is not in the constant table, a cregister is not the way to go.  The method of defining an absolute address has some very negative features to it.  So, I’d avoid that approach.

The specifics on the L4 interconnect are contained in chapter 10 (for flavor, not useful to this topic).

In this specific example, the #define creates a pointer to the top of the register block of memory.  The pointer (CM_PER_BASE) is then passed to the local pointer, ptr_cm.  The specific register is accessed using the offset, SPI0_CLKCTRL, and the value ON is assigned to that location.

I should place this in the “Accessing GPIO off the PRU file.”  I need to use the GPIO registers off the PRU to accomplish.


Section 7.2 of the TRM contains info on the OCMC-RAM.  This is where cregister 30 points.

 

 

Next Blog: Beaglebone: “Time … it’s ticking away with my sanity”

Posted in: Robotics

Comments are closed.