Calefacción por PWM: modo, sesión, potencia¶
Después de esta página la tarjeta inicia la calefacción con una temperatura y un tiempo, muestra la sesión y la potencia, y el dispositivo mantiene la temperatura con una salida PWM según el sensor.
Qué necesitas¶
- el sensor SHT31 del paso de telemetría;
- un MOSFET de nivel lógico (abre con 3,3 V) en el pin 3 y un calefactor para su tensión de alimentación;
- en
platformio.ini:robtillaart/SHT31 @ ^0.5.0.
Warning
No dejes sin vigilancia un calefactor sin fusible térmico: el firmware puede colgarse y el MOSFET puede fallar en cortocircuito.
Código¶
#include <Arduino.h>
#include <Wire.h>
#include <SHT31.h>
#include <iDryer.h>
#define HEATER_PIN 3 // puerta del MOSFET
#define PWM_CHANNEL 0
#define PWM_FREQ_HZ 1000
#define PWM_BITS 8 // ciclo de trabajo 0..255
static const iDryer::Config CFG = {
.deviceType = iDryer::DeviceType::Unknown,
.unitsCount = 1,
.hasHeater = true, // celda de potencia de calefacción
.hasAirTemp = true,
.hasAirHumidity = true,
.hardwareVersion = "1.0",
.firmwareVersion = "0.1.0",
.model = "PWM Heater",
};
static iDryer::Link s_link(CFG);
static SHT31 s_sht(0x44, &Wire);
static void readSensor() {
const bool ok = s_sht.read();
s_link.telemetry.airTempC[0] = ok ? s_sht.getTemperature() : NAN;
s_link.telemetry.airHumidityPct[0] = ok ? s_sht.getHumidity() : NAN;
}
static uint32_t s_startMs = 0;
static void stopHeat(uint8_t unit) {
ledcWrite(PWM_CHANNEL, 0);
s_link.telemetry.heaterPower01[unit] = 0.0f;
s_link.status.mode[unit] = iDryer::UnitMode::Idle;
s_link.status.targetTempC[unit] = 0.0f;
s_link.status.durationS[unit] = 0;
s_link.status.elapsedS[unit] = 0;
s_link.publishStatusNow();
}
static void onHeat(uint8_t unit, JsonObjectConst args) {
s_link.status.mode[unit] = iDryer::UnitMode::Heating;
s_link.status.targetTempC[unit] = args["temperature"].as<float>();
s_link.status.durationS[unit] = args["duration"].as<uint32_t>() * 60; // 0: sin límite
s_link.status.elapsedS[unit] = 0;
s_startMs = millis();
s_link.publishStatusNow();
}
static void onStop(uint8_t unit, JsonObjectConst) { stopHeat(unit); }
// Una vez por segundo: potencia según la diferencia con la consigna, tiempo de la sesión.
static void regulate() {
if (s_link.status.mode[0] != iDryer::UnitMode::Heating) return;
const float t = s_link.telemetry.airTempC[0];
// Sin lectura = calefactor apagado: nunca calentar a ciegas.
float power = 0.0f;
if (!isnan(t)) power = constrain((s_link.status.targetTempC[0] - t) * 0.1f, 0.0f, 1.0f);
ledcWrite(PWM_CHANNEL, (uint32_t)(power * 255));
s_link.telemetry.heaterPower01[0] = power;
const uint32_t elapsed = (millis() - s_startMs) / 1000;
s_link.status.elapsedS[0] = elapsed;
if (s_link.status.durationS[0] && elapsed >= s_link.status.durationS[0]) stopHeat(0);
}
void setup() {
ledcSetup(PWM_CHANNEL, PWM_FREQ_HZ, PWM_BITS);
ledcAttachPin(HEATER_PIN, PWM_CHANNEL);
ledcWrite(PWM_CHANNEL, 0);
Wire.begin(8, 9);
s_sht.begin();
s_link.begin();
s_link.onCommand("revoke", [](JsonObjectConst) { s_link.handleRevoke(); });
auto& card = s_link.card();
card.action("heat", "HEATING", onHeat)
.name("ru", "Нагрев").name("en", "Heat")
.param("temperature", "target_temperature", 30, 70, 1, 45, "°C")
.param("duration", "duration", 0, 720, 10, 120, "min");
card.action("stop", "IDLE", onStop)
.name("ru", "Стоп").name("en", "Stop");
s_link.every(2000, readSensor);
s_link.every(1000, regulate);
}
void loop() {
s_link.loop();
}
Cómo funciona¶
- Una acción con el modo
HEATINGinicia una sesión: el callback fija el modo, la consigna y la duración y llama apublishStatusNow(). Según el modo, la tarjeta muestra el bloque de sesión y Stop. - El portal y la aplicación rotulan solos los purposes
target_temperatureyduration. Los números enargsya están limitados a los límites del parámetro. durationS = 0significa sin límite de tiempo; la tarjeta muestraelapsedScomo tiempo transcurrido.hasHeaterda la celda de potencia. En funcionamiento, el núcleo envía la media deheaterPower01de los dos últimos periodos de publicación, así que los cambios frecuentes del PWM no saltan en la tarjeta.- El regulador aquí es proporcional: 10 °C por debajo de la consigna es potencia completa. Para mantener con precisión, sustitúyelo por un PID.
ledcSetup/ledcAttachPinson la API de arduino-esp32 2.x. En 3.x usaledcAttach(pin, freq, bits)yledcWrite(pin, duty).
Comprobación¶
Inicia Heat en la tarjeta: aparece el bloque de sesión con la consigna y el tiempo, y la celda de potencia muestra cuánto está encendido el calefactor. Stop apaga la salida.
Siguiente¶
- Tarjeta del dispositivo: el card manifest: parámetros desde el menú del dispositivo, perfiles, disposición de la tarjeta.
- Cómo añadir un producto nuevo.