Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

FastPID

A fast 32-bit fixed-point PID controller for Arduino

About

This PID controller is faster than alternatives for Arduino becuase it avoids expensive floating point operations. The PID controller is configured with floating point coefficients and translates them to fixed point internally. This imposes limitations on the domain of the coefficients. Setting the I and D terms to zero makes the controller run faster. The controller is configured to run at a fixed frequency and calling code is responsible for running at that frequency. The Ki and Kd parameters are scaled by the frequency to save time during the step() operation.

Description of Coefficients

  • Kp - P term of the PID controller.
  • Ki - I term of the PID controller.
  • Kd - D term of the PID controller.
  • Hz - The execution frequency of the controller.

Coefficient Domain

The computation pipeline expects 16 bit coefficients. This is controlled by PARAM_BITS and should not be changed or caluclations may overflow. The number of bits before and after the decimal place is controlled by PARAM_SHIFT in FastPID.h. The default value for PARAM_SHIFT is 8 and can be changed to suit your application.

  • The parameter P domain is [0.00390625 to 255] inclusive.
  • The parameter I domain is P / Hz
  • The parameter D domain is P * Hz

The controller checks for parameter domain violations and won't operate if a coefficient is outside of the range. All of the configuration operations return bool to alert the user of an error. The err() function checks the error condition. Errors can be cleared with the clear() function.

Execution Frequency

The execution frequency is not automatically detected as of version v1.1.0 This greatly improves the controller performance. Instead the Ki and Kd terms are scaled in the configuration step. It's essential to call step() at the rate that you specify.

Input and Output

The input and the setpoint are an int16_t this matches the width of Analog pins and accomodate negative readings and setpoints. The output of the PID is an int16_t. The actual bit-width and signedness of the output can be configured.

  • bits - The output width will be limited to values inside of this bit range. Valid values are 1 through 16
  • sign If true the output range is [-2^(bits-1), -2^(bits-1)-1]. If false output range is [0, 2^(bits-1)-1]. The maximum output value of the controller is 32767 (even in 16 bit unsigned mode)

Performance

FastPID performance varies depending on the coefficients. When a coefficient is zero less calculation is done. The controller was benchmarked using an Arduino UNO and the code below.

KpKiKdStep Time (uS)
0.10.50.1~64
0.10.50~56
0.100~28

For comparison the excellent ArduinoPID library takes an average of about 90-100 uS per step with all non-zero coefficients.

API

The API strives to be simple and clear. I won't implment functions in the controller that would be better implemented outside of the controller.

FastPID()

Construct a default controller. The default coefficients are all zero. Do not use a default-constructed controller until after you've called setCoefficients() and setOutputconfig()

FastPID(float kp, float ki, float kd, float hz, int bits=16, bool sign=false)

Construct a controller that's ready to go. Calls the following:

configure(kp, ki, kd, hz, bits, sign);
boolsetCoefficients(float kp, float ki, float kd, float hz);

Set the PID coefficients. The coefficients ki and kd are scaled by the value of hz. The hz value informs the PID of the rate you will call step(). Calling code is responsible for calling step() at the rate in hz. Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputConfig(int bits, bool sign);

Set the ouptut configuration by bits/sign. The ouput range will be:

For signed equal to true

  • 2^(n-1) - 1 down to -2^(n-1)

For signed equal to false

  • 2^n-1 down to 0

Bits equals 16 is a special case. When bits is 16 and sign is false the output range is

  • 32767 down to 0

Returns false if a configuration error has occured. Which could be from a previous call.

boolsetOutputRange(int16_t min, int16_t max);

Set the ouptut range directly. The effective range is:

  • Min: -32768 to 32766
  • Max: -32767 to 32767

Min must be greater than max.

Returns false if a configuration error has occured. Which could be from a previous call.

voidclear();

Reset the controller. This should be done before changing the configuration in any way.

boolconfigure(float kp, float ki, float kd, float hz, int bits=16, bool sign=false);

Bulk configure the controller. Equivalent to:

clear();
setCoefficients(kp, ki, kd, hz);
setOutputConfig(bits, sign);
int16_tstep(int16_t sp, int16_t fb);

Run a single step of the controller and return the next output.

boolerr();

Test for a confiuration error. The controller will not run if this function returns true.

Integeral Windup

Applications that control slow moving systems and have a non-zero integral term often see significant overshoot on startup. This is caused by the integral sum "winidng up" as it remembers a long time away from the setpoint. If this describes your system there are two things you can do.

Addressing Windup: Limit the Sum

There are constants in FastPID.h that control the maximum allowable integral. Lowering these prevents the controller from remembering as much offset from the setpoint and will reduce the overshoot.

#defineINTEG_MAX (INT32_MAX)
#defineINTEG_MIN (INT32_MIN)

Change these constants with caution. Setting them too low will fix your overshoot problem but it will negatively affect the controller's ability to regulate the load. If you're unsure of the right constant use the next solution instead of limiting the sum.

Limiting Windup: Bounded Regulation

The PID controller works best when the system is close to the setpoint. During the startup phase, or in the case of a significant excursion, you can disable PID control entirely. An example of this can be found in the Sous-Vide controller example in this project. The core of the logic is in this code:

if (feedback < (setpoint * 0.9)) {
analogWrite(PIN_OUTPUT, 1);
myPID.clear();
}
else {
analogWrite(PIN_OUTPUT, myPID.step(setpoint, feedback));
}

The code bypasses the PID when the temperature is less than 90% of the setpoint, simply turning the heater on. When the temperature is above 90% of the setpoint the PID is enabled. Fixing your overshoot this way gives you much better control of your system without having to add complex, invalid and difficult to understand features to the PID controller.

Sample Code

#include<FastPID.h>
#definePIN_INPUTA0
#definePIN_SETPOINTA1
#definePIN_OUTPUT9float Kp=0.1, Ki=0.5, Kd=0.1, Hz=10;
int output_bits = 8;
bool output_signed = false;
FastPID myPID(Kp, Ki, Kd, Hz, output_bits, output_signed);
voidsetup()
{
Serial.begin(9600);
if (myPID.err()) {
Serial.println("There is a configuration error!");
for (;;) {}
}
}
voidloop()
{
int setpoint = analogRead(PIN_SETPOINT) / 2; int feedback = analogRead(PIN_INPUT);
int ts = micros();
uint8_t output = myPID.step(setpoint, feedback);
int tss = micros();
analogWrite(PIN_OUTPUT, output);
Serial.print("(Fast) micros: "); Serial.print(tss - ts);
Serial.print(" sp: "); Serial.print(setpoint); Serial.print(" fb: "); Serial.print(feedback);
Serial.print(" out: ");
Serial.println(output);
delay(100);
}

About

A fast, integer based PID controller suitable for Arduino.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages