Andrew B. Wright, S. M. ’88, Ph. D.
June 9, 2018
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.
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.
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:
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.
echo “1” > /sys/class/gpio/gpio44/value
and the voltmeter value should change to about 3.3 v.
echo “0” > /sys/class/gpio/gpio44/value
and the voltmeter should return to 0 V.
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.
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 (see the next blog for 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 consequences.
Aside: When I was finishing my doctoral work, another graduate student in the lab was working on a slip-ring to allow his project (a rotating beam to simulate a gun turret) to make 360 degrees of rotation. The 100 pound apparatus was set up on one end of a table. My computer was at the other end of the table. On the first test, the control loop was unstable, and the gun turret started to rotate in an increasingly rapid circle. Due to rotating imbalance, the whole apparatus started to walk across the table towards me! Fortunately, the wires weren’t long enough, and the connection to the apparatus was severed before it could finish its march towards my computer, me, and, most importantly, the only copy of my dissertation draft. This all happened so fast that the other student could not intervene by punching the stop button before the machine reached me.
The redesigned logic uses a state machine (rpmsg_init) to step through the various tasks in order. Once the final task has been accomplished, the state (rpmsg_state) is used to bypass the initialization routine. This logic is placed in both the PRU-side code (main.c, pru_comm.c) and the ARM-side code (toggle.c, arm_comm.c).
A second state machine is used to parse the commands from the remoteproc driver (parse_message). By using a communication format of required 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. The ARM-side code sends “afS” and waits for the PRU to respond with “afs”. The ARM-side code then sends “afs” and awaits the response “afS”. This allows an ARM-side loop to send a series of these commands, which will produce a square-ish wave from the P8.12 pin on the beaglebone. If you use a voltmeter instead (placed in AC mode rather than DC), you will get a brief increase in voltage (the rms value between 0V and 3.3V) before the system returns to zero.
You should create a subdirectory under your $(HOME) called toggle (e.g., /root/toggle) and put your code files and the Makefile in that directory. You will have to create subdirectories pru0 and pru1 in your code directory for this example to work.
You should set the variable, CODE_ROOT, in the Makefile to the directory containing the code. The archive (below) is set for /root/code/toggle/toggle, which may not be desirable in all cases.
Download the archive to a directory (/root/code if you want the code to work out of the box) and unpack it using the command “tar -xzf ‘archive name’ “. This will create an extra directory /root/code/toggle in which the files will be located. The archive should create the pru0 and pru1 subdirectories as well. If you go to the directory, typing “make toggle” and “make install” will create the executable files. Typing “./toggle” should give you the output (10 iterations of P8.12 toggling and “aft” back from PRU0).
If you connect a voltmeter across pins P8.1 (ground) and P8.12 (signal), you should get a brief value above zero. If you connect an oscilloscope, you will see a burst of low->high-> low (10 cycles).
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.
For this code, I genericized the routines update_command_state and act_on_command. These routines interact with the code add_command_state and add_action_state. In your code (both ARM and PRU), you use the command add_command_state to send a code and a pointer to a function in your file. Likewise for add_action_state. The code in arm_comm and pru_comm loops through the codes stored when you added them and executes the function that you added when the codes match.
This allows you to easily create a flexible set of codes without changing the library (arm_comm or pru_comm).
For this code, I modified arm_comm.c to allow either PRU0 or PRU1 to be assigned at the command line. Since the files used to start and stop the PRU are different, sending the desired PRU at compile time allows you to choose without modifying arm_comm.c.
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
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 188.8.131.52 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.