Debugging your PRU C code
Recently I decided to turn my attention to the fairly unique Programmable Realtime Units on the BeagleBone's TI Sitara processor. My earlier blog post covered Coding for the BeagleBone PRU with C in 2019 and hopefully showed how it's become easier to work with the PRUs over the last few years. Whilst it's great to be able to write C code in a nice IDE before deploying, it's even better if you can debug your code and see what's happening when things don't quite go to plan (i.e. 90% of the time). If you're looking a working with the PRUs and you haven't read that post then I suggest you do, as I'll be starting where that blog post left off.
What you'll need
To follow along with this guide you'll need:
- A BeagleBone. I've worked with the black and green but I'm pretty sure any one will do.
- A debugger. I used a standalone XDS100. There are more expensive options like the XDS200, etc. but this will do. The XDS110 from a Launchpad might work. It failed when i tried before, but I know a bit more now!
- A JTAG headerheader to solder to your beaglebone. There are a few options, I went with FTR-110-51-L-D-06, but the important bit is FTR-110-**-*-D-06
Get your code deploying manually
There's no point trying to get this setup unless you have some simple code that you know you can build, deploy and run on the PRU. I suggest something like blinky to start with. Follow my previous guide to get it running from a manual deployment. Use the same code for debugging and you can be sure that it's the process that you're tweaking and not your code.
Solder on your header
I hope you're up for a little surface mount soldering. The header is 0.05" pitch with is small, but definitely manageable with a reasonable soldering iron. I considered using hot air, but decided just to go with an iron instead. The pads should be fairly obvious on the bottom of the BeagleBone. I found that they were pre-tinned on the green but left clear on the black (both the Element14 and BeagleBone.org versions). As I mentioned earlier there are a few different versions of the header. Provided it's a FTR-110-nn-p-D-x then you will have 0.05" pitch pins, and each rows will be 0.1" apart. The 110 stands for 10 pins per row and D for a double row header. I think the correct length is the nn=51 version but I know other people have used slightly different lengths without a problem. The p is for the plating - user preference on that one. The x should be 06 meaning that pin 6 is deliberately missing to match up with TI's cTI-20 IDC connector on the XDS110. If you're not using this programmer then I don't suppose it will matter. Otherwise, if you couldn't find the 06 version then yank out pin 6 now. Personally, I have almost connected this the wrong way round, so a polarised connector is probably worth the effort!
I also found that with the header in place you'll probably want some stand-offs on the board. Mine are just 20mm M3 nylon screws and they seem to work just fine.
Set up CCS
Setup target configuration
You will need to add a target configuration. This just tells CCS what debugger to use and what processor to expect it to be connected to. Go to the target configurations window, add a new one, select "BeagleBone_Black" and you're done. There is an advanced tab that I spent a while messing around with, but you don't need to. You can also click "Test Connection" to verify that everything is connected OK.
{gallery} Target configuration |
---|
Target Configuration for the BeagleBone: This is all you need to connect to your BeagleBone |
Target Configuration (Advanced): Leaving everything here as default works fine. |
Target Configuration (Test Connection): Check that your debugger is working and you can see the BB. |
Setup debug configuration
Setting up the Target Configuration isn't too tricky. The bit that had me stumped for a while was the Debug Configuration. This tells CCS what to do when you want to debug your code. I was banging my head again the wall with errors like "Cannot access the DAP" and "Verification failed". TI's PRU training was fairly helpful but didn't mention these problems. "Just click debug" and everything should be fine.
However, if Linux has ever been running on your BeagleBone, you need to undo some of the things it has configured. There is a small snippet of JavaScript that you need to run as the debug configuration starts - the magical bbb_pru_startup,js which I stumbled across referenced here and available here. This is the missing link. It's too important to be hidden away and isn't mentioned at all in TI's PRU training. I can only assume that the training is done on a virgin Sitara processor. It's not included in the otherwise excellent PRU software support package. I have discovered that as long as you run this script once before debugging that is enough. Further debuggging session don't need it - until you boot to Linux again. It doesn't get in the way though, so I suggest you leave it as part of your debug configuration.
Update: As the link to bbb_pru_startup.js appear to have vanished, here's the file contents:
//**************************************************************************** // BBB_PRU_STARTUP.JS // Version 0.20.00 // Released subject to the license at the end of this file //**************************************************************************** //---------------------------------------------------------------------------- // Purpose: Prepare the AM355x SOC so CCS can connect to the PRU as if it were // any other standalone CPU. // // This script is only intended to be used as described in // http://processors.wiki.ti.com/index.php/Debug_Configuration_Initialization_Scripts // In particular, it does NOT WORK as a standalone script. // // Presumptions: // - Target system is BeagleBone Black or BeagleBone White // - The target configuration has launched // - PRU connection occurs after this script runs //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // Constants //---------------------------------------------------------------------------- var AM335X_INPUT_EN = 1 << 5; var AM335X_PULL_DISA = 1 << 3; var AM335X_PIN_OUTPUT = 0; var AM335X_PIN_INPUT = AM335X_INPUT_EN | AM335X_PULL_DISA; //---------------------------------------------------------------------------- // Invoke the main function in this file //---------------------------------------------------------------------------- startup_pru(); //**************************************************************************** // STARTUP_PRU //**************************************************************************** function startup_pru() { var errCode = 0; //------------------------------------------------------------------------- // Import the DSS packages into our namespace to save on typing //------------------------------------------------------------------------- importPackage(Packages.com.ti.debug.engine.scripting); importPackage(Packages.com.ti.ccstudio.scripting.environment); importPackage(Packages.java.lang); //------------------------------------------------------------------------- // Create our scripting environment object - which is the main entry // point into any script and the factory for creating other Scriptable // servers and Sessions //------------------------------------------------------------------------- script = ScriptingEnvironment.instance(); //------------------------------------------------------------------------- // Get the Debug Server and start a Debug Session //------------------------------------------------------------------------- debugServer = script.getServer("DebugServer.1"); //------------------------------------------------------------------------- // Open a session on the Debug Access Port (DAP) //------------------------------------------------------------------------- var debugSessionDAP = debugServer.openSession("*", "CS_DAP_DebugSS"); //------------------------------------------------------------------------- // Connect to the DAP. Error check. //------------------------------------------------------------------------- print("Connecting to DAP"); try { debugSessionDAP.target.connect(); } catch (ex) { errCode = getErrorCode(ex); print("Error code #" + errCode + ", could not connect to DAP!"); print("Aborting!"); java.lang.System.exit(errCode != 0 ? errCode : 1); } //------------------------------------------------------------------------- // Init steps for PRU carried out by the DAP //------------------------------------------------------------------------- print("Configuring PRU pins"); PRU_PINMUX_Config(debugSessionDAP.memory); print("Enabling ICSS clock"); debugSessionDAP.expression.evaluate( "*((unsigned int*) 0x44E000E8 ) |= 0x02;"); print("Resetting ICSS"); debugSessionDAP.expression.evaluate( "*((unsigned int*) 0x44E00C00 ) |= 0x2;"); debugSessionDAP.expression.evaluate( "*((unsigned int*) 0x44E00C00 ) &= 0xFFFFFFFD;"); print("Done"); } //**************************************************************************** // PRU_PINMUX_Config //**************************************************************************** function PRU_PINMUX_Config(dsDAP_mem) [ // GEL_TextOut("****** PRU Cape GPI/O PINMUX is being configured ***** \n","Output",1,1,1); //------------------------------------------------------------------------- // LEDS //------------------------------------------------------------------------- //red led = pru0 r30_3 ARM pin c12 dsDAP_mem.writeData(0, 0x44e1099c, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //green led = pru0 r30_2 arm pin d12 dsDAP_mem.writeData(0, 0x44e10998, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //blue led = pru0 r30_0 arm pin a13 dsDAP_mem.writeData(0, 0x44e10990, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //orange led = pru0 r30_1 arm pin b13 dsDAP_mem.writeData(0, 0x44e10994, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //RGB_0 led = pru1 r30_2 arm pin r3 dsDAP_mem.writeData(0, 0x44e108AC, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //RGB_1 led = pru1 r30_3 arm pin r4 dsDAP_mem.writeData(0, 0x44e108B0, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //RGB_2 led = pru0 r30_4 arm pin t1 dsDAP_mem.writeData(0, 0x44e108B4, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //------------------------------------------------------------------------- // Switches //------------------------------------------------------------------------- //switch 1 = pru0 r31_5 c13 dsDAP_mem.writeData(0, 0x44e109a4, AM335X_PIN_INPUT | 6, 32); //mode 6 //switch 2 = pru0 r31_7 a14 dsDAP_mem.writeData(0, 0x44e109ac, AM335X_PIN_INPUT | 6, 32); //mode 6 //------------------------------------------------------------------------- // Audio //------------------------------------------------------------------------- //audio data pr1 r30_0 r1 dsDAP_mem.writeData(0, 0x44e108a0, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //audio clk pr1 r30_1 r2 dsDAP_mem.writeData(0, 0x44e108a4, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //audio sync pr1 r30_2 r3 dsDAP_mem.writeData(0, 0x44e108a8, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //------------------------------------------------------------------------- // UART //------------------------------------------------------------------------- //Uart txd d15 dsDAP_mem.writeData(0, 0x44e10984, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //UART rxd d16 dsDAP_mem.writeData(0, 0x44e10980, AM335X_PIN_INPUT | 5, 32); //mode 5 //UART rts b17 dsDAP_mem.writeData(0, 0x44e1097C, AM335X_PIN_OUTPUT | 5, 32); //mode 5 //UART cts a17 dsDAP_mem.writeData(0, 0x44e10978, AM335X_PIN_INPUT | 5, 32); //mode 5 //------------ ------------------------------------------------------------ // LCD //------------------------------------------------------------------------- //lcd rs t3 dsDAP_mem.writeData(0, 0x44e108B8, AM335X_PIN_OUTPUT | 4, 32); //mode 4 //lcd r/w t4 dsDAP_mem.writeData(0, 0x44e108BC, AM335X_PIN_OUTPUT | 4, 32); //mode 4 //lcd e v5 dsDAP_mem.writeData(0, 0x44e108E8, AM335X_PIN_OUTPUT | 4, 32); //mode 4 //lcd data4 b16 dsDAP_mem.writeData(0, 0x44e10958, AM335X_PIN_OUTPUT | 6, 32); //mode 6 //lcd data5 a16 dsDAP_mem.writeData(0, 0x44e1095C, AM335X_PIN_OUTPUT | 6, 32); //mode 6 //lcd data6 u5 dsDAP_mem.writeData(0, 0x44e108e0, AM335X_PIN_OUTPUT | 4, 32); //mode 4 //lcd data7 r5 dsDAP_mem.writeData(0, 0x44e108e4, AM335X_PIN_OUTPUT | 4, 32); //mode 4 //------------------------------------------------------------------------- // TEMP SENSOR //------------------------------------------------------------------------- //gpmc ad14 - input (GPI direct) dsDAP_mem.writeData(0, 0x44E10838, AM335X_PIN_INPUT | 6, 32); //mode 6 //lcd data7 - output (DIGIO) dsDAP_mem.writeData(0, 0x44E108BC, AM335X_PIN_OUTPUT | 4, 32); //mode 4 } //**************************************************************************** // getErrorCode //**************************************************************************** function getErrorCode(exception) { var ex2 = exception.javaException; if (ex2 instanceof Packages.com.ti.ccstudio.scripting.environment.ScriptingException) { return ex2.getErrorID(); } return 0; } /* * * Copyright (C) 2015 Texas Instruments Incorporated - * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */
To get this set up, click the down arrow next to the green bug and select "Debug Configurations...". Click the button to create a new Launch Configuration (different name, same thing). Select the Target Configuration from the next step. Were you to go ahead and try to debug now, you'd get the "Cannot access the DAP" error that plagued me for ages. Just to the right of the empty initialization script box you can click "File System..." or "Workspace..." and reference bbb_pru_startup.js from wherever you saved it. I put it in the workspace but it doesn't matter where it is.
You don't need to poke around in the JavaScript, but of course I did. It sets up a few pins related to the PRU Cape which is great if you have one. The interesting thing though seem to be this:
print("Enabling ICSS clock");
debugSessionDAP.expression.evaluate("*((unsigned int*) 0x44E000E8 ) |= 0x02;");
print("Resetting ICSS");
debugSessionDAP.expression.evaluate("*((unsigned int*) 0x44E00C00 ) |= 0x2;");
debugSessionDAP.expression.evaluate("*((unsigned int*) 0x44E00C00 ) &= 0xFFFFFFFD;");
This is documented on the PRU_ICSS Debug page as follows:
Because the PRU-ICSS cores are deeply embedded inside the AM335x, there are multiple registers that need to be configured in order to perform JTAG debug. This includes:
- ICSS clocks
- ICSS reset
- Pin muxing for PRU-ICSS signals
- And in some cases even enabling various JTAG clocks so you can connect. (Linux turns many clocks off.)
A script was developed in large part from the work in Debug Configuration Initialization Scripts to handle this automatically.
.
Let's debug!
Prevent Linux from booting
You need to make sure the Linux isn't currently running on the BeagleBone. Unfortunately I don't think you can debug the PRU whilst the Sitara's A8 core is busy running Linux. Obviously this means you can't debug any interaction between your Linux code and the PRU. Maybe it's possible, but I've not heard of it. (Jan Cumps has an excellent post on using CCS to debug just the Linux bit by the way.)
The easiest way is to make sure you have no micro SD card inserted and hold down the "Boot" button (S2) whilst powering on. I read other guides that suggested formatting the eMMC but you don't have to go this far.
Debug your code
With Linux temporarily disabled, and the startup script in your debug configuration, all is right with the world. Debug your PRU project and you should see it fire up in CCS, deploy and stop at a breakpoint in the first line of your PRU code. We've cracked it! Happy PRU debugging.
Top Comments