PWM fan with temperature control usermod (#2246)
* PWM fan with temperature control usermod * Fix for incorrect RPM reported.
This commit is contained in:
parent
05b532b9eb
commit
b003ed3f03
36
usermods/PWM_fan/readme.md
Normal file
36
usermods/PWM_fan/readme.md
Normal file
@ -0,0 +1,36 @@
|
||||
# PWM fan
|
||||
|
||||
v2 Usermod to to control PWM fan with RPM feedback and temperature control
|
||||
|
||||
This usermod requires Dallas Temperature usermod to obtain temperature information. If this is not available the fan will always run at 100% speed.
|
||||
If the fan does not have _tacho_ (RPM) output you can set the _tacho-pin_ to -1 to not use that feature.
|
||||
|
||||
You can also set the thershold temperature at which fan runs at lowest speed. If the actual temperature measured will be 3°C greater than threshold temperature the fan will run at 100%.
|
||||
|
||||
If the _tacho_ is supported the current speed (in RPM) will be repored in WLED Info page.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_PWM_FAN` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_PWM_FAN` in `myconfig.h`.
|
||||
You will also need `-D USERMOD_DALLASTEMPERATURE`.
|
||||
|
||||
### Define Your Options
|
||||
|
||||
All of the parameters are configured during run-time using Usermods settings page.
|
||||
This includes:
|
||||
|
||||
* PWM output pin
|
||||
* tacho input pin
|
||||
* sampling frequency in seconds
|
||||
* threshold temperature in degees C
|
||||
|
||||
_NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency to match PWM fan sampling frequency.
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
No special requirements.
|
||||
|
||||
## Change Log
|
||||
|
||||
2021-10
|
||||
* First public release
|
323
usermods/PWM_fan/usermod_PWM_fan.h
Normal file
323
usermods/PWM_fan/usermod_PWM_fan.h
Normal file
@ -0,0 +1,323 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef USERMOD_DALLASTEMPERATURE
|
||||
#error The "PWM fan" usermod requires "Dallas Temeprature" usermod to function properly.
|
||||
#endif
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
// PWM & tacho code curtesy of @KlausMu
|
||||
// https://github.com/KlausMu/esp32-fan-controller/tree/main/src
|
||||
// adapted for WLED usermod by @blazoncek
|
||||
|
||||
|
||||
// tacho counter
|
||||
static volatile unsigned long counter_rpm = 0;
|
||||
// Interrupt counting every rotation of the fan
|
||||
// https://desire.giesecke.tk/index.php/2018/01/30/change-global-variables-from-isr/
|
||||
static void IRAM_ATTR rpm_fan() {
|
||||
counter_rpm++;
|
||||
}
|
||||
|
||||
|
||||
class PWMFanUsermod : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
bool initDone = false;
|
||||
bool enabled = true;
|
||||
|
||||
const int numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups.
|
||||
const int pwmMinimumValue = 120;
|
||||
const int pwmStep = 10;
|
||||
|
||||
unsigned long msLastTachoMeasurement = 0;
|
||||
uint16_t last_rpm = 0;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t pwmChannel = 255;
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
UsermodTemperature* tempUM;
|
||||
#endif
|
||||
|
||||
// configurable parameters
|
||||
int8_t tachoPin = -1;
|
||||
int8_t pwmPin = -1;
|
||||
uint8_t tachoUpdateSec = 30;
|
||||
float targetTemperature = 25.0;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _tachoPin[];
|
||||
static const char _pwmPin[];
|
||||
static const char _temperature[];
|
||||
static const char _tachoUpdateSec[];
|
||||
|
||||
void initTacho(void) {
|
||||
if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){
|
||||
tachoPin = -1;
|
||||
return;
|
||||
}
|
||||
pinMode(tachoPin, INPUT);
|
||||
digitalWrite(tachoPin, HIGH);
|
||||
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
|
||||
DEBUG_PRINTLN(F("Tacho sucessfully initialized."));
|
||||
}
|
||||
|
||||
void deinitTacho(void) {
|
||||
if (tachoPin < 0) return;
|
||||
detachInterrupt(digitalPinToInterrupt(tachoPin));
|
||||
pinManager.deallocatePin(tachoPin, PinOwner::UM_Unspecified);
|
||||
tachoPin = -1;
|
||||
}
|
||||
|
||||
void updateTacho(void) {
|
||||
if (tachoPin < 0) return;
|
||||
|
||||
// start of tacho measurement
|
||||
// detach interrupt while calculating rpm
|
||||
detachInterrupt(digitalPinToInterrupt(tachoPin));
|
||||
// calculate rpm
|
||||
last_rpm = counter_rpm * (60 / numberOfInterrupsInOneSingleRotation);
|
||||
last_rpm /= tachoUpdateSec;
|
||||
// reset counter
|
||||
counter_rpm = 0;
|
||||
// store milliseconds when tacho was measured the last time
|
||||
msLastTachoMeasurement = millis();
|
||||
// attach interrupt again
|
||||
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
|
||||
}
|
||||
|
||||
// https://randomnerdtutorials.com/esp32-pwm-arduino-ide/
|
||||
void initPWMfan(void) {
|
||||
if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) {
|
||||
pwmPin = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
analogWriteRange(255);
|
||||
analogWriteFreq(WLED_PWM_FREQ);
|
||||
#else
|
||||
pwmChannel = pinManager.allocateLedc(1);
|
||||
if (pwmChannel == 255) { //no more free LEDC channels
|
||||
deinitPWMfan(); return;
|
||||
}
|
||||
// configure LED PWM functionalitites
|
||||
ledcSetup(pwmChannel, 25000, 8);
|
||||
// attach the channel to the GPIO to be controlled
|
||||
ledcAttachPin(pwmPin, pwmChannel);
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Fan PWM sucessfully initialized."));
|
||||
}
|
||||
|
||||
void deinitPWMfan(void) {
|
||||
if (pwmPin < 0) return;
|
||||
|
||||
pinManager.deallocatePin(pwmPin, PinOwner::UM_Unspecified);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
pinManager.deallocateLedc(pwmChannel, 1);
|
||||
#endif
|
||||
pwmPin = -1;
|
||||
}
|
||||
|
||||
void updateFanSpeed(uint8_t pwmValue){
|
||||
if (pwmPin < 0) return;
|
||||
|
||||
#ifdef ESP8266
|
||||
analogWrite(pwmPin, pwmValue);
|
||||
#else
|
||||
ledcWrite(pwmChannel, pwmValue);
|
||||
#endif
|
||||
}
|
||||
|
||||
float getActualTemperature(void) {
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
if (tempUM != nullptr)
|
||||
return tempUM->getTemperatureC();
|
||||
#endif
|
||||
return -127.0f;
|
||||
}
|
||||
|
||||
void setFanPWMbasedOnTemperature(void) {
|
||||
float temp = getActualTemperature();
|
||||
float difftemp = temp - targetTemperature;
|
||||
// Default to run fan at full speed.
|
||||
int newPWMvalue = 255;
|
||||
|
||||
if ((temp == NAN) || (temp <= 0.0)) {
|
||||
DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255."));
|
||||
} else if (difftemp <= 0.0) {
|
||||
// Temperature is below target temperature. Run fan at minimum speed.
|
||||
newPWMvalue = pwmMinimumValue;
|
||||
} else if (difftemp <= 0.5) {
|
||||
newPWMvalue = 140;
|
||||
} else if (difftemp <= 1.0) {
|
||||
newPWMvalue = 160;
|
||||
} else if (difftemp <= 1.5) {
|
||||
newPWMvalue = 180;
|
||||
} else if (difftemp <= 2.0) {
|
||||
newPWMvalue = 200;
|
||||
} else if (difftemp <= 2.5) {
|
||||
newPWMvalue = 220;
|
||||
} else if (difftemp <= 3.0) {
|
||||
newPWMvalue = 240;
|
||||
}
|
||||
updateFanSpeed(newPWMvalue);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void setup() {
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
// This Usermod requires Temperature usermod
|
||||
tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE);
|
||||
#endif
|
||||
initTacho();
|
||||
initPWMfan();
|
||||
updateFanSpeed(pwmMinimumValue);
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||
// interfaces here
|
||||
void connected() {}
|
||||
|
||||
/*
|
||||
* Da loop.
|
||||
*/
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
unsigned long now = millis();
|
||||
if ((now - msLastTachoMeasurement) < (tachoUpdateSec * 1000)) return;
|
||||
|
||||
updateTacho();
|
||||
setFanPWMbasedOnTemperature();
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
if (tachoPin < 0) return;
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
JsonArray data = user.createNestedArray(FPSTR(_name));
|
||||
data.add(last_rpm);
|
||||
data.add(F("rpm"));
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
//void addToJsonState(JsonObject& root) {
|
||||
//}
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
//void readFromJsonState(JsonObject& root) {
|
||||
// if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
//}
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* If you want to force saving the current state, use serializeConfig() in your loop().
|
||||
*
|
||||
* CAUTION: serializeConfig() will initiate a filesystem write operation.
|
||||
* It might cause the LEDs to stutter and will cause flash wear if called too often.
|
||||
* Use it sparingly and always in the loop, never in network callbacks!
|
||||
*
|
||||
* addToConfig() will also not yet add your setting to one of the settings pages automatically.
|
||||
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
|
||||
*
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_pwmPin)] = pwmPin;
|
||||
top[FPSTR(_tachoPin)] = tachoPin;
|
||||
top[FPSTR(_tachoUpdateSec)] = tachoUpdateSec;
|
||||
top[FPSTR(_temperature)] = targetTemperature;
|
||||
DEBUG_PRINTLN(F("Autosave config saved."));
|
||||
}
|
||||
|
||||
/*
|
||||
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
|
||||
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot)
|
||||
*
|
||||
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
|
||||
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
|
||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject& root) {
|
||||
int8_t newTachoPin = tachoPin;
|
||||
int8_t newPwmPin = pwmPin;
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
newTachoPin = top[FPSTR(_tachoPin)] | newTachoPin;
|
||||
newPwmPin = top[FPSTR(_pwmPin)] | newPwmPin;
|
||||
tachoUpdateSec = top[FPSTR(_tachoUpdateSec)] | tachoUpdateSec;
|
||||
tachoUpdateSec = (uint8_t) max(1,(int)tachoUpdateSec); // bounds checking
|
||||
targetTemperature = top[FPSTR(_temperature)] | targetTemperature;
|
||||
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
tachoPin = newTachoPin;
|
||||
pwmPin = newPwmPin;
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing paramters from settings page
|
||||
if (tachoPin != newTachoPin || pwmPin != newPwmPin) {
|
||||
DEBUG_PRINTLN(F("Re-init pins."));
|
||||
// deallocate pin and release interrupts
|
||||
deinitTacho();
|
||||
deinitPWMfan();
|
||||
tachoPin = newTachoPin;
|
||||
pwmPin = newPwmPin;
|
||||
// initialise
|
||||
setup();
|
||||
}
|
||||
}
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_enabled)].isNull();
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_PWM_FAN;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char PWMFanUsermod::_name[] PROGMEM = "PWM-fan";
|
||||
const char PWMFanUsermod::_enabled[] PROGMEM = "enabled";
|
||||
const char PWMFanUsermod::_tachoPin[] PROGMEM = "tacho-pin";
|
||||
const char PWMFanUsermod::_pwmPin[] PROGMEM = "PWM-pin";
|
||||
const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C";
|
||||
const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s";
|
@ -59,6 +59,7 @@
|
||||
#define USERMOD_ID_ELEKSTUBE_IPS 16 //Usermod "usermod_elekstube_ips.h"
|
||||
#define USERMOD_ID_SN_PHOTORESISTOR 17 //Usermod "usermod_sn_photoresistor.h"
|
||||
#define USERMOD_ID_BATTERY_STATUS_BASIC 18 //Usermod "usermod_v2_battery_status_basic.h"
|
||||
#define USERMOD_ID_PWM_FAN 19 //Usermod "usermod_PWM_fan.h"
|
||||
|
||||
//Access point behavior
|
||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||
|
@ -23,7 +23,9 @@
|
||||
#include "../usermods/SN_Photoresistor/usermod_sn_photoresistor.h"
|
||||
#endif
|
||||
|
||||
//#include "usermod_v2_empty.h"
|
||||
#ifdef USERMOD_PWM_FAN
|
||||
#include "../usermods/PWM_fan/usermod_PWM_fan.h"
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_BUZZER
|
||||
#include "../usermods/buzzer/usermod_v2_buzzer.h"
|
||||
@ -115,7 +117,9 @@ void registerUsermods()
|
||||
usermods.add(new Usermod_SN_Photoresistor());
|
||||
#endif
|
||||
|
||||
//usermods.add(new UsermodRenameMe());
|
||||
#ifdef USERMOD_PWM_FAN
|
||||
usermods.add(new PWMFanUsermod());
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_BUZZER
|
||||
usermods.add(new BuzzerUsermod());
|
||||
|
Loading…
Reference in New Issue
Block a user