Arduino Thermostat Code — 6 Compile-Tested .ino Sketches for the Uno: Relay Hysteresis, Anti-Short-Cycle Lockout, PI Heater PWM, Night Setback, Stall Guard, Serial Tuning

Six sketches, zero libraries to install — all six compile for arduino:avr:uno with arduino-cli; the five relay sketches enforce a 180-second minimum off-time (MIN_OFF_MS, a const you can change) so a bare hysteresis loop cannot chatter your relay or compressor, and the PWM sketch latches off at MAX_SAFE_C instead.

Board

arduino:avr:uno

Parts you need (9)

Sketches in this pack (6)

ThermostatNtcHysteresis

The plain two-point thermostat done properly: a 10k NTC thermistor, one relay, a real dead-band, an anti-short-cycle timer and a latch that shuts the load off when the probe fails.

This is the file most people are actually looking for when they search for thermostat code, plus the two things a tutorial version leaves out. First, a minimum off-time (MIN_OFF_MS, 180 s) and a minimum on-time (MIN_ON_MS, 60 s): a bare hysteresis loop with a noisy probe can switch a relay repeatedly within a minute, which is exactly what kills a compressor. Second, a sensor-fault latch: an open or shorted thermistor reads at the very top or bottom of the 10-bit ADC range, which a naive sketch happily interprets as 'freezing' and heats forever — here three consecutive out-of-range readings lock the relay off until power is cycled. Wiring: 5V through the NTC to the A0 junction, then a 10k 1% resistor to GND; D8 to the relay module input; D13 is the on-board LED showing the heat call. Change SETPOINT_C, HYSTERESIS_C and the NTC_* constants for your part; set RELAY_ACTIVE_LOW to false if your relay board switches on a HIGH input. No delay() in the control path — the sampling and the timers use millis(), so the switch timers stay honest even if you add printing.

Wiring

PinModeConnect to
A0INPUTNTC divider midpoint (5V - NTC - A0 - 10k - GND)
8OUTPUTRelay module IN (heat call)
13OUTPUTOn-board LED, lit while heating

Connect in this order

  1. NTC divider midpoint (5V - NTC - A0 - 10k - GND) → A0
  2. Relay module IN (heat call) → 8
  3. On-board LED, lit while heating → 13

Values you can change

NameValueUnit
SETPOINT_C21.0deg C
HYSTERESIS_C0.8deg C, half dead-band
SERIES_RESISTOR10000.0ohm
NTC_NOMINAL_R10000.0ohm at NTC_NOMINAL_C
NTC_NOMINAL_C25.0deg C
NTC_BETA3950.0K, datasheet B25/50
ADC_FAULT_LOW15raw ADC
ADC_FAULT_HIGH1008raw ADC
FAULT_SAMPLES3consecutive readings
ADC_SAMPLES8readings averaged
MIN_OFF_MS180000ms, anti-short-cycle
MIN_ON_MS60000ms
SAMPLE_MS1000ms
RELAY_ACTIVE_LOW11 = relay switches on a LOW input
SERIAL_BAUD9600baud
ThermostatPotSetpoint

A thermostat you can adjust without a computer: LM35 probe, a 10k potentiometer as the setpoint dial, and a dial filter so a noisy wiper cannot shift the target every second.

The same relay control as sketch 1, but the setpoint comes from a knob so the finished box needs no laptop. The device a tutorial omits here is the dial filter: a raw analogRead of a cheap potentiometer jitters by several counts, and if you map that straight onto degrees the target wanders and the relay follows it. This file only accepts a new dial position after it has moved more than POT_DEADBAND_COUNTS AND held still for POT_STABLE_MS (800 ms) — a debounce for an analog input. The setpoint is also clamped between SETPOINT_MIN_C and SETPOINT_MAX_C so the knob cannot ask for a temperature your build is not safe at, and a reading outside SENSOR_MIN_VALID_C..SENSOR_MAX_VALID_C latches the relay off. Wiring: LM35 +Vs to 5V, GND to GND, OUT to A1; potentiometer ends to 5V and GND with the wiper to A2; D7 to the relay input. Change SETPOINT_MIN_C / SETPOINT_MAX_C to set the range of your dial.

Wiring

PinModeConnect to
A1INPUTLM35 OUT (10 mV per degree C)
A2INPUTSetpoint potentiometer wiper (10k, ends to 5V and GND)
7OUTPUTRelay module IN (heat call)
13OUTPUTOn-board LED, lit while heating

Connect in this order

  1. LM35 OUT (10 mV per degree C) → A1
  2. Setpoint potentiometer wiper (10k, ends to 5V and GND) → A2
  3. Relay module IN (heat call) → 7
  4. On-board LED, lit while heating → 13

Values you can change

NameValueUnit
ADC_REF_MV5000.0mV, supply used as ADC reference
LM35_MV_PER_C10.0mV per deg C
SETPOINT_MIN_C5.0deg C, dial low end
SETPOINT_MAX_C30.0deg C, dial high end
HYSTERESIS_C0.5deg C, half dead-band
SENSOR_MIN_VALID_C-2.0deg C
SENSOR_MAX_VALID_C90.0deg C
POT_DEADBAND_COUNTS8raw ADC
POT_STABLE_MS800ms the dial must hold still
MIN_OFF_MS180000ms, anti-short-cycle
MIN_ON_MS60000ms
SAMPLE_MS500ms
ADC_SAMPLES16readings averaged
FAULT_SAMPLES4consecutive readings
RELAY_ACTIVE_LOW11 = relay switches on a LOW input
SERIAL_BAUD9600baud
ThermostatPiPwmHeater

Proportional-plus-integral control of a resistive heater through a logic-level MOSFET, with anti-windup and a hard over-temperature latch — for an incubator, a reflow bed or a heated enclosure where on/off would overshoot.

A relay is wrong for a small resistive heater: on/off control overshoots and the mass oscillates. This sketch drives pin D9 with analogWrite into a logic-level N-MOSFET and closes a PI loop every CONTROL_MS (500 ms). Two devices a tutorial version does not have. (1) Anti-windup by conditional integration: the integral term is only updated while the output is inside the usable band, so a cold start does not pile up an enormous integral that keeps the heater at full power long past the setpoint. (2) A hard over-temperature latch: at or above MAX_SAFE_C, or if the sensor reads outside SENSOR_MIN_VALID_C..SENSOR_MAX_VALID_C, the duty goes to zero permanently, the fault LED blinks and only a power cycle clears it — a controller that has lost its probe must not be allowed to argue. PWM_MAX also caps the duty below 255 so a heater sized with margin cannot be driven to full power. Note on frequency: D9 runs at about 490 Hz, which is fine for a resistive load through a MOSFET and is NOT suitable for a mechanical relay — do not put a relay on this pin. Start with KP and KI as shipped, then raise KP until you see a small oscillation and back off.

Wiring

PinModeConnect to
A0INPUTLM35 OUT (10 mV per degree C)
9OUTPUTPWM to the gate resistor of a logic-level N-MOSFET
12OUTPUTFault LED (blinks when latched off)

Connect in this order

  1. LM35 OUT (10 mV per degree C) → A0
  2. PWM to the gate resistor of a logic-level N-MOSFET → 9
  3. Fault LED (blinks when latched off) → 12

Values you can change

NameValueUnit
SETPOINT_C55.0deg C
MAX_SAFE_C70.0deg C, hard latch
KP12.0PWM counts per deg C
KI0.35PWM counts per deg C second
INTEGRAL_LIMIT300.0deg C second
PWM_MAX200PWM counts of 255
ADC_REF_MV5000.0mV
LM35_MV_PER_C10.0mV per deg C
SENSOR_MIN_VALID_C-2.0deg C
SENSOR_MAX_VALID_C120.0deg C
CONTROL_MS500ms per control step
BLINK_MS250ms fault LED half period
ADC_SAMPLES16readings averaged
SERIAL_BAUD9600baud
ThermostatNightSetback

Day, night and away setpoints on one relay: a millis-based minute clock, a night window that crosses midnight correctly, and a debounced away button.

Three targets on one thermostat. The clock is a minute counter driven by millis() and seeded with BOOT_MINUTE_OF_DAY, so you power the box on at a known time and it knows the hour without an RTC — the header says plainly that a ceramic resonator drifts, so this is for a heater, not for a watch; if you need calendar accuracy, add an RTC. The devices a tutorial omits: the minute counter advances in a while loop with unsigned subtraction, so it stays correct across the millis() rollover at about 49.7 days AND recovers the right number of minutes if the loop was ever delayed; the night window is evaluated with a test that handles a range crossing midnight (22:00 to 06:00) instead of the naive comparison that silently never fires; and the away button on D2 uses INPUT_PULLUP with a DEBOUNCE_MS filter so one press is one toggle, not five. Minimum on and off times apply to every mode change, so a setback transition cannot short-cycle the load. Change NIGHT_START_MIN / NIGHT_END_MIN (minutes past midnight) and the three setpoints.

Wiring

PinModeConnect to
A0INPUTNTC divider midpoint (5V - NTC - A0 - 10k - GND)
2INPUT_PULLUPAway button to GND (no external resistor)
8OUTPUTRelay module IN (heat call)
13OUTPUTOn-board LED, lit while heating

Connect in this order

  1. NTC divider midpoint (5V - NTC - A0 - 10k - GND) → A0
  2. Away button to GND (no external resistor) → 2
  3. Relay module IN (heat call) → 8
  4. On-board LED, lit while heating → 13

Values you can change

NameValueUnit
DAY_SETPOINT_C21.0deg C
NIGHT_SETPOINT_C17.5deg C
AWAY_SETPOINT_C12.0deg C
HYSTERESIS_C0.6deg C, half dead-band
NIGHT_START_MIN1320minutes past midnight (22:00)
NIGHT_END_MIN360minutes past midnight (06:00)
BOOT_MINUTE_OF_DAY420minutes past midnight at power on (07:00)
MINUTES_PER_DAY1440minutes
MS_PER_MINUTE60000ms
DEBOUNCE_MS40ms button filter
MIN_OFF_MS180000ms, anti-short-cycle
MIN_ON_MS60000ms
SAMPLE_MS1000ms
SERIES_RESISTOR10000.0ohm
NTC_NOMINAL_R10000.0ohm at NTC_NOMINAL_C
NTC_NOMINAL_C25.0deg C
NTC_BETA3950.0K
ADC_FAULT_LOW15raw ADC
ADC_FAULT_HIGH1008raw ADC
ADC_SAMPLES8readings averaged
SERIAL_BAUD9600baud
ThermostatStallGuard

A thermostat that notices when heating does nothing: if the temperature has not risen by MIN_RISE_C after a full STALL_WINDOW_MS of calling for heat, it locks the load off and raises an alarm — plus a rolling 24-hour runtime cap.

The failure a plain thermostat cannot see is the one where the relay clicks, the contactor never closes, or the door is open — the room never warms, so the sketch calls for heat forever. This file measures the rise: when the heat call starts it records the temperature, and every STALL_WINDOW_MS (10 minutes) it checks whether the reading gained at least MIN_RISE_C. If it did, the reference resets and heating continues. If it did not, the relay is locked off, D12 blinks and the Serial Monitor prints STALL LOCKOUT. The second device is a rolling runtime cap: total heating time is accumulated against a 24-hour window (DAY_MS) and once it exceeds DAILY_CAP_MS (12 hours) the relay is held off until the window rolls, so a sensor that drifted low cannot run a heater around the clock unnoticed. Both counters use unsigned millis() arithmetic and a while loop on the window boundary, so they survive the rollover. Calibrate MIN_RISE_C by watching one real heating cycle in the Serial Monitor and using the rise your enclosure actually achieves in ten minutes.

Wiring

PinModeConnect to
A0INPUTNTC divider midpoint (5V - NTC - A0 - 10k - GND)
8OUTPUTRelay module IN (heat call)
12OUTPUTAlarm LED (blinks on stall lockout)
13OUTPUTOn-board LED, lit while heating

Connect in this order

  1. NTC divider midpoint (5V - NTC - A0 - 10k - GND) → A0
  2. Relay module IN (heat call) → 8
  3. Alarm LED (blinks on stall lockout) → 12
  4. On-board LED, lit while heating → 13

Values you can change

NameValueUnit
SETPOINT_C21.0deg C
HYSTERESIS_C0.8deg C, half dead-band
STALL_WINDOW_MS600000ms (10 minutes) of heating per check
MIN_RISE_C0.5deg C expected per window
DAILY_CAP_MS43200000ms (12 hours) of heating per day
DAY_MS86400000ms rolling window
MIN_OFF_MS180000ms, anti-short-cycle
MIN_ON_MS60000ms
SAMPLE_MS1000ms
BLINK_MS250ms alarm LED half period
SERIES_RESISTOR10000.0ohm
NTC_NOMINAL_R10000.0ohm at NTC_NOMINAL_C
NTC_NOMINAL_C25.0deg C
NTC_BETA3950.0K
ADC_FAULT_LOW15raw ADC
ADC_FAULT_HIGH1008raw ADC
ADC_SAMPLES8readings averaged
SERIAL_BAUD9600baud
ThermostatSerialTuner

Change the setpoint and dead-band over USB without recompiling — with range validation on every command and an override that expires by itself.

Tuning by editing a constant and re-uploading wastes an afternoon. This sketch reads line commands from the Serial Monitor at 9600 baud (set the line ending to Newline): SET 21.5 changes the base setpoint, HYS 0.6 changes the dead-band, OVR 24 applies a temporary override, OFF releases it, and ? prints the status. The devices a tutorial omits: every value is range-checked against its MIN/MAX const and rejected with an ERR line rather than silently accepted — a fat-fingered SET 210 cannot become the target — and the override is not permanent. It expires after OVERRIDE_TIMEOUT_MS (1 hour) and the thermostat returns to its base setpoint by itself, so a remote 'boost' left running by accident cannot heat a room all week. The serial reader is non-blocking: characters are collected into a fixed buffer and only a completed line is parsed, an over-long line is discarded with an error, and the control loop keeps running while you type. The setpoint lives in RAM, so a power cut returns to SETPOINT_DEFAULT_C — the header says so plainly rather than pretending otherwise.

Wiring

PinModeConnect to
A0INPUTNTC divider midpoint (5V - NTC - A0 - 10k - GND)
8OUTPUTRelay module IN (heat call)
13OUTPUTOn-board LED, lit while heating

Connect in this order

  1. NTC divider midpoint (5V - NTC - A0 - 10k - GND) → A0
  2. Relay module IN (heat call) → 8
  3. On-board LED, lit while heating → 13

Values you can change

NameValueUnit
SETPOINT_DEFAULT_C21.0deg C at power on
SETPOINT_MIN_C5.0deg C, accepted range low
SETPOINT_MAX_C30.0deg C, accepted range high
HYSTERESIS_DEFAULT_C0.8deg C
HYST_MIN_C0.2deg C
HYST_MAX_C3.0deg C
OVERRIDE_TIMEOUT_MS3600000ms (1 hour) before override expires
CMD_BUF_LEN24characters per command line
MIN_OFF_MS180000ms, anti-short-cycle
MIN_ON_MS60000ms
SAMPLE_MS1000ms
SERIES_RESISTOR10000.0ohm
NTC_NOMINAL_R10000.0ohm at NTC_NOMINAL_C
NTC_NOMINAL_C25.0deg C
NTC_BETA3950.0K
ADC_FAULT_LOW15raw ADC
ADC_FAULT_HIGH1008raw ADC
ADC_SAMPLES8readings averaged
SERIAL_BAUD9600baud

Get the complete version $44

This page is the working piece. The full pack has everything below.

검로드 1리스팅 · 5개국어 상세 · ★소유 · 전체 깊이 · 오프라인 · 기계가 먹는 파일 · 가격=⛔상수 금지 — ★측정된 경쟁 밴드(L4)

Send it to me