[TUT] Debugging GCC/ASM Language Programs with AVR Studio

Go To Last Post
2 posts / 0 new
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Using AVR Studio to debug gcc / Assembly Language programs

Over the last couple of months, I've been using AVR Studio to develop a program that creates interesting patterns on a standard TV using an AVR microcontroller (various flavors "“ ranging from ATtiny2313 up to 90USB646). While that's been very fun and the AVR Studio has proved to be a great tool for development and debug, I decided I'd like to combine some gcc code with the interrupt service routines that handled the NTSC timing generation. Read more about that project here:http://fit.c2.com/wiki.cgi?TinyTv

The plan was to first convert my files to compile in gas, the gcc assembler. This proved to be a little less obvious than I thought it would be. In fact, I asked a general question about how to do this and got little reply (see https://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=83089 ). So I decided I should learn how to do this and jumped in using AVR Studio in gcc mode. This tutorial describes what I learned about converting my project, but more importantly, it describes how to use the AVR Studio and WinAVR/gcc tools together to debug and develop mixed C/assembly language projects. This tutorial assumes you have AVR Studio and WinAVR installed and have some experience using them. It is not for the newbie!

So what if you don't care about assembly language programming? This tutorial will still be useful as an introduction to using the AVR Studio for C program development using gcc. The ability to run a program in simulation and see what happens to the many control registers is very powerful. This tutorial will tell you how to do that even if you aren't interested in assembly language.

Using AVR Studio to set up a gcc project
Create your gcc project using the Project Wizard "“ just select AVR GCC. Create your gcc file in the usual manner. The editor is a little different from the Programmer's Notepad in WinAVR, but it's easy to get used to.

Since I only wanted the C file to support debugging, all that main had to do was call my starting routine in the assembly language file. Here's the main function in my C file.

// All main does is call the initialization routine.
int main(){

 my_RESET();		//Just want to call this and nothing else


my_RESET is my assembly language initialization routine. I never return from it, so I never come back to main. It just kicks things off. Here's the declaration of the routine in the C file "“ the actual routine is assembly language.

// This is in asm code. Its the reset vector.
void my_RESET(void);	

Create your assembly language files under "Source Files" in the file tree and give them file type ".S". Yes, that's a capital "s". This cues gcc to treat the file as an assembly language file. No special options need to be set. Here's how to declare my_RESET in assembler so the linker can link it:

.global my_RESET

You should be able to compile your program in the AVR Studio window.

Getting more help
Choose "avr-libc Reference Manual" from the AVR Studio Help menu, then choose User Manual from the menu across top of the resulting page. Then pick "avr-libc and assembler programs" from the list. There's lots of good info here. The Inline Assembler Cookbook is of very limited use, IMHO. I find this method to be very clunky.

Use of gcc with AVR Studio is discussed in Help / AVR Studio User Guide. You'll find the gcc section under the "Project" book. The entry under Help / AVR GCC Plug-in Help is much less useful (and may be confusing).

Help! My .S file disappeared!
So you carefully created a new assembly language file by starting with the New Source File in the source file tree. After typing it in you saved it. Then you closed AVR Studio and went on with the rest of your life. When you came back to work on your project the next day, your assembly language file did not appear in the file tree. On no, it disappeared!

To quote the Hitcher's Guide to the Galaxy, Don't Panic when this happens. Your file is still there (use Windows Explorer to verify this if you like), it just doesn't show up in your project's file tree after you close and reopen your project. This can be disconcerting at first, but is merely annoying after a while. To make it re-appear, just right click on Source Files and select Add Existing Source File(s)... Then select your .S file(s) from the window. Double click them or click Open and they'll be added to the Source Files until you close the project again. You can now access them in the usual manner.

Using AVR Studio for debugging a gcc project
OK, now you've compiled your project and loaded it into the AVR Studio Simulator. You're ready to debug! The good news is that AVR Studio uses the gcc tools and tool chain from WinAVR and gives symbolic, source line debug capability. Bad news is this only applies to the C files themselves! The assembly language files and symbols (.S file type "“ note need for capital s) are not recognized in the simulator.

So I thought I was completely stuck at first. What I really needed to debug was the interrupt service routines that handled all the timing for NTSC signal generation. I didn't realize it in the beginning, but I also needed to be able to see what gcc/gas was doing to my addresses as I tried to manipulate them in assembly language.

So after being stuck for a while and verifying that, indeed, the AVR Studio wouldn't allow break points in assembly code, I had the idea to just put tight loops into the code to cause the simulator to hang at a given point. Then when I stopped the simulation (manual break point), I could look at the registers and data memory to see if the results were what I expected. I was able to debug my ISRs this way and it's a pretty effective technique. Using the register, io and memory views showed what was going on. The register and memory views can be opened using the icons in the upper right corner of the AVR Studio window. The io view is open by default; just click the registers you're interested in to see more details. The useful things I learned by doing this are discussed below in the Tips and Hints section. For now, let's focus on debugging with AVR Studio.

The limitations showed up when I wanted to debug the mainline assembly code. I needed to be able to break and resume easily. So I created a simple function in the C code that I could call from the assembly code. This allowed me to set a break point, and resume when it was hit. Here's how to do that.

Define a void function in the C file. All it needs is a return statement.

void myTest(void)

In the assembler (.S) file:

.extern  myTest		; in the C file

Call the C routine from assembler like this:

call	myTest	; Call to C function for debug

You can now set a break point on the C function in the usual manner. Sounds good, but read on to know just how useful this is.

One big discovery (and now my best friend) was the disassembler panel. Open this panel using the icon in the upper right corner. Not only does this panel show you disassembled code (both assembler and C code), it also shows the instruction pointer. So put the call to the C routine (which does nothing but return) in your assembly code just before the section you want to debug. Set a break point on the C routine. When it breaks, open the disassembler view (if it's not already open). You'll see the instruction pointer in the C routine (just a return statement). Now single step and, Presto! You are now single stepping through your assembly code. Admittedly, this isn't as elegant as what you can do in an assembly project using AVR Studio, but it'll get the job done in most cases I can think of.

You can also put the cursor on a statement in the disassembler window (by clicking), then right click and select "Set next Statement" from the drop-down menu. Then click "Step Into" and the simulator will begin executing at the statement where just put the instruction pointer. This provides a way to continue executing on the other side of a "hang" loop.

General tips and hints
Remember you can do anything you want in simulation "“ there's nothing to break and it doesn't have to keep going. So it's easy to put in hang loops, stuff registers, manipulate the stack, etc. all you want. If you use software loops for timing, then put in small values during simulation. Simulation counts cycles, but it doesn't run in real time. And be sure to come back to the AVR Freaks forums for help and to help others.

Converting asm programs to gcc/gas
Following are things I've learned which are probably worth sharing since others may find them helpful.

Reading the map file
The map file (.map) is in the "default" sub-directory in the project directory. I like to use wordpad to look at it so I can have it open during debugging. Notice that the addresses shown in the map file are byte addresses. Since the program memory window and the disassembler windows use word addresses, you need to divide the addresses in the map file by 2. An easy way to do this follows.
Here's the byte address in hexidecimal: 0xD6.
Write it in binary as 1101 0110.
Drop the LSB and add a 0 as MSB: 0110 1011.
Convert back to hex: 0x6B.
That's the word address. See Figures XX to XX.
If you want a label used in your assembler file to appear in the map file, declare it global in your assembler file like this:

     .global my_RESET

Note that memory will appear differently in the memory and the disassembler panels. The memory panel will present a word of data MSB then LSB; this is reversed in the disassembler window. For example, the instruction jmp 0x0069 will appear in the disassembler panel as 940C 0069. At the same address in the program memory window you'll find 0C94 6900. Confusing until you get used to it. I usually ignore the hex values shown in the disassembler window and look at the memory window.

Looking at the highest addresses in the memory panel will show what's on the stack. Data memory is addressed on a byte basis and is presented in that manner. No need to manipulate addresses. However, the values for return addresses pushed on the stack are multiplied by two and you'll have to manipulate them as above to find the real addresses being pointed to.

Address Manipulation
The confusion of byte vs. word addressing is a source of difficulty in assembly language programming. Since I like to manipulate addresses by using them as pointers into tables of addresses, this is a big deal for me and one of the main difficulties I had to overcome. The gcc assembler (gas) provides some pseudo ops to deal with this problem, but you have to know how to use them. What you have to do in gcc/gas is not the same as what is done in the AVR Studio assembler. I got some very valuable help on this here, but I still had to figure out exactly when to use which pseudo op.

Example 1. Creating a table containing addresses.
In AVR Studio, nothing special is needed. Simply say

.word		vIntrvl

where vIntrvl is the label for a routine, and you get the correct address of vIntrvl in your table. But if you do this in gcc/gas, then you will get the byte address. If you load this value into registers, push it onto the stack, and do a return, you'll end up in never-never land "“ or worse! So use the pm pseudo op like this and you'll get the address you expect.

.word		pm(vIntrvl)	

Example 2: Loading the pointer to the table of addresses.
In AVR Studio, here's how to load a pointer into the Z register pair:

		ldi	ZH, high(TABL <<  1)
		ldi	ZL,low(TABL <<  1)

Here's how to do the same thing in gcc/gas:

		ldi	ZH, hi8(TABL)
		ldi	ZL, lo8(TABL)

Using the C preprocessor to define names
Change all lines that look like

	.def	VI_FLAGS	=	r9

to look like

	#define	VI_FLAGS	r9

If you have any in or out statements, they need to change from this

	out	DDR_LED,Temp	; Make LED port an output

to this

	out	_SFR_IO_ADDR(DDR_LED),Temp		; Make LED port an output

Same for cbi and sbi statements.

#IFDEF, #ELSEIF, etc. may not work. These gave me trouble and I haven't sorted this out yet. Maybe someone else can help here.

Declaring interrupt service routines (ISRs).
Do this as described in the avr-libc help file. Building the interrupt jump table will be taken care of by gcc. The only thing you have to do is realize the ISR names will be modified by gcc. Using the disassembler window will allow you to verify your routines are correct.

Rapid Debug on Hardware
Once your simulation is complete, you'll want to test your software on some actual hardware. This usually means dragging out your programmer, plugging in your AVR chip of choice, using avr-dude to program it, unplugging your chip, plugging it into your breadboard, powering it up, and checking out the response. Repeat the cycle each time you find a bug or need to test another iteration.

While I can't help you with the response testing, I have found a way to greatly reduce the plugging, programming, and unplugging business. Using either Teensy or Teensy++ products featured here: http://www.pjrc.com/ along with the free software yields a truly sweet hardware environment. Once your code is compiled and linked in AVR Studio, or simply in WinAVR (the gcc tools are the same, remember?), all you have to do is drag and drop the .hex file onto the Teensy icon window (which appears after you load the software), click download and run. The Teensy is connected using a USB port and has it's own bootloader. It uses power from the USB or can be run stand-alone. The bootloader is tiny, so almost all code and resources are available for your own programs. If you need to change the code, then just edit, compile and drag and drop the new hex file. You'll never want to use avr-dude again (although it's a great tool "“ don't get me wrong). Your design "“ build "“ test "“ debug cycle time will be cut in one quarter at least.

Complete Disclosure: I have no commercial interest in pjrc. While the owner (Paul) is a friend and colleague through Portland Dorkbots http://dorkbotpdx.org/, this recommendation is completely unsolicited.

That's it! You should be able to do three useful things now.
You've learned to use the WinAVR/gcc tool chain in AVR Studio to develop and debug C programs. This is the basic part, so I didn't spend a lot of time on it specifically. Using AVR Studio you can see registers, track variables, etc. See the help files mentioned above for more details.
You've seen how to develop and debug mixed gcc/assembly language programs using AVR Studio. This is primarily what this tutorial is all about. I've shown how to use the tools, how to interpret addresses, and how to work around a few limitations.
One more thing you may not have realized is that you now know how to create combined gcc/assembly language programs using the WinAVR tools without AVR Studio. This can be a big advantage since the AVR Studio is pretty large and resource hungry. WinAVR is much lighter weight. AVR Studio is still the tool of choice to get a program developed and debugged, but a working program can be distributed so that only WinAVR is required to build it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Addendum 1 - Setting Break Points in the Disassembler Panel

A further capability of the Disassembler Panel is that you can set break points in it. To do this, compile your code and immediately bring up the Disassembler Panel before starting simulation. Find the addresses where you want break points (use the .map file as described above) and set them in the usual manner, but do it in the Disassembler Panel (remember you can't set them in your .S source files). Now start the simulation and it'll break at the points you've set.

While this is pretty sweet there is one limitation. If you stop the simulation and recompile/reload, you'll find your break points still show up in the Disassembler Panel. Unfortunately, they're not at the correct addresses. You'll have to move them each time you start a new simulation. While this is really annoying, being able to set break points in your assembler code is still very powerful. Forget about the need to create special "hang" loops that I described above. This is a much better technique.