|
| 1 | +-- Cruise Control + Check Engine LED |
| 2 | +-- |
| 3 | +-- Hardware: |
| 4 | +-- LED : PWM output 0, 50 Hz |
| 5 | +-- Buttons : AuxAnalog 0, resistor-ladder |
| 6 | +-- Set : 0.75-1.40 V -- engage / disengage cruise 4x4.7k in parallel |
| 7 | +-- Decrease : 1.80-2.70 V -- target RPM - RPM_STEP 2x4.7k in parallel |
| 8 | +-- Increase : 2.90-3.55 V -- target RPM + RPM_STEP 1x4.7k |
| 9 | +-- No button : all other voltages |
| 10 | +-- Brake pedal : getOutput("brakePedalState") |
| 11 | +-- |
| 12 | +-- LED priority (highest first): |
| 13 | +-- 1. Fault (CLT > 100) -- blink 1 s period, 100 % |
| 14 | +-- 2. Engine stopped (RPM = 0) -- 100 % steady |
| 15 | +-- 3. Cruise active -- 50 % steady |
| 16 | +-- 4. Normal run -- off |
| 17 | + |
| 18 | +-- ========= tuneable constants ========= |
| 19 | +local CRUISE_MIN_RPM = 1200 -- minimum RPM to allow engagement |
| 20 | +local CRUISE_MAX_RPM = 4000 -- RPM above this forces disengagement |
| 21 | +local RPM_STEP = 100 -- RPM per increase / decrease press |
| 22 | +local CLT_FAULT_TEMP = 100 -- degrees C for fault LED blink |
| 23 | +local PID_KP = 0.02 -- %ETB per RPM of error |
| 24 | +local PID_KI = 0.003 -- integrator gain |
| 25 | +local PID_MIN = 0.0 -- don't wont to decrease ETB below baseline |
| 26 | +local PID_MAX = 50.0 -- limit max ETB increase to avoid big throttle jumps |
| 27 | +local BLINK_HALF = 0.5 -- fault blink half-period [s] |
| 28 | +local DEBOUNCE = 20 -- ticks of stable reading (~100 ms at 200 Hz) |
| 29 | +-- ====================================== |
| 30 | + |
| 31 | +-- button state constants |
| 32 | +local BTN_NONE = 0 |
| 33 | +local BTN_SET = 1 |
| 34 | +local BTN_DECREASE = 2 |
| 35 | +local BTN_INCREASE = 3 |
| 36 | + |
| 37 | +startPwm(0, 50, 0) -- LED: PWM output 0, 50 Hz, off |
| 38 | + |
| 39 | +local pid = Pid.new(PID_KP, PID_KI, 0, PID_MIN, PID_MAX) |
| 40 | + |
| 41 | +local cruiseActive = false |
| 42 | +local targetRpm = 0 |
| 43 | + |
| 44 | +-- debounce state |
| 45 | +local btnCandidate = BTN_NONE |
| 46 | +local btnCount = 0 |
| 47 | +local btnStable = BTN_NONE |
| 48 | +local btnPrev = BTN_NONE |
| 49 | + |
| 50 | +-- LED blink state |
| 51 | +local blinkTimer = Timer.new() |
| 52 | +local blinkOn = false |
| 53 | + |
| 54 | +-- ---- helpers ---- |
| 55 | + |
| 56 | +local function readBtn() |
| 57 | + local v = getAuxAnalog(0) |
| 58 | + if v == nil then return BTN_NONE end |
| 59 | + if v >= 0.75 and v <= 1.40 then return BTN_SET end |
| 60 | + if v >= 1.80 and v <= 2.70 then return BTN_DECREASE end |
| 61 | + if v >= 2.90 and v <= 3.55 then return BTN_INCREASE end |
| 62 | + return BTN_NONE |
| 63 | +end |
| 64 | + |
| 65 | +local function disengageCruise() |
| 66 | + cruiseActive = false |
| 67 | + pid:reset() |
| 68 | + setEtbAdd(0) |
| 69 | + setLuaGauge(1, 0) |
| 70 | + setLuaGauge(2, 0) |
| 71 | + print("Cruise OFF") |
| 72 | +end |
| 73 | + |
| 74 | +-- ---- main loop ---- |
| 75 | + |
| 76 | +function onTick() |
| 77 | + local rpm = getSensor("RPM") or 0 |
| 78 | + |
| 79 | + -- ---- button debounce ---- |
| 80 | + local raw = readBtn() |
| 81 | + if raw == btnCandidate then |
| 82 | + btnCount = btnCount + 1 |
| 83 | + if btnCount >= DEBOUNCE then btnStable = btnCandidate end |
| 84 | + else |
| 85 | + btnCandidate = raw |
| 86 | + btnCount = 1 |
| 87 | + end |
| 88 | + |
| 89 | + -- ---- single-fire edge detection (none -> button) ---- |
| 90 | + if btnStable ~= BTN_NONE and btnPrev == BTN_NONE then |
| 91 | + if btnStable == BTN_SET then |
| 92 | + if not cruiseActive then |
| 93 | + if rpm >= CRUISE_MIN_RPM then |
| 94 | + cruiseActive = true |
| 95 | + targetRpm = rpm |
| 96 | + pid:reset() |
| 97 | + print("Cruise ON target=" .. math.floor(rpm) .. " RPM") |
| 98 | + else |
| 99 | + print("Cruise: RPM too low (" .. math.floor(rpm) .. ")") |
| 100 | + end |
| 101 | + else |
| 102 | + disengageCruise() |
| 103 | + end |
| 104 | + elseif cruiseActive then |
| 105 | + if btnStable == BTN_INCREASE then |
| 106 | + targetRpm = targetRpm + RPM_STEP |
| 107 | + elseif btnStable == BTN_DECREASE then |
| 108 | + targetRpm = math.max(CRUISE_MIN_RPM, targetRpm - RPM_STEP) |
| 109 | + end |
| 110 | + pid:reset() |
| 111 | + print("Cruise target=" .. math.floor(targetRpm) .. " RPM") |
| 112 | + end |
| 113 | + end |
| 114 | + btnPrev = btnStable |
| 115 | + |
| 116 | + -- ---- auto-disengage ---- |
| 117 | + if cruiseActive then |
| 118 | + local brake = getOutput("brakePedalState") |
| 119 | + if (brake ~= nil and brake > 0) then |
| 120 | + print("Cruise OFF: brake") |
| 121 | + disengageCruise() |
| 122 | + elseif rpm > CRUISE_MAX_RPM then |
| 123 | + print("Cruise OFF: RPM " .. math.floor(rpm)) |
| 124 | + disengageCruise() |
| 125 | + end |
| 126 | + end |
| 127 | + |
| 128 | + -- ---- ETB PI control ---- |
| 129 | + if cruiseActive then |
| 130 | + local adj = pid:get(targetRpm, rpm) |
| 131 | + setEtbAdd(adj) |
| 132 | + setLuaGauge(1, adj) |
| 133 | + setLuaGauge(2, targetRpm) |
| 134 | + else |
| 135 | + setEtbAdd(0) |
| 136 | + end |
| 137 | + |
| 138 | + -- ---- LED ---- |
| 139 | + local clt = getSensor("Clt") |
| 140 | + local fault = clt ~= nil and clt > CLT_FAULT_TEMP |
| 141 | + local stopped = rpm == 0 |
| 142 | + |
| 143 | + local duty |
| 144 | + if fault then |
| 145 | + if blinkTimer:getElapsedSeconds() >= BLINK_HALF then |
| 146 | + blinkOn = not blinkOn |
| 147 | + blinkTimer:reset() |
| 148 | + end |
| 149 | + duty = blinkOn and 1.0 or 0.0 |
| 150 | + elseif stopped then |
| 151 | + blinkOn = false ; blinkTimer:reset() |
| 152 | + duty = 1.0 |
| 153 | + elseif cruiseActive then |
| 154 | + blinkOn = false ; blinkTimer:reset() |
| 155 | + duty = 0.5 |
| 156 | + else |
| 157 | + blinkOn = false ; blinkTimer:reset() |
| 158 | + duty = 0.0 |
| 159 | + end |
| 160 | + |
| 161 | + setPwmDuty(0, duty) |
| 162 | +end |
0 commit comments