last update: July 10 2010
Italian version

PIC debugger guide

Guide about using pdb, an in-circuit debugger for PIC16 microcontrollers

Debug monitor
Using pdb


While programming microcontrollers it's not rare that a circuit behaves unexpectedly, often in relation to external stimuli. Using all the simulation techniques it's possible to solve 99% of the problems, but there are cases in which only a debugger can reveal what is really going on.
PIC microcontrollers come with a debugging system called in-circuit, because the device is physically connected to the circuit while allowing the user to stop program execution, step it, and modify all internal variables and peripherals.
The advantage with respect to classic emulators is certainly the cost, because it uses the device itself instead of a stand alone circuit; the disadvantage is that it needs two IO lines and some memory which remain reserved.
The development of this debugger is based on the official Microchip documentation, in particular the application note "On chip debugger specification" (DS51242A) relating to PIC16Fxxx.
I suppose that the reader already built the Open Programmer, which is used for communications between PC and PIC.

Debug monitor

The in-circuit debugger works in the following way:
if the device is configured with debugging option, a break event, which can be a falling edge on RB6 or a hardware breakpoint, stops the normal program execution and forces a jump to address 0x2004; here a "goto ICD" instruction transfers control to a routine called debugger monitor, which will communicate with the host (the user PC) and execute what is specified.
A number of special registers, only available in this mode, allow to control the peripherals, set breakpoints, execute the program in single step.
The debugger monitor is a function similar to a classic interrupt handler; it has to save status registers so it needs some memory locations; obviously it has to be as small as possible.
Presently it needs 8 bytes of memory (from 0x6B to 0x72) and less than 180 program memory locations.
In order to use the debugger it's necessary to include the debug monitor code in the program to be written; this can be done in two ways:
1) copy the debug source in the main code, moving it at the end of program memory; to do so it's sufficient to write the directive ORG 0x1F00 (i.e. 256 bytes from end, when memory size is 0x2000); debug variables go to 0x6B-0x72, specified with cblock 0x6B ... endc.
2) use the precompiled module debugger_mon.o and a corresponding linker script; add the module to the "Object files" in the MPLAB project; the standard linker script is in the MPASM/lkr directory, and it has to be modified to add a code page for the debugger monitor:
CODEPAGE   NAME=pageICD  START=0x1F00   END=0x1FFF	//! shorten the previous page
SECTION    NAME=ICD      ROM=pageICD     // ICD routine
If the program memory is filled it's possible to shorten the ICD page to a minimum of about 164 locations.
To prevent reuse of debug variables it would be better to shorten available memory pages:
DATABANK   NAME=gpr0     START=0x20     END=0x6A
SHAREBANK  NAME=gprnobnk START=0x73     END=0x7F
... and so on for the remaining SHAREBANKs.
Using other development environments the steps are conceptually the same.
In addition to including debugger code it's necessary to:
enable the debug flag in the config options (will be written to config word);
enable write of ICD debugger jump from the programming options (and use the right ICD routine address); it's not necessary to write it every time, since the normal erase doesn't change it (it's located at 0x2004);
insert a NOP instruction at address 0.
The whole procedure is followed in the example I compiled.
After writing the .hex file connect programmer and circuit via ICSP (GND,VDDU,VPPU,SCK,SDA); VDD is only necessary for in-circuit programming, but is usually disconnected during debug sessions to avoid overloading the programmer.
The communication is synchronous with clock (RB6) and designed to use programmer commands TX16 and RX16; so 16 bits are trasferred each time.
Following is the list of instructions (for reference):
Command Value Parameters Answer Description
NOP 0x00 0x00 no doesn't do anything
VER 0x01 x 2B debugger version
STEP 0x02 x no execute a single step
GO 0x03 x no continue execution
RREG 0x04 N,A(2B) N*2B read N registers starting from A; the first byte of every
word indicates the number of remaining words
WREG 0x05 D,A(2B) no write D to register A

Using pdb

pdb is the debugger application that runs on the user PC; it has been developed taking as a reference the well known gdb (which is orders of magnitude more complicated); it is a command line program (for both linux and windows) that can interact with a target PIC mounted in the application circuit.
An Open Programmer is required for communication, see here for considerations about how to connect it.
An example of debug session can explain how to use pdb:
pdb, an In Circuit Debugger for PIC microcontrollers
When the program starts it raises VDD while MCLR is low and the target under reset; it's not necessary to connect VDD to the circuit if it has a separate power supply.
Command run raises MCLR to High so the target executes the first instruction (nop); a hardware break starts the debugger monitor which signals its presence through RB7; pdb then starts reading informations about execution and reports it:
execution stopped
Next instruction: goto  4 (0x2804)      PC=0x0001
STATUS=0x1F (            TO PD Z DC C)
W=0x28 PCLATH=0x00 FSR=0xBF
Now it's possible to examine all registers, and modifiy their value if necessary:
>print PORTB
0x006: PORTB = 0x36
>set PORTB 2
0x02 written to PORTB
Standard register names are included, but it's possible to define others corresponding to code variables:
>define var1 25
var1 (0x25) added to variable list
>print var1
0x025: var1 = 0x3F
Command line option -load loads a variable definition file at startup time; the definitions are written in the form:
variable address
One of the most used functions is the step by step execution:
Next instruction: bcf   STATUS,5 (0x1283)       PC=0x0010
STATUS=0x1A (            TO PD   DC  )
W=0xF1 PCLATH=0x00 FSR=0xBF
It could be interesting to automatically report the value of some registers at every step:
>display PORTC
variable PORTC (0x7) added to display list
0x007: PORTC = 0x00
Next instruction: bcf   STATUS,6 (0x1303)       PC=0x0011
STATUS=0x1A (            TO PD   DC  )
W=0xF1 PCLATH=0x00 FSR=0xBF
To contine execution:
Now the target is running freely but it can be stopped at any time:
0x007: PORTC = 0xFF
Next instruction: nop    (0x0000)       PC=0x0640
STATUS=0x1A (            TO PD   DC  )
Sometimes it is necessary to stop at a specific address; what is needed is a breakpoint:
>break 18
break at address 0x18
execution stopped
0x007: PORTC = 0xFF
Next instruction: xorlw 3 (0x3A03)      PC=0x0019
STATUS=0x1A (            TO PD   DC  )
W=0x4A PCLATH=0x00 FSR=0xBF
These were the basic commands; following is the complete list:
help                command help
break <addr>        set breakpoint at address <addr>
c[ontinue]          continue execution after halt
define <var> <a>    define variable named <var> at address <addr>
define rm <a>       remove definition for variable at address <addr>
define rm <var>     remove definition for variable named <var>
define rm all       remove all variable definitions
define list         list variables
display             if halted read variables in display list
display <var>       add variable named <var> to display list
display rm <a>      remove variable at address <addr> from the display list
display rm <var>    remove variable named <var> from the display list
display rm all      remove all variables from the display list
display list        list variables in display list
display {on off}    turn on-off auto display
freeze [on,off]     freeze peripherals
h[alt]              halt execution
list                display program code in the vicinity of current instruction
n[ext]              step over calls
print <addr>        print variable at address <addr>
print <var>         print variable <var>
print bank <b>      print registers in bank <b>
print p <addr>      print program memory at address <addr>
print ee <addr>     print eeprom memory at address <addr>
print ee            print all eeprom memory
q[uit]              quit program
r[un]               remove MCLR and run
set <a> <d>         write <d> at address <a>
set <var> <d>       write <d> to variable <var>
s[tep] [n]          single step [n times]
version             read version
Commands may change in the future and more may be added following users requests.
For sure I will add support for .cof files, in order to use symbol names present in the source files.
Although pdb is a command-line program, it doesn't mean that it can't be used with graphical environments (as happens with gdb); unfortunately MPLAB has an unknown proprietary protocol, revealed only to commercial developers, so it cannot be paired with pdb; another environment is Piklab, which I hope will be adapted to pdb soon.
Regarding other PIC families (PIC18, PIC24, dsPIC) the problem is the same: up to now Microchip has not released any information regarding their debug system, but may do it in the future if it will receive enough requests.


debugger monitor with example
pdb executable
pdb sources


Open Programmer


For informations or comments:
Alberto Maccioni  email.png
You can also post your questions on the forum on sourceforge.


Main page