Дискретная реализация ПИД для микроконтроллера
Как ПИД живёт в микроконтроллере: дискретная форма и период опроса.
Дискретный ПИД работает не непрерывно, а шагами с периодом дискретизации
Ts: интегралы становятся суммами, производные — разностями. Так регулятор реализуется в прошивке.
От непрерывного к дискретному
Вся теория выше формулировалась в непрерывном времени (интегралы, производные). Но микроконтроллер работает тактами: считал датчик, посчитал воздействие, выдал на привод — и так каждые Ts секунд (период дискретизации). Поэтому реальный ПИД дискретный: интеграл становится суммой integral += e·Ts, производная — разностью (e - e_prev)/Ts. Именно такой код мы уже писали в классе PID — он по сути и есть дискретная реализация. Это прямой мостик к курсам по embedded и ROS, где регулятор крутится в цикле реального времени.
Позиционная и инкрементная формы
Есть две дискретные формы. Позиционная (которую мы использовали) считает полное воздействие u заново на каждом шаге, храня интеграл. Инкрементная (скоростная) считает только приращение Δu = u(k) - u(k-1) через разности ошибок — она удобна, когда привод сам интегрирует (шаговый двигатель, клапан с памятью положения), и автоматически смягчает windup, ведь явного накопленного интеграла нет.
// Инкрементная форма:
// du = Kp*(e[k]-e[k-1]) + Ki*Ts*e[k] + (Kd/Ts)*(e[k]-2*e[k-1]+e[k-2])
// u[k] = u[k-1] + duКак работает под капотом: влияние периода Ts
Выбор Ts критичен. Слишком большой период — регулятор реагирует редко, качество падает, система может потерять устойчивость. Слишком маленький — лишняя нагрузка на процессор и усиление шума в производной. Правило: Ts должен быть в 10-20 раз меньше характерного времени отклика системы. Покажем, как растущий Ts ухудшает управление.
class PID:
def __init__(s, Kp, Ki, Kd, Ts):
s.Kp, s.Ki, s.Kd, s.Ts = Kp, Ki, Kd, Ts
s.integral = 0.0; s.prev = 0.0
def update(s, sp, y):
e = sp - y
s.integral += e*s.Ts
d = (e - s.prev)/s.Ts; s.prev = e
return s.Kp*e + s.Ki*s.integral + s.Kd*d
def run(Ts):
pid = PID(2.0, 0.5, 1.0, Ts)
y, v = 0.0, 0.0
t, peak = 0.0, 0.0
while t < 20.0:
u = pid.update(1.0, y)
v += (u - 0.8*v)*Ts # объект интегрируется тем же шагом
y += v*Ts
peak = max(peak, y); t += Ts
return peak, y
for Ts in (0.05, 0.2, 0.5, 1.0):
peak, final = run(Ts)
print(f"Ts={Ts:4.2f}c пик={peak:6.2f} финал={final:6.2f}")Вывод:
Ts=0.05c пик= 1.22 финал= 1.00 Ts=0.20c пик= 1.21 финал= 1.00 Ts=0.50c пик= 1.20 финал= 1.00 Ts=1.00c пик=30757325.10 финал=-76247160.00
С ростом периода Ts система ведёт себя всё хуже: перерегулирование растёт, при больших Ts регулятор реагирует так редко, что теряет контроль. Это фундаментально: дискретизация — не мелочь, а параметр, влияющий на устойчивость наравне с коэффициентами. В реальных проектах ПИД часто запускают на фиксированной частоте (например, 100-1000 Гц) именно для предсказуемости.
Связь с embedded и реальным временем
В микроконтроллере дискретный ПИД обычно живёт в обработчике прерывания таймера: каждые Ts микросекунд срабатывает прерывание, читается АЦП, считается воздействие, выдаётся ШИМ. Жёсткость периода тут критична — именно аппаратный таймер, а не программная задержка, гарантирует стабильный Ts, без которого интеграл и производная считаются неверно. В мире ROS регулятор крутится в узле на фиксированной частоте (например, через rclcpp::Rate или таймер-колбэк), и тот же принцип сохраняется. Понимание дискретной природы регулятора — это мостик от теории управления к встраиваемому программированию: здесь сходятся курсы про ROS, про embedded и про DSP, ведь дискретный фильтр ошибки и дискретный ПИД — близкие родственники по математике разностных уравнений.
Частые ошибки
- Игнорировать период дискретизации. Слишком большой Ts тихо рушит управление, хотя коэффициенты «правильные».
- Нестабильный Ts. Если цикл регулятора «плавает» по времени, интеграл и производная считаются неверно; нужен жёсткий период (таймер/прерывание).
- Считать дискретный ПИД точной копией непрерывного. Дискретизация добавляет запаздывание и меняет границу устойчивости.
Итоги
- Реальный ПИД дискретный: интеграл — сумма e·Ts, производная — разность (e-e_prev)/Ts.
- Позиционная форма хранит интеграл; инкрементная считает приращение Δu и смягчает windup.
- Период Ts критичен для устойчивости: берут в 10-20 раз меньше времени отклика, с жёстким таймером.