[{"data":1,"prerenderedAt":788},["ShallowReactive",2],{"docs-\u002Fdocs\u002Fmodules\u002Fpid":3},{"id":4,"title":5,"body":6,"description":781,"extension":782,"meta":783,"navigation":69,"path":784,"seo":785,"stem":786,"__hash__":787},"content\u002Fdocs\u002Fmodules\u002Fpid.md","PID Module",{"type":7,"value":8,"toc":770},"minimark",[9,13,22,25,30,53,140,144,147,155,158,162,167,495,499,568,572,687,691,727,731,766],[10,11,5],"h1",{"id":12},"pid-module",[14,15,16,17,21],"p",{},"Run a closed-loop proportional-integral-derivative controller entirely on the firmware. The device samples an analog input pin, computes the PID output, and writes it to a PWM output pin — every ",[18,19,20],"code",{},"_intervalMs"," (default 100 ms). The host sets gains and target setpoint over the wire and can listen for tick events.",[14,23,24],{},"This is the right module for: motor speed control, temperature regulation with a heater + thermistor, light leveling, anything where you need a fast inner loop without round-tripping through the host.",[26,27,29],"h2",{"id":28},"firmware-setup","Firmware setup",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-ini shiki shiki-themes github-light github-dark","; platformio.ini\nbuild_flags = -DCONDUYT_MODULE_PID\n","ini","",[18,38,39,47],{"__ignoreMap":36},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,46],{},"; platformio.ini\n",[40,48,50],{"class":42,"line":49},2,[40,51,52],{},"build_flags = -DCONDUYT_MODULE_PID\n",[31,54,58],{"className":55,"code":56,"language":57,"meta":36,"style":36},"language-cpp shiki shiki-themes github-light github-dark","#include \u003CConduyt.h>\n\nConduytSerial transport(Serial, 115200);\nConduytDevice device(\"PIDBot\", \"1.0.0\", transport);\n\nvoid setup() {\n  Serial.begin(115200);\n  device.addModule(new ConduytModulePID());\n  device.begin();\n}\n\nvoid loop() {\n  device.poll();\n}\n","cpp",[18,59,60,65,71,77,83,88,94,100,106,112,118,123,129,135],{"__ignoreMap":36},[40,61,62],{"class":42,"line":43},[40,63,64],{},"#include \u003CConduyt.h>\n",[40,66,67],{"class":42,"line":49},[40,68,70],{"emptyLinePlaceholder":69},true,"\n",[40,72,74],{"class":42,"line":73},3,[40,75,76],{},"ConduytSerial transport(Serial, 115200);\n",[40,78,80],{"class":42,"line":79},4,[40,81,82],{},"ConduytDevice device(\"PIDBot\", \"1.0.0\", transport);\n",[40,84,86],{"class":42,"line":85},5,[40,87,70],{"emptyLinePlaceholder":69},[40,89,91],{"class":42,"line":90},6,[40,92,93],{},"void setup() {\n",[40,95,97],{"class":42,"line":96},7,[40,98,99],{},"  Serial.begin(115200);\n",[40,101,103],{"class":42,"line":102},8,[40,104,105],{},"  device.addModule(new ConduytModulePID());\n",[40,107,109],{"class":42,"line":108},9,[40,110,111],{},"  device.begin();\n",[40,113,115],{"class":42,"line":114},10,[40,116,117],{},"}\n",[40,119,121],{"class":42,"line":120},11,[40,122,70],{"emptyLinePlaceholder":69},[40,124,126],{"class":42,"line":125},12,[40,127,128],{},"void loop() {\n",[40,130,132],{"class":42,"line":131},13,[40,133,134],{},"  device.poll();\n",[40,136,138],{"class":42,"line":137},14,[40,139,117],{},[26,141,143],{"id":142},"wiring","Wiring",[14,145,146],{},"Anything with one analog input + one PWM output:",[31,148,153],{"className":149,"code":151,"language":152},[150],"language-text","Sensor (e.g. potentiometer)   Board\n──────                        ─────\nWiper                       ──► Analog pin (e.g. A0)\n+ end                       ──► 3V3 \u002F 5V\n- end                       ──► GND\n\nActuator (e.g. LED through resistor)\n                            ──► PWM pin (e.g. 5) → resistor → load → GND\n","text",[18,154,151],{"__ignoreMap":36},[14,156,157],{},"For a real plant — heater, motor, or anything inductive — drive the PWM pin into a transistor or H-bridge, never the load directly.",[26,159,161],{"id":160},"host-usage","Host usage",[163,164,166],"h3",{"id":165},"javascript","JavaScript",[31,168,171],{"className":169,"code":170,"language":165,"meta":36,"style":36},"language-javascript shiki shiki-themes github-light github-dark","import { ConduytDevice } from 'conduyt-js'\nimport { SerialTransport } from 'conduyt-js\u002Ftransports\u002Fserial'\nimport { ConduytPID } from 'conduyt-js\u002Fmodules\u002Fpid'\n\nconst device = await ConduytDevice.connect(new SerialTransport({ path: '\u003CYOUR_PORT>' }))\nconst pid = new ConduytPID(device)\n\nawait pid.config(2.0, 0.5, 0.05)        \u002F\u002F Kp, Ki, Kd\nawait pid.setInput(14)                   \u002F\u002F analog pin (A0 on Uno)\nawait pid.setOutput(5)                   \u002F\u002F PWM pin\nawait pid.setTarget(50.0)                \u002F\u002F setpoint in scaled units\nawait pid.enable()\n\npid.onTick((input, output, error) => {\n  console.log(`in=${input.toFixed(2)} out=${output.toFixed(2)} err=${error.toFixed(2)}`)\n})\n",[18,172,173,190,202,214,218,258,276,280,314,334,353,373,385,389,423,489],{"__ignoreMap":36},[40,174,175,179,183,186],{"class":42,"line":43},[40,176,178],{"class":177},"szBVR","import",[40,180,182],{"class":181},"sVt8B"," { ConduytDevice } ",[40,184,185],{"class":177},"from",[40,187,189],{"class":188},"sZZnC"," 'conduyt-js'\n",[40,191,192,194,197,199],{"class":42,"line":49},[40,193,178],{"class":177},[40,195,196],{"class":181}," { SerialTransport } ",[40,198,185],{"class":177},[40,200,201],{"class":188}," 'conduyt-js\u002Ftransports\u002Fserial'\n",[40,203,204,206,209,211],{"class":42,"line":73},[40,205,178],{"class":177},[40,207,208],{"class":181}," { ConduytPID } ",[40,210,185],{"class":177},[40,212,213],{"class":188}," 'conduyt-js\u002Fmodules\u002Fpid'\n",[40,215,216],{"class":42,"line":79},[40,217,70],{"emptyLinePlaceholder":69},[40,219,220,223,227,230,233,236,240,243,246,249,252,255],{"class":42,"line":85},[40,221,222],{"class":177},"const",[40,224,226],{"class":225},"sj4cs"," device",[40,228,229],{"class":177}," =",[40,231,232],{"class":177}," await",[40,234,235],{"class":181}," ConduytDevice.",[40,237,239],{"class":238},"sScJk","connect",[40,241,242],{"class":181},"(",[40,244,245],{"class":177},"new",[40,247,248],{"class":238}," SerialTransport",[40,250,251],{"class":181},"({ path: ",[40,253,254],{"class":188},"'\u003CYOUR_PORT>'",[40,256,257],{"class":181}," }))\n",[40,259,260,262,265,267,270,273],{"class":42,"line":90},[40,261,222],{"class":177},[40,263,264],{"class":225}," pid",[40,266,229],{"class":177},[40,268,269],{"class":177}," new",[40,271,272],{"class":238}," ConduytPID",[40,274,275],{"class":181},"(device)\n",[40,277,278],{"class":42,"line":96},[40,279,70],{"emptyLinePlaceholder":69},[40,281,282,285,288,291,293,296,299,302,304,307,310],{"class":42,"line":102},[40,283,284],{"class":177},"await",[40,286,287],{"class":181}," pid.",[40,289,290],{"class":238},"config",[40,292,242],{"class":181},[40,294,295],{"class":225},"2.0",[40,297,298],{"class":181},", ",[40,300,301],{"class":225},"0.5",[40,303,298],{"class":181},[40,305,306],{"class":225},"0.05",[40,308,309],{"class":181},")        ",[40,311,313],{"class":312},"sJ8bj","\u002F\u002F Kp, Ki, Kd\n",[40,315,316,318,320,323,325,328,331],{"class":42,"line":108},[40,317,284],{"class":177},[40,319,287],{"class":181},[40,321,322],{"class":238},"setInput",[40,324,242],{"class":181},[40,326,327],{"class":225},"14",[40,329,330],{"class":181},")                   ",[40,332,333],{"class":312},"\u002F\u002F analog pin (A0 on Uno)\n",[40,335,336,338,340,343,345,348,350],{"class":42,"line":114},[40,337,284],{"class":177},[40,339,287],{"class":181},[40,341,342],{"class":238},"setOutput",[40,344,242],{"class":181},[40,346,347],{"class":225},"5",[40,349,330],{"class":181},[40,351,352],{"class":312},"\u002F\u002F PWM pin\n",[40,354,355,357,359,362,364,367,370],{"class":42,"line":120},[40,356,284],{"class":177},[40,358,287],{"class":181},[40,360,361],{"class":238},"setTarget",[40,363,242],{"class":181},[40,365,366],{"class":225},"50.0",[40,368,369],{"class":181},")                ",[40,371,372],{"class":312},"\u002F\u002F setpoint in scaled units\n",[40,374,375,377,379,382],{"class":42,"line":125},[40,376,284],{"class":177},[40,378,287],{"class":181},[40,380,381],{"class":238},"enable",[40,383,384],{"class":181},"()\n",[40,386,387],{"class":42,"line":131},[40,388,70],{"emptyLinePlaceholder":69},[40,390,391,394,397,400,404,406,409,411,414,417,420],{"class":42,"line":137},[40,392,393],{"class":181},"pid.",[40,395,396],{"class":238},"onTick",[40,398,399],{"class":181},"((",[40,401,403],{"class":402},"s4XuR","input",[40,405,298],{"class":181},[40,407,408],{"class":402},"output",[40,410,298],{"class":181},[40,412,413],{"class":402},"error",[40,415,416],{"class":181},") ",[40,418,419],{"class":177},"=>",[40,421,422],{"class":181}," {\n",[40,424,426,429,432,434,437,439,442,445,447,450,453,456,458,460,462,464,466,468,471,473,475,477,479,481,483,486],{"class":42,"line":425},15,[40,427,428],{"class":181},"  console.",[40,430,431],{"class":238},"log",[40,433,242],{"class":181},[40,435,436],{"class":188},"`in=${",[40,438,403],{"class":181},[40,440,441],{"class":188},".",[40,443,444],{"class":238},"toFixed",[40,446,242],{"class":188},[40,448,449],{"class":225},"2",[40,451,452],{"class":188},")",[40,454,455],{"class":188},"} out=${",[40,457,408],{"class":181},[40,459,441],{"class":188},[40,461,444],{"class":238},[40,463,242],{"class":188},[40,465,449],{"class":225},[40,467,452],{"class":188},[40,469,470],{"class":188},"} err=${",[40,472,413],{"class":181},[40,474,441],{"class":188},[40,476,444],{"class":238},[40,478,242],{"class":188},[40,480,449],{"class":225},[40,482,452],{"class":188},[40,484,485],{"class":188},"}`",[40,487,488],{"class":181},")\n",[40,490,492],{"class":42,"line":491},16,[40,493,494],{"class":181},"})\n",[163,496,498],{"id":497},"python","Python",[31,500,503],{"className":501,"code":502,"language":497,"meta":36,"style":36},"language-python shiki shiki-themes github-light github-dark","from conduyt import ConduytDevice\nfrom conduyt.transports.serial import SerialTransport\nfrom conduyt.modules import ConduytPID\n\ndevice = ConduytDevice(SerialTransport('\u003CYOUR_PORT>'))\nawait device.connect()\n\npid = ConduytPID(device, module_id=0)\nawait pid.config(kp=2.0, ki=0.5, kd=0.05)\nawait pid.set_input(14)\nawait pid.set_output(5)\nawait pid.set_target(50.0)\nawait pid.enable()\n",[18,504,505,510,515,520,524,529,534,538,543,548,553,558,563],{"__ignoreMap":36},[40,506,507],{"class":42,"line":43},[40,508,509],{},"from conduyt import ConduytDevice\n",[40,511,512],{"class":42,"line":49},[40,513,514],{},"from conduyt.transports.serial import SerialTransport\n",[40,516,517],{"class":42,"line":73},[40,518,519],{},"from conduyt.modules import ConduytPID\n",[40,521,522],{"class":42,"line":79},[40,523,70],{"emptyLinePlaceholder":69},[40,525,526],{"class":42,"line":85},[40,527,528],{},"device = ConduytDevice(SerialTransport('\u003CYOUR_PORT>'))\n",[40,530,531],{"class":42,"line":90},[40,532,533],{},"await device.connect()\n",[40,535,536],{"class":42,"line":96},[40,537,70],{"emptyLinePlaceholder":69},[40,539,540],{"class":42,"line":102},[40,541,542],{},"pid = ConduytPID(device, module_id=0)\n",[40,544,545],{"class":42,"line":108},[40,546,547],{},"await pid.config(kp=2.0, ki=0.5, kd=0.05)\n",[40,549,550],{"class":42,"line":114},[40,551,552],{},"await pid.set_input(14)\n",[40,554,555],{"class":42,"line":120},[40,556,557],{},"await pid.set_output(5)\n",[40,559,560],{"class":42,"line":125},[40,561,562],{},"await pid.set_target(50.0)\n",[40,564,565],{"class":42,"line":131},[40,566,567],{},"await pid.enable()\n",[26,569,571],{"id":570},"command-reference","Command reference",[573,574,575,594],"table",{},[576,577,578],"thead",{},[579,580,581,585,588,591],"tr",{},[582,583,584],"th",{},"Command",[582,586,587],{},"ID",[582,589,590],{},"Payload",[582,592,593],{},"Description",[595,596,597,616,634,652,669],"tbody",{},[579,598,599,603,608,613],{},[600,601,602],"td",{},"Config",[600,604,605],{},[18,606,607],{},"0x01",[600,609,610],{},[18,611,612],{},"kp(4) + ki(4) + kd(4)",[600,614,615],{},"LE float32 gains",[579,617,618,621,626,631],{},[600,619,620],{},"SetTarget",[600,622,623],{},[18,624,625],{},"0x02",[600,627,628],{},[18,629,630],{},"value(4)",[600,632,633],{},"LE float32 setpoint",[579,635,636,639,644,649],{},[600,637,638],{},"SetInput",[600,640,641],{},[18,642,643],{},"0x03",[600,645,646],{},[18,647,648],{},"pin(1)",[600,650,651],{},"Analog input pin",[579,653,654,657,662,666],{},[600,655,656],{},"SetOutput",[600,658,659],{},[18,660,661],{},"0x04",[600,663,664],{},[18,665,648],{},[600,667,668],{},"PWM output pin",[579,670,671,674,679,684],{},[600,672,673],{},"Enable",[600,675,676],{},[18,677,678],{},"0x05",[600,680,681],{},[18,682,683],{},"enable(1)",[600,685,686],{},"1 = run, 0 = pause (gains preserved)",[26,688,690],{"id":689},"events","Events",[573,692,693,707],{},[576,694,695],{},[579,696,697,700,702,704],{},[582,698,699],{},"Event",[582,701,587],{},[582,703,590],{},[582,705,706],{},"When",[595,708,709],{},[579,710,711,714,718,724],{},[600,712,713],{},"Tick",[600,715,716],{},[18,717,607],{},[600,719,720,723],{},[18,721,722],{},"input(4) + output(4) + error(4)"," LE float32",[600,725,726],{},"Once per PID cycle (100 ms default)",[26,728,730],{"id":729},"notes","Notes",[732,733,734,742,756,763],"ul",{},[735,736,737,738,741],"li",{},"The default tick interval is 100 ms (",[18,739,740],{},"_intervalMs = 100"," in the firmware). Override by editing the module class if you need faster control.",[735,743,744,745,748,749,752,753,755],{},"The analog reading is normalized as ",[18,746,747],{},"analogRead(pin) \u002F 1023.0 * inputScale"," with ",[18,750,751],{},"inputScale = 100.0"," by default — so ",[18,754,403],{}," is in 0–100 \"percent\" units. Set your target in the same scale.",[735,757,758,759,762],{},"PID output is clamped to 0–255 before ",[18,760,761],{},"analogWrite()",". Negative outputs (anti-windup undershoot) clamp to 0.",[735,764,765],{},"Disable resets the integral and derivative state so re-enabling doesn't kick from a stale history.",[767,768,769],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":36,"searchDepth":49,"depth":49,"links":771},[772,773,774,778,779,780],{"id":28,"depth":49,"text":29},{"id":142,"depth":49,"text":143},{"id":160,"depth":49,"text":161,"children":775},[776,777],{"id":165,"depth":73,"text":166},{"id":497,"depth":73,"text":498},{"id":570,"depth":49,"text":571},{"id":689,"depth":49,"text":690},{"id":729,"depth":49,"text":730},"Run a closed-loop PID controller on the device — analog input, PWM output, host-tunable gains.","md",{},"\u002Fdocs\u002Fmodules\u002Fpid",{"title":5,"description":781},"docs\u002Fmodules\u002Fpid","DG8wOi0WKccpij5DFz9TlgTCgIfU_NaOHK_GcYFknnw",1777412314787]