/*============================================================================== * pid_controller.cpp -- A QSpice component implementation of a discrete PID * controller. * * Developed by QSpice forum members @KSKelvin and @RDunn. * * Revision History: * 2023.10.10 -- Initial release. * * To build with Digital Mars C++ Compiler: * dmc -mn -WD pid_controller.cpp kernel32.lib *============================================================================*/ #include #include #include #include union uData { bool b; char c; unsigned char uc; short s; unsigned short us; int i; unsigned int ui; float f; double d; long long int i64; unsigned long long int ui64; char *str; unsigned char *bytes; }; void msg(const char *fmt, ...) { // for diagnostic print statements msleep(30); fflush(stdout); va_list args = {0}; va_start(args, fmt); vprintf(fmt, args); va_end(args); fflush(stdout); msleep(30); } // int DllMain() must exist and return 1 for a process to load the .DLL // See https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain for more // information. int __stdcall DllMain(void *module, unsigned int reason, void *reserved) { return 1; } // #undef pin names lest they collide with names in any header file(s) you might // include. #undef setpt #undef ctrl #undef clk #undef fb struct InstData { bool clk_n1; // clk[n-1] double error_n1; // error[n-1] double errorI_n1; // errorI[n-1] double lastT; double ctrl1; double errorI1; double errorD1; // technically, we need initialize only non-zero data members InstData() : clk_n1(0.0), error_n1(0.0), errorI_n1(0.0), lastT(0.0){}; }; #define UDATA(data) \ double setpt = data[0].d; /* input */ \ bool clk = data[1].b; /* input*/ \ double fb = data[2].d; /* input*/ \ double Kp = data[3].d; /* input parameter : proportional*/ \ double Ki = data[4].d; /* input parameter : integral*/ \ double Kd = data[5].d; /* input parameter : derivative*/ \ double Kv = data[6].d; /* input parameter : overall gain*/ \ bool Itype = data[7].b; /* 0=rectangular; 1=trapezoidal */ \ double Plimit = data[9].d; /* input*/ \ double Ilimit = data[10].d; /* input*/ \ double Dlimit = data[11].d; /* input*/ \ double ctrl_limit_high = data[12].d; /* input*/ \ double ctrl_limit_low = data[13].d; /* input*/ \ double &ctrl = data[8].d; /* output*/ extern "C" __declspec(dllexport) void pid_controller_with_limits( InstData **opaque, double t, union uData *data) { UDATA(data); if (!*opaque) { // allocate instance *opaque = new InstData; if (!*opaque) { // terminate with prejudice msg("pid_controller error. Unable to allocate memory. Terminating " "simulation.\n"); std::exit(1); } msg("pid_controller: Kp=%f, Ki=%f, Kd=%f, Kv=%f, Integration Method=%s\n", Kp, Ki, Kd, Kv, Itype ? "Trapezoidal" : "Rectangular"); } InstData *inst = *opaque; if (clk && !inst->clk_n1) // rising edge { ctrl=1; } // save clock state inst->clk_n1 = clk; } extern "C" __declspec(dllexport) void Trunc( InstData *inst, double t, union uData *data, double *timestep) { UDATA(data); const double ttol = 1e-9; // if not rising clock edge, we're done if (clk == inst->clk_n1 || !clk) { return; } *timestep = ttol; } extern "C" __declspec(dllexport) void Destroy(InstData *inst) { // free instance memory delete inst; } /*============================================================================== * End of pid_controller.cpp *============================================================================*/