본문 바로가기

Embedded/dsPIC

dsPIC Programming Notes - Debugging Startup Code


Microchip dsPIC Programming Notes

Microchip has provided very good documentation and tutorial on their website, you can get yourself started with the resources. Here I'd like to present a few notes from my experience of programming with MPLAB IDE and dsPIC30F family chips to save your time.


1. Startup Function

 

If you use the Project Wizard in MPLAB to create a project, you need to supply a main function in order for the project to build successfully. The wizard uses the startup function from library file, e.g., libp30F6010A-coff.a. After the initialization of the stack pointer register, the stack size register, PSV and data, the startup code calls function main() in which your application code starts.

 

If you want to have your own startup code, it is strongly recommended to use the supplied source crt0.s and crt1.s as a baseline, and give a name other than the default names (crt0.s, crt1.s) to your startup code, and make sure the following three global variables of function type exist in your startup code:

 

    __reset

    __resetPRI

    __resetALT
 

In case any of the above symbols is missing from your startup file, the linker will supplement it from the default library.

 

2. Build Options

 

Build option settings shall be set up at the beginning of project development. It is easier for one to understand if the project files are organized in logical hierachy. Suppose I have the following project folder structure. All header files and includes files are stored in Header subfolder, all source files are stored in Source subfolder (you may create modular subfolders under this folder), all the generated intermediary files are stored in Obj subfolder, and all output files are stored in Output subfolder.

 

 

Figure 2-1 Project file structure

 

First, add all source files to Source Files folder and all include files to Header Files folder in the project view.

 

 Figure 2-2 Project view

 

Then, click menu Project->Build Options->Project, set up Directories tab to set up directories and search path. The relative path is dependent on the "Build Directory Policy" selection at the lower part of the tab. If the first option is checked, the path is relative to source-file directory except link output; if the second option is checked, the path is relative to the project directory. The following examples are based on the first option being checked.

 

Because Output Directory path is where builds (.hex, .map, etc) are stored, and relative to project root, so set it as .\Output.

 

 Figure 2-3 Build Options - Output Directory

 

Intermediary Directory is where obj files are stored, and relative to source file directory as the first Build Directory Policy

"Assemble/Compile in source-file directory, link in outout directory"

is checked, so set it as ..\Obj.

 

 Figure 2-4 Build Options - Intermedary Directory

 

Assembler include search path is where all include files are stored, and relative to source file directory as the first Build Directory Policy

"Assemble/Compile in source-file directory, link in outout directory"

is checked, so set it as ..\Header.

 

 Figure 2-5 Build Options - Assembly Include Search Path

 

Similarly, Include search path is where all C header files are stored, and relative to C source directory as the first Build Directory Policy

"Assemble/Compile in source-file directory, link in outout directory"

is checked, so set it as ..\Header.

 

 Figure 2-6 Build Options - Include Search Path

 

Depening on build needs, set up other build tabs. The following pictures show Link30 tab General category and Diagnostics category settings.

 

  

Figure 2-7 Build Options - Linker General                                                           Figure 2-8 Build Options - Linker Diagnostics


3. Interrupt Service Routine Names

 

All interrupt service routine names are defined in the vector table in the linkder file.


For example: CAN interrupt service


In p30f4012.gld file, there is definition


LONG(DEFINED(__C1Interrupt) ? ABSOLUTE(__C1Interrupt0)


so in source code, you must use the same ISR name


void __attribute__((__interrupt__)) _C1Interrupt(void){}


4. Program/Debug Connection Port

 

Since program pins and CAN bus pins are multiplexed, if your application uses CAN bus, you must use alternate pair of debug pins (EMUC1/EMUD1, EMUC2/EMUD2) for debugging purpose. The alternate pair of debug pins must be connected to CLK and DATA on RJ11. Besides, there must be a switch or jumper to connect the program pair PGC and PGD to CLK and DATA when programming, and disconnect them when debugging. An example of connection circuitry is shown below.

Figure 4-1 ICSP Interface Example


5. On-Chip EEPROM


To preserve EEPROM contents while programming, make sure to check “Preserve EEPROM on Program” on Program tab in the Settings window. Ignore the warning message that appears after checking it. The warning message is just the opposite, it seems wrong.

 

6. System clock

 

System clock Fosc is derived from one of the four clock sources, primary, second, FRC and LP 32kHz, and then boosted up by a PLL with gain set to 1, 4, 8 or 16, and divided by programmable divider with post-scaler factor 1, 4, 16 or 64. If FRC (7.3728 MHZ) is used with PLL 16x, which results in 117.9648 MHz Fosc. Instruction cycle clock Fcy is derived from Fosc divided by 4, i.e., Fcy = Fosc / 4 = 117.9648/4 = 29.4912 MHz. This frequency is good to generate precise UART baud rate like 115200, however, it causes discrepancy for CAN baud rate and results unreliable communication. To ensure CAN baud rate precisely, an integer value of crystal such as 8, 10, 16 MHz should be used. Alternatively, 7.5 MHz clock oscillator can be used, which renders 30 MHz system clock and reliable CAN communication.

 

Please also note that even though external clock input could be up to 40 MHz for dsPIC30F family, which does not mean that the CPU could run at 40 MIPS with that clock input. It is just an option for input frequency so that various frequencies could be generated with proper PLL and division scales. 30 MIPS is the maximum speed of dsPIC30F family within certain voltage and temperature range.

 

7. Reset to main() Entry or Zero Address

 

When debugging your target, you may want to reset the processor to main() function directly, or sometimes, you may want to reset it to the address 0x00000 of the program memory. Please note that the option is on the Debugger tab in the Settings dialog opened from Configure menu.

 

 Figure 7-1 Debugger Settings

 

If the option "Reset device to the beginning of main function" is checked, resetting the processor will break at the main() function; if the option is unchecked, resetting the processor will break at the address 0x00000 of the program memory, where a jump to _reset instruction will be executed.

 

If the program counter cursor does not go to the appropriate place, you need to make sure that the program code in the memory is current. You make re-build and re-program it.

 

8. How SFR Variables Work in C Code

 

In C programming, one would like to use data structure to get/set values, as mentioned in Register Structure Definition Header File. In a MPLAB project, you may also do so as shown below.

 

    T1CONbits.TON = 1;           //enable timer 1

 

but how does it work? We need to look at two files, the register header definition file and the linker script file. Let's take dsPICF6010A as an example. The register header file is named

 

    p30f6010A.h

 

and the linker script file is named

 

    p30f6010A.gld

 

In p30f6010A file, you can find the data structure of timer 1 control register T1CON

 

typedef struct tagTCON_16BIT {
        unsigned        :1;
        unsigned TCS    :1;
        unsigned TSYNC  :1;
        unsigned        :1;
        unsigned TCKPS  :2;
        unsigned TGATE  :1;
        unsigned        :6;
        unsigned TSIDL  :1;
        unsigned        :1;
        unsigned TON    :1;
} TCON_16BIT;

 

and the declaration of T1CONbits variable

 

extern volatile TCON_16BIT T1CONbits __attribute__((__sfr__));

 

in which, __attribute__((__sfr__)) specifies that the data variable T1CONbits is an SFR. With the data structure definition and the data variable declaration, you can get/set a register in bit level. But how does the data variable T1CONbits relate to its memory location of the CPU? That is done by

 

    T1CONbits = 0x0104;

 

which is defined in the linker script file p30f6010A.gld. You may check your project map file to verify that the data variable is assigned at the right address.

 

    0x0104                  T1CONbits = 0x104

 

 

9. How to debug Startup Code in Source Level

 

In MPLAB IDE v8.10 or older version (don't know what the future version would be), if you try to step through the startup code, for example, crt0.s, the execution line goes to instructions in the Program Memory window, it does not highlight source code, as shown in the figure below.

 

Figure 9-1 Debug Assembly Source Code in Program Memory Window

 

That behavior makes inconvenience to debug the assembly source code. There is a workaround to this issue. Notice that the startup code was declared in section .init, see line 31 in the Figure 9-1 above.

 

      .section .init, code

 

To be able to debug it in assembly source code, change the Section directive to .text temporarily.

 

      .section .text

 

Then, rebuild and program your application, you may step through your assembly source code, see the example in Figure 9-2 below. That should help to debug your own code in assembly easily.

 

Figure 9-2 Debug Assembly Source Code in Source Window

 

However, after the debugging is done, you shall change the Section directive back to the original, which is section .init, because the init section code should be mapped to the beginning of "program" section (usually at 0x100) right after the alternative IVT by the linker file.