Overview
--------
[Serial Wire Output (SWO) Trace](https://processors.wiki.ti.com/index.php/SWO_Trace) is a single pin trace interface that allow users to trace the program flow and profile hardware events. These profiling capabilities are very important for debugging, validating and characterizing applications. In this workshop, you will experience the SWO Trace tools using basic examples.
Requirements
------------
* A SimpleLink LaunchPad
* MSP432P
* MSP432E
* CC13xx
* CC2640R2
* CC2652
* [Code Composer Studio](https://processors.wiki.ti.com/index.php/Download_CCS) (version 8 or greater).
* The latest version of the SimpleLink SDK for your LaunchPad
### Check target configuration
You will need to follow these steps for each new project you create. To use the SWO trace for your project, you need to check if the target configuration is configured for it.
1. Open the target configuration file:
![target config](images/swo_targ_config.PNG)
2. Go to **Advanced Setup**
![target advanced setup](images/swo_adv_set.PNG)
3. Select the top node and follow the steps for your device below:
[[+g For MSP432 boards (expand)
Make sure that the target uses SWD as the debug protocol with TDO connection to the Aux COM port:
![correct setting](images/swo_cor_config.PNG)
+]]
[[+g For CC13xx, CC2640R2, and CC2652 boards (expand)
Make sure that the parameters shown in the screenshot below are configured:
![correct setting](images/swo_cor_config2.PNG)
Additionally, you must modify the board-specific configuration file. The name of the file varies depending the device and board. For launchpads, the format will be (DEVICE)_LAUNCHXL_fxns.c. In this file, there will be a function called Board_initHook(). Add the following lines to the function:
```c
// Map Serial Wire Viewer to JTAG TDO pin
IOCPortConfigureSet(IOID_16, IOC_PORT_MCU_SWV, IOC_STD_OUTPUT);
```
For example, if you are using a [CC1310 LaunchPad](https://store.ti.com/LAUNCHXL-CC1310-SimpleLink-Sub-1-GHz-wireless-microcontroller-MCU-LaunchPad-development-kit-P49907.aspx), the file is called CC1310_LAUNCHXL_fxns.c. The modifications to Board_initHook() would look as so:
![pinmux for swo](images/swo_pinmux.PNG)
[[r Note:
This is not supported on some older SDKs. Make sure you are using the latest version of the SimpleLink SDK for your LaunchPad.]]
+]]
Lab 1: Statistical Function Profiling
---
Statistical Function Profiling is a useful tool that shows the frequency of function calls within a program using statistical methods. The Program Counter (PC) is regularly sampled, and the function being executed is determined based on the PC value. This tool is important because it can show which functions are more crucial.
### 1.1 Example program
The basis for the example is the **empty** project which can be imported from the SDK using [Resource Explorer](https://software-dl.ti.com/ccs/esd/documents/users_guide/ccs_getting-started.html#resource-explorer):
{{b **Software → SimpleLink (YourDeviceFamily) SDK – v:(X.X.X.X) → Examples → Development Tools → (YourLaunchPad) → TI Drivers → empty → TI-RTOS → CCS Compiler → empty**}}
Small modifications to the project are needed to better demonstrate the capabilities of the profiler.
1. Start by adding the following dummy functions above the `mainThread()` function in empty.c.
```c
void divBy2() {
int i = 0;
for (i=0;i<50;i++) {
}
}
void divBy4() {
int i = 0;
for (i=0;i<50;i++) {
}
}
void divBy8() {
int i = 0;
for (i=0;i<50;i++) {
}
}
```
These dummy functions will be the targets that you will investigate with the function profiler.
2. Replace the while loop in the `mainThread()` function with the following for loop.
```c
int counter=0;
for (counter=0;counter<=1600;counter++) {
if (counter % 2 == 0) {
divBy2();
}
if (counter % 4 == 0) {
divBy4();
}
if (counter % 8 == 0) {
divBy8();
}
}
counter=0;
```
This loop will create an interesting function call pattern for each dummy function.
3. Setup a breakpoint at the line `counter=0;` right after exiting the loop. (You can do so by double-clicking in the editor margin of the source file.) This breakpoint will be used to stop the tool from running after the loop exits.
4. Ensure that the target configuration (and the board configuration, if applicable) are properly configured as described [above](#check-target-configuration).
[[+d Expand full example code 1
```c
/*
* Copyright (c) 2015-2017, Texas Instruments Incorporated
* All rights reserved.
*
* 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.
*/
/*
* ======== empty.c ========
*/
/* For usleep() */
#include
#include
#include
/* Driver Header files */
#include
// #include
// #include
// #include
// #include
// #include
/* Board Header file */
#include "Board.h"
void divBy2() {
int i = 0;
for (i=0;i<50;i++) {
}
}
void divBy4() {
int i = 0;
for (i=0;i<50;i++) {
}
}
void divBy8() {
int i = 0;
for (i=0;i<50;i++) {
}
}
/*
* ======== mainThread ========
*/
void *mainThread(void *arg0)
{
/* Call driver init functions */
GPIO_init();
// I2C_init();
// SDSPI_init();
// SPI_init();
// UART_init();
// Watchdog_init();
/* Configure the LED pin */
GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
/* Turn on user LED */
GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_ON);
int counter=0;
for (counter=0;counter<=1600;counter++) {
if (counter % 2 == 0) {
divBy2();
}
if (counter % 4 == 0) {
divBy4();
}
if (counter % 8 == 0) {
divBy8();
}
}
counter=0;
}
```
+]]
### 1.2 Using the tool
1. Click **Run → Debug** (or F11) to launch the debugger.
2. Select **Tools → Hardware Trace Analyzer → Statistical Function Profiling** to launch the Function Profiling utility.
{{b If it is the first time you go to the Hardware Trace Analyzer menu, you will not see a selection list. Click on Hardware Trace Analyzer to initialize the tool. Press *OK* when you see the following prompt:
![tool init screenshot](images/swo_trace_init.PNG)
There will be no notification of completion. Go back to **Tools → Hardware Trace Analyzer**. You should now see a list of selectable usecases}}
3. Start by choosing a sampling interval of 256 cycles (or 512 if using the MSP432E4).
{{b Shorter sampling intervals can be chosen, but high sampling rates can cause overflow and packet loss if the data cannot be drained fast enough. When the sampling rate is getting too high, warnings of overflow will appear in the **Trace Viewer**.}}
![256 cycles function profile](images/swo_256cycles.PNG)
This means that the profiler will read and record the PC location every 256 cycles, creating a dense profile. Click on **Start** to accept the changes and enable the Trace Analyzer.
[[+y CCS cannot determine the clock frequency automatically? (expand)
You might receive an error message saying "Could not determine the System clock frequency automatically. Please set it manually in the receiver properties."
In this case, click the **Advanced Settings** button in the window shown above. Select **Receiver** on the left and then under **Hardware Configuration → Receiver Settings → Receiver → UART Options** set **Clock Frequency** to **Manual** and replace **Frequency in Hz** by the the CPU frequency for your board (120000000 for the MSP432E4, and 48000000 for the other boards used in this lab).
You will also need to set the sampling interval manually. Select **PC Trace** on the left and under **Hardware Configuration → Type → Triggers → Clock or PC Sample** set the **Clock Interval** to the desired value. +]]
4. Resume the program (**Run → Resume**, or F8), and it should run and halt at the breakpoint you have set earlier. In the bottom right corner of the screen, you can find the **Statistical Function Profiling** view. It shows the number of times a function is encountered by the tracer. You might need to wait a few seconds for CCS to process all the data before the view shows the results.
![256 cycles function profile result](images/swo_256_result.PNG)
Looking at the encounter proportion of the dummy functions, you will find that they closely match the expected calling ratio. i.e. `divBy2()` should be called twice as often as `divBy4()` and four times as often as `divBy8()`. Notice that there are a lot of small helper functions in the result list. Also, the "times encountered" field shows very large numbers. Both of these are due to the short sampling interval of 256 cycles.
5. The view to the left of the **Function Profiling** view displays the raw data collected by the tracer. Click on the second button in the **Trace Viewer** view ![function but](images/swo_change_but.PNG) and change the sampling interval to 4096 cycles. Restart the program (**Run → Restart**), resume execution (**Run → Resume**, or F8), and observe the result.
![4096 cycles function profile result](images/swo_4096_result.PNG)
The list is much shorter than last time, with only a few encounters of helper functions. The calling ratio of the dummy functions also diverges from the expectation greater than before. This is because the sampling interval is very long, potentially skipping entire calls to a function.
6. That's it! When you are finished, click **Run → Terminate** (or Ctrl+F2) to terminate the debug session.
Lab 2: Interrupt Profiling
---
### 2.1 Example program
[[+g If using an MSP432 board (expand)
The basis for the example is the **timerled** project which can be imported from the SDK:
{{b **Software → SimpleLink (YourDeviceFamily) SDK – v:(X.X.X.X) → Examples → Development Tools → (YourLaunchPad) → TI Drivers → timerled → TI-RTOS → CCS Compiler → timerled**}}
+]]
[[+g If using a CC13xx, CC2652, or CC2640R2 board (expand)
For this example, the **empty** project is used as a base and the contents of empty.c are replaced with a program that behaves like the **timerled** project used for the MSP432 boards. Before importing the **empty** project again, rename the project used in Lab 1 to something else. Once again, the project can be imported from the SDK:
{{b **Software → SimpleLink (YourDeviceFamily) SDK – v:(X.X.X.X) → Examples → Development Tools → (YourLaunchPad) → TI Drivers → empty → TI-RTOS → CCS Compiler → empty**}}
Replace the contents of empty.c with the following:
```c
/*
* This is a modified version of an example given in the GPTimerCC26XX.h File Reference
* It will toggle the led once per second
*/
/* Driver Header files */
#include
#include
#include // For Types_FreqHz
#include // For BIOS_getCpuFreq
/* Board Header file */
#include "Board.h"
// Callback to toggle the LED
void timerCallback(GPTimerCC26XX_Handle timerHandle, GPTimerCC26XX_IntMask interruptMask)
{
GPIO_toggle(Board_GPIO_LED0);
}
/*
* ======== mainThread ========
*/
void *mainThread(void *arg0)
{
/* Call driver init functions */
GPIO_init();
/* Configure the LED pin */
GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
/* Turn off user LED */
GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_OFF);
GPTimerCC26XX_Params params;
GPTimerCC26XX_Params_init(¶ms);
params.width = GPT_CONFIG_32BIT;
params.mode = GPT_MODE_PERIODIC_UP;
params.debugStallMode = GPTimerCC26XX_DEBUG_STALL_OFF;
GPTimerCC26XX_Handle hTimer = GPTimerCC26XX_open(Board_GPTIMER0A, ¶ms);
if (hTimer == NULL) {
/* Failed to initialized timer */
while (1) {}
}
// This timer will count up (from zero) to the value specified by loadVal and then call our callback.
// We set loadVal equal to our processor frequency so that the callback period is one second.
Types_FreqHz freq;
BIOS_getCpuFreq(&freq);
GPTimerCC26XX_Value loadVal = freq.lo - 1;
GPTimerCC26XX_setLoadValue(hTimer, loadVal);
GPTimerCC26XX_registerInterrupt(hTimer, timerCallback, GPT_INT_TIMEOUT);
GPTimerCC26XX_start(hTimer);
return (NULL);
}
```
+]]
These programs use a timer interrupt to toggle the on-board LED every second. You will use the the interrupt profiling tool to observe these interrupts.
Before proceeding, ensure that the target configuration (and the board configuration, if applicable) are properly configured as described [above](#check-target-configuration).
### 2.2 Using the tool
1. Click **Run → Debug** to launch the debugger.
2. Select **Tools → Hardware Trace Analyzer → Interrupt Profiling** to launch the interrupt profiling trace utility. Use the default options (unless you need to input the clock speed manually) and press the **Start** button to enable the Trace Analyzer.
3. Resume the program, observe the on-board LED. Pause the program from the debugger (**Run → Suspend**, or Alt+F8) after seeing the LED toggle 2-3 times.
{{b A window may appear stating that "You are opening a large file." You can decline to change the scalability settings. If the file seems too large for CCS to handle, you can restart and pause the program after fewer LED toggles.}}
4. In the bottom right area of the screen, you will find the three **Interrupt Profiling** tabs.
* The **Trace Viewer** shows the raw data captured by the trace.
* The **Interrupt Analyzer: Summary** view shows general information about the interrupt trace.
* The **Interrupt Analyzer: Graph** view shows a times-series graph of when the interrupts happen. This is what you will focus on.
5. Look for the row corresponding to the interrupt that was instantiated in the program. The interrupts can vary by board.
Board
Interrupt
MSP432P401R
T32_INT1
MSP432P411
ANALOGCOMP0
MSP432E4
16/32TIMER2A
CC13xx / CC2640R2 / CC2652
GPTIMER_0A
[[+y Is the graph empty? (expand)
If the graph is not empty, but the **Trace Viewer** shows trace information, click on the leftmost button in the **Trace Viewer** view and select **Interrupt Analyzer**. This will open up a new graph and summary view which should display the data correctly.
![Select the Interrupt Analyzer option in the menu after clicking on the leftmost button in the Trace Viewer](images/swo_int_graph_workaround.PNG)
+]]
6. Zoom out using **CTRL \-** or the zoom out button until you see multiple lines
for this interrupt on screen. The number of lines should be the same as the number of times the LED was toggled earlier. These straight lines mark when in the program runtime the interrupt (timer) was triggered.
7. To observe the interrupts in more detail, right click anywhere in the graph area and click **Insert Measurement Mark**.
![measurement insert](images/swo_int_insert.PNG)
The measurement mark will have a magnet effect and binds to interrupt triggering events. Insert two measurement marks at two consecutive interrupt trigger points. You should have a mark labeled **X1** and another labeled **X2**
![measurement insert](images/swo_int_period.PNG)
8. In the top left corner of the graph, the tick value of **X1** and **X2** is displayed as well as the time in between the two ticks **X2 - X1**.
![measurement insert](images/swo_int_time.PNG)
Remember that the program toggles the LED every second. With the exception of the MSP432E4, which runs at 120 MHz, all the boards used in this lab run at 48 MHz. The difference timed by the interrupt profiler is a close match with the expected 48M (or 120M) ticks.
9. That's it! When you are finished, terminate the debug session.
Lab 3: Data Variable Trace
---
### 3.1 Example program
The basis for the example is the **gpiointerrupt** project which can be imported from the SDK:
{{b **Software → SimpleLink (YourDeviceFamily) SDK – v:(X.X.X.X) → Examples → Development Tools → (YourLaunchPad) → TI Drivers → gpiointerrupt → TI-RTOS → CCS Compiler → gpiointerrupt**}}
Small modifications to the project are needed:
1. Open gpiointerrupt.c and add a global variable **click_count** right before all the functions.
```c
unsigned int click_count = 0;
```
{{b We use an unsigned integer because the **Data Variable Trace** does not distinguish between signed and unsigned data.}}
2. At the end of the `gpioButtonFxn0()` function add `click_count++;`, and at the end of the `gpioButtonFxn1()` function add
```c
if (click_count > 0)
click_count--;
```
The functions should now look like this:
```c
void gpioButtonFxn0(uint_least8_t index)
{
/* Clear the GPIO interrupt and toggle an LED */
GPIO_toggle(Board_GPIO_LED0);
click_count++;
}
void gpioButtonFxn1(uint_least8_t index)
{
/* Clear the GPIO interrupt and toggle an LED */
GPIO_toggle(Board_GPIO_LED1);
if (click_count > 0)
click_count--;
}
```
3. Ensure that the target configuration (and the board configuration, if applicable) are properly configured as described [above](#check-target-configuration).
### 3.2 Using the tool
1. Click **Run → Debug** to launch the debugger.
2. Select **Tools → Hardware Trace Analyzer → Data Variable Tracing**. In the **Variable Address** field, enter **&click_count** and press **Start**.
{{y If you are using the advanced settings, select **Data Variable Trace** on the left and under **Hardware Configuration → Type → Triggers → Location** enter **&click_count**}}
3. Resume the program, press **button0** a few times and then press **button1** a few times. Pause the program afterward. As an example, **button0** is pressed 3 times and then **button1** is pressed 3 times.
4. On the bottom right area of the screen, you will find two tabs.
* The **Trace Viewer** shows the raw data captured by the trace.
* The **Data Trace: Graph** shows a graphical view of how the watched variable changes with time.
5. Click on the **Trace Viewer** view, a series of read and writes can be seen because the increment operation first read the value of the variable and then writes back the incremented value.
![data read write](images/swo_data_read_write.PNG)
6. Click on the **Data Trace Graph** view, you can see a graph that shows the value of the variable going up and down. (Try zooming out to see the whole graph.)
![data read write](images/swo_up_down.PNG)
{{y If the data is not showing up in the graph, the solution from the previous lab can be used again. This time, select **Data Trace** instead of **Interupt Analyzer**. }}
7. That's it! When you are finished, terminate the debug session.
Summary
---
The SWO trace functionalities are very helpful tools for analyzing, debugging and monitoring a program.
In this workshop, you have learned the basics of **Statistical Function Profiling**, **Interrupt Profiling** and **Data Variable Profiling**.
These simple examples are meant to get you familiar with the capabilities of SWO trace, apply them to your projects.