PWM fan with temperature control usermod
This commit is contained in:
parent
05b532b9eb
commit
772c80aa85
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
|
322
usermods/PWM_fan/usermod_PWM_fan.h
Normal file
322
usermods/PWM_fan/usermod_PWM_fan.h
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
#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);
|
||||||
|
// 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_ELEKSTUBE_IPS 16 //Usermod "usermod_elekstube_ips.h"
|
||||||
#define USERMOD_ID_SN_PHOTORESISTOR 17 //Usermod "usermod_sn_photoresistor.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_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
|
//Access point behavior
|
||||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
#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"
|
#include "../usermods/SN_Photoresistor/usermod_sn_photoresistor.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//#include "usermod_v2_empty.h"
|
#ifdef USERMOD_PWM_FAN
|
||||||
|
#include "../usermods/PWM_fan/usermod_PWM_fan.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USERMOD_BUZZER
|
#ifdef USERMOD_BUZZER
|
||||||
#include "../usermods/buzzer/usermod_v2_buzzer.h"
|
#include "../usermods/buzzer/usermod_v2_buzzer.h"
|
||||||
@ -115,7 +117,9 @@ void registerUsermods()
|
|||||||
usermods.add(new Usermod_SN_Photoresistor());
|
usermods.add(new Usermod_SN_Photoresistor());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//usermods.add(new UsermodRenameMe());
|
#ifdef USERMOD_PWM_FAN
|
||||||
|
usermods.add(new PWMFanUsermod());
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USERMOD_BUZZER
|
#ifdef USERMOD_BUZZER
|
||||||
usermods.add(new BuzzerUsermod());
|
usermods.add(new BuzzerUsermod());
|
||||||
|
Loading…
Reference in New Issue
Block a user