Configuring VSCode for AVR Programming on Linux

Another unpleasant moment lies in the fact that it is necessary for one controller to register different names in the parameters of different programs. For the compiler, this is the keyMCU = atmega328p and macros-D__AVR_ATmega328P__, and for the programmer DUDE_MCU = m328p

Almost all. You need to give the command in the VSCode terminal make and after a couple of seconds get in the directory build file habr_2.hex… Now you can connect your Adruino and give the command make prog… The letters will run and in a couple of seconds you will see something like this

… Reading | ################################################# | 100% 0.03s

avrdude: verifying … avrdude: 186 bytes of flash verified

avrdude: safemode: Fuses OK (E: 00, H: 00, L: 00)

avrdude done. Thank you.

Restart and your LED blinks.

Congratulations !!!

You can write tasks.json to run the build and firmware of the controller, but now it’s too lazy.

Second action

I thought about it and finish the article and publish, but the feeling of beauty did not allow. I remembered my AVR Dragon, because in more serious stones there is normal debugging and it is needed for normal tasks, which means that the topic will not be sufficiently disclosed without debugging. The ATMega16A which already has a JTAG turned out to be in stock – so you need to connect it. Collected just such nonsense.

An SMD LED with a resistor is soldered on the green wire.

It is necessary to install the packages on the system avarice and avr-gdb… The first communicates with the processor through the programmer and gives the gdb interface on the network port, and the second is the debugger program itself, through which VSCode is connected to the debugging interface. If something is wrong, please correct it in the comments.

The test program is the same as in the first case, only into the function func () added variable t, just to see how it changes in debugging, otherwise it turned out uninteresting, and the result is here.

void func(void)
	volatile uint8_t t = 0;

	for (;;)
		t = t ? 0 : 1;
		PORTB = 1 << PB5;
		PORTB &= ~(1 << PB5);

Due to the fact that the processor and programmer are different – there are minor changes in the Makefile.

MCU = atmega16a
DUDE_MCU = m16
	rm -f $(BUILD_DIR)/*

prog: $(TARGET).hex
	avrdude -p $(DUDE_MCU) -c dragon_jtag -U flash:w:$(BUILD_DIR)/$(TARGET).hex

	avrdude -p $(DUDE_MCU) -c dragon_jtag -U eeprom:r:eeprom.hex:i

write_eeprom: eeprom.hex
	avrdude -p $(DUDE_MCU) -c dragon_jtag -U eeprom:w:eeprom.hex

The value for the key has changed in the programming sections -c, I have – dragon_jtag, you have something of your own. To find out how you designate your programmer and processor, just call avarice, avrdude and the rest with a key –help and spend a little time reading.

To catalog .vscode add tasks.json and launch.json with the following content


	"version": "2.0.0",
	"tasks": [
			"label": "Build",
			"type": "shell",
			"group": "build",
			"command": "make",
			"problemMatcher": []
			"label": "Clean",
			"type": "shell",
			"group": "build",
			"command": "make",
			"args": ["clean"],
			"problemMatcher": []
			"label": "Write firmware",
			"type": "shell",
			"group": "build",
			"command": "make",
			"args": ["prog"],
			"problemMatcher": []
			"label": "Debug server",
			"presentation": {
				"echo": true,
				"reveal": "always",
				"panel": "shared",
				"focus": true,
				"showReuseMessage": false,
				"clear": false
			"isBackground": true,
			"command": "avarice",
			"args": [

Tasks Build, Clean and Write firmware of no interest – it’s just calling the appropriate targets in the Makefile. Here Debug server worth considering. Program avarice does not have a daemon mode, at startup it waits for the client to connect and exits after it is disconnected. Therefore, it must be run every time before starting debugging via preLaunchTask, but since VSCode is waiting for the task completion signal, it is necessary to send the task to the background. The parameter is responsible for this. “isBackground”: true… V command registered by itself avarice, its launch parameters are quite simple: –dragon – my programmer; -R – using a hard reset (and without it it works, but let it be); atmega16 – our processor; /build/habr_2_2.elf – compilation output file (see Makefile); : 3333 – the network port on which the client is expected (I decided to specify the usual port used by openocd). Parameter group presentation is only needed to configure the behavior of the terminal and does not affect the operation, you do not need to write it at all. You rewrite everything with your hands in order to better remember and feel, but copy-paste stupidly ?!


	"version": "0.2.0",
	"configurations": [
			"name": "Debug",
			"type": "cppdbg",
			"request": "launch",
			"program": "${workspaceFolder}/build/habr_2_2.elf",
			"MIMode": "gdb",
			"miDebuggerPath": "/usr/bin/avr-gdb",
			"miDebuggerServerAddress": "localhost:3333",
			"stopAtEntry": true,
			"cwd": "${workspaceFolder}",
			"environment": [],
			"externalConsole": false,
			"preLaunchTask": "Debug server"

By and large, everything is obvious, but still I will clarify a couple of places: program – our assembled .elf (you will have your own name); miDebuggerPath – full path to the debugger program (if you configure on Windows, take this into account); miDebuggerServerAddress – address and network port on which it is waiting for you avarice; stopAtEntry – will stop at the entrance to main () (this is to taste); “preLaunchTask”: “Debug server” – preliminary launch of the task of starting the debug server.

What failed to configure – automatic firmware before starting debugging. On Debian bullseye, avarice is built without programming support and recommends avrdude itself. I tried to create a group task, tried to make the launch of avarice in the Makefile, etc., the result is the same – the firmware is running, then the launch of avarice and everything will get stuck, you can wait an hour – no sense. Therefore, the processor has to be separately flashed and then debugged.

Everything is ready, set it on fire!

First, let’s put together a project. To do this, we will call the build task, you can use your hands in the terminal to give make, you can press Ctrl + Shift + B and select from the list Build… This combination does not work for me (there is even an official warning about this behavior) and I had to change it to Alt + Shift + B… If everything went without errors, then we flash the controller by running the task Write firmware or by command make prog… The LED should blink cheerfully.

Now let’s put a breakpoint on the line t = t ? 0 : 1;, I think you can figure out how – not small. Go to “Run and Debug” and press “Start debugging”

We start debugging and after a few seconds in the main window we see this

The program stopped at the first line of the function main () (remember “stopAtEntry”: true in launch.json) and in the terminal something like this

Waiting for connection on port 3333.

Connection opened by host, port 45102.

Push “Proceed” or F5to start the program and after a couple of seconds the window changes to this

press again *”Proceed”* and the window becomes like this

With further starts, the LED blinks once, everything stops, and the variable t changes its value between zero and one.

That’s all for me !!! I wish you creative success !!!

PS I know about simavr, which allows you to run a simulation with debugging for stones that do not have debugging tools, but I think that if you are able to describe the input and output signals for it, then you did not read this article after the first paragraph – it is not for you …

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *