// Automatically generated C++ file on Tue Dec 9 14:50:52 2025 // // To build with Digital Mars C++ Compiler: // // dmc -mn -WD dab_ctl.cpp kernel32.lib #include #include // for fabs, sqrt extern "C" __declspec(dllexport) void (*bzero)(void *ptr, unsigned int count) = 0; 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; }; // 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 Vin #undef fsw #undef Leq #undef Vout #undef clk #undef P_ref #undef alphaP_pu #undef alphaS_pu // ----------------------------------------------------------------------------- // Per-instance state for this block // ----------------------------------------------------------------------------- struct sDAB_CTL { bool clk_prev; // for edge detection bool first_sample; // for Ts init double last_t; // last time stamp // Normalized phase command in [-0.5, +0.5] // -0.5 -> -90 deg, 0 -> 0 deg, +0.5 -> +90 deg double phi_n; }; extern "C" __declspec(dllexport) void dab_ctl(struct sDAB_CTL **opaque, double t, union uData *data) { double Vin = data[0].d; // input [V] double fsw = data[1].d; // input [Hz] double Leq = data[2].d; // input [H] double Vout = data[3].d; // input [V] double clk = data[4].d; // input [logic] double P_ref = data[5].d; // input [W] (signed power command) double &alphaP_pu = data[6].d; // output 0..1 -> -90..+90 deg double &alphaS_pu = data[7].d; // output (reserved for future EPS use) // Allocate and zero instance on first call if(!*opaque) { *opaque = (struct sDAB_CTL *) malloc(sizeof(struct sDAB_CTL)); bzero(*opaque, sizeof(struct sDAB_CTL)); (*opaque)->first_sample = true; (*opaque)->clk_prev = false; (*opaque)->phi_n = 0.0; } struct sDAB_CTL *inst = *opaque; // ------------------------------------------------------------------------- // 1) Edge detect on CLK (same pattern as PLL block) // Only update control once per rising edge. // ------------------------------------------------------------------------- bool clk_now = (clk > 0.5); // simple logic threshold bool rising = clk_now && !inst->clk_prev; inst->clk_prev = clk_now; if (!rising) { // No new sample: hold last outputs alphaP_pu = 0.5 + inst->phi_n; // keep 0..1 mapping alphaS_pu = 0.0; return; } // ------------------------------------------------------------------------- // 2) Compute Ts from time (not used directly yet, but handy if we add // dynamic features later). // ------------------------------------------------------------------------- double Ts; if (inst->first_sample) { Ts = 1e-4; // fallback for first valid sample (~10 kHz) inst->first_sample = false; } else { Ts = t - inst->last_t; if (Ts <= 0.0) Ts = 1e-6; // guard against numerical weirdness } inst->last_t = t; // ------------------------------------------------------------------------- // 3) Power -> phase inverse mapping (bidirectional SPS model) // // For a single-phase-shift DAB (SPS) with 0 <= |phi| <= pi/2: // // P(phi) ˜ K * (phi/pi) * (1 - phi/pi) // // where K ˜ Vin * Vout / (? * Leq), ? = 2pfsw. // This has a maximum at |phi| = pi/2, with // // P_max = K / 4. // // We: // - compute P_max from Vin, Vout, fsw, Leq // - saturate P_ref to ±P_max // - use sign(P_ref) to choose power-flow direction // - use |P_ref| / P_max = p and invert p = x - x^2 // on x ? [0, 0.5] to get a normalized magnitude // - form phi_n_cmd = sign(P_ref) * x ? [-0.5, +0.5] // // phi_n = ±0.5 corresponds to ±90° phase shift. // ------------------------------------------------------------------------- const double TWO_PI = 6.2831853071795864769; double w = TWO_PI * fsw; double Keq = 0.0; if (w > 0.0 && Leq > 0.0) Keq = Vin * Vout / (w * Leq); double Pmax = 0.25 * Keq; if (Pmax <= 0.0) { // Degenerate parameters -> command zero phase inst->phi_n = 0.0; alphaP_pu = 0.5; // zero phase (0.5 -> 0 degrees) alphaS_pu = 0.0; return; } // Signed power command double P_cmd = P_ref; // Saturate to ±Pmax if (P_cmd > Pmax) P_cmd = Pmax; if (P_cmd < -Pmax) P_cmd = -Pmax; double sgn = 0.0; if (P_cmd > 0.0) sgn = 1.0; if (P_cmd < 0.0) sgn = -1.0; double p = fabs(P_cmd) / Pmax; // 0..1 // Invert p = x - x^2 on x ? [0, 0.5]: // x = 0.5 * (1 - sqrt(1 - 4p)) double x; if (p < 1.0) { double rad = 1.0 - 4.0 * p; if (rad < 0.0) rad = 0.0; x = 0.5 * (1.0 - sqrt(rad)); } else { x = 0.5; // max power -> max phase magnitude (±90°) } double phi_n_cmd = sgn * x; // [-0.5, +0.5] normalized phase // ------------------------------------------------------------------------- // 4) Slew limit on phi_n to avoid abrupt phase jumps // ------------------------------------------------------------------------- const double dphi_max = 100e-3; // per-sample limit (tune as desired) double dphi = phi_n_cmd - inst->phi_n; if (dphi > dphi_max) dphi = dphi_max; if (dphi < -dphi_max) dphi = -dphi_max; inst->phi_n += dphi; // Clamp to valid range if (inst->phi_n > 0.5) inst->phi_n = 0.5; if (inst->phi_n < -0.5) inst->phi_n = -0.5; // ------------------------------------------------------------------------- // 5) Outputs: // Map normalized phase [-0.5, +0.5] -> [0, 1] for the QSpice block that // converts 0..1 V to -90..+90 degrees: // // phi_n = -0.5 -> alphaP_pu = 0.0 (-90°) // phi_n = 0.0 -> alphaP_pu = 0.5 ( 0°) // phi_n = +0.5 -> alphaP_pu = 1.0 (+90°) // ------------------------------------------------------------------------- alphaP_pu = 0.5 + inst->phi_n; alphaS_pu = 0.0; } extern "C" __declspec(dllexport) void Destroy(struct sDAB_CTL *inst) { free(inst); }