![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
mikimtb
Joined: 18 Mar 2012 Posts: 4 Location: Serbia
|
! Problem with UART and TIMER1 interrupt |
Posted: Wed Jun 13, 2012 4:39 pm |
|
|
Hi guys,
in title of this topic you can see what is my problem.
I'm using PIC18F4431 and write code for motion controller. CCS C v4.120
My code is:
Code: |
#include "18F4431.h"
#define RS485_RW PIN_C0
#define RX_PIN PIN_C7
#define TX_PIN PIN_C6
#device ADC = 10
#fuses HS,FCMEN,IESO,NOPUT,NOBROWNOUT,NOWDT,NOWINEN,NOPWMPIN,HPOL_HIGH,LPOL_HIGH,T1LOWPOWER,FLTAD4,SSP_RC,NOPWMPIN,MCLR,NOLVP,NODEBUG
#use delay(clock = 20MHZ)
#use rs232(baud=115200, xmit=TX_PIN, rcv=RX_PIN, enable=RS485_RW)
#define H_BRIDGE_ENABLE() output_low(PIN_D0);
#define H_BRIDGE_DISABLE() output_high(PIN_D0);
#define BRAKE_ENABLE() output_high(PIN_D5);
#define BRAKE_DISABLE() output_low(PIN_D5);
#define ENCODER_ENABLE() output_low(PIN_D6);
#define ENCODER_DISABLE() output_high(PIN_D6);
#define ENCODER_TTL() output_high(PIN_D7);
#define ENCODER_DIFF() output_low(PIN_D7);
#define RCV_OFF() disable_interrupts(INT_RDA);
#define C1 PIN_E0
#define C2 PIN_E1
#define POWER_PWM_PERIOD 249
#define PWM_HALF 498
#define SAMPLE_TIME 0.0015 // Ts = 1.5ms
#define t 0.01 // t = 10ms
#define INPUT_BUFFER_SIZE 9 // Velicina receive bufera
#define OUTPUT_BUFFER_SIZE 32
#bit QEI_DIR=0xFB6.5
#include "DataConversion.c"
void Serial_isr(void);
void ComputeCRC8(BYTE*, BYTE);
void SendPacket(BYTE*,BYTE);
void RCV_ON(void);
void Init(void);
void SaveNewADR(void);
void SaveNewParams(void);
void Timer1_isr(void);
void QEI_Index(void);
void GoToPosition(float);
void Movement(short &enable, int16 &pwm, short &dir);
void DataSend(BYTE*);
struct params
{
BYTE ID;
BYTE MP0;
BYTE POWER_SUPPLY;
BYTE MOTOR_VOLTAGE;
BYTE ENC_MAX_H;
BYTE ENC_MAX_L;
BYTE MP1;
BYTE GEAR_N;
BYTE GEAR_M;
};
struct controls
{
signed int16 PWM;
short DIRECTION; // DIRECTION = true, CW
short ENABLE;
};
signed int pFactor = 0;
signed int dFactor = 0;
int16 adcRef = 0;
int16 adcValue = 0;
signed int16 adcOffset = 0;
signed int16 filteredDataADCV = 0;
signed int16 lastSampleADCV = 0;
signed int16 newSampleADCV = 0;
signed int16 filteredDataREF = 0;
signed int16 lastSampleREF = 0;
signed int16 newSampleREF = 0;
signed int16 filteredDataFS = 0;
signed int16 lastSampleFS = 0;
signed int16 newSampleFS = 0;
unsigned int16 iTermLimit = 0;
float alfa = 0.2206; //0.00934;// 1Hz | 0.2206; //30Hz
//PID controler variables
signed int16 currentFeedback = 0;
signed int16 errorTerm = 0;
int16 currentSetPoint = 0;
BYTE pGain = 40;
signed int16 pTerm = 0;
signed int16 lError = 0;
BYTE dGain = 60;
signed int16 dTerm = 0;
signed int16 aError = 0;
BYTE iGain = 40;
signed int16 iTerm = 0;
signed int16 mPWM = 0;
//End of PID controler variables
int16 forceSensor = 0;
signed int16 encoderIndex = 0;
float position = 0;
float stepSize = 0.000666;
float positionIEEE = 0;
BYTE AUTO_FUNCTION = 0;
short STOP_EXTRACTION = FALSE;
short EXTARCTION_FINISHED = FALSE;
int extractionLogTimer = 0;
float newPosition = 0;
BYTE c;
BYTE iBufferCount = 0;
BYTE crc = 0;
short NEW_MESSAGE_RECEIVE = FALSE, START_BYTE = FALSE;
BYTE iBuffer[9], tBuffer[9];
struct params p;
struct controls con;
#priority rda, timer1, ic2qei
/************* Encoder index detect *****************/
#INT_IC2QEI
void QEI_Index()
{
clear_interrupt(INT_IC2QEI);
if (QEI_DIR)
{
encoderIndex += 1;
}
else
{
encoderIndex -= 1;
}
}
/************* TIMER1 overflow interrupt ************/
#INT_TIMER1
void Timer1_isr()
{
int16 countPos;
signed int16 eIndexCopy = encoderIndex;
//float sVoltage,refVoltage, mVoltage, pCurrent;
set_timer1(58036);
disable_interrupts(GLOBAL);
disable_interrupts(INT_TIMER1);
set_adc_channel(1);
adcValue = read_adc() + adcOffset; // ovo je stari deo koda
newSampleADCV = adcValue;
//adcValue = newSampleADCV;
filteredDataADCV = (int16)(lastSampleADCV + alfa * (newSampleADCV - lastSampleADCV));
lastSampleADCV = filteredDataADCV;
set_adc_channel(0);
adcRef = read_adc(); // ovo je stari deo koda
newSampleREF = adcRef;
//adcRef = newSampleREF;
filteredDataREF = (int16)(lastSampleREF + alfa * (newSampleREF - lastSampleREF));
lastSampleREF = filteredDataREF;
//set_adc_channel(5);
//forceSensor = 0;//read_adc();
/*newSampleFS = forceSensor;
//adcRef = newSampleREF;
filteredDataFS = (int16)(lastSampleFS + alfa * (newSampleFS - lastSampleFS));
lastSampleFS = filteredDataFS;*/
position = eIndexCopy * 4.00;
countPos = qei_get_count(QEI_GET_POSITION_COUNT);
position = position + (stepSize * countPos); // Ovde mi blokira prekid i pravi ITOFF temp promenljivu
position = pFactor * position; // za novu mehaniku dodato mFactor
// Racunanje struje na osnovu vrednosti ocitane sa ADC pravi mi dodatnu promenljivu DIVFF i stopira prekid
//sVoltage = ((float)(adcValue)) * 0.0048828125;
//refVoltage = ((float)(adcRef)) * 0.0048828125;
//mVoltage = sVoltage - refVoltage;
/*pCurrent = mVoltage/83.0;
pCurrent = pCurrent * 20.0;
pCurrent = pCurrent/0.625;*/
//currentFeedback = adcValue - adcRef; //signed int16 currentFeedback
currentFeedback = filteredDataADCV - filteredDataREF;
if (currentFeedback < 0)
currentFeedback = (-1) * currentFeedback;
switch(AUTO_FUNCTION)
{
/***************************************/
/* Kalibracija enkodera */
/***************************************/
case 1:
if (currentFeedback < 160) //0.3A
{
con.ENABLE = TRUE;
con.PWM = 150;
con.DIRECTION = FALSE;
}
else
{
encoderIndex = 0;
qei_set_count(0);
con.PWM = 0;
con.DIRECTION = TRUE;
con.ENABLE = FALSE;
AUTO_FUNCTION = 0;
GoToPosition(60);
}
break;
/***************************************/
/* Odlazak u zadatu poziciju */
/***************************************/
case 2:
if ( !(((newPosition-0.1)<position) && (position<(newPosition+0.1))) )
{
if (position < newPosition)
{
con.PWM = 150;
con.DIRECTION = TRUE;
con.ENABLE = TRUE;
}
else
{
con.PWM = 150;
con.DIRECTION = FALSE;
con.ENABLE = TRUE;
}
}
else
{
AUTO_FUNCTION = 0;
con.PWM = 0;
con.DIRECTION = TRUE;
con.ENABLE = FALSE;
}
break;
}
//Movement(con.ENABLE, con.PWM, con.DIRECTION);
if (con.ENABLE)
{
H_BRIDGE_ENABLE();
}
else
{
H_BRIDGE_DISABLE();
}
if (con.DIRECTION)
{
set_power_pwm0_duty(PWM_HALF - (dFactor * con.PWM));
set_power_pwm2_duty(PWM_HALF + (dFactor * con.PWM));
}
else
{
set_power_pwm0_duty(PWM_HALF + (dFactor * con.PWM));
set_power_pwm2_duty(PWM_HALF - (dFactor * con.PWM));
}
clear_interrupt(INT_TIMER1);
enable_interrupts(INT_TIMER1);
enable_interrupts(INT_RDA);
enable_interrupts(GLOBAL);
}
/*********** UART data received interrupt ***********/
#INT_RDA
void Serial_isr()
{
int i = 0;
disable_interrupts(GLOBAL);
disable_interrupts(INT_RDA);
c = getc();
if ((!START_BYTE) && (c == 0x21))
START_BYTE = TRUE;
if (START_BYTE)
{
if (iBufferCount < 8)
{
iBuffer[iBufferCount] = c;
++iBufferCount;
}
else
{
iBuffer[iBufferCount] = c;
NEW_MESSAGE_RECEIVE = TRUE;
for (i=0;i<9;++i)
{
tBuffer[i] = iBuffer[i];
ibuffer[i] = 0;
}
iBufferCount = 0;
START_BYTE = FALSE;
}
}
enable_interrupts(INT_RDA);
enable_interrupts(GLOBAL);
}
/**** Funkcija racuna CRC8 za zadati niz bajtova ****/
/* *buff - pokazivac na prvi element niza za koji */
/* se CRC racuna */
/* buffLength - broj bajtova za koje se CRC racuna */
/****************************************************/
/*void ComputeCRC8(BYTE *buff, BYTE buffLength)
{
int j=0;
crc = 0;
if (buffLength > 0)
{
for (j=1;j<buffLength;j++)
{
crc = table[(crc ^ buff[j])];
}
}
}*/
/********** Slanje podataka na seriski port *********/
/* *data - pokazivac na prvi element niza */
/* dataLength - broj bajtova za koje treba poslati */
/****************************************************/
/*void SendPacket(BYTE *data,BYTE dataLength)
{
int k;
RCV_OFF();
for (k=0;k<dataLength;k++)
{
putc(data[k]);
}
RCV_ON();
}*/
void DataSend(byte *data)
{
int i = 0;
for (i=0;i<9;i++)
{
putc(data[i]);
}
}
/********* Prebacivanje u Receive mod RS485 *********/
void RCV_ON()
{
clear_interrupt(INT_RDA);
enable_interrupts(INT_RDA);
}
/********* Inicijalizacija mikrokontrolera **********/
void Init()
{
set_tris_a(0b11111111);
set_tris_b(0b00000000);
//set_tris_c(0b10000000);
set_tris_d(0b00000000);
set_tris_e(0b00000011);
delay_ms(1);
ENCODER_ENABLE();
ENCODER_TTL();
BRAKE_ENABLE();//BRAKE_DISABLE();
H_BRIDGE_DISABLE();
delay_ms(1);
setup_adc_ports(sAN0 | sAN1 | VSS_VDD); // Treba dodati i AN5
setup_adc(ADC_CLOCK_INTERNAL);
// Brake control
/*setup_timer_2(T2_DIV_BY_1,249,1); //20KHz
setup_ccp1(CCP_PWM);
set_pwm1_duty(716);*/
setup_power_pwm_pins(PWM_COMPLEMENTARY, PWM_COMPLEMENTARY, PWM_OFF,PWM_OFF);
// Mode = Free Run
// Postscale = 1 (1-16) Timebase output postscaler
// TimeBase = 0 (0-65355) Initial value of PWM Timebase
// Period = 250 (0-4095) Max value of PWM TimeBase
// Compare = 0 (Timebase value for special event trigger)
// Compare Postscale = 1 (Postscaler for Compare value)
// Dead Time
setup_power_pwm(PWM_FREE_RUN, 1, 0, POWER_PWM_PERIOD, 0, 1,10);
set_power_pwm0_duty(PWM_HALF);
set_power_pwm2_duty(PWM_HALF);
setup_qei(QEI_MODE_X4 | QEI_RESET_WHEN_MAXCOUNT | QEI_VELOCITY_MODE_DISABLED, QEI_FILTER_ENABLE_QEA | QEI_FILTER_ENABLE_QEB | QEI_FILTER_ENABLE_INDX | QEI_FILTER_DIV_16, 5999);
delay_ms(1);
qei_set_count(0);
// SAMPLE_TIME interrupt generator, interrupts on every 1.5ms
setup_timer_1 (T1_INTERNAL | T1_DIV_BY_1);
p.ID = read_eeprom(0x00);
/***************************************/
/* FIRST START INIT DEFAULT PARAMETERS */
/***************************************/
if (read_eeprom(0xff) == 255)
{
p.MP0 = 0;
write_eeprom(0x01,p.MP0);
p.POWER_SUPPLY = 35;
write_eeprom(0x02,p.POWER_SUPPLY);
p.MOTOR_VOLTAGE = 24;
write_eeprom(0x03,p.MOTOR_VOLTAGE);
p.ENC_MAX_H = 0;
write_eeprom(0x04,p.ENC_MAX_H);
p.ENC_MAX_L = 0;
write_eeprom(0x05,p.ENC_MAX_L);
p.MP1 = 0;
write_eeprom(0x06,p.MP1);
p.GEAR_N = 0;
write_eeprom(0x07,p.GEAR_N);
p.GEAR_M = 0;
write_eeprom(0x08,p.GEAR_M);
con.ENABLE = FALSE;
con.DIRECTION = FALSE;
con.PWM = 0;
write_eeprom(0xff,100);
}
else
{
p.MP0 = read_eeprom(0x01);
/*if ((p.MP0 & 0x02) == 0x02)
ENCODER_ENABLE();
else
ENCODER_DISABLE();
if ((p.MP0 & 0x04) == 0x04)
ENCODER_TTL();
else
ENCODER_DIFF();
if ((p.MP0 & 0x08) == 0x08)
BRAKE_ENABLE();
else
BRAKE_DISABLE();*/
p.POWER_SUPPLY = read_eeprom(0x02);
p.MOTOR_VOLTAGE = read_eeprom(0x03);
p.ENC_MAX_H = read_eeprom(0x04);
p.ENC_MAX_L = read_eeprom(0x05);
p.MP1 = read_eeprom(0x06);
p.GEAR_N = read_eeprom(0x07);
p.GEAR_M = read_eeprom(0x08);
con.ENABLE = FALSE;
con.DIRECTION = FALSE;
con.PWM = 0;
}
delay_ms(500);
set_adc_channel(0);
adcRef = read_adc();
set_adc_channel(1);
adcValue = read_adc();
adcOffset = adcRef - adcValue;
delay_ms(500);
set_timer1(58036); //58036 za 1.5ms sample time
clear_interrupt(INT_TIMER1);
enable_interrupts(INT_TIMER1);
clear_interrupt(INT_IC2QEI);
enable_interrupts(INT_IC2QEI);
enable_interrupts(INT_RDA);
clear_interrupt(INT_RDA);
enable_interrupts(GLOBAL);
if (read_eeprom(0x09) != 0xff) // Ukoliko je drajver podesen moze da se izvrsi kalibracija
{
switch (read_eeprom(0x09))
{
case 100:
pFactor = -1;
dFactor = 1;
break;
case 150:
pFactor = 1;
dFactor = -1;
break;
case 200:
pFactor = -1;
dFactor = 1;
break;
}
//AUTO_FUNCTION = 1; // Komanda za kalibraciju enkodera u ovoj verziji srki ovo inicira
}
}
/**************** Snimanje parametara ***************/
/* Funkcija snima parametre drajvera u EEPROM */
/****************************************************/
void SaveNewParams()
{
write_eeprom(0x01,p.MP0);
write_eeprom(0x02,p.POWER_SUPPLY);
write_eeprom(0x03,p.MOTOR_VOLTAGE);
write_eeprom(0x04,p.ENC_MAX_H);
write_eeprom(0x05,p.ENC_MAX_L);
write_eeprom(0x06,p.MP1);
write_eeprom(0x07,p.GEAR_N);
write_eeprom(0x08,p.GEAR_M);
}
/**************** Automatsko kretanje ***************/
/* Motor se okrece do zadate pozicije pos */
/****************************************************/
void GoToPosition(float pos)
{
newPosition = pos;
AUTO_FUNCTION = 2;
}
void GoToPosition1(float pos)
{
newPosition = pos;
AUTO_FUNCTION = 2;
}
short CRCCheck(byte *data)
{
int i = 0, crc = 0;
for (i=0;i<8;++i)
{
crc ^= data[i];
}
return crc == data[8];
}
void CRCGet(byte *data)
{
int i = 0, crc = 0;
for (i=0;i<8;++i)
{
crc ^= data[i];
}
data[8] = crc;
}
void main()
{
int j = 0;
BYTE f2b[4] = {0, 0, 0, 0};
int16 pwmmm;
delay_ms(100);
Init();
while (TRUE)
{
if (NEW_MESSAGE_RECEIVE)
{
if (CRCCheck(tBuffer))
{
if (tBuffer[1] == p.ID)
{
switch (tBuffer[2])
{
/***************************************/
/* INIT POSITION */
/***************************************/
case 5:
if (tBuffer[3] == 0x01)
{
AUTO_FUNCTION = 1;
}
break;
/***************************************/
/* GO TO POSITION */
/***************************************/
case 4:
GoToPosition1((float)tBuffer[3]);
break;
/***************************************/
/* SET PWM */
/***************************************/
case 1:
pwmmm = tBuffer[3] * 256 + tBuffer[4];
if (pwmmm > 368)
con.PWM = 368;
else
con.PWM = pwmmm;
break;
/***************************************/
/* SET DIRECTION */
/***************************************/
case 2:
if (tBuffer[3] == 0x01)
con.DIRECTION = TRUE;
else
{
if (tBuffer[3] == 0x02)
con.DIRECTION = FALSE;
}
break;
/***************************************/
/* ENABLE DISABLE */
/***************************************/
case 3:
if (tBuffer[3] == 0x01)
con.ENABLE = TRUE;
else
{
if (tBuffer[3] == 0x00)
con.ENABLE = FALSE;
}
break;
/***************************************/
/* GET POSITION */
/***************************************/
case 6:
positionIEEE = position;
toIEEE(&positionIEEE);
for (j = 0; j < 4; j++)
f2b[j] = *((int8*)&positionIEEE + j);
tBuffer[3] = f2b[3];
tBuffer[4] = f2b[2];
tBuffer[5] = f2b[1];
tBuffer[6] = f2b[0];
CRCGet(tBuffer);
break;
}
DataSend(tBuffer);
}
}
NEW_MESSAGE_RECEIVE = FALSE;
}
}
}
|
When I reset the microcontroller everything is fine before I send some data to uart. After couple of packages INT_RDA interrupt stop to work. I masked INT_TIMER1 interrupt and find that it is a problem. But I can not understand what is the problem. When I disable timer interrupt, rda interrupt going fine without errors.
Can somebody check this code above and tell me were I make mistake?
Every suggestion will be appreciated.
Thanks. |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Jun 13, 2012 4:47 pm |
|
|
Get rid of all the lines where you enable/disable interrupts inside the
interrupt routines. That's causing your problem. Never do this in CCS.
Also add the ERRORS parameter to your #use rs232() statement. |
|
![](templates/subSilver/images/spacer.gif) |
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Wed Jun 13, 2012 5:14 pm |
|
|
You're doing far too much in timer1 ISR.
(1) Keep isr's as short as possible, get in, set flags, get out.
(2) Do most of work in main().
(3) There's no need to enable / disable interrupts in ISR, CCS compiler does all the work for you.
(4) You also should not need to control tris, for same reason.
(5) You are enabling RDA interrupt in timer1 ISR.
(6) Avoid maths in ISR, floats take ages.
Reduce code to a few lines which show problem, rather than having to wade through pages of it.
Mike |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19368
|
|
Posted: Thu Jun 14, 2012 1:45 am |
|
|
To re-iterate, ignoring the much too long routine, and the general enable/disables, which are unnecessary, the one that is the killer, is the line:
enable_interrupts(GLOBAL);
in the ISR. This must _never_ _ever_ be done in a PIC. Basically, the standard PIC cannot have interrupts at the same operating 'level' occurring inside themselves. In order to _ensure_ this does not happen, the _hardware_ disables the global interrupt bit, when the interrupt handler is called. There is then a special 'return from interrupt' instruction, which re-enables this bit _after_ the return is executed. Enabling it before the return, _will_ kill the code completely.
It is just worth emphasising, just how 'bad' this is. It really is 'shooting yourself in the foot', to do this.
Best Wishes |
|
![](templates/subSilver/images/spacer.gif) |
mikimtb
Joined: 18 Mar 2012 Posts: 4 Location: Serbia
|
|
Posted: Thu Jun 14, 2012 8:21 am |
|
|
Thanks for support.
I change everything you said, and now it's going better. But again after I don't know, sometimes 20, sometimes 30, sometimes 4 packages UART receive stop to operate.
I knew that ISR has to be as short as possible. But as you can see on source code, I have a couple of sensors connected to the uC.
One of tasks is to have to keep a constant motor current from point A to point B. Because of that I have implemented PID controller. As you know PID controller is working in discrete time domain and in my case it's 1.5ms sample time. In that time I have to read sensors output and calculate PID which is motor pwm. Because of that I have a lot of math in ISR routine. Is there some other way to synchronize the PID and sensors outside ISR routine?
Thanks one more time.
Best regards. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19368
|
|
Posted: Thu Jun 14, 2012 8:57 am |
|
|
First, remember interrupts only respond to signaled events. You can always do the same things in the main code, by testing interrupt bits. On my own PID's, I commonly trigger the maths at the instant the cycle finishes. in the main.
However realistically the first key thing is to get rid of FP. This is _not_ needed for a PID. Microchip have a number of examples, doing all the PID maths, using scaled integers. Typically treating the integer values as 1/256th step values. Your position code is a killer.
Then look at taking your ADC readings _while_ other things are being done.
There are also problems _with_ your ADC readings. You are selecting a channel, then reading it immediately. This will give incorrect readings. The internal capacitor _must_ be connected to the incoming voltage for Tacq, _before_ you start a reading.
As another comment, ADC_CLOCK_INTERNAL, is usually _slower_ than using the right divisor off the master clock, and is not legal for clock rates above 1MHz.....
This is where the commands:
read_adc(ADC_START_ONLY);
read_adc(ADC_READ_ONLY);
adc_done();
come in.
You can (for example):
1) select you next required ADC channel.
2) Perform other operations that take at least Tacq.
3) Perform 'read_adc(ADC_START_ONLY)'.
4) _Immediately select the next channel_. This will be connected to the ADC as soon as the conversion finishes, reducing the need to wait next time.
5) Do other things for the time the ADC takes to perform the reading.
6) If you are sure you have taken longer than than the acquisition time, then perform:
val=read_adc(ADC_READ_ONLY);
If you are not sure, then check adc_done() first.
etc..
The point is that the ADC takes 13 cycles of it's master clock. Typically 2 to 4uSec/cycle for the internal oscillator (selecting master clock/32, gives you instead just 1.6uSec/cycle). Say 3uSec for your current clock, so the conversion takes 39uSec. For the whole of this time, you can be doing other things, which then don't have to be done later. Parallel tasking!....
You still show the enable/disable interrupt code in the interrupt routines. |
|
![](templates/subSilver/images/spacer.gif) |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|