I already used FreeRTOS for some embedded projects for some year time and It worked really perfectly until now. Currently i'm facing a difficult problem related to using High Speed Interrupt in FreeRTOS porting to PIC24H, hope you all can help me to though this problem. Thanks in advance
I created a small demo project for easy testing:
Two task:
// Task 1
if (xTaskCreate(RTOSTask_1, (signed char) "[T1]", configMINIMAL_STACK_SIZE2, NULL, tskIDLE_PRIORITY + 1, &hTask1) == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY)
{
LREP("\r\nCannot create Task 1.");
Display_Error(1000);
}
// Task 2
if (xTaskCreate(RTOSTask_2, (signed char) "[T2]", configMINIMAL_STACK_SIZE2, NULL, tskIDLE_PRIORITY + 2, &hTask2) == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY)
{
LREP("\r\nCannot create Task 2.");
Display_Error(1000);
}
Implementation of tasks:
void RTOSTask_1(void* pvParameter)
{
while(1)
{
if (xSemaphoreTake(hTask1Semaphore, portMAX_DELAY) == pdTRUE)
{
putchar1('1');
}
}
}
void RTOSTask_2(void* pvParameter)
{
while(1)
{
if (xSemaphoreTake(hTask2Semaphore, portMAX_DELAY) == pdTRUE)
{
putchar1('2');
}
}
}
To let above two tasks running i use one Timer & one UART to give them Semaphores:
void attribute((interrupt, auto_psv)) _T2Interrupt (void)
{
_T2IF = 0;
static signed portBASE_TYPE xTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(hTask1Semaphore, &xTaskWoken );
if( xTaskWoken != pdFALSE )
{
taskYIELD();
}
}
void attribute((interrupt, auto_psv)) _U1TXInterrupt()
{
_U1TXIF = 0;
putchar1('E');
}
void attribute((interrupt, auto_psv)) _U1RXInterrupt()
{
_U1RXIF = 0;
if(U1STAbits.URXDA == 1)
{
uint8 u8Recv = U1RXREG;
}
static signed portBASE_TYPE xTaskWoken;
xTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(hTask2Semaphore, &xTaskWoken);
if( xTaskWoken != pdFALSE )
{
taskYIELD();
}
}
My Timer interrupts in every 100us, UART working at 230400 bps baudrate speed.
After running some second or minutes the program is crashed and the program jump to Traps:
_AddressError or
_StackError
I don't know how this problem could happen. After a long investigating & testing i thought that the problem happen when the program running in & running out of the Interrupt service routine (ISR). It seems we need a couple of SAVE_CONTEXT() & RESTORE_CONTEXT() functions. but on PIC24 port there is no one like that.
Please you kindly give me some advises for this problem
Thank you all !
I already find out my problem, i think. The problem is introduced when the PIC24H gets in & gets out Interrupt Service Routines, here they are UART RX, TX, Timer Interrupts.
Currently i don't use the ISR like this:
void attribute((interrupt, auto_psv))
instead of it i created a mechanism myself with Assembly code:
__U1RXInterrupt:
; Push CPU registers in to Stack
PUSH SR
PUSH W0
PUSH W1
PUSH.D W2
PUSH.D W4
PUSH.D W6
PUSH.D W8
PUSH.D W10
PUSH.D W12
PUSH W14
PUSH RCOUNT
PUSH TBLPAG
PUSH CORCON
PUSH PSVPAG
; Call my ISR
call _UART1_RxISRHandler
; Pop out CPU registers
POP PSVPAG
POP CORCON
POP TBLPAG
POP RCOUNT
POP W14
POP.D W12
POP.D W10
POP.D W8
POP.D W6
POP.D W4
POP.D W2
POP.D W0
POP SR
retfie
UART1_RxISRHandler is my implement of ISR. I do the same with TX, Timer interrupts.
The result is that my program run more smoothly, longer 1 hour (before the program crash after 1-5 minutes only). But at the end it still crash after running 1-2 hours. That means my approach is correct but still there is something wrong. May be i miss something with above code.
If you all have any ideal for this situation, please let me know.
Thanks
I've got a similar problem.
What is the priority of your uart interrupt ?
It should not be higher than the RTOS Kernel interrupt priority set into the FreeRTOSConfig.h which has a default priority of 1 while PIC interrupt have a default priority of 3.
This seem to have caused a very occasionally crash.
The SAVE_CONTEXT() & RESTORE_CONTEXT() are not necessary with PIC as the compiler takes car of that, at least if you are using the compiler function declaration and avoid using _FASTISR or shadowing.
Try using queues.
Example for this on LPC1769. You can easily port it for your mcu.
define mainQUEUE_LENGTH ( 1 )
This will fix maximum bytes that can be stored in queue. Modify this as per your requirement and hence will immune from stack error or address error
:
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/* Priorities at which the tasks are created. */
#define mainQUEUE_RECEIVE_TASK_PRIORITY ( tskIDLE_PRIORITY + 2 )
#define mainQUEUE_SEND_TASK_PRIORITY ( tskIDLE_PRIORITY + 1 )
/* The bit of port 0 that the LPCXpresso LPC13xx LED is connected. */
#define mainLED_BIT ( 22 )
/* The rate at which data is sent to the queue, specified in milliseconds. */
#define mainQUEUE_SEND_FREQUENCY_MS ( 500 / portTICK_RATE_MS )
/* The number of items the queue can hold. This is 1 as the receive task
will remove items as they are added, meaning the send task should always find
the queue empty. */
#define mainQUEUE_LENGTH ( 1 )
/*
* The tasks as described in the accompanying PDF application note.
*/
static void prvQueueReceiveTask( void *pvParameters );
static void prvQueueSendTask( void *pvParameters );
/*
* Simple function to toggle the LED on the LPCXpresso LPC17xx board.
*/
static void prvToggleLED( void );
/* The queue used by both tasks. */
static xQueueHandle xQueue = NULL;
/*-----------------------------------------------------------*/
int main(void)
{
/* Initialise P0_22 for the LED. */
LPC_PINCON->PINSEL1 &= ( ~( 3 << 12 ) );
LPC_GPIO0->FIODIR |= ( 1 << mainLED_BIT );
/* Create the queue. */
xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( unsigned long ) );
if( xQueue != NULL )
{
/* Start the two tasks as described in the accompanying application
note. */
xTaskCreate( prvQueueReceiveTask, ( signed char * ) "Rx", configMINIMAL_STACK_SIZE, NULL, mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
xTaskCreate( prvQueueSendTask, ( signed char * ) "TX", configMINIMAL_STACK_SIZE, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL );
/* Start the tasks running. */
vTaskStartScheduler();
}
/* If all is well we will never reach here as the scheduler will now be
running. If we do reach here then it is likely that there was insufficient
heap available for the idle task to be created. */
for( ;; );
}
/*-----------------------------------------------------------*/
static void prvQueueSendTask( void *pvParameters )
{
portTickType xNextWakeTime;
const unsigned long ulValueToSend = 100UL;
/* Initialise xNextWakeTime - this only needs to be done once. */
xNextWakeTime = xTaskGetTickCount();
for( ;; )
{
/* Place this task in the blocked state until it is time to run again.
The block state is specified in ticks, the constant used converts ticks
to ms. While in the blocked state this task will not consume any CPU
time. */
vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );
/* Send to the queue - causing the queue receive task to flash its LED.
0 is used as the block time so the sending operation will not block -
it shouldn't need to block as the queue should always be empty at this
point in the code. */
xQueueSend( xQueue, &ulValueToSend, 0 );
}
}
/*-----------------------------------------------------------*/
static void prvQueueReceiveTask( void *pvParameters )
{
unsigned long ulReceivedValue;
for( ;; )
{
/* Wait until something arrives in the queue - this task will block
indefinitely provided INCLUDE_vTaskSuspend is set to 1 in
FreeRTOSConfig.h. */
xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY );
/* To get here something must have been received from the queue, but
is it the expected value? If it is, toggle the LED. */
if( ulReceivedValue == 100UL )
{
prvToggleLED();
}
}
}
/*-----------------------------------------------------------*/
static void prvToggleLED( void )
{
unsigned long ulLEDState;
/* Obtain the current P0 state. */
ulLEDState = LPC_GPIO0->FIOPIN;
/* Turn the LED off if it was on, and on if it was off. */
LPC_GPIO0->FIOCLR = ulLEDState & ( 1 << mainLED_BIT );
LPC_GPIO0->FIOSET = ( ( ~ulLEDState ) & ( 1 << mainLED_BIT ) );
}
It might be a stack overrun.
How much stack are you allocating for each task?
Since FreeRTOS interrupt handler shares a stack with FreeRTOS task (AFAIK, there is no system stack), it can easily cause overrun when stack usage of both a task and a handler adds up.
Since any task could be running when interrupt kicks in, this leads to sporadic behavior as a task's stack usage at the point of interrupt differs.
To check it, you can simply increase stack allocation size of ALL tasks, including implicitly-created IDLE task (and other system tasks, if configured to use one). Or, you can add following to your code to catch stack overrun:
// in FreeRTOSConfig.h
#define configCHECK_FOR_STACK_OVERFLOW 1
=== somewhere in your code ===
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
// Embed assembler code to put your processor into debughalt state.
// I'm not familiar with PIC24 asm, but
//
// - http://www.microchip.com/forums/tm.aspx?m=434136
//
// may help.
MACRO_TO_DEBUG_HALT();
}
Related
Currently I'm polling the register to get the expected value and now I want reduce the CPU usage and increase the performance.
So, I think, if we do polling for particular time (Say for 10ms) and if we didn't get expected value then wait for some time (like udelay(10*1000) or usleep(10*1000) delay/sleep in ms) then continue to do polling for more more extra time (Say 100ms) and still if you didn't get the expected value then do sleep/delay for 100ms.....vice versa... need to do till it reach to maximum timeout value.
Please let me know if anything.
This is the old code:
#include <sys/time.h> /* for setitimer */
#include <unistd.h> /* for pause */
#include <signal.h> /* for signal */
#define INTERVAL 500 //timeout in ms
static int timedout = 0;
struct itimerval it_val; /* for setting itimer */
char temp_reg[2];
int main(void)
{
/* Upon SIGALRM, call DoStuff().
* Set interval timer. We want frequency in ms,
* but the setitimer call needs seconds and useconds. */
if (signal(SIGALRM, (void (*)(int)) DoStuff) == SIG_ERR)
{
perror("Unable to catch SIGALRM");
exit(1);
}
it_val.it_value.tv_sec = INTERVAL/1000;
it_val.it_value.tv_usec = (INTERVAL*1000) % 1000000;
it_val.it_interval = it_val.it_value;
if (setitimer(ITIMER_REAL, &it_val, NULL) == -1)
{
perror("error calling setitimer()");
exit(1);
}
do
{
temp_reg[0] = read_reg();
//Read the register here and copy the value into char array (temp_reg
if (timedout == 1 )
return -1;//Timedout
} while (temp_reg[0] != 0 );//Check the value and if not try to read the register again (poll)
}
/*
* DoStuff
*/
void DoStuff(void)
{
timedout = 1;
printf("Timer went off.\n");
}
Now I want to optimize and reduce the CPU usage and want to improve the performance.
Can any one help me on this issue ?
Thanks for your help on this.
Currently I'm polling the register to get the expected value [...]
wow wow wow, hold on a moment here, there is a huge story hidden behind this sentence; what is "the register"? what is "the expected value"? What does read_reg() do? are you polling some external hardware? Well then, it all depends on how your hardware behaves.
There are two possibilities:
Your hardware buffers the values that it produces. This means that the hardware will keep each value available until you read it; it will detect when you have read the value, and then it will provide the next value.
Your hardware does not buffer values. This means that values are being made available in real time, for an unknown length of time each, and they are replaced by new values at a rate that only your hardware knows.
If your hardware is buffering, then you do not need to be afraid that some values might be lost, so there is no need to poll at all: just try reading the next value once and only once, and if it is not what you expect, sleep for a while. Each value will be there when you get around to reading it.
If your hardware is not buffering, then there is no strategy of polling and sleeping that will work for you. Your hardware must provide an interrupt, and you must write an interrupt-handling routine that will read every single new value as quickly as possible from the moment that it has been made available.
Here are some pseudo code that might help:
do
{
// Pseudo code
start_time = get_current_time();
do
{
temp_reg[0] = read_reg();
//Read the register here and copy the value into char array (temp_reg
if (timedout == 1 )
return -1;//Timedout
// Pseudo code
stop_time = get_current_time();
if (stop_time - start_time > some_limit) break;
} while (temp_reg[0] != 0 );
if (temp_reg[0] != 0)
{
usleep(some_time);
start_time = get_current_time();
}
} while (temp_reg[0] != 0 );
To turn the pseudo code into real code, see https://stackoverflow.com/a/2150334/4386427
I have some programming knowledge with regards to Arduino and Msp430 (using Embedded C). But, I am unable to understand where to start learning when it comes to LPC4370. I have a requirement for programming the above mentioned chip but I don't find any material that explains the various function that can be used in programming the LPC4370. LPCopen has a lot of codes but I can find out the utility of various functions used. If someone could give a hint on where to start, it would be really helpful.
Thanks in advance.
I have not worked with LPC 4370 but I have experience with lpc2148.
Few things I would suggest you :
Download LPCEXPRESSO IDE from here
Download LPCXPRESSO IDE GUIDE from here
Learn how to create projects . How to load into hardware.
Then learn to create simple task from rtos like FreeRTOS
Read this ST manual for various RTOS function
Here is sample program of creating simple task. you can refer and similarly you can read following exercises and proceed.
/*
FreeRTOS V6.1.1 - Copyright (C) 2011 Real Time Engineers Ltd.
This file is part of the FreeRTOS distribution.
FreeRTOS is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License (version 2) as published by the
Free Software Foundation AND MODIFIED BY the FreeRTOS exception.
***NOTE*** The exception to the GPL is included to allow you to distribute
a combined work that includes FreeRTOS without being obliged to provide the
source code for proprietary components outside of the FreeRTOS kernel.
FreeRTOS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details. You should have received a copy of the GNU General Public
License and the FreeRTOS license exception along with FreeRTOS; if not it
can be viewed here: http://www.freertos.org/a00114.html and also obtained
by writing to Richard Barry, contact details for whom are available on the
FreeRTOS WEB site.
1 tab == 4 spaces!
http://www.FreeRTOS.org - Documentation, latest information, license and
contact details.
http://www.SafeRTOS.com - A version that is certified for use in safety
critical systems.
http://www.OpenRTOS.com - Commercial support, development, porting,
licensing and training services.
*/
#include "FreeRTOS.h"
#include "task.h"
/* Demo includes. */
#include "basic_io.h"
/* Used as a loop counter to create a very crude delay. */
#define mainDELAY_LOOP_COUNT ( 0xfffff )
/* The task functions. */
void vTask1( void *pvParameters );
void vTask2( void *pvParameters );
int main( void )
{
/* Init the semi-hosting. */
printf( "\n" );
/* Create one of the two tasks. */
xTaskCreate( vTask1, /* Pointer to the function that implements the task. */
"Task 1", /* Text name for the task. This is to facilitate debugging only. */
240, /* Stack depth in words. */
NULL, /* We are not using the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* We are not using the task handle. */
/* Create the other task in exactly the same way. */
xTaskCreate( vTask2, "Task 2", 240, NULL, 1, NULL );
/* Start the scheduler so our tasks start executing. */
//
vTaskStartScheduler();
for( ;; );
return 0;
}
/*-----------------------------------------------------------*/
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\n";
volatile unsigned long ul;
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
//??????delete your task
}
void vTask2( void *pvParameters )
{
const char *pcTaskName = "Task 2 is running\n";
volatile unsigned long ul;
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
}
/*-----------------------------------------------------------*/
void vApplicationMallocFailedHook( void )
{
/* This function will only be called if an API call to create a task, queue
or semaphore fails because there is too little heap RAM remaining. */
for( ;; );
}
/*-----------------------------------------------------------*/
void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed char *pcTaskName )
{
/* This function will only be called if a task overflows its stack. Note
that stack overflow checking does slow down the context switch
implementation. */
for( ;; );
}
/*-----------------------------------------------------------*/
void vApplicationIdleHook( void )
{
/* This example does not use the idle hook to perform any processing. */
}
/*-----------------------------------------------------------*/
void vApplicationTickHook( void )
{
/* This example does not use the tick hook to perform any processing. */
}
What type of example you can practice ? it can be found here
I want to exit this while loop after 2 seconds if condition is not met. What value do I put in for timout here if the the clock frequency is 32MHz.
while(((READ_REG(ADC_NEW) & ADC_DATA) == 0) && (--timeout > 0));
Why don't you use a timer-based solution?
Like
volatile bool adc_timeout_flag;
//configure timer for a desired frequency
config_timeout_tim();
//some code here
while(condition_for_adc_not_met() && (adc_timeout_flag == false));
adc_timeout_flag = false;
//something
void timerISR(void) {
static uint32_t tim;
++tim;
if (tim > TIM_MAX) {
tim = 0;
adc_timeout_flag = true;
}
}
Any value you determine is likely to be different depending on:
The processor architecture/instruction set.
Whether the variable is located in on-chip memory, external memory, or a register.
Different compiler options such as optimisation level.
Using simple loop-counters as timers is a poor choice in almost any circumstance - simply rebuilding your code with an upgraded compiler is likely to affect its timing.
Most microcontrollers have some sort of timing device; and you should use one of these to provide a deterministic timing source. At its simplest, you would initialise the timer to increment a tick counter on an interrupt, and then poll that counter in your loop.
So given the pseudo-code:
static volatile unsigned sys_tick = 0 ;
__interrupt tickHandler()
{
sys_tick++ ;
}
unsigned getTick()
{
return sys_tick ;
}
Note this is pseudo-code - the real code will be hardware/compiler specific, and will require additional code if unsigned access is not atomic on your target.
Then your loop becomes:
unsigned start = getTick() ;
while( READ_REG(ADC_NEW) & ADC_DATA) == 0 &&
(getTick() - start) < TICKS_PER_SECOND * 2 ) ;
If your target's standard library already implements clock() or you have retargetted it to your hardware then you already have the necessary timing support:
unsigned start = clock() ;
while( READ_REG(ADC_NEW) & ADC_DATA) == 0 &&
(clock() - start) < CLOCKS_PER_SECOND * 2 ) ;
The basics of the answer are as follows:
time_outvalue = timeout_seconds / ( ( 1 / 32,000,000 ) * instructions_in_loop )
Assuming (and this assumption WILL be wrong!) there are three instructions (One for READ_REG, one for comparison, and one for the timeout-- ) you get a value of around 21,333,333
This is really not the way to do timeouts. Instead, have a periodic timer interrupt running and deal without time in there.
UPDATE: All bets are off with this solution if you have interrupts enabled.
I could use:
while(1==1)
{
delay(10);
f(); // <-- function to be called every 10 seconds
otherfunctions();
}
But this will take just over 10 seconds because the other functions take some time to execute. Is there a delay function that takes into account the time taken by the other functions so I can then call f() exactly every 10 seconds?
I've heard that this can be done with a clever function that can be found in a header file, but I cant remember which one. I think it might have been #include mbed.h but even if the function is included in this header file I do not know what it is called or how to search for it.
Does anybody know of a function that can do what I am after?
You should of course start by reading the mbed handbook. It is not a large API and you can get a good overview of it very quickly.
The mbed platform is a C++ API, so you will need to use C++ compilation.
There are several ways to achieve what you need, some examples:
Using the Ticker class:
#include "mbed.h"
Ticker TenSecondStuff ;
void TenSecondFunction()
{
f();
otherfunctions();
}
int main()
{
TenSecondStuff.attach( TenSecondFunction, 10.0f ) ;
// spin in a main loop.
for(;;)
{
continuousStuff() ;
}
}
Using wait_us() and the Timer class:
#include "mbed.h"
int main()
{
Timer t ;
for(;;)
{
t.start() ;
f() ;
otherfunctions() ;
t.stop() ;
wait_us( 10.0f - t.read_us() ) ;
}
}
Using the Ticker class, an alternative method:
#include "mbed.h"
Ticker ticksec ;
volatile static unsigned seconds_tick = 0 ;
void tick_sec()
{
seconds_tick++ ;
}
int main()
{
ticksec.attach( tick_sec, 1.0f ) ;
unsigned next_ten_sec = seconds_tick + 10 ;
for(;;)
{
if( (seconds_tick - next_ten_sec) >= 0 )
{
next_ten_sec += 10 ;
f() ;
otherfunctions() ;
}
continuousStuff() ;
}
}
Using mbed RTOS timer
#include "mbed.h"
#include "rtos.h"
void TenSecondFunction( void const* )
{
f();
otherfunctions();
}
int main()
{
RtosTimer every_ten_seconds( TenSecondFunction, osTimerPeriodic, 0);
for(;;)
{
continuousStuff() ;
}
}
If you want it simple try this
int delayTime = DELAY_10_SECS;
while(1==1)
{
delay(delayTime);
lastTime = getCurrTicks(); //Or start some timer with interrupt which tracks time
f(); // <-- function to be called every 10 seconds
otherfunctions();
delayTime = DELAY_10_SECS - ( getCurrTicks() - lastTime ); //Or stop timer and get the time
}
Asssuming you have some type of timer counter, perhaps one generated by a timer driven interrupt, try something like this:
volatile int *pticker; /* pointer to ticker */
tickpersecond = ... ; /* number of ticks per second */
/* ... */
tickcount = *pticker; /* get original reading of timer */
while(1){
tickcount += 10 * tickspersecond;
delaycount = tickcount-*pticker;
delay(delaycount); /* delay delaycount ticks */
/* ... */
}
This assumes that the ticker increments (instead of decrements), that the codde never get 10 seconds behind on a delay and assumes the number of ticks per second is an exact integer. Since an original reading is used as a basis, the loop will not "drift" over a long period of time.
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 */
);
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.
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.
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.