// analog_macros.ems // EtherasMicroScript - Analog handling & transformation MACRO library // Updated to use native: scale(x, x1, x2, y1, y2) -> y // // Example: // mv = scale(ain(1), 32, 31850, 0, 10000) // // Notes: // - Macros use private vars starting with '_' (auto-renamed per call). // - Some macros accept timer IDs (tonId) that MUST be unique per instance. // - Where relevant, outputs are clamped to avoid weird values. // ------------------------------------------------------------ // 1) Basic utilities // ------------------------------------------------------------ MACRO CLAMP(x, lo, hi, OUT y) { y = x IF y < lo { y = lo } IF y > hi { y = hi } } MACRO ABS(x, OUT y) { y = x IF y < 0 { y = 0 - y } } MACRO MIN2(a, b, OUT y) { y = a IF b < a { y = b } } // ------------------------------------------------------------ // 2) Scaling / calibration (uses native scale()) // ------------------------------------------------------------ // SCALE_2PT: pure scaling using native scale(), no clamp MACRO SCALE_2PT(x, x1, x2, y1, y2, OUT y) { y = scale(x, x1, x2, y1, y2) } // SCALE_2PT_CLAMP: scaling then clamp to output range MACRO SCALE_2PT_CLAMP(x, x1, x2, y1, y2, OUT y) { y = scale(x, x1, x2, y1, y2) _lo = y1 _hi = y2 IF _hi < _lo { _t=_lo _lo=_hi _hi=_t } IF y < _lo { y = _lo } IF y > _hi { y = _hi } } // MAP_CLAMP: map x from [inLo..inHi] to [outLo..outHi] and clamp MACRO MAP_CLAMP(x, inLo, inHi, outLo, outHi, OUT y) { y = scale(x, inLo, inHi, outLo, outHi) _lo = outLo _hi = outHi IF _hi < _lo { _t=_lo _lo=_hi _hi=_t } IF y < _lo { y = _lo } IF y > _hi { y = _hi } } // 0-10V helper based on your calibration points (0V=32, 10V=31850) // Output: 0..10000 mV, clamped MACRO AIN_0_10V_TO_mV(raw, OUT mV) { mV = scale(raw, 32, 31850, 0, 10000) IF mV < 0 { mV = 0 } IF mV > 10000 { mV = 10000 } } // 0-10V to percent (0..100), clamped MACRO AIN_0_10V_TO_PCT(raw, OUT pct) { pct = scale(raw, 32, 31850, 0, 100) IF pct < 0 { pct = 0 } IF pct > 100 { pct = 100 } } // 0-10V to Engineering Units [eu0..eu10], clamped to range // Example: AIN_0_10V_TO_EU(ain(1), 0, 16, pressureBar) MACRO AIN_0_10V_TO_EU(raw, eu0, eu10, OUT eu) { eu = scale(raw, 32, 31850, eu0, eu10) _lo = eu0 _hi = eu10 IF _hi < _lo { _t=_lo _lo=_hi _hi=_t } IF eu < _lo { eu = _lo } IF eu > _hi { eu = _hi } } // ------------------------------------------------------------ // 3) Thresholds / comparators (digital outputs from analog) // ------------------------------------------------------------ // THRESH_HYST: ON when value > onTh, OFF when value < offTh (q is memory) MACRO THRESH_HYST(value, onTh, offTh, OUT q) { IF value > onTh { q = 1 } IF value < offTh { q = 0 } } // WINDOW: q=1 if value in [lo..hi] (inclusive) MACRO WINDOW(value, lo, hi, OUT q) { q = 0 IF value >= lo AND value <= hi { q = 1 } } // ------------------------------------------------------------ // 4) Analog filtering / smoothing // ------------------------------------------------------------ // LPF_IIR: exponential smoothing (y is memory) // y = y + (x - y)/k MACRO LPF_IIR(x, k, OUT y) { IF k <= 1 { y = x } ELSE { y = y + (x - y) / k } } // LPF_IIR_INIT: initializes y on first call MACRO LPF_IIR_INIT(x, k, OUT y) { IF !_inited { y = x _inited = 1 } ELSE { LPF_IIR(x, k, y) } } // MOVAVG_4: moving average over 4 samples (cheap) MACRO MOVAVG_4(x, OUT y) { _s3 = _s2 _s2 = _s1 _s1 = _s0 _s0 = x y = (_s0 + _s1 + _s2 + _s3) / 4 } // MEDIAN_3: median of last 3 samples (spike rejection) MACRO MEDIAN_3(x, OUT y) { _c = _b _b = _a _a = x _x = _a _y = _b _z = _c IF _x > _y { _t=_x _x=_y _y=_t } IF _y > _z { _t=_y _y=_z _z=_t } IF _x > _y { _t=_x _x=_y _y=_t } y = _y } // ------------------------------------------------------------ // 5) Rate limiting, ramping, slew control // ------------------------------------------------------------ // RATE_LIMIT: limit change per call (y is memory) MACRO RATE_LIMIT(x, maxUp, maxDn, OUT y) { _d = x - y IF _d > maxUp { _d = maxUp } IF _d < (0 - maxDn) { _d = 0 - maxDn } y = y + _d } // RAMP_TO: ramp y toward target by step per call (y is memory) MACRO RAMP_TO(target, step, OUT y) { IF y < target { y = y + step IF y > target { y = target } } ELSE { IF y > target { y = y - step IF y < target { y = target } } } } // ANALOG_RAMP_MS: ramp using TON ticks (call in a periodic loop) // - tickId must be unique per instance MACRO ANALOG_RAMP_MS(tickId, active, target, stepUp, stepDn, tickMs, OUT y) { IF !active { T_RESET(tickId) } ELSE { IF TON(tickId, 1, tickMs) { T_RESET(tickId) IF y < target { y = y + stepUp IF y > target { y = target } } ELSE { IF y > target { y = y - stepDn IF y < target { y = target } } } } } } // ------------------------------------------------------------ // 6) Noise bands and stability checks // ------------------------------------------------------------ // DEADZONE around 0 MACRO DEADZONE(x, band, OUT y) { y = x IF y < band AND y > (0 - band) { y = 0 } } // STABLE_FOR_MS: q=1 if |x - ref| <= band continuously for ms // - tonId must be unique per instance MACRO STABLE_FOR_MS(tonId, x, ref, band, ms, OUT q) { ABS(x - ref, _err) IF _err <= band { q = TON(tonId, 1, ms) } ELSE { q = 0 T_RESET(tonId) } } // STEP_DETECT: pulse when |x - last| > band MACRO STEP_DETECT(x, band, OUT q) { q = 0 ABS(x - _last, _d) IF _d > band { q = 1 } _last = x }