Debugging¶
Debug Interfaces¶
The CC23xx platform supports SWD interfaces. Debug probe that support SWD like the TI XDS110 can work natively with the CC23xx. The hardware resources included on the devices for debugging are listed as follows. Not all debugging functionality is available in all combinations of debug probe and IDE.
Flash Patch and Breakpoint Unit (FPB) - 6 instruction comparators, 2 literal comparators
Data Watchpoint and Trace Unit (DWT) - 5 watchpoints on memory access
Instrumentation Trace Macrocell (ITM) - 32 x 32 bit stimulus registers
Trace Port Interface Unit (TPIU) - serialization and time-stamping of DWT and ITM events
XDS110 Debug Probe¶
The CC23xx LaunchPad support a 20-pin LP-EM debug connector, and the XDS110ET LaunchPad is the assumed debug probe for most development.
The XDS110 is the latest entry level debug probe (emulators) for TI embedded processors. Designed to be a complete solution that delivers JTAG and SWD connectivity at a low cost, the XDS110 is the debug probe of choice for entry-level debugging of TI microcontrollers, processors and SimpleLink devices. Also, both Core Processor and System Trace are available for all Arm and DSP devices that support Embedded Trace Buffer (ETB).
It is also possible to buy the XDS110 JTAG Debug Probe or use the XDS110 existing on-board another SimpleLink LaunchPad.
Segger J-Link Probes¶
The CC23xx platform also supports Segger J-Link Probes. These probes support
standard debug and flash operations on the CC23xx. To create a J-Link compatible image,
the crc_tool
resource included in {SDK_INSTALL_DIR}/tools/common/crc_tool
should be
used to calculate the CRC. This can be done through the use of a post-build step.
The Adding CRC values to an ELF file section, provides details on how to use the
crc_tool
along with code snippets showcasing its use.
The J-Link Real Time Transfer (RTT) feature may be used with the CC23xx platform when using a J-Link probe by following the guidance outlined in the Segger Real Time Transfer (RTT) website.
To obtain a Segger J-Link probe, please visit the Segger website.
Configuring Debugger in Code Composer Studio¶
If only one debug probe is attached, CCS will automatically select the
connected debug probe when a debug session is started. You can start a debug
session by clicking the debug icon on the toolbar.
If more than one debug probe is attached when a debug session is started, CCS will prompt you to select a debug probe as seen in Figure 115. CCS will save the selected debug probe in the target configuration for the project.

Figure 115. CCS Probe Selection¶
To set or change the selected debug probe for a project, it is necessary to set the serial number for the probe in the project’s target configuration.
|
![]() |
|
![]() |
|
![]() |
|
![]() |
Find The Serial Number¶
To find the serial number for XDS110 debug probe, open a command prompt and run
the xdsdfu.exe
command for your CCS version, e.g.
<CCS_INSTALL_DIR>\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -e
. For
UniFlash, the command will be:
<UNIFLASH_INSTALL_DIR>\deskdb\content\TICloudAgent\win\ccs_base\common\uscif\xds110\xdsdfu.exe -e
.
This will enumerate all the attached XDS110 debug probes. This should result in
output like the following.
C:\>c:\ti\ccs1210\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -e
USB Device Firmware Upgrade Utility
Copyright (c) 2008-2019 Texas Instruments Incorporated. All rights reserved.
Scanning USB buses for supported XDS110 devices...
<<<< Device 0 >>>>
VID: 0x0451 PID: 0xbef3
Device Name: XDS110 Embed with CMSIS-DAP
Version: 3.0.0.22
Manufacturer: Texas Instruments
Serial Num: LS420073
Mode: Runtime
Configuration: Standard
Found 1 devices.
C:\>
Note
More information of the tool can be found in
<CCS_INSTALL_DIR>\ccs\ccs_base\common\uscif\xds110\XDS110SupportReadMe.pdf
For XDS100 series debug probes, open a command prompt and run the
xds100serial.exe
command for your CCS version, e.g.
<CCS_INSTALL_DIR>\ccs\ccs_base\common\uscif\xds100serial.exe
. This should result
in output like the following:
C:\>c:\ti\ccs1210\ccs\ccs_base\common\uscif\xds100serial.exe
Scanning for XDS100 emulators...
VID/PID Type Serial # Description
0403/a6d1 XDS100v3 06EB12213144 Texas Instruments XDS100v3
C:\>
Configure the Serial Number¶
Open the xdsdfu.exe
command for your CCS version, e.g.
<CCS_INSTALL_DIR>\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -m
<CCS_INSTALL_DIR>\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -s LS470000 -r
These will first switch XDS110 to DFU mode if it is already in runtime mode, then set the serial number to the LS470000. The output should be like the following.
C:\>c:\ti\ccs1210\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -m
USB Device Firmware Upgrade Utility
Copyright (c) 2008-2019 Texas Instruments Incorporated. All rights reserved.
Scanning USB buses for supported XDS110 devices...
<<<< Device 0 >>>>
VID: 0x0451 PID: 0xbef3
Device Name: XDS110 Embed with CMSIS-DAP
Version: 3.0.0.22
Manufacturer: Texas Instruments
Serial Num: L1100JU9
Mode: Runtime
Configuration: Standard
Switching device into DFU mode.
C:\>c:\ti\ccs1210\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -s LS470000 -r
USB Device Firmware Upgrade Utility
Copyright (c) 2008-2019 Texas Instruments Incorporated. All rights reserved.
Scanning USB buses for supported XDS110 devices...
Setting serial number to "LS470000"...
C:\>
Program Board ID on LP-EM Boards¶
It is possible that in some very rare circumstances different LP-EM Boards are with the same Board ID or with empty Board ID. In this case:
UniFlash may not identify the device correctly
CCS may not identify the board correctly
Programming the different Board ID to each board should help. It can be done also
by open a command prompt and run the xdsdfu.exe
command. Plug only one LP-EM
board to the host PC. Unplug all other boards. For CCS
version, e.g. <CCS_INSTALL_DIR>\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -p
.
For UniFlash, the command will be:
<UNIFLASH_INSTALL_DIR>\deskdb\content\TICloudAgent\win\ccs_base\common\uscif\xds110\xdsdfu.exe -p
.
This should result in output like the following:
C:\>c:\ti\ccs1210\ccs\ccs_base\common\uscif\xds110\xdsdfu.exe -p LS47XXXX
USB Device Firmware Upgrade Utility
Copyright (c) 2008-2019 Texas Instruments Incorporated. All rights reserved.
Scanning USB buses for supported XDS110 devices...
Setting serial number prefix to "LS47XXXX"...
The serial prefix was set in EEPROM.
Power cycle the XDS110 for the change to take effect.
After running the command, unplug and re-plug the LP-EM board to the host PC to acknowledge the new Board ID to the host PC.
Getting started with CCS debug interface¶
This section describes the debug interface offered by CCS.
Note
More extensive documentation is provided in the CCS Debug Environment Guide.
Start a debug session¶
To start a debug session, click on the debug icon. If enabled to do and required, this will trigger a software build. It will also automatically download the binary on the CC23xx device.
Note
The small arrow next to the debug button offers the possibility to modify the Debug Configurations.
If needed, make sure to select the proper process in the Debug View.

Reset the device while debugging¶
TI recommends to use the default reset command (i.e. click on the
button or use the shortcut
Ctrl+Shift+R
).
Using other reset commands may lead to unexpected device behavior.
Build and flash the device while debugging¶
To rebuild the code and automatically flash the device during a debug session
press the build button. Then press “yes” in the dialog box
you can also click on the Reload Program
button (shortcut
Ctrl+Alt+R
)
Breakpoints¶
Comparators in the Flash Patch and Breakpoint Unit (FPB) of the CC23xx LaunchPad are used to break on an instruction fetch. This can be used to patch a function as it is fetched from instruction memory. Or these comparators can be used to supply a Breakpoint (BKPT) instruction to the CPU. These instructions halt the processors operation, waiting for the debug probe.
Considerations¶
While breakpoints are a useful tool for debugging code online, they have the possibility of altering the execution flow of a piece of code.
Breakpoints and Timing¶
Synchronous RF protocols are timing sensitive. Breakpoints can easily halt the execution long enough to lose network timing and break the link.
To still be able to debug, place breakpoints as close as possible to where the relevant debug information can be read or step through the relevant code segment to debug.
After you hit a breakpoint and read out the necessary debug information, it is recommended that you reset the device and re-establish the connection.
Breakpoints and Optimization¶
When compiler optimizations are enabled, toggling a breakpoint on a line of C code may not result in the expected behavior. Some examples include the following.
Code is removed or not compiled in
Toggling a breakpoint in the IDE results in a breakpoint somewhere other than the intended line. Some IDEs disable breakpoints on nonexistent code.
Code block is part of a common subexpression
A breakpoint might be trigged from a function or piece of code near the marked line. This might have been due to the compiler reusing sections.
An if clause is represented by a conditional branch in assembly
A breakpoint inside an if clause always breaks on the conditional statement, even when the condition is not true.
TI recommends selecting an optimization level as low as possible when debugging. See Optimizations for information on modifying optimization levels.
Note
Due to limitation of Cotex-CM0+, the number of breakpoints for CC23xx devices is 4. Check breakpoints limitation of ARM Cortex-CM0+. During programming stage, debugger will use 3. Breakpoints set by application will be ignored if more than one breakpoint is used by the application. It is possible to gain one more breakpoint back by implementing the following steps. Right click on the project, go to Debug As->Debug Configuration->Target, unchecked ‘Enable CIO function use’.

Breakpoints in CCS¶
Note
CCS reserves one hardware breakpoint for instruction stepping.
To toggle a breakpoint, do any of the following.
Double-click the area to the left of the line number.
Press
Ctrl
+Shift
+B
.Right-click on the line.
A breakpoint set on line 207 looks like the following.

Figure 116. Breakpoint on line 207. Debugger halted at start of main().¶
For an overview of the active and inactive breakpoints, click on View
→ Breakpoints
.

Figure 117. List of breakpoints. Right-click to edit options, or de-select to deactivate.¶
To set a conditional break, do as follows.
Right-click the breakpoint in the overview.
Choose Properties.
When debugging, Skip Count and Condition can help skip a number of breaks or only break if a variable is a certain value.
Note
Conditional breaks require a debugger response and may halt the processor long enough to break an active RF connection, or otherwise disrupt timing on the debug target.
Watching Variables and Registers¶
Debuggers offer several ways of viewing the state of a halted program. Global variables are statically placed during link-time and can end up anywhere in the RAM or Flash of the chip. These variables can be viewed when then target is halted by the debugger through the Watch and Expression windows.
Unless removed due to optimizations, global variables are always available in these views. Local variables or variables that are only valid inside a limited scope are only viewable in that scope. Such variables can also be viewed with the Watch or Expression views, and may also be automatically displayed when breaking or stepping through code.
Considerations¶
Local variables are often placed in CPU registers and not on the stack. These variables also have a limited lifetime even within the scope in which they are valid. Depending on the optimization performed, a variable placed in a register may not have a cohesive view of the current state of the variable. Some possible solutions are:
Move the variable to global scope, so it remains accessible in RAM.
Make the variable volatile, so the compiler doesn’t place the value in a register.
Make a shadow copy of the variable that is global and volatile.
Variables in CCS¶
You can view Global Variables by doing either of the following.
Select
View
→Expressions
.Select a variable name in code.
Right-click and select Add Watch Expression.

Figure 118. Variable watch window. Note that you can cast values, get address and sizeof, etc.¶
Select
View
→Variables
toauto-variables
that are present at the current location when stepping through code.

Figure 119. Local variables. This screenshot is taken during execution of an application function.¶
Memory Watchpoints¶
As mentioned in Debug Interfaces, the Data Watchpoint and Trace Unit (DWT module) contains four memory watchpoints that allow breakpoints on memory access. The hardware match functionality looks only at the address. If intended for use on a variable, the variable must be statically allocated.
Note
If a data watchpoint with value match is used, two of the four watchpoints are used.
Watchpoints in CCS¶
Warning
Watchpoint are not available on CC2340R5 and CC2340R21
Right-click on a global variable.
Select
Breakpoint
→Hardware Watchpoint
.Go to the list of breakpoints (
View
→Breakpoints
).Right-click and edit the Breakpoint Properties to configure the watchpoint.

Figure 120. Adding a watchpoint on a variable.¶
This example configuration ensures that if 0x42 is written to the memory location for Characteristic 1 in the Bluetooth Low Energy basic_ble example project the device halts execution.

Figure 121. Configuring a hardware watchpoint to break on 8-bit write with value 0x42.¶
Enabling Logging in CCS¶
The log
driver may be used to easily add debug logging capabilities
to any project. The main benefit of the log
driver is operates
entirely through JTAG which means that no UART peripherals are necessary
to enable the logging functionality. In other words, you do not have
to use up a UART peripheral or modify your project’s UART operation
to enable debug logging for your debugging purposes.
Setting up the Logging Functionality¶
To start, import the desired project into CCS.
For this example, Basic BLE project will be used.
Next, add
${COM_TI_SIMPLELINK_LOWPOWER_F3_SDK_INSTALL_DIR}/source/ti/log/lib/ticlang/m0p/log_cc23x0r5.a
to the File Search Path, which is found by right clicking the project →Properties
→Build
→Arm Linker
→File Search
. Click the add icon to add the path. The properties tab should look like the image below when completed correctly. After adding the file path, click Apply and Close.Add
ti_log_Log_ENABLE
to the Predefined Symbols. This is also found within the properties of the project. Right click the project →properties
→Build
→Arm Compiler
→Predefined Symbols
. Add this with the paper icon with the green plus. The properties tab should look like the image below when completed correctly. After adding the predefined symbol, click Apply and Close.
Setting up Log Modules¶
Open the Basic BLE SysConfig file. The logging module is configured through SysConfig. Under
LOGGING
, is a section for log modules. Here, the log modules will be added. Once in the Log Modules SysConfig section, click onADD
to add a Log Module instance. Name the moduleLogModule_App1
. Within this module, make sure theEnable Module
check box is checked. Next check theEnable Level DEBUG
,Enable Level VERBOSE
, andEnable Level INFO
. Make set the sink to/ti/log/LogSinkBuf
and ensureLogger Sink Instance
is set toCONFIG_ti_log_LogSinkBuf_0
.The module will look like the following if the previous steps are followed.
Within the
Global Parameters
section checkGlobal Enable Module
, then under theGlobal Log Level Configuration
subsection, checkEnable Level DEBUG
,Enable Level VERBOSE
, andEnable Level INFO
Check
LOG SINKS
module in SysConfig, and verify the name is properly set:To test the log function, add the code below into the main function.
1Log_printf(LogModule_App1, Log_DEBUG, "Hello world");
Add also the following include:
1#include <log/Log.h>
Once this is done, debug and flash the program to the board. While the board is in debug mode, you may open the log by going to
Tools
→Runtime Object View
. After ROV is open, click onLogSinkBuf
within ROV, and set theLogSinkBuf
toRecords
, and then click the continuous refresh button to keep the log constantly updating. It should look like this:
This completes the basic set up for the logging functionality. Additionally, more Log_printf
can be added in various locations to indicate different tasks. The debug level may be changed
or outright disabled during debugging depending on which parts of the programs you want to debug
or would like to receive messages for.
For more information on the log function, make sure to reference the Log Example for a drivers-only example that contains the logging functinoality. The readme for the example also provides valuable insight into how the log driver works and how you can use it in your custom project.
Runtime Object Viewer¶
Debuggers may include the Runtime Object Viewer (ROV) plug-in that provides insight into the current state of FreeRTOS, including task states, stacks, and so forth.
This section discusses some ROV views useful for debugging and profiling.
Viewing the State of Each Task¶
The Task Detailed
view is useful for seeing the state of each task and its
related runtime stack usage. This picture below shows the state the first time the
user-thread is called.

Figure 122. ROV Task Detail View¶
The following table explains the column in ROV Task Detail View
Address |
The memory location of the |
TaskName |
The name of the entry function of the task. |
Priority |
The RTOS priority for the task. |
State |
Current state of the task. |
StackSize |
The size of the runtime stack, configured when instantiating a task. |
StackPeak |
The maximum run-time stack memory used based on watermark in RAM, where the stacks are prefilled with 0xBE and there is a sentinel word at the end of the run-time stack. |
StackLimit |
The logical top of the runtime stack of the task. |
Note
Function calls may push the stack pointer out of the run-time stack, but not actually write to the entire area. A stack peak near stackSize but not exceeding it may indicate stack overflow.
ROV in CCS¶
To access the ROV while in a debug session in CCS:

Attention
You can enable live watch on ROV to automatically retrieve up to date information, however, the refreshing rate is not frequent. You can also pause the debugger or manually click refresh button to enforce a ROV information update.
Using the Memory Browser¶
Open the memory browser at View
→ Memory Browser
to explore the memory on the CC23xx. In
CCS, you can index by address or by symbol name.
Basic BLE Task’s stack (Note the |
GAPRole Task’s stack has probably overflowed as it is completely filled. |
![]() |
![]() |
The solution in this case would be to increase the stack size for the failing
task and see what the stack peak really is. The stackPeak
reported in
rov_task_detailed_cc2340
is relying on how many watermark bytes
are overwritten, so it can’t know how much the overrun amounts to.

Because stacks are utilized from higher addresses towards lower addressed (upwards in the picture), stacks that overrun will tend to overwrite data at locations immediately before the stack.
Read CC2340R5 MAC Address via UniFlash¶
The MAC address of a specific CC2340R5 device may be read through UniFlash. This can be done through the UniFlash GUI or through the UniFlash CLI.
Prerequisites¶
The user is expected to have/do the following:
CC23xx
A computer running a supported operating system listed in the Release Notes
Latest version of UniFlash
Read the UniFlash QuickStart Guide located in
{UNIFLASH_INSTALL_DIR}/docs/quick_start_guide/uniflash_quick_start_guide.html
Reading MAC Address through UniFlash GUI¶
Reading the MAC address through the UniFlash GUI is most useful when the MAC address needs to be read once for testing or documentation. If the value needs to be read as part of some automation process or manufacturing process, then the CLI method discussed in Reading MAC Address through UniFlash CLI. is recommended.
This step-by-step guide will help you to read the MAC address written to your device through the UniFlash GUI.
While the CC23xx is connected to your computer, open UniFlash.
Launch the device configuration of the CC23xx by using the auto-detect feature or by manually searching for the CC23xx in the
New Configuration
section and clicking onStart
.Once the device configuration has been launched, navigate to to the
Memory
tab on the left and click onRead Target Device
On the
Address
search bar on the top left, type0x4E000058
and click onGo
The device memory will be read and the
0x4E000058
address will be highlighted in the browser.The highlighted data will contain the MAC address of the device.
Reading MAC Address through UniFlash CLI¶
If the MAC Address must be read due to a fabrication workflow or as part of an automated testing process, then reading the address through the UniFlash Command Line Interface (CLI) will likely be the most useful method.
This step-by-step guide will help you to read the MAC address written to your device through the UniFlash CLI
While the CC23xx is connected to your computer, open UniFlash.
Launch the device configuration of the CC23xx by using the auto-detect feature or by manually searching for the CC23xx in the
New Configuration
section and clicking onStart
.Once the device configuration has been launched, navigate to to the
Standalone Command Line
tab on the left and click onGenerate Package
. This may take a few minutes.On the save the generated package to a known location (for example,
C:\ti
) and unzip the archive that the known location. The newly extracted folder, should look like the following image:Run the
one_time_setup.bat
script. This script will generated all the necessary setup-files to use the CLI.Open a command-line and navigate to the newly extracted folder.
Type and run the following command:
.\dslite-Cortex_M0P.bat --mode memory --config .\user_files\configs\cc2340r5.ccxml --range=0x4E000058,1 --output=result.hex
The command will read one entire word at memory address
0x4E000058
which will contain the MAC address of the device. The read data will be written to aresult.hex
file located in the extracted folder.The file may be read through a hex viewer/editor program. Looking into the file, we see the following:
The commands specified in this section may be utilized in scripting languages to allow for easy automation.
More information about the UniFlash Command Line Interface may be found in the Quick Start Guide included with your UniFlash installation.
Connect the debugger to a running target¶
Connecting the debugger to a target can help when you want to see the status of your target after it has been running for several hours, or even days; or if you cannot reproduce a crash with the debugger attached. Once the debugger connected to the target, all the usual functionalities (break points, step-by-step, variable view, memory view…) are available.
This step-by-step guide will help you to configure CCS in order to connect to a running target
While the CC23xx is running the desired project, open CCS.
Open the Target Configurations window by click on View → Target Configurations.
In the Target Configurations window, expand the Projects folder.
Expand your project within the Projects folder inside the Target Configurations window.
Expand the targetConfigs folder.
Right click on CC2340R5.ccxml and click on Launch Selected Configuration
After a few seconds, CCS will connect to the target and the core will be visible in the Debug window.
Right click on the core and select Connect Target
Click on Run → Load → Load Symbols…
Click on Browse Project.
Select your project’s out file and press OK.
Press OK on Load Symbols window.
At this point you are connected to the target and should see where in the project the target is currently located.
Optimizations¶
Compiler optimizations are great for saving space or speeding up execution. However, these optimizations can be very difficult to debug around. There are multiple levels at which optimization can be turned on or off.
Project-wide optimization settings are the most general. Sometimes, given the constraints of the device, it is impossible to lower the size optimization level. File-wide optimization settings can be used like project-wide optimizations to turn on or off certain settings. The most granular control is using compiler directives to control optimization at a function level.
Optimizations in CCS¶
Project-Wide Optimizations¶
Open the project optimization settings by going to Project Properties
→ CCS Build
→ ARM Compiler
→ Optimization

Figure 123. Project-level optimization setting in CCS¶
Single-File Optimizations¶
Note
Do single-file optimizations with care because this also overrides the project-wide preprocessor symbols.
Right-click on the file in the Workspace pane.
Choose Properties.
Change the optimization level of the file using the same menu in the CCS project-wide optimization menu.
Single-Function Optimizations¶
Warning
Care must be taken when using pragmas, since they are very specific to the toolchain and may render non-reusable code.
Important
- The TI ARM Clang compiler does not support single-function optimizations. Use Single File optimizations instead.
For additional details, check section 3 of the TI ARM Clang User’s Guide.
#pragma GCC push_options
#pragma GCC optimize ("O0")
static void myFunction(int number)
{
// ...
return yourFunction(other_number);
}
#pragma GCC pop_options
Deciphering Exceptions¶
Several possible exception causes exist. If an exception is caught, an
exception handler function can be called.
Arm Cortex User Guide
describes more about exception handler.
There are ways to determine whether the hang up is due to the hard
fault. One is to watch the Program Counter (PC)
register which will indicate operation in the
hard fault handler. The second method is to watch the special-purpose program status registers (xPSR)
.

Figure 124. Exception Registers¶
Debugging Memory Problems¶
This section describes how to debug a situation where the program runs out of memory, either on the heap or on the runtime stack for the individual thread contexts. Exceeding array bounds or dynamically allocating too little memory for a structure corrupts the memory and can cause an exception like INVPC, INVSTATE, IBUSERR to appear in the CFSR register.
Task and System Stack Overflow¶
If an overflow on the runtime stack of the task or the system stack occurs (as found using the ROV plug-in), perform the following steps.
Note the current size of the runtime stack of each task.
Increase by a few 100 bytes
Reduce the runtime stack sizes so that they are larger than their respective stackPeaks to save some memory.
Check System Flash and RAM Usage With Map File¶
Building an application produce a map file (.map) which can be used to compute the combined flash and RAM system memory usage. The map file is in the output folder of the project. To compute the total memory usage, do as follows.
Open the map file.
Note
The last row of the Module Summary table contain the breakdown of memory usage for read-only code (code), read-only data (ro data), and read/write data (rw data).
Sum the values for read-only code with read-only data memory.
Note
This sum is the total flash memory usage for the application project. The read/write data memory is the total RAM usage by the application project.
For CCS, the map file gives a summary of flash and RAM usage. To determine the remaining available memory see Flash and RAM. Due to section placement and alignment requirements, some remaining memory may be unavailable. The map file memory usage is valid only if the project builds and links successfully.
Debugging Common Heap Issues¶
The BLE5-Stack uses FreeRTOS heap_4 module for its memory allocation and the memory usage can be viewed using Runtime Object Viewer.

However, ROV does not provide you where in the software the heap statistics was updated. To have full control on where you want to see the heap usage, you can take advantage of vPortGetHeapStats. Here is an example showing the heap information under advertising and upon connection established using basic_ble example.
Include
#include <FreeRTOS.h>
and declare global heapStats in app_peripheral.c1#include <FreeRTOS.h> 2 3// Struct to hold the heap statistics 4HeapStats_t heapStats;
Register for BLEAPPUTIL_ADV_END mask
1BLEAppUtil_EventHandler_t peripheralAdvHandler = 2{ 3 .handlerType = BLEAPPUTIL_GAP_ADV_TYPE, 4 .pEventHandler = Peripheral_AdvEventHandler, 5 .eventMask = BLEAPPUTIL_ADV_START_AFTER_ENABLE | 6 BLEAPPUTIL_ADV_END_AFTER_DISABLE | 7 BLEAPPUTIL_ADV_END 8};
Add the following code under
Peripheral_AdvEventHandler
to handle BLEAPPUTIL_ADV_END mask1void Peripheral_AdvEventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) 2{ 3 BLEAppUtil_AdvEventData_t *advData = (BLEAppUtil_AdvEventData_t *)pMsgData; 4 5 switch(event) 6 { 7 case BLEAPPUTIL_ADV_END: 8 { 9 //get heap status here 10 vPortGetHeapStats(&heapStats); 11 break; 12 }
Add the following code under
Peripheral_GAPConnEventHandler
to get heap information when connection is established1void Peripheral_GAPConnEventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) 2{ 3 case BLEAPPUTIL_LINK_ESTABLISHED_EVENT: 4 { 5 gapEstLinkReqEvent_t *gapEstMsg = (gapEstLinkReqEvent_t *)pMsgData; 6 vPortGetHeapStats( &heapStats );
After programming the device, you can go to Expressions and enable live watch. You can then observe that the xNumberOfSuccessfulAllocations and xNumberOfSuccessfulFrees continue increasing when the advertising event is ongoing where the rest of the fields stay constant, this indicates that there is no memory leak during advertising.

After connection established, you will see that the xAvailableHeapSpaceInBytes decrease which is due to BLE5-Stack allocates memory for connection related information.

Troubleshooting Heap Problems¶
Issues with dynamic allocated memory can be notoriously hard to track down and debug; this section aims to give tips on how to debug the most common issues with dynamic memory.
Writing to already freed memory
Pointers to memory which have already been freed using ICall_free()
should
not be used. A common practice is to set pointers to NULL after they have
been freed, and check them for NULL before using them.
Writing a an already freed pointer will cause assert.
1 // Allocate Memory
2 uint32_t *myPtr = ICall_malloc(500);
3
4 //..
5
6 // Later free the pointer, set it to NULL
7 ICall_free(myPtr);
8 myPtr = NULL;
9
10 // This check will protect against writing to already freed memory
11 if( NULL != myPtr)
12 {
13 *myPtr = 42;
14 }
Freeing Already freed Memory
The cause of this bug is the same as the one from the previous section, double frees will trigger assert in heap_4.
1 // Allocate Memory
2 uint32_t *myPtr = ICall_malloc(500);
3
4 //..
5
6 ICall_free(myPtr);
7 ICall_free(myPtr);
ICall Abort¶
ICall abort is a function call used to indicate a serious failure in the ICall or OSAL mechanism used by the stack.
The BLE Stack will call ICall_abort()
when one of the below happens:
Calling a stack function through ICall in a stack callback or TI-RTOS/FreeRTOS SWI or HWI
Misconfiguring of additional ICall tasks or entities (usually when
OSAL_MAX_NUM_PROXY_TASKS + 1
< than number of ICall tasks)Incorrect ICall task registering
Stack API call timed out while ICall is waiting for a response
ICall encountered an error while executing a stack API
ICall_primSetTimer()
orICall_setTimer()
are unable to create a clock object
By default ICall_abort()
will raise a HAL assert if they are enabled.
You can set a breakpoint in the HAL assert handler and inspect the call stack
to understand the function that called the abort.
If HAL asserts are not enabled then the abort function will spin lock.
The user can set a breakpoint in ICall abort to see what is causing the abort.
See the HAL Assert Handling section for more information.
HAL Assert Handling¶
The HAL Assert module defines a mechanism for handling undesirable or unrecoverable state states in the code. The HAL Assert module is intended to be configurable and shared between the application and stack.
HAL Assert Design¶
The HAL_ASSERT()
macro from hal_assert.h
can be used to process a
failure state or condition. The way this macro is evaluated corresponds to how
the HAL assert module is configured. There are two types of HAL Assert:
Legacy HAL Assert
Extended HAL Assert
Legacy HAL assert will execute the halAssertHandler()
from hal_assert.c
.
The implementation of halAssertHandler()
is controlled by preprocessor
defines that are explained in the next section.
Extended HAL assert will execute halAssertHandlerExt()
from
hal_assert.c
. This extended handler will execute any user provided callbacks
before executing the default halAssertHandler()
. The user provided
callback will contain information including the cause and subcause of the error.
The subcause can be set with the HAL_ASSERT_SET_SUBCAUSE()
macro before
executing HAL_ASSERT()
.
Attention
TI recommends that extended HAL asserts are always used whenever possible to ensure the most debugging features.
HAL Assert configuration¶
The behavior of the HAL Assert module is based on both the selection of
preprocessor defines and the call to halAssertInit()
.
EXT_HAL_ASSERT
: Enables extended HAL asserts
HAL_ASSERT_RESET
: Reset on assert usingHAL_SYSTEM_RESET()
HAL_ASSERT_SPIN
: Spinlock on assert usinghalAssertSpinlock()
HAL_ASSERT_LIGHTS
should never be used.
To fully enable extended HAL Asserts halAssertInit()
must be called with
HAL_ASSERT_LEGACY_MODE_DISABLED
, otherwise the user supplied callback will
not be executed.
Recommended HAL Assert Configuration¶
TI recommends the following settings. Any other configuration is not recommended for new development.
EXT_HAL_ASSERT
is defined in all BLE-Stack projects for of split-image and stack_library type
halAssertInit()
should not be called by the application. Instead it is initialized by the stack inble_user_config.c
(ble_user_config_stack.c
for examples that do not have a stack project).
The TI sample applications will plug a user callback for use
with the extended HAL assert in main.c
using:
/* Register Application callback to trap asserts raised in the Stack */
RegisterAssertCback(AssertHandler);
The main.c
file also contains an example AssertHandler()
function.
The user is free to modify this function to meet the needs of their application,
but it should contain cases for all HAL assert types.
Hint
It is recommend to develop, debug, and vigorously stress test a product with asserts enabled. For production it is recommended that assert handling that results in a spinlock is removed.
Adding HAL Assert Support To the Application¶
Single Project
Define EXT_HAL_ASSERT
in the app project predefined symbols list.
halAssertInit()
is initilized by the stack in ble_user_config_stack.c
for examples that do not have a stack project.
Stack Library
In the stack library configuration, the stack and application images are
combined, and no action is needed to use HAL asserts in the application layer
or to plug the user call back using RegisterAssertCback()
. (For stack
library configuration, there is no need to define EXT_HAL_ASSERT
in the
app project.)
Split Image
The HAL assert code must live on both the stack and the application side in
order for the user to supply a callback via RegisterAssertCback()
or to use
HAL asserts in the application. This includes
Adding
hal_assert.c
to the application projectDefining
EXT_HAL_ASSERT
in the application project as a preprocessor symbol
HAL Assert Causes and Explanation¶
As mentioned above, the BLE-Stack will use HAL_ASSERT()
to trap in the case
a failure state is encountered. By default, asserts will be sent to the
AssertHandler()
function that is provided in main.c of the TI sample
applications. The table below describes the various HAL assert types that are
used in the stack and what they mean. This table assumes EXT_HAL_ASSERT
is defined as recommended above.
HAL Assert Cause |
Reason |
HAL_ASSERT_CAUSE_OUT_OF_MEMORY |
Malloc failed when |
HAL_ASSERT_CAUSE_OUT_OF_MEMORY |
Critical callbacks cannot be allocated by the GAP central or peripheral link managers or SM |
HAL_ASSERT_CAUSE_RF_DRIVER_ERROR |
RF error callback is executed by RF driver. |
HAL_ASSERT_CAUSE_ICALL_ABORT |
ICall has encountered a critical error and needs to abort using |
HAL_ASSERT_CAUSE_ICALL_TIMEOUT |
A stack API that was executed via ICall took longer than ICALL_TIMEOUT_PREDEFINE to return. This usually means that the stack has hung. |
HAL_ASSERT_CAUSE_INTERNAL_ERROR |
When a split image application executes an API call that is not available in the stack image. |
HAL_ASSERT_CAUSE_UNEXPECTED_ERROR |
Used by NPI UART and SPI transport layers to indicate the the driver is already in use |
HCI Hardware Errors¶
HCI Hardware errors are another mechanism by which the BLE-Stack may indicate that it has encountered an unrecoverable or unexpected state.
The stack will use ICall/OSAL to send a message to the app’s message queue with more information about the failure.
HCI hardware errors can be masked by the application using the
HCI_SetEventMaskCmd()
, but this is not the default behavior.
Registering for HCI Hardware Errors¶
By default the TI provided sample applications will register for HCI events, and
process them using the _processStackMsg
function within the application.
HCI hardware errors will be sent to the application as HCI_GAP_EVENT_EVENT
with an event code of HCI_BLE_HARDWARE_ERROR_EVENT_CODE
. For more on
processing HCI events in the application, please see Host Controller Interface (HCI).
By default the sample applications will assert when a HCI Hardware error
is received, using the same assert handler as the extended HAL asserts
(AssertHandler()
).
Decoding Hardware Error Reason¶
In the application’s _processStackMsg(ICall_Hdr *pMsg)
function, there
is a case for HCI_GAP_EVENT_EVENT
which has a subcase where
HCI_BLE_HARDWARE_ERROR_EVENT_CODE
is processed.
The case for the hardware error processing can be enhanced to expose the error code like so:
case HCI_BLE_HARDWARE_ERROR_EVENT_CODE:
{
hciEvt_HardwareError_t *hardCode =(hciEvt_HardwareError_t*)pMsg;
AssertHandler(HAL_ASSERT_CAUSE_HARDWARE_ERROR, hardCode->hardwareCode);
}
A breakpoint can be set in this function to determine the error code. This
code should be reported to a TI engineer when experiencing a HCI hardware
assert. These error codes are defined in ll_common.h
and hci.h
.
Debugging RF Output¶
As mentioned in the CC23xx SimpleLink Wireless MCU Technical Reference Manual, the RF output can be mapped to pins on the LaunchPad for RF signal debugging. These pins are intended to be used when connecting an RF range extender. However, they can also help in instances where it is unclear if the device is transmitting or receiving in the right window.
On the CC23xx, the RF observables should be enabled through SysConfig.
In SysConfig -> TI DRIVERS
-> RCL
-> RCL Observables
,
For
Use Case
selectPA/LNA Pins
The settings for the
Additional RF GPIO Signals
should be automatically selected (RFEGPO0 (PA_EN)
andRFEGPO1 (LNA_EN)
)
The DIO the signal is outputted on can then be
configured under SysConfig -> TI DRIVERS
-> RCL
-> PinMux
LRF GPIO
should be left toLRF
RFEGPO0 Pin
should be set to the DIO wanted for the PA signal (Radio Tx)
RFEGPO0 Pin
should be set to the DIO wanted for the LNA signal (Radio Rx)

Figure 125. Configuration to set under SysConfig -> TI DRIVERS
-> RCL
to enable RF Observables¶
Packet Sniffer¶
In order to determine what is going on over the air, it’s recommended to have a packet sniffer that can track BLE advertisement packets as well as an established BLE connection.
Note
SmartRF Protocol Packet Sniffer and SmartRF Packet Sniffer 2 can be used for Bluetooth LE debugging when working with any Bluetooth LE compatible devices, including the CC23xx devices. However, the CC23xx devices cannot be used as sampling device for the Packet Sniffer tools. Refer to the “Required Hardware” section for more details.
Install the Required Software¶
Download and install the following software:
Important
It is important that you use the appropriate version of Wireshark. Please find the version you need in the SmartRF Packet Sniffer 2 User’s Guide, under ‘Getting Started > Installation’.
SmartRF Packet Sniffer 2, which contains sniffer firmware for the CC13xx and CC26xx family
UniFlash, used to program devices from the CC13xx and CC26xx family
Flash Programmer 2, used to program devices from the CC13xx and CC26xx family
Required Hardware¶
Any device from the CC13xx and CC26xx family which supports BLE commands can be used as a sniffer. Following is a list of all supported development boards:
UniFlash Setup¶
Connect the LaunchPad via USB
Start UniFlash
Connected device should be under ‘Detected Devices’. Click Start.
If it is not, choose your device type from ‘New Configuration’. Click Start.

Figure 126. Connected Devices Displayed on UniFlash.¶
Browse for the Flash image corresponding to your device under
<SmartRF Tools installpath>\SmartRF Packet Sniffer 2\sniffer_fw\bin
. Click Load Image.

Figure 127. Browse for and Load Image on UniFlash.¶
SmartRF Sniffer Agent Setup¶
Start SmartRF Sniffer Agent.
Select Options->Data->Data Out. Check Use Pipe and click ‘OK’.

Figure 128. Select Use Pipe for Data Out¶
Click Detect Devices to find the sniffer device.
-If it does not show up, disconnect and reconnect the device and try again.
Once your device is discovered click the checkbox under ‘Use’ and then click ‘Configure’.

Figure 129. Configure the Sniffer Device¶
Use the settings shown for BLE configuration.
-For BLE, use advertising channel 37, 38, or 39. -The sniffer can be configured to follow a connection between a specific BLE Central (initiator) and Peripheral device. Click the checkbox next to ‘Connect to Initiator Address’ and write the address of the Central (Initiator) device. The address of the device is printed to the display in most of our examples, but can also be found using an external tool such as the SimpleLink Starter mobile app. If this option is not selected, the device will follow the first connection that appears on the channel that was selected.
Settings for BLE configuration.
Click ‘OK’, then press ‘Start All’. -The ‘Incoming Packets’ indicator should turn green, and the ‘Outgoing Packets’ indicator should turn blue. Shown below.~
![]()
Wireshark Setup¶
Create a new Wireshark desktop shortcut. Modify the Target setting by adding
-i\\.\pipe\tiwspc_data -k
to the end of the current target entry.

Figure 130. Modifying the target entry.¶
Run Wireshark from the new shortcut.
The packets should now be displayed.

Figure 131. Example BLE packets displayed using Wireshark.¶
BLE Health Toolkit¶
The Health Toolkit aims to provide a better understanding of the BLEStack’s status by allowing access to detailed diagnostic information in an easy and organized way. Here we will address the functionalities of the Debug Information Module.
Note
This feature is currently provided as a preview and only implemented
in the data_stream
example. The location and content of the files
may change in the future.
The Debug Info module is a collection of information from the BLE stack and it is composed of the following domains:
Scheduler Domain: contains information on active tasks, as well as previous tasks.
Connection Domain: contains connection related information.
Error Domain: provides a record of detected set of unique errors.
Available Services¶
The Health Toolkit provides the following services to read and clear debug information data for a specific domain or for all of them:
DbgInf_init(): initializes the Debug Info module and start the data collection process. This function also clears the domain before being used and sets it as active. The service receives a
domainBitmap
as input, which indicates the specific domain to collect data from.DbgInf_clear(): clears the Debug Info module. The service receives a
domainBitmap
as input, which indicates the specific domain to use. For each domain in the bitmap, it clears the domain data.DbgInf_get(): collects the debug information. The service receives a pointer to a user’s buffer, the number of bytes to write, and a
domainBitmap
of the domains to copy data from. The service returns the number of bytes read from the debug info module to the user’s buffer.DbgInf_halt(): terminates the process of collecting new data.
Note
The
domainBitmap
can have one of the following values based on the domain that should be selected:DBGINF_DOMAIN_NONE
,DBGINF_DOMAIN_SCHEDULER
,DBGINF_DOMAIN_CONNECTION
,DBGINF_DOMAIN_ERROR
,DBGINF_DOMAIN_ALL
.
Domains - Data Resources and Structures¶
Each domain will consume a specific amount of memory that should be accounted for. A record refers to the structure of the collected information for each domain.
Scheduler - 128 bytes (with a suggested amount of 10 history records).
Scheduler domain general information: 8 bytes.
Scheduler record: 12 bytes.
Connection - 84 bytes (with a suggested amount of 1 max connection record history of last 4 connections).
Connection domain general information: 32 bytes.
Connection established record: 4 bytes
Connection terminated record: 12 bytes
Error - 24 bytes (with a suggested amount of 11 records).
Error domain general info: 2 bytes.
Error record: 2 bytes.
Note
Using default parameters, the total amount of data that can be collected by the Debug Info buffer is 236 bytes.
When using the `DbgInf_get()`
API, the user’s buffer length should be 256 bytes (header included) to get all the debug info data.
Therefore, if more information of a specific domain is required, then it is recommended to enable only the domain of interest.
The Debug Info size is defined by several parameters that can be configured inside the debuginfo.h
file:
Parameter
Description
Default Value
Range
DBGINF_SCHED_MAX_NUM_OF_RECORDS
Circular buffer size for the scheduler records.
10
1-255
DBGINF_CONN_MAX_NUM_OF_TERM_RECORDS
Circular buffer size for the terminated connections records.
4
1-255
DBGINF_CONN_MAX_NUM_OF_EST_RECORDS
Circular buffer size for the established connections records.
1
1-255
DBGINF_ERROR_MAX_NUM_OF_ERROR_RECORDS
Circular buffer size for the error records.
11
1-255
Note
The debug info module only collects data for domains that are active which are activate using
DbgInf_init()
API.
The debug information data for each domain is stored in the pre-allocated buffer following a specific format defined by the following structures. Please consider reviewing the information stored in each structure and the description of each element when analyzing the data after being parsed with the tool presented in the next section: How to Parse the Debug Information.
Scheduler Domain Structure:
// Scheduler Record: typedef struct DbgInf_schedRec_t_ { uint8_t taskID; //!< Type of Link Layer task. uint8_t llState; //!< State of Link Layer. uint16_t reserved; uint32_t startTimeDelta; //!< The time difference between current task start time and previous task end time. uint32_t taskDuration; //!< Task duration. } DbgInf_schedRec_t; // Scheduler Domain General Information: typedef struct DbgInf_SchedInfo_t_ { uint8_t activeTasks; //!< Bits that indicate which tasks are active. uint8_t recCnt; //!< Scheduler records counter. uint16_t reserved; uint32_t lastCmdStartTime; //!< The start time of the last scheduled command. DbgInf_schedRec_t schedHistory[DBGINF_SCHED_MAX_NUM_OF_RECORDS]; //!< Cyclic buffer of Scheduler Records. } DbgInf_SchedInfo_t;Connection Domain Structure:
// Connection Established Record: typedef struct DbgInf_ConnEst_t_ { uint8_t flags; //!< Connection flags bitmap: //!< 0: connection active (1) or inactive (0) //!< 1: peripheral (1), central (0) //!< 2: encryption enabled (1) or disabled (0) uint8_t reserved; uint16_t eventNumEst; //!< Event number for connection establish }DbgInf_ConnEst_t; // Connection Termination Record: typedef struct DbgInf_ConnTerm_t_ { uint8_t peerAddr[2]; //!< 2 LSB bytes of peer device address uint8_t termReason; //!< Termination reason code uint8_t flags; //!< Connection flags uint16_t connInterval; //!< Connection interval uint16_t connEvent; //!< Current event number uint16_t eventNumEst; //!< Event number for connection establish uint16_t eventNumTerm; //!< Event number for connection terminate }DbgInf_ConnTerm_t; // Connection Domain General Information: typedef struct DbgInf_ConnInfo_t_ { uint8_t numActiveConns; //!< Number of current active BLE connections uint8_t maxActiveConns; //!< Maximum of active BLE connections reported uint16_t eventCtr; //!< Connection domain event number uint16_t termReasonStat[DBGINF_CONN_NUM_OF_TERM_STAT]; //!< Disconnect reason statistics uint8_t connTermCnt; //!< Connection terminated records counter uint8_t reserved; DbgInf_ConnEst_t connEst[MAX_NUM_BLE_CONNS]; //!< Connection information DbgInf_ConnTerm_t connTerm[DBGINF_CONN_MAX_NUM_OF_TERM_RECORDS]; //!< Previous connections records } DbgInf_ConnInfo_t;Error Domain Structure:
At the moment, only two
errors
can be identified:
DBGINF_ERROR_SCHED_SUBMIT_FAILS
- In case the scheduler fails to post a command (in llScheduleTask() function).
DBGINF_ERROR_LL_OUT_OF_TX_MEM
- In case of Tx buffer overflow (in LL_TxData() function).// Error Domain General Info: typedef struct DbgInf_ErrorInfo_t_ { uint8_t errorCnt; //!< Errors records counter uint8_t reserved; uint16 errorHistory[DBGINF_ERROR_MAX_NUM_OF_ERROR_RECORDS]; //!< Error Record } DbgInf_ErrorInfo_t;
How to Parse the Debug Information¶
The Debug Information Parser (debug_info_parser.exe
) is a tool that converts a binary file with
the collected debug information in binary format and translates it into a readable format based on
the domain structures as explained in Domains - Data Resources and Structures.
The tool can be found inside the SDK: <SDK>\tools\ble5stack
To use the tool, open a command line interface from the directory where the Debug Information Parser tool is located and enter the following command line:
debug_info_parser.exe parse [-h] --mem_dump MEM_DUMP [--out_json OUT_JSON] [--verbose] [--num_sched_rec NUM_SCHED_REC] [--max_num_est_rec MAX_NUM_EST_REC] [--num_conn_term_rec NUM_CONN_TERM_REC] [--num_err_rec NUM_ERR_REC]
Argument
Description
Type
Default Value
-h, –help
Show help message and exit
Optional
none
–mem_dump
Path to memory dump binary file
Mandatory
–out_json
Path to json file where to save parsed data
Optional
Path to memory dump binary file
–verbose
Show debug messages
Optional
none
–num_sched_rec
Must match the DBGINF_SCHED_MAX_NUM_OF_RECORDS define value
Optional
10
–max_num_est_rec
Must match the DBGINF_CONN_MAX_NUM_OF_EST_RECORDS define value
Optional
1
–num_conn_term_rec
Must match the DBGINF_CONN_MAX_NUM_OF_TERM_RECORDS define value
Optional
4
–num_conn_term_rec
Must match the BGINF_ERROR_MAX_NUM_OF_ERROR_RECORDS define value
Optional
11
Note
All unused optional arguments will be set to their default values.
Warning
Please note that: --num_sched_rec
, --max_num_est_rec
, --num_conn_term_rec
, --num_conn_term_rec
must match the defined
values inside debugInfo.h
, and that the debug information in the binary file must be complete in order for the parsing tool to
work correctly.
Example of how to use the Health Toolkit inside your Project¶
The following example shows how to enable the Health Toolkit - Debug Info module using the data_stream
out of the box project.
The Health Toolkit - Debug Info module is enabled using SysConfing inside the BLE section. By default, when the Health Toolkit is enabled, all the domains are active. However, they still need to be initialized.
Figure 132. Enable the Health Toolkit debug information in SysConfing.¶
Include the
debugInfo.h
file where you are calling the Debug Info functions.#include <ti/bleapp/health_toolkit/inc/debugInfo.h>.
Initialize the debug info module and start collecting data with the
DbgInf_init()
function. Here we initialize all the domains, however we can enable each domain individually using the correspondingdomainBitmap
as explained in Available Services.void appMain(void) { // Call the BLEAppUtil module init function BLEAppUtil_init(&criticalErrorHandler, &App_StackInitDoneHandler, &appMainParams, &appMainPeriCentParams); // Initialize ALL the domains. DbgInf_init(DBGINF_DOMAIN_ALL); }
Get the debug information using the
DbgInf_get()
function. In this case, it is possible to get the information of each domain or of all of them using the correspondingdomainBitmap
due to the fact that we have initialized them all. However, if only one domain is initialized, we can only successfully get the information from that same domain. In this example, we will collect the data when a connection has been established.uint8_t const pBuf[256]; void Connection_ConnEventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) { switch(event) { case BLEAPPUTIL_LINK_ESTABLISHED_EVENT: { ... // Get the debug information for all of the domains. DbgInf_get(pBuf, sizeof(pBuf), DBGINF_DOMAIN_ALL); break; }
You can use the
DbgInf_halt()
wherever you consider to stop collecting new data. In our example, we will terminate the data collection after establishing a connection.void Connection_ConnEventHandler(uint32 event, BLEAppUtil_msgHdr_t *pMsgData) { switch(event) { case BLEAPPUTIL_LINK_ESTABLISHED_EVENT: { ... // Terminates the process of collecting new data. DbgInf_halt(); break; } ...
In order to use the
debug_info_parser
tool, we need to export the collected data (pBuf
) in binary format. To do this, run the code in debug mode as explained in the previous section: Configuring Debugger in Code Composer Studio.After the code has finished executing, look for the address of
pBuf
inside theExpressions
window.Figure 133. Get the
pBuf
memory address.¶Go into the
Memory Browser
window and selectSave Memory
.Figure 134. Use the Memory Browser window to export memory data.¶
Select where to save the memory dump, select the format as binary and define a name (for our example:
mem_bump
).Figure 135. Select where to export the binary file.¶
Select the
Start Address
which is thepBuf
address found in theExpressions
window and click onFinish
to export the binary file.Figure 136. Select the address and length of the memory data to be exported.¶
Open a command line interface from the directory where the
debug_info_parser
tool is located and run the command shown below. Please see this is based on the full command shown in section How to Parse the Debug Information..\debug_info_parser.exe parse --mem_dump <PATH>\mem_bump
Look into the parsed data (
.json
) file saved in the same directory as the memory dump binary file.Figure 137. Example of part of the Debug Info parsed data in
.json
.¶Analyze the data based on the elements stored in each of the domain structures explained in Domains - Data Resources and Structures.
Debugging guide for serial interfaces¶
The aim of this chapter is to give some pointers on what to check if some issues are seen when using the UART, SPI, or I2C interfaces.
Note: TI only supports how to use the serial interface modules and the TI supplied drivers for these interfaces. TI are not able to help on the usage of a non-TI device connected to I2C, SPI or UART.
I2C¶
The I2C interface needs an external pull-up resistor. On the LauchPad these are 3.3 kOhm.
Measure the waveform on the I2C interface with an oscilloscope to verify the SCL and SDA levels
Monitor the interface with a logic analyzer. Check that data on the bus is the same as the wanted data.
Check the interface by reading out the device ID of the sensor.
Check the slave address. The I2C module uses a 7-bit address. The last 8th bit is the data direction bit. This last bit is appended automatically by the driver/HW. The Slave address given to the driver has therefore to be 7-bit.
Ensure that the readCount and writeCount is according to the register size used. If the result register in the sensor is 16-bit, the readCount has to be set to 2.
SPI¶
Monitor the interface with a logic analyzer. Compare the waveform with the waveform given in the datasheet for the device you are communicating with.
Check that the CSn is set correctly.
Check that the frame format is set correctly. Check if the device you are communicating with uses SPO = 1 or 0 and SPH = 1 or 0.
UART¶
Make sure to connect the TX pin of one device to the RX pin of the other device, and vice versa. If flow control is enabled, then the same applies towards the CTS and RTS pins.
Verify all communication settings between devices, including baud rate, data length, stop/parity bits and flow control.
Evaluate the interface with a logic analyzer or oscilloscope, using an analyzer tool if possible to convert the waveforms into their numerical counterpart.
Low Power Optimizations¶
If the device seems to be drawing too much current for your application, the following section will guide through the most common issues.
Note
Please refer to EnergyTrace User Guide as well as the Current Consumption Application Note for how to setup device for current consumption testing.
Check if the debugger is currently detached from the device. If using the XDS110 as debuger make sure to only connect the Ground and Power pins (this can be accomplished with jumper cables).

Figure 138. Modular LP EnergyTrace stand-alone Configuration¶
Warning
Confirm that the device has been power cycled since last time it was connected to the debugger.
Isolate the CC23xx from all peripheral devices (such as LEDS, External Flash, etc..) as these leakage currents might be the cause of higher power consumption than expected. If using a Launchpad confirm all jumpers are removed.
Isolate threads in the application until the thread with high power consumption is found. Once found, start isolating which peripherals (UART, I2C, SPI, ADC, Timers) are used in the thread and check if one of these could be the source of the issue. If any peripheral is set to start a transfer in blocking mode, the CC23xx will not be able to go into standby if the transfer never finished. Ex, if UART is set to restart receiving in blocking mode, the device will not go into standby until the expected data is received.
Debugging LF/HF Clock Output¶
As mentioned in the CC23xx SimpleLink Wireless MCU Technical Reference Manual, the DTB can be used to access internal signals. The LF (Low Frequency) and HF (High Frequency) crystal clock signals can be mapped to pins on the LaunchPad for signal debugging, for example to analyze the clock accuracy or to correlate against sniffer log events.
The code snippet below shows how to use DIO19 for observing the HF crystal or LF crystal clock signals, for example with a logic analyzer.
Initialize these IOs in your main
function with the following content:
/*******************************************************************************
* INCLUDES
*/
#include <ti/drivers/GPIO.h>
#include DeviceFamily_constructPath(inc/hw_types.h)
#include DeviceFamily_constructPath(inc/hw_memmap.h)
#include DeviceFamily_constructPath(inc/hw_ckmd.h)
#include DeviceFamily_constructPath(inc/hw_ioc.h)
#include DeviceFamily_constructPath(inc/hw_pmctl.h)
// ...
int main()
{
// ...
/*
* Add the following after Board_init();
* Be sure IOID used below is not used by any entries in PIN or
* GPIO tables from the board files.
* The clock source can be switched with constant clockSrc.
*/
uint8_t clockSrc = 0xC; //for HF crystal clock divided by 8
// uint8_t clockSrc = 0xF; //for LF crystal clock
// drive output low first
GPIO_setConfig(19, GPIO_CFG_OUTPUT | GPIO_CFG_OUT_LOW);
// Configure the IOC.IOC19.PORTCFG MMR to select DTB
HWREG(IOC_BASE + IOC_O_IOC19) &= ~IOC_IOC19_PORTCFG_M;
HWREG(IOC_BASE + IOC_O_IOC19) |= IOC_IOC19_PORTCFG_DTB;
// Make sure the DTB mux selects in IOC (and if required in
// source clock IP) are reset that zero is driven on DTB0.
// ULLSEL mux select (select CKMD)
HWREG(IOC_BASE + IOC_O_DTBCFG) &= ~IOC_DTBCFG_ULLSEL_M;
HWREG(IOC_BASE + IOC_O_DTBCFG) |= 0x1 << IOC_DTBCFG_ULLSEL_S; // 0x1 to route CKMD to DTB0
// Enable IOC.DTBOE.EN0
HWREG(IOC_BASE + IOC_O_DTBOE) &= ~IOC_DTBOE_EN0_M;
HWREG(IOC_BASE + IOC_O_DTBOE) |= IOC_DTBOE_EN0_EN;
// select which clock (CKMD) to output on DTB0 (DTB[0])
HWREG(CKMD_BASE + CKMD_O_DTBCTL) &= ~CKMD_DTBCTL_CLKSEL_M;
HWREG(CKMD_BASE + CKMD_O_DTBCTL) |= (clockSrc) << CKMD_DTBCTL_CLKSEL_S;
// enable DTB output
HWREG(CKMD_BASE + CKMD_O_DTBCTL) &= ~CKMD_DTBCTL_EN_M;
HWREG(CKMD_BASE + CKMD_O_DTBCTL) |= CKMD_DTBCTL_EN;
// ...
}
Note
The clock source can be selected with the variable clockSrc
. Please
refer to the DTBCTL register field description in the CC23xx SimpleLink Wireless MCU Technical Reference Manual for the
clock source. The HF crystal clock should be mapped as a divided clock,
as the IO pin output frequency is limited.
Note
Alternatively DTB0 can be mapped to DIO12 by setting DTBCFG.PADSEL bit field to 0x5. Other pins cannot output DBT0 signals.
When the HF crystal clock divided by 8 is selected in the above example, the output signal of 6 MHz can be observed (48 MHz / 8 = 6 MHz). When the LF crystal clock is selected in the above example, then the output signal of 32.768 kHz can be expected.
The HF crystal clock is automatically disabled when the device enters standby mode and the clock is re-enabled when the device wakes up from standby. As a result, only short bursts of HF crystal clock might be observed in a typical Bluetooth LE application (such as basic_ble). These bursts represent the active mode phases of the device. For enabling permanent HF crystal clock output please select Power Policy Function PowerCC23X0_doWFI in the SysConfig - TI DRIVERS - Power module. Alternatively the device is also permanently in active mode during a debug session in CCS.

Figure 139. Power Policy Function in SysConfig¶
Note
The LF crystal clock is permanently enabled in standby mode.