xref: /freebsd/sys/kern/subr_pidctrl.c (revision fdafd315ad0d0f28a11b9fb4476a9ab059c62b92)
15f8cd1c0SJeff Roberson /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
35f8cd1c0SJeff Roberson  *
45f8cd1c0SJeff Roberson  * Copyright (c) 2017,  Jeffrey Roberson <jeff@freebsd.org>
55f8cd1c0SJeff Roberson  * All rights reserved.
65f8cd1c0SJeff Roberson  *
75f8cd1c0SJeff Roberson  * Redistribution and use in source and binary forms, with or without
85f8cd1c0SJeff Roberson  * modification, are permitted provided that the following conditions
95f8cd1c0SJeff Roberson  * are met:
105f8cd1c0SJeff Roberson  * 1. Redistributions of source code must retain the above copyright
11ea0939f0SEd Maste  *    notice, this list of conditions and the following disclaimer.
125f8cd1c0SJeff Roberson  * 2. Redistributions in binary form must reproduce the above copyright
135f8cd1c0SJeff Roberson  *    notice, this list of conditions and the following disclaimer in the
145f8cd1c0SJeff Roberson  *    documentation and/or other materials provided with the distribution.
155f8cd1c0SJeff Roberson  *
16ea0939f0SEd Maste  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17ea0939f0SEd Maste  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18ea0939f0SEd Maste  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19ea0939f0SEd Maste  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20ea0939f0SEd Maste  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21ea0939f0SEd Maste  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22ea0939f0SEd Maste  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23ea0939f0SEd Maste  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24ea0939f0SEd Maste  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25ea0939f0SEd Maste  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26ea0939f0SEd Maste  * SUCH DAMAGE.
275f8cd1c0SJeff Roberson  */
285f8cd1c0SJeff Roberson 
295f8cd1c0SJeff Roberson #include <sys/param.h>
305f8cd1c0SJeff Roberson #include <sys/kernel.h>
315f8cd1c0SJeff Roberson #include <sys/systm.h>
325f8cd1c0SJeff Roberson #include <sys/sysctl.h>
335f8cd1c0SJeff Roberson #include <sys/pidctrl.h>
345f8cd1c0SJeff Roberson 
355f8cd1c0SJeff Roberson void
pidctrl_init(struct pidctrl * pc,int interval,int setpoint,int bound,int Kpd,int Kid,int Kdd)365f8cd1c0SJeff Roberson pidctrl_init(struct pidctrl *pc, int interval, int setpoint, int bound,
375f8cd1c0SJeff Roberson     int Kpd, int Kid, int Kdd)
385f8cd1c0SJeff Roberson {
395f8cd1c0SJeff Roberson 
405f8cd1c0SJeff Roberson 	bzero(pc, sizeof(*pc));
415f8cd1c0SJeff Roberson 	pc->pc_setpoint = setpoint;
425f8cd1c0SJeff Roberson 	pc->pc_interval = interval;
435f8cd1c0SJeff Roberson 	pc->pc_bound = bound * setpoint * Kid;
445f8cd1c0SJeff Roberson 	pc->pc_Kpd = Kpd;
455f8cd1c0SJeff Roberson 	pc->pc_Kid = Kid;
465f8cd1c0SJeff Roberson 	pc->pc_Kdd = Kdd;
475f8cd1c0SJeff Roberson }
485f8cd1c0SJeff Roberson 
495f8cd1c0SJeff Roberson void
pidctrl_init_sysctl(struct pidctrl * pc,struct sysctl_oid_list * parent)505f8cd1c0SJeff Roberson pidctrl_init_sysctl(struct pidctrl *pc, struct sysctl_oid_list *parent)
515f8cd1c0SJeff Roberson {
525f8cd1c0SJeff Roberson 
535f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "error", CTLFLAG_RD,
545f8cd1c0SJeff Roberson 	    &pc->pc_error, 0, "Current difference from setpoint value (P)");
555f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "olderror", CTLFLAG_RD,
565f8cd1c0SJeff Roberson 	    &pc->pc_olderror, 0, "Error value from last interval");
575f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "integral", CTLFLAG_RD,
585f8cd1c0SJeff Roberson 	    &pc->pc_integral, 0, "Accumulated error integral (I)");
59e768070cSAlan Cox 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "derivative", CTLFLAG_RD,
60e768070cSAlan Cox 	    &pc->pc_derivative, 0, "Error derivative (D)");
615f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "input", CTLFLAG_RD,
625f8cd1c0SJeff Roberson 	    &pc->pc_input, 0, "Last controller process variable input");
635f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "output", CTLFLAG_RD,
645f8cd1c0SJeff Roberson 	    &pc->pc_output, 0, "Last controller output");
655f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "ticks", CTLFLAG_RD,
66e768070cSAlan Cox 	    &pc->pc_ticks, 0, "Last controller runtime");
675f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "setpoint", CTLFLAG_RW,
685f8cd1c0SJeff Roberson 	    &pc->pc_setpoint, 0, "Desired level for process variable");
695f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "interval", CTLFLAG_RD,
705f8cd1c0SJeff Roberson 	    &pc->pc_interval, 0, "Interval between calculations (ticks)");
715f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "bound", CTLFLAG_RW,
725f8cd1c0SJeff Roberson 	    &pc->pc_bound, 0, "Integral wind-up limit");
735f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "kpd", CTLFLAG_RW,
745f8cd1c0SJeff Roberson 	    &pc->pc_Kpd, 0, "Inverse of proportional gain");
755f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "kid", CTLFLAG_RW,
765f8cd1c0SJeff Roberson 	    &pc->pc_Kid, 0, "Inverse of integral gain");
775f8cd1c0SJeff Roberson 	SYSCTL_ADD_INT(NULL, parent, OID_AUTO, "kdd", CTLFLAG_RW,
785f8cd1c0SJeff Roberson 	    &pc->pc_Kdd, 0, "Inverse of derivative gain");
795f8cd1c0SJeff Roberson }
805f8cd1c0SJeff Roberson 
815f8cd1c0SJeff Roberson int
pidctrl_classic(struct pidctrl * pc,int input)825f8cd1c0SJeff Roberson pidctrl_classic(struct pidctrl *pc, int input)
835f8cd1c0SJeff Roberson {
845f8cd1c0SJeff Roberson 	int output, error;
855f8cd1c0SJeff Roberson 	int Kpd, Kid, Kdd;
865f8cd1c0SJeff Roberson 
875f8cd1c0SJeff Roberson 	error = pc->pc_setpoint - input;
885f8cd1c0SJeff Roberson 	pc->pc_ticks = ticks;
895f8cd1c0SJeff Roberson 	pc->pc_olderror = pc->pc_error;
905f8cd1c0SJeff Roberson 
915f8cd1c0SJeff Roberson 	/* Fetch gains and prevent divide by zero. */
925f8cd1c0SJeff Roberson 	Kpd = MAX(pc->pc_Kpd, 1);
935f8cd1c0SJeff Roberson 	Kid = MAX(pc->pc_Kid, 1);
945f8cd1c0SJeff Roberson 	Kdd = MAX(pc->pc_Kdd, 1);
955f8cd1c0SJeff Roberson 
96e768070cSAlan Cox 	/* Compute P (proportional error), I (integral), D (derivative). */
975f8cd1c0SJeff Roberson 	pc->pc_error = error;
985f8cd1c0SJeff Roberson 	pc->pc_integral =
995f8cd1c0SJeff Roberson 	    MAX(MIN(pc->pc_integral + error, pc->pc_bound), -pc->pc_bound);
1005f8cd1c0SJeff Roberson 	pc->pc_derivative = error - pc->pc_olderror;
1015f8cd1c0SJeff Roberson 
1025f8cd1c0SJeff Roberson 	/* Divide by inverse gain values to produce output. */
103e768070cSAlan Cox 	output = (pc->pc_error / Kpd) + (pc->pc_integral / Kid) +
104ae6be8e6SMatt Macy 	    (pc->pc_derivative / Kdd);
1055f8cd1c0SJeff Roberson 	/* Save for sysctl. */
1065f8cd1c0SJeff Roberson 	pc->pc_output = output;
1075f8cd1c0SJeff Roberson 	pc->pc_input = input;
1085f8cd1c0SJeff Roberson 
109e768070cSAlan Cox 	return (output);
1105f8cd1c0SJeff Roberson }
1115f8cd1c0SJeff Roberson 
1125f8cd1c0SJeff Roberson int
pidctrl_daemon(struct pidctrl * pc,int input)1135f8cd1c0SJeff Roberson pidctrl_daemon(struct pidctrl *pc, int input)
1145f8cd1c0SJeff Roberson {
1155f8cd1c0SJeff Roberson 	int output, error;
1165f8cd1c0SJeff Roberson 	int Kpd, Kid, Kdd;
1175f8cd1c0SJeff Roberson 
1185f8cd1c0SJeff Roberson 	error = pc->pc_setpoint - input;
1195f8cd1c0SJeff Roberson 	/*
120e768070cSAlan Cox 	 * When ticks expires we reset our variables and start a new
1215f8cd1c0SJeff Roberson 	 * interval.  If we're called multiple times during one interval
1225f8cd1c0SJeff Roberson 	 * we attempt to report a target as if the entire error came at
1235f8cd1c0SJeff Roberson 	 * the interval boundary.
1245f8cd1c0SJeff Roberson 	 */
125e768070cSAlan Cox 	if ((u_int)ticks - pc->pc_ticks >= pc->pc_interval) {
1265f8cd1c0SJeff Roberson 		pc->pc_ticks = ticks;
1275f8cd1c0SJeff Roberson 		pc->pc_olderror = pc->pc_error;
1285f8cd1c0SJeff Roberson 		pc->pc_output = pc->pc_error = 0;
1295f8cd1c0SJeff Roberson 	} else {
130e768070cSAlan Cox 		/* Calculate the error relative to the last call. */
131e768070cSAlan Cox 		error -= pc->pc_error - pc->pc_output;
1325f8cd1c0SJeff Roberson 	}
1335f8cd1c0SJeff Roberson 
1345f8cd1c0SJeff Roberson 	/* Fetch gains and prevent divide by zero. */
1355f8cd1c0SJeff Roberson 	Kpd = MAX(pc->pc_Kpd, 1);
1365f8cd1c0SJeff Roberson 	Kid = MAX(pc->pc_Kid, 1);
1375f8cd1c0SJeff Roberson 	Kdd = MAX(pc->pc_Kdd, 1);
1385f8cd1c0SJeff Roberson 
139e768070cSAlan Cox 	/* Compute P (proportional error), I (integral), D (derivative). */
140e768070cSAlan Cox 	pc->pc_error += error;
1415f8cd1c0SJeff Roberson 	pc->pc_integral =
1425f8cd1c0SJeff Roberson 	    MAX(MIN(pc->pc_integral + error, pc->pc_bound), 0);
143e768070cSAlan Cox 	pc->pc_derivative = pc->pc_error - pc->pc_olderror;
1445f8cd1c0SJeff Roberson 
1455f8cd1c0SJeff Roberson 	/* Divide by inverse gain values to produce output. */
1465b274055SAlan Cox 	output = (pc->pc_error / Kpd) + (pc->pc_integral / Kid) +
147ae6be8e6SMatt Macy 	    (pc->pc_derivative / Kdd);
1485f8cd1c0SJeff Roberson 	output = MAX(output - pc->pc_output, 0);
149e768070cSAlan Cox 	/* Save for sysctl. */
1505f8cd1c0SJeff Roberson 	pc->pc_output += output;
1515f8cd1c0SJeff Roberson 	pc->pc_input = input;
1525f8cd1c0SJeff Roberson 
153e768070cSAlan Cox 	return (output);
1545f8cd1c0SJeff Roberson }
155