import control
The control module provides lightweight closed-loop helpers for motors, temperature, position, speed, heading, distance, and similar systems. A control loop compares a target value with a measured value and produces an output that can drive a motor, heater, servo, PWM output, or another actuator. PID controllers use monotonic device timing internally, so the integral and derivative terms track elapsed time between update calls. Run controllers at a reasonably consistent interval with system.schedule.
Quick example
import control
import system
pid = control.pid(
kp=1.0,
ki=0.1,
kd=0.05,
output_min=0,
output_max=100,
)
print(pid.update(target=50, measured=42))
pid.set_target(120)
def control_loop():
measured = 86
power = pid.update(measured=measured)
print(power)
system.schedule("pid", 20, 0, -1, control_loop)
# Small errors can be ignored with a deadband.
error = control.deadband(target - measured, 0.5)
# Outputs can be constrained before sending them to hardware.
power = control.clamp(power, 0, 100)
# Raw inputs can be mapped into actuator ranges.
speed = control.map(2048, 0, 4095, -100, 100)
Notes:
PID output assumes error = target - measured, and that increasing output
tends to increase the measured value. If the physical system moves in the
opposite direction, invert the output before sending it to the actuator.
Configure output limits for real actuators. Limits help keep motor power,
heater power, steering, or other outputs inside the range the device can
actually use.
Reset a PID controller when restarting a stopped control operation so old
integral or derivative state does not affect the next run.
importcontrol
Feedback control and PID controllers.
The control module provides lightweight closed-loop helpers for motors, temperature, position, speed, heading, distance, and similar systems. A control loop compares a target value with a measured value and produces an output that can drive a motor, heater, servo, PWM output, or another actuator. PID controllers use monotonic device timing internally, so the integral and derivative terms track elapsed time between update calls. Run controllers at a reasonably consistent interval with system.schedule.
control.pid(kp: float, ki: any = None, kd: any = None, output_min: any = None, output_max: any = None) -> pid_controller
control.pid(kp: float, ki: any = None, kd: any = None, output_min: any = None, output_max: any = None) -> pid_controllerCreate a PID controller.
A PID controller converts a target value and a measured value into an output. It can be used as proportional-only, PI, or full PID control by choosing which gains are nonzero.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
kp |
float |
positional or keyword | Yes | Required proportional gain. Controls how strongly output reacts to the current error. |
ki |
any |
positional or keyword | No | Optional integral gain. Defaults to 0. Accumulates persistent error over time. |
kd |
any |
positional or keyword | No | Optional derivative gain. Defaults to 0. Reacts to the rate of error change. |
output_min |
any |
positional or keyword | No | Optional minimum controller output. Use None for no lower limit. |
output_max |
any |
positional or keyword | No | Optional maximum controller output. Use None for no upper limit. |
pid_controller pid_controller object.
control.pid().update(target=None, measured=None) -> float
control.pid().update(target=None, measured=None) -> floatCalculate the next controller output. Pass target and measured, or call set_target(value) first and then pass only measured. The controller uses elapsed monotonic time internally for integral and derivative behavior.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
target |
any |
positional or keyword | No | Optional value. Defaults to None. |
measured |
any |
positional or keyword | No | Optional value. Defaults to None. |
control.pid().reset() -> bool
control.pid().reset() -> boolClear accumulated integral error, previous error, previous output, and previous update time.
control.pid().set_target(value) -> bool
control.pid().set_target(value) -> boolStore a persistent setpoint for measured-only update calls.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
value |
any |
positional or keyword | Yes | Required value. |
control.pid().target() -> float
control.pid().target() -> floatReturn the persistent target, or None if no target has been set.
control.pid().set_gains(kp, ki, kd) -> bool
control.pid().set_gains(kp, ki, kd) -> boolUpdate proportional, integral, and derivative gains without recreating the controller.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
kp |
any |
positional or keyword | Yes | Required value. |
ki |
any |
positional or keyword | Yes | Required value. |
kd |
any |
positional or keyword | Yes | Required value. |
control.pid().set_limits(output_min=None, output_max=None) -> bool
control.pid().set_limits(output_min=None, output_max=None) -> boolChange output limits. Pass None for either side to remove that limit.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
output_min |
any |
positional or keyword | No | Optional value. Defaults to None. |
output_max |
any |
positional or keyword | No | Optional value. Defaults to None. |
control.pid().error() -> float
control.pid().error() -> floatReturn the most recently calculated target-minus-measured error.
control.pid().output() -> float
control.pid().output() -> floatReturn the most recently calculated controller output.
control.pid().integral() -> float
control.pid().integral() -> floatReturn the accumulated integral term for debugging and tuning.
control.pid().Examples
control.pid().Examplesimport control pid = control.pid( kp=1.0, ki=0.1, kd=0.05, output_min=0, output_max=100, ) power = pid.update(target=50, measured=42) print(power) pid.set_target(120) print(pid.update(measured=86)) pid.set_gains(kp=1.2, ki=0.15, kd=0.03) pid.set_limits(output_min=-100, output_max=100)
control.deadband(value: any, threshold: float) -> float
control.deadband(value: any, threshold: float) -> floatReturn zero when a value is close enough to zero.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
value |
any |
positional or keyword | Yes | Input value, usually an error term. |
threshold |
float |
positional or keyword | Yes | Deadband around zero. Values between -threshold and threshold return 0. |
float float Filtered value. Examples: error = target - measured error = control.deadband(error, 0.5)
control.clamp(value: any, minimum: any, maximum: any) -> float
control.clamp(value: any, minimum: any, maximum: any) -> floatConstrain a value to a numeric range.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
value |
any |
positional or keyword | Yes | Input value. |
minimum |
any |
positional or keyword | Yes | Lower bound. |
maximum |
any |
positional or keyword | Yes | Upper bound. |
float float Clamped value. Examples: power = control.clamp(power, 0, 100)
control.map(value: any, input_min: any, input_max: any, output_min: any, output_max: any) -> dict
control.map(value: any, input_min: any, input_max: any, output_min: any, output_max: any) -> dictMap a value from one numeric range to another.
| Name | Type | Pass as | Required | Description |
|---|---|---|---|---|
value |
any |
positional or keyword | Yes | Input value. |
input_min |
any |
positional or keyword | Yes | Minimum of the input range. |
input_max |
any |
positional or keyword | Yes | Maximum of the input range. |
output_min |
any |
positional or keyword | Yes | Minimum of the output range. |
output_max |
any |
positional or keyword | Yes | Maximum of the output range. |
dict float Mapped value. Examples: speed = control.map(joystick, 0, 4095, -100, 100)