This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

PID controlling the PWM duty cycle.

Hi all,

I'm trying to get my DC motor run like a cruise control system, by means of a PID controller. I managed to get it to work, but when I started tuning my PID parameters, I noticed it was having a neglible affect on the PWM signal.

This is what I had at that time in terms of my PID controller.

int
updatePID(int targetRPM, int currentRPM)
{
	int error;
	static int last_error;
	static int integrated_error;

	float pTerm, iTerm, dTerm;

/*	Calculate error and proportional value.*/
	error = targetRPM - currentRPM;
	pTerm = i32Kp * error;

/*	Calculate error and intergral value.*/
	integrated_error += error;
	iTerm = i32Ki * constrain(integrated_error, -1, 1);

/*	Calculate deriviative value and reset error.*/
	dTerm = i32Kd * (error - last_error);
	last_error = error;

/*	Return the PID controlled duty cycle value. */
	return constrain((i32K*(pTerm + dTerm)), -1, 1);

}

Within this function, is the constrain function, which is shown below.

int
constrain(int x, int a, int b)
{
	if(x < a)
	{
		return a;
	}
	if(b < x)
	{
		return b;
	}
	else
	{
		return x;
	}
}

The constrain function was to ensure the dutycycle never exceed 100%, but I quickly noticed this wasn't working as I intended, as the dutycycle is the value of clockticks, thus when configuring the PWM signal to work at a frequency of 24kHz when the system clock is running at 120MHz, the loaded value is 624. The code for this configuration is shown below.

void
InitPWM(void)
{

/*	Enable PWM clock.*/
	PWMClockSet(PWM0_BASE,PWM_SYSCLK_DIV_8);

/*	Configure the GPIO pins.*/
	GPIOPinConfigure(GPIO_PF0_M0PWM0);
	GPIOPinConfigure(GPIO_PF1_M0PWM1);
	GPIOPinConfigure(GPIO_PF2_M0PWM2);
	GPIOPinConfigure(GPIO_PF3_M0PWM3);

/*	Configure the GPIO pins as PWM pins.*/
	GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);

/*	Calculate period and duty cycle.*/
	ui32PWMClockFreq = (ui32SysClkFreq / 8); //(120000000 / 8 = 15000000
	ui32PWMLoad = ((ui32PWMClockFreq / PWM_FREQUENCY) -1); //15000000 / 24000 = 624
	//ui32PWMDutyCycle = ui32PWMLoad;

/*	Configure the PWMs to count-down, and sync with each other.*/
	PWMGenConfigure(PWM0_BASE, PWM_GEN_0, PWM_GEN_MODE_DOWN | PWM_GEN_MODE_SYNC);// | PWM_GEN_MODE_GEN_SYNC_GLOBAL | PWM_GEN_MODE_DB_SYNC_GLOBAL);
	PWMGenConfigure(PWM0_BASE, PWM_GEN_1, PWM_GEN_MODE_DOWN | PWM_GEN_MODE_SYNC);// | PWM_GEN_MODE_GEN_SYNC_GLOBAL | PWM_GEN_MODE_DB_SYNC_GLOBAL);

/*	Set the periods.*/
	PWMGenPeriodSet(PWM0_BASE, PWM_GEN_0, ui32PWMLoad);
	PWMGenPeriodSet(PWM0_BASE, PWM_GEN_1, ui32PWMLoad);

/*	Set the duty-cycle.*/
	PWMPulseWidthSet(PWM0_BASE, PWM_OUT_0, ui32PWMLoad*0.1);
	PWMPulseWidthSet(PWM0_BASE, PWM_OUT_2, ui32PWMLoad*0.1);

/*	Enable the dead-time.*/
	//PWMDeadBandEnable(PWM0_BASE, PWM_GEN_0, deadTime, deadTime); //15 pulses is roughly 1 us.
	//PWMDeadBandEnable(PWM0_BASE, PWM_GEN_1, deadTime, deadTime);

/*	Enable all of the PWM signal outputs.*/
	PWMOutputState(PWM0_BASE, PWM_OUT_0_BIT, true);//PF0 - PWM
	PWMOutputState(PWM0_BASE, PWM_OUT_1_BIT, false);//PF1 - \PWM
	PWMOutputState(PWM0_BASE, PWM_OUT_2_BIT, true);//PF2 - PWM
	PWMOutputState(PWM0_BASE, PWM_OUT_3_BIT, false);//PF3 - \PWM

/*	Enable the sync.*/
	PWMSyncTimeBase(PWM0_BASE, PWM_GEN_0_BIT | PWM_GEN_1_BIT);
	PWMSyncUpdate(PWM0_BASE, PWM_GEN_0_BIT | PWM_GEN_1_BIT);

/*	Enable the PWM gens.*/
	PWMGenEnable(PWM0_BASE, PWM_GEN_0);
	PWMGenEnable(PWM0_BASE, PWM_GEN_1);

}

e
I figured that my tuning was neglible, because I'm sending only a fraction of the dutycycle due to the constrain until -1 and 1 when it should in reality be 0 and 624. However, when I implement these methods, the dutycycle goes crazy as the value it loads to PWMPulseWidthSet() is in the thousands, causing the dutycyle to be a 100% at all times, rendering the whole controller useless.

I figured this was because I was dealing with the unsigned vs. signed values incorrectly, therefore I changed it once to allow for larger numbers and make them all signed, but this still provided crazy numbers.

What I dont understand is that my constrain function should prevent this from happening, and even though the dutycycle wants to be 10,000 because I set my Kp, Ki, Kd values really high, it should still only return the value between 0 and 624 due to the constrain function.

Anybody know what might be causing this irregularity in the numbers?

  • Getting the "D" portion of any "PID" controller - under full/proper control - usually proves the most challenging task.   And - for many, many tasks - simpler, "PI" (only) control mechanisms prove precise & robust.

    Thus - suggest that you (at least temporarily) abandon the "D" element & focus upon "P & I."

    I can report that our "PI" control loop - operating 1KW rated, BLDC motors - achieves quite excellent speed & current control - much (most) of that thanks to the PI contribution...   

    So that I'm not (at least today) guilty of the always delightful, "Does not work" (or in my case "quite excellent speed/current control") here's our IAR IDE "screen cap" of one of our BLDC motors - targeted to run @ 12,264 RPM - and (thanks to the PI loop) reaching 12,251 RPM.   A 1% speed error would yield a 122 RPM difference in speed - note that I'm (off) by just 13 RPM here - that's ten times better than 1% - and achieved (just) w/ "P & I"! 

    Thus - believe the claim of, "quite excellent" has been documented!    (this was achieved w/vendors past LX4F MCU.

    Just prior to this data cap. - I bumped the load - the yellow highlights (to the right of this chart) denote key data for your review.   Toward the bottom appear 4 "PI loop" live values - you can see that the "PID" error is composed (solely) of the algebraic sum of P & I.    And when this cap occurred - our Target Speed to Actual Speed was "off" by 175RPM - which the PI loop quickly (and effectively) corrected...   Note that I've "zero'ed" the "D portion" of the PID loop! 

    You may note that we achieved 12K+ RPM w/just a 28% duty cycle and only 1.35 Amps reported.   (these motors will reach beyond 25K RPM - and draw substantial currents - when under heavy load)    Under such loads - especially when loads change - the PI loop performs admirably...

  • Hi James,

    Please note that your "updatePID" function returns one of 3 values: -1, 0, +1.  I doubt that is what you intended!  Perhaps the +/-1 to the constrain function should be scaled up a bit?

    Regards,

    Dave

  • cb1- said:
    Thus - suggest that you (at least temporarily) abandon the "D" element & focus upon "P & I."

    Absolutely agree.

    Tune with straight P, then add I to bring the error down.

    The D term is to increase response speed.  Only use if necessary.  It is sensitive to noise on the input.

    Robert

  • OMG - can it be - a (heralded) Source-Two sighting!    (and one (likely) to chastise this reporter for failure to review an "early stage" code dump - which clearly violates KISS!)   (i.e. never/ever "start" w/"D" enabled)

    Source-two's presence here - as rare as, "Defanged Tiger's Canine!"

    As friend Robert so well states - "D" has (long) been known to make strong men cry - eat time/money/effort!   And - unless you're extremely capable w/noise management - that "D" will "run wild" - best to hold in abeyance until (and if) you reach "reasonable" motor control w/simple "P&I."    (does not the demonstrated 0.1% speed control (shown few up) - with NO "D" - make the case for, "P & I" alone?)    Ans: (certainement, mon ami...)

  • Thanks for your detailed reply cb1-! I'll have to have a go at simply tinkering with the P&I values for now then, and see what I come up with.

    On a side note though, I'm still a little confused as to why my dutycycle is able to go into the thousands, when I set it to constrain to larger numbers. For example, when I set my constrain function as -1 and 1, the dutycyle (the value sent to PWMPulseWidthSet()) never really exceeds the maximum, it allows for pretty decent control of the speed with a little oscillation.

    However, as as SourceTwo mentioned, that when I scale up the constrain function to values that would accurately represent a constrain on my dutycycle (in my case, the minimum is 0 and the maximum is 624, as my dutycycle has a width of 624 clock ticks) it sends the dutycycle value into the thousands, far exceeding the maximum that my PWMPulseWidthSet() can take, rendering my whole control loop useless.

    I suspect the huge increase in numbers is because my ui32PWMDutyCycle number in my main() is being changed at a much greater rate (because of the 0, 624 vs. the -1, 1), and therefore it can quite easily reach something in the thousands, but my constrain function should ensure that it never reaches this number, because it should be at a maximum of 624!

    Surely this shouldn't be the case, even for a P, PI, PD, or PID controller.
  • Feel your pain - yet this appears very much a program issue or an imperfect code design.    And neither constitute a "real" TM4C MCU issue.

    As the PWM generators (usually) support up to 16 bits - your constraining PWM values to < 1,000 - seems sub-optimal.     In our design we strive to use "all" of the PWM range we can harvest - so that the highest, "motor control resolution" may be achieved.

    Do keep in mind that (usually) the best control results from regular/repeated (and precise) short interval measurements - and "correcting adjustments."    Those corrections should best match the capabilities of your MCU if you seek best/brightest control performance.     (perhaps your sub 1,000 (max) value is not the best choice - and you've not described how that number was chosen.)    

    For the record - we constrain our critical P&I terms too - but our upper limit is orders of magnitude above yours.    And lower limit cannot go negative.

    Suggest that you "play" with "dummy" variables - no motor connected - and step your program so that you can observe the exact "time/place" in which your program code, "Goes nutz!"   

    Again - that "D" term proves (too often) deadly - avoid until you've achieved good control with "P&I."

  • Yeah, I suspect I'm not handling the numbers between the float values generated by my PID function vs. the integers used to set the PWM dutycycle, and then it makes it go all bonkers.

    Anyways, on a quick note, the reasoning behind the numbers I chose is due to how the PWMPulseWidthSet() function works. The amount the pulse width is set by, is in clock ticks. So you work out the clock frequency, then the PWM frequency, divide them by each other and subtract one, and that'll be the amount of clock ticks 100% dutycycle is. This number is then played with to give yourself the 0-100% dutycycle, which is why I set up my limiter to be between 0 and 620 (0% - ~99%).

    I shall take your advice and start to trial and error it a bit more, to see if I can avoid this value I load into PWMPulseWidthSet() from, as you and General Patton once said, going "NUTS!"
  • If ~600 clock ticks yields (your) 100% duty cycle (btw - beware that!) then your PWM frequency (provided your system clock is near ours) is considerably beyond our 20KHz PWM frequency choice. (20KHz is often chosen as its just above "normal" human hearing response)

    Now our system clock is 50MHz - thus our max "load value" (for near 100% PWM) is (2500 - safety factor.)

    My earlier question was (not so much the "how" of PWM load value) but instead "why" you appear to require so HIGH a PWM frequency? (which that "so low" ~600 load value strongly suggests.)

    Be aware that most all ARM based MCUs (not just those from this vendor) appear not well equipped to handle "extreme" PWM settings! And - the real world has taught us - that little (difference nor benefit) accrues between 98% and 100% duty.    Such may not hold true at the bottom end (0% duty) and in that case you may "defeat" ghost PWM output via a "circuit or program" PWM override...

  • I am pretty sure that the Tiva timers can't generate 100% nor 0% duty, so you should check if the PWM module can.

    Isn't the maximum PWM frequency also capped by the driver used? A really high frequency when using a MOSFET could require more current than your PWM module can source. Or you could be trying to switch your drivers can allow.
    Correct me if I said anything stupid cb1 but I have the idea that non FET based drivers have a maximum switching speed (while FETs depend almost only on gate char and discharge current).
  • Luis - any "serious" PWM signal drive "most always" impose a proper, "Gate Driver IC" between the MCU and Power FETs. Our firm (as you know) currently employs Power FETs with an Rds(ON): 0.75mOhm @ 100A, 10V!    Device can supply 195A continuous drain current @ 25°C.   Never could an MCU directly drive!
     
    Normal/customary Gate Driver ICs - even those w/"charge pumps" seem unbothered by PWM frequencies up to 30KHz. (that's all we've (yet) tested to)

  • Luis Afonso said:
    Isn't the maximum PWM frequency also capped by the driver used?

    Yes, also the power devices, layout and EMI considerations.

    Agree with cb1 that 20kHz is a good target for audio reasons. Only require faster for really low inductance motors and there is no power advantage to being faster.

    Luis Afonso said:
    Correct me if I said anything stupid cb1 but I have the idea that non FET based drivers have a maximum switching speed (while FETs depend almost only on gate char and discharge current).

    Not stupid, just not quite that simple. Even with high power drivers you want to limit the turn-on/turn-off time and the devices themselves will have turn-on/turn-off limitations.

    Layout for high speed switching takes considerable care.

    Robert 

  • Robert Adsett said:
    Even with high power drivers you want to limit the turn-on/turn-off time

    "Small World Department" - here - now!

    Robert - Luis is aware that my small firm is & has been deeply involved in the design/development of a BLDC Controller series for small, yet extremely high-torque, BLDC motors.    And these motors are battery powered - with 18VDC (currently) the highest battery voltage available - and under heavy load may draw in excess of 75 Amps!

    Thus the points you make are totally reflective of our findings.    At such high currents "every aspect" of the board design/layout proves suspect - in need of (near constant tweaks) to achieve "any" chance of optimization.  

    Most always - when firm/myself have realized success - great data harvest (and critical analysis) led the way.    I've had to implement multiple, original techniques - to monitor the key motor drive signals & operating levels - at all motor speeds, loads and currents.    Multiple (180MHz & up) ARM MCUs - receive over 20 channels of "buffered & instrumented" analog data - recovered by "pro" dedicated ADCs - rated beyond 5MSPS.    And such has "caught" rogue signals/levels - which had not been captured nor noted - on fast, multi-channel, DSO.    (now both have their place - yet "wide channel, high-speed, Sync'ed data capture provides great - and needed tech insights - which (even a very good scope) thus far - has missed...)

    We've moved from, "PWM's PID suspect" - perhaps w/some justification - unknown if (something) here alerts o.p...

  • cb1- said:
    "Small World Department" - here - now!

    I have a similar background.  I expect you are a little more up to date on the current crop of MOSFETs but some years ago I worked on motor drives as well.  DC rather than AC and higher voltage and current.

    That company is still using a micro that went out of production about a decade ago

    cb1- said:
    We've moved from, "PWM's PID suspect" - perhaps w/some justification - unknown if (something) here alerts o.p...

    I think so.  "Here be dragons" is an appropriate warning to take care of the unknown/not yet experienced when you see someone entering Terra Incognita.  Even if the OP is aware of the dangers lurking many are not and may at least see the warning.

    Robert

  • Well said - and your, "Here be Dragons" surely earms poster BP101's past, "We be noticing" full, urban-linguistic approval.