1
votes

I am trying to run and debug a C program on a dsPIC30f3011 microcontroller. When I run my code in MPLAB, the code always tends to stop at this ISR and I am stuck with absolutely no output for any variables, with my code not even executing. It seems to be some kind of "trap" program that I assume is for catching simple mistakes (i.e. oscillator failures, etc.) I am using MPLabIDE v8.5, with an MPLab ICD3 in debug mode. It's worth mentioning that MPLAB shows that I am connected to both the target(dsPIC) and the ICD3. Can someone please give me a reason as to why this problem is occurring?

Here is the ISR:

void _ISR __attribute__((no_auto_psv))_AddressError(void)
{

        INTCON1bits.ADDRERR = 0;
        while(1);
}

Here is my code with initializations first, then PID use, then the DSP functions, then the actual DSP header file where the syntax/algorithm is derived. There is also some sort of problem where I define DutyCycle.

///////////////////////////////Initializations/////////////////////////////////////////////

  #include "dsp.h"    //see bottom of program


    tPID SPR4535_PID;                   // Declare a PID Data Structure named, SPR4535_PID, initialize the PID object       
    /* The SPR4535_PID data structure contains a pointer to derived coefficients in X-space and */
    /* pointer to controller state (history) samples in Y-space. So declare variables for the */
    /* derived coefficients and the controller history samples */
    fractional abcCoefficient[3] __attribute__ ((space(xmemory)));          //  ABC Coefficients loaded from X memory
    fractional controlHistory[3] __attribute__ ((space(ymemory)));          //  Control History loaded from Y memory
    /* The abcCoefficients referenced by the SPR4535_PID data structure */
    /* are derived from the gain coefficients, Kp, Ki and Kd */
    /* So, declare Kp, Ki and Kd in an array */
    fractional kCoeffs[] = {0,0,0};     
//////////////////////////////////PID variable use///////////////////////////////

void ControlSpeed(void)
{
    LimitSlew();
    PID_CHANGE_SPEED(SpeedCMD);
    if (timer3avg > 0)  
        ActualSpeed = SPEEDMULT/timer3avg;
    else
        ActualSpeed = 0;
    max=2*(PTPER+1);
    DutyCycle=Fract2Float(PID_COMPUTE(ActualSpeed))*max;
    // Just make sure the speed that will be written to the PDC1 register is not greater than the PTPER register
    if(DutyCycle>max)
        DutyCycle=max;
    else if (DutyCycle<0)
        DutyCycle=0;
}


//////////////////////////////////PID functions//////////////////////////////////

        void INIT_PID(int DESIRED_SPEED)
    {
        SPR4535_PID.abcCoefficients = &abcCoefficient[0];    //Set up pointer to derived coefficients 
        SPR4535_PID.controlHistory = &controlHistory[0];     //Set up pointer to controller history samples 

        PIDInit(&SPR4535_PID);                               //Clear the controller history and the controller output 

        kCoeffs[0] = KP;                                  // Sets the K[0] coefficient to the KP 
        kCoeffs[1] = KI;                                  // Sets the K[1] coefficient to the KI
        kCoeffs[2] = KD;                                  // Sets the K[2] coefficient to the Kd
        PIDCoeffCalc(&kCoeffs[0], &SPR4535_PID);             //Derive the a,b, & c coefficients from the Kp, Ki & Kd 

        SPR4535_PID.controlReference = DESIRED_SPEED;        //Set the Reference Input for your controller
    }

    int PID_COMPUTE(int MEASURED_OUTPUT)
    {
        SPR4535_PID.measuredOutput = MEASURED_OUTPUT;             // Records the measured output
        PID(&SPR4535_PID);  
        return SPR4535_PID.controlOutput;                                      // Computes the control output
    }

    void PID_CHANGE_SPEED (int NEW_SPEED)
    {
        SPR4535_PID.controlReference = NEW_SPEED;                   // Changes the control reference to change the desired speed
    }

/////////////////////////////////////dsp.h/////////////////////////////////////////////////

    typedef struct {
            fractional* abcCoefficients;    /* Pointer to A, B & C coefficients located in X-space */
                                            /* These coefficients are derived from */
                                            /* the PID gain values - Kp, Ki and Kd */
            fractional* controlHistory;     /* Pointer to 3 delay-line samples located in Y-space */
                                            /* with the first sample being the most recent */
            fractional controlOutput;       /* PID Controller Output  */
            fractional measuredOutput;      /* Measured Output sample */
            fractional controlReference;    /* Reference Input sample */
    } tPID;

    /*...........................................................................*/

    extern void PIDCoeffCalc(               /* Derive A, B and C coefficients using PID gain values-Kp, Ki & Kd*/
            fractional* kCoeffs,            /* pointer to array containing Kp, Ki & Kd in sequence */
            tPID* controller                /* pointer to PID data structure */
    );

    /*...........................................................................*/

    extern void PIDInit (                   /* Clear the PID state variables and output sample*/
            tPID* controller               /* pointer to PID data structure */
    );


    /*...........................................................................*/

    extern fractional* PID (                /* PID Controller Function */
            tPID* controller               /* Pointer to PID controller data structure */
    );
3
Well the while(1); statement will certainly hang your chip. I'm not familiar with the chip, but I'd imagine it's due to an unaligned access (i.e. trying to read a 16-bit quantity at an odd byte address). - Drew McGowen
What mode are you operating in? - Treesrule14
@Treesrule14 If you mean "mode" as in how I am driving my equipment and MCU through MPLAB, then I am operating in "Debug". - Stumpyhuck29
Typically when an interrupt occurs then a return address gets pushed onto the stack. If you can examine the stack to find the return address then you can find the code that was executing when the address exception occurred. - kkrambo
Can we see your code? Also generally its not particularly helpful to look in library code for errors. - Treesrule14

3 Answers

3
votes

The dsPIC traps don't offer much information free of charge, so I tend to augment the ISRs with a little assembly language pre-prologue. (Note that the Stack Error trap is a little ropey, as it uses RCALL and RETURN instructions when the stack is already out of order.)

/**
 * \file trap.s
 * \brief Used to provide a little more information during development.
 *
 * The trapPreprologue function is called on entry to each of the routines
 * defined in traps.c.  It looks up the stack to find the value of the IP
 * when the trap occurred and stores it in the _errAddress memory location.
 */

.global __errAddress
.global __intCon1

.global _trapPreprologue

.section .bss

__errAddress:   .space 4
__intCon1:      .space 2

.section .text

_trapPreprologue:
; Disable maskable interrupts and save primary regs to shadow regs
    bclr    INTCON2, #15            ;global interrupt disable
    push.s                          ;Switch to shadow registers

; Retrieve the ISR return address from the stack into w0:w1
    sub     w15, #4, w2             ;set W2 to the ISR.PC (SP = ToS-4)
    mov     [--w2], w0              ;get the ISR return address LSW (ToS-6) in w0
    bclr    w0, #0x0                ;mask out SFA bit (w0<0>)
    mov     [--w2], w1              ;get the ISR return address MSW (ToS-8) in w1
    bclr    w1, #0x7                ;mask out IPL<3> bit (w1<7>)
    ze      w1, w1                  ;mask out SR<7:0> bits (w1<15..8>)

; Save it
    mov     #__errAddress, w2       ;Move address of __errAddress into w2
    mov.d   w0, [w2]                ;save the ISR return address to __errAddress

; Copy the content of the INTCON1 SFR into memory
    mov     #__intCon1, w2          ;Move address of __intCon1 into w2
    mov     INTCON1, WREG           ;Read the trap flags into w0 (WREG)
    mov     w0, [w2]                ;save the trap flags to __intCon1

; Return to the 'C' handler
    pop.s                           ;Switch back to primary registers
    return

Then I keep all the trap ISRs in a single traps.c file that uses the pre-prologue in traps.s. Note that the actual traps may be different for your microcontroller - check the data sheet to see which are implemented.

/**
 * \file traps.c
 * \brief Micro-controller exception interrupt vectors.
 */

#include <stdint.h>

#include "traps.h"      // Internal interface to the micro trap handling.

/* Access to immediate call stack.  Implementation in trap.s */

extern volatile unsigned long _errAddress;
extern volatile unsigned int _intCon1;

extern void trapPreprologue(void);

/* Trap information, set by the traps that use them. */

static unsigned int _intCon2;
static unsigned int _intCon3;
static unsigned int _intCon4;

/* Protected functions exposed by traps.h */

void trapsInitialise(void)
{
    _errAddress = 0;
    _intCon1 = 0;
    _intCon2 = 0;
    _intCon3 = 0;
    _intCon4 = 0;
}


/* Trap Handling */
// The trap routines call the _trapPreprologue assembly routine in traps.s
// to obtain the value of the PC when the trap occurred and store it in
// the _errAddress variable.  They reset the interrupt source in the CPU's
// INTCON SFR and invoke the (#defined) vThrow macro to report the fault.

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _OscillatorFail(void)
{
    INTCON1bits.OSCFAIL = 0;        /* Clear the trap flag */
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _StackError(void)
{
    INTCON1bits.STKERR = 0;         /* Clear the trap flag */
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _AddressError(void)
{
    INTCON1bits.ADDRERR = 0;        /* Clear the trap flag */
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _MathError(void)
{
    INTCON1bits.MATHERR = 0;        /* Clear the trap flag */
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _DMACError(void)
{
    INTCON1bits.DMACERR = 0;        /* Clear the trap flag */
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _HardTrapError(void)
{
    _intCon4 = INTCON4;
    INTCON4 = 0;                    // Clear the hard trap register
    _intCon2 = INTCON2;
    INTCON2bits.SWTRAP = 0;         // Make sure the software hard trap bit is clear
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

void __attribute__((interrupt(preprologue("rcall _trapPreprologue")),no_auto_psv)) _SoftTrapError(void)
{
    _intCon3 = INTCON3;
    INTCON3 = 0;                    // Clear the soft trap register
    vThrow(_intCon1, _intCon2, _intCon3, _intCon4, _errAddress);
}

Implementation of the vThrow macro is up to you. However, it should not use the stack, as this may be unavailable (so no puts() debug calls!) During development, it would be reasonable to use a simple endless loop with a NOP statement in it that you can breakpoint on.

(In a production build, my vThrow macro logs the parameters into a reserved area of RAM that is excluded from being zeroed at start-up by the linker script, and resets the microcontroller. During start-up the program inspects the reserved area and if it is non-zero records the error event for diagnostics.)

Once you get a trap, inspecting the content of the _errAddress variable will give you the return address for the ISR, which is the address immediately following the instruction that generated the interrupt. You can then inspect your MAP files to find the routine and, if you're really keen, inspect the disassembly to find the specific instruction. After that, debugging it is up to you.

1
votes

As suggested in the comments, the while(1) statement is where your code is getting hung. Note however, that your code is executing - you're just in an infinite loop. That's also why you can't view your variables or current program counter. Generally when you're attached to a ucontroller via PC host, you can't view state information while the ucontroller is executing. Everything is running too fast, even on a slow one, to constantly update your screen.

To try to identify the cause, you can set a breakpoint in the ISR and reset the controller. When the breakpoint is hit, execution will halt, and you may be able to investigate your stack frames to see the last line of code executed before the ISR was triggered. This is not guaranteed though - depending on how your particular ucontroller handles interrupts, the call stack may not be continuous between normal program execution and the interrupt context.

If that doesn't work, set a breakpoint in your code before the ISR is invoked, and step through your code until it is. The last line of code you executed before the ISR will be the cause. Keep in mind, this may take some time, especially if the offending line is in the loop and only trips the ISR after a certain number of iterations.

EDIT
After posting this answer, I noticed your last comment about the linkscript warning. This is a perfect example of why you should, with very few exceptions, work just as hard to resolve warnings as you do to resolve compiler errors. Especially if you don't understand what the warning means and what caused it.

1
votes

A PID algorithm involves multiplication. On a dspic, this is done via the built in hardware multiplier. This multiplier has one register which must point to xmemory space and another pointing to ymemory space. The dsp core then multiplies these two and the result can be found in the accumulator (there a two of them).

An addres error trap will be triggered if an xmemory address range is loaded into the ymemory register and viceversa. You can check this by single stepping the code in the assembly.

This is not the only instance the trap is triggered. There are also silicon bugs that can cause this, check the errata.