Arduino – Software development with Visual Studio Code and PlatformIO, Debugging

Introduction

Instead of the Arduino IDE, Visual Studio Code can also be used as a development environment for Arduion projects. Visual Studio Code (VS Code) is a very powerful Integrated Development Environment (IDE) and is free. VS Code can be extended by thousands of so-called extensions.

The extension that will be used here is PlatformIO. It provides everything necessary for software development for embedded systems (microcontrollers). The entire toolchain for development, debugging and testing, libraries, frameworks, management tools, etc. for countless microcontrollers, microcomputers, platforms and hardware boards. PlatformIO can be used together with various development environments, but the integration with Visual Studio Code is probably the best and the most widely used.

Visual Studio Code and PlatformIO are not bound to a specific operating system, Windows, Mac OS X, Linux can be used as development platform. The upload and debugging to or with the target platform is done e.g. via USB or IP networks.

In this tutorial an Arduino Uno R3 with the microcontroller ATmega328P is used as target system.

Installation

Installing Visual Studio Code

Download from https://code.visualstudio.com/ and start installation.

Installing Visual Studio Code Extensions

Start VS Code, on the left select “Extensions”, search for “PlatformIO”, select “PlatformIO IDE” and click “Install”. The installation will take some time. After completion, VS Code must be restarted. After that, the PlatformIO icon will be displayed on the left.

Select the target Platform

After that you have to install the appropriate framework for the used hardware. To do this, select the PlatformIO icon on the left, then click on “Platforms”, search for “Atmel” and select “Atmel AVR” …

… and finally click “Install”.

The first program

Now the first program can be created and executed on the Arduino Uno.

Connect Arduino Uno to the PC using an USB cable.

Start VS Code and open an empty folder (Menu: File –> Open Folder…)

On the left click the “PlatformIO” icon, then select “PLATFORMIO QUICK ACCESS” –> “PIO Home” –> “Open” and “New Project”.

Enter the project name, select the used hardware board (here “Arduino Uno”) and select the parent folder for the new project or leave “Use Default Location”. Finish with “Finish”.

After that a new project is created with all necessary folders and files. main.cpp contains two empty functions for the initialization (setup()) and the actual program loop (loop()).

Modify the code in main.cpp with this one (https://github.com/schaeren/micro_controller/tree/master/arduino_avr/c/arduino_avr_blinky):

#include "Arduino.h"

void setup() {
    Serial.begin(115200);             // configure serial monitor (serial port over USB, 115200 is the speed)
    pinMode(LED_BUILTIN, OUTPUT);     // configure output for onboard LED
}

int loopCount = 0;

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);  // turn the onboard LED on (HIGH is the voltage level)
    delay(5);                         // wait for 5 milliseconds
    digitalWrite(LED_BUILTIN, LOW);   // turn the onboard LED off
    delay(500);                       // wait for 500 milliseconds
    Serial.print("loopCount = ");
    Serial.println(++loopCount);
}

Because this program uses the serial monitor you have to configure in the PlatformIO Project Configuration File platfomio.ini the monitor port (add the two lines at the end):

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:uno]
platform = atmelavr
board = uno
framework = arduino

monitor_port = COM5
; monitor_speed = 115200

Click on the following icon in the status bar of VS Code to compile the program and load it on the target computer (Arduino Uno):

After uploading to the Arduino Uno the LED L flashes (5 ms light followed by 500 ms dark):

The PlatformIO icons in the statusbar:

1HomeShow PlatformIO Home. Form here you can create a new project or have access to libraries, boards, platforms. devices, etc.
2BuildBuild (compile) the current project.
3UploadBuild (compile) the current project and upload it to the target.
4CleanClean project (temporary files).
5Serial MonitorShow the Serial Monitor window: the terminal shows the output from the application running on the target (Serial.print...())x§
6New TerminalOpen a new terminal window.

Click the “Serial Monitor” icon in the statusbar to see the output (Serial.print...()) from the program.

If it doesn’t work you may have to configure the monitor_port in platformio.ini. To check which port is used you can click on the “Home” icon in the statusbar and select “Devices”:

Debugging

Preparation

Programs running on the target may be debugged with VS Code and PlatformIO at source code level. For example, breakpoints can be set in the source code and variables can be viewed.

The ATmega 328P microcontroller on the Arduino Uno supports remote debugging via the USB port without additional hardware.

To do this, a debug library avr-debugger must be added to the program …

… then click on Add to Project …

… and if necessary select the project:

Furthermore, the following additions must be made in the platformio.ini configuration file (lines 6…20) (https://github.com/schaeren/micro_controller/tree/master/arduino_avr/c/arduino_avr_blinkyWithDebugger):

[env:uno]
platform = atmelavr
board = uno
framework = arduino
; settings for avr-debugger
build_type = debug
debug_tool = avr-stub
debug_port = COM5    ; <-- debug_port may be wrong, check with 'PlatformIO Home' -> 'Devices'
lib_deps = 
    jdolinay/avr-debugger @ ~1.4
debug_build_flags = 
    -Og
    -g2
    -DDEBUG
    -DAVR8_BREAKPOINT_MODE=1

For more information about debug build flags see https://docs.platformio.org/en/latest/projectconf/section_env_debug.html#debug-build-flags.

Furthermore, the debugger must be initialized in the program (here lines 2 and 3, as well as line 17), see the following example (https://github.com/schaeren/micro_controller/tree/master/arduino_avr/c/arduino_avr_blinkyWithDebugger):

#include "Arduino.h"
#include "avr8-stub.h"
#include "app_api.h" // only needed with flash breakpoints

double cycleTime;
double dutyCycle;
double step;
bool increase;

void setup() {
    cycleTime = 10.0; // Duration [ms] of a cycle consisting of one light and one dark phase
    dutyCycle = 0.0;  // duty cycle light on vs. off during one cycle [0.0...1.0]
    step = 0.01;      // dutyCycle increment between two cycles
    increase = true;  // true -> light becomes brighter / false -> light becomes darker 

    pinMode(LED_BUILTIN, OUTPUT);
    debug_init();
}

// LED periodically becomes brighter and darker again.
// Duration from dark to bright or vice versa (time of a half period): (1/step)*cycleTime [ms] aprox.
void loop() {
    int on = dutyCycle * cycleTime;
    int off = (1.0 - dutyCycle) * cycleTime;
    digitalWrite(LED_BUILTIN, HIGH);
    delay(on);
    digitalWrite(LED_BUILTIN, LOW);
    delay(off);
    dutyCycle += increase ? step : -step;
    if (dutyCycle <= 0.0) {
        dutyCycle = 0.0;
        increase = !increase;
    }
    else if (dutyCycle >= 1.0) {
        dutyCycle = 1.0;
        increase = !increase;
    }
}

Important: If the debugger is used, i.e. if debug_init() is used, then Serial.print...() cannot be used, because the serial connection is already needed for debugging.

Start program with debugger

  1. Select the debugger view in the toolbar on the left.
  2. Select debug configuration PIO Debug.
  3. Set breakpoints as required.
  4. Compile the program, load it on the target system and start it.

Starting takes a few seconds, i.e. the program is started first and then the debugger. This means that the loop() function has already been started and possibly run several times before the debugger stops at a breakpoint. For the same reason breakpoints in the setup() function are ignored. Beachten sie auch, dass die Ausführung des Programms wesentlich verlangsamt wird, auch wenn Codeteile ohne Breakpoint ausgeführt werden.

Technical details: With default breakpoint mode enabled (platformio.ini: build_flags = -DAVR8_BREAKPOINT_MODE=1), the debugger on the target triggers a software interrupt after executing each (machine) instruction to check if the program has to be stopped due to a breakpoint. This can slow down the execution by a factor of 100 or more. Other breakpoint modes are possible, but require special debugging hardware (debug probe) and/or a special bootloader that can write and erase breakpoints directly in the flash memory (but this can then significantly reduce the lifetime of the flash memory and thus of the microcontroller; typically only 10,000 write cycles are specified).

Weitere Informationen

Add an additional target/board to an existing project

If you want to use another Arduino board, you only have to enter the corresponding code in the file platformio.ini at the entry board = ,,,. You can find the code e.g. by calling PlatformIO Home in VS Code (Home icon in the status bar), then clicking on Boards, searching for the desired board and copying the code into the platformio.ini file.

If you want to add an additional board of other type to an existing project, then you must proceed as described here:

Assumtion: The existing project supports the target Arduino Uno and you want to add an Arduino Mega R3 2560 board to this project.

  1. Open PlatformIO Home (click Home icon in the status bar).
  2. Select Home.
  3. Click New Project.
  4. The Project Wizard is displayed:
    1. Enter exactly the same project name that is already open.
    2. Select the board to be added.
    3. Uncheck Use default location and …
    4. … select the parent directory of the current project (the one that is already open).
  5. Click Finish.

Now open the file platformio.ini: A new section for the new board (environment) has been added:

...
[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino

Copy any missing settings (e.g. for debugging) from the existing section to the new section and add a new section [platformio] with the setting default_envs = {new environment}. Often you also have to adjust the COM port for the debugger. How to find the appropriate port has already been described above, search for configure the monitor_port in platformio.ini.

Finally you should have something like this:

[platformio]
default_envs = megaatmega2560   ; <-- set board/environment, 'uno' or 'megaatmega2560'

[env:uno]
platform = atmelavr
board = uno
framework = arduino
; settings for avr-debugger
build_type = debug
debug_tool = avr-stub
debug_port = COM5    ; <-- debug_port may be wrong, check with 'PlatformIO Home' -> 'Devices'
lib_deps = 
    jdolinay/avr-debugger @ ~1.4
debug_build_flags = 
    -Og
    -g2
    -DDEBUG
    -DAVR8_BREAKPOINT_MODE=1

[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino
; settings for avr-debugger
build_type = debug
debug_tool = avr-stub
debug_port = COM6    ; <-- debug_port may be wrong, check with 'PlatformIO Home' -> 'Devices'
lib_deps = 
    jdolinay/avr-debugger @ ~1.4
debug_build_flags = 
    -Og
    -g2
    -DDEBUG
    -DAVR8_BREAKPOINT_MODE=1