Developing Embedded Applications with VS Code Arduino

Greg Terrell
14 min readApr 15, 2020

Based on the popularity of the “Feather” form-factor created by Adafruit, there are numerous people out there working with a similar setup to one I use frequently. I also know there are experienced developers out there familiar with enterprise software tools that find the Arduino IDE frustratingly simple.

The Arduino IDE is a good set of training wheels for simple projects and a straight-forward tool for non-developers to get started creating IoT embedded applications. But for those of us creating something more, it doesn’t have the horsepower we are used to and need for productive development.

Enter Visual Studio Code Arduino, a nice extension to Microsoft Visual Studio Code for creating embedded applications in the Arduino style. If you aren’t familiar with VS Code, it is a highly extensible, open-source, runs anywhere code editor. Runs anywhere means Windows (of course) but also Linux x64 and macOS on Apple devices.

For those of you with experience in Visual Studio, VS Code is a lightweight sibling with a short learning curve. Because of its popularity there are some great extensions for a huge list of development scenarios. Enough about VS Code; if you aren’t convinced yet, look around and you won’t have to look far.

One thing I like about the VS Code Arduino approach is that it stays close enough to the Arduino IDE environment that switching back and forth between the two is not difficult. I need to write library software that Arduino IDE users could consume, so the examples I create need to be tested and run from the Arduino IDE environment. If you are writing software to share with others using the Arduino IDE, VS Code Arduino will let you do that without changes to your code. This is good, because while I really like VS Code Arduino, there are others that will find its takes a little more experience than they have; they should stick with the Arduino IDE until they are ready.

Through the rest of this guide I am assuming you are comfortable with the Arduino IDE, embedded libraries and boards, and embedded development in general. If so and you are ready to move on to a more powerful development workflow, below I will show you my development workbench for SAMD devices that has helped increase my productivity a great deal.

One other thing… debugging.

Debugging is core to software development. You can do it slow or do it fast. But no matter how you do it, you will be debugging your code. I prefer fast.

To debug efficiently, like many enterprise developers are used to, in the embedded space you need a debug probe. The debug probe connects to the processor in your embedded device and interfaces with your development environment to set breakpoints, inspect variables and upload your software into your device.

The reigning king of embedded debugging is Segger and their J-Link family of debugging probes. You can find J-Links everywhere and for the hobbyists reading this, there are cost effective EDU versions you can use for your non-commercial projects.

The Setup: Adafruit Feather M0 Express and Segger J-Link Plus
The Setup: Adafruit Feather M0 Express and Segger J-Link Plus

I am using a J-Link Plus attached via USB.

You can benefit from VS Code Arduino as simply a better code editor than the Arduino IDE, you don’t have to invest in a debugger. But if you are serious about writing embedded applications you should consider that a must purchase.

Setup

To start you will need to install VS Code. You will also need to install Arduino IDE. VS Code Arduino relies on some Arduino functionality to build applications and to manage both libraries and 3-party boards (like Adafruit or Sparkfun SAMD development boards).

To use a Segger J-Link you need to also install the Segger combination software and documentation download (see downloads section at the end of this article for a recommended set of links).

To add superpowers to your VS Code environment, I recommend you do that from within VS Code using the built-in Extensions manager (Ctrl+Shift+X).

VS Code, Extension showing list of install extensions
VS Code, Extension showing list of install extensions

To install an extension, search for the extension by entering its identifier (listed below, use the Bold highlighted text for the search phrase). Note the cortex-debug extension is only useful if you have a J-Link in you workflow. There are plenty of other useful VS Code extensions, but the ones listed below directly support the topics here.

  • vsciot-vscode.vscode-arduino (Arduino for Visual Studio Code)
  • ms-vscode.cpptools (C/C++ Intellisense, debugging, and code browsing)
  • austin.code-gnu-global (C/C++ Intellisense for GNU toolchain)
  • marus25.cortex-debug (ARM Cortex-M GDB Debugger support for VSCode)

VS Code UI

In the image below I have highlighted the main controls you will interact with while developing in VS Code Arduino. There is so much here. For you to learn it, you have to use it. Open your projects and try it out. Checkout the mouse-over and right-click context menus to see all there is here. I have also recorded a video that highlights a VS Code Arduino session, it is found on YouTube and the link to it is in the Resources-Links section at the end of the article.

Important: Open your Arduino .INO project folder, not the .INO file when working in VS Code Arduino. VS Code utilizes the folder structure to associate the parts of your project.

A quick tour of the VS Code Arduino user interface.
A quick tour of the VS Code Arduino user interface.

Writing Code

Getting started writing a new sketch takes a couple extra steps beyond the Arduino IDE way. One thing to keep in mind is that VS Code Arduino is “folder” centric. Remember, you want to open/close containing folders, not individual files.

You can get started two ways:

  1. Use an existing Arduino example by selecting it from the Arduino Examples list (see navigation overview above). This will create a new folder and .ino file in the “generated_examples” folder (below your Arduino sketch folder). Note: the sketch folder settings from the Arduino IDE is used by VS Code Arduino as your sketch collection folder.
  2. Create a new file (File, New File), type in void setup(){} void loop(){} and save it in a new folder with an .ino extension.

Intellisense and Code Navigation

VS Code inherits the Intellisense and code-navigation functionality of its big-brother Visual Studio. Code problems are highlighted, you can navigate to where items are defined, and see the signature of a function as you code. Try it out.

  • Comment out the #include for the SEGGER_RTT.h, see how it creates problems for the SEGGER_RTT_printf() on lines 72 and 73.
  • To comment/uncomment: use Ctrl+/ for toggle, or Ctrl+K, Ctrl+C and Ctrl+K, Ctrl+U to comment/uncomment code respectively.
  • Mouse over SEGGER_RTT_printf on line 72 or 73 and see the function signature, or mouse over the RTT_CTRL_TEXT_CYAN on line 73 to see how that macro expands.
  • Click on either of those definitions and press F12 to navigate into SEGGER_RTT file where they are defined. Likewise click on a header file and press F12 to open it.

Config Files

arduino.json: this file is more or less the project configuration file. One improvement over the Arduino IDE is the main source file for your project is specified in this file vs. having to match the name of the folder it lives in.

The only lines that you would likely edit directly are the sketch file name (I change file names seems like all the time) and the output setting (last line). By setting the output folder, VS Code Arduino will cache build cycle assets. This decreases build times as assets are reused and makes it easy to grab generated files for debugging.

Note: the build folder can be deleted at anytime to remove cached items, they will simply be rebuilt on the next verify cycle.

The remaining lines are automatically maintained by GUI tools in the VS Code Arduino extension. When you select the board and port settings from the status line (bottom, blue line/white text), your choices are saved here.

{
"sketch": "BlinkRTT.ino",
"board": "adafruit:samd:adafruit_feather_m0_express",
"configuration": "usbstack=arduino,debug=on",
"port": "COM16",
"output": ".//.build"
}

c_cpp_properties.json: this configuration file controls the Intellisense and code navigation features in VS Code. This file does not impact the GCC tool chain or the verify/build process.

This is an area of the VS Code Arduino experience that takes some tuning and tailoring and keeps the VS Code Arduino tool set outside the realm of beginners. Be prepared to find your header files and adjust accordingly. My setup for the BlinkRTT.ino example is shown below. A couple items to think about.

  • Line 6: this line was added automatically when selecting an Adafruit SAMD board.
  • I have found that the trailing ** doesn’t always work for finding headers in sub-directories as documented. That is why lines 9 and 10 are not replaced by a single line ending in /7.2.1/**.
  • Line 8 may not be needed in your environment. I have my Arduino sketch folder in a non-default location, so I needed to provide some help locating my Arduino libraries collection.
  • The Intellisense engine seems to misbehave if a folder is reference more than once in the c_cpp_properties file. Once again, mileage varies be prepared to tailor and tune this file.
Example c_cpp_properties.json file contents.
Example c_cpp_properties.json file contents.

launch.json: I will cover the contents of the launch config file here, but this configuration is only applicable to J-Link debugging, which is covered in a later section.

{
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug",
"type": "cortex-debug",
"cwd": "${workspaceRoot}",
"executable": ".//.build/${fileBasename}.elf",
"request": "launch",
"servertype": "jlink",
"device": "ATSAMD21G18",
"runToMain": true
}
]
}
  • I have crafted the executable setting to find the .elf file in my project’s .build folder. The ${fileBasename} is a variable supplied by VS Code. This allows me to reuse the launch.json file in any project with the same ATSAMD21G18 device without changes.
  • I found that request set to launch works most reliably (another option is attach).
  • runToMain sets a soft breakpoint at the start of your program, so that on reset the program is ready to run. This allows you to set/reset breakpoints before you actually start your test.

Verifying

The “build” step in the development cycle is referred to as Verify in VS Code Arduino. The results are sent to the OUTPUT window at the bottom of the VS Code UI. If you have errors verifying, scroll up in the OUTPUT window to look for the problem, just like you would have done in Arduino IDE.

  • GOOD : [Done] Finished verify sketch — BlinkRTT.ino
  • BAD : [Error] Exit with code=1

Uploading

I use the J-Link Commander utility (jlink.exe) to upload my embedded application binary up to my device. To use the steps I list below you will need to add the C:\Program Files (x86)\SEGGER\JLink folder to your path if you are using Windows. This allows me to run Powershell from within VS Code, start the jlink.exe application and then re-use a couple commands to upload the .bin file created by the verify step and the GCC tool-chain.

Using the TERMINAL window below you can command jlink.exe to be your upload tool. These are the 3 simple steps to upload the binary of your embedded application to your device. Subsequent uploads are easily done with the up-arrow keystroke in the TERMINAL window.

  • Open the TERMINAL window and CD to your project folder.
  • Start jlink.exe (only needed once per development session). The follow line starts my session.
jlink.exe -device ATSAMD21G18 -if SWD -speed 4000 -autoconnect 1
  • Now to upload (reset, halt, load binary file at address 0)
rhloadbin .build\blinkrtt.ino.bin, 0

See below for this in the context of actual use.

> jlink.exe -device ATSAMD21G18 -if SWD -speed 4000 -autoconnect 1  SEGGER J-Link Commander V6.64b (Compiled Mar 20 2020 10:05:37)
DLL version V6.64b, compiled Mar 20 2020 10:05:06
Connecting to J-Link via USB...O.K.
Firmware: J-Link V10 compiled Mar 18 2020 17:38:51
Hardware version: V10.10
S/N: 600105265
License(s): RDI, FlashBP, FlashDL, JFlash, GDB
VTref=3.296V
Device "ATSAMD21G18" selected.
Connecting to target via SWD
InitTarget() start
InitTarget()
InitTarget() end
Found SW-DP with ID 0x0BC11477
DPIDR: 0x0BC11477
Scanning AP map to find all available APs
AP[1]: Stopped AP scan as end of AP map has been reached
AP[0]: AHB-AP (IDR: 0x04770031)
Iterating through AP map to find AHB-AP to use
AP[0]: Core found
AP[0]: AHB-AP ROM base: 0x41003000
CPUID register: 0x410CC601. Implementer code: 0x41 (ARM)
Found Cortex-M0 r0p1, Little endian.
FPUnit: 4 code (BP) slots and 0 literal slots
CoreSight components:
ROMTbl[0] @ 41003000
ROMTbl[0][0]: E00FF000, CID: B105100D, PID: 000BB4C0 ROM Table
ROMTbl[1] @ E00FF000
ROMTbl[1][0]: E000E000, CID: B105E00D, PID: 000BB008 SCS
ROMTbl[1][1]: E0001000, CID: B105E00D, PID: 000BB00A DWT
ROMTbl[1][2]: E0002000, CID: B105E00D, PID: 000BB00B FPB
ROMTbl[0][1]: 41006000, CID: B105900D, PID: 001BB932 MTB-M0+
Cortex-M0 identified.
J-Link>r
Reset delay: 0 ms
Reset type NORMAL: Resets core & peripherals via SYSRESETREQ & VECTRESET bit.
ResetTarget() start
ResetTarget() end
J-Link>h
PC = 00002BC8, CycleCnt = 00000000
R0 = 00000001, R1 = 00000004, R2 = 00000000, R3 = 00000001
R4 = 20000664, R5 = 200006BC, R6 = 200006BC, R7 = 20007FE8
R8 = 67A7FCF3, R9 = FC9FFF7E, R10= FF7EFFFE, R11= FDFFFFDB
R12= 000003E8
SP(R13)= 20008000, MSP= 20008000, PSP= F7FFFB5C, R14(LR) = 00005B23
XPSR = 21000000: APSR = nzCvq, EPSR = 01000000, IPSR = 000 (NoException)
CFBP = 00000000, CONTROL = 00, FAULTMASK = 00, BASEPRI = 00, PRIMASK = 00
FPU regs: FPU not enabled / not implemented on connected CPU.
J-Link>loadbin .build\blinkrtt.ino.bin, 0
Downloading file [.build\blinkrtt.ino.bin]...
J-Link: Flash download: Bank 0 @ 0x00000000: 1 range affected (16896 bytes)
J-Link: Flash download: Total time needed: 0.742s (Prepare: 0.042s, Compare: 0.347s, Erase: 0.062s, Program: 0.268s, Verify: 0.018s, Restore: 0.003s)
O.K.
J-Link>

Debugging

As mentioned above, I am using a J-Link Plus with an Adafruit Feather M0 Express development board. The Feather doesn’t have the necessary SWD 10-pin header to connect between the Feather and the JLink. I have glued a header on the proto area of the Feather and soldered leads to power, ground, SWDIO/SWCLK (SWD on bottom, as shown below). You can use an Adafruit 2743 breakout (Adafruit, Digikey, Mouser) to make the soldering a little easier.

Modified Feather M0 Express for debugging with J-Link.
Modified Feather M0 Express for debugging with J-Link.

To make attaching a debugger easier, LooUQ has created a Feather to SWD adapter, which is available here. This adapter uses pogo pins to make the connection, no need to alter your Feather and NO SOLDERING.

With your embedded application verified (built), uploaded to your device and your J-Link connected… let the debugging session begin.

  1. Place breakpoints in your application code where you want to stop and examine your code’s state. To do this, cursor to the line where you want to break, press F9 to toggle the breakpoint there. Breaks show as a red dot next to the line number.
  2. Ensure that you have focus on the .INO file by clicking on the file name. Then press F5 to start debugging. If you have set runToMain to true in your launch.json file, the application will be automatically stopped for you in the main() function behind the scenes of the .ino. This function contains the familiar setup() and loop() functions of the Arduino pattern.
  3. Once started you will see the debugging control appear in your VS Code window (shown 1st below). The following image shows the program cursor (line 35) and a breakpoint (line 50).
The debugging control panel.
The debugging control panel.
  • Run to next breakpoint (F5)
  • Step over next line/function (F10)
  • Step into next line/function (F11)
  • Step out of current function (Shift+F11)
  • Restart debugging (Ctrl+Shift+F5)
  • Stop debugging (Shift+F5)
Behind the scenes of main() and line indicators
Behind the scenes of main() and line indicators

If you are new to debugging or embedded debugging specifically, I recommend that you use a simple application like blink.ino or similar to get familiar with the process. Debugging embedded applications is a slightly different experience that debugging on larger systems. In particular #MACRO and some code constructs can make your program jump a line or two and seem like it is out of sync. This is because the J-Link is actually tracing through the assembly code version of your application and stepping forward one machine instruction at a time.

As you start using the learning the debugging process, take time to look at the context menu (right-click on line) for advanced breakpoints, look over the debug left panel (click on the beetle icon) and play around to see everything you can discover about your program’s internals.

A note about WATCH: you can put expressions in a watch like

loopCount + 1&_SEGGER_RTT

I haven’t tried anything fancy on the expressions front, so I can’t tell you exactly how far is possible but the use of expressions extends just looking at variables.

J-Link RTT

Segger’s Real-Time Terminal (RTT) facility is a great replacement for the typical “print” statements judicially (for most developers) placed through out your code. Rather than using your development board’s serial port, RTT print output is channeled through the J-Link probe and the USB/IP link to an application on your PC workstation. This happens quickly and with nearly no impact on your embedded application because the “printf()” only places the debugging output into a memory control block that is read by the J-Link in the background (for more on how RTT works, see the links section below).

The JLink RTT Viewer application provides the workstation side of the RTT/printf() feature. You can use the RTT Viewer to visualize output from your embedded application with or without an active debugging session. If you started RTT Viewer as shown below (Existing Session) and you exit VS Code (with an active debug session) you will loose connection. To reestablish the connection without a debug session, choose File, Connect (F2) and instead pick USB (serial number is only required if you have multiple J-Link probes attached), pick your target device, clock and Auto Detection for the RTT Control Block. If you were wondering, yes this even works from an application running inside Arduino IDE.

To add RTT to your embedded application code you only need to add the SEGGER_RTT.h header reference and then start dropping in SEGGER_RTT_printf() function calls where ever you need see something from your code in an ongoing basis.

  • Look at the example below for how to include of the SEGGER_RTT.h in your .INO or .CPP files. The Segger header is a pure C header and needs to be either modified (changes would be lost if you upgrade the Segger files later on) or wrapped when used in .ino or .cpp files. Otherwise you will experience linker errors for RTT functions like SEGGER_RTT_printf().
extern "C" {
#include <SEGGER_RTT.h>
}
  • Atmel Studio J-Link Tools and RTT do not appear to get along. I recommend using jlink.exe to upload your binaries. If you use Atmel Studio (AS) for binary upload, you will need to exit AS after uploading before starting RTT Viewer. Otherwise it will not show output, as it seems unable to locate the RTT Control Block in your device’s memory.
  • To make my installation of RTT reusable across projects, I structured the Segger supplied files into an Arduino library. This is the JLinkRTT folder in the referenced GitHub repository. All of the files in the src folder are unmodified from Segger, but the folders organization is flattened.

Resources/Links

Microsoft Visual Studio Code

Segger J-Link Software and Documentation Pack

Segger J-Link RTT Overview

Video of a Development Session

GitHub Repository of Example and Supporting Files

Summary

I hope you take a look at Visual Studio Code and the VS Code Arduino extension to see if it will work for you and help you write better Arduino code faster. If you have been on the fence about putting up the funds for a Segger J-Link maybe these examples will help you better determine the value it would have to you. Finally, I hope that you learned something you can apply to your development workflow and you continue to enjoy building cool embedded applications.

Note: As of this writing, Adafruit is temporarily closed due to the Covid-19 emergency in New York City. You can still purchase Adafruit and Segger J-Link products mentioned in this article from Digi-Key and Mouser.

--

--

Greg Terrell

Founder and CEO of LooUQ, LooUQ creates communications hardware and software to enable rapid, robust and secure embedded applications… www.LooUQ.com