Home Getting Started Download Discussions Source Report An Issue

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:

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:

Edit debug configurations in CLion

Create a new 'Embedded GDB Server' configuration:

Debug configuration templates in CLion

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:

Debug configuration templates in CLion

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:

Select the 'Debug' action from the 'Run' menu in CLion
Select a debug configuration to begin a debug session

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.

Active AVR debug session in CLion

The Insight window will also appear:

Bloom Insight ATxmega16C4

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:

CLion GDB console tab
CLion GDB console tab

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':

CLion show variable in memory view
CLion memory view of SRAM

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:

Inspect AVR SRAM with Bloom

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:

Inspect AVR EEPROM with Bloom

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:

Peripherals tab in CLion's 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:

Running the monitor svd command in CLion

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

GDB monitor svd command output

Our SVD file has now been generated:

SVD file for the ATxmega16C4 AVR target

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 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:

AVR register values in Bloom Insight

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

AVR register inspection in Bloom Insight

Conditional breakpoints in CLion

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

Creating a conditional breakpoint in CLion

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:

Defining a conditional breakpoint to be evaluated on the AVR target

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":

Evaluate expressions tool button in Clion debug pane
Evaluate expressions window in Clion

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.