View previous topic :: View next topic |
Author |
Message |
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
PID issues - with Code |
Posted: Tue Jan 16, 2024 4:35 pm |
|
|
Hi all, its been a while.
after 20 years of PICs its time for my first PID proyect...
I got 2 small 12v brushed DC motores with gear reductions and magnetic encoders on the back:
https://www.amazon.com/uxcell-Motor-Encoder-201RPM-Ratio/dp/B0792SBB5T
...driven by the classic L298 red PCB available under any rock.
Im using one motor as output (plant) and one as input by simply spining the encoder manually.
the goal is to be able to mirror the input to the output.
input encoder goes to 200 steps, the PID driven motor goes to 200 steps.
"simple", right.
there is no load on my output motor, just a plastic arrow for visual confirmation.
I did a lot of testing and got the best PWM response at a low frequency. lowest i was able to get was 133Hz which gave me a PWM range of 50 to 255 Duty.
anything below 50 the motor would not start, just vibrate.
while spinning it would go down to 25(duty).
PROBLEMS:
1) I would like to bring the PWM frequency lower. possibly 100 or 60Hz as it seems to continue to improve response. i tested using the 32MHz internal clock but was not able to get it working (the PWM) reliably... but in the moments it did work, i did get well below the previous 50 duty limit at 133hz.
2) I have crazy jitter/chatter on my motor when near the setpoint.
ive adjusted K gains as much as any sane person could... and get close to no improvement... probably worse.
any help going over the logic of my code would be awesome... maybe i have some bug i cant see from clawing my eyes out with this.
3) Having a faster loop time (calls to Compute()) seems to improve things. makes sense. BUT because im bringing the clock down to get lower PWM frequencies im stuck in this push pull scenario where fixing something breaks another...
current code below has me at a jittery medium... faster loop, not so low PWM which limits my duty 100-255.... kinda works.
help?
CODE:
Code: |
#include <18F67K40.h>
#device PASS_STRINGS=IN_RAM
#device adc=10
#fuses MCLR
#fuses NOWDT
#fuses NOLVP
#fuses NOXINST
#fuses NODEBUG
#fuses NOPROTECT
#fuses HS
#fuses RSTOSC_EXT_PLL
#use delay(clock=32MHz)
#include <string.h>
#include <stdlib.h>
#include <Hardware - AZ.h>
#INT_EXT
void Encoder_1();
#INT_EXT1
void Encoder_2();
#INT_TIMER2
void Timer_2_ISR();
void Set_Motor_1(int,int);
void Compute_PID();
signed int32 Control_One_Position=0; //setpoint or...
signed int32 Motor_One_Position=0;
signed int32 Set_Point=0; //Control_one_positon depending on what im doing...
signed int32 Last_Set_Point=0;
float Kp=10; //the guessing game #1
float Ki=0; //the guessing game #2
float Kd=1; //the guessing game #3
float Interval=0.0082; //Timer 2 is calling Compute() at about 8.2ms as per O-Scope
signed int32 Error=0;
signed int32 Error_Prev=0;
float Integral=0;
float Derivative=0;
float Output=0;
int16 M1_Duty=0;
int Current_Direction=0;
//==========================
void main()
{
setup_oscillator(OSC_EXTOSC_PLL|OSC_CLK_DIV_BY_2|OSC_EXTOSC_ENABLED|OSC_EXTOSC_READY);
setup_timer_2(T2_DIV_BY_128|T2_CLK_INTERNAL,255,2);
delay_ms(1000); //Stabilize CLK
setup_ccp1(CCP_PWM); // Configure CCP1 as a PWM
setup_pwm6(PWM_ENABLED|PWM_ACTIVE_HIGH|PWM_TIMER2); //Uses timer 2
enable_interrupts(INT_TIMER2); //Calls PID Compute at regular intervals
enable_interrupts(INT_EXT); //Motor Encoder
enable_interrupts(INT_EXT1); //Control Encoder
ext_int_edge(0, INT_EXT_L2H); //Encoder interrupts Edges
ext_int_edge(0, INT_EXT1_L2H); //Encoder2 interrupts Edges
enable_interrupts(GLOBAL);
fprintf(FTDI,"\fSTART\r\n"); //just to know things are running...
delay_ms(1000); //to be able to read the start message.
while(TRUE)
{
fprintf(FTDI,"%03Ld,%03Ld,%03Ld,%3.2f,%3.2f,%lu,Dir:%d\r\n",Set_Point,Motor_One_Position,Error,Integral,Derivative,M1_Duty,Current_Direction);
/*
//FOR PWM TESTING
fprintf(FTDI,"Position:%Lu - PWM:%Lu\r\n",Motor_One_Position, M1_Duty);
Set_Motor_1(1,M1_Duty);
delay_ms(1000);
if(Flag==1)
M1_Duty+=5;
else
M1_Duty-=5;
if(M1_Duty==250)Flag=0;
if(M1_Duty==25)Flag=1;
*/
//fprintf(FTDI,"Position:%04Ld\r",Control_One_Position);
}
}
void Compute_PID()
{
output_high(PIN_H2); //set pins to see call frequency and execution time.
//PROPORTINAL
Error=Set_Point-Motor_One_Position;
//INTEGRAL
Integral+=(Error*Interval);
if(Integral>255)Integral=255; //Cap Integral at 255 to prevent windup
else if(Integral<-255)Integral=-255;
//DERIVATIVE
Derivative=(Error-Error_Prev)/Interval;
if(Derivative>255)Derivative=255; //Cap Derivative at 255 --delete??
else if(Derivative<-255)Derivative=-255;
//COMPUTE OUTPUT
Output=(Kp*Error)+(Ki*Integral)+(Kd*Derivative);
M1_Duty=abs(Output);
if(M1_Duty>255)M1_Duty=255; //ensure duty is within values that actually cause the motor to spin
if(M1_Duty<50)M1_Duty=50;
if(Error==0) //added this because otherwise it never stops jittering
Set_Motor_1(0,M1_Duty); //dispair solution, dont judge.
else if(Output<0) //control the motor This way
Set_Motor_1(-1,M1_Duty);
else //control the motor That way
Set_Motor_1(1,M1_Duty);
Error_Prev=Error;
output_low(PIN_H2); //set pins to see call frequency and execution time.
}
void Set_Motor_1(int Dir,int Duty) //controls a L298 H-bridge board.
{
set_pwm1_duty(Duty); //Change PWM Duty
if(Dir!=Current_Direction) //figured it was a good idea to bring both Ctrl lines low
{ //before changing direction on the L298
output_low(M1_FWD); //avoid any timing or whatever pin switching mishappenings.
output_low(M1_RWD);
}
if(Dir==1)
{
output_high(M1_FWD);
output_low(M1_RWD);
}
else if(Dir==-1)
{
output_low(M1_FWD);
output_high(M1_RWD);
}
else
{
output_low(M1_FWD);
output_low(M1_RWD);
Duty=0;
}
Current_Direction=Dir;
}
//encoder reading stuff.
#INT_EXT
void Encoder_1()
{
if(input(ENC_B)==TRUE)
Motor_One_Position++;
else Motor_One_Position--;
}
#INT_EXT1
void Encoder_2()
{
if(input(CTRL_B)==TRUE)
Set_Point+=10; //since im spining this encoder by hand ive added a multiplier to make changes bigger.
//Control_One_Position++;
else //Control_One_Position--;
Set_Point-=10;
}
#INT_TIMER2
void Timer_2_ISR()
{
Compute_PID();
}
|
there is a re-entrancy warning on the timer 2 ISR.
I know, i need help with that too.
i would like to eventually move this to the 18F47Q43 or Q84 - i have both so if pic choice makes a diference, let me know!
thanks G. _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1908
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19529
|
|
Posted: Wed Jan 17, 2024 2:12 am |
|
|
Several things.
First the L298, is not a great driver. It introduces a lot of voltage drop
(typically about 2v). To drive a motor well, you need a good driver that
allows narrow PWM pulses at full power.
Second, your PID loop should not use floating point maths. The PID
calculation wants to be quick. Look at using scaled integer maths. This
has been discussed here before. This is your main problem. The sheer
time needed by the loop means the motor has started moving the wrong
way before the loop corrects. So oscillation.
Then voltage. Now it sounds odd here, this is a 12v motor, but the way
to drive a motor slower with PWM, is to use a much higher supply voltage!.
You have to setup the PWM and controller to limit the maximum current
at the maximum the motor is rated for, but with any inductive load, the
speed the current rises in the coil when power is applied, is limited by
the supply voltage. Apply a really narrow pulse, and it takes time for the
current to build. I have an industrial controller here using 24v servos. The
supply voltage is 150v DC!...
Chattering is a sign your servo parameter are not correctly tuned. Some
research online will find a lot of posts about how best to perform this
tuning, and even programs to help with this. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9232 Location: Greensville,Ontario
|
|
Posted: Wed Jan 17, 2024 6:57 am |
|
|
also...
be sure to use a power supply with LOTS of AMPS !
While the 298 driver is rated for 2 amps per unit, you need more than a simple 4 amp PSU
Depending on layout, wiring, connections,oad, etc. the motor might end up 'current starved'.
In the 'dinosaur days' I'd use a linear supply good for 5x the 'needed' current for the servomotors. Maybe 'overkill' but never ,ever had a servo not perform properly. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19529
|
|
Posted: Wed Jan 17, 2024 7:52 am |
|
|
and remember that the trap diodes across the bridge elements are
essential, but that thee only work if the supply is capable of sinking
this current, without it's output voltage shooting up...... |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Wed Jan 17, 2024 4:17 pm |
|
|
Im gonna run this again with a 24V psu:
https://www.mouser.com/ProductDetail/MEAN-WELL/SE-1000-24?qs=4Ewz1atfbqKd24YTAQMbSQ%3D%3D
The one on the link to be specific... should be enough to run a small 12v motor.
If that doesnt cut it i dont know what will...
Im reading the PID without PHD doc...
I know L298 is dated... availability and ease of use, available resources, etc made it a good choice for me. Plenty tutorials using it... _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Wed Jan 17, 2024 6:04 pm |
|
|
higher/better power supply did not bring any changes... it settles a bit faster, and the motor gets a bit warm when oscilating.
im having the same behaviour, just faster jejejeje... _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19529
|
|
Posted: Thu Jan 18, 2024 5:50 am |
|
|
Which means the parameters of the PID are set wrong. You are
over-correcting for the initial error and then not damping enough. |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Thu Jan 18, 2024 6:16 am |
|
|
Ive played for hours with the P/D gains... keeping Integral at 0.
Ajusting loop times too...when semi acceptable peformance is achieved i add some integral... more.hours... no improvement.
Is my math right?
This cant be this dificult...
I believe i need to lower my PWM frequency for finer adjustments.
With higher voltages my PWM DUTY range narrowed 30 to 100 duty vs 45ish to 255.
Up my loop time to as high as possible
i want to try and use the 32mhz internal clock to drive the PWM/timer2 and use the ext osc with pll at full blast to run the code.
I dont know if this is possible... datasheet reading time.
This should give me lower PWM and higher loop times. _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Thu Jan 18, 2024 6:51 am |
|
|
PWM has to run from fosc/4... so im at the lowest feasable PWM freq. Which impacts my execution time clock so, slower loop times too.
I know its possible to go lower with other osc configurations but im trying to keep loop times fast. _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
gaugeguy
Joined: 05 Apr 2011 Posts: 303
|
|
Posted: Thu Jan 18, 2024 7:22 am |
|
|
PID aside, you need to test your hardware.
Using direct PWM drive, what is the smallest rotation you can reliably move the motor. Is it less than one encoder count?
As you have already found the motor requires more power to start turning than to keep turning.
Your PID cannot work better than your hardware is capable of. |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Thu Jan 18, 2024 7:33 am |
|
|
That makes sense... i may be atributing super powers to PID.
However i see in youtube plenty people reaching precision with similar hardware.
How can i test this?
Should i set this to accept values within lets say 5% of target?...
So if im trying to get 100 encoder counts anything within 95 to 105 encoder counts is taken as a succesfull setpoint reach? Is that the suggestion/fix? _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
gaugeguy
Joined: 05 Apr 2011 Posts: 303
|
|
Posted: Thu Jan 18, 2024 7:53 am |
|
|
Regarding PID, what is your PID calculation loop time? Your sample time? |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Thu Jan 18, 2024 8:25 am |
|
|
i was calling my compute function within the TMR2 ISR which was executing at 8.2ms more or less.
othertimes in dispair i would call Compute_PID() from within the main loop and nothing else... and that would give me about 250us
the sample time i have not measured... its interrupt driven with the Encoder pulses EXT_INT.
I tried "slowing" the encoder by having the encoder mark 2 pulses as 1 step. this would make the motor travel twice as far obviously... but i figured it would give the PID more time to react to the measurements...
I could try again. _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Sat Jan 20, 2024 7:46 am |
|
|
Ive added a conditional to the Error calculation.
Basically if im with 0 to 2 counts away from the setpoint just make error =0.
Good enough = 0 error jajajaj
So basically starting from 0 if i setpoint =100 my motor goes to 99 or 101... im getting about 1 count error...
I also upped my loop time to 4.1ms
And increased my min output to 80 instead of 50 duty.
No integral and a bit of derivative damping and its smooth as butter now.
Ill be testing repeatability and maybe some drift compensation if any (adding back that missed count)... _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
|