Debugging AVR-based embedded systems with CLion and Bloom
Bloom exposes an interface to the connected AVR microcontroller (target) via a GDB server. This means that any IDE with remote GDB capabilities can effectively gain access to the connected target, via Bloom, to perform debugging operations. CLion possesses this ability.
This article will explore the debugging and programming of AVR microcontrollers with CLion and Bloom. We will be using an ATxmega16C4 8-bit AVR target, with the Atmel-ICE debug tool.
Prerequisites
Before following this article, please ensure that you have completed the following:
- Install Bloom (version 0.11.0 or later)
- Install avr-gdb (version 10.1 or later)
- Setup your CMake/Makefile project in CLion
Bloom configuration
This step can be skipped if you've already created a bloom.yaml configuration file for your project.
Before we can begin a debug session with Bloom, we must create a bloom.yaml configuration file for our project, which Bloom will use to extract debug tool, target and debug server configuration.
We will use Bloom's init
command to create the configuration file in our
project directory.
$ cd /path/to/project/directory/;
$ bloom init;
At this point, we can amend the newly created bloom.yaml file to include configuration for our debug tool and target:
environments:
# Configuration for our "default" environment. Bloom will fall back to
# the "default" environment when no environment name is passed.
default:
debugTool:
name: "atmel-ice"
target:
name: "atxmega16c4"
# The ATxmega16C4 employs the PDI physical interface for programming and debugging.
physicalInterface: "pdi"
debugServer:
name: "avr-gdb-rsp"
ipAddress: "127.0.0.1"
port: 1442
insight:
enabled: true
With the configuration above, we configure our "default" environment to use the Atmel-ICE debug tool,
with the ATxmega16C4 target, which employs the PDI physical interface. We also select the AVR GDB debug
server and configure it to listen on 127.0.0.1:1442
.
The supported physical interfaces for your target can be found on the Supported Targets page. Alternatively, this information can be found in the target's datasheet.
Finally, we enable the Insight GUI at the project level. This will allow us to inspect AVR registers and memories (such as SRAM and EEPROM), during our debug sessions.
Verify project configuration and target connection
Before running Bloom under CLion, it's best to confirm that Bloom will accept our project configuration and is able to establish a connection to the debug tool and AVR target. We can do this by performing a test run from our project directory.
Ensure that the debug tool is connected to the AVR target via the appropriate physical interface pins. Then, run Bloom from the project directory:
cd /path/to/project/directory/;
bloom;
2022-09-23 23:00:53 BST [MT] [1]: [INFO] Selected environment: "default"
2022-09-23 23:00:53 BST [TC] [2]: [INFO] Starting TargetController
2022-09-23 23:00:53 BST [TC] [3]: [INFO] Connecting to debug tool
2022-09-23 23:00:53 BST [TC] [4]: [INFO] Debug tool connected
2022-09-23 23:00:53 BST [TC] [5]: [INFO] Debug tool name: Atmel-ICE
2022-09-23 23:00:53 BST [TC] [6]: [INFO] Debug tool serial: J41800057594
2022-09-23 23:00:53 BST [TC] [7]: [INFO] Activating target
2022-09-23 23:00:54 BST [TC] [8]: [INFO] Target activated
2022-09-23 23:00:54 BST [TC] [9]: [INFO] AVR8 target promoted to XMega target
2022-09-23 23:00:55 BST [TC] [10]: [INFO] Target ID: 0x1e9443
2022-09-23 23:00:55 BST [TC] [11]: [INFO] Target name: ATxmega16C4
2022-09-23 23:00:55 BST [DS] [12]: [INFO] Starting DebugServer
2022-09-23 23:00:55 BST [DS] [13]: [INFO] Selected DebugServer: AVR GDB Remote Serial Protocol Debug Server
2022-09-23 23:00:55 BST [DS] [14]: [INFO] GDB RSP address: 127.0.0.1
2022-09-23 23:00:55 BST [DS] [15]: [INFO] GDB RSP port: 1442
2022-09-23 23:00:55 BST [DS] [16]: [INFO] DebugServer ready
2022-09-23 23:00:55 BST [DS] [17]: [INFO] Waiting for GDB RSP connection
2022-09-23 23:00:55 BST [MT] [18]: [INFO] Starting Insight
2022-09-23 23:00:55 BST [MT] [19]: [INFO] Insight ready
In the command above, no environment name was passed to Bloom. This means Bloom will fall back to the "default" environment, which is the desired behaviour in this instance.
If Bloom fails to start up, review your project configuration and target connection. Follow the instructions in the error logs.
Once you have Bloom running without issue, trigger a shutdown by pressing Ctrl+C or by closing the Insight window.
Setup an 'Embedded GDB Server' configuration in CLion
To configure CLion to interface with Bloom, an 'Embedded GDB Server' configuration must be created. With this configuration, CLion will automatically manage Bloom's process, as well as interface with Bloom's GDB server (via avr-gdb), to gain access to the AVR target.
Open the "Run/Debug Configurations" window via the 'Edit Configurations' action in the 'Run' menu:

Create a new 'Embedded GDB Server' configuration:

Ensure that the correct 'Target' and 'Executable' is selected for the debug configuration. Then set the 'GDB' field to the path of the avr-gdb executable.
The avr-gdb executable can be located via the following command:
$ which avr-gdb;
The "Bundled GDB" provided by CLion cannot be used for debugging AVR programs - it does not support AVR binaries.
In the 'GDB Server' field, specify the path to Bloom's executable. Use the 'GDB Server args' field to
pass any arguments to Bloom. Then, in the 'target remote args' field, specify the IP address and port
on which Bloom's GDB server is configured to listen. We configured this to be
127.0.0.1:1442
in our bloom.yaml file.
Bloom's executable can be located via the following command:
$ which bloom;
Configure automatic programming via the 'Download executable' configuration
With Bloom, the connected AVR target can be programmed via GDB's load
command. CLion possesses the ability to invoke the load
command at the
beginning of a debug session, to ensure that the latest binary resides on the target's program memory
before the user begins debugging.
This functionality can be controlled via the 'Download executable' configuration in the 'Embedded GDB Server' configuration:
- Always - CLion will always program the target with the latest binary.
- Updated only (recommended) - CLion will only program the target with the latest binary, if the binary has changed since it was last programmed.
-
None - CLion will not program the target with the latest binary. Users can still
program the target by manually invoking the
load
command via the GDB console.
The new 'Embedded GDB Server' configuration should closely resemble the following:

Begin a debug session in CLion
To begin a debug session in CLion, select your new debug configuration via the 'Debug' action in the Run menu:


CLion will now start Bloom's process and wait for Bloom's GDB server to start up. During this time, Bloom will be establishing a connection to the debug tool and AVR target, preparing the two for a debug session.
With the 'Embedded GDB Server' configuration, CLion assumes responsibility for managing Bloom's process. It will start and stop Bloom's process without any further action required by the user.
Once CLion has established a connection to Bloom's GDB server, it will be in full control of the AVR target. From this point, debugging operations can take place.

The Insight window will also appear:

Program the AVR target with CLion
When setting up our 'Embedded GDB Server' configuration, we configured CLion to automatically program the AVR target with the latest binary, if the binary has changed since the previous debug session.
To program the target: Make changes to your code and then restart the debug session. CLion will then
build the new AVR binary and use GDB's load
command to upload the newly
built binary to the target.
The 'Embedded GDB Server' configuration was configured to automatically trigger a build before starting a debug session. See the 'Before launch' section in the 'Embedded GDB Server' configuration for more.
Upon the completion of programming, the AVR target will be reset to its reset vector. From this point, debugging can be resumed with the newly applied changes.
Manual programming
To manually trigger programming, simply invoke the load
command via the GDB
console.
The GDB console can be accessed via the 'GDB' tab in the debug pane:


When manually programming the target, GDB may attempt to reset the target for a second time, by setting the target's program counter to the start address of the program being debugged. This may result in unexpected behaviour, as the start address of the program being debugged may differ from the reset vector.
To circumvent this, use CLion's 'Reset' button (in the debug pane) to force the target to be reset to
its reset vector, upon the completion of programming. Alternatively, use the
monitor reset
GDB command, in the GDB console, to trigger the reset.
This is not required when CLion automatically invokes the load
command,
because CLion will trigger a subsequent target reset upon the completion of programming (if
configured to do so - see the 'Advanced GDB Server Options' section in the 'Embedded
GDB Server' configuration for more).
Inspect AVR memories in CLion
During a debug session, AVR SRAM and FLASH memories can be inspected via CLion's 'Memory View':


When addressing AVR memories in CLion, it's important to note that CLion forwards memory addresses to GDB, and GDB expects all AVR memory addresses to have the memory type (FLASH, SRAM, EEPROM, etc) embedded into the address.
In other words, GDB does not provide any other means to specify the type of AVR memory being addressed, so it must be embedded into the address.
To address SRAM, the address must be OR'ed with 0x00800000
. For EEPROM,
the address must be OR'ed with 0x00810000
. FLASH addresses do not
require any modification.
For example, to access SRAM at address 0x0000229C
, via GDB, we must use
0x0080229C
. To access FLASH memory at address
0x00000208
, we can use the same address, as no modifications are
required for FLASH addresses.
This only applies when addressing memory via GDB (and CLion). Bloom's Insight GUI works independently of GDB - address modifications are not required when inspecting memory via Bloom's Insight GUI.
Bloom does not currently support EEPROM access via GDB. This will be supported in a future release. For the time being, users can use Bloom's Insight GUI for EEPROM access.
As an alternative to CLion's 'Memory View', Bloom's Insight GUI can be used to inspect SRAM:

EEPROM inspection
As mentioned above, Bloom does not currently support EEPROM access via GDB. However, EEPROM can still be inspected via Bloom's Insight GUI:

Support for EEPROM access via GDB will be introduced in a future release. See the corresponding ticket for more.
Inspect AVR peripheral registers in CLion
CLion provides the ability to inspect peripheral registers on embedded targets such as AVR microcontrollers.
Peripheral registers can be accessed via the 'Peripherals' tab in the debug pane:

CLion requires a System View Description (SVD) file, from which it will extract addresses, offsets and other register information, in order to read and present register values in the 'Peripherals' view.
If you do not possess an SVD file for your AVR target, Bloom can generate this for you, via the
monitor svd
GDB command. Invoke the command via the GDB console. Bloom will
then generate the SVD file and save it in the current project directory:

Oddly, CLion presents the output of GDB's monitor
commands in the 'Console'
tab:

Our SVD file has now been generated:

The SVD file can now be selected in CLion's 'Peripherals' view. Once selected, CLion will present AVR register values in the 'Peripherals' view:

AVR registers are accessed via SRAM addresses - this is why the addresses displayed above are OR'ed
with 0x00800000
. See the Inspect
AVR memories in CLion section above, for more on accessing SRAM via GDB.
Alternatively, AVR registers can be inspected via Bloom's Insight GUI:

Bloom's Insight GUI also provides the ability to manipulate register values, whilst the target is stopped:

Conditional breakpoints in CLion
CLion provides the ability to specify conditions against breakpoints. Right click any breakpoint to access the associated condition:

With conditional breakpoints, GDB will only halt target execution once the associated condition is met (not really - see below).
Significant performance impact
Although conditional breakpoints can be useful, it's important to understand their potential impact on program performance.
In GDB and Bloom, conditional breakpoints are implemented as standard software breakpoints. The conditions associated with conditional breakpoints are evaluated by GDB. This means conditional breakpoints will always halt target execution, regardless of whether the associated condition is met. Once the breakpoint is reached, GDB will then evaluate the condition and automatically resume target execution, if the associated condition is not met.
In other words, the associated conditions of conditional breakpoints are evaluated on the host machine, as opposed to the AVR target. This may result in a significant performance impact.
To avoid this performance impact, consider implementing the condition in your code, as demonstrated below.
Evaluate conditions on the target
To avoid the potential performance impact of standard conditional breakpoints, an alternative approach can be taken, where the condition is evaluated on the AVR target, as such:

With the example above, the breakpoint will only be reached once the condition
counter == 10
is met. The performance impact of this approach is almost
minimal. One of the drawbacks of this approach is the requirement to
program the AVR target after creating a new conditional breakpoint.
In the example above, the NOP
AVR instruction was used instead of the
BREAK
instruction. This is due to a known bug in GDB (or, potentially,
Bloom), where BREAK
instructions are not handled properly when resuming
target execution.
To avoid this bug, please avoid using BREAK
instructions in your code.
Evaluate expressions on the AVR target
CLion provides the ability to evaluate expressions on the AVR target. Open the "Evaluate Expression" window and input the expression, then click "Evaluate":


CLion forwards the expression to GDB for evaluation, then presents the result to the user.
The evaluation process employed by GDB depends on the expression. For trivial memory-access expressions, such as reading or updating a variable, GDB will access the memory directly via Bloom, without evaluating the expression on the target.
For expressions involving function calls, GDB will record the current value of all 32 general purpose AVR registers before updating them with any values required to evaluate the expression (function arguments). Then it will set any relevant breakpoints, update the program counter and resume execution. Target execution will stop once the expression has been evaluated. Then, GDB will restore the recorded values of all 32 AVR registers, as well as the value of the program counter, allowing execution to be resumed from the initial point at which the evaluation was triggered.
Function calls can only be evaluated if the function is present in program memory. Some compilers may inline or completely remove functions when appropriate.
When calling member functions in the expression, it is not necessary to include
the argument for the implicit this
parameter - GDB will do this
automatically.
Default argument values cannot be used when evaluating a function call.
void blink(std::uint8_t times, std::uint16_t delay = 100) {...}
// Expressions to evaluate via CLion (GDB):
blink(1); // Incorrect - argument for `delay` parameter not specified
blink(1, 100); // OK
GDB is unaware of these values at runtime, so they must be specified in the expression.
Questions or feedback?
Questions can be raised on Bloom's GitHub discussions page (GitHub account required). Please feel free to submit any questions or feedback via a new discussion.