Skip to content

Instantly share code, notes, and snippets.

@bisko
Last active November 15, 2019 12:29
Show Gist options
  • Save bisko/646045b0d808f137c98db4f5870468ed to your computer and use it in GitHub Desktop.
Save bisko/646045b0d808f137c98db4f5870468ed to your computer and use it in GitHub Desktop.
Fan Controller code
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include "SparkFunBME280.h"
/**
* TODO:
* make WIFI information external or use AutoWIFI
* split into two threads
* - wifi, web, logging, temp reading on thread 0
* - fan control on thread 1
* publish on GitHub
* fix duty cycle increase/decrease to make it work without ±10
* mqtt support
* better WIFI checks and support
*/
// keep in sync with slave struct
struct __attribute__((packed)) SENSOR_DATA {
float temp;
float humidity;
float pressure;
} sensorData;
BME280 bme280;
const char *ssid = "MYWIFI";
const char *password = "MYPASSWORD";
WebServer server(80);
/**
* PWM Setup
*/
int freq = 25000;
int fan_pwm_channel = 0;
int resolution = 8;
int fan_pwm_pin = 16;
int fan_tacho_pin = 12;
/**
* Current interrupt count
*/
volatile int interruptCounter = 0;
/**
* Interrupts in the last `fan_tacho_read_interval_ms` interval.
*
* This value is doubled as the fans send an interrupt every half-revolution
*/
int last_RPS = 0;
/* Mutex to work with interrupts */
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
/* Current Fan duty cycle [0-255] */
int fan_pwm_duty_cycle = 0;
/* Fan duty cycle target [0-255] */
int fan_pwm_duty_cycle_target = 0;
/**
* Timings
*/
/**
* When was the value last checkd
*/
int last_fan_tacho_read_ms = 0;
int last_fan_duty_check_ms = 0;
int last_temp_check_ms = 0;
/* How often to check the values, in milliseconds */
int fan_duty_check_interval_ms = 1000;
int fan_tacho_read_interval_ms = 1000;
int temp_check_interval_ms = 5000;
/**
* Fan Tacho read interrupt handler
*/
void IRAM_ATTR handleInterrupt() {
portENTER_CRITICAL_ISR(&mux);
interruptCounter++;
portEXIT_CRITICAL_ISR(&mux);
}
/**
* Resets the interrupt counter
*/
void resetInterrupt() {
portENTER_CRITICAL_ISR(&mux);
last_RPS = interruptCounter; // Store the last PRS value before resetting the counter
interruptCounter = 0;
portEXIT_CRITICAL_ISR(&mux);
}
String prepare_data_output() {
char buffer[512];
String out_string = (""
"<html>"
"<head>"
"<meta http-equiv=\"refresh\" content=\"5\">"
"</head>"
"<body>"
" Duty: %d <br/>"
" Target: %d <br/>"
" RPS: %d <br/>"
" RPM: %d <br/>"
" Temp: %.2f <br/>"
" Humidity: %.2f <br/>"
" Pressure: %.2f <br/>"
"</body>"
"</html>"
);
sprintf(
buffer,
out_string.c_str(),
fan_pwm_duty_cycle,
fan_pwm_duty_cycle_target,
last_RPS/2,
(last_RPS/2)*60,
sensorData.temp,
sensorData.humidity,
sensorData.pressure
);
return (String)buffer;
}
void connect_to_wifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Wait for connection
while ( WiFi.status() != WL_CONNECTED ) {
delay(500);
Serial.print(".");
}
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Monitoring interrupts: ");
// Setup PWM control
ledcSetup(fan_pwm_channel, freq, resolution);
ledcAttachPin(fan_pwm_pin, fan_pwm_channel);
ledcWrite(fan_pwm_channel, fan_pwm_duty_cycle);
// Setup fan RPM readouts
pinMode(fan_tacho_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(fan_tacho_pin), handleInterrupt, FALLING);
connect_to_wifi();
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if ( MDNS.begin( "esp32" ) ) {
Serial.println("MDNS responder started");
}
server.on("/", []() {
server.send(200, "text/plain", "Hello from the ESP32 Fan Controller!");
});
server.on("/status", []() {
server.send(200, "text/html", prepare_data_output() );
});
server.on("/target/{}", []() {
String target = server.pathArg(0);
fan_pwm_duty_cycle_target = target.toInt();
server.send(200, "text/html", prepare_data_output() );
});
server.begin();
Serial.println("HTTP server started");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.setPort(8277);
ArduinoOTA.begin();
Wire.begin(15,2);
//Wire.setClock(400000); //Increase to fast I2C speed!
bme280.setI2CAddress(0x76);
}
void loop() {
if ( WiFi.status() != WL_CONNECTED ) {
ledcWrite(fan_pwm_channel, 250); // TODO make better
connect_to_wifi();
}
ArduinoOTA.handle();
server.handleClient();
double current_millis = millis();
if (current_millis >= last_fan_tacho_read_ms + fan_tacho_read_interval_ms) {
resetInterrupt();
last_fan_tacho_read_ms = current_millis;
}
if (current_millis >= last_fan_duty_check_ms + fan_duty_check_interval_ms) {
if (fan_pwm_duty_cycle < fan_pwm_duty_cycle_target) {
fan_pwm_duty_cycle += 10;
}
else if (fan_pwm_duty_cycle > fan_pwm_duty_cycle_target) {
fan_pwm_duty_cycle -= 10;
}
ledcWrite(fan_pwm_channel, fan_pwm_duty_cycle);
last_fan_duty_check_ms = current_millis;
}
// Temperature reading every 5 seconds
if (current_millis >= last_temp_check_ms + temp_check_interval_ms) {
bme280.beginI2C();
bme280.setMode(MODE_FORCED);
while(bme280.isMeasuring() == false) yield(); //Wait for sensor to start measurment
while(bme280.isMeasuring() == true) yield(); //Hang out while sensor completes the reading
sensorData.temp = bme280.readTempC();
sensorData.humidity = bme280.readFloatHumidity();
sensorData.pressure = bme280.readFloatPressure() / 100.0;
Serial.println("TEMPS");
Serial.println(sensorData.temp);
Serial.println(sensorData.humidity);
Serial.println(sensorData.pressure);
fan_pwm_duty_cycle_target = min(max( (int)map(sensorData.temp, 24, 37, 0, 250), 0), 250);
last_temp_check_ms = current_millis;
}
}
esptool.py v2.6
Serial port /dev/cu.usbserial-A800evSx
Connecting........___
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 3c:71:bf:0d:4d:38
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Warning: Could not auto-detect Flash size (FlashID=0x0, SizeID=0x0), defaulting to 4MB
Compressed 15872 bytes to 10319...
Writing at 0x00001000... (100 %)
Wrote 15872 bytes (10319 compressed) at 0x00001000 in 0.2 seconds (effective 528.8 kbit/s)...
A fatal error occurred: Timed out waiting for packet header
*** [upload] Error 2
---------------------------------------------------------
~ ❯❯❯ esptool.py --port /dev/cu.usbserial-A800evSx chip_id ✘ 2
esptool.py v2.8
Serial port /dev/cu.usbserial-A800evSx
Connecting........__
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 3c:71:bf:0d:4d:38
Uploading stub...
Running stub...
Stub running...
Warning: ESP32 has no Chip ID. Reading MAC instead.
MAC: 3c:71:bf:0d:4d:38
Hard resetting via RTS pin...
---------------------------------------------------------
~ ❯❯❯ esptool.py --port /dev/cu.usbserial-A800evSx flash_id ✘ 2
esptool.py v2.8
Serial port /dev/cu.usbserial-A800evSx
Connecting........_
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 3c:71:bf:0d:4d:38
Uploading stub...
Running stub...
Stub running...
Manufacturer: 00
Device: 0000
Detected flash size: Unknown
Hard resetting via RTS pin...
---------------------------------------------------------
~ ❯❯❯ esptool.py --before default_reset --port /dev/cu.usbserial-A800evSx erase_flash
esptool.py v2.8
Serial port /dev/cu.usbserial-A800evSx
Connecting........__
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 3c:71:bf:0d:4d:38
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
A fatal error occurred: Timed out waiting for packet content
---------------------------------------------------------
~ ❯❯❯ espefuse.py -p /dev/cu.usbserial-A800evSx summary ✘ 1
espefuse.py v2.8
Connecting........_
EFUSE_NAME Description = [Meaningful Value] [Readable/Writeable] (Hex Value)
----------------------------------------------------------------------------------------
Efuse fuses:
WR_DIS Efuse write disable mask = 0 R/W (0x0)
RD_DIS Efuse read disablemask = 0 R/W (0x0)
CODING_SCHEME Efuse variable block length scheme = 0 R/W (0x0)
KEY_STATUS Usage of efuse block 3 (reserved) = 0 R/W (0x0)
Config fuses:
XPD_SDIO_FORCE Ignore MTDI pin (GPIO12) for VDD_SDIO on reset = 0 R/W (0x0)
XPD_SDIO_REG If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset = 0 R/W (0x0)
XPD_SDIO_TIEH If XPD_SDIO_FORCE & XPD_SDIO_REG, 1=3.3V 0=1.8V = 0 R/W (0x0)
CLK8M_FREQ 8MHz clock freq override = 55 R/W (0x37)
SPI_PAD_CONFIG_CLK Override SD_CLK pad (GPIO6/SPICLK) = 0 R/W (0x0)
SPI_PAD_CONFIG_Q Override SD_DATA_0 pad (GPIO7/SPIQ) = 0 R/W (0x0)
SPI_PAD_CONFIG_D Override SD_DATA_1 pad (GPIO8/SPID) = 0 R/W (0x0)
SPI_PAD_CONFIG_HD Override SD_DATA_2 pad (GPIO9/SPIHD) = 0 R/W (0x0)
SPI_PAD_CONFIG_CS0 Override SD_CMD pad (GPIO11/SPICS0) = 0 R/W (0x0)
DISABLE_SDIO_HOST Disable SDIO host = 0 R/W (0x0)
Security fuses:
FLASH_CRYPT_CNT Flash encryption mode counter = 0 R/W (0x0)
FLASH_CRYPT_CONFIG Flash encryption config (key tweak bits) = 0 R/W (0x0)
CONSOLE_DEBUG_DISABLE Disable ROM BASIC interpreter fallback = 1 R/W (0x1)
ABS_DONE_0 secure boot enabled for bootloader = 0 R/W (0x0)
ABS_DONE_1 secure boot abstract 1 locked = 0 R/W (0x0)
JTAG_DISABLE Disable JTAG = 0 R/W (0x0)
DISABLE_DL_ENCRYPT Disable flash encryption in UART bootloader = 0 R/W (0x0)
DISABLE_DL_DECRYPT Disable flash decryption in UART bootloader = 0 R/W (0x0)
DISABLE_DL_CACHE Disable flash cache in UART bootloader = 0 R/W (0x0)
BLK1 Flash encryption key
= 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK2 Secure boot key
= 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK3 Variable Block 3
= 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
Calibration fuses:
BLK3_PART_RESERVE BLOCK3 partially served for ADC calibration data = 0 R/W (0x0)
ADC_VREF Voltage reference calibration = 1093 R/W (0x11)
Identity fuses:
MAC Factory MAC Address
= 3c:71:bf:0d:4d:38 (CRC 0x45 OK) R/W
CHIP_VER_REV1 Silicon Revision 1 = 1 R/W (0x1)
CHIP_VER_REV2 Silicon Revision 2 = 0 R/W (0x0)
CHIP_VERSION Reserved for future chip versions = 2 R/W (0x2)
CHIP_PACKAGE Chip package identifier = 0 R/W (0x0)
Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment