Last active
November 15, 2019 12:29
-
-
Save bisko/646045b0d808f137c98db4f5870468ed to your computer and use it in GitHub Desktop.
Fan Controller code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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