|
|
View previous topic :: View next topic |
Author |
Message |
gunking88
Joined: 18 Jun 2004 Posts: 11 Location: WV
|
how can I make the table lookup faster? thanks |
Posted: Tue Aug 03, 2004 12:35 pm |
|
|
I have been trying to make a controller for DC/DC converter. and I first try the switching frequency at 40kHz, 25us. I would like the duty cycle can be updated at same frequency, which means only 25us to do the A/D and PID calculation. is it possible? even I use the table look up, it looks like still take about 18us to calculate the new duty. Could somebody check my code and give me some suggestions? of I have to update the duty at 50us or 40us, which is enough to do the job. thanks ahead.
have a good day
#include <18F458.h>
#device ADC=8
#use delay(clock=40000000)
#fuses HS,NOWDT
#use fixed_io(c_outputs=PIN_C2)
#BYTE ADCON0=0X1F
///digital PID table a=kp*error,b=kb*error,c=kc*error
signed int16 a[11]={-618,-494,-371,-247,-123,0,124,248,372,495,619};
signed int16 b[11]={-1215,-972,-729,-486,-243,0,243,486,729,972,1215};
signed int16 c[11]={-600,-480,-360,-240,-120,0,120,240,360,480,600};
int PWM_MAX=180;
int PWM_MIN=100;
signed int error=0;
signed int error_1=0;
signed int error_2=0;
int u=0;
int u_1=0;
int temp;
int vref=51; //51 in 6 bits for 4V, output at 12V
//interupt, once newduty start, start to read output voltage and calculate next duty
#int_TIMER2
void TIMER2_irs(void)
{
temp=(read_adc()+2)>>2; //just need 6 bit( 5v--64)
error=vref-temp;//difference between ref and output
if (error>5){error=5;}
if (error<-5){error=-5;}
error=error+5;
// implement digital PID by reading table.
u=u_1+a[error]-b[error]+c[error];
if( u>PWM_MAX)
{u=PWM_MAX;}
if (u<PWM_MIN)
{u=PWM_MIN;}
set_pwm1_duty(u);
u_1=u;
error_1=error;
error_2=error_1;
}
void main()
{
setup_adc_ports(all_analog);
setup_adc(adc_clock_div_64);
setup_timer_2(T2_DIV_by_1,249,1);
setup_ccp1(CCP_PWM);
set_adc_channel(0);
ADCON0|=0X4;
enable_interrupts(INT_TIMER2);
enable_interrupts(global);
while(1);
} |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Tue Aug 03, 2004 2:23 pm |
|
|
Quote: | u=u_1+a[error]-b[error]+c[error]; | Is this your real code or a simplified version of it? Now the index into the three arrays is the same, so you can simplify the calculation by using only a single array instead.
One other thing that takes a lot of time is that the CCS compiler has a generic interrupt dispatcher, taking care of all possible registers you might change in your interrupt routine. This comes at the price of about 100 instructions of overhead on a PIC18, or 10us when running 40MHz. The PIC18 is equiped with two interrupt levels, using the high interrupt will bypass the long CCS code. Just add the keyword FAST to your ISR and add some code yourself for saving and restoring only the registers you use. This will save you at least 5us.
Unrolling the tables is another option for improving speed. This will make your program larger but faster to execute, example for unrolling a single table: Code: |
// a[11]={-618,-494,-371,-247,-123,0,124,248,372,495,619}
// Using a binary sort like algorithm for finding a[error].
if (error < 0)
{
if (error < -2)
{
if (error < -4)
a = -618; // -5
else
{
if (error > -4)
a = -494; // -3
else
a = -371; // -4
}
}
else
{
if (error < -1)
a = -247; // -2
else
a = -123; // -1
}
}
else
{
if (error < 2)
{
if (error < 1)
a = 0; // 0
else
a = 124; // 1
}
else if (error < 4)
{
if (error < 3)
a = 248; // 2
else
a = 372; // 3
}
else if (error == 4)
a = 495; // 4
else
a = 619; // 5
}
|
|
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Tue Aug 03, 2004 3:15 pm |
|
|
I would recomend not running this with interupts.
This is what I would do.
Code: | While(!PWM_Timer_Interupt_Flag_Set)
{ PWM_Timer_Interupt_Flag_Set=0; //Clear the interupt flag
GO_Done=1; //Start an analog reading
u=u+A[Error]-B[Error]+C[Error];
set_pwm1_duty(u);
temp=(read_adc(read_Only)+2)>>2; //just need 6 bit( 5v--64)
error=vref-temp; //difference between ref and output
if (error>5){error=5;}
if (error<-5){error=-5;}
error=error+5;
} |
|
|
|
gunking88
Joined: 18 Jun 2004 Posts: 11 Location: WV
|
yes, here is a mistake... |
Posted: Tue Aug 03, 2004 3:16 pm |
|
|
should be u=u_1+a[error]-b[error_1]+c[error_2], thank you for finding this mistake for me. and I will try what you said.
ckielstra wrote: | Quote: | u=u_1+a[error]-b[error]+c[error]; | Is this your real code or a simplified version of it? Now the index into the three arrays is the same, so you can simplify the calculation by using only a single array instead.
One other thing that takes a lot of time is that the CCS compiler has a generic interrupt dispatcher, taking care of all possible registers you might change in your interrupt routine. This comes at the price of about 100 instructions of overhead on a PIC18, or 10us when running 40MHz. The PIC18 is equiped with two interrupt levels, using the high interrupt will bypass the long CCS code. Just add the keyword FAST to your ISR and add some code yourself for saving and restoring only the registers you use. This will save you at least 5us.
Unrolling the tables is another option for improving speed. This will make your program larger but faster to execute, example for unrolling a single table: Code: |
// a[11]={-618,-494,-371,-247,-123,0,124,248,372,495,619}
// Using a binary sort like algorithm for finding a[error].
if (error < 0)
{
if (error < -2)
{
if (error < -4)
a = -618; // -5
else
{
if (error > -4)
a = -494; // -3
else
a = -371; // -4
}
}
else
{
if (error < -1)
a = -247; // -2
else
a = -123; // -1
}
}
else
{
if (error < 2)
{
if (error < 1)
a = 0; // 0
else
a = 124; // 1
}
else if (error < 4)
{
if (error < 3)
a = 248; // 2
else
a = 372; // 3
}
else if (error == 4)
a = 495; // 4
else
a = 619; // 5
}
|
|
|
|
|
gunking88
Joined: 18 Jun 2004 Posts: 11 Location: WV
|
table lookup+A/D take 40.3us to get a new duty...sigh... |
Posted: Tue Aug 03, 2004 3:21 pm |
|
|
it looks like I have to run at 50us, 20khz.
Neutone wrote: | I would recomend not running this with interupts.
This is what I would do.
Code: | While(!PWM_Timer_Interupt_Flag_Set)
{ PWM_Timer_Interupt_Flag_Set=0; //Clear the interupt flag
GO_Done=1; //Start an analog reading
u=u+A[Error]-B[Error]+C[Error];
set_pwm1_duty(u);
temp=(read_adc(read_Only)+2)>>2; //just need 6 bit( 5v--64)
error=vref-temp; //difference between ref and output
if (error>5){error=5;}
if (error<-5){error=-5;}
error=error+5;
} |
|
|
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Tue Aug 03, 2004 4:54 pm |
|
|
Code: | error_1=error;
error_2=error_1; |
This looks like an error too, most likely it should be: Code: | error_2=error_1;
error_1=error; |
I thought you gave us just a code snippet, but if this is your whole program than I'm sure you can easily run at 40kHz. Just forget about the interrupt routine this gives way too much overhead and working around it gives you a headache, just follow Neutone's hint.
code will then become: Code: |
#include <18F458.h>
#device ADC=8
#use delay(clock=40000000)
#fuses HS,NOWDT,NOLVP
#use fixed_io(c_outputs=PIN_C2)
#BYTE ADCON0=0X0FC2
#BIT TMR2IF=0x0F9E.1
///digital PID table a=kp*error,b=kb*error,c=kc*error
signed int16 a[11]={-618,-494,-371,-247,-123,0,124,248,372,495,619};
signed int16 b[11]={-1215,-972,-729,-486,-243,0,243,486,729,972,1215};
signed int16 c[11]={-600,-480,-360,-240,-120,0,120,240,360,480,600};
int PWM_MAX=180;
int PWM_MIN=100;
signed int error=0;
signed int error_1=0;
signed int error_2=0;
int u=0;
int u_1=0;
int temp;
int vref=51; //51 in 6 bits for 4V, output at 12V
void main()
{
setup_adc_ports(all_analog);
setup_adc(adc_clock_div_64);
setup_timer_2(T2_DIV_by_1,249,1);
setup_ccp1(CCP_PWM);
set_adc_channel(0);
ADCON0|=0X4;
//enable_interrupts(INT_TIMER2); // disabled
//enable_interrupts(global); // disabled
clear_interrupt(INT_TIMER2);
while(1)
{
// Wait for PWM_Timer_interrupt_flag
if (TMR2IF)
{
// Clear PWM_Timer_interrupt_flag
clear_interrupt(INT_TIMER2); // equal to: TMR2IF=0;
// read output voltage and calculate next duty
temp=(read_adc()+2)>>2; //just need 6 bit( 5v--64)
error=vref-temp;//difference between ref and output
if (error>5){error=5;}
if (error<-5){error=-5;}
error=error+5;
// implement digital PID by reading table.
u=u_1+a[error]-b[error_1]+c[error_2];
if (u>PWM_MAX)
{u=PWM_MAX;}
if (u<PWM_MIN)
{u=PWM_MIN;}
set_pwm1_duty(u);
u_1=u;
error_2=error_1; // Switched these two lines
error_1=error; //
}
}
} |
I also changed ADCON0 from 0x1F to 0x0FC2 and added the fuse NOLVP. |
|
|
gunking88
Joined: 18 Jun 2004 Posts: 11 Location: WV
|
thanks for your reply and finding another mistake in side.. |
Posted: Tue Aug 03, 2004 5:39 pm |
|
|
but I checked with break point,
from
error=error+5;
// implement digital PID by reading table.
u=u_1+a[error]-b[error_1]+c[error_2];
if (u>PWM_MAX)
{u=PWM_MAX;}
if (u<PWM_MIN)
{u=PWM_MIN;}
set_pwm1_duty(u);
u_1=u;
it takes about 20us already. plus A/D 19us... seems impossible to run at
40Khz...anyway, thank a lot
ckielstra wrote: | Code: | error_1=error;
error_2=error_1; |
This looks like an error too, most likely it should be: Code: | error_2=error_1;
error_1=error; |
I thought you gave us just a code snippet, but if this is your whole program than I'm sure you can easily run at 40kHz. Just forget about the interrupt routine this gives way too much overhead and working around it gives you a headache, just follow Neutone's hint.
code will then become: Code: |
#include <18F458.h>
#device ADC=8
#use delay(clock=40000000)
#fuses HS,NOWDT,NOLVP
#use fixed_io(c_outputs=PIN_C2)
#BYTE ADCON0=0X0FC2
#BIT TMR2IF=0x0F9E.1
///digital PID table a=kp*error,b=kb*error,c=kc*error
signed int16 a[11]={-618,-494,-371,-247,-123,0,124,248,372,495,619};
signed int16 b[11]={-1215,-972,-729,-486,-243,0,243,486,729,972,1215};
signed int16 c[11]={-600,-480,-360,-240,-120,0,120,240,360,480,600};
int PWM_MAX=180;
int PWM_MIN=100;
signed int error=0;
signed int error_1=0;
signed int error_2=0;
int u=0;
int u_1=0;
int temp;
int vref=51; //51 in 6 bits for 4V, output at 12V
void main()
{
setup_adc_ports(all_analog);
setup_adc(adc_clock_div_64);
setup_timer_2(T2_DIV_by_1,249,1);
setup_ccp1(CCP_PWM);
set_adc_channel(0);
ADCON0|=0X4;
//enable_interrupts(INT_TIMER2); // disabled
//enable_interrupts(global); // disabled
clear_interrupt(INT_TIMER2);
while(1)
{
// Wait for PWM_Timer_interrupt_flag
if (TMR2IF)
{
// Clear PWM_Timer_interrupt_flag
clear_interrupt(INT_TIMER2); // equal to: TMR2IF=0;
// read output voltage and calculate next duty
temp=(read_adc()+2)>>2; //just need 6 bit( 5v--64)
error=vref-temp;//difference between ref and output
if (error>5){error=5;}
if (error<-5){error=-5;}
error=error+5;
// implement digital PID by reading table.
u=u_1+a[error]-b[error_1]+c[error_2];
if (u>PWM_MAX)
{u=PWM_MAX;}
if (u<PWM_MIN)
{u=PWM_MIN;}
set_pwm1_duty(u);
u_1=u;
error_2=error_1; // Switched these two lines
error_1=error; //
}
}
} |
I also changed ADCON0 from 0x1F to 0x0FC2 and added the fuse NOLVP. |
|
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Tue Aug 03, 2004 6:55 pm |
|
|
This will start the ADC measurment
Then you can perform some math while aquiring a new reading and then read the new ADC reading.
Code: | read_adc(read_Only) |
|
|
|
|
|
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
|