/* ============================================
code is placed under the MIT license
Copyright (c) 2023 J-M-L
For the Arduino Forum : https://forum.arduino.cc/u/j-m-l
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================
*/
// When used on Teensy and Arduino, Encoder uses very optimized interrupt routines written in assembly language. Normally, Encoder uses attachInterrupt(), which allows dynamically attaching functions to each interrupt. The dynamic function call adds slight overhead. To eliminate this extra overhead, you can use this option.
// This optional setting causes Encoder to use more optimized code,
// It must be defined before Encoder.h is included.
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>
#include <Toggle.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <DallasTemperature.h>
#include <OneWire.h>
#include <PID_v1.h>
//********************** CONFIGURATION ******************************
constexpr byte pinClk = 2;
constexpr byte pinDt = 3;
constexpr byte pinSw = 7;
constexpr byte pinPwm = 8;
constexpr byte pinDht = A1;
constexpr byte pinDs = A3;
constexpr byte dhtType = DHT22;
constexpr int screenRst = -1;
constexpr byte screenWidth = 128;
constexpr byte screenHeight = 64;
//********************** VARIABLES ******************************
OneWire oneWire(pinDs);
DallasTemperature ds(&oneWire);
DHT dht(pinDht, dhtType);
constexpr unsigned char feu[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x00,
0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x67, 0xf0, 0x00,
0x00, 0xef, 0xf0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xfe, 0x00,
0x01, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0,
0x04, 0xff, 0x9f, 0xc0, 0x06, 0xff, 0x5f, 0xe0, 0x06, 0xfe, 0xff, 0xe0, 0x07, 0xfe, 0xef, 0xf0,
0x0f, 0xfe, 0xef, 0xf0, 0x0f, 0xed, 0xf7, 0xf0, 0x0f, 0xcd, 0xfb, 0xf0, 0x07, 0xd5, 0xfb, 0xe0,
0x07, 0xb9, 0xfd, 0xe0, 0x07, 0xbf, 0xfd, 0xe0, 0x03, 0xbf, 0xfd, 0xc0, 0x01, 0xbf, 0xfd, 0x80,
0x00, 0xbf, 0xfd, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00
};
float correctionTemperature = -2;
float humiditePiece = 0;
float temperaturePiece = 0;
float temperaturePieceCorrigee = 0;
float temperatureChauffe = 0;
constexpr float pasDeLaConsigne = 0.50; // 0.5°C par cran
float consignePiece = 23;
constexpr float temperatureMaxChauffe = 40;
// PID
double entreePiece, sortiePiece, consignePIDPiece;
double kpPiece = 2, kiPiece = 0.5, kdPiece = 1;
double entreeChauffe, sortieChauffe, consignePIDChauffe;
double kpChauffe = 5, kiChauffe = 0.8, kdChauffe = 2;
PID pidPiece(&entreePiece, &sortiePiece, &consignePIDPiece, kpPiece, kiPiece, kdPiece, DIRECT);
PID pidChauffe(&entreeChauffe, &sortieChauffe, &consignePIDChauffe, kpChauffe, kiChauffe, kdChauffe, DIRECT);
// Encodeur et bouton
Encoder encodeurConsigne(pinClk, pinDt);
Toggle boutonEncodeur;
long anciennePositionEnc = 0;
// Gestion des mesures
constexpr unsigned long periodeMesures = 4000;
unsigned long dernierMesure = - periodeMesures; // pour déclencher immédiatement
// Ecran OLED
Adafruit_SSD1306 ecran(screenWidth, screenHeight, &Wire, screenRst);
// Etat du système
enum EtatSysteme {NORMAL, CONSIGNE} etatActuel = NORMAL;
//********************** FONCTIONS ******************************
void mesurerCapteurs() {
if (millis() - dernierMesure >= periodeMesures) {
dernierMesure = millis();
humiditePiece = dht.readHumidity();
temperaturePiece = dht.readTemperature();
temperaturePieceCorrigee = temperaturePiece + correctionTemperature;
ds.requestTemperatures();
temperatureChauffe = ds.getTempCByIndex(0);
}
}
void calculPID() {
entreePiece = temperaturePiece;
consignePIDPiece = consignePiece;
pidPiece.Compute();
consignePIDChauffe = sortiePiece;
if (consignePIDChauffe > temperatureMaxChauffe) consignePIDChauffe = temperatureMaxChauffe;
entreeChauffe = temperatureChauffe;
pidChauffe.Compute();
}
void afficherNormal(bool forceAffichage = false) {
static float derniereTemperaturePiece = -999;
static float derniereHumiditePiece = -999;
static float derniereTemperatureChauffe = -999;
static int derniereSortieChauffe = -1;
bool aChanger = forceAffichage
|| (temperaturePieceCorrigee != derniereTemperaturePiece)
|| (humiditePiece != derniereHumiditePiece)
|| (temperatureChauffe != derniereTemperatureChauffe)
|| (sortieChauffe != derniereSortieChauffe);
if (!aChanger) return;
derniereTemperaturePiece = temperaturePieceCorrigee;
derniereHumiditePiece = humiditePiece;
derniereTemperatureChauffe = temperatureChauffe;
derniereSortieChauffe = sortieChauffe;
ecran.clearDisplay();
ecran.setTextSize(2);
ecran.setCursor(5, 5);
ecran.setTextColor(WHITE);
ecran.println(temperaturePieceCorrigee);
ecran.drawCircle(72, 5, 4, WHITE);
ecran.setCursor(80, 5);
ecran.println("C");
ecran.setTextSize(2);
ecran.setCursor(0, 45);
ecran.println(humiditePiece);
ecran.setTextSize(1);
ecran.setCursor(60, 52);
ecran.println("%HR");
ecran.setCursor(5, 36);
ecran.println(temperatureChauffe);
if (sortieChauffe > 0) {
ecran.drawBitmap(95, 28, feu, 32, 32, WHITE);
}
ecran.display();
}
void gererConsigne(bool configuration = false) {
static long anciennePosition = 0;
if (configuration) anciennePosition = encodeurConsigne.read() >> 2;
long nouvellePosition = encodeurConsigne.read() >> 2; // on divise par 4 car on a 4 tick pour un click
long delta = nouvellePosition - anciennePosition;
if (configuration || delta != 0 ) {
consignePiece += delta * pasDeLaConsigne;
anciennePosition = nouvellePosition;
ecran.clearDisplay();
ecran.setTextSize(2);
ecran.setCursor(10, 10);
ecran.println("CONSIGNE");
ecran.setTextSize(3);
ecran.setCursor(10, 30);
ecran.println(consignePiece);
ecran.display();
}
}
void gestionAffichage() {
switch (etatActuel) {
case NORMAL:
afficherNormal();
break;
case CONSIGNE:
gererConsigne();
break;
}
}
void gestionEncodeur() {
boutonEncodeur.poll();
if (boutonEncodeur.onPress()) {
if (etatActuel == NORMAL) {
etatActuel = CONSIGNE;
gererConsigne(true); // on force l'affichage et initialise l'encodeur
} else {
etatActuel = NORMAL;
afficherNormal(true); // on force l'affichage
}
}
}
//********************** SETUP ******************************
void setup() {
pinMode(pinPwm, OUTPUT);
ecran.begin(SSD1306_SWITCHCAPVCC, 0x3C);
dht.begin();
ds.begin();
boutonEncodeur.begin(pinSw);
Serial.begin(115200);
pidPiece.SetMode(AUTOMATIC);
pidChauffe.SetMode(AUTOMATIC);
pidPiece.SetOutputLimits(0, temperatureMaxChauffe);
pidChauffe.SetOutputLimits(0, 255);
}
//********************** LOOP ******************************
void loop() {
mesurerCapteurs();
calculPID();
gestionEncodeur();
gestionAffichage();
}
Loading
ds18b20
ds18b20