Software

Debugging 1: Break Points, Stepping, and Call Stack

This section will introduce the debugging capabilities of Microchip Studio, both as video (linked below) and hands-on document. The main topics are; breakpoints, basic code stepping using the Breakpoint and Callstack-Windows and adjusting project compiler optimization settings.

Getting Started Topics

Video: Microchip Studio Debugging-1

The same code as the one created in section Editor: Writing and Re-Factoring Code (Visual Assist) is used.

Todo: Place a breakpoint and inspect a list of all breakpoints in the project.
  1. 1.Set a breakpoint on the line getting the switch state, as indicated in Figure 1.
    Figure 1. Placing a Breakpoint
    Info: A breakpoint can be placed at a line of code by:
    • Clicking the gray bar on the left edge of the editor window
    • In the top menu bar, go to Debug → Toggle Breakpoint
    • By pressing F9 on the keyboard
  2. 2.Launch a debug session . The breakpoint will be hit when clicking the switch (SW0) on the Xplained Pro kit. Observe that execution halts when hitting the breakpoint, and the execution arrow indicates that the line of code will execute where the breakpoint is placed. See Figure 2.
    Figure 2. Execution Halting when a Breakpoint is Hit
    Tip: If a breakpoint is hit in a file that is not currently open, Microchip Studio will open the file in a temporary pane. A file containing a hit breakpoint in a debug session will always be in focus.
  3. 3.Since most of the logic of the program is handled only when an ISR is processed, it is now possible to check the logical flow of the program. If the switch is pressed and then released when the ISR is hit - what will be the state of the switch that the function returns? The assumption is that since pressing the switch triggered the interrupt, that switch will be set as pressed, and the LED will thus be turned ON.
    Code stepping can be used to check this assumption. The key buttons used for code stepping are illustrated in the table below, found in the top menu bar or the Debug menu. The corresponding functionality and keyboard shortcuts are outlined in the figure below.
    Figure 3. Microchip Studio Buttons for Code Stepping
    Table 1. Microchip Studio Button Functionality (Code Stepping)
    Button Functionality Keyboard Shortcut
    Step Into Function Call F11
    Step Over F10
    Step Out of Function Call Shift + F11
    Run to Cursor Ctrl + F10
    Issue System Reset  
    Todo: Find out what state is returned if the switch is pressed and released when hitting the ISR. Is our assumption correct that since pressing the switch triggered the interrupt, it will be set as pressed, and the LED will turn ON?
Use the Step Into Function Call first. Use the Step Out of Function Call to move to the following line after returning from the function to enter the SW_get_state( ) function. Pressing Step Over from the breakpoint would land us at this same point directly. Note that we could step further into the LED_set_state(SW_state) function to determine if the LED is turned ON or not. However, we could hover the mouse pointer over the SW_state variable to see that it is 0, i.e., the LED will turn OFF. Verify this by stepping further.
Figure 4. Checking Value of SW_state Using Mouse Hover
Info: The switch state is recorded when calling the SW_get_state( ) function, although the breakpoint is triggered by the falling edge when pressing the switch. Verify that SW_state will read 1 when pressing the switch when stepping over this line.
  1. 1.A window or view to keep track of the breakpoints in a program is needed. The Quick Launch bar performs a search of the Microchip Studio user interface menus, which is demonstrated below by comparing Figure 5 and Figure 6. Note that each of the hits in the Quick Launch bar is from 'break' related entries in the Debug menu.
    Figure 5. 'Break' Search in the Quick Launch Bar
    Figure 6. 'Break' Hits in Debug Menu

    Open the Breakpoints window by clicking on the top result (Debug → Windows → Breakpoints). The Breakpoints window lists all the breakpoints in the project, along with the current hit count, as depicted in Figure 7.

    Tip: Temporarily disable a breakpoint by unchecking the checkbox next to a breakpoint in the list.
    Tip: The Disassembly view can be conveniently displayed alongside the source code, as demonstrated in Figure 8.
    Figure 7. Breakpoints Window
    Figure 8. Disassembly View
ToDo: Examine the Call Stack and the effect on it when optimizations are disabled.
  1. 1.Following from the previous section, set a breakpoint on the LED_on() function. Then trigger the breakpoint so that it is hit.
  2. 2.Open the Call Stack window by typing 'Call' in the Quick Launch bar, selecting Debug → Windows → Call Stack, as shown in Figure 9.
    Note: A debug session needs to be active to open this window.
    Figure 9. Open the Call Stack Window
  3. 3.Expect the Call Stack showing LED_set_state() as the caller of LED_on(), as this is how the code is written. However, in the Call Stack window, _vector_4 is listed as the caller (see Figure 10) because of compiler optimization.
    Figure 10. Call Stack with Optimization
    Info: The call order is different because of the compiler optimization. This code is relatively simple to follow, and it is possible to understand what is going on even though the compiler was optimized and made subtle changes to what is expected. In a more complex project, it can sometimes be helpful to disable the compiler optimization to track down a bug.
    Note: To see why the Call Stack shows that it comes from _vector_4 initially, click on PORTB_PORT_vect and look in the context field for the definition, as shown in Figure 11.
    Figure 11. __vector_4 Is the PORTB ISR Vector
  4. 4.Stop debugging by clicking the Stop Debugging button or pressing Shift + F5.
  5. 5.Open the project settings by going to Project → <project_name> properties or pressing Alt + F7. Go to the Toolchain tab on the left menu, as in Figure 12.
  6. 6.Under AVR/GNU C Compiler → Optimization, set the Optimization Level to None (-O0) using the drop-down menu.
    Figure 12. Disabling Compiler Optimizations
    Warning: Disabling compiler optimization will result in increased memory consumption and can cause changes in execution timing, which can be important to consider when debugging time is a critical code.
  7. 7.Launch a new debug session and break code execution inside LED_on().
  8. 8.Observe the Call Stack. It should now adhere to how the code is written and list LED_set_state() as the caller of LED_on(), as shown in Figure 13.
    Figure 13. Call Stack Without Optimization
Tip: Microchip Studio will try to link the compiled code to the source code as best as possible, but the compiler optimization can make this challenging. Disabling compiler optimization can help if breakpoints seem to be ignored during debugging or if the execution flow is hard to follow during code stepping.
Result: The call stack has now been examined both with and without optimization enabled.

Code Used for Debugging 1

/*
 LED is turned on when switch is pressed, LED is turned on (via a pin change interrupt). 
 MY_mistake() written to demonstrate Attach to Target, is commented out, to avoid hanging 
 project unintentionally. 

 From the schematics, it is concluded that:
 The LED can be turned on by driving PB4 low.
 SW0 is connected directly to GND and to PB5 through a current limiting resistor.
 SW0 does not have an external pull-up resistor.
 SW0 will be read as '0' when pushed and as '1' when released, if the ATtiny817 internal pull-up is enabled.
*/

#include <avr/io.h>
#include <stdbool.h>
#include <avr/interrupt.h>

void LED_on();
void LED_off();
bool SW_get_state();
void LED_set_state(bool SW_state);

int main(void)
{
	PORTB.DIRSET = PIN4_bm;
	PORTB.OUTSET = PIN4_bm; 
 	PORTB.PIN5CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
	sei();
	
    while (1) 
    {    
    }
}

#pragma region LED_functions
void LED_on()
{
	PORTB.OUTCLR = PIN4_bm;  //LED on
}

void LED_off()
{
	PORTB.OUTSET = PIN4_bm;  //LED off
}

void LED_set_state(bool SW_state)
{
	if (SW_state)
	{
		LED_on();	
	}
	else
	{
		LED_off();
	}
}
#pragma endregion LED_functions

bool SW_get_state()
{
	return !(PORTB.IN & PIN5_bm);
}

/*
void My_mistake()
{
	while(1)
	{
		asm("nop");
	}
}
*/

ISR(PORTB_PORT_vect)
{
	uint8_t intflags = PORTB.INTFLAGS;
	PORTB.INTFLAGS = intflags;
	//My_mistake();
	
	bool SW_state = SW_get_state();
	
	LED_set_state(SW_state);
	
}