Introduction
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
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:
>run
running
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:
>step
step
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
>step
step
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:
>continue
running
/
Now the target is running freely but it can be stopped at any time:
>halt
halted
0x007: PORTC = 0xFF
Next instruction: nop (0x0000) PC=0x0640
STATUS=0x1A ( TO PD DC )
W=0xFF PCLATH=0x00 FSR=0xBF
>
Sometimes it is necessary to stop at a specific address; what is needed is a breakpoint:
>break 18
break at address 0x18
>continue
running
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.
Download
debugger monitor with example
pdb executable
pdb sources
Links
Open Programmer
Microchip.com
DevC++
GNU/GPL
Contacts
For informations or comments:
Alberto Maccioni
You can also post your questions on the
forum on sourceforge.