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

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

6/22/2017

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

Next Post: Beaglebone: Accessing GPIO off the PRU

 

After debugging tasks, the next most important task in real-time control is to figure a way to have a real-time loop.

The classic methods are

  1. to program a timer and fire an interrupt
  2. to poll a timer at the bottom of a loop
  3. to calculate the number of instructions and enter a loop of nops to fill in the remaining time
  4. use PRU0 as an accurate timer to update PRU1

The first method won’t work on the PRU, since the PRU is not interruptible by design.

The second method may work, but the PRU doesn’t have an on-board timer and must look through one of the busses to get to a timer count (dmtimer).  This introduces uncertain bus delays into the system and could reduce accuracy unacceptably.

With a 5 ns instruction time, method 3 is going to yield the most accurate results.  Even if a few instructions are miscounted, the error in the loop won’t be much more than 15 ns.  If you’re controlling at the msec order of magnitude, that’s going to be difficult to detect and won’t influence the problem.

Method 4 would be a waste of a PRU.

The following intrinsic function on the PRU would be nice

void __delay_cycles(unsigned int cycles);

But, it needs to know the number of cycles at compile time.  So, it’s useless for the current problem (variable precise delay).

The CONTROL register has a CYCLE count.  This is how many cycles since the PRU was enabled.  The CNT_EN bit has to be set to enable the counter.  CYCLE = 0 resets the counter.

So, a loop like

while(CYCLE< 0x1000);

should count with minimal non-deterministic “depends on the calculation” instructions and give a pretty precise delta time. Perhaps the time won’t be exactly 1000 cycles, but cycle-to-cycle variations should be pretty close.

Most of the code will be identical to the bit-toggling code.  The enforcement of the timing will be done at the bottom of the while(1) loop, by determining how many cycles have been used and counting the remainder of the cycles until the desired number of cycles have passed.  The toggle will be moved to the top of the loop, where the number of cycles will always be the same.

Archive of code

Make a new directory under /root called realtime.

Change the name of toggle.cmd to realtime.cmd.  Change the name of toggle.c to realtime.c.  Make pru0 and pru1 under /root/realtime.  The files pru_comm.c and resource_table.h are the same.

Makefile:

realtime: realtime.c
 gcc realtime.c -o realtime

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

main.c:

#include <stdint.h>
#include <pru_cfg.h>
#include <pru_ctrl.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

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

void main(void)
{
 unsigned short toggle=0;
 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
 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;
 }
 }

 #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
 }
}

 

 


NOTES:

Archived Post: Beaglebone … all you want to do is talk talk, talk talk.

Archived Post: Beaglebone … turning a motor

 

Posted in: Robotics

Comments are closed.